Compare commits

...

4 Commits

Author SHA1 Message Date
Juhani Krekelä 316e839804 Document the API 2018-05-27 13:39:43 +03:00
Juhani Krekelä d74b6ab594 Support all the types of EOF 2018-05-27 12:56:02 +03:00
Juhani Krekelä 1b262941ca Implement a readMemory() in runVM() that returns 0 on reads to nonexistent memory 2018-05-27 12:46:47 +03:00
Juhani Krekelä 00eb264cb4 Document writeInt, readInt, breakPoint 2018-05-27 12:32:18 +03:00
5 changed files with 202 additions and 46 deletions

View File

@ -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

98
api.md Normal file
View File

@ -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);`

View File

@ -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
View File

@ -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
View File

@ -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
---------