├── .gitignore ├── .jscsrc ├── .jshintrc ├── README.md ├── lib ├── ast.js └── ast │ ├── api.js │ ├── lexer.js │ ├── parser.js │ └── scope.js ├── package.json └── test ├── fixtures.js ├── lexer-test.js └── parser-test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "disallowKeywordsOnNewLine": [ "else" ], 3 | "disallowMixedSpacesAndTabs": true, 4 | "disallowMultipleLineStrings": true, 5 | "disallowMultipleVarDecl": true, 6 | "disallowNewlineBeforeBlockStatements": true, 7 | "disallowQuotedKeysInObjects": true, 8 | "disallowSpaceAfterObjectKeys": true, 9 | "disallowSpaceAfterPrefixUnaryOperators": true, 10 | "disallowSpaceBeforePostfixUnaryOperators": true, 11 | "disallowSpacesInCallExpression": true, 12 | "disallowTrailingComma": true, 13 | "disallowTrailingWhitespace": true, 14 | "disallowYodaConditions": true, 15 | 16 | "requireCommaBeforeLineBreak": true, 17 | "requireOperatorBeforeLineBreak": true, 18 | "requireSpaceAfterBinaryOperators": true, 19 | "requireSpaceAfterKeywords": [ "if", "for", "while", "else", "try", "catch" ], 20 | "requireSpaceAfterLineComment": true, 21 | "requireSpaceBeforeBinaryOperators": true, 22 | "requireSpaceBeforeBlockStatements": true, 23 | "requireSpaceBeforeKeywords": [ "else", "catch" ], 24 | "requireSpaceBeforeObjectValues": true, 25 | "requireSpaceBetweenArguments": true, 26 | "requireSpacesInAnonymousFunctionExpression": { 27 | "beforeOpeningCurlyBrace": true 28 | }, 29 | "requireSpacesInFunctionDeclaration": { 30 | "beforeOpeningCurlyBrace": true 31 | }, 32 | "requireSpacesInFunctionExpression": { 33 | "beforeOpeningCurlyBrace": true 34 | }, 35 | "requireSpacesInConditionalExpression": true, 36 | "requireSpacesInForStatement": true, 37 | "requireSpacesInsideArrayBrackets": "all", 38 | "requireSpacesInsideObjectBrackets": "all", 39 | "requireDotNotation": true, 40 | 41 | "maximumLineLength": 80, 42 | "validateIndentation": 2, 43 | "validateLineBreaks": "LF", 44 | "validateParameterSeparator": ", ", 45 | "validateQuoteMarks": "'" 46 | } 47 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // JSHint Default Configuration File (as on JSHint website) 3 | // See http://jshint.com/docs/ for more details 4 | 5 | "maxerr" : 50, // {int} Maximum error before stopping 6 | 7 | // Enforcing 8 | "bitwise" : false, // true: Prohibit bitwise operators (&, |, ^, etc.) 9 | "camelcase" : false, // true: Identifiers must be in camelCase 10 | "curly" : false, // true: Require {} for every new block or scope 11 | "eqeqeq" : true, // true: Require triple equals (===) for comparison 12 | "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() 13 | "freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc. 14 | "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` 15 | "indent" : 2, // {int} Number of spaces to use for indentation 16 | "latedef" : true, // true: Require variables/functions to be defined before being used 17 | "newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()` 18 | "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` 19 | "noempty" : false, // true: Prohibit use of empty blocks 20 | "nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters. 21 | "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) 22 | "plusplus" : false, // true: Prohibit use of `++` & `--` 23 | "quotmark" : "single", // Quotation mark consistency: 24 | // false : do nothing (default) 25 | // true : ensure whatever is used is consistent 26 | // "single" : require single quotes 27 | // "double" : require double quotes 28 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) 29 | "unused" : true, // true: Require all defined variables be used 30 | "strict" : true, // true: Requires all functions run in ES5 Strict Mode 31 | "maxparams" : false, // {int} Max number of formal params allowed per function 32 | "maxdepth" : 3, // {int} Max depth of nested blocks (within functions) 33 | "maxstatements" : false, // {int} Max number statements per function 34 | "maxcomplexity" : false, // {int} Max cyclomatic complexity per function 35 | "maxlen" : false, // {int} Max number of characters per line 36 | 37 | // Relaxing 38 | "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) 39 | "boss" : false, // true: Tolerate assignments where comparisons would be expected 40 | "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. 41 | "eqnull" : false, // true: Tolerate use of `== null` 42 | "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) 43 | "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`) 44 | "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) 45 | // (ex: `for each`, multiple try/catch, function expression…) 46 | "evil" : false, // true: Tolerate use of `eval` and `new Function()` 47 | "expr" : false, // true: Tolerate `ExpressionStatement` as Programs 48 | "funcscope" : false, // true: Tolerate defining variables inside control statements 49 | "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict') 50 | "iterator" : false, // true: Tolerate using the `__iterator__` property 51 | "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block 52 | "laxbreak" : false, // true: Tolerate possibly unsafe line breakings 53 | "laxcomma" : false, // true: Tolerate comma-first style coding 54 | "loopfunc" : false, // true: Tolerate functions being defined in loops 55 | "multistr" : false, // true: Tolerate multi-line strings 56 | "noyield" : false, // true: Tolerate generator functions with no yield statement in them. 57 | "notypeof" : false, // true: Tolerate invalid typeof operator values 58 | "proto" : false, // true: Tolerate using the `__proto__` property 59 | "scripturl" : false, // true: Tolerate script-targeted URLs 60 | "shadow" : true, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` 61 | "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation 62 | "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` 63 | "validthis" : false, // true: Tolerate using this in a non-constructor function 64 | 65 | // Environments 66 | "browser" : true, // Web Browser (window, document, etc) 67 | "browserify" : true, // Browserify (node.js code in the browser) 68 | "couch" : false, // CouchDB 69 | "devel" : true, // Development/debugging (alert, confirm, etc) 70 | "dojo" : false, // Dojo Toolkit 71 | "jasmine" : false, // Jasmine 72 | "jquery" : false, // jQuery 73 | "mocha" : true, // Mocha 74 | "mootools" : false, // MooTools 75 | "node" : true, // Node.js 76 | "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) 77 | "prototypejs" : false, // Prototype and Scriptaculous 78 | "qunit" : false, // QUnit 79 | "rhino" : false, // Rhino 80 | "shelljs" : false, // ShellJS 81 | "worker" : false, // Web Workers 82 | "wsh" : false, // Windows Scripting Host 83 | "yui" : false, // Yahoo User Interface 84 | 85 | // Custom Globals 86 | "globals" : { 87 | "module": true 88 | } // additional predefined global variables 89 | } 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebAssembly AST Parser 2 | 3 | ## LICENSE 4 | 5 | This software is licensed under the MIT License. 6 | 7 | Copyright Fedor Indutny, 2015. 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a 10 | copy of this software and associated documentation files (the 11 | "Software"), to deal in the Software without restriction, including 12 | without limitation the rights to use, copy, modify, merge, publish, 13 | distribute, sublicense, and/or sell copies of the Software, and to permit 14 | persons to whom the Software is furnished to do so, subject to the 15 | following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included 18 | in all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 21 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 23 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 24 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 25 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 26 | USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | -------------------------------------------------------------------------------- /lib/ast.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.Lexer = require('./ast/lexer'); 4 | exports.Scope = require('./ast/scope'); 5 | exports.Parser = require('./ast/parser'); 6 | 7 | exports.parse = require('./ast/api').parse; 8 | -------------------------------------------------------------------------------- /lib/ast/api.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var ast = require('../ast'); 4 | 5 | exports.parse = function parse(source, options) { 6 | return ast.Parser.create(source, options).parse(); 7 | }; 8 | -------------------------------------------------------------------------------- /lib/ast/lexer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var keywords = [ 4 | 'call_import', 'call_indirect', 'addressof', 5 | 'if', 'else', 'do', 'while', 'forever', 'continue', 'break', 'return', 6 | 'switch', 'export', 'import' 7 | ]; 8 | 9 | var template = 10 | '\\s*' + // skip whitespace 11 | '(?:\\/\\/[^\\r\\n]*(?:[\\r\\n]+|$))*' + // skip comments 12 | '(?:' + 13 | '([\\.\\,{}\\(\\);=]|::)|' + // punctuation 14 | '(void|addr|i(?:32|64)|f(?:32|64))|' + // type 15 | '(' + keywords.join('|') + ')|' + // keyword 16 | '(' + 17 | '0x[a-fA-F0-9]+' + // hex literal 18 | '|' + 19 | '[+-]?[\\d_]+(?:\\.(?:[\\d_]*(?:e[+-]?\\d+)?)?)?' + // decimal literal 20 | ')|' + 21 | '([\\$a-z][\\$a-z\\d_]*)' + // identifier 22 | ')?'; 23 | 24 | function Lexer(source) { 25 | this.source = source.trim(); 26 | this.length = this.source.length; 27 | this.re = new RegExp(template, 'gi'); 28 | } 29 | module.exports = Lexer; 30 | 31 | Lexer.create = function create(source) { 32 | return new Lexer(source); 33 | }; 34 | 35 | Lexer.prototype.save = function save() { 36 | return this.re.lastIndex; 37 | }; 38 | 39 | Lexer.prototype.restore = function restore(index) { 40 | this.re.lastIndex = index; 41 | }; 42 | 43 | Lexer.prototype.next = function next() { 44 | while (true) { 45 | var prev = this.re.lastIndex; 46 | var out = this.re.exec(this.source); 47 | 48 | if (out === null || out[0].length === 0) 49 | throw new Error('Lexer failed at: ' + prev); 50 | if (prev + out[0].length !== this.re.lastIndex) 51 | throw new Error('Lexer failed at: ' + (prev + out[0].length)); 52 | 53 | if (out[1] !== undefined) 54 | return { type: 'Punctuation', value: out[1] }; 55 | else if (out[2] !== undefined) 56 | return { type: 'Type', value: out[2] }; 57 | else if (out[3] !== undefined) 58 | return { type: 'Keyword', value: out[3] }; 59 | else if (out[4] !== undefined) 60 | return { type: 'Literal', value: out[4] }; 61 | else if (out[5] !== undefined) 62 | return { type: 'Identifier', value: out[5] }; 63 | 64 | // Comment - loop 65 | } 66 | }; 67 | 68 | Lexer.prototype.end = function end() { 69 | return this.re.lastIndex >= this.length; 70 | }; 71 | -------------------------------------------------------------------------------- /lib/ast/parser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var BN = require('bn.js'); 5 | 6 | var ast = require('../ast'); 7 | var Scope = ast.Scope; 8 | 9 | function Parser(source, options) { 10 | this.lexer = ast.Lexer.create(source); 11 | this.options = options || {}; 12 | this.imported = {}; 13 | this.scope = new Scope(null); 14 | this.localScope = this.scope; 15 | this.loopDepth = 0; 16 | } 17 | module.exports = Parser; 18 | 19 | Parser.create = function create(source, options) { 20 | return new Parser(source, options); 21 | }; 22 | 23 | Parser.prototype.parse = function parse() { 24 | var body = []; 25 | while (!this.lexer.end()) { 26 | body.push(this.parseTopLevel()); 27 | } 28 | this.localScope.check(); 29 | 30 | return { 31 | type: 'Program', 32 | body: body 33 | }; 34 | }; 35 | 36 | Parser.prototype.end = function end() { 37 | return this.lexer.end(); 38 | }; 39 | 40 | Parser.prototype.maybe = function maybe(type, value) { 41 | var save = this.lexer.save(); 42 | var lexem = this.lexer.next(); 43 | if (lexem.type === type && (value === undefined || lexem.value === value)) 44 | return lexem.value; 45 | 46 | this.lexer.restore(save); 47 | return false; 48 | }; 49 | 50 | Parser.prototype.expect = function expect(type, value) { 51 | var lexem = this.lexer.next(); 52 | if (lexem.type === type && (value === undefined || lexem.value === value)) 53 | return lexem.value; 54 | 55 | throw new Error('Unexpected lexem: ' + JSON.stringify(lexem)); 56 | }; 57 | 58 | Parser.prototype.peek = function peek(type, value) { 59 | var save = this.lexer.save(); 60 | var lexem = this.lexer.next(); 61 | this.lexer.restore(save); 62 | if (lexem.type === type && (value === undefined || lexem.value === value)) 63 | return lexem.value; 64 | 65 | return false; 66 | }; 67 | 68 | Parser.prototype.parseTopLevel = function parseTopLevel() { 69 | var type = this.maybe('Type'); 70 | if (type !== false) 71 | return this.parseTopLevelFn(type); 72 | 73 | var keyword = this.maybe('Keyword'); 74 | if (keyword === 'import') 75 | return this.parseImport(); 76 | 77 | if (keyword === 'export') 78 | return this.parseExport(); 79 | 80 | if (keyword === false) 81 | assert(false, 'Expected either type or keyword'); 82 | else 83 | assert(false, 'Unexpected keyword: ' + keyword); 84 | }; 85 | 86 | Parser.prototype.type = function type(name) { 87 | return { type: 'Type', name: name }; 88 | }; 89 | 90 | Parser.prototype.lookup = function lookup(name, type) { 91 | if (this.options.index !== true) 92 | return { type: 'Identifier', name: name }; 93 | 94 | return this.scope.lookup(name, type); 95 | }; 96 | 97 | Parser.prototype.assign = function assign(name, kind) { 98 | if (this.options.index !== true) 99 | return { type: 'Identifier', name: name }; 100 | 101 | return this.scope.assign(name, kind); 102 | }; 103 | 104 | Parser.prototype.reserveLocal = function reserveLocal(name, type) { 105 | if (this.imported.hasOwnProperty(name)) 106 | return this.externalFn(this.imported[name], name); 107 | 108 | if (this.options.index !== true) 109 | return { type: 'Identifier', name: name }; 110 | 111 | return this.localScope.reserve(name, type); 112 | }; 113 | 114 | Parser.prototype.externalFn = function externalFn(module, name) { 115 | return { type: 'External', module: module, name: name }; 116 | }; 117 | 118 | Parser.prototype.fulfillLocal = function fulfillLocal(name, type) { 119 | if (this.options.index !== true) 120 | return { type: 'Identifier', name: name }; 121 | 122 | return this.localScope.fulfill(name, type); 123 | }; 124 | 125 | Parser.prototype.parseTopLevelFn = function parseTopLevelFn(result) { 126 | assert.notEqual(result, 'addr', 'addr type can\'t be returned'); 127 | 128 | var type = this.type(result); 129 | var name = this.fulfillLocal(this.expect('Identifier'), 'FunctionRef'); 130 | 131 | this.scope = new Scope(this.scope); 132 | var out = { 133 | type: 'Function', 134 | result: type, 135 | name: name, 136 | params: this.parseFnParams(), 137 | body: null, 138 | localCount: 0 139 | }; 140 | var block = this.parseBlock(); 141 | out.body = block.body; 142 | out.localCount = this.scope.localCount(); 143 | this.scope.check(); 144 | this.scope = this.scope.parent; 145 | return out; 146 | }; 147 | 148 | Parser.prototype.parseFnParams = function parseFnParams() { 149 | this.expect('Punctuation', '('); 150 | 151 | if (this.maybe('Punctuation', ')') !== false) 152 | return []; 153 | 154 | var out = []; 155 | while (true) { 156 | var type = this.expect('Type'); 157 | assert.notEqual(type, 'addr', 'addr type can\'t be a parameter'); 158 | out.push({ 159 | type: 'ParamDeclaration', 160 | result: this.type(type), 161 | name: this.assign(this.expect('Identifier'), 'Param') 162 | }); 163 | 164 | if (this.maybe('Punctuation', ',') !== false) 165 | continue; 166 | 167 | break; 168 | } 169 | 170 | this.expect('Punctuation', ')'); 171 | 172 | return out; 173 | }; 174 | 175 | Parser.prototype.parseBlock = function parseBlock() { 176 | this.expect('Punctuation', '{'); 177 | 178 | var out = []; 179 | while (true) { 180 | if (this.maybe('Punctuation', '}') !== false) 181 | break; 182 | 183 | var stmt = this.parseStatement(); 184 | out.push(stmt); 185 | if (stmt.type !== 'IfStatement' && stmt.type !== 'ForeverStatement') 186 | this.expect('Punctuation', ';'); 187 | while (this.maybe('Punctuation', ';') !== false) {} 188 | } 189 | 190 | return { type: 'BlockStatement', body: out }; 191 | }; 192 | 193 | Parser.prototype.parseStatement = function parseStatement() { 194 | var keyword = this.maybe('Keyword'); 195 | 196 | if (keyword === 'return') 197 | return this.parseReturn(); 198 | 199 | if (keyword === 'if') 200 | return this.parseIf(); 201 | 202 | if (keyword === 'forever') 203 | return this.parseForever(); 204 | 205 | if (keyword === 'do') 206 | return this.parseDoWhile(); 207 | 208 | if (keyword === 'break') 209 | return this.parseBreak(); 210 | 211 | if (keyword === 'continue') 212 | return this.parseContinue(); 213 | 214 | if (keyword !== false) { 215 | assert.equal(keyword, false, 'Unexpected keyword: ' + keyword); 216 | return; 217 | } 218 | 219 | // `i32 a` or `i32 a = ...` 220 | var save = this.lexer.save(); 221 | var type = this.maybe('Type'); 222 | if (type !== false && this.peek('Identifier')) 223 | return this.parseVarDecl(type); 224 | 225 | this.lexer.restore(save); 226 | return { 227 | type: 'ExpressionStatement', 228 | expression: this.parseExpression() 229 | }; 230 | }; 231 | 232 | Parser.prototype.parseReturn = function parseReturn() { 233 | return { 234 | type: 'ReturnStatement', 235 | argument: this.peek('Punctuation', ';') !== false ? 236 | null : this.parseExpression() 237 | }; 238 | }; 239 | 240 | Parser.prototype.parseIf = function parseIf() { 241 | this.expect('Punctuation', '('); 242 | var test = this.parseExpression(); 243 | this.expect('Punctuation', ')'); 244 | 245 | return { 246 | type: 'IfStatement', 247 | test: test, 248 | consequent: this.parseBlockOrStmt(), 249 | alternate: this.maybe('Keyword', 'else') ? this.parseBlockOrStmt() : null 250 | }; 251 | }; 252 | 253 | Parser.prototype.parseBlockOrStmt = function parseBlockOrStmt() { 254 | if (this.maybe('Punctuation', '{') === false) { 255 | var res = this.parseStatement(); 256 | this.expect('Punctuation', ';'); 257 | return res; 258 | } 259 | 260 | var out = []; 261 | while (true) { 262 | if (this.maybe('Punctuation', '}') !== false) 263 | break; 264 | 265 | var stmt = this.parseStatement(); 266 | out.push(stmt); 267 | if (stmt.type !== 'IfStatement') 268 | this.expect('Punctuation', ';'); 269 | while (this.maybe('Punctuation', ';') !== false) {} 270 | } 271 | 272 | return { type: 'BlockStatement', body: out }; 273 | }; 274 | 275 | Parser.prototype.parseExpression = function parseExpression() { 276 | var paren = this.maybe('Punctuation', '('); 277 | 278 | // (a, b, c) 279 | if (paren !== false) 280 | return this.parseSeqExpression(); 281 | 282 | var name = this.maybe('Identifier'); 283 | if (name !== false) { 284 | if (this.maybe('Punctuation', '=') !== false) 285 | return this.parseAssignment(name); 286 | if (this.peek('Punctuation', '(') !== false || 287 | this.peek('Punctuation', '::') !== false) { 288 | return this.parseCall(name); 289 | } 290 | return this.lookup(name); 291 | } 292 | 293 | // type.routine 294 | var type = this.maybe('Type'); 295 | if (type !== false) 296 | return this.parseBuiltin(type); 297 | 298 | var literal = this.maybe('Literal'); 299 | if (literal !== false) 300 | return this.parseLiteral(literal); 301 | 302 | throw new Error('Not implemented: ' + this.lexer.next().type); 303 | }; 304 | 305 | Parser.prototype.parseSeqExpression = function parseSeqExpression() { 306 | var out = []; 307 | 308 | while (true) { 309 | out.push(this.parseExpression()); 310 | var punc = this.expect('Punctuation'); 311 | if (punc === ',') 312 | continue; 313 | 314 | assert.equal(punc, ')', 'Matching closing paren not found'); 315 | break; 316 | } 317 | 318 | return { 319 | type: 'SequenceExpression', 320 | expressions: out 321 | }; 322 | }; 323 | 324 | Parser.prototype.parseBuiltin = function parseBuiltin(type) { 325 | this.expect('Punctuation', '.'); 326 | var method = this.expect('Identifier'); 327 | 328 | return { 329 | type: 'Builtin', 330 | result: this.type(type), 331 | method: method, 332 | arguments: this.parseCallParams() 333 | }; 334 | }; 335 | 336 | Parser.prototype.parseCallParams = function parseCallParams() { 337 | this.expect('Punctuation', '('); 338 | 339 | if (this.maybe('Punctuation', ')') !== false) 340 | return []; 341 | 342 | var out = []; 343 | while (true) { 344 | out.push(this.parseExpression()); 345 | if (this.maybe('Punctuation', ',') !== false) 346 | continue; 347 | 348 | break; 349 | } 350 | 351 | this.expect('Punctuation', ')'); 352 | 353 | return out; 354 | }; 355 | 356 | Parser.prototype.parseLiteral = function parseLiteral(value) { 357 | var num; 358 | if (/^0x/.test(value)) 359 | num = new BN(value.slice(2), 16); 360 | else if (/[e\.]/.test(value)) 361 | num = parseFloat(value); 362 | else 363 | num = new BN(value, 10); 364 | 365 | return { type: 'Literal', value: num }; 366 | }; 367 | 368 | Parser.prototype.parseVarDecl = function parseVarDecl(type) { 369 | assert(type !== 'void', 'Can\'t declare void variable'); 370 | var name = this.expect('Identifier'); 371 | 372 | var init = null; 373 | var assign = this.maybe('Punctuation', '='); 374 | if (assign !== false) 375 | init = this.parseExpression(); 376 | 377 | return { 378 | type: 'VariableDeclaration', 379 | result: this.type(type), 380 | id: this.assign(name, 'Local'), 381 | init: init 382 | }; 383 | }; 384 | 385 | Parser.prototype.parseAssignment = function parseAssignment(name) { 386 | return { 387 | type: 'AssignmentExpression', 388 | operator: '=', 389 | left: this.lookup(name, 'Local'), 390 | right: this.parseExpression() 391 | }; 392 | }; 393 | 394 | Parser.prototype.parseForever = function parseForever() { 395 | this.loopDepth++; 396 | var res = { 397 | type: 'ForeverStatement', 398 | body: this.parseBlockOrStmt() 399 | }; 400 | this.loopDepth--; 401 | return res; 402 | }; 403 | 404 | Parser.prototype.parseDoWhile = function parseDoWhile() { 405 | this.loopDepth++; 406 | var body = this.parseBlockOrStmt(); 407 | this.expect('Keyword', 'while'); 408 | this.expect('Punctuation', '('); 409 | var test = this.parseExpression(); 410 | this.expect('Punctuation', ')'); 411 | var res = { 412 | type: 'DoWhileStatement', 413 | body: body, 414 | test: test 415 | }; 416 | this.loopDepth--; 417 | return res; 418 | }; 419 | 420 | Parser.prototype.parseBreak = function parseBreak() { 421 | assert(this.loopDepth > 0, '`break` outside of the loop'); 422 | return { 423 | type: 'BreakStatement' 424 | }; 425 | }; 426 | 427 | Parser.prototype.parseContinue = function parseContinue() { 428 | assert(this.loopDepth > 0, '`continue` outside of the loop'); 429 | return { 430 | type: 'ContinueStatement' 431 | }; 432 | }; 433 | 434 | Parser.prototype.parseCall = function parseCall(name) { 435 | var fn; 436 | if (this.maybe('Punctuation', '::')) { 437 | var module = name; 438 | name = this.expect('Identifier'); 439 | fn = this.externalFn(module, name); 440 | } else { 441 | fn = this.reserveLocal(name, 'FunctionRef'); 442 | } 443 | 444 | return { 445 | type: 'CallExpression', 446 | fn: fn, 447 | arguments: this.parseCallParams() 448 | }; 449 | }; 450 | 451 | Parser.prototype.parseImport = function parseImport() { 452 | var names = []; 453 | do 454 | names.push(this.expect('Identifier')); 455 | while (this.maybe('Punctuation', ',')); 456 | 457 | // TODO(indutny): should it be a keyword? 458 | this.expect('Identifier', 'from'); 459 | 460 | var module = this.expect('Identifier'); 461 | 462 | for (var i = 0; i < names.length; i++) { 463 | assert(!this.imported.hasOwnProperty(names[i]), 464 | 'Duplicate imported fn: ' + names[i]); 465 | this.imported[names[i]] = module; 466 | names[i] = this.externalFn(module, names[i]); 467 | } 468 | 469 | return { 470 | type: 'ImportStatement', 471 | names: names, 472 | module: { type: 'Identifier', name: module } 473 | }; 474 | }; 475 | 476 | Parser.prototype.parseExport = function parseExport() { 477 | var names = []; 478 | do 479 | names.push(this.reserveLocal(this.expect('Identifier'))); 480 | while (!this.end() && this.maybe('Punctuation', ',')); 481 | 482 | return { 483 | type: 'ExportStatement', 484 | names: names 485 | }; 486 | }; 487 | -------------------------------------------------------------------------------- /lib/ast/scope.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | 5 | function Scope(parent) { 6 | this.parent = parent; 7 | 8 | this.map = {}; 9 | this.indexes = { 10 | FunctionRef: 0, 11 | Local: 0, 12 | Param: 0 13 | }; 14 | this.unresolved = {}; 15 | } 16 | module.exports = Scope; 17 | 18 | Scope.prototype.assign = function assign(name, type) { 19 | assert(!this.map.hasOwnProperty(name), 'Failed to redeclare: ' + name); 20 | var res = { 21 | type: type, 22 | name: name, 23 | index: this.indexes[type]++ 24 | }; 25 | this.map[name] = res; 26 | return res; 27 | }; 28 | 29 | Scope.prototype.fulfill = function fulfill(name, type) { 30 | var isPending = this.unresolved.hasOwnProperty(name); 31 | if (!isPending) 32 | return this.assign(name, type); 33 | 34 | var res = this.lookup(name, type); 35 | delete this.unresolved[name]; 36 | return res; 37 | }; 38 | 39 | Scope.prototype.lookup = function lookup(name, type) { 40 | var current = this; 41 | while (current !== null && !current.map.hasOwnProperty(name)) 42 | current = current.parent; 43 | 44 | assert(current !== null, 'Failed to lookup: ' + name); 45 | var res = current.map[name]; 46 | if (type !== undefined) 47 | assert.equal(res.type, type, 'Invalid variable type'); 48 | 49 | return res; 50 | }; 51 | 52 | Scope.prototype.reserve = function reserve(name, type) { 53 | var current = this; 54 | while (current !== null && !current.map.hasOwnProperty(name)) 55 | current = current.parent; 56 | 57 | if (current !== null) { 58 | var res = current.map[name]; 59 | if (type !== undefined) 60 | assert.equal(res.type, type, 'Invalid variable type'); 61 | return res; 62 | } 63 | 64 | var res = this.assign(name, type); 65 | this.unresolved[name] = true; 66 | return res; 67 | }; 68 | 69 | Scope.prototype.localCount = function localCount() { 70 | return this.indexes.Local; 71 | }; 72 | 73 | Scope.prototype.check = function check() { 74 | var keys = Object.keys(this.unresolved); 75 | assert.equal(keys.length, 0, 76 | 'Following names were used, but were not resolved: ' + 77 | JSON.stringify(keys)); 78 | }; 79 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wasm-ast", 3 | "version": "3.0.1", 4 | "description": "WebAssembly AST parser", 5 | "main": "lib/ast.js", 6 | "scripts": { 7 | "test": "mocha --reporter=spec test/*-test.js && jscs lib/*.js lib/**/*.js test/*.js && jshint lib/*.js lib/**/*.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com/indutny/wasm-ast.git" 12 | }, 13 | "keywords": [ 14 | "WebAssembly", 15 | "AST", 16 | "parser" 17 | ], 18 | "author": "Fedor Indutny ", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/indutny/wasm-ast/issues" 22 | }, 23 | "homepage": "https://github.com/indutny/wasm-ast#readme", 24 | "devDependencies": { 25 | "jscs": "^2.1.1", 26 | "jshint": "^2.8.0", 27 | "mocha": "^2.3.3" 28 | }, 29 | "dependencies": { 30 | "bn.js": "^3.1.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/fixtures.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.fn2str = function fn2str(fn) { 4 | return fn.toString().replace(/^function[^{]+{\/\*|\*\/}$/g, ''); 5 | }; 6 | -------------------------------------------------------------------------------- /test/lexer-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var fixtures = require('./fixtures'); 5 | 6 | var wasmAST = require('../'); 7 | 8 | function test(source, expected) { 9 | var lexer = wasmAST.Lexer.create(fixtures.fn2str(source)); 10 | 11 | var out = []; 12 | while (!lexer.end()) { 13 | out.push(lexer.next()); 14 | } 15 | assert.deepEqual(out, expected); 16 | } 17 | 18 | describe('Lexer', function() { 19 | it('should handle comments', function() { 20 | test(function() {/* 21 | test1 22 | // comment 23 | test2 24 | */}, [ 25 | { type: 'Identifier', value: 'test1' }, 26 | { type: 'Identifier', value: 'test2' } 27 | ]); 28 | }); 29 | 30 | it('should handle empty function', function() { 31 | test(function() {/* 32 | void name() { 33 | } 34 | */}, [ 35 | { type: 'Type', value: 'void' }, 36 | { type: 'Identifier', value: 'name' }, 37 | { type: 'Punctuation', value: '(' }, 38 | { type: 'Punctuation', value: ')' }, 39 | { type: 'Punctuation', value: '{' }, 40 | { type: 'Punctuation', value: '}' } 41 | ]); 42 | }); 43 | 44 | it('should handle function with parameters', function() { 45 | test(function() {/* 46 | i32 name(i32 a, i32 b) { 47 | } 48 | */}, [ 49 | { type: 'Type', value: 'i32' }, 50 | { type: 'Identifier', value: 'name' }, 51 | { type: 'Punctuation', value: '(' }, 52 | { type: 'Type', value: 'i32' }, 53 | { type: 'Identifier', value: 'a' }, 54 | { type: 'Punctuation', value: ',' }, 55 | { type: 'Type', value: 'i32' }, 56 | { type: 'Identifier', value: 'b' }, 57 | { type: 'Punctuation', value: ')' }, 58 | { type: 'Punctuation', value: '{' }, 59 | { type: 'Punctuation', value: '}' } 60 | ]); 61 | }); 62 | 63 | it('should handle function with single expression', function() { 64 | test(function() {/* 65 | i32 name(i32 a, i32 b) { 66 | return i32.mul(a, b); 67 | } 68 | */}, [ 69 | { type: 'Type', value: 'i32' }, 70 | { type: 'Identifier', value: 'name' }, 71 | { type: 'Punctuation', value: '(' }, 72 | { type: 'Type', value: 'i32' }, 73 | { type: 'Identifier', value: 'a' }, 74 | { type: 'Punctuation', value: ',' }, 75 | { type: 'Type', value: 'i32' }, 76 | { type: 'Identifier', value: 'b' }, 77 | { type: 'Punctuation', value: ')' }, 78 | { type: 'Punctuation', value: '{' }, 79 | { type: 'Keyword', value: 'return' }, 80 | { type: 'Type', value: 'i32' }, 81 | { type: 'Punctuation', value: '.' }, 82 | { type: 'Identifier', value: 'mul' }, 83 | { type: 'Punctuation', value: '(' }, 84 | { type: 'Identifier', value: 'a' }, 85 | { type: 'Punctuation', value: ',' }, 86 | { type: 'Identifier', value: 'b' }, 87 | { type: 'Punctuation', value: ')' }, 88 | { type: 'Punctuation', value: ';' }, 89 | { type: 'Punctuation', value: '}' } 90 | ]); 91 | }); 92 | 93 | it('should save/restore', function() { 94 | var source = 'a b c d'; 95 | var lexer = wasmAST.Lexer.create(source); 96 | 97 | assert.equal(lexer.next().value, 'a'); 98 | assert.equal(lexer.next().value, 'b'); 99 | 100 | var save = lexer.save(); 101 | 102 | assert.equal(lexer.next().value, 'c'); 103 | assert.equal(lexer.next().value, 'd'); 104 | 105 | assert(lexer.end()); 106 | 107 | lexer.restore(save); 108 | assert(!lexer.end()); 109 | 110 | assert.equal(lexer.next().value, 'c'); 111 | assert.equal(lexer.next().value, 'd'); 112 | 113 | assert(lexer.end()); 114 | }); 115 | 116 | describe('literals', function() { 117 | it('should handle int', function() { 118 | test('123', [ { type: 'Literal', value: '123' } ]); 119 | }); 120 | 121 | it('should handle -int', function() { 122 | test('-123', [ { type: 'Literal', value: '-123' } ]); 123 | }); 124 | 125 | it('should handle +int', function() { 126 | test('+123', [ { type: 'Literal', value: '+123' } ]); 127 | }); 128 | 129 | it('should handle 1.', function() { 130 | test('1.', [ { type: 'Literal', value: '1.' } ]); 131 | }); 132 | 133 | it('should handle 0xdeadBEEF', function() { 134 | test('0xdeadBEEF', [ { type: 'Literal', value: '0xdeadBEEF' } ]); 135 | }); 136 | 137 | it('should handle 123.456', function() { 138 | test('123.456', [ { type: 'Literal', value: '123.456' } ]); 139 | }); 140 | 141 | it('should handle 123.456e1', function() { 142 | test('123.456e1', [ { type: 'Literal', value: '123.456e1' } ]); 143 | }); 144 | 145 | it('should handle 123.456e+1', function() { 146 | test('123.456e+1', [ { type: 'Literal', value: '123.456e+1' } ]); 147 | }); 148 | 149 | it('should handle 123.456e-1', function() { 150 | test('123.456e-1', [ { type: 'Literal', value: '123.456e-1' } ]); 151 | }); 152 | 153 | it('should handle 123)', function() { 154 | test('123)', [ 155 | { type: 'Literal', value: '123' }, 156 | { type: 'Punctuation', value: ')' } 157 | ]); 158 | }); 159 | }); 160 | }); 161 | -------------------------------------------------------------------------------- /test/parser-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var BN = require('bn.js'); 5 | var fixtures = require('./fixtures'); 6 | 7 | var wasmAST = require('../'); 8 | 9 | function test(source, expected, options) { 10 | source = fixtures.fn2str(source); 11 | 12 | var ast = wasmAST.parse(source, options); 13 | assert.deepEqual(ast, expected); 14 | } 15 | 16 | function testBody(source, expected, options) { 17 | source = fixtures.fn2str(source); 18 | 19 | var ast = wasmAST.parse(source, options); 20 | assert.deepEqual(ast.body[0].body, expected); 21 | } 22 | 23 | describe('Parser', function() { 24 | it('should parse basic function', function() { 25 | test(function() {/* 26 | i64 mul(i32 a, i32 b) { 27 | return i64.mul(i64.extend_u(a), i64.extend_u(b)); 28 | } 29 | */}, { 30 | type: 'Program', 31 | body: [ 32 | { 33 | type: 'Function', 34 | localCount: 0, 35 | name: { type: 'Identifier', name: 'mul' }, 36 | params: [ { 37 | type: 'ParamDeclaration', 38 | result: { type: 'Type', name: 'i32' }, 39 | name: { type: 'Identifier', name: 'a' } 40 | }, { 41 | type: 'ParamDeclaration', 42 | result: { type: 'Type', name: 'i32' }, 43 | name: { type: 'Identifier', name: 'b' } 44 | } ], 45 | result: { type: 'Type', name: 'i64' }, 46 | body: [ 47 | { 48 | type: 'ReturnStatement', 49 | argument: { 50 | type: 'Builtin', 51 | result: { type: 'Type', name: 'i64' }, 52 | method: 'mul', 53 | arguments: [ { 54 | type: 'Builtin', 55 | result: { type: 'Type', name: 'i64' }, 56 | method: 'extend_u', 57 | arguments: [ { type: 'Identifier', name: 'a' } ] 58 | }, { 59 | type: 'Builtin', 60 | result: { type: 'Type', name: 'i64' }, 61 | method: 'extend_u', 62 | arguments: [ { type: 'Identifier', name: 'b' } ] 63 | } ] 64 | } 65 | } 66 | ] 67 | } 68 | ] 69 | }); 70 | }); 71 | 72 | it('should index params', function() { 73 | test(function() {/* 74 | i64 second(i32 a, i32 b, i32 c) { 75 | return b; 76 | } 77 | */}, { 78 | type: 'Program', 79 | body: [ 80 | { 81 | type: 'Function', 82 | localCount: 0, 83 | name: { type: 'FunctionRef', name: 'second', index: 0 }, 84 | params: [ 85 | { 86 | type: 'ParamDeclaration', 87 | result: { type: 'Type', name: 'i32' }, 88 | name: { type: 'Param', name: 'a', index: 0 } 89 | }, 90 | { 91 | type: 'ParamDeclaration', 92 | result: { type: 'Type', name: 'i32' }, 93 | name: { type: 'Param', name: 'b', index: 1 } 94 | }, 95 | { 96 | type: 'ParamDeclaration', 97 | result: { type: 'Type', name: 'i32' }, 98 | name: { type: 'Param', name: 'c', index: 2 } 99 | } 100 | ], 101 | result: { type: 'Type', name: 'i64' }, 102 | body: [ 103 | { 104 | type: 'ReturnStatement', 105 | argument: { 106 | type: 'Param', name: 'b', index: 1 107 | } 108 | } 109 | ] 110 | } 111 | ] 112 | }, { 113 | index: true 114 | }); 115 | }); 116 | 117 | it('should parse literal', function() { 118 | testBody(function() {/* 119 | i64 mul() { 120 | return i64.const(1); 121 | } 122 | */}, [ 123 | { 124 | type: 'ReturnStatement', 125 | argument: { 126 | type: 'Builtin', 127 | result: { type: 'Type', name: 'i64' }, 128 | method: 'const', 129 | arguments: [ { 130 | type: 'Literal', 131 | value: new BN(1) 132 | } ] 133 | } 134 | } 135 | ]); 136 | }); 137 | 138 | it('should parse 64bit literal', function() { 139 | testBody(function() {/* 140 | i64 mul() { 141 | return i64.const(0xdeadbeefABBADEAD); 142 | } 143 | */}, [ 144 | { 145 | type: 'ReturnStatement', 146 | argument: { 147 | type: 'Builtin', 148 | result: { type: 'Type', name: 'i64' }, 149 | method: 'const', 150 | arguments: [ { 151 | type: 'Literal', 152 | value: new BN('deadbeefabbadead', 16) 153 | } ] 154 | } 155 | } 156 | ]); 157 | }); 158 | 159 | it('should parse SequenceExpression', function() { 160 | testBody(function() {/* 161 | i64 mul() { 162 | return (i64.const(1), i64.const(2), i64.const(3)); 163 | } 164 | */}, [ 165 | { 166 | type: 'ReturnStatement', 167 | argument: { 168 | type: 'SequenceExpression', 169 | expressions: [ 170 | { 171 | type: 'Builtin', 172 | result: { type: 'Type', name: 'i64' }, 173 | method: 'const', 174 | arguments: [ { 175 | type: 'Literal', 176 | value: new BN(1) 177 | } ] 178 | }, 179 | { 180 | type: 'Builtin', 181 | result: { type: 'Type', name: 'i64' }, 182 | method: 'const', 183 | arguments: [ { 184 | type: 'Literal', 185 | value: new BN(2) 186 | } ] 187 | }, 188 | { 189 | type: 'Builtin', 190 | result: { type: 'Type', name: 'i64' }, 191 | method: 'const', 192 | arguments: [ { 193 | type: 'Literal', 194 | value: new BN(3) 195 | } ] 196 | } 197 | ] 198 | } 199 | } 200 | ]); 201 | }); 202 | 203 | it('should parse VariableDeclaration', function() { 204 | testBody(function() {/* 205 | void mul() { 206 | i64 a = i64.const(1); 207 | i64 b; 208 | } 209 | */}, [ 210 | { 211 | id: { 212 | name: 'a', 213 | type: 'Identifier' 214 | }, 215 | result: { 216 | type: 'Type', 217 | name: 'i64' 218 | }, 219 | init: { 220 | type: 'Builtin', 221 | result: { type: 'Type', name: 'i64' }, 222 | method: 'const', 223 | arguments: [ { 224 | type: 'Literal', 225 | value: new BN(1) 226 | } ] 227 | }, 228 | type: 'VariableDeclaration' 229 | }, 230 | { 231 | id: { 232 | name: 'b', 233 | type: 'Identifier' 234 | }, 235 | result: { 236 | type: 'Type', 237 | name: 'i64' 238 | }, 239 | init: null, 240 | type: 'VariableDeclaration' 241 | } 242 | ]); 243 | }); 244 | 245 | it('should parse AssignmentExpression', function() { 246 | testBody(function() {/* 247 | void mul() { 248 | a = b = c; 249 | } 250 | */}, [ 251 | { 252 | type: 'ExpressionStatement', 253 | expression: { 254 | type: 'AssignmentExpression', 255 | operator: '=', 256 | left: { type: 'Identifier', name: 'a' }, 257 | right: { 258 | type: 'AssignmentExpression', 259 | operator: '=', 260 | left: { type: 'Identifier', name: 'b' }, 261 | right: { type: 'Identifier', name: 'c' } 262 | } 263 | } 264 | } 265 | ]); 266 | }); 267 | 268 | it('should parse empty ReturnStatement', function() { 269 | testBody(function() {/* 270 | void mul() { 271 | return; 272 | } 273 | */}, [ { 274 | type: 'ReturnStatement', 275 | argument: null 276 | } ]); 277 | }); 278 | 279 | it('should parse IfStatement', function() { 280 | testBody(function() {/* 281 | i64 mul(i64 a) { 282 | if (a) { 283 | return a; 284 | } else 285 | return i64.const(1); 286 | } 287 | */}, [ { 288 | type: 'IfStatement', 289 | test: { type: 'Identifier', name: 'a' }, 290 | consequent: { 291 | type: 'BlockStatement', 292 | body: [ { 293 | type: 'ReturnStatement', 294 | argument: { type: 'Identifier', name: 'a' } 295 | } ] 296 | }, 297 | alternate: { 298 | type: 'ReturnStatement', 299 | argument: { 300 | type: 'Builtin', 301 | result: { type: 'Type', name: 'i64' }, 302 | method: 'const', 303 | arguments: [ { 304 | type: 'Literal', 305 | value: new BN(1) 306 | } ] 307 | } 308 | } 309 | } ]); 310 | }); 311 | 312 | it('should parse blockless IfStatement', function() { 313 | testBody(function() {/* 314 | i64 mul(i64 a) { 315 | if (a) 316 | return a; 317 | else 318 | return i64.const(1); 319 | } 320 | */}, [ { 321 | type: 'IfStatement', 322 | test: { type: 'Identifier', name: 'a' }, 323 | consequent: { 324 | type: 'ReturnStatement', 325 | argument: { type: 'Identifier', name: 'a' } 326 | }, 327 | alternate: { 328 | type: 'ReturnStatement', 329 | argument: { 330 | type: 'Builtin', 331 | result: { type: 'Type', name: 'i64' }, 332 | method: 'const', 333 | arguments: [ { 334 | type: 'Literal', 335 | value: new BN(1) 336 | } ] 337 | } 338 | } 339 | } ]); 340 | }); 341 | 342 | it('should parse forever loop', function() { 343 | testBody(function() {/* 344 | i64 mul() { 345 | i64 t = i64.const(1); 346 | forever { 347 | t = i64.add(t, t); 348 | } 349 | 350 | // Not going to happen 351 | return t; 352 | } 353 | */}, [ 354 | { 355 | type: 'VariableDeclaration', 356 | id: { 357 | name: 't', 358 | type: 'Identifier' 359 | }, 360 | result: { 361 | type: 'Type', 362 | name: 'i64' 363 | }, 364 | init: { 365 | type: 'Builtin', 366 | result: { type: 'Type', name: 'i64' }, 367 | method: 'const', 368 | arguments: [ { 369 | type: 'Literal', 370 | value: new BN(1) 371 | } ] 372 | } 373 | }, 374 | { 375 | type: 'ForeverStatement', 376 | body: { 377 | type: 'BlockStatement', 378 | body: [ 379 | { 380 | type: 'ExpressionStatement', 381 | expression: { 382 | type: 'AssignmentExpression', 383 | operator: '=', 384 | left: { type: 'Identifier', name: 't' }, 385 | right: { 386 | type: 'Builtin', 387 | result: { type: 'Type', name: 'i64' }, 388 | method: 'add', 389 | arguments: [ { 390 | type: 'Identifier', name: 't' 391 | }, { 392 | type: 'Identifier', name: 't' 393 | } ] 394 | } 395 | } 396 | } 397 | ] 398 | } 399 | }, 400 | { 401 | type: 'ReturnStatement', 402 | argument: { type: 'Identifier', name: 't' } 403 | } 404 | ]); 405 | }); 406 | 407 | it('should parse forever loop with break/continue', function() { 408 | testBody(function() {/* 409 | void mul() { 410 | forever { 411 | continue; 412 | break; 413 | } 414 | } 415 | */}, [ 416 | { 417 | type: 'ForeverStatement', 418 | body: { 419 | type: 'BlockStatement', 420 | body: [ 421 | { 422 | type: 'ContinueStatement' 423 | }, 424 | { 425 | type: 'BreakStatement' 426 | } 427 | ] 428 | } 429 | } 430 | ]); 431 | }); 432 | 433 | it('should parse do_while loop', function() { 434 | testBody(function() {/* 435 | void mul(i64 a) { 436 | do { 437 | } while (a); 438 | } 439 | */}, [ 440 | { 441 | type: 'DoWhileStatement', 442 | body: { 443 | type: 'BlockStatement', 444 | body: [ ] 445 | }, 446 | test: { type: 'Identifier', name: 'a' } 447 | } 448 | ]); 449 | }); 450 | 451 | it('should parser builtin statement', function() { 452 | testBody(function() {/* 453 | void mul(i64 a) { 454 | addr.page_size(); 455 | } 456 | */}, [ 457 | { 458 | type: 'ExpressionStatement', 459 | expression: { 460 | type: 'Builtin', 461 | result: { type: 'Type', name: 'addr' }, 462 | method: 'page_size', 463 | arguments: [] 464 | } 465 | } 466 | ]); 467 | }); 468 | 469 | it('should parse call statement', function() { 470 | testBody(function() {/* 471 | void mul(i64 a) { 472 | test(a); 473 | } 474 | void test(i64 a) { 475 | } 476 | */}, [ 477 | { 478 | type: 'ExpressionStatement', 479 | expression: { 480 | type: 'CallExpression', 481 | fn: { type: 'FunctionRef', name: 'test', index: 1 }, 482 | arguments: [ { type: 'Param', name: 'a', index: 0 } ] 483 | } 484 | } 485 | ], { 486 | index: true 487 | }); 488 | }); 489 | 490 | it('should parse export/import', function() { 491 | test(function() {/* 492 | import resize_memory, log from std 493 | 494 | void mul(i32 a, i32 b) { 495 | resize_memory(a); 496 | } 497 | 498 | void div(i32 a, i32 b) { 499 | std::assert(a); 500 | } 501 | 502 | export mul, div 503 | */}, { 504 | type: 'Program', 505 | body: [ 506 | { 507 | type: 'ImportStatement', 508 | names: [ 509 | { type: 'External', module: 'std', name: 'resize_memory' }, 510 | { type: 'External', module: 'std', name: 'log' } 511 | ], 512 | module: { type: 'Identifier', name: 'std' } 513 | }, 514 | { 515 | type: 'Function', 516 | result: { type: 'Type', name: 'void' }, 517 | name: { type: 'Identifier', name: 'mul' }, 518 | params: [ 519 | { 520 | type: 'ParamDeclaration', 521 | result: { type: 'Type', name: 'i32' }, 522 | name: { type: 'Identifier', name: 'a' } 523 | }, 524 | { 525 | type: 'ParamDeclaration', 526 | result: { type: 'Type', name: 'i32' }, 527 | name: { type: 'Identifier', name: 'b' } 528 | } 529 | ], 530 | body: [ { 531 | type: 'ExpressionStatement', 532 | expression: { 533 | type: 'CallExpression', 534 | fn: { type: 'External', module: 'std', name: 'resize_memory' }, 535 | arguments: [ { type: 'Identifier', name: 'a' } ] 536 | } 537 | } ], 538 | localCount: 0 539 | }, 540 | { 541 | type: 'Function', 542 | result: { type: 'Type', name: 'void' }, 543 | name: { type: 'Identifier', name: 'div' }, 544 | params: [ 545 | { 546 | type: 'ParamDeclaration', 547 | result: { type: 'Type', name: 'i32' }, 548 | name: { type: 'Identifier', name: 'a' } 549 | }, 550 | { 551 | type: 'ParamDeclaration', 552 | result: { type: 'Type', name: 'i32' }, 553 | name: { type: 'Identifier', name: 'b' } 554 | } 555 | ], 556 | body: [ { 557 | type: 'ExpressionStatement', 558 | expression: { 559 | type: 'CallExpression', 560 | fn: { type: 'External', module: 'std', name: 'assert' }, 561 | arguments: [ { type: 'Identifier', name: 'a' } ] 562 | } 563 | } ], 564 | localCount: 0 565 | }, 566 | { 567 | type: 'ExportStatement', 568 | names: [ 569 | { type: 'Identifier', name: 'mul' }, 570 | { type: 'Identifier', name: 'div' } 571 | ] 572 | } 573 | ] 574 | }); 575 | }); 576 | }); 577 | --------------------------------------------------------------------------------