function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } import { codeFrameFromSource } from "@webassemblyjs/helper-code-frame"; import * as t from "@webassemblyjs/ast"; import { parse32I } from "./number-literals"; import { parseString } from "./string-literals"; import { tokens, keywords } from "./tokenizer"; function hasPlugin(name) { if (name !== "wast") throw new Error("unknow plugin"); return true; } function isKeyword(token, id) { return token.type === tokens.keyword && token.value === id; } function tokenToString(token) { if (token.type === "keyword") { return "keyword (".concat(token.value, ")"); } return token.type; } function identifierFromToken(token) { var _token$loc = token.loc, end = _token$loc.end, start = _token$loc.start; return t.withLoc(t.identifier(token.value), end, start); } export function parse(tokensList, source) { var current = 0; var getUniqueName = t.getUniqueNameGenerator(); var state = { registredExportedElements: [] }; // But this time we're going to use recursion instead of a `while` loop. So we // define a `walk` function. function walk() { var token = tokensList[current]; function eatToken() { token = tokensList[++current]; } function getEndLoc() { var currentToken = token; if (typeof currentToken === "undefined") { var lastToken = tokensList[tokensList.length - 1]; currentToken = lastToken; } return currentToken.loc.end; } function getStartLoc() { return token.loc.start; } function eatTokenOfType(type) { if (token.type !== type) { throw new Error("\n" + codeFrameFromSource(source, token.loc) + "Assertion error: expected token of type " + type + ", given " + tokenToString(token)); } eatToken(); } function parseExportIndex(token) { if (token.type === tokens.identifier) { var index = identifierFromToken(token); eatToken(); return index; } else if (token.type === tokens.number) { var _index = t.numberLiteralFromRaw(token.value); eatToken(); return _index; } else { throw function () { return new Error("\n" + codeFrameFromSource(source, token.loc) + "\n" + "unknown export index" + ", given " + tokenToString(token)); }(); } } function lookaheadAndCheck() { var len = arguments.length; for (var i = 0; i < len; i++) { var tokenAhead = tokensList[current + i]; var expectedToken = i < 0 || arguments.length <= i ? undefined : arguments[i]; if (tokenAhead.type === "keyword") { if (isKeyword(tokenAhead, expectedToken) === false) { return false; } } else if (expectedToken !== tokenAhead.type) { return false; } } return true; } // TODO(sven): there is probably a better way to do this // can refactor it if it get out of hands function maybeIgnoreComment() { if (typeof token === "undefined") { // Ignore return; } while (token.type === tokens.comment) { eatToken(); if (typeof token === "undefined") { // Hit the end break; } } } /** * Parses a memory instruction * * WAST: * * memory: ( memory <name>? <memory_sig> ) * ( memory <name>? ( export <string> ) <...> ) * ( memory <name>? ( import <string> <string> ) <memory_sig> ) * ( memory <name>? ( export <string> )* ( data <string>* ) * memory_sig: <nat> <nat>? * */ function parseMemory() { var id = t.identifier(getUniqueName("memory")); var limits = t.limit(0); if (token.type === tokens.string || token.type === tokens.identifier) { id = t.identifier(token.value); eatToken(); } else { id = t.withRaw(id, ""); // preserve anonymous } /** * Maybe data */ if (lookaheadAndCheck(tokens.openParen, keywords.data)) { eatToken(); // ( eatToken(); // data // TODO(sven): do something with the data collected here var stringInitializer = token.value; eatTokenOfType(tokens.string); // Update limits accordingly limits = t.limit(stringInitializer.length); eatTokenOfType(tokens.closeParen); } /** * Maybe export */ if (lookaheadAndCheck(tokens.openParen, keywords.export)) { eatToken(); // ( eatToken(); // export if (token.type !== tokens.string) { throw function () { return new Error("\n" + codeFrameFromSource(source, token.loc) + "\n" + "Expected string in export" + ", given " + tokenToString(token)); }(); } var _name = token.value; eatToken(); state.registredExportedElements.push({ exportType: "Memory", name: _name, id: id }); eatTokenOfType(tokens.closeParen); } /** * Memory signature */ if (token.type === tokens.number) { limits = t.limit(parse32I(token.value)); eatToken(); if (token.type === tokens.number) { limits.max = parse32I(token.value); eatToken(); } } return t.memory(limits, id); } /** * Parses a data section * https://webassembly.github.io/spec/core/text/modules.html#data-segments * * WAST: * * data: ( data <index>? <offset> <string> ) */ function parseData() { // optional memory index var memidx = 0; if (token.type === tokens.number) { memidx = token.value; eatTokenOfType(tokens.number); // . } eatTokenOfType(tokens.openParen); var offset; if (token.type === tokens.valtype) { eatTokenOfType(tokens.valtype); // i32 eatTokenOfType(tokens.dot); // . if (token.value !== "const") { throw new Error("constant expression required"); } eatTokenOfType(tokens.name); // const var numberLiteral = t.numberLiteralFromRaw(token.value, "i32"); offset = t.objectInstruction("const", "i32", [numberLiteral]); eatToken(); eatTokenOfType(tokens.closeParen); } else { eatTokenOfType(tokens.name); // get_global var _numberLiteral = t.numberLiteralFromRaw(token.value, "i32"); offset = t.instruction("get_global", [_numberLiteral]); eatToken(); eatTokenOfType(tokens.closeParen); } var byteArray = parseString(token.value); eatToken(); // "string" return t.data(t.memIndexLiteral(memidx), offset, t.byteArray(byteArray)); } /** * Parses a table instruction * * WAST: * * table: ( table <name>? <table_type> ) * ( table <name>? ( export <string> ) <...> ) * ( table <name>? ( import <string> <string> ) <table_type> ) * ( table <name>? ( export <string> )* <elem_type> ( elem <var>* ) ) * * table_type: <nat> <nat>? <elem_type> * elem_type: anyfunc * * elem: ( elem <var>? (offset <instr>* ) <var>* ) * ( elem <var>? <expr> <var>* ) */ function parseTable() { var name = t.identifier(getUniqueName("table")); var limit = t.limit(0); var elemIndices = []; var elemType = "anyfunc"; if (token.type === tokens.string || token.type === tokens.identifier) { name = identifierFromToken(token); eatToken(); } else { name = t.withRaw(name, ""); // preserve anonymous } while (token.type !== tokens.closeParen) { /** * Maybe export */ if (lookaheadAndCheck(tokens.openParen, keywords.elem)) { eatToken(); // ( eatToken(); // elem while (token.type === tokens.identifier) { elemIndices.push(t.identifier(token.value)); eatToken(); } eatTokenOfType(tokens.closeParen); } else if (lookaheadAndCheck(tokens.openParen, keywords.export)) { eatToken(); // ( eatToken(); // export if (token.type !== tokens.string) { throw function () { return new Error("\n" + codeFrameFromSource(source, token.loc) + "\n" + "Expected string in export" + ", given " + tokenToString(token)); }(); } var exportName = token.value; eatToken(); state.registredExportedElements.push({ exportType: "Table", name: exportName, id: name }); eatTokenOfType(tokens.closeParen); } else if (isKeyword(token, keywords.anyfunc)) { // It's the default value, we can ignore it eatToken(); // anyfunc } else if (token.type === tokens.number) { /** * Table type */ var min = parseInt(token.value); eatToken(); if (token.type === tokens.number) { var max = parseInt(token.value); eatToken(); limit = t.limit(min, max); } else { limit = t.limit(min); } eatToken(); } else { throw function () { return new Error("\n" + codeFrameFromSource(source, token.loc) + "\n" + "Unexpected token" + ", given " + tokenToString(token)); }(); } } if (elemIndices.length > 0) { return t.table(elemType, limit, name, elemIndices); } else { return t.table(elemType, limit, name); } } /** * Parses an import statement * * WAST: * * import: ( import <string> <string> <imkind> ) * imkind: ( func <name>? <func_sig> ) * ( global <name>? <global_sig> ) * ( table <name>? <table_sig> ) * ( memory <name>? <memory_sig> ) * * global_sig: <type> | ( mut <type> ) */ function parseImport() { if (token.type !== tokens.string) { throw new Error("Expected a string, " + token.type + " given."); } var moduleName = token.value; eatToken(); if (token.type !== tokens.string) { throw new Error("Expected a string, " + token.type + " given."); } var name = token.value; eatToken(); eatTokenOfType(tokens.openParen); var descr; if (isKeyword(token, keywords.func)) { eatToken(); // keyword var fnParams = []; var fnResult = []; var typeRef; var fnName = t.identifier(getUniqueName("func")); if (token.type === tokens.identifier) { fnName = identifierFromToken(token); eatToken(); } while (token.type === tokens.openParen) { eatToken(); if (lookaheadAndCheck(keywords.type) === true) { eatToken(); typeRef = parseTypeReference(); } else if (lookaheadAndCheck(keywords.param) === true) { eatToken(); fnParams.push.apply(fnParams, _toConsumableArray(parseFuncParam())); } else if (lookaheadAndCheck(keywords.result) === true) { eatToken(); fnResult.push.apply(fnResult, _toConsumableArray(parseFuncResult())); } else { throw function () { return new Error("\n" + codeFrameFromSource(source, token.loc) + "\n" + "Unexpected token in import of type" + ", given " + tokenToString(token)); }(); } eatTokenOfType(tokens.closeParen); } if (typeof fnName === "undefined") { throw new Error("Imported function must have a name"); } descr = t.funcImportDescr(fnName, typeRef !== undefined ? typeRef : t.signature(fnParams, fnResult)); } else if (isKeyword(token, keywords.global)) { eatToken(); // keyword if (token.type === tokens.openParen) { eatToken(); // ( eatTokenOfType(tokens.keyword); // mut keyword var valtype = token.value; eatToken(); descr = t.globalType(valtype, "var"); eatTokenOfType(tokens.closeParen); } else { var _valtype = token.value; eatTokenOfType(tokens.valtype); descr = t.globalType(_valtype, "const"); } } else if (isKeyword(token, keywords.memory) === true) { eatToken(); // Keyword descr = parseMemory(); } else if (isKeyword(token, keywords.table) === true) { eatToken(); // Keyword descr = parseTable(); } else { throw new Error("Unsupported import type: " + tokenToString(token)); } eatTokenOfType(tokens.closeParen); return t.moduleImport(moduleName, name, descr); } /** * Parses a block instruction * * WAST: * * expr: ( block <name>? <block_sig> <instr>* ) * instr: block <name>? <block_sig> <instr>* end <name>? * block_sig : ( result <type>* )* * */ function parseBlock() { var label = t.identifier(getUniqueName("block")); var blockResult = null; var instr = []; if (token.type === tokens.identifier) { label = identifierFromToken(token); eatToken(); } else { label = t.withRaw(label, ""); // preserve anonymous } while (token.type === tokens.openParen) { eatToken(); if (lookaheadAndCheck(keywords.result) === true) { eatToken(); blockResult = token.value; eatToken(); } else if (lookaheadAndCheck(tokens.name) === true || lookaheadAndCheck(tokens.valtype) === true || token.type === "keyword" // is any keyword ) { // Instruction instr.push(parseFuncInstr()); } else { throw function () { return new Error("\n" + codeFrameFromSource(source, token.loc) + "\n" + "Unexpected token in block body of type" + ", given " + tokenToString(token)); }(); } maybeIgnoreComment(); eatTokenOfType(tokens.closeParen); } return t.blockInstruction(label, instr, blockResult); } /** * Parses a if instruction * * WAST: * * expr: * ( if <name>? <block_sig> ( then <instr>* ) ( else <instr>* )? ) * ( if <name>? <block_sig> <expr>+ ( then <instr>* ) ( else <instr>* )? ) * * instr: * if <name>? <block_sig> <instr>* end <name>? * if <name>? <block_sig> <instr>* else <name>? <instr>* end <name>? * * block_sig : ( result <type>* )* * */ function parseIf() { var blockResult = null; var label = t.identifier(getUniqueName("if")); var testInstrs = []; var consequent = []; var alternate = []; if (token.type === tokens.identifier) { label = identifierFromToken(token); eatToken(); } else { label = t.withRaw(label, ""); // preserve anonymous } while (token.type === tokens.openParen) { eatToken(); // ( /** * Block signature */ if (isKeyword(token, keywords.result) === true) { eatToken(); blockResult = token.value; eatTokenOfType(tokens.valtype); eatTokenOfType(tokens.closeParen); continue; } /** * Then */ if (isKeyword(token, keywords.then) === true) { eatToken(); // then while (token.type === tokens.openParen) { eatToken(); // Instruction if (lookaheadAndCheck(tokens.name) === true || lookaheadAndCheck(tokens.valtype) === true || token.type === "keyword" // is any keyword ) { consequent.push(parseFuncInstr()); } else { throw function () { return new Error("\n" + codeFrameFromSource(source, token.loc) + "\n" + "Unexpected token in consequent body of type" + ", given " + tokenToString(token)); }(); } eatTokenOfType(tokens.closeParen); } eatTokenOfType(tokens.closeParen); continue; } /** * Alternate */ if (isKeyword(token, keywords.else)) { eatToken(); // else while (token.type === tokens.openParen) { eatToken(); // Instruction if (lookaheadAndCheck(tokens.name) === true || lookaheadAndCheck(tokens.valtype) === true || token.type === "keyword" // is any keyword ) { alternate.push(parseFuncInstr()); } else { throw function () { return new Error("\n" + codeFrameFromSource(source, token.loc) + "\n" + "Unexpected token in alternate body of type" + ", given " + tokenToString(token)); }(); } eatTokenOfType(tokens.closeParen); } eatTokenOfType(tokens.closeParen); continue; } /** * Test instruction */ if (lookaheadAndCheck(tokens.name) === true || lookaheadAndCheck(tokens.valtype) === true || token.type === "keyword" // is any keyword ) { testInstrs.push(parseFuncInstr()); eatTokenOfType(tokens.closeParen); continue; } throw function () { return new Error("\n" + codeFrameFromSource(source, token.loc) + "\n" + "Unexpected token in if body" + ", given " + tokenToString(token)); }(); } return t.ifInstruction(label, testInstrs, blockResult, consequent, alternate); } /** * Parses a loop instruction * * WAT: * * blockinstr :: 'loop' I:label rt:resulttype (in:instr*) 'end' id? * * WAST: * * instr :: loop <name>? <block_sig> <instr>* end <name>? * expr :: ( loop <name>? <block_sig> <instr>* ) * block_sig :: ( result <type>* )* * */ function parseLoop() { var label = t.identifier(getUniqueName("loop")); var blockResult; var instr = []; if (token.type === tokens.identifier) { label = identifierFromToken(token); eatToken(); } else { label = t.withRaw(label, ""); // preserve anonymous } while (token.type === tokens.openParen) { eatToken(); if (lookaheadAndCheck(keywords.result) === true) { eatToken(); blockResult = token.value; eatToken(); } else if (lookaheadAndCheck(tokens.name) === true || lookaheadAndCheck(tokens.valtype) === true || token.type === "keyword" // is any keyword ) { // Instruction instr.push(parseFuncInstr()); } else { throw function () { return new Error("\n" + codeFrameFromSource(source, token.loc) + "\n" + "Unexpected token in loop body" + ", given " + tokenToString(token)); }(); } eatTokenOfType(tokens.closeParen); } return t.loopInstruction(label, blockResult, instr); } function parseCallIndirect() { var typeRef; var params = []; var results = []; var instrs = []; while (token.type !== tokens.closeParen) { if (lookaheadAndCheck(tokens.openParen, keywords.type)) { eatToken(); // ( eatToken(); // type typeRef = parseTypeReference(); } else if (lookaheadAndCheck(tokens.openParen, keywords.param)) { eatToken(); // ( eatToken(); // param /** * Params can be empty: * (params)` */ if (token.type !== tokens.closeParen) { params.push.apply(params, _toConsumableArray(parseFuncParam())); } } else if (lookaheadAndCheck(tokens.openParen, keywords.result)) { eatToken(); // ( eatToken(); // result /** * Results can be empty: * (result)` */ if (token.type !== tokens.closeParen) { results.push.apply(results, _toConsumableArray(parseFuncResult())); } } else { eatTokenOfType(tokens.openParen); instrs.push(parseFuncInstr()); } eatTokenOfType(tokens.closeParen); } return t.callIndirectInstruction(typeRef !== undefined ? typeRef : t.signature(params, results), instrs); } /** * Parses an export instruction * * WAT: * * export: ( export <string> <exkind> ) * exkind: ( func <var> ) * ( global <var> ) * ( table <var> ) * ( memory <var> ) * var: <nat> | <name> * */ function parseExport() { if (token.type !== tokens.string) { throw new Error("Expected string after export, got: " + token.type); } var name = token.value; eatToken(); var moduleExportDescr = parseModuleExportDescr(); return t.moduleExport(name, moduleExportDescr); } function parseModuleExportDescr() { var startLoc = getStartLoc(); var type = ""; var index; eatTokenOfType(tokens.openParen); while (token.type !== tokens.closeParen) { if (isKeyword(token, keywords.func)) { type = "Func"; eatToken(); index = parseExportIndex(token); } else if (isKeyword(token, keywords.table)) { type = "Table"; eatToken(); index = parseExportIndex(token); } else if (isKeyword(token, keywords.global)) { type = "Global"; eatToken(); index = parseExportIndex(token); } else if (isKeyword(token, keywords.memory)) { type = "Memory"; eatToken(); index = parseExportIndex(token); } eatToken(); } if (type === "") { throw new Error("Unknown export type"); } if (index === undefined) { throw new Error("Exported function must have a name"); } var node = t.moduleExportDescr(type, index); var endLoc = getEndLoc(); eatTokenOfType(tokens.closeParen); return t.withLoc(node, endLoc, startLoc); } function parseModule() { var name = null; var isBinary = false; var isQuote = false; var moduleFields = []; if (token.type === tokens.identifier) { name = token.value; eatToken(); } if (hasPlugin("wast") && token.type === tokens.name && token.value === "binary") { eatToken(); isBinary = true; } if (hasPlugin("wast") && token.type === tokens.name && token.value === "quote") { eatToken(); isQuote = true; } if (isBinary === true) { var blob = []; while (token.type === tokens.string) { blob.push(token.value); eatToken(); maybeIgnoreComment(); } eatTokenOfType(tokens.closeParen); return t.binaryModule(name, blob); } if (isQuote === true) { var string = []; while (token.type === tokens.string) { string.push(token.value); eatToken(); } eatTokenOfType(tokens.closeParen); return t.quoteModule(name, string); } while (token.type !== tokens.closeParen) { moduleFields.push(walk()); if (state.registredExportedElements.length > 0) { state.registredExportedElements.forEach(function (decl) { moduleFields.push(t.moduleExport(decl.name, t.moduleExportDescr(decl.exportType, decl.id))); }); state.registredExportedElements = []; } token = tokensList[current]; } eatTokenOfType(tokens.closeParen); return t.module(name, moduleFields); } /** * Parses the arguments of an instruction */ function parseFuncInstrArguments(signature) { var args = []; var namedArgs = {}; var signaturePtr = 0; while (token.type === tokens.name || isKeyword(token, keywords.offset)) { var key = token.value; eatToken(); eatTokenOfType(tokens.equal); var value = void 0; if (token.type === tokens.number) { value = t.numberLiteralFromRaw(token.value); } else { throw new Error("Unexpected type for argument: " + token.type); } namedArgs[key] = value; eatToken(); } // $FlowIgnore var signatureLength = signature.vector ? Infinity : signature.length; while (token.type !== tokens.closeParen && ( // $FlowIgnore token.type === tokens.openParen || signaturePtr < signatureLength)) { if (token.type === tokens.identifier) { args.push(t.identifier(token.value)); eatToken(); } else if (token.type === tokens.valtype) { // Handle locals args.push(t.valtypeLiteral(token.value)); eatToken(); } else if (token.type === tokens.string) { args.push(t.stringLiteral(token.value)); eatToken(); } else if (token.type === tokens.number) { args.push( // TODO(sven): refactor the type signature handling // https://github.com/xtuc/webassemblyjs/pull/129 is a good start t.numberLiteralFromRaw(token.value, // $FlowIgnore signature[signaturePtr] || "f64")); // $FlowIgnore if (!signature.vector) { ++signaturePtr; } eatToken(); } else if (token.type === tokens.openParen) { /** * Maybe some nested instructions */ eatToken(); // Instruction if (lookaheadAndCheck(tokens.name) === true || lookaheadAndCheck(tokens.valtype) === true || token.type === "keyword" // is any keyword ) { // $FlowIgnore args.push(parseFuncInstr()); } else { throw function () { return new Error("\n" + codeFrameFromSource(source, token.loc) + "\n" + "Unexpected token in nested instruction" + ", given " + tokenToString(token)); }(); } if (token.type === tokens.closeParen) { eatToken(); } } else { throw function () { return new Error("\n" + codeFrameFromSource(source, token.loc) + "\n" + "Unexpected token in instruction argument" + ", given " + tokenToString(token)); }(); } } return { args: args, namedArgs: namedArgs }; } /** * Parses an instruction * * WAT: * * instr :: plaininst * blockinstr * * blockinstr :: 'block' I:label rt:resulttype (in:instr*) 'end' id? * 'loop' I:label rt:resulttype (in:instr*) 'end' id? * 'if' I:label rt:resulttype (in:instr*) 'else' id? (in2:intr*) 'end' id? * * plaininst :: 'unreachable' * 'nop' * 'br' l:labelidx * 'br_if' l:labelidx * 'br_table' l*:vec(labelidx) ln:labelidx * 'return' * 'call' x:funcidx * 'call_indirect' x, I:typeuse * * WAST: * * instr: * <expr> * <op> * block <name>? <block_sig> <instr>* end <name>? * loop <name>? <block_sig> <instr>* end <name>? * if <name>? <block_sig> <instr>* end <name>? * if <name>? <block_sig> <instr>* else <name>? <instr>* end <name>? * * expr: * ( <op> ) * ( <op> <expr>+ ) * ( block <name>? <block_sig> <instr>* ) * ( loop <name>? <block_sig> <instr>* ) * ( if <name>? <block_sig> ( then <instr>* ) ( else <instr>* )? ) * ( if <name>? <block_sig> <expr>+ ( then <instr>* ) ( else <instr>* )? ) * * op: * unreachable * nop * br <var> * br_if <var> * br_table <var>+ * return * call <var> * call_indirect <func_sig> * drop * select * get_local <var> * set_local <var> * tee_local <var> * get_global <var> * set_global <var> * <type>.load((8|16|32)_<sign>)? <offset>? <align>? * <type>.store(8|16|32)? <offset>? <align>? * current_memory * grow_memory * <type>.const <value> * <type>.<unop> * <type>.<binop> * <type>.<testop> * <type>.<relop> * <type>.<cvtop>/<type> * * func_type: ( type <var> )? <param>* <result>* */ function parseFuncInstr() { var startLoc = getStartLoc(); maybeIgnoreComment(); /** * A simple instruction */ if (token.type === tokens.name || token.type === tokens.valtype) { var _name2 = token.value; var object; eatToken(); if (token.type === tokens.dot) { object = _name2; eatToken(); if (token.type !== tokens.name) { throw new TypeError("Unknown token: " + token.type + ", name expected"); } _name2 = token.value; eatToken(); } if (token.type === tokens.closeParen) { var _endLoc = token.loc.end; if (typeof object === "undefined") { return t.withLoc(t.instruction(_name2), _endLoc, startLoc); } else { return t.withLoc(t.objectInstruction(_name2, object, []), _endLoc, startLoc); } } var signature = t.signatureForOpcode(object || "", _name2); var _parseFuncInstrArgume = parseFuncInstrArguments(signature), _args = _parseFuncInstrArgume.args, _namedArgs = _parseFuncInstrArgume.namedArgs; var endLoc = token.loc.end; if (typeof object === "undefined") { return t.withLoc(t.instruction(_name2, _args, _namedArgs), endLoc, startLoc); } else { return t.withLoc(t.objectInstruction(_name2, object, _args, _namedArgs), endLoc, startLoc); } } else if (isKeyword(token, keywords.loop)) { /** * Else a instruction with a keyword (loop or block) */ eatToken(); // keyword return parseLoop(); } else if (isKeyword(token, keywords.block)) { eatToken(); // keyword return parseBlock(); } else if (isKeyword(token, keywords.call_indirect)) { eatToken(); // keyword return parseCallIndirect(); } else if (isKeyword(token, keywords.call)) { eatToken(); // keyword var index; if (token.type === tokens.identifier) { index = identifierFromToken(token); eatToken(); } else if (token.type === tokens.number) { index = t.indexLiteral(token.value); eatToken(); } var instrArgs = []; // Nested instruction while (token.type === tokens.openParen) { eatToken(); instrArgs.push(parseFuncInstr()); eatTokenOfType(tokens.closeParen); } if (typeof index === "undefined") { throw new Error("Missing argument in call instruciton"); } if (instrArgs.length > 0) { return t.callInstruction(index, instrArgs); } else { return t.callInstruction(index); } } else if (isKeyword(token, keywords.if)) { eatToken(); // Keyword return parseIf(); } else if (isKeyword(token, keywords.module) && hasPlugin("wast")) { eatToken(); // In WAST you can have a module as an instruction's argument // we will cast it into a instruction to not break the flow // $FlowIgnore var module = parseModule(); return module; } else { throw function () { return new Error("\n" + codeFrameFromSource(source, token.loc) + "\n" + "Unexpected instruction in function body" + ", given " + tokenToString(token)); }(); } } /* * Parses a function * * WAT: * * functype :: ( 'func' t1:vec(param) t2:vec(result) ) * param :: ( 'param' id? t:valtype ) * result :: ( 'result' t:valtype ) * * WAST: * * func :: ( func <name>? <func_sig> <local>* <instr>* ) * ( func <name>? ( export <string> ) <...> ) * ( func <name>? ( import <string> <string> ) <func_sig> ) * func_sig :: ( type <var> )? <param>* <result>* * param :: ( param <type>* ) | ( param <name> <type> ) * result :: ( result <type>* ) * local :: ( local <type>* ) | ( local <name> <type> ) * */ function parseFunc() { var fnName = t.identifier(getUniqueName("func")); var typeRef; var fnBody = []; var fnParams = []; var fnResult = []; // name if (token.type === tokens.identifier) { fnName = identifierFromToken(token); eatToken(); } else { fnName = t.withRaw(fnName, ""); // preserve anonymous } maybeIgnoreComment(); while (token.type === tokens.openParen || token.type === tokens.name || token.type === tokens.valtype) { // Instructions without parens if (token.type === tokens.name || token.type === tokens.valtype) { fnBody.push(parseFuncInstr()); continue; } eatToken(); if (lookaheadAndCheck(keywords.param) === true) { eatToken(); fnParams.push.apply(fnParams, _toConsumableArray(parseFuncParam())); } else if (lookaheadAndCheck(keywords.result) === true) { eatToken(); fnResult.push.apply(fnResult, _toConsumableArray(parseFuncResult())); } else if (lookaheadAndCheck(keywords.export) === true) { eatToken(); parseFuncExport(fnName); } else if (lookaheadAndCheck(keywords.type) === true) { eatToken(); typeRef = parseTypeReference(); } else if (lookaheadAndCheck(tokens.name) === true || lookaheadAndCheck(tokens.valtype) === true || token.type === "keyword" // is any keyword ) { // Instruction fnBody.push(parseFuncInstr()); } else { throw function () { return new Error("\n" + codeFrameFromSource(source, token.loc) + "\n" + "Unexpected token in func body" + ", given " + tokenToString(token)); }(); } eatTokenOfType(tokens.closeParen); } return t.func(fnName, typeRef !== undefined ? typeRef : t.signature(fnParams, fnResult), fnBody); } /** * Parses shorthand export in func * * export :: ( export <string> ) */ function parseFuncExport(funcId) { if (token.type !== tokens.string) { throw function () { return new Error("\n" + codeFrameFromSource(source, token.loc) + "\n" + "Function export expected a string" + ", given " + tokenToString(token)); }(); } var name = token.value; eatToken(); /** * Func export shorthand, we trait it as a syntaxic sugar. * A export ModuleField will be added later. * * We give the anonymous function a generated name and export it. */ var id = t.identifier(funcId.value); state.registredExportedElements.push({ exportType: "Func", name: name, id: id }); } /** * Parses a type instruction * * WAST: * * typedef: ( type <name>? ( func <param>* <result>* ) ) */ function parseType() { var id; var params = []; var result = []; if (token.type === tokens.identifier) { id = identifierFromToken(token); eatToken(); } if (lookaheadAndCheck(tokens.openParen, keywords.func)) { eatToken(); // ( eatToken(); // func if (token.type === tokens.closeParen) { eatToken(); // function with an empty signature, we can abort here return t.typeInstruction(id, t.signature([], [])); } if (lookaheadAndCheck(tokens.openParen, keywords.param)) { eatToken(); // ( eatToken(); // param params = parseFuncParam(); eatTokenOfType(tokens.closeParen); } if (lookaheadAndCheck(tokens.openParen, keywords.result)) { eatToken(); // ( eatToken(); // result result = parseFuncResult(); eatTokenOfType(tokens.closeParen); } eatTokenOfType(tokens.closeParen); } return t.typeInstruction(id, t.signature(params, result)); } /** * Parses a function result * * WAST: * * result :: ( result <type>* ) */ function parseFuncResult() { var results = []; while (token.type !== tokens.closeParen) { if (token.type !== tokens.valtype) { throw function () { return new Error("\n" + codeFrameFromSource(source, token.loc) + "\n" + "Unexpected token in func result" + ", given " + tokenToString(token)); }(); } var valtype = token.value; eatToken(); results.push(valtype); } return results; } /** * Parses a type reference * */ function parseTypeReference() { var ref; if (token.type === tokens.identifier) { ref = identifierFromToken(token); eatToken(); } else if (token.type === tokens.number) { ref = t.numberLiteralFromRaw(token.value); eatToken(); } return ref; } /** * Parses a global instruction * * WAST: * * global: ( global <name>? <global_sig> <instr>* ) * ( global <name>? ( export <string> ) <...> ) * ( global <name>? ( import <string> <string> ) <global_sig> ) * * global_sig: <type> | ( mut <type> ) * */ function parseGlobal() { var name = t.identifier(getUniqueName("global")); var type; // Keep informations in case of a shorthand import var importing = null; maybeIgnoreComment(); if (token.type === tokens.identifier) { name = identifierFromToken(token); eatToken(); } else { name = t.withRaw(name, ""); // preserve anonymous } /** * maybe export */ if (lookaheadAndCheck(tokens.openParen, keywords.export)) { eatToken(); // ( eatToken(); // export var exportName = token.value; eatTokenOfType(tokens.string); state.registredExportedElements.push({ exportType: "Global", name: exportName, id: name }); eatTokenOfType(tokens.closeParen); } /** * maybe import */ if (lookaheadAndCheck(tokens.openParen, keywords.import)) { eatToken(); // ( eatToken(); // import var moduleName = token.value; eatTokenOfType(tokens.string); var _name3 = token.value; eatTokenOfType(tokens.string); importing = { module: moduleName, name: _name3, descr: undefined }; eatTokenOfType(tokens.closeParen); } /** * global_sig */ if (token.type === tokens.valtype) { type = t.globalType(token.value, "const"); eatToken(); } else if (token.type === tokens.openParen) { eatToken(); // ( if (isKeyword(token, keywords.mut) === false) { throw function () { return new Error("\n" + codeFrameFromSource(source, token.loc) + "\n" + "Unsupported global type, expected mut" + ", given " + tokenToString(token)); }(); } eatToken(); // mut type = t.globalType(token.value, "var"); eatToken(); eatTokenOfType(tokens.closeParen); } if (type === undefined) { throw function () { return new Error("\n" + codeFrameFromSource(source, token.loc) + "\n" + "Could not determine global type" + ", given " + tokenToString(token)); }(); } maybeIgnoreComment(); var init = []; if (importing != null) { importing.descr = type; init.push(t.moduleImport(importing.module, importing.name, importing.descr)); } /** * instr* */ while (token.type === tokens.openParen) { eatToken(); init.push(parseFuncInstr()); eatTokenOfType(tokens.closeParen); } return t.global(type, init, name); } /** * Parses a function param * * WAST: * * param :: ( param <type>* ) | ( param <name> <type> ) */ function parseFuncParam() { var params = []; var id; var valtype; if (token.type === tokens.identifier) { id = token.value; eatToken(); } if (token.type === tokens.valtype) { valtype = token.value; eatToken(); params.push({ id: id, valtype: valtype }); /** * Shorthand notation for multiple anonymous parameters * @see https://webassembly.github.io/spec/core/text/types.html#function-types * @see https://github.com/xtuc/webassemblyjs/issues/6 */ if (id === undefined) { while (token.type === tokens.valtype) { valtype = token.value; eatToken(); params.push({ id: undefined, valtype: valtype }); } } } else {// ignore } return params; } /** * Parses an element segments instruction * * WAST: * * elem: ( elem <var>? (offset <instr>* ) <var>* ) * ( elem <var>? <expr> <var>* ) * * var: <nat> | <name> */ function parseElem() { var tableIndex = t.indexLiteral(0); var offset = []; var funcs = []; if (token.type === tokens.identifier) { tableIndex = identifierFromToken(token); eatToken(); } if (token.type === tokens.number) { tableIndex = t.indexLiteral(token.value); eatToken(); } while (token.type !== tokens.closeParen) { if (lookaheadAndCheck(tokens.openParen, keywords.offset)) { eatToken(); // ( eatToken(); // offset while (token.type !== tokens.closeParen) { eatTokenOfType(tokens.openParen); offset.push(parseFuncInstr()); eatTokenOfType(tokens.closeParen); } eatTokenOfType(tokens.closeParen); } else if (token.type === tokens.identifier) { funcs.push(t.identifier(token.value)); eatToken(); } else if (token.type === tokens.number) { funcs.push(t.indexLiteral(token.value)); eatToken(); } else if (token.type === tokens.openParen) { eatToken(); // ( offset.push(parseFuncInstr()); eatTokenOfType(tokens.closeParen); } else { throw function () { return new Error("\n" + codeFrameFromSource(source, token.loc) + "\n" + "Unsupported token in elem" + ", given " + tokenToString(token)); }(); } } return t.elem(tableIndex, offset, funcs); } /** * Parses the start instruction in a module * * WAST: * * start: ( start <var> ) * var: <nat> | <name> * * WAT: * start ::= ‘(’ ‘start’ x:funcidx ‘)’ */ function parseStart() { if (token.type === tokens.identifier) { var index = identifierFromToken(token); eatToken(); return t.start(index); } if (token.type === tokens.number) { var _index2 = t.indexLiteral(token.value); eatToken(); return t.start(_index2); } throw new Error("Unknown start, token: " + tokenToString(token)); } if (token.type === tokens.openParen) { eatToken(); var startLoc = getStartLoc(); if (isKeyword(token, keywords.export)) { eatToken(); var node = parseExport(); var _endLoc2 = getEndLoc(); return t.withLoc(node, _endLoc2, startLoc); } if (isKeyword(token, keywords.loop)) { eatToken(); var _node = parseLoop(); var _endLoc3 = getEndLoc(); return t.withLoc(_node, _endLoc3, startLoc); } if (isKeyword(token, keywords.func)) { eatToken(); var _node2 = parseFunc(); var _endLoc4 = getEndLoc(); maybeIgnoreComment(); eatTokenOfType(tokens.closeParen); return t.withLoc(_node2, _endLoc4, startLoc); } if (isKeyword(token, keywords.module)) { eatToken(); var _node3 = parseModule(); var _endLoc5 = getEndLoc(); return t.withLoc(_node3, _endLoc5, startLoc); } if (isKeyword(token, keywords.import)) { eatToken(); var _node4 = parseImport(); var _endLoc6 = getEndLoc(); eatTokenOfType(tokens.closeParen); return t.withLoc(_node4, _endLoc6, startLoc); } if (isKeyword(token, keywords.block)) { eatToken(); var _node5 = parseBlock(); var _endLoc7 = getEndLoc(); eatTokenOfType(tokens.closeParen); return t.withLoc(_node5, _endLoc7, startLoc); } if (isKeyword(token, keywords.memory)) { eatToken(); var _node6 = parseMemory(); var _endLoc8 = getEndLoc(); eatTokenOfType(tokens.closeParen); return t.withLoc(_node6, _endLoc8, startLoc); } if (isKeyword(token, keywords.data)) { eatToken(); var _node7 = parseData(); var _endLoc9 = getEndLoc(); eatTokenOfType(tokens.closeParen); return t.withLoc(_node7, _endLoc9, startLoc); } if (isKeyword(token, keywords.table)) { eatToken(); var _node8 = parseTable(); var _endLoc10 = getEndLoc(); eatTokenOfType(tokens.closeParen); return t.withLoc(_node8, _endLoc10, startLoc); } if (isKeyword(token, keywords.global)) { eatToken(); var _node9 = parseGlobal(); var _endLoc11 = getEndLoc(); eatTokenOfType(tokens.closeParen); return t.withLoc(_node9, _endLoc11, startLoc); } if (isKeyword(token, keywords.type)) { eatToken(); var _node10 = parseType(); var _endLoc12 = getEndLoc(); eatTokenOfType(tokens.closeParen); return t.withLoc(_node10, _endLoc12, startLoc); } if (isKeyword(token, keywords.start)) { eatToken(); var _node11 = parseStart(); var _endLoc13 = getEndLoc(); eatTokenOfType(tokens.closeParen); return t.withLoc(_node11, _endLoc13, startLoc); } if (isKeyword(token, keywords.elem)) { eatToken(); var _node12 = parseElem(); var _endLoc14 = getEndLoc(); eatTokenOfType(tokens.closeParen); return t.withLoc(_node12, _endLoc14, startLoc); } var instruction = parseFuncInstr(); var endLoc = getEndLoc(); maybeIgnoreComment(); if (_typeof(instruction) === "object") { if (typeof token !== "undefined") { eatTokenOfType(tokens.closeParen); } return t.withLoc(instruction, endLoc, startLoc); } } if (token.type === tokens.comment) { var _startLoc = getStartLoc(); var builder = token.opts.type === "leading" ? t.leadingComment : t.blockComment; var _node13 = builder(token.value); eatToken(); // comment var _endLoc15 = getEndLoc(); return t.withLoc(_node13, _endLoc15, _startLoc); } throw function () { return new Error("\n" + codeFrameFromSource(source, token.loc) + "\n" + "Unknown token" + ", given " + tokenToString(token)); }(); } var body = []; while (current < tokensList.length) { body.push(walk()); } return t.program(body); }