Spaces:
Sleeping
Sleeping
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); | |
} |