├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .husky └── pre-commit ├── .npmignore ├── .travis.yml ├── Readme.md ├── grammar └── dice.pegjs ├── index.js ├── lib ├── Conditional.js ├── Expression.js ├── Factorial.js ├── Function.js ├── Not.js ├── Number.js ├── Operation.js ├── Parentheses.js ├── Repeat.js ├── Roll.js ├── Variable.js ├── defaultScope.js ├── parser.js ├── rolldie.js └── utils.js ├── package-lock.json ├── package.json ├── tests ├── Conditional.spec.js ├── Factorial.spec.js ├── Function.spec.js ├── Not.spec.js ├── Number.spec.js ├── Operation.spec.js ├── Parentheses.spec.js ├── Repeat.spec.js ├── Roll.spec.js ├── Variable.spec.js ├── defaultScope.spec.js ├── parser.spec.js ├── rollDist.js ├── test.html └── utils.spec.js └── types └── index.d.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | # This project uses EditorConfig for setting code formatting options: http://EditorConfig.org 2 | 3 | root = true 4 | 5 | # Default settings for all files 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | indent_style = space 12 | indent_size = 4 13 | tab_width = 4 14 | max_line_length = 120 15 | 16 | spaces_around_operators = true 17 | spaces_around_brackets = none 18 | curly_bracket_next_line = true 19 | indent_brace_style = Allman 20 | continuation_indent_size = 4 21 | 22 | [*.{css,less}] 23 | curly_bracket_next_line = false 24 | continuation_indent_size = 0 25 | 26 | [*.yml] 27 | indent_size = 2 28 | 29 | # Special overrides for automatically-generated files 30 | [package.json] 31 | indent_size = 2 32 | 33 | # For some formats, trailing whitespace is significant; don't strip it. 34 | [*.{md,diff,patch}] 35 | trim_trailing_whitespace = false 36 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Generated filed 2 | lib/parser.js 3 | types 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "node": true, 4 | "commonjs": true, 5 | "es6": true, 6 | "mocha": true 7 | }, 8 | "extends": [ 9 | "eslint:recommended" 10 | ], 11 | "parserOptions": { 12 | "ecmaVersion": 2018 13 | }, 14 | "rules": { 15 | // Bad Practices 16 | "curly": "error", 17 | "no-await-in-loop": "error", 18 | "no-eval": "error", 19 | "no-implicit-globals": "error", 20 | "no-lone-blocks": "error", 21 | "no-return-await": "error", 22 | "no-self-compare": "error", 23 | "no-sequences": "error", 24 | "no-throw-literal": "error", 25 | "no-with": "error", 26 | "prefer-arrow-callback": [ "error", { "allowNamedFunctions": true } ], 27 | "prefer-promise-reject-errors": "error", 28 | "prefer-rest-params": "warn", 29 | "no-label-var": "error", 30 | "no-undefined": "off", 31 | "no-use-before-define": "error", 32 | "no-array-constructor": "error", 33 | "no-new-object": "error", 34 | "no-continue": "error", 35 | "no-unneeded-ternary": "error", 36 | 37 | // Common Mistakes 38 | "no-extra-bind": "warn", 39 | "no-floating-decimal": "warn", 40 | "no-multi-spaces": "warn", 41 | "no-useless-call": "warn", 42 | "no-useless-return": "warn", 43 | "require-await": "off", 44 | "no-mixed-operators": "warn", 45 | 46 | // Style 47 | "array-bracket-newline": [ "warn", "consistent" ], 48 | "array-bracket-spacing": [ "warn", "always" ], 49 | "array-element-newline": [ "warn", "consistent" ], 50 | "arrow-parens": [ "warn", "always" ], 51 | "arrow-spacing": "warn", 52 | "block-spacing": "warn", 53 | "brace-style": [ "warn", "allman", { "allowSingleLine": true } ], 54 | "camelcase": "warn", 55 | "comma-dangle": [ "warn", "never" ], 56 | "comma-spacing": [ "warn", { "before": false, "after": true } ], 57 | "comma-style": [ "warn", "last" ], 58 | "computed-property-spacing": [ "warn", "never" ], 59 | "consistent-this": [ "warn", "self" ], 60 | "eol-last": [ "warn", "always" ], 61 | "func-call-spacing": ["warn", "never"], 62 | "func-style": [ "warn", "declaration", { "allowArrowFunctions": true } ], 63 | "function-paren-newline": [ "warn", "multiline" ], 64 | "generator-star-spacing": [ "warn", { "before": true, "after": false } ], 65 | "id-length": [ "warn", { "min": 2, "exceptions": [ "_", "a", "b", "i", "x", "y", "z" ] } ], 66 | "indent": [ 67 | "warn", 68 | 4, 69 | { 70 | "SwitchCase": 1, 71 | "VariableDeclarator": 1, 72 | "outerIIFEBody": 1, 73 | "MemberExpression": 1, 74 | "FunctionDeclaration": { 75 | "parameters": 1, 76 | "body": 1 77 | }, 78 | "FunctionExpression": { 79 | "parameters": 1, 80 | "body": 1 81 | }, 82 | "CallExpression": { 83 | "arguments": 1 84 | }, 85 | "ArrayExpression": 1, 86 | "ObjectExpression": 1, 87 | "ImportDeclaration": 1, 88 | "flatTernaryExpressions": false 89 | } 90 | ], 91 | "key-spacing": [ "warn", { "beforeColon": false, "afterColon": true } ], 92 | "keyword-spacing": [ 93 | "warn", 94 | { 95 | "overrides": { 96 | "if": { "after": false }, 97 | "for": { "after": false }, 98 | "while": { "after": false } 99 | } 100 | } 101 | ], 102 | "lines-between-class-members": [ "warn", "always", { exceptAfterSingleLine: true } ], 103 | "new-parens": "warn", 104 | "newline-per-chained-call": [ "warn", { "ignoreChainWithDepth": 2 } ], 105 | "no-confusing-arrow": [ "warn", { "allowParens": false } ], 106 | "no-duplicate-imports": "warn", 107 | "no-lonely-if": "warn", 108 | "no-multi-assign": "warn", 109 | "no-multiple-empty-lines": [ "warn", { "max": 1, "maxBOF": 0, "maxEOF": 1 } ], 110 | "no-useless-computed-key": "warn", 111 | "no-useless-rename": [ 112 | "error", 113 | { 114 | "ignoreDestructuring": false, 115 | "ignoreImport": false, 116 | "ignoreExport": false 117 | } 118 | ], 119 | "no-var": "warn", 120 | "no-whitespace-before-property": "warn", 121 | "object-curly-spacing": [ "warn", "always" ], 122 | "object-property-newline": [ "warn", { "allowAllPropertiesOnSameLine": true } ], 123 | "object-shorthand": [ "warn", "always" ], 124 | "one-var": [ "warn", "never" ], 125 | "operator-linebreak": [ "warn", "before" ], 126 | "padded-blocks": [ "warn", "never" ], 127 | "prefer-const": "warn", 128 | "prefer-object-spread": "warn", 129 | "prefer-template": "warn", 130 | "quote-props": [ "warn", "consistent-as-needed" ], 131 | "quotes": [ "warn", "single", { "avoidEscape": true, "allowTemplateLiterals": true } ], 132 | "semi": [ "error", "always" ], 133 | "semi-spacing": [ "warn", { "before": false, "after": true } ], 134 | "space-before-blocks": "warn", 135 | "space-before-function-paren": [ "warn", "never" ], 136 | "space-in-parens": [ "warn", "never" ], 137 | "space-infix-ops": "warn", 138 | "space-unary-ops": "warn", 139 | "spaced-comment": [ 140 | "warn", 141 | "always", { 142 | "line": { 143 | "markers": [ "/" ], 144 | "exceptions": [ "-", "+" ] 145 | }, 146 | "block": { 147 | "markers": ["!"], 148 | "exceptions": ["*"], 149 | "balanced": true 150 | } 151 | } 152 | ], 153 | "switch-colon-spacing": "warn", 154 | "template-curly-spacing": [ "warn", "always" ], 155 | "template-tag-spacing": "warn", 156 | "valid-jsdoc": [ 157 | "warn", 158 | { 159 | "prefer": { 160 | "arg": "param", 161 | "argument": "param", 162 | "class": "class", 163 | "return": "returns", 164 | "virtual": "abstract" 165 | }, 166 | "preferType": { 167 | "Boolean": "boolean", 168 | "Number": "number", 169 | "Object": "object", 170 | "String": "string" 171 | }, 172 | "requireReturn": true, 173 | "requireParamDescription": true, 174 | "requireParamType": true 175 | } 176 | ], 177 | "yield-star-spacing": [ "warn", "before" ], 178 | "yoda": [ "warn", "never", { "exceptRange": true } ] 179 | } 180 | }; 181 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # IntelliJ artifacts 4 | .idea 5 | 6 | # build artifacts 7 | .cache 8 | dist 9 | 10 | # Node artifacts 11 | node_modules 12 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # We want to publish the dist file in the npm package 2 | !dist 3 | !types 4 | 5 | # Slim down the package on npm some. 6 | .cache 7 | .idea 8 | .travis.yml 9 | grammar 10 | tests 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | - "lts/*" 5 | - "14" 6 | - "12" 7 | - "10" 8 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # RPGDice 2 | 3 | ![Build Status](https://travis-ci.org/Morgul/rpgdice.svg) [![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/Morgul/rpgdice.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/Morgul/rpgdice/context:javascript) 4 | [![Total alerts](https://img.shields.io/lgtm/alerts/g/Morgul/rpgdice.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/Morgul/rpgdice/alerts/) [![](https://data.jsdelivr.com/v1/package/npm/rpgdicejs/badge)](https://www.jsdelivr.com/package/npm/rpgdicejs) 5 | 6 | This project is an opinionated dice syntax and roller library designed to be used for any dice-based RPG system. Its 7 | main goal is to use a straightforward, easy to use grammar that has enough flexibility to allow players to easily 8 | codify their dice rolls. One of its central features, the ability to use variables, exists to facilitate a 'write once' 9 | philosophy of dice rolls. 10 | 11 | ## Dice are Hard 12 | 13 | The single largest complaint I ever hear about a given RPG system is that the dice are "too complicated", or "I can 14 | never remember how to roll that", or "I keep forgetting my bonuses." This is why RPGDice exists: computers are amazing 15 | at calculations; humans aren't. That's why we let the computer do the hard work of keeping track of everything, and the 16 | user just gets to see the results of what they told it to roll. 17 | 18 | ## Opinionated 19 | 20 | There is a semi-formal [dice notation][] floating around. I personally find its syntax clunky, difficult to use, and 21 | nearly impossible to extend. Instead, I've created a syntax that is, in essence, mathematical operations, plus 22 | functions, variables, and `XdY` syntax. I feel my version is easy enough to learn for veterans and newbies alike, while 23 | leveraging some basic programing concepts, like the [principal of least surprise][pola]. 24 | 25 | [dice notation]: http://en.wikipedia.org/wiki/Dice_notation 26 | [pola]: http://en.wikipedia.org/wiki/Principle_of_least_astonishment 27 | 28 | ## Usage 29 | 30 | ### Getting RPGDice 31 | 32 | Our recommended way is via npm: 33 | 34 | ```bash 35 | $ npm install --save rpgdicejs 36 | ``` 37 | 38 | Or with `yarn`: 39 | 40 | ```bash 41 | $ yarn add rpgdicejs 42 | ``` 43 | 44 | Now, if you want to use this in a browser, any of the major bundlers should be able to handle this module just fine. It 45 | should be noted that as this is still a CJS module, you may be required to do some work, but it has no dependencies, 46 | and works in both node and the browser, so bundling it should be easy. 47 | 48 | We used to provide a bundled version but have removed it in an interest of maintainability. In the future the code base 49 | will be converted to typescript, and at that time we'll provide UMD, CJS and ESM module versions for easy consumption. 50 | 51 | ### Syntax Summary 52 | 53 | `d20 + floor(level / 2) + floor(('strength.score' - 10) / 2) + proficiency + 'Weapon Enhancement' + [Misc.Attack.Bonus]` 54 | 55 | As you can see, the syntax is very nearly a super-simplified version of javascript. It supports standard order of 56 | operations, `XdY` for rolls, function calls, and variables. (This particular roll is the formula for a D&D 4e attack, 57 | with the added pathology of showing all the various ways of escaping variables.) 58 | 59 | When you make a roll, you will pass in a `scope` object, which RPGDice will use to look up all variables and functions. 60 | By default, we provide several mathematical functions, such as `min()`, `max()`, `floor()`, `ceil()`, `round()`. 61 | Additionally, we provide some common RPG rules: `explode()`, `dropLowest()`, `dropHighest()`, `reroll()`. 62 | 63 | If you set a variable on the `scope` to a function, but reference it without parenthesis, RPGDice will call it, passing 64 | in no arguments. Ex: `3d8 + strMod`, where `strMod` was defined as: 65 | 66 | ```javascript 67 | function strMod() 68 | { 69 | return Math.floor((scope.strength - 10) / 2); 70 | } // end strMod 71 | ``` 72 | 73 | This gives you a lot of power in how you define your scope. You can additionally extend the functionality to support any 74 | rule set your heart desires, without needing explicit support in the syntax. For example, let's say you wanted to play 75 | with loaded dice. There's no special syntax support for that, but you can add it yourself: 76 | 77 | ```javascript 78 | function rollLoaded(sides) 79 | { 80 | var roll = Math.floor(Math.random() * sides) + 1; 81 | 82 | // We make ourselves 3 times as likely to roll max, and impossible to roll the minimum. 83 | // Simply returning the max might look suspicious. :-p 84 | if(roll < sides/3) 85 | { 86 | return sides; 87 | } // end if 88 | 89 | return roll; 90 | } // end strMod 91 | ``` 92 | 93 | Now, you can roll your loaded dice like such: 94 | 95 | `3(rollLoaded(6)) + 4` 96 | 97 | This expression calls `rollLoaded(6)` three times, and then adds `4`. It's the equivalent to `3d4`, except the dice 98 | rolling logic has been replaced by your loaded dice rules. Functions get the full results object, which includes the 99 | parse tree for each expression they get as an argument, which means functions can be incredibly powerful. 100 | 101 | _If you would like to dive further into the syntax, please check out our 102 | [Syntax Documentation](https://github.com/Morgul/rpgdice/wiki/Syntax-Documentation)._ 103 | 104 | ### API 105 | 106 | The API for rolling dice is super simple. There are exactly 2 functions, `rpgdice.parse()` and `rpgdice.eval()`. Each 107 | take a dice string to parse, and only differ in what they output; `parse()` simply returns you the tokenized roll as a 108 | parse tree, while `eval()` will return you a populated version of the parse tree. (The final result is in the `value` 109 | property of the root node.) Additionally, `roll()` can take a parse tree (such as the results of `parse()`) not just a 110 | string. This allows for a small optimization by only needing to tokenize the expression once, and calling `eval()` 111 | multiple times. 112 | 113 | Here's a few examples: 114 | 115 | ```javascript 116 | // Roll a simple equation 117 | var results = rpgDice.roll('3d6 + 4'); 118 | 119 | // Render the results as a string 120 | console.log('Results:', results.render()); 121 | 122 | // Print the final result: 123 | console.log('Total:', results.value); 124 | 125 | //---------------------------------------------------------------- 126 | 127 | // Evaluate an expression 128 | var eval = rpgDice.parse('3(4d10 - 2)') 129 | 130 | // Maybe do something with the evaluated expresion 131 | 132 | // Now, get the results for this roll 133 | var results = rpgDice.roll(eval); 134 | ``` 135 | 136 | #### Expression API 137 | 138 | The results of `rpgdice.parse()` and `rpgdice.eval()` are `Expression` objects. These represent the parse tree of the 139 | expression. While for a general use case you won't need the power they provide, they do expose a few useful functions: 140 | 141 | * `render()` - Renders a parse tree to a string. If the parse tree has been evaluated, it includes the intermediate results. 142 | * `eval()` - Evaluates the parse tree from this node down. (This is the same as passing the `Expression` object to `rpgdice.eval()`. 143 | 144 | 145 | _For more details on the API, please check out our 146 | [API Documentation](https://github.com/Morgul/rpgdice/wiki/API-Documentation)._ 147 | -------------------------------------------------------------------------------- /grammar/dice.pegjs: -------------------------------------------------------------------------------- 1 | { 2 | /* Import expression types */ 3 | const Conditional = require('./Conditional'); 4 | const Operation = require('./Operation'); 5 | const Not = require('./Not'); 6 | const Repeat = require('./Repeat'); 7 | const Func = require('./Function'); 8 | const Roll = require('./Roll'); 9 | const Factorial = require('./Factorial'); 10 | const Variable = require('./Variable'); 11 | const Num = require('./Number'); 12 | const Parentheses = require('./Parentheses'); 13 | 14 | /* Define side-associative operation helper functions */ 15 | function leftAssocOperation(left, rest) { 16 | return rest.reduce((left, current) => { 17 | return new Operation(current.oper, left, current.right); 18 | }, left); 19 | } 20 | function rightAssocOperation(rest, right) { 21 | return rest.reduceRight((right, current) => { 22 | return new Operation(current.oper, current.left, right); 23 | }, right); 24 | } 25 | } 26 | 27 | 28 | ///////// Primary parser rules 29 | 30 | 31 | /* Trim leading & trailing whitespace */ 32 | start "start" 33 | = OWS restart:restart OWS 34 | { return restart; } 35 | 36 | /* The restart point for later rules, purely organizational */ 37 | restart "restart" 38 | = conditional 39 | / or 40 | 41 | /* Parse conditionals to be right-associative */ 42 | conditional "conditional" 43 | = condition:or OWS '?' OWS thenExpr:restart OWS ':' OWS elseExpr:restart 44 | { return new Conditional(condition, thenExpr, elseExpr); } 45 | 46 | /* Parse ors to be left-associative */ 47 | or "or" 48 | = left:and rest:(OWS oper:'||' OWS right:and { return {oper: oper, right: right}; })* 49 | { return leftAssocOperation(left, rest); } 50 | 51 | /* Parse ands to be left-associative */ 52 | and "and" 53 | = left:equality rest:(OWS oper:'&&' OWS right:equality { return {oper: oper, right: right}; })* 54 | { return leftAssocOperation(left, rest); } 55 | 56 | /* Parse equalities to be left-associative */ 57 | equality "equality" 58 | = left:comparative rest:(OWS oper:('!=' / '==') OWS right:comparative { return {oper: oper, right: right}; })* 59 | { return leftAssocOperation(left, rest); } 60 | 61 | /* Parse comparatives to be left-associative */ 62 | comparative "comparative" 63 | = left:additive rest:(OWS oper:('>=' / '<=' / '>' / '<') OWS right:additive { return {oper: oper, right: right}; })* 64 | { return leftAssocOperation(left, rest); } 65 | 66 | /* Parse additives to be left-associative */ 67 | additive "additive" 68 | = left:multiplicative rest:(OWS oper:[+-] OWS right:multiplicative { return {oper: oper, right: right}; })* 69 | { return leftAssocOperation(left, rest); } 70 | 71 | /* Parse multiplicatives to be left-associative */ 72 | multiplicative "multiplicative" 73 | = left:exponent rest:(OWS oper:[*/%] OWS right:exponent { return {oper: oper, right: right}; })* 74 | { return leftAssocOperation(left, rest); } 75 | 76 | /* Parse exponents to be right-associative */ 77 | exponent "exponent" 78 | = rest:(left:value OWS oper:'^' OWS { return {left: left, oper: oper}; })* right:value 79 | { return rightAssocOperation(rest, right); } 80 | 81 | /* For the rest of the parsing rules we can just use any order that avoids false positives, purely organizational */ 82 | value "value" 83 | = not 84 | / repeat 85 | / func 86 | / roll 87 | / factorial 88 | / variable 89 | / num 90 | / parentheses 91 | 92 | /* Strait forward not */ 93 | not "not" 94 | = '!' OWS content:restart 95 | { return new Not(content); } 96 | 97 | /* Repeat with as many count options as possible */ 98 | repeat "repeat" 99 | = count:(parentheses / factorial / num) OWS '(' OWS content:restart OWS ')' 100 | { return new Repeat(count, content); } 101 | 102 | /* Function allows an array of arguments, if no arguments found return empty array */ 103 | func "function" 104 | = name:identifier OWS '(' args:(OWS first:restart? rest:(OWS ',' OWS arg:restart { return arg; })* { return (first ? [first] : []).concat(rest); }) OWS ')' 105 | { return new Func(name, args); } 106 | 107 | /* Roll uses simplified right-associativity */ 108 | roll "die roll" 109 | = count:(count:(parentheses / factorial / num)? { return count || undefined; }) OWS 'd' OWS sides:(parentheses / roll / factorial / num) 110 | { return new Roll(count, sides); } 111 | 112 | /* Factorial with a fix to prevent '5 != 4' false positive */ 113 | factorial "factorial" 114 | = content:posintnum OWS '!' !('=' !'=') 115 | { return new Factorial(content); } 116 | 117 | /* Strait forward variable */ 118 | variable "variable" 119 | = name:identifier 120 | { return new Variable(name); } 121 | 122 | /* Positive or negative float */ 123 | num "number" 124 | = value:(sign:'-'? value:float { return parseFloat((sign||'')+value); }) 125 | { return new Num(value); } 126 | 127 | /* Positive integer */ 128 | posintnum "positive integer number" 129 | = value:integer 130 | { return new Num(value); } 131 | 132 | /* Strait forward parentheses */ 133 | parentheses "parentheses" 134 | = '(' OWS content:restart OWS ')' 135 | { return new Parentheses(content); } 136 | 137 | 138 | ///////// Helper parser rules 139 | 140 | 141 | /* Float value */ 142 | float "float" 143 | = value:$([0-9]+ ('.' [0-9]*)? / '.' [0-9]+) 144 | { return parseFloat(value); } 145 | 146 | /* Integer value */ 147 | integer "integer" 148 | = value:$([0-9]+) 149 | { return parseInt(value, 10); } 150 | 151 | /* Identifier string */ 152 | identifier "identifier" 153 | = name:$([A-Za-z_][A-Za-z0-9_]+) 154 | { return name; } 155 | / "'" name:$([^']* ("''" [^']+)*) "'" 156 | { return name; } 157 | / '[' name:$([^[\]]* ('\\]' [^[\]]+)*) ']' 158 | { return name; } 159 | 160 | /* Omit white space and comments */ 161 | OWS "omit white space" 162 | = ([ \t\r\n] / comment)* 163 | 164 | /* Comment */ 165 | comment "comment" 166 | = '/*' (!'*/' .)* '*/' 167 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------------------------------------------------- 2 | // RPGDice 3 | //---------------------------------------------------------------------------------------------------------------------- 4 | 5 | const { parse } = require('./lib/parser'); 6 | const { rollDie } = require('./lib/rolldie'); 7 | 8 | //---------------------------------------------------------------------------------------------------------------------- 9 | 10 | module.exports = { 11 | parse(expr) 12 | { 13 | return parse(expr); 14 | }, 15 | eval(expr, scope) 16 | { 17 | if(typeof expr === 'string') 18 | { 19 | expr = parse(expr); 20 | } // end if 21 | 22 | return expr.eval(scope); 23 | }, 24 | rollDie(sides) 25 | { 26 | return rollDie(sides); 27 | } 28 | }; // end exports 29 | 30 | //---------------------------------------------------------------------------------------------------------------------- 31 | -------------------------------------------------------------------------------- /lib/Conditional.js: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------------------------------------------------- 2 | // A conditional expression. 3 | // ---------------------------------------------------------------------------------------------------------------------- 4 | 5 | const Expression = require('./Expression'); 6 | const defaultScope = require('./defaultScope'); 7 | 8 | // ---------------------------------------------------------------------------------------------------------------------- 9 | 10 | class Conditional extends Expression 11 | { 12 | constructor(condition, thenExpr, elseExpr) 13 | { 14 | super('conditional'); 15 | 16 | this.condition = condition; 17 | this.thenExpr = thenExpr; 18 | this.elseExpr = elseExpr; 19 | } // end constructor 20 | 21 | toString() 22 | { 23 | return `${ this.condition.toString() } ? ${ this.thenExpr.toString() } : ${ this.elseExpr.toString() }`; 24 | } // end toString 25 | 26 | render() 27 | { 28 | return `${ this.condition.render() } ? ${ this.thenExpr.render() } : ${ this.elseExpr.render() }`; 29 | } // end render 30 | 31 | // noinspection JSAnnotator 32 | eval(scope, depth = 1) 33 | { 34 | scope = defaultScope.buildDefaultScope(scope); 35 | 36 | this.condition = this.condition.eval(scope, depth + 1); 37 | this.thenExpr = this.thenExpr.eval(scope, depth + 1); 38 | this.elseExpr = this.elseExpr.eval(scope, depth + 1); 39 | 40 | if(this.condition.value !== 0) 41 | { 42 | this.value = this.thenExpr.value; 43 | } 44 | else 45 | { 46 | this.value = this.elseExpr.value; 47 | } 48 | 49 | return this; 50 | } // end eval 51 | } // end Conditional 52 | 53 | // ---------------------------------------------------------------------------------------------------------------------- 54 | 55 | module.exports = Conditional; 56 | 57 | // ---------------------------------------------------------------------------------------------------------------------- 58 | -------------------------------------------------------------------------------- /lib/Expression.js: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------------------------------------------------- 2 | // A base expression object. 3 | //---------------------------------------------------------------------------------------------------------------------- 4 | 5 | class Expression 6 | { 7 | constructor(type = 'expression') 8 | { 9 | this.type = type; 10 | } // end constructor 11 | 12 | toJSON() 13 | { 14 | /* eslint-disable prefer-object-spread */ 15 | return Object.assign({ type: this.type }, this); 16 | } // end toJSON 17 | 18 | render() 19 | { 20 | return this.toString(); 21 | } // end render 22 | 23 | // noinspection JSAnnotator 24 | eval() 25 | { 26 | return this.render(); 27 | } // end eval 28 | } // end Expression 29 | 30 | //---------------------------------------------------------------------------------------------------------------------- 31 | 32 | module.exports = Expression; 33 | 34 | //---------------------------------------------------------------------------------------------------------------------- 35 | -------------------------------------------------------------------------------- /lib/Factorial.js: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------------------------------------------------- 2 | // A factorial expression. 3 | // ---------------------------------------------------------------------------------------------------------------------- 4 | 5 | const Expression = require('./Expression'); 6 | const defaultScope = require('./defaultScope'); 7 | 8 | // ---------------------------------------------------------------------------------------------------------------------- 9 | 10 | class Factorial extends Expression 11 | { 12 | constructor(content) 13 | { 14 | super('factorial'); 15 | 16 | this.content = content; 17 | } // end constructor 18 | 19 | toString() 20 | { 21 | return `${ this.content }!`; 22 | } // end toString 23 | 24 | // noinspection JSAnnotator 25 | eval(scope, depth = 1) 26 | { 27 | scope = defaultScope.buildDefaultScope(scope); 28 | 29 | this.content = this.content.eval(scope, depth + 1); 30 | 31 | // Anything higher than 170 will cause value to exceed the maximum double precision float 32 | if(this.content.value > 170 || this.content.value < 0) 33 | { 34 | throw new RangeError('Factorial content out of range, must be between 0 and 170, inclusive.'); 35 | } 36 | 37 | this.value = 1; 38 | 39 | for(let i = 2; i <= this.content.value; i++) 40 | { 41 | this.value *= i; 42 | } 43 | 44 | return this; 45 | } // end eval 46 | } // end Factorial 47 | 48 | // ---------------------------------------------------------------------------------------------------------------------- 49 | 50 | module.exports = Factorial; 51 | 52 | // ---------------------------------------------------------------------------------------------------------------------- 53 | -------------------------------------------------------------------------------- /lib/Function.js: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------------------------------------------------- 2 | // A function expression. 3 | //--------------------------------------------------------------------------------------------------------------------- 4 | 5 | const Expression = require('./Expression'); 6 | const defaultScope = require('./defaultScope'); 7 | 8 | //--------------------------------------------------------------------------------------------------------------------- 9 | 10 | class Func extends Expression 11 | { 12 | constructor(name, args) 13 | { 14 | super('function'); 15 | 16 | this.name = name; 17 | this.args = args || []; 18 | this.results = []; 19 | } // end constructor 20 | 21 | needsEscaping() 22 | { 23 | return !(/^[A-Za-z_][A-Za-z0-9_]+$/g.test(this.name)); 24 | } // end needsEscaping 25 | 26 | toString() 27 | { 28 | const name = this.needsEscaping() ? `'${ this.name }'` : this.name; 29 | return `${ name }(${ this.args.join(', ') })`; 30 | } // end toString 31 | 32 | render() 33 | { 34 | const name = this.needsEscaping() ? `'${ this.name }'` : this.name; 35 | const args = this.args.reduce((results, arg) => 36 | { 37 | results.push(arg.render()); 38 | return results; 39 | }, []); 40 | 41 | return `{ ${ name }(${ args.join(', ') }): ${ this.value } }`; 42 | } // end render 43 | 44 | // noinspection JSAnnotator 45 | eval(scope) 46 | { 47 | scope = defaultScope.buildDefaultScope(scope); 48 | 49 | const func = scope[this.name]; 50 | 51 | if(typeof func !== 'function') 52 | { 53 | const error = new TypeError(`'${ this.name }' is not a function on the provided scope.`); 54 | error.scope = scope; 55 | error.name = this.name; 56 | error.code = 'FUNC_MISSING'; 57 | 58 | throw (error); 59 | } // end if 60 | 61 | // Always pass the `this` and `scope` as the last arguments 62 | this.value = func.apply(this, this.args.concat([ scope ])); 63 | 64 | return this; 65 | } // end eval 66 | } // end Func 67 | 68 | //--------------------------------------------------------------------------------------------------------------------- 69 | 70 | module.exports = Func; 71 | 72 | //--------------------------------------------------------------------------------------------------------------------- 73 | -------------------------------------------------------------------------------- /lib/Not.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------------------------------------------------- 2 | // A not expression. 3 | // --------------------------------------------------------------------------------------------------------------------- 4 | 5 | const Expression = require('./Expression'); 6 | const defaultScope = require('./defaultScope'); 7 | 8 | // --------------------------------------------------------------------------------------------------------------------- 9 | 10 | class Not extends Expression 11 | { 12 | constructor(content) 13 | { 14 | super('not'); 15 | 16 | this.content = content; 17 | } // end constructor 18 | 19 | toString() 20 | { 21 | return `!${ this.content.toString() }`; 22 | } // end toString 23 | 24 | render() 25 | { 26 | return `!${ this.content.render() }`; 27 | } // end render 28 | 29 | // noinspection JSAnnotator 30 | eval(scope, depth = 1) 31 | { 32 | scope = defaultScope.buildDefaultScope(scope); 33 | 34 | this.content = this.content.eval(scope, depth + 1); 35 | 36 | this.value = !this.content.value ? 1 : 0; 37 | 38 | return this; 39 | } // end eval 40 | } // end Not 41 | 42 | // --------------------------------------------------------------------------------------------------------------------- 43 | 44 | module.exports = Not; 45 | 46 | // --------------------------------------------------------------------------------------------------------------------- 47 | -------------------------------------------------------------------------------- /lib/Number.js: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------------------------------------------------- 2 | // A number expression. 3 | //--------------------------------------------------------------------------------------------------------------------- 4 | 5 | const Expression = require('./Expression'); 6 | 7 | //--------------------------------------------------------------------------------------------------------------------- 8 | 9 | class Num extends Expression 10 | { 11 | constructor(value) 12 | { 13 | super('number'); 14 | this.value = value; 15 | } // end Num 16 | 17 | toString() 18 | { 19 | return this.value.toString(); 20 | } // end toString 21 | 22 | // noinspection JSAnnotator 23 | eval() 24 | { 25 | return this; 26 | } // end eval 27 | } // end Num 28 | //--------------------------------------------------------------------------------------------------------------------- 29 | 30 | module.exports = Num; 31 | 32 | //--------------------------------------------------------------------------------------------------------------------- 33 | -------------------------------------------------------------------------------- /lib/Operation.js: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------------------------------------------------- 2 | // An operation expression. 3 | // ---------------------------------------------------------------------------------------------------------------------- 4 | 5 | const Expression = require('./Expression'); 6 | const defaultScope = require('./defaultScope'); 7 | 8 | // ---------------------------------------------------------------------------------------------------------------------- 9 | 10 | const ops 11 | = { 12 | add: 13 | { 14 | symbol: '+', 15 | evalValue: (left, right) => 16 | { 17 | return left.value + right.value; 18 | } 19 | }, 20 | subtract: 21 | { 22 | symbol: '-', 23 | evalValue: (left, right) => 24 | { 25 | return left.value - right.value; 26 | } 27 | }, 28 | multiply: 29 | { 30 | symbol: '*', 31 | evalValue: (left, right) => 32 | { 33 | return left.value * right.value; 34 | } 35 | }, 36 | divide: 37 | { 38 | symbol: '/', 39 | evalValue: (left, right) => 40 | { 41 | return left.value / right.value; 42 | } 43 | }, 44 | modulo: 45 | { 46 | symbol: '%', 47 | evalValue: (left, right) => 48 | { 49 | return left.value % right.value; 50 | } 51 | }, 52 | exponent: 53 | { 54 | symbol: '^', 55 | evalValue: (left, right) => 56 | { 57 | return Math.pow(left.value, right.value); 58 | } 59 | }, 60 | or: 61 | { 62 | symbol: '||', 63 | evalValue: (left, right) => 64 | { 65 | return left.value || right.value; 66 | } 67 | }, 68 | and: 69 | { 70 | symbol: '&&', 71 | evalValue: (left, right) => 72 | { 73 | return left.value && right.value; 74 | } 75 | }, 76 | equal: 77 | { 78 | symbol: '==', 79 | evalValue: (left, right) => 80 | { 81 | return left.value == right.value ? 1 : 0; 82 | } 83 | }, 84 | notEqual: 85 | { 86 | symbol: '!=', 87 | evalValue: (left, right) => 88 | { 89 | return left.value != right.value ? 1 : 0; 90 | } 91 | }, 92 | greaterThan: 93 | { 94 | symbol: '>', 95 | evalValue: (left, right) => 96 | { 97 | return left.value > right.value ? 1 : 0; 98 | } 99 | }, 100 | lessThan: 101 | { 102 | symbol: '<', 103 | evalValue: (left, right) => 104 | { 105 | return left.value < right.value ? 1 : 0; 106 | } 107 | }, 108 | greaterThanOrEqual: 109 | { 110 | symbol: '>=', 111 | evalValue: (left, right) => 112 | { 113 | return left.value >= right.value ? 1 : 0; 114 | } 115 | }, 116 | lessThanOrEqual: 117 | { 118 | symbol: '<=', 119 | evalValue: (left, right) => 120 | { 121 | return left.value <= right.value ? 1 : 0; 122 | } 123 | } 124 | }; 125 | 126 | function symbolToType(symbol) 127 | { 128 | let type = symbol; 129 | 130 | Object.keys(ops).forEach((key) => 131 | { 132 | if(ops[key].symbol === symbol) 133 | { 134 | type = key; 135 | } 136 | }); 137 | 138 | return type; 139 | } 140 | 141 | function typeToSymbol(type) 142 | { 143 | if(ops[type]) 144 | { 145 | return ops[type].symbol; 146 | } 147 | 148 | return type; 149 | } 150 | 151 | class Operation extends Expression 152 | { 153 | constructor(symbol, left, right) 154 | { 155 | super(symbolToType(symbol)); 156 | 157 | this.left = left; 158 | this.right = right; 159 | } // end constructor 160 | 161 | toString() 162 | { 163 | return `${ this.left.toString() } ${ typeToSymbol(this.type) } ${ this.right.toString() }`; 164 | } // end toString 165 | 166 | render() 167 | { 168 | return `${ this.left.render() } ${ typeToSymbol(this.type) } ${ this.right.render() }`; 169 | } // end render 170 | 171 | // noinspection JSAnnotator 172 | eval(scope, depth = 1) 173 | { 174 | scope = defaultScope.buildDefaultScope(scope); 175 | 176 | this.left = this.left.eval(scope, depth + 1); 177 | this.right = this.right.eval(scope, depth + 1); 178 | 179 | if(!ops[this.type]) 180 | { 181 | // unknown types are not supported 182 | const error = new TypeError(`'${ this.type }' is not a known operation.`); 183 | error.code = 'OP_MISSING'; 184 | error.type = this.type; 185 | 186 | throw (error); 187 | } 188 | 189 | this.value = ops[this.type].evalValue.call(this, this.left, this.right); 190 | 191 | return this; 192 | } // end eval 193 | } // end Operation 194 | 195 | // ---------------------------------------------------------------------------------------------------------------------- 196 | 197 | module.exports = Operation; 198 | 199 | // ---------------------------------------------------------------------------------------------------------------------- 200 | -------------------------------------------------------------------------------- /lib/Parentheses.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------------------------------------------------- 2 | // An parentheses expression. 3 | // --------------------------------------------------------------------------------------------------------------------- 4 | 5 | const Expression = require('./Expression'); 6 | const defaultScope = require('./defaultScope'); 7 | 8 | // --------------------------------------------------------------------------------------------------------------------- 9 | 10 | class Parentheses extends Expression 11 | { 12 | constructor(content) 13 | { 14 | super('parentheses'); 15 | 16 | this.content = content; 17 | } // end constructor 18 | 19 | toString() 20 | { 21 | return `(${ this.content.toString() })`; 22 | } // end toString 23 | 24 | render() 25 | { 26 | return `(${ this.content.render() })`; 27 | } // end render 28 | 29 | // noinspection JSAnnotator 30 | eval(scope, depth = 1) 31 | { 32 | scope = defaultScope.buildDefaultScope(scope); 33 | 34 | this.content = this.content.eval(scope, depth + 1); 35 | 36 | this.value = this.content.value; 37 | 38 | return this; 39 | } // end eval 40 | } // end Parentheses 41 | 42 | // --------------------------------------------------------------------------------------------------------------------- 43 | 44 | module.exports = Parentheses; 45 | 46 | // --------------------------------------------------------------------------------------------------------------------- 47 | -------------------------------------------------------------------------------- /lib/Repeat.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------------------------------------------------- 2 | // An repeat and sum expression. 3 | // --------------------------------------------------------------------------------------------------------------------- 4 | 5 | const Expression = require('./Expression'); 6 | const defaultScope = require('./defaultScope'); 7 | 8 | const { range } = require('./utils'); 9 | 10 | // --------------------------------------------------------------------------------------------------------------------- 11 | 12 | class Repeat extends Expression 13 | { 14 | constructor(count, content) 15 | { 16 | super('repeat'); 17 | 18 | this.count = count; 19 | this.content = content; 20 | } // end constructor 21 | 22 | toString() 23 | { 24 | return `${ this.count.toString() }(${ this.content.toString() })`; 25 | } // end toString 26 | 27 | render() 28 | { 29 | let content = this.content.render(); 30 | 31 | if(this.results && this.results.length) 32 | { 33 | const results = this.results.reduce((results, result) => 34 | { 35 | results.push(result.render()); 36 | return results; 37 | }, []); 38 | 39 | content = results.join(', '); 40 | } 41 | 42 | return `${ this.count.render() }(${ content })`; 43 | } // end render 44 | 45 | eval(scope, depth = 1) 46 | { 47 | const { parse } = require('./parser'); 48 | scope = defaultScope.buildDefaultScope(scope); 49 | 50 | this.count = this.count.eval(scope, depth + 1); 51 | 52 | const count = Math.floor(Math.abs(this.count.value)); 53 | 54 | this.results = range(count).reduce((results) => 55 | { 56 | results.push(parse(this.content.toString()).eval(scope, depth + 1)); 57 | return results; 58 | }, []); 59 | 60 | this.value = this.results.reduce((results, result) => results + result.value, 0); 61 | 62 | this.value *= (this.count.value < 0 ? -1 : 1); 63 | 64 | return this; 65 | } // end eval 66 | } // end Repeat 67 | 68 | // --------------------------------------------------------------------------------------------------------------------- 69 | 70 | module.exports = Repeat; 71 | 72 | // --------------------------------------------------------------------------------------------------------------------- 73 | -------------------------------------------------------------------------------- /lib/Roll.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------------------------------------------------- 2 | // A roll expression. 3 | // --------------------------------------------------------------------------------------------------------------------- 4 | 5 | const Expression = require('./Expression'); 6 | const defaultScope = require('./defaultScope'); 7 | 8 | const { rollDie } = require('./rolldie'); 9 | const { range } = require('./utils'); 10 | 11 | // --------------------------------------------------------------------------------------------------------------------- 12 | 13 | class Roll extends Expression 14 | { 15 | constructor(count, sides) 16 | { 17 | super('roll'); 18 | 19 | this.count = count; 20 | this.sides = sides; 21 | this.results = []; 22 | } // end constructor 23 | 24 | toString() 25 | { 26 | const count = (this.count === undefined) ? '' : this.count; 27 | 28 | return `${ count }d${ this.sides }`; 29 | } // end toString 30 | 31 | render() 32 | { 33 | const count = (this.count === undefined) ? '' : this.count.render(); 34 | 35 | return `{ ${ count }d${ this.sides.render() }: [ ${ this.results.join(', ') } ] }`; 36 | } // end render 37 | 38 | // noinspection JSAnnotator 39 | eval(scope, depth = 1) 40 | { 41 | scope = defaultScope.buildDefaultScope(scope); 42 | 43 | let count = 1; 44 | 45 | if(this.count !== undefined) 46 | { 47 | this.count = this.count.eval(scope, depth + 1); 48 | 49 | count = Math.floor(Math.abs(this.count.value)); 50 | } // end if 51 | 52 | this.sides = this.sides.eval(scope, depth + 1); 53 | 54 | this.results = range(count).reduce((results) => 55 | { 56 | results.push(rollDie(this.sides.value)); 57 | return results; 58 | }, []); 59 | 60 | this.value = this.results.reduce((results, roll) => results + roll, 0); 61 | 62 | this.value *= (this.count && this.count.value < 0 ? -1 : 1); 63 | 64 | return this; 65 | } // end eval 66 | } // end Roll 67 | 68 | // --------------------------------------------------------------------------------------------------------------------- 69 | 70 | module.exports = Roll; 71 | 72 | // --------------------------------------------------------------------------------------------------------------------- 73 | -------------------------------------------------------------------------------- /lib/Variable.js: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------------------------------------------------- 2 | // A variable expression. 3 | //--------------------------------------------------------------------------------------------------------------------- 4 | 5 | const Expression = require('./Expression'); 6 | const defaultScope = require('./defaultScope'); 7 | 8 | const { get } = require('./utils'); 9 | 10 | //--------------------------------------------------------------------------------------------------------------------- 11 | 12 | const MAX_DEPTH = 256; // An arbitrary limit; I wanted 'big', but not 'too big'. 13 | 14 | //--------------------------------------------------------------------------------------------------------------------- 15 | 16 | class Variable extends Expression 17 | { 18 | constructor(name) 19 | { 20 | super('variable'); 21 | this.name = name; 22 | } // end constructor 23 | 24 | needsEscaping() 25 | { 26 | return !(/^[A-Za-z_][A-Za-z0-9_]+$/g.test(this.name)); 27 | } // end needsEscaping 28 | 29 | toString() 30 | { 31 | return this.needsEscaping() ? `'${ this.name }'` : this.name; 32 | } // end toString 33 | 34 | render() 35 | { 36 | const name = this.toString(); 37 | return `{ ${ name }: ${ this.value } }`; 38 | } // end render 39 | 40 | // noinspection JSAnnotator 41 | eval(scope, depth = 1) 42 | { 43 | scope = defaultScope.buildDefaultScope(scope); 44 | this.value = get(scope, this.name); 45 | 46 | if(typeof (this.value) === 'string') 47 | { 48 | if(depth > MAX_DEPTH) 49 | { 50 | const error = new Error(`Evaluation exceeded maximum depth (${ MAX_DEPTH }); refusing to eval.`); 51 | error.code = 'VAR_MAX_DEPTH'; 52 | 53 | throw error; 54 | } // end if 55 | 56 | const parser = require('./parser'); 57 | 58 | try 59 | { 60 | this.expr = parser.parse(this.value); 61 | this.expr.eval(scope, depth + 1); 62 | this.value = this.expr.value; 63 | } 64 | catch (error) 65 | { 66 | // If it's a 'variable not found' error, we swallow the error, and assume they meant the literal string 67 | if(error.code !== 'VAR_NOT_FOUND') 68 | { 69 | // We rethrow the error, since it was legit. 70 | throw error; 71 | } // end if 72 | } // end try/catch 73 | } // end if 74 | 75 | if(this.value === undefined) 76 | { 77 | const error = new Error(`Variable '${ this.name }' not found in scope.`); 78 | error.scope = scope; 79 | error.name = this.name; 80 | error.code = 'VAR_NOT_FOUND'; 81 | 82 | throw error; 83 | } // end if 84 | 85 | return this; 86 | } // end eval 87 | } // end Variable 88 | 89 | //--------------------------------------------------------------------------------------------------------------------- 90 | 91 | module.exports = Variable; 92 | 93 | //--------------------------------------------------------------------------------------------------------------------- 94 | -------------------------------------------------------------------------------- /lib/defaultScope.js: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------------------------------------------------- 2 | // This is the default scope for dice evaluations 3 | //---------------------------------------------------------------------------------------------------------------------- 4 | 5 | const { rollDie } = require('./rolldie'); 6 | 7 | //---------------------------------------------------------------------------------------------------------------------- 8 | 9 | function ensureFinite(expr, name) 10 | { 11 | if(!Number.isFinite(expr.value)) 12 | { 13 | const error = new TypeError(`Non-finite number passed to '${ name }()': ${ expr.value }`); 14 | error.expr = expr; 15 | 16 | throw error; 17 | } // end if 18 | } // end ensureFinite 19 | 20 | function ensureRollType(expr, name) 21 | { 22 | if(expr.type !== 'roll') 23 | { 24 | const error = new Error(`Non-roll passed to '${ name }()': ${ expr.toString() }`); 25 | error.expr = expr; 26 | 27 | throw error; 28 | } // end if 29 | } // end ensureRollType 30 | 31 | //---------------------------------------------------------------------------------------------------------------------- 32 | 33 | const defaultScope = { 34 | min(expr1, expr2, scope) 35 | { 36 | expr1 = expr1.eval(scope); 37 | ensureFinite(expr1, 'min'); 38 | 39 | expr2 = expr2.eval(scope); 40 | ensureFinite(expr2, 'min'); 41 | 42 | return Math.min(expr1.value, expr2.value); 43 | }, 44 | max(expr1, expr2, scope) 45 | { 46 | expr1 = expr1.eval(scope); 47 | ensureFinite(expr1, 'max'); 48 | 49 | expr2 = expr2.eval(scope); 50 | ensureFinite(expr2, 'max'); 51 | 52 | return Math.max(expr1.value, expr2.value); 53 | }, 54 | floor(expr, scope) 55 | { 56 | this.expr = expr.eval(scope); 57 | ensureFinite(this.expr, 'floor'); 58 | 59 | return Math.floor(this.expr.value); 60 | }, 61 | ceil(expr, scope) 62 | { 63 | this.expr = expr.eval(scope); 64 | ensureFinite(this.expr, 'ceil'); 65 | 66 | return Math.ceil(this.expr.value); 67 | }, 68 | round(expr, scope) 69 | { 70 | this.expr = expr.eval(scope); 71 | ensureFinite(this.expr, 'round'); 72 | 73 | return Math.round(this.expr.value); 74 | }, 75 | explode(expr, scope) 76 | { 77 | ensureRollType(expr, 'explode'); 78 | 79 | let roll = expr.eval(scope); 80 | 81 | this.expr = roll; 82 | this.results = [ roll.value ]; 83 | 84 | while(roll.value === roll.sides.value) 85 | { 86 | roll = expr.eval(scope); 87 | this.results.push(roll.value); 88 | } // end while 89 | 90 | // We return the sum of everything rolled 91 | return this.results.reduce((results, roll) => results + roll, 0); 92 | }, 93 | dropLowest(expr, scope) 94 | { 95 | ensureRollType(expr, 'dropLowest'); 96 | 97 | this.expr = expr.eval(scope); 98 | 99 | // Sort the array by value 100 | this.results = this.expr.results.concat().sort((a, b) => { return (a > b) ? 1 : (b > a) ? -1 : 0; }); 101 | 102 | // Drop the lowest (left most) value 103 | this.results.shift(); 104 | 105 | // We return the sum of everything but the lowest value rolled 106 | return this.results.reduce((results, roll) => results + roll, 0); 107 | }, 108 | dropHighest(expr, scope) 109 | { 110 | ensureRollType(expr, 'dropHighest'); 111 | 112 | this.expr = expr.eval(scope); 113 | 114 | // Sort the array by value 115 | this.results = this.expr.results.concat().sort((a, b) => { return (a > b) ? 1 : (b > a) ? -1 : 0; }); 116 | 117 | // Drop the lowest (right most) value 118 | this.results.pop(); 119 | 120 | // We return the sum of everything but the highest value rolled 121 | return this.results.reduce((results, roll) => results + roll, 0); 122 | }, 123 | rerollAbove(maxVal, expr, scope) 124 | { 125 | const roll = expr.eval(scope); 126 | maxVal = maxVal.eval(scope); 127 | 128 | ensureRollType(expr, 'rerollAbove'); 129 | ensureFinite(maxVal, 'rerollAbove'); 130 | 131 | this.expr = roll; 132 | 133 | this.results = roll.results.reduce((results, rollVal) => 134 | { 135 | if(rollVal >= maxVal.value) 136 | { 137 | // We reroll, and keep 138 | results.push(rollDie(roll.sides.value)); 139 | } 140 | else 141 | { 142 | results.push(rollVal); 143 | } // end if 144 | 145 | return results; 146 | }, []); 147 | 148 | // We return the sum of everything rolled 149 | return this.results.reduce((results, roll) => results + roll, 0); 150 | }, 151 | rerollBelow(minVal, expr, scope) 152 | { 153 | const roll = expr.eval(scope); 154 | minVal = minVal.eval(scope); 155 | 156 | ensureRollType(expr, 'rerollBelow'); 157 | ensureFinite(minVal, 'rerollBelow'); 158 | 159 | this.expr = roll; 160 | 161 | this.results = roll.results.reduce((results, rollVal) => 162 | { 163 | if(rollVal <= minVal.value) 164 | { 165 | // We reroll, and keep 166 | results.push(rollDie(roll.sides.value)); 167 | } 168 | else 169 | { 170 | results.push(rollVal); 171 | } // end if 172 | 173 | return results; 174 | }, []); 175 | 176 | // We return the sum of everything rolled 177 | return this.results.reduce((results, roll) => results + roll, 0); 178 | } 179 | }; // end defaultScope 180 | 181 | //---------------------------------------------------------------------------------------------------------------------- 182 | 183 | module.exports = { 184 | buildDefaultScope(scope) 185 | { 186 | /* eslint-disable prefer-object-spread */ 187 | return Object.assign({}, defaultScope, scope); 188 | }, 189 | defaultScope 190 | }; // end exports 191 | 192 | //---------------------------------------------------------------------------------------------------------------------- 193 | -------------------------------------------------------------------------------- /lib/parser.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Generated by PEG.js 0.10.0. 3 | * 4 | * http://pegjs.org/ 5 | */ 6 | 7 | "use strict"; 8 | 9 | function peg$subclass(child, parent) { 10 | function ctor() { this.constructor = child; } 11 | ctor.prototype = parent.prototype; 12 | child.prototype = new ctor(); 13 | } 14 | 15 | function peg$SyntaxError(message, expected, found, location) { 16 | this.message = message; 17 | this.expected = expected; 18 | this.found = found; 19 | this.location = location; 20 | this.name = "SyntaxError"; 21 | 22 | if (typeof Error.captureStackTrace === "function") { 23 | Error.captureStackTrace(this, peg$SyntaxError); 24 | } 25 | } 26 | 27 | peg$subclass(peg$SyntaxError, Error); 28 | 29 | peg$SyntaxError.buildMessage = function(expected, found) { 30 | var DESCRIBE_EXPECTATION_FNS = { 31 | literal: function(expectation) { 32 | return "\"" + literalEscape(expectation.text) + "\""; 33 | }, 34 | 35 | "class": function(expectation) { 36 | var escapedParts = "", 37 | i; 38 | 39 | for (i = 0; i < expectation.parts.length; i++) { 40 | escapedParts += expectation.parts[i] instanceof Array 41 | ? classEscape(expectation.parts[i][0]) + "-" + classEscape(expectation.parts[i][1]) 42 | : classEscape(expectation.parts[i]); 43 | } 44 | 45 | return "[" + (expectation.inverted ? "^" : "") + escapedParts + "]"; 46 | }, 47 | 48 | any: function(expectation) { 49 | return "any character"; 50 | }, 51 | 52 | end: function(expectation) { 53 | return "end of input"; 54 | }, 55 | 56 | other: function(expectation) { 57 | return expectation.description; 58 | } 59 | }; 60 | 61 | function hex(ch) { 62 | return ch.charCodeAt(0).toString(16).toUpperCase(); 63 | } 64 | 65 | function literalEscape(s) { 66 | return s 67 | .replace(/\\/g, '\\\\') 68 | .replace(/"/g, '\\"') 69 | .replace(/\0/g, '\\0') 70 | .replace(/\t/g, '\\t') 71 | .replace(/\n/g, '\\n') 72 | .replace(/\r/g, '\\r') 73 | .replace(/[\x00-\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) 74 | .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return '\\x' + hex(ch); }); 75 | } 76 | 77 | function classEscape(s) { 78 | return s 79 | .replace(/\\/g, '\\\\') 80 | .replace(/\]/g, '\\]') 81 | .replace(/\^/g, '\\^') 82 | .replace(/-/g, '\\-') 83 | .replace(/\0/g, '\\0') 84 | .replace(/\t/g, '\\t') 85 | .replace(/\n/g, '\\n') 86 | .replace(/\r/g, '\\r') 87 | .replace(/[\x00-\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) 88 | .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return '\\x' + hex(ch); }); 89 | } 90 | 91 | function describeExpectation(expectation) { 92 | return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation); 93 | } 94 | 95 | function describeExpected(expected) { 96 | var descriptions = new Array(expected.length), 97 | i, j; 98 | 99 | for (i = 0; i < expected.length; i++) { 100 | descriptions[i] = describeExpectation(expected[i]); 101 | } 102 | 103 | descriptions.sort(); 104 | 105 | if (descriptions.length > 0) { 106 | for (i = 1, j = 1; i < descriptions.length; i++) { 107 | if (descriptions[i - 1] !== descriptions[i]) { 108 | descriptions[j] = descriptions[i]; 109 | j++; 110 | } 111 | } 112 | descriptions.length = j; 113 | } 114 | 115 | switch (descriptions.length) { 116 | case 1: 117 | return descriptions[0]; 118 | 119 | case 2: 120 | return descriptions[0] + " or " + descriptions[1]; 121 | 122 | default: 123 | return descriptions.slice(0, -1).join(", ") 124 | + ", or " 125 | + descriptions[descriptions.length - 1]; 126 | } 127 | } 128 | 129 | function describeFound(found) { 130 | return found ? "\"" + literalEscape(found) + "\"" : "end of input"; 131 | } 132 | 133 | return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found."; 134 | }; 135 | 136 | function peg$parse(input, options) { 137 | options = options !== void 0 ? options : {}; 138 | 139 | var peg$FAILED = {}, 140 | 141 | peg$startRuleFunctions = { start: peg$parsestart }, 142 | peg$startRuleFunction = peg$parsestart, 143 | 144 | peg$c0 = peg$otherExpectation("start"), 145 | peg$c1 = function(restart) { return restart; }, 146 | peg$c2 = peg$otherExpectation("restart"), 147 | peg$c3 = peg$otherExpectation("conditional"), 148 | peg$c4 = "?", 149 | peg$c5 = peg$literalExpectation("?", false), 150 | peg$c6 = ":", 151 | peg$c7 = peg$literalExpectation(":", false), 152 | peg$c8 = function(condition, thenExpr, elseExpr) { return new Conditional(condition, thenExpr, elseExpr); }, 153 | peg$c9 = peg$otherExpectation("or"), 154 | peg$c10 = "||", 155 | peg$c11 = peg$literalExpectation("||", false), 156 | peg$c12 = function(left, oper, right) { return {oper: oper, right: right}; }, 157 | peg$c13 = function(left, rest) { return leftAssocOperation(left, rest); }, 158 | peg$c14 = peg$otherExpectation("and"), 159 | peg$c15 = "&&", 160 | peg$c16 = peg$literalExpectation("&&", false), 161 | peg$c17 = peg$otherExpectation("equality"), 162 | peg$c18 = "!=", 163 | peg$c19 = peg$literalExpectation("!=", false), 164 | peg$c20 = "==", 165 | peg$c21 = peg$literalExpectation("==", false), 166 | peg$c22 = peg$otherExpectation("comparative"), 167 | peg$c23 = ">=", 168 | peg$c24 = peg$literalExpectation(">=", false), 169 | peg$c25 = "<=", 170 | peg$c26 = peg$literalExpectation("<=", false), 171 | peg$c27 = ">", 172 | peg$c28 = peg$literalExpectation(">", false), 173 | peg$c29 = "<", 174 | peg$c30 = peg$literalExpectation("<", false), 175 | peg$c31 = peg$otherExpectation("additive"), 176 | peg$c32 = /^[+\-]/, 177 | peg$c33 = peg$classExpectation(["+", "-"], false, false), 178 | peg$c34 = peg$otherExpectation("multiplicative"), 179 | peg$c35 = /^[*\/%]/, 180 | peg$c36 = peg$classExpectation(["*", "/", "%"], false, false), 181 | peg$c37 = peg$otherExpectation("exponent"), 182 | peg$c38 = "^", 183 | peg$c39 = peg$literalExpectation("^", false), 184 | peg$c40 = function(left, oper) { return {left: left, oper: oper}; }, 185 | peg$c41 = function(rest, right) { return rightAssocOperation(rest, right); }, 186 | peg$c42 = peg$otherExpectation("value"), 187 | peg$c43 = peg$otherExpectation("not"), 188 | peg$c44 = "!", 189 | peg$c45 = peg$literalExpectation("!", false), 190 | peg$c46 = function(content) { return new Not(content); }, 191 | peg$c47 = peg$otherExpectation("repeat"), 192 | peg$c48 = "(", 193 | peg$c49 = peg$literalExpectation("(", false), 194 | peg$c50 = ")", 195 | peg$c51 = peg$literalExpectation(")", false), 196 | peg$c52 = function(count, content) { return new Repeat(count, content); }, 197 | peg$c53 = peg$otherExpectation("function"), 198 | peg$c54 = ",", 199 | peg$c55 = peg$literalExpectation(",", false), 200 | peg$c56 = function(name, first, arg) { return arg; }, 201 | peg$c57 = function(name, first, rest) { return (first ? [first] : []).concat(rest); }, 202 | peg$c58 = function(name, args) { return new Func(name, args); }, 203 | peg$c59 = peg$otherExpectation("die roll"), 204 | peg$c60 = function(count) { return count || undefined; }, 205 | peg$c61 = "d", 206 | peg$c62 = peg$literalExpectation("d", false), 207 | peg$c63 = function(count, sides) { return new Roll(count, sides); }, 208 | peg$c64 = peg$otherExpectation("factorial"), 209 | peg$c65 = "=", 210 | peg$c66 = peg$literalExpectation("=", false), 211 | peg$c67 = function(content) { return new Factorial(content); }, 212 | peg$c68 = peg$otherExpectation("variable"), 213 | peg$c69 = function(name) { return new Variable(name); }, 214 | peg$c70 = peg$otherExpectation("number"), 215 | peg$c71 = "-", 216 | peg$c72 = peg$literalExpectation("-", false), 217 | peg$c73 = function(sign, value) { return parseFloat((sign||'')+value); }, 218 | peg$c74 = function(value) { return new Num(value); }, 219 | peg$c75 = peg$otherExpectation("positive integer number"), 220 | peg$c76 = peg$otherExpectation("parentheses"), 221 | peg$c77 = function(content) { return new Parentheses(content); }, 222 | peg$c78 = peg$otherExpectation("float"), 223 | peg$c79 = /^[0-9]/, 224 | peg$c80 = peg$classExpectation([["0", "9"]], false, false), 225 | peg$c81 = ".", 226 | peg$c82 = peg$literalExpectation(".", false), 227 | peg$c83 = function(value) { return parseFloat(value); }, 228 | peg$c84 = peg$otherExpectation("integer"), 229 | peg$c85 = function(value) { return parseInt(value, 10); }, 230 | peg$c86 = peg$otherExpectation("identifier"), 231 | peg$c87 = /^[A-Za-z_]/, 232 | peg$c88 = peg$classExpectation([["A", "Z"], ["a", "z"], "_"], false, false), 233 | peg$c89 = /^[A-Za-z0-9_]/, 234 | peg$c90 = peg$classExpectation([["A", "Z"], ["a", "z"], ["0", "9"], "_"], false, false), 235 | peg$c91 = function(name) { return name; }, 236 | peg$c92 = "'", 237 | peg$c93 = peg$literalExpectation("'", false), 238 | peg$c94 = /^[^']/, 239 | peg$c95 = peg$classExpectation(["'"], true, false), 240 | peg$c96 = "''", 241 | peg$c97 = peg$literalExpectation("''", false), 242 | peg$c98 = "[", 243 | peg$c99 = peg$literalExpectation("[", false), 244 | peg$c100 = /^[^[\]]/, 245 | peg$c101 = peg$classExpectation(["[", "]"], true, false), 246 | peg$c102 = "\\]", 247 | peg$c103 = peg$literalExpectation("\\]", false), 248 | peg$c104 = "]", 249 | peg$c105 = peg$literalExpectation("]", false), 250 | peg$c106 = peg$otherExpectation("omit white space"), 251 | peg$c107 = /^[ \t\r\n]/, 252 | peg$c108 = peg$classExpectation([" ", "\t", "\r", "\n"], false, false), 253 | peg$c109 = peg$otherExpectation("comment"), 254 | peg$c110 = "/*", 255 | peg$c111 = peg$literalExpectation("/*", false), 256 | peg$c112 = "*/", 257 | peg$c113 = peg$literalExpectation("*/", false), 258 | peg$c114 = peg$anyExpectation(), 259 | 260 | peg$currPos = 0, 261 | peg$savedPos = 0, 262 | peg$posDetailsCache = [{ line: 1, column: 1 }], 263 | peg$maxFailPos = 0, 264 | peg$maxFailExpected = [], 265 | peg$silentFails = 0, 266 | 267 | peg$result; 268 | 269 | if ("startRule" in options) { 270 | if (!(options.startRule in peg$startRuleFunctions)) { 271 | throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); 272 | } 273 | 274 | peg$startRuleFunction = peg$startRuleFunctions[options.startRule]; 275 | } 276 | 277 | function text() { 278 | return input.substring(peg$savedPos, peg$currPos); 279 | } 280 | 281 | function location() { 282 | return peg$computeLocation(peg$savedPos, peg$currPos); 283 | } 284 | 285 | function expected(description, location) { 286 | location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos) 287 | 288 | throw peg$buildStructuredError( 289 | [peg$otherExpectation(description)], 290 | input.substring(peg$savedPos, peg$currPos), 291 | location 292 | ); 293 | } 294 | 295 | function error(message, location) { 296 | location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos) 297 | 298 | throw peg$buildSimpleError(message, location); 299 | } 300 | 301 | function peg$literalExpectation(text, ignoreCase) { 302 | return { type: "literal", text: text, ignoreCase: ignoreCase }; 303 | } 304 | 305 | function peg$classExpectation(parts, inverted, ignoreCase) { 306 | return { type: "class", parts: parts, inverted: inverted, ignoreCase: ignoreCase }; 307 | } 308 | 309 | function peg$anyExpectation() { 310 | return { type: "any" }; 311 | } 312 | 313 | function peg$endExpectation() { 314 | return { type: "end" }; 315 | } 316 | 317 | function peg$otherExpectation(description) { 318 | return { type: "other", description: description }; 319 | } 320 | 321 | function peg$computePosDetails(pos) { 322 | var details = peg$posDetailsCache[pos], p; 323 | 324 | if (details) { 325 | return details; 326 | } else { 327 | p = pos - 1; 328 | while (!peg$posDetailsCache[p]) { 329 | p--; 330 | } 331 | 332 | details = peg$posDetailsCache[p]; 333 | details = { 334 | line: details.line, 335 | column: details.column 336 | }; 337 | 338 | while (p < pos) { 339 | if (input.charCodeAt(p) === 10) { 340 | details.line++; 341 | details.column = 1; 342 | } else { 343 | details.column++; 344 | } 345 | 346 | p++; 347 | } 348 | 349 | peg$posDetailsCache[pos] = details; 350 | return details; 351 | } 352 | } 353 | 354 | function peg$computeLocation(startPos, endPos) { 355 | var startPosDetails = peg$computePosDetails(startPos), 356 | endPosDetails = peg$computePosDetails(endPos); 357 | 358 | return { 359 | start: { 360 | offset: startPos, 361 | line: startPosDetails.line, 362 | column: startPosDetails.column 363 | }, 364 | end: { 365 | offset: endPos, 366 | line: endPosDetails.line, 367 | column: endPosDetails.column 368 | } 369 | }; 370 | } 371 | 372 | function peg$fail(expected) { 373 | if (peg$currPos < peg$maxFailPos) { return; } 374 | 375 | if (peg$currPos > peg$maxFailPos) { 376 | peg$maxFailPos = peg$currPos; 377 | peg$maxFailExpected = []; 378 | } 379 | 380 | peg$maxFailExpected.push(expected); 381 | } 382 | 383 | function peg$buildSimpleError(message, location) { 384 | return new peg$SyntaxError(message, null, null, location); 385 | } 386 | 387 | function peg$buildStructuredError(expected, found, location) { 388 | return new peg$SyntaxError( 389 | peg$SyntaxError.buildMessage(expected, found), 390 | expected, 391 | found, 392 | location 393 | ); 394 | } 395 | 396 | function peg$parsestart() { 397 | var s0, s1, s2, s3; 398 | 399 | peg$silentFails++; 400 | s0 = peg$currPos; 401 | s1 = peg$parseOWS(); 402 | if (s1 !== peg$FAILED) { 403 | s2 = peg$parserestart(); 404 | if (s2 !== peg$FAILED) { 405 | s3 = peg$parseOWS(); 406 | if (s3 !== peg$FAILED) { 407 | peg$savedPos = s0; 408 | s1 = peg$c1(s2); 409 | s0 = s1; 410 | } else { 411 | peg$currPos = s0; 412 | s0 = peg$FAILED; 413 | } 414 | } else { 415 | peg$currPos = s0; 416 | s0 = peg$FAILED; 417 | } 418 | } else { 419 | peg$currPos = s0; 420 | s0 = peg$FAILED; 421 | } 422 | peg$silentFails--; 423 | if (s0 === peg$FAILED) { 424 | s1 = peg$FAILED; 425 | if (peg$silentFails === 0) { peg$fail(peg$c0); } 426 | } 427 | 428 | return s0; 429 | } 430 | 431 | function peg$parserestart() { 432 | var s0, s1; 433 | 434 | peg$silentFails++; 435 | s0 = peg$parseconditional(); 436 | if (s0 === peg$FAILED) { 437 | s0 = peg$parseor(); 438 | } 439 | peg$silentFails--; 440 | if (s0 === peg$FAILED) { 441 | s1 = peg$FAILED; 442 | if (peg$silentFails === 0) { peg$fail(peg$c2); } 443 | } 444 | 445 | return s0; 446 | } 447 | 448 | function peg$parseconditional() { 449 | var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9; 450 | 451 | peg$silentFails++; 452 | s0 = peg$currPos; 453 | s1 = peg$parseor(); 454 | if (s1 !== peg$FAILED) { 455 | s2 = peg$parseOWS(); 456 | if (s2 !== peg$FAILED) { 457 | if (input.charCodeAt(peg$currPos) === 63) { 458 | s3 = peg$c4; 459 | peg$currPos++; 460 | } else { 461 | s3 = peg$FAILED; 462 | if (peg$silentFails === 0) { peg$fail(peg$c5); } 463 | } 464 | if (s3 !== peg$FAILED) { 465 | s4 = peg$parseOWS(); 466 | if (s4 !== peg$FAILED) { 467 | s5 = peg$parserestart(); 468 | if (s5 !== peg$FAILED) { 469 | s6 = peg$parseOWS(); 470 | if (s6 !== peg$FAILED) { 471 | if (input.charCodeAt(peg$currPos) === 58) { 472 | s7 = peg$c6; 473 | peg$currPos++; 474 | } else { 475 | s7 = peg$FAILED; 476 | if (peg$silentFails === 0) { peg$fail(peg$c7); } 477 | } 478 | if (s7 !== peg$FAILED) { 479 | s8 = peg$parseOWS(); 480 | if (s8 !== peg$FAILED) { 481 | s9 = peg$parserestart(); 482 | if (s9 !== peg$FAILED) { 483 | peg$savedPos = s0; 484 | s1 = peg$c8(s1, s5, s9); 485 | s0 = s1; 486 | } else { 487 | peg$currPos = s0; 488 | s0 = peg$FAILED; 489 | } 490 | } else { 491 | peg$currPos = s0; 492 | s0 = peg$FAILED; 493 | } 494 | } else { 495 | peg$currPos = s0; 496 | s0 = peg$FAILED; 497 | } 498 | } else { 499 | peg$currPos = s0; 500 | s0 = peg$FAILED; 501 | } 502 | } else { 503 | peg$currPos = s0; 504 | s0 = peg$FAILED; 505 | } 506 | } else { 507 | peg$currPos = s0; 508 | s0 = peg$FAILED; 509 | } 510 | } else { 511 | peg$currPos = s0; 512 | s0 = peg$FAILED; 513 | } 514 | } else { 515 | peg$currPos = s0; 516 | s0 = peg$FAILED; 517 | } 518 | } else { 519 | peg$currPos = s0; 520 | s0 = peg$FAILED; 521 | } 522 | peg$silentFails--; 523 | if (s0 === peg$FAILED) { 524 | s1 = peg$FAILED; 525 | if (peg$silentFails === 0) { peg$fail(peg$c3); } 526 | } 527 | 528 | return s0; 529 | } 530 | 531 | function peg$parseor() { 532 | var s0, s1, s2, s3, s4, s5, s6, s7; 533 | 534 | peg$silentFails++; 535 | s0 = peg$currPos; 536 | s1 = peg$parseand(); 537 | if (s1 !== peg$FAILED) { 538 | s2 = []; 539 | s3 = peg$currPos; 540 | s4 = peg$parseOWS(); 541 | if (s4 !== peg$FAILED) { 542 | if (input.substr(peg$currPos, 2) === peg$c10) { 543 | s5 = peg$c10; 544 | peg$currPos += 2; 545 | } else { 546 | s5 = peg$FAILED; 547 | if (peg$silentFails === 0) { peg$fail(peg$c11); } 548 | } 549 | if (s5 !== peg$FAILED) { 550 | s6 = peg$parseOWS(); 551 | if (s6 !== peg$FAILED) { 552 | s7 = peg$parseand(); 553 | if (s7 !== peg$FAILED) { 554 | peg$savedPos = s3; 555 | s4 = peg$c12(s1, s5, s7); 556 | s3 = s4; 557 | } else { 558 | peg$currPos = s3; 559 | s3 = peg$FAILED; 560 | } 561 | } else { 562 | peg$currPos = s3; 563 | s3 = peg$FAILED; 564 | } 565 | } else { 566 | peg$currPos = s3; 567 | s3 = peg$FAILED; 568 | } 569 | } else { 570 | peg$currPos = s3; 571 | s3 = peg$FAILED; 572 | } 573 | while (s3 !== peg$FAILED) { 574 | s2.push(s3); 575 | s3 = peg$currPos; 576 | s4 = peg$parseOWS(); 577 | if (s4 !== peg$FAILED) { 578 | if (input.substr(peg$currPos, 2) === peg$c10) { 579 | s5 = peg$c10; 580 | peg$currPos += 2; 581 | } else { 582 | s5 = peg$FAILED; 583 | if (peg$silentFails === 0) { peg$fail(peg$c11); } 584 | } 585 | if (s5 !== peg$FAILED) { 586 | s6 = peg$parseOWS(); 587 | if (s6 !== peg$FAILED) { 588 | s7 = peg$parseand(); 589 | if (s7 !== peg$FAILED) { 590 | peg$savedPos = s3; 591 | s4 = peg$c12(s1, s5, s7); 592 | s3 = s4; 593 | } else { 594 | peg$currPos = s3; 595 | s3 = peg$FAILED; 596 | } 597 | } else { 598 | peg$currPos = s3; 599 | s3 = peg$FAILED; 600 | } 601 | } else { 602 | peg$currPos = s3; 603 | s3 = peg$FAILED; 604 | } 605 | } else { 606 | peg$currPos = s3; 607 | s3 = peg$FAILED; 608 | } 609 | } 610 | if (s2 !== peg$FAILED) { 611 | peg$savedPos = s0; 612 | s1 = peg$c13(s1, s2); 613 | s0 = s1; 614 | } else { 615 | peg$currPos = s0; 616 | s0 = peg$FAILED; 617 | } 618 | } else { 619 | peg$currPos = s0; 620 | s0 = peg$FAILED; 621 | } 622 | peg$silentFails--; 623 | if (s0 === peg$FAILED) { 624 | s1 = peg$FAILED; 625 | if (peg$silentFails === 0) { peg$fail(peg$c9); } 626 | } 627 | 628 | return s0; 629 | } 630 | 631 | function peg$parseand() { 632 | var s0, s1, s2, s3, s4, s5, s6, s7; 633 | 634 | peg$silentFails++; 635 | s0 = peg$currPos; 636 | s1 = peg$parseequality(); 637 | if (s1 !== peg$FAILED) { 638 | s2 = []; 639 | s3 = peg$currPos; 640 | s4 = peg$parseOWS(); 641 | if (s4 !== peg$FAILED) { 642 | if (input.substr(peg$currPos, 2) === peg$c15) { 643 | s5 = peg$c15; 644 | peg$currPos += 2; 645 | } else { 646 | s5 = peg$FAILED; 647 | if (peg$silentFails === 0) { peg$fail(peg$c16); } 648 | } 649 | if (s5 !== peg$FAILED) { 650 | s6 = peg$parseOWS(); 651 | if (s6 !== peg$FAILED) { 652 | s7 = peg$parseequality(); 653 | if (s7 !== peg$FAILED) { 654 | peg$savedPos = s3; 655 | s4 = peg$c12(s1, s5, s7); 656 | s3 = s4; 657 | } else { 658 | peg$currPos = s3; 659 | s3 = peg$FAILED; 660 | } 661 | } else { 662 | peg$currPos = s3; 663 | s3 = peg$FAILED; 664 | } 665 | } else { 666 | peg$currPos = s3; 667 | s3 = peg$FAILED; 668 | } 669 | } else { 670 | peg$currPos = s3; 671 | s3 = peg$FAILED; 672 | } 673 | while (s3 !== peg$FAILED) { 674 | s2.push(s3); 675 | s3 = peg$currPos; 676 | s4 = peg$parseOWS(); 677 | if (s4 !== peg$FAILED) { 678 | if (input.substr(peg$currPos, 2) === peg$c15) { 679 | s5 = peg$c15; 680 | peg$currPos += 2; 681 | } else { 682 | s5 = peg$FAILED; 683 | if (peg$silentFails === 0) { peg$fail(peg$c16); } 684 | } 685 | if (s5 !== peg$FAILED) { 686 | s6 = peg$parseOWS(); 687 | if (s6 !== peg$FAILED) { 688 | s7 = peg$parseequality(); 689 | if (s7 !== peg$FAILED) { 690 | peg$savedPos = s3; 691 | s4 = peg$c12(s1, s5, s7); 692 | s3 = s4; 693 | } else { 694 | peg$currPos = s3; 695 | s3 = peg$FAILED; 696 | } 697 | } else { 698 | peg$currPos = s3; 699 | s3 = peg$FAILED; 700 | } 701 | } else { 702 | peg$currPos = s3; 703 | s3 = peg$FAILED; 704 | } 705 | } else { 706 | peg$currPos = s3; 707 | s3 = peg$FAILED; 708 | } 709 | } 710 | if (s2 !== peg$FAILED) { 711 | peg$savedPos = s0; 712 | s1 = peg$c13(s1, s2); 713 | s0 = s1; 714 | } else { 715 | peg$currPos = s0; 716 | s0 = peg$FAILED; 717 | } 718 | } else { 719 | peg$currPos = s0; 720 | s0 = peg$FAILED; 721 | } 722 | peg$silentFails--; 723 | if (s0 === peg$FAILED) { 724 | s1 = peg$FAILED; 725 | if (peg$silentFails === 0) { peg$fail(peg$c14); } 726 | } 727 | 728 | return s0; 729 | } 730 | 731 | function peg$parseequality() { 732 | var s0, s1, s2, s3, s4, s5, s6, s7; 733 | 734 | peg$silentFails++; 735 | s0 = peg$currPos; 736 | s1 = peg$parsecomparative(); 737 | if (s1 !== peg$FAILED) { 738 | s2 = []; 739 | s3 = peg$currPos; 740 | s4 = peg$parseOWS(); 741 | if (s4 !== peg$FAILED) { 742 | if (input.substr(peg$currPos, 2) === peg$c18) { 743 | s5 = peg$c18; 744 | peg$currPos += 2; 745 | } else { 746 | s5 = peg$FAILED; 747 | if (peg$silentFails === 0) { peg$fail(peg$c19); } 748 | } 749 | if (s5 === peg$FAILED) { 750 | if (input.substr(peg$currPos, 2) === peg$c20) { 751 | s5 = peg$c20; 752 | peg$currPos += 2; 753 | } else { 754 | s5 = peg$FAILED; 755 | if (peg$silentFails === 0) { peg$fail(peg$c21); } 756 | } 757 | } 758 | if (s5 !== peg$FAILED) { 759 | s6 = peg$parseOWS(); 760 | if (s6 !== peg$FAILED) { 761 | s7 = peg$parsecomparative(); 762 | if (s7 !== peg$FAILED) { 763 | peg$savedPos = s3; 764 | s4 = peg$c12(s1, s5, s7); 765 | s3 = s4; 766 | } else { 767 | peg$currPos = s3; 768 | s3 = peg$FAILED; 769 | } 770 | } else { 771 | peg$currPos = s3; 772 | s3 = peg$FAILED; 773 | } 774 | } else { 775 | peg$currPos = s3; 776 | s3 = peg$FAILED; 777 | } 778 | } else { 779 | peg$currPos = s3; 780 | s3 = peg$FAILED; 781 | } 782 | while (s3 !== peg$FAILED) { 783 | s2.push(s3); 784 | s3 = peg$currPos; 785 | s4 = peg$parseOWS(); 786 | if (s4 !== peg$FAILED) { 787 | if (input.substr(peg$currPos, 2) === peg$c18) { 788 | s5 = peg$c18; 789 | peg$currPos += 2; 790 | } else { 791 | s5 = peg$FAILED; 792 | if (peg$silentFails === 0) { peg$fail(peg$c19); } 793 | } 794 | if (s5 === peg$FAILED) { 795 | if (input.substr(peg$currPos, 2) === peg$c20) { 796 | s5 = peg$c20; 797 | peg$currPos += 2; 798 | } else { 799 | s5 = peg$FAILED; 800 | if (peg$silentFails === 0) { peg$fail(peg$c21); } 801 | } 802 | } 803 | if (s5 !== peg$FAILED) { 804 | s6 = peg$parseOWS(); 805 | if (s6 !== peg$FAILED) { 806 | s7 = peg$parsecomparative(); 807 | if (s7 !== peg$FAILED) { 808 | peg$savedPos = s3; 809 | s4 = peg$c12(s1, s5, s7); 810 | s3 = s4; 811 | } else { 812 | peg$currPos = s3; 813 | s3 = peg$FAILED; 814 | } 815 | } else { 816 | peg$currPos = s3; 817 | s3 = peg$FAILED; 818 | } 819 | } else { 820 | peg$currPos = s3; 821 | s3 = peg$FAILED; 822 | } 823 | } else { 824 | peg$currPos = s3; 825 | s3 = peg$FAILED; 826 | } 827 | } 828 | if (s2 !== peg$FAILED) { 829 | peg$savedPos = s0; 830 | s1 = peg$c13(s1, s2); 831 | s0 = s1; 832 | } else { 833 | peg$currPos = s0; 834 | s0 = peg$FAILED; 835 | } 836 | } else { 837 | peg$currPos = s0; 838 | s0 = peg$FAILED; 839 | } 840 | peg$silentFails--; 841 | if (s0 === peg$FAILED) { 842 | s1 = peg$FAILED; 843 | if (peg$silentFails === 0) { peg$fail(peg$c17); } 844 | } 845 | 846 | return s0; 847 | } 848 | 849 | function peg$parsecomparative() { 850 | var s0, s1, s2, s3, s4, s5, s6, s7; 851 | 852 | peg$silentFails++; 853 | s0 = peg$currPos; 854 | s1 = peg$parseadditive(); 855 | if (s1 !== peg$FAILED) { 856 | s2 = []; 857 | s3 = peg$currPos; 858 | s4 = peg$parseOWS(); 859 | if (s4 !== peg$FAILED) { 860 | if (input.substr(peg$currPos, 2) === peg$c23) { 861 | s5 = peg$c23; 862 | peg$currPos += 2; 863 | } else { 864 | s5 = peg$FAILED; 865 | if (peg$silentFails === 0) { peg$fail(peg$c24); } 866 | } 867 | if (s5 === peg$FAILED) { 868 | if (input.substr(peg$currPos, 2) === peg$c25) { 869 | s5 = peg$c25; 870 | peg$currPos += 2; 871 | } else { 872 | s5 = peg$FAILED; 873 | if (peg$silentFails === 0) { peg$fail(peg$c26); } 874 | } 875 | if (s5 === peg$FAILED) { 876 | if (input.charCodeAt(peg$currPos) === 62) { 877 | s5 = peg$c27; 878 | peg$currPos++; 879 | } else { 880 | s5 = peg$FAILED; 881 | if (peg$silentFails === 0) { peg$fail(peg$c28); } 882 | } 883 | if (s5 === peg$FAILED) { 884 | if (input.charCodeAt(peg$currPos) === 60) { 885 | s5 = peg$c29; 886 | peg$currPos++; 887 | } else { 888 | s5 = peg$FAILED; 889 | if (peg$silentFails === 0) { peg$fail(peg$c30); } 890 | } 891 | } 892 | } 893 | } 894 | if (s5 !== peg$FAILED) { 895 | s6 = peg$parseOWS(); 896 | if (s6 !== peg$FAILED) { 897 | s7 = peg$parseadditive(); 898 | if (s7 !== peg$FAILED) { 899 | peg$savedPos = s3; 900 | s4 = peg$c12(s1, s5, s7); 901 | s3 = s4; 902 | } else { 903 | peg$currPos = s3; 904 | s3 = peg$FAILED; 905 | } 906 | } else { 907 | peg$currPos = s3; 908 | s3 = peg$FAILED; 909 | } 910 | } else { 911 | peg$currPos = s3; 912 | s3 = peg$FAILED; 913 | } 914 | } else { 915 | peg$currPos = s3; 916 | s3 = peg$FAILED; 917 | } 918 | while (s3 !== peg$FAILED) { 919 | s2.push(s3); 920 | s3 = peg$currPos; 921 | s4 = peg$parseOWS(); 922 | if (s4 !== peg$FAILED) { 923 | if (input.substr(peg$currPos, 2) === peg$c23) { 924 | s5 = peg$c23; 925 | peg$currPos += 2; 926 | } else { 927 | s5 = peg$FAILED; 928 | if (peg$silentFails === 0) { peg$fail(peg$c24); } 929 | } 930 | if (s5 === peg$FAILED) { 931 | if (input.substr(peg$currPos, 2) === peg$c25) { 932 | s5 = peg$c25; 933 | peg$currPos += 2; 934 | } else { 935 | s5 = peg$FAILED; 936 | if (peg$silentFails === 0) { peg$fail(peg$c26); } 937 | } 938 | if (s5 === peg$FAILED) { 939 | if (input.charCodeAt(peg$currPos) === 62) { 940 | s5 = peg$c27; 941 | peg$currPos++; 942 | } else { 943 | s5 = peg$FAILED; 944 | if (peg$silentFails === 0) { peg$fail(peg$c28); } 945 | } 946 | if (s5 === peg$FAILED) { 947 | if (input.charCodeAt(peg$currPos) === 60) { 948 | s5 = peg$c29; 949 | peg$currPos++; 950 | } else { 951 | s5 = peg$FAILED; 952 | if (peg$silentFails === 0) { peg$fail(peg$c30); } 953 | } 954 | } 955 | } 956 | } 957 | if (s5 !== peg$FAILED) { 958 | s6 = peg$parseOWS(); 959 | if (s6 !== peg$FAILED) { 960 | s7 = peg$parseadditive(); 961 | if (s7 !== peg$FAILED) { 962 | peg$savedPos = s3; 963 | s4 = peg$c12(s1, s5, s7); 964 | s3 = s4; 965 | } else { 966 | peg$currPos = s3; 967 | s3 = peg$FAILED; 968 | } 969 | } else { 970 | peg$currPos = s3; 971 | s3 = peg$FAILED; 972 | } 973 | } else { 974 | peg$currPos = s3; 975 | s3 = peg$FAILED; 976 | } 977 | } else { 978 | peg$currPos = s3; 979 | s3 = peg$FAILED; 980 | } 981 | } 982 | if (s2 !== peg$FAILED) { 983 | peg$savedPos = s0; 984 | s1 = peg$c13(s1, s2); 985 | s0 = s1; 986 | } else { 987 | peg$currPos = s0; 988 | s0 = peg$FAILED; 989 | } 990 | } else { 991 | peg$currPos = s0; 992 | s0 = peg$FAILED; 993 | } 994 | peg$silentFails--; 995 | if (s0 === peg$FAILED) { 996 | s1 = peg$FAILED; 997 | if (peg$silentFails === 0) { peg$fail(peg$c22); } 998 | } 999 | 1000 | return s0; 1001 | } 1002 | 1003 | function peg$parseadditive() { 1004 | var s0, s1, s2, s3, s4, s5, s6, s7; 1005 | 1006 | peg$silentFails++; 1007 | s0 = peg$currPos; 1008 | s1 = peg$parsemultiplicative(); 1009 | if (s1 !== peg$FAILED) { 1010 | s2 = []; 1011 | s3 = peg$currPos; 1012 | s4 = peg$parseOWS(); 1013 | if (s4 !== peg$FAILED) { 1014 | if (peg$c32.test(input.charAt(peg$currPos))) { 1015 | s5 = input.charAt(peg$currPos); 1016 | peg$currPos++; 1017 | } else { 1018 | s5 = peg$FAILED; 1019 | if (peg$silentFails === 0) { peg$fail(peg$c33); } 1020 | } 1021 | if (s5 !== peg$FAILED) { 1022 | s6 = peg$parseOWS(); 1023 | if (s6 !== peg$FAILED) { 1024 | s7 = peg$parsemultiplicative(); 1025 | if (s7 !== peg$FAILED) { 1026 | peg$savedPos = s3; 1027 | s4 = peg$c12(s1, s5, s7); 1028 | s3 = s4; 1029 | } else { 1030 | peg$currPos = s3; 1031 | s3 = peg$FAILED; 1032 | } 1033 | } else { 1034 | peg$currPos = s3; 1035 | s3 = peg$FAILED; 1036 | } 1037 | } else { 1038 | peg$currPos = s3; 1039 | s3 = peg$FAILED; 1040 | } 1041 | } else { 1042 | peg$currPos = s3; 1043 | s3 = peg$FAILED; 1044 | } 1045 | while (s3 !== peg$FAILED) { 1046 | s2.push(s3); 1047 | s3 = peg$currPos; 1048 | s4 = peg$parseOWS(); 1049 | if (s4 !== peg$FAILED) { 1050 | if (peg$c32.test(input.charAt(peg$currPos))) { 1051 | s5 = input.charAt(peg$currPos); 1052 | peg$currPos++; 1053 | } else { 1054 | s5 = peg$FAILED; 1055 | if (peg$silentFails === 0) { peg$fail(peg$c33); } 1056 | } 1057 | if (s5 !== peg$FAILED) { 1058 | s6 = peg$parseOWS(); 1059 | if (s6 !== peg$FAILED) { 1060 | s7 = peg$parsemultiplicative(); 1061 | if (s7 !== peg$FAILED) { 1062 | peg$savedPos = s3; 1063 | s4 = peg$c12(s1, s5, s7); 1064 | s3 = s4; 1065 | } else { 1066 | peg$currPos = s3; 1067 | s3 = peg$FAILED; 1068 | } 1069 | } else { 1070 | peg$currPos = s3; 1071 | s3 = peg$FAILED; 1072 | } 1073 | } else { 1074 | peg$currPos = s3; 1075 | s3 = peg$FAILED; 1076 | } 1077 | } else { 1078 | peg$currPos = s3; 1079 | s3 = peg$FAILED; 1080 | } 1081 | } 1082 | if (s2 !== peg$FAILED) { 1083 | peg$savedPos = s0; 1084 | s1 = peg$c13(s1, s2); 1085 | s0 = s1; 1086 | } else { 1087 | peg$currPos = s0; 1088 | s0 = peg$FAILED; 1089 | } 1090 | } else { 1091 | peg$currPos = s0; 1092 | s0 = peg$FAILED; 1093 | } 1094 | peg$silentFails--; 1095 | if (s0 === peg$FAILED) { 1096 | s1 = peg$FAILED; 1097 | if (peg$silentFails === 0) { peg$fail(peg$c31); } 1098 | } 1099 | 1100 | return s0; 1101 | } 1102 | 1103 | function peg$parsemultiplicative() { 1104 | var s0, s1, s2, s3, s4, s5, s6, s7; 1105 | 1106 | peg$silentFails++; 1107 | s0 = peg$currPos; 1108 | s1 = peg$parseexponent(); 1109 | if (s1 !== peg$FAILED) { 1110 | s2 = []; 1111 | s3 = peg$currPos; 1112 | s4 = peg$parseOWS(); 1113 | if (s4 !== peg$FAILED) { 1114 | if (peg$c35.test(input.charAt(peg$currPos))) { 1115 | s5 = input.charAt(peg$currPos); 1116 | peg$currPos++; 1117 | } else { 1118 | s5 = peg$FAILED; 1119 | if (peg$silentFails === 0) { peg$fail(peg$c36); } 1120 | } 1121 | if (s5 !== peg$FAILED) { 1122 | s6 = peg$parseOWS(); 1123 | if (s6 !== peg$FAILED) { 1124 | s7 = peg$parseexponent(); 1125 | if (s7 !== peg$FAILED) { 1126 | peg$savedPos = s3; 1127 | s4 = peg$c12(s1, s5, s7); 1128 | s3 = s4; 1129 | } else { 1130 | peg$currPos = s3; 1131 | s3 = peg$FAILED; 1132 | } 1133 | } else { 1134 | peg$currPos = s3; 1135 | s3 = peg$FAILED; 1136 | } 1137 | } else { 1138 | peg$currPos = s3; 1139 | s3 = peg$FAILED; 1140 | } 1141 | } else { 1142 | peg$currPos = s3; 1143 | s3 = peg$FAILED; 1144 | } 1145 | while (s3 !== peg$FAILED) { 1146 | s2.push(s3); 1147 | s3 = peg$currPos; 1148 | s4 = peg$parseOWS(); 1149 | if (s4 !== peg$FAILED) { 1150 | if (peg$c35.test(input.charAt(peg$currPos))) { 1151 | s5 = input.charAt(peg$currPos); 1152 | peg$currPos++; 1153 | } else { 1154 | s5 = peg$FAILED; 1155 | if (peg$silentFails === 0) { peg$fail(peg$c36); } 1156 | } 1157 | if (s5 !== peg$FAILED) { 1158 | s6 = peg$parseOWS(); 1159 | if (s6 !== peg$FAILED) { 1160 | s7 = peg$parseexponent(); 1161 | if (s7 !== peg$FAILED) { 1162 | peg$savedPos = s3; 1163 | s4 = peg$c12(s1, s5, s7); 1164 | s3 = s4; 1165 | } else { 1166 | peg$currPos = s3; 1167 | s3 = peg$FAILED; 1168 | } 1169 | } else { 1170 | peg$currPos = s3; 1171 | s3 = peg$FAILED; 1172 | } 1173 | } else { 1174 | peg$currPos = s3; 1175 | s3 = peg$FAILED; 1176 | } 1177 | } else { 1178 | peg$currPos = s3; 1179 | s3 = peg$FAILED; 1180 | } 1181 | } 1182 | if (s2 !== peg$FAILED) { 1183 | peg$savedPos = s0; 1184 | s1 = peg$c13(s1, s2); 1185 | s0 = s1; 1186 | } else { 1187 | peg$currPos = s0; 1188 | s0 = peg$FAILED; 1189 | } 1190 | } else { 1191 | peg$currPos = s0; 1192 | s0 = peg$FAILED; 1193 | } 1194 | peg$silentFails--; 1195 | if (s0 === peg$FAILED) { 1196 | s1 = peg$FAILED; 1197 | if (peg$silentFails === 0) { peg$fail(peg$c34); } 1198 | } 1199 | 1200 | return s0; 1201 | } 1202 | 1203 | function peg$parseexponent() { 1204 | var s0, s1, s2, s3, s4, s5, s6; 1205 | 1206 | peg$silentFails++; 1207 | s0 = peg$currPos; 1208 | s1 = []; 1209 | s2 = peg$currPos; 1210 | s3 = peg$parsevalue(); 1211 | if (s3 !== peg$FAILED) { 1212 | s4 = peg$parseOWS(); 1213 | if (s4 !== peg$FAILED) { 1214 | if (input.charCodeAt(peg$currPos) === 94) { 1215 | s5 = peg$c38; 1216 | peg$currPos++; 1217 | } else { 1218 | s5 = peg$FAILED; 1219 | if (peg$silentFails === 0) { peg$fail(peg$c39); } 1220 | } 1221 | if (s5 !== peg$FAILED) { 1222 | s6 = peg$parseOWS(); 1223 | if (s6 !== peg$FAILED) { 1224 | peg$savedPos = s2; 1225 | s3 = peg$c40(s3, s5); 1226 | s2 = s3; 1227 | } else { 1228 | peg$currPos = s2; 1229 | s2 = peg$FAILED; 1230 | } 1231 | } else { 1232 | peg$currPos = s2; 1233 | s2 = peg$FAILED; 1234 | } 1235 | } else { 1236 | peg$currPos = s2; 1237 | s2 = peg$FAILED; 1238 | } 1239 | } else { 1240 | peg$currPos = s2; 1241 | s2 = peg$FAILED; 1242 | } 1243 | while (s2 !== peg$FAILED) { 1244 | s1.push(s2); 1245 | s2 = peg$currPos; 1246 | s3 = peg$parsevalue(); 1247 | if (s3 !== peg$FAILED) { 1248 | s4 = peg$parseOWS(); 1249 | if (s4 !== peg$FAILED) { 1250 | if (input.charCodeAt(peg$currPos) === 94) { 1251 | s5 = peg$c38; 1252 | peg$currPos++; 1253 | } else { 1254 | s5 = peg$FAILED; 1255 | if (peg$silentFails === 0) { peg$fail(peg$c39); } 1256 | } 1257 | if (s5 !== peg$FAILED) { 1258 | s6 = peg$parseOWS(); 1259 | if (s6 !== peg$FAILED) { 1260 | peg$savedPos = s2; 1261 | s3 = peg$c40(s3, s5); 1262 | s2 = s3; 1263 | } else { 1264 | peg$currPos = s2; 1265 | s2 = peg$FAILED; 1266 | } 1267 | } else { 1268 | peg$currPos = s2; 1269 | s2 = peg$FAILED; 1270 | } 1271 | } else { 1272 | peg$currPos = s2; 1273 | s2 = peg$FAILED; 1274 | } 1275 | } else { 1276 | peg$currPos = s2; 1277 | s2 = peg$FAILED; 1278 | } 1279 | } 1280 | if (s1 !== peg$FAILED) { 1281 | s2 = peg$parsevalue(); 1282 | if (s2 !== peg$FAILED) { 1283 | peg$savedPos = s0; 1284 | s1 = peg$c41(s1, s2); 1285 | s0 = s1; 1286 | } else { 1287 | peg$currPos = s0; 1288 | s0 = peg$FAILED; 1289 | } 1290 | } else { 1291 | peg$currPos = s0; 1292 | s0 = peg$FAILED; 1293 | } 1294 | peg$silentFails--; 1295 | if (s0 === peg$FAILED) { 1296 | s1 = peg$FAILED; 1297 | if (peg$silentFails === 0) { peg$fail(peg$c37); } 1298 | } 1299 | 1300 | return s0; 1301 | } 1302 | 1303 | function peg$parsevalue() { 1304 | var s0, s1; 1305 | 1306 | peg$silentFails++; 1307 | s0 = peg$parsenot(); 1308 | if (s0 === peg$FAILED) { 1309 | s0 = peg$parserepeat(); 1310 | if (s0 === peg$FAILED) { 1311 | s0 = peg$parsefunc(); 1312 | if (s0 === peg$FAILED) { 1313 | s0 = peg$parseroll(); 1314 | if (s0 === peg$FAILED) { 1315 | s0 = peg$parsefactorial(); 1316 | if (s0 === peg$FAILED) { 1317 | s0 = peg$parsevariable(); 1318 | if (s0 === peg$FAILED) { 1319 | s0 = peg$parsenum(); 1320 | if (s0 === peg$FAILED) { 1321 | s0 = peg$parseparentheses(); 1322 | } 1323 | } 1324 | } 1325 | } 1326 | } 1327 | } 1328 | } 1329 | peg$silentFails--; 1330 | if (s0 === peg$FAILED) { 1331 | s1 = peg$FAILED; 1332 | if (peg$silentFails === 0) { peg$fail(peg$c42); } 1333 | } 1334 | 1335 | return s0; 1336 | } 1337 | 1338 | function peg$parsenot() { 1339 | var s0, s1, s2, s3; 1340 | 1341 | peg$silentFails++; 1342 | s0 = peg$currPos; 1343 | if (input.charCodeAt(peg$currPos) === 33) { 1344 | s1 = peg$c44; 1345 | peg$currPos++; 1346 | } else { 1347 | s1 = peg$FAILED; 1348 | if (peg$silentFails === 0) { peg$fail(peg$c45); } 1349 | } 1350 | if (s1 !== peg$FAILED) { 1351 | s2 = peg$parseOWS(); 1352 | if (s2 !== peg$FAILED) { 1353 | s3 = peg$parserestart(); 1354 | if (s3 !== peg$FAILED) { 1355 | peg$savedPos = s0; 1356 | s1 = peg$c46(s3); 1357 | s0 = s1; 1358 | } else { 1359 | peg$currPos = s0; 1360 | s0 = peg$FAILED; 1361 | } 1362 | } else { 1363 | peg$currPos = s0; 1364 | s0 = peg$FAILED; 1365 | } 1366 | } else { 1367 | peg$currPos = s0; 1368 | s0 = peg$FAILED; 1369 | } 1370 | peg$silentFails--; 1371 | if (s0 === peg$FAILED) { 1372 | s1 = peg$FAILED; 1373 | if (peg$silentFails === 0) { peg$fail(peg$c43); } 1374 | } 1375 | 1376 | return s0; 1377 | } 1378 | 1379 | function peg$parserepeat() { 1380 | var s0, s1, s2, s3, s4, s5, s6, s7; 1381 | 1382 | peg$silentFails++; 1383 | s0 = peg$currPos; 1384 | s1 = peg$parseparentheses(); 1385 | if (s1 === peg$FAILED) { 1386 | s1 = peg$parsefactorial(); 1387 | if (s1 === peg$FAILED) { 1388 | s1 = peg$parsenum(); 1389 | } 1390 | } 1391 | if (s1 !== peg$FAILED) { 1392 | s2 = peg$parseOWS(); 1393 | if (s2 !== peg$FAILED) { 1394 | if (input.charCodeAt(peg$currPos) === 40) { 1395 | s3 = peg$c48; 1396 | peg$currPos++; 1397 | } else { 1398 | s3 = peg$FAILED; 1399 | if (peg$silentFails === 0) { peg$fail(peg$c49); } 1400 | } 1401 | if (s3 !== peg$FAILED) { 1402 | s4 = peg$parseOWS(); 1403 | if (s4 !== peg$FAILED) { 1404 | s5 = peg$parserestart(); 1405 | if (s5 !== peg$FAILED) { 1406 | s6 = peg$parseOWS(); 1407 | if (s6 !== peg$FAILED) { 1408 | if (input.charCodeAt(peg$currPos) === 41) { 1409 | s7 = peg$c50; 1410 | peg$currPos++; 1411 | } else { 1412 | s7 = peg$FAILED; 1413 | if (peg$silentFails === 0) { peg$fail(peg$c51); } 1414 | } 1415 | if (s7 !== peg$FAILED) { 1416 | peg$savedPos = s0; 1417 | s1 = peg$c52(s1, s5); 1418 | s0 = s1; 1419 | } else { 1420 | peg$currPos = s0; 1421 | s0 = peg$FAILED; 1422 | } 1423 | } else { 1424 | peg$currPos = s0; 1425 | s0 = peg$FAILED; 1426 | } 1427 | } else { 1428 | peg$currPos = s0; 1429 | s0 = peg$FAILED; 1430 | } 1431 | } else { 1432 | peg$currPos = s0; 1433 | s0 = peg$FAILED; 1434 | } 1435 | } else { 1436 | peg$currPos = s0; 1437 | s0 = peg$FAILED; 1438 | } 1439 | } else { 1440 | peg$currPos = s0; 1441 | s0 = peg$FAILED; 1442 | } 1443 | } else { 1444 | peg$currPos = s0; 1445 | s0 = peg$FAILED; 1446 | } 1447 | peg$silentFails--; 1448 | if (s0 === peg$FAILED) { 1449 | s1 = peg$FAILED; 1450 | if (peg$silentFails === 0) { peg$fail(peg$c47); } 1451 | } 1452 | 1453 | return s0; 1454 | } 1455 | 1456 | function peg$parsefunc() { 1457 | var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12; 1458 | 1459 | peg$silentFails++; 1460 | s0 = peg$currPos; 1461 | s1 = peg$parseidentifier(); 1462 | if (s1 !== peg$FAILED) { 1463 | s2 = peg$parseOWS(); 1464 | if (s2 !== peg$FAILED) { 1465 | if (input.charCodeAt(peg$currPos) === 40) { 1466 | s3 = peg$c48; 1467 | peg$currPos++; 1468 | } else { 1469 | s3 = peg$FAILED; 1470 | if (peg$silentFails === 0) { peg$fail(peg$c49); } 1471 | } 1472 | if (s3 !== peg$FAILED) { 1473 | s4 = peg$currPos; 1474 | s5 = peg$parseOWS(); 1475 | if (s5 !== peg$FAILED) { 1476 | s6 = peg$parserestart(); 1477 | if (s6 === peg$FAILED) { 1478 | s6 = null; 1479 | } 1480 | if (s6 !== peg$FAILED) { 1481 | s7 = []; 1482 | s8 = peg$currPos; 1483 | s9 = peg$parseOWS(); 1484 | if (s9 !== peg$FAILED) { 1485 | if (input.charCodeAt(peg$currPos) === 44) { 1486 | s10 = peg$c54; 1487 | peg$currPos++; 1488 | } else { 1489 | s10 = peg$FAILED; 1490 | if (peg$silentFails === 0) { peg$fail(peg$c55); } 1491 | } 1492 | if (s10 !== peg$FAILED) { 1493 | s11 = peg$parseOWS(); 1494 | if (s11 !== peg$FAILED) { 1495 | s12 = peg$parserestart(); 1496 | if (s12 !== peg$FAILED) { 1497 | peg$savedPos = s8; 1498 | s9 = peg$c56(s1, s6, s12); 1499 | s8 = s9; 1500 | } else { 1501 | peg$currPos = s8; 1502 | s8 = peg$FAILED; 1503 | } 1504 | } else { 1505 | peg$currPos = s8; 1506 | s8 = peg$FAILED; 1507 | } 1508 | } else { 1509 | peg$currPos = s8; 1510 | s8 = peg$FAILED; 1511 | } 1512 | } else { 1513 | peg$currPos = s8; 1514 | s8 = peg$FAILED; 1515 | } 1516 | while (s8 !== peg$FAILED) { 1517 | s7.push(s8); 1518 | s8 = peg$currPos; 1519 | s9 = peg$parseOWS(); 1520 | if (s9 !== peg$FAILED) { 1521 | if (input.charCodeAt(peg$currPos) === 44) { 1522 | s10 = peg$c54; 1523 | peg$currPos++; 1524 | } else { 1525 | s10 = peg$FAILED; 1526 | if (peg$silentFails === 0) { peg$fail(peg$c55); } 1527 | } 1528 | if (s10 !== peg$FAILED) { 1529 | s11 = peg$parseOWS(); 1530 | if (s11 !== peg$FAILED) { 1531 | s12 = peg$parserestart(); 1532 | if (s12 !== peg$FAILED) { 1533 | peg$savedPos = s8; 1534 | s9 = peg$c56(s1, s6, s12); 1535 | s8 = s9; 1536 | } else { 1537 | peg$currPos = s8; 1538 | s8 = peg$FAILED; 1539 | } 1540 | } else { 1541 | peg$currPos = s8; 1542 | s8 = peg$FAILED; 1543 | } 1544 | } else { 1545 | peg$currPos = s8; 1546 | s8 = peg$FAILED; 1547 | } 1548 | } else { 1549 | peg$currPos = s8; 1550 | s8 = peg$FAILED; 1551 | } 1552 | } 1553 | if (s7 !== peg$FAILED) { 1554 | peg$savedPos = s4; 1555 | s5 = peg$c57(s1, s6, s7); 1556 | s4 = s5; 1557 | } else { 1558 | peg$currPos = s4; 1559 | s4 = peg$FAILED; 1560 | } 1561 | } else { 1562 | peg$currPos = s4; 1563 | s4 = peg$FAILED; 1564 | } 1565 | } else { 1566 | peg$currPos = s4; 1567 | s4 = peg$FAILED; 1568 | } 1569 | if (s4 !== peg$FAILED) { 1570 | s5 = peg$parseOWS(); 1571 | if (s5 !== peg$FAILED) { 1572 | if (input.charCodeAt(peg$currPos) === 41) { 1573 | s6 = peg$c50; 1574 | peg$currPos++; 1575 | } else { 1576 | s6 = peg$FAILED; 1577 | if (peg$silentFails === 0) { peg$fail(peg$c51); } 1578 | } 1579 | if (s6 !== peg$FAILED) { 1580 | peg$savedPos = s0; 1581 | s1 = peg$c58(s1, s4); 1582 | s0 = s1; 1583 | } else { 1584 | peg$currPos = s0; 1585 | s0 = peg$FAILED; 1586 | } 1587 | } else { 1588 | peg$currPos = s0; 1589 | s0 = peg$FAILED; 1590 | } 1591 | } else { 1592 | peg$currPos = s0; 1593 | s0 = peg$FAILED; 1594 | } 1595 | } else { 1596 | peg$currPos = s0; 1597 | s0 = peg$FAILED; 1598 | } 1599 | } else { 1600 | peg$currPos = s0; 1601 | s0 = peg$FAILED; 1602 | } 1603 | } else { 1604 | peg$currPos = s0; 1605 | s0 = peg$FAILED; 1606 | } 1607 | peg$silentFails--; 1608 | if (s0 === peg$FAILED) { 1609 | s1 = peg$FAILED; 1610 | if (peg$silentFails === 0) { peg$fail(peg$c53); } 1611 | } 1612 | 1613 | return s0; 1614 | } 1615 | 1616 | function peg$parseroll() { 1617 | var s0, s1, s2, s3, s4, s5; 1618 | 1619 | peg$silentFails++; 1620 | s0 = peg$currPos; 1621 | s1 = peg$currPos; 1622 | s2 = peg$parseparentheses(); 1623 | if (s2 === peg$FAILED) { 1624 | s2 = peg$parsefactorial(); 1625 | if (s2 === peg$FAILED) { 1626 | s2 = peg$parsenum(); 1627 | } 1628 | } 1629 | if (s2 === peg$FAILED) { 1630 | s2 = null; 1631 | } 1632 | if (s2 !== peg$FAILED) { 1633 | peg$savedPos = s1; 1634 | s2 = peg$c60(s2); 1635 | } 1636 | s1 = s2; 1637 | if (s1 !== peg$FAILED) { 1638 | s2 = peg$parseOWS(); 1639 | if (s2 !== peg$FAILED) { 1640 | if (input.charCodeAt(peg$currPos) === 100) { 1641 | s3 = peg$c61; 1642 | peg$currPos++; 1643 | } else { 1644 | s3 = peg$FAILED; 1645 | if (peg$silentFails === 0) { peg$fail(peg$c62); } 1646 | } 1647 | if (s3 !== peg$FAILED) { 1648 | s4 = peg$parseOWS(); 1649 | if (s4 !== peg$FAILED) { 1650 | s5 = peg$parseparentheses(); 1651 | if (s5 === peg$FAILED) { 1652 | s5 = peg$parseroll(); 1653 | if (s5 === peg$FAILED) { 1654 | s5 = peg$parsefactorial(); 1655 | if (s5 === peg$FAILED) { 1656 | s5 = peg$parsenum(); 1657 | } 1658 | } 1659 | } 1660 | if (s5 !== peg$FAILED) { 1661 | peg$savedPos = s0; 1662 | s1 = peg$c63(s1, s5); 1663 | s0 = s1; 1664 | } else { 1665 | peg$currPos = s0; 1666 | s0 = peg$FAILED; 1667 | } 1668 | } else { 1669 | peg$currPos = s0; 1670 | s0 = peg$FAILED; 1671 | } 1672 | } else { 1673 | peg$currPos = s0; 1674 | s0 = peg$FAILED; 1675 | } 1676 | } else { 1677 | peg$currPos = s0; 1678 | s0 = peg$FAILED; 1679 | } 1680 | } else { 1681 | peg$currPos = s0; 1682 | s0 = peg$FAILED; 1683 | } 1684 | peg$silentFails--; 1685 | if (s0 === peg$FAILED) { 1686 | s1 = peg$FAILED; 1687 | if (peg$silentFails === 0) { peg$fail(peg$c59); } 1688 | } 1689 | 1690 | return s0; 1691 | } 1692 | 1693 | function peg$parsefactorial() { 1694 | var s0, s1, s2, s3, s4, s5, s6, s7, s8; 1695 | 1696 | peg$silentFails++; 1697 | s0 = peg$currPos; 1698 | s1 = peg$parseposintnum(); 1699 | if (s1 !== peg$FAILED) { 1700 | s2 = peg$parseOWS(); 1701 | if (s2 !== peg$FAILED) { 1702 | if (input.charCodeAt(peg$currPos) === 33) { 1703 | s3 = peg$c44; 1704 | peg$currPos++; 1705 | } else { 1706 | s3 = peg$FAILED; 1707 | if (peg$silentFails === 0) { peg$fail(peg$c45); } 1708 | } 1709 | if (s3 !== peg$FAILED) { 1710 | s4 = peg$currPos; 1711 | peg$silentFails++; 1712 | s5 = peg$currPos; 1713 | if (input.charCodeAt(peg$currPos) === 61) { 1714 | s6 = peg$c65; 1715 | peg$currPos++; 1716 | } else { 1717 | s6 = peg$FAILED; 1718 | if (peg$silentFails === 0) { peg$fail(peg$c66); } 1719 | } 1720 | if (s6 !== peg$FAILED) { 1721 | s7 = peg$currPos; 1722 | peg$silentFails++; 1723 | if (input.charCodeAt(peg$currPos) === 61) { 1724 | s8 = peg$c65; 1725 | peg$currPos++; 1726 | } else { 1727 | s8 = peg$FAILED; 1728 | if (peg$silentFails === 0) { peg$fail(peg$c66); } 1729 | } 1730 | peg$silentFails--; 1731 | if (s8 === peg$FAILED) { 1732 | s7 = void 0; 1733 | } else { 1734 | peg$currPos = s7; 1735 | s7 = peg$FAILED; 1736 | } 1737 | if (s7 !== peg$FAILED) { 1738 | s6 = [s6, s7]; 1739 | s5 = s6; 1740 | } else { 1741 | peg$currPos = s5; 1742 | s5 = peg$FAILED; 1743 | } 1744 | } else { 1745 | peg$currPos = s5; 1746 | s5 = peg$FAILED; 1747 | } 1748 | peg$silentFails--; 1749 | if (s5 === peg$FAILED) { 1750 | s4 = void 0; 1751 | } else { 1752 | peg$currPos = s4; 1753 | s4 = peg$FAILED; 1754 | } 1755 | if (s4 !== peg$FAILED) { 1756 | peg$savedPos = s0; 1757 | s1 = peg$c67(s1); 1758 | s0 = s1; 1759 | } else { 1760 | peg$currPos = s0; 1761 | s0 = peg$FAILED; 1762 | } 1763 | } else { 1764 | peg$currPos = s0; 1765 | s0 = peg$FAILED; 1766 | } 1767 | } else { 1768 | peg$currPos = s0; 1769 | s0 = peg$FAILED; 1770 | } 1771 | } else { 1772 | peg$currPos = s0; 1773 | s0 = peg$FAILED; 1774 | } 1775 | peg$silentFails--; 1776 | if (s0 === peg$FAILED) { 1777 | s1 = peg$FAILED; 1778 | if (peg$silentFails === 0) { peg$fail(peg$c64); } 1779 | } 1780 | 1781 | return s0; 1782 | } 1783 | 1784 | function peg$parsevariable() { 1785 | var s0, s1; 1786 | 1787 | peg$silentFails++; 1788 | s0 = peg$currPos; 1789 | s1 = peg$parseidentifier(); 1790 | if (s1 !== peg$FAILED) { 1791 | peg$savedPos = s0; 1792 | s1 = peg$c69(s1); 1793 | } 1794 | s0 = s1; 1795 | peg$silentFails--; 1796 | if (s0 === peg$FAILED) { 1797 | s1 = peg$FAILED; 1798 | if (peg$silentFails === 0) { peg$fail(peg$c68); } 1799 | } 1800 | 1801 | return s0; 1802 | } 1803 | 1804 | function peg$parsenum() { 1805 | var s0, s1, s2, s3; 1806 | 1807 | peg$silentFails++; 1808 | s0 = peg$currPos; 1809 | s1 = peg$currPos; 1810 | if (input.charCodeAt(peg$currPos) === 45) { 1811 | s2 = peg$c71; 1812 | peg$currPos++; 1813 | } else { 1814 | s2 = peg$FAILED; 1815 | if (peg$silentFails === 0) { peg$fail(peg$c72); } 1816 | } 1817 | if (s2 === peg$FAILED) { 1818 | s2 = null; 1819 | } 1820 | if (s2 !== peg$FAILED) { 1821 | s3 = peg$parsefloat(); 1822 | if (s3 !== peg$FAILED) { 1823 | peg$savedPos = s1; 1824 | s2 = peg$c73(s2, s3); 1825 | s1 = s2; 1826 | } else { 1827 | peg$currPos = s1; 1828 | s1 = peg$FAILED; 1829 | } 1830 | } else { 1831 | peg$currPos = s1; 1832 | s1 = peg$FAILED; 1833 | } 1834 | if (s1 !== peg$FAILED) { 1835 | peg$savedPos = s0; 1836 | s1 = peg$c74(s1); 1837 | } 1838 | s0 = s1; 1839 | peg$silentFails--; 1840 | if (s0 === peg$FAILED) { 1841 | s1 = peg$FAILED; 1842 | if (peg$silentFails === 0) { peg$fail(peg$c70); } 1843 | } 1844 | 1845 | return s0; 1846 | } 1847 | 1848 | function peg$parseposintnum() { 1849 | var s0, s1; 1850 | 1851 | peg$silentFails++; 1852 | s0 = peg$currPos; 1853 | s1 = peg$parseinteger(); 1854 | if (s1 !== peg$FAILED) { 1855 | peg$savedPos = s0; 1856 | s1 = peg$c74(s1); 1857 | } 1858 | s0 = s1; 1859 | peg$silentFails--; 1860 | if (s0 === peg$FAILED) { 1861 | s1 = peg$FAILED; 1862 | if (peg$silentFails === 0) { peg$fail(peg$c75); } 1863 | } 1864 | 1865 | return s0; 1866 | } 1867 | 1868 | function peg$parseparentheses() { 1869 | var s0, s1, s2, s3, s4, s5; 1870 | 1871 | peg$silentFails++; 1872 | s0 = peg$currPos; 1873 | if (input.charCodeAt(peg$currPos) === 40) { 1874 | s1 = peg$c48; 1875 | peg$currPos++; 1876 | } else { 1877 | s1 = peg$FAILED; 1878 | if (peg$silentFails === 0) { peg$fail(peg$c49); } 1879 | } 1880 | if (s1 !== peg$FAILED) { 1881 | s2 = peg$parseOWS(); 1882 | if (s2 !== peg$FAILED) { 1883 | s3 = peg$parserestart(); 1884 | if (s3 !== peg$FAILED) { 1885 | s4 = peg$parseOWS(); 1886 | if (s4 !== peg$FAILED) { 1887 | if (input.charCodeAt(peg$currPos) === 41) { 1888 | s5 = peg$c50; 1889 | peg$currPos++; 1890 | } else { 1891 | s5 = peg$FAILED; 1892 | if (peg$silentFails === 0) { peg$fail(peg$c51); } 1893 | } 1894 | if (s5 !== peg$FAILED) { 1895 | peg$savedPos = s0; 1896 | s1 = peg$c77(s3); 1897 | s0 = s1; 1898 | } else { 1899 | peg$currPos = s0; 1900 | s0 = peg$FAILED; 1901 | } 1902 | } else { 1903 | peg$currPos = s0; 1904 | s0 = peg$FAILED; 1905 | } 1906 | } else { 1907 | peg$currPos = s0; 1908 | s0 = peg$FAILED; 1909 | } 1910 | } else { 1911 | peg$currPos = s0; 1912 | s0 = peg$FAILED; 1913 | } 1914 | } else { 1915 | peg$currPos = s0; 1916 | s0 = peg$FAILED; 1917 | } 1918 | peg$silentFails--; 1919 | if (s0 === peg$FAILED) { 1920 | s1 = peg$FAILED; 1921 | if (peg$silentFails === 0) { peg$fail(peg$c76); } 1922 | } 1923 | 1924 | return s0; 1925 | } 1926 | 1927 | function peg$parsefloat() { 1928 | var s0, s1, s2, s3, s4, s5, s6, s7; 1929 | 1930 | peg$silentFails++; 1931 | s0 = peg$currPos; 1932 | s1 = peg$currPos; 1933 | s2 = peg$currPos; 1934 | s3 = []; 1935 | if (peg$c79.test(input.charAt(peg$currPos))) { 1936 | s4 = input.charAt(peg$currPos); 1937 | peg$currPos++; 1938 | } else { 1939 | s4 = peg$FAILED; 1940 | if (peg$silentFails === 0) { peg$fail(peg$c80); } 1941 | } 1942 | if (s4 !== peg$FAILED) { 1943 | while (s4 !== peg$FAILED) { 1944 | s3.push(s4); 1945 | if (peg$c79.test(input.charAt(peg$currPos))) { 1946 | s4 = input.charAt(peg$currPos); 1947 | peg$currPos++; 1948 | } else { 1949 | s4 = peg$FAILED; 1950 | if (peg$silentFails === 0) { peg$fail(peg$c80); } 1951 | } 1952 | } 1953 | } else { 1954 | s3 = peg$FAILED; 1955 | } 1956 | if (s3 !== peg$FAILED) { 1957 | s4 = peg$currPos; 1958 | if (input.charCodeAt(peg$currPos) === 46) { 1959 | s5 = peg$c81; 1960 | peg$currPos++; 1961 | } else { 1962 | s5 = peg$FAILED; 1963 | if (peg$silentFails === 0) { peg$fail(peg$c82); } 1964 | } 1965 | if (s5 !== peg$FAILED) { 1966 | s6 = []; 1967 | if (peg$c79.test(input.charAt(peg$currPos))) { 1968 | s7 = input.charAt(peg$currPos); 1969 | peg$currPos++; 1970 | } else { 1971 | s7 = peg$FAILED; 1972 | if (peg$silentFails === 0) { peg$fail(peg$c80); } 1973 | } 1974 | while (s7 !== peg$FAILED) { 1975 | s6.push(s7); 1976 | if (peg$c79.test(input.charAt(peg$currPos))) { 1977 | s7 = input.charAt(peg$currPos); 1978 | peg$currPos++; 1979 | } else { 1980 | s7 = peg$FAILED; 1981 | if (peg$silentFails === 0) { peg$fail(peg$c80); } 1982 | } 1983 | } 1984 | if (s6 !== peg$FAILED) { 1985 | s5 = [s5, s6]; 1986 | s4 = s5; 1987 | } else { 1988 | peg$currPos = s4; 1989 | s4 = peg$FAILED; 1990 | } 1991 | } else { 1992 | peg$currPos = s4; 1993 | s4 = peg$FAILED; 1994 | } 1995 | if (s4 === peg$FAILED) { 1996 | s4 = null; 1997 | } 1998 | if (s4 !== peg$FAILED) { 1999 | s3 = [s3, s4]; 2000 | s2 = s3; 2001 | } else { 2002 | peg$currPos = s2; 2003 | s2 = peg$FAILED; 2004 | } 2005 | } else { 2006 | peg$currPos = s2; 2007 | s2 = peg$FAILED; 2008 | } 2009 | if (s2 === peg$FAILED) { 2010 | s2 = peg$currPos; 2011 | if (input.charCodeAt(peg$currPos) === 46) { 2012 | s3 = peg$c81; 2013 | peg$currPos++; 2014 | } else { 2015 | s3 = peg$FAILED; 2016 | if (peg$silentFails === 0) { peg$fail(peg$c82); } 2017 | } 2018 | if (s3 !== peg$FAILED) { 2019 | s4 = []; 2020 | if (peg$c79.test(input.charAt(peg$currPos))) { 2021 | s5 = input.charAt(peg$currPos); 2022 | peg$currPos++; 2023 | } else { 2024 | s5 = peg$FAILED; 2025 | if (peg$silentFails === 0) { peg$fail(peg$c80); } 2026 | } 2027 | if (s5 !== peg$FAILED) { 2028 | while (s5 !== peg$FAILED) { 2029 | s4.push(s5); 2030 | if (peg$c79.test(input.charAt(peg$currPos))) { 2031 | s5 = input.charAt(peg$currPos); 2032 | peg$currPos++; 2033 | } else { 2034 | s5 = peg$FAILED; 2035 | if (peg$silentFails === 0) { peg$fail(peg$c80); } 2036 | } 2037 | } 2038 | } else { 2039 | s4 = peg$FAILED; 2040 | } 2041 | if (s4 !== peg$FAILED) { 2042 | s3 = [s3, s4]; 2043 | s2 = s3; 2044 | } else { 2045 | peg$currPos = s2; 2046 | s2 = peg$FAILED; 2047 | } 2048 | } else { 2049 | peg$currPos = s2; 2050 | s2 = peg$FAILED; 2051 | } 2052 | } 2053 | if (s2 !== peg$FAILED) { 2054 | s1 = input.substring(s1, peg$currPos); 2055 | } else { 2056 | s1 = s2; 2057 | } 2058 | if (s1 !== peg$FAILED) { 2059 | peg$savedPos = s0; 2060 | s1 = peg$c83(s1); 2061 | } 2062 | s0 = s1; 2063 | peg$silentFails--; 2064 | if (s0 === peg$FAILED) { 2065 | s1 = peg$FAILED; 2066 | if (peg$silentFails === 0) { peg$fail(peg$c78); } 2067 | } 2068 | 2069 | return s0; 2070 | } 2071 | 2072 | function peg$parseinteger() { 2073 | var s0, s1, s2, s3; 2074 | 2075 | peg$silentFails++; 2076 | s0 = peg$currPos; 2077 | s1 = peg$currPos; 2078 | s2 = []; 2079 | if (peg$c79.test(input.charAt(peg$currPos))) { 2080 | s3 = input.charAt(peg$currPos); 2081 | peg$currPos++; 2082 | } else { 2083 | s3 = peg$FAILED; 2084 | if (peg$silentFails === 0) { peg$fail(peg$c80); } 2085 | } 2086 | if (s3 !== peg$FAILED) { 2087 | while (s3 !== peg$FAILED) { 2088 | s2.push(s3); 2089 | if (peg$c79.test(input.charAt(peg$currPos))) { 2090 | s3 = input.charAt(peg$currPos); 2091 | peg$currPos++; 2092 | } else { 2093 | s3 = peg$FAILED; 2094 | if (peg$silentFails === 0) { peg$fail(peg$c80); } 2095 | } 2096 | } 2097 | } else { 2098 | s2 = peg$FAILED; 2099 | } 2100 | if (s2 !== peg$FAILED) { 2101 | s1 = input.substring(s1, peg$currPos); 2102 | } else { 2103 | s1 = s2; 2104 | } 2105 | if (s1 !== peg$FAILED) { 2106 | peg$savedPos = s0; 2107 | s1 = peg$c85(s1); 2108 | } 2109 | s0 = s1; 2110 | peg$silentFails--; 2111 | if (s0 === peg$FAILED) { 2112 | s1 = peg$FAILED; 2113 | if (peg$silentFails === 0) { peg$fail(peg$c84); } 2114 | } 2115 | 2116 | return s0; 2117 | } 2118 | 2119 | function peg$parseidentifier() { 2120 | var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9; 2121 | 2122 | peg$silentFails++; 2123 | s0 = peg$currPos; 2124 | s1 = peg$currPos; 2125 | s2 = peg$currPos; 2126 | if (peg$c87.test(input.charAt(peg$currPos))) { 2127 | s3 = input.charAt(peg$currPos); 2128 | peg$currPos++; 2129 | } else { 2130 | s3 = peg$FAILED; 2131 | if (peg$silentFails === 0) { peg$fail(peg$c88); } 2132 | } 2133 | if (s3 !== peg$FAILED) { 2134 | s4 = []; 2135 | if (peg$c89.test(input.charAt(peg$currPos))) { 2136 | s5 = input.charAt(peg$currPos); 2137 | peg$currPos++; 2138 | } else { 2139 | s5 = peg$FAILED; 2140 | if (peg$silentFails === 0) { peg$fail(peg$c90); } 2141 | } 2142 | if (s5 !== peg$FAILED) { 2143 | while (s5 !== peg$FAILED) { 2144 | s4.push(s5); 2145 | if (peg$c89.test(input.charAt(peg$currPos))) { 2146 | s5 = input.charAt(peg$currPos); 2147 | peg$currPos++; 2148 | } else { 2149 | s5 = peg$FAILED; 2150 | if (peg$silentFails === 0) { peg$fail(peg$c90); } 2151 | } 2152 | } 2153 | } else { 2154 | s4 = peg$FAILED; 2155 | } 2156 | if (s4 !== peg$FAILED) { 2157 | s3 = [s3, s4]; 2158 | s2 = s3; 2159 | } else { 2160 | peg$currPos = s2; 2161 | s2 = peg$FAILED; 2162 | } 2163 | } else { 2164 | peg$currPos = s2; 2165 | s2 = peg$FAILED; 2166 | } 2167 | if (s2 !== peg$FAILED) { 2168 | s1 = input.substring(s1, peg$currPos); 2169 | } else { 2170 | s1 = s2; 2171 | } 2172 | if (s1 !== peg$FAILED) { 2173 | peg$savedPos = s0; 2174 | s1 = peg$c91(s1); 2175 | } 2176 | s0 = s1; 2177 | if (s0 === peg$FAILED) { 2178 | s0 = peg$currPos; 2179 | if (input.charCodeAt(peg$currPos) === 39) { 2180 | s1 = peg$c92; 2181 | peg$currPos++; 2182 | } else { 2183 | s1 = peg$FAILED; 2184 | if (peg$silentFails === 0) { peg$fail(peg$c93); } 2185 | } 2186 | if (s1 !== peg$FAILED) { 2187 | s2 = peg$currPos; 2188 | s3 = peg$currPos; 2189 | s4 = []; 2190 | if (peg$c94.test(input.charAt(peg$currPos))) { 2191 | s5 = input.charAt(peg$currPos); 2192 | peg$currPos++; 2193 | } else { 2194 | s5 = peg$FAILED; 2195 | if (peg$silentFails === 0) { peg$fail(peg$c95); } 2196 | } 2197 | while (s5 !== peg$FAILED) { 2198 | s4.push(s5); 2199 | if (peg$c94.test(input.charAt(peg$currPos))) { 2200 | s5 = input.charAt(peg$currPos); 2201 | peg$currPos++; 2202 | } else { 2203 | s5 = peg$FAILED; 2204 | if (peg$silentFails === 0) { peg$fail(peg$c95); } 2205 | } 2206 | } 2207 | if (s4 !== peg$FAILED) { 2208 | s5 = []; 2209 | s6 = peg$currPos; 2210 | if (input.substr(peg$currPos, 2) === peg$c96) { 2211 | s7 = peg$c96; 2212 | peg$currPos += 2; 2213 | } else { 2214 | s7 = peg$FAILED; 2215 | if (peg$silentFails === 0) { peg$fail(peg$c97); } 2216 | } 2217 | if (s7 !== peg$FAILED) { 2218 | s8 = []; 2219 | if (peg$c94.test(input.charAt(peg$currPos))) { 2220 | s9 = input.charAt(peg$currPos); 2221 | peg$currPos++; 2222 | } else { 2223 | s9 = peg$FAILED; 2224 | if (peg$silentFails === 0) { peg$fail(peg$c95); } 2225 | } 2226 | if (s9 !== peg$FAILED) { 2227 | while (s9 !== peg$FAILED) { 2228 | s8.push(s9); 2229 | if (peg$c94.test(input.charAt(peg$currPos))) { 2230 | s9 = input.charAt(peg$currPos); 2231 | peg$currPos++; 2232 | } else { 2233 | s9 = peg$FAILED; 2234 | if (peg$silentFails === 0) { peg$fail(peg$c95); } 2235 | } 2236 | } 2237 | } else { 2238 | s8 = peg$FAILED; 2239 | } 2240 | if (s8 !== peg$FAILED) { 2241 | s7 = [s7, s8]; 2242 | s6 = s7; 2243 | } else { 2244 | peg$currPos = s6; 2245 | s6 = peg$FAILED; 2246 | } 2247 | } else { 2248 | peg$currPos = s6; 2249 | s6 = peg$FAILED; 2250 | } 2251 | while (s6 !== peg$FAILED) { 2252 | s5.push(s6); 2253 | s6 = peg$currPos; 2254 | if (input.substr(peg$currPos, 2) === peg$c96) { 2255 | s7 = peg$c96; 2256 | peg$currPos += 2; 2257 | } else { 2258 | s7 = peg$FAILED; 2259 | if (peg$silentFails === 0) { peg$fail(peg$c97); } 2260 | } 2261 | if (s7 !== peg$FAILED) { 2262 | s8 = []; 2263 | if (peg$c94.test(input.charAt(peg$currPos))) { 2264 | s9 = input.charAt(peg$currPos); 2265 | peg$currPos++; 2266 | } else { 2267 | s9 = peg$FAILED; 2268 | if (peg$silentFails === 0) { peg$fail(peg$c95); } 2269 | } 2270 | if (s9 !== peg$FAILED) { 2271 | while (s9 !== peg$FAILED) { 2272 | s8.push(s9); 2273 | if (peg$c94.test(input.charAt(peg$currPos))) { 2274 | s9 = input.charAt(peg$currPos); 2275 | peg$currPos++; 2276 | } else { 2277 | s9 = peg$FAILED; 2278 | if (peg$silentFails === 0) { peg$fail(peg$c95); } 2279 | } 2280 | } 2281 | } else { 2282 | s8 = peg$FAILED; 2283 | } 2284 | if (s8 !== peg$FAILED) { 2285 | s7 = [s7, s8]; 2286 | s6 = s7; 2287 | } else { 2288 | peg$currPos = s6; 2289 | s6 = peg$FAILED; 2290 | } 2291 | } else { 2292 | peg$currPos = s6; 2293 | s6 = peg$FAILED; 2294 | } 2295 | } 2296 | if (s5 !== peg$FAILED) { 2297 | s4 = [s4, s5]; 2298 | s3 = s4; 2299 | } else { 2300 | peg$currPos = s3; 2301 | s3 = peg$FAILED; 2302 | } 2303 | } else { 2304 | peg$currPos = s3; 2305 | s3 = peg$FAILED; 2306 | } 2307 | if (s3 !== peg$FAILED) { 2308 | s2 = input.substring(s2, peg$currPos); 2309 | } else { 2310 | s2 = s3; 2311 | } 2312 | if (s2 !== peg$FAILED) { 2313 | if (input.charCodeAt(peg$currPos) === 39) { 2314 | s3 = peg$c92; 2315 | peg$currPos++; 2316 | } else { 2317 | s3 = peg$FAILED; 2318 | if (peg$silentFails === 0) { peg$fail(peg$c93); } 2319 | } 2320 | if (s3 !== peg$FAILED) { 2321 | peg$savedPos = s0; 2322 | s1 = peg$c91(s2); 2323 | s0 = s1; 2324 | } else { 2325 | peg$currPos = s0; 2326 | s0 = peg$FAILED; 2327 | } 2328 | } else { 2329 | peg$currPos = s0; 2330 | s0 = peg$FAILED; 2331 | } 2332 | } else { 2333 | peg$currPos = s0; 2334 | s0 = peg$FAILED; 2335 | } 2336 | if (s0 === peg$FAILED) { 2337 | s0 = peg$currPos; 2338 | if (input.charCodeAt(peg$currPos) === 91) { 2339 | s1 = peg$c98; 2340 | peg$currPos++; 2341 | } else { 2342 | s1 = peg$FAILED; 2343 | if (peg$silentFails === 0) { peg$fail(peg$c99); } 2344 | } 2345 | if (s1 !== peg$FAILED) { 2346 | s2 = peg$currPos; 2347 | s3 = peg$currPos; 2348 | s4 = []; 2349 | if (peg$c100.test(input.charAt(peg$currPos))) { 2350 | s5 = input.charAt(peg$currPos); 2351 | peg$currPos++; 2352 | } else { 2353 | s5 = peg$FAILED; 2354 | if (peg$silentFails === 0) { peg$fail(peg$c101); } 2355 | } 2356 | while (s5 !== peg$FAILED) { 2357 | s4.push(s5); 2358 | if (peg$c100.test(input.charAt(peg$currPos))) { 2359 | s5 = input.charAt(peg$currPos); 2360 | peg$currPos++; 2361 | } else { 2362 | s5 = peg$FAILED; 2363 | if (peg$silentFails === 0) { peg$fail(peg$c101); } 2364 | } 2365 | } 2366 | if (s4 !== peg$FAILED) { 2367 | s5 = []; 2368 | s6 = peg$currPos; 2369 | if (input.substr(peg$currPos, 2) === peg$c102) { 2370 | s7 = peg$c102; 2371 | peg$currPos += 2; 2372 | } else { 2373 | s7 = peg$FAILED; 2374 | if (peg$silentFails === 0) { peg$fail(peg$c103); } 2375 | } 2376 | if (s7 !== peg$FAILED) { 2377 | s8 = []; 2378 | if (peg$c100.test(input.charAt(peg$currPos))) { 2379 | s9 = input.charAt(peg$currPos); 2380 | peg$currPos++; 2381 | } else { 2382 | s9 = peg$FAILED; 2383 | if (peg$silentFails === 0) { peg$fail(peg$c101); } 2384 | } 2385 | if (s9 !== peg$FAILED) { 2386 | while (s9 !== peg$FAILED) { 2387 | s8.push(s9); 2388 | if (peg$c100.test(input.charAt(peg$currPos))) { 2389 | s9 = input.charAt(peg$currPos); 2390 | peg$currPos++; 2391 | } else { 2392 | s9 = peg$FAILED; 2393 | if (peg$silentFails === 0) { peg$fail(peg$c101); } 2394 | } 2395 | } 2396 | } else { 2397 | s8 = peg$FAILED; 2398 | } 2399 | if (s8 !== peg$FAILED) { 2400 | s7 = [s7, s8]; 2401 | s6 = s7; 2402 | } else { 2403 | peg$currPos = s6; 2404 | s6 = peg$FAILED; 2405 | } 2406 | } else { 2407 | peg$currPos = s6; 2408 | s6 = peg$FAILED; 2409 | } 2410 | while (s6 !== peg$FAILED) { 2411 | s5.push(s6); 2412 | s6 = peg$currPos; 2413 | if (input.substr(peg$currPos, 2) === peg$c102) { 2414 | s7 = peg$c102; 2415 | peg$currPos += 2; 2416 | } else { 2417 | s7 = peg$FAILED; 2418 | if (peg$silentFails === 0) { peg$fail(peg$c103); } 2419 | } 2420 | if (s7 !== peg$FAILED) { 2421 | s8 = []; 2422 | if (peg$c100.test(input.charAt(peg$currPos))) { 2423 | s9 = input.charAt(peg$currPos); 2424 | peg$currPos++; 2425 | } else { 2426 | s9 = peg$FAILED; 2427 | if (peg$silentFails === 0) { peg$fail(peg$c101); } 2428 | } 2429 | if (s9 !== peg$FAILED) { 2430 | while (s9 !== peg$FAILED) { 2431 | s8.push(s9); 2432 | if (peg$c100.test(input.charAt(peg$currPos))) { 2433 | s9 = input.charAt(peg$currPos); 2434 | peg$currPos++; 2435 | } else { 2436 | s9 = peg$FAILED; 2437 | if (peg$silentFails === 0) { peg$fail(peg$c101); } 2438 | } 2439 | } 2440 | } else { 2441 | s8 = peg$FAILED; 2442 | } 2443 | if (s8 !== peg$FAILED) { 2444 | s7 = [s7, s8]; 2445 | s6 = s7; 2446 | } else { 2447 | peg$currPos = s6; 2448 | s6 = peg$FAILED; 2449 | } 2450 | } else { 2451 | peg$currPos = s6; 2452 | s6 = peg$FAILED; 2453 | } 2454 | } 2455 | if (s5 !== peg$FAILED) { 2456 | s4 = [s4, s5]; 2457 | s3 = s4; 2458 | } else { 2459 | peg$currPos = s3; 2460 | s3 = peg$FAILED; 2461 | } 2462 | } else { 2463 | peg$currPos = s3; 2464 | s3 = peg$FAILED; 2465 | } 2466 | if (s3 !== peg$FAILED) { 2467 | s2 = input.substring(s2, peg$currPos); 2468 | } else { 2469 | s2 = s3; 2470 | } 2471 | if (s2 !== peg$FAILED) { 2472 | if (input.charCodeAt(peg$currPos) === 93) { 2473 | s3 = peg$c104; 2474 | peg$currPos++; 2475 | } else { 2476 | s3 = peg$FAILED; 2477 | if (peg$silentFails === 0) { peg$fail(peg$c105); } 2478 | } 2479 | if (s3 !== peg$FAILED) { 2480 | peg$savedPos = s0; 2481 | s1 = peg$c91(s2); 2482 | s0 = s1; 2483 | } else { 2484 | peg$currPos = s0; 2485 | s0 = peg$FAILED; 2486 | } 2487 | } else { 2488 | peg$currPos = s0; 2489 | s0 = peg$FAILED; 2490 | } 2491 | } else { 2492 | peg$currPos = s0; 2493 | s0 = peg$FAILED; 2494 | } 2495 | } 2496 | } 2497 | peg$silentFails--; 2498 | if (s0 === peg$FAILED) { 2499 | s1 = peg$FAILED; 2500 | if (peg$silentFails === 0) { peg$fail(peg$c86); } 2501 | } 2502 | 2503 | return s0; 2504 | } 2505 | 2506 | function peg$parseOWS() { 2507 | var s0, s1; 2508 | 2509 | peg$silentFails++; 2510 | s0 = []; 2511 | if (peg$c107.test(input.charAt(peg$currPos))) { 2512 | s1 = input.charAt(peg$currPos); 2513 | peg$currPos++; 2514 | } else { 2515 | s1 = peg$FAILED; 2516 | if (peg$silentFails === 0) { peg$fail(peg$c108); } 2517 | } 2518 | if (s1 === peg$FAILED) { 2519 | s1 = peg$parsecomment(); 2520 | } 2521 | while (s1 !== peg$FAILED) { 2522 | s0.push(s1); 2523 | if (peg$c107.test(input.charAt(peg$currPos))) { 2524 | s1 = input.charAt(peg$currPos); 2525 | peg$currPos++; 2526 | } else { 2527 | s1 = peg$FAILED; 2528 | if (peg$silentFails === 0) { peg$fail(peg$c108); } 2529 | } 2530 | if (s1 === peg$FAILED) { 2531 | s1 = peg$parsecomment(); 2532 | } 2533 | } 2534 | peg$silentFails--; 2535 | if (s0 === peg$FAILED) { 2536 | s1 = peg$FAILED; 2537 | if (peg$silentFails === 0) { peg$fail(peg$c106); } 2538 | } 2539 | 2540 | return s0; 2541 | } 2542 | 2543 | function peg$parsecomment() { 2544 | var s0, s1, s2, s3, s4, s5; 2545 | 2546 | peg$silentFails++; 2547 | s0 = peg$currPos; 2548 | if (input.substr(peg$currPos, 2) === peg$c110) { 2549 | s1 = peg$c110; 2550 | peg$currPos += 2; 2551 | } else { 2552 | s1 = peg$FAILED; 2553 | if (peg$silentFails === 0) { peg$fail(peg$c111); } 2554 | } 2555 | if (s1 !== peg$FAILED) { 2556 | s2 = []; 2557 | s3 = peg$currPos; 2558 | s4 = peg$currPos; 2559 | peg$silentFails++; 2560 | if (input.substr(peg$currPos, 2) === peg$c112) { 2561 | s5 = peg$c112; 2562 | peg$currPos += 2; 2563 | } else { 2564 | s5 = peg$FAILED; 2565 | if (peg$silentFails === 0) { peg$fail(peg$c113); } 2566 | } 2567 | peg$silentFails--; 2568 | if (s5 === peg$FAILED) { 2569 | s4 = void 0; 2570 | } else { 2571 | peg$currPos = s4; 2572 | s4 = peg$FAILED; 2573 | } 2574 | if (s4 !== peg$FAILED) { 2575 | if (input.length > peg$currPos) { 2576 | s5 = input.charAt(peg$currPos); 2577 | peg$currPos++; 2578 | } else { 2579 | s5 = peg$FAILED; 2580 | if (peg$silentFails === 0) { peg$fail(peg$c114); } 2581 | } 2582 | if (s5 !== peg$FAILED) { 2583 | s4 = [s4, s5]; 2584 | s3 = s4; 2585 | } else { 2586 | peg$currPos = s3; 2587 | s3 = peg$FAILED; 2588 | } 2589 | } else { 2590 | peg$currPos = s3; 2591 | s3 = peg$FAILED; 2592 | } 2593 | while (s3 !== peg$FAILED) { 2594 | s2.push(s3); 2595 | s3 = peg$currPos; 2596 | s4 = peg$currPos; 2597 | peg$silentFails++; 2598 | if (input.substr(peg$currPos, 2) === peg$c112) { 2599 | s5 = peg$c112; 2600 | peg$currPos += 2; 2601 | } else { 2602 | s5 = peg$FAILED; 2603 | if (peg$silentFails === 0) { peg$fail(peg$c113); } 2604 | } 2605 | peg$silentFails--; 2606 | if (s5 === peg$FAILED) { 2607 | s4 = void 0; 2608 | } else { 2609 | peg$currPos = s4; 2610 | s4 = peg$FAILED; 2611 | } 2612 | if (s4 !== peg$FAILED) { 2613 | if (input.length > peg$currPos) { 2614 | s5 = input.charAt(peg$currPos); 2615 | peg$currPos++; 2616 | } else { 2617 | s5 = peg$FAILED; 2618 | if (peg$silentFails === 0) { peg$fail(peg$c114); } 2619 | } 2620 | if (s5 !== peg$FAILED) { 2621 | s4 = [s4, s5]; 2622 | s3 = s4; 2623 | } else { 2624 | peg$currPos = s3; 2625 | s3 = peg$FAILED; 2626 | } 2627 | } else { 2628 | peg$currPos = s3; 2629 | s3 = peg$FAILED; 2630 | } 2631 | } 2632 | if (s2 !== peg$FAILED) { 2633 | if (input.substr(peg$currPos, 2) === peg$c112) { 2634 | s3 = peg$c112; 2635 | peg$currPos += 2; 2636 | } else { 2637 | s3 = peg$FAILED; 2638 | if (peg$silentFails === 0) { peg$fail(peg$c113); } 2639 | } 2640 | if (s3 !== peg$FAILED) { 2641 | s1 = [s1, s2, s3]; 2642 | s0 = s1; 2643 | } else { 2644 | peg$currPos = s0; 2645 | s0 = peg$FAILED; 2646 | } 2647 | } else { 2648 | peg$currPos = s0; 2649 | s0 = peg$FAILED; 2650 | } 2651 | } else { 2652 | peg$currPos = s0; 2653 | s0 = peg$FAILED; 2654 | } 2655 | peg$silentFails--; 2656 | if (s0 === peg$FAILED) { 2657 | s1 = peg$FAILED; 2658 | if (peg$silentFails === 0) { peg$fail(peg$c109); } 2659 | } 2660 | 2661 | return s0; 2662 | } 2663 | 2664 | 2665 | /* Import expression types */ 2666 | const Conditional = require('./Conditional'); 2667 | const Operation = require('./Operation'); 2668 | const Not = require('./Not'); 2669 | const Repeat = require('./Repeat'); 2670 | const Func = require('./Function'); 2671 | const Roll = require('./Roll'); 2672 | const Factorial = require('./Factorial'); 2673 | const Variable = require('./Variable'); 2674 | const Num = require('./Number'); 2675 | const Parentheses = require('./Parentheses'); 2676 | 2677 | /* Define side-associative operation helper functions */ 2678 | function leftAssocOperation(left, rest) { 2679 | return rest.reduce((left, current) => { 2680 | return new Operation(current.oper, left, current.right); 2681 | }, left); 2682 | } 2683 | function rightAssocOperation(rest, right) { 2684 | return rest.reduceRight((right, current) => { 2685 | return new Operation(current.oper, current.left, right); 2686 | }, right); 2687 | } 2688 | 2689 | 2690 | peg$result = peg$startRuleFunction(); 2691 | 2692 | if (peg$result !== peg$FAILED && peg$currPos === input.length) { 2693 | return peg$result; 2694 | } else { 2695 | if (peg$result !== peg$FAILED && peg$currPos < input.length) { 2696 | peg$fail(peg$endExpectation()); 2697 | } 2698 | 2699 | throw peg$buildStructuredError( 2700 | peg$maxFailExpected, 2701 | peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null, 2702 | peg$maxFailPos < input.length 2703 | ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) 2704 | : peg$computeLocation(peg$maxFailPos, peg$maxFailPos) 2705 | ); 2706 | } 2707 | } 2708 | 2709 | module.exports = { 2710 | SyntaxError: peg$SyntaxError, 2711 | parse: peg$parse 2712 | }; 2713 | -------------------------------------------------------------------------------- /lib/rolldie.js: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------------------------------------------------- 2 | // Rolls a single die of the given number of sides. 3 | //---------------------------------------------------------------------------------------------------------------------- 4 | 5 | // While not always true, modern `Math.random()` implementations provide a uniform distribution. Perfectly usable for 6 | // dice rolling. You can't blame this implementation for your crappy rolls. 7 | function random() 8 | { 9 | return Math.random(); 10 | } // end random 11 | 12 | //---------------------------------------------------------------------------------------------------------------------- 13 | 14 | module.exports = { 15 | rollDie(sides) 16 | { 17 | const rollSides = Math.floor(Math.abs(sides)); 18 | return (Math.floor(random() * rollSides) + (rollSides >= 1 ? 1 : 0)) * (sides < 0 ? -1 : 1); 19 | } // end rollDice 20 | }; // end exports 21 | 22 | //---------------------------------------------------------------------------------------------------------------------- 23 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------------------------------------------------- 2 | // Utility functions 3 | //---------------------------------------------------------------------------------------------------------------------- 4 | 5 | module.exports = { 6 | get(obj, path, def) 7 | { 8 | if(Array.isArray(path)) 9 | { 10 | path = path.join('.'); 11 | } // end if 12 | 13 | const fullPath = path 14 | .replace(/\[/g, '.') 15 | .replace(/]/g, '') 16 | .split('.') 17 | .filter(Boolean); 18 | 19 | return fullPath.every((step) => !(step && (obj = obj[step]) === undefined)) ? obj : def; 20 | }, 21 | range(start, end, step = 1) 22 | { 23 | if(end === undefined) 24 | { 25 | if(start < 0) 26 | { 27 | step = -1; 28 | } // end if 29 | 30 | end = start; 31 | start = 0; 32 | } // end if 33 | 34 | const length = Math.floor((end - start) / step); 35 | return Array.from({ length }, (_, i) => (i * step) + start); 36 | } 37 | }; // end exports 38 | 39 | //---------------------------------------------------------------------------------------------------------------------- 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rpgdicejs", 3 | "version": "2.0.2", 4 | "description": "A generic RPG dice roller syntax and library.", 5 | "main": "index.js", 6 | "types": "types/index.d.ts", 7 | "scripts": { 8 | "peg": "pegjs --format commonjs -o lib/parser.js grammar/dice.pegjs", 9 | "lint:fix": "eslint --fix index.js lib/ tests/", 10 | "test": "mocha ./tests/**/*.spec.js --reporter spec --recursive" 11 | }, 12 | "keywords": [ 13 | "dice", 14 | "rpg", 15 | "roller" 16 | ], 17 | "author": "Christopher S. Case ", 18 | "license": "MIT", 19 | "engineStrict": true, 20 | "engines": { 21 | "node": ">= 10.0.0" 22 | }, 23 | "devDependencies": { 24 | "chai": "^4.2.0", 25 | "chai-spies": "^1.0.0", 26 | "eslint": "^8.10.0", 27 | "husky": "^7.0.4", 28 | "lint-staged": "^12.3.4", 29 | "mocha": "^9.2.1", 30 | "pegjs": "^0.10.0" 31 | }, 32 | "lint-staged": { 33 | "*.{ts,js}": "npm run lint:fix" 34 | }, 35 | "dependencies": { 36 | "proxyquire": "^2.1.3" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Conditional.spec.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------------------------------------------------- 2 | // Unit Tests for the Conditional class. 3 | // --------------------------------------------------------------------------------------------------------------------- 4 | 5 | const { expect } = require('chai'); 6 | 7 | const parser = require('../lib/parser'); 8 | const Num = require('../lib/Number'); 9 | const Operation = require('../lib/Operation'); 10 | const Conditional = require('../lib/Conditional'); 11 | 12 | // --------------------------------------------------------------------------------------------------------------------- 13 | 14 | describe('Conditional Class', () => 15 | { 16 | let conditional; 17 | beforeEach(() => 18 | { 19 | conditional = new Conditional(new Operation('>=', new Num(6), new Num(3)), new Num(3), new Num(6)); 20 | }); 21 | 22 | it('can be converted to json', () => 23 | { 24 | expect(JSON.stringify(conditional)).to.equal('{"type":"conditional","condition":{"type":"greaterThanOrEqual","left":{"type":"number","value":6},"right":{"type":"number","value":3}},"thenExpr":{"type":"number","value":3},"elseExpr":{"type":"number","value":6}}'); 25 | }); 26 | 27 | it('can be converted to a parsable string', () => 28 | { 29 | expect(conditional.toString()).to.equal('6 >= 3 ? 3 : 6'); 30 | expect(parser.parse(conditional.toString())).to.deep.equal(conditional); 31 | }); 32 | 33 | describe('#eval()', () => 34 | { 35 | it('returns itself with the value populated', () => 36 | { 37 | const results = conditional.eval(); 38 | expect(results.value).to.exist; 39 | expect(results.value).to.equal(3); 40 | }); 41 | 42 | it('stores the evaluation of `condition`, `thenExpr`, and `elseExpr` properties', () => 43 | { 44 | const results = conditional.eval(); 45 | 46 | expect(results.condition.value).to.exist; 47 | expect(results.condition.type).to.equal('greaterThanOrEqual'); 48 | 49 | expect(results.thenExpr.value).to.exist; 50 | expect(results.thenExpr.type).to.equal('number'); 51 | 52 | expect(results.elseExpr.value).to.exist; 53 | expect(results.elseExpr.type).to.equal('number'); 54 | }); 55 | }); 56 | }); 57 | 58 | // --------------------------------------------------------------------------------------------------------------------- 59 | -------------------------------------------------------------------------------- /tests/Factorial.spec.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------------------------------------------------- 2 | // Unit Tests for the Factorial class. 3 | // --------------------------------------------------------------------------------------------------------------------- 4 | 5 | const { expect } = require('chai'); 6 | 7 | const parser = require('../lib/parser'); 8 | const Factorial = require('../lib/Factorial'); 9 | const Num = require('../lib/Number'); 10 | 11 | // --------------------------------------------------------------------------------------------------------------------- 12 | 13 | describe('Factorial Class', () => 14 | { 15 | let factorial; 16 | beforeEach(() => 17 | { 18 | factorial = new Factorial(new Num(3)); 19 | }); 20 | 21 | it('can be converted to json', () => 22 | { 23 | expect(JSON.stringify(factorial)).to.equal('{"type":"factorial","content":{"type":"number","value":3}}'); 24 | }); 25 | 26 | it('can be converted to a parsable string', () => 27 | { 28 | expect(factorial.toString()).to.equal('3!'); 29 | expect(parser.parse(factorial.toString())).to.deep.equal(factorial); 30 | }); 31 | 32 | describe('#eval()', () => 33 | { 34 | it('returns itself with the value populated', () => 35 | { 36 | const factorial = new Factorial(new Num(3)); 37 | const results = factorial.eval(); 38 | 39 | expect(results.value).to.exist; 40 | expect(results.value).to.equal(6); 41 | }); 42 | }); 43 | }); 44 | 45 | // --------------------------------------------------------------------------------------------------------------------- 46 | -------------------------------------------------------------------------------- /tests/Function.spec.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------------------------------------------------- 2 | // Unit Tests for the Function class. 3 | // --------------------------------------------------------------------------------------------------------------------- 4 | 5 | const { expect } = require('chai'); 6 | 7 | const parser = require('../lib/parser'); 8 | const Func = require('../lib/Function'); 9 | const Roll = require('../lib/Roll'); 10 | const Num = require('../lib/Number'); 11 | 12 | // --------------------------------------------------------------------------------------------------------------------- 13 | 14 | describe('Function Class', () => 15 | { 16 | let func; let escapedFunc; 17 | beforeEach(() => 18 | { 19 | func = new Func('foo', [ new Roll(new Num(3), new Num(6)) ]); 20 | escapedFunc = new Func('Some Function', [ new Roll(new Num(3), new Num(6)) ]); 21 | }); 22 | 23 | it('can be converted to json', () => 24 | { 25 | expect(JSON.stringify(func)).to.equal('{"type":"function","name":"foo","args":[{"type":"roll","count":{"type":"number","value":3},"sides":{"type":"number","value":6},"results":[]}],"results":[]}'); 26 | }); 27 | 28 | it('can be converted to a parsable string', () => 29 | { 30 | expect(func.toString()).to.equal('foo(3d6)'); 31 | expect(parser.parse(func.toString())).to.deep.equal(func); 32 | 33 | expect(escapedFunc.toString()).to.equal("'Some Function'(3d6)"); 34 | expect(parser.parse(escapedFunc.toString())).to.deep.equal(escapedFunc); 35 | }); 36 | 37 | describe('#eval()', () => 38 | { 39 | it('returns itself with the value populated', () => 40 | { 41 | const results = func.eval({ 42 | foo(expr, scope) 43 | { 44 | this.expr = expr.eval(scope); 45 | return this.expr.value + 1; 46 | } 47 | }); 48 | 49 | expect(results.value).to.exist; 50 | expect(results.value).to.equal(results.expr.value + 1); 51 | }); 52 | 53 | it('throws an error if the function is not found on the scope, or is not a function', () => 54 | { 55 | expect(func.eval.bind(func)).to.throw("'foo' is not a function on the provided scope."); 56 | expect(() => { func.eval({ foo: 'bleh' }); }).to.throw("'foo' is not a function on the provided scope."); 57 | }); 58 | }); 59 | }); 60 | 61 | // --------------------------------------------------------------------------------------------------------------------- 62 | 63 | -------------------------------------------------------------------------------- /tests/Not.spec.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------------------------------------------------- 2 | // Unit Tests for the Not class. 3 | // --------------------------------------------------------------------------------------------------------------------- 4 | 5 | const { expect } = require('chai'); 6 | 7 | const parser = require('../lib/parser'); 8 | const Not = require('../lib/Not'); 9 | const Num = require('../lib/Number'); 10 | 11 | // --------------------------------------------------------------------------------------------------------------------- 12 | 13 | describe('Not Class', () => 14 | { 15 | let not; 16 | beforeEach(() => 17 | { 18 | not = new Not(new Num(5)); 19 | }); 20 | 21 | it('can be converted to json', () => 22 | { 23 | expect(JSON.stringify(not)).to.equal('{"type":"not","content":{"type":"number","value":5}}'); 24 | }); 25 | 26 | it('can be converted to a parsable string', () => 27 | { 28 | expect(not.toString()).to.equal('!5'); 29 | expect(parser.parse(not.toString())).to.deep.equal(not); 30 | }); 31 | 32 | describe('#eval()', () => 33 | { 34 | it('returns itself with the value populated', () => 35 | { 36 | not = new Not(new Num(5)); 37 | const results = not.eval(); 38 | 39 | expect(results.value).to.exist; 40 | expect(results.value).to.equal(0); 41 | }); 42 | 43 | it('has a value of 1 when content value is 0', () => 44 | { 45 | not = new Not(new Num(0)); 46 | const results = not.eval(); 47 | 48 | expect(results.value).to.exist; 49 | expect(results.value).to.equal(1); 50 | }); 51 | }); 52 | }); 53 | 54 | // --------------------------------------------------------------------------------------------------------------------- 55 | -------------------------------------------------------------------------------- /tests/Number.spec.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------------------------------------------------- 2 | // Unit Tests for the Number class. 3 | // --------------------------------------------------------------------------------------------------------------------- 4 | 5 | const { expect } = require('chai'); 6 | 7 | const parser = require('../lib/parser'); 8 | const Num = require('../lib/Number'); 9 | 10 | // --------------------------------------------------------------------------------------------------------------------- 11 | 12 | describe('Number Class', () => 13 | { 14 | let number; 15 | beforeEach(() => 16 | { 17 | number = new Num(3); 18 | }); 19 | 20 | it('can be converted to json', () => 21 | { 22 | expect(JSON.stringify(number)).to.equal('{"type":"number","value":3}'); 23 | }); 24 | 25 | it('can be converted to a parsable string', () => 26 | { 27 | expect(number.toString()).to.equal('3'); 28 | expect(parser.parse(number.toString())).to.deep.equal(number); 29 | }); 30 | 31 | describe('#eval()', () => 32 | { 33 | it('returns itself with the value populated', () => 34 | { 35 | const results = number.eval(); 36 | 37 | // Ensure we populated value correctly 38 | expect(results.value).to.equal(3); 39 | }); 40 | }); 41 | }); 42 | 43 | // --------------------------------------------------------------------------------------------------------------------- 44 | -------------------------------------------------------------------------------- /tests/Operation.spec.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------------------------------------------------- 2 | // Unit Tests for the Operation class. 3 | // --------------------------------------------------------------------------------------------------------------------- 4 | 5 | const { expect } = require('chai'); 6 | 7 | const parser = require('../lib/parser'); 8 | const Num = require('../lib/Number'); 9 | const Operation = require('../lib/Operation'); 10 | 11 | // --------------------------------------------------------------------------------------------------------------------- 12 | 13 | describe('Operation Class', () => 14 | { 15 | let op; 16 | beforeEach(() => 17 | { 18 | op = new Operation('add', new Num(3), new Num(6)); 19 | }); 20 | 21 | it('can be converted to json', () => 22 | { 23 | expect(JSON.stringify(op)).to.equal('{"type":"add","left":{"type":"number","value":3},"right":{"type":"number","value":6}}'); 24 | }); 25 | 26 | it('can be converted to a parsable string', () => 27 | { 28 | expect(op.toString()).to.equal('3 + 6'); 29 | expect(parser.parse(op.toString())).to.deep.equal(op); 30 | }); 31 | 32 | describe('#eval()', () => 33 | { 34 | it('returns itself with the value populated', () => 35 | { 36 | const results = op.eval(); 37 | expect(results.value).to.exist; 38 | expect(results.value).to.equal(9); 39 | }); 40 | 41 | it('stores the evaluation of both `left` and `right` properties', () => 42 | { 43 | const results = op.eval(); 44 | 45 | expect(results.left.value).to.exist; 46 | expect(results.left.type).to.equal('number'); 47 | 48 | expect(results.right.value).to.exist; 49 | expect(results.right.type).to.equal('number'); 50 | }); 51 | }); 52 | }); 53 | 54 | // --------------------------------------------------------------------------------------------------------------------- 55 | -------------------------------------------------------------------------------- /tests/Parentheses.spec.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------------------------------------------------- 2 | // Unit Tests for the Parentheses class. 3 | // --------------------------------------------------------------------------------------------------------------------- 4 | 5 | const { expect } = require('chai'); 6 | 7 | const parser = require('../lib/parser'); 8 | const Parentheses = require('../lib/Parentheses'); 9 | const Num = require('../lib/Number'); 10 | const Roll = require('../lib/Roll'); 11 | 12 | // --------------------------------------------------------------------------------------------------------------------- 13 | 14 | describe('Parentheses Class', () => 15 | { 16 | let parentheses; 17 | beforeEach(() => 18 | { 19 | parentheses = new Parentheses(new Roll(new Num(3), new Num(6))); 20 | }); 21 | 22 | it('can be converted to json', () => 23 | { 24 | expect(JSON.stringify(parentheses)).to.equal('{"type":"parentheses","content":{"type":"roll","count":{"type":"number","value":3},"sides":{"type":"number","value":6},"results":[]}}'); 25 | }); 26 | 27 | it('can be converted to a parsable string', () => 28 | { 29 | expect(parentheses.toString()).to.equal('(3d6)'); 30 | expect(parser.parse(parentheses.toString())).to.deep.equal(parentheses); 31 | }); 32 | 33 | describe('#eval()', () => 34 | { 35 | it('returns itself with the value populated', () => 36 | { 37 | parentheses = new Parentheses(new Num(5)); 38 | const results = parentheses.eval(); 39 | 40 | expect(results.value).to.exist; 41 | expect(results.value).to.equal(5); 42 | }); 43 | }); 44 | }); 45 | 46 | // --------------------------------------------------------------------------------------------------------------------- 47 | -------------------------------------------------------------------------------- /tests/Repeat.spec.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------------------------------------------------- 2 | // Unit Tests for the Repeat class. 3 | // --------------------------------------------------------------------------------------------------------------------- 4 | 5 | const { expect } = require('chai'); 6 | 7 | const parser = require('../lib/parser'); 8 | const Repeat = require('../lib/Repeat'); 9 | const Num = require('../lib/Number'); 10 | const Roll = require('../lib/Roll'); 11 | 12 | // --------------------------------------------------------------------------------------------------------------------- 13 | 14 | describe('Repeat Class', () => 15 | { 16 | let repeat; 17 | beforeEach(() => 18 | { 19 | repeat = new Repeat(new Num(3), new Roll(new Num(3), new Num(6))); 20 | }); 21 | 22 | it('can be converted to json', () => 23 | { 24 | expect(JSON.stringify(repeat)).to.equal('{"type":"repeat","count":{"type":"number","value":3},"content":{"type":"roll","count":{"type":"number","value":3},"sides":{"type":"number","value":6},"results":[]}}'); 25 | }); 26 | 27 | it('can be converted to a parsable string', () => 28 | { 29 | expect(repeat.toString()).to.equal('3(3d6)'); 30 | expect(parser.parse(repeat.toString())).to.deep.equal(repeat); 31 | }); 32 | 33 | describe('#eval()', () => 34 | { 35 | it('returns itself with the value populated', () => 36 | { 37 | repeat = new Repeat(new Num(3), new Num(5)); 38 | const results = repeat.eval(); 39 | 40 | expect(results.value).to.exist; 41 | expect(results.value).to.equal(15); 42 | }); 43 | 44 | it('allows a negative count', () => 45 | { 46 | repeat = new Repeat(new Num(-3), new Num(5)); 47 | const results = repeat.eval(); 48 | 49 | expect(results.value).to.exist; 50 | expect(results.value).to.equal(-15); 51 | }); 52 | 53 | it('allows a float count, floored', () => 54 | { 55 | repeat = new Repeat(new Num(3.75), new Num(5)); 56 | const results = repeat.eval(); 57 | 58 | expect(results.value).to.exist; 59 | expect(results.value).to.equal(15); 60 | }); 61 | 62 | it('stores the results of each iteration in the `results` property', () => 63 | { 64 | repeat = new Repeat(new Num(3), new Num(5)); 65 | const results = repeat.eval(); 66 | 67 | expect(results.value).to.exist; 68 | expect(results.results.length).to.equal(3); 69 | expect(results.results[0].type).to.equal('number'); 70 | }); 71 | }); 72 | }); 73 | 74 | // --------------------------------------------------------------------------------------------------------------------- 75 | -------------------------------------------------------------------------------- /tests/Roll.spec.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------------------------------------------------- 2 | // Unit Tests for the Roll class. 3 | // --------------------------------------------------------------------------------------------------------------------- 4 | 5 | const { expect } = require('chai'); 6 | 7 | const parser = require('../lib/parser'); 8 | const Roll = require('../lib/Roll'); 9 | const Num = require('../lib/Number'); 10 | 11 | // --------------------------------------------------------------------------------------------------------------------- 12 | 13 | describe('Roll Class', () => 14 | { 15 | let roll; 16 | beforeEach(() => 17 | { 18 | roll = new Roll(new Num(3), new Num(6)); 19 | }); 20 | 21 | it('can be converted to json', () => 22 | { 23 | expect(JSON.stringify(roll)).to.equal('{"type":"roll","count":{"type":"number","value":3},"sides":{"type":"number","value":6},"results":[]}'); 24 | }); 25 | 26 | it('can be converted to a parsable string', () => 27 | { 28 | expect(roll.toString()).to.equal('3d6'); 29 | expect(parser.parse(roll.toString())).to.deep.equal(roll); 30 | 31 | roll = new Roll(undefined, new Num(6)); 32 | 33 | expect(roll.toString()).to.equal('d6'); 34 | expect(parser.parse(roll.toString())).to.deep.equal(roll); 35 | }); 36 | 37 | describe('#eval()', () => 38 | { 39 | it('returns itself with the value populated', () => 40 | { 41 | const results = roll.eval(); 42 | 43 | expect(results.value).to.exist; 44 | expect(results.value).to.be.at.least(1); 45 | expect(results.value).to.be.at.most(18); 46 | }); 47 | 48 | it('assumes count equals 1 if count is undefined', () => 49 | { 50 | roll = new Roll(undefined, new Num(6)); 51 | 52 | const results = roll.eval(); 53 | 54 | expect(results.results).to.exist; 55 | expect(results.results.length).to.equal(1); 56 | expect(results.value).to.exist; 57 | expect(results.value).to.be.at.least(1); 58 | expect(results.value).to.be.at.most(6); 59 | }); 60 | 61 | it('allows a negative count', () => 62 | { 63 | roll = new Roll(new Num(-1), new Num(6)); 64 | 65 | const results = roll.eval(); 66 | 67 | expect(results.results).to.exist; 68 | expect(results.results.length).to.equal(1); 69 | expect(results.value).to.exist; 70 | expect(results.value).to.be.at.most(-1); 71 | expect(results.value).to.be.at.least(-6); 72 | }); 73 | 74 | it('allows a float count, floored', () => 75 | { 76 | roll = new Roll(new Num(1.75), new Num(6)); 77 | 78 | const results = roll.eval(); 79 | 80 | expect(results.results).to.exist; 81 | expect(results.results.length).to.equal(1); 82 | expect(results.value).to.exist; 83 | expect(results.value).to.be.at.least(1); 84 | expect(results.value).to.be.at.most(6); 85 | }); 86 | 87 | it('allows a negative number of sides', () => 88 | { 89 | roll = new Roll(new Num(1), new Num(-3)); 90 | 91 | const results = roll.eval(); 92 | 93 | expect(results.results).to.exist; 94 | expect(results.value).to.exist; 95 | expect(results.value).to.be.at.least(-3); 96 | expect(results.value).to.be.at.most(-1); 97 | }); 98 | 99 | it('allows a float number of sides, floored', () => 100 | { 101 | roll = new Roll(new Num(1), new Num(6.75)); 102 | 103 | const results = roll.eval(); 104 | 105 | expect(results.results).to.exist; 106 | expect(results.results.length).to.equal(1); 107 | expect(results.value).to.exist; 108 | expect(results.value).to.be.at.least(1); 109 | expect(results.value).to.be.at.most(6); 110 | }); 111 | }); 112 | }); 113 | 114 | // --------------------------------------------------------------------------------------------------------------------- 115 | -------------------------------------------------------------------------------- /tests/Variable.spec.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------------------------------------------------- 2 | // Unit Tests for the Variable class. 3 | // --------------------------------------------------------------------------------------------------------------------- 4 | 5 | const { expect } = require('chai'); 6 | 7 | const parser = require('../lib/parser'); 8 | const Variable = require('../lib/Variable'); 9 | const Roll = require('../lib/Roll'); 10 | const Operation = require('../lib/Operation'); 11 | 12 | // --------------------------------------------------------------------------------------------------------------------- 13 | 14 | describe('Variable Class', () => 15 | { 16 | let variable; let nestedVar; let escapedVar; 17 | beforeEach(() => 18 | { 19 | variable = new Variable('foo'); 20 | nestedVar = new Variable('foo.bar.0.baz'); 21 | escapedVar = new Variable('This is a var!'); 22 | }); 23 | 24 | it('can be converted to json', () => 25 | { 26 | expect(JSON.stringify(variable)).to.equal('{"type":"variable","name":"foo"}'); 27 | }); 28 | 29 | it('can be converted to a parsable string', () => 30 | { 31 | expect(variable.toString()).to.equal('foo'); 32 | expect(parser.parse(variable.toString())).to.deep.equal(variable); 33 | 34 | expect(escapedVar.toString()).to.equal("'This is a var!'"); 35 | expect(parser.parse(escapedVar.toString())).to.deep.equal(escapedVar); 36 | }); 37 | 38 | describe('#eval()', () => 39 | { 40 | it('returns itself with the value populated', () => 41 | { 42 | const results = variable.eval({ foo: 'bar' }); 43 | 44 | // Ensure we populated value correctly 45 | expect(results.value).to.equal('bar'); 46 | }); 47 | 48 | it('supports nested variables', () => 49 | { 50 | const results = nestedVar.eval({ foo: { bar: [ { baz: 23 } ] } }); 51 | expect(results.value).to.equal(23); 52 | }); 53 | 54 | it('throws an error if the variable is not found in the scope', () => 55 | { 56 | expect(variable.eval.bind(variable)).to.throw(`Variable '${ variable.name }' not found in scope.`); 57 | }); 58 | 59 | it('expands parsable expressions', () => 60 | { 61 | const results = variable.eval({ foo: '1 + 2' }); 62 | expect(results.expr).to.be.instanceOf(Operation); 63 | expect(results.value).to.equal(3); 64 | 65 | const results2 = variable.eval({ foo: '1d6' }); 66 | expect(results2.expr).to.be.instanceOf(Roll); 67 | expect(results2.value).to.be.at.least(1); 68 | expect(results2.value).to.be.at.most(6); 69 | }); 70 | 71 | it('throws an error if evaluation becomes too deeply nested', () => 72 | { 73 | expect(() => variable.eval({ foo: 'foo + 2' })).to.throw(Error).with.property('code', 'VAR_MAX_DEPTH'); 74 | }); 75 | }); 76 | }); 77 | 78 | // --------------------------------------------------------------------------------------------------------------------- 79 | -------------------------------------------------------------------------------- /tests/defaultScope.spec.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------------------------------------------------- 2 | // Unit Tests for the defaultScope.js module. 3 | // --------------------------------------------------------------------------------------------------------------------- 4 | 5 | const chai = require('chai'); 6 | const spies = require('chai-spies'); 7 | 8 | chai.use(spies); 9 | 10 | const { expect } = chai; 11 | const proxyquire = require('proxyquire'); 12 | 13 | const rollDie = require('../lib/rolldie'); 14 | const parser = require('../lib/parser'); 15 | 16 | const Num = require('../lib/Number'); 17 | 18 | // --------------------------------------------------------------------------------------------------------------------- 19 | 20 | describe('Default Scope', () => 21 | { 22 | let rollDieMock; let defaultScope; 23 | beforeEach(() => 24 | { 25 | rollDieMock = { 26 | rollDie: chai.spy(rollDie.rollDie) 27 | }; 28 | 29 | defaultScope = proxyquire('../lib/defaultScope', { './rolldie': rollDieMock }); 30 | }); 31 | 32 | afterEach(() => 33 | { 34 | rollDieMock = {}; 35 | }); 36 | 37 | it('builds a scope that includes default functions', () => 38 | { 39 | const scope = defaultScope.buildDefaultScope({ foo: 3 }); 40 | expect(scope.floor).to.be.an.instanceOf(Function); 41 | expect(scope.foo).to.equal(3); 42 | }); 43 | 44 | it('supports not passing in a scope', () => 45 | { 46 | const scope = defaultScope.buildDefaultScope(); 47 | expect(scope.floor).to.be.an.instanceOf(Function); 48 | }); 49 | 50 | it('allows you to overwrite default functions', () => 51 | { 52 | const scope = defaultScope.buildDefaultScope({ floor: 3 }); 53 | expect(scope.floor).to.equal(3); 54 | }); 55 | 56 | describe('Built-in functions', () => 57 | { 58 | describe('min()', () => 59 | { 60 | it('performs a min on the two passed in expressions', () => 61 | { 62 | let expr = parser.parse('min(5,2)'); 63 | let results = expr.eval(); 64 | expect(results.value).to.equal(2); 65 | 66 | expr = parser.parse('10 + min(foo, 2)'); 67 | results = expr.eval({ foo: 5 }); 68 | expect(results.value).to.equal(12); 69 | }); 70 | }); 71 | 72 | describe('max()', () => 73 | { 74 | it('performs a max on the two passed in expressions', () => 75 | { 76 | let expr = parser.parse('max(5,2)'); 77 | let results = expr.eval(); 78 | expect(results.value).to.equal(5); 79 | 80 | expr = parser.parse('10 + max(foo, 2)'); 81 | results = expr.eval({ foo: 5 }); 82 | expect(results.value).to.equal(15); 83 | }); 84 | }); 85 | 86 | describe('floor()', () => 87 | { 88 | it('performs a floor on the results of any operation', () => 89 | { 90 | let expr = parser.parse('floor(5/2)'); 91 | let results = expr.eval(); 92 | expect(results.value).to.equal(2); 93 | 94 | expr = parser.parse('floor(3d6 / 2)'); 95 | results = expr.eval(); 96 | 97 | expect(results.value).to.equal(Math.floor(results.expr.value)); 98 | }); 99 | }); 100 | 101 | describe('ceil()', () => 102 | { 103 | it('performs a ceil on the results of any operation', () => 104 | { 105 | let expr = parser.parse('ceil(5/2)'); 106 | let results = expr.eval(); 107 | expect(results.value).to.equal(3); 108 | 109 | expr = parser.parse('ceil(3d6 / 2)'); 110 | results = expr.eval(); 111 | 112 | expect(results.value).to.equal(Math.ceil(results.expr.value)); 113 | }); 114 | }); 115 | 116 | describe('round()', () => 117 | { 118 | it('performs a round on the results of any operation', () => 119 | { 120 | let expr = parser.parse('round(5/2)'); 121 | let results = expr.eval(); 122 | expect(results.value).to.equal(3); 123 | 124 | expr = parser.parse('round(3d6 / 2)'); 125 | results = expr.eval(); 126 | 127 | expect(results.value).to.equal(Math.round(results.expr.value)); 128 | }); 129 | }); 130 | 131 | describe('explode()', () => 132 | { 133 | it('rolls the expression again every time the value equals the maximum, summing the results', () => 134 | { 135 | const expr = parser.parse('explode(1d6)'); 136 | let rolls = 0; 137 | 138 | // Replace the eval function on the Roll instance. 139 | expr.args[0].eval = () => 140 | { 141 | if(rolls < 3) { rolls++; return { sides: new Num(6), value: 6 }; } 142 | else { return { sides: new Num(6), value: 5 }; } 143 | }; 144 | 145 | const results = expr.eval(); 146 | 147 | expect(results.value).to.equal(23); 148 | expect(results.results.length).to.equal(4); 149 | }); 150 | 151 | it('throws an error if the expression is not a roll expression', () => 152 | { 153 | const expr = parser.parse('explode(5)'); 154 | expect(expr.eval.bind(expr)).to.throw("Non-roll passed to 'explode()': 5"); 155 | }); 156 | }); 157 | 158 | describe('dropLowest()', () => 159 | { 160 | it('removes the lowest result, and sums the remainder', () => 161 | { 162 | const expr = parser.parse('dropLowest(3d6)'); 163 | expr.args[0].eval = () => 164 | { 165 | return { results: [ 1, 1, 3, 4, 5 ] }; 166 | }; 167 | 168 | const results = expr.eval(); 169 | 170 | expect(results.value).to.equal(13); 171 | expect(results.results.length).to.equal(4); 172 | }); 173 | 174 | it('throws an error if the expression is not a roll expression', () => 175 | { 176 | const expr = parser.parse('dropLowest(5)'); 177 | expect(expr.eval.bind(expr)).to.throw("Non-roll passed to 'dropLowest()': 5"); 178 | }); 179 | }); 180 | 181 | describe('dropHighest()', () => 182 | { 183 | it('removes the highest result, and sums the remainder', () => 184 | { 185 | const expr = parser.parse('dropHighest(3d6)'); 186 | expr.args[0].eval = () => 187 | { 188 | return { results: [ 1, 1, 3, 4, 5 ] }; 189 | }; 190 | 191 | const results = expr.eval(); 192 | 193 | expect(results.value).to.equal(9); 194 | expect(results.results.length).to.equal(4); 195 | }); 196 | 197 | it('throws an error if the expression is not a roll expression', () => 198 | { 199 | const expr = parser.parse('dropHighest(5)'); 200 | expect(expr.eval.bind(expr)).to.throw("Non-roll passed to 'dropHighest()': 5"); 201 | }); 202 | }); 203 | 204 | describe('rerollAbove()', () => 205 | { 206 | it('rerolls any dice above the give maximum', () => 207 | { 208 | const expr = parser.parse('rerollAbove(4, 5d6)'); 209 | expr.args[1].eval = () => 210 | { 211 | return { results: [ 1, 1, 3, 4, 5 ], sides: 6, count: 5 }; 212 | }; 213 | 214 | const results = expr.eval(defaultScope.defaultScope); 215 | expect(results.results.length).to.equal(5); 216 | expect(rollDieMock.rollDie).to.have.been.called.exactly(2); 217 | expect(results.results[0]).to.equal(1); 218 | expect(results.results[1]).to.equal(1); 219 | expect(results.results[2]).to.equal(3); 220 | }); 221 | 222 | it('throws an error if the expression is not a roll expression', () => 223 | { 224 | const expr = parser.parse('rerollAbove(1, 5)'); 225 | expect(expr.eval.bind(expr)).to.throw("Non-roll passed to 'rerollAbove()': 5"); 226 | }); 227 | 228 | it('throws an error if the maximum value is not a number', () => 229 | { 230 | const expr = parser.parse('rerollAbove(foo, 5d6)'); 231 | expect(() => { expr.eval({ foo: 'bleh' }); }).to.throw("Non-finite number passed to 'rerollAbove()': bleh"); 232 | }); 233 | }); 234 | 235 | describe('rerollBelow()', () => 236 | { 237 | it('rerolls any results below the given minimum', () => 238 | { 239 | const expr = parser.parse('rerollBelow(1, 5d6)'); 240 | expr.args[1].eval = () => 241 | { 242 | return { results: [ 1, 1, 3, 4, 5 ], sides: 6, count: 5 }; 243 | }; 244 | 245 | const results = expr.eval(defaultScope.defaultScope); 246 | expect(results.results.length).to.equal(5); 247 | expect(rollDieMock.rollDie).to.have.been.called.exactly(2); 248 | expect(results.results[2]).to.equal(3); 249 | expect(results.results[3]).to.equal(4); 250 | expect(results.results[4]).to.equal(5); 251 | }); 252 | 253 | it('throws an error if the expression is not a roll expression', () => 254 | { 255 | const expr = parser.parse('rerollBelow(1, 5)'); 256 | expect(expr.eval.bind(expr)).to.throw("Non-roll passed to 'rerollBelow()': 5"); 257 | }); 258 | 259 | it('throws an error if the minimum value is not a number', () => 260 | { 261 | const expr = parser.parse('rerollBelow(foo, 5d6)'); 262 | expect(() => { expr.eval({ foo: 'bleh' }); }).to.throw("Non-finite number passed to 'rerollBelow()': bleh"); 263 | }); 264 | }); 265 | }); 266 | }); 267 | 268 | // --------------------------------------------------------------------------------------------------------------------- 269 | -------------------------------------------------------------------------------- /tests/parser.spec.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------------------------------------------------- 2 | // Unit Tests for the parser.js module. 3 | // --------------------------------------------------------------------------------------------------------------------- 4 | 5 | const { expect } = require('chai'); 6 | 7 | const parser = require('../lib/parser'); 8 | 9 | // --------------------------------------------------------------------------------------------------------------------- 10 | 11 | describe('Dice Syntax Parser', () => 12 | { 13 | describe('White Space', () => 14 | { 15 | it('supports omitting whitespace', () => 16 | { 17 | const results = parser.parse(' 3 \r\n d\t 6'); 18 | 19 | expect(results.type).to.equal('roll'); 20 | expect(results).to.have.nested.property('count.type', 'number'); 21 | expect(results).to.have.nested.property('count.value', 3); 22 | expect(results).to.have.nested.property('sides.type', 'number'); 23 | expect(results).to.have.nested.property('sides.value', 6); 24 | }); 25 | 26 | it('supports omitting comments', () => 27 | { 28 | const results = parser.parse('/* comment */3d/* comment 3d6 */6'); 29 | 30 | expect(results.type).to.equal('roll'); 31 | expect(results).to.have.nested.property('count.type', 'number'); 32 | expect(results).to.have.nested.property('count.value', 3); 33 | expect(results).to.have.nested.property('sides.type', 'number'); 34 | expect(results).to.have.nested.property('sides.value', 6); 35 | }); 36 | }); 37 | 38 | describe('Dice Syntax', () => 39 | { 40 | it('supports `XdY` dice format', () => 41 | { 42 | const results = parser.parse('3d6'); 43 | 44 | expect(results.type).to.equal('roll'); 45 | expect(results).to.have.nested.property('count.type', 'number'); 46 | expect(results).to.have.nested.property('count.value', 3); 47 | expect(results).to.have.nested.property('sides.type', 'number'); 48 | expect(results).to.have.nested.property('sides.value', 6); 49 | }); 50 | 51 | it('leaves count undefined if you only specify `dY`', () => 52 | { 53 | const results = parser.parse('d6'); 54 | 55 | expect(results.type).to.equal('roll'); 56 | expect(results.count).to.not.exist; 57 | expect(results).to.have.nested.property('sides.type', 'number'); 58 | expect(results).to.have.nested.property('sides.value', 6); 59 | }); 60 | 61 | it('supports a negative count', () => 62 | { 63 | const results = parser.parse('-3d6'); 64 | 65 | expect(results.type).to.equal('roll'); 66 | expect(results).to.have.nested.property('count.type', 'number'); 67 | expect(results).to.have.nested.property('count.value', -3); 68 | expect(results).to.have.nested.property('sides.type', 'number'); 69 | expect(results).to.have.nested.property('sides.value', 6); 70 | }); 71 | 72 | it('supports a float count', () => 73 | { 74 | const results = parser.parse('3.75d6'); 75 | 76 | expect(results.type).to.equal('roll'); 77 | expect(results).to.have.nested.property('count.type', 'number'); 78 | expect(results).to.have.nested.property('count.value', 3.75); 79 | expect(results).to.have.nested.property('sides.type', 'number'); 80 | expect(results).to.have.nested.property('sides.value', 6); 81 | }); 82 | 83 | it('supports a factorial count', () => 84 | { 85 | const results = parser.parse('3!d6'); 86 | 87 | expect(results.type).to.equal('roll'); 88 | expect(results).to.have.nested.property('count.type', 'factorial'); 89 | expect(results).to.have.nested.property('count.content.type', 'number'); 90 | expect(results).to.have.nested.property('count.content.value', 3); 91 | expect(results).to.have.nested.property('sides.type', 'number'); 92 | expect(results).to.have.nested.property('sides.value', 6); 93 | }); 94 | 95 | it('supports a parentheses count', () => 96 | { 97 | const results = parser.parse('(3)d6'); 98 | 99 | expect(results.type).to.equal('roll'); 100 | expect(results).to.have.nested.property('count.type', 'parentheses'); 101 | expect(results).to.have.nested.property('count.content.type', 'number'); 102 | expect(results).to.have.nested.property('count.content.value', 3); 103 | expect(results).to.have.nested.property('sides.type', 'number'); 104 | expect(results).to.have.nested.property('sides.value', 6); 105 | }); 106 | 107 | it('supports a negative number of sides', () => 108 | { 109 | const results = parser.parse('3d-6'); 110 | 111 | expect(results.type).to.equal('roll'); 112 | expect(results).to.have.nested.property('count.type', 'number'); 113 | expect(results).to.have.nested.property('count.value', 3); 114 | expect(results).to.have.nested.property('sides.type', 'number'); 115 | expect(results).to.have.nested.property('sides.value', -6); 116 | }); 117 | 118 | it('supports a float number of sides', () => 119 | { 120 | const results = parser.parse('3d6.75'); 121 | 122 | expect(results.type).to.equal('roll'); 123 | expect(results).to.have.nested.property('count.type', 'number'); 124 | expect(results).to.have.nested.property('count.value', 3); 125 | expect(results).to.have.nested.property('sides.type', 'number'); 126 | expect(results).to.have.nested.property('sides.value', 6.75); 127 | }); 128 | 129 | it('supports a factorial number of sides', () => 130 | { 131 | const results = parser.parse('3d6!'); 132 | 133 | expect(results.type).to.equal('roll'); 134 | expect(results).to.have.nested.property('count.type', 'number'); 135 | expect(results).to.have.nested.property('count.value', 3); 136 | expect(results).to.have.nested.property('sides.type', 'factorial'); 137 | expect(results).to.have.nested.property('sides.content.type', 'number'); 138 | expect(results).to.have.nested.property('sides.content.value', 6); 139 | }); 140 | 141 | it('supports a dice roll number of sides', () => 142 | { 143 | const results = parser.parse('3d1d6'); 144 | 145 | expect(results.type).to.equal('roll'); 146 | expect(results).to.have.nested.property('count.type', 'number'); 147 | expect(results).to.have.nested.property('count.value', 3); 148 | expect(results).to.have.nested.property('sides.type', 'roll'); 149 | expect(results).to.have.nested.property('sides.count.type', 'number'); 150 | expect(results).to.have.nested.property('sides.count.value', 1); 151 | expect(results).to.have.nested.property('sides.sides.type', 'number'); 152 | expect(results).to.have.nested.property('sides.sides.value', 6); 153 | }); 154 | 155 | it('supports a parentheses number of sides', () => 156 | { 157 | const results = parser.parse('3d(6)'); 158 | 159 | expect(results.type).to.equal('roll'); 160 | expect(results).to.have.nested.property('count.type', 'number'); 161 | expect(results).to.have.nested.property('count.value', 3); 162 | expect(results).to.have.nested.property('sides.type', 'parentheses'); 163 | expect(results).to.have.nested.property('sides.content.type', 'number'); 164 | expect(results).to.have.nested.property('sides.content.value', 6); 165 | }); 166 | }); 167 | 168 | describe('Mathematical Operations', () => 169 | { 170 | it('supports addition', () => 171 | { 172 | const results = parser.parse('3d6 + 4'); 173 | 174 | expect(results.type).to.equal('add'); 175 | expect(results.left).to.have.property('type', 'roll'); 176 | expect(results.right).to.have.property('type', 'number'); 177 | }); 178 | 179 | it('supports subtraction', () => 180 | { 181 | const results = parser.parse('3d6 - 4'); 182 | 183 | expect(results.type).to.equal('subtract'); 184 | expect(results.left).to.have.property('type', 'roll'); 185 | expect(results.right).to.have.property('type', 'number'); 186 | }); 187 | 188 | it('supports multiplication', () => 189 | { 190 | const results = parser.parse('3d6 * 4'); 191 | 192 | expect(results.type).to.equal('multiply'); 193 | expect(results.left).to.have.property('type', 'roll'); 194 | expect(results.right).to.have.property('type', 'number'); 195 | }); 196 | 197 | it('supports division', () => 198 | { 199 | const results = parser.parse('3d6 / 4'); 200 | 201 | expect(results.type).to.equal('divide'); 202 | expect(results.left).to.have.property('type', 'roll'); 203 | expect(results.right).to.have.property('type', 'number'); 204 | }); 205 | 206 | it('supports modulo', () => 207 | { 208 | const results = parser.parse('3d6 % 4'); 209 | 210 | expect(results.type).to.equal('modulo'); 211 | expect(results.left).to.have.property('type', 'roll'); 212 | expect(results.right).to.have.property('type', 'number'); 213 | }); 214 | 215 | it('supports exponent', () => 216 | { 217 | const results = parser.parse('3d6 ^ 4'); 218 | 219 | expect(results.type).to.equal('exponent'); 220 | expect(results.left).to.have.property('type', 'roll'); 221 | expect(results.right).to.have.property('type', 'number'); 222 | }); 223 | 224 | it('supports order of operations', () => 225 | { 226 | let results = parser.parse('3 + 2 - 5 * 4 / 6 % 7 ^ 9 ^ 2 * (1 + 2)'); 227 | expect(results).to.have.property('type', 'subtract'); 228 | expect(results).to.have.nested.property('left.type', 'add'); 229 | expect(results).to.have.nested.property('left.left.type', 'number'); 230 | expect(results).to.have.nested.property('left.left.value', 3); 231 | expect(results).to.have.nested.property('left.right.type', 'number'); 232 | expect(results).to.have.nested.property('left.right.value', 2); 233 | expect(results).to.have.nested.property('right.type', 'multiply'); 234 | expect(results).to.have.nested.property('right.left.type', 'modulo'); 235 | expect(results).to.have.nested.property('right.left.left.type', 'divide'); 236 | expect(results).to.have.nested.property('right.left.left.left.type', 'multiply'); 237 | expect(results).to.have.nested.property('right.left.left.left.left.type', 'number'); 238 | expect(results).to.have.nested.property('right.left.left.left.left.value', 5); 239 | expect(results).to.have.nested.property('right.left.left.left.right.type', 'number'); 240 | expect(results).to.have.nested.property('right.left.left.left.right.value', 4); 241 | expect(results).to.have.nested.property('right.left.left.right.type', 'number'); 242 | expect(results).to.have.nested.property('right.left.left.right.value', 6); 243 | expect(results).to.have.nested.property('right.left.right.type', 'exponent'); 244 | expect(results).to.have.nested.property('right.left.right.left.type', 'number'); 245 | expect(results).to.have.nested.property('right.left.right.left.value', 7); 246 | expect(results).to.have.nested.property('right.left.right.right.type', 'exponent'); 247 | expect(results).to.have.nested.property('right.left.right.right.left.type', 'number'); 248 | expect(results).to.have.nested.property('right.left.right.right.left.value', 9); 249 | expect(results).to.have.nested.property('right.left.right.right.right.type', 'number'); 250 | expect(results).to.have.nested.property('right.left.right.right.right.value', 2); 251 | expect(results).to.have.nested.property('right.right.type', 'parentheses'); 252 | expect(results).to.have.nested.property('right.right.content.type', 'add'); 253 | expect(results).to.have.nested.property('right.right.content.left.type', 'number'); 254 | expect(results).to.have.nested.property('right.right.content.left.value', 1); 255 | expect(results).to.have.nested.property('right.right.content.right.type', 'number'); 256 | expect(results).to.have.nested.property('right.right.content.right.value', 2); 257 | 258 | results = parser.parse('(2 + 1) * 2 ^ 9 ^ 7 % 6 / 4 * 5 - 2 + 3'); 259 | expect(results).to.have.property('type', 'add'); 260 | expect(results).to.have.nested.property('left.type', 'subtract'); 261 | expect(results).to.have.nested.property('left.left.type', 'multiply'); 262 | expect(results).to.have.nested.property('left.left.left.type', 'divide'); 263 | expect(results).to.have.nested.property('left.left.left.left.type', 'modulo'); 264 | expect(results).to.have.nested.property('left.left.left.left.left.type', 'multiply'); 265 | expect(results).to.have.nested.property('left.left.left.left.left.left.type', 'parentheses'); 266 | expect(results).to.have.nested.property('left.left.left.left.left.left.content.type', 'add'); 267 | expect(results).to.have.nested.property('left.left.left.left.left.left.content.left.type', 'number'); 268 | expect(results).to.have.nested.property('left.left.left.left.left.left.content.left.value', 2); 269 | expect(results).to.have.nested.property('left.left.left.left.left.left.content.right.type', 'number'); 270 | expect(results).to.have.nested.property('left.left.left.left.left.left.content.right.value', 1); 271 | expect(results).to.have.nested.property('left.left.left.left.left.right.type', 'exponent'); 272 | expect(results).to.have.nested.property('left.left.left.left.left.right.left.type', 'number'); 273 | expect(results).to.have.nested.property('left.left.left.left.left.right.left.value', 2); 274 | expect(results).to.have.nested.property('left.left.left.left.left.right.right.type', 'exponent'); 275 | expect(results).to.have.nested.property('left.left.left.left.left.right.right.left.type', 'number'); 276 | expect(results).to.have.nested.property('left.left.left.left.left.right.right.left.value', 9); 277 | expect(results).to.have.nested.property('left.left.left.left.left.right.right.right.type', 'number'); 278 | expect(results).to.have.nested.property('left.left.left.left.left.right.right.right.value', 7); 279 | expect(results).to.have.nested.property('left.left.left.left.right.type', 'number'); 280 | expect(results).to.have.nested.property('left.left.left.left.right.value', 6); 281 | expect(results).to.have.nested.property('left.left.left.right.type', 'number'); 282 | expect(results).to.have.nested.property('left.left.left.right.value', 4); 283 | expect(results).to.have.nested.property('left.left.right.type', 'number'); 284 | expect(results).to.have.nested.property('left.left.right.value', 5); 285 | expect(results).to.have.nested.property('left.right.type', 'number'); 286 | expect(results).to.have.nested.property('left.right.value', 2); 287 | expect(results).to.have.nested.property('right.type', 'number'); 288 | expect(results).to.have.nested.property('right.value', 3); 289 | }); 290 | }); 291 | 292 | describe('Boolean Operations', () => 293 | { 294 | it('supports not', () => 295 | { 296 | const results = parser.parse('!5'); 297 | 298 | expect(results.type).to.equal('not'); 299 | expect(results.content).to.have.property('type', 'number'); 300 | }); 301 | 302 | it('supports equal', () => 303 | { 304 | const results = parser.parse('1d20 == 5'); 305 | 306 | expect(results.type).to.equal('equal'); 307 | expect(results.left).to.have.property('type', 'roll'); 308 | expect(results.right).to.have.property('type', 'number'); 309 | }); 310 | 311 | it('supports notEqual', () => 312 | { 313 | const results = parser.parse('1d20 != 5'); 314 | 315 | expect(results.type).to.equal('notEqual'); 316 | expect(results.left).to.have.property('type', 'roll'); 317 | expect(results.right).to.have.property('type', 'number'); 318 | }); 319 | 320 | it('supports greaterThan', () => 321 | { 322 | const results = parser.parse('1d20 > 5'); 323 | 324 | expect(results.type).to.equal('greaterThan'); 325 | expect(results.left).to.have.property('type', 'roll'); 326 | expect(results.right).to.have.property('type', 'number'); 327 | }); 328 | 329 | it('supports lessThan', () => 330 | { 331 | const results = parser.parse('1d20 < 5'); 332 | 333 | expect(results.type).to.equal('lessThan'); 334 | expect(results.left).to.have.property('type', 'roll'); 335 | expect(results.right).to.have.property('type', 'number'); 336 | }); 337 | 338 | it('supports greaterThanOrEqual', () => 339 | { 340 | const results = parser.parse('1d20 >= 5'); 341 | 342 | expect(results.type).to.equal('greaterThanOrEqual'); 343 | expect(results.left).to.have.property('type', 'roll'); 344 | expect(results.right).to.have.property('type', 'number'); 345 | }); 346 | 347 | it('supports lessThanOrEqual', () => 348 | { 349 | const results = parser.parse('1d20 <= 5'); 350 | 351 | expect(results.type).to.equal('lessThanOrEqual'); 352 | expect(results.left).to.have.property('type', 'roll'); 353 | expect(results.right).to.have.property('type', 'number'); 354 | }); 355 | }); 356 | 357 | describe('Logical Operations', () => 358 | { 359 | it('supports or', () => 360 | { 361 | const results = parser.parse('0 || 10'); 362 | 363 | expect(results.type).to.equal('or'); 364 | expect(results.left).to.have.property('type', 'number'); 365 | expect(results.right).to.have.property('type', 'number'); 366 | }); 367 | 368 | it('supports and', () => 369 | { 370 | const results = parser.parse('0 && 10'); 371 | 372 | expect(results.type).to.equal('and'); 373 | expect(results.left).to.have.property('type', 'number'); 374 | expect(results.right).to.have.property('type', 'number'); 375 | }); 376 | }); 377 | 378 | describe('Conditionals', () => 379 | { 380 | it('supports conditionals', () => 381 | { 382 | const results = parser.parse('1d20 + 5 >= 10 ? 2d8 + 3 : 0'); 383 | 384 | expect(results.type).to.equal('conditional'); 385 | expect(results.condition).to.have.property('type', 'greaterThanOrEqual'); 386 | expect(results.thenExpr).to.have.property('type', 'add'); 387 | expect(results.elseExpr).to.have.property('type', 'number'); 388 | }); 389 | }); 390 | 391 | describe('Repeats', () => 392 | { 393 | it('supports implicit repeats with `X(...)`', () => 394 | { 395 | const results = parser.parse('3(2d6 + 4)'); 396 | expect(results.type).to.equal('repeat'); 397 | expect(results).to.have.nested.property('count.type', 'number'); 398 | expect(results).to.have.nested.property('count.value', 3); 399 | expect(results.content).to.exist; 400 | }); 401 | 402 | it('supports a negative count', () => 403 | { 404 | const results = parser.parse('-3(2d6 + 4)'); 405 | expect(results.type).to.equal('repeat'); 406 | expect(results).to.have.nested.property('count.type', 'number'); 407 | expect(results).to.have.nested.property('count.value', -3); 408 | expect(results.content).to.exist; 409 | }); 410 | 411 | it('supports a float count', () => 412 | { 413 | const results = parser.parse('3.75(2d6 + 4)'); 414 | expect(results.type).to.equal('repeat'); 415 | expect(results).to.have.nested.property('count.type', 'number'); 416 | expect(results).to.have.nested.property('count.value', 3.75); 417 | expect(results.content).to.exist; 418 | }); 419 | 420 | it('supports a factorial count', () => 421 | { 422 | const results = parser.parse('3!(2d6 + 4)'); 423 | expect(results.type).to.equal('repeat'); 424 | expect(results).to.have.nested.property('count.type', 'factorial'); 425 | expect(results).to.have.nested.property('count.content.type', 'number'); 426 | expect(results).to.have.nested.property('count.content.value', 3); 427 | expect(results.content).to.exist; 428 | }); 429 | 430 | it('supports a parentheses count', () => 431 | { 432 | const results = parser.parse('(3)(2d6 + 4)'); 433 | expect(results.type).to.equal('repeat'); 434 | expect(results).to.have.nested.property('count.type', 'parentheses'); 435 | expect(results).to.have.nested.property('count.content.type', 'number'); 436 | expect(results).to.have.nested.property('count.content.value', 3); 437 | expect(results.content).to.exist; 438 | }); 439 | }); 440 | 441 | describe('Variables', () => 442 | { 443 | it('supports standard variable names', () => 444 | { 445 | let results = parser.parse('foobar'); 446 | expect(results.type).to.equal('variable'); 447 | expect(results.name).to.equal('foobar'); 448 | 449 | results = parser.parse('fooBar239875'); 450 | expect(results.type).to.equal('variable'); 451 | expect(results.name).to.equal('fooBar239875'); 452 | }); 453 | 454 | it('supports quoted variable names', () => 455 | { 456 | let results = parser.parse('\'var with spaces\''); 457 | expect(results.type).to.equal('variable'); 458 | expect(results.name).to.equal('var with spaces'); 459 | 460 | results = parser.parse('[var with spaces]'); 461 | expect(results.type).to.equal('variable'); 462 | expect(results.name).to.equal('var with spaces'); 463 | }); 464 | 465 | it('supports (escaped) nested variables', () => 466 | { 467 | let results = parser.parse('\'foo.bar.0.baz\''); 468 | expect(results.type).to.equal('variable'); 469 | expect(results.name).to.equal('foo.bar.0.baz'); 470 | 471 | results = parser.parse('[foo.bar.0.baz]'); 472 | expect(results.type).to.equal('variable'); 473 | expect(results.name).to.equal('foo.bar.0.baz'); 474 | }); 475 | }); 476 | 477 | describe('Functions', () => 478 | { 479 | it('supports functions', () => 480 | { 481 | const results = parser.parse('foobar(2d6)'); 482 | expect(results.type).to.equal('function'); 483 | expect(results.name).to.equal('foobar'); 484 | expect(results).to.have.nested.property('args.length', 1); 485 | expect(results).to.have.nested.property('args[0].count.type', 'number'); 486 | expect(results).to.have.nested.property('args[0].count.value', 2); 487 | expect(results).to.have.nested.property('args[0].sides.type', 'number'); 488 | expect(results).to.have.nested.property('args[0].sides.value', 6); 489 | }); 490 | 491 | it('supports functions with no arguments', () => 492 | { 493 | const results = parser.parse('foobar()'); 494 | expect(results.type).to.equal('function'); 495 | expect(results.name).to.equal('foobar'); 496 | expect(results.args).to.exist; 497 | expect(results.args.length).to.equal(0); 498 | }); 499 | 500 | it('supports functions with multiple arguments', () => 501 | { 502 | const results = parser.parse('foobar(2d6, 3)'); 503 | expect(results.type).to.equal('function'); 504 | expect(results.name).to.equal('foobar'); 505 | expect(results).to.have.nested.property('args.length', 2); 506 | expect(results).to.have.nested.property('args[0].count.type', 'number'); 507 | expect(results).to.have.nested.property('args[0].count.value', 2); 508 | expect(results).to.have.nested.property('args[0].sides.type', 'number'); 509 | expect(results).to.have.nested.property('args[0].sides.value', 6); 510 | expect(results).to.have.nested.property('args[1].type', 'number'); 511 | expect(results).to.have.nested.property('args[1].value', 3); 512 | }); 513 | 514 | it('supports quoted names for functions', () => 515 | { 516 | const results = parser.parse('\'func with spaces\'(2d6)'); 517 | expect(results.type).to.equal('function'); 518 | expect(results.name).to.equal('func with spaces'); 519 | }); 520 | }); 521 | 522 | describe('Speed', () => 523 | { 524 | it('performs long parses quickly', () => 525 | { 526 | const start = Date.now(); 527 | const results = parser.parse('5 + 5 + 5 + 5 + 5 + 5 + 5 + 5 + 5 + 5 + 5 + 5 + 5 + 5 + 5 + 5 + 5 + 5 + 5 + 5 + 5 + 5 + 5 + 5 + 5 + 5 + 5 + 5 + 5'); 528 | const end = Date.now(); 529 | 530 | expect(results.type).to.equal('add'); 531 | 532 | const time = (end - start); 533 | if(time > 500) 534 | { 535 | throw Error(`Parse took ${ time }ms!`); 536 | } 537 | }); 538 | 539 | // Future test we hope to be able to clear, currently not consistant due to limitations in PEG.js 540 | /* 541 | it('performs deeply nested parses quickly', () => 542 | { 543 | const start = Date.now(); 544 | const results = parser.parse('((((((((5))))))))'); 545 | const end = Date.now(); 546 | 547 | expect(results.type).to.equal('parentheses'); 548 | 549 | const time = (end - start); 550 | if(time > 500) 551 | { 552 | throw Error(`Parse took ${ time }ms!`); 553 | } 554 | }); 555 | */ 556 | }); 557 | }); 558 | 559 | // --------------------------------------------------------------------------------------------------------------------- 560 | -------------------------------------------------------------------------------- /tests/rollDist.js: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------------------------------------------------- 2 | // A simple test for uniform distributions. 3 | //---------------------------------------------------------------------------------------------------------------------- 4 | 5 | /* eslint-disable no-console */ 6 | 7 | const dice = require('../lib/rolldie'); 8 | const { range } = require('../lib/utils'); 9 | 10 | //---------------------------------------------------------------------------------------------------------------------- 11 | 12 | const SIDES = 20; 13 | const ITERATIONS = 10000000; 14 | 15 | //---------------------------------------------------------------------------------------------------------------------- 16 | 17 | // Populate result object 18 | const results = {}; 19 | range(1, SIDES + 1).forEach((side) => { results[side] = 0; }); 20 | 21 | // Collect results 22 | range(0, ITERATIONS).forEach(() => 23 | { 24 | const roll = dice.rollDie(SIDES); 25 | results[roll] += 1; 26 | }); 27 | 28 | const expectedCount = Math.floor(ITERATIONS / SIDES); 29 | 30 | // Report to the user 31 | console.log('Raw: %j', results); 32 | 33 | const variances = Object.keys(results).reduce((accum, key) => 34 | { 35 | const val = results[key]; 36 | accum[key] = (((val - expectedCount) / expectedCount) * 100).toFixed(2); 37 | 38 | return accum; 39 | }, {}); 40 | 41 | console.log('Variance: %j', variances); 42 | 43 | //---------------------------------------------------------------------------------------------------------------------- 44 | -------------------------------------------------------------------------------- /tests/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RPGDice Test 6 | 7 | 8 | 9 |

RPGDice Test

10 |

11 | Just enter a roll syntax below, and we'll roll it, and spit out the results. 12 |

13 | 14 |
15 | 16 |
17 | 18 | 19 |
20 |
21 | 22 |
23 | 24 |
25 | 26 |

Output

27 |
Not rolls yet...
28 | 29 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /tests/utils.spec.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------------------------------------------------- 2 | // Unit Tests for the utils.js module. 3 | // --------------------------------------------------------------------------------------------------------------------- 4 | 5 | const { expect } = require('chai'); 6 | 7 | const utils = require('../lib/utils'); 8 | 9 | // --------------------------------------------------------------------------------------------------------------------- 10 | 11 | describe('Utils', () => 12 | { 13 | describe('#get()', () => 14 | { 15 | /* eslint-disable id-length */ 16 | const object = { a: [ { b: { c: 3 } } ] }; 17 | 18 | it('gets the value at `path` of `object`', () => 19 | { 20 | const results = utils.get(object, 'a[0].b.c'); 21 | expect(results).to.equal(3); 22 | 23 | const results2 = utils.get(object, [ 'a', '0', 'b', 'c' ]); 24 | expect(results2).to.equal(3); 25 | }); 26 | 27 | it('returns the `defaultValue` if the resolved value is `undefined`', () => 28 | { 29 | const results = utils.get(object, 'a.b.c', 'default'); 30 | expect(results).to.equal('default'); 31 | 32 | const results2 = utils.get(object, [ 'a', 'b', 'c' ], 'default'); 33 | expect(results2).to.equal('default'); 34 | }); 35 | }); 36 | 37 | describe('#range()', () => 38 | { 39 | it('takes a single parameter, and assumes a `start` of 0, `end` of the parameter, `step` of 1', () => 40 | { 41 | const results = utils.range(4); 42 | expect(results).to.be.an('array'); 43 | expect(results).to.deep.equal([ 0, 1, 2, 3 ]); 44 | }); 45 | 46 | it('takes a single negative parameter, and assumes a `start` of 0, `end` of the parameter, `step` of -1', () => 47 | { 48 | const results = utils.range(-4); 49 | expect(results).to.be.an('array'); 50 | expect(results).to.deep.equal([ 0, -1, -2, -3 ]); 51 | }); 52 | 53 | it('takes a `start` and `stop` parameter, creating an array progressing from `start` up to, but not including, `end`.', () => 54 | { 55 | const results = utils.range(1, 5); 56 | expect(results).to.be.an('array'); 57 | expect(results).to.deep.equal([ 1, 2, 3, 4 ]); 58 | }); 59 | 60 | it('takes three parameters, creating an array progressing from `start` up to, but not including, `end`, with appropriate step size.', () => 61 | { 62 | const results = utils.range(0, 20, 5); 63 | expect(results).to.be.an('array'); 64 | expect(results).to.deep.equal([ 0, 5, 10, 15 ]); 65 | 66 | const results2 = utils.range(5, 25, 5); 67 | expect(results2).to.be.an('array'); 68 | expect(results2).to.deep.equal([ 5, 10, 15, 20 ]); 69 | }); 70 | }); 71 | }); 72 | 73 | // --------------------------------------------------------------------------------------------------------------------- 74 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------------------------------------------------- 2 | // Typescript Types 3 | // --------------------------------------------------------------------------------------------------------------------- 4 | 5 | export class Expression { 6 | /** 7 | * Returns the Expression as a meaningful plain object. 8 | */ 9 | toJSON() : Record; 10 | 11 | /** 12 | * Renders the Roll Expression into readable human output. 13 | */ 14 | render() : string; 15 | 16 | /** 17 | * Evaluates the expression. 18 | */ 19 | eval() : string; 20 | } 21 | 22 | export class Num extends Expression { 23 | // Overridden in this class 24 | eval() : never; 25 | 26 | /** 27 | * Evaluates the number it represents. Since there's no work to do, it just returns itself. 28 | * 29 | * @returns Returns the exact same Num instance. 30 | */ 31 | eval() : Num; 32 | eval(scope ?: Record, depth ?: number) : Num; 33 | 34 | /** 35 | * The current total value of the roll. 36 | */ 37 | value : number; 38 | } 39 | 40 | export class Factorial extends Expression { 41 | // Overridden in this class 42 | eval() : never; 43 | 44 | /** 45 | * Evaluates the Factorial in the given scope, updating the Factorial's `value`. 46 | * 47 | * @param scope - The scope for evaluating variables. 48 | * @param [depth = 1] - How deep in the AST this evaluation is. 49 | * 50 | * @returns Returns the same Factorial instance, but with the factorial's value having been evaluated. 51 | */ 52 | eval(scope : Record, depth : number) : Factorial; 53 | } 54 | 55 | export class Parentheses extends Expression 56 | { 57 | // Overridden in this class 58 | eval() : never; 59 | 60 | /** 61 | * Evaluates the Parentheses in the given scope, updating the Parentheses's `value`. 62 | * 63 | * @param scope - The scope for evaluating variables. 64 | * @param [depth = 1] - How deep in the AST this evaluation is. 65 | * 66 | * @returns Returns the same Parentheses instance, but with the paren's value having been evaluated. 67 | */ 68 | eval(scope : Record, depth : number) : Factorial; 69 | } 70 | 71 | export class Roll extends Expression { 72 | /** 73 | * Returns the roll as a parsable string. 74 | * 75 | * @returns The roll, in parsable roll syntax. 76 | */ 77 | toString() : string; 78 | 79 | // Overridden in this class 80 | eval() : never; 81 | 82 | /** 83 | * Evaluates the Roll in the given scope, updating the Roll's `value` and `results`. 84 | * 85 | * @param scope - The scope for evaluating variables. 86 | * @param [depth = 1] - How deep in the AST this evaluation is. 87 | * 88 | * @returns Returns the same Roll instance, but with the roll having been executed. 89 | */ 90 | eval(scope : Record, depth : number) : Roll; 91 | 92 | /** 93 | * The current total value of the roll. 94 | */ 95 | value : number; 96 | 97 | /** 98 | * All the sub-rolls (if any) that make up the roll's value. 99 | */ 100 | results : number[]; 101 | 102 | /** 103 | * How many sides the die for this roll has. (i.e. `${ count }d${ sides }`) 104 | */ 105 | sides : Parentheses | Factorial | Num; 106 | 107 | /** 108 | * How many of `sides` numbered dice to roll. (i.e. `${ count }d${ sides }`) 109 | */ 110 | count : Parentheses | Factorial | Num; 111 | } 112 | 113 | // --------------------------------------------------------------------------------------------------------------------- 114 | // Default Library Export 115 | // --------------------------------------------------------------------------------------------------------------------- 116 | 117 | /** 118 | * Parses a roll string and returns the unevaluated Roll object. 119 | * 120 | * @param expr - The expression (in string notation) to evaluate. 121 | * 122 | * @returns Returns a parsed (but not evaluated) roll. 123 | */ 124 | export function parse(expr : string) : Roll; 125 | 126 | /** 127 | * Takes either a string or an expression and evaluates it, returning a roll object. 128 | * 129 | * @param expr - The expression (either parsed or in string notation) to evaluate. 130 | * @param scope - The scope to evaluate the expression in. 131 | */ 132 | // @ts-ignore - This is a function we export named eval, but ts gets it wrong... 133 | export function eval(expr : Roll | string, scope : Record) : Roll; 134 | 135 | /** 136 | * Rolls a single die of the given number of sides. 137 | * @param sides - The number of sides on the die to roll. 138 | */ 139 | export function rollDie(sides : number) : number; 140 | 141 | // --------------------------------------------------------------------------------------------------------------------- 142 | --------------------------------------------------------------------------------