diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..3d7c221 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,30 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "chrome", + "request": "launch", + "name": "Karma Tests", + "sourceMaps": true, + "webRoot": "${workspaceRoot}", + "url": "http://localhost:9876/debug.html", + // "runtimeArgs": [ + // "--headless" + // ], + "pathMapping": { + "/": "${workspaceRoot}", + "/base/": "${workspaceRoot}/" + }, + "sourceMapPathOverrides": { + "webpack:///./*": "${webRoot}/*", + "webpack:///src/*": "${webRoot}/*", + "webpack:///*": "*", + "webpack:///./~/*": "${webRoot}/node_modules/*", + "meteor://💻app/*": "${webRoot}/*" + } + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 82bc24c..7c41180 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ npm install dice-typescript At its simplest, the dice roller is very simple to use. Take the following example: ```typescript -import * as Dice from "dice-typescript"; +import { Dice } from "dice-typescript"; const dice = new Dice(); const result = dice.roll("1d20").total; @@ -57,7 +57,7 @@ In addition to the ```abs```, ```ceil```, ```floor```, ```round``` and ```sqrt`` ```typescript const customFunctions = new FunctionDefinitionList(); customFunctions["floor"] = (interpreter: DiceInterpreter, functionNode: ExpressionNode, errors: ErrorMessage[]): number => { - return Math.floor(interpreter.evaluate(functionNode.getChild(0), errors)); + return Math.floor(interpreter.evaluate(functionNode.getChild(0), errors)); } const dice = new Dice(customFunctions); @@ -71,10 +71,10 @@ By default, the Dice library uses [random-js](https://www.npmjs.com/package/rand ```typescript export class CustomRandom implements RandomProvider { - numberBetween(min: number, max: number) { - return 4; // chosen by fair dice roll. - // guaranteed to be random. - } + numberBetween(min: number, max: number) { + return 4; // chosen by fair dice roll. + // guaranteed to be random. + } } const dice = new Dice(null, new CustomRandom()); @@ -89,7 +89,7 @@ The dice rolling syntax is based on the system used by Roll20, a detailed explan In addition to the above syntax rules, some slightly more complicated variations are available. For example, you can roll a variable number of dice using an expression similar to the following: ```dice - (4d4)d20 + (4d4)d20 ``` ##### Conditional Operators @@ -101,7 +101,7 @@ As per the Roll20 syntax, you can use conditional operators, such as in ```4d20> Sometimes it is necessary to roll complex groups of dice that aren't supported by the basic syntax. For example, rolling a saving throw at disadvantage for 10 creatures. For this, you can use the group repeater modifier, which works like this: ```dice - {2d20kl...10}>=14 + {2d20kl...10}>=14 ``` The above will roll 10 disadvantaged saving throws, reporting successes for those that break DC14. @@ -111,7 +111,7 @@ The above will roll 10 disadvantaged saving throws, reporting successes for thos Using the allowed syntax, it is possible to request a fractional number of dice to be rolled. Take the following example: ```dice - (2 / 5)d6 + (2 / 5)d6 ``` In this instance, the number of dice to be rolled will be rounded to the nearest integer (2.5 gets rounded up to 3). diff --git a/karma.conf.js b/karma.conf.js index f1576d8..744a7ca 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -4,48 +4,52 @@ const watching = process.env.npm_lifecycle_script.indexOf("--single-run") === -1 console.log("Watching: " + watching); module.exports = function (config) { - config.set({ - browserNoActivityTimeout: 20000, - frameworks: ["jasmine", "karma-typescript"], - files: [ - { pattern: "spec/**/*.ts" }, - { pattern: "src/**/*.ts" }, - ], - preprocessors: { - "**/*.ts": ["karma-typescript"], - }, - reporters: ["progress", "karma-typescript"], - browsers: ["Chrome"], - plugins: [ - "karma-chrome-launcher", - "karma-jasmine", - "karma-typescript" - ], - customLaunchers: { - chromeTravisCi: { - base: "Chrome", - flags: ["--no-sandbox"] - } - }, - karmaTypescriptConfig: { - coverageOptions: { - instrumentation: true - }, - reports: { - lcovonly: { - directory: "coverage", - filename: "lcov.info", - subdirectory: "lcov" - } - } + config.set({ + browserNoActivityTimeout: 20000, + frameworks: ["jasmine", "karma-typescript"], + files: [ + { pattern: "spec/**/*.ts" }, + { pattern: "src/**/*.ts" }, + ], + preprocessors: { + "**/*.ts": ["karma-typescript"], + }, + reporters: ["progress", "karma-typescript"], + browsers: ["chromeDebugging"], + plugins: [ + "karma-chrome-launcher", + "karma-jasmine", + "karma-typescript" + ], + customLaunchers: { + chromeDebugging: { + base: 'Chrome', + flags: [ '--remote-debugging-port=9333' ] + }, + chromeTravisCi: { + base: "Chrome", + flags: ["--no-sandbox"] + } + }, + karmaTypescriptConfig: { + coverageOptions: { + instrumentation: true + }, + reports: { + lcovonly: { + directory: "coverage", + filename: "lcov.info", + subdirectory: "lcov" } - }); - - if (watching) { - config.karmaTypescriptConfig.coverageOptions.instrumentation = false; + } } + }); - if (process.env.TRAVIS) { - config.browsers = ["chromeTravisCi"]; - } + if (watching) { + config.karmaTypescriptConfig.coverageOptions.instrumentation = false; + } + + if (process.env.TRAVIS) { + config.browsers = ["chromeTravisCi"]; + } }; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index df8a11e..a2040d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=", "dev": true, "requires": { - "mime-types": "2.1.18", + "mime-types": "~2.1.11", "negotiator": "0.6.1" } }, @@ -50,10 +50,10 @@ "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "dev": true, "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.1.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" } }, "align-text": { @@ -62,9 +62,9 @@ "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", "dev": true, "requires": { - "kind-of": "3.2.2", - "longest": "1.0.1", - "repeat-string": "1.6.1" + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" } }, "amdefine": { @@ -115,8 +115,8 @@ "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", "dev": true, "requires": { - "micromatch": "2.3.11", - "normalize-path": "2.1.1" + "micromatch": "^2.1.5", + "normalize-path": "^2.0.0" } }, "argparse": { @@ -125,7 +125,7 @@ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { - "sprintf-js": "1.0.3" + "sprintf-js": "~1.0.2" } }, "arr-diff": { @@ -134,7 +134,7 @@ "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", "dev": true, "requires": { - "arr-flatten": "1.1.0" + "arr-flatten": "^1.0.1" } }, "arr-flatten": { @@ -185,9 +185,9 @@ "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", "dev": true, "requires": { - "bn.js": "4.11.8", - "inherits": "2.0.3", - "minimalistic-assert": "1.0.0" + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" } }, "assert": { @@ -241,9 +241,9 @@ "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", "dev": true, "requires": { - "chalk": "1.1.3", - "esutils": "2.0.2", - "js-tokens": "3.0.2" + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" }, "dependencies": { "chalk": { @@ -252,11 +252,11 @@ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" } }, "supports-color": { @@ -304,7 +304,7 @@ "dev": true, "optional": true, "requires": { - "tweetnacl": "0.14.5" + "tweetnacl": "^0.14.3" } }, "better-assert": { @@ -347,15 +347,15 @@ "dev": true, "requires": { "bytes": "3.0.0", - "content-type": "1.0.4", + "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "1.1.2", - "http-errors": "1.6.2", + "depd": "~1.1.1", + "http-errors": "~1.6.2", "iconv-lite": "0.4.19", - "on-finished": "2.3.0", + "on-finished": "~2.3.0", "qs": "6.5.1", "raw-body": "2.3.2", - "type-is": "1.6.16" + "type-is": "~1.6.15" } }, "boom": { @@ -364,7 +364,7 @@ "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", "dev": true, "requires": { - "hoek": "4.2.1" + "hoek": "4.x.x" } }, "brace-expansion": { @@ -373,7 +373,7 @@ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { - "balanced-match": "1.0.0", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, @@ -383,9 +383,9 @@ "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", "dev": true, "requires": { - "expand-range": "1.8.2", - "preserve": "0.2.0", - "repeat-element": "1.1.2" + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" } }, "brorand": { @@ -409,12 +409,12 @@ "integrity": "sha512-UGnTYAnB2a3YuYKIRy1/4FB2HdM866E0qC46JXvVTYKlBlZlnvfpSfY6OKfXZAkv70eJ2a1SqzpAo5CRhZGDFg==", "dev": true, "requires": { - "buffer-xor": "1.0.3", - "cipher-base": "1.0.4", - "create-hash": "1.1.3", - "evp_bytestokey": "1.0.3", - "inherits": "2.0.3", - "safe-buffer": "5.1.1" + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" } }, "browserify-cipher": { @@ -423,9 +423,9 @@ "integrity": "sha1-mYgkSHS/XtTijalWZtzWasj8Njo=", "dev": true, "requires": { - "browserify-aes": "1.1.1", - "browserify-des": "1.0.0", - "evp_bytestokey": "1.0.3" + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" } }, "browserify-des": { @@ -434,9 +434,9 @@ "integrity": "sha1-2qJ3cXRwki7S/hhZQRihdUOXId0=", "dev": true, "requires": { - "cipher-base": "1.0.4", - "des.js": "1.0.0", - "inherits": "2.0.3" + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1" } }, "browserify-rsa": { @@ -445,8 +445,8 @@ "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "dev": true, "requires": { - "bn.js": "4.11.8", - "randombytes": "2.0.6" + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" } }, "browserify-sign": { @@ -455,13 +455,13 @@ "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", "dev": true, "requires": { - "bn.js": "4.11.8", - "browserify-rsa": "4.0.1", - "create-hash": "1.1.3", - "create-hmac": "1.1.6", - "elliptic": "6.4.0", - "inherits": "2.0.3", - "parse-asn1": "5.1.0" + "bn.js": "^4.1.1", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.2", + "elliptic": "^6.0.0", + "inherits": "^2.0.1", + "parse-asn1": "^5.0.0" } }, "browserify-zlib": { @@ -470,7 +470,7 @@ "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", "dev": true, "requires": { - "pako": "1.0.6" + "pako": "~1.0.5" } }, "buffer": { @@ -479,8 +479,8 @@ "integrity": "sha512-YkIRgwsZwJWTnyQrsBTWefizHh+8GYj3kbL1BTiAQ/9pwpino0G7B2gp5tx/FUBqUlvtxV85KNR3mwfAtv15Yw==", "dev": true, "requires": { - "base64-js": "1.2.3", - "ieee754": "1.1.10" + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" } }, "buffer-xor": { @@ -526,8 +526,8 @@ "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "dev": true, "requires": { - "camelcase": "2.1.1", - "map-obj": "1.0.1" + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" }, "dependencies": { "camelcase": { @@ -551,8 +551,8 @@ "dev": true, "optional": true, "requires": { - "align-text": "0.1.4", - "lazy-cache": "1.0.4" + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" } }, "chalk": { @@ -561,9 +561,9 @@ "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.3.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" }, "dependencies": { "ansi-styles": { @@ -572,7 +572,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.1" + "color-convert": "^1.9.0" } }, "has-flag": { @@ -587,7 +587,7 @@ "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -598,15 +598,15 @@ "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", "dev": true, "requires": { - "anymatch": "1.3.2", - "async-each": "1.0.1", - "fsevents": "1.1.3", - "glob-parent": "2.0.0", - "inherits": "2.0.3", - "is-binary-path": "1.0.1", - "is-glob": "2.0.1", - "path-is-absolute": "1.0.1", - "readdirp": "2.1.0" + "anymatch": "^1.3.0", + "async-each": "^1.0.0", + "fsevents": "^1.0.0", + "glob-parent": "^2.0.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^2.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0" } }, "cipher-base": { @@ -615,8 +615,8 @@ "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", "dev": true, "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.1" + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" } }, "cliui": { @@ -626,8 +626,8 @@ "dev": true, "optional": true, "requires": { - "center-align": "0.1.3", - "right-align": "0.1.3", + "center-align": "^0.1.1", + "right-align": "^0.1.1", "wordwrap": "0.0.2" }, "dependencies": { @@ -658,14 +658,14 @@ "integrity": "sha512-MGMkPS5d9AqQEXTZ4grn/syl/7VvOehgWTeU2B41E22q767QolclfdfadKAndL287cIPEOEdwh9JBqCwQJLtFw==", "dev": true, "requires": { - "bluebird": "3.5.1", - "commander": "2.15.1", - "joi": "12.0.0", - "lcov-parse": "1.0.0", - "lodash": "4.17.5", - "log-driver": "1.2.7", - "request": "2.85.0", - "request-promise": "4.2.2" + "bluebird": "^3.5.x", + "commander": "^2.x", + "joi": "^12.x", + "lcov-parse": "^1.x", + "lodash": "^4.17.4", + "log-driver": "^1.x", + "request": "^2.83.0", + "request-promise": "^4.x" } }, "color-convert": { @@ -674,7 +674,7 @@ "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", "dev": true, "requires": { - "color-name": "1.1.3" + "color-name": "^1.1.1" } }, "color-name": { @@ -695,7 +695,7 @@ "integrity": "sha1-RYwH4J4NkA/Ci3Cj/sLazR0st/Y=", "dev": true, "requires": { - "lodash": "4.17.5" + "lodash": "^4.5.0" } }, "combine-source-map": { @@ -704,10 +704,10 @@ "integrity": "sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos=", "dev": true, "requires": { - "convert-source-map": "1.1.3", - "inline-source-map": "0.6.2", - "lodash.memoize": "3.0.4", - "source-map": "0.5.7" + "convert-source-map": "~1.1.0", + "inline-source-map": "~0.6.0", + "lodash.memoize": "~3.0.3", + "source-map": "~0.5.3" }, "dependencies": { "convert-source-map": { @@ -730,7 +730,7 @@ "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", "dev": true, "requires": { - "delayed-stream": "1.0.0" + "delayed-stream": "~1.0.0" } }, "commander": { @@ -771,7 +771,7 @@ "requires": { "debug": "2.6.9", "finalhandler": "1.1.0", - "parseurl": "1.3.2", + "parseurl": "~1.3.2", "utils-merge": "1.0.1" } }, @@ -781,7 +781,7 @@ "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", "dev": true, "requires": { - "date-now": "0.1.4" + "date-now": "^0.1.4" } }, "constants-browserify": { @@ -826,8 +826,8 @@ "integrity": "sha1-iIxyNZbN92EvZJgjPuvXo1MBc30=", "dev": true, "requires": { - "bn.js": "4.11.8", - "elliptic": "6.4.0" + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" } }, "create-hash": { @@ -836,10 +836,10 @@ "integrity": "sha1-YGBCrIuSYnUPSDyt2rD1gZFy2P0=", "dev": true, "requires": { - "cipher-base": "1.0.4", - "inherits": "2.0.3", - "ripemd160": "2.0.1", - "sha.js": "2.4.11" + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "sha.js": "^2.4.0" } }, "create-hmac": { @@ -848,12 +848,12 @@ "integrity": "sha1-rLniIaThe9sHbpBlfEK5PjcmzwY=", "dev": true, "requires": { - "cipher-base": "1.0.4", - "create-hash": "1.1.3", - "inherits": "2.0.3", - "ripemd160": "2.0.1", - "safe-buffer": "5.1.1", - "sha.js": "2.4.11" + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" } }, "cryptiles": { @@ -862,7 +862,7 @@ "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", "dev": true, "requires": { - "boom": "5.2.0" + "boom": "5.x.x" }, "dependencies": { "boom": { @@ -871,7 +871,7 @@ "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", "dev": true, "requires": { - "hoek": "4.2.1" + "hoek": "4.x.x" } } } @@ -882,17 +882,17 @@ "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", "dev": true, "requires": { - "browserify-cipher": "1.0.0", - "browserify-sign": "4.0.4", - "create-ecdh": "4.0.0", - "create-hash": "1.1.3", - "create-hmac": "1.1.6", - "diffie-hellman": "5.0.2", - "inherits": "2.0.3", - "pbkdf2": "3.0.14", - "public-encrypt": "4.0.0", - "randombytes": "2.0.6", - "randomfill": "1.0.4" + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" } }, "currently-unhandled": { @@ -901,7 +901,7 @@ "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", "dev": true, "requires": { - "array-find-index": "1.0.2" + "array-find-index": "^1.0.1" } }, "custom-event": { @@ -916,7 +916,7 @@ "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "dev": true, "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" } }, "date-format": { @@ -937,8 +937,8 @@ "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", "dev": true, "requires": { - "get-stdin": "4.0.1", - "meow": "3.7.0" + "get-stdin": "^4.0.1", + "meow": "^3.3.0" } }, "debug": { @@ -968,7 +968,7 @@ "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", "dev": true, "requires": { - "clone": "1.0.3" + "clone": "^1.0.2" } }, "delayed-stream": { @@ -989,8 +989,8 @@ "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", "dev": true, "requires": { - "inherits": "2.0.3", - "minimalistic-assert": "1.0.0" + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" } }, "di": { @@ -1011,9 +1011,9 @@ "integrity": "sha1-tYNXOScM/ias9jIJn97SoH8gnl4=", "dev": true, "requires": { - "bn.js": "4.11.8", - "miller-rabin": "4.0.1", - "randombytes": "2.0.6" + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" } }, "dom-serialize": { @@ -1022,10 +1022,10 @@ "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", "dev": true, "requires": { - "custom-event": "1.0.1", - "ent": "2.2.0", - "extend": "3.0.1", - "void-elements": "2.0.1" + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" } }, "domain-browser": { @@ -1041,7 +1041,7 @@ "dev": true, "optional": true, "requires": { - "jsbn": "0.1.1" + "jsbn": "~0.1.0" } }, "ee-first": { @@ -1056,13 +1056,13 @@ "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", "dev": true, "requires": { - "bn.js": "4.11.8", - "brorand": "1.1.0", - "hash.js": "1.1.3", - "hmac-drbg": "1.0.1", - "inherits": "2.0.3", - "minimalistic-assert": "1.0.0", - "minimalistic-crypto-utils": "1.0.1" + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" } }, "encodeurl": { @@ -1171,7 +1171,7 @@ "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", "dev": true, "requires": { - "is-arrayish": "0.2.1" + "is-arrayish": "^0.2.1" } }, "escape-html": { @@ -1192,11 +1192,11 @@ "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", "dev": true, "requires": { - "esprima": "2.7.3", - "estraverse": "1.9.3", - "esutils": "2.0.2", - "optionator": "0.8.2", - "source-map": "0.2.0" + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.2.0" } }, "esprima": { @@ -1235,8 +1235,8 @@ "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", "dev": true, "requires": { - "md5.js": "1.3.4", - "safe-buffer": "5.1.1" + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" } }, "expand-braces": { @@ -1245,9 +1245,9 @@ "integrity": "sha1-SIsdHSRRyz06axks/AMPRMWFX+o=", "dev": true, "requires": { - "array-slice": "0.2.3", - "array-unique": "0.2.1", - "braces": "0.1.5" + "array-slice": "^0.2.3", + "array-unique": "^0.2.1", + "braces": "^0.1.2" }, "dependencies": { "braces": { @@ -1256,7 +1256,7 @@ "integrity": "sha1-wIVxEIUpHYt1/ddOqw+FlygHEeY=", "dev": true, "requires": { - "expand-range": "0.1.1" + "expand-range": "^0.1.0" } }, "expand-range": { @@ -1265,8 +1265,8 @@ "integrity": "sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ=", "dev": true, "requires": { - "is-number": "0.1.1", - "repeat-string": "0.2.2" + "is-number": "^0.1.1", + "repeat-string": "^0.2.2" } }, "is-number": { @@ -1289,7 +1289,7 @@ "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", "dev": true, "requires": { - "is-posix-bracket": "0.1.1" + "is-posix-bracket": "^0.1.0" } }, "expand-range": { @@ -1298,7 +1298,7 @@ "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", "dev": true, "requires": { - "fill-range": "2.2.3" + "fill-range": "^2.1.0" } }, "extend": { @@ -1313,7 +1313,7 @@ "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", "dev": true, "requires": { - "kind-of": "1.1.0" + "kind-of": "^1.1.0" }, "dependencies": { "kind-of": { @@ -1330,7 +1330,7 @@ "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", "dev": true, "requires": { - "is-extglob": "1.0.0" + "is-extglob": "^1.0.0" } }, "extsprintf": { @@ -1369,11 +1369,11 @@ "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", "dev": true, "requires": { - "is-number": "2.1.0", - "isobject": "2.1.0", - "randomatic": "1.1.7", - "repeat-element": "1.1.2", - "repeat-string": "1.6.1" + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^1.1.3", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" } }, "finalhandler": { @@ -1383,12 +1383,12 @@ "dev": true, "requires": { "debug": "2.6.9", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "on-finished": "2.3.0", - "parseurl": "1.3.2", - "statuses": "1.3.1", - "unpipe": "1.0.0" + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.3.1", + "unpipe": "~1.0.0" }, "dependencies": { "statuses": { @@ -1405,8 +1405,8 @@ "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "dev": true, "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "for-in": { @@ -1421,7 +1421,7 @@ "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", "dev": true, "requires": { - "for-in": "1.0.2" + "for-in": "^1.0.1" } }, "forever-agent": { @@ -1436,9 +1436,9 @@ "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", "dev": true, "requires": { - "asynckit": "0.4.0", + "asynckit": "^0.4.0", "combined-stream": "1.0.6", - "mime-types": "2.1.18" + "mime-types": "^2.1.12" } }, "fs-access": { @@ -1447,7 +1447,7 @@ "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", "dev": true, "requires": { - "null-check": "1.0.0" + "null-check": "^1.0.0" } }, "fs.realpath": { @@ -1463,8 +1463,8 @@ "dev": true, "optional": true, "requires": { - "nan": "2.10.0", - "node-pre-gyp": "0.6.39" + "nan": "^2.3.0", + "node-pre-gyp": "^0.6.39" }, "dependencies": { "abbrev": { @@ -1552,6 +1552,7 @@ "version": "0.0.9", "bundled": true, "dev": true, + "optional": true, "requires": { "inherits": "2.0.3" } @@ -1576,7 +1577,8 @@ "buffer-shims": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "caseless": { "version": "0.12.0", @@ -1593,12 +1595,14 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "combined-stream": { "version": "1.0.5", "bundled": true, "dev": true, + "optional": true, "requires": { "delayed-stream": "1.0.0" } @@ -1611,17 +1615,20 @@ "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "cryptiles": { "version": "2.0.5", "bundled": true, "dev": true, + "optional": true, "requires": { "boom": "2.10.1" } @@ -1661,7 +1668,8 @@ "delayed-stream": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "delegates": { "version": "1.0.0", @@ -1693,7 +1701,8 @@ "extsprintf": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "forever-agent": { "version": "0.6.1", @@ -1816,6 +1825,7 @@ "version": "3.1.3", "bundled": true, "dev": true, + "optional": true, "requires": { "boom": "2.10.1", "cryptiles": "2.0.5", @@ -1863,6 +1873,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "1.0.1" } @@ -1876,7 +1887,8 @@ "isarray": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "isstream": { "version": "0.1.2", @@ -1949,12 +1961,14 @@ "mime-db": { "version": "1.27.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "mime-types": { "version": "2.1.15", "bundled": true, "dev": true, + "optional": true, "requires": { "mime-db": "1.27.0" } @@ -2030,7 +2044,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "oauth-sign": { "version": "0.8.2", @@ -2088,7 +2103,8 @@ "process-nextick-args": { "version": "1.0.7", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "punycode": { "version": "1.4.1", @@ -2126,6 +2142,7 @@ "version": "2.2.9", "bundled": true, "dev": true, + "optional": true, "requires": { "buffer-shims": "1.0.0", "core-util-is": "1.0.2", @@ -2177,7 +2194,8 @@ "safe-buffer": { "version": "5.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "semver": { "version": "5.3.0", @@ -2201,6 +2219,7 @@ "version": "1.0.9", "bundled": true, "dev": true, + "optional": true, "requires": { "hoek": "2.16.3" } @@ -2234,6 +2253,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "1.1.0", "is-fullwidth-code-point": "1.0.0", @@ -2244,6 +2264,7 @@ "version": "1.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "5.0.1" } @@ -2272,6 +2293,7 @@ "version": "2.2.1", "bundled": true, "dev": true, + "optional": true, "requires": { "block-stream": "0.0.9", "fstream": "1.0.11", @@ -2327,7 +2349,8 @@ "util-deprecate": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "uuid": { "version": "3.0.1", @@ -2372,7 +2395,7 @@ "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "dev": true, "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" } }, "glob": { @@ -2381,11 +2404,11 @@ "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", "dev": true, "requires": { - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "glob-base": { @@ -2394,8 +2417,8 @@ "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", "dev": true, "requires": { - "glob-parent": "2.0.0", - "is-glob": "2.0.1" + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" } }, "glob-parent": { @@ -2404,7 +2427,7 @@ "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", "dev": true, "requires": { - "is-glob": "2.0.1" + "is-glob": "^2.0.0" } }, "graceful-fs": { @@ -2419,10 +2442,10 @@ "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", "dev": true, "requires": { - "async": "1.5.2", - "optimist": "0.6.1", - "source-map": "0.4.4", - "uglify-js": "2.8.29" + "async": "^1.4.0", + "optimist": "^0.6.1", + "source-map": "^0.4.4", + "uglify-js": "^2.6" }, "dependencies": { "source-map": { @@ -2431,7 +2454,7 @@ "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", "dev": true, "requires": { - "amdefine": "1.0.1" + "amdefine": ">=0.0.4" } } } @@ -2448,8 +2471,8 @@ "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", "dev": true, "requires": { - "ajv": "5.5.2", - "har-schema": "2.0.0" + "ajv": "^5.1.0", + "har-schema": "^2.0.0" } }, "has-ansi": { @@ -2458,7 +2481,7 @@ "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "has-binary": { @@ -2496,7 +2519,7 @@ "integrity": "sha1-ZuodhW206KVHDK32/OI65SRO8uE=", "dev": true, "requires": { - "inherits": "2.0.3" + "inherits": "^2.0.1" } }, "hash.js": { @@ -2505,8 +2528,8 @@ "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", "dev": true, "requires": { - "inherits": "2.0.3", - "minimalistic-assert": "1.0.0" + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.0" } }, "hawk": { @@ -2515,10 +2538,10 @@ "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", "dev": true, "requires": { - "boom": "4.3.1", - "cryptiles": "3.1.2", - "hoek": "4.2.1", - "sntp": "2.1.0" + "boom": "4.x.x", + "cryptiles": "3.x.x", + "hoek": "4.x.x", + "sntp": "2.x.x" } }, "hmac-drbg": { @@ -2527,9 +2550,9 @@ "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", "dev": true, "requires": { - "hash.js": "1.1.3", - "minimalistic-assert": "1.0.0", - "minimalistic-crypto-utils": "1.0.1" + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" } }, "hoek": { @@ -2553,7 +2576,7 @@ "depd": "1.1.1", "inherits": "2.0.3", "setprototypeof": "1.0.3", - "statuses": "1.4.0" + "statuses": ">= 1.3.1 < 2" }, "dependencies": { "depd": { @@ -2570,8 +2593,8 @@ "integrity": "sha1-Bt/ykpUr9k2+hHH6nfcwZtTzd0I=", "dev": true, "requires": { - "eventemitter3": "1.2.0", - "requires-port": "1.0.0" + "eventemitter3": "1.x.x", + "requires-port": "1.x.x" } }, "http-signature": { @@ -2580,9 +2603,9 @@ "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "dev": true, "requires": { - "assert-plus": "1.0.0", - "jsprim": "1.4.1", - "sshpk": "1.14.1" + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" } }, "https-browserify": { @@ -2609,7 +2632,7 @@ "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", "dev": true, "requires": { - "repeating": "2.0.1" + "repeating": "^2.0.0" } }, "indexof": { @@ -2624,8 +2647,8 @@ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -2640,7 +2663,7 @@ "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=", "dev": true, "requires": { - "source-map": "0.5.7" + "source-map": "~0.5.3" }, "dependencies": { "source-map": { @@ -2663,7 +2686,7 @@ "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", "dev": true, "requires": { - "binary-extensions": "1.11.0" + "binary-extensions": "^1.0.0" } }, "is-buffer": { @@ -2678,7 +2701,7 @@ "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "dev": true, "requires": { - "builtin-modules": "1.1.1" + "builtin-modules": "^1.0.0" } }, "is-dotfile": { @@ -2693,7 +2716,7 @@ "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", "dev": true, "requires": { - "is-primitive": "2.0.0" + "is-primitive": "^2.0.0" } }, "is-extendable": { @@ -2714,7 +2737,7 @@ "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", "dev": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "is-glob": { @@ -2723,7 +2746,7 @@ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, "requires": { - "is-extglob": "1.0.0" + "is-extglob": "^1.0.0" } }, "is-number": { @@ -2732,7 +2755,7 @@ "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" } }, "is-posix-bracket": { @@ -2777,7 +2800,7 @@ "integrity": "sha512-mVjAjvdPkpwXW61agT2E9AkGoegZO7SdJGCezWwxnETL58f5KwJ4vSVAMBUL5idL6rTlYAIGkX3n4suiviMLNw==", "dev": true, "requires": { - "punycode": "2.1.0" + "punycode": "2.x.x" } }, "isexe": { @@ -2807,20 +2830,20 @@ "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", "dev": true, "requires": { - "abbrev": "1.0.9", - "async": "1.5.2", - "escodegen": "1.8.1", - "esprima": "2.7.3", - "glob": "5.0.15", - "handlebars": "4.0.11", - "js-yaml": "3.11.0", - "mkdirp": "0.5.1", - "nopt": "3.0.6", - "once": "1.4.0", - "resolve": "1.1.7", - "supports-color": "3.2.3", - "which": "1.3.0", - "wordwrap": "1.0.0" + "abbrev": "1.0.x", + "async": "1.x", + "escodegen": "1.8.x", + "esprima": "2.7.x", + "glob": "^5.0.15", + "handlebars": "^4.0.1", + "js-yaml": "3.x", + "mkdirp": "0.5.x", + "nopt": "3.x", + "once": "1.x", + "resolve": "1.1.x", + "supports-color": "^3.1.0", + "which": "^1.1.1", + "wordwrap": "^1.0.0" } }, "jasmine-core": { @@ -2835,9 +2858,9 @@ "integrity": "sha512-z0FNlV4NGgjQN1fdtHYXf5kmgludM65fG/JlXzU6+rwkt9U5UWuXVYnXa2FpK0u6+qBuCmrm5byPNuiiddAHvQ==", "dev": true, "requires": { - "hoek": "4.2.1", - "isemail": "3.1.1", - "topo": "2.0.2" + "hoek": "4.x.x", + "isemail": "3.x.x", + "topo": "2.x.x" } }, "js-tokens": { @@ -2852,8 +2875,8 @@ "integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==", "dev": true, "requires": { - "argparse": "1.0.10", - "esprima": "4.0.0" + "argparse": "^1.0.7", + "esprima": "^4.0.0" }, "dependencies": { "esprima": { @@ -2913,33 +2936,33 @@ "integrity": "sha512-k5pBjHDhmkdaUccnC7gE3mBzZjcxyxYsYVaqiL2G5AqlfLyBO5nw2VdNK+O16cveEPd/gIOWULH7gkiYYwVNHg==", "dev": true, "requires": { - "bluebird": "3.5.1", - "body-parser": "1.18.2", - "chokidar": "1.7.0", - "colors": "1.2.1", - "combine-lists": "1.0.1", - "connect": "3.6.6", - "core-js": "2.5.3", - "di": "0.0.1", - "dom-serialize": "2.2.1", - "expand-braces": "0.1.2", - "glob": "7.1.2", - "graceful-fs": "4.1.11", - "http-proxy": "1.16.2", - "isbinaryfile": "3.0.2", - "lodash": "3.10.1", - "log4js": "0.6.38", - "mime": "1.6.0", - "minimatch": "3.0.4", - "optimist": "0.6.1", - "qjobs": "1.2.0", - "range-parser": "1.2.0", - "rimraf": "2.6.2", - "safe-buffer": "5.1.1", + "bluebird": "^3.3.0", + "body-parser": "^1.16.1", + "chokidar": "^1.4.1", + "colors": "^1.1.0", + "combine-lists": "^1.0.0", + "connect": "^3.6.0", + "core-js": "^2.2.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.0", + "expand-braces": "^0.1.1", + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "http-proxy": "^1.13.0", + "isbinaryfile": "^3.0.0", + "lodash": "^3.8.0", + "log4js": "^0.6.31", + "mime": "^1.3.4", + "minimatch": "^3.0.2", + "optimist": "^0.6.1", + "qjobs": "^1.1.4", + "range-parser": "^1.2.0", + "rimraf": "^2.6.0", + "safe-buffer": "^5.0.1", "socket.io": "1.7.3", - "source-map": "0.5.7", + "source-map": "^0.5.3", "tmp": "0.0.31", - "useragent": "2.3.0" + "useragent": "^2.1.12" }, "dependencies": { "glob": { @@ -2948,12 +2971,12 @@ "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "lodash": { @@ -2976,8 +2999,8 @@ "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", "dev": true, "requires": { - "fs-access": "1.0.1", - "which": "1.3.0" + "fs-access": "^1.0.0", + "which": "^1.2.1" } }, "karma-coverage": { @@ -2986,11 +3009,11 @@ "integrity": "sha1-Wv+LOc9plNwi3kyENix2ABtjfPY=", "dev": true, "requires": { - "dateformat": "1.0.12", - "istanbul": "0.4.5", - "lodash": "3.10.1", - "minimatch": "3.0.4", - "source-map": "0.5.7" + "dateformat": "^1.0.6", + "istanbul": "^0.4.0", + "lodash": "^3.8.0", + "minimatch": "^3.0.0", + "source-map": "^0.5.1" }, "dependencies": { "lodash": { @@ -3019,45 +3042,45 @@ "integrity": "sha1-qiy90RFEKgnG28uq6vSEmWVMaRM=", "dev": true, "requires": { - "acorn": "4.0.13", - "assert": "1.4.1", - "async": "2.6.0", - "browser-resolve": "1.11.2", - "browserify-zlib": "0.2.0", - "buffer": "5.1.0", - "combine-source-map": "0.8.0", - "console-browserify": "1.1.0", - "constants-browserify": "1.0.0", - "convert-source-map": "1.5.1", - "crypto-browserify": "3.12.0", - "diff": "3.5.0", - "domain-browser": "1.2.0", - "events": "1.1.1", - "glob": "7.1.2", - "https-browserify": "1.0.0", + "acorn": "^4.0.4", + "assert": "^1.4.1", + "async": "^2.1.4", + "browser-resolve": "^1.11.0", + "browserify-zlib": "^0.2.0", + "buffer": "^5.0.6", + "combine-source-map": "^0.8.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "convert-source-map": "^1.5.0", + "crypto-browserify": "^3.11.1", + "diff": "^3.2.0", + "domain-browser": "^1.1.7", + "events": "^1.1.1", + "glob": "^7.1.1", + "https-browserify": "^1.0.0", "istanbul": "0.4.5", - "json-stringify-safe": "5.0.1", - "karma-coverage": "1.1.1", - "lodash": "4.17.5", - "log4js": "1.1.1", - "minimatch": "3.0.4", - "os-browserify": "0.3.0", - "pad": "2.0.3", + "json-stringify-safe": "^5.0.1", + "karma-coverage": "^1.1.1", + "lodash": "^4.17.4", + "log4js": "^1.1.1", + "minimatch": "^3.0.3", + "os-browserify": "^0.3.0", + "pad": "^2.0.0", "path-browserify": "0.0.0", - "process": "0.11.10", - "punycode": "1.4.1", - "querystring-es3": "0.2.1", - "readable-stream": "2.3.5", - "remap-istanbul": "0.10.1", + "process": "^0.11.10", + "punycode": "^1.4.1", + "querystring-es3": "^0.2.1", + "readable-stream": "^2.3.3", + "remap-istanbul": "^0.10.1", "source-map": "0.6.1", - "stream-browserify": "2.0.1", - "stream-http": "2.8.1", - "string_decoder": "1.0.3", - "timers-browserify": "2.0.6", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.3", + "timers-browserify": "^2.0.2", "tmp": "0.0.29", "tty-browserify": "0.0.0", - "url": "0.11.0", - "util": "0.10.3", + "url": "^0.11.0", + "util": "^0.10.3", "vm-browserify": "0.0.4" }, "dependencies": { @@ -3067,7 +3090,7 @@ "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", "dev": true, "requires": { - "lodash": "4.17.5" + "lodash": "^4.14.0" } }, "glob": { @@ -3076,12 +3099,12 @@ "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "log4js": { @@ -3090,9 +3113,9 @@ "integrity": "sha1-wh0px2BAieTyVYM+f5SzRh3h/0M=", "dev": true, "requires": { - "debug": "2.6.9", - "semver": "5.5.0", - "streamroller": "0.4.1" + "debug": "^2.2.0", + "semver": "^5.3.0", + "streamroller": "^0.4.0" } }, "punycode": { @@ -3119,7 +3142,7 @@ "integrity": "sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA=", "dev": true, "requires": { - "os-tmpdir": "1.0.2" + "os-tmpdir": "~1.0.1" } } } @@ -3130,7 +3153,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } }, "lazy-cache": { @@ -3152,8 +3175,8 @@ "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", "dev": true, "requires": { - "prelude-ls": "1.1.2", - "type-check": "0.3.2" + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" } }, "load-json-file": { @@ -3162,11 +3185,11 @@ "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "strip-bom": "2.0.0" + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" } }, "lodash": { @@ -3193,8 +3216,8 @@ "integrity": "sha1-LElBFmldb7JUgJQ9P8hy5mKlIv0=", "dev": true, "requires": { - "readable-stream": "1.0.34", - "semver": "4.3.6" + "readable-stream": "~1.0.2", + "semver": "~4.3.3" }, "dependencies": { "isarray": { @@ -3209,10 +3232,10 @@ "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", "isarray": "0.0.1", - "string_decoder": "0.10.31" + "string_decoder": "~0.10.x" } }, "string_decoder": { @@ -3235,8 +3258,8 @@ "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", "dev": true, "requires": { - "currently-unhandled": "0.4.1", - "signal-exit": "3.0.2" + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" } }, "lru-cache": { @@ -3245,8 +3268,8 @@ "integrity": "sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==", "dev": true, "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" } }, "map-obj": { @@ -3261,8 +3284,8 @@ "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", "dev": true, "requires": { - "hash-base": "3.0.4", - "inherits": "2.0.3" + "hash-base": "^3.0.0", + "inherits": "^2.0.1" }, "dependencies": { "hash-base": { @@ -3271,8 +3294,8 @@ "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", "dev": true, "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.1" + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" } } } @@ -3289,16 +3312,16 @@ "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "dev": true, "requires": { - "camelcase-keys": "2.1.0", - "decamelize": "1.2.0", - "loud-rejection": "1.6.0", - "map-obj": "1.0.1", - "minimist": "1.2.0", - "normalize-package-data": "2.4.0", - "object-assign": "4.1.0", - "read-pkg-up": "1.0.1", - "redent": "1.0.0", - "trim-newlines": "1.0.0" + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" }, "dependencies": { "minimist": { @@ -3315,19 +3338,19 @@ "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", "dev": true, "requires": { - "arr-diff": "2.0.0", - "array-unique": "0.2.1", - "braces": "1.8.5", - "expand-brackets": "0.1.5", - "extglob": "0.3.2", - "filename-regex": "2.0.1", - "is-extglob": "1.0.0", - "is-glob": "2.0.1", - "kind-of": "3.2.2", - "normalize-path": "2.1.1", - "object.omit": "2.0.1", - "parse-glob": "3.0.4", - "regex-cache": "0.4.4" + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" } }, "miller-rabin": { @@ -3336,8 +3359,8 @@ "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", "dev": true, "requires": { - "bn.js": "4.11.8", - "brorand": "1.1.0" + "bn.js": "^4.0.0", + "brorand": "^1.0.1" } }, "mime": { @@ -3358,7 +3381,7 @@ "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", "dev": true, "requires": { - "mime-db": "1.33.0" + "mime-db": "~1.33.0" } }, "minimalistic-assert": { @@ -3379,7 +3402,7 @@ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { - "brace-expansion": "1.1.11" + "brace-expansion": "^1.1.7" } }, "minimist": { @@ -3430,7 +3453,7 @@ "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", "dev": true, "requires": { - "abbrev": "1.0.9" + "abbrev": "1" } }, "normalize-package-data": { @@ -3439,10 +3462,10 @@ "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", "dev": true, "requires": { - "hosted-git-info": "2.6.0", - "is-builtin-module": "1.0.0", - "semver": "4.3.6", - "validate-npm-package-license": "3.0.3" + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" } }, "normalize-path": { @@ -3451,7 +3474,7 @@ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, "requires": { - "remove-trailing-separator": "1.1.0" + "remove-trailing-separator": "^1.0.1" } }, "null-check": { @@ -3490,8 +3513,8 @@ "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", "dev": true, "requires": { - "for-own": "0.1.5", - "is-extendable": "0.1.1" + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" } }, "on-finished": { @@ -3509,7 +3532,7 @@ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "optimist": { @@ -3518,8 +3541,8 @@ "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", "dev": true, "requires": { - "minimist": "0.0.10", - "wordwrap": "0.0.3" + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" }, "dependencies": { "wordwrap": { @@ -3536,12 +3559,12 @@ "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", "dev": true, "requires": { - "deep-is": "0.1.3", - "fast-levenshtein": "2.0.6", - "levn": "0.3.0", - "prelude-ls": "1.1.2", - "type-check": "0.3.2", - "wordwrap": "1.0.0" + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" } }, "options": { @@ -3568,7 +3591,7 @@ "integrity": "sha512-YVlBmpDQilhUl69RY/0Ku9Il0sNPLgVOVePhCJUfN8qDZKrq1zIY+ZwtCLtaWFtJzuJswwAcZHxf4a5RliV2pQ==", "dev": true, "requires": { - "wcwidth": "1.0.1" + "wcwidth": "^1.0.1" } }, "pako": { @@ -3583,11 +3606,11 @@ "integrity": "sha1-N8T5t+06tlx0gXtfJICTf7+XxxI=", "dev": true, "requires": { - "asn1.js": "4.10.1", - "browserify-aes": "1.1.1", - "create-hash": "1.1.3", - "evp_bytestokey": "1.0.3", - "pbkdf2": "3.0.14" + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3" } }, "parse-glob": { @@ -3596,10 +3619,10 @@ "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", "dev": true, "requires": { - "glob-base": "0.3.0", - "is-dotfile": "1.0.3", - "is-extglob": "1.0.0", - "is-glob": "2.0.1" + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" } }, "parse-json": { @@ -3608,7 +3631,7 @@ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { - "error-ex": "1.3.1" + "error-ex": "^1.2.0" } }, "parsejson": { @@ -3617,7 +3640,7 @@ "integrity": "sha1-q343WfIJ7OmUN5c/fQ8fZK4OZKs=", "dev": true, "requires": { - "better-assert": "1.0.2" + "better-assert": "~1.0.0" } }, "parseqs": { @@ -3626,7 +3649,7 @@ "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", "dev": true, "requires": { - "better-assert": "1.0.2" + "better-assert": "~1.0.0" } }, "parseuri": { @@ -3635,7 +3658,7 @@ "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", "dev": true, "requires": { - "better-assert": "1.0.2" + "better-assert": "~1.0.0" } }, "parseurl": { @@ -3656,7 +3679,7 @@ "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "dev": true, "requires": { - "pinkie-promise": "2.0.1" + "pinkie-promise": "^2.0.0" } }, "path-is-absolute": { @@ -3677,9 +3700,9 @@ "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "pbkdf2": { @@ -3688,11 +3711,11 @@ "integrity": "sha512-gjsZW9O34fm0R7PaLHRJmLLVfSoesxztjPjE9o6R+qtVJij90ltg1joIovN9GKrRW3t1PzhDDG3UMEMFfZ+1wA==", "dev": true, "requires": { - "create-hash": "1.1.3", - "create-hmac": "1.1.6", - "ripemd160": "2.0.1", - "safe-buffer": "5.1.1", - "sha.js": "2.4.11" + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" } }, "performance-now": { @@ -3719,7 +3742,7 @@ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "dev": true, "requires": { - "pinkie": "2.0.4" + "pinkie": "^2.0.0" } }, "plugin-error": { @@ -3728,11 +3751,11 @@ "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", "dev": true, "requires": { - "ansi-cyan": "0.1.1", - "ansi-red": "0.1.1", - "arr-diff": "1.1.0", - "arr-union": "2.1.0", - "extend-shallow": "1.1.4" + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" }, "dependencies": { "arr-diff": { @@ -3741,8 +3764,8 @@ "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", "dev": true, "requires": { - "arr-flatten": "1.1.0", - "array-slice": "0.2.3" + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" } } } @@ -3783,11 +3806,11 @@ "integrity": "sha1-OfaZ86RlYN1eusvKaTyvfGXBjMY=", "dev": true, "requires": { - "bn.js": "4.11.8", - "browserify-rsa": "4.0.1", - "create-hash": "1.1.3", - "parse-asn1": "5.1.0", - "randombytes": "2.0.6" + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1" } }, "punycode": { @@ -3831,8 +3854,8 @@ "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", "dev": true, "requires": { - "is-number": "3.0.0", - "kind-of": "4.0.0" + "is-number": "^3.0.0", + "kind-of": "^4.0.0" }, "dependencies": { "is-number": { @@ -3841,7 +3864,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -3850,7 +3873,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -3861,7 +3884,7 @@ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -3872,7 +3895,7 @@ "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "^5.1.0" } }, "randomfill": { @@ -3881,8 +3904,8 @@ "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", "dev": true, "requires": { - "randombytes": "2.0.6", - "safe-buffer": "5.1.1" + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" } }, "range-parser": { @@ -3909,9 +3932,9 @@ "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", "dev": true, "requires": { - "load-json-file": "1.1.0", - "normalize-package-data": "2.4.0", - "path-type": "1.1.0" + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" } }, "read-pkg-up": { @@ -3920,8 +3943,8 @@ "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", "dev": true, "requires": { - "find-up": "1.1.2", - "read-pkg": "1.1.0" + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" } }, "readable-stream": { @@ -3930,13 +3953,13 @@ "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.0.3", + "util-deprecate": "~1.0.1" } }, "readdirp": { @@ -3945,10 +3968,10 @@ "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "minimatch": "3.0.4", - "readable-stream": "2.3.5", - "set-immediate-shim": "1.0.1" + "graceful-fs": "^4.1.2", + "minimatch": "^3.0.2", + "readable-stream": "^2.0.2", + "set-immediate-shim": "^1.0.1" } }, "redent": { @@ -3957,8 +3980,8 @@ "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", "dev": true, "requires": { - "indent-string": "2.1.0", - "strip-indent": "1.0.1" + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" } }, "regex-cache": { @@ -3967,7 +3990,7 @@ "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", "dev": true, "requires": { - "is-equal-shallow": "0.1.3" + "is-equal-shallow": "^0.1.3" } }, "remap-istanbul": { @@ -3976,11 +3999,11 @@ "integrity": "sha512-gsNQXs5kJLhErICSyYhzVZ++C8LBW8dgwr874Y2QvzAUS75zBlD/juZgXs39nbYJ09fZDlX2AVLVJAY2jbFJoQ==", "dev": true, "requires": { - "amdefine": "1.0.1", + "amdefine": "^1.0.0", "istanbul": "0.4.5", - "minimatch": "3.0.4", - "plugin-error": "0.1.2", - "source-map": "0.6.1", + "minimatch": "^3.0.3", + "plugin-error": "^0.1.2", + "source-map": "^0.6.1", "through2": "2.0.1" }, "dependencies": { @@ -4016,7 +4039,7 @@ "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "dev": true, "requires": { - "is-finite": "1.0.2" + "is-finite": "^1.0.0" } }, "request": { @@ -4025,28 +4048,28 @@ "integrity": "sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==", "dev": true, "requires": { - "aws-sign2": "0.7.0", - "aws4": "1.6.0", - "caseless": "0.12.0", - "combined-stream": "1.0.6", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.3.2", - "har-validator": "5.0.3", - "hawk": "6.0.2", - "http-signature": "1.2.0", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.18", - "oauth-sign": "0.8.2", - "performance-now": "2.1.0", - "qs": "6.5.1", - "safe-buffer": "5.1.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.4", - "tunnel-agent": "0.6.0", - "uuid": "3.2.1" + "aws-sign2": "~0.7.0", + "aws4": "^1.6.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.1", + "forever-agent": "~0.6.1", + "form-data": "~2.3.1", + "har-validator": "~5.0.3", + "hawk": "~6.0.2", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.17", + "oauth-sign": "~0.8.2", + "performance-now": "^2.1.0", + "qs": "~6.5.1", + "safe-buffer": "^5.1.1", + "stringstream": "~0.0.5", + "tough-cookie": "~2.3.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.1.0" } }, "request-promise": { @@ -4055,10 +4078,10 @@ "integrity": "sha1-0epG1lSm7k+O5qT+oQGMIpEZBLQ=", "dev": true, "requires": { - "bluebird": "3.5.1", + "bluebird": "^3.5.0", "request-promise-core": "1.1.1", - "stealthy-require": "1.1.1", - "tough-cookie": "2.3.4" + "stealthy-require": "^1.1.0", + "tough-cookie": ">=2.3.3" } }, "request-promise-core": { @@ -4067,7 +4090,7 @@ "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", "dev": true, "requires": { - "lodash": "4.17.5" + "lodash": "^4.13.1" } }, "requires-port": { @@ -4089,7 +4112,7 @@ "dev": true, "optional": true, "requires": { - "align-text": "0.1.4" + "align-text": "^0.1.1" } }, "rimraf": { @@ -4098,7 +4121,7 @@ "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "dev": true, "requires": { - "glob": "7.1.2" + "glob": "^7.0.5" }, "dependencies": { "glob": { @@ -4107,12 +4130,12 @@ "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } } } @@ -4123,8 +4146,8 @@ "integrity": "sha1-D0WEKVxTo2KK9+bXmsohzlfRxuc=", "dev": true, "requires": { - "hash-base": "2.0.2", - "inherits": "2.0.3" + "hash-base": "^2.0.0", + "inherits": "^2.0.1" } }, "safe-buffer": { @@ -4163,8 +4186,8 @@ "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.1" + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" } }, "signal-exit": { @@ -4179,7 +4202,7 @@ "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", "dev": true, "requires": { - "hoek": "4.2.1" + "hoek": "4.x.x" } }, "socket.io": { @@ -4325,7 +4348,7 @@ "dev": true, "optional": true, "requires": { - "amdefine": "1.0.1" + "amdefine": ">=0.0.4" } }, "spdx-correct": { @@ -4334,8 +4357,8 @@ "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", "dev": true, "requires": { - "spdx-expression-parse": "3.0.0", - "spdx-license-ids": "3.0.0" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, "spdx-exceptions": { @@ -4350,8 +4373,8 @@ "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", "dev": true, "requires": { - "spdx-exceptions": "2.1.0", - "spdx-license-ids": "3.0.0" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, "spdx-license-ids": { @@ -4372,14 +4395,14 @@ "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", "dev": true, "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "tweetnacl": "~0.14.0" } }, "statuses": { @@ -4400,8 +4423,8 @@ "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", "dev": true, "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.5" + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" } }, "stream-http": { @@ -4410,11 +4433,11 @@ "integrity": "sha512-cQ0jo17BLca2r0GfRdZKYAGLU6JRoIWxqSOakUMuKOT6MOK7AAlE856L33QuDmAy/eeOrhLee3dZKX0Uadu93A==", "dev": true, "requires": { - "builtin-status-codes": "3.0.0", - "inherits": "2.0.3", - "readable-stream": "2.3.5", - "to-arraybuffer": "1.0.1", - "xtend": "4.0.1" + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.3", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" } }, "streamroller": { @@ -4423,10 +4446,10 @@ "integrity": "sha1-1DW9WXQ3Or2b2QaDWVEwhRBswF8=", "dev": true, "requires": { - "date-format": "0.0.0", - "debug": "0.7.4", - "mkdirp": "0.5.1", - "readable-stream": "1.1.14" + "date-format": "^0.0.0", + "debug": "^0.7.2", + "mkdirp": "^0.5.1", + "readable-stream": "^1.1.7" }, "dependencies": { "debug": { @@ -4447,10 +4470,10 @@ "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", "isarray": "0.0.1", - "string_decoder": "0.10.31" + "string_decoder": "~0.10.x" } }, "string_decoder": { @@ -4467,7 +4490,7 @@ "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } }, "stringstream": { @@ -4482,7 +4505,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "strip-bom": { @@ -4491,7 +4514,7 @@ "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, "requires": { - "is-utf8": "0.2.1" + "is-utf8": "^0.2.0" } }, "strip-indent": { @@ -4500,7 +4523,7 @@ "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", "dev": true, "requires": { - "get-stdin": "4.0.1" + "get-stdin": "^4.0.1" } }, "supports-color": { @@ -4509,7 +4532,7 @@ "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", "dev": true, "requires": { - "has-flag": "1.0.0" + "has-flag": "^1.0.0" } }, "through2": { @@ -4518,8 +4541,8 @@ "integrity": "sha1-OE51MU1J8y3hLuu4E2uOtrXVnak=", "dev": true, "requires": { - "readable-stream": "2.0.6", - "xtend": "4.0.1" + "readable-stream": "~2.0.0", + "xtend": "~4.0.0" }, "dependencies": { "process-nextick-args": { @@ -4534,12 +4557,12 @@ "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "0.10.31", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "string_decoder": "~0.10.x", + "util-deprecate": "~1.0.1" } }, "string_decoder": { @@ -4556,7 +4579,7 @@ "integrity": "sha512-HQ3nbYRAowdVd0ckGFvmJPPCOH/CHleFN/Y0YQCX1DVaB7t+KFvisuyN09fuP8Jtp1CpfSh8O8bMkHbdbPe6Pw==", "dev": true, "requires": { - "setimmediate": "1.0.5" + "setimmediate": "^1.0.4" } }, "tmp": { @@ -4565,7 +4588,7 @@ "integrity": "sha1-jzirlDjhcxXl29izZX6L+yd65Kc=", "dev": true, "requires": { - "os-tmpdir": "1.0.2" + "os-tmpdir": "~1.0.1" } }, "to-array": { @@ -4586,7 +4609,7 @@ "integrity": "sha1-zVYVdSU5BXwNwEkaYhw7xvvh0YI=", "dev": true, "requires": { - "hoek": "4.2.1" + "hoek": "4.x.x" } }, "tough-cookie": { @@ -4595,7 +4618,7 @@ "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", "dev": true, "requires": { - "punycode": "1.4.1" + "punycode": "^1.4.1" }, "dependencies": { "punycode": { @@ -4624,18 +4647,18 @@ "integrity": "sha1-ElX4ej/1frCw4fDmEKi0dIBGya4=", "dev": true, "requires": { - "babel-code-frame": "6.26.0", - "builtin-modules": "1.1.1", - "chalk": "2.3.2", - "commander": "2.15.1", - "diff": "3.5.0", - "glob": "7.1.2", - "js-yaml": "3.11.0", - "minimatch": "3.0.4", - "resolve": "1.6.0", - "semver": "5.5.0", - "tslib": "1.9.0", - "tsutils": "2.22.2" + "babel-code-frame": "^6.22.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.7.0", + "minimatch": "^3.0.4", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.12.1" }, "dependencies": { "glob": { @@ -4644,12 +4667,12 @@ "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "resolve": { @@ -4658,7 +4681,7 @@ "integrity": "sha512-mw7JQNu5ExIkcw4LPih0owX/TZXjD/ZUF/ZQ/pDnkw3ZKhDcZZw5klmBlj6gVMwjQ3Pz5Jgu7F3d0jcDVuEWdw==", "dev": true, "requires": { - "path-parse": "1.0.5" + "path-parse": "^1.0.5" } }, "semver": { @@ -4675,7 +4698,7 @@ "integrity": "sha512-u06FUSulCJ+Y8a2ftuqZN6kIGqdP2yJjUPEngXqmdPND4UQfb04igcotH+dw+IFr417yP6muCLE8/5/Qlfnx0w==", "dev": true, "requires": { - "tslib": "1.9.0" + "tslib": "^1.8.1" } }, "tty-browserify": { @@ -4690,7 +4713,7 @@ "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "^5.0.1" } }, "tweetnacl": { @@ -4706,7 +4729,7 @@ "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", "dev": true, "requires": { - "prelude-ls": "1.1.2" + "prelude-ls": "~1.1.2" } }, "type-is": { @@ -4716,7 +4739,7 @@ "dev": true, "requires": { "media-typer": "0.3.0", - "mime-types": "2.1.18" + "mime-types": "~2.1.18" } }, "typescript": { @@ -4732,9 +4755,9 @@ "dev": true, "optional": true, "requires": { - "source-map": "0.5.7", - "uglify-to-browserify": "1.0.2", - "yargs": "3.10.0" + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" }, "dependencies": { "source-map": { @@ -4789,8 +4812,8 @@ "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==", "dev": true, "requires": { - "lru-cache": "4.1.2", - "tmp": "0.0.31" + "lru-cache": "4.1.x", + "tmp": "0.0.x" } }, "util": { @@ -4834,8 +4857,8 @@ "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", "dev": true, "requires": { - "spdx-correct": "3.0.0", - "spdx-expression-parse": "3.0.0" + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" } }, "verror": { @@ -4844,9 +4867,9 @@ "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "dev": true, "requires": { - "assert-plus": "1.0.0", + "assert-plus": "^1.0.0", "core-util-is": "1.0.2", - "extsprintf": "1.3.0" + "extsprintf": "^1.2.0" } }, "vm-browserify": { @@ -4870,7 +4893,7 @@ "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", "dev": true, "requires": { - "defaults": "1.0.3" + "defaults": "^1.0.3" } }, "which": { @@ -4879,7 +4902,7 @@ "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", "dev": true, "requires": { - "isexe": "2.0.0" + "isexe": "^2.0.0" } }, "window-size": { @@ -4907,8 +4930,8 @@ "integrity": "sha1-iiRPoFJAHgjJiGz0SoUYnh/UBn8=", "dev": true, "requires": { - "options": "0.0.6", - "ultron": "1.0.2" + "options": ">=0.0.5", + "ultron": "1.0.x" } }, "wtf-8": { @@ -4942,9 +4965,9 @@ "dev": true, "optional": true, "requires": { - "camelcase": "1.2.1", - "cliui": "2.1.0", - "decamelize": "1.2.0", + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", "window-size": "0.1.0" } }, @@ -4955,4 +4978,4 @@ "dev": true } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 6ce6339..8e10833 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dice-typescript", - "version": "1.4.1", + "version": "1.5.0", "description": "A TypeScript library for parsing dice rolling expressions, most commonly used in tabletop RPGs.", "main": "./dist/index.js", "scripts": { @@ -12,7 +12,7 @@ "lint:fix": "tslint -p ./tslint.json --fix", "tsc": "tsc --project ./tsconfig.build.json", "test": "npm run lint && karma start --single-run", - "test_watch": "karma start" + "test:watch": "karma start" }, "repository": { "type": "git", @@ -52,4 +52,4 @@ "dependencies": { "random-js": "^1.0.8" } -} +} \ No newline at end of file diff --git a/spec/ast/expression-node.spec.ts b/spec/ast/expression-node.spec.ts index 269916f..c7aba2c 100644 --- a/spec/ast/expression-node.spec.ts +++ b/spec/ast/expression-node.spec.ts @@ -1,35 +1,35 @@ import * as Ast from '../../src/ast'; describe('ExpressionNode', () => { - describe('copy', () => { - const root = Ast.Factory.create(Ast.NodeType.Add); - root.addChild(Ast.Factory.create(Ast.NodeType.Dice)) - .setAttribute('times', 4) - .setAttribute('dice', 20); - root.addChild(Ast.Factory.create(Ast.NodeType.Number)) - .setAttribute('value', 10); + describe('copy', () => { + const root = Ast.Factory.create(Ast.NodeType.Add); + root.addChild(Ast.Factory.create(Ast.NodeType.Dice)) + .setAttribute('times', 4) + .setAttribute('dice', 20); + root.addChild(Ast.Factory.create(Ast.NodeType.Number)) + .setAttribute('value', 10); - it('should be equal', () => { - expect(root.copy()).toEqual(root); - }); - it('should not be the same object', () => { - expect(root.copy()).not.toBe(root); - }); + it('should be equal', () => { + expect(root.copy()).toEqual(root); }); - describe('getChild', () => { - it('should throw if index is out of bounds.', () => { - const root = Ast.Factory.create(Ast.NodeType.Number); - expect(() => { - root.getChild(100); - }).toThrow(); - }); + it('should not be the same object', () => { + expect(root.copy()).not.toBe(root); }); - describe('insertChild', () => { - it('should throw when adding a node as a child of itself.', () => { - const root = Ast.Factory.create(Ast.NodeType.Number); - expect(() => { - root.insertChild(root, 0); - }).toThrow(); - }); + }); + describe('getChild', () => { + it('should throw if index is out of bounds.', () => { + const root = Ast.Factory.create(Ast.NodeType.Number); + expect(() => { + root.getChild(100); + }).toThrow(); }); + }); + describe('insertChild', () => { + it('should throw when adding a node as a child of itself.', () => { + const root = Ast.Factory.create(Ast.NodeType.Number); + expect(() => { + root.insertChild(root, 0); + }).toThrow(); + }); + }); }); diff --git a/spec/ast/expression.spec.ts b/spec/ast/expression.spec.ts index 77722bc..82f52c3 100644 --- a/spec/ast/expression.spec.ts +++ b/spec/ast/expression.spec.ts @@ -1,17 +1,17 @@ import * as Ast from '../../src/ast'; describe('Expression', () => { - describe('stringify', () => { - it('should convert to JSON', () => { - const root = Ast.Factory.create(Ast.NodeType.Add); - root.addChild(Ast.Factory.create(Ast.NodeType.Dice)) - .setAttribute('times', 4) - .setAttribute('dice', 20); - root.addChild(Ast.Factory.create(Ast.NodeType.Number)) - .setAttribute('value', 10); - expect(() => { - JSON.stringify(root); - }).not.toThrow(); - }); + describe('stringify', () => { + it('should convert to JSON', () => { + const root = Ast.Factory.create(Ast.NodeType.Add); + root.addChild(Ast.Factory.create(Ast.NodeType.Dice)) + .setAttribute('times', 4) + .setAttribute('dice', 20); + root.addChild(Ast.Factory.create(Ast.NodeType.Number)) + .setAttribute('value', 10); + expect(() => { + JSON.stringify(root); + }).not.toThrow(); }); + }); }); diff --git a/spec/dice.spec.ts b/spec/dice.spec.ts index bf24dcc..cc015a2 100644 --- a/spec/dice.spec.ts +++ b/spec/dice.spec.ts @@ -1,39 +1,44 @@ import { Dice } from '../src'; -import { MockRandomProvider } from './helpers'; +import { MockListRandomProvider } from './helpers'; describe('Dice', () => { - describe('constructor', () => { - it('should not throw', function () { - expect(() => { - const dice = new Dice(); - }).not.toThrow(); - }); + describe('constructor', () => { + it('should not throw', function () { + expect(() => { + const dice = new Dice(); + }).not.toThrow(); }); - describe('roll', () => { - it('returns a number from random', function () { - const dice = new Dice(); - const result = dice.roll('1d20').total; - expect(result).toBeGreaterThanOrEqual(1); - expect(result).toBeLessThanOrEqual(20); - }); - it('succeeds on a complex expression', function () { - const dice = new Dice(); - const result = dice.roll('floor((2d4)d20 / 3) + 6'); - JSON.stringify(result); - }); - it('correctly handles operator precedence regardless of order (10 * 5 + 2) and (2 + 10 * 5)', () => { - const dice = new Dice(); + }); + describe('roll', () => { + it('returns a number from random', function () { + const dice = new Dice(); + const result = dice.roll('1d20').total; + expect(result).toBeGreaterThanOrEqual(1); + expect(result).toBeLessThanOrEqual(20); + }); + it('succeeds on a complex expression', function () { + const dice = new Dice(); + expect(() => dice.roll('floor((2d4)d20 / 3) + 6')).not.toThrow(); + }); + it('correctly handles operator precedence regardless of order (10 * 5 + 2) and (2 + 10 * 5)', () => { + const dice = new Dice(); - const mult1st = dice.roll('10 * 5 + 2'); - const mult2nd = dice.roll('2 + 10 * 5'); + const mult1st = dice.roll('10 * 5 + 2'); + const mult2nd = dice.roll('2 + 10 * 5'); - expect(mult1st.total).toBe(52); - expect(mult2nd.total).toBe(52); - }); - it('correctly factors brackets in operator precedence (10 * (5 + 2))', () => { - const dice = new Dice(); - const exp = dice.roll('10 * (5 + 2)'); - expect(exp.total).toBe(70); - }); + expect(mult1st.total).toBe(52); + expect(mult2nd.total).toBe(52); + }); + it('correctly factors brackets in operator precedence (10 * (5 + 2))', () => { + const dice = new Dice(); + const exp = dice.roll('10 * (5 + 2)'); + expect(exp.total).toBe(70); + }); + it('correctly handles keep lowest (2d10kl)', () => { + const mock = new MockListRandomProvider([5, 4]); + const dice = new Dice(null, mock); + const exp = dice.roll('2d10kl'); + expect(exp.total).toBe(4); }); + }); }); diff --git a/spec/generator/dice-generator.constructor.spec.ts b/spec/generator/dice-generator.constructor.spec.ts index 41f9b9c..d17c242 100644 --- a/spec/generator/dice-generator.constructor.spec.ts +++ b/spec/generator/dice-generator.constructor.spec.ts @@ -1,11 +1,11 @@ import * as Generator from '../../src/generator'; describe('DiceGenerator', () => { - describe('constructor', () => { - it('does not throw.', () => { - expect(() => { - const generator = new Generator.DiceGenerator(); - }).not.toThrow(); - }); + describe('constructor', () => { + it('does not throw.', () => { + expect(() => { + const generator = new Generator.DiceGenerator(); + }).not.toThrow(); }); + }); }); diff --git a/spec/generator/dice-generator.generate.critical.spec.ts b/spec/generator/dice-generator.generate.critical.spec.ts index 4b97f7d..42ca0a4 100644 --- a/spec/generator/dice-generator.generate.critical.spec.ts +++ b/spec/generator/dice-generator.generate.critical.spec.ts @@ -2,35 +2,35 @@ import * as Ast from '../../src/ast'; import * as Generator from '../../src/generator'; describe('DiceGenerator', () => { - describe('generate', () => { - it('generates a simple critical success (2d6cs).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Critical) - .setAttribute('type', 'success'); + describe('generate', () => { + it('generates a simple critical success (2d6cs).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Critical) + .setAttribute('type', 'success'); - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - exp.addChild(dice); + exp.addChild(dice); - const generator = new Generator.DiceGenerator(); - expect(generator.generate(exp)).toBe('2d6cs'); - }); - it('generates a critical success (2d6cs=6).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Critical) - .setAttribute('type', 'success'); + const generator = new Generator.DiceGenerator(); + expect(generator.generate(exp)).toBe('2d6cs'); + }); + it('generates a critical success (2d6cs=6).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Critical) + .setAttribute('type', 'success'); - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - exp.addChild(dice); + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + exp.addChild(dice); - const eq = Ast.Factory.create(Ast.NodeType.Equal); - eq.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 6)); - exp.addChild(eq); + const eq = Ast.Factory.create(Ast.NodeType.Equal); + eq.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 6)); + exp.addChild(eq); - const generator = new Generator.DiceGenerator(); - expect(generator.generate(exp)).toBe('2d6cs=6'); - }); + const generator = new Generator.DiceGenerator(); + expect(generator.generate(exp)).toBe('2d6cs=6'); }); + }); }); diff --git a/spec/generator/dice-generator.generate.dice.spec.ts b/spec/generator/dice-generator.generate.dice.spec.ts index e80b3ae..836ac8a 100644 --- a/spec/generator/dice-generator.generate.dice.spec.ts +++ b/spec/generator/dice-generator.generate.dice.spec.ts @@ -2,112 +2,112 @@ import * as Ast from '../../src/ast'; import * as Generator from '../../src/generator'; describe('DiceGenerator', () => { - describe('generate', () => { - it('generates a simple dice expression (2d6).', () => { - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - - const generator = new Generator.DiceGenerator(); - expect(generator.generate(dice)).toBe('2d6'); - }); - it('generates a hidden simple dice expression (2d6 + 4).', () => { - const add = Ast.Factory.create(Ast.NodeType.Add); + describe('generate', () => { + it('generates a simple dice expression (2d6).', () => { + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + + const generator = new Generator.DiceGenerator(); + expect(generator.generate(dice)).toBe('2d6'); + }); + it('generates a hidden simple dice expression (2d6 + 4).', () => { + const add = Ast.Factory.create(Ast.NodeType.Add); - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - add.addChild(dice); - add.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); + add.addChild(dice); + add.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); - const generator = new Generator.DiceGenerator(); - expect(generator.generate(add)).toBe('2d6 + 4'); - }); - it('generates a complex dice expression ((1 + 2)d6).', () => { - const dice = Ast.Factory.create(Ast.NodeType.Dice); + const generator = new Generator.DiceGenerator(); + expect(generator.generate(add)).toBe('2d6 + 4'); + }); + it('generates a complex dice expression ((1 + 2)d6).', () => { + const dice = Ast.Factory.create(Ast.NodeType.Dice); - const add = Ast.Factory.create(Ast.NodeType.Add); - add.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 1)); - add.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + const add = Ast.Factory.create(Ast.NodeType.Add); + add.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 1)); + add.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(add); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + dice.addChild(add); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - const generator = new Generator.DiceGenerator(); + const generator = new Generator.DiceGenerator(); - expect(generator.generate(dice)).toBe('(1 + 2)d6'); - }); + expect(generator.generate(dice)).toBe('(1 + 2)d6'); + }); - it('generates a simple fate dice expression (2dF).', () => { - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 'fate')); + it('generates a simple fate dice expression (2dF).', () => { + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 'fate')); - const generator = new Generator.DiceGenerator(); + const generator = new Generator.DiceGenerator(); - expect(generator.generate(dice)).toBe('2dF'); - }); - it('generates fractional dice rolls >=.5 ((5 / 2)d6).', () => { - const dice = Ast.Factory.create(Ast.NodeType.Dice); + expect(generator.generate(dice)).toBe('2dF'); + }); + it('generates fractional dice rolls >=.5 ((5 / 2)d6).', () => { + const dice = Ast.Factory.create(Ast.NodeType.Dice); - const divide = Ast.Factory.create(Ast.NodeType.Divide); - divide.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - divide.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + const divide = Ast.Factory.create(Ast.NodeType.Divide); + divide.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + divide.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(divide); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + dice.addChild(divide); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - const generator = new Generator.DiceGenerator(); + const generator = new Generator.DiceGenerator(); - // 5 / 2 = 2.5, which is rounded to 3. - expect(generator.generate(dice)).toBe('(5 / 2)d6'); - }); - it('generates fractional dice rolls <.5 ((7 / 5)d6).', () => { - const dice = Ast.Factory.create(Ast.NodeType.Dice); + // 5 / 2 = 2.5, which is rounded to 3. + expect(generator.generate(dice)).toBe('(5 / 2)d6'); + }); + it('generates fractional dice rolls <.5 ((7 / 5)d6).', () => { + const dice = Ast.Factory.create(Ast.NodeType.Dice); - const divide = Ast.Factory.create(Ast.NodeType.Divide); - divide.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 7)); - divide.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + const divide = Ast.Factory.create(Ast.NodeType.Divide); + divide.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 7)); + divide.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - dice.addChild(divide); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + dice.addChild(divide); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - const generator = new Generator.DiceGenerator(); + const generator = new Generator.DiceGenerator(); - expect(generator.generate(dice)).toBe('(7 / 5)d6'); - }); - it('generates negative dice rolls at 0. ((-5)d6).', () => { - const dice = Ast.Factory.create(Ast.NodeType.Dice); + expect(generator.generate(dice)).toBe('(7 / 5)d6'); + }); + it('generates negative dice rolls at 0. ((-5)d6).', () => { + const dice = Ast.Factory.create(Ast.NodeType.Dice); - const negate = Ast.Factory.create(Ast.NodeType.Negate); - negate.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + const negate = Ast.Factory.create(Ast.NodeType.Negate); + negate.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - dice.addChild(negate); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + dice.addChild(negate); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - const generator = new Generator.DiceGenerator(); + const generator = new Generator.DiceGenerator(); - // -5d6 will be interpreted as 0. - expect(generator.generate(dice)).toBe('(-5)d6'); - }); - it('generates dice roll values ([4, 5, 6]).', () => { - const dice = Ast.Factory.create(Ast.NodeType.Dice).setAttribute('sides', 6); + // -5d6 will be interpreted as 0. + expect(generator.generate(dice)).toBe('(-5)d6'); + }); + it('generates dice roll values ([4, 5, 6]).', () => { + const dice = Ast.Factory.create(Ast.NodeType.Dice).setAttribute('sides', 6); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceRoll).setAttribute('value', 4)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceRoll).setAttribute('value', 5)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceRoll).setAttribute('value', 6)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceRoll).setAttribute('value', 4)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceRoll).setAttribute('value', 5)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceRoll).setAttribute('value', 6)); - const generator = new Generator.DiceGenerator(); + const generator = new Generator.DiceGenerator(); - expect(generator.generate(dice)).toBe('[4, 5, 6]'); - }); - it('throws on malformed dice expression (2d).', () => { - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + expect(generator.generate(dice)).toBe('[4, 5, 6]'); + }); + it('throws on malformed dice expression (2d).', () => { + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - const generator = new Generator.DiceGenerator(); - expect(() => generator.generate(dice)).toThrow(); - }); + const generator = new Generator.DiceGenerator(); + expect(() => generator.generate(dice)).toThrow(); }); + }); }); diff --git a/spec/generator/dice-generator.generate.drop.spec.ts b/spec/generator/dice-generator.generate.drop.spec.ts index 643851f..28dba13 100644 --- a/spec/generator/dice-generator.generate.drop.spec.ts +++ b/spec/generator/dice-generator.generate.drop.spec.ts @@ -2,32 +2,32 @@ import * as Ast from '../../src/ast'; import * as Generator from '../../src/generator'; describe('DiceGenerator', () => { - describe('generate', () => { - it('generates a simple drop (2d6dl).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Drop) - .setAttribute('type', 'lowest'); + describe('generate', () => { + it('generates a simple drop (2d6dl).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Drop) + .setAttribute('type', 'lowest'); - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - exp.addChild(dice); + exp.addChild(dice); - const generator = new Generator.DiceGenerator(); - expect(generator.generate(exp)).toBe('2d6dl'); - }); - it('generates a simple drop (2d6dh).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Drop) - .setAttribute('type', 'highest'); + const generator = new Generator.DiceGenerator(); + expect(generator.generate(exp)).toBe('2d6dl'); + }); + it('generates a simple drop (2d6dh).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Drop) + .setAttribute('type', 'highest'); - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - exp.addChild(dice); + exp.addChild(dice); - const generator = new Generator.DiceGenerator(); - expect(generator.generate(exp)).toBe('2d6dh'); - }); + const generator = new Generator.DiceGenerator(); + expect(generator.generate(exp)).toBe('2d6dh'); }); + }); }); diff --git a/spec/generator/dice-generator.generate.explode.spec.ts b/spec/generator/dice-generator.generate.explode.spec.ts index 834edba..a5cb1c0 100644 --- a/spec/generator/dice-generator.generate.explode.spec.ts +++ b/spec/generator/dice-generator.generate.explode.spec.ts @@ -2,62 +2,62 @@ import * as Ast from '../../src/ast'; import * as Generator from '../../src/generator'; describe('DiceGenerator', () => { - describe('generate', () => { - it('generates a simple explosion (2d6!).', () => { - const explode = Ast.Factory.create(Ast.NodeType.Explode) - .setAttribute('compound', false) - .setAttribute('penetrate', false); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - - explode.addChild(dice); - - const generator = new Generator.DiceGenerator(); - expect(generator.generate(explode)).toBe('2d6!'); - }); - it('generates a simple explosion (2d6!!).', () => { - const explode = Ast.Factory.create(Ast.NodeType.Explode) - .setAttribute('compound', true) - .setAttribute('penetrate', false); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - - explode.addChild(dice); - - const generator = new Generator.DiceGenerator(); - expect(generator.generate(explode)).toBe('2d6!!'); - }); - it('generates a simple explosion (2d6!p).', () => { - const explode = Ast.Factory.create(Ast.NodeType.Explode) - .setAttribute('compound', false) - .setAttribute('penetrate', true); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - - explode.addChild(dice); - - const generator = new Generator.DiceGenerator(); - expect(generator.generate(explode)).toBe('2d6!p'); - }); - it('generates a simple explosion (2d6!!p).', () => { - const explode = Ast.Factory.create(Ast.NodeType.Explode) - .setAttribute('compound', true) - .setAttribute('penetrate', true); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - - explode.addChild(dice); - - const generator = new Generator.DiceGenerator(); - expect(generator.generate(explode)).toBe('2d6!!p'); - }); + describe('generate', () => { + it('generates a simple explosion (2d6!).', () => { + const explode = Ast.Factory.create(Ast.NodeType.Explode) + .setAttribute('compound', false) + .setAttribute('penetrate', false); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + + explode.addChild(dice); + + const generator = new Generator.DiceGenerator(); + expect(generator.generate(explode)).toBe('2d6!'); + }); + it('generates a simple explosion (2d6!!).', () => { + const explode = Ast.Factory.create(Ast.NodeType.Explode) + .setAttribute('compound', true) + .setAttribute('penetrate', false); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + + explode.addChild(dice); + + const generator = new Generator.DiceGenerator(); + expect(generator.generate(explode)).toBe('2d6!!'); + }); + it('generates a simple explosion (2d6!p).', () => { + const explode = Ast.Factory.create(Ast.NodeType.Explode) + .setAttribute('compound', false) + .setAttribute('penetrate', true); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + + explode.addChild(dice); + + const generator = new Generator.DiceGenerator(); + expect(generator.generate(explode)).toBe('2d6!p'); + }); + it('generates a simple explosion (2d6!!p).', () => { + const explode = Ast.Factory.create(Ast.NodeType.Explode) + .setAttribute('compound', true) + .setAttribute('penetrate', true); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + + explode.addChild(dice); + + const generator = new Generator.DiceGenerator(); + expect(generator.generate(explode)).toBe('2d6!!p'); }); + }); }); diff --git a/spec/generator/dice-generator.generate.function.spec.ts b/spec/generator/dice-generator.generate.function.spec.ts index 3554dfc..57620ad 100644 --- a/spec/generator/dice-generator.generate.function.spec.ts +++ b/spec/generator/dice-generator.generate.function.spec.ts @@ -3,66 +3,66 @@ import * as Generator from '../../src/generator'; import { MockRandomProvider } from '../helpers'; describe('DiceGenerator', () => { - describe('evaluate', () => { - it('correctly evaluates a function(floor(5 / 2)).', () => { - const func = Ast.Factory.create(Ast.NodeType.Function).setAttribute('name', 'floor'); + describe('evaluate', () => { + it('correctly evaluates a function(floor(5 / 2)).', () => { + const func = Ast.Factory.create(Ast.NodeType.Function).setAttribute('name', 'floor'); - const exp = Ast.Factory.create(Ast.NodeType.Divide); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + const exp = Ast.Factory.create(Ast.NodeType.Divide); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - func.addChild(exp); + func.addChild(exp); - const generator = new Generator.DiceGenerator(); + const generator = new Generator.DiceGenerator(); - const res = generator.generate(func); - expect(res).toBe('floor(5 / 2)'); - }); - it('correctly evaluates a function(ceil(5 / 2)).', () => { - const func = Ast.Factory.create(Ast.NodeType.Function).setAttribute('name', 'ceil'); + const res = generator.generate(func); + expect(res).toBe('floor(5 / 2)'); + }); + it('correctly evaluates a function(ceil(5 / 2)).', () => { + const func = Ast.Factory.create(Ast.NodeType.Function).setAttribute('name', 'ceil'); - const exp = Ast.Factory.create(Ast.NodeType.Divide); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + const exp = Ast.Factory.create(Ast.NodeType.Divide); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - func.addChild(exp); + func.addChild(exp); - const generator = new Generator.DiceGenerator(); + const generator = new Generator.DiceGenerator(); - const res = generator.generate(func); - expect(res).toBe('ceil(5 / 2)'); - }); - it('correctly evaluates a function(sqrt(9)).', () => { - const func = Ast.Factory.create(Ast.NodeType.Function).setAttribute('name', 'sqrt'); - func.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 9)); + const res = generator.generate(func); + expect(res).toBe('ceil(5 / 2)'); + }); + it('correctly evaluates a function(sqrt(9)).', () => { + const func = Ast.Factory.create(Ast.NodeType.Function).setAttribute('name', 'sqrt'); + func.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 9)); - const generator = new Generator.DiceGenerator(); + const generator = new Generator.DiceGenerator(); - const res = generator.generate(func); - expect(res).toBe('sqrt(9)'); - }); - it('correctly evaluates a function(abs(-9)).', () => { - const func = Ast.Factory.create(Ast.NodeType.Function).setAttribute('name', 'abs'); + const res = generator.generate(func); + expect(res).toBe('sqrt(9)'); + }); + it('correctly evaluates a function(abs(-9)).', () => { + const func = Ast.Factory.create(Ast.NodeType.Function).setAttribute('name', 'abs'); - const negate = Ast.Factory.create(Ast.NodeType.Negate); - negate.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 9)); + const negate = Ast.Factory.create(Ast.NodeType.Negate); + negate.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 9)); - func.addChild(negate); + func.addChild(negate); - const generator = new Generator.DiceGenerator(); + const generator = new Generator.DiceGenerator(); - const res = generator.generate(func); - expect(res).toBe('abs(-9)'); - }); - it('correctly evaluates multiple parameters function(round(5, 2)).', () => { - const func = Ast.Factory.create(Ast.NodeType.Function).setAttribute('name', 'round'); - func.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - func.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + const res = generator.generate(func); + expect(res).toBe('abs(-9)'); + }); + it('correctly evaluates multiple parameters function(round(5, 2)).', () => { + const func = Ast.Factory.create(Ast.NodeType.Function).setAttribute('name', 'round'); + func.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + func.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - const generator = new Generator.DiceGenerator(); + const generator = new Generator.DiceGenerator(); - const res = generator.generate(func); - expect(res).toBe('round(5, 2)'); - }); + const res = generator.generate(func); + expect(res).toBe('round(5, 2)'); }); + }); }); diff --git a/spec/generator/dice-generator.generate.group.spec.ts b/spec/generator/dice-generator.generate.group.spec.ts index cfdd2d3..b64b30f 100644 --- a/spec/generator/dice-generator.generate.group.spec.ts +++ b/spec/generator/dice-generator.generate.group.spec.ts @@ -3,44 +3,44 @@ import * as Generator from '../../src/generator'; import { MockRandomProvider } from '../helpers'; describe('DiceGenerator', () => { - describe('evaluate', () => { - it('correctly evaluates a group {5, 2}.', () => { - const group = Ast.Factory.create(Ast.NodeType.Group); + describe('evaluate', () => { + it('correctly evaluates a group {5, 2}.', () => { + const group = Ast.Factory.create(Ast.NodeType.Group); - group.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - group.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + group.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + group.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - const generator = new Generator.DiceGenerator(); + const generator = new Generator.DiceGenerator(); - expect(generator.generate(group)).toBe('{5, 2}'); - }); - it('correctly evaluates a group with a repeater {5...2}.', () => { - const group = Ast.Factory.create(Ast.NodeType.Group); + expect(generator.generate(group)).toBe('{5, 2}'); + }); + it('correctly evaluates a group with a repeater {5...2}.', () => { + const group = Ast.Factory.create(Ast.NodeType.Group); - const repeat = Ast.Factory.create(Ast.NodeType.Repeat); - repeat.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - repeat.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + const repeat = Ast.Factory.create(Ast.NodeType.Repeat); + repeat.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + repeat.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - group.addChild(repeat); + group.addChild(repeat); - const generator = new Generator.DiceGenerator(); + const generator = new Generator.DiceGenerator(); - expect(generator.generate(group)).toBe('{5...2}'); - }); - it('correctly evaluates a group with modifiers {5, 2}kh.', () => { - const keep = Ast.Factory.create(Ast.NodeType.Keep); - keep.setAttribute('type', 'highest'); + expect(generator.generate(group)).toBe('{5...2}'); + }); + it('correctly evaluates a group with modifiers {5, 2}kh.', () => { + const keep = Ast.Factory.create(Ast.NodeType.Keep); + keep.setAttribute('type', 'highest'); - const group = Ast.Factory.create(Ast.NodeType.Group); + const group = Ast.Factory.create(Ast.NodeType.Group); - group.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - group.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + group.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + group.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - keep.addChild(group); + keep.addChild(group); - const generator = new Generator.DiceGenerator(); + const generator = new Generator.DiceGenerator(); - expect(generator.generate(keep)).toBe('{5, 2}kh'); - }); + expect(generator.generate(keep)).toBe('{5, 2}kh'); }); + }); }); diff --git a/spec/generator/dice-generator.generate.keep.spec.ts b/spec/generator/dice-generator.generate.keep.spec.ts index 7c4f7ec..1de295b 100644 --- a/spec/generator/dice-generator.generate.keep.spec.ts +++ b/spec/generator/dice-generator.generate.keep.spec.ts @@ -2,32 +2,32 @@ import * as Ast from '../../src/ast'; import * as Generator from '../../src/generator'; describe('DiceGenerator', () => { - describe('generate', () => { - it('generates a simple keep (2d6kh).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Keep) - .setAttribute('type', 'highest'); + describe('generate', () => { + it('generates a simple keep (2d6kh).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Keep) + .setAttribute('type', 'highest'); - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - exp.addChild(dice); + exp.addChild(dice); - const generator = new Generator.DiceGenerator(); - expect(generator.generate(exp)).toBe('2d6kh'); - }); - it('generates a simple keep (2d6kl).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Keep) - .setAttribute('type', 'lowest'); + const generator = new Generator.DiceGenerator(); + expect(generator.generate(exp)).toBe('2d6kh'); + }); + it('generates a simple keep (2d6kl).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Keep) + .setAttribute('type', 'lowest'); - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - exp.addChild(dice); + exp.addChild(dice); - const generator = new Generator.DiceGenerator(); - expect(generator.generate(exp)).toBe('2d6kl'); - }); + const generator = new Generator.DiceGenerator(); + expect(generator.generate(exp)).toBe('2d6kl'); }); + }); }); diff --git a/spec/generator/dice-generator.generate.reroll.spec.ts b/spec/generator/dice-generator.generate.reroll.spec.ts index f7212a1..4bfd0b9 100644 --- a/spec/generator/dice-generator.generate.reroll.spec.ts +++ b/spec/generator/dice-generator.generate.reroll.spec.ts @@ -2,48 +2,48 @@ import * as Ast from '../../src/ast'; import * as Generator from '../../src/generator'; describe('DiceGenerator', () => { - describe('generate', () => { - it('generates a simple reroll (2d6r).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Reroll) - .setAttribute('once', false); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - - exp.addChild(dice); - - const generator = new Generator.DiceGenerator(); - expect(generator.generate(exp)).toBe('2d6r'); - }); - it('generates a reroll once (2d6ro).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Reroll) - .setAttribute('once', true); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - - exp.addChild(dice); - - const generator = new Generator.DiceGenerator(); - expect(generator.generate(exp)).toBe('2d6ro'); - }); - it('generates a reroll with a condition (2d6r<3).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Reroll) - .setAttribute('once', false); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - exp.addChild(dice); - - const less = Ast.Factory.create(Ast.NodeType.Less); - less.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 3)); - exp.addChild(less); - - const generator = new Generator.DiceGenerator(); - expect(generator.generate(exp)).toBe('2d6r<3'); - }); + describe('generate', () => { + it('generates a simple reroll (2d6r).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Reroll) + .setAttribute('once', false); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + + exp.addChild(dice); + + const generator = new Generator.DiceGenerator(); + expect(generator.generate(exp)).toBe('2d6r'); + }); + it('generates a reroll once (2d6ro).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Reroll) + .setAttribute('once', true); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + + exp.addChild(dice); + + const generator = new Generator.DiceGenerator(); + expect(generator.generate(exp)).toBe('2d6ro'); + }); + it('generates a reroll with a condition (2d6r<3).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Reroll) + .setAttribute('once', false); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + exp.addChild(dice); + + const less = Ast.Factory.create(Ast.NodeType.Less); + less.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 3)); + exp.addChild(less); + + const generator = new Generator.DiceGenerator(); + expect(generator.generate(exp)).toBe('2d6r<3'); }); + }); }); diff --git a/spec/generator/dice-generator.generate.simple.spec.ts b/spec/generator/dice-generator.generate.simple.spec.ts index 4ed10e3..231fb2d 100644 --- a/spec/generator/dice-generator.generate.simple.spec.ts +++ b/spec/generator/dice-generator.generate.simple.spec.ts @@ -2,87 +2,87 @@ import * as Ast from '../../src/ast'; import * as Generator from '../../src/generator'; describe('DiceGenerator', () => { - describe('generate', () => { - it('generates a simple number.', () => { - const num = Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4); - const generator = new Generator.DiceGenerator(); - expect(generator.generate(num)).toBe('4'); - }); - it('correctly generates a subtraction (10 - 2).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Subtract); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - const generator = new Generator.DiceGenerator(); - expect(generator.generate(exp)).toBe('10 - 2'); - }); - it('correctly generates a multiplication (10 * 2).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Multiply); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - const generator = new Generator.DiceGenerator(); - expect(generator.generate(exp)).toBe('10 * 2'); - }); - it('correctly generates a division (10 / 2).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Divide); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - const generator = new Generator.DiceGenerator(); - expect(generator.generate(exp)).toBe('10 / 2'); - }); - it('correctly generates a exponentiation (10 ^ 2).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Exponent); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - const generator = new Generator.DiceGenerator(); - expect(generator.generate(exp)).toBe('10 ^ 2'); - }); - it('correctly generates a modulo (10 % 2).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Modulo); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - const generator = new Generator.DiceGenerator(); - expect(generator.generate(exp)).toBe('10 % 2'); - }); - it('correctly generates a negation (-10).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Negate); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); - const generator = new Generator.DiceGenerator(); - expect(generator.generate(exp)).toBe('-10'); - }); - it('correctly generates a boolean condition (1 = 2).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Equal); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 1)); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - const generator = new Generator.DiceGenerator(); - expect(generator.generate(exp)).toBe('1 = 2'); - }); - it('correctly generates a boolean condition (1 > 2).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Greater); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 1)); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - const generator = new Generator.DiceGenerator(); - expect(generator.generate(exp)).toBe('1 > 2'); - }); - it('correctly generates a boolean condition (1 >= 2).', () => { - const exp = Ast.Factory.create(Ast.NodeType.GreaterOrEqual); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 1)); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - const generator = new Generator.DiceGenerator(); - expect(generator.generate(exp)).toBe('1 >= 2'); - }); - it('correctly generates a boolean condition (1 < 2).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Less); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 1)); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - const generator = new Generator.DiceGenerator(); - expect(generator.generate(exp)).toBe('1 < 2'); - }); - it('correctly generates a boolean condition (1 <= 2).', () => { - const exp = Ast.Factory.create(Ast.NodeType.LessOrEqual); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 1)); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - const generator = new Generator.DiceGenerator(); - expect(generator.generate(exp)).toBe('1 <= 2'); - }); + describe('generate', () => { + it('generates a simple number.', () => { + const num = Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4); + const generator = new Generator.DiceGenerator(); + expect(generator.generate(num)).toBe('4'); }); + it('correctly generates a subtraction (10 - 2).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Subtract); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + const generator = new Generator.DiceGenerator(); + expect(generator.generate(exp)).toBe('10 - 2'); + }); + it('correctly generates a multiplication (10 * 2).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Multiply); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + const generator = new Generator.DiceGenerator(); + expect(generator.generate(exp)).toBe('10 * 2'); + }); + it('correctly generates a division (10 / 2).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Divide); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + const generator = new Generator.DiceGenerator(); + expect(generator.generate(exp)).toBe('10 / 2'); + }); + it('correctly generates a exponentiation (10 ^ 2).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Exponent); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + const generator = new Generator.DiceGenerator(); + expect(generator.generate(exp)).toBe('10 ^ 2'); + }); + it('correctly generates a modulo (10 % 2).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Modulo); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + const generator = new Generator.DiceGenerator(); + expect(generator.generate(exp)).toBe('10 % 2'); + }); + it('correctly generates a negation (-10).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Negate); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); + const generator = new Generator.DiceGenerator(); + expect(generator.generate(exp)).toBe('-10'); + }); + it('correctly generates a boolean condition (1 = 2).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Equal); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 1)); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + const generator = new Generator.DiceGenerator(); + expect(generator.generate(exp)).toBe('1 = 2'); + }); + it('correctly generates a boolean condition (1 > 2).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Greater); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 1)); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + const generator = new Generator.DiceGenerator(); + expect(generator.generate(exp)).toBe('1 > 2'); + }); + it('correctly generates a boolean condition (1 >= 2).', () => { + const exp = Ast.Factory.create(Ast.NodeType.GreaterOrEqual); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 1)); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + const generator = new Generator.DiceGenerator(); + expect(generator.generate(exp)).toBe('1 >= 2'); + }); + it('correctly generates a boolean condition (1 < 2).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Less); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 1)); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + const generator = new Generator.DiceGenerator(); + expect(generator.generate(exp)).toBe('1 < 2'); + }); + it('correctly generates a boolean condition (1 <= 2).', () => { + const exp = Ast.Factory.create(Ast.NodeType.LessOrEqual); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 1)); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + const generator = new Generator.DiceGenerator(); + expect(generator.generate(exp)).toBe('1 <= 2'); + }); + }); }); diff --git a/spec/generator/dice-generator.generate.sort.spec.ts b/spec/generator/dice-generator.generate.sort.spec.ts index 1e3fb60..cf5713c 100644 --- a/spec/generator/dice-generator.generate.sort.spec.ts +++ b/spec/generator/dice-generator.generate.sort.spec.ts @@ -2,32 +2,32 @@ import * as Ast from '../../src/ast'; import * as Generator from '../../src/generator'; describe('DiceGenerator', () => { - describe('generate', () => { - it('generates a simple sort (2d6sa).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Sort) - .setAttribute('direction', 'ascending'); + describe('generate', () => { + it('generates a simple sort (2d6sa).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Sort) + .setAttribute('direction', 'ascending'); - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - exp.addChild(dice); + exp.addChild(dice); - const generator = new Generator.DiceGenerator(); - expect(generator.generate(exp)).toBe('2d6sa'); - }); - it('generates a simple sort (2d6sd).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Sort) - .setAttribute('direction', 'descending'); + const generator = new Generator.DiceGenerator(); + expect(generator.generate(exp)).toBe('2d6sa'); + }); + it('generates a simple sort (2d6sd).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Sort) + .setAttribute('direction', 'descending'); - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - exp.addChild(dice); + exp.addChild(dice); - const generator = new Generator.DiceGenerator(); - expect(generator.generate(exp)).toBe('2d6sd'); - }); + const generator = new Generator.DiceGenerator(); + expect(generator.generate(exp)).toBe('2d6sd'); }); + }); }); diff --git a/spec/generator/dice-generator.generate.spec.ts b/spec/generator/dice-generator.generate.spec.ts index 823bada..56bcbfd 100644 --- a/spec/generator/dice-generator.generate.spec.ts +++ b/spec/generator/dice-generator.generate.spec.ts @@ -2,19 +2,19 @@ import * as Ast from '../../src/ast'; import * as Generator from '../../src/generator'; describe('DiceGenerator', () => { - describe('generate', () => { - it('generates a dice roll.', () => { - const exp = Ast.Factory.create(Ast.NodeType.Critical) - .setAttribute('type', 'success'); + describe('generate', () => { + it('generates a dice roll.', () => { + const exp = Ast.Factory.create(Ast.NodeType.Critical) + .setAttribute('type', 'success'); - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - exp.addChild(dice); + exp.addChild(dice); - const generator = new Generator.DiceGenerator(); - expect(generator.generate(exp)).toBe('2d6cs'); - }); + const generator = new Generator.DiceGenerator(); + expect(generator.generate(exp)).toBe('2d6cs'); }); + }); }); diff --git a/spec/generator/dice-generator.generate.throws.spec.ts b/spec/generator/dice-generator.generate.throws.spec.ts index 76b110b..79c0236 100644 --- a/spec/generator/dice-generator.generate.throws.spec.ts +++ b/spec/generator/dice-generator.generate.throws.spec.ts @@ -2,17 +2,17 @@ import * as Ast from '../../src/ast'; import * as Generator from '../../src/generator'; describe('DiceGenerator', () => { - describe('generate', () => { - it('throws on unrecognized node type (--).', () => { - const exp = new Ast.ExpressionNode('Face', null); + describe('generate', () => { + it('throws on unrecognized node type (--).', () => { + const exp = new Ast.ExpressionNode('Face', null); - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - exp.addChild(dice); + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + exp.addChild(dice); - const generator = new Generator.DiceGenerator(); - expect(() => generator.generate(exp)).toThrow(); - }); + const generator = new Generator.DiceGenerator(); + expect(() => generator.generate(exp)).toThrow(); }); + }); }); diff --git a/spec/generator/dice-generator.hasGenerateMethods.spec.ts b/spec/generator/dice-generator.hasGenerateMethods.spec.ts index 18cff38..3b46f3d 100644 --- a/spec/generator/dice-generator.hasGenerateMethods.spec.ts +++ b/spec/generator/dice-generator.hasGenerateMethods.spec.ts @@ -2,13 +2,13 @@ import * as Ast from '../../src/ast'; import * as Generator from '../../src/generator'; describe('DiceGenerator', () => { - describe('generate method', () => { - it('exists for each node type.', () => { - const generator = new Generator.DiceGenerator(); - Object.keys(Ast.NodeType).forEach(nodeType => { - const methodName = 'generate' + nodeType; - expect(generator[methodName]).toBeDefined(`Generate method ${methodName} does not exist.`); - }); - }); + describe('generate method', () => { + it('exists for each node type.', () => { + const generator = new Generator.DiceGenerator(); + Object.keys(Ast.NodeType).forEach(nodeType => { + const methodName = 'generate' + nodeType; + expect(generator[methodName]).toBeDefined(`Generate method ${methodName} does not exist.`); + }); }); + }); }); diff --git a/spec/helpers/mock-lexer.class.ts b/spec/helpers/mock-lexer.class.ts index eeab2b8..a781fb5 100644 --- a/spec/helpers/mock-lexer.class.ts +++ b/spec/helpers/mock-lexer.class.ts @@ -1,25 +1,25 @@ import { Lexer, Token, TokenType } from '../../src/lexer'; export class MockLexer implements Lexer { - private index = -1; - private terminator: Token; + private index = -1; + private terminator: Token; - constructor(private readonly tokens: Token[]) { - this.terminator = new Token(TokenType.Terminator, this.getEndOfTokens()); - } + constructor(private readonly tokens: Token[]) { + this.terminator = new Token(TokenType.Terminator, this.getEndOfTokens()); + } - peekNextToken(): Token { - if (this.index >= this.tokens.length - 1) { return this.terminator; } - return this.tokens[this.index + 1]; - } - getNextToken(): Token { - if (this.index >= this.tokens.length - 1) { return this.terminator; } - return this.tokens[++this.index]; - } + peekNextToken(): Token { + if (this.index >= this.tokens.length - 1) { return this.terminator; } + return this.tokens[this.index + 1]; + } + getNextToken(): Token { + if (this.index >= this.tokens.length - 1) { return this.terminator; } + return this.tokens[++this.index]; + } - private getEndOfTokens(): number { - if (this.tokens.length === 0) { return 0; } - const lastToken = this.tokens[this.tokens.length - 1]; - return lastToken.position + lastToken.value.length; - } + private getEndOfTokens(): number { + if (this.tokens.length === 0) { return 0; } + const lastToken = this.tokens[this.tokens.length - 1]; + return lastToken.position + lastToken.value.length; + } } diff --git a/spec/helpers/mock-list-random-provider.class.ts b/spec/helpers/mock-list-random-provider.class.ts index 1ef2f37..dcb241b 100644 --- a/spec/helpers/mock-list-random-provider.class.ts +++ b/spec/helpers/mock-list-random-provider.class.ts @@ -1,19 +1,19 @@ import { RandomProvider } from '../../src/random'; export class MockListRandomProvider implements RandomProvider { - private index = -1; + private index = -1; - public readonly numbers: number[]; + public readonly numbers: number[]; - constructor(numbers?: number[]) { - this.numbers = numbers || []; - } + constructor(numbers?: number[]) { + this.numbers = numbers || []; + } - numberBetween(min: number, max: number) { - this.index++; - if (this.index >= this.numbers.length) { - throw new Error('Requested too many random numbers!'); - } - return this.numbers[this.index]; + numberBetween(min: number, max: number) { + this.index++; + if (this.index >= this.numbers.length) { + throw new Error('Requested too many random numbers!'); } + return this.numbers[this.index]; + } } diff --git a/spec/helpers/mock-list-random-provider.spec.ts b/spec/helpers/mock-list-random-provider.spec.ts index af19a85..431d933 100644 --- a/spec/helpers/mock-list-random-provider.spec.ts +++ b/spec/helpers/mock-list-random-provider.spec.ts @@ -1,30 +1,30 @@ import { MockListRandomProvider } from './mock-list-random-provider.class'; describe('MockListRandomProvider', () => { - describe('constructor', () => { - it('should not throw', function () { - expect(() => { - const random = new MockListRandomProvider(); - }).not.toThrow(); - }); + describe('constructor', () => { + it('should not throw', function () { + expect(() => { + const random = new MockListRandomProvider(); + }).not.toThrow(); }); - describe('numberBetween', () => { - it('returns numbers in order.', function () { - const random = new MockListRandomProvider(); - random.numbers.push(1, 2, 3, 4, 5); - expect(random.numberBetween(-100, 100)).toEqual(1); - expect(random.numberBetween(-100, 100)).toEqual(2); - expect(random.numberBetween(-100, 100)).toEqual(3); - expect(random.numberBetween(-100, 100)).toEqual(4); - expect(random.numberBetween(-100, 100)).toEqual(5); - }); - it('throws if too many numbers are requested.', function () { - const random = new MockListRandomProvider(); - random.numbers.push(1, 2, 3); - expect(random.numberBetween(-100, 100)).toEqual(1); - expect(random.numberBetween(-100, 100)).toEqual(2); - expect(random.numberBetween(-100, 100)).toEqual(3); - expect(() => { random.numberBetween(-100, 100); }).toThrow(); - }); + }); + describe('numberBetween', () => { + it('returns numbers in order.', function () { + const random = new MockListRandomProvider(); + random.numbers.push(1, 2, 3, 4, 5); + expect(random.numberBetween(-100, 100)).toEqual(1); + expect(random.numberBetween(-100, 100)).toEqual(2); + expect(random.numberBetween(-100, 100)).toEqual(3); + expect(random.numberBetween(-100, 100)).toEqual(4); + expect(random.numberBetween(-100, 100)).toEqual(5); }); + it('throws if too many numbers are requested.', function () { + const random = new MockListRandomProvider(); + random.numbers.push(1, 2, 3); + expect(random.numberBetween(-100, 100)).toEqual(1); + expect(random.numberBetween(-100, 100)).toEqual(2); + expect(random.numberBetween(-100, 100)).toEqual(3); + expect(() => { random.numberBetween(-100, 100); }).toThrow(); + }); + }); }); diff --git a/spec/helpers/mock-random-provider.class.ts b/spec/helpers/mock-random-provider.class.ts index 52af19a..5df9f47 100644 --- a/spec/helpers/mock-random-provider.class.ts +++ b/spec/helpers/mock-random-provider.class.ts @@ -1,9 +1,9 @@ import { RandomProvider } from '../../src/random'; export class MockRandomProvider implements RandomProvider { - constructor(private number: number) { } + constructor(private number: number) { } - numberBetween(min: number, max: number) { - return this.number; - } + numberBetween(min: number, max: number) { + return this.number; + } } diff --git a/spec/helpers/mock-random-provider.spec.ts b/spec/helpers/mock-random-provider.spec.ts index c8b4d9f..c731485 100644 --- a/spec/helpers/mock-random-provider.spec.ts +++ b/spec/helpers/mock-random-provider.spec.ts @@ -1,22 +1,22 @@ import { MockRandomProvider } from './mock-random-provider.class'; describe('MockRandomProvider', () => { - describe('constructor', () => { - it('should not throw', function () { - expect(() => { - const random = new MockRandomProvider(4); - }).not.toThrow(); - }); + describe('constructor', () => { + it('should not throw', function () { + expect(() => { + const random = new MockRandomProvider(4); + }).not.toThrow(); }); - describe('numberBetween', () => { - it('returns number passed into the constructor (hard-coded).', function () { - const random = new MockRandomProvider(4); - expect(random.numberBetween(-100, 100)).toEqual(4); - }); - it('returns number passed into the constructor (random).', function () { - const number = Math.random() * 100; - const random = new MockRandomProvider(number); - expect(random.numberBetween(-100, 100)).toEqual(number); - }); + }); + describe('numberBetween', () => { + it('returns number passed into the constructor (hard-coded).', function () { + const random = new MockRandomProvider(4); + expect(random.numberBetween(-100, 100)).toEqual(4); }); + it('returns number passed into the constructor (random).', function () { + const number = Math.random() * 100; + const random = new MockRandomProvider(number); + expect(random.numberBetween(-100, 100)).toEqual(number); + }); + }); }); diff --git a/spec/interpreter/dice-interpreter.constructor.spec.ts b/spec/interpreter/dice-interpreter.constructor.spec.ts index 533f52b..4e9b472 100644 --- a/spec/interpreter/dice-interpreter.constructor.spec.ts +++ b/spec/interpreter/dice-interpreter.constructor.spec.ts @@ -1,11 +1,11 @@ import * as Interpreter from '../../src/interpreter'; describe('DiceInterpreter', () => { - describe('constructor', () => { - it('does not throw.', () => { - expect(() => { - const interpreter = new Interpreter.DiceInterpreter(); - }).not.toThrow(); - }); + describe('constructor', () => { + it('does not throw.', () => { + expect(() => { + const interpreter = new Interpreter.DiceInterpreter(); + }).not.toThrow(); }); + }); }); diff --git a/spec/interpreter/dice-interpreter.countSuccesses.spec.ts b/spec/interpreter/dice-interpreter.countSuccesses.spec.ts index 7332a1f..6d3eb61 100644 --- a/spec/interpreter/dice-interpreter.countSuccesses.spec.ts +++ b/spec/interpreter/dice-interpreter.countSuccesses.spec.ts @@ -3,53 +3,53 @@ import * as Interpreter from '../../src/interpreter'; import { MockListRandomProvider } from '../helpers'; describe('DiceInterpreter', () => { - describe('evaluate', () => { - it('evaluates successes (5d20>10).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Greater); + describe('evaluate', () => { + it('evaluates successes (5d20>10).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Greater); - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); - exp.addChild(dice); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); + exp.addChild(dice); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); - const mockList = new MockListRandomProvider(); - mockList.numbers.push(8, 12, 6, 20, 14); + const mockList = new MockListRandomProvider(); + mockList.numbers.push(8, 12, 6, 20, 14); - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(exp, errors); + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(exp, errors); - const res = interpreter.countSuccesses(exp, errors); + const res = interpreter.countSuccesses(exp, errors); - expect(res).toBe(3); - }); - it('evaluates successes in a group {1d20, 1d20, 1d20}>10.', () => { - const exp = Ast.Factory.create(Ast.NodeType.Greater); + expect(res).toBe(3); + }); + it('evaluates successes in a group {1d20, 1d20, 1d20}>10.', () => { + const exp = Ast.Factory.create(Ast.NodeType.Greater); - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 1)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 1)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); - const group = Ast.Factory.create(Ast.NodeType.Group); - group.addChild(dice.copy()); - group.addChild(dice.copy()); - group.addChild(dice.copy()); + const group = Ast.Factory.create(Ast.NodeType.Group); + group.addChild(dice.copy()); + group.addChild(dice.copy()); + group.addChild(dice.copy()); - exp.addChild(group); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); + exp.addChild(group); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); - const mockList = new MockListRandomProvider(); - mockList.numbers.push(8, 12, 20); + const mockList = new MockListRandomProvider(); + mockList.numbers.push(8, 12, 20); - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(exp, errors); + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(exp, errors); - const res = interpreter.countSuccesses(exp, errors); + const res = interpreter.countSuccesses(exp, errors); - expect(res).toBe(2); - }); + expect(res).toBe(2); }); + }); }); diff --git a/spec/interpreter/dice-interpreter.evaluate.compare.spec.ts b/spec/interpreter/dice-interpreter.evaluate.compare.spec.ts index 6876f53..3483941 100644 --- a/spec/interpreter/dice-interpreter.evaluate.compare.spec.ts +++ b/spec/interpreter/dice-interpreter.evaluate.compare.spec.ts @@ -3,132 +3,132 @@ import * as Interpreter from '../../src/interpreter'; import { MockListRandomProvider } from '../helpers'; describe('DiceInterpreter', () => { - describe('evaluate', () => { - it('evaluates dice compare modifier (4d20>5).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Greater); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); - - exp.addChild(dice); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - - const mockList = new MockListRandomProvider(); - mockList.numbers.push(1, 2, 6, 20); - - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - - interpreter.evaluate(exp, errors); - - expect(dice.getChildCount()).toBe(4); - expect(dice.getAttribute('value')).toBe(29); - expect(dice.getChild(0).getAttribute('value')).toBe(1); - expect(dice.getChild(1).getAttribute('value')).toBe(2); - expect(dice.getChild(2).getAttribute('value')).toBe(6); - expect(dice.getChild(3).getAttribute('value')).toBe(20); - - expect(dice.getChild(0).getAttribute('success')).toBe(false); - expect(dice.getChild(1).getAttribute('success')).toBe(false); - expect(dice.getChild(2).getAttribute('success')).toBe(true); - expect(dice.getChild(3).getAttribute('success')).toBe(true); - }); - it('evaluates dice compare modifier (4d20>=5).', () => { - const exp = Ast.Factory.create(Ast.NodeType.GreaterOrEqual); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); - - exp.addChild(dice); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - - const mockList = new MockListRandomProvider(); - mockList.numbers.push(1, 2, 5, 20); - - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(exp, errors); - - expect(dice.getChildCount()).toBe(4); - expect(dice.getAttribute('value')).toBe(28); - expect(dice.getChild(0).getAttribute('success')).toBe(false); - expect(dice.getChild(1).getAttribute('success')).toBe(false); - expect(dice.getChild(2).getAttribute('success')).toBe(true); - expect(dice.getChild(3).getAttribute('success')).toBe(true); - }); - it('evaluates dice compare modifier (4d20=5).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Equal); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); - - exp.addChild(dice); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - - const mockList = new MockListRandomProvider(); - mockList.numbers.push(1, 2, 5, 20); - - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(exp, errors); - - expect(dice.getChildCount()).toBe(4); - expect(dice.getAttribute('value')).toBe(28); - expect(dice.getChild(0).getAttribute('success')).toBe(false); - expect(dice.getChild(1).getAttribute('success')).toBe(false); - expect(dice.getChild(2).getAttribute('success')).toBe(true); - expect(dice.getChild(3).getAttribute('success')).toBe(false); - }); - it('evaluates dice compare modifier (4d20<5).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Less); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); - - exp.addChild(dice); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - - const mockList = new MockListRandomProvider(); - mockList.numbers.push(1, 2, 5, 20); - - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(exp, errors); - - expect(dice.getChildCount()).toBe(4); - expect(dice.getAttribute('value')).toBe(28); - expect(dice.getChild(0).getAttribute('success')).toBe(true); - expect(dice.getChild(1).getAttribute('success')).toBe(true); - expect(dice.getChild(2).getAttribute('success')).toBe(false); - expect(dice.getChild(3).getAttribute('success')).toBe(false); - }); - it('evaluates dice compare modifier (4d20<=5).', () => { - const exp = Ast.Factory.create(Ast.NodeType.LessOrEqual); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); - - exp.addChild(dice); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - - const mockList = new MockListRandomProvider(); - mockList.numbers.push(1, 2, 5, 20); - - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(exp, errors); - - expect(dice.getChildCount()).toBe(4); - expect(dice.getAttribute('value')).toBe(28); - expect(dice.getChild(0).getAttribute('success')).toBe(true); - expect(dice.getChild(1).getAttribute('success')).toBe(true); - expect(dice.getChild(2).getAttribute('success')).toBe(true); - expect(dice.getChild(3).getAttribute('success')).toBe(false); - }); + describe('evaluate', () => { + it('evaluates dice compare modifier (4d20>5).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Greater); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); + + exp.addChild(dice); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + + const mockList = new MockListRandomProvider(); + mockList.numbers.push(1, 2, 6, 20); + + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + + interpreter.evaluate(exp, errors); + + expect(dice.getChildCount()).toBe(4); + expect(dice.getAttribute('value')).toBe(29); + expect(dice.getChild(0).getAttribute('value')).toBe(1); + expect(dice.getChild(1).getAttribute('value')).toBe(2); + expect(dice.getChild(2).getAttribute('value')).toBe(6); + expect(dice.getChild(3).getAttribute('value')).toBe(20); + + expect(dice.getChild(0).getAttribute('success')).toBe(false); + expect(dice.getChild(1).getAttribute('success')).toBe(false); + expect(dice.getChild(2).getAttribute('success')).toBe(true); + expect(dice.getChild(3).getAttribute('success')).toBe(true); + }); + it('evaluates dice compare modifier (4d20>=5).', () => { + const exp = Ast.Factory.create(Ast.NodeType.GreaterOrEqual); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); + + exp.addChild(dice); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + + const mockList = new MockListRandomProvider(); + mockList.numbers.push(1, 2, 5, 20); + + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(exp, errors); + + expect(dice.getChildCount()).toBe(4); + expect(dice.getAttribute('value')).toBe(28); + expect(dice.getChild(0).getAttribute('success')).toBe(false); + expect(dice.getChild(1).getAttribute('success')).toBe(false); + expect(dice.getChild(2).getAttribute('success')).toBe(true); + expect(dice.getChild(3).getAttribute('success')).toBe(true); + }); + it('evaluates dice compare modifier (4d20=5).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Equal); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); + + exp.addChild(dice); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + + const mockList = new MockListRandomProvider(); + mockList.numbers.push(1, 2, 5, 20); + + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(exp, errors); + + expect(dice.getChildCount()).toBe(4); + expect(dice.getAttribute('value')).toBe(28); + expect(dice.getChild(0).getAttribute('success')).toBe(false); + expect(dice.getChild(1).getAttribute('success')).toBe(false); + expect(dice.getChild(2).getAttribute('success')).toBe(true); + expect(dice.getChild(3).getAttribute('success')).toBe(false); + }); + it('evaluates dice compare modifier (4d20<5).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Less); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); + + exp.addChild(dice); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + + const mockList = new MockListRandomProvider(); + mockList.numbers.push(1, 2, 5, 20); + + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(exp, errors); + + expect(dice.getChildCount()).toBe(4); + expect(dice.getAttribute('value')).toBe(28); + expect(dice.getChild(0).getAttribute('success')).toBe(true); + expect(dice.getChild(1).getAttribute('success')).toBe(true); + expect(dice.getChild(2).getAttribute('success')).toBe(false); + expect(dice.getChild(3).getAttribute('success')).toBe(false); + }); + it('evaluates dice compare modifier (4d20<=5).', () => { + const exp = Ast.Factory.create(Ast.NodeType.LessOrEqual); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); + + exp.addChild(dice); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + + const mockList = new MockListRandomProvider(); + mockList.numbers.push(1, 2, 5, 20); + + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(exp, errors); + + expect(dice.getChildCount()).toBe(4); + expect(dice.getAttribute('value')).toBe(28); + expect(dice.getChild(0).getAttribute('success')).toBe(true); + expect(dice.getChild(1).getAttribute('success')).toBe(true); + expect(dice.getChild(2).getAttribute('success')).toBe(true); + expect(dice.getChild(3).getAttribute('success')).toBe(false); }); + }); }); diff --git a/spec/interpreter/dice-interpreter.evaluate.complex.spec.ts b/spec/interpreter/dice-interpreter.evaluate.complex.spec.ts index 8bea57d..6e932ad 100644 --- a/spec/interpreter/dice-interpreter.evaluate.complex.spec.ts +++ b/spec/interpreter/dice-interpreter.evaluate.complex.spec.ts @@ -3,51 +3,51 @@ import * as Interpreter from '../../src/interpreter'; import { MockListRandomProvider } from '../helpers'; describe('DiceInterpreter', () => { - describe('evaluate', () => { - it('evaluates a complex dice roll with modifiers (4d6!kh2sa).', () => { - const sort = Ast.Factory.create(Ast.NodeType.Sort) - .setAttribute('direction', 'ascending'); - - const keep = Ast.Factory.create(Ast.NodeType.Keep); - keep.setAttribute('type', 'highest'); - sort.addChild(keep); - - const exp = Ast.Factory.create(Ast.NodeType.Explode) - .setAttribute('penetrate', false) - .setAttribute('compound', false); - keep.addChild(exp); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - exp.addChild(dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - - keep.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - - const mockList = new MockListRandomProvider(); - mockList.numbers.push(1, 6, 4, 2, 5); - - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(sort, errors); - - expect(sort.getAttribute('value')).toBe(11); - expect(keep.getAttribute('value')).toBe(11); - expect(dice.getAttribute('value')).toBe(13); - expect(exp.getAttribute('value')).toBe(18); - - expect(dice.getChildCount()).toBe(5); - expect(dice.getChild(0).getAttribute('value')).toBe(1); - expect(dice.getChild(1).getAttribute('value')).toBe(2); - expect(dice.getChild(2).getAttribute('value')).toBe(4); - expect(dice.getChild(3).getAttribute('value')).toBe(5); - expect(dice.getChild(4).getAttribute('value')).toBe(6); - - expect(dice.getChild(0).getAttribute('drop')).toBe(true); - expect(dice.getChild(1).getAttribute('drop')).toBe(true); - expect(dice.getChild(2).getAttribute('drop')).toBe(true); - expect(dice.getChild(3).getAttribute('drop')).toBe(false); - expect(dice.getChild(4).getAttribute('drop')).toBe(false); - }); + describe('evaluate', () => { + it('evaluates a complex dice roll with modifiers (4d6!kh2sa).', () => { + const sort = Ast.Factory.create(Ast.NodeType.Sort) + .setAttribute('direction', 'ascending'); + + const keep = Ast.Factory.create(Ast.NodeType.Keep); + keep.setAttribute('type', 'highest'); + sort.addChild(keep); + + const exp = Ast.Factory.create(Ast.NodeType.Explode) + .setAttribute('penetrate', false) + .setAttribute('compound', false); + keep.addChild(exp); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + exp.addChild(dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + + keep.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + + const mockList = new MockListRandomProvider(); + mockList.numbers.push(1, 6, 4, 2, 5); + + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(sort, errors); + + expect(sort.getAttribute('value')).toBe(11); + expect(keep.getAttribute('value')).toBe(11); + expect(dice.getAttribute('value')).toBe(13); + expect(exp.getAttribute('value')).toBe(18); + + expect(dice.getChildCount()).toBe(5); + expect(dice.getChild(0).getAttribute('value')).toBe(1); + expect(dice.getChild(1).getAttribute('value')).toBe(2); + expect(dice.getChild(2).getAttribute('value')).toBe(4); + expect(dice.getChild(3).getAttribute('value')).toBe(5); + expect(dice.getChild(4).getAttribute('value')).toBe(6); + + expect(dice.getChild(0).getAttribute('drop')).toBe(true); + expect(dice.getChild(1).getAttribute('drop')).toBe(true); + expect(dice.getChild(2).getAttribute('drop')).toBe(true); + expect(dice.getChild(3).getAttribute('drop')).toBe(false); + expect(dice.getChild(4).getAttribute('drop')).toBe(false); }); + }); }); diff --git a/spec/interpreter/dice-interpreter.evaluate.critical.spec.ts b/spec/interpreter/dice-interpreter.evaluate.critical.spec.ts index 97de41f..813c987 100644 --- a/spec/interpreter/dice-interpreter.evaluate.critical.spec.ts +++ b/spec/interpreter/dice-interpreter.evaluate.critical.spec.ts @@ -3,118 +3,118 @@ import * as Interpreter from '../../src/interpreter'; import { MockListRandomProvider } from '../helpers'; describe('DiceInterpreter', () => { - describe('evaluate', () => { - it('evaluates critical success dice no condition (4d20cs).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Critical) - .setAttribute('type', 'success'); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); - - exp.addChild(dice); - - const mockList = new MockListRandomProvider(); - mockList.numbers.push(8, 12, 18, 20); - - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(exp, errors); - - expect(exp.getAttribute('value')).toBe(20); - - expect(dice.getChildCount()).toBe(4); - expect(dice.getAttribute('value')).toBe(58); - expect(dice.getChild(0).getAttribute('critical')).toBeUndefined(); - expect(dice.getChild(1).getAttribute('critical')).toBeUndefined(); - expect(dice.getChild(2).getAttribute('critical')).toBeUndefined(); - expect(dice.getChild(3).getAttribute('critical')).toBe('success'); - }); - it('evaluates critical success dice (4d20cs>=18).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Critical) - .setAttribute('type', 'success'); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); - - const comp = Ast.Factory.create(Ast.NodeType.GreaterOrEqual); - comp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 18)); - - exp.addChild(dice); - exp.addChild(comp); - - const mockList = new MockListRandomProvider(); - mockList.numbers.push(8, 12, 18, 19); - - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(exp, errors); - - expect(exp.getAttribute('value')).toBe(37); - - expect(dice.getChildCount()).toBe(4); - expect(dice.getAttribute('value')).toBe(57); - expect(dice.getChild(0).getAttribute('critical')).toBeUndefined(); - expect(dice.getChild(1).getAttribute('critical')).toBeUndefined(); - expect(dice.getChild(2).getAttribute('critical')).toBe('success'); - expect(dice.getChild(3).getAttribute('critical')).toBe('success'); - }); - it('evaluates critical failure dice no condition (4d20cf).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Critical) - .setAttribute('type', 'fail'); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); - - exp.addChild(dice); - - const mockList = new MockListRandomProvider(); - mockList.numbers.push(8, 1, 18, 20); - - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(exp, errors); - - expect(exp.getAttribute('value')).toBe(1); - - expect(dice.getChildCount()).toBe(4); - expect(dice.getAttribute('value')).toBe(47); - expect(dice.getChild(0).getAttribute('critical')).toBeUndefined(); - expect(dice.getChild(1).getAttribute('critical')).toBe('fail'); - expect(dice.getChild(2).getAttribute('critical')).toBeUndefined(); - expect(dice.getChild(3).getAttribute('critical')).toBeUndefined(); - }); - it('evaluates critical success dice (4d20cf<=3).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Critical) - .setAttribute('type', 'fail'); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); - - const comp = Ast.Factory.create(Ast.NodeType.LessOrEqual); - comp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 3)); - - exp.addChild(dice); - exp.addChild(comp); - - const mockList = new MockListRandomProvider(); - mockList.numbers.push(8, 2, 3, 19); - - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(exp, errors); - - expect(exp.getAttribute('value')).toBe(5); - - expect(dice.getChildCount()).toBe(4); - expect(dice.getAttribute('value')).toBe(32); - expect(dice.getChild(0).getAttribute('critical')).toBeUndefined(); - expect(dice.getChild(1).getAttribute('critical')).toBe('fail'); - expect(dice.getChild(2).getAttribute('critical')).toBe('fail'); - expect(dice.getChild(3).getAttribute('critical')).toBeUndefined(); - }); + describe('evaluate', () => { + it('evaluates critical success dice no condition (4d20cs).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Critical) + .setAttribute('type', 'success'); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); + + exp.addChild(dice); + + const mockList = new MockListRandomProvider(); + mockList.numbers.push(8, 12, 18, 20); + + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(exp, errors); + + expect(exp.getAttribute('value')).toBe(20); + + expect(dice.getChildCount()).toBe(4); + expect(dice.getAttribute('value')).toBe(58); + expect(dice.getChild(0).getAttribute('critical')).toBeUndefined(); + expect(dice.getChild(1).getAttribute('critical')).toBeUndefined(); + expect(dice.getChild(2).getAttribute('critical')).toBeUndefined(); + expect(dice.getChild(3).getAttribute('critical')).toBe('success'); + }); + it('evaluates critical success dice (4d20cs>=18).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Critical) + .setAttribute('type', 'success'); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); + + const comp = Ast.Factory.create(Ast.NodeType.GreaterOrEqual); + comp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 18)); + + exp.addChild(dice); + exp.addChild(comp); + + const mockList = new MockListRandomProvider(); + mockList.numbers.push(8, 12, 18, 19); + + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(exp, errors); + + expect(exp.getAttribute('value')).toBe(37); + + expect(dice.getChildCount()).toBe(4); + expect(dice.getAttribute('value')).toBe(57); + expect(dice.getChild(0).getAttribute('critical')).toBeUndefined(); + expect(dice.getChild(1).getAttribute('critical')).toBeUndefined(); + expect(dice.getChild(2).getAttribute('critical')).toBe('success'); + expect(dice.getChild(3).getAttribute('critical')).toBe('success'); + }); + it('evaluates critical failure dice no condition (4d20cf).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Critical) + .setAttribute('type', 'fail'); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); + + exp.addChild(dice); + + const mockList = new MockListRandomProvider(); + mockList.numbers.push(8, 1, 18, 20); + + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(exp, errors); + + expect(exp.getAttribute('value')).toBe(1); + + expect(dice.getChildCount()).toBe(4); + expect(dice.getAttribute('value')).toBe(47); + expect(dice.getChild(0).getAttribute('critical')).toBeUndefined(); + expect(dice.getChild(1).getAttribute('critical')).toBe('fail'); + expect(dice.getChild(2).getAttribute('critical')).toBeUndefined(); + expect(dice.getChild(3).getAttribute('critical')).toBeUndefined(); + }); + it('evaluates critical success dice (4d20cf<=3).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Critical) + .setAttribute('type', 'fail'); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); + + const comp = Ast.Factory.create(Ast.NodeType.LessOrEqual); + comp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 3)); + + exp.addChild(dice); + exp.addChild(comp); + + const mockList = new MockListRandomProvider(); + mockList.numbers.push(8, 2, 3, 19); + + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(exp, errors); + + expect(exp.getAttribute('value')).toBe(5); + + expect(dice.getChildCount()).toBe(4); + expect(dice.getAttribute('value')).toBe(32); + expect(dice.getChild(0).getAttribute('critical')).toBeUndefined(); + expect(dice.getChild(1).getAttribute('critical')).toBe('fail'); + expect(dice.getChild(2).getAttribute('critical')).toBe('fail'); + expect(dice.getChild(3).getAttribute('critical')).toBeUndefined(); }); + }); }); diff --git a/spec/interpreter/dice-interpreter.evaluate.dice-roll.spec.ts b/spec/interpreter/dice-interpreter.evaluate.dice-roll.spec.ts index 2bf190b..46acc76 100644 --- a/spec/interpreter/dice-interpreter.evaluate.dice-roll.spec.ts +++ b/spec/interpreter/dice-interpreter.evaluate.dice-roll.spec.ts @@ -3,26 +3,26 @@ import * as Interpreter from '../../src/interpreter'; import { MockRandomProvider } from '../helpers'; describe('DiceInterpreter', () => { - describe('evaluate', () => { - it('evaluates a simple dice roll expression (dropped).', () => { - const dice = Ast.Factory.create(Ast.NodeType.DiceRoll) - .setAttribute('drop', true) - .setAttribute('value', 4); + describe('evaluate', () => { + it('evaluates a simple dice roll expression (dropped).', () => { + const dice = Ast.Factory.create(Ast.NodeType.DiceRoll) + .setAttribute('drop', true) + .setAttribute('value', 4); - const interpreter = new Interpreter.DiceInterpreter(); - const errors: Interpreter.ErrorMessage[] = []; - expect(interpreter.evaluate(dice, errors)).toBe(0); - expect(dice.getAttribute('value')).toBe(4); - }); - it('evaluates a simple dice roll expression (not dropped).', () => { - const dice = Ast.Factory.create(Ast.NodeType.DiceRoll) - .setAttribute('drop', false) - .setAttribute('value', 4); + const interpreter = new Interpreter.DiceInterpreter(); + const errors: Interpreter.InterpreterError[] = []; + expect(interpreter.evaluate(dice, errors)).toBe(0); + expect(dice.getAttribute('value')).toBe(4); + }); + it('evaluates a simple dice roll expression (not dropped).', () => { + const dice = Ast.Factory.create(Ast.NodeType.DiceRoll) + .setAttribute('drop', false) + .setAttribute('value', 4); - const interpreter = new Interpreter.DiceInterpreter(); - const errors: Interpreter.ErrorMessage[] = []; - expect(interpreter.evaluate(dice, errors)).toBe(4); - expect(dice.getAttribute('value')).toBe(4); - }); + const interpreter = new Interpreter.DiceInterpreter(); + const errors: Interpreter.InterpreterError[] = []; + expect(interpreter.evaluate(dice, errors)).toBe(4); + expect(dice.getAttribute('value')).toBe(4); }); + }); }); diff --git a/spec/interpreter/dice-interpreter.evaluate.dice-sides.spec.ts b/spec/interpreter/dice-interpreter.evaluate.dice-sides.spec.ts index 7cf652c..33c39ca 100644 --- a/spec/interpreter/dice-interpreter.evaluate.dice-sides.spec.ts +++ b/spec/interpreter/dice-interpreter.evaluate.dice-sides.spec.ts @@ -3,13 +3,13 @@ import * as Interpreter from '../../src/interpreter'; import { MockRandomProvider } from '../helpers'; describe('DiceInterpreter', () => { - describe('evaluate', () => { - it('correctly evaluates a dice side.', () => { - const int = Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 'fate'); - const interpreter = new Interpreter.DiceInterpreter(); - const errors: Interpreter.ErrorMessage[] = []; - expect(interpreter.evaluate(int, errors)).toBe('fate'); - expect(int.getAttribute('value')).toBe('fate'); - }); + describe('evaluate', () => { + it('correctly evaluates a dice side.', () => { + const int = Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 'fate'); + const interpreter = new Interpreter.DiceInterpreter(); + const errors: Interpreter.InterpreterError[] = []; + expect(interpreter.evaluate(int, errors)).toBe('fate'); + expect(int.getAttribute('value')).toBe('fate'); }); + }); }); diff --git a/spec/interpreter/dice-interpreter.evaluate.dice.spec.ts b/spec/interpreter/dice-interpreter.evaluate.dice.spec.ts index b58f8c7..86b41a7 100644 --- a/spec/interpreter/dice-interpreter.evaluate.dice.spec.ts +++ b/spec/interpreter/dice-interpreter.evaluate.dice.spec.ts @@ -3,193 +3,193 @@ import * as Interpreter from '../../src/interpreter'; import { MockRandomProvider } from '../helpers'; describe('DiceInterpreter', () => { - describe('evaluate', () => { - it('evaluates a simple dice expression (2d6).', () => { - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - - const interpreter = new Interpreter.DiceInterpreter(null, new MockRandomProvider(4)); - const errors: Interpreter.ErrorMessage[] = []; - expect(interpreter.evaluate(dice, errors)).toBe(8); - expect(dice.getAttribute('value')).toBe(8); - }); - it('evaluates a hidden simple dice expression (2d6 + 4).', () => { - const add = Ast.Factory.create(Ast.NodeType.Add); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - - add.addChild(dice); - add.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); - - const interpreter = new Interpreter.DiceInterpreter(null, new MockRandomProvider(3)); - const errors: Interpreter.ErrorMessage[] = []; - expect(interpreter.evaluate(add, errors)).toBe(10); - expect(dice.getAttribute('value')).toBe(6); - }); - it('evaluates a complex dice expression ((1 + 2)d6).', () => { - const dice = Ast.Factory.create(Ast.NodeType.Dice); - - const add = Ast.Factory.create(Ast.NodeType.Add); - add.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 1)); - add.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - - dice.addChild(add); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - - const interpreter = new Interpreter.DiceInterpreter(null, new MockRandomProvider(2)); - const errors: Interpreter.ErrorMessage[] = []; - expect(interpreter.evaluate(dice, errors)).toBe(6); - expect(dice.getAttribute('value')).toBe(6); - }); - it('reduces a simple dice expression (2d6).', () => { - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - - const interpreter = new Interpreter.DiceInterpreter(); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(dice, errors); - - expect(dice.getChildCount()).toBe(2); - expect(dice.getChild(0).type).toBe(Ast.NodeType.DiceRoll); - expect(dice.getChild(0).getAttribute('value')).toBeGreaterThanOrEqual(1); - expect(dice.getChild(0).getAttribute('value')).toBeLessThanOrEqual(6); - - expect(dice.getChild(1).type).toBe(Ast.NodeType.DiceRoll); - expect(dice.getChild(1).getAttribute('value')).toBeGreaterThanOrEqual(1); - expect(dice.getChild(1).getAttribute('value')).toBeLessThanOrEqual(6); - }); - it('reduces a simple fate dice expression (2dF).', () => { - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 'fate')); - - const interpreter = new Interpreter.DiceInterpreter(); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(dice, errors); - - expect(dice.getChildCount()).toBe(2); - expect(dice.getChild(0).type).toBe(Ast.NodeType.DiceRoll); - expect(dice.getChild(0).getAttribute('value')).toBeGreaterThanOrEqual(-1); - expect(dice.getChild(0).getAttribute('value')).toBeLessThanOrEqual(1); - - expect(dice.getChild(1).type).toBe(Ast.NodeType.DiceRoll); - expect(dice.getChild(1).getAttribute('value')).toBeGreaterThanOrEqual(-1); - expect(dice.getChild(1).getAttribute('value')).toBeLessThanOrEqual(1); - }); - it('reduces a hidden simple dice expression (2d6 + 4).', () => { - const add = Ast.Factory.create(Ast.NodeType.Add); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - - add.addChild(dice); - add.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); - - const interpreter = new Interpreter.DiceInterpreter(); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(add, errors); - - expect(add.getChildCount()).toBe(2); - expect(add.getChild(0).type).toBe(Ast.NodeType.Dice); - expect(add.getChild(0).getChildCount()).toBe(2); - expect(add.getChild(0).getChild(0).type).toBe(Ast.NodeType.DiceRoll); - expect(add.getChild(0).getChild(0).getAttribute('value')).toBeLessThanOrEqual(6); - expect(add.getChild(0).getChild(0).getAttribute('value')).toBeGreaterThanOrEqual(1); - expect(add.getChild(0).getChild(1).type).toBe(Ast.NodeType.DiceRoll); - expect(add.getChild(0).getChild(1).getAttribute('value')).toBeLessThanOrEqual(6); - expect(add.getChild(0).getChild(1).getAttribute('value')).toBeGreaterThanOrEqual(1); - - expect(add.getChild(1).type).toBe(Ast.NodeType.Number); - expect(add.getChild(1).getAttribute('value')).toBe(4); - }); - it('reduces a complex dice expression ((1 + 2)d6).', () => { - const dice = Ast.Factory.create(Ast.NodeType.Dice); - - const add = Ast.Factory.create(Ast.NodeType.Add); - add.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 1)); - add.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - - dice.addChild(add); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - - const interpreter = new Interpreter.DiceInterpreter(); - - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(dice, errors); - - expect(dice.getChildCount()).toBe(3); - expect(dice.getChild(0).type).toBe(Ast.NodeType.DiceRoll); - expect(dice.getChild(0).getAttribute('value')).toBeGreaterThanOrEqual(1); - expect(dice.getChild(0).getAttribute('value')).toBeLessThanOrEqual(6); - - expect(dice.getChild(1).type).toBe(Ast.NodeType.DiceRoll); - expect(dice.getChild(1).getAttribute('value')).toBeGreaterThanOrEqual(1); - expect(dice.getChild(1).getAttribute('value')).toBeLessThanOrEqual(6); - - expect(dice.getChild(2).type).toBe(Ast.NodeType.DiceRoll); - expect(dice.getChild(2).getAttribute('value')).toBeGreaterThanOrEqual(1); - expect(dice.getChild(2).getAttribute('value')).toBeLessThanOrEqual(6); - }); - it('evaluates fractional dice rolls >=.5 ((5 / 2)d6).', () => { - const dice = Ast.Factory.create(Ast.NodeType.Dice); - - const divide = Ast.Factory.create(Ast.NodeType.Divide); - divide.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - divide.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - - dice.addChild(divide); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - - const interpreter = new Interpreter.DiceInterpreter(null, new MockRandomProvider(2)); - - // 5 / 2 = 2.5, which is rounded to 3. - const errors: Interpreter.ErrorMessage[] = []; - expect(interpreter.evaluate(dice, errors)).toBe(6); - }); - it('evaluates fractional dice rolls <.5 ((7 / 5)d6).', () => { - const dice = Ast.Factory.create(Ast.NodeType.Dice); - - const divide = Ast.Factory.create(Ast.NodeType.Divide); - divide.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 7)); - divide.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - - dice.addChild(divide); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - - const interpreter = new Interpreter.DiceInterpreter(null, new MockRandomProvider(2)); - - // 7 / 5 = 1.4, which is rounded to 1. - const errors: Interpreter.ErrorMessage[] = []; - expect(interpreter.evaluate(dice, errors)).toBe(2); - }); - it('evaluates negative dice rolls at 0. ((-5)d6).', () => { - const dice = Ast.Factory.create(Ast.NodeType.Dice); - - const negate = Ast.Factory.create(Ast.NodeType.Negate); - negate.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - - dice.addChild(negate); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - - const interpreter = new Interpreter.DiceInterpreter(null, new MockRandomProvider(2)); - - // -5d6 will be interpreted as 0. - const errors: Interpreter.ErrorMessage[] = []; - expect(interpreter.evaluate(dice, errors)).toBe(0); - }); - it('throws on an invalid dice expression (2d).', () => { - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - - const interpreter = new Interpreter.DiceInterpreter(null, new MockRandomProvider(4)); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(dice, errors); - expect(errors.length).toBeGreaterThanOrEqual(1); - }); + describe('evaluate', () => { + it('evaluates a simple dice expression (2d6).', () => { + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + + const interpreter = new Interpreter.DiceInterpreter(null, new MockRandomProvider(4)); + const errors: Interpreter.InterpreterError[] = []; + expect(interpreter.evaluate(dice, errors)).toBe(8); + expect(dice.getAttribute('value')).toBe(8); }); + it('evaluates a hidden simple dice expression (2d6 + 4).', () => { + const add = Ast.Factory.create(Ast.NodeType.Add); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + + add.addChild(dice); + add.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); + + const interpreter = new Interpreter.DiceInterpreter(null, new MockRandomProvider(3)); + const errors: Interpreter.InterpreterError[] = []; + expect(interpreter.evaluate(add, errors)).toBe(10); + expect(dice.getAttribute('value')).toBe(6); + }); + it('evaluates a complex dice expression ((1 + 2)d6).', () => { + const dice = Ast.Factory.create(Ast.NodeType.Dice); + + const add = Ast.Factory.create(Ast.NodeType.Add); + add.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 1)); + add.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + + dice.addChild(add); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + + const interpreter = new Interpreter.DiceInterpreter(null, new MockRandomProvider(2)); + const errors: Interpreter.InterpreterError[] = []; + expect(interpreter.evaluate(dice, errors)).toBe(6); + expect(dice.getAttribute('value')).toBe(6); + }); + it('reduces a simple dice expression (2d6).', () => { + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + + const interpreter = new Interpreter.DiceInterpreter(); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(dice, errors); + + expect(dice.getChildCount()).toBe(2); + expect(dice.getChild(0).type).toBe(Ast.NodeType.DiceRoll); + expect(dice.getChild(0).getAttribute('value')).toBeGreaterThanOrEqual(1); + expect(dice.getChild(0).getAttribute('value')).toBeLessThanOrEqual(6); + + expect(dice.getChild(1).type).toBe(Ast.NodeType.DiceRoll); + expect(dice.getChild(1).getAttribute('value')).toBeGreaterThanOrEqual(1); + expect(dice.getChild(1).getAttribute('value')).toBeLessThanOrEqual(6); + }); + it('reduces a simple fate dice expression (2dF).', () => { + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 'fate')); + + const interpreter = new Interpreter.DiceInterpreter(); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(dice, errors); + + expect(dice.getChildCount()).toBe(2); + expect(dice.getChild(0).type).toBe(Ast.NodeType.DiceRoll); + expect(dice.getChild(0).getAttribute('value')).toBeGreaterThanOrEqual(-1); + expect(dice.getChild(0).getAttribute('value')).toBeLessThanOrEqual(1); + + expect(dice.getChild(1).type).toBe(Ast.NodeType.DiceRoll); + expect(dice.getChild(1).getAttribute('value')).toBeGreaterThanOrEqual(-1); + expect(dice.getChild(1).getAttribute('value')).toBeLessThanOrEqual(1); + }); + it('reduces a hidden simple dice expression (2d6 + 4).', () => { + const add = Ast.Factory.create(Ast.NodeType.Add); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + + add.addChild(dice); + add.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); + + const interpreter = new Interpreter.DiceInterpreter(); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(add, errors); + + expect(add.getChildCount()).toBe(2); + expect(add.getChild(0).type).toBe(Ast.NodeType.Dice); + expect(add.getChild(0).getChildCount()).toBe(2); + expect(add.getChild(0).getChild(0).type).toBe(Ast.NodeType.DiceRoll); + expect(add.getChild(0).getChild(0).getAttribute('value')).toBeLessThanOrEqual(6); + expect(add.getChild(0).getChild(0).getAttribute('value')).toBeGreaterThanOrEqual(1); + expect(add.getChild(0).getChild(1).type).toBe(Ast.NodeType.DiceRoll); + expect(add.getChild(0).getChild(1).getAttribute('value')).toBeLessThanOrEqual(6); + expect(add.getChild(0).getChild(1).getAttribute('value')).toBeGreaterThanOrEqual(1); + + expect(add.getChild(1).type).toBe(Ast.NodeType.Number); + expect(add.getChild(1).getAttribute('value')).toBe(4); + }); + it('reduces a complex dice expression ((1 + 2)d6).', () => { + const dice = Ast.Factory.create(Ast.NodeType.Dice); + + const add = Ast.Factory.create(Ast.NodeType.Add); + add.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 1)); + add.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + + dice.addChild(add); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + + const interpreter = new Interpreter.DiceInterpreter(); + + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(dice, errors); + + expect(dice.getChildCount()).toBe(3); + expect(dice.getChild(0).type).toBe(Ast.NodeType.DiceRoll); + expect(dice.getChild(0).getAttribute('value')).toBeGreaterThanOrEqual(1); + expect(dice.getChild(0).getAttribute('value')).toBeLessThanOrEqual(6); + + expect(dice.getChild(1).type).toBe(Ast.NodeType.DiceRoll); + expect(dice.getChild(1).getAttribute('value')).toBeGreaterThanOrEqual(1); + expect(dice.getChild(1).getAttribute('value')).toBeLessThanOrEqual(6); + + expect(dice.getChild(2).type).toBe(Ast.NodeType.DiceRoll); + expect(dice.getChild(2).getAttribute('value')).toBeGreaterThanOrEqual(1); + expect(dice.getChild(2).getAttribute('value')).toBeLessThanOrEqual(6); + }); + it('evaluates fractional dice rolls >=.5 ((5 / 2)d6).', () => { + const dice = Ast.Factory.create(Ast.NodeType.Dice); + + const divide = Ast.Factory.create(Ast.NodeType.Divide); + divide.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + divide.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + + dice.addChild(divide); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + + const interpreter = new Interpreter.DiceInterpreter(null, new MockRandomProvider(2)); + + // 5 / 2 = 2.5, which is rounded to 3. + const errors: Interpreter.InterpreterError[] = []; + expect(interpreter.evaluate(dice, errors)).toBe(6); + }); + it('evaluates fractional dice rolls <.5 ((7 / 5)d6).', () => { + const dice = Ast.Factory.create(Ast.NodeType.Dice); + + const divide = Ast.Factory.create(Ast.NodeType.Divide); + divide.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 7)); + divide.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + + dice.addChild(divide); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + + const interpreter = new Interpreter.DiceInterpreter(null, new MockRandomProvider(2)); + + // 7 / 5 = 1.4, which is rounded to 1. + const errors: Interpreter.InterpreterError[] = []; + expect(interpreter.evaluate(dice, errors)).toBe(2); + }); + it('evaluates negative dice rolls at 0. ((-5)d6).', () => { + const dice = Ast.Factory.create(Ast.NodeType.Dice); + + const negate = Ast.Factory.create(Ast.NodeType.Negate); + negate.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + + dice.addChild(negate); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + + const interpreter = new Interpreter.DiceInterpreter(null, new MockRandomProvider(2)); + + // -5d6 will be interpreted as 0. + const errors: Interpreter.InterpreterError[] = []; + expect(interpreter.evaluate(dice, errors)).toBe(0); + }); + it('throws on an invalid dice expression (2d).', () => { + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + + const interpreter = new Interpreter.DiceInterpreter(null, new MockRandomProvider(4)); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(dice, errors); + expect(errors.length).toBeGreaterThanOrEqual(1); + }); + }); }); diff --git a/spec/interpreter/dice-interpreter.evaluate.drop.spec.ts b/spec/interpreter/dice-interpreter.evaluate.drop.spec.ts index 97dbaaf..5e8511f 100644 --- a/spec/interpreter/dice-interpreter.evaluate.drop.spec.ts +++ b/spec/interpreter/dice-interpreter.evaluate.drop.spec.ts @@ -3,116 +3,116 @@ import * as Interpreter from '../../src/interpreter'; import { MockListRandomProvider } from '../helpers'; describe('DiceInterpreter', () => { - describe('evaluate', () => { - it('evaluates drop modifier (5d20dh2).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Drop) - .setAttribute('type', 'highest'); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); - - exp.addChild(dice); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - - const mockList = new MockListRandomProvider(); - mockList.numbers.push(8, 12, 18, 20, 14); - - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(exp, errors); - - expect(exp.getAttribute('value')).toBe(34); - expect(dice.getAttribute('value')).toBe(72); - - expect(dice.getChildCount()).toBe(5); - expect(dice.getChild(0).getAttribute('drop')).toBe(false); - expect(dice.getChild(1).getAttribute('drop')).toBe(false); - expect(dice.getChild(2).getAttribute('drop')).toBe(true); - expect(dice.getChild(3).getAttribute('drop')).toBe(true); - expect(dice.getChild(4).getAttribute('drop')).toBe(false); - }); - it('evaluates drop modifier (5d20dh).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Drop) - .setAttribute('type', 'highest'); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); - - exp.addChild(dice); - - const mockList = new MockListRandomProvider(); - mockList.numbers.push(8, 12, 18, 20, 14); - - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(exp, errors); - - expect(exp.getAttribute('value')).toBe(52); - expect(dice.getAttribute('value')).toBe(72); - - expect(dice.getChildCount()).toBe(5); - expect(dice.getChild(0).getAttribute('drop')).toBe(false); - expect(dice.getChild(1).getAttribute('drop')).toBe(false); - expect(dice.getChild(2).getAttribute('drop')).toBe(false); - expect(dice.getChild(3).getAttribute('drop')).toBe(true); - expect(dice.getChild(4).getAttribute('drop')).toBe(false); - }); - it('evaluates drop modifier (5d20dl2).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Drop) - .setAttribute('type', 'lowest'); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); - - exp.addChild(dice); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - - const mockList = new MockListRandomProvider(); - mockList.numbers.push(8, 12, 18, 20, 14); - - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(exp, errors); - - expect(exp.getAttribute('value')).toBe(52); - expect(dice.getAttribute('value')).toBe(72); - - expect(dice.getChildCount()).toBe(5); - expect(dice.getChild(0).getAttribute('drop')).toBe(true); - expect(dice.getChild(1).getAttribute('drop')).toBe(true); - expect(dice.getChild(2).getAttribute('drop')).toBe(false); - expect(dice.getChild(3).getAttribute('drop')).toBe(false); - expect(dice.getChild(4).getAttribute('drop')).toBe(false); - }); - it('evaluates drop modifier (5d20dl).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Drop) - .setAttribute('type', 'lowest'); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); - - exp.addChild(dice); - - const mockList = new MockListRandomProvider(); - mockList.numbers.push(8, 12, 18, 20, 14); - - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(exp, errors); - - expect(exp.getAttribute('value')).toBe(64); - expect(dice.getAttribute('value')).toBe(72); - - expect(dice.getChildCount()).toBe(5); - expect(dice.getChild(0).getAttribute('drop')).toBe(true); - expect(dice.getChild(1).getAttribute('drop')).toBe(false); - expect(dice.getChild(2).getAttribute('drop')).toBe(false); - expect(dice.getChild(3).getAttribute('drop')).toBe(false); - expect(dice.getChild(4).getAttribute('drop')).toBe(false); - }); + describe('evaluate', () => { + it('evaluates drop modifier (5d20dh2).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Drop) + .setAttribute('type', 'highest'); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); + + exp.addChild(dice); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + + const mockList = new MockListRandomProvider(); + mockList.numbers.push(8, 12, 18, 20, 14); + + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(exp, errors); + + expect(exp.getAttribute('value')).toBe(34); + expect(dice.getAttribute('value')).toBe(72); + + expect(dice.getChildCount()).toBe(5); + expect(dice.getChild(0).getAttribute('drop')).toBe(false); + expect(dice.getChild(1).getAttribute('drop')).toBe(false); + expect(dice.getChild(2).getAttribute('drop')).toBe(true); + expect(dice.getChild(3).getAttribute('drop')).toBe(true); + expect(dice.getChild(4).getAttribute('drop')).toBe(false); }); + it('evaluates drop modifier (5d20dh).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Drop) + .setAttribute('type', 'highest'); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); + + exp.addChild(dice); + + const mockList = new MockListRandomProvider(); + mockList.numbers.push(8, 12, 18, 20, 14); + + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(exp, errors); + + expect(exp.getAttribute('value')).toBe(52); + expect(dice.getAttribute('value')).toBe(72); + + expect(dice.getChildCount()).toBe(5); + expect(dice.getChild(0).getAttribute('drop')).toBe(false); + expect(dice.getChild(1).getAttribute('drop')).toBe(false); + expect(dice.getChild(2).getAttribute('drop')).toBe(false); + expect(dice.getChild(3).getAttribute('drop')).toBe(true); + expect(dice.getChild(4).getAttribute('drop')).toBe(false); + }); + it('evaluates drop modifier (5d20dl2).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Drop) + .setAttribute('type', 'lowest'); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); + + exp.addChild(dice); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + + const mockList = new MockListRandomProvider(); + mockList.numbers.push(8, 12, 18, 20, 14); + + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(exp, errors); + + expect(exp.getAttribute('value')).toBe(52); + expect(dice.getAttribute('value')).toBe(72); + + expect(dice.getChildCount()).toBe(5); + expect(dice.getChild(0).getAttribute('drop')).toBe(true); + expect(dice.getChild(1).getAttribute('drop')).toBe(true); + expect(dice.getChild(2).getAttribute('drop')).toBe(false); + expect(dice.getChild(3).getAttribute('drop')).toBe(false); + expect(dice.getChild(4).getAttribute('drop')).toBe(false); + }); + it('evaluates drop modifier (5d20dl).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Drop) + .setAttribute('type', 'lowest'); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); + + exp.addChild(dice); + + const mockList = new MockListRandomProvider(); + mockList.numbers.push(8, 12, 18, 20, 14); + + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(exp, errors); + + expect(exp.getAttribute('value')).toBe(64); + expect(dice.getAttribute('value')).toBe(72); + + expect(dice.getChildCount()).toBe(5); + expect(dice.getChild(0).getAttribute('drop')).toBe(true); + expect(dice.getChild(1).getAttribute('drop')).toBe(false); + expect(dice.getChild(2).getAttribute('drop')).toBe(false); + expect(dice.getChild(3).getAttribute('drop')).toBe(false); + expect(dice.getChild(4).getAttribute('drop')).toBe(false); + }); + }); }); diff --git a/spec/interpreter/dice-interpreter.evaluate.explode.spec.ts b/spec/interpreter/dice-interpreter.evaluate.explode.spec.ts index ff02ac5..5149f2a 100644 --- a/spec/interpreter/dice-interpreter.evaluate.explode.spec.ts +++ b/spec/interpreter/dice-interpreter.evaluate.explode.spec.ts @@ -3,60 +3,60 @@ import * as Interpreter from '../../src/interpreter'; import { MockListRandomProvider } from '../helpers'; describe('DiceInterpreter', () => { - describe('evaluate', () => { - it('evaluates an exploding dice (4d6!>3).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Explode) - .setAttribute('compound', false) - .setAttribute('penetrate', false); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 6)); - - const greater = Ast.Factory.create(Ast.NodeType.Greater); - greater.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 3)); - - exp.addChild(dice); - exp.addChild(greater); - - const mockList = new MockListRandomProvider(); - mockList.numbers.push( - 1, 2, 3, 4, // This 4 should get exploded into the next. - 1 - ); - - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - expect(interpreter.evaluate(exp, errors)).toBe(11); - - expect(dice.getChildCount()).toBe(5); - }); - it('evaluates a penetrating dice (4d6!p>3).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Explode) - .setAttribute('compound', false) - .setAttribute('penetrate', true); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 6)); - - const greater = Ast.Factory.create(Ast.NodeType.Greater); - greater.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 3)); - - exp.addChild(dice); - exp.addChild(greater); - - const mockList = new MockListRandomProvider(); - mockList.numbers.push( - 1, 2, 3, 4, // This 4 should get exploded into the next. - 2 - ); - - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - expect(interpreter.evaluate(exp, errors)).toBe(11); - - expect(dice.getChildCount()).toBe(5); - }); + describe('evaluate', () => { + it('evaluates an exploding dice (4d6!>3).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Explode) + .setAttribute('compound', false) + .setAttribute('penetrate', false); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 6)); + + const greater = Ast.Factory.create(Ast.NodeType.Greater); + greater.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 3)); + + exp.addChild(dice); + exp.addChild(greater); + + const mockList = new MockListRandomProvider(); + mockList.numbers.push( + 1, 2, 3, 4, // This 4 should get exploded into the next. + 1 + ); + + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + expect(interpreter.evaluate(exp, errors)).toBe(11); + + expect(dice.getChildCount()).toBe(5); + }); + it('evaluates a penetrating dice (4d6!p>3).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Explode) + .setAttribute('compound', false) + .setAttribute('penetrate', true); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 6)); + + const greater = Ast.Factory.create(Ast.NodeType.Greater); + greater.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 3)); + + exp.addChild(dice); + exp.addChild(greater); + + const mockList = new MockListRandomProvider(); + mockList.numbers.push( + 1, 2, 3, 4, // This 4 should get exploded into the next. + 2 + ); + + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + expect(interpreter.evaluate(exp, errors)).toBe(11); + + expect(dice.getChildCount()).toBe(5); }); + }); }); diff --git a/spec/interpreter/dice-interpreter.evaluate.functions.spec.ts b/spec/interpreter/dice-interpreter.evaluate.functions.spec.ts index 11659db..a707664 100644 --- a/spec/interpreter/dice-interpreter.evaluate.functions.spec.ts +++ b/spec/interpreter/dice-interpreter.evaluate.functions.spec.ts @@ -2,84 +2,86 @@ import * as Ast from '../../src/ast'; import * as Interpreter from '../../src/interpreter'; describe('DiceInterpreter', () => { - describe('evaluate', () => { - it('correctly evaluates a function(floor(5 / 2)).', () => { - const func = Ast.Factory.create(Ast.NodeType.Function).setAttribute('name', 'floor'); - - const exp = Ast.Factory.create(Ast.NodeType.Divide); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - - func.addChild(exp); - - const interpreter = new Interpreter.DiceInterpreter(); - const errors: Interpreter.ErrorMessage[] = []; - const res = interpreter.evaluate(func, errors); - expect(res).toBe(2); - }); - it('correctly evaluates a function(ceil(5 / 2)).', () => { - const func = Ast.Factory.create(Ast.NodeType.Function).setAttribute('name', 'ceil'); - - const exp = Ast.Factory.create(Ast.NodeType.Divide); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - - func.addChild(exp); - - const interpreter = new Interpreter.DiceInterpreter(); - - const errors: Interpreter.ErrorMessage[] = []; - const res = interpreter.evaluate(func, errors); - expect(res).toBe(3); - }); - it('correctly evaluates a function(sqrt(9)).', () => { - const func = Ast.Factory.create(Ast.NodeType.Function).setAttribute('name', 'sqrt'); - func.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 9)); - - const interpreter = new Interpreter.DiceInterpreter(); - const errors: Interpreter.ErrorMessage[] = []; - const res = interpreter.evaluate(func, errors); - expect(res).toBe(3); - }); - it('correctly evaluates a function(abs(-9)).', () => { - const func = Ast.Factory.create(Ast.NodeType.Function).setAttribute('name', 'abs'); - - const negate = Ast.Factory.create(Ast.NodeType.Negate); - negate.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 9)); - - func.addChild(negate); - - const interpreter = new Interpreter.DiceInterpreter(); - const errors: Interpreter.ErrorMessage[] = []; - const res = interpreter.evaluate(func, errors); - expect(res).toBe(9); - }); - it('correctly evaluates a function(round(5 / 2)).', () => { - const func = Ast.Factory.create(Ast.NodeType.Function).setAttribute('name', 'round'); - - const exp = Ast.Factory.create(Ast.NodeType.Divide); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - - func.addChild(exp); - - const interpreter = new Interpreter.DiceInterpreter(); - const errors: Interpreter.ErrorMessage[] = []; - const res = interpreter.evaluate(func, errors); - expect(res).toBe(3); - }); - it('throws an error on an unknown function.', () => { - const func = Ast.Factory.create(Ast.NodeType.Function).setAttribute('name', 'xxx'); - - const exp = Ast.Factory.create(Ast.NodeType.Divide); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - - func.addChild(exp); - - const interpreter = new Interpreter.DiceInterpreter(); - const errors: Interpreter.ErrorMessage[] = []; - expect(() => interpreter.evaluate(func, errors)).toThrow(); - }); + describe('evaluate', () => { + it('correctly evaluates a function(floor(5 / 2)).', () => { + const func = Ast.Factory.create(Ast.NodeType.Function).setAttribute('name', 'floor'); + + const exp = Ast.Factory.create(Ast.NodeType.Divide); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + + func.addChild(exp); + + const interpreter = new Interpreter.DiceInterpreter(); + const errors: Interpreter.InterpreterError[] = []; + const res = interpreter.evaluate(func, errors); + expect(res).toBe(2); + }); + it('correctly evaluates a function(ceil(5 / 2)).', () => { + const func = Ast.Factory.create(Ast.NodeType.Function).setAttribute('name', 'ceil'); + + const exp = Ast.Factory.create(Ast.NodeType.Divide); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + + func.addChild(exp); + + const interpreter = new Interpreter.DiceInterpreter(); + + const errors: Interpreter.InterpreterError[] = []; + const res = interpreter.evaluate(func, errors); + expect(res).toBe(3); + }); + it('correctly evaluates a function(sqrt(9)).', () => { + const func = Ast.Factory.create(Ast.NodeType.Function).setAttribute('name', 'sqrt'); + func.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 9)); + + const interpreter = new Interpreter.DiceInterpreter(); + const errors: Interpreter.InterpreterError[] = []; + const res = interpreter.evaluate(func, errors); + expect(res).toBe(3); + }); + it('correctly evaluates a function(abs(-9)).', () => { + const func = Ast.Factory.create(Ast.NodeType.Function).setAttribute('name', 'abs'); + + const negate = Ast.Factory.create(Ast.NodeType.Negate); + negate.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 9)); + + func.addChild(negate); + + const interpreter = new Interpreter.DiceInterpreter(); + const errors: Interpreter.InterpreterError[] = []; + const res = interpreter.evaluate(func, errors); + expect(res).toBe(9); + }); + it('correctly evaluates a function(round(5 / 2)).', () => { + const func = Ast.Factory.create(Ast.NodeType.Function).setAttribute('name', 'round'); + + const exp = Ast.Factory.create(Ast.NodeType.Divide); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + + func.addChild(exp); + + const interpreter = new Interpreter.DiceInterpreter(); + const errors: Interpreter.InterpreterError[] = []; + const res = interpreter.evaluate(func, errors); + expect(res).toBe(3); + }); + it('returns an error on an unknown function.', () => { + const func = Ast.Factory.create(Ast.NodeType.Function).setAttribute('name', 'xxx'); + + const exp = Ast.Factory.create(Ast.NodeType.Divide); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + + func.addChild(exp); + + const interpreter = new Interpreter.DiceInterpreter(); + const errors: Interpreter.InterpreterError[] = []; + const res = interpreter.evaluate(func, errors); + expect(res).toBeFalsy(); + expect(errors.length).toBe(1); }); + }); }); diff --git a/spec/interpreter/dice-interpreter.evaluate.groups.spec.ts b/spec/interpreter/dice-interpreter.evaluate.groups.spec.ts index 2750618..d1b9f5a 100644 --- a/spec/interpreter/dice-interpreter.evaluate.groups.spec.ts +++ b/spec/interpreter/dice-interpreter.evaluate.groups.spec.ts @@ -2,57 +2,57 @@ import * as Ast from '../../src/ast'; import * as Interpreter from '../../src/interpreter'; describe('DiceInterpreter', () => { - describe('evaluate', () => { - it('correctly evaluates a group {5, 2}.', () => { - const group = Ast.Factory.create(Ast.NodeType.Group); - - group.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - group.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - - const interpreter = new Interpreter.DiceInterpreter(); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(group, errors); - expect(group.getChildCount()).toBe(2); - }); - it('correctly evaluates a group with a repeat {5...2}.', () => { - const group = Ast.Factory.create(Ast.NodeType.Group); - - const repeat = Ast.Factory.create(Ast.NodeType.Repeat); - repeat.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - repeat.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - - group.addChild(repeat); - - const interpreter = new Interpreter.DiceInterpreter(); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(group, errors); - expect(group.getChildCount()).toBe(2); - expect(group.getChild(0).type).toEqual(Ast.NodeType.Number); - expect(group.getChild(0).getAttribute('value')).toEqual(5); - expect(group.getChild(1).type).toEqual(Ast.NodeType.Number); - expect(group.getChild(1).getAttribute('value')).toEqual(5); - expect(group.getAttribute('value')).toEqual(10); - }); - it('correctly evaluates a group with modifiers {5, 2, 4}kh2.', () => { - const exp = Ast.Factory.create(Ast.NodeType.Keep) - .setAttribute('type', 'highest'); - - const group = Ast.Factory.create(Ast.NodeType.Group); - - group.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - group.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - group.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); - - exp.addChild(group); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - - const interpreter = new Interpreter.DiceInterpreter(); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(exp, errors); - expect(group.getChildCount()).toBe(3); - expect(group.getChild(0).getAttribute('drop')).toBe(false); - expect(group.getChild(1).getAttribute('drop')).toBe(true); - expect(group.getChild(2).getAttribute('drop')).toBe(false); - }); + describe('evaluate', () => { + it('correctly evaluates a group {5, 2}.', () => { + const group = Ast.Factory.create(Ast.NodeType.Group); + + group.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + group.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + + const interpreter = new Interpreter.DiceInterpreter(); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(group, errors); + expect(group.getChildCount()).toBe(2); + }); + it('correctly evaluates a group with a repeat {5...2}.', () => { + const group = Ast.Factory.create(Ast.NodeType.Group); + + const repeat = Ast.Factory.create(Ast.NodeType.Repeat); + repeat.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + repeat.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + + group.addChild(repeat); + + const interpreter = new Interpreter.DiceInterpreter(); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(group, errors); + expect(group.getChildCount()).toBe(2); + expect(group.getChild(0).type).toEqual(Ast.NodeType.Number); + expect(group.getChild(0).getAttribute('value')).toEqual(5); + expect(group.getChild(1).type).toEqual(Ast.NodeType.Number); + expect(group.getChild(1).getAttribute('value')).toEqual(5); + expect(group.getAttribute('value')).toEqual(10); + }); + it('correctly evaluates a group with modifiers {5, 2, 4}kh2.', () => { + const exp = Ast.Factory.create(Ast.NodeType.Keep) + .setAttribute('type', 'highest'); + + const group = Ast.Factory.create(Ast.NodeType.Group); + + group.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + group.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + group.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); + + exp.addChild(group); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + + const interpreter = new Interpreter.DiceInterpreter(); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(exp, errors); + expect(group.getChildCount()).toBe(3); + expect(group.getChild(0).getAttribute('drop')).toBe(false); + expect(group.getChild(1).getAttribute('drop')).toBe(true); + expect(group.getChild(2).getAttribute('drop')).toBe(false); }); + }); }); diff --git a/spec/interpreter/dice-interpreter.evaluate.keep.spec.ts b/spec/interpreter/dice-interpreter.evaluate.keep.spec.ts index aa193b8..f85bb2f 100644 --- a/spec/interpreter/dice-interpreter.evaluate.keep.spec.ts +++ b/spec/interpreter/dice-interpreter.evaluate.keep.spec.ts @@ -3,104 +3,104 @@ import * as Interpreter from '../../src/interpreter'; import { MockListRandomProvider } from '../helpers'; describe('DiceInterpreter', () => { - describe('evaluate', () => { - it('evaluates keep modifier (5d20kh2).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Keep) - .setAttribute('type', 'highest'); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); - - exp.addChild(dice); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - - const mockList = new MockListRandomProvider(); - mockList.numbers.push(8, 12, 18, 20, 14); - - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(exp, errors); - - expect(dice.getChildCount()).toBe(5); - expect(dice.getChild(0).getAttribute('drop')).toBe(true); - expect(dice.getChild(1).getAttribute('drop')).toBe(true); - expect(dice.getChild(2).getAttribute('drop')).toBe(false); - expect(dice.getChild(3).getAttribute('drop')).toBe(false); - expect(dice.getChild(4).getAttribute('drop')).toBe(true); - }); - it('evaluates keep modifier (5d20kh).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Keep) - .setAttribute('type', 'highest'); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); - - exp.addChild(dice); - - const mockList = new MockListRandomProvider(); - mockList.numbers.push(8, 12, 18, 20, 14); - - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(exp, errors); - - expect(dice.getChildCount()).toBe(5); - expect(dice.getChild(0).getAttribute('drop')).toBe(true); - expect(dice.getChild(1).getAttribute('drop')).toBe(true); - expect(dice.getChild(2).getAttribute('drop')).toBe(true); - expect(dice.getChild(3).getAttribute('drop')).toBe(false); - expect(dice.getChild(4).getAttribute('drop')).toBe(true); - }); - it('evaluates keep modifier (5d20kl2).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Keep) - .setAttribute('type', 'lowest'); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); - - exp.addChild(dice); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - - const mockList = new MockListRandomProvider(); - mockList.numbers.push(8, 12, 18, 20, 14); - - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(exp, errors); - - expect(dice.getChildCount()).toBe(5); - expect(dice.getChild(0).getAttribute('drop')).toBe(false); - expect(dice.getChild(1).getAttribute('drop')).toBe(false); - expect(dice.getChild(2).getAttribute('drop')).toBe(true); - expect(dice.getChild(3).getAttribute('drop')).toBe(true); - expect(dice.getChild(4).getAttribute('drop')).toBe(true); - }); - it('evaluates keep modifier (5d20kl).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Keep) - .setAttribute('type', 'lowest'); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); - - exp.addChild(dice); - - const mockList = new MockListRandomProvider(); - mockList.numbers.push(8, 12, 18, 20, 14); - - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(exp, errors); - - expect(dice.getChildCount()).toBe(5); - expect(dice.getChild(0).getAttribute('drop')).toBe(false); - expect(dice.getChild(1).getAttribute('drop')).toBe(true); - expect(dice.getChild(2).getAttribute('drop')).toBe(true); - expect(dice.getChild(3).getAttribute('drop')).toBe(true); - expect(dice.getChild(4).getAttribute('drop')).toBe(true); - }); + describe('evaluate', () => { + it('evaluates keep modifier (5d20kh2).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Keep) + .setAttribute('type', 'highest'); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); + + exp.addChild(dice); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + + const mockList = new MockListRandomProvider(); + mockList.numbers.push(8, 12, 18, 20, 14); + + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(exp, errors); + + expect(dice.getChildCount()).toBe(5); + expect(dice.getChild(0).getAttribute('drop')).toBe(true); + expect(dice.getChild(1).getAttribute('drop')).toBe(true); + expect(dice.getChild(2).getAttribute('drop')).toBe(false); + expect(dice.getChild(3).getAttribute('drop')).toBe(false); + expect(dice.getChild(4).getAttribute('drop')).toBe(true); }); + it('evaluates keep modifier (5d20kh).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Keep) + .setAttribute('type', 'highest'); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); + + exp.addChild(dice); + + const mockList = new MockListRandomProvider(); + mockList.numbers.push(8, 12, 18, 20, 14); + + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(exp, errors); + + expect(dice.getChildCount()).toBe(5); + expect(dice.getChild(0).getAttribute('drop')).toBe(true); + expect(dice.getChild(1).getAttribute('drop')).toBe(true); + expect(dice.getChild(2).getAttribute('drop')).toBe(true); + expect(dice.getChild(3).getAttribute('drop')).toBe(false); + expect(dice.getChild(4).getAttribute('drop')).toBe(true); + }); + it('evaluates keep modifier (5d20kl2).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Keep) + .setAttribute('type', 'lowest'); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); + + exp.addChild(dice); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + + const mockList = new MockListRandomProvider(); + mockList.numbers.push(8, 12, 18, 20, 14); + + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(exp, errors); + + expect(dice.getChildCount()).toBe(5); + expect(dice.getChild(0).getAttribute('drop')).toBe(false); + expect(dice.getChild(1).getAttribute('drop')).toBe(false); + expect(dice.getChild(2).getAttribute('drop')).toBe(true); + expect(dice.getChild(3).getAttribute('drop')).toBe(true); + expect(dice.getChild(4).getAttribute('drop')).toBe(true); + }); + it('evaluates keep modifier (5d20kl).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Keep) + .setAttribute('type', 'lowest'); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); + + exp.addChild(dice); + + const mockList = new MockListRandomProvider(); + mockList.numbers.push(8, 12, 18, 20, 14); + + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(exp, errors); + + expect(dice.getChildCount()).toBe(5); + expect(dice.getChild(0).getAttribute('drop')).toBe(false); + expect(dice.getChild(1).getAttribute('drop')).toBe(true); + expect(dice.getChild(2).getAttribute('drop')).toBe(true); + expect(dice.getChild(3).getAttribute('drop')).toBe(true); + expect(dice.getChild(4).getAttribute('drop')).toBe(true); + }); + }); }); diff --git a/spec/interpreter/dice-interpreter.evaluate.number.spec.ts b/spec/interpreter/dice-interpreter.evaluate.number.spec.ts index e983276..f1f7c07 100644 --- a/spec/interpreter/dice-interpreter.evaluate.number.spec.ts +++ b/spec/interpreter/dice-interpreter.evaluate.number.spec.ts @@ -2,12 +2,12 @@ import * as Ast from '../../src/ast'; import * as Interpreter from '../../src/interpreter'; describe('DiceInterpreter', () => { - describe('evaluate', () => { - it('correctly evaluates a group {5, 2}.', () => { - const int = Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4); - const interpreter = new Interpreter.DiceInterpreter(); - const errors: Interpreter.ErrorMessage[] = []; - expect(interpreter.evaluate(int, errors)).toBe(4); - }); + describe('evaluate', () => { + it('correctly evaluates a group {5, 2}.', () => { + const int = Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4); + const interpreter = new Interpreter.DiceInterpreter(); + const errors: Interpreter.InterpreterError[] = []; + expect(interpreter.evaluate(int, errors)).toBe(4); }); + }); }); diff --git a/spec/interpreter/dice-interpreter.evaluate.reroll.spec.ts b/spec/interpreter/dice-interpreter.evaluate.reroll.spec.ts index daa897b..83f8247 100644 --- a/spec/interpreter/dice-interpreter.evaluate.reroll.spec.ts +++ b/spec/interpreter/dice-interpreter.evaluate.reroll.spec.ts @@ -3,119 +3,119 @@ import * as Interpreter from '../../src/interpreter'; import { MockListRandomProvider } from '../helpers'; describe('DiceInterpreter', () => { - describe('evaluate', () => { - it('evaluates rerolling dice (4d6r<3).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Reroll) - .setAttribute('once', false); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 6)); - - const less = Ast.Factory.create(Ast.NodeType.Less); - less.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 3)); - - exp.addChild(dice); - exp.addChild(less); - - const mockList = new MockListRandomProvider(); - mockList.numbers.push( - 6, 5, 6, 2, // This 2 should get re-rolled into a 1, then re-rolled into a 4. - 1, 4 - ); - - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - expect(interpreter.evaluate(exp, errors)).toBe(21); - expect(dice.getChildCount()).toBe(4); - }); - it('evaluates a rerolling dice (4d6ro<3).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Reroll) - .setAttribute('once', true); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 6)); - - const less = Ast.Factory.create(Ast.NodeType.Less); - less.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 3)); - - exp.addChild(dice); - exp.addChild(less); - - const mockList = new MockListRandomProvider(); - mockList.numbers.push( - 6, 5, 6, 2, // This 2 should get re-rolled into a 1, and then stop. - 1 - ); - - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - expect(interpreter.evaluate(exp, errors)).toBe(18); - expect(dice.getChildCount()).toBe(4); - }); - it('evaluates rerolling dice (4d6r).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Reroll) - .setAttribute('once', false); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 6)); - exp.addChild(dice); - - const mockList = new MockListRandomProvider(); - mockList.numbers.push( - 6, 5, 6, 1, // This 1 should get re-rolled into another 1, then re-rolled into a 4. - 1, 4 - ); - - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - expect(interpreter.evaluate(exp, errors)).toBe(21); - expect(dice.getChildCount()).toBe(4); - }); - it('evaluates a rerolling dice no condition (4d6ro).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Reroll) - .setAttribute('once', true); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 6)); - exp.addChild(dice); - - const mockList = new MockListRandomProvider(); - mockList.numbers.push( - 2, 5, 6, 1, // This 1 should get re-rolled into a 2, and then stop. - 2 - ); - - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - expect(interpreter.evaluate(exp, errors)).toBe(15); - expect(dice.getChildCount()).toBe(4); - }); - it('errors on an invalid condition (4d6ro[dice]).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Reroll) - .setAttribute('once', true); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 6)); - - const d2 = Ast.Factory.create(Ast.NodeType.Dice); - d2.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 1)); - d2.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - - exp.addChild(dice); - exp.addChild(d2); - - const mockList = new MockListRandomProvider(); - mockList.numbers.push(6, 5, 6, 2, 1); - - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(exp, errors); - expect(errors.length).toBeGreaterThanOrEqual(1); - }); + describe('evaluate', () => { + it('evaluates rerolling dice (4d6r<3).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Reroll) + .setAttribute('once', false); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 6)); + + const less = Ast.Factory.create(Ast.NodeType.Less); + less.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 3)); + + exp.addChild(dice); + exp.addChild(less); + + const mockList = new MockListRandomProvider(); + mockList.numbers.push( + 6, 5, 6, 2, // This 2 should get re-rolled into a 1, then re-rolled into a 4. + 1, 4 + ); + + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + expect(interpreter.evaluate(exp, errors)).toBe(21); + expect(dice.getChildCount()).toBe(4); }); + it('evaluates a rerolling dice (4d6ro<3).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Reroll) + .setAttribute('once', true); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 6)); + + const less = Ast.Factory.create(Ast.NodeType.Less); + less.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 3)); + + exp.addChild(dice); + exp.addChild(less); + + const mockList = new MockListRandomProvider(); + mockList.numbers.push( + 6, 5, 6, 2, // This 2 should get re-rolled into a 1, and then stop. + 1 + ); + + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + expect(interpreter.evaluate(exp, errors)).toBe(18); + expect(dice.getChildCount()).toBe(4); + }); + it('evaluates rerolling dice (4d6r).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Reroll) + .setAttribute('once', false); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 6)); + exp.addChild(dice); + + const mockList = new MockListRandomProvider(); + mockList.numbers.push( + 6, 5, 6, 1, // This 1 should get re-rolled into another 1, then re-rolled into a 4. + 1, 4 + ); + + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + expect(interpreter.evaluate(exp, errors)).toBe(21); + expect(dice.getChildCount()).toBe(4); + }); + it('evaluates a rerolling dice no condition (4d6ro).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Reroll) + .setAttribute('once', true); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 6)); + exp.addChild(dice); + + const mockList = new MockListRandomProvider(); + mockList.numbers.push( + 2, 5, 6, 1, // This 1 should get re-rolled into a 2, and then stop. + 2 + ); + + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + expect(interpreter.evaluate(exp, errors)).toBe(15); + expect(dice.getChildCount()).toBe(4); + }); + it('errors on an invalid condition (4d6ro[dice]).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Reroll) + .setAttribute('once', true); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 6)); + + const d2 = Ast.Factory.create(Ast.NodeType.Dice); + d2.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 1)); + d2.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + + exp.addChild(dice); + exp.addChild(d2); + + const mockList = new MockListRandomProvider(); + mockList.numbers.push(6, 5, 6, 2, 1); + + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(exp, errors); + expect(errors.length).toBeGreaterThanOrEqual(1); + }); + }); }); diff --git a/spec/interpreter/dice-interpreter.evaluate.simple.spec.ts b/spec/interpreter/dice-interpreter.evaluate.simple.spec.ts index a073774..25e931f 100644 --- a/spec/interpreter/dice-interpreter.evaluate.simple.spec.ts +++ b/spec/interpreter/dice-interpreter.evaluate.simple.spec.ts @@ -2,60 +2,60 @@ import * as Ast from '../../src/ast'; import * as Interpreter from '../../src/interpreter'; describe('DiceInterpreter', () => { - describe('evaluate', () => { - it('evaluates a simple number.', () => { - const num = Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4); + describe('evaluate', () => { + it('evaluates a simple number.', () => { + const num = Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4); - const interpreter = new Interpreter.DiceInterpreter(); - const errors: Interpreter.ErrorMessage[] = []; - expect(interpreter.evaluate(num, errors)).toBe(4); - }); - it('correctly evaluates a subtraction (10-2).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Subtract); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - const interpreter = new Interpreter.DiceInterpreter(); - const errors: Interpreter.ErrorMessage[] = []; - expect(interpreter.evaluate(exp, errors)).toBe(8); - }); - it('correctly evaluates a multiplication (10*2).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Multiply); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - const interpreter = new Interpreter.DiceInterpreter(); - const errors: Interpreter.ErrorMessage[] = []; - expect(interpreter.evaluate(exp, errors)).toBe(20); - }); - it('correctly evaluates a division (10/2).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Divide); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - const interpreter = new Interpreter.DiceInterpreter(); - const errors: Interpreter.ErrorMessage[] = []; - expect(interpreter.evaluate(exp, errors)).toBe(5); - }); - it('correctly evaluates a exponentiation (10^2).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Exponent); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - const interpreter = new Interpreter.DiceInterpreter(); - const errors: Interpreter.ErrorMessage[] = []; - expect(interpreter.evaluate(exp, errors)).toBe(100); - }); - it('correctly evaluates a modulo (10%2).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Modulo); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - const interpreter = new Interpreter.DiceInterpreter(); - const errors: Interpreter.ErrorMessage[] = []; - expect(interpreter.evaluate(exp, errors)).toBe(0); - }); - it('correctly evaluates a negation (-10).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Negate); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); - const interpreter = new Interpreter.DiceInterpreter(); - const errors: Interpreter.ErrorMessage[] = []; - expect(interpreter.evaluate(exp, errors)).toBe(-10); - }); + const interpreter = new Interpreter.DiceInterpreter(); + const errors: Interpreter.InterpreterError[] = []; + expect(interpreter.evaluate(num, errors)).toBe(4); }); + it('correctly evaluates a subtraction (10-2).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Subtract); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + const interpreter = new Interpreter.DiceInterpreter(); + const errors: Interpreter.InterpreterError[] = []; + expect(interpreter.evaluate(exp, errors)).toBe(8); + }); + it('correctly evaluates a multiplication (10*2).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Multiply); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + const interpreter = new Interpreter.DiceInterpreter(); + const errors: Interpreter.InterpreterError[] = []; + expect(interpreter.evaluate(exp, errors)).toBe(20); + }); + it('correctly evaluates a division (10/2).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Divide); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + const interpreter = new Interpreter.DiceInterpreter(); + const errors: Interpreter.InterpreterError[] = []; + expect(interpreter.evaluate(exp, errors)).toBe(5); + }); + it('correctly evaluates a exponentiation (10^2).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Exponent); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + const interpreter = new Interpreter.DiceInterpreter(); + const errors: Interpreter.InterpreterError[] = []; + expect(interpreter.evaluate(exp, errors)).toBe(100); + }); + it('correctly evaluates a modulo (10%2).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Modulo); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + const interpreter = new Interpreter.DiceInterpreter(); + const errors: Interpreter.InterpreterError[] = []; + expect(interpreter.evaluate(exp, errors)).toBe(0); + }); + it('correctly evaluates a negation (-10).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Negate); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); + const interpreter = new Interpreter.DiceInterpreter(); + const errors: Interpreter.InterpreterError[] = []; + expect(interpreter.evaluate(exp, errors)).toBe(-10); + }); + }); }); diff --git a/spec/interpreter/dice-interpreter.evaluate.sort.spec.ts b/spec/interpreter/dice-interpreter.evaluate.sort.spec.ts index 6bef7c3..d72a0e2 100644 --- a/spec/interpreter/dice-interpreter.evaluate.sort.spec.ts +++ b/spec/interpreter/dice-interpreter.evaluate.sort.spec.ts @@ -3,81 +3,81 @@ import * as Interpreter from '../../src/interpreter'; import { MockListRandomProvider } from '../helpers'; describe('DiceInterpreter', () => { - describe('evaluate', () => { - it('evaluates ascending sort dice (4d6sa).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Sort) - .setAttribute('direction', 'ascending'); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 6)); - - exp.addChild(dice); - - const mockList = new MockListRandomProvider(); - mockList.numbers.push(6, 5, 4, 3); - - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(exp, errors); - expect(dice.getChildCount()).toBe(4); - - expect(dice.getChild(0).getAttribute('value')).toBe(3); - expect(dice.getChild(1).getAttribute('value')).toBe(4); - expect(dice.getChild(2).getAttribute('value')).toBe(5); - expect(dice.getChild(3).getAttribute('value')).toBe(6); - }); - it('evaluates descending sort dice (4d6sd).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Sort) - .setAttribute('direction', 'descending'); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 6)); - - exp.addChild(dice); - - const mockList = new MockListRandomProvider(); - mockList.numbers.push(3, 5, 4, 6); - - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(exp, errors); - expect(dice.getChildCount()).toBe(4); - - expect(dice.getChild(0).getAttribute('value')).toBe(6); - expect(dice.getChild(1).getAttribute('value')).toBe(5); - expect(dice.getChild(2).getAttribute('value')).toBe(4); - expect(dice.getChild(3).getAttribute('value')).toBe(3); - }); - it('errors on unknown sort direction (4d6s-).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Sort) - .setAttribute('direction', 'face'); - - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 6)); - - exp.addChild(dice); - - const mockList = new MockListRandomProvider(); - mockList.numbers.push(6, 5, 4, 3); - - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(exp, errors); - expect(errors.length).toBeGreaterThanOrEqual(1); - }); - it('errors on missing dice node (-sa).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Sort) - .setAttribute('direction', 'ascending'); - - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 6)); - - const interpreter = new Interpreter.DiceInterpreter(null); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(exp, errors); - expect(errors.length).toBeGreaterThanOrEqual(1); - }); + describe('evaluate', () => { + it('evaluates ascending sort dice (4d6sa).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Sort) + .setAttribute('direction', 'ascending'); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 6)); + + exp.addChild(dice); + + const mockList = new MockListRandomProvider(); + mockList.numbers.push(6, 5, 4, 3); + + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(exp, errors); + expect(dice.getChildCount()).toBe(4); + + expect(dice.getChild(0).getAttribute('value')).toBe(3); + expect(dice.getChild(1).getAttribute('value')).toBe(4); + expect(dice.getChild(2).getAttribute('value')).toBe(5); + expect(dice.getChild(3).getAttribute('value')).toBe(6); + }); + it('evaluates descending sort dice (4d6sd).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Sort) + .setAttribute('direction', 'descending'); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 6)); + + exp.addChild(dice); + + const mockList = new MockListRandomProvider(); + mockList.numbers.push(3, 5, 4, 6); + + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(exp, errors); + expect(dice.getChildCount()).toBe(4); + + expect(dice.getChild(0).getAttribute('value')).toBe(6); + expect(dice.getChild(1).getAttribute('value')).toBe(5); + expect(dice.getChild(2).getAttribute('value')).toBe(4); + expect(dice.getChild(3).getAttribute('value')).toBe(3); + }); + it('errors on unknown sort direction (4d6s-).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Sort) + .setAttribute('direction', 'face'); + + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 6)); + + exp.addChild(dice); + + const mockList = new MockListRandomProvider(); + mockList.numbers.push(6, 5, 4, 3); + + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(exp, errors); + expect(errors.length).toBeGreaterThanOrEqual(1); + }); + it('errors on missing dice node (-sa).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Sort) + .setAttribute('direction', 'ascending'); + + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 6)); + + const interpreter = new Interpreter.DiceInterpreter(null); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(exp, errors); + expect(errors.length).toBeGreaterThanOrEqual(1); }); + }); }); diff --git a/spec/interpreter/dice-interpreter.evaluate.success.spec.ts b/spec/interpreter/dice-interpreter.evaluate.success.spec.ts index c2c2d4f..b186db8 100644 --- a/spec/interpreter/dice-interpreter.evaluate.success.spec.ts +++ b/spec/interpreter/dice-interpreter.evaluate.success.spec.ts @@ -3,30 +3,30 @@ import * as Interpreter from '../../src/interpreter'; import { MockListRandomProvider } from '../helpers'; describe('DiceInterpreter', () => { - describe('evaluate', () => { - it('evaluates successes (5d20>10).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Greater); + describe('evaluate', () => { + it('evaluates successes (5d20>10).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Greater); - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); - exp.addChild(dice); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); + exp.addChild(dice); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); - const mockList = new MockListRandomProvider(); - mockList.numbers.push(8, 12, 6, 20, 14); + const mockList = new MockListRandomProvider(); + mockList.numbers.push(8, 12, 6, 20, 14); - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const errors: Interpreter.ErrorMessage[] = []; - const res = interpreter.evaluate(exp, errors); + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const errors: Interpreter.InterpreterError[] = []; + const res = interpreter.evaluate(exp, errors); - expect(dice.getChildCount()).toBe(5); - expect(dice.getChild(0).getAttribute('success')).toBe(false); - expect(dice.getChild(1).getAttribute('success')).toBe(true); - expect(dice.getChild(2).getAttribute('success')).toBe(false); - expect(dice.getChild(3).getAttribute('success')).toBe(true); - expect(dice.getChild(4).getAttribute('success')).toBe(true); - }); + expect(dice.getChildCount()).toBe(5); + expect(dice.getChild(0).getAttribute('success')).toBe(false); + expect(dice.getChild(1).getAttribute('success')).toBe(true); + expect(dice.getChild(2).getAttribute('success')).toBe(false); + expect(dice.getChild(3).getAttribute('success')).toBe(true); + expect(dice.getChild(4).getAttribute('success')).toBe(true); }); + }); }); diff --git a/spec/interpreter/dice-interpreter.evaluate.throws.spec.ts b/spec/interpreter/dice-interpreter.evaluate.throws.spec.ts index 14fbd24..a420369 100644 --- a/spec/interpreter/dice-interpreter.evaluate.throws.spec.ts +++ b/spec/interpreter/dice-interpreter.evaluate.throws.spec.ts @@ -3,20 +3,20 @@ import * as Interpreter from '../../src/interpreter'; import { MockRandomProvider } from '../helpers'; describe('DiceInterpreter', () => { - describe('evaluate', () => { - it('throws on unrecognized node type (face).', () => { - const face = Ast.Factory.create('Face'); + describe('evaluate', () => { + it('throws on unrecognized node type (face).', () => { + const face = Ast.Factory.create('Face'); - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6)); - face.addChild(dice); + face.addChild(dice); - const interpreter = new Interpreter.DiceInterpreter(null, new MockRandomProvider(4)); - const errors: Interpreter.ErrorMessage[] = []; - interpreter.evaluate(face, errors); - expect(errors.length).toBeGreaterThanOrEqual(1); - }); + const interpreter = new Interpreter.DiceInterpreter(null, new MockRandomProvider(4)); + const errors: Interpreter.InterpreterError[] = []; + interpreter.evaluate(face, errors); + expect(errors.length).toBeGreaterThanOrEqual(1); }); + }); }); diff --git a/spec/interpreter/dice-interpreter.hasEvaluateMethods.spec.ts b/spec/interpreter/dice-interpreter.hasEvaluateMethods.spec.ts index 9c870b0..119a1a8 100644 --- a/spec/interpreter/dice-interpreter.hasEvaluateMethods.spec.ts +++ b/spec/interpreter/dice-interpreter.hasEvaluateMethods.spec.ts @@ -2,13 +2,13 @@ import * as Ast from '../../src/ast'; import * as Interpreter from '../../src/interpreter'; describe('DiceInterpreter', () => { - describe('evaluate method', () => { - it('exists for each node type.', () => { - const interpreter = new Interpreter.DiceInterpreter(); - Object.keys(Ast.NodeType).forEach(nodeType => { - const methodName = 'evaluate' + nodeType; - expect(interpreter[methodName]).toBeDefined(`Evaluate method ${methodName} does not exist.`); - }); - }); + describe('evaluate method', () => { + it('exists for each node type.', () => { + const interpreter = new Interpreter.DiceInterpreter(); + Object.keys(Ast.NodeType).forEach(nodeType => { + const methodName = 'evaluate' + nodeType; + expect(interpreter[methodName]).toBeDefined(`Evaluate method ${methodName} does not exist.`); + }); }); + }); }); diff --git a/spec/interpreter/dice-interpreter.interpret.complex.spec.ts b/spec/interpreter/dice-interpreter.interpret.complex.spec.ts index df2a4a9..9ffa8fe 100644 --- a/spec/interpreter/dice-interpreter.interpret.complex.spec.ts +++ b/spec/interpreter/dice-interpreter.interpret.complex.spec.ts @@ -3,70 +3,70 @@ import * as Interpreter from '../../src/interpreter'; import { MockListRandomProvider } from '../helpers'; describe('DiceInterpreter', () => { - describe('interpret', () => { - it('interprets a complex dice expression (2d20kl>14).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Greater); + describe('interpret', () => { + it('interprets a complex dice expression (2d20kl>14).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Greater); - const keep = Ast.Factory.create(Ast.NodeType.Keep); - keep.setAttribute('type', 'lowest'); + const keep = Ast.Factory.create(Ast.NodeType.Keep); + keep.setAttribute('type', 'lowest'); - exp.addChild(keep); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 14)); + exp.addChild(keep); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 14)); - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); - keep.addChild(dice); + keep.addChild(dice); - const mockList = new MockListRandomProvider(); - mockList.numbers.push(20, 15); + const mockList = new MockListRandomProvider(); + mockList.numbers.push(20, 15); - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const res = interpreter.interpret(exp); + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const res = interpreter.interpret(exp); - expect(res.errors.length).toBe(0, 'Unexpected errors found.'); - expect(res.successes).toBe(1, 'Successes counted incorrectly'); - expect(res.failures).toBe(0, 'Failures counted incorrectly'); - expect(res.total).toBe(15, 'Total counted incorrectly'); - expect(res.renderedExpression).toBe('[20, 15]kl > 14', 'Expression rendered incorrectly.'); - }); - it('interprets a complex dice expression {2d20kl..5}>=14).', () => { - const exp = Ast.Factory.create(Ast.NodeType.GreaterOrEqual); + expect(res.errors.length).toBe(0, 'Unexpected errors found.'); + expect(res.successes).toBe(1, 'Successes counted incorrectly'); + expect(res.failures).toBe(0, 'Failures counted incorrectly'); + expect(res.total).toBe(15, 'Total counted incorrectly'); + expect(res.renderedExpression).toBe('[20, 15]kl > 14', 'Expression rendered incorrectly.'); + }); + it('interprets a complex dice expression {2d20kl..5}>=14).', () => { + const exp = Ast.Factory.create(Ast.NodeType.GreaterOrEqual); - const group = Ast.Factory.create(Ast.NodeType.Group); - exp.addChild(group); + const group = Ast.Factory.create(Ast.NodeType.Group); + exp.addChild(group); - const repeat = Ast.Factory.create(Ast.NodeType.Repeat); - group.addChild(repeat); + const repeat = Ast.Factory.create(Ast.NodeType.Repeat); + group.addChild(repeat); - const keep = Ast.Factory.create(Ast.NodeType.Keep); - keep.setAttribute('type', 'lowest'); - repeat.addChild(keep); + const keep = Ast.Factory.create(Ast.NodeType.Keep); + keep.setAttribute('type', 'lowest'); + repeat.addChild(keep); - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); - keep.addChild(dice); + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 20)); + keep.addChild(dice); - repeat.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + repeat.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 14)); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 14)); - const mockList = new MockListRandomProvider(); - mockList.numbers.push(20, 15, 14, 10, 18, 14, 2, 13, 18, 10); + const mockList = new MockListRandomProvider(); + mockList.numbers.push(20, 15, 14, 10, 18, 14, 2, 13, 18, 10); - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const res = interpreter.interpret(exp); + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const res = interpreter.interpret(exp); - expect(res.errors.length).toBe(0, 'Unexpected errors found.'); - expect(res.successes).toBe(2, 'Successes counted incorrectly'); - expect(res.failures).toBe(3, 'Failures counted incorrectly'); - expect(res.total).toBe(29, 'Total counted incorrectly'); - expect(res.renderedExpression).toBe( - '{[20, 15]kl, [14, 10]kl, [18, 14]kl, [2, 13]kl, [18, 10]kl} >= 14', - 'Expression rendered incorrectly.' - ); - }); + expect(res.errors.length).toBe(0, 'Unexpected errors found.'); + expect(res.successes).toBe(2, 'Successes counted incorrectly'); + expect(res.failures).toBe(3, 'Failures counted incorrectly'); + expect(res.total).toBe(29, 'Total counted incorrectly'); + expect(res.renderedExpression).toBe( + '{[20, 15]kl, [14, 10]kl, [18, 14]kl, [2, 13]kl, [18, 10]kl} >= 14', + 'Expression rendered incorrectly.' + ); }); + }); }); diff --git a/spec/interpreter/dice-interpreter.interpret.simple.ts b/spec/interpreter/dice-interpreter.interpret.simple.ts index 7639715..5513d05 100644 --- a/spec/interpreter/dice-interpreter.interpret.simple.ts +++ b/spec/interpreter/dice-interpreter.interpret.simple.ts @@ -3,26 +3,26 @@ import * as Interpreter from '../../src/interpreter'; import { MockListRandomProvider } from '../helpers'; describe('DiceInterpreter', () => { - describe('interpret', () => { - it('interprets a simple dice expression (4d10 + 5).', () => { - const exp = Ast.Factory.create(Ast.NodeType.Add); + describe('interpret', () => { + it('interprets a simple dice expression (4d10 + 5).', () => { + const exp = Ast.Factory.create(Ast.NodeType.Add); - const dice = Ast.Factory.create(Ast.NodeType.Dice); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); - dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); + const dice = Ast.Factory.create(Ast.NodeType.Dice); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 4)); + dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 10)); - exp.addChild(dice); - exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); + exp.addChild(dice); + exp.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5)); - const mockList = new MockListRandomProvider(); - mockList.numbers.push(1, 7, 4, 5); + const mockList = new MockListRandomProvider(); + mockList.numbers.push(1, 7, 4, 5); - const interpreter = new Interpreter.DiceInterpreter(null, mockList); - const res = interpreter.interpret(exp); + const interpreter = new Interpreter.DiceInterpreter(null, mockList); + const res = interpreter.interpret(exp); - expect(res.errors.length).toBe(0, 'Unexpected errors found.'); - expect(res.renderedExpression).toBe('[1, 7, 4, 5] + 5', 'Expression rendered incorrectly'); - expect(res.total).toBe(22, 'Total counted incorrectly'); - }); + expect(res.errors.length).toBe(0, 'Unexpected errors found.'); + expect(res.renderedExpression).toBe('[1, 7, 4, 5] + 5', 'Expression rendered incorrectly'); + expect(res.total).toBe(22, 'Total counted incorrectly'); }); + }); }); diff --git a/spec/lexer/dice-lexer.spec.ts b/spec/lexer/dice-lexer.spec.ts index 3fba4c4..f1aaae2 100644 --- a/spec/lexer/dice-lexer.spec.ts +++ b/spec/lexer/dice-lexer.spec.ts @@ -1,148 +1,153 @@ import * as Lexer from '../../src/lexer'; describe('DiceLexer', () => { - const input = 'floor(4d6!!+5d10kl2/2+4)'; - describe('constructor', () => { - it('does not throw for string input.', function () { - expect(() => { - const lexer = new Lexer.DiceLexer(input); - }).not.toThrow(); - }); - it('does not throw for stream input.', function () { - expect(() => { - const lexer = new Lexer.DiceLexer(new Lexer.StringCharacterStream(input)); - }).not.toThrow(); - }); - it('throws for invalid input.', function () { - expect(() => { - const lexer = new Lexer.DiceLexer(6 as any); - }).toThrow(); - }); + const input = 'floor(4d6!!+5d10kl2/2+4)'; + describe('constructor', () => { + it('does not throw for string input.', function () { + expect(() => { + const lexer = new Lexer.DiceLexer(input); + }).not.toThrow(); }); - describe('getNextToken', () => { - it('last token is a terminator', () => { - const lexer = new Lexer.DiceLexer(''); - const token = lexer.getNextToken(); - expect(token).toEqual(new Lexer.Token(Lexer.TokenType.Terminator, 0)); - }); - it('returns correct tokens (simple)', () => { - const inputSimple = 'floor(4d6!!)'; - const lexer = new Lexer.DiceLexer(inputSimple); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Identifier, 0, 'floor')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.ParenthesisOpen, 5, '(')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 6, '4')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Identifier, 7, 'd')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 8, '6')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Exclamation, 9, '!')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Exclamation, 10, '!')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.ParenthesisClose, 11, ')')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Terminator, 12)); - }); - it('returns correct tokens (complex)', () => { - const lexer = new Lexer.DiceLexer(input); // floor(4d6!!+5d10kl2/2+4) - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Identifier, 0, 'floor')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.ParenthesisOpen, 5, '(')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 6, '4')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Identifier, 7, 'd')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 8, '6')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Exclamation, 9, '!')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Exclamation, 10, '!')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Plus, 11, '+')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 12, '5')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Identifier, 13, 'd')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 14, '10')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Identifier, 16, 'kl')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 18, '2')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Slash, 19, '/')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 20, '2')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Plus, 21, '+')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 22, '4')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.ParenthesisClose, 23, ')')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Terminator, 24)); - }); - it('interprets remaining operators correctly', () => { - const lexer = new Lexer.DiceLexer('2d10%8-2*3**1d4!>1<2<=2>=2d3!!=3+{4,5}'); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 0, '2')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Identifier, 1, 'd')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 2, '10')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Percent, 4, '%')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 5, '8')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Minus, 6, '-')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 7, '2')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Asterisk, 8, '*')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 9, '3')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.DoubleAsterisk, 10, '**')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 12, '1')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Identifier, 13, 'd')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 14, '4')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Exclamation, 15, '!')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Greater, 16, '>')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 17, '1')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Less, 18, '<')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 19, '2')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.LessOrEqual, 20, '<=')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 22, '2')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.GreaterOrEqual, 23, '>=')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 25, '2')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Identifier, 26, 'd')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 27, '3')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Exclamation, 28, '!')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Exclamation, 29, '!')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Equals, 30, '=')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 31, '3')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Plus, 32, '+')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.BraceOpen, 33, '{')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 34, '4')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Comma, 35, ',')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 36, '5')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.BraceClose, 37, '}')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Terminator, 38)); - }); - it('interprets group repeater correctly', () => { - const lexer = new Lexer.DiceLexer('{2d10,...3}'); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.BraceOpen, 0, '{')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 1, '2')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Identifier, 2, 'd')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 3, '10')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Comma, 5, ',')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Ellipsis, 6, '...')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 9, '3')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.BraceClose, 10, '}')); - }); - it('interprets a floating point number correctly', () => { - const lexer = new Lexer.DiceLexer('2.23'); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 0, '2.23')); - }); - it('throws on unrecognized tokens', () => { - const lexer = new Lexer.DiceLexer('test_face'); - lexer.getNextToken(); - expect(() => { lexer.getNextToken(); }).toThrow(); - }); - it('throws on too short ellipsis', () => { - const lexer = new Lexer.DiceLexer('..'); - expect(() => { lexer.getNextToken(); }).toThrow(); - }); - it('skips over whitespace.', () => { - const lexer = new Lexer.DiceLexer('2 d\t10 \t + \t\t 3'); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 0, '2')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Identifier, 3, 'd')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 5, '10')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Plus, 10, '+')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 15, '3')); - expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Terminator, 16)); - }); + it('does not throw for stream input.', function () { + expect(() => { + const lexer = new Lexer.DiceLexer(new Lexer.StringCharacterStream(input)); + }).not.toThrow(); }); - describe('peekNextToken', () => { - it('gives next token without cycling through.', () => { - const lexer = new Lexer.DiceLexer(input); - lexer.getNextToken(); - expect(lexer.peekNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.ParenthesisOpen, 5, '(')); - expect(lexer.peekNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.ParenthesisOpen, 5, '(')); - lexer.getNextToken(); - expect(lexer.peekNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 6, '4')); - lexer.getNextToken(); - lexer.getNextToken(); - expect(lexer.peekNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 8, '6')); - }); + it('throws for invalid input.', function () { + expect(() => { + const lexer = new Lexer.DiceLexer(6 as any); + }).toThrow(); }); + }); + describe('getNextToken', () => { + it('last token is a terminator', () => { + const lexer = new Lexer.DiceLexer(''); + const token = lexer.getNextToken(); + expect(token).toEqual(new Lexer.Token(Lexer.TokenType.Terminator, 0)); + }); + it('returns correct tokens (simple)', () => { + const inputSimple = 'floor(4d6!!)'; + const lexer = new Lexer.DiceLexer(inputSimple); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Identifier, 0, 'floor')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.ParenthesisOpen, 5, '(')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 6, '4')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Identifier, 7, 'd')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 8, '6')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Exclamation, 9, '!')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Exclamation, 10, '!')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.ParenthesisClose, 11, ')')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Terminator, 12)); + }); + it('returns correct tokens (complex)', () => { + const lexer = new Lexer.DiceLexer(input); // floor(4d6!!+5d10kl2/2+4) + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Identifier, 0, 'floor')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.ParenthesisOpen, 5, '(')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 6, '4')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Identifier, 7, 'd')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 8, '6')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Exclamation, 9, '!')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Exclamation, 10, '!')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Plus, 11, '+')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 12, '5')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Identifier, 13, 'd')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 14, '10')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Identifier, 16, 'kl')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 18, '2')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Slash, 19, '/')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 20, '2')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Plus, 21, '+')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 22, '4')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.ParenthesisClose, 23, ')')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Terminator, 24)); + }); + it('interprets remaining operators correctly', () => { + const lexer = new Lexer.DiceLexer('2d10%8-2*3**1d4!>1<2<=2>=2d3!!=3+{4,5}'); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 0, '2')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Identifier, 1, 'd')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 2, '10')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Percent, 4, '%')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 5, '8')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Minus, 6, '-')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 7, '2')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Asterisk, 8, '*')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 9, '3')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.DoubleAsterisk, 10, '**')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 12, '1')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Identifier, 13, 'd')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 14, '4')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Exclamation, 15, '!')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Greater, 16, '>')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 17, '1')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Less, 18, '<')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 19, '2')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.LessOrEqual, 20, '<=')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 22, '2')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.GreaterOrEqual, 23, '>=')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 25, '2')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Identifier, 26, 'd')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 27, '3')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Exclamation, 28, '!')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Exclamation, 29, '!')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Equals, 30, '=')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 31, '3')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Plus, 32, '+')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.BraceOpen, 33, '{')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 34, '4')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Comma, 35, ',')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 36, '5')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.BraceClose, 37, '}')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Terminator, 38)); + }); + it('interprets group repeater correctly', () => { + const lexer = new Lexer.DiceLexer('{2d10,...3}'); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.BraceOpen, 0, '{')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 1, '2')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Identifier, 2, 'd')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 3, '10')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Comma, 5, ',')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Ellipsis, 6, '...')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 9, '3')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.BraceClose, 10, '}')); + }); + it('interprets a floating point number correctly', () => { + const lexer = new Lexer.DiceLexer('2.23'); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 0, '2.23')); + }); + it('throws on unrecognized tokens', () => { + const lexer = new Lexer.DiceLexer('test_face'); + lexer.getNextToken(); + expect(() => { lexer.getNextToken(); }).toThrow(); + }); + it('throws on too short ellipsis', () => { + const lexer = new Lexer.DiceLexer('..'); + expect(() => { lexer.getNextToken(); }).toThrow(); + }); + it('skips over whitespace.', () => { + const lexer = new Lexer.DiceLexer('2 d\t10 \t + \t\t 3'); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 0, '2')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Identifier, 3, 'd')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 5, '10')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Plus, 10, '+')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 15, '3')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Terminator, 16)); + }); + it('successfully parses an identifier at the end of a string.', () => { + const lexer = new Lexer.DiceLexer('2kl'); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 0, '2')); + expect(lexer.getNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Identifier, 1, 'kl')); + }); + }); + describe('peekNextToken', () => { + it('gives next token without cycling through.', () => { + const lexer = new Lexer.DiceLexer(input); + lexer.getNextToken(); + expect(lexer.peekNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.ParenthesisOpen, 5, '(')); + expect(lexer.peekNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.ParenthesisOpen, 5, '(')); + lexer.getNextToken(); + expect(lexer.peekNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 6, '4')); + lexer.getNextToken(); + lexer.getNextToken(); + expect(lexer.peekNextToken()).toEqual(new Lexer.Token(Lexer.TokenType.Number, 8, '6')); + }); + }); }); diff --git a/spec/lexer/string-character-stream.spec.ts b/spec/lexer/string-character-stream.spec.ts index 961f8b6..a739371 100644 --- a/spec/lexer/string-character-stream.spec.ts +++ b/spec/lexer/string-character-stream.spec.ts @@ -1,88 +1,88 @@ import * as Lexer from '../../src/lexer'; describe('StringCharacterStream', () => { - const input = 'floor(4d6!!+5d10kl2/2+4)'; - describe('constructor', () => { - it('does not throw.', function () { - expect(() => { - const stream = new Lexer.StringCharacterStream(input); - }).not.toThrow(); - }); + const input = 'floor(4d6!!+5d10kl2/2+4)'; + describe('constructor', () => { + it('does not throw.', function () { + expect(() => { + const stream = new Lexer.StringCharacterStream(input); + }).not.toThrow(); }); - describe('currentCharacter', () => { - it('starts as null', () => { - const stream = new Lexer.StringCharacterStream('face'); - expect(stream.getCurrentCharacter()).toEqual(null); - }); - it('returns the current character', () => { - const stream = new Lexer.StringCharacterStream('face'); - stream.getNextCharacter(); - expect(stream.getCurrentCharacter()).toEqual('f'); - expect(stream.getCurrentCharacter()).toEqual('f'); - stream.getNextCharacter(); - stream.getNextCharacter(); - expect(stream.getCurrentCharacter()).toEqual('c'); - }); - it('returns null at the end.', () => { - const stream = new Lexer.StringCharacterStream('face'); - stream.getNextCharacter(); - stream.getNextCharacter(); - stream.getNextCharacter(); - stream.getNextCharacter(); - stream.getNextCharacter(); - expect(stream.getCurrentCharacter()).toBeNull(); - }); + }); + describe('currentCharacter', () => { + it('starts as null', () => { + const stream = new Lexer.StringCharacterStream('face'); + expect(stream.getCurrentCharacter()).toEqual(null); }); - describe('getNextCharacter', () => { - it('progresses through the characters, finishing with null.', () => { - const stream = new Lexer.StringCharacterStream(input); - expect(stream.getNextCharacter()).toEqual('f'); - expect(stream.getNextCharacter()).toEqual('l'); - expect(stream.getNextCharacter()).toEqual('o'); - expect(stream.getNextCharacter()).toEqual('o'); - expect(stream.getNextCharacter()).toEqual('r'); - expect(stream.getNextCharacter()).toEqual('('); - expect(stream.getNextCharacter()).toEqual('4'); - expect(stream.getNextCharacter()).toEqual('d'); - expect(stream.getNextCharacter()).toEqual('6'); - expect(stream.getNextCharacter()).toEqual('!'); - expect(stream.getNextCharacter()).toEqual('!'); - expect(stream.getNextCharacter()).toEqual('+'); - expect(stream.getNextCharacter()).toEqual('5'); - expect(stream.getNextCharacter()).toEqual('d'); - expect(stream.getNextCharacter()).toEqual('1'); - expect(stream.getNextCharacter()).toEqual('0'); - expect(stream.getNextCharacter()).toEqual('k'); - expect(stream.getNextCharacter()).toEqual('l'); - expect(stream.getNextCharacter()).toEqual('2'); - expect(stream.getNextCharacter()).toEqual('/'); - expect(stream.getNextCharacter()).toEqual('2'); - expect(stream.getNextCharacter()).toEqual('+'); - expect(stream.getNextCharacter()).toEqual('4'); - expect(stream.getNextCharacter()).toEqual(')'); - expect(stream.getNextCharacter()).toBeNull(); - }); - it('returns null when stream ends.', () => { - const stream = new Lexer.StringCharacterStream('abc'); - stream.getNextCharacter(); - stream.getNextCharacter(); - stream.getNextCharacter(); - for (let x = 0; x < 10; x++) { - expect(stream.getNextCharacter()).toBeNull(); - } - }); + it('returns the current character', () => { + const stream = new Lexer.StringCharacterStream('face'); + stream.getNextCharacter(); + expect(stream.getCurrentCharacter()).toEqual('f'); + expect(stream.getCurrentCharacter()).toEqual('f'); + stream.getNextCharacter(); + stream.getNextCharacter(); + expect(stream.getCurrentCharacter()).toEqual('c'); }); - describe('peekNextCharacter', () => { - it('gives next character without cycling through.', () => { - const stream = new Lexer.StringCharacterStream(input); - stream.getNextCharacter(); - expect(stream.peekNextCharacter()).toEqual('l'); - expect(stream.peekNextCharacter()).toEqual('l'); - stream.getNextCharacter(); - expect(stream.peekNextCharacter()).toEqual('o'); - stream.getNextCharacter(); - stream.getNextCharacter(); - expect(stream.peekNextCharacter()).toEqual('r'); - }); + it('returns null at the end.', () => { + const stream = new Lexer.StringCharacterStream('face'); + stream.getNextCharacter(); + stream.getNextCharacter(); + stream.getNextCharacter(); + stream.getNextCharacter(); + stream.getNextCharacter(); + expect(stream.getCurrentCharacter()).toBeNull(); }); + }); + describe('getNextCharacter', () => { + it('progresses through the characters, finishing with null.', () => { + const stream = new Lexer.StringCharacterStream(input); + expect(stream.getNextCharacter()).toEqual('f'); + expect(stream.getNextCharacter()).toEqual('l'); + expect(stream.getNextCharacter()).toEqual('o'); + expect(stream.getNextCharacter()).toEqual('o'); + expect(stream.getNextCharacter()).toEqual('r'); + expect(stream.getNextCharacter()).toEqual('('); + expect(stream.getNextCharacter()).toEqual('4'); + expect(stream.getNextCharacter()).toEqual('d'); + expect(stream.getNextCharacter()).toEqual('6'); + expect(stream.getNextCharacter()).toEqual('!'); + expect(stream.getNextCharacter()).toEqual('!'); + expect(stream.getNextCharacter()).toEqual('+'); + expect(stream.getNextCharacter()).toEqual('5'); + expect(stream.getNextCharacter()).toEqual('d'); + expect(stream.getNextCharacter()).toEqual('1'); + expect(stream.getNextCharacter()).toEqual('0'); + expect(stream.getNextCharacter()).toEqual('k'); + expect(stream.getNextCharacter()).toEqual('l'); + expect(stream.getNextCharacter()).toEqual('2'); + expect(stream.getNextCharacter()).toEqual('/'); + expect(stream.getNextCharacter()).toEqual('2'); + expect(stream.getNextCharacter()).toEqual('+'); + expect(stream.getNextCharacter()).toEqual('4'); + expect(stream.getNextCharacter()).toEqual(')'); + expect(stream.getNextCharacter()).toBeNull(); + }); + it('returns null when stream ends.', () => { + const stream = new Lexer.StringCharacterStream('abc'); + stream.getNextCharacter(); + stream.getNextCharacter(); + stream.getNextCharacter(); + for (let x = 0; x < 10; x++) { + expect(stream.getNextCharacter()).toBeNull(); + } + }); + }); + describe('peekNextCharacter', () => { + it('gives next character without cycling through.', () => { + const stream = new Lexer.StringCharacterStream(input); + stream.getNextCharacter(); + expect(stream.peekNextCharacter()).toEqual('l'); + expect(stream.peekNextCharacter()).toEqual('l'); + stream.getNextCharacter(); + expect(stream.peekNextCharacter()).toEqual('o'); + stream.getNextCharacter(); + stream.getNextCharacter(); + expect(stream.peekNextCharacter()).toEqual('r'); + }); + }); }); diff --git a/spec/parser/dice-parser.constructor.spec.ts b/spec/parser/dice-parser.constructor.spec.ts index 6090dbf..e2af964 100644 --- a/spec/parser/dice-parser.constructor.spec.ts +++ b/spec/parser/dice-parser.constructor.spec.ts @@ -1,16 +1,16 @@ import * as Parser from '../../src/parser'; describe('DiceParser', () => { - describe('constructor', () => { - it('does not throw.', () => { - expect(() => { - const parser = new Parser.DiceParser(''); - }).not.toThrow(); - }); - it('throws for invalid input.', () => { - expect(() => { - const parser = new Parser.DiceParser(6 as any); - }).toThrow(); - }); + describe('constructor', () => { + it('does not throw.', () => { + expect(() => { + const parser = new Parser.DiceParser(''); + }).not.toThrow(); }); + it('throws for invalid input.', () => { + expect(() => { + const parser = new Parser.DiceParser(6 as any); + }).toThrow(); + }); + }); }); diff --git a/spec/parser/dice-parser.parseBracketedExpression.spec.ts b/spec/parser/dice-parser.parseBracketedExpression.spec.ts index 0e7ea7b..576a598 100644 --- a/spec/parser/dice-parser.parseBracketedExpression.spec.ts +++ b/spec/parser/dice-parser.parseBracketedExpression.spec.ts @@ -4,52 +4,52 @@ import * as Parser from '../../src/parser'; import { MockLexer } from '../helpers'; describe('DiceParser', () => { - describe('parseBracketedExpression', () => { - it('can correctly parse a simple expression', () => { - const lexer = new MockLexer([ - new Token(TokenType.ParenthesisOpen, 0, '('), - new Token(TokenType.Number, 1, '10'), - new Token(TokenType.ParenthesisClose, 3, ')') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new Parser.ParseResult(); - const exp = parser.parseBracketedExpression(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Number); - expect(exp.getChildCount()).toBe(0); - expect(exp.getAttribute('value')).toBe(10); - }); - it('can correctly parse an addition', () => { - const lexer = new MockLexer([ - new Token(TokenType.ParenthesisOpen, 0, '('), - new Token(TokenType.Number, 1, '10'), - new Token(TokenType.Plus, 3, '+'), - new Token(TokenType.Number, 4, '6'), - new Token(TokenType.ParenthesisClose, 5, ')'), - ]); - const parser = new Parser.DiceParser(lexer); - const result = new Parser.ParseResult(); - const exp = parser.parseBracketedExpression(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Add); - expect(exp.getChildCount()).toBe(2); - expect(exp.getChild(0).type).toBe(NodeType.Number); - expect(exp.getChild(0).getAttribute('value')).toBe(10); - expect(exp.getChild(1).type).toBe(NodeType.Number); - expect(exp.getChild(1).getAttribute('value')).toBe(6); - }); - it('throws on missing closing bracket', () => { - const lexer = new MockLexer([ - new Token(TokenType.ParenthesisOpen, 0, '('), - new Token(TokenType.Number, 1, '10'), - new Token(TokenType.Plus, 3, '+'), - new Token(TokenType.Number, 4, '6') - ]); - const parser = new Parser.DiceParser(lexer); + describe('parseBracketedExpression', () => { + it('can correctly parse a simple expression', () => { + const lexer = new MockLexer([ + new Token(TokenType.ParenthesisOpen, 0, '('), + new Token(TokenType.Number, 1, '10'), + new Token(TokenType.ParenthesisClose, 3, ')') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new Parser.ParseResult(); + const exp = parser.parseBracketedExpression(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Number); + expect(exp.getChildCount()).toBe(0); + expect(exp.getAttribute('value')).toBe(10); + }); + it('can correctly parse an addition', () => { + const lexer = new MockLexer([ + new Token(TokenType.ParenthesisOpen, 0, '('), + new Token(TokenType.Number, 1, '10'), + new Token(TokenType.Plus, 3, '+'), + new Token(TokenType.Number, 4, '6'), + new Token(TokenType.ParenthesisClose, 5, ')'), + ]); + const parser = new Parser.DiceParser(lexer); + const result = new Parser.ParseResult(); + const exp = parser.parseBracketedExpression(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Add); + expect(exp.getChildCount()).toBe(2); + expect(exp.getChild(0).type).toBe(NodeType.Number); + expect(exp.getChild(0).getAttribute('value')).toBe(10); + expect(exp.getChild(1).type).toBe(NodeType.Number); + expect(exp.getChild(1).getAttribute('value')).toBe(6); + }); + it('throws on missing closing bracket', () => { + const lexer = new MockLexer([ + new Token(TokenType.ParenthesisOpen, 0, '('), + new Token(TokenType.Number, 1, '10'), + new Token(TokenType.Plus, 3, '+'), + new Token(TokenType.Number, 4, '6') + ]); + const parser = new Parser.DiceParser(lexer); - const result = new Parser.ParseResult(); - const exp = parser.parseBracketedExpression(result); - expect(result.errors.length).toBeGreaterThanOrEqual(1); - }); + const result = new Parser.ParseResult(); + const exp = parser.parseBracketedExpression(result); + expect(result.errors.length).toBeGreaterThanOrEqual(1); }); + }); }); diff --git a/spec/parser/dice-parser.parseCompare.spec.ts b/spec/parser/dice-parser.parseCompare.spec.ts index 77fa889..1ab7aff 100644 --- a/spec/parser/dice-parser.parseCompare.spec.ts +++ b/spec/parser/dice-parser.parseCompare.spec.ts @@ -4,41 +4,41 @@ import * as Parser from '../../src/parser'; import { MockLexer } from '../helpers'; describe('DiceParser', () => { - describe('parseCompareModifier', () => { - it('can correctly parse a compare modifier (>3).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Greater, 0, '>'), - new Token(TokenType.Number, 1, '3'), - ]); - const parser = new Parser.DiceParser(lexer); - const result = new Parser.ParseResult(); - const mod = parser.parseCompareModifier(result); - expect(result.errors.length).toBe(0); - expect(mod.type).toBe(NodeType.Greater); - expect(mod.getChildCount()).toBe(1); - expect(mod.getChild(0).getAttribute('value')).toBe(3); - }); - it('can correctly parse a compare modifier (3).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Number, 0, '3'), - ]); - const parser = new Parser.DiceParser(lexer); - const result = new Parser.ParseResult(); - const mod = parser.parseCompareModifier(result); - expect(result.errors.length).toBe(0); - expect(mod.type).toBe(NodeType.Equal); - expect(mod.getChildCount()).toBe(1); - }); - it('throws an error on unexpected token (sx).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'sx') - ]); - const parser = new Parser.DiceParser(lexer); - expect(() => { - const result = new Parser.ParseResult(); - const mod = parser.parseCompareModifier(result); - expect(result.errors.length).toBeGreaterThanOrEqual(1); - }).toThrow(); - }); + describe('parseCompareModifier', () => { + it('can correctly parse a compare modifier (>3).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Greater, 0, '>'), + new Token(TokenType.Number, 1, '3'), + ]); + const parser = new Parser.DiceParser(lexer); + const result = new Parser.ParseResult(); + const mod = parser.parseCompareModifier(result); + expect(result.errors.length).toBe(0); + expect(mod.type).toBe(NodeType.Greater); + expect(mod.getChildCount()).toBe(1); + expect(mod.getChild(0).getAttribute('value')).toBe(3); }); + it('can correctly parse a compare modifier (3).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '3'), + ]); + const parser = new Parser.DiceParser(lexer); + const result = new Parser.ParseResult(); + const mod = parser.parseCompareModifier(result); + expect(result.errors.length).toBe(0); + expect(mod.type).toBe(NodeType.Equal); + expect(mod.getChildCount()).toBe(1); + }); + it('throws an error on unexpected token (sx).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'sx') + ]); + const parser = new Parser.DiceParser(lexer); + expect(() => { + const result = new Parser.ParseResult(); + const mod = parser.parseCompareModifier(result); + expect(result.errors.length).toBeGreaterThanOrEqual(1); + }).toThrow(); + }); + }); }); diff --git a/spec/parser/dice-parser.parseCritical.spec.ts b/spec/parser/dice-parser.parseCritical.spec.ts index ddc9f0c..bf2baa7 100644 --- a/spec/parser/dice-parser.parseCritical.spec.ts +++ b/spec/parser/dice-parser.parseCritical.spec.ts @@ -4,66 +4,66 @@ import * as Parser from '../../src/parser'; import { MockLexer } from '../helpers'; describe('DiceParser', () => { - describe('parseCritical', () => { - it('can correctly parse a critical modifier (c).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'c') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new Parser.ParseResult(); - const mod = parser.parseCritical(result); - expect(result.errors.length).toBe(0); - expect(mod.type).toBe(NodeType.Critical); - expect(mod.getAttribute('type')).toBe('success'); - }); - it('can correctly parse a critical modifier (cs).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'cs') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new Parser.ParseResult(); - const mod = parser.parseCritical(result); - expect(result.errors.length).toBe(0); - expect(mod.type).toBe(NodeType.Critical); - expect(mod.getAttribute('type')).toBe('success'); - }); - it('can correctly parse a critical modifier (cf).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'cf') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new Parser.ParseResult(); - const mod = parser.parseCritical(result); - expect(result.errors.length).toBe(0); - expect(mod.type).toBe(NodeType.Critical); - expect(mod.getAttribute('type')).toBe('failure'); - }); - it('can correctly parse a critical modifier with a compare point (cf<3).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'cf'), - new Token(TokenType.Less, 2, '<'), - new Token(TokenType.Number, 3, '3') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new Parser.ParseResult(); - const mod = parser.parseCritical(result); - expect(result.errors.length).toBe(0); - expect(mod.type).toBe(NodeType.Critical); - expect(mod.getAttribute('type')).toBe('failure'); - expect(mod.getChildCount()).toBe(1); - expect(mod.getChild(0).type).toBe(NodeType.Less); - }); - it('throws an error on an unrecognized critical type (cz<3).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'cz'), - new Token(TokenType.Less, 2, '<'), - new Token(TokenType.Number, 3, '3') - ]); - const parser = new Parser.DiceParser(lexer); + describe('parseCritical', () => { + it('can correctly parse a critical modifier (c).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'c') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new Parser.ParseResult(); + const mod = parser.parseCritical(result); + expect(result.errors.length).toBe(0); + expect(mod.type).toBe(NodeType.Critical); + expect(mod.getAttribute('type')).toBe('success'); + }); + it('can correctly parse a critical modifier (cs).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'cs') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new Parser.ParseResult(); + const mod = parser.parseCritical(result); + expect(result.errors.length).toBe(0); + expect(mod.type).toBe(NodeType.Critical); + expect(mod.getAttribute('type')).toBe('success'); + }); + it('can correctly parse a critical modifier (cf).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'cf') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new Parser.ParseResult(); + const mod = parser.parseCritical(result); + expect(result.errors.length).toBe(0); + expect(mod.type).toBe(NodeType.Critical); + expect(mod.getAttribute('type')).toBe('failure'); + }); + it('can correctly parse a critical modifier with a compare point (cf<3).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'cf'), + new Token(TokenType.Less, 2, '<'), + new Token(TokenType.Number, 3, '3') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new Parser.ParseResult(); + const mod = parser.parseCritical(result); + expect(result.errors.length).toBe(0); + expect(mod.type).toBe(NodeType.Critical); + expect(mod.getAttribute('type')).toBe('failure'); + expect(mod.getChildCount()).toBe(1); + expect(mod.getChild(0).type).toBe(NodeType.Less); + }); + it('throws an error on an unrecognized critical type (cz<3).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'cz'), + new Token(TokenType.Less, 2, '<'), + new Token(TokenType.Number, 3, '3') + ]); + const parser = new Parser.DiceParser(lexer); - const result = new Parser.ParseResult(); - const mod = parser.parseCritical(result); - expect(result.errors.length).toBeGreaterThanOrEqual(1); - }); + const result = new Parser.ParseResult(); + const mod = parser.parseCritical(result); + expect(result.errors.length).toBeGreaterThanOrEqual(1); }); + }); }); diff --git a/spec/parser/dice-parser.parseDice.spec.ts b/spec/parser/dice-parser.parseDice.spec.ts index 0120ad7..842bab0 100644 --- a/spec/parser/dice-parser.parseDice.spec.ts +++ b/spec/parser/dice-parser.parseDice.spec.ts @@ -5,192 +5,215 @@ import { ParseResult } from '../../src/parser/parse-result.class'; import { MockLexer } from '../helpers'; describe('DiceParser', () => { - describe('parseDice', () => { - it('can correctly parse a simple dice roll with pre-parsed number.', () => { - const lexer = new MockLexer([ - new Token(TokenType.Number, 0, '10'), - new Token(TokenType.Identifier, 2, 'd'), - new Token(TokenType.Number, 3, '6') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const num = parser.parseNumber(result); - const dice = parser.parseDice(result, num); - expect(result.errors.length).toBe(0); + describe('parseDice', () => { + it('can correctly parse a simple dice roll with pre-parsed number.', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '10'), + new Token(TokenType.Identifier, 2, 'd'), + new Token(TokenType.Number, 3, '6') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const num = parser.parseNumber(result); + const dice = parser.parseDice(result, num); + expect(result.errors.length).toBe(0); - expect(dice.type).toBe(NodeType.Dice); - expect(dice.getChildCount()).toBe(2); - expect(dice.getChild(0).type).toBe(NodeType.Number); - expect(dice.getChild(0).getAttribute('value')).toBe(10); - expect(dice.getChild(1).type).toBe(NodeType.DiceSides); - expect(dice.getChild(1).getAttribute('value')).toBe(6); - }); - it('can correctly parse a simple dice roll with modifier (10d6dl3).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Number, 0, '10'), - new Token(TokenType.Identifier, 2, 'd'), - new Token(TokenType.Number, 3, '6'), - new Token(TokenType.Identifier, 4, 'dl'), - new Token(TokenType.Number, 5, '3'), - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const dice = parser.parseDice(result); - expect(result.errors.length).toBe(0); - expect(dice.type).toBe(NodeType.Drop); - expect(dice.getAttribute('type')).toBe('lowest'); - expect(dice.getChildCount()).toBe(2); - expect(dice.getChild(0).type).toBe(NodeType.Dice); - expect(dice.getChild(1).type).toBe(NodeType.Number); - }); - it('can correctly parse a dice roll with a explode modifier (4d6!p>3).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Number, 0, '4'), - new Token(TokenType.Identifier, 1, 'd'), - new Token(TokenType.Number, 2, '6'), - new Token(TokenType.Exclamation, 3, '!'), - new Token(TokenType.Identifier, 4, 'p'), - new Token(TokenType.Greater, 5, '>'), - new Token(TokenType.Number, 6, '3'), - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseDice(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Explode); - expect(exp.getAttribute('compound')).toBe(false); - expect(exp.getAttribute('penetrate')).toBe(true); - expect(exp.getChildCount()).toBe(2); + expect(dice.type).toBe(NodeType.Dice); + expect(dice.getChildCount()).toBe(2); + expect(dice.getChild(0).type).toBe(NodeType.Number); + expect(dice.getChild(0).getAttribute('value')).toBe(10); + expect(dice.getChild(1).type).toBe(NodeType.DiceSides); + expect(dice.getChild(1).getAttribute('value')).toBe(6); + }); + it('can correctly parse a simple dice roll with modifier (10d6dl3).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '10'), + new Token(TokenType.Identifier, 2, 'd'), + new Token(TokenType.Number, 3, '6'), + new Token(TokenType.Identifier, 4, 'dl'), + new Token(TokenType.Number, 5, '3'), + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const dice = parser.parseDice(result); + expect(result.errors.length).toBe(0); + expect(dice.type).toBe(NodeType.Drop); + expect(dice.getAttribute('type')).toBe('lowest'); + expect(dice.getChildCount()).toBe(2); + expect(dice.getChild(0).type).toBe(NodeType.Dice); + expect(dice.getChild(1).type).toBe(NodeType.Number); + }); + it('can correctly parse a dice roll with a explode modifier (4d6!p>3).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '4'), + new Token(TokenType.Identifier, 1, 'd'), + new Token(TokenType.Number, 2, '6'), + new Token(TokenType.Exclamation, 3, '!'), + new Token(TokenType.Identifier, 4, 'p'), + new Token(TokenType.Greater, 5, '>'), + new Token(TokenType.Number, 6, '3'), + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseDice(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Explode); + expect(exp.getAttribute('compound')).toBe(false); + expect(exp.getAttribute('penetrate')).toBe(true); + expect(exp.getChildCount()).toBe(2); - const dice = exp.getChild(0); - expect(dice.type).toBe(NodeType.Dice); - expect(dice.getChildCount()).toBe(2); + const dice = exp.getChild(0); + expect(dice.type).toBe(NodeType.Dice); + expect(dice.getChildCount()).toBe(2); - const greater = exp.getChild(1); - expect(greater.type).toBe(NodeType.Greater); - expect(greater.getChildCount()).toBe(1); - }); - it('can correctly parse a dice roll with a critical modifier (4d6cs).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Number, 0, '4'), - new Token(TokenType.Identifier, 1, 'd'), - new Token(TokenType.Number, 2, '6'), - new Token(TokenType.Identifier, 3, 'cs') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseDice(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Critical); - expect(exp.getAttribute('type')).toBe('success'); - expect(exp.getChildCount()).toBe(1); + const greater = exp.getChild(1); + expect(greater.type).toBe(NodeType.Greater); + expect(greater.getChildCount()).toBe(1); + }); + it('can correctly parse a dice roll with a critical modifier (4d6cs).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '4'), + new Token(TokenType.Identifier, 1, 'd'), + new Token(TokenType.Number, 2, '6'), + new Token(TokenType.Identifier, 3, 'cs') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseDice(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Critical); + expect(exp.getAttribute('type')).toBe('success'); + expect(exp.getChildCount()).toBe(1); - const dice = exp.getChild(0); - expect(dice.type).toBe(NodeType.Dice); - expect(dice.getChildCount()).toBe(2); - }); - it('can correctly parse a dice roll with a keep modifier (4d6kl3).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Number, 0, '4'), - new Token(TokenType.Identifier, 1, 'd'), - new Token(TokenType.Number, 2, '6'), - new Token(TokenType.Identifier, 3, 'kl'), - new Token(TokenType.Number, 5, '3') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseDice(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Keep); - expect(exp.getAttribute('type')).toBe('lowest'); - expect(exp.getChildCount()).toBe(2); + const dice = exp.getChild(0); + expect(dice.type).toBe(NodeType.Dice); + expect(dice.getChildCount()).toBe(2); + }); + it('can correctly parse a dice roll with a simple keep modifier (4d6kl).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '4'), + new Token(TokenType.Identifier, 1, 'd'), + new Token(TokenType.Number, 2, '6'), + new Token(TokenType.Identifier, 3, 'kl') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseDice(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Keep); + expect(exp.getAttribute('type')).toBe('lowest'); + expect(exp.getChildCount()).toBe(2); - const dice = exp.getChild(0); - expect(dice.type).toBe(NodeType.Dice); - expect(dice.getChildCount()).toBe(2); + const dice = exp.getChild(0); + expect(dice.type).toBe(NodeType.Dice); + expect(dice.getChildCount()).toBe(2); - const low = exp.getChild(1); - expect(low.type).toBe(NodeType.Number); - expect(low.getAttribute('value')).toBe(3); - }); - it('can correctly parse a dice roll with a reroll modifier (4d6ro>3).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Number, 0, '4'), - new Token(TokenType.Identifier, 1, 'd'), - new Token(TokenType.Number, 2, '6'), - new Token(TokenType.Identifier, 3, 'ro'), - new Token(TokenType.Greater, 5, '>'), - new Token(TokenType.Number, 6, '3') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseDice(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Reroll); - expect(exp.getAttribute('once')).toBe(true); - expect(exp.getChildCount()).toBe(2); + const low = exp.getChild(1); + expect(low.type).toBe(NodeType.Number); + expect(low.getAttribute('value')).toBe(1); + }); + it('can correctly parse a dice roll with a numbered keep modifier (4d6kl3).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '4'), + new Token(TokenType.Identifier, 1, 'd'), + new Token(TokenType.Number, 2, '6'), + new Token(TokenType.Identifier, 3, 'kl'), + new Token(TokenType.Number, 5, '3') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseDice(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Keep); + expect(exp.getAttribute('type')).toBe('lowest'); + expect(exp.getChildCount()).toBe(2); - const dice = exp.getChild(0); - expect(dice.type).toBe(NodeType.Dice); - expect(dice.getChildCount()).toBe(2); + const dice = exp.getChild(0); + expect(dice.type).toBe(NodeType.Dice); + expect(dice.getChildCount()).toBe(2); - const greater = exp.getChild(1); - expect(greater.type).toBe(NodeType.Greater); - expect(greater.getChildCount()).toBe(1); - }); - it('can correctly parse a dice roll with a sort modifier (4d6sa).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Number, 0, '4'), - new Token(TokenType.Identifier, 1, 'd'), - new Token(TokenType.Number, 2, '6'), - new Token(TokenType.Identifier, 3, 'sa'), - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseDice(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Sort); - expect(exp.getAttribute('direction')).toBe('ascending'); - expect(exp.getChildCount()).toBe(1); + const low = exp.getChild(1); + expect(low.type).toBe(NodeType.Number); + expect(low.getAttribute('value')).toBe(3); + }); + it('can correctly parse a dice roll with a reroll modifier (4d6ro>3).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '4'), + new Token(TokenType.Identifier, 1, 'd'), + new Token(TokenType.Number, 2, '6'), + new Token(TokenType.Identifier, 3, 'ro'), + new Token(TokenType.Greater, 5, '>'), + new Token(TokenType.Number, 6, '3') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseDice(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Reroll); + expect(exp.getAttribute('once')).toBe(true); + expect(exp.getChildCount()).toBe(2); - const dice = exp.getChild(0); - expect(dice.type).toBe(NodeType.Dice); - expect(dice.getChildCount()).toBe(2); - }); - it('can correctly parse a compare modifier (4d6>3.', () => { - const lexer = new MockLexer([ - new Token(TokenType.Number, 0, '4'), - new Token(TokenType.Identifier, 1, 'd'), - new Token(TokenType.Number, 2, '6'), - new Token(TokenType.Greater, 3, '>'), - new Token(TokenType.Number, 4, '3'), - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseDice(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Greater); - expect(exp.getChildCount()).toBe(2); + const dice = exp.getChild(0); + expect(dice.type).toBe(NodeType.Dice); + expect(dice.getChildCount()).toBe(2); - const dice = exp.getChild(0); - expect(dice.type).toBe(NodeType.Dice); - expect(dice.getChildCount()).toBe(2); + const greater = exp.getChild(1); + expect(greater.type).toBe(NodeType.Greater); + expect(greater.getChildCount()).toBe(1); + }); + it('can correctly parse a dice roll with a sort modifier (4d6sa).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '4'), + new Token(TokenType.Identifier, 1, 'd'), + new Token(TokenType.Number, 2, '6'), + new Token(TokenType.Identifier, 3, 'sa'), + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseDice(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Sort); + expect(exp.getAttribute('direction')).toBe('ascending'); + expect(exp.getChildCount()).toBe(1); + + const dice = exp.getChild(0); + expect(dice.type).toBe(NodeType.Dice); + expect(dice.getChildCount()).toBe(2); + }); + it('can correctly parse a compare modifier (4d6>3.', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '4'), + new Token(TokenType.Identifier, 1, 'd'), + new Token(TokenType.Number, 2, '6'), + new Token(TokenType.Greater, 3, '>'), + new Token(TokenType.Number, 4, '3'), + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseDice(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Greater); + expect(exp.getChildCount()).toBe(2); - const num = exp.getChild(1); - expect(num.type).toBe(NodeType.Number); - expect(num.getAttribute('value')).toBe(3); - }); - it('throws exception on unrecognized modifier (4d6x).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Number, 0, '4'), - new Token(TokenType.Identifier, 1, 'd'), - new Token(TokenType.Number, 2, '6'), - new Token(TokenType.Identifier, 3, 'x'), - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const mod = parser.parseDice(result); - expect(result.errors.length).toBeGreaterThanOrEqual(1); - }); + const dice = exp.getChild(0); + expect(dice.type).toBe(NodeType.Dice); + expect(dice.getChildCount()).toBe(2); + + const num = exp.getChild(1); + expect(num.type).toBe(NodeType.Number); + expect(num.getAttribute('value')).toBe(3); + }); + it('throws exception on unrecognized modifier (4d6x).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '4'), + new Token(TokenType.Identifier, 1, 'd'), + new Token(TokenType.Number, 2, '6'), + new Token(TokenType.Identifier, 3, 'x'), + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const mod = parser.parseDice(result); + expect(result.errors.length).toBeGreaterThanOrEqual(1); }); + }); }); diff --git a/spec/parser/dice-parser.parseDiceRoll.spec.ts b/spec/parser/dice-parser.parseDiceRoll.spec.ts index dab68b0..e728b0a 100644 --- a/spec/parser/dice-parser.parseDiceRoll.spec.ts +++ b/spec/parser/dice-parser.parseDiceRoll.spec.ts @@ -4,123 +4,123 @@ import * as Parser from '../../src/parser'; import { MockLexer } from '../helpers'; describe('DiceParser', () => { - describe('parseSimpleDiceRoll', () => { - it('can correctly parse a simple dice roll with pre-parsed number.', () => { - const lexer = new MockLexer([ - new Token(TokenType.Number, 0, '10'), - new Token(TokenType.Identifier, 2, 'd'), - new Token(TokenType.Number, 3, '6') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new Parser.ParseResult(); - const num = parser.parseNumber(result); - expect(result.errors.length).toBe(0); - const dice = parser.parseDiceRoll(result, num); - expect(dice.type).toBe(NodeType.Dice); - expect(dice.getChildCount()).toBe(2); - expect(dice.getChild(0).type).toBe(NodeType.Number); - expect(dice.getChild(0).getAttribute('value')).toBe(10); - expect(dice.getChild(1).type).toBe(NodeType.DiceSides); - expect(dice.getChild(1).getAttribute('value')).toBe(6); - }); - it('can correctly parse a simple dice roll.', () => { - const lexer = new MockLexer([ - new Token(TokenType.Number, 0, '10'), - new Token(TokenType.Identifier, 2, 'd'), - new Token(TokenType.Number, 3, '6') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new Parser.ParseResult(); - const dice = parser.parseDiceRoll(result); - expect(result.errors.length).toBe(0); - expect(dice.type).toBe(NodeType.Dice); - expect(dice.getChildCount()).toBe(2); - expect(dice.getChild(0).type).toBe(NodeType.Number); - expect(dice.getChild(0).getAttribute('value')).toBe(10); - expect(dice.getChild(1).type).toBe(NodeType.DiceSides); - expect(dice.getChild(1).getAttribute('value')).toBe(6); - }); - it('can correctly parse a simple fate dice roll', () => { - const lexer = new MockLexer([ - new Token(TokenType.Number, 0, '10'), - new Token(TokenType.Identifier, 2, 'dF') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new Parser.ParseResult(); - const dice = parser.parseDiceRoll(result); - expect(result.errors.length).toBe(0); - expect(dice.type).toBe(NodeType.Dice); - expect(dice.getChildCount()).toBe(2); - expect(dice.getChild(0).type).toBe(NodeType.Number); - expect(dice.getChild(0).getAttribute('value')).toBe(10); - expect(dice.getChild(1).type).toBe(NodeType.DiceSides); - expect(dice.getChild(1).getAttribute('value')).toBe('fate'); - }); - it('can correctly parse a dice roll with a bracketed number', () => { - const lexer = new MockLexer([ - new Token(TokenType.ParenthesisOpen, 0, '('), - new Token(TokenType.Number, 1, '10'), - new Token(TokenType.ParenthesisClose, 3, ')'), - new Token(TokenType.Identifier, 4, 'd'), - new Token(TokenType.Number, 5, '6') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new Parser.ParseResult(); - const dice = parser.parseDiceRoll(result); - expect(result.errors.length).toBe(0); - expect(dice.type).toBe(NodeType.Dice); - expect(dice.getChildCount()).toBe(2); - expect(dice.getChild(0).type).toBe(NodeType.Number); - expect(dice.getChild(0).getAttribute('value')).toBe(10); - expect(dice.getChild(1).type).toBe(NodeType.DiceSides); - expect(dice.getChild(1).getAttribute('value')).toBe(6); - }); - it('can correctly parse a dice roll with a bracketed expression', () => { - const lexer = new MockLexer([ - new Token(TokenType.ParenthesisOpen, 0, '('), - new Token(TokenType.Number, 1, '10'), - new Token(TokenType.Plus, 3, '+'), - new Token(TokenType.Number, 4, '3'), - new Token(TokenType.ParenthesisClose, 5, ')'), - new Token(TokenType.Identifier, 6, 'd'), - new Token(TokenType.Number, 7, '6') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new Parser.ParseResult(); - const dice = parser.parseDiceRoll(result); - expect(result.errors.length).toBe(0); - expect(dice.type).toBe(NodeType.Dice); - expect(dice.getChildCount()).toBe(2); - expect(dice.getChild(0).type).toBe(NodeType.Add); - expect(dice.getChild(0).getChildCount()).toBe(2); - expect(dice.getChild(0).getChild(0).type).toBe(NodeType.Number); - expect(dice.getChild(0).getChild(0).getAttribute('value')).toBe(10); - expect(dice.getChild(0).getChild(1).type).toBe(NodeType.Number); - expect(dice.getChild(0).getChild(1).getAttribute('value')).toBe(3); - expect(dice.getChild(1).type).toBe(NodeType.DiceSides); - expect(dice.getChild(1).getAttribute('value')).toBe(6); - }); - it('throws on missing roll times', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'd'), - new Token(TokenType.Number, 1, '10') - ]); - const parser = new Parser.DiceParser(lexer); + describe('parseSimpleDiceRoll', () => { + it('can correctly parse a simple dice roll with pre-parsed number.', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '10'), + new Token(TokenType.Identifier, 2, 'd'), + new Token(TokenType.Number, 3, '6') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new Parser.ParseResult(); + const num = parser.parseNumber(result); + expect(result.errors.length).toBe(0); + const dice = parser.parseDiceRoll(result, num); + expect(dice.type).toBe(NodeType.Dice); + expect(dice.getChildCount()).toBe(2); + expect(dice.getChild(0).type).toBe(NodeType.Number); + expect(dice.getChild(0).getAttribute('value')).toBe(10); + expect(dice.getChild(1).type).toBe(NodeType.DiceSides); + expect(dice.getChild(1).getAttribute('value')).toBe(6); + }); + it('can correctly parse a simple dice roll.', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '10'), + new Token(TokenType.Identifier, 2, 'd'), + new Token(TokenType.Number, 3, '6') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new Parser.ParseResult(); + const dice = parser.parseDiceRoll(result); + expect(result.errors.length).toBe(0); + expect(dice.type).toBe(NodeType.Dice); + expect(dice.getChildCount()).toBe(2); + expect(dice.getChild(0).type).toBe(NodeType.Number); + expect(dice.getChild(0).getAttribute('value')).toBe(10); + expect(dice.getChild(1).type).toBe(NodeType.DiceSides); + expect(dice.getChild(1).getAttribute('value')).toBe(6); + }); + it('can correctly parse a simple fate dice roll', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '10'), + new Token(TokenType.Identifier, 2, 'dF') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new Parser.ParseResult(); + const dice = parser.parseDiceRoll(result); + expect(result.errors.length).toBe(0); + expect(dice.type).toBe(NodeType.Dice); + expect(dice.getChildCount()).toBe(2); + expect(dice.getChild(0).type).toBe(NodeType.Number); + expect(dice.getChild(0).getAttribute('value')).toBe(10); + expect(dice.getChild(1).type).toBe(NodeType.DiceSides); + expect(dice.getChild(1).getAttribute('value')).toBe('fate'); + }); + it('can correctly parse a dice roll with a bracketed number', () => { + const lexer = new MockLexer([ + new Token(TokenType.ParenthesisOpen, 0, '('), + new Token(TokenType.Number, 1, '10'), + new Token(TokenType.ParenthesisClose, 3, ')'), + new Token(TokenType.Identifier, 4, 'd'), + new Token(TokenType.Number, 5, '6') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new Parser.ParseResult(); + const dice = parser.parseDiceRoll(result); + expect(result.errors.length).toBe(0); + expect(dice.type).toBe(NodeType.Dice); + expect(dice.getChildCount()).toBe(2); + expect(dice.getChild(0).type).toBe(NodeType.Number); + expect(dice.getChild(0).getAttribute('value')).toBe(10); + expect(dice.getChild(1).type).toBe(NodeType.DiceSides); + expect(dice.getChild(1).getAttribute('value')).toBe(6); + }); + it('can correctly parse a dice roll with a bracketed expression', () => { + const lexer = new MockLexer([ + new Token(TokenType.ParenthesisOpen, 0, '('), + new Token(TokenType.Number, 1, '10'), + new Token(TokenType.Plus, 3, '+'), + new Token(TokenType.Number, 4, '3'), + new Token(TokenType.ParenthesisClose, 5, ')'), + new Token(TokenType.Identifier, 6, 'd'), + new Token(TokenType.Number, 7, '6') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new Parser.ParseResult(); + const dice = parser.parseDiceRoll(result); + expect(result.errors.length).toBe(0); + expect(dice.type).toBe(NodeType.Dice); + expect(dice.getChildCount()).toBe(2); + expect(dice.getChild(0).type).toBe(NodeType.Add); + expect(dice.getChild(0).getChildCount()).toBe(2); + expect(dice.getChild(0).getChild(0).type).toBe(NodeType.Number); + expect(dice.getChild(0).getChild(0).getAttribute('value')).toBe(10); + expect(dice.getChild(0).getChild(1).type).toBe(NodeType.Number); + expect(dice.getChild(0).getChild(1).getAttribute('value')).toBe(3); + expect(dice.getChild(1).type).toBe(NodeType.DiceSides); + expect(dice.getChild(1).getAttribute('value')).toBe(6); + }); + it('throws on missing roll times', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'd'), + new Token(TokenType.Number, 1, '10') + ]); + const parser = new Parser.DiceParser(lexer); - const result = new Parser.ParseResult(); - const dice = parser.parseDiceRoll(result); - expect(result.errors.length).toBeGreaterThanOrEqual(1); - }); - it('throws on missing dice value', () => { - const lexer = new MockLexer([ - new Token(TokenType.Number, 0, '6'), - new Token(TokenType.Identifier, 1, 'd') - ]); - const parser = new Parser.DiceParser(lexer); + const result = new Parser.ParseResult(); + const dice = parser.parseDiceRoll(result); + expect(result.errors.length).toBeGreaterThanOrEqual(1); + }); + it('throws on missing dice value', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '6'), + new Token(TokenType.Identifier, 1, 'd') + ]); + const parser = new Parser.DiceParser(lexer); - const result = new Parser.ParseResult(); - const dice = parser.parseDiceRoll(result); - expect(result.errors.length).toBeGreaterThanOrEqual(1); - }); + const result = new Parser.ParseResult(); + const dice = parser.parseDiceRoll(result); + expect(result.errors.length).toBeGreaterThanOrEqual(1); }); + }); }); diff --git a/spec/parser/dice-parser.parseDrop.spec.ts b/spec/parser/dice-parser.parseDrop.spec.ts index a96d6e5..175aca4 100644 --- a/spec/parser/dice-parser.parseDrop.spec.ts +++ b/spec/parser/dice-parser.parseDrop.spec.ts @@ -5,83 +5,83 @@ import { ParseResult } from '../../src/parser/parse-result.class'; import { MockLexer } from '../helpers'; describe('DiceParser', () => { - describe('parseDrop', () => { - it('can correctly parse a drop modifier (d).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'd') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const mod = parser.parseDrop(result); - expect(result.errors.length).toBe(0); - expect(mod.type).toBe(NodeType.Drop); - expect(mod.getAttribute('type')).toBe('lowest'); - }); - it('can correctly parse a drop modifier (dh).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'dh') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const mod = parser.parseDrop(result); - expect(result.errors.length).toBe(0); - expect(mod.type).toBe(NodeType.Drop); - expect(mod.getAttribute('type')).toBe('highest'); - }); - it('can correctly parse a drop modifier (dl).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'dl') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const mod = parser.parseDrop(result); - expect(result.errors.length).toBe(0); - expect(mod.type).toBe(NodeType.Drop); - expect(mod.getAttribute('type')).toBe('lowest'); - }); - it('can correctly parse a drop modifier with simple number (dl3).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'dl'), - new Token(TokenType.Number, 2, '3') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const mod = parser.parseDrop(result); - expect(result.errors.length).toBe(0); - expect(mod.type).toBe(NodeType.Drop); - expect(mod.getAttribute('type')).toBe('lowest'); - expect(mod.getChildCount()).toBe(1); - expect(mod.getChild(0).type).toBe(NodeType.Number); - expect(mod.getChild(0).getAttribute('value')).toBe(3); - }); - it('can correctly parse a drop modifier with simple number (dl(5+3)).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'dl'), - new Token(TokenType.ParenthesisOpen, 2, '('), - new Token(TokenType.Number, 3, '5'), - new Token(TokenType.Plus, 4, '+'), - new Token(TokenType.Number, 5, '3'), - new Token(TokenType.ParenthesisClose, 6, ')') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const mod = parser.parseDrop(result); - expect(result.errors.length).toBe(0); - expect(mod.type).toBe(NodeType.Drop); - expect(mod.getAttribute('type')).toBe('lowest'); - expect(mod.getChildCount()).toBe(1); - expect(mod.getChild(0).type).toBe(NodeType.Add); - }); - it('throws an error on unknown keep type (dg3).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'dg'), - new Token(TokenType.Number, 2, '3') - ]); - const parser = new Parser.DiceParser(lexer); + describe('parseDrop', () => { + it('can correctly parse a drop modifier (d).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'd') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const mod = parser.parseDrop(result); + expect(result.errors.length).toBe(0); + expect(mod.type).toBe(NodeType.Drop); + expect(mod.getAttribute('type')).toBe('lowest'); + }); + it('can correctly parse a drop modifier (dh).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'dh') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const mod = parser.parseDrop(result); + expect(result.errors.length).toBe(0); + expect(mod.type).toBe(NodeType.Drop); + expect(mod.getAttribute('type')).toBe('highest'); + }); + it('can correctly parse a drop modifier (dl).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'dl') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const mod = parser.parseDrop(result); + expect(result.errors.length).toBe(0); + expect(mod.type).toBe(NodeType.Drop); + expect(mod.getAttribute('type')).toBe('lowest'); + }); + it('can correctly parse a drop modifier with simple number (dl3).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'dl'), + new Token(TokenType.Number, 2, '3') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const mod = parser.parseDrop(result); + expect(result.errors.length).toBe(0); + expect(mod.type).toBe(NodeType.Drop); + expect(mod.getAttribute('type')).toBe('lowest'); + expect(mod.getChildCount()).toBe(1); + expect(mod.getChild(0).type).toBe(NodeType.Number); + expect(mod.getChild(0).getAttribute('value')).toBe(3); + }); + it('can correctly parse a drop modifier with simple number (dl(5+3)).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'dl'), + new Token(TokenType.ParenthesisOpen, 2, '('), + new Token(TokenType.Number, 3, '5'), + new Token(TokenType.Plus, 4, '+'), + new Token(TokenType.Number, 5, '3'), + new Token(TokenType.ParenthesisClose, 6, ')') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const mod = parser.parseDrop(result); + expect(result.errors.length).toBe(0); + expect(mod.type).toBe(NodeType.Drop); + expect(mod.getAttribute('type')).toBe('lowest'); + expect(mod.getChildCount()).toBe(1); + expect(mod.getChild(0).type).toBe(NodeType.Add); + }); + it('throws an error on unknown keep type (dg3).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'dg'), + new Token(TokenType.Number, 2, '3') + ]); + const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const mod = parser.parseDrop(result); - expect(result.errors.length).toBeGreaterThanOrEqual(1); - }); + const result = new ParseResult(); + const mod = parser.parseDrop(result); + expect(result.errors.length).toBeGreaterThanOrEqual(1); }); + }); }); diff --git a/spec/parser/dice-parser.parseExplode.spec.ts b/spec/parser/dice-parser.parseExplode.spec.ts index 152bc8c..421d683 100644 --- a/spec/parser/dice-parser.parseExplode.spec.ts +++ b/spec/parser/dice-parser.parseExplode.spec.ts @@ -5,75 +5,75 @@ import { ParseResult } from '../../src/parser/parse-result.class'; import { MockLexer } from '../helpers'; describe('DiceParser', () => { - describe('parseExplode', () => { - it('can correctly parse an explode modifier (!).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Exclamation, 0, '!') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const mod = parser.parseExplode(result); - expect(result.errors.length).toBe(0); - expect(mod.type).toBe(NodeType.Explode); - expect(mod.getAttribute('compound')).toBe(false); - expect(mod.getAttribute('penetrate')).toBe(false); - }); - it('can correctly parse an explode modifier (!!).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Exclamation, 0, '!'), - new Token(TokenType.Exclamation, 1, '!'), - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const mod = parser.parseExplode(result); - expect(result.errors.length).toBe(0); - expect(mod.type).toBe(NodeType.Explode); - expect(mod.getAttribute('compound')).toBe(true); - expect(mod.getAttribute('penetrate')).toBe(false); - }); - it('can correctly parse an explode modifier (!p).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Exclamation, 0, '!'), - new Token(TokenType.Identifier, 1, 'p'), - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const mod = parser.parseExplode(result); - expect(result.errors.length).toBe(0); - expect(mod.type).toBe(NodeType.Explode); - expect(mod.getAttribute('compound')).toBe(false); - expect(mod.getAttribute('penetrate')).toBe(true); - }); - it('can correctly parse an explode modifier (!p).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Exclamation, 0, '!'), - new Token(TokenType.Exclamation, 1, '!'), - new Token(TokenType.Identifier, 2, 'p'), - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const mod = parser.parseExplode(result); - expect(result.errors.length).toBe(0); - expect(mod.type).toBe(NodeType.Explode); - expect(mod.getAttribute('compound')).toBe(true); - expect(mod.getAttribute('penetrate')).toBe(true); - }); - it('can correctly parse an explode modifier with a compare point(!p<3).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Exclamation, 0, '!'), - new Token(TokenType.Identifier, 1, 'p'), - new Token(TokenType.Less, 2, '<'), - new Token(TokenType.Number, 3, '3'), - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const mod = parser.parseExplode(result); - expect(result.errors.length).toBe(0); - expect(mod.type).toBe(NodeType.Explode); - expect(mod.getAttribute('compound')).toBe(false); - expect(mod.getAttribute('penetrate')).toBe(true); - expect(mod.getChildCount()).toBe(1); - expect(mod.getChild(0).type).toBe(NodeType.Less); - }); + describe('parseExplode', () => { + it('can correctly parse an explode modifier (!).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Exclamation, 0, '!') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const mod = parser.parseExplode(result); + expect(result.errors.length).toBe(0); + expect(mod.type).toBe(NodeType.Explode); + expect(mod.getAttribute('compound')).toBe(false); + expect(mod.getAttribute('penetrate')).toBe(false); }); + it('can correctly parse an explode modifier (!!).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Exclamation, 0, '!'), + new Token(TokenType.Exclamation, 1, '!'), + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const mod = parser.parseExplode(result); + expect(result.errors.length).toBe(0); + expect(mod.type).toBe(NodeType.Explode); + expect(mod.getAttribute('compound')).toBe(true); + expect(mod.getAttribute('penetrate')).toBe(false); + }); + it('can correctly parse an explode modifier (!p).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Exclamation, 0, '!'), + new Token(TokenType.Identifier, 1, 'p'), + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const mod = parser.parseExplode(result); + expect(result.errors.length).toBe(0); + expect(mod.type).toBe(NodeType.Explode); + expect(mod.getAttribute('compound')).toBe(false); + expect(mod.getAttribute('penetrate')).toBe(true); + }); + it('can correctly parse an explode modifier (!p).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Exclamation, 0, '!'), + new Token(TokenType.Exclamation, 1, '!'), + new Token(TokenType.Identifier, 2, 'p'), + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const mod = parser.parseExplode(result); + expect(result.errors.length).toBe(0); + expect(mod.type).toBe(NodeType.Explode); + expect(mod.getAttribute('compound')).toBe(true); + expect(mod.getAttribute('penetrate')).toBe(true); + }); + it('can correctly parse an explode modifier with a compare point(!p<3).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Exclamation, 0, '!'), + new Token(TokenType.Identifier, 1, 'p'), + new Token(TokenType.Less, 2, '<'), + new Token(TokenType.Number, 3, '3'), + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const mod = parser.parseExplode(result); + expect(result.errors.length).toBe(0); + expect(mod.type).toBe(NodeType.Explode); + expect(mod.getAttribute('compound')).toBe(false); + expect(mod.getAttribute('penetrate')).toBe(true); + expect(mod.getChildCount()).toBe(1); + expect(mod.getChild(0).type).toBe(NodeType.Less); + }); + }); }); diff --git a/spec/parser/dice-parser.parseExpression.spec.ts b/spec/parser/dice-parser.parseExpression.spec.ts index 679b982..b6a767b 100644 --- a/spec/parser/dice-parser.parseExpression.spec.ts +++ b/spec/parser/dice-parser.parseExpression.spec.ts @@ -5,41 +5,41 @@ import { ParseResult } from '../../src/parser/parse-result.class'; import { MockLexer } from '../helpers'; describe('DiceParser', () => { - describe('parseExpression', () => { - it('can correctly identify a boolean operator', () => { - const lexer = new MockLexer([ - new Token(TokenType.Number, 0, '10'), - new Token(TokenType.Greater, 2, '>'), - new Token(TokenType.Number, 3, '5'), - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseExpression(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Greater); - expect(exp.getChildCount()).toBe(2); - expect(exp.getChild(0).type).toBe(NodeType.Number); - expect(exp.getChild(0).getAttribute('value')).toBe(10); - expect(exp.getChild(1).type).toBe(NodeType.Number); - expect(exp.getChild(1).getAttribute('value')).toBe(5); - }); - it('correctly handles operator precedence (10 * 5 + 2)', () => { - const lexer = new MockLexer([ - new Token(TokenType.Number, 0, '10'), - new Token(TokenType.Asterisk, 2, '*'), - new Token(TokenType.Number, 3, '5'), - new Token(TokenType.Plus, 4, '+'), - new Token(TokenType.Number, 5, '2'), - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseExpression(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Add); - expect(exp.getChildCount()).toBe(2); - expect(exp.getChild(0).type).toBe(NodeType.Multiply); - expect(exp.getChild(1).type).toBe(NodeType.Number); - expect(exp.getChild(1).getAttribute('value')).toBe(2); - }); + describe('parseExpression', () => { + it('can correctly identify a boolean operator', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '10'), + new Token(TokenType.Greater, 2, '>'), + new Token(TokenType.Number, 3, '5'), + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseExpression(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Greater); + expect(exp.getChildCount()).toBe(2); + expect(exp.getChild(0).type).toBe(NodeType.Number); + expect(exp.getChild(0).getAttribute('value')).toBe(10); + expect(exp.getChild(1).type).toBe(NodeType.Number); + expect(exp.getChild(1).getAttribute('value')).toBe(5); }); + it('correctly handles operator precedence (10 * 5 + 2)', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '10'), + new Token(TokenType.Asterisk, 2, '*'), + new Token(TokenType.Number, 3, '5'), + new Token(TokenType.Plus, 4, '+'), + new Token(TokenType.Number, 5, '2'), + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseExpression(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Add); + expect(exp.getChildCount()).toBe(2); + expect(exp.getChild(0).type).toBe(NodeType.Multiply); + expect(exp.getChild(1).type).toBe(NodeType.Number); + expect(exp.getChild(1).getAttribute('value')).toBe(2); + }); + }); }); diff --git a/spec/parser/dice-parser.parseFactor.spec.ts b/spec/parser/dice-parser.parseFactor.spec.ts index 9aedf37..9eae7b2 100644 --- a/spec/parser/dice-parser.parseFactor.spec.ts +++ b/spec/parser/dice-parser.parseFactor.spec.ts @@ -5,146 +5,146 @@ import { ParseResult } from '../../src/parser/parse-result.class'; import { MockLexer } from '../helpers'; describe('DiceParser', () => { - describe('parseFactor', () => { - it('can correctly identify a number factor', () => { - const lexer = new MockLexer([ - new Token(TokenType.Number, 0, '10') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseFactor(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Number); - expect(exp.getChildCount()).toBe(0); - expect(exp.getAttribute('value')).toBe(10); - }); - it('can correctly identify a function call', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'floor'), - new Token(TokenType.ParenthesisOpen, 5, '('), - new Token(TokenType.Number, 6, '10'), - new Token(TokenType.ParenthesisClose, 8, ')') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseFactor(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Function); - expect(exp.getChildCount()).toBe(1); - expect(exp.getChild(0).type).toBe(NodeType.Number); - expect(exp.getChild(0).getAttribute('value')).toBe(10); - }); - it('can correctly identify a bracketed expression', () => { - const lexer = new MockLexer([ - new Token(TokenType.ParenthesisOpen, 0, '('), - new Token(TokenType.Number, 1, '6'), - new Token(TokenType.Plus, 2, '+'), - new Token(TokenType.Number, 3, '4'), - new Token(TokenType.ParenthesisClose, 4, ')'), - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseFactor(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Add); - expect(exp.getChildCount()).toBe(2); - expect(exp.getChild(0).type).toBe(NodeType.Number); - expect(exp.getChild(0).getAttribute('value')).toBe(6); - expect(exp.getChild(1).type).toBe(NodeType.Number); - expect(exp.getChild(1).getAttribute('value')).toBe(4); - }); - it('can correctly identify a simple dice roll.', () => { - const lexer = new MockLexer([ - new Token(TokenType.Number, 0, '10'), - new Token(TokenType.Identifier, 1, 'd'), - new Token(TokenType.Number, 2, '6') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const dice = parser.parseFactor(result); - expect(result.errors.length).toBe(0); - expect(dice.type).toBe(NodeType.Dice); - expect(dice.getChildCount()).toBe(2); - expect(dice.getChild(0).type).toBe(NodeType.Number); - expect(dice.getChild(0).getAttribute('value')).toBe(10); - expect(dice.getChild(1).type).toBe(NodeType.DiceSides); - expect(dice.getChild(1).getAttribute('value')).toBe(6); - }); - it('can correctly identify a dice roll with a bracketed number', () => { - const lexer = new MockLexer([ - new Token(TokenType.ParenthesisOpen, 0, '('), - new Token(TokenType.Number, 1, '10'), - new Token(TokenType.ParenthesisClose, 3, ')'), - new Token(TokenType.Identifier, 4, 'd'), - new Token(TokenType.Number, 5, '6') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const dice = parser.parseFactor(result); - expect(result.errors.length).toBe(0); - expect(dice.type).toBe(NodeType.Dice); - expect(dice.getChildCount()).toBe(2); - expect(dice.getChild(0).type).toBe(NodeType.Number); - expect(dice.getChild(0).getAttribute('value')).toBe(10); - expect(dice.getChild(1).type).toBe(NodeType.DiceSides); - expect(dice.getChild(1).getAttribute('value')).toBe(6); - }); - it('can correctly identify a dice roll with a bracketed expression', () => { - const lexer = new MockLexer([ - new Token(TokenType.ParenthesisOpen, 0, '('), - new Token(TokenType.Number, 1, '10'), - new Token(TokenType.Plus, 3, '+'), - new Token(TokenType.Number, 4, '3'), - new Token(TokenType.ParenthesisClose, 5, ')'), - new Token(TokenType.Identifier, 6, 'd'), - new Token(TokenType.Number, 7, '6') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const dice = parser.parseFactor(result); - expect(result.errors.length).toBe(0); - expect(dice.type).toBe(NodeType.Dice); - expect(dice.getChildCount()).toBe(2); - expect(dice.getChild(0).type).toBe(NodeType.Add); - expect(dice.getChild(0).getChildCount()).toBe(2); - expect(dice.getChild(0).getChild(0).type).toBe(NodeType.Number); - expect(dice.getChild(0).getChild(0).getAttribute('value')).toBe(10); - expect(dice.getChild(0).getChild(1).type).toBe(NodeType.Number); - expect(dice.getChild(0).getChild(1).getAttribute('value')).toBe(3); - expect(dice.getChild(1).type).toBe(NodeType.DiceSides); - expect(dice.getChild(1).getAttribute('value')).toBe(6); - }); - it('can correctly identify a group.', () => { - const lexer = new MockLexer([ - new Token(TokenType.BraceOpen, 5, '{'), - new Token(TokenType.Number, 6, '10'), - new Token(TokenType.Comma, 8, ','), - new Token(TokenType.Number, 9, '5'), - new Token(TokenType.BraceClose, 10, '}') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseFactor(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Group); - expect(exp.getChildCount()).toBe(2); + describe('parseFactor', () => { + it('can correctly identify a number factor', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '10') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseFactor(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Number); + expect(exp.getChildCount()).toBe(0); + expect(exp.getAttribute('value')).toBe(10); + }); + it('can correctly identify a function call', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'floor'), + new Token(TokenType.ParenthesisOpen, 5, '('), + new Token(TokenType.Number, 6, '10'), + new Token(TokenType.ParenthesisClose, 8, ')') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseFactor(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Function); + expect(exp.getChildCount()).toBe(1); + expect(exp.getChild(0).type).toBe(NodeType.Number); + expect(exp.getChild(0).getAttribute('value')).toBe(10); + }); + it('can correctly identify a bracketed expression', () => { + const lexer = new MockLexer([ + new Token(TokenType.ParenthesisOpen, 0, '('), + new Token(TokenType.Number, 1, '6'), + new Token(TokenType.Plus, 2, '+'), + new Token(TokenType.Number, 3, '4'), + new Token(TokenType.ParenthesisClose, 4, ')'), + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseFactor(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Add); + expect(exp.getChildCount()).toBe(2); + expect(exp.getChild(0).type).toBe(NodeType.Number); + expect(exp.getChild(0).getAttribute('value')).toBe(6); + expect(exp.getChild(1).type).toBe(NodeType.Number); + expect(exp.getChild(1).getAttribute('value')).toBe(4); + }); + it('can correctly identify a simple dice roll.', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '10'), + new Token(TokenType.Identifier, 1, 'd'), + new Token(TokenType.Number, 2, '6') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const dice = parser.parseFactor(result); + expect(result.errors.length).toBe(0); + expect(dice.type).toBe(NodeType.Dice); + expect(dice.getChildCount()).toBe(2); + expect(dice.getChild(0).type).toBe(NodeType.Number); + expect(dice.getChild(0).getAttribute('value')).toBe(10); + expect(dice.getChild(1).type).toBe(NodeType.DiceSides); + expect(dice.getChild(1).getAttribute('value')).toBe(6); + }); + it('can correctly identify a dice roll with a bracketed number', () => { + const lexer = new MockLexer([ + new Token(TokenType.ParenthesisOpen, 0, '('), + new Token(TokenType.Number, 1, '10'), + new Token(TokenType.ParenthesisClose, 3, ')'), + new Token(TokenType.Identifier, 4, 'd'), + new Token(TokenType.Number, 5, '6') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const dice = parser.parseFactor(result); + expect(result.errors.length).toBe(0); + expect(dice.type).toBe(NodeType.Dice); + expect(dice.getChildCount()).toBe(2); + expect(dice.getChild(0).type).toBe(NodeType.Number); + expect(dice.getChild(0).getAttribute('value')).toBe(10); + expect(dice.getChild(1).type).toBe(NodeType.DiceSides); + expect(dice.getChild(1).getAttribute('value')).toBe(6); + }); + it('can correctly identify a dice roll with a bracketed expression', () => { + const lexer = new MockLexer([ + new Token(TokenType.ParenthesisOpen, 0, '('), + new Token(TokenType.Number, 1, '10'), + new Token(TokenType.Plus, 3, '+'), + new Token(TokenType.Number, 4, '3'), + new Token(TokenType.ParenthesisClose, 5, ')'), + new Token(TokenType.Identifier, 6, 'd'), + new Token(TokenType.Number, 7, '6') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const dice = parser.parseFactor(result); + expect(result.errors.length).toBe(0); + expect(dice.type).toBe(NodeType.Dice); + expect(dice.getChildCount()).toBe(2); + expect(dice.getChild(0).type).toBe(NodeType.Add); + expect(dice.getChild(0).getChildCount()).toBe(2); + expect(dice.getChild(0).getChild(0).type).toBe(NodeType.Number); + expect(dice.getChild(0).getChild(0).getAttribute('value')).toBe(10); + expect(dice.getChild(0).getChild(1).type).toBe(NodeType.Number); + expect(dice.getChild(0).getChild(1).getAttribute('value')).toBe(3); + expect(dice.getChild(1).type).toBe(NodeType.DiceSides); + expect(dice.getChild(1).getAttribute('value')).toBe(6); + }); + it('can correctly identify a group.', () => { + const lexer = new MockLexer([ + new Token(TokenType.BraceOpen, 5, '{'), + new Token(TokenType.Number, 6, '10'), + new Token(TokenType.Comma, 8, ','), + new Token(TokenType.Number, 9, '5'), + new Token(TokenType.BraceClose, 10, '}') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseFactor(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Group); + expect(exp.getChildCount()).toBe(2); - expect(exp.getChild(0).type).toBe(NodeType.Number); - expect(exp.getChild(0).getAttribute('value')).toBe(10); + expect(exp.getChild(0).type).toBe(NodeType.Number); + expect(exp.getChild(0).getAttribute('value')).toBe(10); - expect(exp.getChild(1).type).toBe(NodeType.Number); - expect(exp.getChild(1).getAttribute('value')).toBe(5); - }); - it('throws on unexpected token type.', () => { - const lexer = new MockLexer([ - new Token(TokenType.Comma, 5, ','), - new Token(TokenType.Number, 6, '10'), - ]); - const parser = new Parser.DiceParser(lexer); + expect(exp.getChild(1).type).toBe(NodeType.Number); + expect(exp.getChild(1).getAttribute('value')).toBe(5); + }); + it('throws on unexpected token type.', () => { + const lexer = new MockLexer([ + new Token(TokenType.Comma, 5, ','), + new Token(TokenType.Number, 6, '10'), + ]); + const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseFactor(result); - expect(result.errors.length).toBeGreaterThanOrEqual(1); - }); + const result = new ParseResult(); + const exp = parser.parseFactor(result); + expect(result.errors.length).toBeGreaterThanOrEqual(1); }); + }); }); diff --git a/spec/parser/dice-parser.parseFunction.spec.ts b/spec/parser/dice-parser.parseFunction.spec.ts index 9f56851..36ab4ac 100644 --- a/spec/parser/dice-parser.parseFunction.spec.ts +++ b/spec/parser/dice-parser.parseFunction.spec.ts @@ -5,85 +5,85 @@ import { ParseResult } from '../../src/parser/parse-result.class'; import { MockLexer } from '../helpers'; describe('DiceParser', () => { - describe('parseFunction', () => { - it('can correctly parse a function with no arguments', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'floor'), - new Token(TokenType.ParenthesisOpen, 5, '('), - new Token(TokenType.ParenthesisClose, 6, ')') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseFunction(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Function); - expect(exp.getChildCount()).toBe(0); - expect(exp.getAttribute('name')).toBe('floor'); - }); - it('can correctly parse a function with one simple argument', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'floor'), - new Token(TokenType.ParenthesisOpen, 5, '('), - new Token(TokenType.Number, 6, '10'), - new Token(TokenType.ParenthesisClose, 8, ')') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseFunction(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Function); - expect(exp.getChildCount()).toBe(1); - expect(exp.getAttribute('name')).toBe('floor'); + describe('parseFunction', () => { + it('can correctly parse a function with no arguments', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'floor'), + new Token(TokenType.ParenthesisOpen, 5, '('), + new Token(TokenType.ParenthesisClose, 6, ')') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseFunction(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Function); + expect(exp.getChildCount()).toBe(0); + expect(exp.getAttribute('name')).toBe('floor'); + }); + it('can correctly parse a function with one simple argument', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'floor'), + new Token(TokenType.ParenthesisOpen, 5, '('), + new Token(TokenType.Number, 6, '10'), + new Token(TokenType.ParenthesisClose, 8, ')') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseFunction(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Function); + expect(exp.getChildCount()).toBe(1); + expect(exp.getAttribute('name')).toBe('floor'); - expect(exp.getChild(0).type).toBe(NodeType.Number); - expect(exp.getChild(0).getAttribute('value')).toBe(10); - }); - it('can correctly parse a function with one complex argument', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'floor'), - new Token(TokenType.ParenthesisOpen, 5, '('), - new Token(TokenType.Number, 6, '10'), - new Token(TokenType.Asterisk, 8, '*'), - new Token(TokenType.Number, 9, '2'), - new Token(TokenType.ParenthesisClose, 10, ')') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseFunction(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Function); - expect(exp.getChildCount()).toBe(1); - expect(exp.getAttribute('name')).toBe('floor'); + expect(exp.getChild(0).type).toBe(NodeType.Number); + expect(exp.getChild(0).getAttribute('value')).toBe(10); + }); + it('can correctly parse a function with one complex argument', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'floor'), + new Token(TokenType.ParenthesisOpen, 5, '('), + new Token(TokenType.Number, 6, '10'), + new Token(TokenType.Asterisk, 8, '*'), + new Token(TokenType.Number, 9, '2'), + new Token(TokenType.ParenthesisClose, 10, ')') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseFunction(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Function); + expect(exp.getChildCount()).toBe(1); + expect(exp.getAttribute('name')).toBe('floor'); - expect(exp.getChild(0).type).toBe(NodeType.Multiply); - expect(exp.getChild(0).getChildCount()).toBe(2); - expect(exp.getChild(0).getChild(0).type).toBe(NodeType.Number); - expect(exp.getChild(0).getChild(0).getAttribute('value')).toBe(10); - expect(exp.getChild(0).getChild(1).type).toBe(NodeType.Number); - expect(exp.getChild(0).getChild(1).getAttribute('value')).toBe(2); - }); - it('can correctly parse a function with two multiple arguments', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'floor'), - new Token(TokenType.ParenthesisOpen, 5, '('), - new Token(TokenType.Number, 6, '10'), - new Token(TokenType.Comma, 8, ','), - new Token(TokenType.Number, 9, '5'), - new Token(TokenType.ParenthesisClose, 10, ')') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseFunction(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Function); - expect(exp.getChildCount()).toBe(2); - expect(exp.getAttribute('name')).toBe('floor'); + expect(exp.getChild(0).type).toBe(NodeType.Multiply); + expect(exp.getChild(0).getChildCount()).toBe(2); + expect(exp.getChild(0).getChild(0).type).toBe(NodeType.Number); + expect(exp.getChild(0).getChild(0).getAttribute('value')).toBe(10); + expect(exp.getChild(0).getChild(1).type).toBe(NodeType.Number); + expect(exp.getChild(0).getChild(1).getAttribute('value')).toBe(2); + }); + it('can correctly parse a function with two multiple arguments', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'floor'), + new Token(TokenType.ParenthesisOpen, 5, '('), + new Token(TokenType.Number, 6, '10'), + new Token(TokenType.Comma, 8, ','), + new Token(TokenType.Number, 9, '5'), + new Token(TokenType.ParenthesisClose, 10, ')') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseFunction(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Function); + expect(exp.getChildCount()).toBe(2); + expect(exp.getAttribute('name')).toBe('floor'); - expect(exp.getChild(0).type).toBe(NodeType.Number); - expect(exp.getChild(0).getAttribute('value')).toBe(10); + expect(exp.getChild(0).type).toBe(NodeType.Number); + expect(exp.getChild(0).getAttribute('value')).toBe(10); - expect(exp.getChild(1).type).toBe(NodeType.Number); - expect(exp.getChild(1).getAttribute('value')).toBe(5); - }); + expect(exp.getChild(1).type).toBe(NodeType.Number); + expect(exp.getChild(1).getAttribute('value')).toBe(5); }); + }); }); diff --git a/spec/parser/dice-parser.parseGroup.spec.ts b/spec/parser/dice-parser.parseGroup.spec.ts index 2729ea1..00689d5 100644 --- a/spec/parser/dice-parser.parseGroup.spec.ts +++ b/spec/parser/dice-parser.parseGroup.spec.ts @@ -5,189 +5,190 @@ import { ParseResult } from '../../src/parser/parse-result.class'; import { MockLexer } from '../helpers'; describe('DiceParser', () => { - describe('parseGroup', () => { - it('can correctly parse a group with no elements.', () => { - const lexer = new MockLexer([ - new Token(TokenType.BraceOpen, 0, '{'), - new Token(TokenType.BraceClose, 1, '}') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseGroup(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Group); - expect(exp.getChildCount()).toBe(0); - }); - it('can correctly parse a group with one simple argument', () => { - const lexer = new MockLexer([ - new Token(TokenType.BraceOpen, 0, '{'), - new Token(TokenType.Number, 1, '10'), - new Token(TokenType.BraceClose, 3, '}') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseGroup(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Group); - expect(exp.getChildCount()).toBe(1); + describe('parseGroup', () => { + it('can correctly parse a group with no elements.', () => { + const lexer = new MockLexer([ + new Token(TokenType.BraceOpen, 0, '{'), + new Token(TokenType.BraceClose, 1, '}') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseGroup(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Group); + expect(exp.getChildCount()).toBe(0); + }); + it('can correctly parse a group with one simple argument', () => { + const lexer = new MockLexer([ + new Token(TokenType.BraceOpen, 0, '{'), + new Token(TokenType.Number, 1, '10'), + new Token(TokenType.BraceClose, 3, '}') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseGroup(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Group); + expect(exp.getChildCount()).toBe(1); - expect(exp.getChild(0).type).toBe(NodeType.Number); - expect(exp.getChild(0).getAttribute('value')).toBe(10); - }); - it('can correctly parse a group with one complex argument', () => { - const lexer = new MockLexer([ - new Token(TokenType.BraceOpen, 0, '{'), - new Token(TokenType.Number, 1, '10'), - new Token(TokenType.Asterisk, 3, '*'), - new Token(TokenType.Number, 4, '2'), - new Token(TokenType.BraceClose, 5, '}') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseGroup(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Group); - expect(exp.getChildCount()).toBe(1); + expect(exp.getChild(0).type).toBe(NodeType.Number); + expect(exp.getChild(0).getAttribute('value')).toBe(10); + }); + it('can correctly parse a group with one complex argument', () => { + const lexer = new MockLexer([ + new Token(TokenType.BraceOpen, 0, '{'), + new Token(TokenType.Number, 1, '10'), + new Token(TokenType.Asterisk, 3, '*'), + new Token(TokenType.Number, 4, '2'), + new Token(TokenType.BraceClose, 5, '}') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseGroup(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Group); + expect(exp.getChildCount()).toBe(1); - expect(exp.getChild(0).type).toBe(NodeType.Multiply); - expect(exp.getChild(0).getChildCount()).toBe(2); - expect(exp.getChild(0).getChild(0).type).toBe(NodeType.Number); - expect(exp.getChild(0).getChild(0).getAttribute('value')).toBe(10); - expect(exp.getChild(0).getChild(1).type).toBe(NodeType.Number); - expect(exp.getChild(0).getChild(1).getAttribute('value')).toBe(2); - }); - it('can correctly parse a group with two arguments', () => { - const lexer = new MockLexer([ - new Token(TokenType.BraceOpen, 0, '{'), - new Token(TokenType.Number, 1, '10'), - new Token(TokenType.Comma, 3, ','), - new Token(TokenType.Number, 4, '5'), - new Token(TokenType.BraceClose, 5, '}') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseGroup(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Group); - expect(exp.getChildCount()).toBe(2); + expect(exp.getChild(0).type).toBe(NodeType.Multiply); + expect(exp.getChild(0).getChildCount()).toBe(2); + expect(exp.getChild(0).getChild(0).type).toBe(NodeType.Number); + expect(exp.getChild(0).getChild(0).getAttribute('value')).toBe(10); + expect(exp.getChild(0).getChild(1).type).toBe(NodeType.Number); + expect(exp.getChild(0).getChild(1).getAttribute('value')).toBe(2); + }); + it('can correctly parse a group with two arguments', () => { + const lexer = new MockLexer([ + new Token(TokenType.BraceOpen, 0, '{'), + new Token(TokenType.Number, 1, '10'), + new Token(TokenType.Comma, 3, ','), + new Token(TokenType.Number, 4, '5'), + new Token(TokenType.BraceClose, 5, '}') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseGroup(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Group); + expect(exp.getChildCount()).toBe(2); - expect(exp.getChild(0).type).toBe(NodeType.Number); - expect(exp.getChild(0).getAttribute('value')).toBe(10); + expect(exp.getChild(0).type).toBe(NodeType.Number); + expect(exp.getChild(0).getAttribute('value')).toBe(10); - expect(exp.getChild(1).type).toBe(NodeType.Number); - expect(exp.getChild(1).getAttribute('value')).toBe(5); - }); - it('can correctly parse a group with a keep modifier', () => { - const lexer = new MockLexer([ - new Token(TokenType.BraceOpen, 0, '{'), - new Token(TokenType.Number, 1, '10'), - new Token(TokenType.Comma, 3, ','), - new Token(TokenType.Number, 4, '5'), - new Token(TokenType.BraceClose, 5, '}'), - new Token(TokenType.Identifier, 6, 'kh') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseGroup(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Keep); - expect(exp.getChildCount()).toBe(1); + expect(exp.getChild(1).type).toBe(NodeType.Number); + expect(exp.getChild(1).getAttribute('value')).toBe(5); + }); + it('can correctly parse a group with a keep modifier', () => { + const lexer = new MockLexer([ + new Token(TokenType.BraceOpen, 0, '{'), + new Token(TokenType.Number, 1, '10'), + new Token(TokenType.Comma, 3, ','), + new Token(TokenType.Number, 4, '5'), + new Token(TokenType.BraceClose, 5, '}'), + new Token(TokenType.Identifier, 6, 'kh') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseGroup(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Keep); + expect(exp.getChildCount()).toBe(2); - expect(exp.getChild(0).type).toBe(NodeType.Group); - }); - it('can correctly parse a group with a drop modifier', () => { - const lexer = new MockLexer([ - new Token(TokenType.BraceOpen, 0, '{'), - new Token(TokenType.Number, 1, '10'), - new Token(TokenType.Comma, 3, ','), - new Token(TokenType.Number, 4, '5'), - new Token(TokenType.BraceClose, 5, '}'), - new Token(TokenType.Identifier, 6, 'dl') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseGroup(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Drop); - expect(exp.getChildCount()).toBe(1); + expect(exp.getChild(0).type).toBe(NodeType.Group); + expect(exp.getChild(1).type).toBe(NodeType.Number); + }); + it('can correctly parse a group with a drop modifier', () => { + const lexer = new MockLexer([ + new Token(TokenType.BraceOpen, 0, '{'), + new Token(TokenType.Number, 1, '10'), + new Token(TokenType.Comma, 3, ','), + new Token(TokenType.Number, 4, '5'), + new Token(TokenType.BraceClose, 5, '}'), + new Token(TokenType.Identifier, 6, 'dl') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseGroup(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Drop); + expect(exp.getChildCount()).toBe(1); - expect(exp.getChild(0).type).toBe(NodeType.Group); - }); - it('can correctly parse a group with a sort modifier', () => { - const lexer = new MockLexer([ - new Token(TokenType.BraceOpen, 0, '{'), - new Token(TokenType.Number, 1, '10'), - new Token(TokenType.Comma, 3, ','), - new Token(TokenType.Number, 4, '5'), - new Token(TokenType.BraceClose, 5, '}'), - new Token(TokenType.Identifier, 6, 'sa') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseGroup(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Sort); - expect(exp.getChildCount()).toBe(1); + expect(exp.getChild(0).type).toBe(NodeType.Group); + }); + it('can correctly parse a group with a sort modifier', () => { + const lexer = new MockLexer([ + new Token(TokenType.BraceOpen, 0, '{'), + new Token(TokenType.Number, 1, '10'), + new Token(TokenType.Comma, 3, ','), + new Token(TokenType.Number, 4, '5'), + new Token(TokenType.BraceClose, 5, '}'), + new Token(TokenType.Identifier, 6, 'sa') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseGroup(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Sort); + expect(exp.getChildCount()).toBe(1); - expect(exp.getChild(0).type).toBe(NodeType.Group); - }); - it('can correctly parse a group with a boolean condition {10, 5}>5', () => { - const lexer = new MockLexer([ - new Token(TokenType.BraceOpen, 0, '{'), - new Token(TokenType.Number, 1, '10'), - new Token(TokenType.Comma, 3, ','), - new Token(TokenType.Number, 4, '5'), - new Token(TokenType.BraceClose, 5, '}'), - new Token(TokenType.Greater, 6, '>'), - new Token(TokenType.Number, 7, '5') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseGroup(result); + expect(exp.getChild(0).type).toBe(NodeType.Group); + }); + it('can correctly parse a group with a boolean condition {10, 5}>5', () => { + const lexer = new MockLexer([ + new Token(TokenType.BraceOpen, 0, '{'), + new Token(TokenType.Number, 1, '10'), + new Token(TokenType.Comma, 3, ','), + new Token(TokenType.Number, 4, '5'), + new Token(TokenType.BraceClose, 5, '}'), + new Token(TokenType.Greater, 6, '>'), + new Token(TokenType.Number, 7, '5') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseGroup(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Greater); - expect(exp.getChildCount()).toBe(2); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Greater); + expect(exp.getChildCount()).toBe(2); - expect(exp.getChild(0).type).toBe(NodeType.Group); - expect(exp.getChild(1).type).toBe(NodeType.Number); - }); - it('can correctly parse a group with a repeating argument', () => { - const lexer = new MockLexer([ - new Token(TokenType.BraceOpen, 0, '{'), - new Token(TokenType.Number, 1, '2'), - new Token(TokenType.Identifier, 2, 'd'), - new Token(TokenType.Number, 3, '20'), - new Token(TokenType.Ellipsis, 5, '...'), - new Token(TokenType.Number, 8, '10'), - new Token(TokenType.BraceClose, 10, '}') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseGroup(result); - expect(result.errors.length).toBe(0, `Unexpected errors occurred: ${result.errors.map(e => e.message).join(', ')}.`); - expect(exp.type).toBe(NodeType.Group, 'A group node was not returned.'); - expect(exp.getChildCount()).toBe(1, 'Group does not have a single child.'); + expect(exp.getChild(0).type).toBe(NodeType.Group); + expect(exp.getChild(1).type).toBe(NodeType.Number); + }); + it('can correctly parse a group with a repeating argument', () => { + const lexer = new MockLexer([ + new Token(TokenType.BraceOpen, 0, '{'), + new Token(TokenType.Number, 1, '2'), + new Token(TokenType.Identifier, 2, 'd'), + new Token(TokenType.Number, 3, '20'), + new Token(TokenType.Ellipsis, 5, '...'), + new Token(TokenType.Number, 8, '10'), + new Token(TokenType.BraceClose, 10, '}') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseGroup(result); + expect(result.errors.length).toBe(0, `Unexpected errors occurred: ${result.errors.map(e => e.message).join(', ')}.`); + expect(exp.type).toBe(NodeType.Group, 'A group node was not returned.'); + expect(exp.getChildCount()).toBe(1, 'Group does not have a single child.'); - expect(exp.getChild(0).type).toBe(NodeType.Repeat, 'Group node child is not of type repeat.'); - expect(exp.getChild(0).getChildCount()).toBe(2, 'Repeat node does not have two operands.'); - expect(exp.getChild(0).getChild(0).type).toBe(NodeType.Dice, 'Repeat node LHS is not of type Dice.'); - expect(exp.getChild(0).getChild(1).type).toBe(NodeType.Number, 'Repeat node RHS is not of type Number'); - expect(exp.getChild(0).getChild(1).getAttribute('value')).toBe(10); - }); - it('throws on group with bad modifier', () => { - const lexer = new MockLexer([ - new Token(TokenType.BraceOpen, 0, '{'), - new Token(TokenType.Number, 1, '10'), - new Token(TokenType.Comma, 3, ','), - new Token(TokenType.Number, 4, '5'), - new Token(TokenType.BraceClose, 5, '}'), - new Token(TokenType.Identifier, 6, 'ro') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseGroup(result); - expect(result.errors.length).toBe(1); - }); + expect(exp.getChild(0).type).toBe(NodeType.Repeat, 'Group node child is not of type repeat.'); + expect(exp.getChild(0).getChildCount()).toBe(2, 'Repeat node does not have two operands.'); + expect(exp.getChild(0).getChild(0).type).toBe(NodeType.Dice, 'Repeat node LHS is not of type Dice.'); + expect(exp.getChild(0).getChild(1).type).toBe(NodeType.Number, 'Repeat node RHS is not of type Number'); + expect(exp.getChild(0).getChild(1).getAttribute('value')).toBe(10); + }); + it('throws on group with bad modifier', () => { + const lexer = new MockLexer([ + new Token(TokenType.BraceOpen, 0, '{'), + new Token(TokenType.Number, 1, '10'), + new Token(TokenType.Comma, 3, ','), + new Token(TokenType.Number, 4, '5'), + new Token(TokenType.BraceClose, 5, '}'), + new Token(TokenType.Identifier, 6, 'ro') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseGroup(result); + expect(result.errors.length).toBe(1); }); + }); }); diff --git a/spec/parser/dice-parser.parseKeep.spec.ts b/spec/parser/dice-parser.parseKeep.spec.ts index d7b57b0..2d40354 100644 --- a/spec/parser/dice-parser.parseKeep.spec.ts +++ b/spec/parser/dice-parser.parseKeep.spec.ts @@ -5,83 +5,83 @@ import { ParseResult } from '../../src/parser/parse-result.class'; import { MockLexer } from '../helpers'; describe('DiceParser', () => { - describe('parseKeep', () => { - it('can correctly parse a keep modifier (k).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'k') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const mod = parser.parseKeep(result); - expect(result.errors.length).toBe(0); - expect(mod.type).toBe(NodeType.Keep); - expect(mod.getAttribute('type')).toBe('highest'); - }); - it('can correctly parse a keep modifier (kh).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'kh') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const mod = parser.parseKeep(result); - expect(result.errors.length).toBe(0); - expect(mod.type).toBe(NodeType.Keep); - expect(mod.getAttribute('type')).toBe('highest'); - }); - it('can correctly parse a keep modifier (kl).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'kl') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const mod = parser.parseKeep(result); - expect(result.errors.length).toBe(0); - expect(mod.type).toBe(NodeType.Keep); - expect(mod.getAttribute('type')).toBe('lowest'); - }); - it('can correctly parse a keep modifier with simple number (kl3).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'kl'), - new Token(TokenType.Number, 2, '3') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const mod = parser.parseKeep(result); - expect(result.errors.length).toBe(0); - expect(mod.type).toBe(NodeType.Keep); - expect(mod.getAttribute('type')).toBe('lowest'); - expect(mod.getChildCount()).toBe(1); - expect(mod.getChild(0).type).toBe(NodeType.Number); - expect(mod.getChild(0).getAttribute('value')).toBe(3); - }); - it('can correctly parse a keep modifier with simple number (kl(5+3)).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'kl'), - new Token(TokenType.ParenthesisOpen, 2, '('), - new Token(TokenType.Number, 3, '5'), - new Token(TokenType.Plus, 4, '+'), - new Token(TokenType.Number, 5, '3'), - new Token(TokenType.ParenthesisClose, 6, ')') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const mod = parser.parseKeep(result); - expect(result.errors.length).toBe(0); - expect(mod.type).toBe(NodeType.Keep); - expect(mod.getAttribute('type')).toBe('lowest'); - expect(mod.getChildCount()).toBe(1); - expect(mod.getChild(0).type).toBe(NodeType.Add); - }); - it('throws an error on unknown keep type (kg3).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'kg'), - new Token(TokenType.Number, 2, '3') - ]); - const parser = new Parser.DiceParser(lexer); + describe('parseKeep', () => { + it('can correctly parse a keep modifier (k).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'k') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const mod = parser.parseKeep(result); + expect(result.errors.length).toBe(0); + expect(mod.type).toBe(NodeType.Keep); + expect(mod.getAttribute('type')).toBe('highest'); + }); + it('can correctly parse a keep modifier (kh).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'kh') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const mod = parser.parseKeep(result); + expect(result.errors.length).toBe(0); + expect(mod.type).toBe(NodeType.Keep); + expect(mod.getAttribute('type')).toBe('highest'); + }); + it('can correctly parse a keep modifier (kl).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'kl') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const mod = parser.parseKeep(result); + expect(result.errors.length).toBe(0); + expect(mod.type).toBe(NodeType.Keep); + expect(mod.getAttribute('type')).toBe('lowest'); + }); + it('can correctly parse a keep modifier with simple number (kl3).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'kl'), + new Token(TokenType.Number, 2, '3') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const mod = parser.parseKeep(result); + expect(result.errors.length).toBe(0); + expect(mod.type).toBe(NodeType.Keep); + expect(mod.getAttribute('type')).toBe('lowest'); + expect(mod.getChildCount()).toBe(1); + expect(mod.getChild(0).type).toBe(NodeType.Number); + expect(mod.getChild(0).getAttribute('value')).toBe(3); + }); + it('can correctly parse a keep modifier with simple number (kl(5+3)).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'kl'), + new Token(TokenType.ParenthesisOpen, 2, '('), + new Token(TokenType.Number, 3, '5'), + new Token(TokenType.Plus, 4, '+'), + new Token(TokenType.Number, 5, '3'), + new Token(TokenType.ParenthesisClose, 6, ')') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const mod = parser.parseKeep(result); + expect(result.errors.length).toBe(0); + expect(mod.type).toBe(NodeType.Keep); + expect(mod.getAttribute('type')).toBe('lowest'); + expect(mod.getChildCount()).toBe(1); + expect(mod.getChild(0).type).toBe(NodeType.Add); + }); + it('throws an error on unknown keep type (kg3).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'kg'), + new Token(TokenType.Number, 2, '3') + ]); + const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const mod = parser.parseKeep(result); - expect(result.errors.length).toBeGreaterThanOrEqual(1); - }); + const result = new ParseResult(); + const mod = parser.parseKeep(result); + expect(result.errors.length).toBeGreaterThanOrEqual(1); }); + }); }); diff --git a/spec/parser/dice-parser.parseNumber.spec.ts b/spec/parser/dice-parser.parseNumber.spec.ts index 36fdbce..ae18f75 100644 --- a/spec/parser/dice-parser.parseNumber.spec.ts +++ b/spec/parser/dice-parser.parseNumber.spec.ts @@ -5,35 +5,35 @@ import { ParseResult } from '../../src/parser/parse-result.class'; import { MockLexer } from '../helpers'; describe('DiceParser', () => { - describe('constructor', () => { - it('does not throw.', () => { - expect(() => { - const parser = new Parser.DiceParser(''); - }).not.toThrow(); - }); + describe('constructor', () => { + it('does not throw.', () => { + expect(() => { + const parser = new Parser.DiceParser(''); + }).not.toThrow(); }); - describe('parseNumber', () => { - it('can correctly parse an integer', () => { - const lexer = new MockLexer([ - new Token(TokenType.Number, 0, '12') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const node = parser.parseNumber(result); - expect(result.errors.length).toBe(0); - expect(node.type).toBe(NodeType.Number); - expect(node.getAttribute('value')).toBe(12); - }); - it('can correctly parse a real number', () => { - const lexer = new MockLexer([ - new Token(TokenType.Number, 0, '12.56') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const node = parser.parseNumber(result); - expect(result.errors.length).toBe(0); - expect(node.type).toBe(NodeType.Number); - expect(node.getAttribute('value')).toBe(12.56); - }); + }); + describe('parseNumber', () => { + it('can correctly parse an integer', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '12') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const node = parser.parseNumber(result); + expect(result.errors.length).toBe(0); + expect(node.type).toBe(NodeType.Number); + expect(node.getAttribute('value')).toBe(12); }); + it('can correctly parse a real number', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '12.56') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const node = parser.parseNumber(result); + expect(result.errors.length).toBe(0); + expect(node.type).toBe(NodeType.Number); + expect(node.getAttribute('value')).toBe(12.56); + }); + }); }); diff --git a/spec/parser/dice-parser.parseReroll.spec.ts b/spec/parser/dice-parser.parseReroll.spec.ts index 4c4c910..cbf0836 100644 --- a/spec/parser/dice-parser.parseReroll.spec.ts +++ b/spec/parser/dice-parser.parseReroll.spec.ts @@ -5,55 +5,55 @@ import { ParseResult } from '../../src/parser/parse-result.class'; import { MockLexer } from '../helpers'; describe('DiceParser', () => { - describe('parseReroll', () => { - it('can correctly parse a reroll modifier (r).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'r') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const mod = parser.parseReroll(result); - expect(result.errors.length).toBe(0); - expect(mod.type).toBe(NodeType.Reroll); - expect(mod.getAttribute('once')).toBe(false); - }); - it('can correctly parse a drop modifier (ro).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'ro') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const mod = parser.parseReroll(result); - expect(result.errors.length).toBe(0); - expect(mod.type).toBe(NodeType.Reroll); - expect(mod.getAttribute('once')).toBe(true); - }); - it('can correctly parse a drop modifier (ro<3).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'ro'), - new Token(TokenType.Less, 2, '<'), - new Token(TokenType.Number, 3, '3') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const mod = parser.parseReroll(result); - expect(result.errors.length).toBe(0); - expect(mod.type).toBe(NodeType.Reroll); - expect(mod.getAttribute('once')).toBe(true); - expect(mod.getChildCount()).toBe(1); - expect(mod.getChild(0).type).toBe(NodeType.Less); - }); - it('throws an error on unknown drop type (dx<3).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'dx'), - new Token(TokenType.Less, 2, '<'), - new Token(TokenType.Number, 3, '3') - ]); - const parser = new Parser.DiceParser(lexer); + describe('parseReroll', () => { + it('can correctly parse a reroll modifier (r).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'r') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const mod = parser.parseReroll(result); + expect(result.errors.length).toBe(0); + expect(mod.type).toBe(NodeType.Reroll); + expect(mod.getAttribute('once')).toBe(false); + }); + it('can correctly parse a drop modifier (ro).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'ro') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const mod = parser.parseReroll(result); + expect(result.errors.length).toBe(0); + expect(mod.type).toBe(NodeType.Reroll); + expect(mod.getAttribute('once')).toBe(true); + }); + it('can correctly parse a drop modifier (ro<3).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'ro'), + new Token(TokenType.Less, 2, '<'), + new Token(TokenType.Number, 3, '3') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const mod = parser.parseReroll(result); + expect(result.errors.length).toBe(0); + expect(mod.type).toBe(NodeType.Reroll); + expect(mod.getAttribute('once')).toBe(true); + expect(mod.getChildCount()).toBe(1); + expect(mod.getChild(0).type).toBe(NodeType.Less); + }); + it('throws an error on unknown drop type (dx<3).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'dx'), + new Token(TokenType.Less, 2, '<'), + new Token(TokenType.Number, 3, '3') + ]); + const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const mod = parser.parseReroll(result); - expect(result.errors.length).toBeGreaterThanOrEqual(1); - }); + const result = new ParseResult(); + const mod = parser.parseReroll(result); + expect(result.errors.length).toBeGreaterThanOrEqual(1); }); + }); }); diff --git a/spec/parser/dice-parser.parseSimpleExpression.spec.ts b/spec/parser/dice-parser.parseSimpleExpression.spec.ts index fa631f0..c838610 100644 --- a/spec/parser/dice-parser.parseSimpleExpression.spec.ts +++ b/spec/parser/dice-parser.parseSimpleExpression.spec.ts @@ -5,81 +5,81 @@ import { ParseResult } from '../../src/parser/parse-result.class'; import { MockLexer } from '../helpers'; describe('DiceParser', () => { - describe('parseSimpleExpression', () => { - it('can correctly parse a simple addition', () => { - const lexer = new MockLexer([ - new Token(TokenType.Number, 0, '10'), - new Token(TokenType.Plus, 2, '+'), - new Token(TokenType.Number, 3, '6') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseSimpleExpression(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Add); - expect(exp.getChildCount()).toBe(2); - expect(exp.getChild(0).type).toBe(NodeType.Number); - expect(exp.getChild(0).getAttribute('value')).toBe(10); - expect(exp.getChild(1).type).toBe(NodeType.Number); - expect(exp.getChild(1).getAttribute('value')).toBe(6); - }); - it('can correctly parse a simple subtraction', () => { - const lexer = new MockLexer([ - new Token(TokenType.Number, 0, '10'), - new Token(TokenType.Minus, 2, '-'), - new Token(TokenType.Number, 3, '6') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseSimpleExpression(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Subtract); - expect(exp.getChildCount()).toBe(2); - expect(exp.getChild(0).type).toBe(NodeType.Number); - expect(exp.getChild(0).getAttribute('value')).toBe(10); - expect(exp.getChild(1).type).toBe(NodeType.Number); - expect(exp.getChild(1).getAttribute('value')).toBe(6); - }); - it('can correctly parse a simple negation', () => { - const lexer = new MockLexer([ - new Token(TokenType.Minus, 0, '-'), - new Token(TokenType.Number, 1, '4') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseSimpleExpression(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Negate); - expect(exp.getChildCount()).toBe(1); - expect(exp.getChild(0).type).toBe(NodeType.Number); - expect(exp.getChild(0).getAttribute('value')).toBe(4); - }); - it('can correctly parse multiple operators', () => { - const lexer = new MockLexer([ - new Token(TokenType.Number, 0, '4'), - new Token(TokenType.Plus, 1, '+'), - new Token(TokenType.Number, 2, '3'), - new Token(TokenType.Minus, 3, '-'), - new Token(TokenType.Number, 4, '1') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseSimpleExpression(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Subtract); - expect(exp.getChildCount()).toBe(2); + describe('parseSimpleExpression', () => { + it('can correctly parse a simple addition', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '10'), + new Token(TokenType.Plus, 2, '+'), + new Token(TokenType.Number, 3, '6') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseSimpleExpression(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Add); + expect(exp.getChildCount()).toBe(2); + expect(exp.getChild(0).type).toBe(NodeType.Number); + expect(exp.getChild(0).getAttribute('value')).toBe(10); + expect(exp.getChild(1).type).toBe(NodeType.Number); + expect(exp.getChild(1).getAttribute('value')).toBe(6); + }); + it('can correctly parse a simple subtraction', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '10'), + new Token(TokenType.Minus, 2, '-'), + new Token(TokenType.Number, 3, '6') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseSimpleExpression(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Subtract); + expect(exp.getChildCount()).toBe(2); + expect(exp.getChild(0).type).toBe(NodeType.Number); + expect(exp.getChild(0).getAttribute('value')).toBe(10); + expect(exp.getChild(1).type).toBe(NodeType.Number); + expect(exp.getChild(1).getAttribute('value')).toBe(6); + }); + it('can correctly parse a simple negation', () => { + const lexer = new MockLexer([ + new Token(TokenType.Minus, 0, '-'), + new Token(TokenType.Number, 1, '4') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseSimpleExpression(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Negate); + expect(exp.getChildCount()).toBe(1); + expect(exp.getChild(0).type).toBe(NodeType.Number); + expect(exp.getChild(0).getAttribute('value')).toBe(4); + }); + it('can correctly parse multiple operators', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '4'), + new Token(TokenType.Plus, 1, '+'), + new Token(TokenType.Number, 2, '3'), + new Token(TokenType.Minus, 3, '-'), + new Token(TokenType.Number, 4, '1') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseSimpleExpression(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Subtract); + expect(exp.getChildCount()).toBe(2); - const lhs = exp.getChild(0); - expect(lhs.type).toBe(NodeType.Add); - expect(lhs.getChildCount()).toBe(2); - expect(lhs.getChild(0).type).toBe(NodeType.Number); - expect(lhs.getChild(0).getAttribute('value')).toBe(4); - expect(lhs.getChild(1).type).toBe(NodeType.Number); - expect(lhs.getChild(1).getAttribute('value')).toBe(3); + const lhs = exp.getChild(0); + expect(lhs.type).toBe(NodeType.Add); + expect(lhs.getChildCount()).toBe(2); + expect(lhs.getChild(0).type).toBe(NodeType.Number); + expect(lhs.getChild(0).getAttribute('value')).toBe(4); + expect(lhs.getChild(1).type).toBe(NodeType.Number); + expect(lhs.getChild(1).getAttribute('value')).toBe(3); - const rhs = exp.getChild(1); - expect(rhs.type).toBe(NodeType.Number); - expect(rhs.getAttribute('value')).toBe(1); - }); + const rhs = exp.getChild(1); + expect(rhs.type).toBe(NodeType.Number); + expect(rhs.getAttribute('value')).toBe(1); }); + }); }); diff --git a/spec/parser/dice-parser.parseSort.spec.ts b/spec/parser/dice-parser.parseSort.spec.ts index 8073045..25cef26 100644 --- a/spec/parser/dice-parser.parseSort.spec.ts +++ b/spec/parser/dice-parser.parseSort.spec.ts @@ -5,49 +5,49 @@ import { ParseResult } from '../../src/parser/parse-result.class'; import { MockLexer } from '../helpers'; describe('DiceParser', () => { - describe('parseSort', () => { - it('can correctly parse a sort modifier (s).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 's') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const mod = parser.parseSort(result); - expect(result.errors.length).toBe(0); - expect(mod.type).toBe(NodeType.Sort); - expect(mod.getAttribute('direction')).toBe('ascending'); - }); - it('can correctly parse a sort modifier (sa).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'sa') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const mod = parser.parseSort(result); - expect(result.errors.length).toBe(0); - expect(mod.type).toBe(NodeType.Sort); - expect(mod.getAttribute('direction')).toBe('ascending'); - }); - it('can correctly parse a sort modifier (sd).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'sd') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const mod = parser.parseSort(result); - expect(result.errors.length).toBe(0); - expect(mod.type).toBe(NodeType.Sort); - expect(mod.getAttribute('direction')).toBe('descending'); - }); - it('throws an error on unknown sort type (sx).', () => { - const lexer = new MockLexer([ - new Token(TokenType.Identifier, 0, 'sx') - ]); - const parser = new Parser.DiceParser(lexer); + describe('parseSort', () => { + it('can correctly parse a sort modifier (s).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 's') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const mod = parser.parseSort(result); + expect(result.errors.length).toBe(0); + expect(mod.type).toBe(NodeType.Sort); + expect(mod.getAttribute('direction')).toBe('ascending'); + }); + it('can correctly parse a sort modifier (sa).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'sa') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const mod = parser.parseSort(result); + expect(result.errors.length).toBe(0); + expect(mod.type).toBe(NodeType.Sort); + expect(mod.getAttribute('direction')).toBe('ascending'); + }); + it('can correctly parse a sort modifier (sd).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'sd') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const mod = parser.parseSort(result); + expect(result.errors.length).toBe(0); + expect(mod.type).toBe(NodeType.Sort); + expect(mod.getAttribute('direction')).toBe('descending'); + }); + it('throws an error on unknown sort type (sx).', () => { + const lexer = new MockLexer([ + new Token(TokenType.Identifier, 0, 'sx') + ]); + const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const mod = parser.parseSort(result); - expect(result.errors.length).toBeGreaterThanOrEqual(1); - }); + const result = new ParseResult(); + const mod = parser.parseSort(result); + expect(result.errors.length).toBeGreaterThanOrEqual(1); }); + }); }); diff --git a/spec/parser/dice-parser.parseTerm.spec.ts b/spec/parser/dice-parser.parseTerm.spec.ts index ec08bf1..d54bb5d 100644 --- a/spec/parser/dice-parser.parseTerm.spec.ts +++ b/spec/parser/dice-parser.parseTerm.spec.ts @@ -5,101 +5,101 @@ import { ParseResult } from '../../src/parser/parse-result.class'; import { MockLexer } from '../helpers'; describe('DiceParser', () => { - describe('parseTerm', () => { - it('can correctly identify a multiplication', () => { - const lexer = new MockLexer([ - new Token(TokenType.Number, 0, '6'), - new Token(TokenType.Asterisk, 1, '*'), - new Token(TokenType.Number, 2, '4'), - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseTerm(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Multiply); - expect(exp.getChildCount()).toBe(2); - expect(exp.getChild(0).type).toBe(NodeType.Number); - expect(exp.getChild(0).getAttribute('value')).toBe(6); - expect(exp.getChild(1).type).toBe(NodeType.Number); - expect(exp.getChild(1).getAttribute('value')).toBe(4); - }); - it('can correctly identify a division', () => { - const lexer = new MockLexer([ - new Token(TokenType.Number, 0, '6'), - new Token(TokenType.Slash, 1, '/'), - new Token(TokenType.Number, 2, '4'), - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseTerm(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Divide); - expect(exp.getChildCount()).toBe(2); - expect(exp.getChild(0).type).toBe(NodeType.Number); - expect(exp.getChild(0).getAttribute('value')).toBe(6); - expect(exp.getChild(1).type).toBe(NodeType.Number); - expect(exp.getChild(1).getAttribute('value')).toBe(4); - }); - it('can correctly identify an exponent', () => { - const lexer = new MockLexer([ - new Token(TokenType.Number, 0, '6'), - new Token(TokenType.DoubleAsterisk, 1, '**'), - new Token(TokenType.Number, 3, '4'), - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseTerm(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Exponent); - expect(exp.getChildCount()).toBe(2); - expect(exp.getChild(0).type).toBe(NodeType.Number); - expect(exp.getChild(0).getAttribute('value')).toBe(6); - expect(exp.getChild(1).type).toBe(NodeType.Number); - expect(exp.getChild(1).getAttribute('value')).toBe(4); - }); - it('can correctly identify a modulo', () => { - const lexer = new MockLexer([ - new Token(TokenType.Number, 0, '6'), - new Token(TokenType.Percent, 1, '%'), - new Token(TokenType.Number, 2, '4'), - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseTerm(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Modulo); - expect(exp.getChildCount()).toBe(2); - expect(exp.getChild(0).type).toBe(NodeType.Number); - expect(exp.getChild(0).getAttribute('value')).toBe(6); - expect(exp.getChild(1).type).toBe(NodeType.Number); - expect(exp.getChild(1).getAttribute('value')).toBe(4); - }); - it('can correctly parse multiple operators', () => { - const lexer = new MockLexer([ - new Token(TokenType.Number, 0, '4'), - new Token(TokenType.Asterisk, 1, '*'), - new Token(TokenType.Number, 2, '3'), - new Token(TokenType.Slash, 3, '/'), - new Token(TokenType.Number, 4, '1') - ]); - const parser = new Parser.DiceParser(lexer); - const result = new ParseResult(); - const exp = parser.parseTerm(result); - expect(result.errors.length).toBe(0); - expect(exp.type).toBe(NodeType.Divide); - expect(exp.getChildCount()).toBe(2); + describe('parseTerm', () => { + it('can correctly identify a multiplication', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '6'), + new Token(TokenType.Asterisk, 1, '*'), + new Token(TokenType.Number, 2, '4'), + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseTerm(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Multiply); + expect(exp.getChildCount()).toBe(2); + expect(exp.getChild(0).type).toBe(NodeType.Number); + expect(exp.getChild(0).getAttribute('value')).toBe(6); + expect(exp.getChild(1).type).toBe(NodeType.Number); + expect(exp.getChild(1).getAttribute('value')).toBe(4); + }); + it('can correctly identify a division', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '6'), + new Token(TokenType.Slash, 1, '/'), + new Token(TokenType.Number, 2, '4'), + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseTerm(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Divide); + expect(exp.getChildCount()).toBe(2); + expect(exp.getChild(0).type).toBe(NodeType.Number); + expect(exp.getChild(0).getAttribute('value')).toBe(6); + expect(exp.getChild(1).type).toBe(NodeType.Number); + expect(exp.getChild(1).getAttribute('value')).toBe(4); + }); + it('can correctly identify an exponent', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '6'), + new Token(TokenType.DoubleAsterisk, 1, '**'), + new Token(TokenType.Number, 3, '4'), + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseTerm(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Exponent); + expect(exp.getChildCount()).toBe(2); + expect(exp.getChild(0).type).toBe(NodeType.Number); + expect(exp.getChild(0).getAttribute('value')).toBe(6); + expect(exp.getChild(1).type).toBe(NodeType.Number); + expect(exp.getChild(1).getAttribute('value')).toBe(4); + }); + it('can correctly identify a modulo', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '6'), + new Token(TokenType.Percent, 1, '%'), + new Token(TokenType.Number, 2, '4'), + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseTerm(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Modulo); + expect(exp.getChildCount()).toBe(2); + expect(exp.getChild(0).type).toBe(NodeType.Number); + expect(exp.getChild(0).getAttribute('value')).toBe(6); + expect(exp.getChild(1).type).toBe(NodeType.Number); + expect(exp.getChild(1).getAttribute('value')).toBe(4); + }); + it('can correctly parse multiple operators', () => { + const lexer = new MockLexer([ + new Token(TokenType.Number, 0, '4'), + new Token(TokenType.Asterisk, 1, '*'), + new Token(TokenType.Number, 2, '3'), + new Token(TokenType.Slash, 3, '/'), + new Token(TokenType.Number, 4, '1') + ]); + const parser = new Parser.DiceParser(lexer); + const result = new ParseResult(); + const exp = parser.parseTerm(result); + expect(result.errors.length).toBe(0); + expect(exp.type).toBe(NodeType.Divide); + expect(exp.getChildCount()).toBe(2); - const lhs = exp.getChild(0); - expect(lhs.type).toBe(NodeType.Multiply); - expect(lhs.getChildCount()).toBe(2); - expect(lhs.getChild(0).type).toBe(NodeType.Number); - expect(lhs.getChild(0).getAttribute('value')).toBe(4); - expect(lhs.getChild(1).type).toBe(NodeType.Number); - expect(lhs.getChild(1).getAttribute('value')).toBe(3); + const lhs = exp.getChild(0); + expect(lhs.type).toBe(NodeType.Multiply); + expect(lhs.getChildCount()).toBe(2); + expect(lhs.getChild(0).type).toBe(NodeType.Number); + expect(lhs.getChild(0).getAttribute('value')).toBe(4); + expect(lhs.getChild(1).type).toBe(NodeType.Number); + expect(lhs.getChild(1).getAttribute('value')).toBe(3); - const rhs = exp.getChild(1); - expect(rhs.type).toBe(NodeType.Number); - expect(rhs.getAttribute('value')).toBe(1); - }); + const rhs = exp.getChild(1); + expect(rhs.type).toBe(NodeType.Number); + expect(rhs.getAttribute('value')).toBe(1); }); + }); }); diff --git a/src/ast/expression-node.class.ts b/src/ast/expression-node.class.ts index 65e61ae..f133238 100644 --- a/src/ast/expression-node.class.ts +++ b/src/ast/expression-node.class.ts @@ -3,89 +3,89 @@ import { NodeAttributes } from './node-attributes.class'; import { NodeType } from './node-type.enum'; export class ExpressionNode { - readonly type: NodeType; - private attributes: NodeAttributes; - private parent: ExpressionNode; - private children: ExpressionNode[]; + readonly type: NodeType; + private attributes: NodeAttributes; + private parent: ExpressionNode; + private children: ExpressionNode[]; - constructor(type: NodeType, parent: ExpressionNode = null) { - this.type = type; - this.parent = parent; - } + constructor(type: NodeType, parent: ExpressionNode = null) { + this.type = type; + this.parent = parent; + } - copy(): ExpressionNode { - const copy = Factory.create(this.type); - if (this.attributes) { - Object.keys(this.attributes).forEach(attr => { - copy.setAttribute(attr, this.attributes[attr]); - }); - } - if (this.children) { - this.children.forEach(child => { - copy.addChild(child.copy()); - }); - } - return copy; + copy(): ExpressionNode { + const copy = Factory.create(this.type); + if (this.attributes) { + Object.keys(this.attributes).forEach(attr => { + copy.setAttribute(attr, this.attributes[attr]); + }); } - - addChild(node: ExpressionNode): ExpressionNode { - return this.insertChild(node); + if (this.children) { + this.children.forEach(child => { + copy.addChild(child.copy()); + }); } + return copy; + } - insertChild(node: ExpressionNode, index?: number): ExpressionNode { - if (node) { - if (node === this) { throw new Error('Cannot add a node as a child of itself.'); } - if (!this.children) { this.children = []; } - this.children.splice(index || this.children.length, 0, node); - node.parent = this; - } - return node; - } + addChild(node: ExpressionNode): ExpressionNode { + return this.insertChild(node); + } - clearChildren(): void { - this.children = undefined; + insertChild(node: ExpressionNode, index?: number): ExpressionNode { + if (node) { + if (node === this) { throw new Error('Cannot add a node as a child of itself.'); } + if (!this.children) { this.children = []; } + this.children.splice(index || this.children.length, 0, node); + node.parent = this; } + return node; + } - removeChild(expression: ExpressionNode): ExpressionNode { - if (expression.parent !== this) { return null; } - this.children.splice(this.children.indexOf(expression, 1)); - return expression; - } + clearChildren(): void { + this.children = undefined; + } - getParent(): ExpressionNode { - return this.parent; - } + removeChild(expression: ExpressionNode): ExpressionNode { + if (expression.parent !== this) { return null; } + this.children.splice(this.children.indexOf(expression, 1)); + return expression; + } - getChild(index: number): ExpressionNode { - if (!this.children || this.children.length <= index) { - throw new Error(`Child node at index ${index} does not exist.`); - } - return this.children[index]; - } + getParent(): ExpressionNode { + return this.parent; + } - getChildCount(): number { - return this.children ? this.children.length : 0; + getChild(index: number): ExpressionNode { + if (!this.children || this.children.length <= index) { + throw new Error(`Child node at index ${index} does not exist.`); } + return this.children[index]; + } - forEachChild(fn: (child: ExpressionNode, index?: number) => boolean | void) { - const children = [...this.children || []]; - children.forEach(fn); - } + getChildCount(): number { + return this.children ? this.children.length : 0; + } - getAttribute(key: string) { - return this.attributes ? this.attributes[key] : undefined; - } + forEachChild(fn: (child: ExpressionNode, index?: number) => boolean | void) { + const children = [...this.children || []]; + children.forEach(fn); + } - setAttribute(key: string, value: any) { - if (!this.attributes) { this.attributes = new NodeAttributes(); } - this.attributes[key] = value; - return this; - } + getAttribute(key: string) { + return this.attributes ? this.attributes[key] : undefined; + } - toJSON(): any { - const keys = Object.keys(this).filter(k => k !== 'parent'); - const obj = {}; - keys.forEach(k => obj[k] = this[k]); - return obj; - } + setAttribute(key: string, value: any) { + if (!this.attributes) { this.attributes = new NodeAttributes(); } + this.attributes[key] = value; + return this; + } + + toJSON(): any { + const keys = Object.keys(this).filter(k => k !== 'parent'); + const obj = {}; + keys.forEach(k => obj[k] = this[k]); + return obj; + } } diff --git a/src/ast/factory.class.ts b/src/ast/factory.class.ts index 1b57bdb..8d22c01 100644 --- a/src/ast/factory.class.ts +++ b/src/ast/factory.class.ts @@ -2,7 +2,7 @@ import { ExpressionNode } from './expression-node.class'; import { NodeType } from './node-type.enum'; export class Factory { - static create(type: NodeType): ExpressionNode { - return new ExpressionNode(type); - } + static create(type: NodeType): ExpressionNode { + return new ExpressionNode(type); + } } diff --git a/src/ast/node-attributes.class.ts b/src/ast/node-attributes.class.ts index 1d51241..e2965c4 100644 --- a/src/ast/node-attributes.class.ts +++ b/src/ast/node-attributes.class.ts @@ -1,4 +1,4 @@ export class NodeAttributes { - [key: string]: any + [key: string]: any } diff --git a/src/ast/node-type.enum.ts b/src/ast/node-type.enum.ts index bd76033..c405ddc 100644 --- a/src/ast/node-type.enum.ts +++ b/src/ast/node-type.enum.ts @@ -1,33 +1,33 @@ export enum NodeType { - Function = 'Function', + Function = 'Function', - Group = 'Group', - Repeat = 'Repeat', + Group = 'Group', + Repeat = 'Repeat', - Add = 'Add', - Subtract = 'Subtract', - Negate = 'Negate', + Add = 'Add', + Subtract = 'Subtract', + Negate = 'Negate', - Exponent = 'Exponent', - Multiply = 'Multiply', - Divide = 'Divide', - Modulo = 'Modulo', + Exponent = 'Exponent', + Multiply = 'Multiply', + Divide = 'Divide', + Modulo = 'Modulo', - Equal = 'Equal', - Greater = 'Greater', - GreaterOrEqual = 'GreaterOrEqual', - Less = 'Less', - LessOrEqual = 'LessOrEqual', + Equal = 'Equal', + Greater = 'Greater', + GreaterOrEqual = 'GreaterOrEqual', + Less = 'Less', + LessOrEqual = 'LessOrEqual', - Explode = 'Explode', - Keep = 'Keep', - Drop = 'Drop', - Critical = 'Critical', - Reroll = 'Reroll', - Sort = 'Sort', + Explode = 'Explode', + Keep = 'Keep', + Drop = 'Drop', + Critical = 'Critical', + Reroll = 'Reroll', + Sort = 'Sort', - Dice = 'Dice', - DiceSides = 'DiceSides', - DiceRoll = 'DiceRoll', - Number = 'Number' + Dice = 'Dice', + DiceSides = 'DiceSides', + DiceRoll = 'DiceRoll', + Number = 'Number' } diff --git a/src/dice.class.ts b/src/dice.class.ts index 4e92989..93ebc29 100644 --- a/src/dice.class.ts +++ b/src/dice.class.ts @@ -9,34 +9,32 @@ import { DiceParser } from './parser/dice-parser.class'; import { RandomProvider } from './random'; export class Dice { - constructor( - protected functions?: FunctionDefinitionList, - protected randomProvider?: RandomProvider, - protected generator?: DiceGenerator - ) { } + constructor( + protected functions?: FunctionDefinitionList, + protected randomProvider?: RandomProvider + ) { } - roll(input: string | CharacterStream): DiceResult { - const lexer = this.createLexer(input); - const parser = this.createParser(lexer); - const interpreter = this.createInterpreter(); - const generator = this.createGenerator(); - const parseResult = parser.parse(); - return interpreter.interpret(parseResult.root); - } + roll(input: string | CharacterStream): DiceResult { + const lexer = this.createLexer(input); + const parser = this.createParser(lexer); + const interpreter = this.createInterpreter(); + const parseResult = parser.parse(); + return interpreter.interpret(parseResult.root); + } - protected createLexer(input: string | CharacterStream): Lexer { - return new DiceLexer(input); - } + protected createLexer(input: string | CharacterStream): Lexer { + return new DiceLexer(input); + } - protected createParser(lexer: Lexer): Parser { - return new DiceParser(lexer); - } + protected createParser(lexer: Lexer): Parser { + return new DiceParser(lexer); + } - protected createInterpreter(): DiceInterpreter { - return new DiceInterpreter(this.functions, this.randomProvider, this.generator); - } + protected createInterpreter(): DiceInterpreter { + return new DiceInterpreter(this.functions, this.randomProvider, this.createGenerator()); + } - protected createGenerator(): DiceGenerator { - return new DiceGenerator(); - } + protected createGenerator(): DiceGenerator { + return new DiceGenerator(); + } } diff --git a/src/generator/dice-generator.class.ts b/src/generator/dice-generator.class.ts index e0861f2..a172bbb 100644 --- a/src/generator/dice-generator.class.ts +++ b/src/generator/dice-generator.class.ts @@ -2,207 +2,207 @@ import * as Ast from '../ast'; import { Generator } from './generator.interface'; export class DiceGenerator implements Generator { - generate(expression: Ast.ExpressionNode): string { - switch (expression.type) { - case Ast.NodeType.Number: return this.generateNumber(expression); - case Ast.NodeType.Add: return this.generateAdd(expression); - case Ast.NodeType.Subtract: return this.generateSubtract(expression); - case Ast.NodeType.Multiply: return this.generateMultiply(expression); - case Ast.NodeType.Divide: return this.generateDivide(expression); - case Ast.NodeType.Modulo: return this.generateModulo(expression); - case Ast.NodeType.Exponent: return this.generateExponent(expression); - case Ast.NodeType.Negate: return this.generateNegate(expression); - case Ast.NodeType.Dice: return this.generateDice(expression); - case Ast.NodeType.DiceSides: return this.generateDiceSides(expression); - case Ast.NodeType.DiceRoll: return this.generateDiceRoll(expression); - case Ast.NodeType.Function: return this.generateFunction(expression); - case Ast.NodeType.Group: return this.generateGroup(expression); - case Ast.NodeType.Repeat: return this.generateRepeat(expression); - case Ast.NodeType.Equal: return this.generateEqual(expression); - case Ast.NodeType.Greater: return this.generateGreater(expression); - case Ast.NodeType.GreaterOrEqual: return this.generateGreaterOrEqual(expression); - case Ast.NodeType.Less: return this.generateLess(expression); - case Ast.NodeType.LessOrEqual: return this.generateLessOrEqual(expression); - case Ast.NodeType.Explode: return this.generateExplode(expression); - case Ast.NodeType.Keep: return this.generateKeep(expression); - case Ast.NodeType.Drop: return this.generateDrop(expression); - case Ast.NodeType.Critical: return this.generateCritical(expression); - case Ast.NodeType.Reroll: return this.generateReroll(expression); - case Ast.NodeType.Sort: return this.generateSort(expression); - default: throw new Error('Unrecognized node type.'); - } - } - - generateNumber(expression: Ast.ExpressionNode): string { - return expression.getAttribute('value').toString(); - } - - generateAdd(expression: Ast.ExpressionNode): string { - this.expectChildCount(expression, 2); - return this.generate(expression.getChild(0)) + ' + ' + this.generate(expression.getChild(1)); - } - - generateSubtract(expression: Ast.ExpressionNode): string { - this.expectChildCount(expression, 2); - return this.generate(expression.getChild(0)) + ' - ' + this.generate(expression.getChild(1)); - } - - generateMultiply(expression: Ast.ExpressionNode): string { - this.expectChildCount(expression, 2); - return this.generate(expression.getChild(0)) + ' * ' + this.generate(expression.getChild(1)); - } - - generateDivide(expression: Ast.ExpressionNode): string { - this.expectChildCount(expression, 2); - return this.generate(expression.getChild(0)) + ' / ' + this.generate(expression.getChild(1)); - } - - generateModulo(expression: Ast.ExpressionNode): string { - this.expectChildCount(expression, 2); - return this.generate(expression.getChild(0)) + ' % ' + this.generate(expression.getChild(1)); - } - - generateExponent(expression: Ast.ExpressionNode): string { - this.expectChildCount(expression, 2); - return this.generate(expression.getChild(0)) + ' ^ ' + this.generate(expression.getChild(1)); - } - - generateNegate(expression: Ast.ExpressionNode): string { - this.expectChildCount(expression, 1); - return '-' + this.generate(expression.getChild(0)); - } - - generateDice(expression: Ast.ExpressionNode): string { - if (expression.getChildCount() === 0 || expression.getChild(0).type === Ast.NodeType.DiceRoll) { - return '[' + this.generateCommaList(expression) + ']'; - } else { - this.expectChildCount(expression, 2); - return this.generateWithParens(expression.getChild(0)) + 'd' + this.generateWithParens(expression.getChild(1)); - } - } - - generateDiceSides(expression: Ast.ExpressionNode): string { - const val = expression.getAttribute('value').toString(); - return val === 'fate' ? 'F' : val; - } - - generateDiceRoll(expression: Ast.ExpressionNode): string { - return expression.getAttribute('value').toString(); - } - - generateFunction(expression: Ast.ExpressionNode): string { - return expression.getAttribute('name') + '(' + this.generateCommaList(expression) + ')'; - } - - generateGroup(expression: Ast.ExpressionNode): string { - return '{' + this.generateCommaList(expression) + '}'; - } - - generateRepeat(expression: Ast.ExpressionNode): string { - this.expectChildCount(expression, 2); - return this.generate(expression.getChild(0)) + '...' + this.generate(expression.getChild(1)); - } - - generateEqual(expression: Ast.ExpressionNode): string { - return this.generateEqualityExpression(expression, '='); - } - - generateGreater(expression: Ast.ExpressionNode): string { - return this.generateEqualityExpression(expression, '>'); - } - - generateGreaterOrEqual(expression: Ast.ExpressionNode): string { - return this.generateEqualityExpression(expression, '>='); - } - - generateLess(expression: Ast.ExpressionNode): string { - return this.generateEqualityExpression(expression, '<'); - } - - generateLessOrEqual(expression: Ast.ExpressionNode): string { - return this.generateEqualityExpression(expression, '<='); - } - - generateExplode(expression: Ast.ExpressionNode): string { - this.expectChildCount(expression, 1); - let exp = '!'; - if (expression.getAttribute('compound')) { exp += '!'; } - if (expression.getAttribute('penetrate')) { exp += 'p'; } - if (expression.getChildCount() > 1) { exp += this.generate(expression.getChild(1)); } - return this.generate(expression.getChild(0)) + exp; - } - - generateKeep(expression: Ast.ExpressionNode): string { - this.expectChildCount(expression, 1); - let keep = 'k'; - if (expression.getAttribute('type') === 'highest') { keep += 'h'; } - if (expression.getAttribute('type') === 'lowest') { keep += 'l'; } - return this.generate(expression.getChild(0)) + keep; - } - - generateDrop(expression: Ast.ExpressionNode): string { - this.expectChildCount(expression, 1); - let drop = 'd'; - if (expression.getAttribute('type') === 'highest') { drop += 'h'; } - if (expression.getAttribute('type') === 'lowest') { drop += 'l'; } - return this.generate(expression.getChild(0)) + drop; - } - - generateCritical(expression: Ast.ExpressionNode): string { - this.expectChildCount(expression, 1); - let critical = 'c'; - if (expression.getAttribute('type') === 'success') { critical += 's'; } - if (expression.getAttribute('type') === 'failure') { critical += 'f'; } - if (expression.getChildCount() > 1) { critical += this.generate(expression.getChild(1)); } - return this.generate(expression.getChild(0)) + critical; - } - - generateReroll(expression: Ast.ExpressionNode): string { - this.expectChildCount(expression, 1); - let reroll = 'r'; - if (expression.getAttribute('once')) { reroll += 'o'; } - if (expression.getChildCount() > 1) { reroll += this.generate(expression.getChild(1)); } - return this.generate(expression.getChild(0)) + reroll; - } - - generateSort(expression: Ast.ExpressionNode): string { - this.expectChildCount(expression, 1); - let sort = 's'; - if (expression.getAttribute('direction') === 'ascending') { sort += 'a'; } - if (expression.getAttribute('direction') === 'descending') { sort += 'd'; } - return this.generate(expression.getChild(0)) + sort; - } - - private generateEqualityExpression(expression: Ast.ExpressionNode, operator: string): string { - this.expectChildCount(expression, 1); - if (expression.getChildCount() === 1) { - return operator + this.generate(expression.getChild(0)); - } else { - return this.generate(expression.getChild(0)) + ' ' + operator + ' ' + this.generate(expression.getChild(1)); - } - } - - private generateCommaList(expression: Ast.ExpressionNode): string { - let buffer = ''; - for (let x = 0; x < expression.getChildCount(); x++) { - if (x > 0) { buffer += ', '; } - buffer += this.generate(expression.getChild(x)); - } - return buffer; - } - - private generateWithParens(expression: Ast.ExpressionNode): string { - if (expression.getChildCount() === 0) { - return this.generate(expression); - } else { - return '(' + this.generate(expression) + ')'; - } - } - - private expectChildCount(expression: Ast.ExpressionNode, count: number) { - const findCount = expression.getChildCount(); - if (findCount < count) { - throw new Error(`Expected ${expression.type} node to have ${count} children, but found ${findCount}.`); - } - } + generate(expression: Ast.ExpressionNode): string { + switch (expression.type) { + case Ast.NodeType.Number: return this.generateNumber(expression); + case Ast.NodeType.Add: return this.generateAdd(expression); + case Ast.NodeType.Subtract: return this.generateSubtract(expression); + case Ast.NodeType.Multiply: return this.generateMultiply(expression); + case Ast.NodeType.Divide: return this.generateDivide(expression); + case Ast.NodeType.Modulo: return this.generateModulo(expression); + case Ast.NodeType.Exponent: return this.generateExponent(expression); + case Ast.NodeType.Negate: return this.generateNegate(expression); + case Ast.NodeType.Dice: return this.generateDice(expression); + case Ast.NodeType.DiceSides: return this.generateDiceSides(expression); + case Ast.NodeType.DiceRoll: return this.generateDiceRoll(expression); + case Ast.NodeType.Function: return this.generateFunction(expression); + case Ast.NodeType.Group: return this.generateGroup(expression); + case Ast.NodeType.Repeat: return this.generateRepeat(expression); + case Ast.NodeType.Equal: return this.generateEqual(expression); + case Ast.NodeType.Greater: return this.generateGreater(expression); + case Ast.NodeType.GreaterOrEqual: return this.generateGreaterOrEqual(expression); + case Ast.NodeType.Less: return this.generateLess(expression); + case Ast.NodeType.LessOrEqual: return this.generateLessOrEqual(expression); + case Ast.NodeType.Explode: return this.generateExplode(expression); + case Ast.NodeType.Keep: return this.generateKeep(expression); + case Ast.NodeType.Drop: return this.generateDrop(expression); + case Ast.NodeType.Critical: return this.generateCritical(expression); + case Ast.NodeType.Reroll: return this.generateReroll(expression); + case Ast.NodeType.Sort: return this.generateSort(expression); + default: throw new Error('Unrecognized node type.'); + } + } + + generateNumber(expression: Ast.ExpressionNode): string { + return expression.getAttribute('value').toString(); + } + + generateAdd(expression: Ast.ExpressionNode): string { + this.expectChildCount(expression, 2); + return this.generate(expression.getChild(0)) + ' + ' + this.generate(expression.getChild(1)); + } + + generateSubtract(expression: Ast.ExpressionNode): string { + this.expectChildCount(expression, 2); + return this.generate(expression.getChild(0)) + ' - ' + this.generate(expression.getChild(1)); + } + + generateMultiply(expression: Ast.ExpressionNode): string { + this.expectChildCount(expression, 2); + return this.generate(expression.getChild(0)) + ' * ' + this.generate(expression.getChild(1)); + } + + generateDivide(expression: Ast.ExpressionNode): string { + this.expectChildCount(expression, 2); + return this.generate(expression.getChild(0)) + ' / ' + this.generate(expression.getChild(1)); + } + + generateModulo(expression: Ast.ExpressionNode): string { + this.expectChildCount(expression, 2); + return this.generate(expression.getChild(0)) + ' % ' + this.generate(expression.getChild(1)); + } + + generateExponent(expression: Ast.ExpressionNode): string { + this.expectChildCount(expression, 2); + return this.generate(expression.getChild(0)) + ' ^ ' + this.generate(expression.getChild(1)); + } + + generateNegate(expression: Ast.ExpressionNode): string { + this.expectChildCount(expression, 1); + return '-' + this.generate(expression.getChild(0)); + } + + generateDice(expression: Ast.ExpressionNode): string { + if (expression.getChildCount() === 0 || expression.getChild(0).type === Ast.NodeType.DiceRoll) { + return '[' + this.generateCommaList(expression) + ']'; + } else { + this.expectChildCount(expression, 2); + return this.generateWithParens(expression.getChild(0)) + 'd' + this.generateWithParens(expression.getChild(1)); + } + } + + generateDiceSides(expression: Ast.ExpressionNode): string { + const val = expression.getAttribute('value').toString(); + return val === 'fate' ? 'F' : val; + } + + generateDiceRoll(expression: Ast.ExpressionNode): string { + return expression.getAttribute('value').toString(); + } + + generateFunction(expression: Ast.ExpressionNode): string { + return expression.getAttribute('name') + '(' + this.generateCommaList(expression) + ')'; + } + + generateGroup(expression: Ast.ExpressionNode): string { + return '{' + this.generateCommaList(expression) + '}'; + } + + generateRepeat(expression: Ast.ExpressionNode): string { + this.expectChildCount(expression, 2); + return this.generate(expression.getChild(0)) + '...' + this.generate(expression.getChild(1)); + } + + generateEqual(expression: Ast.ExpressionNode): string { + return this.generateEqualityExpression(expression, '='); + } + + generateGreater(expression: Ast.ExpressionNode): string { + return this.generateEqualityExpression(expression, '>'); + } + + generateGreaterOrEqual(expression: Ast.ExpressionNode): string { + return this.generateEqualityExpression(expression, '>='); + } + + generateLess(expression: Ast.ExpressionNode): string { + return this.generateEqualityExpression(expression, '<'); + } + + generateLessOrEqual(expression: Ast.ExpressionNode): string { + return this.generateEqualityExpression(expression, '<='); + } + + generateExplode(expression: Ast.ExpressionNode): string { + this.expectChildCount(expression, 1); + let exp = '!'; + if (expression.getAttribute('compound')) { exp += '!'; } + if (expression.getAttribute('penetrate')) { exp += 'p'; } + if (expression.getChildCount() > 1) { exp += this.generate(expression.getChild(1)); } + return this.generate(expression.getChild(0)) + exp; + } + + generateKeep(expression: Ast.ExpressionNode): string { + this.expectChildCount(expression, 1); + let keep = 'k'; + if (expression.getAttribute('type') === 'highest') { keep += 'h'; } + if (expression.getAttribute('type') === 'lowest') { keep += 'l'; } + return this.generate(expression.getChild(0)) + keep; + } + + generateDrop(expression: Ast.ExpressionNode): string { + this.expectChildCount(expression, 1); + let drop = 'd'; + if (expression.getAttribute('type') === 'highest') { drop += 'h'; } + if (expression.getAttribute('type') === 'lowest') { drop += 'l'; } + return this.generate(expression.getChild(0)) + drop; + } + + generateCritical(expression: Ast.ExpressionNode): string { + this.expectChildCount(expression, 1); + let critical = 'c'; + if (expression.getAttribute('type') === 'success') { critical += 's'; } + if (expression.getAttribute('type') === 'failure') { critical += 'f'; } + if (expression.getChildCount() > 1) { critical += this.generate(expression.getChild(1)); } + return this.generate(expression.getChild(0)) + critical; + } + + generateReroll(expression: Ast.ExpressionNode): string { + this.expectChildCount(expression, 1); + let reroll = 'r'; + if (expression.getAttribute('once')) { reroll += 'o'; } + if (expression.getChildCount() > 1) { reroll += this.generate(expression.getChild(1)); } + return this.generate(expression.getChild(0)) + reroll; + } + + generateSort(expression: Ast.ExpressionNode): string { + this.expectChildCount(expression, 1); + let sort = 's'; + if (expression.getAttribute('direction') === 'ascending') { sort += 'a'; } + if (expression.getAttribute('direction') === 'descending') { sort += 'd'; } + return this.generate(expression.getChild(0)) + sort; + } + + private generateEqualityExpression(expression: Ast.ExpressionNode, operator: string): string { + this.expectChildCount(expression, 1); + if (expression.getChildCount() === 1) { + return operator + this.generate(expression.getChild(0)); + } else { + return this.generate(expression.getChild(0)) + ' ' + operator + ' ' + this.generate(expression.getChild(1)); + } + } + + private generateCommaList(expression: Ast.ExpressionNode): string { + let buffer = ''; + for (let x = 0; x < expression.getChildCount(); x++) { + if (x > 0) { buffer += ', '; } + buffer += this.generate(expression.getChild(x)); + } + return buffer; + } + + private generateWithParens(expression: Ast.ExpressionNode): string { + if (expression.getChildCount() === 0) { + return this.generate(expression); + } else { + return '(' + this.generate(expression) + ')'; + } + } + + private expectChildCount(expression: Ast.ExpressionNode, count: number) { + const findCount = expression.getChildCount(); + if (findCount < count) { + throw new Error(`Expected ${expression.type} node to have ${count} children, but found ${findCount}.`); + } + } } diff --git a/src/generator/generator.interface.ts b/src/generator/generator.interface.ts index 4268890..9b2f761 100644 --- a/src/generator/generator.interface.ts +++ b/src/generator/generator.interface.ts @@ -1,5 +1,5 @@ import * as Ast from '../ast'; export interface Generator { - generate(expression: Ast.ExpressionNode): TResult; + generate(expression: Ast.ExpressionNode): TResult; } diff --git a/src/index.ts b/src/index.ts index f8d8736..f10e0f3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,6 @@ +export * from './ast'; +export * from './random'; +export * from './generator'; +export * from './interpreter'; +export * from './parser'; export * from './dice.class'; diff --git a/src/interpreter/default-function-definitions.ts b/src/interpreter/default-function-definitions.ts index 47ba85a..18d4f12 100644 --- a/src/interpreter/default-function-definitions.ts +++ b/src/interpreter/default-function-definitions.ts @@ -3,21 +3,21 @@ import { FunctionDefinitionList } from './function-definition-list.class'; export const DefaultFunctionDefinitions = new FunctionDefinitionList(); DefaultFunctionDefinitions['floor'] = (interpreter, functionNode, errors) => { - return Math.floor(interpreter.evaluate(functionNode.getChild(0), errors)); + return Math.floor(interpreter.evaluate(functionNode.getChild(0), errors)); }; DefaultFunctionDefinitions['ceil'] = (interpreter, functionNode, errors) => { - return Math.ceil(interpreter.evaluate(functionNode.getChild(0), errors)); + return Math.ceil(interpreter.evaluate(functionNode.getChild(0), errors)); }; DefaultFunctionDefinitions['abs'] = (interpreter, functionNode, errors) => { - return Math.abs(interpreter.evaluate(functionNode.getChild(0), errors)); + return Math.abs(interpreter.evaluate(functionNode.getChild(0), errors)); }; DefaultFunctionDefinitions['round'] = (interpreter, functionNode, errors) => { - return Math.round(interpreter.evaluate(functionNode.getChild(0), errors)); + return Math.round(interpreter.evaluate(functionNode.getChild(0), errors)); }; DefaultFunctionDefinitions['sqrt'] = (interpreter, functionNode, errors) => { - return Math.sqrt(interpreter.evaluate(functionNode.getChild(0), errors)); + return Math.sqrt(interpreter.evaluate(functionNode.getChild(0), errors)); }; diff --git a/src/interpreter/dice-interpreter.class.ts b/src/interpreter/dice-interpreter.class.ts index d5229cd..25f097d 100644 --- a/src/interpreter/dice-interpreter.class.ts +++ b/src/interpreter/dice-interpreter.class.ts @@ -3,472 +3,477 @@ import { DiceGenerator } from '../generator'; import { DefaultRandomProvider, RandomProvider } from '../random'; import { DefaultFunctionDefinitions } from './default-function-definitions'; import { DiceResult } from './dice-result.class'; -import { ErrorMessage } from './error-message.class'; +import { InterpreterError } from './error-message.class'; import { FunctionDefinitionList } from './function-definition-list.class'; import { Interpreter } from './interpreter.interface'; -export class DiceInterpreter implements Interpreter { - protected functions: FunctionDefinitionList; - protected random: RandomProvider; - protected generator: DiceGenerator; - - constructor(functions?: FunctionDefinitionList, random?: RandomProvider, generator?: DiceGenerator) { - this.functions = DefaultFunctionDefinitions; - (Object).assign(this.functions, functions); - this.random = random || new DefaultRandomProvider(); - this.generator = generator || new DiceGenerator(); - } - - interpret(expression: Ast.ExpressionNode): DiceResult { - const exp = expression.copy(); - const errors: ErrorMessage[] = []; - const total = this.evaluate(exp, errors); - const successes = this.countSuccesses(exp, errors); - const fails = this.countFailures(exp, errors); - const renderedExpression = this.generator.generate(exp); - return new DiceResult(exp, renderedExpression, total, successes, fails, errors); - } - - evaluate(expression: Ast.ExpressionNode, errors: ErrorMessage[]): any { - if (!expression) { errors.push(new ErrorMessage('Unexpected null node reference found.', expression)); return 0; } - if (expression.type === Ast.NodeType.DiceRoll) { - return this.evaluateDiceRoll(expression, errors); - } else if (expression.type === Ast.NodeType.Number) { - return this.evaluateNumber(expression, errors); - } else if (expression.type === Ast.NodeType.DiceSides) { - return this.evaluateDiceSides(expression, errors); - } else if (!expression.getAttribute('value')) { - let value: any = 0; - switch (expression.type) { - case Ast.NodeType.Add: value = this.evaluateAdd(expression, errors); break; - case Ast.NodeType.Subtract: value = this.evaluateSubtract(expression, errors); break; - case Ast.NodeType.Multiply: value = this.evaluateMultiply(expression, errors); break; - case Ast.NodeType.Divide: value = this.evaluateDivide(expression, errors); break; - case Ast.NodeType.Modulo: value = this.evaluateModulo(expression, errors); break; - case Ast.NodeType.Negate: value = this.evaluateNegate(expression, errors); break; - case Ast.NodeType.Exponent: value = this.evaluateExponent(expression, errors); break; - case Ast.NodeType.Dice: value = this.evaluateDice(expression, errors); break; - case Ast.NodeType.Function: value = this.evaluateFunction(expression, errors); break; - case Ast.NodeType.Group: value = this.evaluateGroup(expression, errors); break; - case Ast.NodeType.Repeat: value = this.evaluateRepeat(expression, errors); break; - case Ast.NodeType.Explode: value = this.evaluateExplode(expression, errors); break; - case Ast.NodeType.Keep: value = this.evaluateKeep(expression, errors); break; - case Ast.NodeType.Drop: value = this.evaluateDrop(expression, errors); break; - case Ast.NodeType.Critical: value = this.evaluateCritical(expression, errors); break; - case Ast.NodeType.Reroll: value = this.evaluateReroll(expression, errors); break; - case Ast.NodeType.Sort: value = this.evaluateSort(expression, errors); break; - case Ast.NodeType.Equal: value = this.evaluateEqual(expression, errors); break; - case Ast.NodeType.Greater: value = this.evaluateGreater(expression, errors); break; - case Ast.NodeType.GreaterOrEqual: value = this.evaluateGreaterOrEqual(expression, errors); break; - case Ast.NodeType.Less: value = this.evaluateLess(expression, errors); break; - case Ast.NodeType.LessOrEqual: value = this.evaluateLessOrEqual(expression, errors); break; - default: - errors.push(new ErrorMessage(`Unrecognized node type '${expression.type}'.`, expression)); - return 0; - } - expression.setAttribute('value', value); - } - return expression.getAttribute('value'); - } - - evaluateAdd(expression: Ast.ExpressionNode, errors: ErrorMessage[]): number { - if (!this.expectChildCount(expression, 2, errors)) { return 0; } - return this.evaluate(expression.getChild(0), errors) + this.evaluate(expression.getChild(1), errors); - } - - evaluateSubtract(expression: Ast.ExpressionNode, errors: ErrorMessage[]): number { - if (!this.expectChildCount(expression, 2, errors)) { return 0; } - return this.evaluate(expression.getChild(0), errors) - this.evaluate(expression.getChild(1), errors); - } - - evaluateMultiply(expression: Ast.ExpressionNode, errors: ErrorMessage[]): number { - if (!this.expectChildCount(expression, 2, errors)) { return 0; } - return this.evaluate(expression.getChild(0), errors) * this.evaluate(expression.getChild(1), errors); - } - - evaluateDivide(expression: Ast.ExpressionNode, errors: ErrorMessage[]): number { - if (!this.expectChildCount(expression, 2, errors)) { return 0; } - return this.evaluate(expression.getChild(0), errors) / this.evaluate(expression.getChild(1), errors); - } - - evaluateModulo(expression: Ast.ExpressionNode, errors: ErrorMessage[]): number { - if (!this.expectChildCount(expression, 2, errors)) { return 0; } - return this.evaluate(expression.getChild(0), errors) % this.evaluate(expression.getChild(1), errors); - } - - evaluateExponent(expression: Ast.ExpressionNode, errors: ErrorMessage[]): number { - if (!this.expectChildCount(expression, 2, errors)) { return 0; } - return Math.pow(this.evaluate(expression.getChild(0), errors), this.evaluate(expression.getChild(1), errors)); - } - - evaluateNegate(expression: Ast.ExpressionNode, errors: ErrorMessage[]): number { - if (!this.expectChildCount(expression, 1, errors)) { return 0; } - return -this.evaluate(expression.getChild(0), errors); - } - - evaluateNumber(expression: Ast.ExpressionNode, errors: ErrorMessage[]): number { - return expression.getAttribute('value'); - } - - evaluateDiceSides(expression: Ast.ExpressionNode, errors: ErrorMessage[]): number { - return expression.getAttribute('value'); - } - - evaluateDiceRoll(expression: Ast.ExpressionNode, errors: ErrorMessage[]): number { - if (expression.getAttribute('drop') !== true) { - return expression.getAttribute('value'); - } - return 0; - } - - evaluateDice(expression: Ast.ExpressionNode, errors: ErrorMessage[]): number { - if (!this.expectChildCount(expression, 2, errors)) { return 0; } - const num = Math.round(this.evaluate(expression.getChild(0), errors)); - const sides = expression.getChild(1); - expression.setAttribute('sides', this.evaluate(sides, errors)); - - expression.clearChildren(); - - let total = 0; - for (let x = 0; x < num; x++) { - const diceRoll = this.createDiceRoll(sides, errors); - expression.addChild(diceRoll); - total += this.evaluate(diceRoll, errors); - } - return total; - } - - evaluateFunction(expression: Ast.ExpressionNode, errors: ErrorMessage[]): number { - const fName = expression.getAttribute('name'); - if (Object.keys(this.functions).indexOf(fName) === -1) { - errors.push(new ErrorMessage(`Unknown function: ${fName}`, expression)); - } - const result = this.functions[fName](this, expression, errors); - return result; - } - - evaluateGroup(expression: Ast.ExpressionNode, errors: ErrorMessage[]): number { - let total = 0; - expression.forEachChild(child => { - total += this.evaluate(child, errors); - }); - return total; - } - - evaluateRepeat(expression: Ast.ExpressionNode, errors: ErrorMessage[]): number { - if (!this.expectChildCount(expression, 2, errors)) { return 0; } - const lhs = expression.getChild(0); - const times = this.evaluate(expression.getChild(1), errors); - - const parent = expression.getParent(); - parent.removeChild(expression); - - let total = 0; - for (let x = 0; x < times; x++) { - const copy = lhs.copy(); - parent.addChild(copy); - total += this.evaluate(copy, errors); - } - return total; - } - - evaluateExplode(expression: Ast.ExpressionNode, errors: ErrorMessage[]): number { - if (!this.expectChildCount(expression, 1, errors)) { return 0; } - const dice = this.findDiceOrGroupNode(expression, errors); - if (!dice) { return 0; } - let condition: Ast.ExpressionNode; - const penetrate = expression.getAttribute('penetrate'); - if (expression.getChildCount() > 1) { - condition = expression.getChild(1); - } - - this.evaluate(dice, errors); - - const newRolls: Ast.ExpressionNode[] = []; - - let total = 0; - const sides = dice.getAttribute('sides'); - dice.forEachChild(die => { - if (!die.getAttribute('drop')) { - let dieValue = this.evaluate(die, errors); - total += dieValue; - while (condition && this.evaluateComparison(dieValue, condition, errors) || dieValue === sides) { - die = this.createDiceRoll(sides, errors); - dieValue = this.evaluate(die, errors); - if (penetrate) { dieValue -= 1; } - total += dieValue; - newRolls.push(die); - } - } - }); - - newRolls.forEach(newRoll => dice.addChild(newRoll)); - return total; - } - - evaluateKeep(expression: Ast.ExpressionNode, errors: ErrorMessage[]): number { - if (!this.expectChildCount(expression, 1, errors)) { return 0; } - const dice = this.findDiceOrGroupNode(expression, errors); - if (!dice) { return 0; } - const countTotal = (expression.getChildCount() > 1) ? this.evaluate(expression.getChild(1), errors) : 1; - const type = expression.getAttribute('type'); - this.evaluate(dice, errors); - - const rolls = this.getSortedDiceRolls(dice, (type === 'lowest') ? 'ascending' : 'descending', errors).rolls; - - let count = 0; - let total = 0; - rolls.forEach(roll => { - if (count < countTotal) { - roll.setAttribute('drop', false); - total += roll.getAttribute('value'); - } else { - roll.setAttribute('drop', true); - } - count++; - }); - return total; - } - - evaluateDrop(expression: Ast.ExpressionNode, errors: ErrorMessage[]): number { - if (!this.expectChildCount(expression, 1, errors)) { return 0; } - const dice = this.findDiceOrGroupNode(expression, errors); - if (!dice) { return 0; } - const countTotal = (expression.getChildCount() > 1) ? this.evaluate(expression.getChild(1), errors) : 1; - const type = expression.getAttribute('type'); - this.evaluate(dice, errors); - - const rolls = this.getSortedDiceRolls(dice, (type === 'lowest') ? 'ascending' : 'descending', errors).rolls; - let count = 0; - let total = 0; - rolls.forEach(roll => { - if (count < countTotal) { - roll.setAttribute('drop', true); - } else { - roll.setAttribute('drop', false); - total += roll.getAttribute('value'); - } - count++; - }); - return total; - } - - evaluateCritical(expression: Ast.ExpressionNode, errors: ErrorMessage[]): number { - if (!this.expectChildCount(expression, 1, errors)) { return 0; } - const dice = this.findDiceOrGroupNode(expression, errors); - if (!dice) { return 0; } - const type = expression.getAttribute('type'); - - let condition: Ast.ExpressionNode; - if (expression.getChildCount() > 1) { - condition = expression.getChild(1); - } else { - condition = Ast.Factory.create(Ast.NodeType.Equal); - if (type === 'success') { - this.expectChildCount(dice, 2, errors); - condition.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', dice.getAttribute('sides'))); - } else { - condition.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 1)); - } - } - - this.evaluate(dice, errors); - - let total = 0; - dice.forEachChild((die) => { - const dieValue = this.evaluate(die, errors); - if (this.evaluateComparison(dieValue, condition, errors)) { - die.setAttribute('critical', type); - total += dieValue; - } - }); - - return total; - } - - evaluateReroll(expression: Ast.ExpressionNode, errors: ErrorMessage[]): number { - if (!this.expectChildCount(expression, 1, errors)) { return 0; } - const dice = this.findDiceOrGroupNode(expression, errors); - if (!dice) { return 0; } - let condition: Ast.ExpressionNode; - const once = expression.getAttribute('once'); - - if (expression.getChildCount() > 1) { - condition = expression.getChild(1); - } - - this.evaluate(dice, errors); - - let total = 0; - const sides = dice.getAttribute('sides'); - dice.forEachChild(die => { - if (!die.getAttribute('drop')) { - let dieValue = this.evaluate(die, errors); - while (condition && this.evaluateComparison(dieValue, condition, errors) || dieValue === 1) { - dieValue = this.createDiceRollValue(sides, errors); - if (once) { break; } - } - die.setAttribute('value', dieValue); - total += dieValue; - } - }); - - return total; - } - - evaluateSort(expression: Ast.ExpressionNode, errors: ErrorMessage[]): number { - if (!this.expectChildCount(expression, 1, errors)) { return 0; } - const dice = this.findDiceOrGroupNode(expression, errors); - if (!dice) { return 0; } - const rolls = this.getSortedDiceRolls(dice, expression.getAttribute('direction'), errors); - dice.clearChildren(); - rolls.rolls.forEach(roll => dice.addChild(roll)); - return rolls.total; - } - - evaluateEqual(expression: Ast.ExpressionNode, errors: ErrorMessage[]): number { - return this.evaluateSuccess(expression, (l, r) => (l === r), errors); - } - - evaluateGreater(expression: Ast.ExpressionNode, errors: ErrorMessage[]): number { - return this.evaluateSuccess(expression, (l, r) => (l > r), errors); - } - - evaluateGreaterOrEqual(expression: Ast.ExpressionNode, errors: ErrorMessage[]): number { - return this.evaluateSuccess(expression, (l, r) => (l >= r), errors); - } - - evaluateLess(expression: Ast.ExpressionNode, errors: ErrorMessage[]): number { - return this.evaluateSuccess(expression, (l, r) => (l < r), errors); - } - - evaluateLessOrEqual(expression: Ast.ExpressionNode, errors: ErrorMessage[]): number { - return this.evaluateSuccess(expression, (l, r) => (l <= r), errors); - } - - countSuccesses(expression: Ast.ExpressionNode, errors: ErrorMessage[]): number { - return this.countSuccessOrFailure(expression, die => die.getAttribute('success'), errors); - } - - countFailures(expression: Ast.ExpressionNode, errors: ErrorMessage[]): number { - return this.countSuccessOrFailure(expression, die => !die.getAttribute('success'), errors); - } - - private countSuccessOrFailure(expression: Ast.ExpressionNode, - condition: (die: Ast.ExpressionNode) => boolean, errors: ErrorMessage[]): number { - let total = 0; - if (expression.type === Ast.NodeType.Dice || expression.type === Ast.NodeType.Group) { - expression.forEachChild(die => { - if (!die.getAttribute('drop') && condition(die)) { total++; } - }); - } else { - expression.forEachChild(die => { - total += this.countSuccessOrFailure(die, condition, errors); - }); - } - return total; - } - - private expectChildCount(expression: Ast.ExpressionNode, count: number, errors: ErrorMessage[]): boolean { - const findCount = expression.getChildCount(); - if (findCount < count) { - const err = new ErrorMessage(`Expected ${expression.type} node to have ${count} children, but found ${findCount}.`, expression); - errors.push(err); - return false; - } - return true; - } - - private evaluateComparison(lhs: number, expression: Ast.ExpressionNode, errors: ErrorMessage[]): boolean { - if (!this.expectChildCount(expression, 1, errors)) { return false; } - switch (expression.type) { - case Ast.NodeType.Equal: return lhs === this.evaluate(expression.getChild(0), errors); - case Ast.NodeType.Greater: return lhs > this.evaluate(expression.getChild(0), errors); - case Ast.NodeType.GreaterOrEqual: return lhs >= this.evaluate(expression.getChild(0), errors); - case Ast.NodeType.Less: return lhs < this.evaluate(expression.getChild(0), errors); - case Ast.NodeType.LessOrEqual: return lhs <= this.evaluate(expression.getChild(0), errors); - default: - errors.push(new ErrorMessage(`Unrecognized comparison operator '${expression.type}'.`, expression)); - return false; - } - } - - evaluateSuccess(expression: Ast.ExpressionNode, compare: (lhs: number, rhs: number) => boolean, errors: ErrorMessage[]): number { - if (!this.expectChildCount(expression, 2, errors)) { return 0; } - const rhv = this.evaluate(expression.getChild(1), errors); - - let total = 0; - const diceOrGroup = this.findDiceOrGroupNode(expression, errors); - if (!diceOrGroup) { return 0; } - diceOrGroup.forEachChild(die => { - if (!die.getAttribute('drop')) { - const val = this.evaluate(die, errors); - const res = compare(this.evaluate(die, errors), rhv); - die.setAttribute('success', res); - if (res) { total += val; } - } - }); - - return total; - } - - private findDiceOrGroupNode(expression: Ast.ExpressionNode, errors: ErrorMessage[]): Ast.ExpressionNode { - if (expression.type === Ast.NodeType.Dice || expression.type === Ast.NodeType.Group) { - return expression; - } - if (expression.getChildCount() < 1) { - errors.push(new ErrorMessage('Missing dice/group node.', expression)); - return null; - } - const child = expression.getChild(0); - this.evaluate(child, errors); - return this.findDiceOrGroupNode(child, errors); - } +interface SortedDiceRolls { + rolls: Ast.ExpressionNode[]; + total: number; +} - private getSortedDiceRolls(dice: Ast.ExpressionNode, direction: string, errors: ErrorMessage[]): - { rolls: Ast.ExpressionNode[], total: number } { - const output = { rolls: [], total: 0 }; - - dice.forEachChild(die => { - output.rolls.push(die); - output.total += this.evaluate(die, errors); - }); - - let sortOrder; - if (direction === 'descending') { - sortOrder = (a, b) => b.getAttribute('value') - a.getAttribute('value'); - } else if (direction === 'ascending') { - sortOrder = (a, b) => a.getAttribute('value') - b.getAttribute('value'); - } else { - errors.push(new ErrorMessage(`Unknown sort direction: ${direction}. Expected 'ascending' or 'descending'.`, dice)); +export class DiceInterpreter implements Interpreter { + protected functions: FunctionDefinitionList; + protected random: RandomProvider; + protected generator: DiceGenerator; + + constructor(functions?: FunctionDefinitionList, random?: RandomProvider, generator?: DiceGenerator) { + this.functions = DefaultFunctionDefinitions; + (Object).assign(this.functions, functions); + this.random = random || new DefaultRandomProvider(); + this.generator = generator || new DiceGenerator(); + } + + interpret(expression: Ast.ExpressionNode): DiceResult { + const exp = expression.copy(); + const errors: InterpreterError[] = []; + const total = this.evaluate(exp, errors); + const successes = this.countSuccesses(exp, errors); + const fails = this.countFailures(exp, errors); + const renderedExpression = this.generator.generate(exp); + return new DiceResult(exp, renderedExpression, total, successes, fails, errors); + } + + evaluate(expression: Ast.ExpressionNode, errors: InterpreterError[]): any { + if (!expression) { errors.push(new InterpreterError('Unexpected null node reference found.', expression)); return 0; } + if (expression.type === Ast.NodeType.DiceRoll) { + return this.evaluateDiceRoll(expression, errors); + } else if (expression.type === Ast.NodeType.Number) { + return this.evaluateNumber(expression, errors); + } else if (expression.type === Ast.NodeType.DiceSides) { + return this.evaluateDiceSides(expression, errors); + } else if (!expression.getAttribute('value')) { + let value: any = 0; + switch (expression.type) { + case Ast.NodeType.Add: value = this.evaluateAdd(expression, errors); break; + case Ast.NodeType.Subtract: value = this.evaluateSubtract(expression, errors); break; + case Ast.NodeType.Multiply: value = this.evaluateMultiply(expression, errors); break; + case Ast.NodeType.Divide: value = this.evaluateDivide(expression, errors); break; + case Ast.NodeType.Modulo: value = this.evaluateModulo(expression, errors); break; + case Ast.NodeType.Negate: value = this.evaluateNegate(expression, errors); break; + case Ast.NodeType.Exponent: value = this.evaluateExponent(expression, errors); break; + case Ast.NodeType.Dice: value = this.evaluateDice(expression, errors); break; + case Ast.NodeType.Function: value = this.evaluateFunction(expression, errors); break; + case Ast.NodeType.Group: value = this.evaluateGroup(expression, errors); break; + case Ast.NodeType.Repeat: value = this.evaluateRepeat(expression, errors); break; + case Ast.NodeType.Explode: value = this.evaluateExplode(expression, errors); break; + case Ast.NodeType.Keep: value = this.evaluateKeep(expression, errors); break; + case Ast.NodeType.Drop: value = this.evaluateDrop(expression, errors); break; + case Ast.NodeType.Critical: value = this.evaluateCritical(expression, errors); break; + case Ast.NodeType.Reroll: value = this.evaluateReroll(expression, errors); break; + case Ast.NodeType.Sort: value = this.evaluateSort(expression, errors); break; + case Ast.NodeType.Equal: value = this.evaluateEqual(expression, errors); break; + case Ast.NodeType.Greater: value = this.evaluateGreater(expression, errors); break; + case Ast.NodeType.GreaterOrEqual: value = this.evaluateGreaterOrEqual(expression, errors); break; + case Ast.NodeType.Less: value = this.evaluateLess(expression, errors); break; + case Ast.NodeType.LessOrEqual: value = this.evaluateLessOrEqual(expression, errors); break; + default: + errors.push(new InterpreterError(`Unrecognized node type '${expression.type}'.`, expression)); + return 0; + } + expression.setAttribute('value', value); + } + return expression.getAttribute('value'); + } + + evaluateAdd(expression: Ast.ExpressionNode, errors: InterpreterError[]): number { + if (!this.expectChildCount(expression, 2, errors)) { return 0; } + return this.evaluate(expression.getChild(0), errors) + this.evaluate(expression.getChild(1), errors); + } + + evaluateSubtract(expression: Ast.ExpressionNode, errors: InterpreterError[]): number { + if (!this.expectChildCount(expression, 2, errors)) { return 0; } + return this.evaluate(expression.getChild(0), errors) - this.evaluate(expression.getChild(1), errors); + } + + evaluateMultiply(expression: Ast.ExpressionNode, errors: InterpreterError[]): number { + if (!this.expectChildCount(expression, 2, errors)) { return 0; } + return this.evaluate(expression.getChild(0), errors) * this.evaluate(expression.getChild(1), errors); + } + + evaluateDivide(expression: Ast.ExpressionNode, errors: InterpreterError[]): number { + if (!this.expectChildCount(expression, 2, errors)) { return 0; } + return this.evaluate(expression.getChild(0), errors) / this.evaluate(expression.getChild(1), errors); + } + + evaluateModulo(expression: Ast.ExpressionNode, errors: InterpreterError[]): number { + if (!this.expectChildCount(expression, 2, errors)) { return 0; } + return this.evaluate(expression.getChild(0), errors) % this.evaluate(expression.getChild(1), errors); + } + + evaluateExponent(expression: Ast.ExpressionNode, errors: InterpreterError[]): number { + if (!this.expectChildCount(expression, 2, errors)) { return 0; } + return Math.pow(this.evaluate(expression.getChild(0), errors), this.evaluate(expression.getChild(1), errors)); + } + + evaluateNegate(expression: Ast.ExpressionNode, errors: InterpreterError[]): number { + if (!this.expectChildCount(expression, 1, errors)) { return 0; } + return -this.evaluate(expression.getChild(0), errors); + } + + evaluateNumber(expression: Ast.ExpressionNode, errors: InterpreterError[]): number { + return expression.getAttribute('value'); + } + + evaluateDiceSides(expression: Ast.ExpressionNode, errors: InterpreterError[]): number { + return expression.getAttribute('value'); + } + + evaluateDiceRoll(expression: Ast.ExpressionNode, errors: InterpreterError[]): number { + if (expression.getAttribute('drop') !== true) { + return expression.getAttribute('value'); + } + return 0; + } + + evaluateDice(expression: Ast.ExpressionNode, errors: InterpreterError[]): number { + if (!this.expectChildCount(expression, 2, errors)) { return 0; } + const num = Math.round(this.evaluate(expression.getChild(0), errors)); + const sides = expression.getChild(1); + expression.setAttribute('sides', this.evaluate(sides, errors)); + + expression.clearChildren(); + + let total = 0; + for (let x = 0; x < num; x++) { + const diceRoll = this.createDiceRoll(sides, errors); + expression.addChild(diceRoll); + total += this.evaluate(diceRoll, errors); + } + return total; + } + + evaluateFunction(expression: Ast.ExpressionNode, errors: InterpreterError[]): number { + const fName = expression.getAttribute('name'); + if (Object.keys(this.functions).indexOf(fName) === -1) { + errors.push(new InterpreterError(`Unknown function: ${fName}`, expression)); + return 0; + } + const result = this.functions[fName](this, expression, errors); + return result; + } + + evaluateGroup(expression: Ast.ExpressionNode, errors: InterpreterError[]): number { + let total = 0; + expression.forEachChild(child => { + total += this.evaluate(child, errors); + }); + return total; + } + + evaluateRepeat(expression: Ast.ExpressionNode, errors: InterpreterError[]): number { + if (!this.expectChildCount(expression, 2, errors)) { return 0; } + const lhs = expression.getChild(0); + const times = this.evaluate(expression.getChild(1), errors); + + const parent = expression.getParent(); + parent.removeChild(expression); + + let total = 0; + for (let x = 0; x < times; x++) { + const copy = lhs.copy(); + parent.addChild(copy); + total += this.evaluate(copy, errors); + } + return total; + } + + evaluateExplode(expression: Ast.ExpressionNode, errors: InterpreterError[]): number { + if (!this.expectChildCount(expression, 1, errors)) { return 0; } + const dice = this.findDiceOrGroupNode(expression, errors); + if (!dice) { return 0; } + let condition: Ast.ExpressionNode; + const penetrate = expression.getAttribute('penetrate'); + if (expression.getChildCount() > 1) { + condition = expression.getChild(1); + } + + this.evaluate(dice, errors); + + const newRolls: Ast.ExpressionNode[] = []; + + let total = 0; + const sides = dice.getAttribute('sides'); + dice.forEachChild(die => { + if (!die.getAttribute('drop')) { + let dieValue = this.evaluate(die, errors); + total += dieValue; + while (condition && this.evaluateComparison(dieValue, condition, errors) || dieValue === sides) { + die = this.createDiceRoll(sides, errors); + dieValue = this.evaluate(die, errors); + if (penetrate) { dieValue -= 1; } + total += dieValue; + newRolls.push(die); } - - output.rolls = output.rolls.sort(sortOrder); - return output; - } - - private createDiceRoll(sides: Ast.ExpressionNode | number, errors: ErrorMessage[]): Ast.ExpressionNode { - const sidesValue = sides instanceof Ast.ExpressionNode - ? sides.getAttribute('value') - : sides; - const diceRoll = this.createDiceRollValue(sides, errors); - return Ast.Factory.create(Ast.NodeType.DiceRoll) - .setAttribute('value', diceRoll) - .setAttribute('drop', false); - } - - private createDiceRollValue(sides: Ast.ExpressionNode | number, errors: ErrorMessage[]): number { - let minValue = 1, maxValue = 0; - - const sidesValue = sides instanceof Ast.ExpressionNode - ? sides.getAttribute('value') - : sides; - - if (sidesValue === 'fate') { - minValue = -1; maxValue = 1; - } else { - maxValue = Math.round(sides instanceof Ast.ExpressionNode ? this.evaluate(sides, errors) : sides); + } + }); + + newRolls.forEach(newRoll => dice.addChild(newRoll)); + return total; + } + + evaluateKeep(expression: Ast.ExpressionNode, errors: InterpreterError[]): number { + if (!this.expectChildCount(expression, 1, errors)) { return 0; } + const dice = this.findDiceOrGroupNode(expression, errors); + if (!dice) { return 0; } + const countTotal = (expression.getChildCount() > 1) ? this.evaluate(expression.getChild(1), errors) : 1; + const type = expression.getAttribute('type'); + this.evaluate(dice, errors); + + const rolls = this.getSortedDiceRolls(dice, (type === 'lowest') ? 'ascending' : 'descending', errors).rolls; + + let count = 0; + let total = 0; + rolls.forEach(roll => { + if (count < countTotal) { + roll.setAttribute('drop', false); + total += roll.getAttribute('value'); + } else { + roll.setAttribute('drop', true); + } + count++; + }); + return total; + } + + evaluateDrop(expression: Ast.ExpressionNode, errors: InterpreterError[]): number { + if (!this.expectChildCount(expression, 1, errors)) { return 0; } + const dice = this.findDiceOrGroupNode(expression, errors); + if (!dice) { return 0; } + const countTotal = (expression.getChildCount() > 1) ? this.evaluate(expression.getChild(1), errors) : 1; + const type = expression.getAttribute('type'); + this.evaluate(dice, errors); + + const rolls = this.getSortedDiceRolls(dice, (type === 'lowest') ? 'ascending' : 'descending', errors).rolls; + let count = 0; + let total = 0; + rolls.forEach(roll => { + if (count < countTotal) { + roll.setAttribute('drop', true); + } else { + roll.setAttribute('drop', false); + total += roll.getAttribute('value'); + } + count++; + }); + return total; + } + + evaluateCritical(expression: Ast.ExpressionNode, errors: InterpreterError[]): number { + if (!this.expectChildCount(expression, 1, errors)) { return 0; } + const dice = this.findDiceOrGroupNode(expression, errors); + if (!dice) { return 0; } + const type = expression.getAttribute('type'); + + let condition: Ast.ExpressionNode; + if (expression.getChildCount() > 1) { + condition = expression.getChild(1); + } else { + condition = Ast.Factory.create(Ast.NodeType.Equal); + if (type === 'success') { + this.expectChildCount(dice, 2, errors); + condition.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', dice.getAttribute('sides'))); + } else { + condition.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 1)); + } + } + + this.evaluate(dice, errors); + + let total = 0; + dice.forEachChild((die) => { + const dieValue = this.evaluate(die, errors); + if (this.evaluateComparison(dieValue, condition, errors)) { + die.setAttribute('critical', type); + total += dieValue; + } + }); + + return total; + } + + evaluateReroll(expression: Ast.ExpressionNode, errors: InterpreterError[]): number { + if (!this.expectChildCount(expression, 1, errors)) { return 0; } + const dice = this.findDiceOrGroupNode(expression, errors); + if (!dice) { return 0; } + let condition: Ast.ExpressionNode; + const once = expression.getAttribute('once'); + + if (expression.getChildCount() > 1) { + condition = expression.getChild(1); + } + + this.evaluate(dice, errors); + + let total = 0; + const sides = dice.getAttribute('sides'); + dice.forEachChild(die => { + if (!die.getAttribute('drop')) { + let dieValue = this.evaluate(die, errors); + while (condition && this.evaluateComparison(dieValue, condition, errors) || dieValue === 1) { + dieValue = this.createDiceRollValue(sides, errors); + if (once) { break; } } - return this.random.numberBetween(minValue, maxValue); - } + die.setAttribute('value', dieValue); + total += dieValue; + } + }); + + return total; + } + + evaluateSort(expression: Ast.ExpressionNode, errors: InterpreterError[]): number { + if (!this.expectChildCount(expression, 1, errors)) { return 0; } + const dice = this.findDiceOrGroupNode(expression, errors); + if (!dice) { return 0; } + const rolls = this.getSortedDiceRolls(dice, expression.getAttribute('direction'), errors); + dice.clearChildren(); + rolls.rolls.forEach(roll => dice.addChild(roll)); + return rolls.total; + } + + evaluateEqual(expression: Ast.ExpressionNode, errors: InterpreterError[]): number { + return this.evaluateSuccess(expression, (l, r) => (l === r), errors); + } + + evaluateGreater(expression: Ast.ExpressionNode, errors: InterpreterError[]): number { + return this.evaluateSuccess(expression, (l, r) => (l > r), errors); + } + + evaluateGreaterOrEqual(expression: Ast.ExpressionNode, errors: InterpreterError[]): number { + return this.evaluateSuccess(expression, (l, r) => (l >= r), errors); + } + + evaluateLess(expression: Ast.ExpressionNode, errors: InterpreterError[]): number { + return this.evaluateSuccess(expression, (l, r) => (l < r), errors); + } + + evaluateLessOrEqual(expression: Ast.ExpressionNode, errors: InterpreterError[]): number { + return this.evaluateSuccess(expression, (l, r) => (l <= r), errors); + } + + countSuccesses(expression: Ast.ExpressionNode, errors: InterpreterError[]): number { + return this.countSuccessOrFailure(expression, die => die.getAttribute('success'), errors); + } + + countFailures(expression: Ast.ExpressionNode, errors: InterpreterError[]): number { + return this.countSuccessOrFailure(expression, die => !die.getAttribute('success'), errors); + } + + private countSuccessOrFailure(expression: Ast.ExpressionNode, + condition: (die: Ast.ExpressionNode) => boolean, errors: InterpreterError[]): number { + let total = 0; + if (expression.type === Ast.NodeType.Dice || expression.type === Ast.NodeType.Group) { + expression.forEachChild(die => { + if (!die.getAttribute('drop') && condition(die)) { total++; } + }); + } else { + expression.forEachChild(die => { + total += this.countSuccessOrFailure(die, condition, errors); + }); + } + return total; + } + + private expectChildCount(expression: Ast.ExpressionNode, count: number, errors: InterpreterError[]): boolean { + const findCount = expression.getChildCount(); + if (findCount < count) { + const err = new InterpreterError(`Expected ${expression.type} node to have ${count} children, but found ${findCount}.`, expression); + errors.push(err); + return false; + } + return true; + } + + private evaluateComparison(lhs: number, expression: Ast.ExpressionNode, errors: InterpreterError[]): boolean { + if (!this.expectChildCount(expression, 1, errors)) { return false; } + switch (expression.type) { + case Ast.NodeType.Equal: return lhs === this.evaluate(expression.getChild(0), errors); + case Ast.NodeType.Greater: return lhs > this.evaluate(expression.getChild(0), errors); + case Ast.NodeType.GreaterOrEqual: return lhs >= this.evaluate(expression.getChild(0), errors); + case Ast.NodeType.Less: return lhs < this.evaluate(expression.getChild(0), errors); + case Ast.NodeType.LessOrEqual: return lhs <= this.evaluate(expression.getChild(0), errors); + default: + errors.push(new InterpreterError(`Unrecognized comparison operator '${expression.type}'.`, expression)); + return false; + } + } + + evaluateSuccess(expression: Ast.ExpressionNode, compare: (lhs: number, rhs: number) => boolean, errors: InterpreterError[]): number { + if (!this.expectChildCount(expression, 2, errors)) { return 0; } + const rhv = this.evaluate(expression.getChild(1), errors); + + let total = 0; + const diceOrGroup = this.findDiceOrGroupNode(expression, errors); + if (!diceOrGroup) { return 0; } + diceOrGroup.forEachChild(die => { + if (!die.getAttribute('drop')) { + const val = this.evaluate(die, errors); + const res = compare(this.evaluate(die, errors), rhv); + die.setAttribute('success', res); + if (res) { total += val; } + } + }); + + return total; + } + + private findDiceOrGroupNode(expression: Ast.ExpressionNode, errors: InterpreterError[]): Ast.ExpressionNode { + if (expression.type === Ast.NodeType.Dice || expression.type === Ast.NodeType.Group) { + return expression; + } + if (expression.getChildCount() < 1) { + errors.push(new InterpreterError('Missing dice/group node.', expression)); + return null; + } + const child = expression.getChild(0); + this.evaluate(child, errors); + return this.findDiceOrGroupNode(child, errors); + } + + private getSortedDiceRolls(dice: Ast.ExpressionNode, direction: string, errors: InterpreterError[]): SortedDiceRolls { + const output: SortedDiceRolls = { rolls: [], total: 0 }; + + dice.forEachChild(die => { + output.rolls.push(die); + output.total += this.evaluate(die, errors); + }); + + let sortOrder; + if (direction === 'descending') { + sortOrder = (a, b) => b.getAttribute('value') - a.getAttribute('value'); + } else if (direction === 'ascending') { + sortOrder = (a, b) => a.getAttribute('value') - b.getAttribute('value'); + } else { + errors.push(new InterpreterError(`Unknown sort direction: ${direction}. Expected 'ascending' or 'descending'.`, dice)); + } + + output.rolls = output.rolls.sort(sortOrder); + return output; + } + + private createDiceRoll(sides: Ast.ExpressionNode | number, errors: InterpreterError[]): Ast.ExpressionNode { + const sidesValue = sides instanceof Ast.ExpressionNode + ? sides.getAttribute('value') + : sides; + const diceRoll = this.createDiceRollValue(sides, errors); + return Ast.Factory.create(Ast.NodeType.DiceRoll) + .setAttribute('value', diceRoll) + .setAttribute('drop', false); + } + + private createDiceRollValue(sides: Ast.ExpressionNode | number, errors: InterpreterError[]): number { + let minValue = 1, maxValue = 0; + + const sidesValue = sides instanceof Ast.ExpressionNode + ? sides.getAttribute('value') + : sides; + + if (sidesValue === 'fate') { + minValue = -1; maxValue = 1; + } else { + maxValue = Math.round(sides instanceof Ast.ExpressionNode ? this.evaluate(sides, errors) : sides); + } + return this.random.numberBetween(minValue, maxValue); + } } diff --git a/src/interpreter/dice-result.class.ts b/src/interpreter/dice-result.class.ts index e634857..f16092f 100644 --- a/src/interpreter/dice-result.class.ts +++ b/src/interpreter/dice-result.class.ts @@ -1,23 +1,23 @@ import { ExpressionNode } from '../ast'; -import { ErrorMessage } from '../parser/error-message.class'; +import { InterpreterError } from '../interpreter/error-message.class'; import { Result } from './result.class'; export class DiceResult extends Result { - readonly successes: number; - readonly failures: number; - readonly errors: ErrorMessage[]; + readonly successes: number; + readonly failures: number; + readonly errors: InterpreterError[]; - constructor( - expression: ExpressionNode, - renderedExpression: string, - total: number, - successes: number, - failures: number, - errors: ErrorMessage[] - ) { - super(expression, renderedExpression, total); - this.successes = successes; - this.failures = failures; - this.errors = errors; - } + constructor( + expression: ExpressionNode, + renderedExpression: string, + total: number, + successes: number, + failures: number, + errors: InterpreterError[] + ) { + super(expression, renderedExpression, total); + this.successes = successes; + this.failures = failures; + this.errors = errors; + } } diff --git a/src/interpreter/error-message.class.ts b/src/interpreter/error-message.class.ts index 91ebd4f..6d4a866 100644 --- a/src/interpreter/error-message.class.ts +++ b/src/interpreter/error-message.class.ts @@ -1,5 +1,5 @@ import { ExpressionNode } from '../ast'; -export class ErrorMessage { - constructor(public message: string, expression: ExpressionNode, stack: string = (new Error().stack)) { } +export class InterpreterError { + constructor(public message: string, public expression: ExpressionNode, public stack: string = (new Error().stack)) { } } diff --git a/src/interpreter/function-definition.type.ts b/src/interpreter/function-definition.type.ts index d152cd9..690703c 100644 --- a/src/interpreter/function-definition.type.ts +++ b/src/interpreter/function-definition.type.ts @@ -1,5 +1,5 @@ -import { ErrorMessage } from './error-message.class'; import { ExpressionNode } from '../ast'; import { DiceInterpreter } from './dice-interpreter.class'; +import { InterpreterError } from './error-message.class'; -export type FunctionDefinition = (interpreter: DiceInterpreter, functionNode: ExpressionNode, errors: ErrorMessage[]) => number; +export type FunctionDefinition = (interpreter: DiceInterpreter, functionNode: ExpressionNode, errors: InterpreterError[]) => number; diff --git a/src/interpreter/interpreter.interface.ts b/src/interpreter/interpreter.interface.ts index f9e06d6..5e13b6d 100644 --- a/src/interpreter/interpreter.interface.ts +++ b/src/interpreter/interpreter.interface.ts @@ -1,5 +1,5 @@ import * as Ast from '../ast'; export interface Interpreter { - interpret(expression: Ast.ExpressionNode): TResult; + interpret(expression: Ast.ExpressionNode): TResult; } diff --git a/src/interpreter/result.class.ts b/src/interpreter/result.class.ts index 5f2c04f..5c99723 100644 --- a/src/interpreter/result.class.ts +++ b/src/interpreter/result.class.ts @@ -1,13 +1,13 @@ import { ExpressionNode } from '../ast'; export class Result { - readonly reducedExpression: ExpressionNode; - readonly renderedExpression: string; - readonly total: number; + readonly reducedExpression: ExpressionNode; + readonly renderedExpression: string; + readonly total: number; - constructor(reducedExpression: ExpressionNode, renderedExpression: string, total: number) { - this.reducedExpression = reducedExpression; - this.renderedExpression = renderedExpression; - this.total = total; - } + constructor(reducedExpression: ExpressionNode, renderedExpression: string, total: number) { + this.reducedExpression = reducedExpression; + this.renderedExpression = renderedExpression; + this.total = total; + } } diff --git a/src/lexer/character-stream.interface.ts b/src/lexer/character-stream.interface.ts index 5d52f5f..95eb776 100644 --- a/src/lexer/character-stream.interface.ts +++ b/src/lexer/character-stream.interface.ts @@ -1,7 +1,7 @@ export interface CharacterStream { - getCurrentCharacter(): string; - getCurrentPosition(): number; - getNextCharacter(): string; - peekNextCharacter(): string; + getCurrentCharacter(): string; + getCurrentPosition(): number; + getNextCharacter(): string; + peekNextCharacter(): string; } diff --git a/src/lexer/dice-lexer.class.ts b/src/lexer/dice-lexer.class.ts index 9cc4d75..8e0988e 100644 --- a/src/lexer/dice-lexer.class.ts +++ b/src/lexer/dice-lexer.class.ts @@ -5,129 +5,130 @@ import { Token } from './token.class'; import { TokenType } from './token-type.enum'; export class DiceLexer implements Lexer { - protected stream: CharacterStream; - private currentToken: Token; - private nextToken: Token; + protected stream: CharacterStream; + private currentToken: Token; + private nextToken: Token; - private numCharRegex: RegExp = /[0-9]/; - private idCharRegex: RegExp = /[a-zA-Z]/; + private numCharRegex: RegExp = /[0-9]/; + private idCharRegex: RegExp = /[a-zA-Z]/; - constructor(input: CharacterStream | string) { - if (this.isCharacterStream(input)) { - this.stream = input; - } else if (typeof input === 'string') { - this.stream = new StringCharacterStream(input); - } else { - throw new Error('Unrecognized input type. input must be of type \'CharacterStream | string\'.'); - } + constructor(input: CharacterStream | string) { + if (this.isCharacterStream(input)) { + this.stream = input; + } else if (typeof input === 'string') { + this.stream = new StringCharacterStream(input); + } else { + throw new Error('Unrecognized input type. input must be of type \'CharacterStream | string\'.'); } + } - private isCharacterStream(input: any): input is CharacterStream { - return input.getCurrentCharacter; - } + private isCharacterStream(input: any): input is CharacterStream { + return input.getCurrentCharacter; + } - public peekNextToken(): Token { - if (!this.nextToken) { - this.nextToken = this.constructNextToken(); - } - return this.nextToken; + public peekNextToken(): Token { + if (!this.nextToken) { + this.nextToken = this.constructNextToken(); } + return this.nextToken; + } - public getNextToken(): Token { - if (this.nextToken) { - this.currentToken = this.nextToken; - this.nextToken = null; - } else { - this.currentToken = this.constructNextToken(); - } - return this.currentToken; + public getNextToken(): Token { + if (this.nextToken) { + this.currentToken = this.nextToken; + this.nextToken = null; + } else { + this.currentToken = this.constructNextToken(); } + return this.currentToken; + } - protected parseIdentifier(): Token { - let buffer = this.stream.getCurrentCharacter(); - while (this.idCharRegex.test(this.stream.peekNextCharacter())) { - buffer += this.stream.getNextCharacter(); - } - return this.createToken(TokenType.Identifier, buffer); + protected parseIdentifier(): Token { + let buffer = this.stream.getCurrentCharacter(); + // TODO: klnull?! + while (this.stream.peekNextCharacter() && this.idCharRegex.test(this.stream.peekNextCharacter())) { + buffer += this.stream.getNextCharacter(); } + return this.createToken(TokenType.Identifier, buffer); + } - protected parseNumber(): Token { - let buffer = this.stream.getCurrentCharacter(); - let hasDot = false; - let nextChar = this.stream.peekNextCharacter(); - while (nextChar === '.' || this.numCharRegex.test(nextChar)) { - if (nextChar === '.') { - if (hasDot) { break; } - hasDot = true; - } - buffer += this.stream.getNextCharacter(); - nextChar = this.stream.peekNextCharacter(); - } - return this.createToken(TokenType.Number, buffer); + protected parseNumber(): Token { + let buffer = this.stream.getCurrentCharacter(); + let hasDot = false; + let nextChar = this.stream.peekNextCharacter(); + while (nextChar === '.' || this.numCharRegex.test(nextChar)) { + if (nextChar === '.') { + if (hasDot) { break; } + hasDot = true; + } + buffer += this.stream.getNextCharacter(); + nextChar = this.stream.peekNextCharacter(); } + return this.createToken(TokenType.Number, buffer); + } - protected parseEllipsis(): Token { - for (let x = 0; x < 2; x++) { - if (this.stream.peekNextCharacter() !== '.') { - throw new Error('Missing period in ellipsis.'); - } - this.stream.getNextCharacter(); - } - return this.createToken(TokenType.Ellipsis, '...'); + protected parseEllipsis(): Token { + for (let x = 0; x < 2; x++) { + if (this.stream.peekNextCharacter() !== '.') { + throw new Error('Missing period in ellipsis.'); + } + this.stream.getNextCharacter(); } + return this.createToken(TokenType.Ellipsis, '...'); + } - private constructNextToken() { - let curChar: string; - while (curChar = this.stream.getNextCharacter()) { - switch (true) { - case this.idCharRegex.test(curChar): return this.parseIdentifier(); - case this.numCharRegex.test(curChar): return this.parseNumber(); - case curChar === '{': return this.createToken(TokenType.BraceOpen, curChar); - case curChar === '}': return this.createToken(TokenType.BraceClose, curChar); - case curChar === ',': return this.createToken(TokenType.Comma, curChar); - case curChar === '(': return this.createToken(TokenType.ParenthesisOpen, curChar); - case curChar === ')': return this.createToken(TokenType.ParenthesisClose, curChar); - case curChar === '=': return this.createToken(TokenType.Equals, curChar); - case curChar === '+': return this.createToken(TokenType.Plus, curChar); - case curChar === '/': return this.createToken(TokenType.Slash, curChar); - case curChar === '-': return this.createToken(TokenType.Minus, curChar); - case curChar === '%': return this.createToken(TokenType.Percent, curChar); - case curChar === '!': return this.createToken(TokenType.Exclamation, curChar); - case curChar === '.': return this.parseEllipsis(); - case curChar === '*': - if (this.stream.peekNextCharacter() === '*') { - this.stream.getNextCharacter(); - return this.createToken(TokenType.DoubleAsterisk, curChar + this.stream.getCurrentCharacter()); - } else { - return this.createToken(TokenType.Asterisk, curChar); - } - case curChar === '>': - if (this.stream.peekNextCharacter() === '=') { - this.stream.getNextCharacter(); - return this.createToken(TokenType.GreaterOrEqual, curChar + this.stream.getCurrentCharacter()); - } else { - return this.createToken(TokenType.Greater, curChar); - } - case curChar === '<': - if (this.stream.peekNextCharacter() === '=') { - this.stream.getNextCharacter(); - return this.createToken(TokenType.LessOrEqual, curChar + this.stream.getCurrentCharacter()); - } else { - return this.createToken(TokenType.Less, curChar); - } - case /\W/.test(curChar): - // Ignore whitespace. - break; - default: throw new Error(`Unknown token: '${curChar}'.`); - } - } - // Terminator at end of stream. - return this.createToken(TokenType.Terminator); + private constructNextToken() { + let curChar: string; + while (curChar = this.stream.getNextCharacter()) { + switch (true) { + case this.idCharRegex.test(curChar): return this.parseIdentifier(); + case this.numCharRegex.test(curChar): return this.parseNumber(); + case curChar === '{': return this.createToken(TokenType.BraceOpen, curChar); + case curChar === '}': return this.createToken(TokenType.BraceClose, curChar); + case curChar === ',': return this.createToken(TokenType.Comma, curChar); + case curChar === '(': return this.createToken(TokenType.ParenthesisOpen, curChar); + case curChar === ')': return this.createToken(TokenType.ParenthesisClose, curChar); + case curChar === '=': return this.createToken(TokenType.Equals, curChar); + case curChar === '+': return this.createToken(TokenType.Plus, curChar); + case curChar === '/': return this.createToken(TokenType.Slash, curChar); + case curChar === '-': return this.createToken(TokenType.Minus, curChar); + case curChar === '%': return this.createToken(TokenType.Percent, curChar); + case curChar === '!': return this.createToken(TokenType.Exclamation, curChar); + case curChar === '.': return this.parseEllipsis(); + case curChar === '*': + if (this.stream.peekNextCharacter() === '*') { + this.stream.getNextCharacter(); + return this.createToken(TokenType.DoubleAsterisk, curChar + this.stream.getCurrentCharacter()); + } else { + return this.createToken(TokenType.Asterisk, curChar); + } + case curChar === '>': + if (this.stream.peekNextCharacter() === '=') { + this.stream.getNextCharacter(); + return this.createToken(TokenType.GreaterOrEqual, curChar + this.stream.getCurrentCharacter()); + } else { + return this.createToken(TokenType.Greater, curChar); + } + case curChar === '<': + if (this.stream.peekNextCharacter() === '=') { + this.stream.getNextCharacter(); + return this.createToken(TokenType.LessOrEqual, curChar + this.stream.getCurrentCharacter()); + } else { + return this.createToken(TokenType.Less, curChar); + } + case /\W/.test(curChar): + // Ignore whitespace. + break; + default: throw new Error(`Unknown token: '${curChar}'.`); + } } + // Terminator at end of stream. + return this.createToken(TokenType.Terminator); + } - private createToken(type: TokenType, value?: string): Token { - let position = this.stream.getCurrentPosition(); - if (value) { position -= value.length - 1; } - return new Token(type, position, value); - } + private createToken(type: TokenType, value?: string): Token { + let position = this.stream.getCurrentPosition(); + if (value) { position -= value.length - 1; } + return new Token(type, position, value); + } } diff --git a/src/lexer/lexer.interface.ts b/src/lexer/lexer.interface.ts index b3ff165..a9ed408 100644 --- a/src/lexer/lexer.interface.ts +++ b/src/lexer/lexer.interface.ts @@ -1,6 +1,6 @@ import { Token } from './token.class'; export interface Lexer { - peekNextToken(): Token; - getNextToken(): Token; + peekNextToken(): Token; + getNextToken(): Token; } diff --git a/src/lexer/non-global-definition-error.class.ts b/src/lexer/non-global-definition-error.class.ts index 4f80224..127b412 100644 --- a/src/lexer/non-global-definition-error.class.ts +++ b/src/lexer/non-global-definition-error.class.ts @@ -1,5 +1,5 @@ export class NonGlobalDefinitionError extends Error { - constructor() { - super('TokenDefinition pattern RegExp must be global.'); - } + constructor() { + super('TokenDefinition pattern RegExp must be global.'); + } } diff --git a/src/lexer/string-character-stream.class.ts b/src/lexer/string-character-stream.class.ts index d7f6a81..8edf1f5 100644 --- a/src/lexer/string-character-stream.class.ts +++ b/src/lexer/string-character-stream.class.ts @@ -1,27 +1,27 @@ import { CharacterStream } from './character-stream.interface'; export class StringCharacterStream implements CharacterStream { - private index = -1; + private index = -1; - constructor(private readonly input: string) { } + constructor(private readonly input: string) { } - getCurrentPosition(): number { - return this.index; - } + getCurrentPosition(): number { + return this.index; + } - getNextCharacter(): string { - this.index = Math.min(this.index + 1, this.input.length); - if (this.index >= this.input.length) { return null; } - return this.input[this.index]; - } + getNextCharacter(): string { + this.index = Math.min(this.index + 1, this.input.length); + if (this.index >= this.input.length) { return null; } + return this.input[this.index]; + } - getCurrentCharacter(): string { - if (this.index < 0 || this.index >= this.input.length) { return null; } - return this.input[this.index]; - } + getCurrentCharacter(): string { + if (this.index < 0 || this.index >= this.input.length) { return null; } + return this.input[this.index]; + } - peekNextCharacter(): string { - if (this.index >= this.input.length) { return null; } - return this.input[this.index + 1]; - } + peekNextCharacter(): string { + if (this.index >= this.input.length) { return null; } + return this.input[this.index + 1]; + } } diff --git a/src/lexer/token-type.enum.ts b/src/lexer/token-type.enum.ts index 04e567b..31fcb58 100644 --- a/src/lexer/token-type.enum.ts +++ b/src/lexer/token-type.enum.ts @@ -1,23 +1,23 @@ export enum TokenType { - Equals = '=', - Greater = '>', - GreaterOrEqual = '>=', - Less = '<', - LessOrEqual = '<=', - BraceClose = '}', - BraceOpen = '{', - Comma = ',', - Ellipsis = '...', - Identifier = 'identifier', - Plus = '+', - Slash = '/', - DoubleAsterisk = '**', - Percent = '%', - Asterisk = '*', - Minus = '-', - Number = 'number', - ParenthesisClose = ')', - ParenthesisOpen = '(', - Terminator = 'terminator', - Exclamation = '!', + Equals = '=', + Greater = '>', + GreaterOrEqual = '>=', + Less = '<', + LessOrEqual = '<=', + BraceClose = '}', + BraceOpen = '{', + Comma = ',', + Ellipsis = '...', + Identifier = 'identifier', + Plus = '+', + Slash = '/', + DoubleAsterisk = '**', + Percent = '%', + Asterisk = '*', + Minus = '-', + Number = 'number', + ParenthesisClose = ')', + ParenthesisOpen = '(', + Terminator = 'terminator', + Exclamation = '!', } diff --git a/src/lexer/token.class.ts b/src/lexer/token.class.ts index 636a36f..8fbe375 100644 --- a/src/lexer/token.class.ts +++ b/src/lexer/token.class.ts @@ -1,5 +1,5 @@ import { TokenType } from './token-type.enum'; export class Token { - constructor(public type: TokenType, public position: number, public value?: string) { } + constructor(public type: TokenType, public position: number, public value?: string) { } } diff --git a/src/parser/basic-parser.class.ts b/src/parser/basic-parser.class.ts index d1fc94a..b3bb090 100644 --- a/src/parser/basic-parser.class.ts +++ b/src/parser/basic-parser.class.ts @@ -1,47 +1,47 @@ import { DiceLexer, Lexer, Token, TokenType } from '../lexer'; -import { ErrorMessage } from './error-message.class'; +import { ParserError } from './error-message.class'; import { ParseResult } from './parse-result.class'; import { Parser } from './parser.interface'; export abstract class BasicParser implements Parser { - protected readonly lexer: Lexer; - - constructor(input: Lexer | string) { - if (this.isLexer(input)) { - this.lexer = input; - } else if (typeof input === 'string') { - this.lexer = new DiceLexer(input); - } else { - throw new Error('Unrecognized input type. input must be of type \'Lexer | string\'.'); - } + protected readonly lexer: Lexer; + + constructor(input: Lexer | string) { + if (this.isLexer(input)) { + this.lexer = input; + } else if (typeof input === 'string') { + this.lexer = new DiceLexer(input); + } else { + throw new Error('Unrecognized input type. input must be of type \'Lexer | string\'.'); } + } - private isLexer(input: any): input is Lexer { - return input.getNextToken; - } - - abstract parse(): ParseResult; - - protected expectAndConsume(result: ParseResult, expected: TokenType, actual?: Token): Token { - this.expect(result, expected, actual); - return this.lexer.getNextToken(); - } + private isLexer(input: any): input is Lexer { + return input.getNextToken; + } - protected expect(result: ParseResult, expected: TokenType, actual?: Token): Token { - actual = actual || this.lexer.peekNextToken(); - if (actual.type !== expected) { - this.errorToken(result, expected, actual); - } - return actual; - } + abstract parse(): ParseResult; - protected errorToken(result: ParseResult, expected: TokenType, actual: Token) { - let message = `Error at position ${actual.position}.`; - message += ` Expected token of type ${expected}, found token of type ${actual.type} of value "${actual.value}".`; - this.errorMessage(result, message, actual); - } + protected expectAndConsume(result: ParseResult, expected: TokenType, actual?: Token): Token { + this.expect(result, expected, actual); + return this.lexer.getNextToken(); + } - protected errorMessage(result: ParseResult, message: string, token: Token) { - result.errors.push(new ErrorMessage(message, token, new Error().stack)); + protected expect(result: ParseResult, expected: TokenType, actual?: Token): Token { + actual = actual || this.lexer.peekNextToken(); + if (actual.type !== expected) { + this.errorToken(result, expected, actual); } + return actual; + } + + protected errorToken(result: ParseResult, expected: TokenType, actual: Token) { + let message = `Error at position ${actual.position}.`; + message += ` Expected token of type ${expected}, found token of type ${actual.type} of value "${actual.value}".`; + this.errorMessage(result, message, actual); + } + + protected errorMessage(result: ParseResult, message: string, token: Token) { + result.errors.push(new ParserError(message, token, new Error().stack)); + } } diff --git a/src/parser/dice-parser.class.ts b/src/parser/dice-parser.class.ts index 240f1a8..643322e 100644 --- a/src/parser/dice-parser.class.ts +++ b/src/parser/dice-parser.class.ts @@ -21,406 +21,408 @@ MultiOperatorMap[TokenType.Slash] = Ast.NodeType.Divide; MultiOperatorMap[TokenType.Percent] = Ast.NodeType.Modulo; export class DiceParser extends BasicParser { - constructor(input: Lexer | string) { super(input); } - - parse(): ParseResult { - const result = new ParseResult(); - result.root = this.parseExpression(result); - return result; + constructor(input: Lexer | string) { super(input); } + + parse(): ParseResult { + const result = new ParseResult(); + result.root = this.parseExpression(result); + return result; + } + + parseExpression(result: ParseResult): Ast.ExpressionNode { + let root = this.parseSimpleExpression(result); + const tokenType = this.lexer.peekNextToken().type; + if (Object.keys(BooleanOperatorMap).indexOf(tokenType.toString()) > -1) { + const newRoot = Ast.Factory.create(BooleanOperatorMap[tokenType]); + this.lexer.getNextToken(); + newRoot.addChild(root); + newRoot.addChild(this.parseSimpleExpression(result)); + root = newRoot; } + return root; + } - parseExpression(result: ParseResult): Ast.ExpressionNode { - let root = this.parseSimpleExpression(result); - const tokenType = this.lexer.peekNextToken().type; - if (Object.keys(BooleanOperatorMap).indexOf(tokenType.toString()) > -1) { - const newRoot = Ast.Factory.create(BooleanOperatorMap[tokenType]); - this.lexer.getNextToken(); - newRoot.addChild(root); - newRoot.addChild(this.parseSimpleExpression(result)); - root = newRoot; - } - return root; + parseSimpleExpression(result: ParseResult): Ast.ExpressionNode { + let tokenType = this.lexer.peekNextToken().type; + if (Object.keys(AddOperatorMap).indexOf(tokenType.toString()) > -1) { + this.lexer.getNextToken(); } - parseSimpleExpression(result: ParseResult): Ast.ExpressionNode { - let tokenType = this.lexer.peekNextToken().type; - if (Object.keys(AddOperatorMap).indexOf(tokenType.toString()) > -1) { - this.lexer.getNextToken(); - } - - let root = this.parseTerm(result); + let root = this.parseTerm(result); - if (tokenType === TokenType.Minus) { - const negateNode = Ast.Factory.create(Ast.NodeType.Negate); - negateNode.addChild(root); - root = negateNode; - } + if (tokenType === TokenType.Minus) { + const negateNode = Ast.Factory.create(Ast.NodeType.Negate); + negateNode.addChild(root); + root = negateNode; + } - tokenType = this.lexer.peekNextToken().type; - while (Object.keys(AddOperatorMap).indexOf(tokenType.toString()) > -1) { - const newRoot = Ast.Factory.create(AddOperatorMap[tokenType]); - newRoot.addChild(root); + tokenType = this.lexer.peekNextToken().type; + while (Object.keys(AddOperatorMap).indexOf(tokenType.toString()) > -1) { + const newRoot = Ast.Factory.create(AddOperatorMap[tokenType]); + newRoot.addChild(root); - // Consume the operator. - this.lexer.getNextToken(); + // Consume the operator. + this.lexer.getNextToken(); - newRoot.addChild(this.parseTerm(result)); + newRoot.addChild(this.parseTerm(result)); - root = newRoot; - tokenType = this.lexer.peekNextToken().type; - } - return root; + root = newRoot; + tokenType = this.lexer.peekNextToken().type; } + return root; + } - parseTerm(result: ParseResult): Ast.ExpressionNode { - let root: Ast.ExpressionNode = this.parseFactor(result); + parseTerm(result: ParseResult): Ast.ExpressionNode { + let root: Ast.ExpressionNode = this.parseFactor(result); - let tokenType = this.lexer.peekNextToken().type; - while (Object.keys(MultiOperatorMap).indexOf(tokenType.toString()) > -1) { - const newRoot = Ast.Factory.create(MultiOperatorMap[tokenType]); - newRoot.addChild(root); + let tokenType = this.lexer.peekNextToken().type; + while (Object.keys(MultiOperatorMap).indexOf(tokenType.toString()) > -1) { + const newRoot = Ast.Factory.create(MultiOperatorMap[tokenType]); + newRoot.addChild(root); - // Consume the operator. - this.lexer.getNextToken(); - newRoot.addChild(this.parseFactor(result)); + // Consume the operator. + this.lexer.getNextToken(); + newRoot.addChild(this.parseFactor(result)); - root = newRoot; - tokenType = this.lexer.peekNextToken().type; - } - - return root; + root = newRoot; + tokenType = this.lexer.peekNextToken().type; } - parseFactor(result: ParseResult): Ast.ExpressionNode { - let root: Ast.ExpressionNode; - const token = this.lexer.peekNextToken(); - switch (token.type) { - case TokenType.Identifier: - root = this.parseFunction(result); - break; - case TokenType.ParenthesisOpen: - root = this.parseBracketedExpression(result); - if (this.lexer.peekNextToken().type === TokenType.Identifier) { - root = this.parseDiceRoll(result, root); - } - break; - case TokenType.BraceOpen: - root = this.parseGroup(result); - break; - case TokenType.Number: - const number = this.parseNumber(result); - if (this.lexer.peekNextToken().type !== TokenType.Identifier) { - root = number; - } else { - root = this.parseDiceRoll(result, number); - } - break; - default: this.errorToken(result, TokenType.Number, token); + return root; + } + + parseFactor(result: ParseResult): Ast.ExpressionNode { + let root: Ast.ExpressionNode; + const token = this.lexer.peekNextToken(); + switch (token.type) { + case TokenType.Identifier: + root = this.parseFunction(result); + break; + case TokenType.ParenthesisOpen: + root = this.parseBracketedExpression(result); + if (this.lexer.peekNextToken().type === TokenType.Identifier) { + root = this.parseDice(result, root); } - return root; - } - - parseSimpleFactor(result: ParseResult): Ast.ExpressionNode { - const token = this.lexer.peekNextToken(); - switch (token.type) { - case TokenType.Number: return this.parseNumber(result); - case TokenType.ParenthesisOpen: return this.parseBracketedExpression(result); - default: this.errorToken(result, TokenType.Number, token); - } - } - - parseFunction(result: ParseResult): Ast.ExpressionNode { - const functionName = this.expectAndConsume(result, TokenType.Identifier); - const root = Ast.Factory.create(Ast.NodeType.Function) - .setAttribute('name', functionName.value); - - this.expectAndConsume(result, TokenType.ParenthesisOpen); - - // Parse function arguments. - const token = this.lexer.peekNextToken(); - if (token.type !== TokenType.ParenthesisClose) { - root.addChild(this.parseExpression(result)); - while (this.lexer.peekNextToken().type === TokenType.Comma) { - this.lexer.getNextToken(); // Consume the comma. - root.addChild(this.parseExpression(result)); - } + break; + case TokenType.BraceOpen: + root = this.parseGroup(result); + break; + case TokenType.Number: + const number = this.parseNumber(result); + if (this.lexer.peekNextToken().type !== TokenType.Identifier) { + root = number; + } else { + root = this.parseDice(result, number); } - - this.expectAndConsume(result, TokenType.ParenthesisClose); - - return root; + break; + default: this.errorToken(result, TokenType.Number, token); } - - parseNumber(result: ParseResult): Ast.ExpressionNode { - const numberToken = this.lexer.getNextToken(); - return Ast.Factory.create(Ast.NodeType.Number) - .setAttribute('value', Number(numberToken.value)); + return root; + } + + parseSimpleFactor(result: ParseResult): Ast.ExpressionNode { + const token = this.lexer.peekNextToken(); + switch (token.type) { + case TokenType.Number: return this.parseNumber(result); + case TokenType.ParenthesisOpen: return this.parseBracketedExpression(result); + default: this.errorToken(result, TokenType.Number, token); } + } - parseBracketedExpression(result: ParseResult): Ast.ExpressionNode { - this.lexer.getNextToken(); // Consume the opening bracket. - const root = this.parseExpression(result); - this.expectAndConsume(result, TokenType.ParenthesisClose); - return root; - } + parseFunction(result: ParseResult): Ast.ExpressionNode { + const functionName = this.expectAndConsume(result, TokenType.Identifier); + const root = Ast.Factory.create(Ast.NodeType.Function) + .setAttribute('name', functionName.value); - parseGroup(result: ParseResult): Ast.ExpressionNode { - this.lexer.getNextToken(); // Consume the opening brace. - const root = Ast.Factory.create(Ast.NodeType.Group); - - // Parse group elements. - const token = this.lexer.peekNextToken(); - if (token.type !== TokenType.BraceClose) { - do { - if (this.lexer.peekNextToken().type === TokenType.Comma) { - this.lexer.getNextToken(); // Consume the comma. - } - let exp = this.parseExpression(result); - if (this.lexer.peekNextToken().type === TokenType.Ellipsis) { - exp = this.parseRepeat(result, exp); - } - root.addChild(exp); - } while (this.lexer.peekNextToken().type === TokenType.Comma); - } + this.expectAndConsume(result, TokenType.ParenthesisOpen); - this.expectAndConsume(result, TokenType.BraceClose); - return this.parseGroupModifiers(result, root); - } - - parseRepeat(result: ParseResult, lhs: Ast.ExpressionNode): Ast.ExpressionNode { - this.lexer.getNextToken(); // Consume the ellipsis. - const root = Ast.Factory.create(Ast.NodeType.Repeat); - root.addChild(lhs); + // Parse function arguments. + const token = this.lexer.peekNextToken(); + if (token.type !== TokenType.ParenthesisClose) { + root.addChild(this.parseExpression(result)); + while (this.lexer.peekNextToken().type === TokenType.Comma) { + this.lexer.getNextToken(); // Consume the comma. root.addChild(this.parseExpression(result)); - return root; - } - - parseDice(result: ParseResult, rollTimes?: Ast.ExpressionNode): Ast.ExpressionNode { - let root = this.parseDiceRoll(result, rollTimes); - root = this.parseDiceModifiers(result, root); - return root; + } } - parseDiceRoll(result: ParseResult, rollTimes?: Ast.ExpressionNode): Ast.ExpressionNode { - if (!rollTimes) { rollTimes = this.parseSimpleFactor(result); } - const token = this.expectAndConsume(result, TokenType.Identifier); - - const root = Ast.Factory.create(Ast.NodeType.Dice); - root.addChild(rollTimes); - - switch (token.value) { - case 'd': - const sidesToken = this.expectAndConsume(result, TokenType.Number); - root.addChild(Ast.Factory.create(Ast.NodeType.DiceSides)) - .setAttribute('value', Number(sidesToken.value)); - break; - case 'dF': - root.addChild(Ast.Factory.create(Ast.NodeType.DiceSides)) - .setAttribute('value', 'fate'); - break; + this.expectAndConsume(result, TokenType.ParenthesisClose); + + return root; + } + + parseNumber(result: ParseResult): Ast.ExpressionNode { + const numberToken = this.lexer.getNextToken(); + return Ast.Factory.create(Ast.NodeType.Number) + .setAttribute('value', Number(numberToken.value)); + } + + parseBracketedExpression(result: ParseResult): Ast.ExpressionNode { + this.lexer.getNextToken(); // Consume the opening bracket. + const root = this.parseExpression(result); + this.expectAndConsume(result, TokenType.ParenthesisClose); + return root; + } + + parseGroup(result: ParseResult): Ast.ExpressionNode { + this.lexer.getNextToken(); // Consume the opening brace. + const root = Ast.Factory.create(Ast.NodeType.Group); + + // Parse group elements. + const token = this.lexer.peekNextToken(); + if (token.type !== TokenType.BraceClose) { + do { + if (this.lexer.peekNextToken().type === TokenType.Comma) { + this.lexer.getNextToken(); // Consume the comma. } - - return root; + let exp = this.parseExpression(result); + if (this.lexer.peekNextToken().type === TokenType.Ellipsis) { + exp = this.parseRepeat(result, exp); + } + root.addChild(exp); + } while (this.lexer.peekNextToken().type === TokenType.Comma); } - parseExplode(result: ParseResult, lhs?: Ast.ExpressionNode): Ast.ExpressionNode { - const root = Ast.Factory.create(Ast.NodeType.Explode); - root.setAttribute('compound', false); - root.setAttribute('penetrate', false); + this.expectAndConsume(result, TokenType.BraceClose); + return this.parseGroupModifiers(result, root); + } + + parseRepeat(result: ParseResult, lhs: Ast.ExpressionNode): Ast.ExpressionNode { + this.lexer.getNextToken(); // Consume the ellipsis. + const root = Ast.Factory.create(Ast.NodeType.Repeat); + root.addChild(lhs); + root.addChild(this.parseExpression(result)); + return root; + } + + parseDice(result: ParseResult, rollTimes?: Ast.ExpressionNode): Ast.ExpressionNode { + let root = this.parseDiceRoll(result, rollTimes); + root = this.parseDiceModifiers(result, root); + return root; + } + + parseDiceRoll(result: ParseResult, rollTimes?: Ast.ExpressionNode): Ast.ExpressionNode { + if (!rollTimes) { rollTimes = this.parseSimpleFactor(result); } + const token = this.expectAndConsume(result, TokenType.Identifier); + + const root = Ast.Factory.create(Ast.NodeType.Dice); + root.addChild(rollTimes); + + switch (token.value) { + case 'd': + const sidesToken = this.expectAndConsume(result, TokenType.Number); + root.addChild(Ast.Factory.create(Ast.NodeType.DiceSides)) + .setAttribute('value', Number(sidesToken.value)); + break; + case 'dF': + root.addChild(Ast.Factory.create(Ast.NodeType.DiceSides)) + .setAttribute('value', 'fate'); + break; + } - if (lhs) { root.addChild(lhs); } + return root; + } - this.lexer.getNextToken(); + parseExplode(result: ParseResult, lhs?: Ast.ExpressionNode): Ast.ExpressionNode { + const root = Ast.Factory.create(Ast.NodeType.Explode); + root.setAttribute('compound', false); + root.setAttribute('penetrate', false); - let token = this.lexer.peekNextToken(); - if (token.type === TokenType.Exclamation) { - root.setAttribute('compound', true); - this.lexer.getNextToken(); // Consume second !. - } + if (lhs) { root.addChild(lhs); } - token = this.lexer.peekNextToken(); - if (token.type === TokenType.Identifier) { - if (token.value === 'p') { - root.setAttribute('penetrate', true); - } - this.lexer.getNextToken(); // Consume p. - } + this.lexer.getNextToken(); - const tokenType = this.lexer.peekNextToken().type; - if (Object.keys(BooleanOperatorMap).indexOf(tokenType.toString()) > -1) { - root.addChild(this.parseCompareModifier(result)); - } - return root; + let token = this.lexer.peekNextToken(); + if (token.type === TokenType.Exclamation) { + root.setAttribute('compound', true); + this.lexer.getNextToken(); // Consume second !. } - parseCritical(result: ParseResult, lhs?: Ast.ExpressionNode): Ast.ExpressionNode { - const root = Ast.Factory.create(Ast.NodeType.Critical); - root.setAttribute('type', 'success'); - if (lhs) { root.addChild(lhs); } - - const token = this.lexer.peekNextToken(); - if (token.type === TokenType.Identifier) { - switch (token.value) { - case 'c': root.setAttribute('type', 'success'); break; - case 'cs': root.setAttribute('type', 'success'); break; - case 'cf': root.setAttribute('type', 'failure'); break; - default: this.errorMessage(result, `Unknown critical type ${token.value}. Must be (c|cs|cf).`, token); - } - } - - this.lexer.getNextToken(); - - const tokenType = this.lexer.peekNextToken().type; - if (Object.keys(BooleanOperatorMap).indexOf(tokenType.toString()) > -1) { - root.addChild(this.parseCompareModifier(result)); - } - return root; + token = this.lexer.peekNextToken(); + if (token.type === TokenType.Identifier) { + if (token.value === 'p') { + root.setAttribute('penetrate', true); + } + this.lexer.getNextToken(); // Consume p. } - parseKeep(result: ParseResult, lhs?: Ast.ExpressionNode): Ast.ExpressionNode { - const root = Ast.Factory.create(Ast.NodeType.Keep); - root.setAttribute('type', 'highest'); - if (lhs) { root.addChild(lhs); } - - const token = this.lexer.peekNextToken(); - if (token.type === TokenType.Identifier) { - switch (token.value) { - case 'k': root.setAttribute('type', 'highest'); break; - case 'kh': root.setAttribute('type', 'highest'); break; - case 'kl': root.setAttribute('type', 'lowest'); break; - default: this.errorMessage(result, `Unknown keep type ${token.value}. Must be (k|kh|kl).`, token); - } - } - - this.lexer.getNextToken(); // Consume. - - const tokenType = this.lexer.peekNextToken().type; - if (tokenType === TokenType.Number || tokenType === TokenType.ParenthesisOpen) { - root.addChild(this.parseSimpleFactor(result)); - } - return root; + const tokenType = this.lexer.peekNextToken().type; + if (Object.keys(BooleanOperatorMap).indexOf(tokenType.toString()) > -1) { + root.addChild(this.parseCompareModifier(result)); + } + return root; + } + + parseCritical(result: ParseResult, lhs?: Ast.ExpressionNode): Ast.ExpressionNode { + const root = Ast.Factory.create(Ast.NodeType.Critical); + root.setAttribute('type', 'success'); + if (lhs) { root.addChild(lhs); } + + const token = this.lexer.peekNextToken(); + if (token.type === TokenType.Identifier) { + switch (token.value) { + case 'c': root.setAttribute('type', 'success'); break; + case 'cs': root.setAttribute('type', 'success'); break; + case 'cf': root.setAttribute('type', 'failure'); break; + default: this.errorMessage(result, `Unknown critical type ${token.value}. Must be (c|cs|cf).`, token); + } } - parseDrop(result: ParseResult, lhs?: Ast.ExpressionNode): Ast.ExpressionNode { - const root = Ast.Factory.create(Ast.NodeType.Drop); - root.setAttribute('type', 'lowest'); - if (lhs) { root.addChild(lhs); } - - const token = this.lexer.peekNextToken(); - if (token.type === TokenType.Identifier) { - switch (token.value) { - case 'd': root.setAttribute('type', 'lowest'); break; - case 'dh': root.setAttribute('type', 'highest'); break; - case 'dl': root.setAttribute('type', 'lowest'); break; - default: this.errorMessage(result, `Unknown drop type ${token.value}. Must be (d|dh|dl).`, token); - } - } - - this.lexer.getNextToken(); // Consume. + this.lexer.getNextToken(); - const tokenType = this.lexer.peekNextToken().type; - if (tokenType === TokenType.Number || tokenType === TokenType.ParenthesisOpen) { - root.addChild(this.parseSimpleFactor(result)); - } - return root; + const tokenType = this.lexer.peekNextToken().type; + if (Object.keys(BooleanOperatorMap).indexOf(tokenType.toString()) > -1) { + root.addChild(this.parseCompareModifier(result)); + } + return root; + } + + parseKeep(result: ParseResult, lhs?: Ast.ExpressionNode): Ast.ExpressionNode { + const root = Ast.Factory.create(Ast.NodeType.Keep); + root.setAttribute('type', 'highest'); + if (lhs) { root.addChild(lhs); } + + const token = this.lexer.peekNextToken(); + if (token.type === TokenType.Identifier) { + switch (token.value) { + case 'k': root.setAttribute('type', 'highest'); break; + case 'kh': root.setAttribute('type', 'highest'); break; + case 'kl': root.setAttribute('type', 'lowest'); break; + default: this.errorMessage(result, `Unknown keep type ${token.value}. Must be (k|kh|kl).`, token); + } } - parseReroll(result: ParseResult, lhs?: Ast.ExpressionNode): Ast.ExpressionNode { - const root = Ast.Factory.create(Ast.NodeType.Reroll); - root.setAttribute('once', false); - if (lhs) { root.addChild(lhs); } - - const token = this.lexer.peekNextToken(); - if (token.type === TokenType.Identifier) { - switch (token.value) { - case 'r': root.setAttribute('once', false); break; - case 'ro': root.setAttribute('once', true); break; - default: this.errorMessage(result, `Unknown drop type ${token.value}. Must be (r|ro).`, token); - } - } - this.lexer.getNextToken(); // Consume. + this.lexer.getNextToken(); // Consume. - const tokenType = this.lexer.peekNextToken().type; - if (Object.keys(BooleanOperatorMap).indexOf(tokenType.toString()) > -1) { - root.addChild(this.parseCompareModifier(result)); - } - return root; + const tokenType = this.lexer.peekNextToken().type; + if (tokenType === TokenType.Number || tokenType === TokenType.ParenthesisOpen) { + root.addChild(this.parseSimpleFactor(result)); + } else { + root.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 1)); + } + return root; + } + + parseDrop(result: ParseResult, lhs?: Ast.ExpressionNode): Ast.ExpressionNode { + const root = Ast.Factory.create(Ast.NodeType.Drop); + root.setAttribute('type', 'lowest'); + if (lhs) { root.addChild(lhs); } + + const token = this.lexer.peekNextToken(); + if (token.type === TokenType.Identifier) { + switch (token.value) { + case 'd': root.setAttribute('type', 'lowest'); break; + case 'dh': root.setAttribute('type', 'highest'); break; + case 'dl': root.setAttribute('type', 'lowest'); break; + default: this.errorMessage(result, `Unknown drop type ${token.value}. Must be (d|dh|dl).`, token); + } } - parseSort(result: ParseResult, lhs?: Ast.ExpressionNode): Ast.ExpressionNode { - const root = Ast.Factory.create(Ast.NodeType.Sort); - root.setAttribute('direction', 'ascending'); - if (lhs) { root.addChild(lhs); } - - const token = this.lexer.peekNextToken(); - if (token.type === TokenType.Identifier) { - switch (token.value) { - case 's': root.setAttribute('direction', 'ascending'); break; - case 'sa': root.setAttribute('direction', 'ascending'); break; - case 'sd': root.setAttribute('direction', 'descending'); break; - default: this.errorMessage(result, `Unknown sort type ${token.value}. Must be (s|sa|sd).`, token); - } - } - this.lexer.getNextToken(); // Consume. + this.lexer.getNextToken(); // Consume. - return root; + const tokenType = this.lexer.peekNextToken().type; + if (tokenType === TokenType.Number || tokenType === TokenType.ParenthesisOpen) { + root.addChild(this.parseSimpleFactor(result)); } - - parseCompareModifier(result: ParseResult, lhs?: Ast.ExpressionNode): Ast.ExpressionNode { - const token = this.lexer.peekNextToken(); - let root: Ast.ExpressionNode; - if (token.type === TokenType.Number) { - root = Ast.Factory.create(Ast.NodeType.Equal); - } else if (Object.keys(BooleanOperatorMap).indexOf(token.type.toString()) > -1) { - root = Ast.Factory.create(BooleanOperatorMap[token.type]); - this.lexer.getNextToken(); - } else { - this.errorToken(result, TokenType.Number, token); - } - if (lhs) { root.addChild(lhs); } - root.addChild(this.parseSimpleFactor(result)); - return root; + return root; + } + + parseReroll(result: ParseResult, lhs?: Ast.ExpressionNode): Ast.ExpressionNode { + const root = Ast.Factory.create(Ast.NodeType.Reroll); + root.setAttribute('once', false); + if (lhs) { root.addChild(lhs); } + + const token = this.lexer.peekNextToken(); + if (token.type === TokenType.Identifier) { + switch (token.value) { + case 'r': root.setAttribute('once', false); break; + case 'ro': root.setAttribute('once', true); break; + default: this.errorMessage(result, `Unknown drop type ${token.value}. Must be (r|ro).`, token); + } } + this.lexer.getNextToken(); // Consume. - private parseDiceModifiers(result: ParseResult, root: Ast.ExpressionNode) { - while (true) { - const token = this.lexer.peekNextToken(); - if (Object.keys(BooleanOperatorMap).indexOf(token.type.toString()) > -1) { - root = this.parseCompareModifier(result, root); - } else if (token.type === TokenType.Identifier) { - switch (token.value[0]) { - case 'c': root = this.parseCritical(result, root); break; - case 'd': root = this.parseDrop(result, root); break; - case 'k': root = this.parseKeep(result, root); break; - case 'r': root = this.parseReroll(result, root); break; - case 's': root = this.parseSort(result, root); break; - default: - this.errorToken(result, TokenType.Identifier, token); - return root; - } - } else if (token.type === TokenType.Exclamation) { - root = this.parseExplode(result, root); - } else { break; } + const tokenType = this.lexer.peekNextToken().type; + if (Object.keys(BooleanOperatorMap).indexOf(tokenType.toString()) > -1) { + root.addChild(this.parseCompareModifier(result)); + } + return root; + } + + parseSort(result: ParseResult, lhs?: Ast.ExpressionNode): Ast.ExpressionNode { + const root = Ast.Factory.create(Ast.NodeType.Sort); + root.setAttribute('direction', 'ascending'); + if (lhs) { root.addChild(lhs); } + + const token = this.lexer.peekNextToken(); + if (token.type === TokenType.Identifier) { + switch (token.value) { + case 's': root.setAttribute('direction', 'ascending'); break; + case 'sa': root.setAttribute('direction', 'ascending'); break; + case 'sd': root.setAttribute('direction', 'descending'); break; + default: this.errorMessage(result, `Unknown sort type ${token.value}. Must be (s|sa|sd).`, token); + } + } + this.lexer.getNextToken(); // Consume. + + return root; + } + + parseCompareModifier(result: ParseResult, lhs?: Ast.ExpressionNode): Ast.ExpressionNode { + const token = this.lexer.peekNextToken(); + let root: Ast.ExpressionNode; + if (token.type === TokenType.Number) { + root = Ast.Factory.create(Ast.NodeType.Equal); + } else if (Object.keys(BooleanOperatorMap).indexOf(token.type.toString()) > -1) { + root = Ast.Factory.create(BooleanOperatorMap[token.type]); + this.lexer.getNextToken(); + } else { + this.errorToken(result, TokenType.Number, token); + } + if (lhs) { root.addChild(lhs); } + root.addChild(this.parseSimpleFactor(result)); + return root; + } + + private parseDiceModifiers(result: ParseResult, root: Ast.ExpressionNode) { + while (true) { + const token = this.lexer.peekNextToken(); + if (Object.keys(BooleanOperatorMap).indexOf(token.type.toString()) > -1) { + root = this.parseCompareModifier(result, root); + } else if (token.type === TokenType.Identifier) { + switch (token.value[0]) { + case 'c': root = this.parseCritical(result, root); break; + case 'd': root = this.parseDrop(result, root); break; + case 'k': root = this.parseKeep(result, root); break; + case 'r': root = this.parseReroll(result, root); break; + case 's': root = this.parseSort(result, root); break; + default: + this.errorToken(result, TokenType.Identifier, token); + return root; } - return root; + } else if (token.type === TokenType.Exclamation) { + root = this.parseExplode(result, root); + } else { break; } } - - private parseGroupModifiers(result: ParseResult, root: Ast.ExpressionNode) { - while (true) { - const token = this.lexer.peekNextToken(); - if (Object.keys(BooleanOperatorMap).indexOf(token.type.toString()) > -1) { - root = this.parseCompareModifier(result, root); - } else if (token.type === TokenType.Identifier) { - switch (token.value[0]) { - case 'd': root = this.parseDrop(result, root); break; - case 'k': root = this.parseKeep(result, root); break; - case 's': root = this.parseSort(result, root); break; - default: - this.errorToken(result, TokenType.Identifier, token); - return root; - } - } else { break; } + return root; + } + + private parseGroupModifiers(result: ParseResult, root: Ast.ExpressionNode) { + while (true) { + const token = this.lexer.peekNextToken(); + if (Object.keys(BooleanOperatorMap).indexOf(token.type.toString()) > -1) { + root = this.parseCompareModifier(result, root); + } else if (token.type === TokenType.Identifier) { + switch (token.value[0]) { + case 'd': root = this.parseDrop(result, root); break; + case 'k': root = this.parseKeep(result, root); break; + case 's': root = this.parseSort(result, root); break; + default: + this.errorToken(result, TokenType.Identifier, token); + return root; } - return root; + } else { break; } } + return root; + } } diff --git a/src/parser/error-message.class.ts b/src/parser/error-message.class.ts index ba85698..048133d 100644 --- a/src/parser/error-message.class.ts +++ b/src/parser/error-message.class.ts @@ -1,5 +1,5 @@ import { Token } from '../lexer'; -export class ErrorMessage { - constructor(public message: string, token: Token, stackTrace: string) { } +export class ParserError { + constructor(public message: string, public token: Token, public stackTrace: string) { } } diff --git a/src/parser/parse-result.class.ts b/src/parser/parse-result.class.ts index ba5783d..0a7bf62 100644 --- a/src/parser/parse-result.class.ts +++ b/src/parser/parse-result.class.ts @@ -1,7 +1,7 @@ import * as Ast from '../ast'; -import { ErrorMessage } from './error-message.class'; +import { ParserError } from './error-message.class'; export class ParseResult { - root: Ast.ExpressionNode; - errors: ErrorMessage[] = []; + root: Ast.ExpressionNode; + errors: ParserError[] = []; } diff --git a/src/parser/parser.interface.ts b/src/parser/parser.interface.ts index bec1a43..fc955aa 100644 --- a/src/parser/parser.interface.ts +++ b/src/parser/parser.interface.ts @@ -1,5 +1,5 @@ import { ParseResult } from './parse-result.class'; export interface Parser { - parse(): ParseResult; + parse(): ParseResult; } diff --git a/src/random/default-random-provider.class.ts b/src/random/default-random-provider.class.ts index 22038ce..2aa8986 100644 --- a/src/random/default-random-provider.class.ts +++ b/src/random/default-random-provider.class.ts @@ -4,13 +4,13 @@ import { RandomProvider } from './random-provider.class'; export class DefaultRandomProvider implements RandomProvider { - private random: Random; + private random: Random; - constructor() { - this.random = new Random(Random.engines.mt19937().autoSeed()); - } + constructor() { + this.random = new Random(Random.engines.mt19937().autoSeed()); + } - numberBetween(min: number, max: number) { - return this.random.integer(min, max); - } + numberBetween(min: number, max: number) { + return this.random.integer(min, max); + } } diff --git a/src/random/random-provider.class.ts b/src/random/random-provider.class.ts index b6598af..21f1f4c 100644 --- a/src/random/random-provider.class.ts +++ b/src/random/random-provider.class.ts @@ -1,3 +1,3 @@ export interface RandomProvider { - numberBetween(min: number, max: number): number; + numberBetween(min: number, max: number): number; }