UNPKG

fruitsconfits

Version:

FruitsConfits - A well typed and sugared parser combinator framework for TypeScript/JavaScript.

244 lines 16.9 kB
// Copyright (c) 2019 Shellyl_N and Authors // license: ISC // https://github.com/shellyln // tslint:disable: no-implicit-dependencies import { parserInput } from '../../lib/types'; import { formatErrorMessage } from '../../lib/parser'; import { getStringParsers } from '../../lib/string-parser'; import { getObjectParsers } from '../../lib/object-parser'; import * as liyad from 'liyad'; const $s = getStringParsers({ rawToToken: rawToken => rawToken, concatTokens: tokens => (tokens.length ? // eslint-disable-next-line @typescript-eslint/restrict-plus-operands [tokens.reduce((a, b) => String(a) + b)] : []), }); const $o = getObjectParsers({ rawToToken: rawToken => rawToken, concatTokens: tokens => (tokens.length ? // eslint-disable-next-line @typescript-eslint/restrict-plus-operands [tokens.reduce((a, b) => String(a) + b)] : []), comparator: (a, b) => a === b, }); const { seq, cls, notCls, clsFn, classes, numbers, cat, once, repeat, qty, zeroWidth, err, beginning, end, first, or, combine, erase, trans, ahead, rules, makeProgram } = $s; const lineComment = combine(seq('//'), repeat(notCls('\r\n', '\n', '\r')), classes.newline); const hashLineComment = combine(seq('#'), repeat(notCls('\r\n', '\n', '\r')), classes.newline); const blockComment = combine(seq('/*'), repeat(notCls('*/')), seq('*/')); const commentOrSpace = first(classes.space, lineComment, hashLineComment, blockComment); const trueValue = trans(tokens => [true])(seq('true')); const falseValue = trans(tokens => [false])(seq('false')); const nullValue = trans(tokens => [null])(seq('null')); const undefinedValue = trans(tokens => [void 0])(seq('undefined')); const positiveInfinityValue = trans(tokens => [Number.POSITIVE_INFINITY])(qty(0, 1)(seq('+')), seq('Infinity')); const negativeInfinityValue = trans(tokens => [Number.NEGATIVE_INFINITY])(seq('-Infinity')); const nanValue = trans(tokens => [Number.NaN])(seq('NaN')); const binaryIntegerValue = trans(tokens => [Number.parseInt(tokens[0].replace(/_/g, ''), 2)])(numbers.bin(seq('0b'))); const octalIntegerValue = trans(tokens => [Number.parseInt(tokens[0].replace(/_/g, ''), 8)])(numbers.oct(seq('0o'), seq('0'))); const hexIntegerValue = trans(tokens => [Number.parseInt(tokens[0].replace(/_/g, ''), 16)])(numbers.hex(seq('0x'), seq('0X'))); const decimalIntegerValue = trans(tokens => [Number.parseInt(tokens[0].replace(/_/g, ''), 10)])(numbers.int); const floatingPointNumberValue = trans(tokens => [Number.parseFloat(tokens[0].replace(/_/g, ''))])(numbers.float); const numberValue = first(octalIntegerValue, hexIntegerValue, binaryIntegerValue, floatingPointNumberValue, decimalIntegerValue, positiveInfinityValue, negativeInfinityValue, nanValue); const stringEscapeSeq = first(trans(t => ['\''])(seq('\\\'')), trans(t => ['"'])(seq('\\"')), trans(t => ['`'])(seq('\\`')), trans(t => ['\\'])(seq('\\\\')), trans(t => [''])(seq('\\\r\n')), trans(t => [''])(seq('\\\r')), trans(t => [''])(seq('\\\n')), trans(t => ['\n'])(seq('\\n')), trans(t => ['\r'])(seq('\\r')), trans(t => ['\v'])(seq('\\v')), trans(t => ['\t'])(seq('\\t')), trans(t => ['\b'])(seq('\\b')), trans(t => ['\f'])(seq('\\f')), trans(t => [String.fromCodePoint(Number.parseInt(t[0], 16))])(cat(erase(seq('\\u')), qty(4, 4)(classes.hex))), trans(t => [String.fromCodePoint(Number.parseInt(t[0], 16))])(cat(erase(seq('\\u{')), qty(1, 6)(classes.hex), erase(seq('}')))), trans(t => [String.fromCodePoint(Number.parseInt(t[0], 16))])(cat(erase(seq('\\x')), qty(2, 2)(classes.hex))), trans(t => [String.fromCodePoint(Number.parseInt(t[0], 8))])(cat(erase(seq('\\')), qty(3, 3)(classes.oct)))); const signleQuotStringValue = trans(tokens => [tokens[0]])(erase(seq("'")), cat(repeat(first(stringEscapeSeq, combine(cls('\r', '\n'), err('Line breaks within strings are not allowed.')), notCls("'")))), erase(seq("'"))); const doubleQuotStringValue = trans(tokens => [tokens[0]])(erase(seq('"')), cat(repeat(first(stringEscapeSeq, combine(cls('\r', '\n'), err('Line breaks within strings are not allowed.')), notCls('"')))), erase(seq('"'))); const backQuotStringValue = trans(tokens => [tokens[0]])(erase(seq('`')), cat(repeat(first(stringEscapeSeq, notCls('`')))), erase(seq('`'))); const stringValue = first(signleQuotStringValue, doubleQuotStringValue, backQuotStringValue); const atomValue = first(trueValue, falseValue, nullValue, undefinedValue, numberValue, stringValue); const symbolName = trans(tokens => [{ symbol: tokens[0] }])(cat(combine(classes.alpha, repeat(classes.alnum)))); const objKey = first(stringValue, symbolName); const listValue = first(trans(tokens => [[]])(erase(seq('['), repeat(commentOrSpace), seq(']'))), trans(tokens => { const ast = [{ symbol: '$list' }]; for (const token of tokens) { ast.push(token); } return [ast]; })(erase(seq('[')), once(combine(erase(repeat(commentOrSpace)), first(input => listValue(input), // NOTE: recursive definitions // NOTE: recursive definitions input => objectValue(input), // should place as lambda. // should place as lambda. input => expr(first(seq(','), seq(']')), false)(input)), erase(repeat(commentOrSpace)))), repeat(combine(erase(repeat(commentOrSpace), seq(','), repeat(commentOrSpace)), first(input => listValue(input), // NOTE: recursive definitions // NOTE: recursive definitions input => objectValue(input), // should place as lambda. // should place as lambda. input => expr(first(seq(','), seq(']')), false)(input)), erase(repeat(commentOrSpace)))), qty(0, 1)(erase(seq(','), repeat(commentOrSpace))), first(ahead(seq(']')), err('Unexpected token has appeared.')), erase(seq(']')))); const objectKeyValuePair = combine(objKey, erase(repeat(commentOrSpace), first(seq(':'), err('":" is needed.')), repeat(commentOrSpace)), first(input => listValue(input), // NOTE: recursive definitions // NOTE: recursive definitions input => objectValue(input), // should place as lambda. // should place as lambda. input => expr(first(seq(','), seq('}')), false)(input), err('object value is needed.'))); const objectValue = first(trans(tokens => [[{ symbol: '#' }]])(erase(seq('{'), repeat(commentOrSpace), seq('}'))), trans(tokens => { const ast = [{ symbol: '#' }]; for (let i = 0; i < tokens.length; i += 2) { if (tokens[i] === '__proto__') { continue; // NOTE: prevent prototype pollution attacks } ast.push([tokens[i], tokens[i + 1]]); } return [ast]; })(erase(seq('{')), once(combine(erase(repeat(commentOrSpace)), objectKeyValuePair, erase(repeat(commentOrSpace)))), repeat(combine(erase(seq(','), repeat(commentOrSpace)), objectKeyValuePair, erase(repeat(commentOrSpace)))), qty(0, 1)(erase(seq(','), repeat(commentOrSpace))), first(ahead(seq('}')), err('Unexpected token has appeared.')), erase(seq('}')))); const unaryOp = (op, op1) => { // eslint-disable-next-line @typescript-eslint/no-unsafe-return return [{ symbol: op }, op1]; }; const binaryOp = (op, op1, op2) => { if (op === ',') { const operands = []; if (Array.isArray(op1) && liyad.isSymbol(op1[0], '$last')) { operands.push(...op1.slice(1)); } else { operands.push(op1); } if (Array.isArray(op2) && liyad.isSymbol(op2[0], '$last')) { operands.push(...op2.slice(1)); } else { operands.push(op2); } return [{ symbol: '$last' }, ...operands]; } // eslint-disable-next-line @typescript-eslint/no-unsafe-return return [{ symbol: op }, op1, op2]; }; const ternaryOp = (op, op1, op2, op3) => { // eslint-disable-next-line @typescript-eslint/no-unsafe-return return [{ symbol: op }, op1, op2, op3]; }; const isOperator = (v, op) => { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (typeof v === 'object' && v.op === op) { return true; } return false; }; const isValue = (v) => { switch (typeof v) { case 'number': case 'boolean': case 'string': case 'undefined': case 'bigint': case 'function': return true; case 'symbol': return false; } if (v === null) { return true; } if (Object.prototype.hasOwnProperty.call(v, '#')) { return true; } if (Array.isArray(v)) { return true; } return false; }; const exprOpsTokens = ['**', '*', '/', '%', '+', '-', '?', ':']; const edgeOpsTokens = exprOpsTokens.concat(',', '('); const exprOps = cls(...exprOpsTokens); const transformOp = (op) => trans(tokens => [{ op: tokens[0] }])(op); const beginningOrEdgeOp = $o.first($o.beginning(() => ({ op: '$noop' })), $o.behind(1, () => ({ op: '$noop' }))( // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access $o.clsFn(t => t && edgeOpsTokens.includes(t.op) ? true : false))); // production rule: // beginning S -> beginning "(" E ")" // op S -> op "(" E ")" const exprRule20 = $o.trans(tokens => { return [tokens[2]]; })(beginningOrEdgeOp, $o.clsFn(t => isOperator(t, '(')), $o.clsFn(t => isValue(t)), $o.clsFn(t => isOperator(t, ')'))); // production rule: // S -> S<<symbol>> "(" S ")" // S -> S<<value>> "(" S ")" // S -> S<<symbol>> "(" ")" // S -> S<<value>> "(" ")" const exprRule18 = $o.trans(tokens => { if (Array.isArray(tokens[1]) && liyad.isSymbol(tokens[1][0], '$last')) { return [[tokens[0], ...tokens[1].slice(1)]]; } else { return [[tokens[0], ...(tokens.length > 1 ? [tokens[1]] : [])]]; } })($o.first($o.clsFn(t => liyad.isSymbol(t) ? true : false), $o.clsFn(t => isValue(t))), $o.erase($o.clsFn(t => isOperator(t, '('))), $o.qty(0, 1)($o.first($o.clsFn(t => Array.isArray(t) && liyad.isSymbol(t[0], '$last') ? true : false), $o.clsFn(t => isValue(t)))), $o.erase($o.clsFn(t => isOperator(t, ')')))); // production rule: // beginning S -> beginning "+" S // op S -> op "+" S // beginning S -> beginning "-" S // op S -> op "-" S const exprRule16 = $o.trans(tokens => { return ([unaryOp(tokens[1].op, tokens[2])]); })(beginningOrEdgeOp, $o.clsFn(t => isOperator(t, '+') || isOperator(t, '-')), $o.clsFn(t => isValue(t))); // production rule: // S -> S "**" S const exprRule15 = $o.trans(tokens => [binaryOp(tokens[1].op, tokens[0], tokens[2])])($o.clsFn(t => isValue(t)), $o.clsFn(t => isOperator(t, '**')), $o.clsFn(t => isValue(t))); // production rules: // S -> S "*" S // S -> S "/" S // S -> S "%" S const exprRule14 = $o.trans(tokens => [binaryOp(tokens[1].op, tokens[0], tokens[2])])($o.clsFn(t => isValue(t)), $o.clsFn(t => isOperator(t, '*') || isOperator(t, '/') || isOperator(t, '%')), $o.clsFn(t => isValue(t))); // production rules: // S -> S "+" S // S -> S "-" S const exprRule13 = $o.trans(tokens => [binaryOp(tokens[1].op, tokens[0], tokens[2])])($o.clsFn(t => isValue(t)), $o.clsFn(t => isOperator(t, '+') || isOperator(t, '-')), $o.clsFn(t => isValue(t))); // production rule: // beginning S -> beginning S "?" S ":" S // "," S -> "," S "?" S ":" S const exprRule4 = $o.trans(tokens => { return [ternaryOp('$if', tokens[1], tokens[3], tokens[5])]; })($o.first($o.beginning(() => ({ op: '$noop' })), $o.behind(1, () => ({ op: '$noop' }))($o.clsFn(t => isOperator(t, ',')))), $o.clsFn(t => isValue(t)), $o.clsFn(t => isOperator(t, '?')), $o.clsFn(t => isValue(t)), $o.clsFn(t => isOperator(t, ':')), $o.clsFn(t => isValue(t))); // production rule: // beginning S -> beginning S "," S // "(" S -> "(" S "," S const exprRule1 = $o.trans(tokens => { return [binaryOp(tokens[2].op, tokens[1], tokens[3])]; })($o.first($o.beginning(() => ({ op: '$noop' })), $o.behind(1, () => ({ op: '$noop' }))($o.clsFn(t => isOperator(t, '(')))), $o.clsFn(t => isValue(t)), $o.clsFn(t => isOperator(t, ',')), $o.clsFn(t => isValue(t))); const exprNested = (input) => exprInner(cls(')'), true)(input); const exprInner = (edge, nested) => combine(qty(1)(first(erase(commentOrSpace), transformOp(combine(cls('+', '-'), ahead(classes.num))), listValue, objectValue, // TODO: lambdaFnValue atomValue, symbolName, transformOp(nested ? first(exprOps, cls(',')) : exprOps), combine(transformOp(cls('(')), qty(0, 1)(exprNested), transformOp(cls(')'))))), ahead(repeat(commentOrSpace), edge)); const expr = (edge, nested) => rules({ rules: [ exprRule20, exprRule18, { parser: exprRule16, rtol: true }, exprRule15, exprRule14, exprRule13, { parser: exprRule4, rtol: true }, exprRule1, ], check: $o.combine($o.classes.any, $o.end()), })(exprInner(edge, nested)); const exprStatement = expr(first(end(), seq(';')), true); const letStatementInner = combine(symbolName, erase(repeat(commentOrSpace)), erase(seq('=')), erase(repeat(commentOrSpace)), expr(first(end(), seq(';')), true)); const letStatement = combine(erase(seq('let')), erase(qty(1)(commentOrSpace)), first(combine(letStatementInner, repeat(combine(erase(repeat(commentOrSpace), seq(','), repeat(commentOrSpace)), letStatementInner))), err('Unexpected token has appeared.'))); const singleStatement = first(exprStatement, letStatement); const singleStatementSC = combine(singleStatement, erase(repeat(commentOrSpace)), first(ahead(end()), ahead(cls('{', '}')), ahead(seq('let'), first(commentOrSpace, cls('{'))), ahead(seq('for'), first(commentOrSpace, cls('{'))), ahead(seq('while'), first(commentOrSpace, cls('{'))), ahead(seq('do'), first(commentOrSpace, cls('{'))), ahead(seq('if'), first(commentOrSpace, cls('{'))), erase(seq(';')), err('Unexpected token has appeared.'))); const blockStatement = combine(erase(seq('{')), (input) => statements(input), erase(seq('}'))); // TODO: for, while, do, if(if-elseif-else), switch, return, break statements const ifStatement = combine(erase(seq('if')), erase(repeat(commentOrSpace)), erase(seq('(')), erase(repeat(commentOrSpace)), expr(first(seq(')')), true), erase(repeat(commentOrSpace)), erase(seq(')')), erase(repeat(commentOrSpace)), first(blockStatement, singleStatementSC)); const switchStatement = combine(erase(seq('switch')), erase(repeat(commentOrSpace)), erase(seq('(')), erase(repeat(commentOrSpace)), expr(first(seq(')')), true), erase(repeat(commentOrSpace)), erase(seq(')')), erase(repeat(commentOrSpace)), erase(seq('{')), erase(repeat(commentOrSpace)), repeat(first(combine(erase(seq('case')), erase(repeat(commentOrSpace)), expr(first(seq(':')), true), erase(seq(':')), erase(repeat(commentOrSpace)), repeat(first(blockStatement, singleStatementSC)), erase(repeat(commentOrSpace))), combine(erase(seq('default')), erase(repeat(commentOrSpace)), erase(seq(':')), erase(repeat(commentOrSpace)), repeat(first(blockStatement, singleStatementSC)), erase(repeat(commentOrSpace))))), erase(seq('}'))); const forStatement = combine(erase(seq('while')), erase(repeat(commentOrSpace)), erase(seq('(')), erase(repeat(commentOrSpace)), expr(first(seq(';')), true), erase(seq(';')), erase(repeat(commentOrSpace)), expr(first(seq(';')), true), erase(seq(';')), erase(repeat(commentOrSpace)), expr(first(seq(')')), true), erase(seq(')')), erase(repeat(commentOrSpace)), first(blockStatement, singleStatementSC)); const whileStatement = combine(erase(seq('while')), erase(repeat(commentOrSpace)), erase(seq('(')), erase(repeat(commentOrSpace)), expr(first(seq(')')), true), erase(seq(')')), erase(repeat(commentOrSpace)), first(blockStatement, singleStatementSC)); const doWhileStatement = combine(erase(seq('do')), erase(repeat(commentOrSpace)), first(blockStatement, singleStatementSC), erase(repeat(commentOrSpace)), erase(seq('while')), erase(seq('(')), erase(repeat(commentOrSpace)), expr(first(seq(')')), true), erase(seq(')'))); const statements = qty(1)(first(blockStatement, ifStatement, switchStatement, forStatement, whileStatement, doWhileStatement, singleStatementSC)); const program = makeProgram(trans(tokens => tokens)(erase(repeat(commentOrSpace)), expr(end(), true), erase(repeat(commentOrSpace)), end())); // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function parse(s) { const z = program(parserInput(s)); if (!z.succeeded) { throw new Error(formatErrorMessage(z)); } return z.tokens[0]; } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function evaluate(s) { const z = parse(s); liyad.lisp.setGlobals({ max: Math.max, twice: (x) => x * 2, one: () => 1, }); return liyad.lisp.evaluateAST([z]); } //# sourceMappingURL=index.js.map