├── LICENSE ├── __tests__ ├── apps.ts ├── compiler.ts ├── interpreter.ts ├── parser.ts ├── tokenizer.ts └── traverse.ts ├── docs ├── GitHub-Mark-64px.png ├── bundle.js ├── index.html └── index.ts ├── jest.config.js ├── package.json ├── src ├── compiler.ts ├── emitter.ts ├── encoding.ts ├── interpreter.ts ├── parser.ts ├── tokenizer.ts ├── transformer.ts ├── traverse.ts └── types │ ├── compiler.ts │ ├── emitter.ts │ ├── parser.ts │ ├── runtime.ts │ ├── tokenizer.ts │ ├── transformer.ts │ └── traverse.ts ├── tsconfig.json └── yarn.lock /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Mridul M Kumar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /__tests__/apps.ts: -------------------------------------------------------------------------------- 1 | let apps = [ 2 | { name: "an empty program", input: "", output: [] }, 3 | { name: "a print statement", input: "print 8", output: [8] }, 4 | { 5 | name: "multiple statements", 6 | input: "print 8 print 24", 7 | output: [8, 24] 8 | }, 9 | { 10 | name: "binary expressions", 11 | input: "print(2+ 4)", 12 | output: [6] 13 | }, 14 | { 15 | name: "nested binary expressions", 16 | input: "print ((6 - 4)+10)", 17 | output: [12] 18 | }, 19 | { 20 | name: "variable declaration", 21 | input: "var f = 22 print f", 22 | output: [22] 23 | }, 24 | { 25 | name: "variable declaration", 26 | input: "var foo = 21 print foo", 27 | output: [21] 28 | }, 29 | { 30 | name: "floating point variable declaration", 31 | input: "var f = 22.5 print f", 32 | output: [22.5] 33 | }, 34 | { 35 | name: "variable assignment", 36 | input: "var f = 22 f = (f+1) print f", 37 | output: [23] 38 | }, 39 | { 40 | name: "floating point variable assignment", 41 | input: "var f = 22.5 f = (f+1.5) print f", 42 | output: [24] 43 | }, 44 | { 45 | name: "handles scientific notation and various other numeric formats", 46 | input: "print 23e02 print -2 print .5", 47 | output: [2300, -2, 0.5] 48 | }, 49 | { 50 | name: "while statements", 51 | input: "var f = 0 while (f < 5) f = (f + 1) print f endwhile", 52 | output: [1, 2, 3, 4, 5] 53 | }, 54 | { 55 | name: "setpixel statements", 56 | input: "setpixel (1, 2, 3)", 57 | output: [] as any[], 58 | pixels: [[201, 3]] 59 | }, 60 | { 61 | name: "if statement", 62 | input: ` 63 | var f = 5 64 | if (f < 10) 65 | print 2 66 | endif 67 | if (f > 10) 68 | print 3 69 | endif 70 | `, 71 | output: [2] 72 | }, 73 | { 74 | name: "else statement operator", 75 | input: ` 76 | if (5 < 3) 77 | print 2 78 | else 79 | print 3 80 | endif 81 | `, 82 | output: [3] 83 | }, 84 | { 85 | name: "support a single main proc", 86 | input: ` 87 | proc main() 88 | print 22 89 | endproc`, 90 | output: [22] 91 | }, 92 | { 93 | name: "supports procedure invocation", 94 | input: ` 95 | proc foo() 96 | print 27 97 | endproc 98 | proc main() 99 | foo() 100 | endproc`, 101 | output: [27] 102 | }, 103 | { 104 | name: "supports procedure invocation with arguments", 105 | input: ` 106 | proc foo(f) 107 | print (f + 1) 108 | endproc 109 | proc main() 110 | foo(28) 111 | endproc`, 112 | output: [29] 113 | } 114 | ]; 115 | 116 | // https://github.com/facebook/jest/issues/7280 117 | test.skip("skip", () => {}); 118 | 119 | export default apps; 120 | -------------------------------------------------------------------------------- /__tests__/compiler.ts: -------------------------------------------------------------------------------- 1 | import { runtime } from "../src/compiler"; 2 | import apps from "./apps"; 3 | 4 | const executeCode = async (code: string, done: jest.DoneCallback) => { 5 | const output: any[] = []; 6 | const display = new Uint8Array(10000); 7 | const pixels: any[] = []; 8 | 9 | try { 10 | const tick = await runtime(code, { 11 | print: d => output.push(d), 12 | display 13 | }); 14 | tick(); 15 | 16 | // find any pixels that have been written to 17 | display.forEach((value, index) => { 18 | if (value !== 0) { 19 | pixels.push([index, value]); 20 | } 21 | }); 22 | 23 | done(); 24 | return { output, pixels }; 25 | } catch (e) { 26 | console.error(e); 27 | done.fail(); 28 | } 29 | }; 30 | 31 | describe("compiler", () => { 32 | apps.forEach(app => { 33 | test(app.name, async done => { 34 | const result = await executeCode(app.input, done); 35 | expect(result.output).toEqual(app.output); 36 | if (app.pixels || result.pixels.length) { 37 | expect(result.pixels).toEqual(app.pixels); 38 | } 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /__tests__/interpreter.ts: -------------------------------------------------------------------------------- 1 | import { runtime } from "../src/interpreter"; 2 | import apps from "./apps"; 3 | 4 | // execute the app, recording print statements and pixel writes 5 | const executeCode = async (code: string) => { 6 | const output: any[] = []; 7 | const pixels: any[] = []; 8 | const display = new Uint8Array(10000); 9 | 10 | const tick = await runtime(code, { 11 | print: d => output.push(d), 12 | display 13 | }); 14 | tick(); 15 | 16 | // find any pixels that have been written to 17 | display.forEach((value, index) => { 18 | if (value !== 0) { 19 | pixels.push([index, value]); 20 | } 21 | }); 22 | 23 | return { 24 | output, pixels 25 | }; 26 | }; 27 | 28 | describe("interpreter", () => { 29 | apps.forEach(app => { 30 | test(app.name, async () => { 31 | const result = await executeCode(app.input); 32 | expect(result.output).toEqual(app.output); 33 | if (app.pixels || result.pixels.length) { 34 | expect(result.pixels).toEqual(app.pixels); 35 | } 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /__tests__/parser.ts: -------------------------------------------------------------------------------- 1 | const { parse } = require("../src/parser"); 2 | 3 | test("parses single statements", () => { 4 | const tokens = [ 5 | { type: "keyword", value: "print" }, 6 | { type: "number", value: "22" } 7 | ]; 8 | 9 | const ast = parse(tokens); 10 | expect(ast.length).toEqual(1); 11 | }); 12 | 13 | test("parses multiple homogenous statements", () => { 14 | const tokens = [ 15 | // print 22 16 | { type: "keyword", value: "print" }, 17 | { type: "number", value: "22" }, 18 | // print 22 19 | { type: "keyword", value: "print" }, 20 | { type: "number", value: "22" } 21 | ]; 22 | 23 | const ast = parse(tokens); 24 | expect(ast.length).toEqual(2); 25 | }); 26 | 27 | test("parses print statement with unary expression", () => { 28 | const tokens = [ 29 | { 30 | type: "keyword", 31 | value: "print" 32 | }, 33 | { 34 | type: "number", 35 | value: "22" 36 | } 37 | ]; 38 | 39 | const ast = parse(tokens); 40 | const node = ast[0]; 41 | 42 | expect(node).toEqual({ 43 | type: "printStatement", 44 | expression: { type: "numberLiteral", value: 22 } 45 | }); 46 | }); 47 | 48 | test("parses proc with single arg", () => { 49 | const tokens = [ 50 | { type: "keyword", value: "proc" }, 51 | { type: "identifier", value: "fish" }, 52 | { type: "parens", value: "(" }, 53 | { type: "identifier", value: "foo" }, 54 | { type: "parens", value: ")" }, 55 | { type: "keyword", value: "print" }, 56 | { type: "number", value: "22" }, 57 | { type: "keyword", value: "endproc" } 58 | ]; 59 | 60 | const ast = parse(tokens); 61 | expect(ast.length).toEqual(1); 62 | 63 | const node = ast[0]; 64 | expect(node).toEqual({ 65 | type: "procStatement", 66 | name: "fish", 67 | args: [ 68 | { 69 | type: "identifier", 70 | value: "foo" 71 | } 72 | ], 73 | statements: [ 74 | { 75 | type: "printStatement", 76 | expression: { type: "numberLiteral", value: 22 } 77 | } 78 | ] 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /__tests__/tokenizer.ts: -------------------------------------------------------------------------------- 1 | const { tokenize } = require("../src/tokenizer"); 2 | 3 | describe("tokenize", () => { 4 | test("copes with a single token", () => { 5 | const input = " print"; 6 | const tokens = tokenize(input); 7 | expect(tokens.length).toBe(1); 8 | expect(tokens[0].type).toBe("keyword"); 9 | }); 10 | 11 | test("copes with multiple tokens", () => { 12 | const input = " printprint"; 13 | const tokens = tokenize(input); 14 | expect(tokens.length).toBe(2); 15 | }); 16 | 17 | test("consumes whitespace", () => { 18 | const input = " print print"; 19 | const tokens = tokenize(input); 20 | expect(tokens.length).toBe(2); 21 | }); 22 | 23 | test("barfs on unrecognized token", () => { 24 | expect(() => { 25 | const input = " print $ print"; 26 | tokenize(input); 27 | }).toThrowError("Unexpected token $"); 28 | }); 29 | 30 | test("copes with multiple mixed tokens", () => { 31 | const input = " print 2"; 32 | const tokens = tokenize(input); 33 | expect(tokens.length).toBe(2); 34 | expect(tokens[0].type).toBe("keyword"); 35 | expect(tokens[1].type).toBe("number"); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /__tests__/traverse.ts: -------------------------------------------------------------------------------- 1 | import traverse from "../src/traverse"; 2 | 3 | test("traverses post order - visiting leaves first", () => { 4 | const ast = { 5 | type: "foo", 6 | bar: { 7 | type: "baz" 8 | } 9 | }; 10 | 11 | const visited: string[] = []; 12 | const visitor: Visitor = node => visited.push(node.type); 13 | traverse(ast, visitor); 14 | 15 | expect(visited).toEqual(["baz", "foo"]); 16 | }); 17 | 18 | test("traverses array properties", () => { 19 | const ast = { 20 | type: "foo", 21 | bar: [ 22 | { 23 | type: "baz" 24 | }, 25 | { 26 | type: "bar" 27 | } 28 | ] 29 | }; 30 | 31 | const visited: string[] = []; 32 | const visitor: Visitor = node => visited.push(node.type); 33 | traverse(ast, visitor); 34 | 35 | expect(visited).toEqual(["baz", "bar", "foo"]); 36 | }); 37 | 38 | test("traverses array root", () => { 39 | const ast = [ 40 | { 41 | type: "baz" 42 | }, 43 | { 44 | type: "bar" 45 | } 46 | ]; 47 | 48 | const visited: string[] = []; 49 | const visitor: Visitor = node => visited.push(node.type); 50 | traverse(ast, visitor); 51 | 52 | expect(visited).toEqual(["baz", "bar"]); 53 | }); 54 | -------------------------------------------------------------------------------- /docs/GitHub-Mark-64px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mridul0703/Web-Assembly-Compiler/4506bb69b596f7f5c4ee87daee6f791422b53428/docs/GitHub-Mark-64px.png -------------------------------------------------------------------------------- /docs/bundle.js: -------------------------------------------------------------------------------- 1 | (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i { 19 | const scaled = ctx.createImageData(imageData.width * scale, imageData.height * scale); 20 | const subLine = ctx.createImageData(scale, 1).data; 21 | for (let row = 0; row < imageData.height; row++) { 22 | for (let col = 0; col < imageData.width; col++) { 23 | const sourcePixel = imageData.data.subarray((row * imageData.width + col) * 4, (row * imageData.width + col) * 4 + 4); 24 | for (let x = 0; x < scale; x++) 25 | subLine.set(sourcePixel, x * 4); 26 | for (let y = 0; y < scale; y++) { 27 | const destRow = row * scale + y; 28 | const destCol = col * scale; 29 | scaled.data.set(subLine, (destRow * scaled.width + destCol) * 4); 30 | } 31 | } 32 | } 33 | return scaled; 34 | }; 35 | CodeMirror.defineSimpleMode("simplemode", { 36 | start: [ 37 | { 38 | regex: new RegExp(`(${keywords.join("|")})`), 39 | token: "keyword" 40 | }, 41 | { 42 | regex: /0x[a-f\d]+|[-+]?(?:\.\d+|\d+\.?\d*)(?:e[-+]?\d+)?/i, 43 | token: "number" 44 | }, 45 | { regex: /[-+\/*=<>!]+/, token: "operator" }, 46 | { regex: /[a-z$][\w$]*/, token: "variable" } 47 | ] 48 | }); 49 | const editor = CodeMirror.fromTextArea(codeArea, { 50 | mode: "simplemode", 51 | theme: "abcdef", 52 | lineNumbers: true 53 | }); 54 | $("#shareModal").on("show.bs.modal", () => { 55 | const baseUrl = window.location.href.split("#")[0]; 56 | const code = encodeURIComponent(btoa(editor.getValue())); 57 | shareUrl.value = `${baseUrl}#${code}`; 58 | }); 59 | copyUrl.addEventListener("click", () => navigator.clipboard.writeText(shareUrl.value)); 60 | const sleep = async (ms) => await new Promise(resolve => setTimeout(resolve, ms)); 61 | let marker; 62 | const logMessage = (message) => (outputArea.value = outputArea.value + message + "\n"); 63 | const markError = (token) => { 64 | marker = editor.markText({ line: token.line, ch: token.char }, { line: token.line, ch: token.char + token.value.length }, { className: "error" }); 65 | }; 66 | const updateCanvas = (display) => { 67 | const context = canvas.getContext("2d"); 68 | const imgData = context.createImageData(100, 100); 69 | for (let i = 0; i < 100 * 100; i++) { 70 | imgData.data[i * 4] = display[i]; 71 | imgData.data[i * 4 + 1] = display[i]; 72 | imgData.data[i * 4 + 2] = display[i]; 73 | imgData.data[i * 4 + 3] = 255; 74 | } 75 | const data = scaleImageData(imgData, 3, context); 76 | context.putImageData(data, 0, 0); 77 | }; 78 | const run = async (runtime) => { 79 | if (marker) { 80 | marker.clear(); 81 | } 82 | await sleep(10); 83 | let tickFunction; 84 | try { 85 | const display = new Uint8Array(10000); 86 | tickFunction = await runtime(editor.getValue(), { 87 | print: logMessage, 88 | display 89 | }); 90 | outputArea.value = ""; 91 | logMessage(`Executing ... `); 92 | tickFunction(); 93 | updateCanvas(display); 94 | interpretButton.classList.remove("active"); 95 | compileButton.classList.remove("active"); 96 | } 97 | catch (error) { 98 | logMessage(error.message); 99 | markError(error.token); 100 | } 101 | }; 102 | interpretButton.addEventListener("click", async () => { 103 | interpretButton.classList.add("active"); 104 | compileButton.classList.remove("active"); 105 | await run(interpreterRuntime); 106 | }); 107 | compileButton.addEventListener("click", async () => { 108 | compileButton.classList.add("active"); 109 | interpretButton.classList.remove("active"); 110 | await run(compilerRuntime); 111 | }); 112 | 113 | },{"../src/compiler":5,"../src/interpreter":8,"../src/tokenizer":10}],2:[function(require,module,exports){ 114 | 'use strict' 115 | 116 | exports.byteLength = byteLength 117 | exports.toByteArray = toByteArray 118 | exports.fromByteArray = fromByteArray 119 | 120 | var lookup = [] 121 | var revLookup = [] 122 | var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array 123 | 124 | var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' 125 | for (var i = 0, len = code.length; i < len; ++i) { 126 | lookup[i] = code[i] 127 | revLookup[code.charCodeAt(i)] = i 128 | } 129 | 130 | // Support decoding URL-safe base64 strings, as Node.js does. 131 | // See: https://en.wikipedia.org/wiki/Base64#URL_applications 132 | revLookup['-'.charCodeAt(0)] = 62 133 | revLookup['_'.charCodeAt(0)] = 63 134 | 135 | function getLens (b64) { 136 | var len = b64.length 137 | 138 | if (len % 4 > 0) { 139 | throw new Error('Invalid string. Length must be a multiple of 4') 140 | } 141 | 142 | // Trim off extra bytes after placeholder bytes are found 143 | // See: https://github.com/beatgammit/base64-js/issues/42 144 | var validLen = b64.indexOf('=') 145 | if (validLen === -1) validLen = len 146 | 147 | var placeHoldersLen = validLen === len 148 | ? 0 149 | : 4 - (validLen % 4) 150 | 151 | return [validLen, placeHoldersLen] 152 | } 153 | 154 | // base64 is 4/3 + up to two characters of the original data 155 | function byteLength (b64) { 156 | var lens = getLens(b64) 157 | var validLen = lens[0] 158 | var placeHoldersLen = lens[1] 159 | return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen 160 | } 161 | 162 | function _byteLength (b64, validLen, placeHoldersLen) { 163 | return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen 164 | } 165 | 166 | function toByteArray (b64) { 167 | var tmp 168 | var lens = getLens(b64) 169 | var validLen = lens[0] 170 | var placeHoldersLen = lens[1] 171 | 172 | var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen)) 173 | 174 | var curByte = 0 175 | 176 | // if there are placeholders, only get up to the last complete 4 chars 177 | var len = placeHoldersLen > 0 178 | ? validLen - 4 179 | : validLen 180 | 181 | for (var i = 0; i < len; i += 4) { 182 | tmp = 183 | (revLookup[b64.charCodeAt(i)] << 18) | 184 | (revLookup[b64.charCodeAt(i + 1)] << 12) | 185 | (revLookup[b64.charCodeAt(i + 2)] << 6) | 186 | revLookup[b64.charCodeAt(i + 3)] 187 | arr[curByte++] = (tmp >> 16) & 0xFF 188 | arr[curByte++] = (tmp >> 8) & 0xFF 189 | arr[curByte++] = tmp & 0xFF 190 | } 191 | 192 | if (placeHoldersLen === 2) { 193 | tmp = 194 | (revLookup[b64.charCodeAt(i)] << 2) | 195 | (revLookup[b64.charCodeAt(i + 1)] >> 4) 196 | arr[curByte++] = tmp & 0xFF 197 | } 198 | 199 | if (placeHoldersLen === 1) { 200 | tmp = 201 | (revLookup[b64.charCodeAt(i)] << 10) | 202 | (revLookup[b64.charCodeAt(i + 1)] << 4) | 203 | (revLookup[b64.charCodeAt(i + 2)] >> 2) 204 | arr[curByte++] = (tmp >> 8) & 0xFF 205 | arr[curByte++] = tmp & 0xFF 206 | } 207 | 208 | return arr 209 | } 210 | 211 | function tripletToBase64 (num) { 212 | return lookup[num >> 18 & 0x3F] + 213 | lookup[num >> 12 & 0x3F] + 214 | lookup[num >> 6 & 0x3F] + 215 | lookup[num & 0x3F] 216 | } 217 | 218 | function encodeChunk (uint8, start, end) { 219 | var tmp 220 | var output = [] 221 | for (var i = start; i < end; i += 3) { 222 | tmp = 223 | ((uint8[i] << 16) & 0xFF0000) + 224 | ((uint8[i + 1] << 8) & 0xFF00) + 225 | (uint8[i + 2] & 0xFF) 226 | output.push(tripletToBase64(tmp)) 227 | } 228 | return output.join('') 229 | } 230 | 231 | function fromByteArray (uint8) { 232 | var tmp 233 | var len = uint8.length 234 | var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes 235 | var parts = [] 236 | var maxChunkLength = 16383 // must be multiple of 3 237 | 238 | // go through the array every three bytes, we'll deal with trailing stuff later 239 | for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) { 240 | parts.push(encodeChunk( 241 | uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength) 242 | )) 243 | } 244 | 245 | // pad the end with zeros, but make sure to not forget the extra bytes 246 | if (extraBytes === 1) { 247 | tmp = uint8[len - 1] 248 | parts.push( 249 | lookup[tmp >> 2] + 250 | lookup[(tmp << 4) & 0x3F] + 251 | '==' 252 | ) 253 | } else if (extraBytes === 2) { 254 | tmp = (uint8[len - 2] << 8) + uint8[len - 1] 255 | parts.push( 256 | lookup[tmp >> 10] + 257 | lookup[(tmp >> 4) & 0x3F] + 258 | lookup[(tmp << 2) & 0x3F] + 259 | '=' 260 | ) 261 | } 262 | 263 | return parts.join('') 264 | } 265 | 266 | },{}],3:[function(require,module,exports){ 267 | (function (Buffer){ 268 | /*! 269 | * The buffer module from node.js, for the browser. 270 | * 271 | * @author Feross Aboukhadijeh 272 | * @license MIT 273 | */ 274 | /* eslint-disable no-proto */ 275 | 276 | 'use strict' 277 | 278 | var base64 = require('base64-js') 279 | var ieee754 = require('ieee754') 280 | 281 | exports.Buffer = Buffer 282 | exports.SlowBuffer = SlowBuffer 283 | exports.INSPECT_MAX_BYTES = 50 284 | 285 | var K_MAX_LENGTH = 0x7fffffff 286 | exports.kMaxLength = K_MAX_LENGTH 287 | 288 | /** 289 | * If `Buffer.TYPED_ARRAY_SUPPORT`: 290 | * === true Use Uint8Array implementation (fastest) 291 | * === false Print warning and recommend using `buffer` v4.x which has an Object 292 | * implementation (most compatible, even IE6) 293 | * 294 | * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+, 295 | * Opera 11.6+, iOS 4.2+. 296 | * 297 | * We report that the browser does not support typed arrays if the are not subclassable 298 | * using __proto__. Firefox 4-29 lacks support for adding new properties to `Uint8Array` 299 | * (See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438). IE 10 lacks support 300 | * for __proto__ and has a buggy typed array implementation. 301 | */ 302 | Buffer.TYPED_ARRAY_SUPPORT = typedArraySupport() 303 | 304 | if (!Buffer.TYPED_ARRAY_SUPPORT && typeof console !== 'undefined' && 305 | typeof console.error === 'function') { 306 | console.error( 307 | 'This browser lacks typed array (Uint8Array) support which is required by ' + 308 | '`buffer` v5.x. Use `buffer` v4.x if you require old browser support.' 309 | ) 310 | } 311 | 312 | function typedArraySupport () { 313 | // Can typed array instances can be augmented? 314 | try { 315 | var arr = new Uint8Array(1) 316 | arr.__proto__ = { __proto__: Uint8Array.prototype, foo: function () { return 42 } } 317 | return arr.foo() === 42 318 | } catch (e) { 319 | return false 320 | } 321 | } 322 | 323 | Object.defineProperty(Buffer.prototype, 'parent', { 324 | enumerable: true, 325 | get: function () { 326 | if (!Buffer.isBuffer(this)) return undefined 327 | return this.buffer 328 | } 329 | }) 330 | 331 | Object.defineProperty(Buffer.prototype, 'offset', { 332 | enumerable: true, 333 | get: function () { 334 | if (!Buffer.isBuffer(this)) return undefined 335 | return this.byteOffset 336 | } 337 | }) 338 | 339 | function createBuffer (length) { 340 | if (length > K_MAX_LENGTH) { 341 | throw new RangeError('The value "' + length + '" is invalid for option "size"') 342 | } 343 | // Return an augmented `Uint8Array` instance 344 | var buf = new Uint8Array(length) 345 | buf.__proto__ = Buffer.prototype 346 | return buf 347 | } 348 | 349 | /** 350 | * The Buffer constructor returns instances of `Uint8Array` that have their 351 | * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of 352 | * `Uint8Array`, so the returned instances will have all the node `Buffer` methods 353 | * and the `Uint8Array` methods. Square bracket notation works as expected -- it 354 | * returns a single octet. 355 | * 356 | * The `Uint8Array` prototype remains unmodified. 357 | */ 358 | 359 | function Buffer (arg, encodingOrOffset, length) { 360 | // Common case. 361 | if (typeof arg === 'number') { 362 | if (typeof encodingOrOffset === 'string') { 363 | throw new TypeError( 364 | 'The "string" argument must be of type string. Received type number' 365 | ) 366 | } 367 | return allocUnsafe(arg) 368 | } 369 | return from(arg, encodingOrOffset, length) 370 | } 371 | 372 | // Fix subarray() in ES2016. See: https://github.com/feross/buffer/pull/97 373 | if (typeof Symbol !== 'undefined' && Symbol.species != null && 374 | Buffer[Symbol.species] === Buffer) { 375 | Object.defineProperty(Buffer, Symbol.species, { 376 | value: null, 377 | configurable: true, 378 | enumerable: false, 379 | writable: false 380 | }) 381 | } 382 | 383 | Buffer.poolSize = 8192 // not used by this implementation 384 | 385 | function from (value, encodingOrOffset, length) { 386 | if (typeof value === 'string') { 387 | return fromString(value, encodingOrOffset) 388 | } 389 | 390 | if (ArrayBuffer.isView(value)) { 391 | return fromArrayLike(value) 392 | } 393 | 394 | if (value == null) { 395 | throw TypeError( 396 | 'The first argument must be one of type string, Buffer, ArrayBuffer, Array, ' + 397 | 'or Array-like Object. Received type ' + (typeof value) 398 | ) 399 | } 400 | 401 | if (isInstance(value, ArrayBuffer) || 402 | (value && isInstance(value.buffer, ArrayBuffer))) { 403 | return fromArrayBuffer(value, encodingOrOffset, length) 404 | } 405 | 406 | if (typeof value === 'number') { 407 | throw new TypeError( 408 | 'The "value" argument must not be of type number. Received type number' 409 | ) 410 | } 411 | 412 | var valueOf = value.valueOf && value.valueOf() 413 | if (valueOf != null && valueOf !== value) { 414 | return Buffer.from(valueOf, encodingOrOffset, length) 415 | } 416 | 417 | var b = fromObject(value) 418 | if (b) return b 419 | 420 | if (typeof Symbol !== 'undefined' && Symbol.toPrimitive != null && 421 | typeof value[Symbol.toPrimitive] === 'function') { 422 | return Buffer.from( 423 | value[Symbol.toPrimitive]('string'), encodingOrOffset, length 424 | ) 425 | } 426 | 427 | throw new TypeError( 428 | 'The first argument must be one of type string, Buffer, ArrayBuffer, Array, ' + 429 | 'or Array-like Object. Received type ' + (typeof value) 430 | ) 431 | } 432 | 433 | /** 434 | * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError 435 | * if value is a number. 436 | * Buffer.from(str[, encoding]) 437 | * Buffer.from(array) 438 | * Buffer.from(buffer) 439 | * Buffer.from(arrayBuffer[, byteOffset[, length]]) 440 | **/ 441 | Buffer.from = function (value, encodingOrOffset, length) { 442 | return from(value, encodingOrOffset, length) 443 | } 444 | 445 | // Note: Change prototype *after* Buffer.from is defined to workaround Chrome bug: 446 | // https://github.com/feross/buffer/pull/148 447 | Buffer.prototype.__proto__ = Uint8Array.prototype 448 | Buffer.__proto__ = Uint8Array 449 | 450 | function assertSize (size) { 451 | if (typeof size !== 'number') { 452 | throw new TypeError('"size" argument must be of type number') 453 | } else if (size < 0) { 454 | throw new RangeError('The value "' + size + '" is invalid for option "size"') 455 | } 456 | } 457 | 458 | function alloc (size, fill, encoding) { 459 | assertSize(size) 460 | if (size <= 0) { 461 | return createBuffer(size) 462 | } 463 | if (fill !== undefined) { 464 | // Only pay attention to encoding if it's a string. This 465 | // prevents accidentally sending in a number that would 466 | // be interpretted as a start offset. 467 | return typeof encoding === 'string' 468 | ? createBuffer(size).fill(fill, encoding) 469 | : createBuffer(size).fill(fill) 470 | } 471 | return createBuffer(size) 472 | } 473 | 474 | /** 475 | * Creates a new filled Buffer instance. 476 | * alloc(size[, fill[, encoding]]) 477 | **/ 478 | Buffer.alloc = function (size, fill, encoding) { 479 | return alloc(size, fill, encoding) 480 | } 481 | 482 | function allocUnsafe (size) { 483 | assertSize(size) 484 | return createBuffer(size < 0 ? 0 : checked(size) | 0) 485 | } 486 | 487 | /** 488 | * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance. 489 | * */ 490 | Buffer.allocUnsafe = function (size) { 491 | return allocUnsafe(size) 492 | } 493 | /** 494 | * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance. 495 | */ 496 | Buffer.allocUnsafeSlow = function (size) { 497 | return allocUnsafe(size) 498 | } 499 | 500 | function fromString (string, encoding) { 501 | if (typeof encoding !== 'string' || encoding === '') { 502 | encoding = 'utf8' 503 | } 504 | 505 | if (!Buffer.isEncoding(encoding)) { 506 | throw new TypeError('Unknown encoding: ' + encoding) 507 | } 508 | 509 | var length = byteLength(string, encoding) | 0 510 | var buf = createBuffer(length) 511 | 512 | var actual = buf.write(string, encoding) 513 | 514 | if (actual !== length) { 515 | // Writing a hex string, for example, that contains invalid characters will 516 | // cause everything after the first invalid character to be ignored. (e.g. 517 | // 'abxxcd' will be treated as 'ab') 518 | buf = buf.slice(0, actual) 519 | } 520 | 521 | return buf 522 | } 523 | 524 | function fromArrayLike (array) { 525 | var length = array.length < 0 ? 0 : checked(array.length) | 0 526 | var buf = createBuffer(length) 527 | for (var i = 0; i < length; i += 1) { 528 | buf[i] = array[i] & 255 529 | } 530 | return buf 531 | } 532 | 533 | function fromArrayBuffer (array, byteOffset, length) { 534 | if (byteOffset < 0 || array.byteLength < byteOffset) { 535 | throw new RangeError('"offset" is outside of buffer bounds') 536 | } 537 | 538 | if (array.byteLength < byteOffset + (length || 0)) { 539 | throw new RangeError('"length" is outside of buffer bounds') 540 | } 541 | 542 | var buf 543 | if (byteOffset === undefined && length === undefined) { 544 | buf = new Uint8Array(array) 545 | } else if (length === undefined) { 546 | buf = new Uint8Array(array, byteOffset) 547 | } else { 548 | buf = new Uint8Array(array, byteOffset, length) 549 | } 550 | 551 | // Return an augmented `Uint8Array` instance 552 | buf.__proto__ = Buffer.prototype 553 | return buf 554 | } 555 | 556 | function fromObject (obj) { 557 | if (Buffer.isBuffer(obj)) { 558 | var len = checked(obj.length) | 0 559 | var buf = createBuffer(len) 560 | 561 | if (buf.length === 0) { 562 | return buf 563 | } 564 | 565 | obj.copy(buf, 0, 0, len) 566 | return buf 567 | } 568 | 569 | if (obj.length !== undefined) { 570 | if (typeof obj.length !== 'number' || numberIsNaN(obj.length)) { 571 | return createBuffer(0) 572 | } 573 | return fromArrayLike(obj) 574 | } 575 | 576 | if (obj.type === 'Buffer' && Array.isArray(obj.data)) { 577 | return fromArrayLike(obj.data) 578 | } 579 | } 580 | 581 | function checked (length) { 582 | // Note: cannot use `length < K_MAX_LENGTH` here because that fails when 583 | // length is NaN (which is otherwise coerced to zero.) 584 | if (length >= K_MAX_LENGTH) { 585 | throw new RangeError('Attempt to allocate Buffer larger than maximum ' + 586 | 'size: 0x' + K_MAX_LENGTH.toString(16) + ' bytes') 587 | } 588 | return length | 0 589 | } 590 | 591 | function SlowBuffer (length) { 592 | if (+length != length) { // eslint-disable-line eqeqeq 593 | length = 0 594 | } 595 | return Buffer.alloc(+length) 596 | } 597 | 598 | Buffer.isBuffer = function isBuffer (b) { 599 | return b != null && b._isBuffer === true && 600 | b !== Buffer.prototype // so Buffer.isBuffer(Buffer.prototype) will be false 601 | } 602 | 603 | Buffer.compare = function compare (a, b) { 604 | if (isInstance(a, Uint8Array)) a = Buffer.from(a, a.offset, a.byteLength) 605 | if (isInstance(b, Uint8Array)) b = Buffer.from(b, b.offset, b.byteLength) 606 | if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) { 607 | throw new TypeError( 608 | 'The "buf1", "buf2" arguments must be one of type Buffer or Uint8Array' 609 | ) 610 | } 611 | 612 | if (a === b) return 0 613 | 614 | var x = a.length 615 | var y = b.length 616 | 617 | for (var i = 0, len = Math.min(x, y); i < len; ++i) { 618 | if (a[i] !== b[i]) { 619 | x = a[i] 620 | y = b[i] 621 | break 622 | } 623 | } 624 | 625 | if (x < y) return -1 626 | if (y < x) return 1 627 | return 0 628 | } 629 | 630 | Buffer.isEncoding = function isEncoding (encoding) { 631 | switch (String(encoding).toLowerCase()) { 632 | case 'hex': 633 | case 'utf8': 634 | case 'utf-8': 635 | case 'ascii': 636 | case 'latin1': 637 | case 'binary': 638 | case 'base64': 639 | case 'ucs2': 640 | case 'ucs-2': 641 | case 'utf16le': 642 | case 'utf-16le': 643 | return true 644 | default: 645 | return false 646 | } 647 | } 648 | 649 | Buffer.concat = function concat (list, length) { 650 | if (!Array.isArray(list)) { 651 | throw new TypeError('"list" argument must be an Array of Buffers') 652 | } 653 | 654 | if (list.length === 0) { 655 | return Buffer.alloc(0) 656 | } 657 | 658 | var i 659 | if (length === undefined) { 660 | length = 0 661 | for (i = 0; i < list.length; ++i) { 662 | length += list[i].length 663 | } 664 | } 665 | 666 | var buffer = Buffer.allocUnsafe(length) 667 | var pos = 0 668 | for (i = 0; i < list.length; ++i) { 669 | var buf = list[i] 670 | if (isInstance(buf, Uint8Array)) { 671 | buf = Buffer.from(buf) 672 | } 673 | if (!Buffer.isBuffer(buf)) { 674 | throw new TypeError('"list" argument must be an Array of Buffers') 675 | } 676 | buf.copy(buffer, pos) 677 | pos += buf.length 678 | } 679 | return buffer 680 | } 681 | 682 | function byteLength (string, encoding) { 683 | if (Buffer.isBuffer(string)) { 684 | return string.length 685 | } 686 | if (ArrayBuffer.isView(string) || isInstance(string, ArrayBuffer)) { 687 | return string.byteLength 688 | } 689 | if (typeof string !== 'string') { 690 | throw new TypeError( 691 | 'The "string" argument must be one of type string, Buffer, or ArrayBuffer. ' + 692 | 'Received type ' + typeof string 693 | ) 694 | } 695 | 696 | var len = string.length 697 | var mustMatch = (arguments.length > 2 && arguments[2] === true) 698 | if (!mustMatch && len === 0) return 0 699 | 700 | // Use a for loop to avoid recursion 701 | var loweredCase = false 702 | for (;;) { 703 | switch (encoding) { 704 | case 'ascii': 705 | case 'latin1': 706 | case 'binary': 707 | return len 708 | case 'utf8': 709 | case 'utf-8': 710 | return utf8ToBytes(string).length 711 | case 'ucs2': 712 | case 'ucs-2': 713 | case 'utf16le': 714 | case 'utf-16le': 715 | return len * 2 716 | case 'hex': 717 | return len >>> 1 718 | case 'base64': 719 | return base64ToBytes(string).length 720 | default: 721 | if (loweredCase) { 722 | return mustMatch ? -1 : utf8ToBytes(string).length // assume utf8 723 | } 724 | encoding = ('' + encoding).toLowerCase() 725 | loweredCase = true 726 | } 727 | } 728 | } 729 | Buffer.byteLength = byteLength 730 | 731 | function slowToString (encoding, start, end) { 732 | var loweredCase = false 733 | 734 | // No need to verify that "this.length <= MAX_UINT32" since it's a read-only 735 | // property of a typed array. 736 | 737 | // This behaves neither like String nor Uint8Array in that we set start/end 738 | // to their upper/lower bounds if the value passed is out of range. 739 | // undefined is handled specially as per ECMA-262 6th Edition, 740 | // Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization. 741 | if (start === undefined || start < 0) { 742 | start = 0 743 | } 744 | // Return early if start > this.length. Done here to prevent potential uint32 745 | // coercion fail below. 746 | if (start > this.length) { 747 | return '' 748 | } 749 | 750 | if (end === undefined || end > this.length) { 751 | end = this.length 752 | } 753 | 754 | if (end <= 0) { 755 | return '' 756 | } 757 | 758 | // Force coersion to uint32. This will also coerce falsey/NaN values to 0. 759 | end >>>= 0 760 | start >>>= 0 761 | 762 | if (end <= start) { 763 | return '' 764 | } 765 | 766 | if (!encoding) encoding = 'utf8' 767 | 768 | while (true) { 769 | switch (encoding) { 770 | case 'hex': 771 | return hexSlice(this, start, end) 772 | 773 | case 'utf8': 774 | case 'utf-8': 775 | return utf8Slice(this, start, end) 776 | 777 | case 'ascii': 778 | return asciiSlice(this, start, end) 779 | 780 | case 'latin1': 781 | case 'binary': 782 | return latin1Slice(this, start, end) 783 | 784 | case 'base64': 785 | return base64Slice(this, start, end) 786 | 787 | case 'ucs2': 788 | case 'ucs-2': 789 | case 'utf16le': 790 | case 'utf-16le': 791 | return utf16leSlice(this, start, end) 792 | 793 | default: 794 | if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) 795 | encoding = (encoding + '').toLowerCase() 796 | loweredCase = true 797 | } 798 | } 799 | } 800 | 801 | // This property is used by `Buffer.isBuffer` (and the `is-buffer` npm package) 802 | // to detect a Buffer instance. It's not possible to use `instanceof Buffer` 803 | // reliably in a browserify context because there could be multiple different 804 | // copies of the 'buffer' package in use. This method works even for Buffer 805 | // instances that were created from another copy of the `buffer` package. 806 | // See: https://github.com/feross/buffer/issues/154 807 | Buffer.prototype._isBuffer = true 808 | 809 | function swap (b, n, m) { 810 | var i = b[n] 811 | b[n] = b[m] 812 | b[m] = i 813 | } 814 | 815 | Buffer.prototype.swap16 = function swap16 () { 816 | var len = this.length 817 | if (len % 2 !== 0) { 818 | throw new RangeError('Buffer size must be a multiple of 16-bits') 819 | } 820 | for (var i = 0; i < len; i += 2) { 821 | swap(this, i, i + 1) 822 | } 823 | return this 824 | } 825 | 826 | Buffer.prototype.swap32 = function swap32 () { 827 | var len = this.length 828 | if (len % 4 !== 0) { 829 | throw new RangeError('Buffer size must be a multiple of 32-bits') 830 | } 831 | for (var i = 0; i < len; i += 4) { 832 | swap(this, i, i + 3) 833 | swap(this, i + 1, i + 2) 834 | } 835 | return this 836 | } 837 | 838 | Buffer.prototype.swap64 = function swap64 () { 839 | var len = this.length 840 | if (len % 8 !== 0) { 841 | throw new RangeError('Buffer size must be a multiple of 64-bits') 842 | } 843 | for (var i = 0; i < len; i += 8) { 844 | swap(this, i, i + 7) 845 | swap(this, i + 1, i + 6) 846 | swap(this, i + 2, i + 5) 847 | swap(this, i + 3, i + 4) 848 | } 849 | return this 850 | } 851 | 852 | Buffer.prototype.toString = function toString () { 853 | var length = this.length 854 | if (length === 0) return '' 855 | if (arguments.length === 0) return utf8Slice(this, 0, length) 856 | return slowToString.apply(this, arguments) 857 | } 858 | 859 | Buffer.prototype.toLocaleString = Buffer.prototype.toString 860 | 861 | Buffer.prototype.equals = function equals (b) { 862 | if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer') 863 | if (this === b) return true 864 | return Buffer.compare(this, b) === 0 865 | } 866 | 867 | Buffer.prototype.inspect = function inspect () { 868 | var str = '' 869 | var max = exports.INSPECT_MAX_BYTES 870 | str = this.toString('hex', 0, max).replace(/(.{2})/g, '$1 ').trim() 871 | if (this.length > max) str += ' ... ' 872 | return '' 873 | } 874 | 875 | Buffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) { 876 | if (isInstance(target, Uint8Array)) { 877 | target = Buffer.from(target, target.offset, target.byteLength) 878 | } 879 | if (!Buffer.isBuffer(target)) { 880 | throw new TypeError( 881 | 'The "target" argument must be one of type Buffer or Uint8Array. ' + 882 | 'Received type ' + (typeof target) 883 | ) 884 | } 885 | 886 | if (start === undefined) { 887 | start = 0 888 | } 889 | if (end === undefined) { 890 | end = target ? target.length : 0 891 | } 892 | if (thisStart === undefined) { 893 | thisStart = 0 894 | } 895 | if (thisEnd === undefined) { 896 | thisEnd = this.length 897 | } 898 | 899 | if (start < 0 || end > target.length || thisStart < 0 || thisEnd > this.length) { 900 | throw new RangeError('out of range index') 901 | } 902 | 903 | if (thisStart >= thisEnd && start >= end) { 904 | return 0 905 | } 906 | if (thisStart >= thisEnd) { 907 | return -1 908 | } 909 | if (start >= end) { 910 | return 1 911 | } 912 | 913 | start >>>= 0 914 | end >>>= 0 915 | thisStart >>>= 0 916 | thisEnd >>>= 0 917 | 918 | if (this === target) return 0 919 | 920 | var x = thisEnd - thisStart 921 | var y = end - start 922 | var len = Math.min(x, y) 923 | 924 | var thisCopy = this.slice(thisStart, thisEnd) 925 | var targetCopy = target.slice(start, end) 926 | 927 | for (var i = 0; i < len; ++i) { 928 | if (thisCopy[i] !== targetCopy[i]) { 929 | x = thisCopy[i] 930 | y = targetCopy[i] 931 | break 932 | } 933 | } 934 | 935 | if (x < y) return -1 936 | if (y < x) return 1 937 | return 0 938 | } 939 | 940 | // Finds either the first index of `val` in `buffer` at offset >= `byteOffset`, 941 | // OR the last index of `val` in `buffer` at offset <= `byteOffset`. 942 | // 943 | // Arguments: 944 | // - buffer - a Buffer to search 945 | // - val - a string, Buffer, or number 946 | // - byteOffset - an index into `buffer`; will be clamped to an int32 947 | // - encoding - an optional encoding, relevant is val is a string 948 | // - dir - true for indexOf, false for lastIndexOf 949 | function bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) { 950 | // Empty buffer means no match 951 | if (buffer.length === 0) return -1 952 | 953 | // Normalize byteOffset 954 | if (typeof byteOffset === 'string') { 955 | encoding = byteOffset 956 | byteOffset = 0 957 | } else if (byteOffset > 0x7fffffff) { 958 | byteOffset = 0x7fffffff 959 | } else if (byteOffset < -0x80000000) { 960 | byteOffset = -0x80000000 961 | } 962 | byteOffset = +byteOffset // Coerce to Number. 963 | if (numberIsNaN(byteOffset)) { 964 | // byteOffset: it it's undefined, null, NaN, "foo", etc, search whole buffer 965 | byteOffset = dir ? 0 : (buffer.length - 1) 966 | } 967 | 968 | // Normalize byteOffset: negative offsets start from the end of the buffer 969 | if (byteOffset < 0) byteOffset = buffer.length + byteOffset 970 | if (byteOffset >= buffer.length) { 971 | if (dir) return -1 972 | else byteOffset = buffer.length - 1 973 | } else if (byteOffset < 0) { 974 | if (dir) byteOffset = 0 975 | else return -1 976 | } 977 | 978 | // Normalize val 979 | if (typeof val === 'string') { 980 | val = Buffer.from(val, encoding) 981 | } 982 | 983 | // Finally, search either indexOf (if dir is true) or lastIndexOf 984 | if (Buffer.isBuffer(val)) { 985 | // Special case: looking for empty string/buffer always fails 986 | if (val.length === 0) { 987 | return -1 988 | } 989 | return arrayIndexOf(buffer, val, byteOffset, encoding, dir) 990 | } else if (typeof val === 'number') { 991 | val = val & 0xFF // Search for a byte value [0-255] 992 | if (typeof Uint8Array.prototype.indexOf === 'function') { 993 | if (dir) { 994 | return Uint8Array.prototype.indexOf.call(buffer, val, byteOffset) 995 | } else { 996 | return Uint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset) 997 | } 998 | } 999 | return arrayIndexOf(buffer, [ val ], byteOffset, encoding, dir) 1000 | } 1001 | 1002 | throw new TypeError('val must be string, number or Buffer') 1003 | } 1004 | 1005 | function arrayIndexOf (arr, val, byteOffset, encoding, dir) { 1006 | var indexSize = 1 1007 | var arrLength = arr.length 1008 | var valLength = val.length 1009 | 1010 | if (encoding !== undefined) { 1011 | encoding = String(encoding).toLowerCase() 1012 | if (encoding === 'ucs2' || encoding === 'ucs-2' || 1013 | encoding === 'utf16le' || encoding === 'utf-16le') { 1014 | if (arr.length < 2 || val.length < 2) { 1015 | return -1 1016 | } 1017 | indexSize = 2 1018 | arrLength /= 2 1019 | valLength /= 2 1020 | byteOffset /= 2 1021 | } 1022 | } 1023 | 1024 | function read (buf, i) { 1025 | if (indexSize === 1) { 1026 | return buf[i] 1027 | } else { 1028 | return buf.readUInt16BE(i * indexSize) 1029 | } 1030 | } 1031 | 1032 | var i 1033 | if (dir) { 1034 | var foundIndex = -1 1035 | for (i = byteOffset; i < arrLength; i++) { 1036 | if (read(arr, i) === read(val, foundIndex === -1 ? 0 : i - foundIndex)) { 1037 | if (foundIndex === -1) foundIndex = i 1038 | if (i - foundIndex + 1 === valLength) return foundIndex * indexSize 1039 | } else { 1040 | if (foundIndex !== -1) i -= i - foundIndex 1041 | foundIndex = -1 1042 | } 1043 | } 1044 | } else { 1045 | if (byteOffset + valLength > arrLength) byteOffset = arrLength - valLength 1046 | for (i = byteOffset; i >= 0; i--) { 1047 | var found = true 1048 | for (var j = 0; j < valLength; j++) { 1049 | if (read(arr, i + j) !== read(val, j)) { 1050 | found = false 1051 | break 1052 | } 1053 | } 1054 | if (found) return i 1055 | } 1056 | } 1057 | 1058 | return -1 1059 | } 1060 | 1061 | Buffer.prototype.includes = function includes (val, byteOffset, encoding) { 1062 | return this.indexOf(val, byteOffset, encoding) !== -1 1063 | } 1064 | 1065 | Buffer.prototype.indexOf = function indexOf (val, byteOffset, encoding) { 1066 | return bidirectionalIndexOf(this, val, byteOffset, encoding, true) 1067 | } 1068 | 1069 | Buffer.prototype.lastIndexOf = function lastIndexOf (val, byteOffset, encoding) { 1070 | return bidirectionalIndexOf(this, val, byteOffset, encoding, false) 1071 | } 1072 | 1073 | function hexWrite (buf, string, offset, length) { 1074 | offset = Number(offset) || 0 1075 | var remaining = buf.length - offset 1076 | if (!length) { 1077 | length = remaining 1078 | } else { 1079 | length = Number(length) 1080 | if (length > remaining) { 1081 | length = remaining 1082 | } 1083 | } 1084 | 1085 | var strLen = string.length 1086 | 1087 | if (length > strLen / 2) { 1088 | length = strLen / 2 1089 | } 1090 | for (var i = 0; i < length; ++i) { 1091 | var parsed = parseInt(string.substr(i * 2, 2), 16) 1092 | if (numberIsNaN(parsed)) return i 1093 | buf[offset + i] = parsed 1094 | } 1095 | return i 1096 | } 1097 | 1098 | function utf8Write (buf, string, offset, length) { 1099 | return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length) 1100 | } 1101 | 1102 | function asciiWrite (buf, string, offset, length) { 1103 | return blitBuffer(asciiToBytes(string), buf, offset, length) 1104 | } 1105 | 1106 | function latin1Write (buf, string, offset, length) { 1107 | return asciiWrite(buf, string, offset, length) 1108 | } 1109 | 1110 | function base64Write (buf, string, offset, length) { 1111 | return blitBuffer(base64ToBytes(string), buf, offset, length) 1112 | } 1113 | 1114 | function ucs2Write (buf, string, offset, length) { 1115 | return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length) 1116 | } 1117 | 1118 | Buffer.prototype.write = function write (string, offset, length, encoding) { 1119 | // Buffer#write(string) 1120 | if (offset === undefined) { 1121 | encoding = 'utf8' 1122 | length = this.length 1123 | offset = 0 1124 | // Buffer#write(string, encoding) 1125 | } else if (length === undefined && typeof offset === 'string') { 1126 | encoding = offset 1127 | length = this.length 1128 | offset = 0 1129 | // Buffer#write(string, offset[, length][, encoding]) 1130 | } else if (isFinite(offset)) { 1131 | offset = offset >>> 0 1132 | if (isFinite(length)) { 1133 | length = length >>> 0 1134 | if (encoding === undefined) encoding = 'utf8' 1135 | } else { 1136 | encoding = length 1137 | length = undefined 1138 | } 1139 | } else { 1140 | throw new Error( 1141 | 'Buffer.write(string, encoding, offset[, length]) is no longer supported' 1142 | ) 1143 | } 1144 | 1145 | var remaining = this.length - offset 1146 | if (length === undefined || length > remaining) length = remaining 1147 | 1148 | if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) { 1149 | throw new RangeError('Attempt to write outside buffer bounds') 1150 | } 1151 | 1152 | if (!encoding) encoding = 'utf8' 1153 | 1154 | var loweredCase = false 1155 | for (;;) { 1156 | switch (encoding) { 1157 | case 'hex': 1158 | return hexWrite(this, string, offset, length) 1159 | 1160 | case 'utf8': 1161 | case 'utf-8': 1162 | return utf8Write(this, string, offset, length) 1163 | 1164 | case 'ascii': 1165 | return asciiWrite(this, string, offset, length) 1166 | 1167 | case 'latin1': 1168 | case 'binary': 1169 | return latin1Write(this, string, offset, length) 1170 | 1171 | case 'base64': 1172 | // Warning: maxLength not taken into account in base64Write 1173 | return base64Write(this, string, offset, length) 1174 | 1175 | case 'ucs2': 1176 | case 'ucs-2': 1177 | case 'utf16le': 1178 | case 'utf-16le': 1179 | return ucs2Write(this, string, offset, length) 1180 | 1181 | default: 1182 | if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) 1183 | encoding = ('' + encoding).toLowerCase() 1184 | loweredCase = true 1185 | } 1186 | } 1187 | } 1188 | 1189 | Buffer.prototype.toJSON = function toJSON () { 1190 | return { 1191 | type: 'Buffer', 1192 | data: Array.prototype.slice.call(this._arr || this, 0) 1193 | } 1194 | } 1195 | 1196 | function base64Slice (buf, start, end) { 1197 | if (start === 0 && end === buf.length) { 1198 | return base64.fromByteArray(buf) 1199 | } else { 1200 | return base64.fromByteArray(buf.slice(start, end)) 1201 | } 1202 | } 1203 | 1204 | function utf8Slice (buf, start, end) { 1205 | end = Math.min(buf.length, end) 1206 | var res = [] 1207 | 1208 | var i = start 1209 | while (i < end) { 1210 | var firstByte = buf[i] 1211 | var codePoint = null 1212 | var bytesPerSequence = (firstByte > 0xEF) ? 4 1213 | : (firstByte > 0xDF) ? 3 1214 | : (firstByte > 0xBF) ? 2 1215 | : 1 1216 | 1217 | if (i + bytesPerSequence <= end) { 1218 | var secondByte, thirdByte, fourthByte, tempCodePoint 1219 | 1220 | switch (bytesPerSequence) { 1221 | case 1: 1222 | if (firstByte < 0x80) { 1223 | codePoint = firstByte 1224 | } 1225 | break 1226 | case 2: 1227 | secondByte = buf[i + 1] 1228 | if ((secondByte & 0xC0) === 0x80) { 1229 | tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F) 1230 | if (tempCodePoint > 0x7F) { 1231 | codePoint = tempCodePoint 1232 | } 1233 | } 1234 | break 1235 | case 3: 1236 | secondByte = buf[i + 1] 1237 | thirdByte = buf[i + 2] 1238 | if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) { 1239 | tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F) 1240 | if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) { 1241 | codePoint = tempCodePoint 1242 | } 1243 | } 1244 | break 1245 | case 4: 1246 | secondByte = buf[i + 1] 1247 | thirdByte = buf[i + 2] 1248 | fourthByte = buf[i + 3] 1249 | if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) { 1250 | tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F) 1251 | if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) { 1252 | codePoint = tempCodePoint 1253 | } 1254 | } 1255 | } 1256 | } 1257 | 1258 | if (codePoint === null) { 1259 | // we did not generate a valid codePoint so insert a 1260 | // replacement char (U+FFFD) and advance only 1 byte 1261 | codePoint = 0xFFFD 1262 | bytesPerSequence = 1 1263 | } else if (codePoint > 0xFFFF) { 1264 | // encode to utf16 (surrogate pair dance) 1265 | codePoint -= 0x10000 1266 | res.push(codePoint >>> 10 & 0x3FF | 0xD800) 1267 | codePoint = 0xDC00 | codePoint & 0x3FF 1268 | } 1269 | 1270 | res.push(codePoint) 1271 | i += bytesPerSequence 1272 | } 1273 | 1274 | return decodeCodePointsArray(res) 1275 | } 1276 | 1277 | // Based on http://stackoverflow.com/a/22747272/680742, the browser with 1278 | // the lowest limit is Chrome, with 0x10000 args. 1279 | // We go 1 magnitude less, for safety 1280 | var MAX_ARGUMENTS_LENGTH = 0x1000 1281 | 1282 | function decodeCodePointsArray (codePoints) { 1283 | var len = codePoints.length 1284 | if (len <= MAX_ARGUMENTS_LENGTH) { 1285 | return String.fromCharCode.apply(String, codePoints) // avoid extra slice() 1286 | } 1287 | 1288 | // Decode in chunks to avoid "call stack size exceeded". 1289 | var res = '' 1290 | var i = 0 1291 | while (i < len) { 1292 | res += String.fromCharCode.apply( 1293 | String, 1294 | codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH) 1295 | ) 1296 | } 1297 | return res 1298 | } 1299 | 1300 | function asciiSlice (buf, start, end) { 1301 | var ret = '' 1302 | end = Math.min(buf.length, end) 1303 | 1304 | for (var i = start; i < end; ++i) { 1305 | ret += String.fromCharCode(buf[i] & 0x7F) 1306 | } 1307 | return ret 1308 | } 1309 | 1310 | function latin1Slice (buf, start, end) { 1311 | var ret = '' 1312 | end = Math.min(buf.length, end) 1313 | 1314 | for (var i = start; i < end; ++i) { 1315 | ret += String.fromCharCode(buf[i]) 1316 | } 1317 | return ret 1318 | } 1319 | 1320 | function hexSlice (buf, start, end) { 1321 | var len = buf.length 1322 | 1323 | if (!start || start < 0) start = 0 1324 | if (!end || end < 0 || end > len) end = len 1325 | 1326 | var out = '' 1327 | for (var i = start; i < end; ++i) { 1328 | out += toHex(buf[i]) 1329 | } 1330 | return out 1331 | } 1332 | 1333 | function utf16leSlice (buf, start, end) { 1334 | var bytes = buf.slice(start, end) 1335 | var res = '' 1336 | for (var i = 0; i < bytes.length; i += 2) { 1337 | res += String.fromCharCode(bytes[i] + (bytes[i + 1] * 256)) 1338 | } 1339 | return res 1340 | } 1341 | 1342 | Buffer.prototype.slice = function slice (start, end) { 1343 | var len = this.length 1344 | start = ~~start 1345 | end = end === undefined ? len : ~~end 1346 | 1347 | if (start < 0) { 1348 | start += len 1349 | if (start < 0) start = 0 1350 | } else if (start > len) { 1351 | start = len 1352 | } 1353 | 1354 | if (end < 0) { 1355 | end += len 1356 | if (end < 0) end = 0 1357 | } else if (end > len) { 1358 | end = len 1359 | } 1360 | 1361 | if (end < start) end = start 1362 | 1363 | var newBuf = this.subarray(start, end) 1364 | // Return an augmented `Uint8Array` instance 1365 | newBuf.__proto__ = Buffer.prototype 1366 | return newBuf 1367 | } 1368 | 1369 | /* 1370 | * Need to make sure that buffer isn't trying to write out of bounds. 1371 | */ 1372 | function checkOffset (offset, ext, length) { 1373 | if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint') 1374 | if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length') 1375 | } 1376 | 1377 | Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) { 1378 | offset = offset >>> 0 1379 | byteLength = byteLength >>> 0 1380 | if (!noAssert) checkOffset(offset, byteLength, this.length) 1381 | 1382 | var val = this[offset] 1383 | var mul = 1 1384 | var i = 0 1385 | while (++i < byteLength && (mul *= 0x100)) { 1386 | val += this[offset + i] * mul 1387 | } 1388 | 1389 | return val 1390 | } 1391 | 1392 | Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) { 1393 | offset = offset >>> 0 1394 | byteLength = byteLength >>> 0 1395 | if (!noAssert) { 1396 | checkOffset(offset, byteLength, this.length) 1397 | } 1398 | 1399 | var val = this[offset + --byteLength] 1400 | var mul = 1 1401 | while (byteLength > 0 && (mul *= 0x100)) { 1402 | val += this[offset + --byteLength] * mul 1403 | } 1404 | 1405 | return val 1406 | } 1407 | 1408 | Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) { 1409 | offset = offset >>> 0 1410 | if (!noAssert) checkOffset(offset, 1, this.length) 1411 | return this[offset] 1412 | } 1413 | 1414 | Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) { 1415 | offset = offset >>> 0 1416 | if (!noAssert) checkOffset(offset, 2, this.length) 1417 | return this[offset] | (this[offset + 1] << 8) 1418 | } 1419 | 1420 | Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) { 1421 | offset = offset >>> 0 1422 | if (!noAssert) checkOffset(offset, 2, this.length) 1423 | return (this[offset] << 8) | this[offset + 1] 1424 | } 1425 | 1426 | Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) { 1427 | offset = offset >>> 0 1428 | if (!noAssert) checkOffset(offset, 4, this.length) 1429 | 1430 | return ((this[offset]) | 1431 | (this[offset + 1] << 8) | 1432 | (this[offset + 2] << 16)) + 1433 | (this[offset + 3] * 0x1000000) 1434 | } 1435 | 1436 | Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) { 1437 | offset = offset >>> 0 1438 | if (!noAssert) checkOffset(offset, 4, this.length) 1439 | 1440 | return (this[offset] * 0x1000000) + 1441 | ((this[offset + 1] << 16) | 1442 | (this[offset + 2] << 8) | 1443 | this[offset + 3]) 1444 | } 1445 | 1446 | Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) { 1447 | offset = offset >>> 0 1448 | byteLength = byteLength >>> 0 1449 | if (!noAssert) checkOffset(offset, byteLength, this.length) 1450 | 1451 | var val = this[offset] 1452 | var mul = 1 1453 | var i = 0 1454 | while (++i < byteLength && (mul *= 0x100)) { 1455 | val += this[offset + i] * mul 1456 | } 1457 | mul *= 0x80 1458 | 1459 | if (val >= mul) val -= Math.pow(2, 8 * byteLength) 1460 | 1461 | return val 1462 | } 1463 | 1464 | Buffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) { 1465 | offset = offset >>> 0 1466 | byteLength = byteLength >>> 0 1467 | if (!noAssert) checkOffset(offset, byteLength, this.length) 1468 | 1469 | var i = byteLength 1470 | var mul = 1 1471 | var val = this[offset + --i] 1472 | while (i > 0 && (mul *= 0x100)) { 1473 | val += this[offset + --i] * mul 1474 | } 1475 | mul *= 0x80 1476 | 1477 | if (val >= mul) val -= Math.pow(2, 8 * byteLength) 1478 | 1479 | return val 1480 | } 1481 | 1482 | Buffer.prototype.readInt8 = function readInt8 (offset, noAssert) { 1483 | offset = offset >>> 0 1484 | if (!noAssert) checkOffset(offset, 1, this.length) 1485 | if (!(this[offset] & 0x80)) return (this[offset]) 1486 | return ((0xff - this[offset] + 1) * -1) 1487 | } 1488 | 1489 | Buffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) { 1490 | offset = offset >>> 0 1491 | if (!noAssert) checkOffset(offset, 2, this.length) 1492 | var val = this[offset] | (this[offset + 1] << 8) 1493 | return (val & 0x8000) ? val | 0xFFFF0000 : val 1494 | } 1495 | 1496 | Buffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) { 1497 | offset = offset >>> 0 1498 | if (!noAssert) checkOffset(offset, 2, this.length) 1499 | var val = this[offset + 1] | (this[offset] << 8) 1500 | return (val & 0x8000) ? val | 0xFFFF0000 : val 1501 | } 1502 | 1503 | Buffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) { 1504 | offset = offset >>> 0 1505 | if (!noAssert) checkOffset(offset, 4, this.length) 1506 | 1507 | return (this[offset]) | 1508 | (this[offset + 1] << 8) | 1509 | (this[offset + 2] << 16) | 1510 | (this[offset + 3] << 24) 1511 | } 1512 | 1513 | Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) { 1514 | offset = offset >>> 0 1515 | if (!noAssert) checkOffset(offset, 4, this.length) 1516 | 1517 | return (this[offset] << 24) | 1518 | (this[offset + 1] << 16) | 1519 | (this[offset + 2] << 8) | 1520 | (this[offset + 3]) 1521 | } 1522 | 1523 | Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) { 1524 | offset = offset >>> 0 1525 | if (!noAssert) checkOffset(offset, 4, this.length) 1526 | return ieee754.read(this, offset, true, 23, 4) 1527 | } 1528 | 1529 | Buffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) { 1530 | offset = offset >>> 0 1531 | if (!noAssert) checkOffset(offset, 4, this.length) 1532 | return ieee754.read(this, offset, false, 23, 4) 1533 | } 1534 | 1535 | Buffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) { 1536 | offset = offset >>> 0 1537 | if (!noAssert) checkOffset(offset, 8, this.length) 1538 | return ieee754.read(this, offset, true, 52, 8) 1539 | } 1540 | 1541 | Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) { 1542 | offset = offset >>> 0 1543 | if (!noAssert) checkOffset(offset, 8, this.length) 1544 | return ieee754.read(this, offset, false, 52, 8) 1545 | } 1546 | 1547 | function checkInt (buf, value, offset, ext, max, min) { 1548 | if (!Buffer.isBuffer(buf)) throw new TypeError('"buffer" argument must be a Buffer instance') 1549 | if (value > max || value < min) throw new RangeError('"value" argument is out of bounds') 1550 | if (offset + ext > buf.length) throw new RangeError('Index out of range') 1551 | } 1552 | 1553 | Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) { 1554 | value = +value 1555 | offset = offset >>> 0 1556 | byteLength = byteLength >>> 0 1557 | if (!noAssert) { 1558 | var maxBytes = Math.pow(2, 8 * byteLength) - 1 1559 | checkInt(this, value, offset, byteLength, maxBytes, 0) 1560 | } 1561 | 1562 | var mul = 1 1563 | var i = 0 1564 | this[offset] = value & 0xFF 1565 | while (++i < byteLength && (mul *= 0x100)) { 1566 | this[offset + i] = (value / mul) & 0xFF 1567 | } 1568 | 1569 | return offset + byteLength 1570 | } 1571 | 1572 | Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) { 1573 | value = +value 1574 | offset = offset >>> 0 1575 | byteLength = byteLength >>> 0 1576 | if (!noAssert) { 1577 | var maxBytes = Math.pow(2, 8 * byteLength) - 1 1578 | checkInt(this, value, offset, byteLength, maxBytes, 0) 1579 | } 1580 | 1581 | var i = byteLength - 1 1582 | var mul = 1 1583 | this[offset + i] = value & 0xFF 1584 | while (--i >= 0 && (mul *= 0x100)) { 1585 | this[offset + i] = (value / mul) & 0xFF 1586 | } 1587 | 1588 | return offset + byteLength 1589 | } 1590 | 1591 | Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) { 1592 | value = +value 1593 | offset = offset >>> 0 1594 | if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0) 1595 | this[offset] = (value & 0xff) 1596 | return offset + 1 1597 | } 1598 | 1599 | Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) { 1600 | value = +value 1601 | offset = offset >>> 0 1602 | if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) 1603 | this[offset] = (value & 0xff) 1604 | this[offset + 1] = (value >>> 8) 1605 | return offset + 2 1606 | } 1607 | 1608 | Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) { 1609 | value = +value 1610 | offset = offset >>> 0 1611 | if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) 1612 | this[offset] = (value >>> 8) 1613 | this[offset + 1] = (value & 0xff) 1614 | return offset + 2 1615 | } 1616 | 1617 | Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) { 1618 | value = +value 1619 | offset = offset >>> 0 1620 | if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) 1621 | this[offset + 3] = (value >>> 24) 1622 | this[offset + 2] = (value >>> 16) 1623 | this[offset + 1] = (value >>> 8) 1624 | this[offset] = (value & 0xff) 1625 | return offset + 4 1626 | } 1627 | 1628 | Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) { 1629 | value = +value 1630 | offset = offset >>> 0 1631 | if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) 1632 | this[offset] = (value >>> 24) 1633 | this[offset + 1] = (value >>> 16) 1634 | this[offset + 2] = (value >>> 8) 1635 | this[offset + 3] = (value & 0xff) 1636 | return offset + 4 1637 | } 1638 | 1639 | Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) { 1640 | value = +value 1641 | offset = offset >>> 0 1642 | if (!noAssert) { 1643 | var limit = Math.pow(2, (8 * byteLength) - 1) 1644 | 1645 | checkInt(this, value, offset, byteLength, limit - 1, -limit) 1646 | } 1647 | 1648 | var i = 0 1649 | var mul = 1 1650 | var sub = 0 1651 | this[offset] = value & 0xFF 1652 | while (++i < byteLength && (mul *= 0x100)) { 1653 | if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) { 1654 | sub = 1 1655 | } 1656 | this[offset + i] = ((value / mul) >> 0) - sub & 0xFF 1657 | } 1658 | 1659 | return offset + byteLength 1660 | } 1661 | 1662 | Buffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) { 1663 | value = +value 1664 | offset = offset >>> 0 1665 | if (!noAssert) { 1666 | var limit = Math.pow(2, (8 * byteLength) - 1) 1667 | 1668 | checkInt(this, value, offset, byteLength, limit - 1, -limit) 1669 | } 1670 | 1671 | var i = byteLength - 1 1672 | var mul = 1 1673 | var sub = 0 1674 | this[offset + i] = value & 0xFF 1675 | while (--i >= 0 && (mul *= 0x100)) { 1676 | if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) { 1677 | sub = 1 1678 | } 1679 | this[offset + i] = ((value / mul) >> 0) - sub & 0xFF 1680 | } 1681 | 1682 | return offset + byteLength 1683 | } 1684 | 1685 | Buffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) { 1686 | value = +value 1687 | offset = offset >>> 0 1688 | if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80) 1689 | if (value < 0) value = 0xff + value + 1 1690 | this[offset] = (value & 0xff) 1691 | return offset + 1 1692 | } 1693 | 1694 | Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) { 1695 | value = +value 1696 | offset = offset >>> 0 1697 | if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) 1698 | this[offset] = (value & 0xff) 1699 | this[offset + 1] = (value >>> 8) 1700 | return offset + 2 1701 | } 1702 | 1703 | Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) { 1704 | value = +value 1705 | offset = offset >>> 0 1706 | if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) 1707 | this[offset] = (value >>> 8) 1708 | this[offset + 1] = (value & 0xff) 1709 | return offset + 2 1710 | } 1711 | 1712 | Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) { 1713 | value = +value 1714 | offset = offset >>> 0 1715 | if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) 1716 | this[offset] = (value & 0xff) 1717 | this[offset + 1] = (value >>> 8) 1718 | this[offset + 2] = (value >>> 16) 1719 | this[offset + 3] = (value >>> 24) 1720 | return offset + 4 1721 | } 1722 | 1723 | Buffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) { 1724 | value = +value 1725 | offset = offset >>> 0 1726 | if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) 1727 | if (value < 0) value = 0xffffffff + value + 1 1728 | this[offset] = (value >>> 24) 1729 | this[offset + 1] = (value >>> 16) 1730 | this[offset + 2] = (value >>> 8) 1731 | this[offset + 3] = (value & 0xff) 1732 | return offset + 4 1733 | } 1734 | 1735 | function checkIEEE754 (buf, value, offset, ext, max, min) { 1736 | if (offset + ext > buf.length) throw new RangeError('Index out of range') 1737 | if (offset < 0) throw new RangeError('Index out of range') 1738 | } 1739 | 1740 | function writeFloat (buf, value, offset, littleEndian, noAssert) { 1741 | value = +value 1742 | offset = offset >>> 0 1743 | if (!noAssert) { 1744 | checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38) 1745 | } 1746 | ieee754.write(buf, value, offset, littleEndian, 23, 4) 1747 | return offset + 4 1748 | } 1749 | 1750 | Buffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) { 1751 | return writeFloat(this, value, offset, true, noAssert) 1752 | } 1753 | 1754 | Buffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) { 1755 | return writeFloat(this, value, offset, false, noAssert) 1756 | } 1757 | 1758 | function writeDouble (buf, value, offset, littleEndian, noAssert) { 1759 | value = +value 1760 | offset = offset >>> 0 1761 | if (!noAssert) { 1762 | checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308) 1763 | } 1764 | ieee754.write(buf, value, offset, littleEndian, 52, 8) 1765 | return offset + 8 1766 | } 1767 | 1768 | Buffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) { 1769 | return writeDouble(this, value, offset, true, noAssert) 1770 | } 1771 | 1772 | Buffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) { 1773 | return writeDouble(this, value, offset, false, noAssert) 1774 | } 1775 | 1776 | // copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length) 1777 | Buffer.prototype.copy = function copy (target, targetStart, start, end) { 1778 | if (!Buffer.isBuffer(target)) throw new TypeError('argument should be a Buffer') 1779 | if (!start) start = 0 1780 | if (!end && end !== 0) end = this.length 1781 | if (targetStart >= target.length) targetStart = target.length 1782 | if (!targetStart) targetStart = 0 1783 | if (end > 0 && end < start) end = start 1784 | 1785 | // Copy 0 bytes; we're done 1786 | if (end === start) return 0 1787 | if (target.length === 0 || this.length === 0) return 0 1788 | 1789 | // Fatal error conditions 1790 | if (targetStart < 0) { 1791 | throw new RangeError('targetStart out of bounds') 1792 | } 1793 | if (start < 0 || start >= this.length) throw new RangeError('Index out of range') 1794 | if (end < 0) throw new RangeError('sourceEnd out of bounds') 1795 | 1796 | // Are we oob? 1797 | if (end > this.length) end = this.length 1798 | if (target.length - targetStart < end - start) { 1799 | end = target.length - targetStart + start 1800 | } 1801 | 1802 | var len = end - start 1803 | 1804 | if (this === target && typeof Uint8Array.prototype.copyWithin === 'function') { 1805 | // Use built-in when available, missing from IE11 1806 | this.copyWithin(targetStart, start, end) 1807 | } else if (this === target && start < targetStart && targetStart < end) { 1808 | // descending copy from end 1809 | for (var i = len - 1; i >= 0; --i) { 1810 | target[i + targetStart] = this[i + start] 1811 | } 1812 | } else { 1813 | Uint8Array.prototype.set.call( 1814 | target, 1815 | this.subarray(start, end), 1816 | targetStart 1817 | ) 1818 | } 1819 | 1820 | return len 1821 | } 1822 | 1823 | // Usage: 1824 | // buffer.fill(number[, offset[, end]]) 1825 | // buffer.fill(buffer[, offset[, end]]) 1826 | // buffer.fill(string[, offset[, end]][, encoding]) 1827 | Buffer.prototype.fill = function fill (val, start, end, encoding) { 1828 | // Handle string cases: 1829 | if (typeof val === 'string') { 1830 | if (typeof start === 'string') { 1831 | encoding = start 1832 | start = 0 1833 | end = this.length 1834 | } else if (typeof end === 'string') { 1835 | encoding = end 1836 | end = this.length 1837 | } 1838 | if (encoding !== undefined && typeof encoding !== 'string') { 1839 | throw new TypeError('encoding must be a string') 1840 | } 1841 | if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) { 1842 | throw new TypeError('Unknown encoding: ' + encoding) 1843 | } 1844 | if (val.length === 1) { 1845 | var code = val.charCodeAt(0) 1846 | if ((encoding === 'utf8' && code < 128) || 1847 | encoding === 'latin1') { 1848 | // Fast path: If `val` fits into a single byte, use that numeric value. 1849 | val = code 1850 | } 1851 | } 1852 | } else if (typeof val === 'number') { 1853 | val = val & 255 1854 | } 1855 | 1856 | // Invalid ranges are not set to a default, so can range check early. 1857 | if (start < 0 || this.length < start || this.length < end) { 1858 | throw new RangeError('Out of range index') 1859 | } 1860 | 1861 | if (end <= start) { 1862 | return this 1863 | } 1864 | 1865 | start = start >>> 0 1866 | end = end === undefined ? this.length : end >>> 0 1867 | 1868 | if (!val) val = 0 1869 | 1870 | var i 1871 | if (typeof val === 'number') { 1872 | for (i = start; i < end; ++i) { 1873 | this[i] = val 1874 | } 1875 | } else { 1876 | var bytes = Buffer.isBuffer(val) 1877 | ? val 1878 | : Buffer.from(val, encoding) 1879 | var len = bytes.length 1880 | if (len === 0) { 1881 | throw new TypeError('The value "' + val + 1882 | '" is invalid for argument "value"') 1883 | } 1884 | for (i = 0; i < end - start; ++i) { 1885 | this[i + start] = bytes[i % len] 1886 | } 1887 | } 1888 | 1889 | return this 1890 | } 1891 | 1892 | // HELPER FUNCTIONS 1893 | // ================ 1894 | 1895 | var INVALID_BASE64_RE = /[^+/0-9A-Za-z-_]/g 1896 | 1897 | function base64clean (str) { 1898 | // Node takes equal signs as end of the Base64 encoding 1899 | str = str.split('=')[0] 1900 | // Node strips out invalid characters like \n and \t from the string, base64-js does not 1901 | str = str.trim().replace(INVALID_BASE64_RE, '') 1902 | // Node converts strings with length < 2 to '' 1903 | if (str.length < 2) return '' 1904 | // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not 1905 | while (str.length % 4 !== 0) { 1906 | str = str + '=' 1907 | } 1908 | return str 1909 | } 1910 | 1911 | function toHex (n) { 1912 | if (n < 16) return '0' + n.toString(16) 1913 | return n.toString(16) 1914 | } 1915 | 1916 | function utf8ToBytes (string, units) { 1917 | units = units || Infinity 1918 | var codePoint 1919 | var length = string.length 1920 | var leadSurrogate = null 1921 | var bytes = [] 1922 | 1923 | for (var i = 0; i < length; ++i) { 1924 | codePoint = string.charCodeAt(i) 1925 | 1926 | // is surrogate component 1927 | if (codePoint > 0xD7FF && codePoint < 0xE000) { 1928 | // last char was a lead 1929 | if (!leadSurrogate) { 1930 | // no lead yet 1931 | if (codePoint > 0xDBFF) { 1932 | // unexpected trail 1933 | if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) 1934 | continue 1935 | } else if (i + 1 === length) { 1936 | // unpaired lead 1937 | if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) 1938 | continue 1939 | } 1940 | 1941 | // valid lead 1942 | leadSurrogate = codePoint 1943 | 1944 | continue 1945 | } 1946 | 1947 | // 2 leads in a row 1948 | if (codePoint < 0xDC00) { 1949 | if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) 1950 | leadSurrogate = codePoint 1951 | continue 1952 | } 1953 | 1954 | // valid surrogate pair 1955 | codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000 1956 | } else if (leadSurrogate) { 1957 | // valid bmp char, but last char was a lead 1958 | if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) 1959 | } 1960 | 1961 | leadSurrogate = null 1962 | 1963 | // encode utf8 1964 | if (codePoint < 0x80) { 1965 | if ((units -= 1) < 0) break 1966 | bytes.push(codePoint) 1967 | } else if (codePoint < 0x800) { 1968 | if ((units -= 2) < 0) break 1969 | bytes.push( 1970 | codePoint >> 0x6 | 0xC0, 1971 | codePoint & 0x3F | 0x80 1972 | ) 1973 | } else if (codePoint < 0x10000) { 1974 | if ((units -= 3) < 0) break 1975 | bytes.push( 1976 | codePoint >> 0xC | 0xE0, 1977 | codePoint >> 0x6 & 0x3F | 0x80, 1978 | codePoint & 0x3F | 0x80 1979 | ) 1980 | } else if (codePoint < 0x110000) { 1981 | if ((units -= 4) < 0) break 1982 | bytes.push( 1983 | codePoint >> 0x12 | 0xF0, 1984 | codePoint >> 0xC & 0x3F | 0x80, 1985 | codePoint >> 0x6 & 0x3F | 0x80, 1986 | codePoint & 0x3F | 0x80 1987 | ) 1988 | } else { 1989 | throw new Error('Invalid code point') 1990 | } 1991 | } 1992 | 1993 | return bytes 1994 | } 1995 | 1996 | function asciiToBytes (str) { 1997 | var byteArray = [] 1998 | for (var i = 0; i < str.length; ++i) { 1999 | // Node's code seems to be doing this and not & 0x7F.. 2000 | byteArray.push(str.charCodeAt(i) & 0xFF) 2001 | } 2002 | return byteArray 2003 | } 2004 | 2005 | function utf16leToBytes (str, units) { 2006 | var c, hi, lo 2007 | var byteArray = [] 2008 | for (var i = 0; i < str.length; ++i) { 2009 | if ((units -= 2) < 0) break 2010 | 2011 | c = str.charCodeAt(i) 2012 | hi = c >> 8 2013 | lo = c % 256 2014 | byteArray.push(lo) 2015 | byteArray.push(hi) 2016 | } 2017 | 2018 | return byteArray 2019 | } 2020 | 2021 | function base64ToBytes (str) { 2022 | return base64.toByteArray(base64clean(str)) 2023 | } 2024 | 2025 | function blitBuffer (src, dst, offset, length) { 2026 | for (var i = 0; i < length; ++i) { 2027 | if ((i + offset >= dst.length) || (i >= src.length)) break 2028 | dst[i + offset] = src[i] 2029 | } 2030 | return i 2031 | } 2032 | 2033 | // ArrayBuffer or Uint8Array objects from other contexts (i.e. iframes) do not pass 2034 | // the `instanceof` check but they should be treated as of that type. 2035 | // See: https://github.com/feross/buffer/issues/166 2036 | function isInstance (obj, type) { 2037 | return obj instanceof type || 2038 | (obj != null && obj.constructor != null && obj.constructor.name != null && 2039 | obj.constructor.name === type.name) 2040 | } 2041 | function numberIsNaN (obj) { 2042 | // For IE11 support 2043 | return obj !== obj // eslint-disable-line no-self-compare 2044 | } 2045 | 2046 | }).call(this,require("buffer").Buffer) 2047 | },{"base64-js":2,"buffer":3,"ieee754":4}],4:[function(require,module,exports){ 2048 | exports.read = function (buffer, offset, isLE, mLen, nBytes) { 2049 | var e, m 2050 | var eLen = (nBytes * 8) - mLen - 1 2051 | var eMax = (1 << eLen) - 1 2052 | var eBias = eMax >> 1 2053 | var nBits = -7 2054 | var i = isLE ? (nBytes - 1) : 0 2055 | var d = isLE ? -1 : 1 2056 | var s = buffer[offset + i] 2057 | 2058 | i += d 2059 | 2060 | e = s & ((1 << (-nBits)) - 1) 2061 | s >>= (-nBits) 2062 | nBits += eLen 2063 | for (; nBits > 0; e = (e * 256) + buffer[offset + i], i += d, nBits -= 8) {} 2064 | 2065 | m = e & ((1 << (-nBits)) - 1) 2066 | e >>= (-nBits) 2067 | nBits += mLen 2068 | for (; nBits > 0; m = (m * 256) + buffer[offset + i], i += d, nBits -= 8) {} 2069 | 2070 | if (e === 0) { 2071 | e = 1 - eBias 2072 | } else if (e === eMax) { 2073 | return m ? NaN : ((s ? -1 : 1) * Infinity) 2074 | } else { 2075 | m = m + Math.pow(2, mLen) 2076 | e = e - eBias 2077 | } 2078 | return (s ? -1 : 1) * m * Math.pow(2, e - mLen) 2079 | } 2080 | 2081 | exports.write = function (buffer, value, offset, isLE, mLen, nBytes) { 2082 | var e, m, c 2083 | var eLen = (nBytes * 8) - mLen - 1 2084 | var eMax = (1 << eLen) - 1 2085 | var eBias = eMax >> 1 2086 | var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0) 2087 | var i = isLE ? 0 : (nBytes - 1) 2088 | var d = isLE ? 1 : -1 2089 | var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0 2090 | 2091 | value = Math.abs(value) 2092 | 2093 | if (isNaN(value) || value === Infinity) { 2094 | m = isNaN(value) ? 1 : 0 2095 | e = eMax 2096 | } else { 2097 | e = Math.floor(Math.log(value) / Math.LN2) 2098 | if (value * (c = Math.pow(2, -e)) < 1) { 2099 | e-- 2100 | c *= 2 2101 | } 2102 | if (e + eBias >= 1) { 2103 | value += rt / c 2104 | } else { 2105 | value += rt * Math.pow(2, 1 - eBias) 2106 | } 2107 | if (value * c >= 2) { 2108 | e++ 2109 | c /= 2 2110 | } 2111 | 2112 | if (e + eBias >= eMax) { 2113 | m = 0 2114 | e = eMax 2115 | } else if (e + eBias >= 1) { 2116 | m = ((value * c) - 1) * Math.pow(2, mLen) 2117 | e = e + eBias 2118 | } else { 2119 | m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen) 2120 | e = 0 2121 | } 2122 | } 2123 | 2124 | for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {} 2125 | 2126 | e = (e << mLen) | m 2127 | eLen += mLen 2128 | for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {} 2129 | 2130 | buffer[offset + i - d] |= s * 128 2131 | } 2132 | 2133 | },{}],5:[function(require,module,exports){ 2134 | "use strict"; 2135 | Object.defineProperty(exports, "__esModule", { value: true }); 2136 | const emitter_1 = require("./emitter"); 2137 | const tokenizer_1 = require("./tokenizer"); 2138 | const parser_1 = require("./parser"); 2139 | const transformer_1 = require("./transformer"); 2140 | exports.compile = src => { 2141 | const tokens = tokenizer_1.tokenize(src); 2142 | const ast = parser_1.parse(tokens); 2143 | const transformedAst = transformer_1.transformer(ast); 2144 | const wasm = emitter_1.emitter(transformedAst); 2145 | return wasm; 2146 | }; 2147 | exports.runtime = async (src, env) => { 2148 | const wasm = exports.compile(src); 2149 | const memory = new WebAssembly.Memory({ initial: 1 }); 2150 | const result = await WebAssembly.instantiate(wasm, { 2151 | env: Object.assign({}, env, { memory }) 2152 | }); 2153 | return () => { 2154 | result.instance.exports.run(); 2155 | env.display.set(new Uint8Array(memory.buffer, 0, 10000)); 2156 | }; 2157 | }; 2158 | 2159 | },{"./emitter":6,"./parser":9,"./tokenizer":10,"./transformer":11}],6:[function(require,module,exports){ 2160 | "use strict"; 2161 | Object.defineProperty(exports, "__esModule", { value: true }); 2162 | const encoding_1 = require("./encoding"); 2163 | const traverse_1 = require("./traverse"); 2164 | const flatten = (arr) => [].concat.apply([], arr); 2165 | // https://webassembly.github.io/spec/core/binary/modules.html#sections 2166 | var Section; 2167 | (function (Section) { 2168 | Section[Section["custom"] = 0] = "custom"; 2169 | Section[Section["type"] = 1] = "type"; 2170 | Section[Section["import"] = 2] = "import"; 2171 | Section[Section["func"] = 3] = "func"; 2172 | Section[Section["table"] = 4] = "table"; 2173 | Section[Section["memory"] = 5] = "memory"; 2174 | Section[Section["global"] = 6] = "global"; 2175 | Section[Section["export"] = 7] = "export"; 2176 | Section[Section["start"] = 8] = "start"; 2177 | Section[Section["element"] = 9] = "element"; 2178 | Section[Section["code"] = 10] = "code"; 2179 | Section[Section["data"] = 11] = "data"; 2180 | })(Section || (Section = {})); 2181 | // https://webassembly.github.io/spec/core/binary/types.html 2182 | var Valtype; 2183 | (function (Valtype) { 2184 | Valtype[Valtype["i32"] = 127] = "i32"; 2185 | Valtype[Valtype["f32"] = 125] = "f32"; 2186 | })(Valtype || (Valtype = {})); 2187 | // https://webassembly.github.io/spec/core/binary/types.html#binary-blocktype 2188 | var Blocktype; 2189 | (function (Blocktype) { 2190 | Blocktype[Blocktype["void"] = 64] = "void"; 2191 | })(Blocktype || (Blocktype = {})); 2192 | // https://webassembly.github.io/spec/core/binary/instructions.html 2193 | var Opcodes; 2194 | (function (Opcodes) { 2195 | Opcodes[Opcodes["block"] = 2] = "block"; 2196 | Opcodes[Opcodes["loop"] = 3] = "loop"; 2197 | Opcodes[Opcodes["br"] = 12] = "br"; 2198 | Opcodes[Opcodes["br_if"] = 13] = "br_if"; 2199 | Opcodes[Opcodes["end"] = 11] = "end"; 2200 | Opcodes[Opcodes["call"] = 16] = "call"; 2201 | Opcodes[Opcodes["get_local"] = 32] = "get_local"; 2202 | Opcodes[Opcodes["set_local"] = 33] = "set_local"; 2203 | Opcodes[Opcodes["i32_store_8"] = 58] = "i32_store_8"; 2204 | Opcodes[Opcodes["i32_const"] = 65] = "i32_const"; 2205 | Opcodes[Opcodes["f32_const"] = 67] = "f32_const"; 2206 | Opcodes[Opcodes["i32_eqz"] = 69] = "i32_eqz"; 2207 | Opcodes[Opcodes["i32_eq"] = 70] = "i32_eq"; 2208 | Opcodes[Opcodes["f32_eq"] = 91] = "f32_eq"; 2209 | Opcodes[Opcodes["f32_lt"] = 93] = "f32_lt"; 2210 | Opcodes[Opcodes["f32_gt"] = 94] = "f32_gt"; 2211 | Opcodes[Opcodes["i32_and"] = 113] = "i32_and"; 2212 | Opcodes[Opcodes["f32_add"] = 146] = "f32_add"; 2213 | Opcodes[Opcodes["f32_sub"] = 147] = "f32_sub"; 2214 | Opcodes[Opcodes["f32_mul"] = 148] = "f32_mul"; 2215 | Opcodes[Opcodes["f32_div"] = 149] = "f32_div"; 2216 | Opcodes[Opcodes["i32_trunc_f32_s"] = 168] = "i32_trunc_f32_s"; 2217 | })(Opcodes || (Opcodes = {})); 2218 | const binaryOpcode = { 2219 | "+": Opcodes.f32_add, 2220 | "-": Opcodes.f32_sub, 2221 | "*": Opcodes.f32_mul, 2222 | "/": Opcodes.f32_div, 2223 | "==": Opcodes.f32_eq, 2224 | ">": Opcodes.f32_gt, 2225 | "<": Opcodes.f32_lt, 2226 | "&&": Opcodes.i32_and 2227 | }; 2228 | // http://webassembly.github.io/spec/core/binary/modules.html#export-section 2229 | var ExportType; 2230 | (function (ExportType) { 2231 | ExportType[ExportType["func"] = 0] = "func"; 2232 | ExportType[ExportType["table"] = 1] = "table"; 2233 | ExportType[ExportType["mem"] = 2] = "mem"; 2234 | ExportType[ExportType["global"] = 3] = "global"; 2235 | })(ExportType || (ExportType = {})); 2236 | // http://webassembly.github.io/spec/core/binary/types.html#function-types 2237 | const functionType = 0x60; 2238 | const emptyArray = 0x0; 2239 | // https://webassembly.github.io/spec/core/binary/modules.html#binary-module 2240 | const magicModuleHeader = [0x00, 0x61, 0x73, 0x6d]; 2241 | const moduleVersion = [0x01, 0x00, 0x00, 0x00]; 2242 | // https://webassembly.github.io/spec/core/binary/conventions.html#binary-vec 2243 | // Vectors are encoded with their length followed by their element sequence 2244 | const encodeVector = (data) => [ 2245 | ...encoding_1.unsignedLEB128(data.length), 2246 | ...flatten(data) 2247 | ]; 2248 | // https://webassembly.github.io/spec/core/binary/modules.html#code-section 2249 | const encodeLocal = (count, type) => [ 2250 | ...encoding_1.unsignedLEB128(count), 2251 | type 2252 | ]; 2253 | // https://webassembly.github.io/spec/core/binary/modules.html#sections 2254 | // sections are encoded by their type followed by their vector contents 2255 | const createSection = (sectionType, data) => [ 2256 | sectionType, 2257 | ...encodeVector(data) 2258 | ]; 2259 | const codeFromProc = (node, program) => { 2260 | const code = []; 2261 | const symbols = new Map(node.args.map((arg, index) => [arg.value, index])); 2262 | const localIndexForSymbol = (name) => { 2263 | if (!symbols.has(name)) { 2264 | symbols.set(name, symbols.size); 2265 | } 2266 | return symbols.get(name); 2267 | }; 2268 | const emitExpression = (node) => traverse_1.default(node, (node) => { 2269 | switch (node.type) { 2270 | case "numberLiteral": 2271 | code.push(Opcodes.f32_const); 2272 | code.push(...encoding_1.ieee754(node.value)); 2273 | break; 2274 | case "identifier": 2275 | code.push(Opcodes.get_local); 2276 | code.push(...encoding_1.unsignedLEB128(localIndexForSymbol(node.value))); 2277 | break; 2278 | case "binaryExpression": 2279 | code.push(binaryOpcode[node.operator]); 2280 | break; 2281 | } 2282 | }); 2283 | const emitStatements = (statements) => statements.forEach(statement => { 2284 | switch (statement.type) { 2285 | case "printStatement": 2286 | emitExpression(statement.expression); 2287 | code.push(Opcodes.call); 2288 | code.push(...encoding_1.unsignedLEB128(0)); 2289 | break; 2290 | case "variableDeclaration": 2291 | emitExpression(statement.initializer); 2292 | code.push(Opcodes.set_local); 2293 | code.push(...encoding_1.unsignedLEB128(localIndexForSymbol(statement.name))); 2294 | break; 2295 | case "variableAssignment": 2296 | emitExpression(statement.value); 2297 | code.push(Opcodes.set_local); 2298 | code.push(...encoding_1.unsignedLEB128(localIndexForSymbol(statement.name))); 2299 | break; 2300 | case "whileStatement": 2301 | // outer block 2302 | code.push(Opcodes.block); 2303 | code.push(Blocktype.void); 2304 | // inner loop 2305 | code.push(Opcodes.loop); 2306 | code.push(Blocktype.void); 2307 | // compute the while expression 2308 | emitExpression(statement.expression); 2309 | code.push(Opcodes.i32_eqz); 2310 | // br_if $label0 2311 | code.push(Opcodes.br_if); 2312 | code.push(...encoding_1.signedLEB128(1)); 2313 | // the nested logic 2314 | emitStatements(statement.statements); 2315 | // br $label1 2316 | code.push(Opcodes.br); 2317 | code.push(...encoding_1.signedLEB128(0)); 2318 | // end loop 2319 | code.push(Opcodes.end); 2320 | // end block 2321 | code.push(Opcodes.end); 2322 | break; 2323 | case "ifStatement": 2324 | // if block 2325 | code.push(Opcodes.block); 2326 | code.push(Blocktype.void); 2327 | // compute the if expression 2328 | emitExpression(statement.expression); 2329 | code.push(Opcodes.i32_eqz); 2330 | // br_if $label0 2331 | code.push(Opcodes.br_if); 2332 | code.push(...encoding_1.signedLEB128(0)); 2333 | // the nested logic 2334 | emitStatements(statement.consequent); 2335 | // end block 2336 | code.push(Opcodes.end); 2337 | // else block 2338 | code.push(Opcodes.block); 2339 | code.push(Blocktype.void); 2340 | // compute the if expression 2341 | emitExpression(statement.expression); 2342 | code.push(Opcodes.i32_const); 2343 | code.push(...encoding_1.signedLEB128(1)); 2344 | code.push(Opcodes.i32_eq); 2345 | // br_if $label0 2346 | code.push(Opcodes.br_if); 2347 | code.push(...encoding_1.signedLEB128(0)); 2348 | // the nested logic 2349 | emitStatements(statement.alternate); 2350 | // end block 2351 | code.push(Opcodes.end); 2352 | break; 2353 | case "callStatement": 2354 | if (statement.name === "setpixel") { 2355 | // compute and cache the setpixel parameters 2356 | emitExpression(statement.args[0]); 2357 | code.push(Opcodes.set_local); 2358 | code.push(...encoding_1.unsignedLEB128(localIndexForSymbol("x"))); 2359 | emitExpression(statement.args[1]); 2360 | code.push(Opcodes.set_local); 2361 | code.push(...encoding_1.unsignedLEB128(localIndexForSymbol("y"))); 2362 | emitExpression(statement.args[2]); 2363 | code.push(Opcodes.set_local); 2364 | code.push(...encoding_1.unsignedLEB128(localIndexForSymbol("color"))); 2365 | // compute the offset (x * 100) + y 2366 | code.push(Opcodes.get_local); 2367 | code.push(...encoding_1.unsignedLEB128(localIndexForSymbol("y"))); 2368 | code.push(Opcodes.f32_const); 2369 | code.push(...encoding_1.ieee754(100)); 2370 | code.push(Opcodes.f32_mul); 2371 | code.push(Opcodes.get_local); 2372 | code.push(...encoding_1.unsignedLEB128(localIndexForSymbol("x"))); 2373 | code.push(Opcodes.f32_add); 2374 | // convert to an integer 2375 | code.push(Opcodes.i32_trunc_f32_s); 2376 | // fetch the color 2377 | code.push(Opcodes.get_local); 2378 | code.push(...encoding_1.unsignedLEB128(localIndexForSymbol("color"))); 2379 | code.push(Opcodes.i32_trunc_f32_s); 2380 | // write 2381 | code.push(Opcodes.i32_store_8); 2382 | code.push(...[0x00, 0x00]); // align and offset 2383 | } 2384 | else { 2385 | statement.args.forEach(arg => { 2386 | emitExpression(arg); 2387 | }); 2388 | const index = program.findIndex(f => f.name === statement.name); 2389 | code.push(Opcodes.call); 2390 | code.push(...encoding_1.unsignedLEB128(index + 1)); 2391 | } 2392 | break; 2393 | } 2394 | }); 2395 | emitStatements(node.statements); 2396 | const localCount = symbols.size; 2397 | const locals = localCount > 0 ? [encodeLocal(localCount, Valtype.f32)] : []; 2398 | return encodeVector([...encodeVector(locals), ...code, Opcodes.end]); 2399 | }; 2400 | exports.emitter = (ast) => { 2401 | // Function types are vectors of parameters and return types. Currently 2402 | // WebAssembly only supports single return values 2403 | const printFunctionType = [ 2404 | functionType, 2405 | ...encodeVector([Valtype.f32]), 2406 | emptyArray 2407 | ]; 2408 | // TODO: optimise - some of the procs might have the same type signature 2409 | const funcTypes = ast.map(proc => [ 2410 | functionType, 2411 | ...encodeVector(proc.args.map(_ => Valtype.f32)), 2412 | emptyArray 2413 | ]); 2414 | // the type section is a vector of function types 2415 | const typeSection = createSection(Section.type, encodeVector([printFunctionType, ...funcTypes])); 2416 | // the function section is a vector of type indices that indicate the type of each function 2417 | // in the code section 2418 | const funcSection = createSection(Section.func, encodeVector(ast.map((_, index) => index + 1 /* type index */))); 2419 | // the import section is a vector of imported functions 2420 | const printFunctionImport = [ 2421 | ...encoding_1.encodeString("env"), 2422 | ...encoding_1.encodeString("print"), 2423 | ExportType.func, 2424 | 0x00 // type index 2425 | ]; 2426 | const memoryImport = [ 2427 | ...encoding_1.encodeString("env"), 2428 | ...encoding_1.encodeString("memory"), 2429 | ExportType.mem, 2430 | /* limits https://webassembly.github.io/spec/core/binary/types.html#limits - 2431 | indicates a min memory size of one page */ 2432 | 0x00, 2433 | 0x01 2434 | ]; 2435 | const importSection = createSection(Section.import, encodeVector([printFunctionImport, memoryImport])); 2436 | // the export section is a vector of exported functions 2437 | const exportSection = createSection(Section.export, encodeVector([ 2438 | [ 2439 | ...encoding_1.encodeString("run"), 2440 | ExportType.func, 2441 | ast.findIndex(a => a.name === "main") + 1 2442 | ] 2443 | ])); 2444 | // the code section contains vectors of functions 2445 | const codeSection = createSection(Section.code, encodeVector(ast.map(a => codeFromProc(a, ast)))); 2446 | return Uint8Array.from([ 2447 | ...magicModuleHeader, 2448 | ...moduleVersion, 2449 | ...typeSection, 2450 | ...importSection, 2451 | ...funcSection, 2452 | ...exportSection, 2453 | ...codeSection 2454 | ]); 2455 | }; 2456 | 2457 | },{"./encoding":7,"./traverse":12}],7:[function(require,module,exports){ 2458 | (function (Buffer){ 2459 | "use strict"; 2460 | Object.defineProperty(exports, "__esModule", { value: true }); 2461 | exports.ieee754 = (n) => { 2462 | const buf = Buffer.allocUnsafe(4); 2463 | buf.writeFloatLE(n, 0); 2464 | return Uint8Array.from(buf); 2465 | }; 2466 | exports.encodeString = (str) => [ 2467 | str.length, 2468 | ...str.split("").map(s => s.charCodeAt(0)) 2469 | ]; 2470 | exports.signedLEB128 = (n) => { 2471 | const buffer = []; 2472 | let more = true; 2473 | while (more) { 2474 | let byte = n & 0x7f; 2475 | n >>>= 7; 2476 | if ((n === 0 && (byte & 0x40) === 0) || (n === -1 && (byte & 0x40) !== 0)) { 2477 | more = false; 2478 | } 2479 | else { 2480 | byte |= 0x80; 2481 | } 2482 | buffer.push(byte); 2483 | } 2484 | return buffer; 2485 | }; 2486 | exports.unsignedLEB128 = (n) => { 2487 | const buffer = []; 2488 | do { 2489 | let byte = n & 0x7f; 2490 | n >>>= 7; 2491 | if (n !== 0) { 2492 | byte |= 0x80; 2493 | } 2494 | buffer.push(byte); 2495 | } while (n !== 0); 2496 | return buffer; 2497 | }; 2498 | 2499 | }).call(this,require("buffer").Buffer) 2500 | },{"buffer":3}],8:[function(require,module,exports){ 2501 | "use strict"; 2502 | Object.defineProperty(exports, "__esModule", { value: true }); 2503 | const tokenizer_1 = require("./tokenizer"); 2504 | const parser_1 = require("./parser"); 2505 | const transformer_1 = require("./transformer"); 2506 | const applyOperator = (operator, left, right) => { 2507 | switch (operator) { 2508 | case "+": 2509 | return left + right; 2510 | case "-": 2511 | return left - right; 2512 | case "*": 2513 | return left * right; 2514 | case "/": 2515 | return left / right; 2516 | case "==": 2517 | return left == right ? 1 : 0; 2518 | case ">": 2519 | return left > right ? 1 : 0; 2520 | case "<": 2521 | return left < right ? 1 : 0; 2522 | case "&&": 2523 | return left && right; 2524 | } 2525 | throw Error(`Unknown binary operator ${operator}`); 2526 | }; 2527 | const executeProc = (node, env, program, args = []) => { 2528 | const symbols = new Map(node.args.map((arg, index) => [arg.value, args[index]])); 2529 | const evaluateExpression = (expression) => { 2530 | switch (expression.type) { 2531 | case "numberLiteral": 2532 | return expression.value; 2533 | case "binaryExpression": 2534 | return applyOperator(expression.operator, evaluateExpression(expression.left), evaluateExpression(expression.right)); 2535 | case "identifier": 2536 | return symbols.get(expression.value); 2537 | } 2538 | }; 2539 | const executeStatements = (statements) => { 2540 | statements.forEach(statement => { 2541 | switch (statement.type) { 2542 | case "printStatement": 2543 | env.print(evaluateExpression(statement.expression)); 2544 | break; 2545 | case "variableDeclaration": 2546 | symbols.set(statement.name, evaluateExpression(statement.initializer)); 2547 | break; 2548 | case "variableAssignment": 2549 | symbols.set(statement.name, evaluateExpression(statement.value)); 2550 | break; 2551 | case "whileStatement": 2552 | while (evaluateExpression(statement.expression)) { 2553 | executeStatements(statement.statements); 2554 | } 2555 | break; 2556 | case "ifStatement": 2557 | if (evaluateExpression(statement.expression)) { 2558 | executeStatements(statement.consequent); 2559 | } 2560 | else { 2561 | executeStatements(statement.alternate); 2562 | } 2563 | break; 2564 | case "callStatement": 2565 | if (statement.name === "setpixel") { 2566 | const x = evaluateExpression(statement.args[0]); 2567 | const y = evaluateExpression(statement.args[1]); 2568 | const color = evaluateExpression(statement.args[2]); 2569 | env.display[y * 100 + x] = color; 2570 | } 2571 | else { 2572 | const procName = statement.name; 2573 | const argValues = statement.args.map(arg => evaluateExpression(arg)); 2574 | const proc = program.find(f => f.name === procName); 2575 | executeProc(proc, env, program, argValues); 2576 | } 2577 | break; 2578 | } 2579 | }); 2580 | }; 2581 | executeStatements(node.statements); 2582 | }; 2583 | exports.runtime = async (src, env) => () => { 2584 | const tokens = tokenizer_1.tokenize(src); 2585 | const program = parser_1.parse(tokens); 2586 | const transformedProgram = transformer_1.transformer(program); 2587 | const main = transformedProgram.find(f => f.name === "main"); 2588 | executeProc(main, env, transformedProgram); 2589 | }; 2590 | 2591 | },{"./parser":9,"./tokenizer":10,"./transformer":11}],9:[function(require,module,exports){ 2592 | "use strict"; 2593 | Object.defineProperty(exports, "__esModule", { value: true }); 2594 | class ParserError extends Error { 2595 | constructor(message, token) { 2596 | super(message); 2597 | this.token = token; 2598 | } 2599 | } 2600 | exports.ParserError = ParserError; 2601 | const asOperator = (value) => { 2602 | // TODO: check it really is an operator 2603 | return value; 2604 | }; 2605 | exports.parse = tokens => { 2606 | const tokenIterator = tokens[Symbol.iterator](); 2607 | let currentToken = tokenIterator.next().value; 2608 | let nextToken = tokenIterator.next().value; 2609 | const currentTokenIsKeyword = (name) => currentToken.value === name && currentToken.type === "keyword"; 2610 | const eatToken = (value) => { 2611 | if (value && value !== currentToken.value) { 2612 | throw new ParserError(`Unexpected token value, expected ${value}, received ${currentToken.value}`, currentToken); 2613 | } 2614 | currentToken = nextToken; 2615 | nextToken = tokenIterator.next().value; 2616 | }; 2617 | const parseExpression = () => { 2618 | let node; 2619 | switch (currentToken.type) { 2620 | case "number": 2621 | node = { 2622 | type: "numberLiteral", 2623 | value: Number(currentToken.value) 2624 | }; 2625 | eatToken(); 2626 | return node; 2627 | case "identifier": 2628 | node = { type: "identifier", value: currentToken.value }; 2629 | eatToken(); 2630 | return node; 2631 | case "parens": 2632 | eatToken("("); 2633 | const left = parseExpression(); 2634 | const operator = currentToken.value; 2635 | eatToken(); 2636 | const right = parseExpression(); 2637 | eatToken(")"); 2638 | return { 2639 | type: "binaryExpression", 2640 | left, 2641 | right, 2642 | operator: asOperator(operator) 2643 | }; 2644 | default: 2645 | throw new ParserError(`Unexpected token type ${currentToken.type}`, currentToken); 2646 | } 2647 | }; 2648 | const parsePrintStatement = () => { 2649 | eatToken("print"); 2650 | return { 2651 | type: "printStatement", 2652 | expression: parseExpression() 2653 | }; 2654 | }; 2655 | const parseIfStatement = () => { 2656 | eatToken("if"); 2657 | const expression = parseExpression(); 2658 | let elseStatements = false; 2659 | const consequent = []; 2660 | const alternate = []; 2661 | while (!currentTokenIsKeyword("endif")) { 2662 | if (currentTokenIsKeyword("else")) { 2663 | eatToken("else"); 2664 | elseStatements = true; 2665 | } 2666 | if (elseStatements) { 2667 | alternate.push(parseStatement()); 2668 | } 2669 | else { 2670 | consequent.push(parseStatement()); 2671 | } 2672 | } 2673 | eatToken("endif"); 2674 | return { type: "ifStatement", expression, consequent, alternate }; 2675 | }; 2676 | const parseWhileStatement = () => { 2677 | eatToken("while"); 2678 | const expression = parseExpression(); 2679 | const statements = []; 2680 | while (!currentTokenIsKeyword("endwhile")) { 2681 | statements.push(parseStatement()); 2682 | } 2683 | eatToken("endwhile"); 2684 | return { type: "whileStatement", expression, statements }; 2685 | }; 2686 | const parseVariableAssignment = () => { 2687 | const name = currentToken.value; 2688 | eatToken(); 2689 | eatToken("="); 2690 | return { type: "variableAssignment", name, value: parseExpression() }; 2691 | }; 2692 | const parseVariableDeclarationStatement = () => { 2693 | eatToken("var"); 2694 | const name = currentToken.value; 2695 | eatToken(); 2696 | eatToken("="); 2697 | return { 2698 | type: "variableDeclaration", 2699 | name, 2700 | initializer: parseExpression() 2701 | }; 2702 | }; 2703 | const parseCallStatementNode = () => { 2704 | const name = currentToken.value; 2705 | eatToken(); 2706 | const args = parseCommaSeperatedList(() => parseExpression()); 2707 | return { 2708 | type: "callStatement", 2709 | name, 2710 | args 2711 | }; 2712 | }; 2713 | function parseCommaSeperatedList(foo) { 2714 | const args = []; 2715 | eatToken("("); 2716 | while (currentToken.value !== ")") { 2717 | args.push(foo()); 2718 | if (currentToken.value !== ")") { 2719 | eatToken(","); 2720 | } 2721 | } 2722 | eatToken(")"); 2723 | return args; 2724 | } 2725 | const parseProcStatement = () => { 2726 | eatToken("proc"); 2727 | const name = currentToken.value; 2728 | eatToken(); 2729 | const args = parseCommaSeperatedList(() => { 2730 | const arg = { type: "identifier", value: currentToken.value }; 2731 | eatToken(); 2732 | return arg; 2733 | }); 2734 | const statements = []; 2735 | while (!currentTokenIsKeyword("endproc")) { 2736 | statements.push(parseStatement()); 2737 | } 2738 | eatToken("endproc"); 2739 | return { 2740 | type: "procStatement", 2741 | name, 2742 | args, 2743 | statements 2744 | }; 2745 | }; 2746 | const parseStatement = () => { 2747 | if (currentToken.type === "keyword") { 2748 | switch (currentToken.value) { 2749 | case "print": 2750 | return parsePrintStatement(); 2751 | case "var": 2752 | return parseVariableDeclarationStatement(); 2753 | case "while": 2754 | return parseWhileStatement(); 2755 | case "if": 2756 | return parseIfStatement(); 2757 | case "proc": 2758 | return parseProcStatement(); 2759 | default: 2760 | throw new ParserError(`Unknown keyword ${currentToken.value}`, currentToken); 2761 | } 2762 | } 2763 | else if (currentToken.type === "identifier") { 2764 | if (nextToken.value === "=") { 2765 | return parseVariableAssignment(); 2766 | } 2767 | else { 2768 | return parseCallStatementNode(); 2769 | } 2770 | } 2771 | }; 2772 | const nodes = []; 2773 | while (currentToken) { 2774 | nodes.push(parseStatement()); 2775 | } 2776 | return nodes; 2777 | }; 2778 | 2779 | },{}],10:[function(require,module,exports){ 2780 | "use strict"; 2781 | Object.defineProperty(exports, "__esModule", { value: true }); 2782 | exports.keywords = [ 2783 | "print", 2784 | "var", 2785 | "while", 2786 | "endwhile", 2787 | "if", 2788 | "endif", 2789 | "else", 2790 | "proc", 2791 | "endproc" 2792 | ]; 2793 | exports.operators = ["+", "-", "*", "/", "==", "<", ">", "&&", ","]; 2794 | const escapeRegEx = (text) => text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); 2795 | class TokenizerError extends Error { 2796 | constructor(message, index) { 2797 | super(message); 2798 | this.index = index; 2799 | } 2800 | } 2801 | exports.TokenizerError = TokenizerError; 2802 | // returns a token if the given regex matches at the current index 2803 | const regexMatcher = (regex, type) => (input, index) => { 2804 | const match = input.substring(index).match(regex); 2805 | return (match && { 2806 | type, 2807 | value: match[0] 2808 | }); 2809 | }; 2810 | // matchers in precedence order 2811 | const matchers = [ 2812 | regexMatcher("^-?[.0-9]+([eE]-?[0-9]{2})?", "number"), 2813 | regexMatcher(`^(${exports.keywords.join("|")})`, "keyword"), 2814 | regexMatcher("^\\s+", "whitespace"), 2815 | regexMatcher(`^(${exports.operators.map(escapeRegEx).join("|")})`, "operator"), 2816 | regexMatcher(`^[a-zA-Z]+`, "identifier"), 2817 | regexMatcher(`^=`, "assignment"), 2818 | regexMatcher("^[()]{1}", "parens") 2819 | ]; 2820 | const locationForIndex = (input, index) => ({ 2821 | char: index - input.lastIndexOf("\n", index) - 1, 2822 | line: input.substring(0, index).split("\n").length - 1 2823 | }); 2824 | exports.tokenize = input => { 2825 | const tokens = []; 2826 | let index = 0; 2827 | while (index < input.length) { 2828 | const matches = matchers.map(m => m(input, index)).filter(f => f); 2829 | if (matches.length > 0) { 2830 | // take the highest priority match 2831 | const match = matches[0]; 2832 | if (match.type !== "whitespace") { 2833 | tokens.push(Object.assign({}, match, locationForIndex(input, index))); 2834 | } 2835 | index += match.value.length; 2836 | } 2837 | else { 2838 | throw new TokenizerError(`Unexpected token ${input.substring(index, index + 1)}`, index); 2839 | } 2840 | } 2841 | return tokens; 2842 | }; 2843 | 2844 | },{}],11:[function(require,module,exports){ 2845 | "use strict"; 2846 | Object.defineProperty(exports, "__esModule", { value: true }); 2847 | exports.transformer = (ast) => { 2848 | // do we have a main proc? 2849 | if (!ast.find(a => a.type === "procStatement" && a.name === "main")) { 2850 | // if not - collect up any 'free' statements and add one. 2851 | const freeStatements = ast.filter(a => a.type !== "procStatement"); 2852 | const mainProc = { 2853 | type: "procStatement", 2854 | name: "main", 2855 | args: [], 2856 | statements: freeStatements 2857 | }; 2858 | ast = [mainProc, ...ast.filter(a => a.type === "procStatement")]; 2859 | } 2860 | return ast.map(a => a); 2861 | }; 2862 | 2863 | },{}],12:[function(require,module,exports){ 2864 | "use strict"; 2865 | Object.defineProperty(exports, "__esModule", { value: true }); 2866 | // post order ast walker 2867 | const traverse = (nodes, visitor) => { 2868 | nodes = Array.isArray(nodes) ? nodes : [nodes]; 2869 | nodes.forEach(node => { 2870 | Object.keys(node).forEach((prop) => { 2871 | const value = node[prop]; 2872 | const valueAsArray = Array.isArray(value) ? value : [value]; 2873 | valueAsArray.forEach((childNode) => { 2874 | if (typeof childNode.type === "string") { 2875 | traverse(childNode, visitor); 2876 | } 2877 | }); 2878 | }); 2879 | visitor(node); 2880 | }); 2881 | }; 2882 | exports.default = traverse; 2883 | 2884 | },{}]},{},[1]); 2885 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 33 | 34 | 35 | 36 | 37 | 39 | 41 | 43 | 44 | 45 | 46 | 47 | 48 | 67 | 68 | 69 | 94 | 95 |
96 |
97 |
98 | Code 99 | 128 |
129 |
130 | Console output 131 | 137 |
138 |
139 | Canvas output 140 | 141 |
142 |
143 |
144 | 145 |

chasm is a very simple programming language which I developed to accompany a talk at 146 | FullStack Conference NYC. It is not intended to be a fully featured language; rather, its purpose is two-fold:

147 | 148 |

149 |

    150 |
  1. Introduce the basic building blocks of compilers - and show that they aren't that scary or difficult!
  2. 151 |
  3. Reveal some of the inner working of WebAssembly, a new and exciting language / runtime that I think people need to 152 | know more about.
  4. 153 |
154 |

155 | 156 |

The chasm compiler is written in TypeScript, you can try it out using the above editor. When you click either of the 'run' buttons above, the code is tokenised then parsed into an Abstract Syntax Tree (AST). If you use the interpreter this AST is executed by a JavaScript interpreter. If you use the compiler, this AST is compiled to a WebAssembly module then executed via the browser's WebAssembly runtime. 157 |

158 | 159 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /docs/index.ts: -------------------------------------------------------------------------------- 1 | declare var CodeMirror: any; 2 | declare var $: any; 3 | 4 | const interpreterRuntime = require("../src/interpreter").runtime; 5 | const compilerRuntime = require("../src/compiler").runtime; 6 | const { keywords, operators } = require("../src/tokenizer"); 7 | 8 | const compileButton = document.getElementById("compile"); 9 | const interpretButton = document.getElementById("interpret"); 10 | const codeArea = document.getElementById("code") as HTMLTextAreaElement; 11 | const outputArea = document.getElementById("output") as HTMLTextAreaElement; 12 | const canvas = document.getElementById("canvas") as HTMLCanvasElement; 13 | const shareUrl = document.getElementById("shareUrl") as HTMLInputElement; 14 | const copyUrl = document.getElementById("copyUrl") as HTMLInputElement; 15 | 16 | if (window.location.hash) { 17 | const encoded = window.location.href.split("#")[1]; 18 | codeArea.value = atob(decodeURIComponent(encoded)); 19 | } 20 | 21 | // quick and dirty image data scaling 22 | // see: https://stackoverflow.com/questions/3448347/how-to-scale-an-imagedata-in-html-canvas 23 | const scaleImageData = ( 24 | imageData: ImageData, 25 | scale: number, 26 | ctx: CanvasRenderingContext2D 27 | ) => { 28 | const scaled = ctx.createImageData( 29 | imageData.width * scale, 30 | imageData.height * scale 31 | ); 32 | const subLine = ctx.createImageData(scale, 1).data; 33 | for (let row = 0; row < imageData.height; row++) { 34 | for (let col = 0; col < imageData.width; col++) { 35 | const sourcePixel = imageData.data.subarray( 36 | (row * imageData.width + col) * 4, 37 | (row * imageData.width + col) * 4 + 4 38 | ); 39 | for (let x = 0; x < scale; x++) subLine.set(sourcePixel, x * 4); 40 | for (let y = 0; y < scale; y++) { 41 | const destRow = row * scale + y; 42 | const destCol = col * scale; 43 | scaled.data.set(subLine, (destRow * scaled.width + destCol) * 4); 44 | } 45 | } 46 | } 47 | return scaled; 48 | }; 49 | 50 | CodeMirror.defineSimpleMode("simplemode", { 51 | start: [ 52 | { 53 | regex: new RegExp(`(${keywords.join("|")})`), 54 | token: "keyword" 55 | }, 56 | { 57 | regex: /0x[a-f\d]+|[-+]?(?:\.\d+|\d+\.?\d*)(?:e[-+]?\d+)?/i, 58 | token: "number" 59 | }, 60 | { regex: /[-+\/*=<>!]+/, token: "operator" }, 61 | { regex: /[a-z$][\w$]*/, token: "variable" } 62 | ] 63 | }); 64 | 65 | const editor = CodeMirror.fromTextArea(codeArea, { 66 | mode: "simplemode", 67 | theme: "abcdef", 68 | lineNumbers: true 69 | }); 70 | 71 | $("#shareModal").on("show.bs.modal", () => { 72 | const baseUrl = window.location.href.split("#")[0]; 73 | const code = encodeURIComponent(btoa(editor.getValue())); 74 | shareUrl.value = `${baseUrl}#${code}`; 75 | }); 76 | 77 | copyUrl.addEventListener("click", () => 78 | navigator.clipboard.writeText(shareUrl.value) 79 | ); 80 | 81 | const sleep = async (ms: number) => 82 | await new Promise(resolve => setTimeout(resolve, ms)); 83 | 84 | let marker: any; 85 | 86 | const logMessage = (message: string | number) => 87 | (outputArea.value = outputArea.value + message + "\n"); 88 | 89 | const markError = (token: Token) => { 90 | marker = editor.markText( 91 | { line: token.line, ch: token.char }, 92 | { line: token.line, ch: token.char + token.value.length }, 93 | { className: "error" } 94 | ); 95 | }; 96 | 97 | const updateCanvas = (display: Uint8Array) => { 98 | const context = canvas.getContext("2d"); 99 | const imgData = context.createImageData(100, 100); 100 | for (let i = 0; i < 100 * 100; i++) { 101 | imgData.data[i * 4] = display[i]; 102 | imgData.data[i * 4 + 1] = display[i]; 103 | imgData.data[i * 4 + 2] = display[i]; 104 | imgData.data[i * 4 + 3] = 255; 105 | } 106 | const data = scaleImageData(imgData, 3, context); 107 | context.putImageData(data, 0, 0); 108 | }; 109 | 110 | const run = async (runtime: Runtime) => { 111 | if (marker) { 112 | marker.clear(); 113 | } 114 | 115 | await sleep(10); 116 | 117 | let tickFunction: TickFunction; 118 | 119 | try { 120 | const display = new Uint8Array(10000); 121 | tickFunction = await runtime(editor.getValue(), { 122 | print: logMessage, 123 | display 124 | }); 125 | 126 | outputArea.value = ""; 127 | logMessage(`Executing ... `); 128 | 129 | tickFunction(); 130 | updateCanvas(display); 131 | 132 | interpretButton.classList.remove("active"); 133 | compileButton.classList.remove("active"); 134 | } catch (error) { 135 | logMessage(error.message); 136 | markError(error.token); 137 | } 138 | }; 139 | 140 | interpretButton.addEventListener("click", async () => { 141 | interpretButton.classList.add("active"); 142 | compileButton.classList.remove("active"); 143 | await run(interpreterRuntime); 144 | }); 145 | 146 | compileButton.addEventListener("click", async () => { 147 | compileButton.classList.add("active"); 148 | interpretButton.classList.remove("active"); 149 | await run(compilerRuntime); 150 | }); 151 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "ts-jest", 3 | testEnvironment: "node" 4 | }; 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chasm", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "author": "Colin Eberhardt ", 6 | "license": "MIT", 7 | "devDependencies": { 8 | "@types/jest": "^24.0.11", 9 | "@types/node": "^11.13.4", 10 | "@types/webassembly-js-api": "^0.0.3", 11 | "browserify": "^16.2.3", 12 | "jest": "^24.7.1", 13 | "ts-jest": "^24.0.2", 14 | "tsify": "^4.0.1", 15 | "typescript": "^3.4.3" 16 | }, 17 | "scripts": { 18 | "test": "yarn run test:tsc && yarn run test:jest", 19 | "test:tsc": "tsc --noEmit", 20 | "test:jest": "jest", 21 | "test:watch": "jest --watch", 22 | "bundle": "browserify docs/index.ts -p [ tsify ] > docs/bundle.js" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/compiler.ts: -------------------------------------------------------------------------------- 1 | import { emitter } from "./emitter"; 2 | import { tokenize } from "./tokenizer"; 3 | import { parse } from "./parser"; 4 | import { transformer } from "./transformer"; 5 | 6 | export const compile: Compiler = src => { 7 | const tokens = tokenize(src); 8 | const ast = parse(tokens); 9 | const transformedAst = transformer(ast); 10 | const wasm = emitter(transformedAst); 11 | return wasm; 12 | }; 13 | 14 | export const runtime: Runtime = async (src, env) => { 15 | const wasm = compile(src); 16 | const memory = new WebAssembly.Memory({ initial: 1 }); 17 | const result: any = await WebAssembly.instantiate(wasm, { 18 | env: { ...env, memory } 19 | }); 20 | return () => { 21 | result.instance.exports.run(); 22 | env.display.set(new Uint8Array(memory.buffer, 0, 10000)); 23 | }; 24 | }; -------------------------------------------------------------------------------- /src/emitter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | unsignedLEB128, 3 | signedLEB128, 4 | encodeString, 5 | ieee754 6 | } from "./encoding"; 7 | import traverse from "./traverse"; 8 | 9 | const flatten = (arr: any[]) => [].concat.apply([], arr); 10 | 11 | // https://webassembly.github.io/spec/core/binary/modules.html#sections 12 | enum Section { 13 | custom = 0, 14 | type = 1, 15 | import = 2, 16 | func = 3, 17 | table = 4, 18 | memory = 5, 19 | global = 6, 20 | export = 7, 21 | start = 8, 22 | element = 9, 23 | code = 10, 24 | data = 11 25 | } 26 | 27 | // https://webassembly.github.io/spec/core/binary/types.html 28 | enum Valtype { 29 | i32 = 0x7f, 30 | f32 = 0x7d 31 | } 32 | 33 | // https://webassembly.github.io/spec/core/binary/types.html#binary-blocktype 34 | enum Blocktype { 35 | void = 0x40 36 | } 37 | 38 | // https://webassembly.github.io/spec/core/binary/instructions.html 39 | enum Opcodes { 40 | block = 0x02, 41 | loop = 0x03, 42 | br = 0x0c, 43 | br_if = 0x0d, 44 | end = 0x0b, 45 | call = 0x10, 46 | get_local = 0x20, 47 | set_local = 0x21, 48 | i32_store_8 = 0x3a, 49 | i32_const = 0x41, 50 | f32_const = 0x43, 51 | i32_eqz = 0x45, 52 | i32_eq = 0x46, 53 | f32_eq = 0x5b, 54 | f32_lt = 0x5d, 55 | f32_gt = 0x5e, 56 | i32_and = 0x71, 57 | f32_add = 0x92, 58 | f32_sub = 0x93, 59 | f32_mul = 0x94, 60 | f32_div = 0x95, 61 | i32_trunc_f32_s = 0xa8 62 | } 63 | 64 | const binaryOpcode = { 65 | "+": Opcodes.f32_add, 66 | "-": Opcodes.f32_sub, 67 | "*": Opcodes.f32_mul, 68 | "/": Opcodes.f32_div, 69 | "==": Opcodes.f32_eq, 70 | ">": Opcodes.f32_gt, 71 | "<": Opcodes.f32_lt, 72 | "&&": Opcodes.i32_and 73 | }; 74 | 75 | // http://webassembly.github.io/spec/core/binary/modules.html#export-section 76 | enum ExportType { 77 | func = 0x00, 78 | table = 0x01, 79 | mem = 0x02, 80 | global = 0x03 81 | } 82 | 83 | // http://webassembly.github.io/spec/core/binary/types.html#function-types 84 | const functionType = 0x60; 85 | 86 | const emptyArray = 0x0; 87 | 88 | // https://webassembly.github.io/spec/core/binary/modules.html#binary-module 89 | const magicModuleHeader = [0x00, 0x61, 0x73, 0x6d]; 90 | const moduleVersion = [0x01, 0x00, 0x00, 0x00]; 91 | 92 | // https://webassembly.github.io/spec/core/binary/conventions.html#binary-vec 93 | // Vectors are encoded with their length followed by their element sequence 94 | const encodeVector = (data: any[]) => [ 95 | ...unsignedLEB128(data.length), 96 | ...flatten(data) 97 | ]; 98 | 99 | // https://webassembly.github.io/spec/core/binary/modules.html#code-section 100 | const encodeLocal = (count: number, type: Valtype) => [ 101 | ...unsignedLEB128(count), 102 | type 103 | ]; 104 | 105 | // https://webassembly.github.io/spec/core/binary/modules.html#sections 106 | // sections are encoded by their type followed by their vector contents 107 | const createSection = (sectionType: Section, data: any[]) => [ 108 | sectionType, 109 | ...encodeVector(data) 110 | ]; 111 | 112 | const codeFromProc = (node: ProcStatementNode, program: TransformedProgram) => { 113 | const code: number[] = []; 114 | 115 | const symbols = new Map( 116 | node.args.map((arg, index) => [arg.value, index]) 117 | ); 118 | 119 | const localIndexForSymbol = (name: string) => { 120 | if (!symbols.has(name)) { 121 | symbols.set(name, symbols.size); 122 | } 123 | return symbols.get(name); 124 | }; 125 | 126 | const emitExpression = (node: ExpressionNode) => 127 | traverse(node, (node: ExpressionNode) => { 128 | switch (node.type) { 129 | case "numberLiteral": 130 | code.push(Opcodes.f32_const); 131 | code.push(...ieee754(node.value)); 132 | break; 133 | case "identifier": 134 | code.push(Opcodes.get_local); 135 | code.push(...unsignedLEB128(localIndexForSymbol(node.value))); 136 | break; 137 | case "binaryExpression": 138 | code.push(binaryOpcode[node.operator]); 139 | break; 140 | } 141 | }); 142 | 143 | const emitStatements = (statements: StatementNode[]) => 144 | statements.forEach(statement => { 145 | switch (statement.type) { 146 | case "printStatement": 147 | emitExpression(statement.expression); 148 | code.push(Opcodes.call); 149 | code.push(...unsignedLEB128(0)); 150 | break; 151 | case "variableDeclaration": 152 | emitExpression(statement.initializer); 153 | code.push(Opcodes.set_local); 154 | code.push(...unsignedLEB128(localIndexForSymbol(statement.name))); 155 | break; 156 | case "variableAssignment": 157 | emitExpression(statement.value); 158 | code.push(Opcodes.set_local); 159 | code.push(...unsignedLEB128(localIndexForSymbol(statement.name))); 160 | break; 161 | case "whileStatement": 162 | // outer block 163 | code.push(Opcodes.block); 164 | code.push(Blocktype.void); 165 | // inner loop 166 | code.push(Opcodes.loop); 167 | code.push(Blocktype.void); 168 | // compute the while expression 169 | emitExpression(statement.expression); 170 | code.push(Opcodes.i32_eqz); 171 | // br_if $label0 172 | code.push(Opcodes.br_if); 173 | code.push(...signedLEB128(1)); 174 | // the nested logic 175 | emitStatements(statement.statements); 176 | // br $label1 177 | code.push(Opcodes.br); 178 | code.push(...signedLEB128(0)); 179 | // end loop 180 | code.push(Opcodes.end); 181 | // end block 182 | code.push(Opcodes.end); 183 | break; 184 | case "ifStatement": 185 | // if block 186 | code.push(Opcodes.block); 187 | code.push(Blocktype.void); 188 | // compute the if expression 189 | emitExpression(statement.expression); 190 | code.push(Opcodes.i32_eqz); 191 | // br_if $label0 192 | code.push(Opcodes.br_if); 193 | code.push(...signedLEB128(0)); 194 | // the nested logic 195 | emitStatements(statement.consequent); 196 | // end block 197 | code.push(Opcodes.end); 198 | 199 | // else block 200 | code.push(Opcodes.block); 201 | code.push(Blocktype.void); 202 | // compute the if expression 203 | emitExpression(statement.expression); 204 | code.push(Opcodes.i32_const); 205 | code.push(...signedLEB128(1)); 206 | code.push(Opcodes.i32_eq); 207 | // br_if $label0 208 | code.push(Opcodes.br_if); 209 | code.push(...signedLEB128(0)); 210 | // the nested logic 211 | emitStatements(statement.alternate); 212 | // end block 213 | code.push(Opcodes.end); 214 | break; 215 | case "callStatement": 216 | if (statement.name === "setpixel") { 217 | // compute and cache the setpixel parameters 218 | emitExpression(statement.args[0]); 219 | code.push(Opcodes.set_local); 220 | code.push(...unsignedLEB128(localIndexForSymbol("x"))); 221 | 222 | emitExpression(statement.args[1]); 223 | code.push(Opcodes.set_local); 224 | code.push(...unsignedLEB128(localIndexForSymbol("y"))); 225 | 226 | emitExpression(statement.args[2]); 227 | code.push(Opcodes.set_local); 228 | code.push(...unsignedLEB128(localIndexForSymbol("color"))); 229 | 230 | // compute the offset (x * 100) + y 231 | code.push(Opcodes.get_local); 232 | code.push(...unsignedLEB128(localIndexForSymbol("y"))); 233 | code.push(Opcodes.f32_const); 234 | code.push(...ieee754(100)); 235 | code.push(Opcodes.f32_mul); 236 | 237 | code.push(Opcodes.get_local); 238 | code.push(...unsignedLEB128(localIndexForSymbol("x"))); 239 | code.push(Opcodes.f32_add); 240 | 241 | // convert to an integer 242 | code.push(Opcodes.i32_trunc_f32_s); 243 | 244 | // fetch the color 245 | code.push(Opcodes.get_local); 246 | code.push(...unsignedLEB128(localIndexForSymbol("color"))); 247 | code.push(Opcodes.i32_trunc_f32_s); 248 | 249 | // write 250 | code.push(Opcodes.i32_store_8); 251 | code.push(...[0x00, 0x00]); // align and offset 252 | } else { 253 | statement.args.forEach(arg => { 254 | emitExpression(arg); 255 | }); 256 | const index = program.findIndex(f => f.name === statement.name); 257 | code.push(Opcodes.call); 258 | code.push(...unsignedLEB128(index + 1)); 259 | } 260 | break; 261 | } 262 | }); 263 | 264 | emitStatements(node.statements); 265 | 266 | const localCount = symbols.size; 267 | const locals = localCount > 0 ? [encodeLocal(localCount, Valtype.f32)] : []; 268 | 269 | return encodeVector([...encodeVector(locals), ...code, Opcodes.end]); 270 | }; 271 | 272 | export const emitter: Emitter = (ast: TransformedProgram) => { 273 | // Function types are vectors of parameters and return types. Currently 274 | // WebAssembly only supports single return values 275 | const printFunctionType = [ 276 | functionType, 277 | ...encodeVector([Valtype.f32]), 278 | emptyArray 279 | ]; 280 | 281 | // TODO: optimise - some of the procs might have the same type signature 282 | const funcTypes = ast.map(proc => [ 283 | functionType, 284 | ...encodeVector(proc.args.map(_ => Valtype.f32)), 285 | emptyArray 286 | ]); 287 | 288 | // the type section is a vector of function types 289 | const typeSection = createSection( 290 | Section.type, 291 | encodeVector([printFunctionType, ...funcTypes]) 292 | ); 293 | 294 | // the function section is a vector of type indices that indicate the type of each function 295 | // in the code section 296 | const funcSection = createSection( 297 | Section.func, 298 | encodeVector(ast.map((_, index) => index + 1 /* type index */)) 299 | ); 300 | 301 | // the import section is a vector of imported functions 302 | const printFunctionImport = [ 303 | ...encodeString("env"), 304 | ...encodeString("print"), 305 | ExportType.func, 306 | 0x00 // type index 307 | ]; 308 | 309 | const memoryImport = [ 310 | ...encodeString("env"), 311 | ...encodeString("memory"), 312 | ExportType.mem, 313 | /* limits https://webassembly.github.io/spec/core/binary/types.html#limits - 314 | indicates a min memory size of one page */ 315 | 0x00, 316 | 0x01 317 | ]; 318 | 319 | const importSection = createSection( 320 | Section.import, 321 | encodeVector([printFunctionImport, memoryImport]) 322 | ); 323 | 324 | // the export section is a vector of exported functions 325 | const exportSection = createSection( 326 | Section.export, 327 | encodeVector([ 328 | [ 329 | ...encodeString("run"), 330 | ExportType.func, 331 | ast.findIndex(a => a.name === "main") + 1 332 | ] 333 | ]) 334 | ); 335 | 336 | // the code section contains vectors of functions 337 | const codeSection = createSection( 338 | Section.code, 339 | encodeVector(ast.map(a => codeFromProc(a, ast))) 340 | ); 341 | 342 | return Uint8Array.from([ 343 | ...magicModuleHeader, 344 | ...moduleVersion, 345 | ...typeSection, 346 | ...importSection, 347 | ...funcSection, 348 | ...exportSection, 349 | ...codeSection 350 | ]); 351 | }; 352 | -------------------------------------------------------------------------------- /src/encoding.ts: -------------------------------------------------------------------------------- 1 | export const ieee754 = (n: number) => { 2 | const buf = Buffer.allocUnsafe(4); 3 | buf.writeFloatLE(n, 0); 4 | return Uint8Array.from(buf); 5 | }; 6 | 7 | export const encodeString = (str: string) => [ 8 | str.length, 9 | ...str.split("").map(s => s.charCodeAt(0)) 10 | ]; 11 | 12 | export const signedLEB128 = (n: number) => { 13 | const buffer = []; 14 | let more = true; 15 | const isNegative = n < 0; 16 | const bitCount = Math.ceil(Math.log2(Math.abs(n))) + 1; 17 | while (more) { 18 | let byte = n & 0x7f; 19 | n >>= 7; 20 | if (isNegative) { 21 | n = n | -(1 << (bitCount - 8)); 22 | } 23 | if ((n === 0 && (byte & 0x40) === 0) || (n === -1 && (byte & 0x40) !== 0x40)) { 24 | more = false; 25 | } else { 26 | byte |= 0x80; 27 | } 28 | buffer.push(byte); 29 | } 30 | return buffer; 31 | }; 32 | 33 | export const unsignedLEB128 = (n: number) => { 34 | const buffer = []; 35 | do { 36 | let byte = n & 0x7f; 37 | n >>>= 7; 38 | if (n !== 0) { 39 | byte |= 0x80; 40 | } 41 | buffer.push(byte); 42 | } while (n !== 0); 43 | return buffer; 44 | }; 45 | -------------------------------------------------------------------------------- /src/interpreter.ts: -------------------------------------------------------------------------------- 1 | import { tokenize } from "./tokenizer"; 2 | import { parse } from "./parser"; 3 | import { transformer } from "./transformer"; 4 | 5 | const applyOperator = (operator: string, left: number, right: number) => { 6 | switch (operator) { 7 | case "+": 8 | return left + right; 9 | case "-": 10 | return left - right; 11 | case "*": 12 | return left * right; 13 | case "/": 14 | return left / right; 15 | case "==": 16 | return left == right ? 1 : 0; 17 | case ">": 18 | return left > right ? 1 : 0; 19 | case "<": 20 | return left < right ? 1 : 0; 21 | case "&&": 22 | return left && right; 23 | } 24 | throw Error(`Unknown binary operator ${operator}`); 25 | }; 26 | 27 | const executeProc = ( 28 | node: ProcStatementNode, 29 | env: Environment, 30 | program: TransformedProgram, 31 | args: number[] = [] 32 | ) => { 33 | const symbols = new Map( 34 | node.args.map((arg, index) => [arg.value, args[index]]) 35 | ); 36 | 37 | const evaluateExpression = (expression: ExpressionNode): number => { 38 | switch (expression.type) { 39 | case "numberLiteral": 40 | return expression.value; 41 | case "binaryExpression": 42 | return applyOperator( 43 | expression.operator, 44 | evaluateExpression(expression.left), 45 | evaluateExpression(expression.right) 46 | ); 47 | case "identifier": 48 | return symbols.get(expression.value); 49 | } 50 | }; 51 | 52 | const executeStatements = (statements: StatementNode[]) => { 53 | statements.forEach(statement => { 54 | switch (statement.type) { 55 | case "printStatement": 56 | env.print(evaluateExpression(statement.expression)); 57 | break; 58 | case "variableDeclaration": 59 | symbols.set( 60 | statement.name, 61 | evaluateExpression(statement.initializer) 62 | ); 63 | break; 64 | case "variableAssignment": 65 | symbols.set(statement.name, evaluateExpression(statement.value)); 66 | break; 67 | case "whileStatement": 68 | while (evaluateExpression(statement.expression)) { 69 | executeStatements(statement.statements); 70 | } 71 | break; 72 | case "ifStatement": 73 | if (evaluateExpression(statement.expression)) { 74 | executeStatements(statement.consequent); 75 | } else { 76 | executeStatements(statement.alternate); 77 | } 78 | break; 79 | case "callStatement": 80 | if (statement.name === "setpixel") { 81 | const x = evaluateExpression(statement.args[0]); 82 | const y = evaluateExpression(statement.args[1]); 83 | const color = evaluateExpression(statement.args[2]); 84 | env.display[y * 100 + x] = color; 85 | } else { 86 | const procName = statement.name; 87 | const argValues = statement.args.map(arg => 88 | evaluateExpression(arg) 89 | ); 90 | const proc = program.find(f => f.name === procName); 91 | executeProc(proc, env, program, argValues); 92 | } 93 | break; 94 | } 95 | }); 96 | }; 97 | 98 | executeStatements(node.statements); 99 | }; 100 | 101 | export const runtime: Runtime = async (src, env) => () => { 102 | const tokens = tokenize(src); 103 | const program = parse(tokens); 104 | const transformedProgram = transformer(program); 105 | 106 | const main = transformedProgram.find(f => f.name === "main"); 107 | 108 | executeProc(main, env, transformedProgram); 109 | }; 110 | -------------------------------------------------------------------------------- /src/parser.ts: -------------------------------------------------------------------------------- 1 | export class ParserError extends Error { 2 | token: Token; 3 | constructor(message: string, token: Token) { 4 | super(message); 5 | this.token = token; 6 | } 7 | } 8 | 9 | const asOperator = (value: string): Operator => { 10 | // TODO: check it really is an operator 11 | return value as Operator; 12 | }; 13 | 14 | export const parse: Parser = tokens => { 15 | const tokenIterator = tokens[Symbol.iterator](); 16 | let currentToken = tokenIterator.next().value; 17 | let nextToken = tokenIterator.next().value; 18 | 19 | const currentTokenIsKeyword = (name: string) => 20 | currentToken.value === name && currentToken.type === "keyword"; 21 | 22 | const eatToken = (value?: string) => { 23 | if (value && value !== currentToken.value) { 24 | throw new ParserError( 25 | `Unexpected token value, expected ${value}, received ${ 26 | currentToken.value 27 | }`, 28 | currentToken 29 | ); 30 | } 31 | currentToken = nextToken; 32 | nextToken = tokenIterator.next().value; 33 | }; 34 | 35 | const parseExpression: ParserStep = () => { 36 | let node: ExpressionNode; 37 | switch (currentToken.type) { 38 | case "number": 39 | node = { 40 | type: "numberLiteral", 41 | value: Number(currentToken.value) 42 | }; 43 | eatToken(); 44 | return node; 45 | case "identifier": 46 | node = { type: "identifier", value: currentToken.value }; 47 | eatToken(); 48 | return node; 49 | case "parens": 50 | eatToken("("); 51 | const left = parseExpression(); 52 | const operator = currentToken.value; 53 | eatToken(); 54 | const right = parseExpression(); 55 | eatToken(")"); 56 | return { 57 | type: "binaryExpression", 58 | left, 59 | right, 60 | operator: asOperator(operator) 61 | }; 62 | default: 63 | throw new ParserError( 64 | `Unexpected token type ${currentToken.type}`, 65 | currentToken 66 | ); 67 | } 68 | }; 69 | 70 | const parsePrintStatement: ParserStep = () => { 71 | eatToken("print"); 72 | return { 73 | type: "printStatement", 74 | expression: parseExpression() 75 | }; 76 | }; 77 | 78 | const parseIfStatement: ParserStep = () => { 79 | eatToken("if"); 80 | 81 | const expression = parseExpression(); 82 | 83 | let elseStatements = false; 84 | const consequent: StatementNode[] = []; 85 | const alternate: StatementNode[] = []; 86 | while (!currentTokenIsKeyword("endif")) { 87 | if (currentTokenIsKeyword("else")) { 88 | eatToken("else"); 89 | elseStatements = true; 90 | } 91 | if (elseStatements) { 92 | alternate.push(parseStatement()); 93 | } else { 94 | consequent.push(parseStatement()); 95 | } 96 | } 97 | 98 | eatToken("endif"); 99 | 100 | return { type: "ifStatement", expression, consequent, alternate }; 101 | }; 102 | 103 | const parseWhileStatement: ParserStep = () => { 104 | eatToken("while"); 105 | 106 | const expression = parseExpression(); 107 | 108 | const statements: StatementNode[] = []; 109 | while (!currentTokenIsKeyword("endwhile")) { 110 | statements.push(parseStatement()); 111 | } 112 | 113 | eatToken("endwhile"); 114 | 115 | return { type: "whileStatement", expression, statements }; 116 | }; 117 | 118 | const parseVariableAssignment: ParserStep = () => { 119 | const name = currentToken.value; 120 | eatToken(); 121 | eatToken("="); 122 | return { type: "variableAssignment", name, value: parseExpression() }; 123 | }; 124 | 125 | const parseVariableDeclarationStatement: ParserStep< 126 | VariableDeclarationNode 127 | > = () => { 128 | eatToken("var"); 129 | const name = currentToken.value; 130 | eatToken(); 131 | eatToken("="); 132 | return { 133 | type: "variableDeclaration", 134 | name, 135 | initializer: parseExpression() 136 | }; 137 | }; 138 | 139 | const parseCallStatementNode: ParserStep = () => { 140 | const name = currentToken.value; 141 | eatToken(); 142 | 143 | const args = parseCommaSeperatedList(() => parseExpression()); 144 | 145 | return { 146 | type: "callStatement", 147 | name, 148 | args 149 | }; 150 | }; 151 | 152 | function parseCommaSeperatedList(foo: () => T): T[] { 153 | const args: T[] = []; 154 | eatToken("("); 155 | while (currentToken.value !== ")") { 156 | args.push(foo()); 157 | if (currentToken.value !== ")") { 158 | eatToken(","); 159 | } 160 | } 161 | eatToken(")"); 162 | return args; 163 | } 164 | 165 | const parseProcStatement: ParserStep = () => { 166 | eatToken("proc"); 167 | 168 | const name = currentToken.value; 169 | eatToken(); 170 | 171 | const args = parseCommaSeperatedList(() => { 172 | const arg: IdentifierNode = { type: "identifier", value: currentToken.value }; 173 | eatToken(); 174 | return arg; 175 | }); 176 | 177 | const statements: StatementNode[] = []; 178 | while (!currentTokenIsKeyword("endproc")) { 179 | statements.push(parseStatement()); 180 | } 181 | eatToken("endproc"); 182 | 183 | return { 184 | type: "procStatement", 185 | name, 186 | args, 187 | statements 188 | }; 189 | }; 190 | 191 | const parseStatement: ParserStep = () => { 192 | if (currentToken.type === "keyword") { 193 | switch (currentToken.value) { 194 | case "print": 195 | return parsePrintStatement(); 196 | case "var": 197 | return parseVariableDeclarationStatement(); 198 | case "while": 199 | return parseWhileStatement(); 200 | case "if": 201 | return parseIfStatement(); 202 | case "proc": 203 | return parseProcStatement(); 204 | default: 205 | throw new ParserError( 206 | `Unknown keyword ${currentToken.value}`, 207 | currentToken 208 | ); 209 | } 210 | } else if (currentToken.type === "identifier") { 211 | if (nextToken.value === "=") { 212 | return parseVariableAssignment(); 213 | } else { 214 | return parseCallStatementNode(); 215 | } 216 | } 217 | }; 218 | 219 | 220 | const nodes: StatementNode[] = []; 221 | while (currentToken) { 222 | nodes.push(parseStatement()); 223 | } 224 | 225 | return nodes; 226 | }; 227 | -------------------------------------------------------------------------------- /src/tokenizer.ts: -------------------------------------------------------------------------------- 1 | export const keywords = [ 2 | "print", 3 | "var", 4 | "while", 5 | "endwhile", 6 | "if", 7 | "endif", 8 | "else", 9 | "proc", 10 | "endproc" 11 | ]; 12 | export const operators = ["+", "-", "*", "/", "==", "<", ">", "&&", ","]; 13 | 14 | const escapeRegEx = (text: string) => 15 | text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); 16 | 17 | export class TokenizerError extends Error { 18 | index: number; 19 | constructor(message: string, index: number) { 20 | super(message); 21 | this.index = index; 22 | } 23 | } 24 | 25 | // returns a token if the given regex matches at the current index 26 | const regexMatcher = (regex: string, type: TokenType): Matcher => ( 27 | input, 28 | index 29 | ) => { 30 | const match = input.substring(index).match(regex); 31 | return ( 32 | match && { 33 | type, 34 | value: match[0] 35 | } 36 | ); 37 | }; 38 | 39 | // matchers in precedence order 40 | const matchers = [ 41 | regexMatcher("^-?[.0-9]+([eE]-?[0-9]{2})?", "number"), 42 | regexMatcher(`^(${keywords.join("|")})`, "keyword"), 43 | regexMatcher("^\\s+", "whitespace"), 44 | regexMatcher(`^(${operators.map(escapeRegEx).join("|")})`, "operator"), 45 | regexMatcher(`^[a-zA-Z]+`, "identifier"), 46 | regexMatcher(`^=`, "assignment"), 47 | regexMatcher("^[()]{1}", "parens") 48 | ]; 49 | 50 | const locationForIndex = (input: string, index: number) => ({ 51 | char: index - input.lastIndexOf("\n", index) - 1, 52 | line: input.substring(0, index).split("\n").length - 1 53 | }); 54 | 55 | export const tokenize: Tokenizer = input => { 56 | const tokens: Token[] = []; 57 | let index = 0; 58 | while (index < input.length) { 59 | const matches = matchers.map(m => m(input, index)).filter(f => f); 60 | if (matches.length > 0) { 61 | // take the highest priority match 62 | const match = matches[0]; 63 | if (match.type !== "whitespace") { 64 | tokens.push({ ...match, ...locationForIndex(input, index) }); 65 | } 66 | index += match.value.length; 67 | } else { 68 | throw new TokenizerError( 69 | `Unexpected token ${input.substring(index, index + 1)}`, 70 | index 71 | ); 72 | } 73 | } 74 | return tokens; 75 | }; 76 | -------------------------------------------------------------------------------- /src/transformer.ts: -------------------------------------------------------------------------------- 1 | export const transformer: ASTTransformer = (ast: Program) => { 2 | 3 | // do we have a main proc? 4 | if (!ast.find(a => a.type === "procStatement" && a.name === "main")) { 5 | // if not - collect up any 'free' statements and add one. 6 | const freeStatements = ast.filter(a => a.type !== "procStatement"); 7 | const mainProc: ProcStatementNode = { 8 | type: "procStatement", 9 | name: "main", 10 | args: [], 11 | statements: freeStatements 12 | }; 13 | 14 | ast = [mainProc, ...ast.filter(a => a.type === "procStatement")]; 15 | } 16 | 17 | return ast.map(a => a as ProcStatementNode); 18 | }; 19 | -------------------------------------------------------------------------------- /src/traverse.ts: -------------------------------------------------------------------------------- 1 | // post order ast walker 2 | const traverse: Traverse = (nodes, visitor) => { 3 | nodes = Array.isArray(nodes) ? nodes : [nodes]; 4 | nodes.forEach(node => { 5 | Object.keys(node).forEach((prop: keyof ProgramNode) => { 6 | const value = node[prop]; 7 | const valueAsArray: string[] = Array.isArray(value) ? value : [value]; 8 | valueAsArray.forEach((childNode: any) => { 9 | if (typeof childNode.type === "string") { 10 | traverse(childNode, visitor); 11 | } 12 | }); 13 | }); 14 | visitor(node); 15 | }); 16 | }; 17 | 18 | export default traverse; 19 | -------------------------------------------------------------------------------- /src/types/compiler.ts: -------------------------------------------------------------------------------- 1 | interface Compiler { 2 | (src: string): Uint8Array; 3 | } 4 | -------------------------------------------------------------------------------- /src/types/emitter.ts: -------------------------------------------------------------------------------- 1 | interface Emitter { 2 | (ast: Program): Uint8Array; 3 | } 4 | -------------------------------------------------------------------------------- /src/types/parser.ts: -------------------------------------------------------------------------------- 1 | interface Parser { 2 | (tokens: Token[]): Program; 3 | } 4 | 5 | interface ProgramNode { 6 | type: string; 7 | } 8 | 9 | type Operator = "+" | "-" | "/" | "*" | "==" | ">" | "<" | "&&"; 10 | 11 | type ExpressionNode = NumberLiteralNode | BinaryExpressionNode | IdentifierNode; 12 | 13 | type StatementNode = 14 | | PrintStatementNode 15 | | VariableDeclarationNode 16 | | VariableAssignmentNode 17 | | WhileStatementNode 18 | | CallStatementNode 19 | | IfStatementNode 20 | | ProcStatementNode; 21 | 22 | type Program = StatementNode[]; 23 | 24 | interface VariableDeclarationNode extends ProgramNode { 25 | type: "variableDeclaration"; 26 | name: string; 27 | initializer: ExpressionNode; 28 | } 29 | 30 | interface VariableAssignmentNode extends ProgramNode { 31 | type: "variableAssignment"; 32 | name: string; 33 | value: ExpressionNode; 34 | } 35 | 36 | interface NumberLiteralNode extends ProgramNode { 37 | type: "numberLiteral"; 38 | value: number; 39 | } 40 | 41 | interface IdentifierNode extends ProgramNode { 42 | type: "identifier"; 43 | value: string; 44 | } 45 | 46 | interface BinaryExpressionNode extends ProgramNode { 47 | type: "binaryExpression"; 48 | left: ExpressionNode; 49 | right: ExpressionNode; 50 | operator: Operator; 51 | } 52 | 53 | interface PrintStatementNode extends ProgramNode { 54 | type: "printStatement"; 55 | expression: ExpressionNode; 56 | } 57 | 58 | interface CallStatementNode extends ProgramNode { 59 | type: "callStatement"; 60 | name: string, 61 | args: ExpressionNode[]; 62 | } 63 | 64 | interface WhileStatementNode extends ProgramNode { 65 | type: "whileStatement"; 66 | expression: ExpressionNode; 67 | statements: StatementNode[]; 68 | } 69 | 70 | interface ProcStatementNode extends ProgramNode { 71 | type: "procStatement"; 72 | name: string, 73 | args: IdentifierNode[]; 74 | statements: StatementNode[]; 75 | } 76 | 77 | interface IfStatementNode extends ProgramNode { 78 | type: "ifStatement"; 79 | expression: ExpressionNode; 80 | consequent: StatementNode[]; 81 | alternate: StatementNode[]; 82 | } 83 | 84 | interface ParserStep { 85 | (): T; 86 | } 87 | -------------------------------------------------------------------------------- /src/types/runtime.ts: -------------------------------------------------------------------------------- 1 | interface Runtime { 2 | (src: string, environment: Environment): Promise; 3 | } 4 | 5 | interface TickFunction { 6 | (): void; 7 | } 8 | 9 | interface Environment { 10 | print: PrintFunction; 11 | display: Uint8Array; 12 | } 13 | 14 | interface PrintFunction { 15 | (output: string | number): void; 16 | } 17 | -------------------------------------------------------------------------------- /src/types/tokenizer.ts: -------------------------------------------------------------------------------- 1 | interface Tokenizer { 2 | (input: string): Token[]; 3 | } 4 | 5 | type TokenType = 6 | | "number" 7 | | "keyword" 8 | | "whitespace" 9 | | "parens" 10 | | "operator" 11 | | "identifier" 12 | | "assignment"; 13 | 14 | interface Token { 15 | type: TokenType; 16 | value: string; 17 | line?: number; 18 | char?: number; 19 | } 20 | 21 | interface Matcher { 22 | (input: string, index: number): Token | null; 23 | } 24 | -------------------------------------------------------------------------------- /src/types/transformer.ts: -------------------------------------------------------------------------------- 1 | type TransformedProgram = ProcStatementNode[]; 2 | 3 | interface ASTTransformer { 4 | (ast: Program): TransformedProgram; 5 | } 6 | -------------------------------------------------------------------------------- /src/types/traverse.ts: -------------------------------------------------------------------------------- 1 | interface Traverse { 2 | (nodes: ProgramNode[] | ProgramNode, visitor: Visitor): void; 3 | } 4 | 5 | interface Visitor { 6 | (node: ProgramNode): void; 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./built", 4 | "allowJs": true, 5 | "lib": ["es2017", "dom"], 6 | "module": "commonjs", 7 | "target": "es2017", 8 | "noImplicitAny": true 9 | }, 10 | "include": ["./src/**/*", "./__tests__/**/*", "./web/**/*"] 11 | } 12 | --------------------------------------------------------------------------------