/**
|
* @licstart The following is the entire license notice for the
|
* JavaScript code in this page
|
*
|
* Copyright 2022 Mozilla Foundation
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License.
|
*
|
* @licend The above is the entire license notice for the
|
* JavaScript code in this page
|
*/
|
"use strict";
|
|
Object.defineProperty(exports, "__esModule", {
|
value: true
|
});
|
exports.Parser = exports.Errors = void 0;
|
|
var _formcalc_lexer = require("./formcalc_lexer.js");
|
|
const Errors = {
|
assignment: "Invalid token in assignment.",
|
block: "Invalid token in do ... end declaration.",
|
elseif: "Invalid elseif declaration.",
|
for: "Invalid token in for ... endfor declaration.",
|
foreach: "Invalid token in foreach ... endfor declaration.",
|
func: "Invalid token in func declaration.",
|
if: "Invalid token if ... endif declaration.",
|
index: "Invalid token in index.",
|
params: "Invalid token in parameter list.",
|
var: "Invalid token in var declaration.",
|
while: "Invalid token while ... endwhile declaration."
|
};
|
exports.Errors = Errors;
|
const BUILTINS = new Set(["abs", "avg", "ceil", "count", "floor", "max", "min", "mod", "round", "sum", "date", "date2num", "datefmt", "isodate2num", "isotime2num", "localdatefmt", "localtimefmt", "num2date", "num2gmtime", "num2time", "time", "time2num", "timefmt", "apr", "cterm", "fv", "ipmt", "npv", "pmt", "ppmt", "pv", "rate", "term", "choose", "exists", "hasvalue", "oneof", "within", "at", "concat", "decode", "encode", "format", "left", "len", "lower", "ltrim", "parse", "replace", "right", "rtrim", "space", "str", "stuff", "substr", "uuid", "upper", "wordnum", "get", "post", "put", "eval", "ref", "unitvalue", "unittype", "acos", "asin", "atan", "cos", "deg2rad", "exp", "log", "pi", "pow", "rad2deg", "sin", "sqrt", "tan"]);
|
const LTR = true;
|
const RTL = false;
|
const Operators = {
|
dot: {
|
id: 0,
|
prec: 0,
|
assoc: RTL,
|
nargs: 0,
|
repr: "."
|
},
|
dotDot: {
|
id: 1,
|
prec: 0,
|
assoc: RTL,
|
nargs: 0,
|
repr: ".."
|
},
|
dotHash: {
|
id: 2,
|
prec: 0,
|
assoc: RTL,
|
nargs: 0,
|
repr: ".#"
|
},
|
call: {
|
id: 1,
|
prec: 1,
|
assoc: LTR,
|
nargs: 0
|
},
|
minus: {
|
id: 4,
|
nargs: 1,
|
prec: 2,
|
assoc: RTL,
|
repr: "-",
|
op: x => -x
|
},
|
plus: {
|
id: 5,
|
nargs: 1,
|
prec: 2,
|
assoc: RTL,
|
repr: "+",
|
op: x => +x
|
},
|
not: {
|
id: 6,
|
nargs: 1,
|
prec: 2,
|
assoc: RTL,
|
repr: "!",
|
op: x => !x ? 1 : 0
|
},
|
mul: {
|
id: 7,
|
nargs: 2,
|
prec: 3,
|
assoc: LTR,
|
repr: "*",
|
op: (x, y) => x * y
|
},
|
div: {
|
id: 8,
|
nargs: 2,
|
prec: 3,
|
assoc: LTR,
|
repr: "/",
|
op: (x, y) => x / y
|
},
|
add: {
|
id: 9,
|
nargs: 2,
|
prec: 4,
|
assoc: LTR,
|
repr: "+",
|
op: (x, y) => x + y
|
},
|
sub: {
|
id: 10,
|
nargs: 2,
|
prec: 4,
|
assoc: LTR,
|
repr: "-",
|
op: (x, y) => x - y
|
},
|
lt: {
|
id: 11,
|
nargs: 2,
|
prec: 5,
|
assoc: LTR,
|
repr: "<",
|
op: (x, y) => x < y ? 1 : 0
|
},
|
le: {
|
id: 12,
|
nargs: 2,
|
prec: 5,
|
assoc: LTR,
|
repr: "<=",
|
op: (x, y) => x <= y ? 1 : 0
|
},
|
gt: {
|
id: 13,
|
nargs: 2,
|
prec: 5,
|
assoc: LTR,
|
repr: ">",
|
op: (x, y) => x > y ? 1 : 0
|
},
|
ge: {
|
id: 14,
|
nargs: 2,
|
prec: 5,
|
assoc: LTR,
|
repr: ">=",
|
op: (x, y) => x >= y ? 1 : 0
|
},
|
eq: {
|
id: 15,
|
nargs: 2,
|
prec: 6,
|
assoc: LTR,
|
repr: "===",
|
op: (x, y) => x === y ? 1 : 0
|
},
|
ne: {
|
id: 16,
|
nargs: 2,
|
prec: 6,
|
assoc: LTR,
|
repr: "!==",
|
op: (x, y) => x !== y ? 1 : 0
|
},
|
and: {
|
id: 17,
|
nargs: 2,
|
prec: 7,
|
assoc: LTR,
|
repr: "&&",
|
op: (x, y) => x && y ? 1 : 0
|
},
|
or: {
|
id: 18,
|
nargs: 2,
|
prec: 8,
|
assoc: LTR,
|
repr: "||",
|
op: (x, y) => x || y ? 1 : 0
|
},
|
paren: {
|
id: 19,
|
prec: 9,
|
assoc: RTL,
|
nargs: 0
|
},
|
subscript: {
|
id: 20,
|
prec: 9,
|
assoc: RTL,
|
nargs: 0
|
}
|
};
|
const OPERATOR = true;
|
const OPERAND = false;
|
|
class SimpleExprParser {
|
constructor(lexer) {
|
this.lexer = lexer;
|
this.operands = [];
|
this.operators = [];
|
this.last = OPERATOR;
|
}
|
|
reset() {
|
this.operands.length = 0;
|
this.operators.length = 0;
|
this.last = OPERATOR;
|
}
|
|
parse(tok) {
|
tok = tok || this.lexer.next();
|
|
while (true) {
|
switch (tok.id) {
|
case _formcalc_lexer.TOKEN.and:
|
if (this.last === OPERAND) {
|
this.pushOperator(Operators.and);
|
break;
|
}
|
|
return [tok, this.getNode()];
|
|
case _formcalc_lexer.TOKEN.divide:
|
if (this.last === OPERAND) {
|
this.pushOperator(Operators.div);
|
break;
|
}
|
|
return [tok, this.getNode()];
|
|
case _formcalc_lexer.TOKEN.dot:
|
if (this.last === OPERAND) {
|
this.pushOperator(Operators.dot);
|
break;
|
}
|
|
return [tok, this.getNode()];
|
|
case _formcalc_lexer.TOKEN.dotDot:
|
if (this.last === OPERAND) {
|
this.pushOperator(Operators.dotDot);
|
break;
|
}
|
|
return [tok, this.getNode()];
|
|
case _formcalc_lexer.TOKEN.dotHash:
|
if (this.last === OPERAND) {
|
this.pushOperator(Operators.dotHash);
|
break;
|
}
|
|
return [tok, this.getNode()];
|
|
case _formcalc_lexer.TOKEN.dotStar:
|
if (this.last === OPERAND) {
|
this.pushOperator(Operators.dot);
|
this.pushOperand(new AstEveryOccurence());
|
break;
|
}
|
|
return [tok, this.getNode()];
|
|
case _formcalc_lexer.TOKEN.eq:
|
if (this.last === OPERAND) {
|
this.pushOperator(Operators.eq);
|
break;
|
}
|
|
return [tok, this.getNode()];
|
|
case _formcalc_lexer.TOKEN.ge:
|
if (this.last === OPERAND) {
|
this.pushOperator(Operators.ge);
|
break;
|
}
|
|
return [tok, this.getNode()];
|
|
case _formcalc_lexer.TOKEN.gt:
|
if (this.last === OPERAND) {
|
this.pushOperator(Operators.gt);
|
break;
|
}
|
|
return [tok, this.getNode()];
|
|
case _formcalc_lexer.TOKEN.le:
|
if (this.last === OPERAND) {
|
this.pushOperator(Operators.le);
|
break;
|
}
|
|
return [tok, this.getNode()];
|
|
case _formcalc_lexer.TOKEN.leftBracket:
|
if (this.last === OPERAND) {
|
this.flushWithOperator(Operators.subscript);
|
const operand = this.operands.pop();
|
const index = SimpleExprParser.parseIndex(this.lexer);
|
this.operands.push(new AstSubscript(operand, index));
|
this.last = OPERAND;
|
break;
|
}
|
|
return [tok, this.getNode()];
|
|
case _formcalc_lexer.TOKEN.leftParen:
|
if (this.last === OPERAND) {
|
const lastOperand = this.operands.at(-1);
|
|
if (!(lastOperand instanceof AstIdentifier)) {
|
return [tok, this.getNode()];
|
}
|
|
lastOperand.toLowerCase();
|
const name = lastOperand.id;
|
this.flushWithOperator(Operators.call);
|
const callee = this.operands.pop();
|
const params = SimpleExprParser.parseParams(this.lexer);
|
|
if (callee instanceof AstIdentifier && BUILTINS.has(name)) {
|
this.operands.push(new AstBuiltinCall(name, params));
|
} else {
|
this.operands.push(new AstCall(callee, params));
|
}
|
|
this.last = OPERAND;
|
} else {
|
this.operators.push(Operators.paren);
|
this.last = OPERATOR;
|
}
|
|
break;
|
|
case _formcalc_lexer.TOKEN.lt:
|
if (this.last === OPERAND) {
|
this.pushOperator(Operators.lt);
|
break;
|
}
|
|
return [tok, this.getNode()];
|
|
case _formcalc_lexer.TOKEN.minus:
|
if (this.last === OPERATOR) {
|
this.pushOperator(Operators.minus);
|
} else {
|
this.pushOperator(Operators.sub);
|
}
|
|
break;
|
|
case _formcalc_lexer.TOKEN.ne:
|
if (this.last === OPERAND) {
|
this.pushOperator(Operators.ne);
|
break;
|
}
|
|
return [tok, this.getNode()];
|
|
case _formcalc_lexer.TOKEN.not:
|
if (this.last === OPERAND) {
|
this.pushOperator(Operators.not);
|
break;
|
}
|
|
return [tok, this.getNode()];
|
|
case _formcalc_lexer.TOKEN.null:
|
if (this.last === OPERATOR) {
|
this.pushOperand(new AstNull());
|
break;
|
}
|
|
return [tok, this.getNode()];
|
|
case _formcalc_lexer.TOKEN.number:
|
if (this.last === OPERATOR) {
|
this.pushOperand(new AstNumber(tok.value));
|
break;
|
}
|
|
return [tok, this.getNode()];
|
|
case _formcalc_lexer.TOKEN.or:
|
if (this.last === OPERAND) {
|
this.pushOperator(Operators.or);
|
break;
|
}
|
|
return [tok, this.getNode()];
|
|
case _formcalc_lexer.TOKEN.plus:
|
if (this.last === OPERATOR) {
|
this.pushOperator(Operators.plus);
|
} else {
|
this.pushOperator(Operators.add);
|
}
|
|
break;
|
|
case _formcalc_lexer.TOKEN.rightBracket:
|
if (!this.flushUntil(Operators.subscript.id)) {
|
return [tok, this.getNode()];
|
}
|
|
break;
|
|
case _formcalc_lexer.TOKEN.rightParen:
|
if (!this.flushUntil(Operators.paren.id)) {
|
return [tok, this.getNode()];
|
}
|
|
break;
|
|
case _formcalc_lexer.TOKEN.string:
|
if (this.last === OPERATOR) {
|
this.pushOperand(new AstString(tok.value));
|
break;
|
}
|
|
return [tok, this.getNode()];
|
|
case _formcalc_lexer.TOKEN.this:
|
if (this.last === OPERATOR) {
|
this.pushOperand(new AstThis());
|
break;
|
}
|
|
return [tok, this.getNode()];
|
|
case _formcalc_lexer.TOKEN.times:
|
if (this.last === OPERAND) {
|
this.pushOperator(Operators.mul);
|
break;
|
}
|
|
return [tok, this.getNode()];
|
|
case _formcalc_lexer.TOKEN.identifier:
|
if (this.last === OPERATOR) {
|
this.pushOperand(new AstIdentifier(tok.value));
|
break;
|
}
|
|
return [tok, this.getNode()];
|
|
default:
|
return [tok, this.getNode()];
|
}
|
|
tok = this.lexer.next();
|
}
|
}
|
|
static parseParams(lexer) {
|
const parser = new SimpleExprParser(lexer);
|
const params = [];
|
|
while (true) {
|
const [tok, param] = parser.parse();
|
|
if (param) {
|
params.push(param);
|
}
|
|
if (tok.id === _formcalc_lexer.TOKEN.rightParen) {
|
return params;
|
} else if (tok.id !== _formcalc_lexer.TOKEN.comma) {
|
throw new Error(Errors.params);
|
}
|
|
parser.reset();
|
}
|
}
|
|
static parseIndex(lexer) {
|
let tok = lexer.next();
|
|
if (tok.id === _formcalc_lexer.TOKEN.times) {
|
tok = lexer.next();
|
|
if (tok.id !== _formcalc_lexer.TOKEN.rightBracket) {
|
throw new Error(Errors.index);
|
}
|
|
return new AstEveryOccurence();
|
}
|
|
const [token, expr] = new SimpleExprParser(lexer).parse(tok);
|
|
if (token.id !== _formcalc_lexer.TOKEN.rightBracket) {
|
throw new Error(Errors.index);
|
}
|
|
return expr;
|
}
|
|
pushOperator(op) {
|
this.flushWithOperator(op);
|
this.operators.push(op);
|
this.last = OPERATOR;
|
}
|
|
pushOperand(op) {
|
this.operands.push(op);
|
this.last = OPERAND;
|
}
|
|
operate(op) {
|
if (op.nargs === 1) {
|
const arg = this.operands.pop();
|
this.operands.push(AstUnaryOperator.getOperatorOrValue(op, arg));
|
} else {
|
const arg2 = this.operands.pop();
|
const arg1 = this.operands.pop();
|
this.operands.push(AstBinaryOperator.getOperatorOrValue(op, arg1, arg2));
|
}
|
}
|
|
flushWithOperator(op) {
|
while (true) {
|
const top = this.operators.at(-1);
|
|
if (top) {
|
if (top.id >= 0 && SimpleExprParser.checkPrecedence(top, op)) {
|
this.operators.pop();
|
this.operate(top);
|
continue;
|
}
|
}
|
|
return;
|
}
|
}
|
|
flush() {
|
while (true) {
|
const op = this.operators.pop();
|
|
if (!op) {
|
return;
|
}
|
|
this.operate(op);
|
}
|
}
|
|
flushUntil(id) {
|
while (true) {
|
const op = this.operators.pop();
|
|
if (!op) {
|
return false;
|
}
|
|
if (op.id === id) {
|
return true;
|
}
|
|
this.operate(op);
|
}
|
}
|
|
getNode() {
|
this.flush();
|
return this.operands.pop();
|
}
|
|
static checkPrecedence(left, right) {
|
return left.prec < right.prec || left.prec === right.prec && left.assoc === LTR;
|
}
|
|
}
|
|
class Leaf {
|
dump() {
|
throw new Error("Not implemented method");
|
}
|
|
isSomPredicate() {
|
return false;
|
}
|
|
isDotExpression() {
|
return false;
|
}
|
|
isConstant() {
|
return false;
|
}
|
|
toNumber() {
|
return 0;
|
}
|
|
toComparable() {
|
return null;
|
}
|
|
}
|
|
class AstCall extends Leaf {
|
constructor(callee, params) {
|
super();
|
this.callee = callee;
|
this.params = params;
|
}
|
|
dump() {
|
return {
|
callee: this.callee.dump(),
|
params: this.params.map(x => x.dump())
|
};
|
}
|
|
}
|
|
class AstBuiltinCall extends Leaf {
|
constructor(id, params) {
|
super();
|
this.id = id;
|
this.params = params;
|
}
|
|
dump() {
|
return {
|
builtin: this.id,
|
params: this.params.map(x => x.dump())
|
};
|
}
|
|
}
|
|
class AstSubscript extends Leaf {
|
constructor(operand, index) {
|
super();
|
this.operand = operand;
|
this.index = index;
|
}
|
|
dump() {
|
return {
|
operand: this.operand.dump(),
|
index: this.index.dump()
|
};
|
}
|
|
}
|
|
class AstBinaryOperator extends Leaf {
|
constructor(id, left, right, repr) {
|
super();
|
this.id = id;
|
this.left = left;
|
this.right = right;
|
this.repr = repr;
|
}
|
|
dump() {
|
return {
|
operator: this.repr,
|
left: this.left.dump(),
|
right: this.right.dump()
|
};
|
}
|
|
isDotExpression() {
|
return Operators.id.dot <= this.id && this.id <= Operators.id.dotHash;
|
}
|
|
isSomPredicate() {
|
return this.isDotExpression() || Operators.id.lt <= this.id && this.id <= Operators.id.or && (this.left.isDotExpression() && this.right.isConstant() || this.left.isConstant() && this.right.isDotExpression() || this.left.isDotExpression() && this.right.isDotExpression());
|
}
|
|
static getOperatorOrValue(operator, left, right) {
|
if (!left.isConstant() || !right.isConstant()) {
|
return new AstBinaryOperator(operator.id, left, right, operator.repr);
|
}
|
|
if (Operators.lt.id <= operator.id && operator.id <= Operators.ne.id && !(left instanceof AstNumber) && !(right instanceof AstNumber)) {
|
return new AstNumber(operator.op(left.toComparable(), right.toComparable()));
|
}
|
|
return new AstNumber(operator.op(left.toNumber(), right.toNumber()));
|
}
|
|
}
|
|
class AstUnaryOperator extends Leaf {
|
constructor(id, arg, repr) {
|
super();
|
this.id = id;
|
this.arg = arg;
|
this.repr = repr;
|
}
|
|
dump() {
|
return {
|
operator: this.repr,
|
arg: this.arg.dump()
|
};
|
}
|
|
static getOperatorOrValue(operator, arg) {
|
if (!arg.isConstant()) {
|
return new AstUnaryOperator(operator.id, arg, operator.repr);
|
}
|
|
return new AstNumber(operator.op(arg.toNumber()));
|
}
|
|
}
|
|
class AstNumber extends Leaf {
|
constructor(number) {
|
super();
|
this.number = number;
|
}
|
|
dump() {
|
return this.number;
|
}
|
|
isConstant() {
|
return true;
|
}
|
|
toNumber() {
|
return this.number;
|
}
|
|
}
|
|
class AstString extends Leaf {
|
constructor(str) {
|
super();
|
this.str = str;
|
}
|
|
dump() {
|
return this.str;
|
}
|
|
isConstant() {
|
return true;
|
}
|
|
toNumber() {
|
return !isNaN(this.str) ? parseFloat(this.str) : 0;
|
}
|
|
toComparable() {
|
return this.str;
|
}
|
|
}
|
|
class AstThis extends Leaf {
|
dump() {
|
return {
|
special: "this"
|
};
|
}
|
|
}
|
|
class AstIdentifier extends Leaf {
|
constructor(id) {
|
super();
|
this.id = id;
|
}
|
|
dump() {
|
return {
|
id: this.id
|
};
|
}
|
|
toLowerCase() {
|
this.id = this.id.toLowerCase();
|
}
|
|
}
|
|
class AstNull extends Leaf {
|
dump() {
|
return {
|
special: null
|
};
|
}
|
|
isConstant() {
|
return true;
|
}
|
|
toComparable() {
|
return null;
|
}
|
|
}
|
|
class AstEveryOccurence {
|
dump() {
|
return {
|
special: "*"
|
};
|
}
|
|
}
|
|
class VarDecl extends Leaf {
|
constructor(id, expr) {
|
super();
|
this.id = id;
|
this.expr = expr;
|
}
|
|
dump() {
|
return {
|
var: this.id,
|
expr: this.expr.dump()
|
};
|
}
|
|
}
|
|
class Assignment extends Leaf {
|
constructor(id, expr) {
|
super();
|
this.id = id;
|
this.expr = expr;
|
}
|
|
dump() {
|
return {
|
assignment: this.id,
|
expr: this.expr.dump()
|
};
|
}
|
|
}
|
|
class FuncDecl extends Leaf {
|
constructor(id, params, body) {
|
super();
|
this.id = id;
|
this.params = params;
|
this.body = body;
|
}
|
|
dump() {
|
return {
|
func: this.id,
|
params: this.params,
|
body: this.body.dump()
|
};
|
}
|
|
}
|
|
class IfDecl extends Leaf {
|
constructor(condition, thenClause, elseIfClause, elseClause) {
|
super();
|
this.condition = condition;
|
this.then = thenClause;
|
this.elseif = elseIfClause;
|
this.else = elseClause;
|
}
|
|
dump() {
|
return {
|
decl: "if",
|
condition: this.condition.dump(),
|
then: this.then.dump(),
|
elseif: this.elseif ? this.elseif.map(x => x.dump()) : null,
|
else: this.else ? this.else.dump() : null
|
};
|
}
|
|
}
|
|
class ElseIfDecl extends Leaf {
|
constructor(condition, thenClause) {
|
super();
|
this.condition = condition;
|
this.then = thenClause;
|
}
|
|
dump() {
|
return {
|
decl: "elseif",
|
condition: this.condition.dump(),
|
then: this.then.dump()
|
};
|
}
|
|
}
|
|
class WhileDecl extends Leaf {
|
constructor(condition, whileClause) {
|
super();
|
this.condition = condition;
|
this.body = whileClause;
|
}
|
|
dump() {
|
return {
|
decl: "while",
|
condition: this.condition.dump(),
|
body: this.body.dump()
|
};
|
}
|
|
}
|
|
class ForDecl extends Leaf {
|
constructor(assignment, upto, end, step, body) {
|
super();
|
this.assignment = assignment;
|
this.upto = upto;
|
this.end = end;
|
this.step = step;
|
this.body = body;
|
}
|
|
dump() {
|
return {
|
decl: "for",
|
assignment: this.assignment.dump(),
|
type: this.upto ? "upto" : "downto",
|
end: this.end.dump(),
|
step: this.step ? this.step.dump() : null,
|
body: this.body.dump()
|
};
|
}
|
|
}
|
|
class ForeachDecl extends Leaf {
|
constructor(id, params, body) {
|
super();
|
this.id = id;
|
this.params = params;
|
this.body = body;
|
}
|
|
dump() {
|
return {
|
decl: "foreach",
|
id: this.id,
|
params: this.params.map(x => x.dump()),
|
body: this.body.dump()
|
};
|
}
|
|
}
|
|
class BlockDecl extends Leaf {
|
constructor(body) {
|
super();
|
this.body = body;
|
}
|
|
dump() {
|
return {
|
decl: "block",
|
body: this.body.dump()
|
};
|
}
|
|
}
|
|
class ExprList extends Leaf {
|
constructor(expressions) {
|
super();
|
this.expressions = expressions;
|
}
|
|
dump() {
|
return this.expressions.map(x => x.dump());
|
}
|
|
}
|
|
class BreakDecl extends Leaf {
|
dump() {
|
return {
|
special: "break"
|
};
|
}
|
|
}
|
|
class ContinueDecl extends Leaf {
|
dump() {
|
return {
|
special: "continue"
|
};
|
}
|
|
}
|
|
class Parser {
|
constructor(code) {
|
this.lexer = new _formcalc_lexer.Lexer(code);
|
}
|
|
parse() {
|
const [tok, decls] = this.parseExprList();
|
|
if (tok.id !== _formcalc_lexer.TOKEN.eof) {
|
throw new Error("Invalid token in Form code");
|
}
|
|
return decls;
|
}
|
|
parseExprList() {
|
const expressions = [];
|
let tok = null,
|
expr;
|
|
while (true) {
|
[tok, expr] = this.parseExpr(tok);
|
|
if (!expr) {
|
return [tok, new ExprList(expressions)];
|
}
|
|
expressions.push(expr);
|
}
|
}
|
|
parseExpr(tok) {
|
tok = tok || this.lexer.next();
|
|
switch (tok.id) {
|
case _formcalc_lexer.TOKEN.identifier:
|
return this.parseAssigmentOrExpr(tok);
|
|
case _formcalc_lexer.TOKEN.break:
|
return [null, new BreakDecl()];
|
|
case _formcalc_lexer.TOKEN.continue:
|
return [null, new ContinueDecl()];
|
|
case _formcalc_lexer.TOKEN.do:
|
return this.parseBlock();
|
|
case _formcalc_lexer.TOKEN.for:
|
return this.parseFor();
|
|
case _formcalc_lexer.TOKEN.foreach:
|
return this.parseForeach();
|
|
case _formcalc_lexer.TOKEN.func:
|
return this.parseFuncDecl();
|
|
case _formcalc_lexer.TOKEN.if:
|
return this.parseIf();
|
|
case _formcalc_lexer.TOKEN.var:
|
return this.parseVarDecl();
|
|
case _formcalc_lexer.TOKEN.while:
|
return this.parseWhile();
|
|
default:
|
return this.parseSimpleExpr(tok);
|
}
|
}
|
|
parseAssigmentOrExpr(tok) {
|
const savedTok = tok;
|
tok = this.lexer.next();
|
|
if (tok.id === _formcalc_lexer.TOKEN.assign) {
|
const [tok1, expr] = this.parseSimpleExpr(null);
|
return [tok1, new Assignment(savedTok.value, expr)];
|
}
|
|
const parser = new SimpleExprParser(this.lexer);
|
parser.pushOperand(new AstIdentifier(savedTok.value));
|
return parser.parse(tok);
|
}
|
|
parseBlock() {
|
const [tok1, body] = this.parseExprList();
|
const tok = tok1 || this.lexer.next();
|
|
if (tok.id !== _formcalc_lexer.TOKEN.end) {
|
throw new Error(Errors.block);
|
}
|
|
return [null, new BlockDecl(body)];
|
}
|
|
parseVarDecl() {
|
let tok = this.lexer.next();
|
|
if (tok.id !== _formcalc_lexer.TOKEN.identifier) {
|
throw new Error(Errors.var);
|
}
|
|
const identifier = tok.value;
|
tok = this.lexer.next();
|
|
if (tok.id !== _formcalc_lexer.TOKEN.assign) {
|
return [tok, new VarDecl(identifier, null)];
|
}
|
|
const [tok1, expr] = this.parseSimpleExpr();
|
return [tok1, new VarDecl(identifier, expr)];
|
}
|
|
parseFuncDecl() {
|
let tok = this.lexer.next();
|
|
if (tok.id !== _formcalc_lexer.TOKEN.identifier) {
|
throw new Error(Errors.func);
|
}
|
|
const identifier = tok.value;
|
const params = this.parseParamList();
|
tok = this.lexer.next();
|
|
if (tok.id !== _formcalc_lexer.TOKEN.do) {
|
throw new Error(Errors.func);
|
}
|
|
const [tok1, body] = this.parseExprList();
|
tok = tok1 || this.lexer.next();
|
|
if (tok.id !== _formcalc_lexer.TOKEN.endfunc) {
|
throw new Error(Errors.func);
|
}
|
|
return [null, new FuncDecl(identifier, params, body)];
|
}
|
|
parseParamList() {
|
const params = [];
|
let tok = this.lexer.next();
|
|
if (tok.id !== _formcalc_lexer.TOKEN.leftParen) {
|
throw new Error(Errors.func);
|
}
|
|
tok = this.lexer.next();
|
|
if (tok.id === _formcalc_lexer.TOKEN.rightParen) {
|
return params;
|
}
|
|
while (true) {
|
if (tok.id !== _formcalc_lexer.TOKEN.identifier) {
|
throw new Error(Errors.func);
|
}
|
|
params.push(tok.value);
|
tok = this.lexer.next();
|
|
if (tok.id === _formcalc_lexer.TOKEN.rightParen) {
|
return params;
|
}
|
|
if (tok.id !== _formcalc_lexer.TOKEN.comma) {
|
throw new Error(Errors.func);
|
}
|
|
tok = this.lexer.next();
|
}
|
}
|
|
parseSimpleExpr(tok = null) {
|
return new SimpleExprParser(this.lexer).parse(tok);
|
}
|
|
parseIf() {
|
let elseIfClause = [];
|
let tok = this.lexer.next();
|
|
if (tok.id !== _formcalc_lexer.TOKEN.leftParen) {
|
throw new Error(Errors.if);
|
}
|
|
const [tok1, condition] = this.parseSimpleExpr();
|
tok = tok1 || this.lexer.next();
|
|
if (tok.id !== _formcalc_lexer.TOKEN.rightParen) {
|
throw new Error(Errors.if);
|
}
|
|
tok = this.lexer.next();
|
|
if (tok.id !== _formcalc_lexer.TOKEN.then) {
|
throw new Error(Errors.if);
|
}
|
|
const [tok2, thenClause] = this.parseExprList();
|
tok = tok2 || this.lexer.next();
|
|
while (tok.id === _formcalc_lexer.TOKEN.elseif) {
|
tok = this.lexer.next();
|
|
if (tok.id !== _formcalc_lexer.TOKEN.leftParen) {
|
throw new Error(Errors.elseif);
|
}
|
|
const [tok3, elseIfCondition] = this.parseSimpleExpr();
|
tok = tok3 || this.lexer.next();
|
|
if (tok.id !== _formcalc_lexer.TOKEN.rightParen) {
|
throw new Error(Errors.elseif);
|
}
|
|
tok = this.lexer.next();
|
|
if (tok.id !== _formcalc_lexer.TOKEN.then) {
|
throw new Error(Errors.elseif);
|
}
|
|
const [tok4, elseIfThenClause] = this.parseExprList();
|
elseIfClause.push(new ElseIfDecl(elseIfCondition, elseIfThenClause));
|
tok = tok4 || this.lexer.next();
|
}
|
|
if (elseIfClause.length === 0) {
|
elseIfClause = null;
|
}
|
|
if (tok.id === _formcalc_lexer.TOKEN.endif) {
|
return [null, new IfDecl(condition, thenClause, elseIfClause, null)];
|
}
|
|
if (tok.id !== _formcalc_lexer.TOKEN.else) {
|
throw new Error(Errors.if);
|
}
|
|
const [tok5, elseClause] = this.parseExprList();
|
tok = tok5 || this.lexer.next();
|
|
if (tok.id !== _formcalc_lexer.TOKEN.endif) {
|
throw new Error(Errors.if);
|
}
|
|
return [null, new IfDecl(condition, thenClause, elseIfClause, elseClause)];
|
}
|
|
parseWhile() {
|
let tok = this.lexer.next();
|
|
if (tok.id !== _formcalc_lexer.TOKEN.leftParen) {
|
throw new Error(Errors.while);
|
}
|
|
const [tok1, condition] = this.parseSimpleExpr();
|
tok = tok1 || this.lexer.next();
|
|
if (tok.id !== _formcalc_lexer.TOKEN.rightParen) {
|
throw new Error(Errors.while);
|
}
|
|
tok = this.lexer.next();
|
|
if (tok.id !== _formcalc_lexer.TOKEN.do) {
|
throw new Error(Errors.while);
|
}
|
|
const [tok2, whileClause] = this.parseExprList();
|
tok = tok2 || this.lexer.next();
|
|
if (tok.id !== _formcalc_lexer.TOKEN.endwhile) {
|
throw new Error(Errors.while);
|
}
|
|
return [null, new WhileDecl(condition, whileClause)];
|
}
|
|
parseAssignment() {
|
let tok = this.lexer.next();
|
let hasVar = false;
|
|
if (tok.id === _formcalc_lexer.TOKEN.var) {
|
hasVar = true;
|
tok = this.lexer.next();
|
}
|
|
if (tok.id !== _formcalc_lexer.TOKEN.identifier) {
|
throw new Error(Errors.assignment);
|
}
|
|
const identifier = tok.value;
|
tok = this.lexer.next();
|
|
if (tok.id !== _formcalc_lexer.TOKEN.assign) {
|
throw new Error(Errors.assignment);
|
}
|
|
const [tok1, expr] = this.parseSimpleExpr();
|
|
if (hasVar) {
|
return [tok1, new VarDecl(identifier, expr)];
|
}
|
|
return [tok1, new Assignment(identifier, expr)];
|
}
|
|
parseFor() {
|
let tok,
|
step = null;
|
let upto = false;
|
const [tok1, assignment] = this.parseAssignment();
|
tok = tok1 || this.lexer.next();
|
|
if (tok.id === _formcalc_lexer.TOKEN.upto) {
|
upto = true;
|
} else if (tok.id !== _formcalc_lexer.TOKEN.downto) {
|
throw new Error(Errors.for);
|
}
|
|
const [tok2, end] = this.parseSimpleExpr();
|
tok = tok2 || this.lexer.next();
|
|
if (tok.id === _formcalc_lexer.TOKEN.step) {
|
[tok, step] = this.parseSimpleExpr();
|
tok = tok || this.lexer.next();
|
}
|
|
if (tok.id !== _formcalc_lexer.TOKEN.do) {
|
throw new Error(Errors.for);
|
}
|
|
const [tok3, body] = this.parseExprList();
|
tok = tok3 || this.lexer.next();
|
|
if (tok.id !== _formcalc_lexer.TOKEN.endfor) {
|
throw new Error(Errors.for);
|
}
|
|
return [null, new ForDecl(assignment, upto, end, step, body)];
|
}
|
|
parseForeach() {
|
let tok = this.lexer.next();
|
|
if (tok.id !== _formcalc_lexer.TOKEN.identifier) {
|
throw new Error(Errors.foreach);
|
}
|
|
const identifier = tok.value;
|
tok = this.lexer.next();
|
|
if (tok.id !== _formcalc_lexer.TOKEN.in) {
|
throw new Error(Errors.foreach);
|
}
|
|
tok = this.lexer.next();
|
|
if (tok.id !== _formcalc_lexer.TOKEN.leftParen) {
|
throw new Error(Errors.foreach);
|
}
|
|
const params = SimpleExprParser.parseParams(this.lexer);
|
tok = this.lexer.next();
|
|
if (tok.id !== _formcalc_lexer.TOKEN.do) {
|
throw new Error(Errors.foreach);
|
}
|
|
const [tok1, body] = this.parseExprList();
|
tok = tok1 || this.lexer.next();
|
|
if (tok.id !== _formcalc_lexer.TOKEN.endfor) {
|
throw new Error(Errors.foreach);
|
}
|
|
return [null, new ForeachDecl(identifier, params, body)];
|
}
|
|
}
|
|
exports.Parser = Parser;
|