├── .gitignore
├── tests
├── misc.test.js
├── subroutines.test.js
├── loadimm.test.js
├── memory.test.js
├── conditional.test.js
├── loops.test.js
├── stack.test.js
└── arithmetic.test.js
├── README.md
├── processor.js
├── LICENSE.md
├── theme.css
├── main.js
├── index.html
└── jibniz.js
/.gitignore:
--------------------------------------------------------------------------------
1 | res/
2 | misc/
3 |
--------------------------------------------------------------------------------
/tests/misc.test.js:
--------------------------------------------------------------------------------
1 | import jibniz from '../jibniz'
2 |
3 | const vm = new jibniz.VM
4 | const vs = vm.vStack
5 | const vr = vm.vRStack
6 |
7 | beforeEach(() => vm.reset())
8 |
9 |
10 | test('Escape loop via an if', () => {
11 | let prog = new jibniz.Program('5,3X7,0?L:')
12 | vm.runOnce(prog)
13 | expect(vs.peek(2)).toEqual([0x70000, 0x50000])
14 | })
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## jibniz
2 |
3 | **jibniz** is a custom javascript implementation of the [IBNIZ VM](http://pelulamu.net/ibniz/).
4 | A tiny demo can be found on https://flupe.github.io/jibniz/.
5 |
6 | ## Running tests (deprecated for now)
7 |
8 | ```
9 | yarn install
10 | yarn test
11 | ```
12 |
13 | ## TODO
14 |
15 | I've tried again and again to produce WASM from IBNIZ programs, sadly the `J`
16 | instruction is too powerful. In the specification, it can jump to any instruction at runtime, depending on the address found on the stack, whereas WASM jumps have to be well-behaved.
17 |
18 | - Fix endianness hacks.
19 | - Basic support for audio.
20 | - "smart" mode detection.
21 | - Compile a version of the code for both T and TYX modes.
22 |
--------------------------------------------------------------------------------
/processor.js:
--------------------------------------------------------------------------------
1 | class AudioProcessor extends AudioWorkletProcessor {
2 | constructor(...args) {
3 | super(...args)
4 | this._buffer = new Float32Array(0x10000)
5 | this.position = 0
6 | this.port.onmessage = ({ data }) => {
7 | data.forEach((v , i) => {
8 | this._buffer[i] = (((v & 0xffff) - 0x8000) / 0x8000)
9 | })
10 | }
11 | }
12 | process (inputs, outputs, params) {
13 | const output = outputs[0]
14 | output.forEach(channel => {
15 | for (let i = 0; i < channel.length; i++)
16 | channel[i] = this._buffer[(i + this.position) & 0xffff]
17 | })
18 | this.position = (this.position + outputs[0][0].length) & 0xffff
19 | return true
20 | }
21 | }
22 |
23 | registerProcessor('processor', AudioProcessor)
24 |
--------------------------------------------------------------------------------
/tests/subroutines.test.js:
--------------------------------------------------------------------------------
1 | import jibniz from '../jibniz'
2 |
3 | const vm = new jibniz.VM
4 | const vs = vm.vStack
5 | const mem = vm.memory
6 |
7 | beforeEach(() => vm.reset())
8 |
9 |
10 | test('Define subroutine in memory #1', () => {
11 | let prog = new jibniz.Program('3{2')
12 | vs.push(7)
13 | vm.runOnce(prog)
14 | expect(mem[3]).toBe(2)
15 | expect(vs.pop()).toBe(7)
16 | })
17 |
18 |
19 | test('Define subroutine in memory #2', () => {
20 | let prog = new jibniz.Program('3{2}1')
21 | vs.push(7)
22 | vm.runOnce(prog)
23 | expect(mem[3]).toBe(2)
24 | expect(vs.peek(2)).toEqual([0x10000, 7])
25 | })
26 |
27 |
28 | test('Visit subroutine', () => {
29 | let prog = new jibniz.Program('0{2}1,0V3')
30 | vs.push(7)
31 | vm.runOnce(prog)
32 | expect(vs.peek(4)).toEqual([0x30000, 0x20000, 0x10000, 7])
33 | })
34 |
35 |
36 | test('Visit multiple subroutines', () => {
37 | let prog = new jibniz.Program('0{2}1{3}1,0V1V4')
38 | vs.push(7)
39 | vm.runOnce(prog)
40 | expect(vs.peek(5)).toEqual([0x40000, 0x30000, 0x20000, 0x10000, 7])
41 | })
42 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 flupe
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/tests/loadimm.test.js:
--------------------------------------------------------------------------------
1 | import jibniz from '../jibniz'
2 |
3 | const vm = new jibniz.VM
4 | const vs = vm.vStack
5 |
6 | beforeEach(() => vm.reset())
7 |
8 |
9 | test('Load imm integer', () => {
10 | let prog = new jibniz.Program('AE')
11 | vm.runOnce(prog)
12 | expect(vs.pop()).toBe(0xAE0000)
13 | })
14 |
15 |
16 | test('Load imm fraction', () => {
17 | let prog = new jibniz.Program('.AE')
18 | vm.runOnce(prog)
19 | expect(vs.pop()).toBe(0xAE00)
20 | })
21 |
22 |
23 | test('Load imm full number', () => {
24 | let prog = new jibniz.Program('1234.5678')
25 | vm.runOnce(prog)
26 | expect(vs.pop()).toBe(0x12345678)
27 | })
28 |
29 |
30 | test('Ignore trailing fraction', () => {
31 | let prog = new jibniz.Program('1.12345')
32 | vm.runOnce(prog)
33 | expect(vs.pop()).toBe(0x11234)
34 | })
35 |
36 |
37 | test('Ignore out of bounds integer', () => {
38 | let prog = new jibniz.Program('12345')
39 | vm.runOnce(prog)
40 | expect(vs.pop()).toBe(0x23450000)
41 | })
42 |
43 |
44 | test('Ignore bounds', () => {
45 | let prog = new jibniz.Program('12345.6789A')
46 | vm.runOnce(prog)
47 | expect(vs.pop()).toBe(0x23456789)
48 | })
49 |
--------------------------------------------------------------------------------
/theme.css:
--------------------------------------------------------------------------------
1 | body {
2 | width: 100%;
3 | margin: 0;
4 | padding: 0;
5 | font-family: 'Anonymous Pro', monospace;
6 | color: #fff;
7 | background: #000;
8 | }
9 |
10 | form {
11 | margin: 100px 0 50px;
12 | }
13 |
14 | form canvas {
15 | display: block;
16 | margin: 0 auto;
17 | }
18 |
19 | form select {
20 | margin: 16px auto 0;
21 | width: 300px;
22 | display: block;
23 | }
24 |
25 | form textarea {
26 | display: block;
27 | outline: none;
28 | border: 2px solid rgba(256, 256, 256, .3);
29 | border-radius: 2px;
30 | width: 300px;
31 | box-sizing: border-box;
32 | padding: 4px 8px;
33 | margin: 16px auto 0;
34 | resize: vertical;
35 | min-height: 100px;
36 | }
37 |
38 | form textarea.error {
39 | border-color: rgba(256, 70, 10, .7);
40 | }
41 |
42 | pre {
43 | margin: 16px auto 0;
44 | font-size: 14px;
45 | color: #555;
46 | cursor: default;
47 | text-align: left;
48 | display: inline-block;
49 | }
50 |
51 | div {
52 | text-align: center;
53 | }
54 |
55 | a {
56 | color: #666;
57 | text-decoration: none;
58 | }
59 |
60 | i, label {
61 | color: #666;
62 | font-style: normal;
63 | cursor: pointer;
64 | }
65 |
66 | i:hover, label:hover {
67 | color: #aaa;
68 | }
69 |
70 | input[type="radio"] { display: none; }
71 | input[type="radio"]:checked+label {
72 | color: #ccc;
73 | }
74 |
75 | a:hover {
76 | text-decoration: underline;
77 | }
78 |
--------------------------------------------------------------------------------
/tests/memory.test.js:
--------------------------------------------------------------------------------
1 | import jibniz from '../jibniz'
2 |
3 | const vm = new jibniz.VM
4 | const vs = vm.vStack
5 | const mem = vm.memory
6 |
7 | beforeEach(() => vm.reset())
8 |
9 |
10 | test('Store in user area #1', () => {
11 | let prog = new jibniz.Program('5,0!')
12 | vs.push(7)
13 | vm.runOnce(prog)
14 | expect(mem[0]).toBe(0x50000)
15 | expect(vs.pop(1)).toBe(7)
16 | })
17 |
18 |
19 | test('Store in user area #2', () => {
20 | let prog = new jibniz.Program('5,F!')
21 | vs.push(7)
22 | vm.runOnce(prog)
23 | expect(mem[15]).toBe(0x50000)
24 | expect(vs.pop(1)).toBe(7)
25 | })
26 |
27 |
28 | test('Store in video stack area', () => {
29 | let prog = new jibniz.Program('5,.000E!')
30 | vs.push(1, 7)
31 | vm.runOnce(prog)
32 | expect(mem[0xE0000]).toBe(0x50000)
33 | // we've written on the stack!
34 | expect(vs.peek(2)).toEqual([7, 0x50000])
35 | })
36 |
37 |
38 | test('Load value from memory', () => {
39 | let prog = new jibniz.Program('0@')
40 | mem[0] = 7
41 | vm.runOnce(prog)
42 | expect(vs.pop()).toBe(7)
43 | })
44 |
45 |
46 | test('Load value from stack area', () => {
47 | let prog = new jibniz.Program('.000E@')
48 | vs.push(1, 7)
49 | vm.runOnce(prog)
50 | expect(vs.peek(3)).toEqual([1, 7, 1])
51 | })
52 |
53 |
54 | test('Write then load value from memory', () => {
55 | let prog = new jibniz.Program('7,0!0@')
56 | vm.runOnce(prog)
57 | expect(vs.pop()).toBe(0x70000)
58 | })
59 |
60 | test('Write with loop to memory', () => {
61 | let prog = new jibniz.Program('4Xii!L')
62 | vm.runOnce(prog)
63 | expect(Array.from(mem.slice(0, 5))).toEqual([0, 0x10000, 0x20000, 0x30000, 0x40000])
64 | })
65 |
--------------------------------------------------------------------------------
/tests/conditional.test.js:
--------------------------------------------------------------------------------
1 | import jibniz from '../jibniz'
2 |
3 | const vm = new jibniz.VM
4 | const vs = vm.vStack
5 | const vr = vm.vRStack
6 |
7 | beforeEach(() => vm.reset())
8 |
9 |
10 | test('Simple if else fi with true cond', () => {
11 | let prog = new jibniz.Program('1?5:3;2')
12 | vs.push(7)
13 | vm.runOnce(prog)
14 | expect(vs.peek(3)).toEqual([0x20000, 0x50000, 7])
15 | })
16 |
17 |
18 | test('Simple if else fi with false cond', () => {
19 | let prog = new jibniz.Program('0?5:3;2')
20 | vs.push(7)
21 | vm.runOnce(prog)
22 | expect(vs.peek(3)).toEqual([0x20000, 0x30000, 7])
23 | })
24 |
25 |
26 | test('Simple if else with true cond', () => {
27 | let prog = new jibniz.Program('1?5:3')
28 | vs.push(7)
29 | vm.runOnce(prog)
30 | expect(vs.peek(2)).toEqual([0x50000, 7])
31 | })
32 |
33 |
34 | test('Simple if else with false cond', () => {
35 | let prog = new jibniz.Program('0?5:3')
36 | vs.push(7)
37 | vm.runOnce(prog)
38 | expect(vs.peek(2)).toEqual([0x30000, 7])
39 | })
40 |
41 |
42 | test('Simple if fi with true cond', () => {
43 | let prog = new jibniz.Program('1?5;2')
44 | vs.push(7)
45 | vm.runOnce(prog)
46 | expect(vs.peek(3)).toEqual([0x20000, 0x50000, 7])
47 | })
48 |
49 |
50 | test('Simple if fi with false cond', () => {
51 | let prog = new jibniz.Program('0?5;2')
52 | vs.push(7)
53 | vm.runOnce(prog)
54 | expect(vs.peek(2)).toEqual([0x20000, 7])
55 | })
56 |
57 |
58 | test('Simple if with true cond', () => {
59 | let prog = new jibniz.Program('1?5')
60 | vs.push(7)
61 | vm.runOnce(prog)
62 | expect(vs.peek(2)).toEqual([0x50000, 7])
63 | })
64 |
65 |
66 | test('Simple if with false cond', () => {
67 | let prog = new jibniz.Program('0?5')
68 | vs.push(7)
69 | vm.runOnce(prog)
70 | expect(vs.peek(1)).toEqual([7])
71 | })
72 |
--------------------------------------------------------------------------------
/tests/loops.test.js:
--------------------------------------------------------------------------------
1 | import jibniz from '../jibniz'
2 |
3 | const vm = new jibniz.VM
4 | const vs = vm.vStack
5 | const vr = vm.vRStack
6 |
7 | beforeEach(() => vm.reset())
8 |
9 |
10 | test('Times loop initialization', () => {
11 | let prog = new jibniz.Program('4X')
12 | vm.runOnce(prog)
13 | expect(vr.peek(2)).toEqual([2, 4])
14 | })
15 |
16 |
17 | test('Simple times loop', () => {
18 | let prog = new jibniz.Program('4X.0001L')
19 | vs.push(0)
20 | vr.push(14)
21 | vm.runOnce(prog)
22 | expect(vs.peek(5)).toEqual([1, 1, 1, 1, 0])
23 | expect(vr.pop(1)).toBe(14)
24 | })
25 |
26 |
27 | test('Times loop with index', () => {
28 | let prog = new jibniz.Program('4XiL')
29 | vs.push(0)
30 | vm.runOnce(prog)
31 | expect(vs.peek(5)).toEqual([0x10000, 0x20000, 0x30000, 0x40000, 0])
32 | })
33 |
34 |
35 | test('Nested times loops initialization', () => {
36 | let prog = new jibniz.Program('3X4X')
37 | vm.runOnce(prog)
38 | expect(vr.peek(4)).toEqual([4, 4, 2, 3])
39 | })
40 |
41 |
42 | test('Nested loops using index', () => {
43 | let prog = new jibniz.Program('2X3XiLL')
44 | vs.push(0)
45 | vm.runOnce(prog)
46 | expect(vs.peek(7)).toEqual([0x10000, 0x20000, 0x30000, 0x10000, 0x20000, 0x30000, 0])
47 | })
48 |
49 |
50 | test('Nested loops using outdex', () => {
51 | let prog = new jibniz.Program('2X3XjLL')
52 | vs.push(0)
53 | vm.runOnce(prog)
54 | expect(vs.peek(7)).toEqual([0x10000, 0x10000, 0x10000, 0x20000, 0x20000, 0x20000, 0])
55 | })
56 |
57 |
58 | test('Do loop initialization', () => {
59 | let prog = new jibniz.Program('[')
60 | vr.push(7)
61 | vm.runOnce(prog)
62 | expect(vr.peek(2)).toEqual([1, 7])
63 | })
64 |
65 |
66 | test('Simple do loop', () => {
67 | let prog = new jibniz.Program('.0003[d.0001-d]9')
68 | vs.push(5)
69 | vr.push(7)
70 | vm.runOnce(prog)
71 | expect(vs.peek(6)).toEqual([0x90000, 0, 1, 2, 3, 5])
72 | expect(vr.pop()).toBe(7)
73 | })
74 |
--------------------------------------------------------------------------------
/tests/stack.test.js:
--------------------------------------------------------------------------------
1 | import jibniz from '../jibniz'
2 |
3 | const vm = new jibniz.VM
4 | const vs = vm.vStack
5 | const vr = vm.vRStack
6 |
7 | beforeEach(() => vm.reset())
8 |
9 |
10 | test('Dup instruction', () => {
11 | let prog = new jibniz.Program('d')
12 | vs.push(0, 1)
13 | vm.runOnce(prog)
14 | expect(vs.peek(3)).toEqual([1, 1, 0])
15 | })
16 |
17 |
18 | test('Pop instruction', () => {
19 | let prog = new jibniz.Program('p')
20 | vs.push(0, 5, 6)
21 | vm.runOnce(prog)
22 | expect(vs.peek(2)).toEqual([5, 0])
23 | })
24 |
25 |
26 | test('Exchange instruction', () => {
27 | let prog = new jibniz.Program('x')
28 | vs.push(1, 2, 3)
29 | vm.runOnce(prog)
30 | expect(vs.peek(3)).toEqual([2, 3, 1])
31 | })
32 |
33 |
34 | test('Trirot instruction', () => {
35 | let prog = new jibniz.Program('v')
36 | vs.push(1, 2, 3)
37 | vm.runOnce(prog)
38 | expect(vs.peek(3)).toEqual([1, 3, 2])
39 | })
40 |
41 |
42 | test('Pick instruction', () => {
43 | let prog = new jibniz.Program(')')
44 | vs.push(5, 0, 0, 0x20000)
45 | vm.runOnce(prog)
46 | expect(vs.pop()).toBe(5)
47 | })
48 |
49 |
50 | test('Duplicate the stack 3 times', () => {
51 | let prog = new jibniz.Program('4X1)L')
52 | vs.push(0, 5, 3)
53 | vm.runOnce(prog)
54 | expect(vs.peek(7)).toEqual([3, 5, 3, 5, 3, 5, 0])
55 | })
56 |
57 |
58 | test('Bury instruction', () => {
59 | let prog = new jibniz.Program('(')
60 | vs.push(5, 0)
61 | vm.runOnce(prog)
62 | expect(vs.pop()).toBe(5)
63 | })
64 |
65 |
66 | test('RetAddr instruction', () => {
67 | let prog = new jibniz.Program('R')
68 | vr.push(5, 7)
69 | vs.push(1)
70 | vm.runOnce(prog)
71 | expect(vr.pop()).toBe(5)
72 | expect(vs.peek(2)).toEqual([7, 1])
73 | })
74 |
75 |
76 | test('PushToRS instruction', () => {
77 | let prog = new jibniz.Program('P')
78 | vs.push(5, 7)
79 | vr.push(1)
80 | vm.runOnce(prog)
81 | expect(vs.pop()).toBe(5)
82 | expect(vr.peek(2)).toEqual([7, 1])
83 | })
84 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | const $ = document.getElementById.bind(document)
2 | const on = (t, e, f) => t.addEventListener(e, f, false)
3 | const $form = document.forms.editor
4 | const $input = $('input')
5 | const $selection = $('selection')
6 | const $fps = $('fps')
7 | const $time = $('time')
8 | const query = new URLSearchParams(window.location.hash.substr(1))
9 |
10 | const format = (x, b = 10) => x.toString(b).toUpperCase()
11 |
12 | class Editor extends jibniz.Console {
13 | constructor(cvs, query) {
14 | super(cvs)
15 |
16 | // initial mode
17 | if (query.has('m')) this.mode = query.get('m')
18 | else this.mode = $form.elements.mode.value
19 |
20 | // initial program
21 | if (query.has('c')) $input.value = query.get('c')
22 | else $input.value = $selection.value
23 |
24 | this.query = query
25 | this.changeProgram($input.value)
26 | }
27 |
28 | step() {
29 | super.step()
30 | $fps.innerText = ('0000' + format(this.fps )).substr(-4)
31 | $time.innerText = ('0000' + format(this.time, 16)).substr(-4)
32 | }
33 |
34 | install(program) {
35 | this._program = program
36 | super.install(program)
37 | }
38 |
39 | changeProgram(code) {
40 | this.reset()
41 | this.install(new jibniz.Program(code))
42 | this.query.set('c', code)
43 | this.updateHash()
44 | }
45 |
46 | reset() {
47 | super.reset()
48 | if (this._program) super.install(this._program)
49 | // if (!this.running) super.step()
50 | }
51 |
52 | updateHash() {
53 | this.query.set('m', this.mode)
54 | window.location.hash = '#' + this.query.toString()
55 | }
56 | }
57 |
58 | const IBNIZ = new Editor($('cvs'), query)
59 |
60 | IBNIZ.init().then(() => {
61 | $form.elements.mode.forEach(i => on(i, 'change', () => {
62 | IBNIZ.mode = $form.elements.mode.value
63 | IBNIZ.updateHash()
64 | }))
65 |
66 | // set text to possibly selected value
67 | on($selection, 'change', () => {
68 | $input.value = $selection.value
69 | IBNIZ.changeProgram($input.value)
70 | })
71 |
72 | // TODO: debounce
73 | on($input, 'input', () => { IBNIZ.changeProgram($input.value) })
74 | on($('reset'), 'click', () => { IBNIZ.reset() })
75 | on($('pause'), 'click', () => { IBNIZ.toggle() })
76 |
77 | IBNIZ.run()
78 | })
79 |
80 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | jibniz
7 |
8 |
9 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/tests/arithmetic.test.js:
--------------------------------------------------------------------------------
1 | import jibniz from '../jibniz'
2 |
3 | const vm = new jibniz.VM
4 |
5 | beforeEach(() => vm.reset())
6 |
7 |
8 | // TODO(flupe):
9 | // here we assume the JS runtime uses 32-bits integers
10 | // fix that.
11 | function testOp(prog, a, b) {
12 | vm.vStack.push(a)
13 | vm.runOnce(prog)
14 | expect(vm.vStack.pop()).toBe(b | 0)
15 | }
16 |
17 |
18 | function testBinOp(prog, a, b, c) {
19 | vm.vStack.push(a)
20 | vm.vStack.push(b)
21 | vm.runOnce(prog)
22 | expect(vm.vStack.pop()).toBe(c | 0)
23 | }
24 |
25 |
26 | test('+', () => {
27 | let prog = new jibniz.Program('+')
28 |
29 | testBinOp(prog, 0, 0, 0)
30 | testBinOp(prog, 1, 1, 2)
31 | testBinOp(prog, -1, 1, 0)
32 | })
33 |
34 |
35 | test('-', () => {
36 | let prog = new jibniz.Program('-')
37 |
38 | testBinOp(prog, 0, 0, 0)
39 | testBinOp(prog, 1, 1, 0)
40 | testBinOp(prog, 2, 4, -2)
41 | })
42 |
43 |
44 | test('*', () => {
45 | let prog = new jibniz.Program('*')
46 |
47 | // 2 bytes of fraction
48 | // XXXX.XXXX
49 | testBinOp(prog, 0, 0, 0)
50 | testBinOp(prog, 0x10000, 0x10000, 0x10000)
51 | testBinOp(prog, 0x10000, -0x10000, -0x10000)
52 | testBinOp(prog, 0x20000, 0x100000, 0x200000)
53 | })
54 |
55 |
56 | test('/', () => {
57 | let prog = new jibniz.Program('/')
58 |
59 | testBinOp(prog, 0x10000, 0x10000, 0x10000)
60 | testBinOp(prog, 0x40000, 0x20000, 0x20000)
61 | testBinOp(prog, 0x40000, -0x20000, -0x20000)
62 | // division by 0
63 | testBinOp(prog, 0, 0, 0)
64 | testBinOp(prog, 1, 0, 0)
65 | })
66 |
67 |
68 | test('%', () => {
69 | let prog = new jibniz.Program('%')
70 |
71 | testBinOp(prog, 17, 2, 1)
72 | testBinOp(prog, -12, 5, -2)
73 | // mod by 0
74 | testBinOp(prog, 25, 0, 0)
75 | })
76 |
77 |
78 | test('q', () => {
79 | let prog = new jibniz.Program('q')
80 |
81 | testOp(prog, 0x40000, 0x20000)
82 | testOp(prog, 0x190000, 0x50000)
83 | // 0 when negative input
84 | testOp(prog, -3, 0)
85 | })
86 |
87 |
88 | test('&', () => {
89 | let prog = new jibniz.Program('&')
90 |
91 | testBinOp(prog, 0, 0, 0)
92 | testBinOp(prog, 1, 0, 0)
93 | testBinOp(prog, 0, 1, 0)
94 | testBinOp(prog, 1, 1, 1)
95 | })
96 |
97 |
98 | test('|', () => {
99 | let prog = new jibniz.Program('|')
100 |
101 | testBinOp(prog, 0, 0, 0)
102 | testBinOp(prog, 1, 0, 1)
103 | testBinOp(prog, 0, 1, 1)
104 | testBinOp(prog, 1, 1, 1)
105 | })
106 |
107 |
108 | test('^', () => {
109 | let prog = new jibniz.Program('^')
110 |
111 | testBinOp(prog, 0, 0, 0)
112 | testBinOp(prog, 1, 0, 1)
113 | testBinOp(prog, 0, 1, 1)
114 | testBinOp(prog, 1, 1, 0)
115 | })
116 |
117 |
118 | test('r', () => {
119 | let prog = new jibniz.Program('r')
120 |
121 | testBinOp(prog, 0, 0, 0)
122 | testBinOp(prog, 25, 0, 25)
123 | testBinOp(prog, 0x10, 0x40000, 0x1)
124 | testBinOp(prog, 0x100, 0x80000, 0x1)
125 | testBinOp(prog, 0x0FFFFF0F, 0x80000, 0x0F0FFFFF)
126 | })
127 |
128 |
129 | test('l', () => {
130 | let prog = new jibniz.Program('l')
131 |
132 | testBinOp(prog, 0, 0, 0)
133 | testBinOp(prog, 25, 0, 25)
134 | testBinOp(prog, 0x1, 0x40000, 0x10)
135 | testBinOp(prog, 0x1, 0x80000, 0x100)
136 | })
137 |
138 |
139 | test('~', () => {
140 | let prog = new jibniz.Program('~')
141 |
142 | testOp(prog, 0, 0xFFFFFFFF)
143 | testOp(prog, 1, 0xFFFFFFFE)
144 | })
145 |
146 |
147 | test('s', () => {
148 | let prog = new jibniz.Program('s')
149 |
150 | testOp(prog, 0, 0)
151 | testOp(prog, 0x8000, 0)
152 | testOp(prog, -0x8000, 0)
153 |
154 | testOp(prog, 0x04000, 0x10000)
155 | testOp(prog, -0x04000, -0x10000)
156 | })
157 |
158 |
159 | test('a', () => {
160 | let prog = new jibniz.Program('a')
161 |
162 | testBinOp(prog, 0, 0x10000, 0x4000)
163 | testBinOp(prog, 0, -0x10000, -0x4000)
164 | testBinOp(prog, 0x10000, 0, 0)
165 | testBinOp(prog, -0x10000, 0, 0X8000)
166 | })
167 |
168 |
169 | test('<', () => {
170 | let prog = new jibniz.Program('<')
171 |
172 | testOp(prog, -1, -1)
173 | testOp(prog, -0xFF, -0xFF)
174 | testOp(prog, 0, 0)
175 | testOp(prog, 1, 0)
176 | testOp(prog, 0xFF, 0)
177 | })
178 |
179 |
180 | test('>', () => {
181 | let prog = new jibniz.Program('>')
182 |
183 | testOp(prog, -1, 0)
184 | testOp(prog, -0xFF, 0)
185 | testOp(prog, 0, 0)
186 | testOp(prog, 1, 1)
187 | testOp(prog, 0xFF, 0xFF)
188 | })
189 |
190 |
191 | test('=', () => {
192 | let prog = new jibniz.Program('=')
193 |
194 | testOp(prog, -1, 0)
195 | testOp(prog, -0xFF, 0)
196 | testOp(prog, 0, 0x10000)
197 | testOp(prog, 1, 0)
198 | testOp(prog, 0xFF, 0)
199 | })
200 |
--------------------------------------------------------------------------------
/jibniz.js:
--------------------------------------------------------------------------------
1 | (function(factory) {
2 | if (typeof module != 'undefined' && typeof module.exports != 'undefined') {
3 | module.exports = factory()
4 | }
5 | else {
6 | window.jibniz = factory()
7 | }
8 | }(function() {
9 |
10 | 'use strict'
11 |
12 | const coord = v => v - 128 << 9
13 |
14 | // In the official IBNIZ documentation, it states
15 | // - the audio stack is 65536 words long
16 | // - regardless of sampling rate, audio stack is one second long
17 | // - default sampling rate is 61440
18 | // to make it easier, we set the sampling rate to the length of the stack
19 | const SAMPLE_RATE = 65536
20 |
21 | class Stack {
22 |
23 | constructor(buffer, offset, size) {
24 | this.buffer = buffer
25 | this.memory = new Int32Array(buffer, offset << 2, size)
26 | this.mask = size - 1
27 | this.top = 0
28 | }
29 |
30 | push(...values) {
31 | values.forEach(x => {
32 | this.memory[this.top] = x
33 | this.top = this.top + 1 & this.mask
34 | })
35 | }
36 |
37 | pop(x) {
38 | return this.memory[this.top = this.top + this.mask & this.mask]
39 | }
40 |
41 | peek(n) {
42 | let r = []
43 | let p = this.top
44 | while (n--) {
45 | p = p + this.mask & this.mask
46 | r.push(this.memory[p])
47 | }
48 | return r
49 | }
50 |
51 | reset() { this.top = 0 }
52 | clear() { this.memory.fill(0) }
53 |
54 | }
55 |
56 | class VM {
57 |
58 | constructor() {
59 | this.memory = new Int32Array(1 << 20)
60 |
61 | let buffer = this.buffer = this.memory.buffer
62 |
63 | this.aRStack = new Stack(buffer, 0xC8000, 0x4000)
64 | this.vRStack = new Stack(buffer, 0xCC000, 0x4000)
65 | this.aStack = new Stack(buffer, 0xD0000, 0x10000)
66 | this.vStack = new Stack(buffer, 0xE0000, 0x20000)
67 |
68 | this.time = 0
69 | this.mode = 'T'
70 | this.fragments = null
71 | }
72 |
73 | reset() {
74 | this.memory.fill(0)
75 | this.vStack.reset()
76 | this.vRStack.reset()
77 | this.aStack.reset()
78 | this.aRStack.reset()
79 | this.time = 0
80 | }
81 |
82 | step(userin = 0) {
83 | if (!this.fragments) return
84 |
85 | let f = this.fragments.video[this.mode]
86 | if (this.mode == 'TYX') {
87 | for (let y = 0; y < 256; y++) {
88 | for(let x = 0; x < 256; x++) {
89 | // TODO(flupe): handle both T and TYX modes
90 | this.vStack.push(this.time << 16, coord(y), coord(x))
91 | f(this.memory, this.vStack, this.vRStack, this.time << 16, coord(x), coord(y), userin)
92 | }
93 | }
94 | }
95 | else {
96 | for (let y = 0; y < 256; y++) {
97 | for(let x = 0; x < 256; x++) {
98 | // TODO(flupe): handle both T and TYX modes
99 | let w = this.time << 16 | y << 8 | x
100 | this.vStack.push(w)
101 | f(this.memory, this.vStack, this.vRStack, w, userin)
102 | }
103 | }
104 | }
105 |
106 | // HACKY
107 | // update audio every second
108 | // 60 frames per second
109 | // 0x1000 samples per second
110 | // 0x1000 / 60 samples per frame
111 | if (this.time % 60 == 0) {
112 | let t = this.time
113 | let f = this.fragments.audio
114 | for (let k = 0; k < 0x10000; k++) {
115 | let v = (k * 60) & 0xffff
116 | let t = this.time + (k * 60 / 0x10000) | 0
117 | let w = (t << 16) | v
118 | this.aStack.push(w)
119 | f(this.memory, this.aStack, this.aRStack, w, userin)
120 | }
121 | }
122 |
123 | this.time++
124 | }
125 |
126 | }
127 |
128 |
129 | class Console extends VM {
130 |
131 | constructor(cvs) {
132 | super()
133 |
134 | cvs = this.domElement = cvs || document.createElement('canvas')
135 | cvs.width = cvs.height = 256
136 |
137 | this.running = false
138 |
139 | this.videoPages = [
140 | new Uint8Array(this.buffer, 0xE0000 << 2, 0x40000),
141 | new Uint8Array(this.buffer, 0xF0000 << 2, 0x40000),
142 | ]
143 |
144 | this.x = 128
145 | this.y = 128
146 | }
147 |
148 | async init() {
149 | let gl = this.gl = cvs.getContext('webgl')
150 |
151 | function loadShader(src, type) {
152 | let shader = gl.createShader(type)
153 | gl.shaderSource(shader, src)
154 | gl.compileShader(shader)
155 |
156 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
157 | let error = `Error occured while compiling shader: ${gl.getShaderInfoLog(shader)}`
158 | gl.deleteShader(shader)
159 | throw error
160 | }
161 |
162 | return shader
163 | }
164 |
165 | let vertSrc = `
166 | attribute vec2 pos;
167 | varying lowp vec2 uv;
168 |
169 | void main() {
170 | gl_Position = vec4(pos, 0.0, 1.0);
171 | uv = vec2((pos.x + 1.0) / 2.0, (1.0 - pos.y) / 2.0);
172 | }
173 | `
174 |
175 | let fragSrc = `
176 | precision lowp float;
177 |
178 | varying lowp vec2 uv;
179 | uniform sampler2D page;
180 |
181 | void main() {
182 | vec4 tex = texture2D(page, uv);
183 | float y = tex.g;
184 | float u = tex.b <= 0.5 ? tex.b : -(1.0 - tex.b);
185 | float v = tex.a <= 0.5 ? tex.a : -(1.0 - tex.a);
186 |
187 | y = 1.1643 * (y - 0.0625);
188 |
189 | float r = clamp(y+1.5958*v, 0.0, 1.0);
190 | float g = clamp(y-0.39173*u-0.81290*v,0.0, 1.0);
191 | float b = clamp(y+2.017*u, 0.0, 1.0);
192 |
193 | gl_FragColor = vec4(r,g,b,1.0);
194 | }
195 | `
196 |
197 | let vert = loadShader(vertSrc, gl.VERTEX_SHADER)
198 | let frag = loadShader(fragSrc, gl.FRAGMENT_SHADER)
199 |
200 | let prog = this.program = gl.createProgram()
201 |
202 | gl.attachShader(prog, vert)
203 | gl.attachShader(prog, frag)
204 | gl.linkProgram(prog)
205 |
206 | let posAttribLoc = gl.getAttribLocation(prog, 'pos')
207 | let positions = gl.createBuffer()
208 |
209 | gl.bindBuffer(gl.ARRAY_BUFFER, positions);
210 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
211 | 1.0, 1.0,
212 | -1.0, 1.0,
213 | 1.0, -1.0,
214 | -1.0, -1.0,
215 | ]), gl.STATIC_DRAW)
216 |
217 | gl.vertexAttribPointer(posAttribLoc, 2, gl.FLOAT, false, 0, 0)
218 | gl.enableVertexAttribArray(posAttribLoc)
219 |
220 | let tex = this.texture = gl.createTexture()
221 | let texLoc = gl.getUniformLocation(prog, 'page')
222 |
223 | gl.bindTexture(gl.TEXTURE_2D, tex)
224 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
225 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
226 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
227 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
228 |
229 | gl.useProgram(prog)
230 | gl.uniform1i(texLoc, 0)
231 |
232 | // wip audio
233 | let actx = this.audioCtx = new AudioContext({ sampleRate: SAMPLE_RATE })
234 | await actx.audioWorklet.addModule('processor.js')
235 | this.audioGain = actx.createGain()
236 | this.audioProcessor = new AudioWorkletNode(actx, 'processor')
237 | this.audioProcessor.connect(this.audioGain)
238 | this.audioGain.connect(actx.destination)
239 |
240 | this.domElement.addEventListener('mousemove', e => {
241 | let x = e.pageX
242 | let y = e.pageY
243 | let elem = this.domElement
244 |
245 | do {
246 | x -= elem.offsetLeft
247 | y -= elem.offsetTop
248 | }
249 | while(elem = elem.offsetParent)
250 |
251 | this.x = x
252 | this.y = y
253 | })
254 | }
255 |
256 | run() {
257 | this.running = true
258 | this.initTime = this.time
259 | this.audioGain.gain.setValueAtTime(1, this.audioCtx.currentTime)
260 |
261 | this.start = performance.now()
262 | let last = this.start
263 |
264 | let step = () => {
265 | if (!this.running) return
266 |
267 | window.requestAnimationFrame(step)
268 |
269 | let now = performance.now()
270 | let elapsed = now - this.start
271 | let dt = (now - last) / 1000
272 |
273 | last = now
274 |
275 | this.fps = (1 / dt) | 0
276 | this.time = this.initTime + (elapsed / 1000) * 60 & 0xffff
277 | this.step()
278 | }
279 |
280 | window.requestAnimationFrame(step)
281 | }
282 |
283 | pause() {
284 | this.running = false
285 | this.audioGain.gain.setValueAtTime(0, this.audioCtx.currentTime)
286 | }
287 |
288 | toggle() {
289 | if (this.running) this.pause()
290 | else this.run()
291 | }
292 |
293 | step() {
294 | super.step(this.y << 8 | this.x)
295 | let video = this.videoPages[(this.vStack.top >> 16) ^ 1]
296 | let gl = this.gl
297 | gl.bindTexture(gl.TEXTURE_2D, this.texture)
298 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 256, 256, 0, gl.RGBA, gl.UNSIGNED_BYTE, video)
299 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)
300 |
301 | // HACKY
302 | if (this.time % 60 == 1) {
303 | this.audioProcessor.port.postMessage(this.aStack.memory)
304 | }
305 | }
306 |
307 | reset() {
308 | this.time = 0
309 | this.initTime = 0
310 | this.start = performance.now()
311 | super.reset()
312 | }
313 |
314 | install(program) {
315 | this.fragments = program.fragments
316 | this.memory.set(program.data.mem)
317 | }
318 |
319 | }
320 |
321 |
322 | class Program {
323 | constructor(source) {
324 | let { fragments, data } = compile(source)
325 | this.fragments = fragments
326 | this.data = data
327 | }
328 | }
329 |
330 | let sincr = 'sn=sn+1&sm;'
331 | let sdecr = 'sn=sn+sm&sm;'
332 |
333 | let rincr = 'rn=rn+1&rm;'
334 | let rdecr = 'rn=rn+rm&rm;'
335 |
336 | let whereami = {
337 | tyx: 'S[sn]=t;'
338 | + 'S[sn=sn+1&sm]=y;'
339 | + 'S[sn=sn+1&sm]=x;'
340 | + sincr,
341 | t: 'S[sn]=t;'
342 | + sincr,
343 | }
344 |
345 | let codes = {
346 |
347 | '+': sdecr
348 | + 'a=sn+sm&sm;'
349 | + 'S[a]=S[a]+S[sn];',
350 |
351 | '-': sdecr
352 | + 'a=sn+sm&sm;'
353 | + 'S[a]=S[a]-S[sn];',
354 |
355 | '*': sdecr
356 | + 'a=sn+sm&sm;'
357 | + 'S[a]=(S[a]/65536)*(S[sn]/65536)*65536|0;',
358 |
359 | '/': sdecr
360 | + 'a=sn+sm&sm;'
361 | + 'S[a]=S[sn]==0?0:(S[a]*65536)/S[sn];',
362 |
363 | '%': sdecr
364 | + 'a=sn+sm&sm;'
365 | + 'S[a]=S[sn]==0?0:S[a]%S[sn];',
366 |
367 | 'q': 'a=sn+sm&sm;'
368 | + 'S[a]=S[a]>0?Math.sqrt(S[a]/65536)*65536|0:0;',
369 |
370 | '&': sdecr
371 | + 'a=sn+sm&sm;'
372 | + 'S[a]=S[a]&S[sn];',
373 |
374 | '|': sdecr
375 | + 'a=sn+sm&sm;'
376 | + 'S[a]=S[a]|S[sn];',
377 |
378 | '^': sdecr
379 | + 'a=sn+sm&sm;'
380 | + 'S[a]=S[a]^S[sn];',
381 |
382 | 'r': sdecr
383 | + 'a=sn+sm&sm;'
384 | + 'b=S[sn]>>>16;'
385 | + 'c=S[a];'
386 | + 'S[a]=(c>>>b)|(c<<(32-b));',
387 |
388 | 'l': sdecr
389 | + 'a=sn+sm&sm;'
390 | + 'S[a]=S[a]<<(S[sn]>>>16);',
391 |
392 | '~': 'a=sn+sm&sm;'
393 | + 'S[a]=~S[a];',
394 |
395 | 's': 'a=sn+sm&sm;'
396 | + 'S[a]=Math.sin(S[a]*Math.PI/32768)*65536;',
397 |
398 | 'a': sdecr
399 | + 'a=sn+sm&sm;'
400 | + 'S[a]=Math.atan2(S[sn]/65536,S[a]/65536)/Math.PI*32768;',
401 |
402 | '<': 'a=sn+sm&sm;'
403 | + 'if(S[a]>0)S[a]=0;',
404 |
405 | '>': 'a=sn+sm&sm;'
406 | + 'if(S[a]<0)S[a]=0;',
407 |
408 | '=': 'a=sn+sm&sm;'
409 | + 'S[a]=(S[a]==0)<<16;',
410 |
411 | 'd': 'S[sn]=S[sn+sm&sm];'
412 | + sincr,
413 |
414 | 'p': sdecr,
415 |
416 | 'x': 'a=sn+sm&sm;'
417 | + 'b=a+sm&sm;'
418 | + 'c=S[a];S[a]=S[b];S[b]=c;',
419 |
420 | 'v': 'a=sn+sm&sm;'
421 | + 'b=a+sm&sm;'
422 | + 'c=b+sm&sm;'
423 | + 'd=S[a];S[a]=S[c];S[c]=S[b];S[b]=d;',
424 |
425 | ')': 'a=sn+sm&sm;'
426 | + 'S[a]=S[a+sm-(S[a]>>>16)&sm];',
427 |
428 | '(': sdecr
429 | + 'a=S[sn]>>16;'
430 | + sdecr
431 | + 'S[sn+sm-a&sm]=S[sn];',
432 |
433 | 'T': 'break;',
434 |
435 | '@': 'a=sn+sm&sm;'
436 | + 'b=S[a];'
437 | + 'S[a]=M[(b>>>16)|(b<<16)];',
438 |
439 | '!': 'b=S[sn=sn+sm&sm];'
440 | + 'a=S[sn=sn+sm&sm];'
441 | + 'M[(b>>>16)|(b<<16)]=a;',
442 |
443 | 'L': 'a=--R[rn+(rm<<1)&rm];'
444 | + 'if(a!=0){i=R[rn+rm&rm];continue}'
445 | + 'else ' + rdecr + rdecr,
446 |
447 | 'i': 'a=R[rn+(rm<<1)&rm];'
448 | + 'S[sn]=(a>>>16)|(a<<16);' + sincr,
449 |
450 | 'j': 'a=R[rn+(rm<<2)&rm];'
451 | + 'S[sn]=(a>>>16)|(a<<16);' + sincr,
452 |
453 | ']': sdecr
454 | + 'if(S[sn]!=0){i=R[rn+rm&rm];continue}'
455 | + 'else ' + rdecr,
456 |
457 | 'J': sdecr
458 | + 'i=S[sn];continue;',
459 |
460 | '}': rdecr
461 | + 'i=R[rn];continue;',
462 |
463 | 'R': rdecr
464 | + 'S[sn]=R[rn];'
465 | + sincr,
466 |
467 | 'P': sdecr
468 | + 'R[rn]=S[sn];'
469 | + rincr,
470 |
471 | 'U': 'S[sn]=U;' + sincr,
472 | }
473 |
474 | let hexchars = "0123456789ABCDEF".split('')
475 | let isHexaDecimal = c => hexchars.includes(c)
476 |
477 | let isBlank = c => c == ' ' || c == ',' || c == '\n'
478 |
479 | let dataModes = {
480 | b: { len: 1, mask: 1 },
481 | q: { len: 2, mask: 3 },
482 | o: { len: 3, mask: 7 },
483 | h: { len: 4, mask: 15 },
484 | }
485 |
486 | function parse(src) {
487 | let pos = 0
488 | let len = src.length
489 | let body = []
490 | let video = null
491 | let audio = null
492 | let c
493 |
494 | let data = []
495 | let currentValues = []
496 | let currentMode = dataModes.h
497 |
498 | while(pos < len) {
499 | c = src[pos++]
500 |
501 | if (isBlank(c))
502 | continue
503 |
504 | if (c == '\\') {
505 | while (pos < len && src[pos++] != '\n') {}
506 | continue
507 | }
508 |
509 | else if (isHexaDecimal(c) || c == '.') {
510 | let imm = 0
511 |
512 | if (c != '.') {
513 | imm = parseInt(c, 16)
514 |
515 | while (pos < len && isHexaDecimal(src[pos]))
516 | imm = (imm & 0xffff) << 4 | parseInt(src[pos++], 16)
517 |
518 | imm <<= 16
519 | c = src[pos]
520 |
521 | if (c == '.') pos++
522 | }
523 |
524 | if (c == '.') {
525 | let i = 4, frac = 0
526 |
527 | for (;i > 0 && isHexaDecimal(src[pos]); i--)
528 | frac = frac << 4 | parseInt(src[pos++], 16)
529 |
530 | while (pos < len && isHexaDecimal(src[pos]))
531 | pos++
532 |
533 | imm |= frac << (i << 2)
534 | }
535 |
536 | body.push(imm)
537 | }
538 |
539 | // parsing data segment
540 | else if (c == '$') {
541 | while (pos < len) {
542 | c = src[pos++]
543 | if (dataModes[c]) {
544 | if (currentValues.length) {
545 | data.push({ mode: currentMode, values: currentValues })
546 | currentValues = []
547 | }
548 | currentMode = dataModes[c]
549 | }
550 | else if (isHexaDecimal(c)) {
551 | currentValues.push(parseInt(c, 16) & currentMode.mask)
552 | }
553 | }
554 | break
555 | }
556 |
557 | else if (c == 'M') {
558 | video = body
559 | body = []
560 | }
561 |
562 | else body.push({ op: c, pos: pos - 1 })
563 | }
564 |
565 | if (video) {
566 | audio = body
567 | findSkips(video)
568 | findSkips(audio)
569 | }
570 | else {
571 | video = audio = body
572 | findSkips(body)
573 | }
574 |
575 | data.push({ mode: currentMode, values: currentValues })
576 | return { video, audio, data }
577 | }
578 |
579 | function findSkips(tokens) {
580 | let len = tokens.length
581 |
582 | // find skip points
583 | for (let i = 0; i < len; i++) {
584 | let t = tokens[i]
585 |
586 | if (typeof t == 'number')
587 | continue
588 |
589 | let c = t.op
590 | let seek0 = null
591 | let seek1 = null
592 |
593 | if (c == '?') {
594 | seek0 = ':'
595 | seek1 = ';'
596 | }
597 |
598 | if (c == ':') { seek0 = ';' }
599 | if (c == '{') { seek0 = '}' }
600 |
601 | if (seek0) {
602 | for (let j = i + 1; j < len; j++) {
603 | let c = tokens[j]
604 | if (typeof c != 'number' && (c.op == seek0 || c.op == seek1)) {
605 | t.skip = j + 1
606 | break
607 | }
608 | }
609 | }
610 | }
611 | }
612 |
613 | function compile(src) {
614 | let { video, audio, data } = parse(src)
615 |
616 | let nbits = data.reduce((n , {values, mode}) => n + values.length * mode.len, 0)
617 | let nbytes = (nbits + 7) >> 3
618 |
619 | // hack because of endianness
620 | let mem = new Uint32Array(new ArrayBuffer(nbytes))
621 | let cb = 0, cv = 0, cl = 0
622 |
623 | data.forEach(({values, mode}) => {
624 | values.forEach(v => {
625 | cv = cv << mode.len | v
626 | cl += mode.len
627 | if (cl > 32) {
628 | mem[cb++] = cv >> (cl -= 32)
629 | }
630 | })
631 | })
632 |
633 | mem[cb] = cv << (32 - cl)
634 |
635 | let videoTYX = codegen(video, whereami.tyx)
636 | let videoT = codegen(video, whereami.t )
637 | let audioT = codegen(audio, whereami.t )
638 |
639 | return {
640 | fragments: {
641 | video: {
642 | T: new Function('M', 'VS', 'RS', 't', 'U', videoT),
643 | TYX: new Function('M', 'VS', 'RS', 't', 'x', 'y', 'U', videoTYX),
644 | },
645 | audio: new Function('M', 'VS', 'RS', 't', 'U', audioT),
646 | },
647 | data: { mem, nbits },
648 | }
649 | }
650 |
651 | function codegen(tokens, whereami) {
652 | let body = `
653 | let l=true,
654 | i=0,
655 | S=VS.memory,
656 | sm=VS.mask,
657 | sn=VS.top,
658 | R=RS.memory,
659 | rm=RS.mask,
660 | rn=RS.top
661 |
662 | while(l) {
663 | switch(i) {`
664 |
665 | let len = tokens.length
666 |
667 | let instructions = tokens.map((tok, k) => {
668 | let sub = `\ncase ${k}:`
669 |
670 | if (typeof tok == 'number')
671 | sub += 'S[sn]=' + tok + ';' + sincr
672 |
673 | else if (codes[tok.op]) sub += codes[tok.op]
674 | else if (tok.op == 'w') sub += whereami
675 | else if (tok.op == '?') {
676 | if (!tok.skip)
677 | sub += sdecr
678 | + 'if(S[sn]==0)break;'
679 | else
680 | sub += sdecr
681 | + `if(S[sn]==0){i=${tok.skip};continue};`
682 | }
683 |
684 | else if (tok.op == ':') {
685 | if (!tok.skip) sub = 'break;' + sub
686 | else sub = `i=${tok.skip};continue;` + sub
687 | }
688 |
689 | else if (tok.op == '{') {
690 | if (!tok.skip)
691 | sub += sdecr
692 | + 'M[S[sn]>>16]=' + (k + 1) + ';break;'
693 | else
694 | sub += sdecr
695 | + 'M[S[sn]>>16]=' + (k + 1) + ';'
696 | + `i=${tok.skip};continue;`
697 | }
698 |
699 | else if (tok.op == 'V') {
700 | sub += sdecr
701 | + 'i=M[S[sn]>>16];'
702 | + `R[rn]=${k + 1};`
703 | + rincr
704 | + 'continue;'
705 | }
706 |
707 | else if (tok.op == 'X') {
708 | sub += sdecr
709 | + 'a=S[sn];'
710 | + 'R[rn]=(a>>>16)|(a<<16);'
711 | + rincr
712 | + 'R[rn]=' + (k + 1) + ';'
713 | + rincr
714 | }
715 |
716 | else if (tok.op == '[') {
717 | sub += 'R[rn]=' + (k + 1) + ';'
718 | + rincr
719 | }
720 |
721 | else return ''
722 |
723 | return sub
724 | })
725 |
726 | body += instructions.join('')
727 |
728 | body += `
729 | }
730 | break
731 | }
732 | VS.top=sn
733 | RS.top=rn`
734 |
735 | return body
736 | }
737 |
738 | return { Stack, VM, Program, Console }
739 |
740 | }))
741 |
--------------------------------------------------------------------------------