rulescribe
Version:
Typescript rule engine
406 lines (405 loc) • 19.8 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const lexer_1 = require("./lexer");
const scope_1 = __importDefault(require("./scope"));
const interfaces_1 = require("./interfaces");
const KEYWORD_WITH_END = new Set(['IF', 'FOR', 'WHILE', 'RULE', 'FUNCTION']);
// Define the parser
class Engine {
constructor(lexer) {
this.lexer = lexer;
this.currentToken = null;
this.lexer = lexer;
this.currentToken = this.lexer.getNextToken();
}
eat(tokenType) {
var _a, _b;
if (((_a = this.currentToken) === null || _a === void 0 ? void 0 : _a.type) === tokenType) {
this.currentToken = this.lexer.getNextToken();
}
else {
throw new Error(`Expected ${tokenType}, got ${(_b = this.currentToken) === null || _b === void 0 ? void 0 : _b.type}`);
}
}
factor(engineScope) {
return __awaiter(this, void 0, void 0, function* () {
const token = this.currentToken;
if ((token === null || token === void 0 ? void 0 : token.type) === 'CALL_FUNCTION') {
const result = yield this.callFunction(engineScope);
return (result === undefined) ? false : result;
}
else if ((token === null || token === void 0 ? void 0 : token.type) === 'NUMBER' || (token === null || token === void 0 ? void 0 : token.type) === 'FLOAT' || (token === null || token === void 0 ? void 0 : token.type) === 'BOOLEAN' || (token === null || token === void 0 ? void 0 : token.type) === 'STRING') {
this.eat(token.type);
return token.value;
}
else if ((token === null || token === void 0 ? void 0 : token.type) === 'IDENTIFIER') {
const name = token.value;
this.eat('IDENTIFIER');
const value = engineScope.lookup(name);
if (value === undefined) {
throw new Error(`Variable '${name}' is not defined`);
}
return value;
}
else if ((token === null || token === void 0 ? void 0 : token.type) === 'LPAREN') {
this.eat('LPAREN');
const result = yield this.expr(engineScope);
this.eat('RPAREN');
return result;
}
else if ((token === null || token === void 0 ? void 0 : token.type) === 'MINUS') {
this.eat('MINUS');
return -(yield this.factor(engineScope));
}
else if ((token === null || token === void 0 ? void 0 : token.type) === 'NOT') {
this.eat('NOT');
return !(yield this.factor(engineScope));
}
throw new Error(`Unexpected token: ${token === null || token === void 0 ? void 0 : token.type}`);
});
}
term(engineScop) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c;
let result = yield this.factor(engineScop);
while (((_a = this.currentToken) === null || _a === void 0 ? void 0 : _a.type) === 'MULTIPLY' || ((_b = this.currentToken) === null || _b === void 0 ? void 0 : _b.type) === 'DIVIDE' || ((_c = this.currentToken) === null || _c === void 0 ? void 0 : _c.type) === 'MODULO') {
const token = this.currentToken;
if ((token === null || token === void 0 ? void 0 : token.type) === 'MULTIPLY') {
this.eat('MULTIPLY');
result *= (yield this.factor(engineScop));
}
else if ((token === null || token === void 0 ? void 0 : token.type) === 'DIVIDE') {
this.eat('DIVIDE');
result /= (yield this.factor(engineScop));
}
else if ((token === null || token === void 0 ? void 0 : token.type) === 'MODULO') {
this.eat('MODULO');
result %= (yield this.factor(engineScop));
}
}
return result;
});
}
expr(engineScop) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b;
const first = yield this.expr_math(engineScop);
if (((_a = this.currentToken) === null || _a === void 0 ? void 0 : _a.type) === 'AND') {
this.eat('AND');
return (yield this.expr(engineScop)) && first;
}
else if (((_b = this.currentToken) === null || _b === void 0 ? void 0 : _b.type) === 'OR') {
this.eat('OR');
return (yield this.expr(engineScop)) || first;
}
return first;
});
}
expr_math(engineScop) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
let result = yield this.term(engineScop);
while (((_a = this.currentToken) === null || _a === void 0 ? void 0 : _a.type) === 'PLUS' ||
((_b = this.currentToken) === null || _b === void 0 ? void 0 : _b.type) === 'MINUS' ||
((_c = this.currentToken) === null || _c === void 0 ? void 0 : _c.type) === 'LESS_THAN' ||
((_d = this.currentToken) === null || _d === void 0 ? void 0 : _d.type) === 'LESS_THAN_OR_EQUAL' ||
((_e = this.currentToken) === null || _e === void 0 ? void 0 : _e.type) === 'GREATER_THAN' ||
((_f = this.currentToken) === null || _f === void 0 ? void 0 : _f.type) === 'GREATER_THAN_OR_EQUAL' ||
((_g = this.currentToken) === null || _g === void 0 ? void 0 : _g.type) === 'EQUALS' ||
((_h = this.currentToken) === null || _h === void 0 ? void 0 : _h.type) === 'NOT_EQUALS' ||
((_j = this.currentToken) === null || _j === void 0 ? void 0 : _j.type) === 'AND' ||
((_k = this.currentToken) === null || _k === void 0 ? void 0 : _k.type) === 'OR') {
const token = this.currentToken;
if ((token === null || token === void 0 ? void 0 : token.type) === 'PLUS') {
this.eat('PLUS');
result = result + (yield this.term(engineScop));
}
else if ((token === null || token === void 0 ? void 0 : token.type) === 'MINUS') {
this.eat('MINUS');
result = result - (yield this.term(engineScop));
}
else if ((token === null || token === void 0 ? void 0 : token.type) === 'LESS_THAN') {
this.eat('LESS_THAN');
return result < (yield this.expr_math(engineScop));
}
else if ((token === null || token === void 0 ? void 0 : token.type) === 'LESS_THAN_OR_EQUAL') {
this.eat('LESS_THAN_OR_EQUAL');
return result <= (yield this.expr_math(engineScop));
}
else if ((token === null || token === void 0 ? void 0 : token.type) === 'GREATER_THAN') {
this.eat('GREATER_THAN');
return result > (yield this.expr_math(engineScop));
}
else if ((token === null || token === void 0 ? void 0 : token.type) === 'GREATER_THAN_OR_EQUAL') {
this.eat('GREATER_THAN_OR_EQUAL');
return result >= (yield this.expr_math(engineScop));
}
else if ((token === null || token === void 0 ? void 0 : token.type) === 'EQUALS') {
this.eat('EQUALS');
return result === (yield this.expr_math(engineScop));
}
else if ((token === null || token === void 0 ? void 0 : token.type) === 'NOT_EQUALS') {
this.eat('NOT_EQUALS');
return result != (yield this.expr_math(engineScop));
}
else if ((token === null || token === void 0 ? void 0 : token.type) === 'AND') {
return result;
}
else if ((token === null || token === void 0 ? void 0 : token.type) === 'OR') {
return result;
}
}
return result;
});
}
assignment(engineScope) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
const name = (_a = this.currentToken) === null || _a === void 0 ? void 0 : _a.value;
this.eat('IDENTIFIER');
this.eat('ASSIGN');
const value = yield this.expr(engineScope);
engineScope.define(name, value);
});
}
statement(engineScope) {
return __awaiter(this, void 0, void 0, function* () {
const token = this.currentToken;
if ((token === null || token === void 0 ? void 0 : token.type) === 'IDENTIFIER') {
yield this.assignment(engineScope);
}
else if ((token === null || token === void 0 ? void 0 : token.type) === 'IF') {
yield this.ifStatement(engineScope);
}
else if ((token === null || token === void 0 ? void 0 : token.type) === 'WHILE') {
yield this.whileLoop(engineScope);
}
else if ((token === null || token === void 0 ? void 0 : token.type) === 'FOR') {
yield this.forLoop(engineScope);
}
else if ((token === null || token === void 0 ? void 0 : token.type) === 'RULE') {
yield this.ruleDefenition(engineScope);
}
else if ((token === null || token === void 0 ? void 0 : token.type) === 'FUNCTION') {
this.functionDefinition(engineScope);
}
else if ((token === null || token === void 0 ? void 0 : token.type) === 'CALL_FUNCTION') {
yield this.callFunction(engineScope);
}
else if ((token === null || token === void 0 ? void 0 : token.type) === 'RETURN') {
this.eat('RETURN');
engineScope.define('@RETURN_VALUE', yield this.expr(engineScope));
}
else {
throw new Error(`Unexpected token: ${token === null || token === void 0 ? void 0 : token.type}`);
}
});
}
ifStatement(engineScop) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d, _e, _f;
this.eat('IF');
const condition = yield this.expr(engineScop);
this.eat('THEN');
while (((_a = this.currentToken) === null || _a === void 0 ? void 0 : _a.type) !== 'ELSE' && ((_b = this.currentToken) === null || _b === void 0 ? void 0 : _b.type) !== 'END') {
if (condition) {
yield this.statement(engineScop);
}
else {
this.eat((_c = this.currentToken) === null || _c === void 0 ? void 0 : _c.type);
}
}
if (((_d = this.currentToken) === null || _d === void 0 ? void 0 : _d.type) === 'ELSE') {
this.eat('ELSE');
while (((_e = this.currentToken) === null || _e === void 0 ? void 0 : _e.type) !== 'END') {
if (!condition) {
yield this.statement(engineScop);
}
else {
this.eat((_f = this.currentToken) === null || _f === void 0 ? void 0 : _f.type);
}
}
}
this.eat('END');
});
}
forLoop(engineScope) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b;
this.eat('FOR');
const name = (_a = this.currentToken) === null || _a === void 0 ? void 0 : _a.value;
this.eat('IDENTIFIER');
this.eat('FROM');
engineScope.define(name, yield this.expr(engineScope));
this.eat('TO');
const to = `@${name}_end`;
engineScope.define(to, yield this.expr(engineScope));
const step = `@${name}_step`;
if (((_b = this.currentToken) === null || _b === void 0 ? void 0 : _b.type) === 'STEP') {
this.eat('STEP');
engineScope.define(step, yield this.expr(engineScope));
}
else {
engineScope.define(step, 1);
}
const body = this.getBody('DO');
const engine = new Engine(new lexer_1.CompiledLexer(body));
while (engineScope.lookup(name) <= engineScope.lookup(to)) {
yield engine.parse(engineScope);
engine.reset();
engineScope.define(name, engineScope.lookup(name) + engineScope.lookup(step));
}
});
}
whileLoop(engineScop) {
return __awaiter(this, void 0, void 0, function* () {
this.eat('WHILE');
const condition = this.getCondition('DO');
const body = this.getBody('DO');
const engine = new Engine(new lexer_1.CompiledLexer(body));
const condParser = new Engine(new lexer_1.CompiledLexer(condition));
let cond = yield condParser.expr(engineScop);
while (cond) {
yield engine.parse(engineScop);
engine.reset();
condParser.reset();
cond = yield condParser.expr(engineScop);
}
});
}
getCondition(end) {
var _a, _b;
const condition = [];
while (((_a = this.currentToken) === null || _a === void 0 ? void 0 : _a.type) !== end) {
condition.push(this.currentToken);
this.eat((_b = this.currentToken) === null || _b === void 0 ? void 0 : _b.type);
}
return condition;
}
getBody(start) {
var _a, _b, _c, _d, _e;
this.eat(start);
const body = [];
let end = 1;
if (KEYWORD_WITH_END.has((_a = this.currentToken) === null || _a === void 0 ? void 0 : _a.type)) {
end += 1;
}
while ((end >= 1) || (((_b = this.currentToken) === null || _b === void 0 ? void 0 : _b.type) !== 'END')) {
body.push(this.currentToken);
this.eat((_c = this.currentToken) === null || _c === void 0 ? void 0 : _c.type);
if (KEYWORD_WITH_END.has((_d = this.currentToken) === null || _d === void 0 ? void 0 : _d.type)) {
end += 1;
}
else if (((_e = this.currentToken) === null || _e === void 0 ? void 0 : _e.type) === 'END') {
end -= 1;
}
}
this.eat('END');
return body;
}
callFunction(engineScope) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c;
const funcEngineScope = new scope_1.default(engineScope.builtinFunction);
const funcName = (_a = this.currentToken) === null || _a === void 0 ? void 0 : _a.value;
const funcType = engineScope.functionType(funcName);
const func = engineScope.getFunc(funcName);
if (funcType === interfaces_1.FunctionType.UNDEFINED || func === undefined) {
throw new Error(`'${funcName}' is not declared!`);
}
this.eat('CALL_FUNCTION');
this.eat('LPAREN');
let idx = 0;
const args = [];
while (((_b = this.currentToken) === null || _b === void 0 ? void 0 : _b.type) !== 'RPAREN') {
const param = yield this.expr(engineScope);
if (func.parameters !== undefined) {
funcEngineScope.define(func.parameters[idx], param);
}
args.push(param);
if (((_c = this.currentToken) === null || _c === void 0 ? void 0 : _c.type) === 'COMMA') {
this.eat('COMMA');
}
idx += 1;
}
this.eat('RPAREN');
if (funcType === interfaces_1.FunctionType.BUILTIN || funcType === interfaces_1.FunctionType.CLASS_METHOD) {
return yield func.func.apply(func.thisArg, args);
}
else {
const engine = new Engine(new lexer_1.CompiledLexer(func.func));
yield engine.parse(funcEngineScope);
return funcEngineScope.lookup('@RETURN_VALUE');
}
});
}
ruleDefenition(engineScope) {
var _a;
this.eat('RULE');
const name = (_a = this.currentToken) === null || _a === void 0 ? void 0 : _a.value;
this.eat('STRING');
this.eat('WHEN');
const condition = this.getCondition('THEN');
const body = this.getBody('THEN');
engineScope.addRule(name, { condition, body });
}
functionDefinition(engineScope) {
var _a, _b, _c, _d;
this.eat('FUNCTION');
const name = (_a = this.currentToken) === null || _a === void 0 ? void 0 : _a.value;
this.eat('IDENTIFIER');
this.eat('LPAREN');
const parameters = [];
while (((_b = this.currentToken) === null || _b === void 0 ? void 0 : _b.type) !== 'RPAREN') {
parameters.push((_c = this.currentToken) === null || _c === void 0 ? void 0 : _c.value);
this.eat('IDENTIFIER');
if (((_d = this.currentToken) === null || _d === void 0 ? void 0 : _d.type) === 'COMMA') {
this.eat('COMMA');
}
}
this.eat('RPAREN');
const body = this.getBody('DO');
engineScope.declare(name, parameters, body);
}
parse(engineScop) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
while (((_a = this.currentToken) === null || _a === void 0 ? void 0 : _a.type) !== 'EOF') {
yield this.statement(engineScop);
}
});
}
fire(engineScope) {
return __awaiter(this, void 0, void 0, function* () {
const rules = engineScope.getRuleNames().map((name) => __awaiter(this, void 0, void 0, function* () {
const rule = engineScope.getRule(name);
if (rule !== undefined) {
const condEngine = new Engine(new lexer_1.CompiledLexer(rule.condition));
const cond = yield condEngine.expr(engineScope);
if (cond) {
const bodyEngine = new Engine(new lexer_1.CompiledLexer(rule.body));
yield bodyEngine.parse(engineScope);
}
}
}));
yield Promise.all(rules);
});
}
reset() {
this.lexer.reset();
this.currentToken = this.lexer.getNextToken();
}
}
exports.default = Engine;