strokepm
Version:
Stroke+- is an esoteric structured programming language
143 lines (124 loc) • 4.08 kB
JavaScript
const MAX_STEPS = 1000000
const interpret = (program, memory, maxSteps, onOutput) => {
// initialize
const p = parse(program)
const m = memory ? memory : []
const ms = maxSteps > 0 ? maxSteps : MAX_STEPS
// execute
let pc = 0 // program counter
let sc = 0 // step counter
while (pc < p.length && sc <= ms) {
const instr = p[pc]
switch (instr.id) {
case '+': {
m[instr.variable] = m[instr.variable] ? m[instr.variable] - 0 + 1 : 1
pc++
break
}
case '-': {
m[instr.variable] = m[instr.variable] ? m[instr.variable] - 0 - 1 : 0
pc++
break
}
case '(': {
if (!m[instr.variable]) pc = findNextLoopEnd(p, pc) + 1
else pc++
break
}
case ')': {
pc = findPreviousLoopStart(p, pc)
break
}
case '!': {
if (onOutput instanceof Function) onOutput(normalizeMem(m))
pc++
break
}
}
sc++
}
if (sc > MAX_STEPS) throw new Error('Maximal steps exceeded')
return normalizeMem(m)
function findPreviousLoopStart(p, pc) {
let pairs = 0
while (pc > 0) {
pc--
if (p[pc].id === ')') pairs++
if (p[pc].id === '(') {
if (!pairs) return pc
pairs--
}
}
throw new Error('Loop start not found')
}
function findNextLoopEnd(p, pc) {
let pairs = 0
while (pc < p.length - 1) {
pc++
if (p[pc].id === '(') pairs++
if (p[pc].id === ')') {
if (!pairs) return pc
pairs--
}
}
throw new Error('Loop end not found')
}
function normalizeMem(m) {
let r = []
const l = findMaxIdx(m)
for (let i = 0; i <= l; i++) {
r[i] = m[i] ? m[i] - 0 : 0
}
return r
function findMaxIdx(m) {
let i = m.length
while (i >= 0 && !m[i]) i--
return i
}
}
}
// parse the program to AST
function parse(program) {
if (-1 !== program.search(/[|]\s+[|]/g)) throw new Error('Syntax error: consequent vars')
const source = program
.replaceAll(/\//g, '(')
.replaceAll(/\\/g, ')')
.replaceAll(/[^)(+\-|!]/g, '') // remove all ignored chars
const ast = []
let open = 0 // count of open loops
let i = 0
while (i < source.length) {
const instr = source[i++]
let attr = ''
while (i < source.length && /[^)(!+\-]/.test(source[i]))
attr += source[i++]
if (/[+]+/.test(instr)) { // increment variable
if (!attr.length || !/[|]+/.test(attr)) throw new Error('Syntax error: missing inc var at ' + i)
} else
if (/[-]+/.test(instr)) { // decrement variable
if (!attr.length || !/[|]+/.test(attr)) throw new Error('Syntax error: missing dec var at ' + i)
} else
if (/[(]/.test(instr)) { // loop start
open++
if (!attr.length || !/[|]+/.test(attr)) throw new Error('Syntax error: missing loop var at ' + i)
} else
if (/[)]/.test(instr)) { // loop end
open--
if (attr.length) throw new Error('Syntax error: not expected var at ' + i)
} else
if (/!+/.test(instr)) { // output
} else
throw new Error('Syntax error: invalid instruction "' + instr + '" at ' + i)
ast.push(new Instr(instr, attr.length - 1))
if (open < 0) throw new Error('Syntax error: missing loop start at ' + i)
}
if (open) throw new Error('Syntax error: missing loop end(s)')
return ast
}
class Instr {
constructor(id, variable) {
this.id = id
this.variable = variable
}
}
module.exports = interpret