aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulien Dessaux2021-10-06 23:25:41 +0200
committerJulien Dessaux2021-10-06 23:25:41 +0200
commitcca0eb2117a98695a0280adbc1693b3c92cfab7a (patch)
treea9167286c69938fb39cea9a45afded2990638621
parentImplemented missing functions for the interpreter (diff)
downloadnimfunge98-cca0eb2117a98695a0280adbc1693b3c92cfab7a.tar.gz
nimfunge98-cca0eb2117a98695a0280adbc1693b3c92cfab7a.tar.bz2
nimfunge98-cca0eb2117a98695a0280adbc1693b3c92cfab7a.zip
Implemented the interpreter
-rw-r--r--README.md4
-rw-r--r--src/defaultIO.nim2
-rw-r--r--src/interpreter.nim33
-rw-r--r--src/nimfunge98.nim44
-rw-r--r--src/pointer.nim322
5 files changed, 403 insertions, 2 deletions
diff --git a/README.md b/README.md
index 311565d..f0c07e4 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,6 @@
This repository contains code for a nim program that can interpret a valid [Funge-98](https://github.com/catseye/Funge-98/blob/master/doc/funge98.markdown) program. It will soon pass the [mycology test suite](https://github.com/Deewiant/Mycology).
Current limitations are :
-- it is not finished!
- currently does not implement any fingerprints
- does not implement concurrent execution with the `t` command
- does not implement file I/O with the `i` and `o` commands
@@ -70,4 +69,7 @@ To debug these particular tests, use :
```
nim c --debugger:on --parallelBuild:1 --debuginfo --linedir:on tests/stack.nim
gdb tests/stack
+set args XXXXX-if-necessary
+b src/truc.nim:123
+r
```
diff --git a/src/defaultIO.nim b/src/defaultIO.nim
index 52bcb83..ccf4476 100644
--- a/src/defaultIO.nim
+++ b/src/defaultIO.nim
@@ -37,4 +37,4 @@ proc defaultCharacterOutput*(v: int) =
discard stdout.writeChars(@[v.char()], 0, 1)
proc defaultDecimalOutput*(v: int) =
- stdout.write(&"{v}")
+ stdout.write(&"{v} ")
diff --git a/src/interpreter.nim b/src/interpreter.nim
new file mode 100644
index 0000000..56313b5
--- /dev/null
+++ b/src/interpreter.nim
@@ -0,0 +1,33 @@
+import field
+import pointer
+
+type
+ Interpreter = object
+ f: ref Field
+ p: ref Pointer
+ argv: seq[string]
+
+func NewInterpreter*(f: ref Field, p: ref Pointer): ref Interpreter =
+ new(result)
+ result.f = f
+ result.p = p
+
+proc Step*(i: var Interpreter): ref int =
+ var prev: ref Pointer
+ var p = i.p
+ while p != nil:
+ let (d, v) = p[].Exec(i.f[])
+ if v != nil:
+ return v
+ if d:
+ if prev == nil:
+ i.p = p.next
+ else:
+ prev.next = p.next
+ p = p.next
+
+proc Run*(i: var Interpreter): int =
+ while i.p != nil:
+ let v = i.Step()
+ if v != nil:
+ return v[]
diff --git a/src/nimfunge98.nim b/src/nimfunge98.nim
new file mode 100644
index 0000000..6e6efb9
--- /dev/null
+++ b/src/nimfunge98.nim
@@ -0,0 +1,44 @@
+import field
+import interpreter
+import pointer
+
+import os
+import parseopt
+import strformat
+
+proc Usage(i: int = 0) =
+ let filename = getAppFilename().extractFilename()
+ echo fmt"""Usage of {filename}:
+ -f string b98 file to interpret
+ -h display this help message
+ """
+ if i != 0:
+ quit i
+
+var filename: string
+
+for kind, key, value in getOpt():
+ case kind
+ of cmdArgument:
+ if filename != "":
+ echo "Invalid argument: ", key
+ Usage(1)
+ filename = key
+ of cmdLongOption, cmdShortOption:
+ case key
+ of "h":
+ Usage()
+ else:
+ echo "Unknown option: ", key
+ Usage(1)
+ of cmdEnd:
+ discard
+
+var f = Load(filename)
+if f == nil:
+ echo "Failed to load ", filename
+ quit 1
+let argv = @[filename]
+var p = NewPointer(argv=argv)
+let v = NewInterpreter(f, p)[].Run()
+quit v
diff --git a/src/pointer.nim b/src/pointer.nim
new file mode 100644
index 0000000..cbf13b7
--- /dev/null
+++ b/src/pointer.nim
@@ -0,0 +1,322 @@
+import defaultIO
+import field
+import stackStack
+
+import math
+import os
+import random
+import times
+
+randomize()
+
+type
+ Pointer* = object
+ x, y: int # The pointer's positiont
+ dx, dy: int # The pointer's traveling delta
+ sox, soy: int # The storage offset
+ stringMode, lastCharWasSpace: bool # The string mode flags
+ ss: ref StackStack
+ next*: ref Pointer # the next element for multi-threaded funge-98
+ characterInput*: proc(): int
+ decimalInput*: proc(): int
+ characterOutput*: proc(v: int)
+ decimalOutput*: proc(v: int)
+ argv: seq[string]
+
+func NewPointer*(next: ref Pointer = nil, argv: seq[string]): ref Pointer =
+ new(result)
+ result.dx = 1
+ result.ss = NewStackStack()
+ result.next = next
+ result.characterInput = defaultCharacterInput
+ result.decimalInput = defaultDecimalInput
+ result.characterOutput = defaultCharacterOutput
+ result.decimalOutput = defaultDecimalOutput
+ result.argv = argv
+
+#func Split(p: var Pointer) =
+# # TODO check the spec, maybe we need to duplicate the stack?
+# p.next = NewPointer(p.next)
+
+func Step(p: var Pointer, f: Field) =
+ (p.x, p.y) = f.Step((p.x, p.y), (p.dx, p.dy))
+
+func StepAndGet(p: var Pointer, f: Field): int =
+ p.Step(f)
+ return f.Get(p.x, p.y)
+
+func Reverse(p: var Pointer) =
+ p.dx = -p.dx; p.dy = -p.dy
+
+proc Redirect(p: var Pointer, c: int): bool =
+ ## Redirects the pointer according to c's value.
+ ## Returns true if a redirect has been performed
+ case c:
+ of int('^'): p.dx = 0; p.dy = -1
+ of int('>'): p.dx = 1; p.dy = 0
+ of int('v'): p.dx = 0; p.dy = 1
+ of int('<'): p.dx = -1; p.dy = 0
+ of int('?'):
+ const directions = [0, -1, 1, 0, 0, 1, -1, 0]
+ let r = 2 * rand(3)
+ p.dx = directions[r]; p.dy = directions[r+1]
+ of int('['): (p.dx, p.dy) = (p.dy, -p.dx)
+ of int(']'): (p.dx, p.dy) = (-p.dy, p.dx)
+ of int('r'): p.Reverse()
+ of int('x'): p.dy = p.ss[].Pop(); p.dx = p.ss[].Pop()
+ else: return false
+ return true
+
+proc Eval(p: var Pointer, f: var Field, c: int): (bool, ref int) =
+ ## Executes the instruction on the field
+ ## Returns true if the pointer terminated, and a return code if
+ ## the program should terminate completely
+ case c:
+ of int('@'): return (true, nil)
+ of int('z'): discard
+ of int('#'): p.Step(f)
+ of int('j'):
+ let n = p.ss[].Pop()
+ if n > 0:
+ for j in 0..<n:
+ p.Step(f)
+ else:
+ p.Reverse()
+ for j in 0 ..< -n:
+ p.Step(f)
+ p.Reverse()
+ of int('q'):
+ var i: ref int
+ new(i); i[] = p.ss[].Pop()
+ return (true, i)
+ of int('k'):
+ let x = p.x
+ let y = p.y
+ let n = p.ss[].Pop()
+ var v = p.StepAndGet(f)
+ var jumpingMode = false
+ while jumpingMode or v == int(' ') or v == int(';'):
+ if v == int(';'):
+ jumpingMode = not jumpingMode
+ v = p.StepAndGet(f)
+ if n > 0:
+ p.x = x; p.y = y
+ if v != int(' ') and v != int(';'):
+ if v == int('q') or v == int('@'):
+ return p.Eval(f, v)
+ for i in 0..<n:
+ discard p.Eval(f, v)
+ of int('!'):
+ if p.ss[].Pop() == 0:
+ p.ss[].Push(1)
+ else:
+ p.ss[].Push(0)
+ of int('`'):
+ let (a, b) = p.ss[].PopVector()
+ if a > b:
+ p.ss[].Push(1)
+ else:
+ p.ss[].Push(0)
+ of int('_'):
+ if p.ss[].Pop() == 0:
+ p.dx = 1
+ else:
+ p.dx = -1
+ p.dy = 0
+ of int('|'):
+ p.dx = 0
+ if p.ss[].Pop() == 0:
+ p.dy = 1
+ else:
+ p.dy = -1
+ of int('w'):
+ let (a, b) = p.ss[].PopVector()
+ if a < b:
+ (p.dx, p.dy) = (p.dy, -p.dx)
+ elif a > b:
+ (p.dx, p.dy) = (-p.dy, p.dx)
+ of int('+'):
+ let (a, b) = p.ss[].PopVector()
+ p.ss[].Push(a+b)
+ of int('*'):
+ let (a, b) = p.ss[].PopVector()
+ p.ss[].Push(a*b)
+ of int('-'):
+ let (a, b) = p.ss[].PopVector()
+ p.ss[].Push(a-b)
+ of int('/'):
+ let (a, b) = p.ss[].PopVector()
+ if b == 0:
+ p.ss[].Push(0)
+ else:
+ p.ss[].Push(a div b)
+ of int('%'):
+ let (a, b) = p.ss[].PopVector()
+ if b == 0:
+ p.ss[].Push(0)
+ else:
+ p.ss[].Push(a mod b)
+ of int('"'):
+ p.stringMode = true
+ of int('\''):
+ p.ss[].Push(p.StepAndGet(f))
+ of int('s'):
+ p.Step(f)
+ f.Set(p.x, p.y, p.ss[].Pop())
+ of int('$'):
+ discard p.ss[].Pop()
+ of int(':'):
+ p.ss[].Duplicate()
+ of int('\\'):
+ p.ss[].Swap()
+ of int('n'):
+ p.ss[].Clear()
+ of int('{'):
+ p.ss[].Begin((p.sox, p.soy))
+ p.sox = p.x + p.dx
+ p.soy = p.y + p.dy
+ of int('}'):
+ var v: tuple[x, y: int]
+ if p.ss[].End(v):
+ p.Reverse()
+ p.sox = v.x; p.soy = v.y
+ of int('u'):
+ if p.ss[].Under():
+ p.Reverse()
+ of int('g'):
+ let (x, y) = p.ss[].PopVector()
+ p.ss[].Push(f.Get(x+p.sox, y+p.soy))
+ of int('p'):
+ let (x, y) = p.ss[].PopVector()
+ let v = p.ss[].Pop()
+ f.Set(x+p.sox, y+p.soy, v)
+ of int('.'):
+ p.decimalOutput(p.ss[].Pop())
+ of int(','):
+ p.characterOutput(p.ss[].Pop())
+ of int('&'):
+ p.ss[].Push(p.decimalInput())
+ of int('~'):
+ p.ss[].Push(p.characterInput())
+ of int('y'):
+ let n = p.ss[].Pop()
+ let now = now()
+ let (x, y, lx, ly) = f.GetSize()
+ let (height, heights) = p.ss[].GetHeights()
+ # 20
+ for key, value in envPairs():
+ case key
+ of "LC_ALL": discard
+ of "PWD": discard
+ of "PATH": discard
+ of "DISPLAY": discard
+ of "USER": discard
+ of "TERM": discard
+ of "LANG": discard
+ of "HOME": discard
+ of "EDITOR": discard
+ of "SHELL": discard
+ else: continue
+ p.ss[].Push(0)
+ for i in value.len-1..0:
+ p.ss[].Push(int(value[i]))
+ # 19
+ p.ss[].PushVector((0, 0))
+ for i in 0..<p.argv.len:
+ p.ss[].Push(0)
+ for j in p.argv[i].len-1..0:
+ p.ss[].Push(int(p.argv[i][j]))
+ # 18
+ for i in 0..<height:
+ p.ss[].Push(heights[i])
+ # 17
+ p.ss[].Push(height)
+ # 16
+ p.ss[].Push(now.hour * 256 * 256 + now.minute * 256 + now.second)
+ # 15
+ p.ss[].Push(now.year * 256 * 256 + int(now.month) * 256 + int(now.monthday))
+ # 14
+ p.ss[].PushVector((lx-1, ly-1))
+ # 13
+ p.ss[].PushVector((x, y))
+ # 12
+ p.ss[].PushVector((p.sox, p.soy))
+ # 11
+ p.ss[].PushVector((p.dx, p.dy))
+ # 10
+ p.ss[].PushVector((p.x, p.y))
+ # 9
+ p.ss[].Push(0)
+ # 8
+ p.ss[].Push(cast[int](addr p))
+ # 7
+ p.ss[].Push(2)
+ # 6
+ p.ss[].Push(int('/'))
+ # 5
+ p.ss[].Push(0) # TODO update when implementing =
+ # 4
+ p.ss[].Push(1)
+ # 3
+ p.ss[].Push(1048577)
+ # 2
+ p.ss[].Push(sizeof(int))
+ # 1
+ p.ss[].Push(0b00000) # TODO update when implementing t, i, o and =
+ if n > 0:
+ p.ss[].YCommandPick(n, heights[0])
+ of int('('):
+ let n = p.ss[].Pop()
+ var v = 0
+ for i in 0..<n:
+ v = v*256+p.ss[].Pop()
+ p.Reverse() # No fingerprints supported yet
+ of int(')'):
+ let n = p.ss[].Pop()
+ var v = 0
+ for i in 0..<n:
+ v = v*256+p.ss[].Pop()
+ p.Reverse() # No fingerprints supported yet
+ of int('i'):
+ quit("not implemented yet", 1)
+ of int('o'):
+ quit("not implemented yet", 1)
+ of int('='):
+ quit("not implemented yet", 1)
+ of int('t'):
+ quit("not implemented yet", 1)
+ else:
+ if not p.Redirect(c):
+ if c >= int('0') and c <= int('9'):
+ p.ss[].Push(c - int('0'))
+ elif c >= int('a') and c <= int('f'):
+ p.ss[].Push(c - int('a') + 10)
+ else:
+ p.Reverse()
+ return (false, nil)
+
+proc Exec*(p: var Pointer, f: var Field): (bool, ref int) =
+ ## Advances to the next instruction of the field and executes it
+ ## Returns true if the pointer terminated, and a return code if
+ ## the program should terminate completely
+ var c = f.Get(p.x, p.y)
+ if p.stringMode:
+ if p.lastCharWasSpace:
+ while c == int(' '):
+ c = p.StepAndGet(f)
+ p.lastCharWasSpace = false
+ if c == int('"'):
+ p.stringMode = false
+ else:
+ if c == int(' '):
+ p.lastCharWasSpace = true
+ p.ss[].Push(c)
+ else:
+ var jumpingMode = false
+ while jumpingMode or c == int(' ') or c == int(';'):
+ if c == int(';'):
+ jumpingMode = not jumpingMode
+ c = p.StepAndGet(f)
+ result = p.Eval(f, c)
+ p.Step(f)
+ return result