dice-typescript
Version:
A TypeScript library for parsing dice rolling expressions, most commonly used in tabletop RPGs.
465 lines • 20.3 kB
JavaScript
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
}
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var Ast = require("../ast");
var lexer_1 = require("../lexer");
var basic_parser_class_1 = require("./basic-parser.class");
var parse_result_class_1 = require("./parse-result.class");
var BooleanOperatorMap = {};
BooleanOperatorMap[lexer_1.TokenType.Equals] = Ast.NodeType.Equal;
BooleanOperatorMap[lexer_1.TokenType.Greater] = Ast.NodeType.Greater;
BooleanOperatorMap[lexer_1.TokenType.Less] = Ast.NodeType.Less;
BooleanOperatorMap[lexer_1.TokenType.GreaterOrEqual] = Ast.NodeType.GreaterOrEqual;
BooleanOperatorMap[lexer_1.TokenType.LessOrEqual] = Ast.NodeType.LessOrEqual;
var AddOperatorMap = {};
AddOperatorMap[lexer_1.TokenType.Plus] = Ast.NodeType.Add;
AddOperatorMap[lexer_1.TokenType.Minus] = Ast.NodeType.Subtract;
var MultiOperatorMap = {};
MultiOperatorMap[lexer_1.TokenType.DoubleAsterisk] = Ast.NodeType.Exponent;
MultiOperatorMap[lexer_1.TokenType.Asterisk] = Ast.NodeType.Multiply;
MultiOperatorMap[lexer_1.TokenType.Slash] = Ast.NodeType.Divide;
MultiOperatorMap[lexer_1.TokenType.Percent] = Ast.NodeType.Modulo;
var DiceParser = /** @class */ (function (_super) {
__extends(DiceParser, _super);
function DiceParser(input) {
return _super.call(this, input) || this;
}
DiceParser.prototype.parse = function () {
var result = new parse_result_class_1.ParseResult();
result.root = this.parseExpression(result);
return result;
};
DiceParser.prototype.parseExpression = function (result) {
var root = this.parseSimpleExpression(result);
var tokenType = this.lexer.peekNextToken().type;
if (Object.keys(BooleanOperatorMap).indexOf(tokenType.toString()) > -1) {
var newRoot = Ast.Factory.create(BooleanOperatorMap[tokenType]);
this.lexer.getNextToken();
newRoot.addChild(root);
newRoot.addChild(this.parseSimpleExpression(result));
root = newRoot;
}
return root;
};
DiceParser.prototype.parseSimpleExpression = function (result) {
var tokenType = this.lexer.peekNextToken().type;
if (Object.keys(AddOperatorMap).indexOf(tokenType.toString()) > -1) {
this.lexer.getNextToken();
}
var root = this.parseTerm(result);
if (tokenType === lexer_1.TokenType.Minus) {
var 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) {
var newRoot = Ast.Factory.create(AddOperatorMap[tokenType]);
newRoot.addChild(root);
// Consume the operator.
this.lexer.getNextToken();
newRoot.addChild(this.parseTerm(result));
root = newRoot;
tokenType = this.lexer.peekNextToken().type;
}
return root;
};
DiceParser.prototype.parseTerm = function (result) {
var root = this.parseFactor(result);
var tokenType = this.lexer.peekNextToken().type;
while (Object.keys(MultiOperatorMap).indexOf(tokenType.toString()) > -1) {
var newRoot = Ast.Factory.create(MultiOperatorMap[tokenType]);
newRoot.addChild(root);
// Consume the operator.
this.lexer.getNextToken();
newRoot.addChild(this.parseFactor(result));
root = newRoot;
tokenType = this.lexer.peekNextToken().type;
}
return root;
};
DiceParser.prototype.parseFactor = function (result) {
var root;
var token = this.lexer.peekNextToken();
switch (token.type) {
case lexer_1.TokenType.Identifier:
if (token.value === 'd' || token.value === 'dF') {
root = this.parseDice(result);
}
else {
root = this.parseFunction(result);
}
break;
case lexer_1.TokenType.ParenthesisOpen:
root = this.parseBracketedExpression(result);
if (this.lexer.peekNextToken().type === lexer_1.TokenType.Identifier) {
root = this.parseDice(result, root);
}
break;
case lexer_1.TokenType.BraceOpen:
root = this.parseGroup(result);
break;
case lexer_1.TokenType.Number:
var number = this.parseNumber(result);
if (this.lexer.peekNextToken().type !== lexer_1.TokenType.Identifier) {
root = number;
}
else {
root = this.parseDice(result, number);
}
break;
default: this.errorToken(result, lexer_1.TokenType.Number, token);
}
return root;
};
DiceParser.prototype.parseSimpleFactor = function (result) {
var token = this.lexer.peekNextToken();
switch (token.type) {
case lexer_1.TokenType.Number: return this.parseNumber(result);
case lexer_1.TokenType.ParenthesisOpen: return this.parseBracketedExpression(result);
case lexer_1.TokenType.Identifier: return Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', Number(1));
default: this.errorToken(result, lexer_1.TokenType.Number, token);
}
};
DiceParser.prototype.parseFunction = function (result) {
var functionName = this.expectAndConsume(result, lexer_1.TokenType.Identifier);
var root = Ast.Factory.create(Ast.NodeType.Function)
.setAttribute('name', functionName.value);
this.expectAndConsume(result, lexer_1.TokenType.ParenthesisOpen);
// Parse function arguments.
var token = this.lexer.peekNextToken();
if (token.type !== lexer_1.TokenType.ParenthesisClose) {
root.addChild(this.parseExpression(result));
while (this.lexer.peekNextToken().type === lexer_1.TokenType.Comma) {
this.lexer.getNextToken(); // Consume the comma.
root.addChild(this.parseExpression(result));
}
}
this.expectAndConsume(result, lexer_1.TokenType.ParenthesisClose);
return root;
};
DiceParser.prototype.parseNumber = function (result) {
var numberToken = this.lexer.getNextToken();
return Ast.Factory.create(Ast.NodeType.Number)
.setAttribute('value', Number(numberToken.value));
};
DiceParser.prototype.parseBracketedExpression = function (result) {
this.lexer.getNextToken(); // Consume the opening bracket.
var root = this.parseExpression(result);
this.expectAndConsume(result, lexer_1.TokenType.ParenthesisClose);
return root;
};
DiceParser.prototype.parseGroup = function (result) {
this.lexer.getNextToken(); // Consume the opening brace.
var root = Ast.Factory.create(Ast.NodeType.Group);
// Parse group elements.
var token = this.lexer.peekNextToken();
if (token.type !== lexer_1.TokenType.BraceClose) {
do {
if (this.lexer.peekNextToken().type === lexer_1.TokenType.Comma) {
this.lexer.getNextToken(); // Consume the comma.
}
var exp = this.parseExpression(result);
if (this.lexer.peekNextToken().type === lexer_1.TokenType.Ellipsis) {
exp = this.parseRepeat(result, exp);
}
root.addChild(exp);
} while (this.lexer.peekNextToken().type === lexer_1.TokenType.Comma);
}
this.expectAndConsume(result, lexer_1.TokenType.BraceClose);
return this.parseGroupModifiers(result, root);
};
DiceParser.prototype.parseRepeat = function (result, lhs) {
this.lexer.getNextToken(); // Consume the ellipsis.
var root = Ast.Factory.create(Ast.NodeType.Repeat);
root.addChild(lhs);
root.addChild(this.parseExpression(result));
return root;
};
DiceParser.prototype.parseDice = function (result, rollTimes) {
var root = this.parseDiceRoll(result, rollTimes);
root = this.parseDiceModifiers(result, root);
return root;
};
DiceParser.prototype.parseDiceRoll = function (result, rollTimes) {
if (!rollTimes) {
rollTimes = this.parseSimpleFactor(result);
}
var token = this.expectAndConsume(result, lexer_1.TokenType.Identifier);
var root = Ast.Factory.create(Ast.NodeType.Dice);
root.addChild(rollTimes);
switch (token.value) {
case 'd':
var sidesToken = this.expectAndConsume(result, lexer_1.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;
}
return root;
};
DiceParser.prototype.parseExplode = function (result, lhs) {
var root = Ast.Factory.create(Ast.NodeType.Explode);
root.setAttribute('compound', false);
root.setAttribute('penetrate', false);
if (lhs) {
root.addChild(lhs);
}
this.lexer.getNextToken();
var token = this.lexer.peekNextToken();
if (token.type === lexer_1.TokenType.Exclamation) {
root.setAttribute('compound', true);
this.lexer.getNextToken(); // Consume second !.
}
token = this.lexer.peekNextToken();
if (token.type === lexer_1.TokenType.Identifier) {
if (token.value === 'p') {
root.setAttribute('penetrate', true);
}
this.lexer.getNextToken(); // Consume p.
}
var tokenType = this.lexer.peekNextToken().type;
if (Object.keys(BooleanOperatorMap).indexOf(tokenType.toString()) > -1) {
root.addChild(this.parseCompareModifier(result));
}
return root;
};
DiceParser.prototype.parseCritical = function (result, lhs) {
var root = Ast.Factory.create(Ast.NodeType.Critical);
root.setAttribute('type', 'success');
if (lhs) {
root.addChild(lhs);
}
var token = this.lexer.peekNextToken();
if (token.type === lexer_1.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();
var tokenType = this.lexer.peekNextToken().type;
if (Object.keys(BooleanOperatorMap).indexOf(tokenType.toString()) > -1) {
root.addChild(this.parseCompareModifier(result));
}
return root;
};
DiceParser.prototype.parseKeep = function (result, lhs) {
var root = Ast.Factory.create(Ast.NodeType.Keep);
root.setAttribute('type', 'highest');
if (lhs) {
root.addChild(lhs);
}
var token = this.lexer.peekNextToken();
if (token.type === lexer_1.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.
var tokenType = this.lexer.peekNextToken().type;
if (tokenType === lexer_1.TokenType.Number || tokenType === lexer_1.TokenType.ParenthesisOpen) {
root.addChild(this.parseSimpleFactor(result));
}
else {
root.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 1));
}
return root;
};
DiceParser.prototype.parseDrop = function (result, lhs) {
var root = Ast.Factory.create(Ast.NodeType.Drop);
root.setAttribute('type', 'lowest');
if (lhs) {
root.addChild(lhs);
}
var token = this.lexer.peekNextToken();
if (token.type === lexer_1.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.
var tokenType = this.lexer.peekNextToken().type;
if (tokenType === lexer_1.TokenType.Number || tokenType === lexer_1.TokenType.ParenthesisOpen) {
root.addChild(this.parseSimpleFactor(result));
}
return root;
};
DiceParser.prototype.parseReroll = function (result, lhs) {
var root = Ast.Factory.create(Ast.NodeType.Reroll);
root.setAttribute('once', false);
if (lhs) {
root.addChild(lhs);
}
var token = this.lexer.peekNextToken();
if (token.type === lexer_1.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.
var tokenType = this.lexer.peekNextToken().type;
if (Object.keys(BooleanOperatorMap).indexOf(tokenType.toString()) > -1) {
root.addChild(this.parseCompareModifier(result));
}
return root;
};
DiceParser.prototype.parseSort = function (result, lhs) {
var root = Ast.Factory.create(Ast.NodeType.Sort);
root.setAttribute('direction', 'ascending');
if (lhs) {
root.addChild(lhs);
}
var token = this.lexer.peekNextToken();
if (token.type === lexer_1.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;
};
DiceParser.prototype.parseCompareModifier = function (result, lhs) {
var token = this.lexer.peekNextToken();
var root;
if (token.type === lexer_1.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, lexer_1.TokenType.Number, token);
}
if (lhs) {
root.addChild(lhs);
}
root.addChild(this.parseSimpleFactor(result));
return root;
};
DiceParser.prototype.parseDiceModifiers = function (result, root) {
while (true) {
var token = this.lexer.peekNextToken();
if (Object.keys(BooleanOperatorMap).indexOf(token.type.toString()) > -1) {
root = this.parseCompareModifier(result, root);
}
else if (token.type === lexer_1.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, lexer_1.TokenType.Identifier, token);
return root;
}
}
else if (token.type === lexer_1.TokenType.Exclamation) {
root = this.parseExplode(result, root);
}
else {
break;
}
}
return root;
};
DiceParser.prototype.parseGroupModifiers = function (result, root) {
while (true) {
var token = this.lexer.peekNextToken();
if (Object.keys(BooleanOperatorMap).indexOf(token.type.toString()) > -1) {
root = this.parseCompareModifier(result, root);
}
else if (token.type === lexer_1.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, lexer_1.TokenType.Identifier, token);
return root;
}
}
else {
break;
}
}
return root;
};
return DiceParser;
}(basic_parser_class_1.BasicParser));
exports.DiceParser = DiceParser;
//# sourceMappingURL=dice-parser.class.js.map