├── .gitignore ├── .prettierrc ├── README.md ├── assets ├── disco.jpg └── liquid.jpg ├── demo ├── popcorn.fs ├── webgl.fs └── xt16.fs ├── deploy.sh ├── index.html ├── lib ├── audio.fs ├── bench.fs ├── canvas.fs ├── glsl.fs ├── list.fs ├── math.fs ├── swizzle.fs └── synth.fs ├── package.json ├── src ├── index.ts ├── kernel.ts ├── repl.ts └── vm.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | dist 3 | out 4 | node_modules 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "tabWidth": 4, 4 | "semi": true, 5 | "singleQuote": false, 6 | "arrowParens": "always", 7 | "endOfLine": "lf" 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @thi.ng/charlie 2 | 3 | ## About 4 | 5 | This is a slightly updated version of my first 6 | [Forth](http://thinking-forth.sourceforge.net/) VM implementation from 7 | 2015, originally written in JavaScript, now updated to TypeScript. Charlie is named in honour of Charles Moore, inventor of Forth. 8 | 9 | Several parts of the VM and core vocabulary are based on @phaendal's 10 | [mkforth4-js](https://github.com/phaendal/mkforth4-js), which evolved in 11 | parallel at roughly the same time span and was highly educational. Other 12 | parts are inspired by [Factor](http://factorcode.org), 13 | [Popr](https://github.com/HackerFoo/poprc) and other [concatenative 14 | languages](http://concatenative.org/). 15 | 16 | The VM & REPL (10KB total) are available online via 17 | [forth.thi.ng](http://forth.thi.ng). The project has been online since 18 | 2015, but was semi-broken due to CSS layout issues (now fixed). 19 | 20 | Related projects resulting from this experiment: 21 | 22 | - [@thi.ng/pointfree-lang](https://github.com/thi-ng/umbrella/tree/develop/packages/pointfree-lang) 23 | - [thi.ng/synstack](https://github.com/thi-ng/synstack/) 24 | 25 | ## Videos / screencasts 26 | 27 | - [WebAudio multi-track synth & fx livecoding (from scratch)](https://youtu.be/NU4PSkA3pAE?t=130) 28 | - [Forth-to-GLSL shader transpiler](https://youtu.be/30s3mgrkzQ0?t=123) 29 | - [Mini text adventure (directed graph)](https://twitter.com/forthcharlie/status/618463324473303040) 30 | - [Closures & destructuring](https://twitter.com/forthcharlie/status/618090661137522688) 31 | - [Vector algebra](https://twitter.com/forthcharlie/status/616465114871504896) 32 | - [Lisp style cons](https://twitter.com/forthcharlie/status/616429574331744256) 33 | - [Prime numbers](https://twitter.com/forthcharlie/status/616296680225435649) 34 | - [Unit conversion](https://twitter.com/forthcharlie/status/616292997261598720) 35 | - [ASCII art](https://twitter.com/forthcharlie/status/616290572706430977) 36 | - [FizzBuzz](https://twitter.com/forthcharlie/status/616281804866211840) 37 | 38 | ## Libraries & demos 39 | 40 | The following libraries and demos are included (also available in the 41 | online REPL): 42 | 43 | - [lib/audio.fs](https://github.com/thi-ng/charlie/tree/master/lib/audio.fs) 44 | - [lib/bench.fs](https://github.com/thi-ng/charlie/tree/master/lib/bench.fs) 45 | - [lib/canvas.fs](https://github.com/thi-ng/charlie/tree/master/lib/canvas.fs) 46 | - [lib/glsl.fs](https://github.com/thi-ng/charlie/tree/master/lib/glsl.fs) 47 | - [lib/list.fs](https://github.com/thi-ng/charlie/tree/master/lib/list.fs) 48 | - [lib/math.fs](https://github.com/thi-ng/charlie/tree/master/lib/math.fs) 49 | - [lib/swizzle.fs](https://github.com/thi-ng/charlie/tree/master/lib/swizzle.fs) 50 | - [lib/synth.fs](https://github.com/thi-ng/charlie/tree/master/lib/synth.fs) 51 | 52 | ### GLSL live coding & cross-compiler 53 | 54 | The above 55 | [lib/glsl.fs](https://github.com/thi-ng/charlie/tree/master/lib/glsl.fs) 56 | library contains a Forth -> GLSL cross-compiler, based on word inlining 57 | and emulating a stack machine via multiple variables. 58 | 59 | The concept was inspired by [Brad Nelson](https://flagxor.com/)'s [Forth 60 | Haiku](https://forthsalon.appspot.com/), however here (as an exercise) 61 | the cross-compiler is entirely written in Forth itself... 62 | 63 | Demo source: [demo/webgl.fs](https://github.com/thi-ng/charlie/tree/master/demo/webgl.fs) 64 | 65 | Usage in the REPL: 66 | 67 | ``` 68 | ( this includes the cross-compiler automatically ) 69 | "demo/webgl.fs" include* 70 | ``` 71 | 72 | Some small examples (more are included in the demo source, also see 73 | lib/glsl for available functions): 74 | 75 | **IMPORTANT:** All shader code must be wrapped by `glsl> ... ;;` 76 | 77 | #### Liquid paint (ported from [GLSL](http://glslsandbox.com/e#8067.3)) 78 | 79 | ![screenshot](https://raw.githubusercontent.com/thi-ng/charlie/master/assets/liquid.jpg) 80 | 81 | ``` 82 | glsl> 83 | : ti 0.3 * t + ; 84 | : amp 0.6 swap / * ; 85 | : col 3 * sin 0.5 * 0.5 + ; 86 | : i ( x y i -- x' y' i' ) 87 | >r over over r@ * r@ ti + sin r@ amp + 1 + -rot 88 | swap r@ * r@ 10 + ti + sin r@ amp + 1.4 - r> 1 + ; 89 | : i5 i i i i i ; 90 | : i10 i5 i5 ; 91 | 92 | x 2 * y 2 * 93 | 1 i10 i10 i10 94 | drop over over 95 | + sin -rot col swap col 96 | 97 | ;; reset 98 | ``` 99 | 100 | #### Disco floor (based on [Forth Haiku](https://forthsalon.appspot.com/haiku-view/ahBzfmZvcnRoc2Fsb24taHJkcg0LEgVIYWlrdRim4xMM)): 101 | 102 | ![screenshot](https://raw.githubusercontent.com/thi-ng/charlie/master/assets/disco.jpg) 103 | 104 | ``` 105 | glsl> 106 | : stripes 9.5 * sin ; 107 | : fade t * sin * ; 108 | 109 | x stripes y stripes * 110 | 2 fade 111 | dup 2 fade 112 | dup 3 fade 113 | 114 | ;; reset 115 | ``` 116 | 117 | ### Web audio demo 118 | 119 | Source: [demo/popcorn.fs](https://github.com/thi-ng/charlie/tree/master/demo/popcorn.fs) 120 | 121 | Usage in the REPL: 122 | 123 | ```text 124 | "demo/popcorn.fs" include* 125 | ``` 126 | 127 | Once all lib files are loaded (give it a few seconds to be sure)... 128 | 129 | ``` 130 | popcorn 131 | ``` 132 | 133 | ## Building 134 | 135 | ```bash 136 | git clone https://github.com/thi-ng/charlie.git 137 | cd charlie 138 | 139 | yarn start # start dev server 140 | 141 | yarn build # production build (written to /out) 142 | ``` 143 | -------------------------------------------------------------------------------- /assets/disco.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thi-ng/charlie/2ca68e0364835d6af85d0665db4bd003d01d4e50/assets/disco.jpg -------------------------------------------------------------------------------- /assets/liquid.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thi-ng/charlie/2ca68e0364835d6af85d0665db4bd003d01d4e50/assets/liquid.jpg -------------------------------------------------------------------------------- /demo/popcorn.fs: -------------------------------------------------------------------------------- 1 | "lib/synth.fs" include* 2 | 3 | "waiting for libs..." . 4 | 2000 sleep 5 | 6 | 1 softy instr-osc1 ! 7 | 10 softy instr-detune2 ! 8 | 80 softy instr-env-release ! 9 | 10 | 180 val> 1/8 11 | 1/8 2 * val> 1/4 12 | 13 | : play swap softy play sleep ; 14 | 15 | : popcorn-a ( offset -- ) 16 | C6 over + 1/8 play 17 | A#5 over + 1/8 play 18 | C6 over + 1/8 play 19 | G5 over + 1/8 play 20 | D#5 over + 1/8 play 21 | G5 over + 1/8 play 22 | C5 swap + 1/4 play ; 23 | 24 | : popcorn-b ( offset -- ) 25 | C6 over + 1/8 play 26 | D6 over + 1/8 play 27 | D#6 over + 1/8 play 28 | D6 over + 1/8 play 29 | D#6 over + 1/8 play 30 | C6 over + 1/8 play 31 | D6 over + 1/8 play 32 | C6 over + 1/8 play 33 | D6 over + 1/8 play 34 | A#5 over + 1/8 play 35 | C6 over + 1/8 play 36 | A#5 over + 1/8 play 37 | C6 over + 1/8 play 38 | G#5 over + 1/8 play 39 | C6 swap + 1/4 play ; 40 | 41 | : popcorn ( -- ) 42 | 0 popcorn-a 43 | 0 popcorn-a 44 | 0 popcorn-b 45 | 12 popcorn-a 46 | 12 popcorn-a 47 | 12 popcorn-b ; 48 | 49 | "ok" . -------------------------------------------------------------------------------- /demo/webgl.fs: -------------------------------------------------------------------------------- 1 | ( 2 | webgl demo including forth -> glsl transpiler (all written in charlie forth) 3 | inspired by, though not based on: 4 | forthsalon.appspot.com 5 | 06bacb42fe3bdfbd.paste.se 6 | ) 7 | 8 | "lib/glsl.fs" 9 | "lib/canvas.fs" 10 | "lib/swizzle.fs" 11 | "lib/bench.fs" 12 | include* 13 | 14 | "#version 300 es 15 | 16 | in vec2 position; 17 | uniform vec2 resolution; 18 | uniform float time; 19 | 20 | void main(void) { 21 | gl_Position = vec4(position, 0.0, 1.0); 22 | }" val> vs-src 23 | 24 | nil val> gl 25 | nil val> canvas 26 | nil var> *shader* 27 | nil var> *vbo* 28 | nil var> *time* 29 | nil var> *resolution* 30 | 31 | : init { fs-src gl } 32 | vs-src "VERTEX_SHADER" gl gl-compile-shader "vs-status: " swap + . { vs } 33 | fs-src "FRAGMENT_SHADER" gl gl-compile-shader "fs-status: " swap + . { fs } 34 | vs fs gl gl-program "prog status: " swap + . { shader } 35 | shader "position" gl gl-attribute-loc dup gl gl-enable-vertex-attrib { vattr } 36 | gl gl-buffer dup gl gl-bind-buffer { vbo } 37 | -1 -1 1 -1 1 1 -1 -1 1 1 -1 1 12 dup float32 ds->array 38 | gl gl-buffer-data 39 | vattr 2 gl gl-vertex-attrib-pointer 40 | shader "time" gl gl-uniform-loc *time* ! 41 | shader "resolution" gl gl-uniform-loc *resolution* ! 42 | shader *shader* ! 43 | vbo *vbo* ! 44 | canvas add-to-repl ; 45 | 46 | : reset gl init clear-repl-out canvas add-to-repl ; 47 | 48 | : update 49 | *shader* @ gl gl-use-program 50 | *time* @ now 0.001 * 86400 mod gl gl-uniform1f! 51 | *resolution* @ 600 600 gl gl-uniform2f! 52 | gl "TRIANGLES" js@ 6 gl gl-draw-arrays ; 53 | 54 | : repeatedly { word } 55 | [ drop word apply rdrop ] quot->js-callback 16 56 | *js-window* "setInterval" js@ js-call-2 drop ; 57 | 58 | 59 | 600 600 "webgl2" make-canvas set> gl set> canvas 60 | 61 | glsl> x y t fract ;; dup . 62 | 63 | gl init [ update ] repeatedly 64 | 65 | ( 66 | 67 | glsl> x 2 pi * * t + y t 2 * + cos 10 * + sin ;; gl init 68 | 69 | ( ------------- ) 70 | glsl> 71 | x 2 * y 3 * t + sin + 3 * 1 mod .5 < 72 | y 2 * x 2 * y 3 * t + sin + t + cos + 3 * 1 mod .5 < and ;; 73 | gl init 74 | 75 | glsl> 76 | x 2 * y 3 * t + sin + 3 * 1 mod 77 | y 2 * x 2 * y 3 * t + sin + t + cos + 3 * 1 mod max 78 | ;; 79 | gl init 80 | 81 | glsl> 82 | x 2 * y 3 * t + sin + 3 * 1 mod 83 | y 2 * x 2 * y 3 * t + sin + t + cos + 3 * 1 mod 2dup max 84 | ;; 85 | gl init 86 | 87 | ( ---------------- ) 88 | 89 | glsl> 90 | y 0.5 - 5 * @r ** ** x 0.5 - 5 * @r ** ** - r> r> * - 91 | t 1.5 * sin 14 * - abs t 3 / cos pow 92 | 3 * dup 1 + sin swap dup sin swap 5 + sin 93 | ;; 94 | gl init 95 | 96 | glsl> 97 | : ^2 dup * ; 98 | : x' x .5 - 5 * ; 99 | : y' y .5 - 5 * ; 100 | : rollettcurve y' ^2 ^2 x' ^2 ^2 - x' y' * - ; 101 | : rainbow 102 | dup 3 * 1 + sin swap 103 | dup 3 * 0 + sin swap 104 | dup 3 * 5 + sin swap drop ; 105 | 106 | rollettcurve t 1.5 * sin 14 * - abs t 3 / cos pow rainbow 107 | ;; . 108 | 109 | 110 | ( based on shader by Manwe ) 111 | glsl> 112 | : j t 0.12 * sin 0.02 * 0.6666 + ; 113 | : i 2dup z* 0.04 t 0.5 * tau mod cos * j z+ ; 114 | : f i i i i i i i i i ; 115 | : rainbow 3 * dup 1 + sin swap dup sin swap 5 + sin ; 116 | t 0.1 * fract tau * sin 0.5 * 1 + dup y 0.5 - * swap 0.5 x - * 117 | f f f f f f f swap drop rainbow ;; gl init 118 | 119 | 120 | ( pacman ) 121 | glsl> 122 | : d dup ; 123 | : m 1 min ; 124 | : f d floor - ; 125 | : c cos abs ; 126 | : j t 4 + 2 * x 8 * floor 8 / + 4 * c 2 / t 4 + 2 / c 4 pow * - ; 127 | : a 1 x x 8 * floor 0.5 + 8 / - d * y ; 128 | : b - d * + sqrt 50 * 8 pow ; 129 | : p x t 4 + pi / f 1.6 * - 0.2 + ; 130 | : v t 4 + pi 2 * / f ; 131 | a j 0.5 b - 132 | v d 0.5 < * 4 * m * 133 | 1 p d * y 0.5 - d * + 36 * 30 pow m - 134 | y 0.5 - p atan2 abs t 10 * c 0.8 * - 16 * m * 0 max 135 | a 0.5 b - 0 max d p 16 * < * + 136 | p d * y 0.58 b m * 137 | v 0.5 >= * 138 | + d 0.2 ;; gl init 139 | 140 | ( tunnel ) 141 | glsl> 142 | : x' x 0.5 - t sin 0.2 * + ; 143 | : y' y 0.5 - t 1.5 * cos 0.2 * + ; 144 | : dist x' x' + y' y' + * sqrt ; 145 | : xor + abs 2 mod ; 146 | : b / floor 2 mod ; 147 | : m 256 * floor ; 148 | : a dup rot swap b -rot b xor ; 149 | : w dup 150 | 151 | x' y' atan2 pi / 512 * t 100 * + 256 mod 152 | 128 dist / t 500 * + 256 mod 153 | 154 | rot a * ; 155 | 1 w 2 w 4 w 8 w 16 w 32 w 64 w 128 w 156 | + + + + + + + 256 / dist * dup dup ;; gl init 157 | 158 | ( fractal2 ) 159 | glsl> 160 | : x' x .5 - 2.4 * ; 161 | : y' y .7 - 2.4 * ; 162 | : dot dup * swap dup * + ; 163 | : l dup -0.04 * r> r> 164 | 2dup * 2 * x' + >r 2dup z* drop y' + r> 165 | 2dup >r >r dot + abs 166 | rot min swap rot over 1.32457 * t + 167 | r> r> 2dup >r >r 168 | rot dup cos -2 * swap sin -2 * 169 | z+ dot min -rot 1 + ; 170 | 171 | y' x' >r >r 4 4 0 l l l l l l l l l drop 172 | log 8 / negate 173 | swap log 8 / negate 174 | swap dup >r 2 pow 175 | over 3 pow + r> 3 pow 176 | r> r> drop drop ;; 177 | gl init 178 | 179 | ( checker board ) 180 | glsl> : xor + 2 mod ; : tile 8 * floor ; x tile y tile xor ;; gl init 181 | 182 | ( x-checkers ) 183 | x 25 * cos y 25 * cos < 184 | 185 | ( binary ) 186 | : d ( x n - d ) 2 swap ** * floor 2 mod ; 187 | x y 8 * floor d 188 | 189 | ( bit clock ) 190 | : h 0.66 t 3600 / floor ; 191 | : m 0.50 t 60 / floor 60 mod ; 192 | : s 0.34 t floor 60 mod ; 193 | : x5 0.10 32 ; 194 | : x4 0.26 16 ; 195 | : x3 0.42 8 ; 196 | : x2 0.58 4 ; 197 | : x1 0.74 2 ; 198 | : x0 0.90 1 ; 199 | : circle y - 2.5 pow swap x - 2.5 pow + sqrt 0.04 < ; 200 | : bit ( x0 b y0 n -- bool ) rot / floor 2 mod -rot circle and ; 201 | : hb h bit + ; 202 | : mb m bit + ; 203 | : sb s bit + ; 204 | x5 h bit 205 | x4 hb 206 | x3 hb 207 | x2 hb 208 | x1 hb 209 | x0 hb 210 | x5 m bit 211 | x4 mb 212 | x3 mb 213 | x2 mb 214 | x1 mb 215 | x0 mb 216 | x5 s bit 217 | x4 sb 218 | x3 sb 219 | x2 sb 220 | x1 sb 221 | x0 sb 222 | 223 | ( 10 PRINT ... 20 GOTO 10 ) 224 | ( https://forthsalon.appspot.com/haiku-view/ahBzfmZvcnRoc2Fsb24taHJkchILEgVIYWlrdRiAgICApaaHCgw ) 225 | : ' 20 * 1 mod ; 226 | : l dup -0.2 >= swap 0.2 < * ; 227 | : x x 2 * ; 228 | : y t 2 * floor 10 mod y - 2 * ; 229 | : _ 20 * floor 5 + ; 230 | x _ y _ ( t 8 * floor - ) 7 + cos x 231 | _ sin / * 1 mod 0.5 >= dup x ' 232 | y ' - l * swap 1 swap - 1 x ' 233 | - y ' - l * + dup dup 0.22 * 234 | 0.2 + swap 0.22 * 0.15 + rot 235 | 0.24 * 0.47 + 236 | 237 | ( beauty basic sphere ) 238 | x 2 * 1 - dup * y 2 * 1 - dup * + x y 239 | 240 | ( amiga ball ) 241 | : q dup * ; 242 | : d2 q swap q + ; 243 | : acos dup q 1 - negate sqrt swap 1 + atan2 2 * ; 244 | 245 | : r 0.5 ; 246 | : r2 r q ; 247 | : tl 1.58 t sin 5 / + ; 248 | 249 | : ' 0.5 - ; 250 | : 's ' tl cos * ; 251 | : 'c ' tl sin * ; 252 | : x' x 'c y 's - ; 253 | : y' y 'c x 's + ; 254 | : l2 x ' y ' d2 ; 255 | 256 | : in? l2 r2 < ; 257 | : z r2 l2 - sqrt ; 258 | 259 | : th y' acos 2 * pi / ; 260 | : ph z x' atan2 pi / t 9 / + ; 261 | 262 | : txtr 25 25 z* cos >r cos r> < ; 263 | 264 | z in? * 1.5 * 265 | ph th txtr 266 | over * dup rot 267 | 268 | ( basic operations on a complex numbers ) 269 | 270 | : z x .5 - y .5 - ; ( a complex number stored as a pair of numbers ) 271 | 272 | : z- swap -rot - push - pop ; ( difference between two complex numbers ) 273 | : z1/ over dup * over dup * + rot over / -rot / ; ( 1 divided by a complex number ) 274 | : zmodule ( module of a complex number ) dup * swap dup * + sqrt ; 275 | : zarg ( arg of a complex number ) swap atan2 ; 276 | : e^ ( e raised to a complex power ) over exp over cos * -rot sin swap exp * ; 277 | : zln ( logarithm of a complex number ) 2dup zmodule log -rot zarg ; 278 | : z^ ( complex number raised to a complex power ) push push zln pop pop z* e^ ; 279 | 280 | : a 2 1.4 ; 281 | : b 1 -4 ; 282 | : c -2 t 2 / sin 4 * 1 + ; 283 | : d 0 1 ; 284 | 285 | a z z* b z+ 286 | c z z* d z+ c z^ 287 | z1/ e^ 288 | z* zln 289 | 290 | 2dup zmodule 4 / 291 | swap 292 | 293 | 294 | ( cellular texture ) 295 | : s - 2 pow ; 296 | : d y s swap x s + sqrt ; 297 | : h 158 * tan tan tan 1 mod ; 298 | : r r> 1 + dup >r h ; 299 | : i r r d min ; 300 | : 8i i i i i i i i i ; 301 | 1 0 >r 8i 8i r> drop 302 | 0.5 swap - 2 * 303 | dup dup 304 | 305 | ( cellular anim toxi ) 306 | glsl> 307 | : s - 2 pow ; 308 | : d y s swap x s + sqrt ; 309 | : h t 0.01 * * sin 1 mod ; 310 | : r r> 1 + dup >r h ; 311 | : i r r d min ; 312 | : 8i i i i i i i i i i i ; 313 | 1 0 >r 8i 8i r> drop 314 | 0.5 swap - 2 * 0.2 over 3 * -1 + sin 0.2 * 0.8 + ;; gl init 315 | 316 | ( mode7 rotation ) 317 | ( https://forthsalon.appspot.com/haiku-view/ahBzfmZvcnRoc2Fsb24taHJkcg0LEgVIYWlrdRiWlRMM ) 318 | : h .5 - * ; : z 1 1 y h / -1 * ; 319 | : s h t sin ; : d x z * z 2 / - ; 320 | t cos y z * s d h - t - 2 * 321 | floor t cos d s y z * h + 322 | 2 * + 2 mod floor z .2 * / 323 | 324 | ( xor tunnel ) 325 | ( https://forthsalon.appspot.com/haiku-view/ahBzfmZvcnRoc2Fsb24taHJkcg0LEgVIYWlrdRiDxxIM ) 326 | glsl> 327 | : p 2 * pi * cos 0.5 * 0.5 + ; 328 | : col dup dup p swap 1 3 / + p rot 2 3 / + p ; 329 | : x' x 0.5 - t sin 0.2 * + ; 330 | : y' y 0.5 - t 1.5 * cos 0.2 * + ; 331 | : dist x' x' * y' y' * + sqrt ; 332 | : b / floor 2 mod ; 333 | : w dup x' y' atan2 pi / 512 * t 100 * + 256 mod 128 dist / t 500 * + 256 mod rot dup rot swap b -rot b + abs 2 mod * + ; 334 | 0 4 w 8 w 16 w 32 w 64 w 128 w 256 / t + col 335 | dist 2 * * swap dist 2 * * rot dist 2 * * 336 | ;; gl init 337 | 338 | ( http://www.thesands.ru/forth-demotool/ ) 339 | glsl> 340 | : q dup * ; 341 | : sincos ( t - s c ) dup sin swap cos ; 342 | : l 343 | 1 x .5 - r@ 1 + * 344 | r@ 3 - t cos t sin z* 345 | y .5 - r@ 1 + * 346 | swap 347 | t 2.7 / cos t 2.7 / sin z* -rot 348 | q swap q + 349 | dup 8 * swap rot 350 | q + 1.8 + q 351 | - abs 1 min .03 - - 15 pow 352 | r@ / r> .2 - >r 353 | max ; 354 | : j l l l l ; 355 | 0 0 4.2 >r j j j j r> drop 1.5 * dup q swap 356 | ;; gl init 357 | 358 | 359 | ( 2d rotation ) 360 | glsl> 361 | : sincos ( x theta -- sin cos ) @r sin over * swap r> cos * ; 362 | : abs2d ( x y -- x y ) abs swap abs swap ; 363 | : add2d ( x y n -- x y ) @r + r> rot + swap ; 364 | : rot2x ( x y theta -- x y ) @r sincos rot r> sincos -rot + -rot swap - swap ; 365 | 366 | x y -0.5 add2d t 2 * rot2x abs2d 367 | ;; gl init 368 | 369 | ( light tunnel // http://glslsandbox.com/e#35712.0 ) 370 | 371 | glsl> 372 | : add2d ( x y n -- x y ) @r + r> rot + swap ; 373 | : mul2d ( x y n -- x y ) @r * r> rot * swap ; 374 | : len2d ( x y -- l ) dup * swap dup * + sqrt ; 375 | 376 | t 0.14 * sin 0.2 * t 4 * 3 mod + ( f ) 377 | 378 | x y -0.5 add2d 2 mul2d over over ( f x y x y ) 379 | atan2 30 * t 16 * - sin 0.02 * 0.5 + ( f x y a ) 1 swap / 380 | mul2d ( f x y ) 381 | len2d ( f l ) 382 | - abs 10 * ( d ) 383 | 384 | 0.8 over / swap ( r d ) 385 | 0.2 over / swap ( r g d ) 386 | 2.2 swap / 387 | 388 | ;; gl init 389 | 390 | ( particles // http://glslsandbox.com/e#35609.0 ) 391 | 392 | : mix ( a b t -- x ) >r over - r> * + ; 393 | 394 | glsl> 395 | : add2n ( x y n -- x y ) @r + r> rot + swap ; 396 | : add2d ( x y x' y' -- x y ) rot + ( x x' y ) -rot + swap ; 397 | : mul2n ( x y n -- x y ) @r * r> rot * swap ; 398 | : len2d ( x y -- l ) dup * swap dup * + sqrt ; 399 | : dup2d over over ; 400 | 401 | : speed t 0.075 * ; 402 | 403 | : bfx ( ax fx -- x' ) speed 0.9 / * sin * 10 * ; 404 | : bfy ( ay fy -- y' fy ) @r speed 2.0 / * cos * 10 * r> ; 405 | 406 | : ball ( x y ax fx ay fy -- c x y ) 407 | bfy t 0.01 * * sin 1 swap / >r -rot 408 | bfx swap add2d r> mul2n len2d 0.05 swap / -rot ; 409 | 410 | x 2 * 1 - y 2 * 1 - dup2d ( x y x y ) 411 | 412 | 0.03 31 0.09 22 ball dup2d ( c x y x y ) 413 | 0.04 22.5 0.04 22.5 ball dup2d ( c c2 x y x y ) 414 | 0.05 12 0.03 23 ball dup2d 415 | 0.06 32.5 0.04 33.5 ball dup2d 416 | 0.07 23 0.03 24 ball dup2d 417 | 0.08 21.5 0.02 22.5 ball dup2d 418 | 0.09 33.1 0.07 21.5 ball 419 | drop drop 420 | + + + + + + 421 | 1.6 * 422 | dup 0.22 * swap 423 | dup 0.34 * swap 424 | 0.9 t sin * * 425 | ;; gl init 426 | 427 | 428 | ( http://glslsandbox.com/e#8067.3 // liquid paint ) 429 | glsl> 430 | : ti 0.3 * t + ; 431 | : amp 0.6 swap / * ; 432 | : col 3 * sin 0.5 * 0.5 + ; 433 | : i ( x y i -- x' y' i' ) 434 | >r over over r@ * r@ ti + sin r@ amp + 1 + -rot 435 | swap r@ * r@ 10 + ti + sin r@ amp + 1.4 - r> 1 + ; 436 | : i5 i i i i i ; 437 | : i10 i5 i5 ; 438 | 439 | x 2 * y 2 * 440 | 1 i10 i10 i10 441 | drop over over 442 | + sin -rot col swap col 443 | ;; gl init 444 | 445 | ( laser lines ) 446 | 447 | glsl> 448 | : trunc 1 * fract ; 449 | : x' x trunc ; 450 | : y' y trunc ; 451 | : w t sin 0.49 * 0.5 + 20 * ; 452 | : line 453 | x' - rot x' swap - rot 454 | y' - * -rot y' rot - * 455 | - abs 2 * 1 min 1 swap - ; 456 | 457 | 0.5 0 t sin 1 line 458 | 1 0.5 0.2 t 1.5 * sin line 459 | 0.25 1 t cos 0.25 line 460 | ;; gl init 461 | 462 | ( disco floor ) 463 | glsl> 464 | x 9.4 * sin 465 | y 9.4 * sin 466 | t 4 * sin 467 | * * 468 | dup t 2 * sin * 469 | dup t 3 * sin * 470 | ;; gl init 471 | 472 | ( pattern ) 473 | glsl> 474 | x 1280 * 640 - dup * y 1280 * 640 - dup * + t 1 / - 475 | 2 * dup 476 | 2 * dup 477 | 2 * 478 | sin rot sin rot sin 479 | ;; 480 | gl init 481 | 482 | 483 | ( sun in the sky ) 484 | 485 | glsl> 486 | ( sun ) 487 | 0.6 0.85 1 488 | 0.8 x y 10 * 2 t * + sin 0.005 * + - abs dup * 489 | 0.5 y x 10 * 3 t * + cos 0.005 * + - abs dup * + sqrt 490 | - smoothstep 491 | dup y 2 * * swap 492 | dup y 1 * * rot ( r g m ) 493 | ( sky ) 494 | 1 swap - 495 | dup 0.8 y 0.5 * - * swap ( r g r m ) 496 | dup 1 y 0.5 * - * swap ( r g r g b ) 497 | >r ( r g r g ) 498 | rot max ( r r g ) 499 | -rot max swap ( r g ) 500 | r> 501 | ;; gl init 502 | 503 | ) 504 | -------------------------------------------------------------------------------- /demo/xt16.fs: -------------------------------------------------------------------------------- 1 | ( beauty sphere ) 2 | glsl> 3 | x 2 * 1 - dup * y 2 * 1 - dup * + x y 4 | ;; gl init 5 | 6 | ( liquid paint ) 7 | glsl> 8 | : ti 0.3 * t + ; 9 | : amp 0.6 swap / * ; 10 | : col 3 * sin 0.5 * 0.5 + ; 11 | : i ( x y i -- x' y' i' ) 12 | >r over over r@ * r@ ti + sin r@ amp + 1 + -rot 13 | swap r@ * r@ 10 + ti + sin r@ amp + 1.4 - r> 1 + ; 14 | : i5 i i i i i ; 15 | : i10 i5 i5 ; 16 | 17 | x 2 * y 2 * 18 | 1 i10 i10 i10 19 | drop over over 20 | + sin -rot col swap col 21 | ;; gl init 22 | 23 | ( laser lines ) 24 | 25 | glsl> 26 | : trunc 1 * fract ; 27 | : x' x trunc ; 28 | : y' y trunc ; 29 | : w t sin 0.49 * 0.5 + 20 * ; 30 | : line 31 | x' - rot x' swap - rot 32 | y' - * -rot y' rot - * 33 | - abs 2 * 1 min 1 swap - ; 34 | 35 | 0.5 0 t sin 1 line 36 | 1 0.5 0.2 t 1.5 * sin line 37 | 0.25 1 t cos 0.25 line 38 | ;; gl init 39 | 40 | ( disco floor ) 41 | glsl> 42 | x 9.4 * sin 43 | y 9.4 * sin 44 | t 4 * sin 45 | * * 46 | dup t 2 * sin * 47 | dup t 3 * sin * 48 | ;; gl init 49 | 50 | ( particles ) 51 | 52 | glsl> 53 | : add2n ( x y n -- x y ) @r + r> rot + swap ; 54 | : add2d ( x y x' y' -- x y ) rot + ( x x' y ) -rot + swap ; 55 | : mul2n ( x y n -- x y ) @r * r> rot * swap ; 56 | : len2d ( x y -- l ) dup * swap dup * + sqrt ; 57 | : dup2d over over ; 58 | 59 | : speed t 0.075 * ; 60 | 61 | : bfx ( ax fx -- x' ) speed 0.9 / * sin * 10 * ; 62 | : bfy ( ay fy -- y' fy ) @r speed 2.0 / * cos * 10 * r> ; 63 | 64 | : ball ( x y ax fx ay fy -- c x y ) 65 | bfy t 0.01 * * sin 1 swap / >r -rot 66 | bfx swap add2d r> mul2n len2d 0.05 swap / -rot ; 67 | 68 | x 2 * 1 - y 2 * 1 - dup2d ( x y x y ) 69 | 70 | 0.03 31 0.09 22 ball dup2d ( c x y x y ) 71 | 0.04 22.5 0.04 22.5 ball dup2d ( c c2 x y x y ) 72 | 0.05 12 0.03 23 ball dup2d 73 | 0.06 32.5 0.04 33.5 ball dup2d 74 | 0.07 23 0.03 24 ball dup2d 75 | 0.08 21.5 0.02 22.5 ball dup2d 76 | 0.09 33.1 0.07 21.5 ball 77 | drop drop 78 | + + + + + + 79 | 1.6 * 80 | dup 0.22 * swap 81 | dup 0.34 * swap 82 | 0.9 t sin * * 83 | ;; gl init 84 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | readonly BUCKET="s3://forth-thi-ng" 4 | readonly OPTS="--profile thing-2025 --acl public-read" 5 | 6 | gzip -r -k -9 dist 7 | 8 | for f in $(find dist -name "*.gz"); do 9 | src="${f/dist\//}" 10 | dest="$BUCKET/${src%.gz}" 11 | name=$(basename -- "${f%.gz}") 12 | ext="${name##*.}" 13 | case $ext in 14 | js) mime="application/javascript;charset=utf-8" ;; 15 | json) mime="application/json;charset=utf-8" ;; 16 | html) mime="text/html;charset=utf-8" ;; 17 | svg) mime="text/svg+xml;charset=utf-8" ;; 18 | css) mime="text/css;charset=utf-8" ;; 19 | png) mime="image/png" ;; 20 | jpg) mime="image/jpeg" ;; 21 | fs) mime="text/plain;charset=utf-8" ;; 22 | *) mime="application/octet-stream";; 23 | esac 24 | echo "$src -> $dest ($mime)" 25 | aws s3 cp $f $dest $OPTS --content-type $mime --content-encoding gzip 26 | done 27 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | CharlieREPL 9 | 215 | 216 | 217 |
218 |
219 |
220 |
221 |

Data stack

222 |
223 |
224 |
225 |
226 |
227 |

Return stack

228 |
229 |
230 |
231 |
232 |
233 | 234 |
235 |
239 |
240 | 245 |
246 |
247 | 248 | 249 | 250 | -------------------------------------------------------------------------------- /lib/audio.fs: -------------------------------------------------------------------------------- 1 | vocab/ Audio 2 | 3 | : new-context 4 | *js-window* "AudioContext" js@ js-new ; 5 | 6 | : new-buffer ^( chan len rate ctx ) 7 | dup "createBuffer" js@ 3 js-call-n-with ; 8 | 9 | : buffer-channel-data ^( buf ch -- array ) 10 | swap dup "getChannelData" js@ js-call-1-with ; 11 | 12 | : fill-buffer! 13 | { buf from n [osc] } 14 | n [ dup [osc] apply buf rot from + js! ] ; 15 | 16 | : connect! ^( ctx node -- ) 17 | swap "destination" js@ swap dup "connect" js@ js-call-1-with drop ; 18 | 19 | : disconnect! ^( ctx node -- ) 20 | swap "destination" js@ swap dup "disconnect" js@ js-call-1-with drop ; 21 | 22 | : new-source ^( buf loop? ctx ) 23 | { buf loop? ctx } 24 | ctx dup "createBufferSource" js@ js-call-with { src } 25 | loop? src "loop" js! 26 | buf src "buffer" js! 27 | ctx src connect! 28 | src ; 29 | 30 | : new-script ^( quot frames ch ctx ) 31 | { quote frames ch ctx } 32 | frames 0 ch ctx dup "createScriptProcessor" js@ 3 js-call-n-with { proc } 33 | ch 1 = if 34 | [ "outputBuffer" js@ dup "getChannelData" js@ 0 -rot js-call-1-with quote apply ] 35 | else 36 | [ dup "outputBuffer" js@ dup "getChannelData" js@ 0 -rot js-call-1-with 37 | swap "outputBuffer" js@ dup "getChannelData" js@ 1 -rot js-call-1-with 38 | quote apply ] 39 | then 40 | quot->js-callback proc "onaudioprocess" js! 41 | ctx proc connect! 42 | proc ; 43 | 44 | : start-source ^( src -- ) 45 | dup "start" js@ js-call-with drop ; 46 | 47 | : stop-source ^( src -- ) 48 | dup "stop" js@ js-call-with drop ; 49 | 50 | : new-track ^( ctx frames rate -- buf bdata src ) 51 | { ctx frames rate } 52 | 1 frames rate ctx new-buffer { buf } 53 | buf 0 buffer-channel-data { bdata } 54 | buf true ctx new-source { src } 55 | buf bdata src ; 56 | 57 | : regular-beat { note off n len } 58 | 0 begin dup n < while dup len * off + note apply 1+ repeat drop ; 59 | 60 | : apply-regular { off len n } 61 | 0 begin dup n < while dup len * off + rot apply 1+ repeat drop ; 62 | 63 | : replicate { x n } 0 begin dup n < while x swap 1+ repeat drop ; 64 | 65 | : wrap { t frames -- t' } t dup neg? if frames + then ; 66 | 67 | : echo { bdata frames delay dry wet } 68 | 0 begin 69 | dup frames < while 70 | bdata over ( t bd t ) 71 | js@ dry * ( t dv ) 72 | over bdata swap ( t dv bd t ) 73 | delay - frames wrap ( t dv bd t' ) 74 | js@ wet * + ( t dw ) 75 | over bdata swap js! 76 | 1+ repeat drop ; 77 | 78 | : reverse { buf len } 79 | len 1 >> { max } 80 | len 1- set> len 81 | 0 begin 82 | dup max <= while 83 | { i } buf i js@ 84 | buf len i - dup { j } js@ 85 | buf i js! 86 | buf j js! 87 | i 1+ repeat drop ; 88 | 89 | /vocab -------------------------------------------------------------------------------- /lib/bench.fs: -------------------------------------------------------------------------------- 1 | *js-window* "Date" js@ val> Date 2 | 3 | : now Date js-new dup "getTime" js@ js-call-with ; 4 | : timed { [timed-part] } now { t0 } [timed-part] apply "" now t0 - + "ms" + . ; 5 | 6 | : timed-n ^( n block -- ) 7 | { n block } 8 | n dup pos? if block timed 1- block tail-recur then drop ; 9 | -------------------------------------------------------------------------------- /lib/canvas.fs: -------------------------------------------------------------------------------- 1 | : get-context ^( ctx-type canvas -- gl ) 2 | dup "getContext" js@ js-call-1-with ; 3 | 4 | : make-canvas 5 | ^( w h ctx-type -- canvas ctx ) 6 | { w h ctx } 7 | ctx "canvas" js-create-element 8 | dup w swap "width" js-attr! 9 | dup h swap "height" js-attr! 10 | tuck get-context ; 11 | 12 | : fill-style! ^( col ctx -- ) "fillStyle" js! ; 13 | 14 | : rect ^( x y w h ctx -- ) 15 | { x y w h ctx } 16 | x y w h ctx dup "fillRect" js@ 4 js-call-n-with drop ; 17 | 18 | : gl-context ^( canvas -- gl ) 19 | "webgl2" swap dup "getContext" js@ js-call-1-with ; 20 | 21 | *js-window* "WebGLRenderingContext" js@ val> *gl* 22 | 23 | : gl-compile-shader { src type gl -- shader status } 24 | gl dup type js@ swap dup "createShader" js@ js-call-1-with { shader } 25 | shader src gl dup "shaderSource" js@ js-call-2-with drop 26 | shader gl dup "compileShader" js@ js-call-1-with drop 27 | ( shader gl dup "getShaderInfoLog" js@ js-call-1-with . ) 28 | shader dup gl dup "COMPILE_STATUS" js@ swap dup "getShaderParameter" js@ js-call-2-with ; 29 | 30 | : gl-attach-shader ^( program shader gl -- ) 31 | dup "attachShader" js@ js-call-2-with drop ; 32 | 33 | : gl-program { vs fs gl -- program status } 34 | gl dup "createProgram" js@ js-call-with dump-ds { program } 35 | program vs gl gl-attach-shader 36 | program fs gl gl-attach-shader 37 | program gl dup "linkProgram" js@ js-call-1-with drop 38 | program dup gl dup "LINK_STATUS" js@ swap dup "getProgramParameter" js@ js-call-2-with ; 39 | 40 | : gl-use-program ^( program gl ) 41 | dup "useProgram" js@ js-call-1-with drop ; 42 | 43 | : gl-attribute-loc ^( program att gl -- loc ) 44 | dup "getAttribLocation" js@ js-call-2-with ; 45 | 46 | : gl-uniform-loc ^( program uni gl -- loc ) 47 | dup "getUniformLocation" js@ js-call-2-with ; 48 | 49 | : gl-uniform1f! ^( uni x gl -- ) 50 | dup "uniform1f" js@ js-call-2-with drop ; 51 | 52 | : gl-uniform2f! ^( uni x y gl -- ) 53 | dup "uniform2f" js@ 3 js-call-n-with drop ; 54 | 55 | : gl-buffer ^( gl -- vbo ) dup "createBuffer" js@ js-call-with ; 56 | 57 | : gl-bind-buffer ^( vbo gl -- ) 58 | dup "ARRAY_BUFFER" js@ -rot dup "bindBuffer" js@ js-call-2-with drop ; 59 | 60 | : gl-buffer-data ^( data gl -- ) 61 | dup "ARRAY_BUFFER" js@ -rot 62 | dup "STATIC_DRAW" js@ swap 63 | dup "bufferData" js@ 3 js-call-n-with drop ; 64 | 65 | : gl-vertex-attrib-pointer { loc stride gl } 66 | loc stride gl "FLOAT" js@ false 0 0 gl dup "vertexAttribPointer" js@ 6 js-call-n-with drop ; 67 | 68 | : gl-enable-vertex-attrib ^( vattr gl -- ) 69 | dup "enableVertexAttribArray" js@ js-call-1-with drop ; 70 | 71 | : gl-draw-arrays ^( type n gl -- ) 72 | 0 -rot dup "drawArrays" js@ 3 js-call-n-with drop ; 73 | -------------------------------------------------------------------------------- /lib/glsl.fs: -------------------------------------------------------------------------------- 1 | 1 var> *glsl-comments* 2 | 3 | : make-var-table { prefix n } 4 | n [ n swap - prefix swap + ] n dup heap-allot ds->heap ; 5 | 6 | "s" 64 make-var-table val> *ds* -1 dup var> *dsp* var> *max-dsp* 7 | "r" 64 make-var-table val> *rs* -1 dup var> *rsp* var> *max-rsp* 8 | 9 | : stack! ^( sp n ) over @ + swap ! ; 10 | : ds@ ^( n ) *dsp* @ + *ds* + @ ; 11 | : rs@ ^( n ) *rsp* @ + *rs* + @ ; 12 | : ds! ^( n ) *dsp* swap stack! ; 13 | : rs! ^( n ) *rsp* swap stack! ; 14 | 15 | : reset-stack 16 | -1 dup *dsp* ! *max-dsp* ! 17 | -1 dup *rsp* ! *max-rsp* ! ; 18 | 19 | : update-max-stack 20 | *dsp* @ *max-dsp* @ max *max-dsp* ! 21 | *rsp* @ *max-rsp* @ max *max-rsp* ! ; 22 | 23 | js-obj var> *glsl-dict* 24 | 25 | ( GLSL preamble ) 26 | 27 | "#version 300 es 28 | 29 | #ifdef GL_FRAGMENT_PRECISION_HIGH 30 | precision highp int; 31 | precision highp float; 32 | #else 33 | precision mediump int; 34 | precision mediump float; 35 | #endif 36 | uniform vec2 resolution; 37 | uniform float time; 38 | layout(location=0) out vec4 fragColor; 39 | 40 | float random(in vec2 p, inout float seed) { 41 | seed = fract(sin(mod(seed + dot(p.xy, vec2(12.9898, 78.233)), 3.14)) * 43758.5453); 42 | return seed; 43 | } 44 | " val> *glsl-preamble* 45 | 46 | ( main function header ) 47 | 48 | "void main() { 49 | float seed = time; 50 | float _x = gl_FragCoord.x / resolution.x; 51 | float _y = gl_FragCoord.y / resolution.y; 52 | float tmp; 53 | " val> *glsl-header* 54 | 55 | : cast "(" + swap + ")" + ; 56 | : vec4 { r g b a } r ", " g ", " b ", " a + + + + + + "vec4" cast ; 57 | : tos= 0 ds@ " = " + swap + ; 58 | : rtos= 0 rs@ " = " + swap + ; 59 | : math-op -1 ds! 0 ds@ swap 1 ds@ + + tos= ; 60 | : math-fn1 "(" 0 ds@ ")" + + + tos= ; 61 | : math-fn2 -1 ds! "(" 0 ds@ ", " 1 ds@ ")" + + + + + tos= ; 62 | : math-fn3 -2 ds! "(" 0 ds@ ", " 1 ds@ ", " 2 ds@ ")" + + + + + + + tos= ; 63 | : logic-op -1 ds! 0 ds@ swap 1 ds@ " ? 1.0 : 0.0" + + + tos= ; 64 | : logic-op* -1 ds! 0 ds@ "bool" cast swap 1 ds@ "bool" cast " ? 1.0 : 0.0" + + + tos= ; 65 | : int-op2 -1 ds! 0 ds@ "int" cast swap 1 ds@ "int" cast + + "float" cast tos= ; 66 | 67 | : ->x 1 ds! "_x" tos= ; 68 | : ->y 1 ds! "_y" tos= ; 69 | : ->t 1 ds! "time" tos= ; 70 | : ->pi 1 ds! 3.14159265358979 tos= ; 71 | : ->half-pi 1 ds! 1.5707963267949 tos= ; 72 | : ->tau 1 ds! 6.28318530717959 tos= ; 73 | : ->random 1 ds! "random(gl_FragCoord.xy / resolution.xy, seed)" tos= ; 74 | : ->dup 1 ds! -1 ds@ tos= ; 75 | : ->2dup 2 ds! -2 ds@ "; " -1 ds@ " = " -3 ds@ + + + + tos= ; 76 | : ->over 1 ds! -2 ds@ tos= ; 77 | : ->drop -1 ds! "" ; 78 | : ->>r -1 ds! 1 rs! 1 ds@ rtos= ; 79 | : ->r> 1 ds! -1 rs! 1 rs@ tos= ; 80 | : ->@r 1 rs! 0 ds@ rtos= ; 81 | : ->r@ 1 ds! 0 rs@ tos= ; 82 | : ->swap "tmp = " -1 ds@ "; " over " = " 0 ds@ "; " over " = tmp" + + + + + + + + ; 83 | : ->rswap "tmp = " -1 rs@ "; " over " = " 0 rs@ "; " over " = tmp" + + + + + + + + ; 84 | : ->rot "tmp = " -2 ds@ "; " over " = " -1 ds@ "; " 85 | over " = " 0 ds@ "; " over " = tmp" + + + + + + + + + + + + ; 86 | : ->-rot "tmp = " -2 ds@ "; " over " = " 0 ds@ "; " 87 | over " = " -1 ds@ "; " over " = tmp" + + + + + + + + + + + + ; 88 | : ->madd -2 ds! 0 ds@ " * " 1 ds@ " + " 2 ds@ + + + + tos= ; 89 | : ->z+ -2 ds! -1 ds@ " = " over " + " 1 ds@ "; " 0 ds@ " = " over " + " 2 ds@ + + + + + + + + + + ; 90 | : ->z* -2 ds! "tmp = " -1 ds@ " * " 1 ds@ " - " 0 ds@ " * " 2 ds@ "; " + + + + + + + + 91 | -1 ds@ " * " 2 ds@ " + " 0 ds@ " * " 1 ds@ + + + + + + tos= 92 | "; " -1 ds@ " = tmp" + + + + ; 93 | : ->** 0 ds@ " * " over + + tos= ; 94 | 95 | : ->+ " + " math-op ; 96 | : ->- " - " math-op ; 97 | : ->* " * " math-op ; 98 | : ->/ " / " math-op ; 99 | : ->= " == " logic-op ; 100 | : ->> " > " logic-op ; 101 | : ->< " < " logic-op ; 102 | : ->>= " >= " logic-op ; 103 | : -><= " <= " logic-op ; 104 | : ->not= " != " logic-op ; 105 | : ->and " && " logic-op* ; 106 | : ->or " || " logic-op* ; 107 | : ->bitand " & " int-op2 ; 108 | : ->bitor " | " int-op2 ; 109 | : ->bitxor " ^ " int-op2 ; 110 | : ->mod "mod" math-fn2 ; 111 | : ->pow "pow" math-fn2 ; 112 | : ->min "min" math-fn2 ; 113 | : ->max "max" math-fn2 ; 114 | : ->atan2 "atan" math-fn2 ; 115 | : ->step "step" math-fn2 ; 116 | : ->sin "sin" math-fn1 ; 117 | : ->cos "cos" math-fn1 ; 118 | : ->tan "tan" math-fn1 ; 119 | : ->log "log" math-fn1 ; 120 | : ->exp "exp" math-fn1 ; 121 | : ->sqrt "sqrt" math-fn1 ; 122 | : ->floor "floor" math-fn1 ; 123 | : ->ceil "ceil" math-fn1 ; 124 | : ->abs "abs" math-fn1 ; 125 | : ->fract "fract" math-fn1 ; 126 | : ->negate "-" math-fn1 ; 127 | : ->mix "mix" math-fn3 ; 128 | : ->clamp "clamp" math-fn3 ; 129 | : ->smoothstep "smoothstep" math-fn3 ; 130 | 131 | : ?float "." over dup "indexOf" js@ js-call-1-with -1 = if ".0" + then ; 132 | : push-literal 1 ds! ?float tos= ; 133 | 134 | : glsl-stack-vars { addr n } 135 | n 0 >= if 136 | "float " 0 begin 137 | dup n <= while 138 | @r addr + @ + r@ n = if ";\n" else ", " then + r> 1+ 139 | repeat drop 140 | else "" then ; 141 | 142 | : glsl-frag-color 143 | "fragColor = " 144 | *dsp* @ 1+ 145 | case/ 146 | 0 of "0.0" dup dup "1.0" endof 147 | 1 of 0 ds@ dup dup "1.0" endof 148 | 2 of -1 ds@ 0 ds@ "0.0" "1.0" endof 149 | 3 of -2 ds@ -1 ds@ 0 ds@ "1.0" endof 150 | default -3 ds@ -2 ds@ -1 ds@ 0 ds@ 151 | /case 152 | vec4 + ";\n" + ; 153 | 154 | : glsl-word-comment ^( acc word -- acc' word ) 155 | *glsl-comments* @ 156 | if dup ":" not= if dup "// " swap + "\n" + rot swap + swap then then ; 157 | 158 | : glsl-slurp-comment drop 1 begin -proc-comment dup zero? until drop ; 159 | 160 | : glsl-end-line dup "length" js@ pos? if ";\n" + then ; 161 | 162 | : glsl-continue? dup dup "" not= swap ";;" not= and ; 163 | 164 | : glsl-lookup-word ^( word -- def ) *glsl-dict* @ swap js@ ; 165 | 166 | : glsl-define-word 167 | read-token> { word } 168 | ( "// define word : " word + ) "" 169 | js-array 170 | begin read-token> dup dup "" not= swap ";" not= and while 171 | dup "(" = if glsl-slurp-comment else js-apush then 172 | repeat drop 173 | *glsl-dict* @ word js! ; 174 | 175 | : glsl-custom-word? 176 | dup glsl-lookup-word js-array? ; 177 | 178 | 0 var> *inline-recur* 179 | 180 | : glsl-inline-word { word } 181 | *glsl-comments* @ if "// inline word: " word "\n" + + else "" then 182 | word glsl-lookup-word dup "length" js@ { def n } 183 | 0 dup { i } begin i n < while 184 | def over js@ 185 | ( acc i word ) 186 | rot swap glsl-word-comment rot swap 187 | glsl-custom-word? not 188 | ( acc i word flag ) 189 | if dup "->" swap + find ( acc i word addr ) 190 | dup undefined = 191 | if drop push-literal else nip call then 192 | rot swap glsl-end-line + swap 193 | update-max-stack 194 | else ( recursive expansion ) 195 | swap >r n >r def >r word >r 196 | *inline-recur* @ call + 197 | r> set> word r> set> def r> set> n r> dup set> i 198 | then 199 | 1+ dup set> i 200 | repeat drop 201 | *glsl-comments* @ if "// end inline" "\n" + + then ; 202 | 203 | find> glsl-inline-word *inline-recur* ! 204 | 205 | : glsl-compile-word ^( token -- glsl ) 206 | dup ":" = 207 | if drop glsl-define-word 208 | else glsl-custom-word? 209 | if glsl-inline-word 210 | else dup "->" swap + find dup undefined = 211 | if drop push-literal else nip call then 212 | then 213 | then ; 214 | 215 | : glsl> 216 | reset-stack 217 | "" begin 218 | read-token> glsl-continue? while 219 | dup "(" = 220 | if glsl-slurp-comment 221 | else 222 | glsl-word-comment glsl-compile-word glsl-end-line + 223 | update-max-stack 224 | then 225 | repeat drop 226 | *glsl-preamble* *glsl-header* + 227 | *ds* *max-dsp* @ glsl-stack-vars + 228 | *rs* *max-rsp* @ glsl-stack-vars + 229 | swap + glsl-frag-color + "}" + ; -------------------------------------------------------------------------------- /lib/list.fs: -------------------------------------------------------------------------------- 1 | vocab/ HList 2 | : node ^( n -- addr ) 2 heap-allot @r ! -1 r@ 1+ ! r> ; 3 | : next ^( addr -- addr ) 1+ @ ; 4 | : proceed? ^( addr -- addr' flag ) dup pos? if next dup 0 >= else false then ; 5 | : cons ^( n addr -- addr ) swap node @r 1+ ! r> ; 6 | : rcons ^( n addr -- addr ) swap node @r swap 1+ ! r> ; 7 | : find-node ^( n addr -- addr ) begin dup pos? if 2dup @ not= else false then while 1+ @ repeat nip ; 8 | : contains? ^( n addr -- flag ) find-node pos? ; 9 | : list ^( a b .. n -- addr ) dup pos? if >r node r> 1- [ drop cons ] else drop then ; 10 | : list/ 0 begin read-token> dup "/list" not= while swap 1+ repeat drop list ; 11 | : map ^( quote addr -- ) 2dup swap apply begin proceed? while 2dup swap apply repeat 2drop ; 12 | : first ^( addr -- n ) dup pos? if @ else drop nil then ; 13 | : second ^( addr -- n ) proceed? if @ else drop nil then ; 14 | : third ^( addr -- n ) proceed? if second else drop nil then ; 15 | : length ^( addr -- len ) 1 >r begin proceed? while r> 1+ >r repeat drop r> ; 16 | : nth ^( addr n -- n' ) [ drop next ] @ ; 17 | /vocab 18 | 19 | \ HList. list/ 10 20 30 /list val> a -------------------------------------------------------------------------------- /lib/math.fs: -------------------------------------------------------------------------------- 1 | vocab/ Math 2 | *js-window* "Math" js@ val> *math* 3 | *math* "sin" js@ val> *sin* 4 | *math* "cos" js@ val> *cos* 5 | *math* "floor" js@ val> *floor* 6 | *math* "ceil" js@ val> *ceil* 7 | *math* "pow" js@ val> *pow* 8 | *math* "random" js@ val> *random* 9 | *math* "sqrt" js@ val> *sqrt* 10 | *math* "min" js@ val> *min* 11 | *math* "max" js@ val> *max* 12 | *math* "PI" js@ val> pi 13 | pi 2 / val> half-pi 14 | pi 2 * val> two-pi 15 | : sin *sin* js-call-1 ; 16 | : cos *cos* js-call-1 ; 17 | : floor *floor* js-call-1 ; 18 | : ceil *ceil* js-call-1 ; 19 | : sqrt *sqrt* js-call-1 ; 20 | : pow *pow* js-call-2 ; 21 | : min *min* js-call-2 ; 22 | : max *max* js-call-2 ; 23 | : random *random* js-call 2 * 1- ; 24 | /vocab -------------------------------------------------------------------------------- /lib/swizzle.fs: -------------------------------------------------------------------------------- 1 | : float32 *js-window* "Float32Array" js@ js-new-1 ; 2 | 3 | : sw2@ ^( i1 i2 v -- v1 v2 ) @r swap js@ r> rot js@ swap ; 4 | : sw4@ ^( i1 i2 i3 i4 v -- v1 v2 v3 v4 ) @r sw2@ r> -rot sw2@ 2r> ; 5 | 6 | : swxx@ ^( v -- x x ) 0 js@ dup ; 7 | : swxy@ ^( v -- x y ) dup 0 js@ swap 1 js@ ; 8 | : swxz@ ^( v -- x z ) dup 0 js@ swap 2 js@ ; 9 | 10 | : sw2xy! ^( x y v -- ) dup -rot 1 js! 0 js! ; 11 | : sw2xz! ^( x z v -- ) dup -rot 2 js! 0 js! ; 12 | : sw2yz! ^( y z v -- ) dup -rot 2 js! 1 js! ; 13 | : sw1xy! ^( n v -- ) 2dup 0 js! 1 js! ; 14 | : sw1xz! ^( n v -- ) 2dup 0 js! 2 js! ; 15 | : sw1xw! ^( n v -- ) 2dup 0 js! 3 js! ; 16 | : sw1yz! ^( n v -- ) 2dup 1 js! 2 js! ; 17 | : sw1yw! ^( n v -- ) 2dup 1 js! 3 js! ; 18 | : sw1zw! ^( n v -- ) 2dup 2 js! 3 js! ; 19 | 20 | : sw-add-xy ^( a b c -- ) 21 | -rot 22 | 2dup 0 js@ swap 0 js@ + >r 23 | 1 js@ swap 1 js@ + 24 | over 1 js! 25 | r> swap 0 js! ; 26 | 27 | : sw-add-xyz ^( a b c -- ) 28 | -rot 29 | 2dup 0 js@ swap 0 js@ + >r 30 | 2dup 1 js@ swap 1 js@ + >r 31 | 2 js@ swap 2 js@ + over 2 js! 32 | r> over 1 js! 33 | r> swap 0 js! ; 34 | 35 | : mat4-identity ^( addr ) 1 over 0 js! 1 over 5 js! 1 over 10 js! 1 swap 15 js! ; 36 | 37 | : madd2 ^( a1 a2 b1 b2 -- prodsum ) * >r * r> + ; 38 | 39 | : madd4 ^( a1 a2 b1 b2 c1 c2 d1 d2 -- prodsum ) madd2 >r madd2 r> + ; 40 | 41 | : mat4@ { addr } 0 begin dup 4 < while addr over js@ swap 1+ repeat drop ; 42 | : mat16@ { addr } 0 begin dup 16 < while addr over js@ swap 1+ repeat drop ; 43 | 44 | : mul4x4 ^( m1addr m2addr ) 45 | >r mat16@ { m00 m01 m02 m03 m10 m11 m12 m13 m20 m21 m22 m23 m30 m31 m32 m33 } 46 | r> mat16@ { n00 n01 n02 n03 n10 n11 n12 n13 n20 n21 n22 n23 n30 n31 n32 n33 } 47 | m00 n00 m10 n01 m20 n02 m30 n03 madd4 48 | m01 n00 m11 n01 m21 n02 m31 n03 madd4 49 | m02 n00 m12 n01 m22 n02 m32 n03 madd4 50 | m03 n00 m13 n01 m23 n02 m33 n03 madd4 51 | 52 | m00 n10 m10 n11 m20 n12 m30 n13 madd4 53 | m01 n10 m11 n11 m21 n12 m31 n13 madd4 54 | m02 n10 m12 n11 m22 n12 m32 n13 madd4 55 | m03 n10 m13 n11 m23 n12 m33 n13 madd4 56 | 57 | m00 n20 m10 n21 m20 n22 m30 n23 madd4 58 | m01 n20 m11 n21 m21 n22 m31 n23 madd4 59 | m02 n20 m12 n21 m22 n22 m32 n23 madd4 60 | m03 n20 m13 n21 m23 n22 m33 n23 madd4 61 | 62 | m00 n30 m10 n31 m20 n32 m30 n33 madd4 63 | m01 n30 m11 n31 m21 n32 m31 n33 madd4 64 | m02 n30 m12 n31 m22 n32 m32 n33 madd4 65 | m03 n30 m13 n31 m23 n32 m33 n33 madd4 ; 66 | 67 | : trace4 { a b c d } a " " + b + " " + c + " " + d + . ; 68 | 69 | : trace16 { m00 m01 m02 m03 m10 m11 m12 m13 m20 m21 m22 m23 m30 m31 m32 m33 } 70 | m00 m01 m02 m03 trace4 71 | m10 m11 m12 m13 trace4 72 | m20 m21 m22 m23 trace4 73 | m30 m31 m32 m33 trace4 ; -------------------------------------------------------------------------------- /lib/synth.fs: -------------------------------------------------------------------------------- 1 | "lib/audio.fs" 2 | "lib/math.fs" 3 | "lib/bench.fs" 4 | "lib/swizzle.fs" 5 | include* 6 | 7 | 44100 val> *rate* 8 | *rate* 44100 / val> *rate-scale* 9 | *rate-scale* 4 * val> *ctrl-rate-scale* 10 | 174.614115728 *rate* / val> *freq-scale* 11 | 0x20000 val> *max-delay* 12 | *max-delay* float32 val> *delay-buffer-left* 13 | *max-delay* float32 val> *delay-buffer-right* 14 | 27 val> *instr-size* 15 | 19 val> *note-size* 16 | 8 val> *max-poly* 17 | *note-size* *max-poly* 18 | * heap-allot val> *note-buf* 19 | *max-poly* heap-allot 20 | dup val> *active-notes* *max-poly* clear-mem! 21 | 22 | Math. two-pi val> tau 23 | Math. *sin* val> sin 24 | 25 | "C" "C#" "D" "D#" "E" "F" "F#" "G" "G#" "A" "A#" "B" 26 | 12 dup heap-allot ds->heap val> *raw-note-names* 27 | 28 | \ C3 = 99, C4 = 111, C5 = 123 29 | : note->freq ( n ) 128 - 12 / 2 swap Math. pow *freq-scale* * ; 30 | 31 | : -make-note-word ^( name note ) 32 | swap create-word compile> lit >dict compile> exit ; 33 | 34 | : make-notes 35 | 0 begin dup 60 < while 36 | dup 12 mod *raw-note-names* + @ ( i name ) 37 | over 12 / 0 bit-or 3 + + ( i name ) 38 | over 99 + ( i name note ) 39 | -make-note-word 40 | 1+ repeat drop ; 41 | 42 | make-notes 43 | 44 | : note> read-token> read-token> -make-note-word ; 45 | 46 | : osc-sin ^( f ) tau * sin js-call-1 ; 47 | : osc-saw ^( f ) 1 mod 2 * 1- ; 48 | : osc-sq ^( f ) 1 mod 0.5 < if 1 else -1 then ; 49 | : osc-tri ^( f ) 1 mod 4 * dup 2 < if 1- else 3 swap - then ; 50 | 51 | [ osc-sin osc-saw osc-sq osc-tri ] val> *osc-table* 52 | 53 | : get-osc ( id ) *osc-table* + @ ; 54 | 55 | \ \\\\\\\\\\\\\\\\\\\\ instrument parameters 56 | 57 | : instr-osc1 ; 58 | : instr-osc1@ @ get-osc ; 59 | : instr-vol1 1+ ; 60 | : instr-semi1 2 + ; 61 | : instr-xenv1 3 + ; 62 | : instr-osc2 4 + ; 63 | : instr-osc2@ 4 + @ get-osc ; 64 | : instr-vol2 5 + ; 65 | : instr-semi2 6 + ; 66 | : instr-detune2 7 + ; 67 | : instr-xenv2 8 + ; 68 | : instr-noise-vol 9 + ; 69 | : instr-env-attack 10 + ; 70 | : instr-env-sustain 11 + ; 71 | : instr-env-release 12 + ; 72 | : instr-lfo-osc 13 + ; 73 | : instr-lfo-osc@ 13 + @ get-osc ; 74 | : instr-lfo-amp 14 + ; 75 | : instr-lfo-freq 15 + ; 76 | : instr-lfo-osc-freq 16 + ; 77 | : instr-lfo-fx-freq 17 + ; 78 | : instr-fx-filter 18 + ; 79 | : instr-fx-freq 19 + ; 80 | : instr-fx-res 20 + ; 81 | : instr-fx-dist 21 + ; 82 | : instr-fx-drive 22 + ; 83 | : instr-fx-pan 23 + ; 84 | : instr-fx-pan-freq 24 + ; 85 | : instr-fx-delay-amp 25 + ; 86 | : instr-fx-delay-time 26 + ; 87 | 88 | \ \\\\\\\\\\\\\\\\\\\\ instrument definition 89 | 90 | : instrument> *instr-size* dup heap-allot ds->heap val> ; 91 | 92 | : clone-instrument ^( addr -- addr' ) *instr-size* heap-allot @r *instr-size* mem-copy r> ; 93 | 94 | 2 100 128 0 3 201 133 10 0 0 5 6 160 0 195 6 0.1 1 2 135 0 0 32 147 6 121 6 instrument> softy 95 | 96 | \ \\\\\\\\\\\\\\\\\\\\ note structure 97 | 98 | : note-start@ @ ; 99 | : note-progress 1+ ; 100 | : note-osc1-time 2 + ; 101 | : note-osc2-time 3 + ; 102 | : note-osc1-freq 4 + ; 103 | : note-osc2-freq 5 + ; 104 | : note-instr 6 + ; \ 13 entries 105 | : note-age ^( t idx -- age ) *note-size* * *note-buf* + @ - ; 106 | 107 | : init-note { addr note inst } 108 | addr now addr ! 109 | note inst instr-semi1 @ + 128 - note->freq 110 | over note-osc1-freq ! 111 | note inst instr-semi2 @ + 128 - note->freq 112 | inst instr-detune2 @ 0.0008 * 1+ * 113 | over note-osc2-freq ! 114 | 0 over 2dup 2dup note-progress ! note-osc1-time ! note-osc2-time ! 115 | inst over note-instr 13 mem-copy ; 116 | 117 | ( check if note slot is active ) 118 | : free-note? ^( idx -- addr flag ) *active-notes* + @ zero? ; 119 | 120 | ( returns 1st free note slot or else oldest ) 121 | : find-free-note ( -- idx ) 122 | now { t } -1 { idx } 0 { oldest-idx } -100 { max-age } 123 | *max-poly* 1- 124 | begin 125 | dup 0 >= idx neg? and while 126 | dup free-note? 127 | if dup set> idx 128 | else t over note-age dup max-age > 129 | if set> max-age dup set> oldest-idx else drop then 130 | then 1- 131 | repeat 132 | drop idx neg? 133 | if oldest-idx else idx then ; 134 | 135 | : note-address ^( id ) *note-size* * *note-buf* + ; 136 | : note-active? *active-notes* + @ 1 = ; 137 | 138 | : clear-buffer! { buf start end } 0 buf end 1- begin dup start >= while js!! 1- repeat 2drop drop ; 139 | 140 | : free-note-slot ^( id -- ) 0 swap *active-notes* + ! ; 141 | 142 | : scaled-env-param ^( param -- param' ) dup *ctrl-rate-scale* * * ; 143 | 144 | : note-env-params ^( noteaddr -- a s r invr ) 145 | note-instr 146 | dup instr-env-attack @ scaled-env-param swap 147 | dup instr-env-sustain @ scaled-env-param swap 148 | instr-env-release @ scaled-env-param 149 | dup -1 swap / ; 150 | 151 | : generate-note { lbuf rbuf size id instr } 152 | id note-active? 153 | if 154 | 1 { e } 155 | id note-address dup { note } 156 | note-env-params 4dup { attack sustain release inv-release } drop 157 | note note-progress @ dup { progress } - + + { remaining } 158 | attack sustain + { sustain-end } 159 | remaining size <= 160 | if id free-note-slot else size set> remaining then 161 | note dup 2dup 2dup 162 | note-osc1-freq @ { osc1f } note-osc1-time @ { osc1t } 163 | note-osc2-freq @ { osc2f } note-osc2-time @ { osc2t } 164 | note-instr dup 2dup 2dup 165 | instr-osc1@ { osc1 } instr-vol1 @ { osc1vol } instr-xenv1 @ { xenv1 } 166 | instr-osc2@ { osc2 } instr-vol2 @ { osc2vol } instr-xenv2 @ { xenv2 } 167 | rbuf 0 begin dup remaining < while 168 | progress dup attack < if 169 | attack / set> e 170 | else 171 | sustain-end >= 172 | if progress sustain-end - inv-release * 1+ set> e then 173 | then 174 | osc1f xenv1 if e dup * * then osc1t + dup set> osc1t osc1 call osc1vol * 175 | osc2f xenv2 if e dup * * then osc2t + dup set> osc2t osc2 call osc2vol * + 176 | e 0.002441481 * * 177 | >r 2dup 2dup js@ r> + -rot js! ( update rbuf ) 178 | progress 1+ set> progress 179 | 1+ 180 | repeat 181 | 2drop 182 | ( note ) 183 | osc1t over note-osc1-time ! 184 | osc2t over note-osc2-time ! 185 | progress swap note-progress ! 186 | then ; 187 | 188 | \ *note-buf* 135 softy init-note 1 *active-notes* ! drop 189 | 190 | \ 2048 float32 val> buf 191 | 192 | \ *note-buf* 7 *note-size* * + 99 softy init-note 1 *active-notes* 7 + ! 193 | 194 | \ *note-buf* *note-size* 8 * dump 195 | 196 | : render-slice { frames } 197 | [ dup 0 frames clear-buffer! 198 | nil swap frames 0 softy generate-note 199 | rdrop rdrop ] ; 200 | 201 | : play { note instr } *note-buf* note instr init-note drop 1 *active-notes* ! ; 202 | 203 | Audio. new-context val> ctx 204 | 2048 render-slice 2048 1 ctx Audio. new-script val> script 205 | 206 | ( 207 | 208 | *note-buf* C6 softy init-note drop 209 | 250 sleep 210 | *note-buf* D#6 softy init-note drop 211 | 375 sleep 212 | *note-buf* A6 softy init-note drop 213 | 214 | ) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@thi.ng/charlie", 3 | "version": "1.0.2", 4 | "main": "index.js", 5 | "repository": "https://github.com/thi-ng/charlie", 6 | "author": "Karsten Schmidt ", 7 | "license": "MIT", 8 | "scripts": { 9 | "clean": "rm -rf .cache build out", 10 | "start": "vite --open", 11 | "build": "yarn clean && vite build --base='./' && cp -R demo lib dist/", 12 | "preview": "vite preview --host --open" 13 | }, 14 | "dependencies": { 15 | "@thi.ng/api": "^8.11.21", 16 | "@thi.ng/expose": "^1.2.48" 17 | }, 18 | "devDependencies": { 19 | "typescript": "^5.8.2", 20 | "vite": "^6.2.0" 21 | }, 22 | "browserslist": [ 23 | "last 3 Chrome versions" 24 | ], 25 | "browser": { 26 | "process": false 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // _______ ___ ___ _______ _______ ___ ___ _______ ___ ___ ___ ___ 2 | // | _ | Y | _ | _ | | | | _ | | Y | Y | 3 | // |. 1___|. 1 |. 1 |. l |. | |. |. 1___| |. | |. | 4 | // |. |___|. _ |. _ |. _ |. |___|. |. __)_ ______|. | |. \_/ | 5 | // |: 1 |: | |: | |: | |: 1 |: |: 1 |______|: 1 |: | | 6 | // |::.. . |::.|:. |::.|:. |::.|:. |::.. . |::.|::.. . | \:.. ./|::.|:. | 7 | // `-------`--- ---`--- ---`--- ---`-------`---`-------' `---' `--- ---' 8 | // 9 | // (c) 2015 - 2025 Karsten Schmidt // MIT licensed 10 | 11 | import { Charlie } from "./vm.js"; 12 | import { KERNEL } from "./kernel.js"; 13 | import { REPL } from "./repl.js"; 14 | 15 | Charlie.interpreter(KERNEL); 16 | 17 | new REPL(Charlie).start(); 18 | -------------------------------------------------------------------------------- /src/kernel.ts: -------------------------------------------------------------------------------- 1 | // _______ ___ ___ _______ _______ ___ ___ _______ ___ ___ ___ ___ 2 | // | _ | Y | _ | _ | | | | _ | | Y | Y | 3 | // |. 1___|. 1 |. 1 |. l |. | |. |. 1___| |. | |. | 4 | // |. |___|. _ |. _ |. _ |. |___|. |. __)_ ______|. | |. \_/ | 5 | // |: 1 |: | |: | |: | |: 1 |: |: 1 |______|: 1 |: | | 6 | // |::.. . |::.|:. |::.|:. |::.|:. |::.. . |::.|::.. . | \:.. ./|::.|:. | 7 | // `-------`--- ---`--- ---`--- ---`-------`---`-------' `---' `--- ---' 8 | // 9 | // (c) 2015 - 2025 Karsten Schmidt // MIT licensed 10 | 11 | /** 12 | * Partially based on: 13 | * https://github.com/phaendal/mkforth4-js/blob/master/builtin.fs 14 | */ 15 | export const KERNEL = [ 16 | ": find> read-token> find ;", 17 | ": xt->dfa xt->cfa cfa->dfa ;", 18 | ": vm-mode? vm-mode@ = ;", 19 | ": compile-mode? *vm-mode-compile* vm-mode? ;", 20 | ": immediate-mode? *vm-mode-immediate* vm-mode? ;", 21 | ": ^immediate immediate! ; immediate!", 22 | ": postpone ^immediate find> >dict ;", 23 | ": 'lit lit lit >dict ;", 24 | ": '>dict lit >dict >dict ;", 25 | ": compile> ^immediate 'lit find> >dict '>dict ;", 26 | ": recur ^immediate latest@ >dict ;", 27 | ": latest-start latest@ xt->dfa ;", 28 | ": tail-recur ^immediate compile> branch latest-start >dict ;", 29 | 30 | // control flow 31 | // if 32 | 33 | ": dict-here@ 0 >dict ;", 34 | ": if ^immediate compile> alt-branch ;", 35 | ": dict-here@ swap ! ;", 36 | ": then ^immediate ;", 37 | ": else ^immediate compile> branch swap ;", 38 | 39 | // loop 40 | 41 | ": begin ^immediate dict-here@ ;", 42 | ": until ^immediate compile> alt-branch >dict ;", 43 | ": again ^immediate compile> branch >dict ;", 44 | ": while ^immediate compile> alt-branch dict-here@ 0 >dict ;", 45 | ": repeat ^immediate compile> branch swap >dict dict-here@ swap ! ;", 46 | 47 | // comments 48 | 49 | ': l-paren? dup dup "(" = swap "^(" = or ;', 50 | ': r-paren? dup ")" = ;', 51 | ": -proc-r-paren r-paren? if drop 1- else drop then ;", 52 | ": -proc-l-paren l-paren? if drop 1+ else -proc-r-paren then ;", 53 | ": -proc-comment read-token> -proc-l-paren ;", 54 | ": ( ^immediate 1 begin -proc-comment dup zero? until drop ;", 55 | 56 | // define word 57 | 58 | ": create-word ( name -- ) new-word-header latest@ set-word-name! ;", 59 | 60 | // values 61 | 62 | ": create-value create-word compile> lit >dict compile> exit ;", 63 | ": val> ( x -- ) read-token> create-value ;", 64 | ": value-addr ( xt -- addr ) xt->dfa 1+ ;", 65 | ": set-when-compile ( addr -- ) compile> lit >dict compile> ! ;", 66 | ": set> ( ?n -- ) ^immediate find> value-addr compile-mode? if set-when-compile else ! then ;", 67 | 68 | // doc comments 69 | 70 | "0 val> *doc-string*", 71 | ": -doc-proc-r-paren r-paren? if drop 1-", 72 | ' else *doc-string* swap + " " + set> *doc-string* then ;', 73 | ": -doc-proc-l-paren l-paren? if drop 1+ else -doc-proc-r-paren then ;", 74 | ": -doc-proc-comment read-token> -doc-proc-l-paren ;", 75 | 76 | ': ^( ^immediate "" set> *doc-string* 1 begin', 77 | " -doc-proc-comment dup zero? until drop", 78 | " compile-mode?", 79 | " if *doc-string* latest@ set-word-doc! then ;", 80 | 81 | ": get-word-name ^( xt -- name ) @ 0 js@ ;", 82 | ": get-word-doc ^( xt -- doc ) @ 4 js@ ;", 83 | ": hidden? ^( xt -- bool ) @ 2 js@ ;", 84 | ": immediate? ^( xt -- bool ) @ 1 js@ ;", 85 | 86 | ": doc> find>", 87 | " dup undefined = if", 88 | ' "Word not found" .', 89 | " else", 90 | ' dup get-word-name " ( " + swap get-word-doc', 91 | ' dup undefined = if drop "no doc" then', 92 | ' + " )" + .', 93 | " then ;", 94 | 95 | // post-add doc strings 96 | 97 | ": set-word-doc> ^( doc -- ) find> set-word-doc! ;", 98 | ": set-doc-latest! ^( doc -- ) latest@ set-word-doc! ;", 99 | '"name --" set-word-doc> create-word', 100 | '"x --" set-word-doc> val>', 101 | '"x --" set-word-doc> set>', 102 | 103 | // vars 104 | 105 | ": cells-after ( n -- addr after-addr ) dict-here@ dup rot + ;", 106 | ": allot ( n -- addr ) cells-after dict-here! ;", 107 | ": create-variable ( name -- addr ) 1 allot swap create-word compile> lit >dict compile> exit ;", 108 | ": var> ( n -- ) read-token> create-variable latest@ call ! ;", 109 | ": ready-value ( n addr - addr value n ) dup @ rot ;", 110 | ": +! ( n addr -- ) ready-value + swap ! ;", // FIXME: really need those? 111 | ": -! ( n addr -- ) ready-value - swap ! ;", // FIXME: 112 | ": inc! ( addr -- ) dup @ 1+ swap ! ;", 113 | ": dec! ( addr -- ) dup @ 1- swap ! ;", 114 | 115 | // local variables 116 | 117 | "0 val> *locals-target-word*", 118 | ": -clear-target-word-addr 0 set> *locals-target-word* ;", 119 | ": -save-target-word latest@ set> *locals-target-word* ;", 120 | 121 | "0 val> *locals-true-prev*", 122 | ": -save-true-prev latest@ prev-word@ set> *locals-true-prev* ;", 123 | ": -restore-true-prev *locals-true-prev* latest@ prev-word! ;", 124 | 125 | ": -2prev-chain ^( xt -- xt prev1 prev2 ) dup prev-word@ ( xt prev1 ) dup prev-word@ ;", 126 | 127 | ": -swap-prev-chain", 128 | " ^( var -> word -> prev => word -> var -> prev )", 129 | " latest@ -2prev-chain ( -- &var &word &prev )", 130 | " rot tuck ( -- &word &var &prev &var )", 131 | " prev-word! swap tuck ( -- &word &var &word )", 132 | " prev-word! ( -- &word )", 133 | " latest! ;", 134 | 135 | "0 val> *local-var-name*", 136 | "0 val> *local-var-addr*", 137 | 138 | ": -allot-var-space ^( -- addr ) compile> branch dict-here@ 0 >dict ;", 139 | ": -jump-to-here ^( addr -- ) dict-here@ swap ! ;", 140 | ": create-local-var ^( name -- ) 0 swap create-value ;", 141 | ": -save-local-var-addr latest@ set> *local-var-addr* ;", 142 | ": -save-target-word *locals-target-word* zero? if -save-target-word -save-true-prev then ;", 143 | ": -restore-target-word *locals-target-word* zero? not if -restore-true-prev then ;", 144 | ": -compile-set-local compile> lit *local-var-addr* value-addr >dict compile> ! ;", 145 | 146 | ": -compile-local-variable-definition ^( name -- addr )", 147 | " set> *local-var-name*", 148 | " -save-target-word", 149 | " -allot-var-space", 150 | " *local-var-name* create-local-var", 151 | " -save-local-var-addr", 152 | " -swap-prev-chain", 153 | " -jump-to-here", 154 | " -compile-set-local ;", 155 | 156 | ': -close-local-variable? ^( token -- bool ) dup "}" = ;', 157 | ': -close-stack-input? ^( token -- bool ) dup "--" = ;', 158 | ": -close-local-definition read-token> -close-local-variable? not if drop tail-recur then ;", 159 | 160 | ": -read-local-definitions", 161 | " read-token>", 162 | " -close-local-variable? not if", 163 | " -close-stack-input? not if", 164 | " tail-recur", 165 | " then drop -close-local-definition", 166 | " then drop ;", 167 | 168 | ": { ^immediate", 169 | " 0 -read-local-definitions", 170 | " begin dup zero? not while", 171 | " -compile-local-variable-definition", 172 | " repeat drop ;", 173 | 174 | ": : -clear-target-word-addr : ;", 175 | 176 | ": ; ^immediate -restore-target-word postpone ; ;", 177 | 178 | // quotation 179 | 180 | ": -open-quote-compile ( -- addr2 )", 181 | " compile> lit dict-here@ 0 >dict ( -- addr1 )", 182 | " compile> branch dict-here@ 0 >dict ( -- addr1 addr2 )", 183 | " swap ( -- addr2 addr1 )", 184 | " dict-here@ ( -- addr2 addr1 start-quotation )", 185 | " swap ! ; ( -- addr2 )", 186 | 187 | ": -open-quote-immediate ( -- addr ) dict-here@ postpone compile-mode! ;", 188 | ": -close-quote-compile ( addr2 -- ) dict-here@ swap ! ;", 189 | 190 | ": [ ^immediate ( -- vm-mode addr2|addr1 )", 191 | " vm-mode@ compile-mode? if -open-quote-compile else -open-quote-immediate then ;", 192 | 193 | ": ] ^immediate ( vm-mode addr2|addr1 -- )", 194 | " compile> exit swap vm-mode! compile-mode? if -close-quote-compile then ;", 195 | 196 | ": apply ^immediate", 197 | " compile-mode? if", 198 | " compile> lit", 199 | " dict-here@ 0 >dict", 200 | " compile> >r", 201 | " compile> jump", 202 | " dict-here@ swap !", 203 | " else", 204 | " 0 >r jump", 205 | " then ;", 206 | 207 | ": quote> ^immediate ( -- dfa )", 208 | " compile-mode? if", 209 | " compile> lit find> >dict compile> xt->dfa", 210 | " else", 211 | " find> xt->dfa", 212 | " then ;", 213 | 214 | // stack ops 215 | 216 | ": 2over 3 pick 3 pick ;", 217 | ": 3dup dup 2over rot ;", 218 | ": 4dup 2over 2over ;", 219 | 220 | // combinators 221 | 222 | "\\ https://gist.github.com/crcx/8060687", 223 | "\\ >r ... r>", 224 | ": dip swap >r apply r> ;", 225 | "\\ dup >r ... r>", 226 | ": sip over >r apply r> ;", 227 | "\\ 12 [ 3 * ] [ 4 * ] bi => 12 3 * 12 4 *", 228 | ": bi >r sip r> apply ;", 229 | "\\ 2 4 [ 3 * ] [ 5 * ] bi* => 2 3 * 4 5 *", 230 | ": bi* >r dip r> apply ;", 231 | "\\ 2 4 [ 3 * ] bi@ => 2 3 * 4 3 *", 232 | ": bi@ dup bi* ;", 233 | 234 | // control flow w/ quotation 235 | 236 | ": { cond [true-part] [false-part] -- ... }", 237 | " cond if [true-part] else [false-part] then apply ;", 238 | 239 | ": ^( cond quot ) [ ] ;", 240 | 241 | ": ^( cond quot ) [ ] swap ;", 242 | 243 | ": ^( [cond-part] [loop-part] -- )", 244 | " { [cond-part] [loop-part] }", 245 | " begin [cond-part] apply while [loop-part] apply repeat ;", 246 | 247 | ": ", 248 | " ^( counter [loop-part] -- ? )", 249 | " { counter [loop-part] }", 250 | " [ counter 0 > ]", 251 | " [ counter [loop-part] apply counter 1- set> counter ]", 252 | " ;", 253 | 254 | ": { cin cout [loop-part] }", 255 | " cin { cin' }", 256 | " [ cout 0 >= ]", 257 | " [ cin cout [loop-part] apply", 258 | " cin 1- dup neg? if drop cin' then dup set> cin", 259 | " cin' = if cout 1- set> cout then ]", 260 | " ;", 261 | 262 | // Case / switch 263 | 264 | ": case/ ^immediate 0 ;", 265 | ": of ^immediate", 266 | " compile> swap", 267 | " compile> tuck", 268 | " compile> =", 269 | " postpone if", 270 | " compile> drop ;", 271 | ": endof ^immediate postpone else ;", 272 | ": default ^immediate compile> drop ;", 273 | ": /case ^immediate", 274 | " begin dup zero? not while postpone then repeat drop ;", 275 | 276 | // debug 277 | 278 | '*js-window* "String" js@ dup val> String "fromCharCode" js@ val> *charcode->str*', 279 | ": [char] *charcode->str* js-call-1 ;", 280 | ": hexdigit dup 10 < if 48 else 87 then + [char] ;", 281 | ": hex8 dup 4 >> 15 bit-and hexdigit swap 15 bit-and hexdigit + ;", 282 | ': hex16 "0x" swap dup 8 >> hex8 swap 255 bit-and hex8 + + ;', 283 | ': hex24 "0x" swap dup 16 >> hex8 swap dup 8 >> hex8 swap 255 bit-and hex8 + + + ;', 284 | 285 | ": hide! ( xt ) true set-hidden! ;", 286 | ": forget { xt -- } xt prev-word@ latest! xt dict-here! ;", 287 | ": forget> find> forget ;", 288 | ': dump-memory dup hex24 " " + swap @ + . ;', 289 | ": dump { addr len i -- } addr i + dump-memory len 1- pos? [ addr len 1- i 1+ tail-recur ] ;", 290 | ": dump ( addr len -- ) 0 dump ;", 291 | 292 | ': dump-ds dsp@ 0 begin 2dup >= while dup "" swap + ": " + over 3 + pick + . 1+ repeat 2drop ;', 293 | ': spy> "entering: " find> @r get-word-name + . dump-ds r> call ;', 294 | 295 | ': show-rsp { rsp -- } rsp ": " + rsp rpick @ get-word-name + . ;', 296 | ": dump-rs { rsp -- } rsp 0 >= [ rsp show-rsp rsp 1- tail-recur ] ;", 297 | ": dump-rs rsp@ 1- dump-rs ;", 298 | 299 | ": show-words", 300 | " latest@ { adr }", 301 | ' ""', 302 | " [ adr pos? ]", 303 | ' [ adr get-word-name + " " + adr prev-word@ set> adr ]', 304 | " . ;", 305 | 306 | ': starts-with? ^( str pre -- flag ) swap dup "startsWith" js@ js-call-1-with ;', 307 | ': public-word? "-" starts-with? not ;', 308 | 309 | ": show-public-words", 310 | " latest@ { adr }", 311 | ' ""', 312 | " [ adr pos? ]", 313 | ' [ adr get-word-name dup public-word? if + " " + else drop then adr prev-word@ set> adr ]', 314 | " . ;", 315 | 316 | ": count-words", 317 | " latest@ { adr }", 318 | " 0", 319 | " [ adr pos? ]", 320 | " [ 1+ adr prev-word@ set> adr ]", 321 | " ;", 322 | 323 | // see 324 | 325 | ": word-length { now-addr word-addr -- }", 326 | " now-addr prev-word@ word-addr =", 327 | " [ now-addr word-addr - ]", 328 | " [ now-addr prev-word@ word-addr tail-recur ] ;", 329 | 330 | ": word-length { word-addr } latest@ word-addr word-length ;", 331 | 332 | ": latest-word-length ( -- len ) dict-here@ latest@ - ;", 333 | 334 | ": word-length { word-addr -- }", 335 | " latest@ word-addr = [ latest-word-length ] [ word-addr word-length ] ;", 336 | 337 | ": ' ^immediate compile> lit find> >dict ;", 338 | 339 | ": -see-1-addr { addr name -- str addr-inc }", 340 | ' "\n" addr hex24 +', 341 | ' " " + name + " " +', 342 | " addr 1+ @ hex24 +", 343 | " 2 ;", 344 | 345 | ': -see-mem-else { obj -- str } " " obj get-word-name + ;', 346 | 347 | ": -see-mem-else { obj -- str }", 348 | ' obj js-fn? [ " [js-fn]" ] [ obj -see-mem-else ] ;', 349 | 350 | ": -see-mem-else { obj -- str }", 351 | ' obj js-array? [ " [js-array]" ] [ obj -see-mem-else ] ;', 352 | 353 | ": -see-mem-else { obj -- str }", 354 | ' obj js-obj? [ " [js-obj]" ] [ obj -see-mem-else ] ;', 355 | 356 | ": see-mem { addr -- str addr-inc }", 357 | " addr @ case/", 358 | ' \' lit of addr "lit" -see-1-addr endof', 359 | ' \' branch of addr "branch" -see-1-addr endof', 360 | ' \' alt-branch of addr "alt-branch" -see-1-addr endof', 361 | ' \' exit of "" 1 endof', 362 | ' default "\n" addr hex24 + addr @ -see-mem-else + 1', 363 | " /case ;", 364 | 365 | ": -see-memory { str addr i -- str i }", 366 | " addr i + see-mem ( str addr-inc )", 367 | " i + set> i", 368 | " str swap + i ;", 369 | 370 | ": see { str addr len i -- code(str) }", 371 | " i len >=", 372 | ' [ str " ;" + ]', 373 | " [ str addr i -see-memory ( str i )", 374 | " set> i set> str", 375 | " str addr len i tail-recur ] ;", 376 | 377 | ": see { name addr len -- code(str) }", 378 | ' addr hex24 " : " + name +', 379 | " addr 2 + len 2 -", 380 | " 0 see ;", 381 | 382 | ": see { addr -- code(str) }", 383 | " addr get-word-name", 384 | " addr", 385 | " addr word-length", 386 | " see ;", 387 | 388 | ": see> find>", 389 | " dup undefined =", 390 | ' [ drop "word not found" . ]', 391 | " [ see . ] ;", 392 | 393 | // heap 394 | ": heap-allot ^( n -- addr ) heap-here@ dup rot + heap-here! ;", 395 | 396 | ": mem-copy ^( addr addr2 n -- )", 397 | " { n } 0 begin dup n < while >r over @ over ! 1+ swap 1+ swap r> 1+ repeat", 398 | " 2drop drop ;", 399 | 400 | ": clear-mem! { addr len }", 401 | " 0 len 1- begin dup 0 >= while 2dup addr + ! 1- repeat 2drop ;", 402 | 403 | ": ds->heap ^( ... n addr )", 404 | " over + 1-", 405 | " swap 1- begin dup 0 >= while >r tuck ! 1- r> 1- repeat", 406 | " drop 1+ ;", 407 | 408 | // arrays 409 | 410 | ": array/ js-array begin", 411 | ' read-token> dup "/array" not= while', 412 | " js-apush", 413 | " repeat drop ;", 414 | 415 | ": ds->array ^( a b c .. n array -- array )", 416 | " { n array }", 417 | " n [ dup pick array rot n swap - js! ] ", 418 | " n begin dup 0 > while swap drop 1- repeat drop array ;", 419 | 420 | // private 421 | 422 | ': -create-priv-anchor "*private-anchor*" create-word ;', 423 | ": compile-prev-to-latest-code ( this-addr -- )", 424 | " compile> lit", 425 | " prev-word@ >dict", 426 | " compile> latest!", 427 | " compile> exit ;", 428 | 429 | ": -compile-first-anchor", 430 | " dict-here@ { this-addr }", 431 | " -create-priv-anchor", 432 | " this-addr compile-prev-to-latest-code ;", 433 | 434 | ': -get-priv-anchor-prev ( -- addr ) "*private-anchor*" find prev-word@ ;', 435 | 436 | ': -hide-priv-anchor ( -- ) "*private-anchor*" find hide! ;', 437 | 438 | ": -compile-prev-to-prev-code ( this-addr prev -- )", 439 | " compile> lit >dict", 440 | " compile> lit >dict", 441 | " compile> prev-word! ;", 442 | 443 | ": -compile-hide-self-code ( this-addr ) compile> lit >dict compile> hide! ;", 444 | 445 | ": -compile-reveal-anchor-code { prev this-addr -- }", 446 | " prev this-addr -compile-prev-to-prev-code", 447 | " compile> exit ;", 448 | 449 | ": -compile-reveal-anchor", 450 | " -get-priv-anchor-prev { prev }", 451 | " -hide-priv-anchor", 452 | " dict-here@ { this-addr }", 453 | " -create-priv-anchor", 454 | " this-addr prev -compile-reveal-anchor-code ;", 455 | 456 | ": private/ ^immediate -compile-first-anchor ;", 457 | ': /private ^immediate "*private-anchor*" find call ;', 458 | ": reveal>> ^immediate -compile-reveal-anchor ;", 459 | 460 | // exceptions 461 | 462 | "0 val> *no-exception-sign*", 463 | ": exception-marker ( -- no-exception-sign ) rdrop *no-exception-sign* ;", 464 | "quote> exception-marker val> *exception-marker-dfa*", 465 | 466 | ": catch ( dfa -- exception-code )", 467 | " dsp@ 1- >r", 468 | " *exception-marker-dfa* >r", 469 | " apply ;", 470 | 471 | ": exception-marker? ( rsp -- ) rpick *exception-marker-dfa* = ;", 472 | 473 | ": continue-search-emarker? { rsp -- bool }", 474 | " rsp -1 > rsp exception-marker? not and ;", 475 | 476 | ": throw-exception { code rsp -- }", 477 | " rsp 1- set> rsp", 478 | " rsp rsp!", 479 | " r> dsp!", 480 | " code ;", 481 | 482 | ": aborted-or-uncaught-throw ( code -- )", 483 | " case/", 484 | ' -1 of "[ABORTED!]" . endof', 485 | ' "[UNCAUGHT THROW] " swap + .', 486 | " /case", 487 | " -1 rsp!", 488 | " 0 >r ;", 489 | 490 | ": throw { exception-code -- }", 491 | " rsp@ { rsp }", 492 | " [ rsp continue-search-emarker? ]", 493 | " [ rsp 1- set> rsp ] ", 494 | " rsp -1 >", 495 | " [ exception-code rsp throw-exception ]", 496 | " [ exception-code aborted-or-uncaught-throw ] ;", 497 | 498 | ": throw { exception-code -- }", 499 | " exception-code 0 not= [ exception-code throw ] ;", 500 | 501 | ": abort -1 throw ;", 502 | 503 | // vocabs 504 | "0 val> *vocab*", 505 | ": vocab->anchor-addr ( addr -- addr ) 5 + ;", 506 | ": vocab->latest-addr ( addr -- addr ) 6 + ;", 507 | ": vocab->prevoc-addr ( addr -- addr ) 7 + ;", 508 | ": vocab->prev ( addr -- addr )", 509 | " vocab->anchor-addr @ prev-word@ ;", 510 | 511 | ": prev-vocab@ ( addr -- addr ) vocab->prevoc-addr @ ;", 512 | ": prev-vocab! ( addr voc-addr -- ) vocab->prevoc-addr ! ;", 513 | 514 | ": -set-anchor-prev { latest vocab-addr -- }", 515 | " latest vocab-addr vocab->anchor-addr @ ( latest anchor-addr )", 516 | " prev-word! ;", 517 | 518 | ": -set-vocab-anchor ( anchor-addr vocab-addr -- ) vocab->anchor-addr ! ;", 519 | ": -set-vocab-latest ( latest-addr vocab-addr -- ) vocab->latest-addr ! ;", 520 | 521 | ": -create-vocab-header { name -- }", 522 | " dict-here@", 523 | " name create-word", 524 | " compile> lit", 525 | " >dict", 526 | " compile> exit ;", 527 | 528 | ": -allot-vocab-addrs", 529 | " 0 >dict ( anchor-addr )", 530 | " 0 >dict ( latest-addr )", 531 | " 0 >dict ( prevoc-addr )", 532 | " ;", 533 | 534 | ": -create-anchor-word ( -- addr ) new-word-header latest@ ;", 535 | 536 | ": search-in { vocab name -- addr }", 537 | " latest@ { latest }", 538 | " vocab vocab->latest-addr @ latest!", 539 | " name find", 540 | " latest latest! ;", 541 | 542 | ": access-word-in ( vocab name -- )", 543 | " search-in { word }", 544 | " word immediate? not compile-mode? and", 545 | " [ word >dict ]", 546 | " [ word call ] ;", 547 | 548 | ": -create-accessor ( name -- addr )", 549 | ' "." + create-word', 550 | " postpone ^immediate", 551 | " compile> lit", 552 | " dict-here@ 0 >dict", 553 | " compile> read-token>", 554 | " compile> access-word-in", 555 | " compile> exit ;", 556 | 557 | ": create-vocab { name -- }", 558 | " name -create-accessor { accessor }", 559 | " name -create-vocab-header", 560 | " latest@ accessor !", 561 | " -allot-vocab-addrs", 562 | " latest@ { vaddr }", 563 | " -create-anchor-word { aaddr }", 564 | " aaddr vaddr -set-vocab-anchor", 565 | " aaddr vaddr -set-vocab-latest", 566 | " vaddr latest! ;", 567 | 568 | ": open-vocab { addr -- }", 569 | " latest@ addr -set-anchor-prev", 570 | " addr vocab->latest-addr @ latest!", 571 | " *vocab* addr prev-vocab!", 572 | " addr set> *vocab* ;", 573 | 574 | ": does-not-exist? ( name ) find undefined = ;", 575 | 576 | ": create-vocab { name -- }", 577 | " name does-not-exist?", 578 | " [ name create-vocab ] ;", 579 | 580 | ": vocab/", 581 | " read-token> { name }", 582 | " name create-vocab", 583 | " name find open-vocab ;", 584 | 585 | ": /vocab", 586 | " latest@ *vocab* -set-vocab-latest", 587 | " *vocab* vocab->prev latest!", 588 | " *vocab* prev-vocab@ set> *vocab* ;", 589 | 590 | // Core vocab 591 | 592 | "0 val> *kernel-latest*", 593 | '"Core" create-vocab', 594 | ": -set-prev-to-now-prev { addr -- }", 595 | " *vocab* vocab->prev addr -set-anchor-prev ;", 596 | 597 | ": -set-now-prev-to-latest ( addr -- )", 598 | " vocab->latest-addr @ *vocab* -set-anchor-prev ;", 599 | 600 | ": with { addr -- }", 601 | " addr -set-prev-to-now-prev ", 602 | " addr -set-now-prev-to-latest ;", 603 | 604 | ": with> find> with ;", 605 | 606 | ": <>", 607 | " /vocab", 608 | " *kernel-latest* latest!", 609 | " Core open-vocab ;", 610 | 611 | "latest@ set> *kernel-latest*", 612 | "Core open-vocab", 613 | 614 | // List 615 | 616 | ': -mark-as-cons-list { list -- } true list "cons?" js! ;', 617 | ": list? { obj -- bool }", 618 | ' obj js-obj? [ obj "cons?" js@ true = ] [ obj nil? ] ;', 619 | 620 | ': set-first ( value list -- ) "first" js! ;', 621 | ': set-rest ( value list -- ) "rest" js! ;', 622 | 623 | ": cons { rest first -- list }", 624 | " js-obj { list }", 625 | " list -mark-as-cons-list", 626 | " first list set-first", 627 | " rest list set-rest", 628 | " list ;", 629 | 630 | ': first ^( list -- value ) dup nil? [ "first" js@ ] ;', 631 | ': rest ^( list -- xs ) dup nil? [ "rest" js@ ] ;', 632 | ": second ^( list -- value ) rest first ;", 633 | ": third ^( list -- value ) rest rest first ;", 634 | 635 | ": list> nil read-token> create-value ;", 636 | 637 | ": reverse { xs acc -- xs }", 638 | " xs nil? [ acc ] [ xs rest acc xs first cons tail-recur ] ;", 639 | ": reverse ( xs ) nil reverse ;", 640 | 641 | ": length { xs acc -- len }", 642 | " xs nil? [ acc ] [ xs rest acc 1+ tail-recur ] ;", 643 | ": length ( xs -- len ) 0 length ;", 644 | 645 | "private/", 646 | " : push-when-compile", 647 | " find> { vaddr }", 648 | " vaddr >dict", 649 | " compile> swap", 650 | " compile> cons", 651 | " compile> lit", 652 | " vaddr value-addr >dict", 653 | " compile> ! ;", 654 | 655 | " : push-when-immediate", 656 | " find> { vaddr }", 657 | " vaddr call swap cons", 658 | " vaddr value-addr ! ;", 659 | 660 | " : pop-when-compile", 661 | " find> { vaddr }", 662 | " vaddr >dict", 663 | " compile> first", 664 | " vaddr >dict", 665 | " compile> rest", 666 | " compile> lit", 667 | " vaddr value-addr >dict", 668 | " compile> ! ;", 669 | 670 | " : pop-when-immediate", 671 | " find> { vaddr }", 672 | " vaddr call first", 673 | " vaddr call rest", 674 | " vaddr value-addr ! ;", 675 | 676 | " reveal>>", 677 | " : push> ^immediate compile-mode? [ push-when-compile ] [ push-when-immediate ] ;", 678 | " : pop> ^immediate compile-mode? [ pop-when-compile ] [ pop-when-immediate ] ;", 679 | "/private", 680 | 681 | "list> *list-expr-dsp*", 682 | 683 | "private/", 684 | " : accum-list-fin ( -- ) pop> *list-expr-dsp* drop ;", 685 | " : accum-list-end? ( -- bool ) dsp@ *list-expr-dsp* first = ;", 686 | " : accum-list { acc -- xs }", 687 | " accum-list-end? [ accum-list-fin acc ] [ acc swap cons tail-recur ] ;", 688 | " reveal>>", 689 | " : list/ ( -- ) dsp@ push> *list-expr-dsp* ;", 690 | " : /list ( -- list ) nil accum-list ;", 691 | "/private", 692 | 693 | // DOM 694 | 695 | " vocab/ DOM private/", 696 | " reveal>>", 697 | ' : create-div ^( -- elm ) "div" js-create-element ;', 698 | ' : create-span ^( -- elm ) "span" js-create-element ;', 699 | 700 | " : set-style { name value elm -- }", 701 | ' elm "style" js@ { style }', 702 | " value style name js! ;", 703 | 704 | " : first-second-rest { xs -- f s r }", 705 | " xs first", 706 | " xs second", 707 | " xs rest rest ;", 708 | 709 | " : set-styles { css-list elm -- }", 710 | " css-list nil?", 711 | " [ css-list first-second-rest { name value rest-list }", 712 | " name value elm set-style", 713 | " rest-list elm tail-recur ] ;", 714 | 715 | ' : hide { elm -- } "display" "none" elm set-style ;', 716 | ' : show { elm -- } "display" "" elm set-style ;', 717 | 718 | " : delete { elm -- }", 719 | " elm js-parent-node { parent }", 720 | " elm parent js-remove-child ;", 721 | 722 | " : add-event-listener { quot elm event -- }", 723 | ' elm "addEventListener" js@ { adder }', 724 | " quot quot->js-callback { callback }", 725 | " callback event elm adder js-call-2-with drop ;", 726 | "/private /vocab", 727 | ].join("\n"); 728 | -------------------------------------------------------------------------------- /src/repl.ts: -------------------------------------------------------------------------------- 1 | // _______ ___ ___ _______ _______ ___ ___ _______ ___ ___ ___ ___ 2 | // | _ | Y | _ | _ | | | | _ | | Y | Y | 3 | // |. 1___|. 1 |. 1 |. l |. | |. |. 1___| |. | |. | 4 | // |. |___|. _ |. _ |. _ |. |___|. |. __)_ ______|. | |. \_/ | 5 | // |: 1 |: | |: | |: | |: 1 |: |: 1 |______|: 1 |: | | 6 | // |::.. . |::.|:. |::.|:. |::.|:. |::.. . |::.|::.. . | \:.. ./|::.|:. | 7 | // `-------`--- ---`--- ---`--- ---`-------`---`-------' `---' `--- ---' 8 | // 9 | // (c) 2015 - 2025 Karsten Schmidt // MIT licensed 10 | 11 | import type { Fn } from "@thi.ng/api"; 12 | import { exposeGlobal } from "@thi.ng/expose"; 13 | import type { IVM, Stack } from "./vm.js"; 14 | 15 | const MAX_HISTORY = 50; 16 | const MAX_SHOW = 200; 17 | const MAX_SHOW_STACK = 20; 18 | 19 | const replInput = document.getElementById("repl-input")!; 20 | const btEval = document.getElementById("bt-eval"); 21 | const replOutput = document.getElementById("repl-out"); 22 | const dsItems = document.getElementById("ds-items"); 23 | const rsItems = document.getElementById("rs-items"); 24 | 25 | const removeAllChildren = (elm: HTMLElement) => { 26 | while (elm.firstChild) { 27 | elm.removeChild(elm.firstChild); 28 | } 29 | }; 30 | 31 | export class REPL { 32 | history: string[] = []; 33 | historySel: number; 34 | wordStart: number; 35 | wordCache: string[]; 36 | wordCandidates: string[]; 37 | nextCandidate: number; 38 | edited: boolean; 39 | codePrinter: Fn; 40 | helpPrinter: Fn; 41 | textPrinter: Fn; 42 | errorPrinter: Fn; 43 | 44 | constructor(public vm: IVM) {} 45 | 46 | replPush(elm) { 47 | replOutput.appendChild(elm); 48 | if (replOutput.children.length > MAX_SHOW) { 49 | replOutput.removeChild(replOutput.firstChild); 50 | } 51 | elm.scrollIntoView(); 52 | replInput.focus(); 53 | } 54 | 55 | defPrinter(class_name, prefix = "") { 56 | return (str: string) => { 57 | const elm = document.createElement("pre"); 58 | elm.textContent = prefix + str; 59 | elm.className = class_name; 60 | if (class_name == "repl-out-code") { 61 | elm.addEventListener("click", () => { 62 | (replInput).value = str; 63 | replInput.focus(); 64 | }); 65 | } 66 | this.replPush(elm); 67 | }; 68 | } 69 | 70 | repl(code: string) { 71 | this.codePrinter(code); 72 | this.vm.interpreter(code); 73 | this.showDS(); 74 | this.showRS(); 75 | this.updateWordCache(); 76 | } 77 | 78 | pushHistory(code) { 79 | const history = this.history; 80 | if (history[history.length - 1] === code) { 81 | return; 82 | } 83 | history.push(code); 84 | if (history.length > MAX_HISTORY) { 85 | history.shift(); 86 | } 87 | } 88 | 89 | clearInput() { 90 | replInput.value = ""; 91 | replInput.focus(); 92 | this.historySel = undefined; 93 | } 94 | 95 | restoreHistory() { 96 | replInput.value = this.history[this.historySel]; 97 | } 98 | 99 | historyBack() { 100 | if (!history.length) { 101 | return; 102 | } 103 | if (this.historySel === undefined || this.historySel === 0) { 104 | this.historySel = this.history.length - 1; 105 | } else { 106 | this.historySel--; 107 | } 108 | this.restoreHistory(); 109 | } 110 | 111 | historyForward() { 112 | if (!history.length) { 113 | return; 114 | } 115 | if ( 116 | this.historySel === undefined || 117 | this.historySel === this.history.length - 1 118 | ) { 119 | this.historySel = 0; 120 | } else { 121 | this.historySel++; 122 | } 123 | this.restoreHistory(); 124 | } 125 | 126 | clearReplOut() { 127 | removeAllChildren(replOutput); 128 | } 129 | 130 | clearStackView(stack: HTMLElement) { 131 | removeAllChildren(stack); 132 | } 133 | 134 | showStackItem(stack: HTMLElement, val: any) { 135 | val = JSON.stringify(val).substr(0, 256); 136 | const el = document.createElement("pre"); 137 | el.textContent = val; 138 | el.className = "stack-item"; 139 | stack.appendChild(el); 140 | } 141 | 142 | showStack(sp: number, stack: Stack, el: HTMLElement) { 143 | this.clearStackView(el); 144 | for (var i = 0; sp >= 0 && i < MAX_SHOW_STACK; sp--, i++) { 145 | this.showStackItem(el, stack[sp]); 146 | } 147 | } 148 | 149 | showDS() { 150 | this.showStack(this.vm.DSP(), this.vm.DS, dsItems); 151 | } 152 | 153 | showRS() { 154 | this.showStack(this.vm.RSP(), this.vm.RS, rsItems); 155 | } 156 | 157 | evalInput() { 158 | this.pushHistory(replInput.value); 159 | this.repl(replInput.value); 160 | this.clearInput(); 161 | } 162 | 163 | updateWordCache() { 164 | this.wordCache = this.vm.allWords(); 165 | } 166 | 167 | injectNextCandidate() { 168 | const candidate = this.wordCandidates[this.nextCandidate]; 169 | const src = replInput.value; 170 | replInput.value = 171 | src.substr(0, this.wordStart) + 172 | candidate + 173 | src.substr(replInput.selectionStart); 174 | replInput.selectionStart = replInput.selectionEnd = 175 | this.wordStart + candidate.length; 176 | this.nextCandidate = 177 | (this.nextCandidate + 1) % this.wordCandidates.length; 178 | } 179 | 180 | updateCandidates() { 181 | this.wordStart = replInput.selectionStart - 1; 182 | while ( 183 | this.wordStart >= 0 && 184 | replInput.value.charAt(this.wordStart) != " " 185 | ) { 186 | this.wordStart--; 187 | } 188 | this.wordStart++; 189 | const word = replInput.value.substr( 190 | this.wordStart, 191 | replInput.selectionStart 192 | ); 193 | this.wordCandidates = []; 194 | if (word.length) { 195 | for (let i = 0, n = this.wordCache.length; i < n; i++) { 196 | const w = this.wordCache[i]; 197 | if (w.indexOf(word) === 0) { 198 | this.wordCandidates.push(w); 199 | } 200 | } 201 | } 202 | this.nextCandidate = 0; 203 | } 204 | 205 | autoComplete() { 206 | if (this.edited) { 207 | this.updateCandidates(); 208 | } 209 | if (this.wordCandidates.length > 0) { 210 | this.injectNextCandidate(); 211 | } 212 | } 213 | 214 | includeURI(url) { 215 | const req = new XMLHttpRequest(); 216 | req.open("GET", url, true); 217 | req.responseType = "text"; 218 | req.onload = () => this.repl(req.response); 219 | req.onerror = () => this.errorPrinter(`failed to load URL: ${url}`); 220 | req.send(); 221 | } 222 | 223 | start() { 224 | replInput.addEventListener("keydown", (e) => { 225 | //console.log(e.which); 226 | if (e.metaKey) { 227 | switch (e.which) { 228 | case 13: // ENTER 229 | this.evalInput(); 230 | break; 231 | case 38: // UP 232 | this.historyBack(); 233 | break; 234 | case 40: // DOWN 235 | this.historyForward(); 236 | break; 237 | case 75: 238 | this.clearReplOut(); 239 | break; 240 | case 82: 241 | e.preventDefault(); 242 | this.updateWordCache(); 243 | this.helpPrinter( 244 | `word cache refreshed (${this.wordCache.length} words)` 245 | ); 246 | break; 247 | } 248 | } 249 | if (e.which == 9) { 250 | e.preventDefault(); 251 | this.autoComplete(); 252 | this.edited = false; 253 | } else { 254 | this.edited = true; 255 | } 256 | }); 257 | 258 | this.codePrinter = this.defPrinter("repl-out-code"); 259 | this.helpPrinter = this.defPrinter("repl-out-help"); 260 | this.vm.stdout = this.textPrinter = this.defPrinter("repl-out-text"); 261 | this.vm.stderr = this.errorPrinter = this.defPrinter( 262 | "repl-out-error", 263 | "[ERROR] " 264 | ); 265 | 266 | btEval.addEventListener("click", this.evalInput.bind(this)); 267 | 268 | exposeGlobal("REPL", this, true); 269 | 270 | this.vm.interpreter( 271 | [ 272 | '*js-window* "REPL" js@ val> *repl*', 273 | "500 val> *include-delay*", 274 | '"REPL JS class" set-doc-latest!', 275 | '"repl-out" js-element-by-id val> *repl-out*', 276 | '"REPL\'s output DOM element" set-doc-latest!', 277 | ": add-to-repl ^( elm -- ) *repl-out* js-append-child ;", 278 | ': include ^( url -- ) *repl* dup "includeURI" js@ js-call-1-with drop ;', 279 | ": include* dsp@ 1+ >r begin dsp@ 0 >= while include repeat r> *include-delay* * sleep ;", 280 | ": clear-repl-out ^( -- )", 281 | ' *repl* dup "clearReplOut" js@ js-call-with drop ;', 282 | ].join("\n") 283 | ); 284 | 285 | this.updateWordCache(); 286 | 287 | this.helpPrinter( 288 | [ 289 | " _______ ___ ___ _______ _______ ___ ___ _______ ", 290 | " | _ | | Y | | _ | | _ \\ | | | | | _ |", 291 | " |. 1___| |. 1 | |. 1 | |. l / |. | |. | |. 1___|", 292 | " |. |___ |. _ | |. _ | |. _ 1 |. |___ |. | |. __)_ ", 293 | " |: 1 | |: | | |: | | |: | | |: 1 | |: | |: 1 |", 294 | " |::.. . | |::.|:. | |::.|:. | |::.|:. | |::.. . | |::.| |::.. . |", 295 | " `-------' `--- ---' `--- ---' `--- ---' `-------' `---' `-------'", 296 | " ---=== thi.ng/charlie ===--- ", 297 | "", 298 | " Command + Enter .................... evaluate", 299 | " Command + up/down .................. cycle history", 300 | " Command + R ........................ refresh autocomplete cache", 301 | " TAB ................................ autocomplete (repeat for alts)", 302 | " `show-words` / `show-public-words` . display known word list", 303 | " `doc> word` ........................ display word documentation", 304 | " `see> word` ........................ disassemble word definition", 305 | ].join("\n") 306 | ); 307 | 308 | replInput.focus(); 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /src/vm.ts: -------------------------------------------------------------------------------- 1 | // _______ ___ ___ _______ _______ ___ ___ _______ ___ ___ ___ ___ 2 | // | _ | Y | _ | _ | | | | _ | | Y | Y | 3 | // |. 1___|. 1 |. 1 |. l |. | |. |. 1___| |. | |. | 4 | // |. |___|. _ |. _ |. _ |. |___|. |. __)_ ______|. | |. \_/ | 5 | // |: 1 |: | |: | |: | |: 1 |: |: 1 |______|: 1 |: | | 6 | // |::.. . |::.|:. |::.|:. |::.|:. |::.. . |::.|::.. . | \:.. ./|::.|:. | 7 | // `-------`--- ---`--- ---`--- ---`-------`---`-------' `---' `--- ---' 8 | // 9 | // (c) 2015 - 2025 Karsten Schmidt // MIT licensed 10 | 11 | import type { Fn0, Fn } from "@thi.ng/api"; 12 | 13 | const VERSION = "1.0.2"; 14 | 15 | const BASE_DICT_ADDR = 0; 16 | const BASE_HEAP_ADDR = 0x10000; 17 | const MODE_IMMEDIATE = 0; 18 | const MODE_COMPILE = 1; 19 | 20 | const REGEXP_LIT_NUMBER = /^-?[0-9]*(\.[0-9]+)?$/; 21 | const REGEXP_LIT_NUMBER_HEX = /^0x[0-9a-fA-F]{1,8}?$/; 22 | const REGEXP_LIT_STRING = /^"(.*)$/; 23 | 24 | // VM state 25 | 26 | const PRIMS = {}; 27 | const MEM: any[] = [0]; 28 | const DS = []; 29 | const RS = []; 30 | let DSP = -1; 31 | let RSP = -1; 32 | let DP = BASE_DICT_ADDR + 1; 33 | let IP = 0; 34 | let NP = 0; 35 | let HP = BASE_HEAP_ADDR; 36 | let LATEST = 0; 37 | let ERROR = false; 38 | let SUSPEND = false; 39 | let MODE = MODE_IMMEDIATE; 40 | 41 | let TIB; 42 | let READER: Reader; 43 | let TOKEN_READER; 44 | 45 | export type Stack = any[]; 46 | export type Memory = any[]; 47 | export type GetPtr = Fn0; 48 | export type Reader = (prior?: boolean) => string; 49 | export type TokenReader = Fn0; 50 | 51 | export interface VMState { 52 | IP: number; 53 | NP: number; 54 | TIB: string; 55 | READER: Reader; 56 | TOKEN_READER: TokenReader; 57 | } 58 | 59 | export interface IVM { 60 | DS: Stack; 61 | DSP: GetPtr; 62 | RS: Stack; 63 | RSP: GetPtr; 64 | MEM: Memory; 65 | PRIMS: Record; 66 | LATEST: GetPtr; 67 | IP: GetPtr; 68 | NP: GetPtr; 69 | VERSION: string; 70 | stdout: Fn; 71 | stderr: Fn; 72 | throwError: Fn; 73 | isError: Fn0; 74 | isSuspended: Fn0; 75 | popD: typeof popD; 76 | pushD: typeof pushD; 77 | popR: typeof popR; 78 | pushR: typeof pushR; 79 | pushDict: typeof pushDict; 80 | next: typeof next; 81 | doColon: typeof doColon; 82 | defWord: typeof defWord; 83 | defColon: typeof defColon; 84 | defConst: typeof defConst; 85 | doWord: typeof doWord; 86 | doContinue: typeof doContinue; 87 | allWords: typeof allWords; 88 | interpreter: typeof interpreter; 89 | } 90 | 91 | export const Charlie = { 92 | DS: DS, 93 | DSP: () => DSP, 94 | RS: RS, 95 | RSP: () => RSP, 96 | MEM: MEM, 97 | PRIMS: PRIMS, 98 | LATEST: () => LATEST, 99 | IP: () => IP, 100 | NP: () => NP, 101 | VERSION: VERSION, 102 | stdout(out) { 103 | console.log(out); 104 | }, 105 | stderr(out) { 106 | console.error(out); 107 | }, 108 | throwError(err) { 109 | this.stderr(err); 110 | ERROR = true; 111 | }, 112 | isError: () => ERROR, 113 | isSuspended: () => SUSPEND, 114 | }; 115 | 116 | const getVMState = (): VMState => ({ 117 | IP: IP, 118 | NP: NP, 119 | TIB: TIB, 120 | READER: READER, 121 | TOKEN_READER: TOKEN_READER, 122 | }); 123 | 124 | const setVMState = (state: VMState) => { 125 | IP = state.IP; 126 | NP = state.NP; 127 | TIB = state.TIB; 128 | READER = state.READER; 129 | TOKEN_READER = state.TOKEN_READER; 130 | }; 131 | 132 | const popD = () => { 133 | if (DSP !== -1) { 134 | return DS[DSP--]; 135 | } else { 136 | Charlie.throwError("DS underflow"); 137 | return undefined; 138 | } 139 | }; 140 | 141 | const pushD = (x) => { 142 | DS[++DSP] = x; 143 | }; 144 | 145 | const popR = () => { 146 | if (RSP !== -1) { 147 | return RS[RSP--]; 148 | } else { 149 | Charlie.throwError("RS underflow"); 150 | return undefined; 151 | } 152 | }; 153 | 154 | const pushR = (x: any) => { 155 | RS[++RSP] = x; 156 | }; 157 | 158 | const pushDict = (x: any) => { 159 | MEM[DP++] = x; 160 | return (DP - 1) | 0; 161 | }; 162 | 163 | const next = () => { 164 | IP = MEM[NP]; 165 | NP++; 166 | }; 167 | 168 | const doColon = () => { 169 | pushR(NP); 170 | IP++; 171 | NP = (IP + 1) | 0; 172 | IP = MEM[IP]; 173 | }; 174 | 175 | function defWord(name: string, fn: Fn0): number; 176 | function defWord(name: string, immediate: boolean, fn: Fn0): number; 177 | function defWord(name: string, immediate, fn?) { 178 | if (!fn) { 179 | fn = immediate; 180 | immediate = false; 181 | } 182 | const hd = [name || "", !!immediate, false, LATEST]; 183 | LATEST = DP; 184 | pushDict(hd); 185 | pushDict(fn); 186 | PRIMS[name] = LATEST; 187 | return LATEST; 188 | } 189 | 190 | function defColon(name: string, words: string): number; 191 | function defColon(name: string, immediate: boolean, words?: string): number; 192 | function defColon(name: string, immediate: any, words?) { 193 | if (!words) { 194 | words = immediate; 195 | immediate = false; 196 | } 197 | if (typeof words === "string") { 198 | words = words.split(/[ \n]+/); 199 | } 200 | defWord(name, immediate, doColon); 201 | words.forEach(function (w) { 202 | pushDict(PRIMS[w]); 203 | }); 204 | pushDict(PRIMS["exit"]); 205 | return LATEST; 206 | } 207 | 208 | const defConst = (name: string, val: any) => 209 | defWord(name, false, () => { 210 | pushD(val); 211 | next(); 212 | }); 213 | 214 | const findWord = (name: string) => { 215 | let curr = LATEST; 216 | while (true) { 217 | const currMem = MEM[curr]; 218 | if (currMem[2] || currMem[0] !== name) { 219 | curr = currMem[3]; // prev 220 | if (curr === 0) { 221 | curr = undefined; 222 | break; 223 | } 224 | } else { 225 | break; 226 | } 227 | } 228 | return curr; 229 | }; 230 | 231 | const allWords = (): string[] => { 232 | const words = []; 233 | let curr = LATEST; 234 | while (curr > 0) { 235 | const currMem = MEM[curr]; 236 | if (!currMem[2]) { 237 | if (words.indexOf(currMem[0]) == -1) { 238 | words.push(currMem[0]); 239 | } 240 | } 241 | curr = currMem[3]; 242 | } 243 | return words; 244 | }; 245 | 246 | const setWordFlag = (flag) => () => { 247 | const f = popD(); 248 | MEM[popD()][flag] = f; 249 | next(); 250 | }; 251 | 252 | const doWord = (addr: number, np: number) => { 253 | IP = addr; 254 | NP = np; 255 | try { 256 | do { 257 | MEM[++IP](); 258 | } while (!ERROR && !SUSPEND && IP !== 0); 259 | } catch (e) { 260 | Charlie.throwError(e); 261 | } 262 | }; 263 | 264 | const doContinue = (state: VMState) => { 265 | console.log("state:", state); 266 | SUSPEND = false; 267 | setVMState(state); 268 | if (IP > 0) { 269 | doWord(IP, NP); 270 | } 271 | return doInterpreter(); 272 | }; 273 | 274 | const makeReader = (str: string) => { 275 | let i = 0; 276 | let ch: string; 277 | TIB = str; 278 | return (isPrior = false) => { 279 | if (isPrior) { 280 | return ch; 281 | } 282 | if (TIB === undefined) { 283 | return undefined; 284 | } 285 | ch = TIB[i++]; 286 | return ch; 287 | }; 288 | }; 289 | 290 | const isTokenSeparator = (ch: string) => ch === " " || ch === "\n"; 291 | 292 | const isEOS = (ch: string) => ch === undefined; 293 | 294 | const makeTokenReader = (read: Reader) => () => { 295 | let ch: string; 296 | let token = ""; 297 | while (true) { 298 | ch = read(); 299 | if (isTokenSeparator(ch)) { 300 | if (token === "") { 301 | continue; 302 | } else { 303 | break; 304 | } 305 | } 306 | if (isEOS(ch)) { 307 | break; 308 | } 309 | token += ch; 310 | } 311 | return token; 312 | }; 313 | 314 | const readString = (str: string) => { 315 | const first = str.match(/(.*)"$/); 316 | if (first) { 317 | return first[1]; 318 | } 319 | str += READER(true); 320 | let ch = ""; 321 | while (ch !== undefined) { 322 | ch = READER(); 323 | if (ch === "\\") { 324 | str += READER(); 325 | continue; 326 | } 327 | if (ch === '"') { 328 | break; 329 | } 330 | str += ch; 331 | } 332 | return str; 333 | }; 334 | 335 | const tokenValue = (token: string) => { 336 | if (token.match(REGEXP_LIT_NUMBER)) { 337 | return parseFloat(token); 338 | } 339 | if (token.match(REGEXP_LIT_NUMBER_HEX)) { 340 | return parseInt(token.substr(2), 16); 341 | } 342 | const str = token.match(REGEXP_LIT_STRING); 343 | if (str) { 344 | return readString(str[1]).replace(/\\n/g, "\n"); 345 | } 346 | Charlie.throwError("Unknown word: " + token); 347 | }; 348 | 349 | const compileToken = (token: string) => { 350 | const word = findWord(token); 351 | if (word === undefined) { 352 | const literal = tokenValue(token); 353 | if (literal !== undefined) { 354 | pushDict(PRIMS["lit"]); 355 | pushDict(literal); 356 | } 357 | return; 358 | } 359 | if (MEM[word] && MEM[word][1]) { 360 | // immediate mode 361 | doWord(word, 0); 362 | return; 363 | } 364 | pushDict(word); 365 | }; 366 | 367 | const executeToken = (token: string) => { 368 | const word = findWord(token); 369 | if (word === undefined) { 370 | const literal = tokenValue(token); 371 | if (literal !== undefined) { 372 | pushD(literal); 373 | } 374 | return; 375 | } 376 | //console.log("exec: "+word); 377 | doWord(word, 0); 378 | }; 379 | 380 | const INTERPRETER = []; 381 | INTERPRETER[MODE_COMPILE] = compileToken; 382 | INTERPRETER[MODE_IMMEDIATE] = executeToken; 383 | 384 | const doInterpreter = () => { 385 | ERROR = false; 386 | SUSPEND = false; 387 | 388 | let token; 389 | 390 | while (!ERROR && !SUSPEND) { 391 | token = TOKEN_READER(); 392 | if (token === "") { 393 | break; 394 | } 395 | INTERPRETER[MODE](token); 396 | } 397 | return DS; 398 | }; 399 | 400 | const interpreter = (code: string) => { 401 | READER = makeReader(code); 402 | TOKEN_READER = makeTokenReader(READER); 403 | return doInterpreter(); 404 | }; 405 | 406 | // primitives 407 | 408 | defWord("exit", () => { 409 | NP = popR(); 410 | next(); 411 | }); 412 | 413 | defWord("suspend!", () => { 414 | SUSPEND = true; 415 | next(); 416 | }); 417 | 418 | defWord("sleep", () => { 419 | const pause = popD(); 420 | next(); 421 | const state = getVMState(); 422 | setTimeout(() => { 423 | doContinue(state); 424 | }, pause); 425 | SUSPEND = true; 426 | }); 427 | 428 | defConst("true", true); 429 | defConst("false", false); 430 | defConst("nil", null); 431 | defConst("undefined", undefined); 432 | defConst("*version*", VERSION); 433 | defConst("*vm-mode-immediate*", MODE_IMMEDIATE); 434 | defConst("*vm-mode-compile*", MODE_COMPILE); 435 | defWord("compile-mode!", true, () => { 436 | MODE = MODE_COMPILE; 437 | next(); 438 | }); 439 | defWord("immediate-mode!", true, () => { 440 | MODE = MODE_IMMEDIATE; 441 | next(); 442 | }); 443 | 444 | defWord("\\", () => { 445 | let ch = ""; 446 | while (ch !== undefined && ch !== "\n") { 447 | ch = READER(); 448 | } 449 | next(); 450 | }); 451 | 452 | defWord("latest@", () => { 453 | pushD(LATEST); 454 | next(); 455 | }); 456 | defWord("latest!", () => { 457 | LATEST = popD(); 458 | next(); 459 | }); 460 | defWord("heap-here@", () => { 461 | pushD(HP); 462 | next(); 463 | }); 464 | defWord("heap-here!", () => { 465 | HP = popD(); 466 | next(); 467 | }); 468 | defWord("dict-here@", () => { 469 | pushD(DP); 470 | next(); 471 | }); 472 | defWord("dict-here!", () => { 473 | DP = popD(); 474 | next(); 475 | }); 476 | defWord(">dict", () => { 477 | pushDict(popD()); 478 | next(); 479 | }); 480 | defWord("dsp@", () => { 481 | pushD(DSP); 482 | next(); 483 | }); 484 | defWord("dsp!", () => { 485 | DSP = popD(); 486 | next(); 487 | }); 488 | defWord("pick", () => { 489 | pushD(DS[DSP - popD() - 1]); 490 | next(); 491 | }); 492 | defWord("rsp@", () => { 493 | pushD(RSP); 494 | next(); 495 | }); 496 | defWord("rsp!", () => { 497 | RSP = popD(); 498 | next(); 499 | }); 500 | defWord("rpick", () => { 501 | pushD(RS[RSP - popD() - 1]); 502 | next(); 503 | }); 504 | defWord("vm-mode@", () => { 505 | pushD(MODE); 506 | next(); 507 | }); 508 | defWord("vm-mode!", () => { 509 | MODE = popD(); 510 | next(); 511 | }); 512 | defWord("vm-state@", () => { 513 | pushD(getVMState()); 514 | next(); 515 | }); 516 | defWord("vm-state!", () => { 517 | doContinue(popD()); 518 | }); 519 | defWord("tib@", () => { 520 | pushD(TIB); 521 | next(); 522 | }); 523 | defWord("tib!", () => { 524 | next(); 525 | const state = getVMState(); 526 | interpreter(popD()); 527 | doContinue(state); 528 | }); 529 | 530 | defWord("find", () => { 531 | pushD(findWord(popD())); 532 | next(); 533 | }); 534 | defWord("call", () => { 535 | IP = popD(); 536 | }); 537 | defWord("jump", () => { 538 | NP = popD(); 539 | next(); 540 | }); 541 | defWord("lit", () => { 542 | pushD(MEM[NP++]); 543 | next(); 544 | }); 545 | defWord("prev-word@", () => { 546 | pushD(MEM[popD()][3]); 547 | next(); 548 | }); 549 | defWord("prev-word!", () => { 550 | const w = popD(); 551 | MEM[w][3] = popD(); 552 | next(); 553 | }); 554 | defWord("xt->cfa", () => { 555 | pushD(popD() + 1); 556 | next(); 557 | }); 558 | defWord("cfa->dfa", () => { 559 | pushD(popD() + 1); 560 | next(); 561 | }); 562 | 563 | defWord("read-token>", () => { 564 | pushD(TOKEN_READER()); 565 | next(); 566 | }); 567 | defWord("new-word-header", () => { 568 | defWord("", false, doColon); 569 | next(); 570 | }); 571 | defWord("set-word-name!", () => { 572 | const w = popD(); 573 | MEM[w][0] = popD(); 574 | next(); 575 | }); 576 | defWord("set-word-doc!", () => { 577 | const w = popD(); 578 | MEM[w][4] = popD(); 579 | next(); 580 | }); 581 | defWord("set-immediate!", setWordFlag(1)); 582 | defWord("set-hidden!", setWordFlag(2)); 583 | 584 | defColon("hide-latest!", "latest@ true set-hidden!"); 585 | defColon("show-latest!", "latest@ false set-hidden!"); 586 | defColon("immediate!", "latest@ true set-immediate!"); 587 | defColon( 588 | "new-word-header>", 589 | "read-token> new-word-header latest@ set-word-name!" 590 | ); 591 | defColon(":", "new-word-header> hide-latest! compile-mode!"); 592 | defColon(";", true, "lit exit >dict immediate-mode! show-latest!"); 593 | 594 | defWord(".", () => { 595 | Charlie.stdout(popD()); 596 | next(); 597 | }); 598 | 599 | // logic 600 | defWord("and", () => { 601 | const a = !!popD(); 602 | const b = !!popD(); 603 | pushD(a && b); 604 | next(); 605 | }); 606 | defWord("or", () => { 607 | const a = !!popD(); 608 | const b = !!popD(); 609 | pushD(a || b); 610 | next(); 611 | }); 612 | defWord("not", () => { 613 | pushD(!popD()); 614 | next(); 615 | }); 616 | defWord("=", () => { 617 | pushD(popD() === popD()); 618 | next(); 619 | }); 620 | defWord("not=", () => { 621 | pushD(popD() !== popD()); 622 | next(); 623 | }); 624 | defWord("<", () => { 625 | pushD(popD() > popD()); 626 | next(); 627 | }); 628 | defWord(">", () => { 629 | pushD(popD() < popD()); 630 | next(); 631 | }); 632 | defWord("<=", () => { 633 | pushD(popD() >= popD()); 634 | next(); 635 | }); 636 | defWord(">=", () => { 637 | pushD(popD() <= popD()); 638 | next(); 639 | }); 640 | defWord("zero?", () => { 641 | pushD(popD() === 0); 642 | next(); 643 | }); 644 | defWord("pos?", () => { 645 | pushD(popD() > 0); 646 | next(); 647 | }); 648 | defWord("neg?", () => { 649 | pushD(popD() < 0); 650 | next(); 651 | }); 652 | defWord("nil?", () => { 653 | pushD(popD() === null); 654 | next(); 655 | }); 656 | 657 | // maths 658 | 659 | defWord("+", () => { 660 | const x = popD(); 661 | pushD(popD() + x); 662 | next(); 663 | }); 664 | defWord("*", () => { 665 | pushD(popD() * popD()); 666 | next(); 667 | }); 668 | defWord("-", () => { 669 | const x = popD(); 670 | pushD(popD() - x); 671 | next(); 672 | }); 673 | defWord("/", () => { 674 | const x = popD(); 675 | pushD(popD() / x); 676 | next(); 677 | }); 678 | defWord("mod", () => { 679 | const x = popD(); 680 | pushD(popD() % x); 681 | next(); 682 | }); 683 | defWord("1+", () => { 684 | pushD(popD() + 1); 685 | next(); 686 | }); 687 | defWord("1-", () => { 688 | pushD(popD() - 1); 689 | next(); 690 | }); 691 | defWord("min", () => { 692 | const x = popD(); 693 | pushD(Math.min(popD(), x)); 694 | next(); 695 | }); 696 | defWord("max", () => { 697 | const x = popD(); 698 | pushD(Math.max(popD(), x)); 699 | next(); 700 | }); 701 | defWord("bit-and", () => { 702 | const b = popD(); 703 | pushD(popD() & b); 704 | next(); 705 | }); 706 | defWord("bit-or", () => { 707 | const b = popD(); 708 | pushD(popD() | b); 709 | next(); 710 | }); 711 | defWord("bit-xor", () => { 712 | const b = popD(); 713 | pushD(popD() ^ b); 714 | next(); 715 | }); 716 | defWord("<<", () => { 717 | const b = popD(); 718 | pushD(popD() << b); 719 | next(); 720 | }); 721 | defWord(">>", () => { 722 | const b = popD(); 723 | pushD(popD() >> b); 724 | next(); 725 | }); 726 | defWord(">>>", () => { 727 | const b = popD(); 728 | pushD(popD() >>> b); 729 | next(); 730 | }); 731 | 732 | // binary 733 | defWord("bit-and", () => { 734 | pushD(popD() & popD()); 735 | next(); 736 | }); 737 | defWord("bit-or", () => { 738 | pushD(popD() | popD()); 739 | next(); 740 | }); 741 | defWord("bit-xor", () => { 742 | pushD(popD() ^ popD()); 743 | next(); 744 | }); 745 | 746 | // data stack ops 747 | 748 | defWord("drop", () => { 749 | popD(); 750 | next(); 751 | }); 752 | defWord("dup", () => { 753 | pushD(DS[DSP]); 754 | next(); 755 | }); 756 | defWord("?dup", () => { 757 | const x = DS[DSP]; 758 | if (x !== 0) { 759 | pushD(x); 760 | } 761 | next(); 762 | }); 763 | defWord("swap", () => { 764 | const b = DS[DSP]; 765 | const a = DS[DSP - 1]; 766 | DS[DSP - 1] = b; 767 | DS[DSP] = a; 768 | next(); 769 | }); 770 | defWord("nip", () => { 771 | const x = popD(); 772 | popD(); 773 | pushD(x); 774 | next(); 775 | }); 776 | defWord("tuck", () => { 777 | const b = popD(); 778 | const a = popD(); 779 | pushD(b); 780 | pushD(a); 781 | pushD(b); 782 | next(); 783 | }); 784 | defWord("over", () => { 785 | pushD(DS[DSP - 1]); 786 | next(); 787 | }); 788 | defWord("rot", () => { 789 | const c = DS[DSP]; 790 | const b = DS[DSP - 1]; 791 | const a = DS[DSP - 2]; 792 | DS[DSP - 2] = b; 793 | DS[DSP - 1] = c; 794 | DS[DSP] = a; 795 | next(); 796 | }); 797 | defWord("-rot", () => { 798 | const c = DS[DSP]; 799 | const b = DS[DSP - 1]; 800 | const a = DS[DSP - 2]; 801 | DS[DSP - 2] = c; 802 | DS[DSP - 1] = a; 803 | DS[DSP] = b; 804 | next(); 805 | }); 806 | defWord("2dup", () => { 807 | const b = DS[DSP]; 808 | const a = DS[DSP - 1]; 809 | pushD(a); 810 | pushD(b); 811 | next(); 812 | }); 813 | defWord("2drop", () => { 814 | popD(); 815 | popD(); 816 | next(); 817 | }); 818 | defWord("2swap", () => { 819 | const d = popD(); 820 | const c = popD(); 821 | const b = popD(); 822 | const a = popD(); 823 | pushD(c); 824 | pushD(d); 825 | pushD(a); 826 | pushD(b); 827 | next(); 828 | }); 829 | 830 | // return stack ops 831 | 832 | defWord(">r", () => { 833 | pushR(popD()); 834 | next(); 835 | }); 836 | defWord("r>", () => { 837 | pushD(popR()); 838 | next(); 839 | }); 840 | defWord("@r", () => { 841 | pushR(DS[DSP]); 842 | next(); 843 | }); 844 | defWord("r@", () => { 845 | pushD(RS[RSP]); 846 | next(); 847 | }); 848 | defWord("rdrop", () => { 849 | popR(); 850 | next(); 851 | }); 852 | defWord("rdup", () => { 853 | pushR(RS[RSP]); 854 | next(); 855 | }); 856 | defWord("rswap", () => { 857 | const b = popR(), 858 | a = popR(); 859 | pushR(b); 860 | pushR(a); 861 | next(); 862 | }); 863 | defWord("2>r", () => { 864 | const b = popD(), 865 | a = popD(); 866 | pushR(a); 867 | pushR(b); 868 | next(); 869 | }); 870 | defWord("2r>", () => { 871 | const b = popR(), 872 | a = popR(); 873 | pushD(a); 874 | pushD(b); 875 | next(); 876 | }); 877 | 878 | // memory 879 | 880 | defWord("@", () => { 881 | pushD(MEM[popD()]); 882 | next(); 883 | }); 884 | defWord("!", () => { 885 | MEM[popD()] = popD(); 886 | next(); 887 | }); 888 | defWord("+!", () => { 889 | MEM[popD()] += popD(); 890 | next(); 891 | }); 892 | defWord("-!", () => { 893 | MEM[popD()] -= popD(); 894 | next(); 895 | }); 896 | 897 | // branching 898 | 899 | defWord("branch", () => { 900 | NP = MEM[NP]; 901 | next(); 902 | }); 903 | defWord("alt-branch", () => { 904 | popD() ? NP++ : (NP = MEM[NP]); 905 | next(); 906 | }); 907 | 908 | // JS 909 | 910 | defWord("js-call", () => { 911 | pushD(popD()()); 912 | next(); 913 | }); 914 | defWord("js-call-1", () => { 915 | pushD(popD()(popD())); 916 | next(); 917 | }); 918 | defWord("js-call-2", () => { 919 | const fn = popD(); 920 | const b = popD(); 921 | pushD(fn(popD(), b)); 922 | next(); 923 | }); 924 | defWord("js-call-n", () => { 925 | const n = popD() - 1; 926 | const fn = popD(); 927 | const args = []; 928 | let m = n; 929 | // TODO refactor 930 | for (; m >= 0; m--) { 931 | args.push(DS[DSP - m]); 932 | } 933 | for (m = n; m >= 0; m--) { 934 | popD(); 935 | } 936 | pushD(fn.apply(window, args)); 937 | next(); 938 | }); 939 | defWord("js-call-with", () => { 940 | const fn = popD(); 941 | pushD(fn.call(popD())); 942 | next(); 943 | }); 944 | defWord("js-call-1-with", () => { 945 | const fn = popD(); 946 | const obj = popD(); 947 | pushD(fn.call(obj, popD())); 948 | next(); 949 | }); 950 | defWord("js-call-2-with", () => { 951 | const fn = popD(); 952 | const obj = popD(); 953 | const b = popD(); 954 | pushD(fn.call(obj, popD(), b)); 955 | next(); 956 | }); 957 | defWord("js-call-n-with", () => { 958 | const n = popD() - 1; 959 | const fn = popD(); 960 | const obj = popD(); 961 | const args = []; 962 | let m = n; 963 | // TODO refactor 964 | for (; m >= 0; m--) { 965 | args.push(DS[DSP - m]); 966 | } 967 | for (m = n; m >= 0; m--) { 968 | popD(); 969 | } 970 | pushD(fn.apply(obj, args)); 971 | next(); 972 | }); 973 | defWord("js-new", () => { 974 | const obj = popD(); 975 | pushD(new obj()); 976 | next(); 977 | }); 978 | defWord("js-new-1", () => { 979 | const obj = popD(); 980 | pushD(new obj(popD())); 981 | next(); 982 | }); 983 | defWord("js-new-2", () => { 984 | const obj = popD(); 985 | const b = popD(); 986 | pushD(new obj(popD(), b)); 987 | next(); 988 | }); 989 | defWord("js-new-n", () => { 990 | const n = popD(); 991 | const obj = popD(); 992 | const args = []; 993 | const inst = Object.create(obj.prototype); 994 | let m = n; 995 | // TODO refactor 996 | for (; m >= 0; m--) { 997 | args.push(DS[DSP - m]); 998 | } 999 | for (m = n; m >= 0; m--) { 1000 | popD(); 1001 | } 1002 | pushD(obj.apply(inst, args) || inst); 1003 | next(); 1004 | }); 1005 | defWord("js-apply", () => { 1006 | pushD(popD().apply(window, popD())); 1007 | next(); 1008 | }); 1009 | defWord("js-apply-with", () => { 1010 | pushD(popD().apply(popD(), popD())); 1011 | next(); 1012 | }); 1013 | defWord("js-obj", () => { 1014 | pushD({}); 1015 | next(); 1016 | }); 1017 | defWord("js-array", () => { 1018 | pushD([]); 1019 | next(); 1020 | }); 1021 | defWord("js@", () => { 1022 | const key = popD(); 1023 | const obj = popD(); 1024 | pushD(obj === undefined || obj === null ? obj : obj[key]); 1025 | next(); 1026 | }); 1027 | defWord("js!", () => { 1028 | const key = popD(); 1029 | const obj = popD(); 1030 | obj[key] = popD(); 1031 | next(); 1032 | }); 1033 | defWord("js!!", () => { 1034 | const key = DS[DSP]; 1035 | const obj = DS[DSP - 1]; 1036 | obj[key] = DS[DSP - 2]; 1037 | next(); 1038 | }); 1039 | defWord("js-apush", () => { 1040 | DS[DSP - 1].push(popD()); 1041 | next(); 1042 | }); 1043 | defWord("typeof", () => { 1044 | pushD(typeof popD()); 1045 | next(); 1046 | }); 1047 | defWord("js-fn?", () => { 1048 | pushD(typeof popD() === "function"); 1049 | next(); 1050 | }); 1051 | defWord("js-obj?", () => { 1052 | const obj = popD(); 1053 | pushD(obj !== null && typeof obj === "object" && !Array.isArray(obj)); 1054 | next(); 1055 | }); 1056 | defWord("js-array?", () => { 1057 | pushD(Array.isArray(popD())); 1058 | next(); 1059 | }); 1060 | defWord("quot->js-callback", () => { 1061 | const qaddr = popD(); 1062 | pushD((e) => { 1063 | pushD(e); 1064 | interpreter(qaddr + " apply"); 1065 | }); 1066 | next(); 1067 | }); 1068 | defWord("quot->js-fn", () => { 1069 | const qaddr = popD(); 1070 | pushD(() => { 1071 | pushR(NP); 1072 | NP = qaddr; 1073 | }); 1074 | next(); 1075 | }); 1076 | defWord("*js-window*", () => { 1077 | pushD(window); 1078 | next(); 1079 | }); 1080 | defWord("*js-document*", () => { 1081 | pushD(document); 1082 | next(); 1083 | }); 1084 | defWord("js-create-element", () => { 1085 | pushD(document.createElement(popD())); 1086 | next(); 1087 | }); 1088 | defWord("js-element-by-id", () => { 1089 | pushD(document.getElementById(popD())); 1090 | next(); 1091 | }); 1092 | defWord("js-text!", () => { 1093 | const elm = popD(); 1094 | elm.textContent = popD(); 1095 | next(); 1096 | }); 1097 | defWord("js-append-child", () => { 1098 | const parent = popD(); 1099 | parent.appendChild(popD()); 1100 | next(); 1101 | }); 1102 | defWord("js-remove-child", () => { 1103 | const parent = popD(); 1104 | parent.removeChild(popD()); 1105 | next(); 1106 | }); 1107 | defWord("js-parent-node", () => { 1108 | pushD(popD().parentNode); 1109 | next(); 1110 | }); 1111 | defWord("js-attr!", () => { 1112 | const attr = popD(); 1113 | const elm = popD(); 1114 | elm.setAttribute(attr, popD()); 1115 | next(); 1116 | }); 1117 | defWord("js-console-log", () => { 1118 | console.log(popD()); 1119 | next(); 1120 | }); 1121 | defWord("js-add-event-listener", () => { 1122 | const elm = popD(); 1123 | const event = popD(); 1124 | const fn = popD(); 1125 | elm.addEventListener(event, fn); 1126 | next(); 1127 | }); 1128 | 1129 | Charlie.popD = popD; 1130 | Charlie.pushD = pushD; 1131 | Charlie.popR = popR; 1132 | Charlie.pushR = pushR; 1133 | Charlie.pushDict = pushDict; 1134 | Charlie.next = next; 1135 | Charlie.doColon = doColon; 1136 | Charlie.defWord = defWord; 1137 | Charlie.defColon = defColon; 1138 | Charlie.defConst = defConst; 1139 | Charlie.doWord = doWord; 1140 | Charlie.doContinue = doContinue; 1141 | Charlie.allWords = allWords; 1142 | Charlie.interpreter = interpreter; 1143 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "noUnusedLocals": true, 7 | "noUnusedParameters": true, 8 | "experimentalDecorators": true, 9 | "verbatimModuleSyntax": true 10 | }, 11 | "include": ["src/**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@esbuild/aix-ppc64@0.25.0": 6 | version "0.25.0" 7 | resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz#499600c5e1757a524990d5d92601f0ac3ce87f64" 8 | integrity sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ== 9 | 10 | "@esbuild/android-arm64@0.25.0": 11 | version "0.25.0" 12 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz#b9b8231561a1dfb94eb31f4ee056b92a985c324f" 13 | integrity sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g== 14 | 15 | "@esbuild/android-arm@0.25.0": 16 | version "0.25.0" 17 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.0.tgz#ca6e7888942505f13e88ac9f5f7d2a72f9facd2b" 18 | integrity sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g== 19 | 20 | "@esbuild/android-x64@0.25.0": 21 | version "0.25.0" 22 | resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.0.tgz#e765ea753bac442dfc9cb53652ce8bd39d33e163" 23 | integrity sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg== 24 | 25 | "@esbuild/darwin-arm64@0.25.0": 26 | version "0.25.0" 27 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz#fa394164b0d89d4fdc3a8a21989af70ef579fa2c" 28 | integrity sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw== 29 | 30 | "@esbuild/darwin-x64@0.25.0": 31 | version "0.25.0" 32 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz#91979d98d30ba6e7d69b22c617cc82bdad60e47a" 33 | integrity sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg== 34 | 35 | "@esbuild/freebsd-arm64@0.25.0": 36 | version "0.25.0" 37 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz#b97e97073310736b430a07b099d837084b85e9ce" 38 | integrity sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w== 39 | 40 | "@esbuild/freebsd-x64@0.25.0": 41 | version "0.25.0" 42 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz#f3b694d0da61d9910ec7deff794d444cfbf3b6e7" 43 | integrity sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A== 44 | 45 | "@esbuild/linux-arm64@0.25.0": 46 | version "0.25.0" 47 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz#f921f699f162f332036d5657cad9036f7a993f73" 48 | integrity sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg== 49 | 50 | "@esbuild/linux-arm@0.25.0": 51 | version "0.25.0" 52 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz#cc49305b3c6da317c900688995a4050e6cc91ca3" 53 | integrity sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg== 54 | 55 | "@esbuild/linux-ia32@0.25.0": 56 | version "0.25.0" 57 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz#3e0736fcfab16cff042dec806247e2c76e109e19" 58 | integrity sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg== 59 | 60 | "@esbuild/linux-loong64@0.25.0": 61 | version "0.25.0" 62 | resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz#ea2bf730883cddb9dfb85124232b5a875b8020c7" 63 | integrity sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw== 64 | 65 | "@esbuild/linux-mips64el@0.25.0": 66 | version "0.25.0" 67 | resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz#4cababb14eede09248980a2d2d8b966464294ff1" 68 | integrity sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ== 69 | 70 | "@esbuild/linux-ppc64@0.25.0": 71 | version "0.25.0" 72 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz#8860a4609914c065373a77242e985179658e1951" 73 | integrity sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw== 74 | 75 | "@esbuild/linux-riscv64@0.25.0": 76 | version "0.25.0" 77 | resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz#baf26e20bb2d38cfb86ee282dff840c04f4ed987" 78 | integrity sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA== 79 | 80 | "@esbuild/linux-s390x@0.25.0": 81 | version "0.25.0" 82 | resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz#8323afc0d6cb1b6dc6e9fd21efd9e1542c3640a4" 83 | integrity sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA== 84 | 85 | "@esbuild/linux-x64@0.25.0": 86 | version "0.25.0" 87 | resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz#08fcf60cb400ed2382e9f8e0f5590bac8810469a" 88 | integrity sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw== 89 | 90 | "@esbuild/netbsd-arm64@0.25.0": 91 | version "0.25.0" 92 | resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz#935c6c74e20f7224918fbe2e6c6fe865b6c6ea5b" 93 | integrity sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw== 94 | 95 | "@esbuild/netbsd-x64@0.25.0": 96 | version "0.25.0" 97 | resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz#414677cef66d16c5a4d210751eb2881bb9c1b62b" 98 | integrity sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA== 99 | 100 | "@esbuild/openbsd-arm64@0.25.0": 101 | version "0.25.0" 102 | resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz#8fd55a4d08d25cdc572844f13c88d678c84d13f7" 103 | integrity sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw== 104 | 105 | "@esbuild/openbsd-x64@0.25.0": 106 | version "0.25.0" 107 | resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz#0c48ddb1494bbc2d6bcbaa1429a7f465fa1dedde" 108 | integrity sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg== 109 | 110 | "@esbuild/sunos-x64@0.25.0": 111 | version "0.25.0" 112 | resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz#86ff9075d77962b60dd26203d7352f92684c8c92" 113 | integrity sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg== 114 | 115 | "@esbuild/win32-arm64@0.25.0": 116 | version "0.25.0" 117 | resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz#849c62327c3229467f5b5cd681bf50588442e96c" 118 | integrity sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw== 119 | 120 | "@esbuild/win32-ia32@0.25.0": 121 | version "0.25.0" 122 | resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz#f62eb480cd7cca088cb65bb46a6db25b725dc079" 123 | integrity sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA== 124 | 125 | "@esbuild/win32-x64@0.25.0": 126 | version "0.25.0" 127 | resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz#c8e119a30a7c8d60b9d2e22d2073722dde3b710b" 128 | integrity sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ== 129 | 130 | "@rollup/rollup-android-arm-eabi@4.34.9": 131 | version "4.34.9" 132 | resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.9.tgz#661a45a4709c70e59e596ec78daa9cb8b8d27604" 133 | integrity sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA== 134 | 135 | "@rollup/rollup-android-arm64@4.34.9": 136 | version "4.34.9" 137 | resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.9.tgz#128fe8dd510d880cf98b4cb6c7add326815a0c4b" 138 | integrity sha512-4KW7P53h6HtJf5Y608T1ISKvNIYLWRKMvfnG0c44M6In4DQVU58HZFEVhWINDZKp7FZps98G3gxwC1sb0wXUUg== 139 | 140 | "@rollup/rollup-darwin-arm64@4.34.9": 141 | version "4.34.9" 142 | resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.9.tgz#363467bc49fd0b1e17075798ac8e9ad1e1e29535" 143 | integrity sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ== 144 | 145 | "@rollup/rollup-darwin-x64@4.34.9": 146 | version "4.34.9" 147 | resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.9.tgz#c2fe3d85fffe47f0ed0f076b3563ada22c8af19c" 148 | integrity sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q== 149 | 150 | "@rollup/rollup-freebsd-arm64@4.34.9": 151 | version "4.34.9" 152 | resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.9.tgz#d95bd8f6eaaf829781144fc8bd2d5d71d9f6a9f5" 153 | integrity sha512-2lzjQPJbN5UnHm7bHIUKFMulGTQwdvOkouJDpPysJS+QFBGDJqcfh+CxxtG23Ik/9tEvnebQiylYoazFMAgrYw== 154 | 155 | "@rollup/rollup-freebsd-x64@4.34.9": 156 | version "4.34.9" 157 | resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.9.tgz#c3576c6011656e4966ded29f051edec636b44564" 158 | integrity sha512-SLl0hi2Ah2H7xQYd6Qaiu01kFPzQ+hqvdYSoOtHYg/zCIFs6t8sV95kaoqjzjFwuYQLtOI0RZre/Ke0nPaQV+g== 159 | 160 | "@rollup/rollup-linux-arm-gnueabihf@4.34.9": 161 | version "4.34.9" 162 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.9.tgz#48c87d0dee4f8dc9591a416717f91b4a89d77e3d" 163 | integrity sha512-88I+D3TeKItrw+Y/2ud4Tw0+3CxQ2kLgu3QvrogZ0OfkmX/DEppehus7L3TS2Q4lpB+hYyxhkQiYPJ6Mf5/dPg== 164 | 165 | "@rollup/rollup-linux-arm-musleabihf@4.34.9": 166 | version "4.34.9" 167 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.9.tgz#f4c4e7c03a7767f2e5aa9d0c5cfbf5c0f59f2d41" 168 | integrity sha512-3qyfWljSFHi9zH0KgtEPG4cBXHDFhwD8kwg6xLfHQ0IWuH9crp005GfoUUh/6w9/FWGBwEHg3lxK1iHRN1MFlA== 169 | 170 | "@rollup/rollup-linux-arm64-gnu@4.34.9": 171 | version "4.34.9" 172 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.9.tgz#1015c9d07a99005025d13b8622b7600029d0b52f" 173 | integrity sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw== 174 | 175 | "@rollup/rollup-linux-arm64-musl@4.34.9": 176 | version "4.34.9" 177 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.9.tgz#8f895eb5577748fc75af21beae32439626e0a14c" 178 | integrity sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A== 179 | 180 | "@rollup/rollup-linux-loongarch64-gnu@4.34.9": 181 | version "4.34.9" 182 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.9.tgz#c9cd5dbbdc6b3ca4dbeeb0337498cf31949004a0" 183 | integrity sha512-dRAgTfDsn0TE0HI6cmo13hemKpVHOEyeciGtvlBTkpx/F65kTvShtY/EVyZEIfxFkV5JJTuQ9tP5HGBS0hfxIg== 184 | 185 | "@rollup/rollup-linux-powerpc64le-gnu@4.34.9": 186 | version "4.34.9" 187 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.9.tgz#7ebb5b4441faa17843a210f7d0583a20c93b40e4" 188 | integrity sha512-PHcNOAEhkoMSQtMf+rJofwisZqaU8iQ8EaSps58f5HYll9EAY5BSErCZ8qBDMVbq88h4UxaNPlbrKqfWP8RfJA== 189 | 190 | "@rollup/rollup-linux-riscv64-gnu@4.34.9": 191 | version "4.34.9" 192 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.9.tgz#10f5d7349fbd2fe78f9e36ecc90aab3154435c8d" 193 | integrity sha512-Z2i0Uy5G96KBYKjeQFKbbsB54xFOL5/y1P5wNBsbXB8yE+At3oh0DVMjQVzCJRJSfReiB2tX8T6HUFZ2k8iaKg== 194 | 195 | "@rollup/rollup-linux-s390x-gnu@4.34.9": 196 | version "4.34.9" 197 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.9.tgz#196347d2fa20593ab09d0b7e2589fb69bdf742c6" 198 | integrity sha512-U+5SwTMoeYXoDzJX5dhDTxRltSrIax8KWwfaaYcynuJw8mT33W7oOgz0a+AaXtGuvhzTr2tVKh5UO8GVANTxyQ== 199 | 200 | "@rollup/rollup-linux-x64-gnu@4.34.9": 201 | version "4.34.9" 202 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz#7193cbd8d128212b8acda37e01b39d9e96259ef8" 203 | integrity sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A== 204 | 205 | "@rollup/rollup-linux-x64-musl@4.34.9": 206 | version "4.34.9" 207 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.9.tgz#29a6867278ca0420b891574cfab98ecad70c59d1" 208 | integrity sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA== 209 | 210 | "@rollup/rollup-win32-arm64-msvc@4.34.9": 211 | version "4.34.9" 212 | resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.9.tgz#89427dcac0c8e3a6d32b13a03a296a275d0de9a9" 213 | integrity sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q== 214 | 215 | "@rollup/rollup-win32-ia32-msvc@4.34.9": 216 | version "4.34.9" 217 | resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.9.tgz#ecb9711ba2b6d2bf6ee51265abe057ab90913deb" 218 | integrity sha512-KB48mPtaoHy1AwDNkAJfHXvHp24H0ryZog28spEs0V48l3H1fr4i37tiyHsgKZJnCmvxsbATdZGBpbmxTE3a9w== 219 | 220 | "@rollup/rollup-win32-x64-msvc@4.34.9": 221 | version "4.34.9" 222 | resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.9.tgz#1973871850856ae72bc678aeb066ab952330e923" 223 | integrity sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw== 224 | 225 | "@thi.ng/api@^8.11.21": 226 | version "8.11.21" 227 | resolved "https://registry.yarnpkg.com/@thi.ng/api/-/api-8.11.21.tgz#afdfbc7c935879820c6e77123348e7c2f0c39b7f" 228 | integrity sha512-J6BUdUtFtwZirL3M9tkCiqBXj228z7zkxWOaDWTymwBeqY9s02vJP3mQV8l5p+YPDIRmYx/q7XVuLW1UTJRN/A== 229 | 230 | "@thi.ng/expose@^1.2.48": 231 | version "1.2.48" 232 | resolved "https://registry.yarnpkg.com/@thi.ng/expose/-/expose-1.2.48.tgz#6ea2027ef5f6752e8ba8ec60fce4d39fc762c875" 233 | integrity sha512-1PaGUKqYVoz4Z2U/BeNg245kdY4s/CvlPiGkmdO+qh5oNBbBA3XK8Xwt+5SACQpBw6jdxlDoecSI5Hgvsqsktg== 234 | 235 | "@types/estree@1.0.6": 236 | version "1.0.6" 237 | resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" 238 | integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== 239 | 240 | esbuild@^0.25.0: 241 | version "0.25.0" 242 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.0.tgz#0de1787a77206c5a79eeb634a623d39b5006ce92" 243 | integrity sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw== 244 | optionalDependencies: 245 | "@esbuild/aix-ppc64" "0.25.0" 246 | "@esbuild/android-arm" "0.25.0" 247 | "@esbuild/android-arm64" "0.25.0" 248 | "@esbuild/android-x64" "0.25.0" 249 | "@esbuild/darwin-arm64" "0.25.0" 250 | "@esbuild/darwin-x64" "0.25.0" 251 | "@esbuild/freebsd-arm64" "0.25.0" 252 | "@esbuild/freebsd-x64" "0.25.0" 253 | "@esbuild/linux-arm" "0.25.0" 254 | "@esbuild/linux-arm64" "0.25.0" 255 | "@esbuild/linux-ia32" "0.25.0" 256 | "@esbuild/linux-loong64" "0.25.0" 257 | "@esbuild/linux-mips64el" "0.25.0" 258 | "@esbuild/linux-ppc64" "0.25.0" 259 | "@esbuild/linux-riscv64" "0.25.0" 260 | "@esbuild/linux-s390x" "0.25.0" 261 | "@esbuild/linux-x64" "0.25.0" 262 | "@esbuild/netbsd-arm64" "0.25.0" 263 | "@esbuild/netbsd-x64" "0.25.0" 264 | "@esbuild/openbsd-arm64" "0.25.0" 265 | "@esbuild/openbsd-x64" "0.25.0" 266 | "@esbuild/sunos-x64" "0.25.0" 267 | "@esbuild/win32-arm64" "0.25.0" 268 | "@esbuild/win32-ia32" "0.25.0" 269 | "@esbuild/win32-x64" "0.25.0" 270 | 271 | fsevents@~2.3.2: 272 | version "2.3.2" 273 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 274 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 275 | 276 | fsevents@~2.3.3: 277 | version "2.3.3" 278 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" 279 | integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== 280 | 281 | nanoid@^3.3.8: 282 | version "3.3.8" 283 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" 284 | integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== 285 | 286 | picocolors@^1.1.1: 287 | version "1.1.1" 288 | resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" 289 | integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== 290 | 291 | postcss@^8.5.3: 292 | version "8.5.3" 293 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.3.tgz#1463b6f1c7fb16fe258736cba29a2de35237eafb" 294 | integrity sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A== 295 | dependencies: 296 | nanoid "^3.3.8" 297 | picocolors "^1.1.1" 298 | source-map-js "^1.2.1" 299 | 300 | rollup@^4.30.1: 301 | version "4.34.9" 302 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.34.9.tgz#e1eb397856476778aeb6ac2ac3d09b2ce177a558" 303 | integrity sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ== 304 | dependencies: 305 | "@types/estree" "1.0.6" 306 | optionalDependencies: 307 | "@rollup/rollup-android-arm-eabi" "4.34.9" 308 | "@rollup/rollup-android-arm64" "4.34.9" 309 | "@rollup/rollup-darwin-arm64" "4.34.9" 310 | "@rollup/rollup-darwin-x64" "4.34.9" 311 | "@rollup/rollup-freebsd-arm64" "4.34.9" 312 | "@rollup/rollup-freebsd-x64" "4.34.9" 313 | "@rollup/rollup-linux-arm-gnueabihf" "4.34.9" 314 | "@rollup/rollup-linux-arm-musleabihf" "4.34.9" 315 | "@rollup/rollup-linux-arm64-gnu" "4.34.9" 316 | "@rollup/rollup-linux-arm64-musl" "4.34.9" 317 | "@rollup/rollup-linux-loongarch64-gnu" "4.34.9" 318 | "@rollup/rollup-linux-powerpc64le-gnu" "4.34.9" 319 | "@rollup/rollup-linux-riscv64-gnu" "4.34.9" 320 | "@rollup/rollup-linux-s390x-gnu" "4.34.9" 321 | "@rollup/rollup-linux-x64-gnu" "4.34.9" 322 | "@rollup/rollup-linux-x64-musl" "4.34.9" 323 | "@rollup/rollup-win32-arm64-msvc" "4.34.9" 324 | "@rollup/rollup-win32-ia32-msvc" "4.34.9" 325 | "@rollup/rollup-win32-x64-msvc" "4.34.9" 326 | fsevents "~2.3.2" 327 | 328 | source-map-js@^1.2.1: 329 | version "1.2.1" 330 | resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" 331 | integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== 332 | 333 | typescript@^5.8.2: 334 | version "5.8.2" 335 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.2.tgz#8170b3702f74b79db2e5a96207c15e65807999e4" 336 | integrity sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ== 337 | 338 | vite@^6.2.0: 339 | version "6.2.0" 340 | resolved "https://registry.yarnpkg.com/vite/-/vite-6.2.0.tgz#9dcb543380dab18d8384eb840a76bf30d78633f0" 341 | integrity sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ== 342 | dependencies: 343 | esbuild "^0.25.0" 344 | postcss "^8.5.3" 345 | rollup "^4.30.1" 346 | optionalDependencies: 347 | fsevents "~2.3.3" 348 | --------------------------------------------------------------------------------