Browse code

Add beginnings of assembler project.

Andrew Alderwick authored on 31/03/2021 22:55:02
Showing 4 changed files
... ...
@@ -4,4 +4,5 @@
4 4
 *gif~
5 5
 *bmp~
6 6
 /bin
7
-*io.bit
8 7
\ No newline at end of file
8
+*io.bit
9
+*.bak
9 10
\ No newline at end of file
10 11
new file mode 100644
... ...
@@ -0,0 +1,295 @@
1
+local build_dag
2
+build_dag = function(t, dag, i, j, level)
3
+  if dag == nil then
4
+    dag = { }
5
+  end
6
+  if i == nil then
7
+    i = 1
8
+  end
9
+  if j == nil then
10
+    j = #t
11
+  end
12
+  if level == nil then
13
+    level = 0
14
+  end
15
+  if i > j then
16
+    return 
17
+  end
18
+  local mid = math.floor((i + j) / 2)
19
+  dag[t[mid]] = {
20
+    (build_dag(t, dag, i, mid - 1, level + 1)),
21
+    (build_dag(t, dag, mid + 1, j, level + 1))
22
+  }
23
+  return t[mid], dag
24
+end
25
+local append_dag
26
+append_dag = function(node, dag, k)
27
+  local i = k > node and 2 or 1
28
+  local next_node = dag[node][i]
29
+  if next_node then
30
+    return append_dag(next_node, dag, k)
31
+  end
32
+  dag[node][i] = k
33
+  dag[k] = { }
34
+end
35
+local build_dag_from_chars
36
+build_dag_from_chars = function(s, ...)
37
+  local t
38
+  do
39
+    local _accum_0 = { }
40
+    local _len_0 = 1
41
+    for i = 1, #s do
42
+      _accum_0[_len_0] = s:sub(i, i)
43
+      _len_0 = _len_0 + 1
44
+    end
45
+    t = _accum_0
46
+  end
47
+  table.sort(t)
48
+  local root, dag = build_dag(t)
49
+  for i = 1, select('#', ...) do
50
+    append_dag(root, dag, (select(i, ...)))
51
+  end
52
+  return root, dag
53
+end
54
+local check_terminals
55
+check_terminals = function(dag, s)
56
+  for i = 1, #s do
57
+    local k = s:sub(i, i)
58
+    assert(not dag[k][1], ('%s has left child node'):format(k))
59
+    assert(not dag[k][2], ('%s has right child node'):format(k))
60
+  end
61
+end
62
+local dump
63
+dump = function(f, root, dag, level)
64
+  if level == nil then
65
+    level = 0
66
+  end
67
+  if dag[root][1] then
68
+    dump(f, dag[root][1], dag, level + 1)
69
+  end
70
+  f:write(('    '):rep(level))
71
+  f:write(root)
72
+  f:write('\n')
73
+  if dag[root][2] then
74
+    return dump(f, dag[root][2], dag, level + 1)
75
+  end
76
+end
77
+local write_opcode_tree
78
+do
79
+  local byte_to_opcode = { }
80
+  local byte = false
81
+  for l in assert(io.lines('src/assembler.c')) do
82
+    if l:match('^%s*char%s+ops%[%]%[4%]') then
83
+      byte = 0
84
+    elseif l:match('%}') then
85
+      byte = false
86
+    elseif byte then
87
+      for opcode in l:gmatch('"([A-Z-][A-Z-][A-Z-])"') do
88
+        byte_to_opcode[byte] = opcode
89
+        byte = byte + 1
90
+      end
91
+    end
92
+  end
93
+  local order_to_opcode
94
+  do
95
+    local _accum_0 = { }
96
+    local _len_0 = 1
97
+    for i = 0, #byte_to_opcode do
98
+      if byte_to_opcode[i] ~= '---' then
99
+        _accum_0[_len_0] = byte_to_opcode[i]
100
+        _len_0 = _len_0 + 1
101
+      end
102
+    end
103
+    order_to_opcode = _accum_0
104
+  end
105
+  table.sort(order_to_opcode)
106
+  local root, opcode_to_links = build_dag(order_to_opcode)
107
+  write_opcode_tree = function(f)
108
+    for i = 0, #byte_to_opcode do
109
+      local opcode = byte_to_opcode[i]
110
+      f:write('\t')
111
+      if opcode == root then
112
+        f:write('$root   ')
113
+      elseif opcode ~= '---' then
114
+        f:write(('$op-%s '):format(opcode:lower()))
115
+      else
116
+        f:write('        ')
117
+      end
118
+      for j = 1, 2 do
119
+        if opcode ~= '---' and opcode_to_links[opcode][j] then
120
+          f:write(('.$op-%s '):format(opcode_to_links[opcode][j]:lower()))
121
+        else
122
+          f:write('[ 0000 ] ')
123
+        end
124
+      end
125
+      if i == 0 then
126
+        f:write('$disasm ')
127
+      else
128
+        f:write('        ')
129
+      end
130
+      if opcode ~= '---' then
131
+        f:write(('[ %s ]'):format(opcode))
132
+      else
133
+        f:write('[ ??? ]')
134
+      end
135
+      if i == 0 then
136
+        f:write(' $asm')
137
+      end
138
+      f:write('\n')
139
+    end
140
+  end
141
+end
142
+local type_byte
143
+type_byte = function(size, has_subtree)
144
+  local n1 = has_subtree and '8' or '0'
145
+  local n2
146
+  local _exp_0 = size
147
+  if '1' == _exp_0 then
148
+    n2 = '1'
149
+  elseif '2' == _exp_0 then
150
+    n2 = '3'
151
+  else
152
+    n2 = '0'
153
+  end
154
+  return n1 .. n2
155
+end
156
+local globals = { }
157
+local add_globals
158
+add_globals = function(root, dag, key_to_label, key_to_contents, pad_before, pad_after)
159
+  if pad_before == nil then
160
+    pad_before = ''
161
+  end
162
+  if pad_after == nil then
163
+    pad_after = ''
164
+  end
165
+  for k in pairs(dag) do
166
+    local l = ''
167
+    if k == root then
168
+      l = l .. ('@%s\n'):format(key_to_label('root'):gsub('%s', ''))
169
+    end
170
+    l = l .. ('@%s '):format(key_to_label(k))
171
+    for j = 1, 2 do
172
+      if dag[k][j] then
173
+        l = l .. ('.%s '):format(key_to_label(dag[k][j]))
174
+      else
175
+        l = l .. ('%s[ 0000 ]%s '):format(pad_before, pad_after)
176
+      end
177
+    end
178
+    l = l .. key_to_contents(k)
179
+    l = l .. '\n'
180
+    globals[key_to_label(k):gsub('%s', '')] = l
181
+  end
182
+  globals[key_to_label('root'):gsub('%s', '')] = ''
183
+end
184
+do
185
+  local root, dag = build_dag_from_chars('{}[]%@$;|=~,.^#"\0', '(', ')')
186
+  check_terminals(dag, ')')
187
+  local convert = {
188
+    ['.'] = 'dot',
189
+    ['\0'] = 'nul'
190
+  }
191
+  local label_name
192
+  label_name = function(s)
193
+    return ('first-char-%-3s'):format(convert[s] or s)
194
+  end
195
+  local label_value
196
+  label_value = function(k)
197
+    return ('[ %02x ]'):format(k:byte())
198
+  end
199
+  add_globals(root, dag, label_name, label_value, '  ', '     ')
200
+end
201
+local devices = { }
202
+local add_device
203
+add_device = function(name, fields)
204
+  local field_sizes
205
+  do
206
+    local _tbl_0 = { }
207
+    for k, size in fields:gmatch('(%S+) (%d+)') do
208
+      _tbl_0[k] = size
209
+    end
210
+    field_sizes = _tbl_0
211
+  end
212
+  field_sizes.pad = nil
213
+  local field_names
214
+  do
215
+    local _accum_0 = { }
216
+    local _len_0 = 1
217
+    for k in pairs(field_sizes) do
218
+      _accum_0[_len_0] = k
219
+      _len_0 = _len_0 + 1
220
+    end
221
+    field_names = _accum_0
222
+  end
223
+  table.sort(field_names)
224
+  local root, dag = build_dag(field_names)
225
+  local label_name
226
+  label_name = function(k)
227
+    return ('l-%-14s'):format(name .. '-' .. k)
228
+  end
229
+  local label_value
230
+  label_value = function(k)
231
+    return ('%-17s [ %s ] .%s.%s'):format(('[ %s 00 ]'):format(k), type_byte(field_sizes[k], false), name, k)
232
+  end
233
+  add_globals(root, dag, label_name, label_value, ' ', '        ')
234
+  return table.insert(devices, name)
235
+end
236
+local add_devices
237
+add_devices = function()
238
+  table.sort(devices)
239
+  local root, dag = build_dag(devices)
240
+  local label_name
241
+  label_name = function(k)
242
+    return ('l-%-14s'):format(k)
243
+  end
244
+  local label_value
245
+  label_value = function(k)
246
+    return ('%-17s [ %s ] .%s .l-%s-root'):format(('[ %s 00 ]'):format(k), type_byte(0, true), k, k)
247
+  end
248
+  return add_globals(root, dag, label_name, label_value, ' ', '        ')
249
+end
250
+local filename = 'projects/software/assembler.usm'
251
+local f = assert(io.open(('%s.tmp'):format(filename), 'w'))
252
+local state = 'normal'
253
+local machine = {
254
+  normal = function(l)
255
+    if l:match('%$disasm .*%$asm') then
256
+      write_opcode_tree(f)
257
+      state = 'opcode'
258
+    elseif l:match('^%@') then
259
+      if l == '@RESET' then
260
+        add_devices()
261
+      end
262
+      for k in l:gmatch('%@(%S+)') do
263
+        if globals[k] then
264
+          f:write(globals[k])
265
+          globals[k] = nil
266
+          return 
267
+        end
268
+      end
269
+      f:write(l)
270
+      return f:write('\n')
271
+    else
272
+      if l:match('^%|%x%x%x%x %;') then
273
+        add_device(l:match('%;(%S+) %{ (.*) %}'))
274
+      end
275
+      f:write(l)
276
+      return f:write('\n')
277
+    end
278
+  end,
279
+  opcode = function(l)
280
+    if not l:match('%[') then
281
+      f:write(l)
282
+      f:write('\n')
283
+      state = 'normal'
284
+    end
285
+  end
286
+}
287
+for l in assert(io.lines(filename)) do
288
+  machine[state](l)
289
+end
290
+for _, l in pairs(globals) do
291
+  f:write(l)
292
+end
293
+f:close()
294
+assert(0 == os.execute(('mv %s %s.bak'):format(filename, filename)))
295
+return assert(0 == os.execute(('mv %s.tmp %s'):format(filename, filename)))
0 296
new file mode 100644
... ...
@@ -0,0 +1,180 @@
1
+build_dag = (t, dag = {}, i = 1, j = #t, level = 0) ->
2
+	if i > j
3
+		return
4
+	mid = math.floor (i + j) / 2
5
+	dag[t[mid]] = {
6
+		(build_dag t, dag, i, mid - 1, level + 1)
7
+		(build_dag t, dag, mid + 1, j, level + 1)
8
+	}
9
+	t[mid], dag
10
+append_dag = (node, dag, k) ->
11
+	i = k > node and 2 or 1
12
+	next_node = dag[node][i]
13
+	if next_node
14
+		return append_dag next_node, dag, k
15
+	dag[node][i] = k
16
+	dag[k] = {}
17
+build_dag_from_chars = (s, ...) ->
18
+	t = [ s\sub i, i for i = 1, #s ]
19
+	table.sort t
20
+	root, dag = build_dag t
21
+	for i = 1, select '#', ...
22
+		append_dag root, dag, (select i, ...)
23
+	return root, dag
24
+check_terminals = (dag, s) ->
25
+	for i = 1, #s
26
+		k = s\sub i, i
27
+		assert not dag[k][1], '%s has left child node'\format k
28
+		assert not dag[k][2], '%s has right child node'\format k
29
+dump = (f, root, dag, level = 0) ->
30
+	if dag[root][1]
31
+		dump f, dag[root][1], dag, level + 1
32
+	f\write '    '\rep level
33
+	f\write root
34
+	f\write '\n'
35
+	if dag[root][2]
36
+		dump f, dag[root][2], dag, level + 1
37
+
38
+-- deal with opcodes
39
+
40
+write_opcode_tree = do
41
+	byte_to_opcode = {}
42
+	byte = false
43
+	for l in assert io.lines 'src/assembler.c'
44
+		if l\match '^%s*char%s+ops%[%]%[4%]'
45
+			byte = 0
46
+		elseif l\match '%}'
47
+			byte = false
48
+		elseif byte
49
+			for opcode in l\gmatch '"([A-Z-][A-Z-][A-Z-])"'
50
+				byte_to_opcode[byte] = opcode
51
+				byte += 1
52
+	order_to_opcode = [ byte_to_opcode[i] for i = 0, #byte_to_opcode when byte_to_opcode[i] != '---' ]
53
+	table.sort order_to_opcode
54
+	root, opcode_to_links = build_dag order_to_opcode
55
+	(f) ->
56
+		for i = 0, #byte_to_opcode
57
+			opcode = byte_to_opcode[i]
58
+			f\write '\t'
59
+			if opcode == root
60
+				f\write '$root   '
61
+			elseif opcode != '---'
62
+				f\write '$op-%s '\format opcode\lower!
63
+			else
64
+				f\write '        '
65
+			for j = 1, 2
66
+				if opcode != '---' and opcode_to_links[opcode][j]
67
+					f\write '.$op-%s '\format opcode_to_links[opcode][j]\lower!
68
+				else
69
+					f\write '[ 0000 ] '
70
+			if i == 0
71
+				f\write '$disasm '
72
+			else
73
+				f\write '        '
74
+			if opcode != '---'
75
+				f\write '[ %s ]'\format opcode
76
+			else
77
+				f\write '[ ??? ]'
78
+			if i == 0
79
+				f\write ' $asm'
80
+			f\write '\n'
81
+
82
+type_byte = (size, has_subtree) ->
83
+	n1 = has_subtree and '8' or '0'
84
+	n2 = switch size
85
+		when '1'
86
+			'1'
87
+		when '2'
88
+			'3'
89
+		else
90
+			'0'
91
+	n1 .. n2
92
+
93
+globals = {}
94
+
95
+add_globals = (root, dag, key_to_label, key_to_contents, pad_before = '', pad_after = '') ->
96
+	for k in pairs dag
97
+		l = ''
98
+		if k == root
99
+			l ..= '@%s\n'\format key_to_label('root')\gsub '%s', ''
100
+		l ..= '@%s '\format key_to_label k
101
+		for j = 1, 2
102
+			if dag[k][j]
103
+				l ..= '.%s '\format key_to_label dag[k][j]
104
+			else
105
+				l ..= '%s[ 0000 ]%s '\format pad_before, pad_after
106
+		l ..= key_to_contents k
107
+		l ..= '\n'
108
+		globals[key_to_label(k)\gsub '%s', ''] = l
109
+	globals[key_to_label('root')\gsub '%s', ''] = ''
110
+
111
+do
112
+	root, dag = build_dag_from_chars '{}[]%@$;|=~,.^#"\0', '(', ')'
113
+	check_terminals dag, ')'
114
+-- 	dump io.stdout, root, dag
115
+	convert = {
116
+		['.']: 'dot'
117
+		['\0']: 'nul'
118
+	}
119
+	label_name = (s) -> 'first-char-%-3s'\format convert[s] or s
120
+	label_value = (k) -> '[ %02x ]'\format k\byte!
121
+	add_globals root, dag, label_name, label_value, '  ', '     '
122
+
123
+devices = {}
124
+
125
+add_device = (name, fields) ->
126
+	field_sizes = { k, size for k, size in fields\gmatch '(%S+) (%d+)' }
127
+	field_sizes.pad = nil
128
+	field_names = [ k for k in pairs field_sizes ]
129
+	table.sort field_names
130
+	root, dag = build_dag field_names
131
+	label_name = (k) -> 'l-%-14s'\format name .. '-' .. k
132
+	label_value = (k) -> '%-17s [ %s ] .%s.%s'\format '[ %s 00 ]'\format(k), type_byte(field_sizes[k], false), name, k
133
+	add_globals root, dag, label_name, label_value, ' ', '        '
134
+	table.insert devices, name
135
+
136
+add_devices = ->
137
+	table.sort devices
138
+	root, dag = build_dag devices
139
+	label_name = (k) -> 'l-%-14s'\format k
140
+	label_value = (k) -> '%-17s [ %s ] .%s .l-%s-root'\format '[ %s 00 ]'\format(k), type_byte(0, true), k, k
141
+	add_globals root, dag, label_name, label_value, ' ', '        '
142
+
143
+filename = 'projects/software/assembler.usm'
144
+
145
+f = assert io.open '%s.tmp'\format(filename), 'w'
146
+-- f = io.stdout
147
+state = 'normal'
148
+machine =
149
+	normal: (l) ->
150
+		if l\match '%$disasm .*%$asm'
151
+			write_opcode_tree f
152
+			state = 'opcode'
153
+		elseif l\match '^%@'
154
+			if l == '@RESET'
155
+				add_devices!
156
+			for k in l\gmatch '%@(%S+)'
157
+				if globals[k]
158
+					f\write globals[k]
159
+					globals[k] = nil
160
+					return
161
+			f\write l
162
+			f\write '\n'
163
+		else
164
+			if l\match '^%|%x%x%x%x %;'
165
+				add_device l\match '%;(%S+) %{ (.*) %}'
166
+			f\write l
167
+			f\write '\n'
168
+	opcode: (l) ->
169
+		if not l\match '%['
170
+			f\write l
171
+			f\write '\n'
172
+			state = 'normal'
173
+for l in assert io.lines filename
174
+	machine[state] l
175
+for _, l in pairs globals
176
+	f\write l
177
+f\close!
178
+assert 0 == os.execute 'mv %s %s.bak'\format filename, filename
179
+assert 0 == os.execute 'mv %s.tmp %s'\format filename, filename
180
+
0 181
new file mode 100644
... ...
@@ -0,0 +1,465 @@
1
+;tree { search-key 2 max-key-len 1 }
2
+;assembler { pass 1 state 1 token 2 scope-len 1 scope 80 }
3
+
4
+%HCF { #0000 DIV }
5
+
6
+( devices )
7
+
8
+|0100 ;Console { pad 8 char 1 byte 1 short 2 string 2 }
9
+|0110 ;Screen { width 2 height 2 pad 4 x 2 y 2 color 1 }
10
+|0120 ;Sprite { pad 8 x 2 y 2 addr 2 color 1 }
11
+|0130 ;Controller { p1 1 }
12
+|0140 ;Keys { key 1 }
13
+|0150 ;Mouse { x 2 y 2 state 1 chord 1 }
14
+|0160 ;File { pad 8 name 2 length 2 load 2 save 2 }
15
+|01F0 ;System { pad 8 r 2 g 2 b 2 }
16
+
17
+( vectors )
18
+
19
+|0200 ,RESET JMP2
20
+|0204 BRK
21
+|0208 BRK
22
+
23
+@RESET
24
+	#b000 #c000 #0010 ,memcpy JSR2
25
+	HCF
26
+
27
+	,$token ,strlen JSR2
28
+	HCF
29
+
30
+	#00
31
+	$loop
32
+	DUP ,highest-bit JSR2
33
+	( )
34
+	POP
35
+	#01 ADD
36
+	DUP ^$loop JNZ
37
+	POP
38
+
39
+
40
+	,$token ^assemble-token JSR
41
+	,$token2 ^assemble-token JSR
42
+	,$token3 ^assemble-token JSR
43
+	~assembler.state
44
+	HCF
45
+
46
+	$token [ hello 00 ]
47
+	$token2 [ 00 ]
48
+	$token3 [ 00 ]
49
+
50
+@assemble-tokens ( string-ptr* -- )
51
+	DUP2 ^assemble-token JSR
52
+
53
+@assemble-token ( string-ptr* -- )
54
+	( get location of tree )
55
+	DUP2
56
+	,state-machine-pointers #00 ~assembler.state ,highest-bit JSR2 #0004 MUL2 ADD2
57
+	DUP2 STH2
58
+	( see if first char is recognised )
59
+	SWP2 #01 ,traverse-tree JSR2
60
+	^$not-found JNZ
61
+	( skip first character of token )
62
+	SWP2 #0001 ADD2 =assembler.token
63
+	( tail call handling function defined in tree )
64
+	POP2r JMP2
65
+
66
+	$not-found
67
+	( not interested in incoming-ptr )
68
+	POP2
69
+	=assembler.token
70
+	( tail call default handling function defined in state-machine-pointers )
71
+	LIT2r [ 0002 ] ADD2r LDR2r
72
+	JMP2r
73
+
74
+@parse-hex-length ( string-ptr* -- value 01 if one or two hex digits
75
+                                OR 00 otherwise )
76
+	DUP2 #0001 ADD2 PEK2 ^parse-hex-string-try-two JNZ
77
+	PEK2 ^parse-hex-digit JSR DUP #04 SFT ^parse-hex-string-fail1 JNZ
78
+	#01 JMP2r
79
+
80
+@parse-hex-string ( string-ptr* -- value* 02 if four hex digits
81
+                                OR value 01 if two hex digits
82
+                                OR 00 otherwise )
83
+	DUP2 #0004 ADD2 PEK2 #00 EQU ^$try-four JNZ
84
+	$try-two
85
+	DUP2 #0002 ADD2 PEK2 ^$fail2 JNZ
86
+	$known-two
87
+	DUP2 PEK2 ^parse-hex-digit JSR DUP #04 SFT ^$fail3 JNZ ROT ROT
88
+	#0001 ADD2 PEK2 ^parse-hex-digit JSR DUP #04 SFT ^$fail2 JNZ
89
+	SWP #40 SFT ORA #01 JMP2r
90
+
91
+	$fail3 POP
92
+	$fail2 POP
93
+	$fail1 POP #00 JMP2r
94
+
95
+	$try-four
96
+	DUP2 #0002 ADD2 ^$known-two JSR ^$maybe-four JNZ
97
+	^$try-two JMP
98
+
99
+	$maybe-four
100
+	ROT ROT ^$known-two JSR ^$four JNZ
101
+	^$fail1 JMP
102
+
103
+	$four
104
+	SWP #02 JMP2r
105
+
106
+@parse-hex-digit ( charcode -- 00-0f if valid hex
107
+                            -- 10-ff otherwise )
108
+	DUP #3a LTH ^$digit JNZ
109
+	DUP #60 GTH ^$lowercase JNZ
110
+	DUP #40 GTH ^$uppercase JNZ
111
+	JMP2r
112
+
113
+	$digit ( #30 is #00 )
114
+	#30 SUB JMP2r
115
+
116
+	$lowercase ( #61 is #0a )
117
+	#57 SUB JMP2r
118
+
119
+	$uppercase ( #41 is #0a )
120
+	#37 SUB JMP2r
121
+
122
+@find-opcode ( name* -- byte 00 if valid opcode name
123
+                     OR 01 if not found )
124
+	,opcodes-tree SWP2 #03 ^traverse-tree JSR
125
+	^$nomatch JNZ
126
+	,opcodes-asm SUB2 #0007 DIV2
127
+	SWP JMP2r
128
+
129
+	$nomatch
130
+	DUP2 EQU2 JMP2r
131
+
132
+@traverse-tree ( tree-ptr* search-key* max-key-len --
133
+		binary-ptr* 00 if key matched
134
+		OR incoming-ptr* 01 if key not found )
135
+	=tree.max-key-len =tree.search-key
136
+
137
+	$loop
138
+	DUP2 LDR2 #0000 NEQ2 ^$valid-node JNZ
139
+	#01 JMP2r
140
+
141
+	$valid-node
142
+	LDR2 DUP2 STH2 #0004 ADD2 ^strcmp-tree JSR
143
+	DUP ^$nomatch JNZ
144
+	POP2r JMP2r
145
+
146
+	$nomatch
147
+	#07 SFT #02 MUL #00 SWP
148
+	STH2r ADD2
149
+	^$loop JMP
150
+
151
+@strcmp-tree ( node-key* -- order if strings differ
152
+                         OR after-node-key* 00 if strings match )
153
+	~tree.search-key STH2
154
+	~tree.max-key-len
155
+
156
+	$loop ( node-key* key-len in wst, search-key* in rst )
157
+	DUP ^$keep-going JNZ
158
+
159
+	( exhausted key-len, match found )
160
+	POP2r
161
+	JMP2r
162
+
163
+	$keep-going
164
+	#01 OVR2 PEK2 DUP2r PEK2r STHr
165
+	DUP2 ORA ^$not-end JNZ
166
+
167
+	( end of C strings, match found )
168
+	POP2r POP ROT POP SWP ADD2 #00
169
+	JMP2r
170
+
171
+	$not-end
172
+	SUB DUP ^$nomatch JNZ
173
+	POP SUB
174
+	LIT2r [ 0001 ] ADD2r STH
175
+	LIT2  [ 0001 ] ADD2  STHr
176
+	^$loop JMP
177
+
178
+	$nomatch
179
+	STH POP2 POP2 STHr POP2r
180
+	JMP2r
181
+
182
+@memcpy ( src-ptr* dest-ptr* length* -- )
183
+	SWP2 STH2
184
+
185
+	$loop
186
+	DUP2 ORA ^$keep-going JNZ
187
+	POP2 POP2 POP2r
188
+	JMP2r
189
+
190
+	$keep-going
191
+	#0001 SUB2
192
+	SWP2 DUP2 PEK2 DUP2r STH2r POK2
193
+	#0001 ADD2 SWP2
194
+	LIT2r [ 0001 ] ADD2r
195
+	^$loop JMP
196
+
197
+@strlen ( string-ptr* -- length* )
198
+	DUP2 #0001 SUB2
199
+	$loop
200
+	#0001 ADD2
201
+	DUP2 PEK2 ^$loop JNZ
202
+	SWP2 SUB2
203
+	JMP2r
204
+
205
+
206
+
207
+
208
+@add-label ( string-ptr* label-flags -- )
209
+	( NYI )
210
+	POP POP2 JMP2r
211
+
212
+@highest-bit ( n -- 00 if n is 00
213
+                 OR 01 if n is 01
214
+                 OR 02 if n is 02..03
215
+                 OR 03 if n is 04..07
216
+                 OR 04 if n is 08..0f
217
+                 ..
218
+                 OR 08 if n is 80..ff )
219
+	DUP #00 NEQ JMP JMP2r
220
+	DUP #01 SFT ORA
221
+	DUP #02 SFT ORA
222
+	DUP #04 SFT ORA
223
+	#1d MUL #05 SFT #00 SWP ,$lookup ADD2 PEK2
224
+	JMP2r
225
+
226
+	$lookup
227
+	[ 01 06 02 07 05 04 03 08 ]
228
+
229
+@opcodes
230
+	(
231
+		The code for this section is automatically generated, and needs to be
232
+		regenerated when the opcode list in src/assembler.c is updated.
233
+
234
+		After editing src/assembler.c, run "lua etc/assembler-trees.lua"
235
+		and this file will be edited automatically.
236
+
237
+		This is the first example of a binary tree in this code, so let's
238
+		explore them in general. The format of a tree node in memory is:
239
+
240
+		left-node* right-node* node-key-cstring binary-data
241
+
242
+		and the general algorithm is to compare the key you're looking for
243
+		against node-key-cstring, and move to the node pointed to by left-node*
244
+		or right-node* if the keys don't match. If your key sorts earlier than
245
+		use left-node*, otherwise go to right-node*. When you find a node that
246
+		matches your key, traverse-bintree gives you a pointer to the
247
+		binary-data straight after the node-key-cstring. This data can contain
248
+		anything you want: fixed length fields, executable code... in this case
249
+		of this opcode tree, we store nothing. traverse-bintree is passed the
250
+		maximum length of node-key-cstring, not including the zero, so the zero
251
+		can be omitted if the string is at that maximum length.
252
+
253
+		If the key isn't present in the tree, you'll eventually get to a node
254
+		where the left-node* or right-node* pointer you'll need to follow is
255
+		null (0000). traverse-bintree will give you the location of that
256
+		pointer, so if you want to insert another node, you can write it to the
257
+		heap and overwrite the pointer with the new node's location. This
258
+		approach works even if the tree is completely empty and the pointer
259
+		you've provided to the root node is null, since that pointer gets
260
+		updated to point to the first node without needing any special logic.
261
+
262
+		The ordering of nodes in memory is totally arbitrary, so for pre-
263
+		prepared trees like this one we can have our own meaning for the order
264
+		of the nodes. By ordering the opcodes by their byte value, we can find
265
+		the byte by subtracting $asm from the binary-data pointer and dividing
266
+		by seven (the size of each node). By multiplying the byte value by seven
267
+		and adding to $disasm, we get the opcode name when disassembling too.
268
+	)
269
+	$tree   .$root
270
+	$op-brk .$op-add .$op-dup $disasm [ BRK ] $asm
271
+	$op-nop .$op-mul .$op-ovr         [ NOP ]
272
+	$op-lit [ 0000 ] [ 0000 ]         [ LIT ]
273
+	$op-pop [ 0000 ] [ 0000 ]         [ POP ]
274
+	$op-dup .$op-div .$op-eor         [ DUP ]
275
+	$op-swp [ 0000 ] [ 0000 ]         [ SWP ]
276
+	$op-ovr .$op-ora .$op-pek         [ OVR ]
277
+	$op-rot .$op-pop .$op-sft         [ ROT ]
278
+	$op-equ .$op-brk .$op-jnz         [ EQU ]
279
+	$op-neq [ 0000 ] [ 0000 ]         [ NEQ ]
280
+	$op-gth [ 0000 ] [ 0000 ]         [ GTH ]
281
+	$root   .$op-equ .$op-pok         [ LTH ]
282
+	$op-gts .$op-gth .$op-jmp         [ GTS ]
283
+	$op-lts [ 0000 ] [ 0000 ]         [ LTS ]
284
+	        [ 0000 ] [ 0000 ]         [ ??? ]
285
+	        [ 0000 ] [ 0000 ]         [ ??? ]
286
+	$op-pek [ 0000 ] [ 0000 ]         [ PEK ]
287
+	$op-pok .$op-nop .$op-sth         [ POK ]
288
+	$op-ldr .$op-jsr .$op-lit         [ LDR ]
289
+	$op-str [ 0000 ] [ 0000 ]         [ STR ]
290
+	$op-jmp [ 0000 ] [ 0000 ]         [ JMP ]
291
+	$op-jnz .$op-gts .$op-ldr         [ JNZ ]
292
+	$op-jsr [ 0000 ] [ 0000 ]         [ JSR ]
293
+	$op-sth .$op-rot .$op-sub         [ STH ]
294
+	$op-add [ 0000 ] .$op-and         [ ADD ]
295
+	$op-sub .$op-str .$op-swp         [ SUB ]
296
+	$op-mul .$op-lts .$op-neq         [ MUL ]
297
+	$op-div [ 0000 ] [ 0000 ]         [ DIV ]
298
+	$op-and [ 0000 ] [ 0000 ]         [ AND ]
299
+	$op-ora [ 0000 ] [ 0000 ]         [ ORA ]
300
+	$op-eor [ 0000 ] [ 0000 ]         [ EOR ]
301
+	$op-sft [ 0000 ] [ 0000 ]         [ SFT ]
302
+
303
+@state-machine-pointers
304
+( normal mode 00 )
305
+.first-char-root .nyi
306
+( FIXME 01 )
307
+.nyi .nyi
308
+( FIXME 02 )
309
+.nyi .nyi
310
+( FIXME 04 )
311
+.nyi .nyi
312
+( FIXME 08 )
313
+.nyi .nyi
314
+( FIXME 10 )
315
+.nyi .nyi
316
+( literal data 20 )
317
+[ 0000 ] .nyi
318
+( FIXME 40 )
319
+.nyi .nyi
320
+( comment 80 )
321
+.first-char-) .ignore
322
+
323
+(
324
+	Next up, we have the tree of code corresponding to each token's
325
+	first character. Here we do have a binary payload, which is
326
+	the code to run when the assembler considers the token.
327
+
328
+	Some special assembler modes have their own trees. Since comments
329
+	have a very simple tree that only understands the end of comments,
330
+	we reuse the terminal branch of the main tree as the root of
331
+	the comment tree.
332
+)
333
+
334
+(
335
+	Left and right parentheses start and end comment sections. They use the
336
+	highest bit in assembler state, so they receive highest priority: it
337
+	doesn't matter what other bits are set, a comment's a comment.
338
+)
339
+
340
+@first-char-(     [ 0000 ]      .first-char-)   [ 28 ]
341
+	~assembler.state #80 ORA =assembler.state
342
+JMP2r
343
+
344
+@first-char-)     [ 0000 ]        [ 0000 ]      [ 29 ]
345
+	~assembler.state #7f AND =assembler.state
346
+JMP2r
347
+
348
+(
349
+	Left and right square brackets start and end literal data sections.
350
+)
351
+
352
+@first-char-[   .first-char-@   .first-char-]   [ 5b ]
353
+	~assembler.state #20 ORA =assembler.state
354
+JMP2r
355
+
356
+@first-char-]     [ 0000 ]        [ 0000 ]      [ 5d ]
357
+	~assembler.state #df AND =assembler.state
358
+JMP2r
359
+
360
+(
361
+	Ampersands introduce global labels, and define the scope for any
362
+	local labels that follow.
363
+)
364
+
365
+@first-char-@     [ 0000 ]        [ 0000 ]      [ 40 ]
366
+	~assembler.pass ^$scope JNZ
367
+	DUP2 #00 ,add-label JSR2
368
+
369
+	$scope
370
+	DUP2 ,strlen JSR2
371
+	DUP2 =assembler.scope-len POP
372
+	,assembler.scope SWP2 JMP2
373
+
374
+@first-char-root
375
+@first-char-=   .first-char-$   .first-char-^   [ 3d ]
376
+@first-char-"   .first-char-nul .first-char-#   [ 22 ]
377
+@first-char-#     [ 0000 ]        [ 0000 ]      [ 23 ]
378
+@first-char-$   .first-char-"   .first-char-,   [ 24 ]
379
+@first-char-%     [ 0000 ]      .first-char-(   [ 25 ]
380
+@first-char-,   .first-char-%   .first-char-dot [ 2c ]
381
+@first-char-dot   [ 0000 ]      .first-char-;   [ 2e ]
382
+@first-char-;     [ 0000 ]        [ 0000 ]      [ 3b ]
383
+@first-char-^   .first-char-[   .first-char-|   [ 5e ]
384
+@first-char-{     [ 0000 ]        [ 0000 ]      [ 7b ]
385
+@first-char-|   .first-char-{   .first-char-}   [ 7c ]
386
+@first-char-}     [ 0000 ]      .first-char-~   [ 7d ]
387
+@first-char-~     [ 0000 ]        [ 0000 ]      [ 7e ]
388
+
389
+@first-char-nul   [ 0000 ]        [ 0000 ]      [ 00 ]
390
+@ignore
391
+JMP2r
392
+
393
+@nyi
394
+	,$string =Console.string
395
+	HCF
396
+
397
+	$string [ Not 20 implemented 0a 00 ]
398
+
399
+(
400
+	Here's the big set of trees relating to labels. Starting from l-root, all
401
+	the devices are stored here, perhaps some helper functions in the future,
402
+	too.
403
+
404
+	left-node* right-node* node-key-cstring binary-data
405
+
406
+	The node-keys are terminated with NUL since, unlike the opcodes and first
407
+	characters, the keys are variable length.
408
+
409
+	The binary-data is either three or five bytes long:
410
+		flags value* [ subtree-pointer* ]
411
+
412
+	The flags byte is divided up into bits:
413
+
414
+	bit 0: 01 means load or store helpers can be used,
415
+	bit 1: 02 means the helpers use STR/LDR, 00 means they use POK/PEK;
416
+	bits 2-6 are reserved; and
417
+	bit 7: 80 means there is a subtree.
418
+
419
+	If there is a subtree, it is searched when the reference contains a dot.
420
+)
421
+
422
+@l-Console         [ 0000 ]          [ 0000 ]         [ Console 00 ]    [ 80 ] .Console .l-Console-root
423
+@l-Console-byte    [ 0000 ]          [ 0000 ]         [ byte 00 ]       [ 01 ] .Console.byte
424
+@l-Console-root
425
+@l-Console-char   .l-Console-byte   .l-Console-short  [ char 00 ]       [ 01 ] .Console.char
426
+@l-Console-short   [ 0000 ]         .l-Console-string [ short 00 ]      [ 03 ] .Console.short
427
+@l-Console-string  [ 0000 ]          [ 0000 ]         [ string 00 ]     [ 03 ] .Console.string
428
+@l-Controller     .l-Console        .l-File           [ Controller 00 ] [ 80 ] .Controller .l-Controller-root
429
+@l-Controller-root
430
+@l-Controller-p1   [ 0000 ]          [ 0000 ]         [ p1 00 ]         [ 01 ] .Controller.p1
431
+@l-File            [ 0000 ]          [ 0000 ]         [ File 00 ]       [ 80 ] .File .l-File-root
432
+@l-File-length     [ 0000 ]          [ 0000 ]         [ length 00 ]     [ 03 ] .File.length
433
+@l-File-root
434
+@l-File-load      .l-File-length    .l-File-name      [ load 00 ]       [ 03 ] .File.load
435
+@l-File-name       [ 0000 ]         .l-File-save      [ name 00 ]       [ 03 ] .File.name
436
+@l-File-save       [ 0000 ]          [ 0000 ]         [ save 00 ]       [ 03 ] .File.save
437
+@l-root
438
+@l-Keys           .l-Controller     .l-Screen         [ Keys 00 ]       [ 80 ] .Keys .l-Keys-root
439
+@l-Keys-root
440
+@l-Keys-key        [ 0000 ]          [ 0000 ]         [ key 00 ]        [ 01 ] .Keys.key
441
+@l-Mouse           [ 0000 ]          [ 0000 ]         [ Mouse 00 ]      [ 80 ] .Mouse .l-Mouse-root
442
+@l-Mouse-chord     [ 0000 ]          [ 0000 ]         [ chord 00 ]      [ 01 ] .Mouse.chord
443
+@l-Mouse-root
444
+@l-Mouse-state    .l-Mouse-chord    .l-Mouse-x        [ state 00 ]      [ 01 ] .Mouse.state
445
+@l-Mouse-x         [ 0000 ]         .l-Mouse-y        [ x 00 ]          [ 03 ] .Mouse.x
446
+@l-Mouse-y         [ 0000 ]          [ 0000 ]         [ y 00 ]          [ 03 ] .Mouse.y
447
+@l-Screen         .l-Mouse          .l-Sprite         [ Screen 00 ]     [ 80 ] .Screen .l-Screen-root
448
+@l-Screen-color    [ 0000 ]         .l-Screen-height  [ color 00 ]      [ 01 ] .Screen.color
449
+@l-Screen-height   [ 0000 ]          [ 0000 ]         [ height 00 ]     [ 03 ] .Screen.height
450
+@l-Screen-root
451
+@l-Screen-width   .l-Screen-color   .l-Screen-x       [ width 00 ]      [ 03 ] .Screen.width
452
+@l-Screen-x        [ 0000 ]         .l-Screen-y       [ x 00 ]          [ 03 ] .Screen.x
453
+@l-Screen-y        [ 0000 ]          [ 0000 ]         [ y 00 ]          [ 03 ] .Screen.y
454
+@l-Sprite          [ 0000 ]         .l-System         [ Sprite 00 ]     [ 80 ] .Sprite .l-Sprite-root
455
+@l-Sprite-addr     [ 0000 ]          [ 0000 ]         [ addr 00 ]       [ 03 ] .Sprite.addr
456
+@l-Sprite-root
457
+@l-Sprite-color   .l-Sprite-addr    .l-Sprite-x       [ color 00 ]      [ 01 ] .Sprite.color
458
+@l-Sprite-x        [ 0000 ]         .l-Sprite-y       [ x 00 ]          [ 03 ] .Sprite.x
459
+@l-Sprite-y        [ 0000 ]          [ 0000 ]         [ y 00 ]          [ 03 ] .Sprite.y
460
+@l-System          [ 0000 ]          [ 0000 ]         [ System 00 ]     [ 80 ] .System .l-System-root
461
+@l-System-b        [ 0000 ]          [ 0000 ]         [ b 00 ]          [ 03 ] .System.b
462
+@l-System-root
463
+@l-System-g       .l-System-b       .l-System-r       [ g 00 ]          [ 03 ] .System.g
464
+@l-System-r        [ 0000 ]          [ 0000 ]         [ r 00 ]          [ 03 ] .System.r
465
+