Compare commits
4 Commits
991136e703
...
316e839804
Author | SHA1 | Date |
---|---|---|
Juhani Krekelä | 316e839804 | |
Juhani Krekelä | d74b6ab594 | |
Juhani Krekelä | 1b262941ca | |
Juhani Krekelä | 00eb264cb4 |
|
@ -21,16 +21,12 @@ Gir supports following optimizations:
|
|||
TODO
|
||||
----
|
||||
### gir.js
|
||||
* Make VM and transformMultiplyLoops use a Proxied object that gives out 0
|
||||
for nonexistent elements for tape and allows using [] interface
|
||||
* Support for other types of EOF?
|
||||
* Move `ircbotRun()` into its own file
|
||||
|
||||
### gir.html
|
||||
* Implement a UI
|
||||
|
||||
### Documentation
|
||||
* Document the VM
|
||||
* Document the user-facing API
|
||||
* Document the overall architecture
|
||||
|
||||
### General
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
Compiling and inspecting programs
|
||||
---------------------------------
|
||||
|
||||
### compile
|
||||
`compile(program, enableExtensions = true)`
|
||||
|
||||
* `program`: String containing the program to compile
|
||||
* `enableExtensions`: Whether to recognize `:;#` or ignore them
|
||||
|
||||
*Returns*: An array holding flat IR
|
||||
|
||||
*Example*: `let compiled = compile(',[>+<,]>:');`
|
||||
|
||||
### prettifyIR
|
||||
`prettifyIR(ir)`
|
||||
|
||||
* `ir`: An array holding any type of IR used by Gir
|
||||
|
||||
*Returns*: A string suitable for creating IR listings
|
||||
|
||||
*Example*: `console.log(prettifyIR(compiled));`
|
||||
|
||||
|
||||
Executing programs
|
||||
------------------
|
||||
|
||||
Gir executes the flattened IR in a simple virtual machine. `runVM()` doesn't
|
||||
mutate a state object that is passed to it but rather returns a new one.
|
||||
|
||||
### VM state object
|
||||
|
||||
property | default value | description
|
||||
-----------|-----------------------|------------
|
||||
`program` | (set by caller) | `compile()`d program
|
||||
`ip` | `0` | Instruction pointer
|
||||
`memory` | `Map {}` | Tape
|
||||
`tapeHead` | `0` | Tape head position
|
||||
`input` | (set by caller) | `encodeUTF8()`d input
|
||||
`output` | `[]` | Output (needs to be `decodeUTF8()`d)
|
||||
`onEof` | `0` / (set by caller) | What to do in case of EOF¹
|
||||
|
||||
¹ If `onEof` is null the cell will not be changed, but otherwise the cell
|
||||
will be set to the value of `onEof`
|
||||
|
||||
### newVM
|
||||
|
||||
`newVM(program, input, onEof = 0)`
|
||||
|
||||
* `program`: An array holding flat IR
|
||||
* `input`: An array of integers which is interpreted as a byte stream
|
||||
* `onEof`: See description of `onEof` field of the VM state object
|
||||
|
||||
*Returns*: VM state object
|
||||
|
||||
*Example*: `let vm = newVM(compile('.[,[-].]'), encodeUTF('foobar'), null);`
|
||||
|
||||
### runVM
|
||||
|
||||
`runVM(vm, maxCycles = null)`
|
||||
|
||||
* `vm`: VM state object
|
||||
* `maxCycles`: Number of cycles the program is allowed to run. `null` means
|
||||
no limit
|
||||
|
||||
*Returns*: An object like following
|
||||
|
||||
property | description
|
||||
------------------|------------
|
||||
state | VM state object of the state after `runVM()` has completed
|
||||
complete | Boolean describing whether the program completed or not
|
||||
cycles | How many cycles did the program run during this invocation of `runVM()`
|
||||
intParseFailed | Boolean describing if `runVM()` exited because `;` failed to parse an int
|
||||
breakPointReached | Boolean describing if `runVM()` exited because it hit `#`
|
||||
lastIndex | Last cell that was accessed by the VM
|
||||
|
||||
*Example*: `let output = decodeUTF8(runVM(vm, 400000).state.output);`
|
||||
|
||||
|
||||
Helper functions
|
||||
----------------
|
||||
|
||||
### encodeUTF8
|
||||
`encodeUTF8(string)`
|
||||
|
||||
* *string*: A javascript string
|
||||
|
||||
*Returns*: An array of integers that interpreted as bytes encode that string
|
||||
|
||||
*Example*: `let encoded = encodeUTF8(input);`
|
||||
|
||||
### decodeUTF8
|
||||
`decodeUTF8(encoded)`
|
||||
|
||||
* *encoded*: An array of integers
|
||||
|
||||
*Returns*: A string encoded by that array
|
||||
|
||||
*Example*: `let output = decodeUTF8(result.state.output);`
|
|
@ -11,10 +11,13 @@ cells that wrap around
|
|||
|
||||
IO
|
||||
--
|
||||
`.` and `,` operate on a utf-8 stream. `,` produces `0` on EOF
|
||||
`.` and `,` operate on a utf-8 stream. `,` produces `0` by default on EOF.
|
||||
This can be changed by passing a third parameter to newVM, where 0/-1 sets
|
||||
the cell to that and null keeps it unchanged
|
||||
|
||||
`:` produces a decimal representation of the current cell. `;` skips any
|
||||
space (U+20) characters in the input stream and then reads 1 or more ASCII
|
||||
digit (U+30 to U+39), clamps the number to the range [0, 255] and sets the
|
||||
cell to it. If it can't read a digit and EOF has been reached it returns 0,
|
||||
but if EOF hasn't been reached it raises an error flag and stops execution
|
||||
cell to it. If it can't read a digit and EOF has been reached it produces an
|
||||
EOF similarily to `,` (by default sets to 0, but can be changed), but if EOF
|
||||
hasn't been reached it raises an error flag and stops execution
|
||||
|
|
97
gir.js
97
gir.js
|
@ -187,14 +187,14 @@ function parse(program, enableExtensions = true) {
|
|||
return parsed;
|
||||
}
|
||||
|
||||
// ([commandObjects/offsetCommandObjects]) → str
|
||||
function prettifyIR(parsed) {
|
||||
// ([commandObjects/offsetCommandObjects], string) → str
|
||||
function worker(parsed, indent = '') {
|
||||
// ([commandObjects/offsetCommandObjects/flatCommandObjects]) → str
|
||||
function prettifyIR(ir) {
|
||||
// ([commandObjects/offsetCommandObjectsflatCommandObjects], string) → str
|
||||
function worker(ir, indent = '') {
|
||||
let lines = [];
|
||||
|
||||
for(let i = 0; i < parsed.length; i++) {
|
||||
let command = parsed[i];
|
||||
for(let i = 0; i < ir.length; i++) {
|
||||
let command = ir[i];
|
||||
|
||||
let line = `${indent}${i} `;
|
||||
if(command.type == add) {
|
||||
|
@ -267,7 +267,7 @@ function prettifyIR(parsed) {
|
|||
|
||||
return lines
|
||||
}
|
||||
return worker(parsed).join('\n');
|
||||
return worker(ir).join('\n');
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
|
@ -573,8 +573,10 @@ function optimize(parsed) {
|
|||
// Virtual machine
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
// ([flatCommandObject], [int]) → girVMState
|
||||
function newVM(program, input) {
|
||||
// ([flatCommandObject], [int], int/null) → girVMState
|
||||
// onEof tells what to set the cell to in case of EOF, or if it null to keep
|
||||
// the same value
|
||||
function newVM(program, input, onEof = 0) {
|
||||
return {
|
||||
// Initial state for the machine
|
||||
program: program,
|
||||
|
@ -584,11 +586,14 @@ function newVM(program, input) {
|
|||
tapeHead: 0,
|
||||
|
||||
input: input,
|
||||
output: []
|
||||
output: [],
|
||||
|
||||
// Configuration
|
||||
onEof: onEof
|
||||
};
|
||||
}
|
||||
|
||||
// (girVMState, int) → {state: girVMState, complete: bool, cycles: int,
|
||||
// (girVMState, int/null) → {state: girVMState, complete: bool, cycles: int,
|
||||
// intParseFailed: bool, breakPointReached: bool,
|
||||
// lastIndex: int/null}
|
||||
// complete is set to true is the program completed its execution
|
||||
|
@ -598,6 +603,16 @@ function newVM(program, input) {
|
|||
// lastIndex tells the last memory index accessed by the VM
|
||||
// If maxCycles is null, the program runs until completion
|
||||
function runVM(state, maxCycles = null) {
|
||||
// (int) → int
|
||||
function readMemory(index) {
|
||||
// Return 0 if index doesn't exist
|
||||
if(memory.has(index)) {
|
||||
return memory.get(index);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
let program = state.program;
|
||||
let ip = state.ip;
|
||||
|
||||
|
@ -630,8 +645,7 @@ function runVM(state, maxCycles = null) {
|
|||
|
||||
let command = program[ip];
|
||||
|
||||
// See if we need to make sure the cell we're on exists and
|
||||
// calculate the index into the array of the cell we're
|
||||
// Calculate the index into the array of the cell we're
|
||||
// accessing
|
||||
let index = tapeHead;
|
||||
switch(command.type) {
|
||||
|
@ -643,21 +657,13 @@ function runVM(state, maxCycles = null) {
|
|||
case readInt:
|
||||
// These have an offset property, add it
|
||||
index += command.offset;
|
||||
// Fall through
|
||||
case multiply:
|
||||
case jumpIfZero:
|
||||
case jumpIfNonZero:
|
||||
// Ensure the cell exists
|
||||
if(!memory.has(index)) {
|
||||
memory.set(index, 0);
|
||||
}
|
||||
lastIndex = index;
|
||||
}
|
||||
lastIndex = index;
|
||||
|
||||
// Run the command
|
||||
switch(command.type) {
|
||||
case add:
|
||||
let old = memory.get(index);
|
||||
let old = readMemory(index);
|
||||
memory.set(index, (command.value + old) & 0xFF);
|
||||
|
||||
ip++;
|
||||
|
@ -665,20 +671,33 @@ function runVM(state, maxCycles = null) {
|
|||
|
||||
case moveHead:
|
||||
tapeHead += command.value;
|
||||
// Set lastIndex to the new tape head
|
||||
// position. Technically we do not access
|
||||
// the cell, but otherwise it will point
|
||||
// to the cell we were in previously, so
|
||||
// this allows better debugging
|
||||
lastIndex = tapeHead;
|
||||
|
||||
ip++;
|
||||
break;
|
||||
|
||||
case writeByte:
|
||||
output.push(memory.get(index));
|
||||
output.push(readMemory(index));
|
||||
ip++;
|
||||
break;
|
||||
|
||||
case readByte:
|
||||
// Have we reached EOF?
|
||||
if(input.length == 0) {
|
||||
// Yes, return 0
|
||||
memory.set(index, 0);
|
||||
// Yes
|
||||
// If state.onEof is null, don't
|
||||
// change the value
|
||||
// If it's something else, set the
|
||||
// cell to that
|
||||
if(state.onEof !== null) {
|
||||
memory.set(index,
|
||||
state.onEof & 0xff);
|
||||
}
|
||||
} else {
|
||||
// No, return character
|
||||
memory.set(index, input.shift());
|
||||
|
@ -697,14 +716,11 @@ function runVM(state, maxCycles = null) {
|
|||
`multiply where change for 0 is ${command.changes.get(0)}`);
|
||||
}
|
||||
|
||||
let multiplier = memory.get(index);
|
||||
let multiplier = readMemory(index);
|
||||
|
||||
for(let [offset, change] of command.changes.entries()) {
|
||||
let index = tapeHead + offset;
|
||||
if(!memory.has(index)) {
|
||||
memory.set(index, 0);
|
||||
}
|
||||
let old = memory.get(index);
|
||||
let old = readMemory(index);
|
||||
let value = old + multiplier * change;
|
||||
memory.set(index, value & 0xff);
|
||||
}
|
||||
|
@ -713,7 +729,7 @@ function runVM(state, maxCycles = null) {
|
|||
break;
|
||||
|
||||
case jumpIfZero:
|
||||
if(memory.get(index) == 0) {
|
||||
if(readMemory(index) == 0) {
|
||||
ip = command.target;
|
||||
} else {
|
||||
ip++;
|
||||
|
@ -721,7 +737,7 @@ function runVM(state, maxCycles = null) {
|
|||
break;
|
||||
|
||||
case jumpIfNonZero:
|
||||
if(memory.get(index) != 0) {
|
||||
if(readMemory(index) != 0) {
|
||||
ip = command.target;
|
||||
} else {
|
||||
ip++;
|
||||
|
@ -729,7 +745,7 @@ function runVM(state, maxCycles = null) {
|
|||
break;
|
||||
|
||||
case writeInt:
|
||||
let outputStr = memory.get(index).toString();
|
||||
let outputStr = readMemory(index).toString();
|
||||
output = output.concat(encodeUTF8(outputStr));
|
||||
ip++;
|
||||
break;
|
||||
|
@ -761,9 +777,15 @@ function runVM(state, maxCycles = null) {
|
|||
0);
|
||||
memory.set(index, number);
|
||||
} else if(input.length == 0) {
|
||||
// No, but there was an EOF, so set
|
||||
// the cell to 0
|
||||
memory.set(index, 0);
|
||||
// No, but there was an EOF
|
||||
// If state.onEof is null, don't
|
||||
// change the value
|
||||
// If it's something else, set the
|
||||
// cell to that
|
||||
if(state.onEof !== null) {
|
||||
memory.set(index,
|
||||
state.onEof & 0xff);
|
||||
}
|
||||
} else {
|
||||
// No, and there wasn't an EOF, so
|
||||
// signal an error
|
||||
|
@ -984,7 +1006,6 @@ function cachedCompile(program, enableExtensions = true) {
|
|||
|
||||
// (string, string, int) → string
|
||||
function ircbotRun(program, input, maxCycles = 400000) {
|
||||
// TODO; Cache programs
|
||||
let compiled = cachedCompile(program);
|
||||
let vm = newVM(compiled, encodeUTF8(input));
|
||||
|
||||
|
|
38
ir.md
38
ir.md
|
@ -52,6 +52,27 @@ type | `clear`
|
|||
|
||||
Not generated by the parser directly, but generated by optimizations
|
||||
|
||||
### writeInt
|
||||
property | value
|
||||
---------|------
|
||||
type | `writeInt`
|
||||
|
||||
Generated on `:` if extensions are enabled
|
||||
|
||||
### readInt
|
||||
property | value
|
||||
---------|------
|
||||
type | `readInt`
|
||||
|
||||
Generated on `:` if extensions are enabled
|
||||
|
||||
### breakPoint
|
||||
property | value
|
||||
---------|------
|
||||
type | `breakPoint`
|
||||
|
||||
Generated on `#` if extensions are enabled
|
||||
|
||||
|
||||
With offsets
|
||||
------------
|
||||
|
@ -101,6 +122,23 @@ property | value
|
|||
type | `multiply`
|
||||
changes | Map of offsets to the number that should be added to those cells multiplies by the current cell
|
||||
|
||||
### writeInt
|
||||
property | value
|
||||
---------|------
|
||||
type | `writeInt`
|
||||
offset | The location of the cell relative to current tape position
|
||||
|
||||
### readInt
|
||||
property | value
|
||||
---------|------
|
||||
type | `readInt`
|
||||
offset | The location of the cell relative to current tape position
|
||||
|
||||
### breakPoint
|
||||
property | value
|
||||
---------|------
|
||||
type | `breakPoint`
|
||||
|
||||
|
||||
Flattened
|
||||
---------
|
||||
|
|
Loading…
Reference in New Issue