killawat
Version:
KillaWAT is a WebAssembly Text Macro Assembler
1,178 lines (1,044 loc) • 39.6 kB
JavaScript
const { constMap, valtypeMap, logError, binaryen, funcSymbolTable, macroTable,
funcSymbolMap, unaryMap, binaryMap, WasmModule, globalSymbolTable, macroMap,
globalSymbolMap, TokenArray, storeMap, storeArray, loadMap, loadArray,
memoryOffsetMap } = require('./shared');
var blockCounter = 0;
const PARSE_STATE = {
START: 0,
PARAM: 1,
RESULT: 2,
LOCAL: 3,
BODY: 4
}
const ParamType = {
LOCAL: 0,
GLOBAL: 1,
CONST: 2,
}
class SymbolTableEntry {
constructor(index, id, type) {
this.index = index;
this.id = id;
this.type = type;
}
}
class StackEntry {
constructor(expression, type) {
this.type = type;
this.expression = expression;
}
}
class Func {
constructor(tokenArray) {
//console.log(TokenArray[0]);
this.meta = tokenArray.pop();
this.fileName = this.meta.file;
this.parseState = PARSE_STATE.START;
this.name = `$func_${funcSymbolTable.length}`;
this.exportName = null;
this.params = [];
this.result = binaryen.none;
this.locals = [];
this.localMap = new Map();
this.body = [];
this.bodyStack = [];
this.blockArray = [];
this.tokenArray = tokenArray;
let startToken = tokenArray[0];
this.bodyStartOffset = startToken.bodyStartOffset; // - startToken.index;
for (let i = 0; i < this.bodyStartOffset; i++) {
let token = tokenArray[i];
if (token.type === 'lp') {
this.subExpression(tokenArray.slice(i + 1, token.endTokenOffset + i));
i += token.endTokenOffset;
}
else if (token.type === 'name') {
this.name = token.text;
}
}
this.index = funcSymbolTable.length;
funcSymbolTable.push(this);
funcSymbolMap.set(this.name, this);
}
/*
this.name = token.value;
this.paramArray = [];
this.body = "";
*/
expandMacros() {
let tokenArray = this.tokenArray.slice(this.bodyStartOffset);
for (let i = 0; i < tokenArray.length; i++) {
let token = tokenArray[i];
if (token.type === 'macro_name') {
// macroMap, macroTable
let macro = macroMap.get(token.text);
let macroBody = [...macro.body];
for (let j = 0; j < macro.paramArray.length; j++) {
let pi = j + i + 1;
let replaceToken = macro.paramArray[j];
let paramToken = tokenArray[pi];
// int_literal, hex_literal, bin_literal, name
if (paramToken.type !== 'int_literal' &&
paramToken.type !== 'hex_literal' &&
paramToken.type !== 'bin_literal' &&
paramToken.type !== 'name') {
logError(`macro name must be followed by parameter values`, paramToken);
return;
}
for (let mbi = 0; mbi < macroBody.length; mbi++) {
let macroBodyToken = macroBody[mbi];
if (macroBodyToken.text === replaceToken.name) {
macroBody[mbi] = paramToken;
}
}
this.tokenArray.splice(this.bodyStartOffset + i, macro.paramArray.length + 1, ...macroBody);
tokenArray = this.tokenArray.slice(this.bodyStartOffset);
}
}
else if (token.type === 'magic_chars') {
let nextToken = tokenArray[i + 1] || {};
if (token.text === '##') {
if (nextToken.type === 'name') {
token.text = 'global.get';
token.value = 'global.get';
token.type = 'terminal';
}
else if (nextToken.type === 'float_literal') {
token.text = 'f64.const';
token.value = 'f64.const';
token.type = 'terminal';
}
else if (nextToken.type === 'int_literal' ||
nextToken.type === 'hex_literal' ||
nextToken.type === 'bin_literal') {
token.text = 'i64.const';
token.value = 'i64.const';
token.type = 'terminal';
}
else {
logError("unexpected token following '##", nextToken);
return;
}
}
else if (token.text === '#') {
if (nextToken.type === 'name') {
token.text = 'local.get';
token.value = 'local.get';
token.type = 'terminal';
}
else if (nextToken.type === 'float_literal') {
token.text = 'f32.const';
token.value = 'f32.const';
token.type = 'terminal';
}
else if (nextToken.type === 'int_literal' ||
nextToken.type === 'hex_literal' ||
nextToken.type === 'bin_literal') {
token.text = 'i32.const';
token.value = 'i32.const';
token.type = 'terminal';
}
else {
logError("unexpected token following '#", nextToken);
return;
}
}
else if (token.text === '=') {
if (nextToken.type !== 'name') {
logError("The '=' character must be followed by a variable name", nextToken);
return;
}
token.text = 'local.set';
token.value = 'local.set';
token.type = 'set';
}
// need to add global.set at some point
}
}
}
addFunction() {
// if I want to be able to call functions that are not declared yet I will need
// to wait to parse the body until after all functions defined in the funcSymbolTable
this.parseBody(this.tokenArray.slice(this.bodyStartOffset));
if (this.bodyStack.length > 0) {
this.body.push(this.bodyStack.pop());
}
this.funcRef = WasmModule.addFunction(this.name.slice(1),
binaryen.createType(this.params.map(s => s.type)),
this.result,
this.locals.map(s => s.type),
WasmModule.block(null, this.body.map(se => se.expression), this.result)
);
if (this.exportName != null) {
WasmModule.addFunctionExport(this.name.slice(1), this.exportName);
}
}
static getFuncSymbol(idToken) {
if (idToken.type === 'name') {
return funcSymbolMap.get(idToken.value);
}
else if (idToken.type === 'int_literal') {
return funcSymbolTable[idToken.value];
}
return null;
}
getLocal(index) {
if (index >= this.params.length) {
return this.locals[index - this.params.length];
}
return this.params[index];
}
subExpression(tokenArray) {
if (tokenArray.length < 2) {
logError(`invalid function subexpression`, tokenArray[0]);
return;
}
let token = tokenArray[0];
let nextToken = tokenArray[1];
if (token.text === 'type' && this.parseState === PARSE_STATE.START) {
if (nextToken.type !== 'name') {
logError(`type must be followed by a type name`, nextToken);
return;
}
logError(`Tell to implement types`, token);
return;
}
else if (token.text === 'export' && this.parseState === PARSE_STATE.START) {
if (nextToken.type !== 'string_literal') {
logError(`unexpected ${nextToken.text} following export`, nextToken);
return;
}
this.exportName = nextToken.value;
return;
}
else if (token.text === 'param') {
if (this.parseState > PARSE_STATE.PARAM) {
logError(`param can not be defined here`, token);
return;
}
this.parseState = PARSE_STATE.PARAM;
let name = `$param_${this.params.length}`;
let type = binaryen.i32;
if (nextToken.type === 'name') {
name = nextToken.text;
if (tokenArray.length < 3 || tokenArray[2].type !== 'valtype') {
logError(`param is missing type`, token);
return;
}
type = tokenArray[2].value;
// this is not right
let symbol = new SymbolTableEntry(this.params.length, name, type);
this.params.push(symbol);
this.localMap.set(name, symbol);
return;
}
else if (nextToken.type === 'valtype') {
for (let i = 1; i < tokenArray.length; i++) {
let valToken = tokenArray[i];
if (valToken.type !== 'valtype') {
logError(`keyword is not a Wasm type ${valToken.text}`, valToken);
return;
}
name = `$param_${this.params.length}`;
type = valToken.value;
let symbol = new SymbolTableEntry(this.params.length, name, type);
this.params.push(symbol);
this.localMap.set(name, symbol);
}
}
else {
logError(`unexpected ${nextToken.text} following param`, nextToken);
return;
}
}
else if (token.text === 'result') {
if (this.parseState >= PARSE_STATE.RESULT) {
logError(`result definition can not be here`, token);
return;
}
this.parseState = PARSE_STATE.RESULT;
if (nextToken.type !== 'valtype') {
logError(`unexpected ${nextToken.text} following result`, nextToken);
return;
}
this.result = nextToken.value;
return;
}
// the state check should probably be inside the if
else if (token.text === 'local') {
if (this.parseState > PARSE_STATE.LOCAL) {
logError(`local can not be defined here`, token);
return;
}
this.parseState = PARSE_STATE.LOCAL;
let name = `$local_${this.locals.length + this.params.length}`;
let type = binaryen.i32;
if (nextToken.type === 'name') {
name = nextToken.text;
if (tokenArray.length < 3 || tokenArray[2].type !== 'valtype') {
logError(`local is missing type`, token);
return;
}
type = tokenArray[2].value;
let symbol = new SymbolTableEntry(this.params.length + this.locals.length, name, type);
this.locals.push(symbol);
this.localMap.set(name, symbol);
return;
}
else if (nextToken.type === 'valtype') {
for (let i = 1; i < tokenArray.length; i++) {
let valToken = tokenArray[i];
if (valToken.type !== 'valtype') {
logError(`keyword is not a Wasm type ${valToken.text}`, valToken);
return;
}
let name = `$local_${this.locals.length + this.params.length}`;
type = valToken.value;
let symbol = new SymbolTableEntry(this.params.length + this.locals.length, name, type);
this.locals.push(symbol);
this.localMap.set(name, symbol);
}
}
else {
logError(`unexpected ${nextToken.text} following local`, nextToken);
return;
}
}
}
terminal(token, nextToken = null) {
if (nextToken === null) {
if (token.text === 'nop') {
return new StackEntry(WasmModule.nop(), binaryen.none);
}
else if (token.text === 'unreachable') {
return new StackEntry(WasmModule.unreachable(), binaryen.none);
}
logError(`terminal next token undefined`, token);
return null;
}
else if (token.text.slice(-5) === 'const') {
let constFunc = constMap.get(token.text);
// hival should be 0 unless this is an
return new StackEntry(constFunc(nextToken.value, nextToken.hival || 0),
valtypeMap.get(token.text.slice(0, 3)));
}
else if (token.text === 'local.get') {
let index = 0;
let localSymbol = null;
if (nextToken.type === 'int_literal') {
/*
index = nextToken.value;
localSymbol = this.getLocal(index);
*/
logError(`killawat does not support index for local.get`, nextToken);
return;
}
else if (nextToken.type === 'name') {
localSymbol = this.localMap.get(nextToken.text);
index = localSymbol.index;
}
else {
logError(`local.get must be followed by an integer or name, not ${nextToken.text}`, nextToken);
return null;
}
return new StackEntry(WasmModule.local.get(index, localSymbol.type), localSymbol.type);
}
else if (token.text === 'global.get') {
let globalSymbol = null;
if (nextToken.type === 'int_literal') {
//globalSymbol = globalSymbolTable[nextToken.value];
logError(`killawat does not support index for global.get`, nextToken);
return;
}
else if (nextToken.type === 'name') {
globalSymbol = globalSymbolMap.get(nextToken.text);
}
else {
logError(`global.get must be followed by an integer or name, not ${nextToken.text}`, nextToken);
return null;
}
return new StackEntry(WasmModule.global.get(globalSymbol.id, globalSymbol.globalType),
globalSymbol.globalType);
}
logError(`unknown keyword '${token.text}'`, token);
return null;
}
ifBranch(tokenArray) {
let condition = null;
let endConditionTokenOffset = null;
if (tokenArray[1].type === 'lp') {
if (tokenArray[2].text === 'then') {
logError(`missing condition branch after '(if'`, tokenArray[2]);
return null;
}
endConditionTokenOffset = tokenArray[1].endTokenOffset;
condition = this.bodyBranch(tokenArray.slice(2, 1 + endConditionTokenOffset));
}
else {
logError(`this style of branch requires a condition`, tokenArray[1]);
return null;
}
let thenStartIndex = (endConditionTokenOffset || 0) + 2;
if (tokenArray[thenStartIndex].type !== 'lp') {
logError(`expeted to find '(' instead found ${tokenArray[thenStartIndex].text}`,
tokenArray[thenStartIndex]);
return null;
}
if (tokenArray[thenStartIndex + 1].text !== 'then') {
logError(`expeted to find 'then' instead found ${tokenArray[thenStartIndex].text}`,
tokenArray[thenStartIndex]);
return null;
}
let thenStack = [];
let thenBlock = [];
let endThenIndex = thenStartIndex + tokenArray[thenStartIndex].endTokenOffset;
this.bodyBlock(tokenArray.slice(thenStartIndex + 2, endThenIndex), thenBlock, thenStack);
let hasElse = false;
let elseStartIndex = endThenIndex + 1;
let elseStack = [];
let elseBlock = [];
if (tokenArray[elseStartIndex] != null &&
tokenArray[elseStartIndex + 1] != null &&
tokenArray[elseStartIndex].type === 'lp' &&
tokenArray[elseStartIndex + 1].text === 'else') {
hasElse = true;
let endElseIndex = tokenArray[elseStartIndex].endTokenOffset + elseStartIndex;
this.bodyBlock(tokenArray.slice(elseStartIndex + 2, endElseIndex), elseBlock, elseStack);
}
// Module#if(condition: ExpressionRef, ifTrue: ExpressionRef, ifFalse?: ExpressionRef)
// Module#block(label: string | null, children: ExpressionRef[], resultType?: Type): ExpressionRef
let blockResult = thenStack[0] || { type: binaryen.none };
if (blockResult.type !== binaryen.none) {
thenBlock.push(thenStack.pop());
}
let thenWasmBlock = WasmModule.block(null, thenBlock.map(entry => entry.expression), blockResult.type)
if (hasElse === false) {
return new StackEntry(
WasmModule.if(condition.expression, thenWasmBlock),
blockResult.type);
}
else {
if (blockResult.type !== binaryen.none) {
elseBlock.push(elseStack.pop());
}
let elseWasmBlock = WasmModule.block(null, elseBlock.map(entry => entry.expression), blockResult.type);
return new StackEntry(
WasmModule.if(condition.expression, thenWasmBlock, elseWasmBlock),
blockResult.type);
}
}
// condition is a StackEntry
// I DON'T HAVE NEARLY ENOUGH ERROR CHECKING HERE
ifBlock(tokenArray, condition) {
let ifToken = tokenArray[0];
let endToken = tokenArray[ifToken.endTokenOffset];
let elseToken = null;
let thenStack = [];
let thenBlock = [];
let elseStack = [];
let elseBlock = [];
if (endToken != null && endToken.type === 'else_block') {
let elseTokenIndex = ifToken.endTokenOffset; //endToken;
let endTokenIndex = elseTokenIndex + tokenArray[elseTokenIndex].endTokenOffset;
this.bodyBlock(tokenArray.slice(1, elseTokenIndex), thenBlock, thenStack);
this.bodyBlock(tokenArray.slice(elseTokenIndex + 1, endTokenIndex), elseBlock, elseStack);
let blockResult = thenStack[0] || { type: binaryen.none };
if (blockResult.type !== binaryen.none) {
thenBlock.push(thenStack.pop());
if (elseStack.length === 0) {
logError(`else must end with a typed value on the stack`, elseToken);
return;
}
elseBlock.push(elseStack.pop()); // will need to make sure there are items on the else stack
}
let thenWasmBlock = WasmModule.block(null, thenBlock.map(entry => entry.expression), blockResult.type)
let elseWasmBlock = WasmModule.block(null, elseBlock.map(entry => entry.expression), blockResult.type)
return new StackEntry(
WasmModule.if(condition.expression, thenWasmBlock, elseWasmBlock),
blockResult.type);
}
else {
this.bodyBlock(tokenArray.slice(1, ifToken.endTokenOffset), thenBlock, thenStack);
let blockResult = thenStack[0] || { type: binaryen.none };
if (blockResult.type !== binaryen.none) {
thenBlock.push(thenStack.pop());
}
let thenWasmBlock = WasmModule.block(null, thenBlock.map(entry => entry.expression), blockResult.type)
// I DON' TTHINK THIS IS RIGHT
return new StackEntry(
WasmModule.if(condition.expression, thenWasmBlock),
blockResult.type);
}
}
bodyBranch(tokenArray) {
let startToken = tokenArray[0];
let nextToken = tokenArray[1];
if (startToken.type === 'set') {
if (nextToken.type !== 'name' && nextToken.type !== 'int_literal') {
logError(`expected to see name or integer instead of '${nextToken.text}'`)
return null;
}
let sub = this.bodyBranch(tokenArray.slice(3, -1));
if (sub == null || sub.type === binaryen.none) {
logError(`${startToken.text} is not receiving the proper input type`, startToken);
return null;
}
if (startToken.text === 'local.set') {
let index = 0;
let localSymbol = null;
if (nextToken.type === 'int_literal') {
/*
index = nextToken.value;
localSymbol = this.getLocal(index);
*/
logError(`killawat does not support index for local.set`, nextToken);
return;
}
else if (nextToken.type === 'name') {
localSymbol = this.localMap.get(nextToken.text);
index = localSymbol.index;
}
else {
logError(`local.set must be followed by an integer or name, not ${nextToken.text}`, nextToken);
return null;
}
return new StackEntry(WasmModule.local.set(index, sub.expression), binaryen.none);
}
else { // global.set
let globalSymbol = null;
if (nextToken.type === 'int_literal') {
//globalSymbol = globalSymbolTable[nextToken.value];
logError(`killawat does not support index for global.set`, nextToken);
return;
}
else if (nextToken.type === 'name') {
globalSymbol = globalSymbolMap.get(nextToken.text);
}
else {
logError(`global.get must be followed by an integer or name, not ${nextToken.text}`, nextToken);
return null;
}
return new StackEntry(WasmModule.global.set(globalSymbol.id.slice(1), sub.expression), binaryen.none);
}
}
else if (startToken.type === 'unary') {
let unaryDef = unaryMap.get(startToken.text);
let unaryFunc = unaryDef.binaryenFunc;
let unaryResult = unaryDef.resultType;
if (nextToken.type !== 'lp') {
logError(`expected to see '(' instead of '${nextToken.text}'`)
return null;
}
let sub = this.bodyBranch(tokenArray.slice(2, -1));
if (sub == null || sub.type !== unaryDef.paramType) {
logError(`${startToken.text} is not receiving the proper input type`, startToken);
return null;
}
return new StackEntry(unaryFunc(sub.expression), unaryResult);
}
else if (startToken.type === 'binary') {
let binaryDef = binaryMap.get(startToken.text);
let binaryFunc = binaryDef.binaryenFunc;
let binaryResult = binaryDef.resultType;
if (nextToken.type !== 'lp') {
logError(`expected to see '(' instead of '${nextToken.text}'`);
return null;
}
let secondParamIndex = nextToken.endTokenOffset + 2;
let secondParamToken = tokenArray[secondParamIndex];
if (secondParamToken.type !== 'lp') {
logError(`expected to see '(' instead of '${secondParamToken.text}'`);
return null;
}
// I need to add 1 to endTokenOffset because nextToken is at position 1
let paramSub1 = this.bodyBranch(tokenArray.slice(2, 1 + nextToken.endTokenOffset));
if (paramSub1 == null || paramSub1.type != binaryDef.paramType1) {
logError(`${startToken.text} is not receiving the proper input type`, startToken);
return null;
}
let paramSub2 = this.bodyBranch(tokenArray.slice(secondParamIndex + 1, -1));
if (paramSub2 == null || paramSub2.type != binaryDef.paramType2) {
logError(`${startToken.text} is not receiving the proper input type`, startToken);
return null;
}
return new StackEntry(binaryFunc(paramSub1.expression, paramSub2.expression), binaryResult);
}
else if (startToken.type === 'call') {
if (nextToken.type !== 'name' &&
nextToken.type !== 'int_literal') {
logError(`call must be followed by a name or integer, not ${nextToken.text}`,
nextToken);
return;
}
let func = Func.getFuncSymbol(nextToken);
let paramCount = func.params.length;
let paramIndex = 2;
let callParams = [];
for (let pi = 0; pi < paramCount; pi++) {
let paramTok = tokenArray[paramIndex];
if (paramTok.type !== 'lp') {
logError(`expected to see '(' instead of '${nextToken.text}'`);
return null;
}
let end = paramIndex + paramTok.endTokenOffset;
callParams.push(this.bodyBranch(tokenArray.slice(paramIndex + 1, end)));
paramIndex = end + 1;
}
return new StackEntry(
WasmModule.call(func.name.slice(1), callParams.map(se => se.expression), func.result),
func.result
);
}
else if (startToken.type === 'load') {
// storeMap, storeArray, loadMap, loadArray
let i = 0;
let load = loadMap.get(startToken.text);
let lookAheadIndex = i + 1;
let lookAheadToken = tokenArray[lookAheadIndex];
let offset = 0;
let align = 0;
if (lookAheadToken.type === 'offset') {
offset = lookAheadToken.value;
i = lookAheadIndex;
lookAheadIndex += 1;
lookAheadToken = tokenArray[lookAheadIndex];
if (lookAheadToken.type === 'align') {
align = lookAheadToken.value;
i = lookAheadIndex;
}
}
else if (lookAheadToken.type === 'align') {
align = lookAheadToken.value;
i = lookAheadIndex;
lookAheadIndex += 1;
lookAheadToken = tokenArray[lookAheadIndex];
if (lookAheadToken.type === 'offset') {
offset = lookAheadToken.value;
i = lookAheadIndex;
}
}
if (tokenArray[i].type !== 'lp') {
logError(`load requires an expression for the address`, tokenArray[i]);
return;
}
let sub = this.bodyBranch(tokenArray.slice(i + 1, -1));
offset += memoryOffsetMap.get(this.fileName);
return new StackEntry(
load.loadFunc(offset, align, sub.expression),
load.resultType
);
}
else if (startToken.type === 'store') {
// storeMap, storeArray, loadMap, loadArray
let i = 0;
let store = storeMap.get(startToken.text);
let lookAheadIndex = i + 1;
let lookAheadToken = tokenArray[lookAheadIndex];
let offset = 0;
let align = 0;
if (lookAheadToken.type === 'offset') {
offset = lookAheadToken.value;
i = lookAheadIndex;
lookAheadIndex += 1;
lookAheadToken = tokenArray[lookAheadIndex];
if (lookAheadToken.type === 'align') {
align = lookAheadToken.value;
i = lookAheadIndex;
}
}
else if (lookAheadToken.type === 'align') {
align = lookAheadToken.value;
i = lookAheadIndex;
lookAheadIndex += 1;
lookAheadToken = tokenArray[lookAheadIndex];
if (lookAheadToken.type === 'offset') {
offset = lookAheadToken.value;
i = lookAheadIndex;
}
}
let lpTok = tokenArray[i];
if (lpTok.type !== 'lp') {
logError(`load requires an expression for the address`, tokenArray[i]);
return;
}
let ptr = this.bodyBranch(tokenArray.slice(i + 1, i + lpTok.endTokenOffset));
i += lpTok.endTokenOffset + 1;
lpTok = tokenArray[i];
if (lpTok.type !== 'lp') {
logError(`load requires an expression for the address`, tokenArray[i]);
return;
}
let setVal = this.bodyBranch(tokenArray.slice(i + 1, -1));
offset += memoryOffsetMap.get(this.fileName);
return new StackEntry(
store.storeFunc(offset, align, ptr.expression, setVal.expression),
store.resultType
);
}
else if (startToken.type === 'br') {
if (nextToken.type !== 'name' &&
nextToken.type !== 'int_literal') {
logError(`br/br_if must be followed by a name or integer, not ${nextToken.text}`,
nextToken);
return;
}
if (this.blockArray.length === 0) {
logError(`br/br_if must be used inside a block or loop`, token);
return;
}
let name = nextToken.value.slice(1);
if (nextToken.type === 'int_literal') {
/*
if (nextToken.value >= this.blockArray.length) {
logError(`break depth of ${nextToken.value} is invalid`, nextToken);
return;
}
name = this.blockArray[nextToken.value];
*/
logError(`killawat does not support index for br`, nextToken);
return;
}
let br = null;
if (startToken.text === 'br_if') {
let sub = this.bodyBranch(tokenArray.slice(3, -1));
if (sub == null || sub.type !== binaryen.i32) {
logError(`br_if takes an i32 as input`, startToken);
return null;
}
br = WasmModule.br(name, sub.expression);
}
else {
br = WasmModule.br(name);
}
return new StackEntry(
br,
binaryen.none
);
}
else if (startToken.type === 'terminal') {
if (startToken.text === 'nop' || startToken.text === 'unreachable') {
return this.terminal(startToken);
}
else {
return this.terminal(startToken, nextToken);
}
}
else if (startToken.text === 'if') {
return this.ifBranch(tokenArray);
}
else if (startToken.type === 'begin_block') {
let blockBody = [];
let blockStack = [];
this.bodyBlock(tokenArray, blockBody, blockStack);
if (blockStack.length > 0) {
return blockStack.pop();
}
return blockBody.pop();
}
}
bodyBlock(tokenArray, blockTokens = null, blockStack = null) {
let block = blockTokens || this.body;
let stack = blockStack || this.bodyStack;
for (let i = 0; i < tokenArray.length; i++) {
let token = tokenArray[i];
let nextToken = tokenArray[i + 1];
let nextEntry = null;
if (token.type === 'lp') {
nextEntry = this.bodyBranch(tokenArray.slice(i + 1, i + token.endTokenOffset));
if (nextEntry.type === binaryen.none) {
block.push(nextEntry);
}
else {
stack.push(nextEntry);
}
i += token.endTokenOffset;
}
else if (token.type === 'terminal') {
if (token.text === 'nop' || token.text === 'unreachable') {
block.push(this.terminal(token));
}
else {
stack.push(this.terminal(token, nextToken));
i++;
}
}
else if (token.type === 'unary') {
let unaryDef = unaryMap.get(token.text);
let unaryFunc = unaryDef.binaryenFunc;
let unaryResult = unaryDef.resultType;
let stackEntry = new StackEntry(unaryFunc(stack.pop().expression), unaryResult);
if (stackEntry.type === binaryen.none) {
block.push(stackEntry);
}
else {
stack.push(stackEntry);
}
}
else if (token.type === 'binary') {
let binaryDef = binaryMap.get(token.text);
let binaryFunc = binaryDef.binaryenFunc;
let binaryResult = binaryDef.resultType;
if (stack.length < 2) {
logError(`this function requires at least two parameters on the stack`, token);
}
let paramSub1 = stack.pop();
let paramSub2 = stack.pop();
if (paramSub1.type != binaryDef.paramType1 || paramSub2.type != binaryDef.paramType2) {
logError(`${token.text} is not receiving the proper input type`, token);
return null;
}
let stackEntry = new StackEntry(binaryFunc(paramSub1.expression, paramSub2.expression),
binaryResult);
if (stackEntry.type === binaryen.none) {
block.push(stackEntry);
}
else {
stack.push(stackEntry);
}
}
else if (token.type === 'set') {
if (nextToken.type !== 'name' && nextToken.type !== 'int_literal') {
logError(`expected to see name or integer instead of '${nextToken.text}'`, token)
return null;
}
if (stack.length === 0) {
logError(`no values to pop from stack`, token);
return null;
}
let sub = stack.pop();
if (token.text === 'local.set') {
let index = 0;
let localSymbol = null;
if (nextToken.type === 'int_literal') {
logError(`killawat does not support index for local.set`, nextToken);
return;
/*
index = nextToken.value;
localSymbol = this.getLocal(index);
*/
}
else if (nextToken.type === 'name') {
localSymbol = this.localMap.get(nextToken.text);
index = localSymbol.index;
}
else {
logError(`local.set must be followed by an integer or name, not ${nextToken.text}`, nextToken);
return null;
}
block.push(new StackEntry(WasmModule.local.set(index, sub.expression), binaryen.none));
}
else { // global.set
let globalSymbol = null;
if (nextToken.type === 'int_literal') {
//globalSymbol = globalSymbolTable[nextToken.value];
logError(`killawat does not support index for global.set`, nextToken);
return;
}
else if (nextToken.type === 'name') {
globalSymbol = globalSymbolMap.get(nextToken.text);
}
else {
logError(`global.get must be followed by an integer or name, not ${nextToken.text}`, nextToken);
return null;
}
block.push(StackEntry(WasmModule.global.set(globalSymbol.id.slice(1), sub.expression), binaryen.none));
}
}
else if (token.type === 'call') {
if (nextToken.type !== 'name' &&
nextToken.type !== 'int_literal') {
logError(`call must be followed by a name or integer, not ${nextToken.text}`,
nextToken);
return;
}
// I NEED TO STEP THROUGH THIS
let func = Func.getFuncSymbol(nextToken);
let paramCount = func.params.length;
let callParams = [];
for (let pi = 0; pi < paramCount; pi++) {
let paramTok = stack.pop();
callParams.unshift(paramTok);
}
let stackEntry = new StackEntry(
WasmModule.call(func.name, callParams.map(se => se.expression), func.result),
func.result
);
if (stackEntry.type === binaryen.none) {
block.push(stackEntry);
}
else {
stack.push(stackEntry);
}
}
else if (token.type === 'load') {
// storeMap, storeArray, loadMap, loadArray
let load = loadMap.get(token.text);
let lookAheadIndex = i + 1;
let lookAheadToken = tokenArray[lookAheadIndex] || {};
let offset = 0;
let align = 0;
if (lookAheadToken.type === 'offset') {
offset = lookAheadToken.value;
i = lookAheadIndex;
lookAheadIndex += 1;
lookAheadToken = tokenArray[lookAheadIndex] || {};
if (lookAheadToken.type === 'align') {
align = lookAheadToken.value;
i = lookAheadIndex;
}
}
else if (lookAheadToken.type === 'align') {
align = lookAheadToken.value;
i = lookAheadIndex;
lookAheadIndex += 1;
lookAheadToken = tokenArray[lookAheadIndex] || {};
if (lookAheadToken.type === 'offset') {
offset = lookAheadToken.value;
i = lookAheadIndex;
}
}
offset += memoryOffsetMap.get(this.fileName);
stack.push(new StackEntry(
load.loadFunc(offset, align, stack.pop().expression),
load.resultType
))
}
else if (token.type === 'store') {
// storeMap, storeArray, loadMap, loadArray
let i = 0;
let store = storeMap.get(startToken.text);
let lookAheadIndex = i + 1;
let lookAheadToken = tokenArray[lookAheadIndex] || {};
let offset = 0;
let align = 0;
if (lookAheadToken.type === 'offset') {
offset = lookAheadToken.value;
i = lookAheadIndex;
lookAheadIndex += 1;
lookAheadToken = tokenArray[lookAheadIndex] || {};
if (lookAheadToken.type === 'align') {
align = lookAheadToken.value;
i = lookAheadIndex;
}
}
else if (lookAheadToken.type === 'align') {
align = lookAheadToken.value;
i = lookAheadIndex;
lookAheadIndex += 1;
lookAheadToken = tokenArray[lookAheadIndex] || {};
if (lookAheadToken.type === 'offset') {
offset = lookAheadToken.value;
i = lookAheadIndex;
}
}
let ptr = stack.pop();
let setVal = stack.pop();
offset += memoryOffsetMap.get(this.fileName);
block.push(new StackEntry(
store.storeFunc(offset, align, ptr.expression, setVal.expression),
store.resultType
));
}
else if (token.type === 'br') {
if (nextToken.type !== 'name' &&
nextToken.type !== 'int_literal') {
logError(`br/br_if must be followed by a name or integer, not ${nextToken.text}`,
nextToken);
return;
}
if (this.blockArray.length === 0) {
logError(`br/br_if must be used inside a block or loop`, token);
return;
}
let name = nextToken.value.slice(1);
if (nextToken.type === 'int_literal') {
/*
if (nextToken.value >= this.blockArray.length) {
logError(`break depth of ${nextToken.value} is invalid`, nextToken);
return;
}
name = this.blockArray[nextToken.value];
*/
logError(`killawat does not support index for br`, nextToken);
return;
}
let br = null;
if (token.text === 'br_if') {
br = WasmModule.br(name, stack.pop().expression);
}
else {
br = WasmModule.br(name);
}
block.push(new StackEntry(
br,
binaryen.none
));
}
else if (token.type === 'begin_block') {
if (token.text === 'if') {
let endIndex = i + (token.endTokenOffset || (tokenArray.length - 1));
if (tokenArray[endIndex].type === 'else_block') {
endIndex += tokenArray[endIndex].endTokenOffset;
}
endIndex++;
let ifStackEntry = this.ifBlock(tokenArray.slice(i, endIndex), stack.pop());
if (ifStackEntry.type === binaryen.none) {
block.push(ifStackEntry);
}
else {
stack.push(ifStackEntry);
}
i += endIndex;
}
else {
//this.blockArray
// this will be for loop and block when I get to it
let endIndex = (i + token.endTokenOffset) || tokenArray.length; // endTokenOffset will be null when in parens
let name = `$block_${this.blockArray.length}`;
let subBlock = [];
if (nextToken.type === 'name') {
name = nextToken.text.slice(1);
i++;
}
// this.blockArray is used by br and br_if
this.blockArray.unshift(name);
let stackLen = stack.length;
let blockType = binaryen.none;
this.bodyBlock(tokenArray.slice(i, endIndex), subBlock, stack);
if (stackLen < stack.length) {
// if the lenght of the stack has grown move an item to the subBlock
let se = stack.pop();
blockType = se.type || binaryen.none;
subBlock.push(se);
}
let blockStackEntry = null;
if (token.text === 'block') {
blockStackEntry = new StackEntry(
WasmModule.block(name, subBlock.map(se => se.expression), blockType),
blockType
);
}
else {
let b = WasmModule.block(null, subBlock.map(se => se.expression), blockType);
blockStackEntry = new StackEntry(
WasmModule.loop(name, b),
blockType // I'm not sure if this should be blockType or binaryen.none
);
}
if (blockType === binaryen.none) {
block.push(blockStackEntry);
}
else {
stack.push(blockStackEntry);
}
i += endIndex;
this.blockArray.shift();
}
}
}
}
parseBody(tokenArray) {
this.bodyBlock(tokenArray);
}
}
module.exports.Func = Func;