-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy paththue.nim
More file actions
148 lines (116 loc) · 3.3 KB
/
thue.nim
File metadata and controls
148 lines (116 loc) · 3.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# compile: nim c -d:release thue
# run: ./thue [flags] program.t
import streams
import strutils
import sequtils
import math
type
ThueRule = tuple[lhs, rhs: string]
ThueOption = enum
thDebug, thRTL, thLTR, thNoNl
Thue = object
rules: seq[ThueRule]
opts: set[ThueOption]
str: string
ThueError = object of Exception
const
Separator = "::="
InputCommand = ":::"
OutputPrefix = "~"
proc newThue(str = "", opts: set[ThueOption] = {}): Thue =
Thue(rules: @[], opts: opts, str: str)
proc readFrom(th: var Thue, stream: Stream) =
var line: TaintedString = newString(100)
var lineCounter = 0
while true:
inc lineCounter
if not stream.readLine(line):
raise newException(ThueError, "rule list left unterminated")
let stripped = line.strip()
if stripped.len == 0 or stripped[0] == '#':
continue
if line == Separator:
break
let split = line.split(Separator)
if split.len != 2:
raise newException(ThueError, "line $1: malformed rule '$2'".format(lineCounter, line))
let rule = (split[0], split[1])
th.rules.add(rule)
while true:
if not stream.readLine(line):
raise newException(ThueError, "no start string after rule list")
let stripped = line.strip()
if stripped.len == 0:
continue
else:
break
th.str = line
proc ruleMatches(th: Thue, rule: ThueRule): bool =
rule.lhs in th.str
proc applyRule(th: var Thue, rule: ThueRule) =
let rhs = rule.rhs
var replacement: string
if rhs == InputCommand:
replacement = stdin.readLine()
elif rhs.startsWith(OutputPrefix):
stdout.write(rhs.substr(1))
if thNoNL notin th.opts or rhs.len == 1:
stdout.write("\n")
replacement = ""
else:
replacement = rhs
th.str = th.str.replace(rule.lhs, replacement)
randomize()
proc step(th: var Thue): bool =
var matches = th.rules.filterIt(th.ruleMatches(it))
if matches.len == 0:
return false
var rule: ThueRule
if thLTR in th.opts:
rule = matches[0]
elif thRTL in th.opts:
rule = matches[^1]
else:
rule = matches[random(matches.len)]
if thDebug in th.opts:
echo "Applying rule: ", rule.lhs, "::=", rule.rhs
th.applyRule(rule)
if thDebug in th.opts:
echo "Tape reads: ", th.str
return true
when isMainModule:
import parseopt2
var filename = ""
var th = newThue()
for kind, key, val in getopt():
case kind:
of cmdArgument:
filename = key
of cmdShortOption, cmdLongOption:
case key:
of "d", "debug":
incl th.opts, thDebug
of "r", "right-to-left":
excl th.opts, thLTR
incl th.opts, thRTL
of "l", "left-to-right":
excl th.opts, thRTL
incl th.opts, thLTR
of "nn", "no-newlines":
incl th.opts, thNoNL
else:
stderr.write("Unexpected option: '", key, "'\n")
quit 1
of cmdEnd: assert(false)
if filename.len == 0:
stderr.write("filename expected\n")
quit 1
let stream = if filename == "STDIN": newFileStream(stdin) else: newFileStream(filename)
if isNil(stream):
stderr.write("file '", filename, "' doesn't exist\n")
quit 1
th.readFrom(newFileStream(filename))
while th.step():
if thDebug in th.opts:
discard stdin.readLine()
discard