-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathasm.neon
563 lines (522 loc) · 24.9 KB
/
asm.neon
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
% ToDo: RECORDs - Write test code that demonstrates REOCRDs, construction, and usage, as well as exportation of RECORDs.
% ToDo: CONSTNAT - Write test code that demonstrates the use of the CONST keyword, as well as the exportation of CONSTANT values.
IMPORT ByteCode
IMPORT file
IMPORT io
IMPORT os
IMPORT sys
IMPORT string
TYPE FunctionInfo IS ByteCode.FunctionInfo
TYPE Exception IS ByteCode.ExceptInfo
TYPE Bytecode IS ByteCode.Bytecode
TYPE Pair IS ByteCode.Pair
% Command line option variables
VAR bQuiet: Boolean
% Setup Bytecode generator
VAR code: Bytecode := Bytecode()
LET Tokens: Dictionary<Number> := {
"ENTER": 0,
"LEAVE": 1,
"PUSHB": 2, %| push boolean immediate |%
"PUSHN": 3, %| push number immediate |%
"PUSHS": 4, %| push string immediate |%
"PUSHPG": 5, %| push pointer to global |%
"PUSHPPG": 6, %| push pointer to predefined global |%
"PUSHPMG": 7, %| push pointer to module global |%
"PUSHPL": 8, %| push pointer to local |%
"PUSHPOL": 9, %| push pointer to outer local |%
"PUSHI": 10, %| push 32-bit integer immediate |%
"LOADB": 11, %| load boolean |%
"LOADN": 12, %| load number |%
"LOADS": 13, %| load string |%
"LOADA": 14, %| load array |%
"LOADD": 15, %| load dictionary |%
"LOADP": 16, %| load pointer |%
"STOREB": 17, %| store boolean |%
"STOREN": 18, %| store number |%
"STORES": 19, %| store string |%
"STOREA": 20, %| store array |%
"STORED": 21, %| store dictionary |%
"STOREP": 22, %| store pointer |%
"NEGN": 23, %| negate number |%
"ADDN": 24, %| add number |%
"SUBN": 25, %| subtract number |%
"MULN": 26, %| multiply number |%
"DIVN": 27, %| divide number |%
"MODN": 28, %| modulo number |%
"EXPN": 29, %| exponentiate number |%
"EQB": 30, %| compare equal boolean |%
"NEB": 31, %| compare unequal boolean |%
"EQN": 32, %| compare equal number |%
"NEN": 33, %| compare unequal number |%
"LTN": 34, %| compare less number |%
"GTN": 35, %| compare greater number |%
"LEN": 36, %| compare less equal number |%
"GEN": 37, %| compare greater equal number |%
"EQS": 38, %| compare equal string |%
"NES": 39, %| compare unequal string |%
"LTS": 40, %| compare less string |%
"GTS": 41, %| compare greater string |%
"LES": 42, %| compare less equal string |%
"GES": 43, %| compare greater equal string |%
"EQA": 44, %| compare equal array |%
"NEA": 45, %| compare unequal array |%
"EQD": 46, %| compare equal dictionary |%
"NED": 47, %| compare unequal dictionary |%
"EQP": 48, %| compare equal pointer |%
"NEP": 49, %| compare unequal pointer |%
"ANDB": 50, %| and boolean |%
"ORB": 51, %| or boolean |%
"NOTB": 52, %| not boolean |%
"INDEXAR": 53, %| index arrayfor read |%
"INDEXAW": 54, %| index arrayfor write |%
"INDEXAV": 55, %| index array value |%
"INDEXAN": 56, %| index array value,no except |%
"INDEXDR": 57, %| index dictionary for read |%
"INDEXDW": 58, %| index dictionary for write |%
"INDEXDV": 59, %| index dictionary value |%
"INA": 60, %| in array |%
"IND": 61, %| in dictionary |%
"CALLP": 62, %| call predefined |%
"CALLF": 63, %| call function |%
"CALLMF": 64, %| call module function |%
"CALLI": 65, %| call indirect |%
"JUMP": 66, %| unconditional jump |%
"JF": 67, %| jump if false |%
"JT": 68, %| jump if true |%
"JFCHAIN": 69, %| jump and drop next if false |%
"DUP": 70, %| duplicate |%
"DUPX1": 71, %| duplicate under second value |%
"DROP": 72, %| drop |%
"RET": 73, %| return |%
"CALLE": 74, %| call external |%
"CONSA": 75, %| construct array |%
"CONSD": 76, %| construct dictionary |%
"EXCEPT": 77, %| throw exception |%
"CLREXC": 78, %| clear exception |%
"ALLOC": 79, %| allocate record |%
"PUSHNIL": 80, %| push nil pointer |%
"JNASSERT": 81, %| jump if assertions disabled |%
"RESETC": 82, %| reset cell |%
"PUSHPEG": 83, %| push pointer to external global |%
}
LET Directives: Dictionary<Number> := {
".GlobalSize": 1, %| Sets the global variable size |%
".const": 2, %| Sets a value to an Identifier |%
".begin": 3, %| Declare Function |%
".end": 4, %| End function Declration |%
".import": 5, %| Import a Neon module |%
".exception": 6, %| Declare an exception |%
".try": 7, %| Begin try block |%
".catch": 8, %| Catch a thrown exception |%
".finish": 9, %| finish catch handler |%
".local": 10, %| Create a local variable |%
}
IF sys.args.size() <= 1 THEN
LET usage: String := @@"Neon Bytecode Assembler v.8 - https://github.com/ghewgill/neon-lang
Usage:
neonx asm.neonx [options] file1 file2 file3...filex
Where [options] is one or more of:
-q Quiet
"@@
print(usage)
sys.exit(1)
END IF
VAR sourceFiles: Array<String> := []
FOREACH o IN sys.args[1 TO LAST] DO
IF o[FIRST] = "-" OR o[FIRST] = "/" THEN
LET option: String := o[1 TO LAST]
IF option = "q" OR option = "Q" THEN
bQuiet := TRUE
END IF
ELSE
sourceFiles.append(o)
END IF
END FOREACH
FOREACH f IN sourceFiles DO
assemble(f)
END FOREACH
FUNCTION makeOutputFilename(s: String): String
VAR dir, fn: String := ""
file.pathSplit(s, OUT dir, OUT fn)
IF NOT string.hasSuffix(fn, ".neona") THEN
error("Not a valid neon assembly file.", 1)
END IF
RETURN file.pathJoin(dir, fn[FIRST TO LAST-6] & ".neonx")
END FUNCTION
FUNCTION parseString(s: String): String
VAR i: Number := 0
VAR r: String := ""
WHILE i <= s.length() DO
VAR c: String := s[i]
IF c = "\"" THEN
INC i
WHILE i < s.length() DO
c := s[i]
INC i
IF c = "\"" THEN
RETURN r
END IF
IF i >= s.length() THEN
error("unterminated string constant", 1)
END IF
IF c = "\n" THEN
error("unterminated string constant", 1)
END IF
r.append(c)
END WHILE
END IF
INC i
r.append(c)
END WHILE
RETURN r
END FUNCTION
TYPE Label IS Dictionary<Number>
TYPE JumpTable IS RECORD
location: Number
label: String
line: Number
END RECORD
TYPE FunctionData IS RECORD
name: String
info: FunctionInfo
END RECORD
FUNCTION PathSupport(source_path: String): Array<String>
VAR paths: Array<String> := []
IF source_path.length() > 0 THEN
paths.append(source_path)
END IF
paths.append("./")
VAR neonpath: String := os.getenv("NEONPATH")
IF neonpath.length() > 0 THEN
IF neonpath[LAST] = file.Separator THEN
neonpath := neonpath[FIRST TO LAST-1]
END IF
paths.append(neonpath)
END IF
TRY
VAR inf: Array<String> := file.readLines(".neonpath")
FOREACH p IN inf DO
paths.append(p)
END FOREACH
EXCEPTION file.FileOpenException DO
RETURN paths
END TRY
RETURN paths
END FUNCTION
FUNCTION error(s: String, exit: Number DEFAULT 1)
io.fprint(io.stderr, s)
sys.exit(exit)
END FUNCTION
FUNCTION FindModule(name: String): String
VAR paths: Array<String> := PathSupport(os.getcwd())
FOREACH path IN paths DO
LET files: Array<String> := file.files(path)
FOREACH f IN files DO
IF name = f THEN
RETURN file.pathJoin(path, name)
END IF
END FOREACH
END FOREACH
error("Could not locate module \(name).", 2)
RETURN ""
END FUNCTION
FUNCTION getModule(fn: String): Bytecode
VAR mod: Bytecode := Bytecode()
TRY
VAR bytes: Bytes := file.readBytes(FindModule(fn))
mod := mod.fromBytes(bytes)
EXCEPTION file.FileOpenException DO
error("Module \(fn) was not found.", CURRENT_EXCEPTION.code)
EXCEPTION ByteCode.BytecodeException DO
error("Error loading module \(fn). Module is not valid Neon Bytecode.", 3)
END TRY
RETURN mod
END FUNCTION
FUNCTION assemble(fn: String)
LET outfn: String := makeOutputFilename(fn)
LET lines: Array<String> := file.readLines(fn)
VAR Imports: Array<String> := []
VAR Functions: Dictionary<FunctionData> := {}
VAR Constants: Dictionary<String> := {}
VAR Locals: Dictionary<String> := {}
VAR Labels: Dictionary<Number> := {}
VAR Modules: Dictionary<Bytecode> := {}
VAR Fixups: Array<JumpTable> := []
VAR ExceptionHandlers: Dictionary<Exception> := {}
VAR CurrentException: Array<String> := []
VAR Exceptions: Array<String> := []
VAR numLines: Number := lines.size()
code.reset()
FOREACH s IN lines INDEX i DO
VAR line: String := ""
VAR ln: String := string.trim(s)
IF ln = "" OR ln[FIRST] = ";" THEN % ignore blank, and commented out lines.
NEXT FOREACH
END IF
IF string.find(ln, ";") > 0 THEN
ln := string.trim(ln[FIRST TO string.find(ln, ";")-1]) % Eat comments before processing the code.
END IF
IF ln[0] = ":" THEN
IF ln[1 TO LAST] IN Labels THEN
error("Duplicate label definition on line \(i).", 1)
END IF
Labels[ln[1 TO LAST]] := code.getCurrentAddress()
NEXT FOREACH
END IF
VAR ref: Number := 0
LET words: Array<String> := string.split(string.trim(ln), " ")
IF words.size() <= 0 THEN
words.append(string.trim(ln))
END IF
IF string.hasPrefix(words[0], ".") THEN
LET directives: Array<String> := string.split(string.trim(ln), ":")
IF directives[0] IN Directives THEN
CASE directives[0]
WHEN ".GlobalSize" DO
code.global_size := code.global_size + num(string.trim(directives[1]))
WHEN ".const", ".local" DO
LET values: Array<String> := string.split(string.trim(directives[1]), " ")
IF values.size() < 2 THEN
error("Missing value: Expected value; line \(i).", 1)
END IF
IF directives[0] = ".const" THEN
IF values[0] IN Constants THEN
error("Duplicate inentifier \"\(values[0])\" on line \(i)", 1)
END IF
Constants[values[0]] := string.trim(values[1])
ELSE
IF values[0] IN Locals THEN
error("Duplicate inentifier \"\(values[0])\" on line \(i)", 1)
END IF
Locals[values[0]] := string.trim(values[1])
END IF
WHEN ".begin" DO
VAR f: FunctionData := FunctionData()
LET name: String := string.trim(directives[1])
IF name IN Functions THEN
error("Function already declared. Error on line \(i).", 1)
END IF
f.name := name
f.info.name := code.addString(name)
Functions[name] := f % Get the function added to the function array first.
Labels[name] := code.getCurrentAddress()
Functions[name].info.entry := Labels[name]
WHEN ".import" DO
IF string.trim(directives[1]) IN Imports THEN
error("Duplicate import directive. \(i)", 1)
END IF
Imports.append(string.trim(directives[1]))
Modules[Imports[LAST]] := getModule(Imports[LAST] & ".neonx")
VAR p: Pair := Pair()
p.first := code.addString(string.trim(directives[1]))
p.second := string.repeat("0", 32)
code.imports.append(p)
WHEN ".exception" DO
IF string.trim(directives[1]) IN Exceptions THEN
error("Duplicate exception declared. Line: \(i)", 1)
END IF
Exceptions.append(string.trim(directives[1]))
WHEN ".try" DO
CurrentException.append(string.trim(directives[1]))
IF CurrentException[LAST] IN ExceptionHandlers THEN
error("Duplicate structured exception handler declared. Each block must have a unique name. Line: \(i)", 1)
END IF
VAR e: Exception := Exception()
e.start := code.getCurrentAddress()
ExceptionHandlers[CurrentException[LAST]] := e
WHEN ".catch" DO
LET excp: String := string.trim(directives[1])
IF excp IN Exceptions THEN
ExceptionHandlers[CurrentException[LAST]].end := code.getCurrentAddress()
ExceptionHandlers[CurrentException[LAST]].handler := code.getCurrentAddress()
ExceptionHandlers[CurrentException[LAST]].excid := code.addString(excp)
code.exceptions.append(ExceptionHandlers[CurrentException[LAST]])
ELSE
error("Exception \"\(excp)\" not declared. Line(i).", 1)
END IF
WHEN ".finish" DO
CurrentException := CurrentException[FIRST TO LAST-1]
END CASE
END IF
NEXT FOREACH
END IF
IF words[0] IN Tokens THEN
line.append("\(code.getCurrentAddress())\t")
code.code.append(Tokens[words[0]])
line.append(words[0])
LET operand: String := string.trim(ln[string.find(ln, " ") + 1 TO LAST])
CASE words[0]
WHEN "PUSHN" DO % push number immediate
IF operand IN Constants THEN
ref := code.addString(Constants[operand])
ELSE
IF operand IN Locals THEN
ref := num(Locals[operand])
END IF
ref := code.addString(operand)
END IF
line.append(" \(ref)")
code.referenceString(ref)
WHEN "CALLP", "CALLMF" DO % call predefined function, call module function
VAR dest: String := ""
VAR module: String := ""
IF string.find(operand, ".") > 0 THEN
% Attempting to call a function in an imported module.
module := operand[FIRST TO string.find(operand, ".")-1]
IF module IN Imports THEN
ref := code.addString(module)
IF words[0] = "CALLMF" THEN
dest := module % Setup Neon module call
END IF
ELSE
error("Unknown module, or module not imported. Line \(i)")
END IF
ELSE
% Attempting to call a predefined function.
dest := operand
END IF
ref := code.addString(dest)
line.append(" \(dest) : (\(ref))")
code.referenceString(ref)
IF words[0] = "CALLMF" THEN
%LET bc: Bytecode := getModule("\(module).neonx")
LET bc: Bytecode := Modules[module]
FOREACH bcf IN bc.functions DO
IF bc.strtable[bcf.name] = operand[string.find(operand, ".")+1 TO LAST] THEN
code.referenceAddress(bcf.entry)
EXIT FOREACH
END IF
END FOREACH
END IF
WHEN "PUSHPPG" DO % push pointer to predefined global
VAR dest: String := ""
VAR module: String := ""
IF string.find(operand, ".") > 0 THEN
% Global variable in a module
dest := operand[string.find(operand, ".") + 1 TO LAST]
module := operand[FIRST TO string.find(operand, ".")-1]
IF module IN Imports THEN
IF module IN [ "sys", "io" ] AND dest IN [ "args", "stdout", "stdin", "stderr" ] THEN
%LET bc: Bytecode := Modules[module]
%ref := bc.getStringIndex("\(module)$\(dest)") % Create an actual Neon-syntax string.
%IF ref = -1 THEN
% error("Variable \(dest) does not exist in module \(module). Line: \(i)", 1)
%END IF
ELSE
error("Variable \(dest) does not exist in module \(module). Line: \(i)", 1)
END IF
ELSE
error("Unknown module, or module not imported. Line \(i)")
END IF
ELSE
% Attempting to call a predefined function.
dest := operand
END IF
ref := code.addString("\(module)$\(dest)")
line.append(" \"\(module).\(dest)[\(ref)]")
code.referenceString(ref)
WHEN "PUSHS" DO % push string immediate
IF operand[0] # "\"" THEN
error("Expected \".")
END IF
ref := code.addString(parseString(operand))
line.append(" \(ref)")
code.referenceString(ref)
WHEN "PUSHB" DO
IF operand IN Constants THEN
ref := num(Constants[operand])
ELSE
ref := num(operand)
END IF
ASSERT ref <= 0x1
line.append(" \(ref)")
code.referenceBoolean((IF ref = 0 THEN FALSE ELSE TRUE))
WHEN "PUSHPG", "CONSA" DO % push pointer to global, Construct Array
IF operand IN Constants THEN
ref := num(Constants[operand])
ELSE
ref := num(operand)
END IF
ASSERT ref <= 0xFFFFFFFF
line.append(" \(ref)")
code.referenceVariable(ref)
WHEN "PUSHPL" DO
IF operand IN Locals THEN
ref := num(Locals[operand])
ELSE
ref := num(operand)
END IF
ASSERT ref <= 0xFFFFFFFF
line.append(" \(ref)")
code.referenceVariable(ref)
WHEN "STOREN" DO % store number
line.append("") % TODO: print the variable name, or imediate value if there is one.
WHEN "LOADN" DO % load number
line.append("") % TODO: print the variable name, or imediate value if there is one.
WHEN "ENTER" DO
VAR frames: Array<String> := string.split(ln[words[0].length() + 1 TO LAST], ",")
IF frames.size() # 2 THEN
error("Incorrect operands for opcode: \(words[0]) line \(i).", 1)
END IF
code.referenceAddress(num(frames[0]))
code.referenceAddress(num(frames[1]))
line.append(" \(frames[0]), \(frames[1])")
WHEN "EXCEPT" DO
IF operand[0] # "\"" THEN
error("Expected \".")
END IF
IF parseString(operand) IN Exceptions THEN
ref := code.addString(parseString(operand))
ELSE
error("Exception not declared. Line\(i).")
END IF
line.append(" \(ref)")
code.referenceString(ref)
WHEN "JUMP", "JF", "JT", "CALLF" DO
IF operand IN Labels THEN
ref := Labels[operand]
ELSE
LET fixup: JumpTable := JumpTable(location WITH code.getCurrentAddress(),
label WITH operand,
line WITH i)
ref := 0
Fixups.append(fixup)
line.append(" [ForwardDecl]: ")
END IF
code.referenceAddress(ref)
line.append(" \(ref)")
WHEN "LEAVE" DO
Locals := {}
END CASE
ELSE
error("Invalid operand on line \(i): \(ln)")
END IF
IF NOT bQuiet THEN
print(line)
END IF
END FOREACH
IF Functions.size() > 0 THEN
IF NOT bQuiet THEN
print("Linking functions...")
END IF
FOREACH f IN Functions.keys() DO
code.functions.append(Functions[f].info)
END FOREACH
END IF
FOREACH f IN Fixups DO
IF f.label IN Labels THEN
code.fixupAddress(f.location, Labels[f.label])
ELSE
io.fprint(io.stderr, "Undefined label: \(f.label) on line \(f.line).")
sys.exit(1)
END IF
END FOREACH
IF CurrentException.size() > 0 THEN
error("Missing .finish operand for exception \(CurrentException[LAST]).", 1)
END IF
IF NOT bQuiet THEN
print("Compiled \(numLines) lines.")
END IF
code.toFile(outfn)
END FUNCTION