blob: ddde33aa6aefb41df9ccdaaa7d113e0ce811e9f7 [file] [log] [blame]
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.parse = parse;
var _helperCodeFrame = require("@webassemblyjs/helper-code-frame");
var t = _interopRequireWildcard(require("@webassemblyjs/ast"));
var _numberLiterals = require("./number-literals");
var _stringLiterals = require("./string-literals");
var _tokenizer = require("./tokenizer");
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
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); } }
function hasPlugin(name) {
if (name !== "wast") throw new Error("unknow plugin");
return true;
}
function isKeyword(token, id) {
return token.type === _tokenizer.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);
}
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" + (0, _helperCodeFrame.codeFrameFromSource)(source, token.loc) + "Assertion error: expected token of type " + type + ", given " + tokenToString(token));
}
eatToken();
}
function parseExportIndex(token) {
if (token.type === _tokenizer.tokens.identifier) {
var index = identifierFromToken(token);
eatToken();
return index;
} else if (token.type === _tokenizer.tokens.number) {
var _index = t.numberLiteralFromRaw(token.value);
eatToken();
return _index;
} else {
throw function () {
return new Error("\n" + (0, _helperCodeFrame.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 === _tokenizer.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 === _tokenizer.tokens.string || token.type === _tokenizer.tokens.identifier) {
id = t.identifier(token.value);
eatToken();
} else {
id = t.withRaw(id, ""); // preserve anonymous
}
/**
* Maybe data
*/
if (lookaheadAndCheck(_tokenizer.tokens.openParen, _tokenizer.keywords.data)) {
eatToken(); // (
eatToken(); // data
// TODO(sven): do something with the data collected here
var stringInitializer = token.value;
eatTokenOfType(_tokenizer.tokens.string); // Update limits accordingly
limits = t.limit(stringInitializer.length);
eatTokenOfType(_tokenizer.tokens.closeParen);
}
/**
* Maybe export
*/
if (lookaheadAndCheck(_tokenizer.tokens.openParen, _tokenizer.keywords.export)) {
eatToken(); // (
eatToken(); // export
if (token.type !== _tokenizer.tokens.string) {
throw function () {
return new Error("\n" + (0, _helperCodeFrame.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(_tokenizer.tokens.closeParen);
}
/**
* Memory signature
*/
if (token.type === _tokenizer.tokens.number) {
limits = t.limit((0, _numberLiterals.parse32I)(token.value));
eatToken();
if (token.type === _tokenizer.tokens.number) {
limits.max = (0, _numberLiterals.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 === _tokenizer.tokens.number) {
memidx = token.value;
eatTokenOfType(_tokenizer.tokens.number); // .
}
eatTokenOfType(_tokenizer.tokens.openParen);
var offset;
if (token.type === _tokenizer.tokens.valtype) {
eatTokenOfType(_tokenizer.tokens.valtype); // i32
eatTokenOfType(_tokenizer.tokens.dot); // .
if (token.value !== "const") {
throw new Error("constant expression required");
}
eatTokenOfType(_tokenizer.tokens.name); // const
var numberLiteral = t.numberLiteralFromRaw(token.value, "i32");
offset = t.objectInstruction("const", "i32", [numberLiteral]);
eatToken();
eatTokenOfType(_tokenizer.tokens.closeParen);
} else {
eatTokenOfType(_tokenizer.tokens.name); // get_global
var _numberLiteral = t.numberLiteralFromRaw(token.value, "i32");
offset = t.instruction("get_global", [_numberLiteral]);
eatToken();
eatTokenOfType(_tokenizer.tokens.closeParen);
}
var byteArray = (0, _stringLiterals.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 === _tokenizer.tokens.string || token.type === _tokenizer.tokens.identifier) {
name = identifierFromToken(token);
eatToken();
} else {
name = t.withRaw(name, ""); // preserve anonymous
}
while (token.type !== _tokenizer.tokens.closeParen) {
/**
* Maybe export
*/
if (lookaheadAndCheck(_tokenizer.tokens.openParen, _tokenizer.keywords.elem)) {
eatToken(); // (
eatToken(); // elem
while (token.type === _tokenizer.tokens.identifier) {
elemIndices.push(t.identifier(token.value));
eatToken();
}
eatTokenOfType(_tokenizer.tokens.closeParen);
} else if (lookaheadAndCheck(_tokenizer.tokens.openParen, _tokenizer.keywords.export)) {
eatToken(); // (
eatToken(); // export
if (token.type !== _tokenizer.tokens.string) {
throw function () {
return new Error("\n" + (0, _helperCodeFrame.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(_tokenizer.tokens.closeParen);
} else if (isKeyword(token, _tokenizer.keywords.anyfunc)) {
// It's the default value, we can ignore it
eatToken(); // anyfunc
} else if (token.type === _tokenizer.tokens.number) {
/**
* Table type
*/
var min = parseInt(token.value);
eatToken();
if (token.type === _tokenizer.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" + (0, _helperCodeFrame.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 !== _tokenizer.tokens.string) {
throw new Error("Expected a string, " + token.type + " given.");
}
var moduleName = token.value;
eatToken();
if (token.type !== _tokenizer.tokens.string) {
throw new Error("Expected a string, " + token.type + " given.");
}
var name = token.value;
eatToken();
eatTokenOfType(_tokenizer.tokens.openParen);
var descr;
if (isKeyword(token, _tokenizer.keywords.func)) {
eatToken(); // keyword
var fnParams = [];
var fnResult = [];
var typeRef;
var fnName = t.identifier(getUniqueName("func"));
if (token.type === _tokenizer.tokens.identifier) {
fnName = identifierFromToken(token);
eatToken();
}
while (token.type === _tokenizer.tokens.openParen) {
eatToken();
if (lookaheadAndCheck(_tokenizer.keywords.type) === true) {
eatToken();
typeRef = parseTypeReference();
} else if (lookaheadAndCheck(_tokenizer.keywords.param) === true) {
eatToken();
fnParams.push.apply(fnParams, _toConsumableArray(parseFuncParam()));
} else if (lookaheadAndCheck(_tokenizer.keywords.result) === true) {
eatToken();
fnResult.push.apply(fnResult, _toConsumableArray(parseFuncResult()));
} else {
throw function () {
return new Error("\n" + (0, _helperCodeFrame.codeFrameFromSource)(source, token.loc) + "\n" + "Unexpected token in import of type" + ", given " + tokenToString(token));
}();
}
eatTokenOfType(_tokenizer.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, _tokenizer.keywords.global)) {
eatToken(); // keyword
if (token.type === _tokenizer.tokens.openParen) {
eatToken(); // (
eatTokenOfType(_tokenizer.tokens.keyword); // mut keyword
var valtype = token.value;
eatToken();
descr = t.globalType(valtype, "var");
eatTokenOfType(_tokenizer.tokens.closeParen);
} else {
var _valtype = token.value;
eatTokenOfType(_tokenizer.tokens.valtype);
descr = t.globalType(_valtype, "const");
}
} else if (isKeyword(token, _tokenizer.keywords.memory) === true) {
eatToken(); // Keyword
descr = parseMemory();
} else if (isKeyword(token, _tokenizer.keywords.table) === true) {
eatToken(); // Keyword
descr = parseTable();
} else {
throw new Error("Unsupported import type: " + tokenToString(token));
}
eatTokenOfType(_tokenizer.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 === _tokenizer.tokens.identifier) {
label = identifierFromToken(token);
eatToken();
} else {
label = t.withRaw(label, ""); // preserve anonymous
}
while (token.type === _tokenizer.tokens.openParen) {
eatToken();
if (lookaheadAndCheck(_tokenizer.keywords.result) === true) {
eatToken();
blockResult = token.value;
eatToken();
} else if (lookaheadAndCheck(_tokenizer.tokens.name) === true || lookaheadAndCheck(_tokenizer.tokens.valtype) === true || token.type === "keyword" // is any keyword
) {
// Instruction
instr.push(parseFuncInstr());
} else {
throw function () {
return new Error("\n" + (0, _helperCodeFrame.codeFrameFromSource)(source, token.loc) + "\n" + "Unexpected token in block body of type" + ", given " + tokenToString(token));
}();
}
maybeIgnoreComment();
eatTokenOfType(_tokenizer.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 === _tokenizer.tokens.identifier) {
label = identifierFromToken(token);
eatToken();
} else {
label = t.withRaw(label, ""); // preserve anonymous
}
while (token.type === _tokenizer.tokens.openParen) {
eatToken(); // (
/**
* Block signature
*/
if (isKeyword(token, _tokenizer.keywords.result) === true) {
eatToken();
blockResult = token.value;
eatTokenOfType(_tokenizer.tokens.valtype);
eatTokenOfType(_tokenizer.tokens.closeParen);
continue;
}
/**
* Then
*/
if (isKeyword(token, _tokenizer.keywords.then) === true) {
eatToken(); // then
while (token.type === _tokenizer.tokens.openParen) {
eatToken(); // Instruction
if (lookaheadAndCheck(_tokenizer.tokens.name) === true || lookaheadAndCheck(_tokenizer.tokens.valtype) === true || token.type === "keyword" // is any keyword
) {
consequent.push(parseFuncInstr());
} else {
throw function () {
return new Error("\n" + (0, _helperCodeFrame.codeFrameFromSource)(source, token.loc) + "\n" + "Unexpected token in consequent body of type" + ", given " + tokenToString(token));
}();
}
eatTokenOfType(_tokenizer.tokens.closeParen);
}
eatTokenOfType(_tokenizer.tokens.closeParen);
continue;
}
/**
* Alternate
*/
if (isKeyword(token, _tokenizer.keywords.else)) {
eatToken(); // else
while (token.type === _tokenizer.tokens.openParen) {
eatToken(); // Instruction
if (lookaheadAndCheck(_tokenizer.tokens.name) === true || lookaheadAndCheck(_tokenizer.tokens.valtype) === true || token.type === "keyword" // is any keyword
) {
alternate.push(parseFuncInstr());
} else {
throw function () {
return new Error("\n" + (0, _helperCodeFrame.codeFrameFromSource)(source, token.loc) + "\n" + "Unexpected token in alternate body of type" + ", given " + tokenToString(token));
}();
}
eatTokenOfType(_tokenizer.tokens.closeParen);
}
eatTokenOfType(_tokenizer.tokens.closeParen);
continue;
}
/**
* Test instruction
*/
if (lookaheadAndCheck(_tokenizer.tokens.name) === true || lookaheadAndCheck(_tokenizer.tokens.valtype) === true || token.type === "keyword" // is any keyword
) {
testInstrs.push(parseFuncInstr());
eatTokenOfType(_tokenizer.tokens.closeParen);
continue;
}
throw function () {
return new Error("\n" + (0, _helperCodeFrame.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 === _tokenizer.tokens.identifier) {
label = identifierFromToken(token);
eatToken();
} else {
label = t.withRaw(label, ""); // preserve anonymous
}
while (token.type === _tokenizer.tokens.openParen) {
eatToken();
if (lookaheadAndCheck(_tokenizer.keywords.result) === true) {
eatToken();
blockResult = token.value;
eatToken();
} else if (lookaheadAndCheck(_tokenizer.tokens.name) === true || lookaheadAndCheck(_tokenizer.tokens.valtype) === true || token.type === "keyword" // is any keyword
) {
// Instruction
instr.push(parseFuncInstr());
} else {
throw function () {
return new Error("\n" + (0, _helperCodeFrame.codeFrameFromSource)(source, token.loc) + "\n" + "Unexpected token in loop body" + ", given " + tokenToString(token));
}();
}
eatTokenOfType(_tokenizer.tokens.closeParen);
}
return t.loopInstruction(label, blockResult, instr);
}
function parseCallIndirect() {
var typeRef;
var params = [];
var results = [];
var instrs = [];
while (token.type !== _tokenizer.tokens.closeParen) {
if (lookaheadAndCheck(_tokenizer.tokens.openParen, _tokenizer.keywords.type)) {
eatToken(); // (
eatToken(); // type
typeRef = parseTypeReference();
} else if (lookaheadAndCheck(_tokenizer.tokens.openParen, _tokenizer.keywords.param)) {
eatToken(); // (
eatToken(); // param
/**
* Params can be empty:
* (params)`
*/
if (token.type !== _tokenizer.tokens.closeParen) {
params.push.apply(params, _toConsumableArray(parseFuncParam()));
}
} else if (lookaheadAndCheck(_tokenizer.tokens.openParen, _tokenizer.keywords.result)) {
eatToken(); // (
eatToken(); // result
/**
* Results can be empty:
* (result)`
*/
if (token.type !== _tokenizer.tokens.closeParen) {
results.push.apply(results, _toConsumableArray(parseFuncResult()));
}
} else {
eatTokenOfType(_tokenizer.tokens.openParen);
instrs.push(parseFuncInstr());
}
eatTokenOfType(_tokenizer.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 !== _tokenizer.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(_tokenizer.tokens.openParen);
while (token.type !== _tokenizer.tokens.closeParen) {
if (isKeyword(token, _tokenizer.keywords.func)) {
type = "Func";
eatToken();
index = parseExportIndex(token);
} else if (isKeyword(token, _tokenizer.keywords.table)) {
type = "Table";
eatToken();
index = parseExportIndex(token);
} else if (isKeyword(token, _tokenizer.keywords.global)) {
type = "Global";
eatToken();
index = parseExportIndex(token);
} else if (isKeyword(token, _tokenizer.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(_tokenizer.tokens.closeParen);
return t.withLoc(node, endLoc, startLoc);
}
function parseModule() {
var name = null;
var isBinary = false;
var isQuote = false;
var moduleFields = [];
if (token.type === _tokenizer.tokens.identifier) {
name = token.value;
eatToken();
}
if (hasPlugin("wast") && token.type === _tokenizer.tokens.name && token.value === "binary") {
eatToken();
isBinary = true;
}
if (hasPlugin("wast") && token.type === _tokenizer.tokens.name && token.value === "quote") {
eatToken();
isQuote = true;
}
if (isBinary === true) {
var blob = [];
while (token.type === _tokenizer.tokens.string) {
blob.push(token.value);
eatToken();
maybeIgnoreComment();
}
eatTokenOfType(_tokenizer.tokens.closeParen);
return t.binaryModule(name, blob);
}
if (isQuote === true) {
var string = [];
while (token.type === _tokenizer.tokens.string) {
string.push(token.value);
eatToken();
}
eatTokenOfType(_tokenizer.tokens.closeParen);
return t.quoteModule(name, string);
}
while (token.type !== _tokenizer.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(_tokenizer.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 === _tokenizer.tokens.name || isKeyword(token, _tokenizer.keywords.offset)) {
var key = token.value;
eatToken();
eatTokenOfType(_tokenizer.tokens.equal);
var value = void 0;
if (token.type === _tokenizer.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 !== _tokenizer.tokens.closeParen && ( // $FlowIgnore
token.type === _tokenizer.tokens.openParen || signaturePtr < signatureLength)) {
if (token.type === _tokenizer.tokens.identifier) {
args.push(t.identifier(token.value));
eatToken();
} else if (token.type === _tokenizer.tokens.valtype) {
// Handle locals
args.push(t.valtypeLiteral(token.value));
eatToken();
} else if (token.type === _tokenizer.tokens.string) {
args.push(t.stringLiteral(token.value));
eatToken();
} else if (token.type === _tokenizer.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 === _tokenizer.tokens.openParen) {
/**
* Maybe some nested instructions
*/
eatToken(); // Instruction
if (lookaheadAndCheck(_tokenizer.tokens.name) === true || lookaheadAndCheck(_tokenizer.tokens.valtype) === true || token.type === "keyword" // is any keyword
) {
// $FlowIgnore
args.push(parseFuncInstr());
} else {
throw function () {
return new Error("\n" + (0, _helperCodeFrame.codeFrameFromSource)(source, token.loc) + "\n" + "Unexpected token in nested instruction" + ", given " + tokenToString(token));
}();
}
if (token.type === _tokenizer.tokens.closeParen) {
eatToken();
}
} else {
throw function () {
return new Error("\n" + (0, _helperCodeFrame.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 === _tokenizer.tokens.name || token.type === _tokenizer.tokens.valtype) {
var _name2 = token.value;
var object;
eatToken();
if (token.type === _tokenizer.tokens.dot) {
object = _name2;
eatToken();
if (token.type !== _tokenizer.tokens.name) {
throw new TypeError("Unknown token: " + token.type + ", name expected");
}
_name2 = token.value;
eatToken();
}
if (token.type === _tokenizer.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, _tokenizer.keywords.loop)) {
/**
* Else a instruction with a keyword (loop or block)
*/
eatToken(); // keyword
return parseLoop();
} else if (isKeyword(token, _tokenizer.keywords.block)) {
eatToken(); // keyword
return parseBlock();
} else if (isKeyword(token, _tokenizer.keywords.call_indirect)) {
eatToken(); // keyword
return parseCallIndirect();
} else if (isKeyword(token, _tokenizer.keywords.call)) {
eatToken(); // keyword
var index;
if (token.type === _tokenizer.tokens.identifier) {
index = identifierFromToken(token);
eatToken();
} else if (token.type === _tokenizer.tokens.number) {
index = t.indexLiteral(token.value);
eatToken();
}
var instrArgs = []; // Nested instruction
while (token.type === _tokenizer.tokens.openParen) {
eatToken();
instrArgs.push(parseFuncInstr());
eatTokenOfType(_tokenizer.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, _tokenizer.keywords.if)) {
eatToken(); // Keyword
return parseIf();
} else if (isKeyword(token, _tokenizer.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" + (0, _helperCodeFrame.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 === _tokenizer.tokens.identifier) {
fnName = identifierFromToken(token);
eatToken();
} else {
fnName = t.withRaw(fnName, ""); // preserve anonymous
}
maybeIgnoreComment();
while (token.type === _tokenizer.tokens.openParen || token.type === _tokenizer.tokens.name || token.type === _tokenizer.tokens.valtype) {
// Instructions without parens
if (token.type === _tokenizer.tokens.name || token.type === _tokenizer.tokens.valtype) {
fnBody.push(parseFuncInstr());
continue;
}
eatToken();
if (lookaheadAndCheck(_tokenizer.keywords.param) === true) {
eatToken();
fnParams.push.apply(fnParams, _toConsumableArray(parseFuncParam()));
} else if (lookaheadAndCheck(_tokenizer.keywords.result) === true) {
eatToken();
fnResult.push.apply(fnResult, _toConsumableArray(parseFuncResult()));
} else if (lookaheadAndCheck(_tokenizer.keywords.export) === true) {
eatToken();
parseFuncExport(fnName);
} else if (lookaheadAndCheck(_tokenizer.keywords.type) === true) {
eatToken();
typeRef = parseTypeReference();
} else if (lookaheadAndCheck(_tokenizer.tokens.name) === true || lookaheadAndCheck(_tokenizer.tokens.valtype) === true || token.type === "keyword" // is any keyword
) {
// Instruction
fnBody.push(parseFuncInstr());
} else {
throw function () {
return new Error("\n" + (0, _helperCodeFrame.codeFrameFromSource)(source, token.loc) + "\n" + "Unexpected token in func body" + ", given " + tokenToString(token));
}();
}
eatTokenOfType(_tokenizer.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 !== _tokenizer.tokens.string) {
throw function () {
return new Error("\n" + (0, _helperCodeFrame.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 === _tokenizer.tokens.identifier) {
id = identifierFromToken(token);
eatToken();
}
if (lookaheadAndCheck(_tokenizer.tokens.openParen, _tokenizer.keywords.func)) {
eatToken(); // (
eatToken(); // func
if (token.type === _tokenizer.tokens.closeParen) {
eatToken(); // function with an empty signature, we can abort here
return t.typeInstruction(id, t.signature([], []));
}
if (lookaheadAndCheck(_tokenizer.tokens.openParen, _tokenizer.keywords.param)) {
eatToken(); // (
eatToken(); // param
params = parseFuncParam();
eatTokenOfType(_tokenizer.tokens.closeParen);
}
if (lookaheadAndCheck(_tokenizer.tokens.openParen, _tokenizer.keywords.result)) {
eatToken(); // (
eatToken(); // result
result = parseFuncResult();
eatTokenOfType(_tokenizer.tokens.closeParen);
}
eatTokenOfType(_tokenizer.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 !== _tokenizer.tokens.closeParen) {
if (token.type !== _tokenizer.tokens.valtype) {
throw function () {
return new Error("\n" + (0, _helperCodeFrame.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 === _tokenizer.tokens.identifier) {
ref = identifierFromToken(token);
eatToken();
} else if (token.type === _tokenizer.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 === _tokenizer.tokens.identifier) {
name = identifierFromToken(token);
eatToken();
} else {
name = t.withRaw(name, ""); // preserve anonymous
}
/**
* maybe export
*/
if (lookaheadAndCheck(_tokenizer.tokens.openParen, _tokenizer.keywords.export)) {
eatToken(); // (
eatToken(); // export
var exportName = token.value;
eatTokenOfType(_tokenizer.tokens.string);
state.registredExportedElements.push({
exportType: "Global",
name: exportName,
id: name
});
eatTokenOfType(_tokenizer.tokens.closeParen);
}
/**
* maybe import
*/
if (lookaheadAndCheck(_tokenizer.tokens.openParen, _tokenizer.keywords.import)) {
eatToken(); // (
eatToken(); // import
var moduleName = token.value;
eatTokenOfType(_tokenizer.tokens.string);
var _name3 = token.value;
eatTokenOfType(_tokenizer.tokens.string);
importing = {
module: moduleName,
name: _name3,
descr: undefined
};
eatTokenOfType(_tokenizer.tokens.closeParen);
}
/**
* global_sig
*/
if (token.type === _tokenizer.tokens.valtype) {
type = t.globalType(token.value, "const");
eatToken();
} else if (token.type === _tokenizer.tokens.openParen) {
eatToken(); // (
if (isKeyword(token, _tokenizer.keywords.mut) === false) {
throw function () {
return new Error("\n" + (0, _helperCodeFrame.codeFrameFromSource)(source, token.loc) + "\n" + "Unsupported global type, expected mut" + ", given " + tokenToString(token));
}();
}
eatToken(); // mut
type = t.globalType(token.value, "var");
eatToken();
eatTokenOfType(_tokenizer.tokens.closeParen);
}
if (type === undefined) {
throw function () {
return new Error("\n" + (0, _helperCodeFrame.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 === _tokenizer.tokens.openParen) {
eatToken();
init.push(parseFuncInstr());
eatTokenOfType(_tokenizer.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 === _tokenizer.tokens.identifier) {
id = token.value;
eatToken();
}
if (token.type === _tokenizer.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 === _tokenizer.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 === _tokenizer.tokens.identifier) {
tableIndex = identifierFromToken(token);
eatToken();
}
if (token.type === _tokenizer.tokens.number) {
tableIndex = t.indexLiteral(token.value);
eatToken();
}
while (token.type !== _tokenizer.tokens.closeParen) {
if (lookaheadAndCheck(_tokenizer.tokens.openParen, _tokenizer.keywords.offset)) {
eatToken(); // (
eatToken(); // offset
while (token.type !== _tokenizer.tokens.closeParen) {
eatTokenOfType(_tokenizer.tokens.openParen);
offset.push(parseFuncInstr());
eatTokenOfType(_tokenizer.tokens.closeParen);
}
eatTokenOfType(_tokenizer.tokens.closeParen);
} else if (token.type === _tokenizer.tokens.identifier) {
funcs.push(t.identifier(token.value));
eatToken();
} else if (token.type === _tokenizer.tokens.number) {
funcs.push(t.indexLiteral(token.value));
eatToken();
} else if (token.type === _tokenizer.tokens.openParen) {
eatToken(); // (
offset.push(parseFuncInstr());
eatTokenOfType(_tokenizer.tokens.closeParen);
} else {
throw function () {
return new Error("\n" + (0, _helperCodeFrame.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 === _tokenizer.tokens.identifier) {
var index = identifierFromToken(token);
eatToken();
return t.start(index);
}
if (token.type === _tokenizer.tokens.number) {
var _index2 = t.indexLiteral(token.value);
eatToken();
return t.start(_index2);
}
throw new Error("Unknown start, token: " + tokenToString(token));
}
if (token.type === _tokenizer.tokens.openParen) {
eatToken();
var startLoc = getStartLoc();
if (isKeyword(token, _tokenizer.keywords.export)) {
eatToken();
var node = parseExport();
var _endLoc2 = getEndLoc();
return t.withLoc(node, _endLoc2, startLoc);
}
if (isKeyword(token, _tokenizer.keywords.loop)) {
eatToken();
var _node = parseLoop();
var _endLoc3 = getEndLoc();
return t.withLoc(_node, _endLoc3, startLoc);
}
if (isKeyword(token, _tokenizer.keywords.func)) {
eatToken();
var _node2 = parseFunc();
var _endLoc4 = getEndLoc();
maybeIgnoreComment();
eatTokenOfType(_tokenizer.tokens.closeParen);
return t.withLoc(_node2, _endLoc4, startLoc);
}
if (isKeyword(token, _tokenizer.keywords.module)) {
eatToken();
var _node3 = parseModule();
var _endLoc5 = getEndLoc();
return t.withLoc(_node3, _endLoc5, startLoc);
}
if (isKeyword(token, _tokenizer.keywords.import)) {
eatToken();
var _node4 = parseImport();
var _endLoc6 = getEndLoc();
eatTokenOfType(_tokenizer.tokens.closeParen);
return t.withLoc(_node4, _endLoc6, startLoc);
}
if (isKeyword(token, _tokenizer.keywords.block)) {
eatToken();
var _node5 = parseBlock();
var _endLoc7 = getEndLoc();
eatTokenOfType(_tokenizer.tokens.closeParen);
return t.withLoc(_node5, _endLoc7, startLoc);
}
if (isKeyword(token, _tokenizer.keywords.memory)) {
eatToken();
var _node6 = parseMemory();
var _endLoc8 = getEndLoc();
eatTokenOfType(_tokenizer.tokens.closeParen);
return t.withLoc(_node6, _endLoc8, startLoc);
}
if (isKeyword(token, _tokenizer.keywords.data)) {
eatToken();
var _node7 = parseData();
var _endLoc9 = getEndLoc();
eatTokenOfType(_tokenizer.tokens.closeParen);
return t.withLoc(_node7, _endLoc9, startLoc);
}
if (isKeyword(token, _tokenizer.keywords.table)) {
eatToken();
var _node8 = parseTable();
var _endLoc10 = getEndLoc();
eatTokenOfType(_tokenizer.tokens.closeParen);
return t.withLoc(_node8, _endLoc10, startLoc);
}
if (isKeyword(token, _tokenizer.keywords.global)) {
eatToken();
var _node9 = parseGlobal();
var _endLoc11 = getEndLoc();
eatTokenOfType(_tokenizer.tokens.closeParen);
return t.withLoc(_node9, _endLoc11, startLoc);
}
if (isKeyword(token, _tokenizer.keywords.type)) {
eatToken();
var _node10 = parseType();
var _endLoc12 = getEndLoc();
eatTokenOfType(_tokenizer.tokens.closeParen);
return t.withLoc(_node10, _endLoc12, startLoc);
}
if (isKeyword(token, _tokenizer.keywords.start)) {
eatToken();
var _node11 = parseStart();
var _endLoc13 = getEndLoc();
eatTokenOfType(_tokenizer.tokens.closeParen);
return t.withLoc(_node11, _endLoc13, startLoc);
}
if (isKeyword(token, _tokenizer.keywords.elem)) {
eatToken();
var _node12 = parseElem();
var _endLoc14 = getEndLoc();
eatTokenOfType(_tokenizer.tokens.closeParen);
return t.withLoc(_node12, _endLoc14, startLoc);
}
var instruction = parseFuncInstr();
var endLoc = getEndLoc();
maybeIgnoreComment();
if (_typeof(instruction) === "object") {
if (typeof token !== "undefined") {
eatTokenOfType(_tokenizer.tokens.closeParen);
}
return t.withLoc(instruction, endLoc, startLoc);
}
}
if (token.type === _tokenizer.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" + (0, _helperCodeFrame.codeFrameFromSource)(source, token.loc) + "\n" + "Unknown token" + ", given " + tokenToString(token));
}();
}
var body = [];
while (current < tokensList.length) {
body.push(walk());
}
return t.program(body);
}