UNPKG

walt-compiler

Version:

Alternative syntax for WebAssembly text format

148 lines (128 loc) 3.86 kB
import { makeFragment } from '../../parser/fragment'; import makeParser from '../../parser'; import test from 'ava'; import { map } from 'walt-parser-tools/map-node'; import { combineParsers } from '..'; import { compile } from '../..'; const stmt = makeFragment(makeParser([])); test('plugin system', t => { const calls = []; // Should be able to call a method to get all plugins const plugin1 = () => { // Should return middleware return { // do something with options parser(_options) { // must return an object return {}; }, /* options here should be things like version number or plugin options */ semantics(_options) { // must return an object return { Identifier: next => ([node]) => { calls.push('plugin1.semantics.Identifier'); return next([node]); }, }; }, }; }; const plugin2 = () => { return { semantics() { return { Identifier: next => ([node, context]) => { calls.push('plugin2.semantics.Identifier'); return next([node, { ...context, extra: 'o' }]); }, BinaryExpression: next => ([node, context = { foo: [] }]) => { calls.push('plugin2.semantics.BinaryExpression'); context = { ...context, foo: [...context.foo, 'bar'] }; return next([ { ...node, type: 'f64', }, context, ]); }, }; }, }; }; const base = () => { return { semantics() { return { // The final stop for parsing, will parse the children/params of all nodes // which have at least one parser attached to them. This is necessary // because the map-node utility bails out (on parsing children) if the parsing function wants // to use the transform argument, which we always do. '*': next => ([node, context], transform) => { calls.push(`base.semantics.${node.Type}`); return next([ { ...node, params: node.params.map(n => { return transform([n, context]); }), }, context, ]); }, }; }, }; }; const plugins = [base, plugin1, plugin2]; const parsers = combineParsers(plugins.map(p => p().semantics())); const binaryExpression = stmt`(x + 2);`; const node = map(parsers)([binaryExpression]); t.deepEqual( calls, [ 'plugin2.semantics.BinaryExpression', 'base.semantics.BinaryExpression', 'plugin2.semantics.Identifier', 'plugin1.semantics.Identifier', 'base.semantics.Identifier', ], 'Plugin precedence is preserved' ); t.is(node.type, 'f64', 'Nodes are actually parsed'); }); test('plugin invariants', t => { const plugin = () => ({ semantics: () => ({ Identifier: _ => ([node], transform) => { // Transform MUST be called with an array [node, context] return transform(node); }, }), }); const binaryExpression = stmt`(x + 2);`; const parsers = combineParsers([plugin].map(p => p().semantics())); t.throws(() => map(parsers)([binaryExpression])); }); test('compiler extensions use', t => { const calls = []; const plugin = (...args) => { calls.push(['plugin', args]); return { semantics(..._args) { calls.push(['semantics', _args]); return {}; }, grammar(..._args) { calls.push(['grammar', _args]); return { ParserRules: {}, }; }, }; }; const plugin2 = () => ({}); compile('const x: i32 = 0;', { extensions: [plugin, plugin2] }); t.snapshot(calls); });