Twocode/Doublecode

From Esolang
Jump to navigation Jump to search
Back to twocode

Doublecode is a twocode interpreter by User:Fergusq in Sve.

Usage

Interpreter reads lines from the standard input until a line with text "EOF" is encountered.

Source code

# Doublecode (twocode + onecode) interpreter

## Utility functions
def: foreach (t, u)
	if: !u return def (u)
		foreach(t, u)
	
	else: {	local l = len::t
		for:	local i=0,
			i < l,
			i++
		
			u(t[i]) }
def: range(x,y) {
	local t = table()
	
	for: local i = 0, x+i <= y, i++
		t[i] = (x + i)
	
	return t
}

## Main namespace
dcode = {}

# debug level, false=no debug, 1=onecode debug, 2=twocode debug
dcode.debug = false

## Onecode

def: dcode.onecode(code, runtime) {
	
	# Lexer
	local r = code->split("(?<![a-zA-Z0-9])|(?![a-zA-Z0-9])")
	local l = []
	local i = 0
	
	local s = nil
	
	foreach(r)::def (t) {
		if: t=="";
		else: if: t=="\"" && !s {
			l[i++] = "\""
			s=""
		}
		
		# ollaanko merkkijonon sisällä
		else: if: !s {
			if: t==""||t==" "||t=="\n"||t=="\r"||t=="\t";
			else: l[i++] = t
		} else: {
			if: s->strlen > 0 && s->charat(s->strlen-1)->chr == "\\" {
				local c = t->charat(0)->chr
				if: c == "n" c="\n"
				if: c == "r" c="\r"
				if: c == "t" c="\t"
				s = s->substr(0,s->strlen-1) + c
				if: t->strlen > 1 s = s + t->substr(1)
			}
			else: if: t == "\"" { l[i++] = s; s = nil }
			else: s=s+t
		}
	}
	
	#local ts=""foreach(r)::def(c) ts=ts+","+c print::ts
	
	# Recursive descent parser
	i = 0
	
	def: seek(n) {
		if: !n n = 0
		if: !defined(l,i+n) return "<EOF>"
		return l[i+n]
	}
	def: next {
		if: !defined(l,i) return "<EOF>"
		return l[i++]
	}
	def: accept(symbol) {
		local n = next()
		if: n != symbol raise("Syntax error: Unexpexted " + n + ", expecting " + symbol)
	}
	
	# stmt := <expr> {";" <expr>}
	def: stmt {
		local a = expr()
		while: seek() == ";" {
			accept(";")
			a = expr()
		}
		return a
	}
	
	# logic := <cond> {("|"|"&"|"^") <cond>}
	def: expr {
		local a = cond()
		while: true {
			local n = seek()
			if: n == "|"
				{accept("|") if: a!=0||cond()!=0 a=1
				else: a=0}
			else: if: n == "&"
				{accept("&") if: a!=0&&cond()!=0 a=1
				else: a=0}
			else: if: n == "^"
				{accept("^") if: a>cond() a=1
				else: a=0}
			else: return a
		}
	}
	
	# cond := <add> {("="|"<"|">") <add>}
	def: cond {
		local a = add()
		while: true {
			local n = seek()
			if: n == "="
				{accept("=")if: a==add() a=1
				else: a=0}
			else: if: n == "<"
				{accept("<")if: a<add() a=1
				else: a=0}
			else: if: n == ">"
				{accept(">")if: a>add() a=1
				else: a=0}
			else: return a
		}
	}
	
	# add := <mul> {("+"|"-") <mul>}
	def: add {
		local a = mul()
		while: true {
			local n = seek()
			if: n == "+"
				{accept("+")a=a+mul()}
			else: if: n == "-"
				{accept("-")a=a-mul()}
			else: return a
		}  
	}
	
	# mul := <prim> {("*"|"/") <prim>}
	def: mul {
		local a = prim()
		while: true {
			local n = seek()
			#print::next
			if: n == "*"
				{accept("*")a=a*prim()}
			else: if: n == "/"
				{acccept("/")a=a/prim()}
			else: return a
		}  
	}
	
	# prim := "*" <prim_t> ["~" <expr>] | <var> ["~" <expr>] | <prim_t>
	def: prim {
		if: seek() == "*" {
			accept("*")
			local addr = prim_t()
			if: seek() == "~" {
				accept("~")
				if: seek() == "[" { # list
					local p = addr
					accept("[")
					do: {
						runtime.memory[p++] = expr()
						if: seek()=="]" break
						else: accept(",")
					} while: true
					accept("]")
					return runtime.memory[addr]
				}
				if: seek() == "\"" { # string
					local p = addr
					accept("\"")
					local s = next()
					local l = s->strlen
					while: p-addr != l {
						runtime.memory[p] = s->charat(p-addr)
						p++
					}
					runtime.memory[p] = 0 # null terminator
					return runtime.memory[addr]
				}
				runtime.memory[addr] = expr()
				return runtime.memory[addr]
			}
			if: defined(runtime.memory, addr) return runtime.memory[addr]
			return 0
		}
		
		if: seek(1) == "~" {
			local var = next()
			accept("~")
			runtime.vars[var] = expr()
			return runtime.vars[var]
		}
		return prim_t()
	}
	
	# prim_t := "(" <expr> ")" | "*" <prim_t> | "!" <prim> | <function> "(" [<expr> {"," <expr>}] ")" | <value>
	def: prim_t {
		local n = seek()
		if: n == "(" {
			accept("(")
			local tmp = expr()
			accept(")")
			return tmp
		}
		if: n == "*" {
			accept("*")
			local addr = prim_t()
			if: defined(runtime.memory, addr) return runtime.memory[addr]
			return 0
		}
		if: n == "!" {
			accept("!")
			local num = prim()
			if: num==0 return 1
			else: return 0
		}
		
		# functions
		
		if: defined(functions, n) {
			accept(n)
			return functions[n](runtime)
		}
		
		return value()
	}
	
	# value := <number> | <var> | "'" <chr> "'"
	def: value {
		local n = seek()
		
		if: n == "'" {
			accept("'")
			local ch = next()
			accept("'")
			return ch->charat(0)
		}
		
		if: !defined(runtime.vars, n) {
			local num = nil
			pcall(def num = number(next()))
			if: num return num
			else: raise("Unknown variable " + n)
		}
		return runtime.vars[next()]
	}
	
	local functions = {
		getc = def {
			accept("(")
			accept(")")
			# impossible to implement in sve :(
			return 0
		},
		putc = def {
			accept("(")
			local ch = expr()
			accept(")")
			runtime.output = runtime.output + chr(ch)
			return 0
		},
		getn = def {
			accept("(")
			accept(")")
			# impossible to implement in sve :(
			return 0
		},
		putn = def {
			accept("(")
			local ch = expr()
			accept(")")
			runtime.output = runtime.output + ch
			return 0
		}
	}
	
	runtime.a = stmt()
	
	if: dcode.debug print::runtime.x+","+runtime.y+" "+code+" --> "+runtime.a
	
	return runtime.a
}

#print::dcode.onecode("s~0;*s~\"h\\ne\\n\\l\\n\\n\";puts(s);*(s+6)", {vars={},memory=[],a=0,x=0,y=0})

## Twocode commands

dcode.commands = {}

# Direction changing commands

dcode.commands./ = def(runtime) {
	if: runtime.direction == :SOUTH runtime.direction = :WEST
	else: if: runtime.direction == :WEST runtime.direction = :SOUTH
	else: if: runtime.direction == :NORTH runtime.direction = :EAST
	else: if: runtime.direction == :EAST runtime.direction = :NORTH
}
dcode.commands.\ = def(runtime) {
	if: runtime.direction == :SOUTH runtime.direction = :EAST
	else: if: runtime.direction == :EAST runtime.direction = :SOUTH
	else: if: runtime.direction == :NORTH runtime.direction = :WEST
	else: if: runtime.direction == :WEST runtime.direction = :NORTH
}
dcode.commands.L = def(runtime) { # turn left
	if: runtime.direction == :NORTH runtime.direction = :EAST
	else: if: runtime.direction == :EAST runtime.direction = :SOUTH
	else: if: runtime.direction == :SOUTH runtime.direction = :WEST
	else: if: runtime.direction == :WEST runtime.direction = :NORTH
}
dcode.commands.R = def(runtime) { # turn right
	if: runtime.direction == :SOUTH runtime.direction = :WEST
	else: if: runtime.direction == :WEST runtime.direction = :NORTH
	else: if: runtime.direction == :NORTH runtime.direction = :EAST
	else: if: runtime.direction == :EAST runtime.direction = :SOUTH
}
dcode.commands.v = def(runtime) {
	runtime.direction = :SOUTH
}
dcode.commands.^ = def(runtime) {
	runtime.direction = :NORTH
}
dcode.commands.< = def(runtime) {
	runtime.direction = :WEST
}
dcode.commands.> = def(runtime) {
	runtime.direction = :EAST
}

dcode.commands.Z = def(runtime) {
	if: runtime.a != 0 {
		if: runtime.direction == :SOUTH runtime.direction = :WEST
		else: if: runtime.direction == :WEST runtime.direction = :SOUTH
		else: if: runtime.direction == :NORTH runtime.direction = :EAST
		else: if: runtime.direction == :EAST runtime.direction = :NORTH
	}
}

dcode.commands.S = def(runtime) {
	if: runtime.a != 0 {
		if: runtime.direction == :SOUTH runtime.direction = :EAST
		else: if: runtime.direction == :EAST runtime.direction = :SOUTH
		else: if: runtime.direction == :NORTH runtime.direction = :WEST
		else: if: runtime.direction == :WEST runtime.direction = :NORTH
	}
}

# Onecode containers

dcode.commands.{ = def(runtime) {
	runtime.direction = :EAST
	
	runtime.x = runtime.x + 1
	
	local startx = runtime.x
	
	local code = ""
	local s = false
	while: runtime.program[runtime.y][runtime.x] != :} {
		if: runtime.program[runtime.y][runtime.x] == "\"" {
			if: s {
				if: runtime.program[runtime.y][runtime.x-1] != "\\" s = false
			} else: s = true
		}
		code = code + runtime.program[runtime.y][runtime.x]
		if: !runtime.program[runtime.y]->defined(runtime.x+1) || (!s&&runtime.program[runtime.y][runtime.x] == :;)
			|| (!s&&runtime.program[runtime.y][runtime.x+1] == :\) {
			runtime.y = runtime.y+1
			runtime.x = startx
		}
		else: runtime.x = runtime.x + 1
	}
	
	dcode.onecode(code, runtime)
}

dcode.commands.} = def(runtime) {
	runtime.direction = :WEST
	
	local startx = runtime.x
	
	runtime.x = runtime.x - 1
	
	local code = ""
	local s = false
	while: runtime.program[runtime.y][runtime.x] != :{ {
		if: runtime.program[runtime.y][runtime.x] == "\"" {
			if: s {
				if: runtime.program[runtime.y][runtime.x-1] != "\\" s = false
			} else: s = true
		}
		code = runtime.program[runtime.y][runtime.x] + code
		if: !runtime.program[runtime.y]->defined(runtime.x-1) || (!s&&runtime.program[runtime.y][runtime.x-1] == ":") {
			runtime.y = runtime.y-1
			runtime.x = startx
		}
		else: runtime.x = runtime.x - 1
	}
	
	dcode.onecode(code, runtime)
}

# IO

dcode.commands.c = def(runtime) {
	runtime.output = runtime.output + chr(runtime.a)
}

dcode.commands.n = def(runtime) {
	runtime.output = runtime.output + runtime.a
}

dcode.commands.f = def(runtime) {
	print::runtime.output
	runtime.output = ""
}

## Reading program

if: !defined(dcode,:program) {
	dcode.program = ""
	local line = ""
	do: {
		line = readln()
		if: line == :EOF break
		else: dcode.program = dcode.program + line + "\n"
	} while: true
}

if: dcode.debug print::dcode.program

# Program array
dcode.program = dcode.program->split("\n")
foreach(dcode.program->keys)::def(key) dcode.program[key] = dcode.program[key]->split("")

def: dbgout(program, runtime) {
	foreach(range(0,program->len-1))::def (line) {
		local out = ""
		foreach(range(0,program[line]->len-1))::def(x) {
			if: x==runtime.x&&line==runtime.y out=out+"X"
			else: out=out+program[line][x]
		}
		print::out
	}
	print::runtime.vars+""+runtime.a
}

## Runtime ~ twocode interpreter

# Runtime instance
runtime = {}
runtime.direction = :EAST
runtime.output = ""
runtime.program = dcode.program

# storage
runtime.vars = {}
runtime.memory = []

runtime.a = 0 # accumulator

runtime.x = 0
runtime.y = 0
local i = 0
while: true {
	local cmd = dcode.program[runtime.y][runtime.x]
	
	if: dcode.debug==2 dcode.program->dbgout(runtime)
	
	if: defined(dcode.commands,cmd) dcode.commands[cmd](runtime)
	else: if: cmd == "#" break
	
	if: runtime.direction == :NORTH runtime.y = runtime.y - 1
	if: runtime.direction == :SOUTH runtime.y = runtime.y + 1
	if: runtime.direction == :EAST runtime.x = runtime.x + 1
	if: runtime.direction == :WEST runtime.x = runtime.x - 1
	
	if: i++ > 100000 break
}

# Flush output
print::runtime.output