UNPKG

arn-language

Version:
414 lines (382 loc) 15.9 kB
const constants = require('./constants.js'); const { ArnError } = require('./errors.js'); const precedence = constants.PRECEDENCE; function compare(original, partial) { return !Object.keys(partial).some((key) => partial[key] !== original[key]); } function getFoldLength(tokens, from) { let bIndex = from; let counter = 0; while (!compare(tokens[bIndex--], {type: "punctuation", value: "{"}) || counter > 0 && bIndex >= 0) { if (tokens[bIndex].type === "punctuation" && tokens[bIndex].value === "}") counter++; else if (tokens[bIndex].type === "punctuation" && tokens[bIndex].value === "{") counter--; }; bIndex += 1; return [0, bIndex]; } Array.prototype.last = function () { return this.length ? this[this.length - 1] : -100000; } // Version 1 as of 8/6/2020 2:42 PM EST module.exports.makeAST = function makeAST(tokens, original, parent_ast = false) { const stream = tokens; let index = -1; const last = () => stream[index - 1]; const look = () => stream[index]; const peek = () => stream[index + 1]; const next = () => stream[++index]; let ast = {type: "prog", contents: []}; // Stores precedence info let current_prec = []; function isPunc(char, val = false) { return (val || look()) && (val || look()).type === "punctuation" && (val || look()).value === char; } // TODO: Make sure this will work for all edge cases function validItem(obj) { return obj && obj.type !== "block" && obj.type !== "function" && (precedence[obj.value] && obj.type !== "string" ? precedence[obj.value] >= current_prec.last() : true);// && obj.type !== "infix"; } function isFunction(key) { let mapped = ast.contents.filter(r => r.type === "function").map(r => [r.value, r.args && r.args.length]); for (let key in constants.builtins) mapped.push([key, constants.builtins[key]]); if (parent_ast) mapped = mapped.concat(parent_ast.contents.filter(r => r.type === "function").map(r => [r.value, r.args && r.args.length])); let res = mapped.filter(r => r[0] === key); return res.length !== 0 && res; } function parseContents(start = "{", end = "}") { let contents = []; let offset = 0; while (next() && (!isPunc(end) || offset)) { if (!offset && isPunc(end)) break; else if (isPunc(end)) offset--; else if (isPunc(start)) offset++; contents.push(look()); } return contents; } function maybeScalar(arg = false, infix = false) { if (!look()) return {type: "variable", value: "_"} // Checks for suffixes/infixes if already the arg of another function to prevent issues if (arg && next() && look().type === "punctuation" && precedence[look().value] && precedence[look().value] > current_prec) { ast.contents.push(last()) return parseFix(arg); } else if (arg) index--; if (look().type === "string" || look().type === "integer" || look().type === "boolean") { if (look().type === "integer") { if (/e[0-9]+/g.test(look().value)) { return {type: "integer", value: +("1" + look().value)} } else { return look(); } } else { return look(); } } else if (look().type === "variable") { let data; if (isPunc("{", peek())) return false; else if (data = isFunction(look().value)[0]) { let count = data[1]; let current = look(); // The first argument will be passed into the function through "." if it exists. if (isPunc(".", last())) count -= 1; let args = []; while (count > 0) { next(); args.push(maybeExpr()); count -= 1; } return { type: "call", value: data[0], args, pos: current.pos, line: current.line }; } else if (isPunc(":=", peek()) || (isPunc("(", peek()) && stream.filter(r => isPunc(':=', r)))) { // The creation of a function let name = look(); next(); let args; let current; if (isPunc("(")) { current = look(); args = parseContents("(", ")"); next(); } if (!isPunc(":=", look())) { ast.contents.push(name); index--; if (args && args.length) return { type: "expression", contents: makeAST(args, original, parent_ast || ast), pos: current.pos, line: current.line }; else return {}; } else { next(); return { type: "function", value: name.value, args: args || false, body: maybeExpr(), pos: name.pos, line: name.line }; } } else return look(); } else { let save = ""; throw ArnError("Couldn't recognize token", original, look().line, look().pos); } } function parseExpr() { let current = look(); let obj = { type: "expression", contents: makeAST(parseContents("(", ")"), original, parent_ast || ast), pos: current.pos, line: current.line }; if (next() && look().type === "punctuation" && precedence[look().value] && precedence[look().value] > current_prec.last()) { ast.contents.push(obj); current_prec.push(precedence[look().value]) return parseFix(); } else if (look() && look().type === "punctuation" && precedence[look().value]) { current_prec.push(precedence[look().value]) } else { current_prec.pop(); } index--; return obj; } // Called from index BEFORE "{" function parseBlock() { let arg = "_" if (look() && look().type === "variable") arg = look().value; next(); let current = look(); let contents = parseContents("{", "}"); return { type: "block", arg: arg, contents: makeAST(contents, original, parent_ast || ast), pos: current.pos, line: current.line }; } function parseArray() { let current = look(); let obj = { type: "array", contents: makeAST(parseContents("[", "]"), original, parent_ast || ast), pos: current.pos, line: current.line }; if (next() && look().type === "punctuation" && precedence[look().value] && precedence[look().value] > current_prec.last()) { ast.contents.push(obj); current_prec.push(precedence[look().value]) return parseFix(); } else if (look() && look().type === "punctuation" && precedence[look().value]) { current_prec.push(precedence[look().value]) } else { current_prec.pop(); } index--; return obj; } function parseFix(arg = false, lone = false) { let tok = look().value; let current = look(); let ret_obj; if (arg && isPunc("\\")) { index--; return false; } current_prec.push(precedence[tok]); if (constants.prefixes.includes(tok)) { // Fold if (tok === "\\") { let block = false; let args; if ((block = ast.contents.pop() || {}).type === "block") { ast.contents.pop(); let fLength = getFoldLength(stream, index); if (fLength[1] === 0) args = []; else args = stream.slice(fLength[0], fLength[1] - (block.arg !== "_" ? 1 : 0)); } else { block = false; args = stream.slice(0, stream.indexOf(look())); } next(); ret_obj = { type: "prefix", value: tok, fold_ops: args, map_ops: block, arg: maybeExpr(true) || {type: "variable", value: "_"}, pos: current.pos, line: current.line } // Filter } else if (tok === "$") { if (peek().type === "variable") next(); let contents = parseBlock(); if (!contents) throw ArnError("Must provide block to prefix $", original, current.line, current.pos); next(); ret_obj = { type: "prefix", value: tok, block: contents, arg: maybeExpr(true) || {type: "variable", value: "_"}, pos: current.pos, line: current.line }; // Any } else if (tok === "$:") { if (peek().type === "variable") next(); let contents = parseBlock(); if (!contents) throw ArnError("Must provide block to prefix $:", original, current.line, current.pos); next(); ret_obj = { type: "prefix", value: tok, block: contents, arg: maybeExpr(true) || {type: "variable", value: "_"}, pos: current.pos, line: current.line }; } else if (tok === "&.") { if (peek().type === "variable") next(); let contents = parseBlock(); if (!contents) throw ArnError("Must provide block to prefix &.", original, current.line, current.pos); next(); let first = maybeExpr(true); next(); let second = maybeExpr(true); ret_obj = { type: "prefix", value: tok, block: contents, args: compare(first, second) ? first : [first, second], pos: current.pos, line: current.lin }; } else { next(); ret_obj = { type: "prefix", value: tok, arg: maybeExpr(true) || {type: "variable", value: "_"}, pos: current.pos, line: current.line } } } else if (constants.infixes.includes(tok)) { let left; if (validItem(ast.contents[ast.contents.length - 1])) left = ast.contents.pop(); next(); if (tok === "." && (!look() || look().type !== "variable")) throw ArnError("Cannot call dot infix on a non-function.", original, current.line, current.pos); if (tok === "z") { if (look().type === "punctuation" && !isPunc("(") && !isPunc("[") && !isPunc("{")) { next(); ret_obj = { type: "infix", value: tok, arg: last(), left: left || {type: "variable", value: "_"}, right: maybeExpr(true, true) || {type: "variable", value: "_"}, pos: current.pos, line: current.line } } else { ret_obj = { type: "infix", value: tok, left: left || {type: "variable", value: "_"}, right: maybeExpr(true, true) || {type: "variable", value: "_"}, pos: current.pos, line: current.line } } } else if (tok === "@") { let fix = parseFix(true, true); next(); ret_obj = { type: "infix", value: tok, fix: fix, left: left || {type: "variable", value: "_"}, pos: current.pos, line: current.line }; } else { ret_obj = { type: "infix", value: tok, left: left || {type: "variable", value: "_"}, right: maybeExpr(true, true) || {type: "variable", value: "_"}, pos: current.pos, line: current.line }; } } else if (constants.suffixes.includes(tok)) { let left; if (!arg && validItem(ast.contents[ast.contents.length - 1])) left = ast.contents.pop(); if (tok === ";") { let ops = next().value.split(""); ret_obj = { type: "suffix", value: ";", arg: left || {type: "variable", value: "_"}, ops, pos: current.pos, line: current.line }; } else { ret_obj = { type: "suffix", value: tok, arg: left || {type: "variable", value: "_"}, pos: current.pos, line: current.line }; } } else { return false; } if (!lone && ret_obj && next() && precedence[look().value] && current_prec.last() <= precedence[look().value]) { ast.contents.push(ret_obj); // parseFix will push the token precedence, so we can omit that here ast.contents.push(parseFix(true)); return ast.contents.pop(); } else if (!lone && look() && precedence[look().value]) { current_prec.push(precedence[look().value]); } else if (!lone) { current_prec.pop(); } index--; return ret_obj; } function maybeExpr(arg = false, infix = false) { if (look() && look().type === "punctuation") { if (look().value === "(") { return parseExpr(); } else if (look().value === "{") { index--; if (infix) return {type: "variable", value: "_"}; return parseBlock(); } else if (look().value === "[") { return parseArray(); } else { return parseFix(arg); } } else { return maybeScalar(arg, infix); } } while (next()) { if (index >= stream.length) break; let obj = maybeExpr(); if (obj) ast.contents.push(obj); } return ast; }