Skip to content

Commit

Permalink
string literals, function pointers
Browse files Browse the repository at this point in the history
  • Loading branch information
Arty Buldauskas committed Sep 10, 2017
1 parent 5c62c13 commit 5c81612
Show file tree
Hide file tree
Showing 13 changed files with 150 additions and 22 deletions.
38 changes: 36 additions & 2 deletions src/__tests__/type-imports-spec.js
Expand Up @@ -3,15 +3,15 @@ import compile from '..';

const compileAndRun = (src, importsObj = {}) => WebAssembly.instantiate(compile(src), importsObj);

test('type-imports', t =>
test('function typed imports', t =>
// What is happening here:
// We are creating a module which takes an import of console.log
// we provide this import to the module in the second param. We
// then invoke the test() function exported from the module and
// test that the correct value was echo-ed back to us!
new Promise(resolve => {
compileAndRun(`
import { log: Log } from env;
import { log: Log } from 'env';
type Log = (i32) => void;
export function test(x: i32): void {
log(42);
Expand All @@ -26,3 +26,37 @@ test('type-imports', t =>
}).then(result => result.instance.exports.test(4434) );
})
);

test.only('function pointers', t =>
new Promise(resolve => {
const table = new WebAssembly.Table({ element: 'anyfunc', initial: 10 });
compileAndRun(`
import { setTimeout: Later } from 'env';
import { log: Log } from 'env';
type Log = (i32) => void;
type Later = (Function, i32) => void;
function echo(): void {
log(42);
}
export function test(): void {
setTimeout(echo, 200);
}
`, {
env: {
table,
log: function(value) {
t.is(value, 42);
resolve();
},
setTimeout: (functionPointer, timeout) => {
const func = table.get(functionPointer);
setTimeout(func, timeout);
}
}
}).then(result => result.instance.exports.test());
})
);

Empty file removed src/emitter/global_type.js
Empty file.
11 changes: 8 additions & 3 deletions src/emitter/section/element.js
Expand Up @@ -11,9 +11,14 @@ type Element = {
};

const emitElement = (stream: OutputStream) =>
({ functionIndex }: Element) => {
stream.push(varuint32, functionIndex, 'function index');
}
({ functionIndex }: Element, index: number) => {
stream.push(varuint32, 0, 'table index');
stream.push(u8, opcode.i32Const.code, 'offset');
stream.push(varuint32, index, '');
stream.push(u8, opcode.End.code, 'end');
stream.push(varuint32, 1, 'number of elements');
stream.push(varuint32, functionIndex, 'function index');
}

const emit = (elements: Element[]) => {
const stream = new OutputStream();
Expand Down
15 changes: 13 additions & 2 deletions src/emitter/section/imports.js
@@ -1,8 +1,12 @@
import OutputStream from '../../utils/output-stream';
import { u8, i8, u32, } from 'wasm-types';
import { varint1, varint7, varuint32 } from '../numbers';
import { getTypeString } from '../value_type';
import { EXTERN_GLOBAL, EXTERN_FUNCTION } from '../external_kind';
import { getTypeString, ANYFUNC } from '../value_type';
import {
EXTERN_GLOBAL,
EXTERN_FUNCTION,
EXTERN_TABLE
} from '../external_kind';
import { emitString } from '../string';
import writer from './writer';

Expand All @@ -25,6 +29,13 @@ const emit = entries => {
payload.push(varuint32, typeIndex, 'type index');
break;
}
case EXTERN_TABLE: {
payload.push(u8, kind, 'Table');
payload.push(u8, ANYFUNC, 'function table types');
payload.push(varint1, 0, 'has max value');
payload.push(varuint32, 0, 'iniital table size');
break;
}
}
});

Expand Down
2 changes: 2 additions & 0 deletions src/parser/__tests__/__snapshots__/parser-spec.js.snap
Expand Up @@ -3,6 +3,7 @@
exports[`compiles exports 1`] = `
Object {
"Code": Array [],
"Element": Array [],
"Exports": Array [
Object {
"field": "answer",
Expand Down Expand Up @@ -96,6 +97,7 @@ Object {
exports[`compiles globals 1`] = `
Object {
"Code": Array [],
"Element": Array [],
"Exports": Array [],
"Functions": Array [],
"Globals": Array [
Expand Down
27 changes: 26 additions & 1 deletion src/parser/context.js
@@ -1,6 +1,8 @@
// @flow
import { getType } from './generator';
import { getType, generateImport, generateElement } from './generator';
import { EXTERN_TABLE } from '../emitter/external_kind';
import type { Node, TypeNode } from './node';
import { find } from 'ramda';

const generateErrorString = (
msg:string,
Expand Down Expand Up @@ -56,6 +58,7 @@ class Context {
this.Program.Exports = [];
this.Program.Imports = [];
this.Program.Globals = [];
this.Program.Element = [];
this.Program.Functions = [];
}

Expand Down Expand Up @@ -140,6 +143,28 @@ class Context {
range: node.range.concat(token.end)
});
}

writeFunctionPointer(functionIndex: number): void {
if (!this.Program.Element.length) {
this.Program.Imports.push.apply(
this.Program.Imports,
generateImport({
module: 'env',
fields: [{
id: 'table',
kind: EXTERN_TABLE
}]
}));
}

const exists = find(
n => n.functionIndex === functionIndex,
this.Program.Element
);
if (exists == null) {
this.Program.Element.push(generateElement(functionIndex));
}
}
}

export default Context;
Expand Down
10 changes: 7 additions & 3 deletions src/parser/generator.js
Expand Up @@ -58,13 +58,13 @@ export const generateExport = decl => {

export const generateImport = node => {
const module = node.module;
return node.fields.map(({ id, nativeType, typeIndex }) => {
const kind = (nativeType && EXTERN_GLOBAL) || EXTERN_FUNCTION;
return node.fields.map(({ id, nativeType, typeIndex, global, kind }) => {
kind = kind || ((nativeType && EXTERN_GLOBAL) || EXTERN_FUNCTION);
return {
module,
field: id,
kind,
global,
kind,
typeIndex
};
});
Expand Down Expand Up @@ -262,6 +262,10 @@ export const generateExpression = (node, parent) => {
return block;
}

export const generateElement = (functionIndex) => {
return { functionIndex };
}

export const generateCode = func => {
const block = {
code: [],
Expand Down
5 changes: 3 additions & 2 deletions src/parser/import.js
Expand Up @@ -20,7 +20,6 @@ export type Import = {
};

const field = (ctx: Context): Field => {
debugger;
const f: Field = {
id: ctx.expect(null, Syntax.Identifier).value
};
Expand Down Expand Up @@ -81,7 +80,9 @@ const _import = (ctx: Context): Import => {
node.fields = fieldList(ctx);
ctx.expect(['from']);

node.module = ctx.expect(null, Syntax.Identifier).value;
node.module = ctx.expect(null, Syntax.StringLiteral).value;
// NOTE: string literals contain the starting and ending quote char
node.module = node.module.substring(1, node.module.length - 1);

ctx.Program.Imports.push.apply(
ctx.Program.Imports,
Expand Down
1 change: 0 additions & 1 deletion src/parser/maybe-function-declaration.js
Expand Up @@ -79,7 +79,6 @@ const maybeFunctionDeclaration = (ctx) => {
// throw ctx.syntaxError(`Return type expected ${node.result}, received ${JSON.stringify(ret)}`);
}


// generate the code block for the emiter
ctx.Program.Code.push(generateCode(node));

Expand Down
14 changes: 11 additions & 3 deletions src/parser/maybe-identifier.js
Expand Up @@ -9,13 +9,21 @@ const maybeIdentifier = (ctx) => {
const functionIndex = ctx.functions.findIndex(f => f.id === ctx.token.value);
const isFuncitonCall = ctx.stream.peek().value === '(';

// if function call then encode it as such
// Function pointer
if (!isFuncitonCall && localIndex < 0 && globalIndex < 0 && functionIndex > -1) {
node.value = functionIndex;
// Save the element
ctx.writeFunctionPointer(functionIndex);
// Encode a function pointer as a i32.const representing the function index
const tableIndex = ctx.Program.Element.findIndex(e => e.functionIndex === functionIndex);
node.value = tableIndex;
return ctx.endNode(node, Syntax.Constant);
} else if (isFuncitonCall) {
// if function call then encode it as such
return functionCall(ctx);
} else if (localIndex !== -1) {
}

// Not a function call or pointer, look-up variables
if (localIndex !== -1) {
node.localIndex = localIndex;
node.target = ctx.func.locals[localIndex];
node.type = node.target.type;
Expand Down
11 changes: 6 additions & 5 deletions src/tokenizer/identifier/index.js
@@ -1,10 +1,11 @@
const token = require('./../token');
const punctuator = require('./../punctuator');
const constant = require('./../constant');
const Syntax = require('../../Syntax');
import token from '../token';
import punctuator from '../punctuator';
import constant from '../constant';
import string from '../string';
import Syntax from '../../Syntax';

const parse = char => {
if (!punctuator(char) && !constant(char))
if (!string(char) && !punctuator(char) && !constant(char))
return parse;
return null;
}
Expand Down
2 changes: 2 additions & 0 deletions src/tokenizer/index.js
Expand Up @@ -3,6 +3,7 @@ import punctuator from './punctuator';
import constant from './constant';
import identifier from './identifier';
import keyword from './keyword';
import string from './string';
import type from './type';

class Tokenizer {
Expand All @@ -13,6 +14,7 @@ class Tokenizer {
constant,
identifier,
keyword,
string,
type
]
) {
Expand Down
36 changes: 36 additions & 0 deletions src/tokenizer/string/index.js
@@ -0,0 +1,36 @@
import token from '../token';
import Syntax from '../../Syntax';

const quoteOK = quoteCheck => char => quoteCheck;
const nextFails = () => null;

const endsInSingleQuote = char => {
if (char === '\\')
return quoteOK(endsInSingleQuote);
if (char === '\'')
return nextFails;

return endsInSingleQuote;
}

const endsInDoubleQuote = char => {
if (char === '\\')
return quoteOK(endsInDoubleQuote);
if (char === '"')
return nextFails;

return endsInDoubleQuote;
}

const maybeQuote = char => {
if (char === '\'')
return endsInSingleQuote;
if (char === '"')
return endsInDoubleQuote;

return null;
}

const stringParser = token(maybeQuote, Syntax.StringLiteral);
export default stringParser;

0 comments on commit 5c81612

Please sign in to comment.