├── README.md ├── bin ├── env.js ├── example.watup ├── index.js ├── package.json └── tests └── env.js /README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | cat example.watup | ./bin - 3 | ``` 4 | ### `(define $name $body*)` 5 | 6 | Defines are exact "search and replace" without any evaluations. They are like 7 | macros without arguments. 8 | 9 | ```watup 10 | (define $PI (f64.const 3.14159)) 11 | 12 | (define $TAU (f64.mul $PI (f64.const 2))) 13 | 14 | (return (f64.sin (get_local $radians) $TAU)) 15 | ``` 16 | 17 | ```wat 18 | ;; Future versions may statically evaluate $TAU to a f64.const 19 | (return (f64.sin (get_local $radians) 20 | (f64.mul (f64.const 3.14159) 21 | (f64.const 2)))) 22 | ``` 23 | 24 | 25 | ### `(macro $name $args $body*)` 26 | 27 | #### Simple replacement 28 | 29 | ```watup 30 | (macro i32.add_in_place ($x $y) 31 | (set_local $x (i32.add (get_local $x) 32 | (get_local $y)))) 33 | 34 | (local $a i32) 35 | (local $b i32) 36 | 37 | (i32.add_in_place $a $b) 38 | ``` 39 | 40 | ```wat 41 | (local $a i32) 42 | (local $b i32) 43 | 44 | (set_local $a (i32.add (get_local $a) 45 | (get_local $b))) 46 | ``` 47 | 48 | #### Instruction replacement 49 | 50 | ```watup 51 | (macro i8.load_grey_pixel (#ptr) 52 | (i32.div_u (i32.add (i32.load8_u offset=0 #ptr) 53 | (i32.add (i32.load8_u offset=1 #ptr) 54 | (i32.load8_u offset=2 #ptr))) 55 | (i32.const 3))) 56 | 57 | (param $input.ptr i32) 58 | 59 | (i8.load_grey_pixel (get_local $input.ptr)) 60 | ``` 61 | 62 | ```wat 63 | (param $input.ptr i32) 64 | 65 | (i32.div_u (i32.add (i32.load8_u offset=0 (get_local $input.ptr)) 66 | (i32.add (i32.load8_u offset=1 (get_local $input.ptr)) 67 | (i32.load8_u offset=2 (get_local $input.ptr)))) 68 | (i32.const 3)) 69 | ``` 70 | 71 | ### `(unroll $placeholder $data $body*)` 72 | 73 | #### Simple unroll 74 | 75 | ```watup 76 | (unroll #idx (seq 0 3) 77 | (local $x{#idx} i32)) 78 | ``` 79 | 80 | ```wat 81 | (local $x0 i32) 82 | (local $x1 i32) 83 | (local $x2 i32) 84 | ``` 85 | 86 | #### Nested unroll 87 | 88 | ```watup 89 | (unroll #i (seq 0 16 4) 90 | (unroll #j '($a $b $c) 91 | (i32.load offset={#i} (get_local {#j}))) 92 | ``` 93 | 94 | ```wat 95 | (i32.load offset=0 (get_local $a)) 96 | (i32.load offset=0 (get_local $b)) 97 | (i32.load offset=0 (get_local $c)) 98 | (i32.load offset=4 (get_local $a)) 99 | (i32.load offset=4 (get_local $b)) 100 | (i32.load offset=4 (get_local $c)) 101 | (i32.load offset=12 (get_local $a)) 102 | (i32.load offset=12 (get_local $b)) 103 | (i32.load offset=12 (get_local $c)) 104 | ``` 105 | 106 | #### Destructuring 107 | 108 | ```watup 109 | (define #IV '( 110 | (128 0x6a09e667f3bcc908) 111 | (136 0xbb67ae8584caa73b) 112 | (144 0x3c6ef372fe94f82b) 113 | (152 0xa54ff53a5f1d36f1) 114 | (160 0x510e527fade682d1) 115 | (168 0x9b05688c2b3e6c1f) 116 | (176 0x1f83d9abfb41bd6b) 117 | (184 0x5be0cd19137e2179))) 118 | 119 | (param $ptr i32) 120 | 121 | (unroll (#offset #const) #IV 122 | (i64.store offset={#offset} (get_local $ptr) (i64.const {#const}))) 123 | ``` 124 | 125 | ```wat 126 | (param $ptr i32) 127 | 128 | (i64.store offset=128 (get_local $ptr) (i64.const 0x6a09e667f3bcc908)) 129 | (i64.store offset=136 (get_local $ptr) (i64.const 0xbb67ae8584caa73b)) 130 | (i64.store offset=144 (get_local $ptr) (i64.const 0x3c6ef372fe94f82b)) 131 | (i64.store offset=152 (get_local $ptr) (i64.const 0xa54ff53a5f1d36f1)) 132 | (i64.store offset=160 (get_local $ptr) (i64.const 0x510e527fade682d1)) 133 | (i64.store offset=168 (get_local $ptr) (i64.const 0x9b05688c2b3e6c1f)) 134 | (i64.store offset=176 (get_local $ptr) (i64.const 0x1f83d9abfb41bd6b)) 135 | (i64.store offset=184 (get_local $ptr) (i64.const 0x5be0cd19137e2179)) 136 | ``` 137 | -------------------------------------------------------------------------------- /bin: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var path = require('path') 3 | var fs = require('fs') 4 | var macros = require('.') 5 | 6 | var file 7 | if (process.argv[2] === '-') { 8 | file = process.stdin 9 | } else { 10 | file = fs.createReadStream(path.join(process.cwd(), process.argv[2])) 11 | } 12 | 13 | var tokenizer = require('wat-tokenizer')() 14 | 15 | file.on('data', d => tokenizer.update(d)).on('end', function () { 16 | console.log(macros(tokenizer.final())) 17 | }) 18 | -------------------------------------------------------------------------------- /env.js: -------------------------------------------------------------------------------- 1 | // Env is a linked list currently, so each scope can have their own appends/set 2 | module.exports = Env 3 | 4 | function Env (depth, parent) { 5 | this.tail = parent 6 | this.map = new Map() 7 | // Used as an optimisation when walking the ast, so a new env can be created 8 | // only when new definitions appear. Ie. if(ast.depth > curEnv.depth) curEnv = curEnv.create() 9 | this.depth = depth 10 | } 11 | 12 | // Static method for creating the initial env 13 | Env.create = function () { 14 | return new Env(0, null) 15 | } 16 | 17 | // Used for recursing 18 | Env.prototype.create = function () { 19 | return new Env(this.depth + 1, this) 20 | } 21 | 22 | // Keys can only be set once (otherwise there should have been a .create or 23 | // it's a duplicate definition) 24 | Env.prototype.set = function (key, value) { 25 | if (this.map.has(key)) throw new Error('Cannot set a key twice') 26 | this.map.set(key, value) 27 | return this 28 | } 29 | 30 | // Return null if not present. O(n) but in practise n will be small 31 | Env.prototype.get = function (key) { 32 | var head = this 33 | 34 | do { 35 | if (head.map.has(key)) return head.map.get(key) 36 | } while ((head = head.tail) != null) 37 | 38 | return null 39 | } 40 | -------------------------------------------------------------------------------- /example.watup: -------------------------------------------------------------------------------- 1 | (macro i32.add! ($x $y) (set_local $x (i32.add (get_local $x) (get_local $y)))) 2 | (macro i32.xor! ($x $y) (set_local $x (i32.add (get_local $x) (get_local $y)))) 3 | (macro i32.rotl! ($x $n) (set_local $x (i32.rotl (get_local $x) (i32.const $n)))) 4 | 5 | (define #FIRST 16) 6 | (define #SECOND 12) 7 | (define #THIRD 8) 8 | (define #FOURTH 7) 9 | 10 | (macro i32.quarterround ($a $b $c $d) 11 | (i32.add! $a $b) 12 | (i32.xor! $d $a) 13 | (i32.rotl! $d #FIRST) 14 | 15 | (i32.add! $c $d) 16 | (i32.xor! $b $c) 17 | (i32.rotl! $b #SECOND) 18 | 19 | (i32.add! $a $b) 20 | (i32.xor! $d $a) 21 | (i32.rotl! $d #THIRD) 22 | 23 | (i32.add! $c $d) 24 | (i32.xor! $b $c) 25 | (i32.rotl! $b #FOURTH)) 26 | 27 | 28 | (module 29 | (memory (export "memory") 1) 30 | 31 | (func $hchacha 32 | (param $x i32) 33 | (param $y i32) 34 | (result i32) 35 | 36 | (i32.quarterround $x0 $x4 $x8 $x12) 37 | (i32.quarterround $x1 $x5 $x9 $x13) 38 | (i32.quarterround $x2 $x6 $x10 $x14) 39 | (i32.quarterround $x3 $x7 $x11 $x15) 40 | (i32.quarterround $x0 $x5 $x10 $x15) 41 | (i32.quarterround $x1 $x6 $x11 $x12) 42 | (i32.quarterround $x2 $x7 $x8 $x13) 43 | (i32.quarterround $x3 $x4 $x9 $x14))) 44 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var dfs = require('depth-first-map') 2 | var env = require('./env') 3 | 4 | function evaluate (ast, env) { 5 | return dfs(ast, function (elm, i) { 6 | if (elm === dfs.break) return dfs.break 7 | if (elm === dfs.continue) return dfs.continue 8 | if (elm === dfs.drop) return dfs.drop 9 | 10 | if(elm == 'macro') { 11 | var name = this[2] 12 | var params = this[4] 13 | var body = this.slice(6) 14 | env.set(name.toString(), { 15 | type: 'macro', 16 | params: params, 17 | body: body 18 | }) 19 | 20 | return dfs.drop 21 | } 22 | 23 | if (elm == 'define') { 24 | var name = this[2] 25 | var body = this.slice(4) 26 | 27 | env.set(name.toString(), { 28 | type: 'definition', 29 | body: body 30 | }) 31 | 32 | return dfs.drop 33 | } 34 | 35 | if (v = env.get(elm.toString())) { 36 | switch(v.type) { 37 | case 'definition': return dfs.spread(v.body) 38 | case 'macro': return dfs.spread(applyMacro(this, v)) 39 | } 40 | } 41 | 42 | return elm 43 | }) 44 | } 45 | 46 | function applyMacro(source, macro) { 47 | var sourceParams = source.slice(2) 48 | 49 | var paramMap = new Map() 50 | 51 | var slen = sourceParams.length 52 | var mlen = macro.params.length 53 | for (var i = 0, j = 0; i < mlen; i++, j++) { 54 | var paramName = macro.params[i].toString() 55 | if (paramName.endsWith('*')) { 56 | var params = sourceParams.slice(j, slen - j) 57 | console.log(j) 58 | j += slen - j - (params.length * 2 - 1) 59 | console.log(j, slen) 60 | console.log('>', sourceParams[j + 1]) 61 | paramMap.set(paramName, dfs.spread(params)) 62 | } else { 63 | paramMap.set(paramName, sourceParams[j].toString()) 64 | } 65 | } 66 | 67 | return dfs(macro.body, function (node, i) { 68 | return paramMap.get(node.toString()) || node 69 | }) 70 | } 71 | 72 | function stringify (ast) { 73 | return ast.reduce(recurse, '') 74 | 75 | function recurse (str, node) { 76 | if (Array.isArray(node)) return str + '(' + node.reduce(recurse, '') + ')' 77 | return str + node 78 | } 79 | } 80 | 81 | module.exports = function (ast) { 82 | var mappedSource = evaluate(ast, env.create()) 83 | 84 | return stringify(mappedSource) 85 | } 86 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "depth-first-map": "^1.0.0", 4 | "wat-tokenizer": "^0.3.0" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tests/env.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | var env = require('../env') 3 | 4 | var sm = env.create() 5 | 6 | assert(sm.set('hello', 'world') === sm) 7 | assert(sm.set('other', true) === sm) 8 | assert(sm.get('hi') === null) 9 | assert(sm.get('hello') === 'world') 10 | var sm2 = sm.create() 11 | var v = {world: true} 12 | assert(sm2.set('hello', v) === sm2) 13 | assert(sm2.get('other') === true) 14 | assert(sm2.get('hello') === v) 15 | assert(sm.get('hello') === 'world') 16 | --------------------------------------------------------------------------------