kompiler 0.3.0.pre.3 → 0.3.0.pre.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/kompiler/arch_manager.rb +1 -1
- data/lib/kompiler/compiler_functions.rb +107 -212
- data/lib/kompiler/config.rb +29 -0
- data/lib/kompiler/directives.rb +354 -4
- data/lib/kompiler/math_ast.rb +665 -0
- data/lib/kompiler/parsers.rb +116 -45
- data/lib/kompiler.rb +3 -1
- metadata +4 -2
data/lib/kompiler/directives.rb
CHANGED
@@ -14,9 +14,12 @@ end
|
|
14
14
|
keyword: "zeros",
|
15
15
|
func: lambda do |operands, state|
|
16
16
|
raise "Incorrect use of the \"zeros\" directive." if operands.size > 1 || (operands[0] && operands[0][:type] != "immediate")
|
17
|
+
|
17
18
|
n_zeros = operands[0][:value]
|
18
19
|
state[:current_address] += n_zeros
|
19
20
|
state[:parsed_lines] << {type: "insert", bits: (n_zeros*8).times.map{0} }
|
21
|
+
state[:line_i] += 1
|
22
|
+
|
20
23
|
state
|
21
24
|
end
|
22
25
|
},
|
@@ -30,6 +33,7 @@ end
|
|
30
33
|
|
31
34
|
state[:parsed_lines] << {type: "insert", bits: insert_bits, address: state[:current_address]}
|
32
35
|
state[:current_address] += insert_bytes.size
|
36
|
+
state[:line_i] += 1
|
33
37
|
|
34
38
|
state
|
35
39
|
end
|
@@ -39,6 +43,8 @@ end
|
|
39
43
|
func: lambda do |operands, state|
|
40
44
|
raise "Incorrect use of the \"align\" directive." if operands.size > 1 || (operands[0] && operands[0][:type] != "immediate")
|
41
45
|
|
46
|
+
state[:line_i] += 1
|
47
|
+
|
42
48
|
alignment = operands[0][:value]
|
43
49
|
to_add = alignment - (state[:current_address] % alignment)
|
44
50
|
|
@@ -72,6 +78,7 @@ end
|
|
72
78
|
|
73
79
|
# Add the label definition
|
74
80
|
state[:parsed_lines] << {type: "label", label_name: label_name, label_address: label_value, address: state[:current_address]}
|
81
|
+
state[:line_i] += 1
|
75
82
|
|
76
83
|
state
|
77
84
|
end
|
@@ -89,6 +96,7 @@ end
|
|
89
96
|
# Insert 64 bits of the value into the program
|
90
97
|
state[:current_address] += 8
|
91
98
|
state[:parsed_lines] << {type: "insert", bits: value_bits}
|
99
|
+
state[:line_i] += 1
|
92
100
|
|
93
101
|
state
|
94
102
|
end
|
@@ -106,6 +114,7 @@ end
|
|
106
114
|
# Insert 64 bits of the value into the program
|
107
115
|
state[:current_address] += 4
|
108
116
|
state[:parsed_lines] << {type: "insert", bits: value_bits}
|
117
|
+
state[:line_i] += 1
|
109
118
|
|
110
119
|
state
|
111
120
|
end
|
@@ -113,8 +122,8 @@ end
|
|
113
122
|
{
|
114
123
|
keyword: "bytes",
|
115
124
|
func: lambda do |operands, state|
|
125
|
+
raise "Incorrect use of the \"bytes\" directive." if (operands.size != 2) || (operands[0][:type] != "immediate" || operands[1][:type] != "immediate")
|
116
126
|
|
117
|
-
raise "Incorrect use of the \"bytes\" directive." if (operands.size != 2) || (operands[0][:type] != "immediate" && operands[1][:type] != "immediate")
|
118
127
|
|
119
128
|
n_bytes = operands[0][:value]
|
120
129
|
value = operands[1][:value]
|
@@ -124,6 +133,7 @@ end
|
|
124
133
|
# Insert the input amount of bytes of the value into the program
|
125
134
|
state[:current_address] += n_bytes
|
126
135
|
state[:parsed_lines] << {type: "insert", bits: value_bits}
|
136
|
+
state[:line_i] += 1
|
127
137
|
|
128
138
|
state
|
129
139
|
end
|
@@ -132,11 +142,12 @@ end
|
|
132
142
|
keyword: "set_pc",
|
133
143
|
func: lambda do |operands, state|
|
134
144
|
|
135
|
-
raise "Incorrect use of the \"set_pc\" directive." if (operands.size != 1) || (operands[0][:type] != "immediate")
|
145
|
+
raise "Incorrect use of the \"set_pc\" directive." if (operands.size != 1) || (operands[0][:type] != "immediate")
|
136
146
|
|
137
147
|
new_pc = operands[0][:value]
|
138
|
-
|
148
|
+
|
139
149
|
state[:current_address] = new_pc
|
150
|
+
state[:line_i] += 1
|
140
151
|
|
141
152
|
state
|
142
153
|
end
|
@@ -155,7 +166,346 @@ end
|
|
155
166
|
|
156
167
|
state[:current_address] += file_content.bytes.size
|
157
168
|
state[:parsed_lines] << {type: "insert", byte_string: file_content}
|
169
|
+
|
170
|
+
state[:line_i] += 1
|
171
|
+
|
172
|
+
state
|
173
|
+
end
|
174
|
+
},
|
175
|
+
{
|
176
|
+
keyword: ["load_end", "include_end"],
|
177
|
+
func: lambda do |operands, state|
|
178
|
+
|
179
|
+
raise "Incorrect use of the \"load_end\" directive" if (operands.size != 1) || (operands[0][:type] != "string")
|
158
180
|
|
181
|
+
file_selector = operands[0][:string]
|
182
|
+
|
183
|
+
files_to_load = Dir[file_selector]
|
184
|
+
|
185
|
+
# Create the loaded_files state entry if it doesn't exist
|
186
|
+
state[:extra_state][:loaded_files] = Array.new if !state[:extra_state].keys.include?(:loaded_files)
|
187
|
+
|
188
|
+
# Select only files that haven't been previously loaded
|
189
|
+
files_to_load.select!{|file_name| !state[:extra_state][:loaded_files].include?(file_name)}
|
190
|
+
|
191
|
+
# Add the files that will be loaded to the state entry
|
192
|
+
state[:extra_state][:loaded_files] += files_to_load
|
193
|
+
|
194
|
+
files_to_load.each do |load_filename|
|
195
|
+
file_content = File.read load_filename
|
196
|
+
|
197
|
+
file_lines = Kompiler::Parsers.get_code_lines(file_content)
|
198
|
+
|
199
|
+
state[:lines] += file_lines
|
200
|
+
end
|
201
|
+
|
202
|
+
state[:line_i] += 1
|
203
|
+
|
204
|
+
state
|
205
|
+
end
|
206
|
+
},
|
207
|
+
{
|
208
|
+
keyword: ["load", "include"],
|
209
|
+
func: lambda do |operands, state|
|
210
|
+
|
211
|
+
raise "Incorrect use of the \"load\" directive" if (operands.size != 1) || (operands[0][:type] != "string")
|
212
|
+
|
213
|
+
file_selector = operands[0][:string]
|
214
|
+
|
215
|
+
files_to_load = Dir[file_selector]
|
216
|
+
|
217
|
+
# Create the loaded_files state entry if it doesn't exist
|
218
|
+
state[:extra_state][:loaded_files] = Array.new if !state[:extra_state].keys.include?(:loaded_files)
|
219
|
+
|
220
|
+
# Select only files that haven't been previously loaded
|
221
|
+
files_to_load.select!{|file_name| !state[:extra_state][:loaded_files].include?(file_name)}
|
222
|
+
|
223
|
+
# Add the files that will be loaded to the state entry
|
224
|
+
state[:extra_state][:loaded_files] += files_to_load
|
225
|
+
|
226
|
+
total_load_lines = []
|
227
|
+
|
228
|
+
files_to_load.each do |load_filename|
|
229
|
+
file_content = File.read load_filename
|
230
|
+
|
231
|
+
file_lines = Kompiler::Parsers.get_code_lines(file_content)
|
232
|
+
|
233
|
+
total_load_lines += file_lines
|
234
|
+
end
|
235
|
+
|
236
|
+
# Move to the next line
|
237
|
+
state[:line_i] += 1
|
238
|
+
|
239
|
+
# Insert the lines at the correct place
|
240
|
+
state[:lines] = state[:lines][0...state[:line_i]] + total_load_lines + state[:lines][state[:line_i]..]
|
241
|
+
|
242
|
+
state
|
243
|
+
end
|
244
|
+
},
|
245
|
+
{
|
246
|
+
keyword: "value",
|
247
|
+
collect_operands: false,
|
248
|
+
func: lambda do |_, state|
|
249
|
+
_, operands = Kompiler::Parsers.extract_instruction_parts(state[:lines][state[:line_i]])
|
250
|
+
|
251
|
+
raise "Incorrect use of the .value directive - expected 2 operands: Program build not possible" if operands.size != 2
|
252
|
+
|
253
|
+
value_name = operands[0]
|
254
|
+
|
255
|
+
# Check that the name is made out of allowed characters
|
256
|
+
if value_name.each_char.map{|char| Kompiler::Config.keyword_chars.include?(char)}.include?(false)
|
257
|
+
raise "Incorrect use of the .value directive - the value name must contain only keyword characters: Program build not possible"
|
258
|
+
end
|
259
|
+
|
260
|
+
value_def = operands[1]
|
261
|
+
|
262
|
+
scan_lines = state[:lines][(state[:line_i] + 1)..]
|
263
|
+
|
264
|
+
scan_lines.each_with_index do |line, line_i|
|
265
|
+
|
266
|
+
start_i = 0
|
267
|
+
|
268
|
+
# Loop through each character starting position
|
269
|
+
while start_i < line.size
|
270
|
+
|
271
|
+
# Skip whitespace characters
|
272
|
+
if Kompiler::Config.whitespace_chars.include?(line[start_i])
|
273
|
+
start_i += 1
|
274
|
+
next
|
275
|
+
end
|
276
|
+
|
277
|
+
# Skip string definitions
|
278
|
+
if ['"', "'"].include? line[start_i]
|
279
|
+
str, parsed_length = Kompiler::Parsers.parse_str line[start_i..]
|
280
|
+
start_i += parsed_length
|
281
|
+
next
|
282
|
+
end
|
283
|
+
|
284
|
+
cut_line = line[start_i..]
|
285
|
+
|
286
|
+
value_word_found = false
|
287
|
+
|
288
|
+
# Check if the value name works
|
289
|
+
|
290
|
+
if cut_line.start_with?(value_name) # Check that the piece of text starts with the value name
|
291
|
+
if !Kompiler::Config.keyword_chars.include?(cut_line[value_name.size]) # Check that the cut text is a full word. This will not fire when value_name='arg', but the cut text is 'arg1'
|
292
|
+
value_word_found = true # Indicate that a replacement was found
|
293
|
+
|
294
|
+
scan_lines[line_i] = scan_lines[line_i][...start_i] + value_def + (scan_lines[line_i][(start_i+value_name.size)..] || "")
|
295
|
+
line = scan_lines[line_i]
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
# Check if the value name wasn't detected
|
300
|
+
# If not, skip the text until the next whitespace character
|
301
|
+
if !value_word_found
|
302
|
+
while start_i < line.size && Kompiler::Config.keyword_chars.include?(line[start_i])
|
303
|
+
start_i += 1
|
304
|
+
end
|
305
|
+
start_i += 1 # Move one more character
|
306
|
+
end
|
307
|
+
|
308
|
+
end
|
309
|
+
|
310
|
+
end
|
311
|
+
|
312
|
+
|
313
|
+
state[:extra_state][:values] = Array.new if !state[:extra_state].keys.include?(:values)
|
314
|
+
|
315
|
+
state[:extra_state][:values] << {name: value_name, def_value: value_def}
|
316
|
+
|
317
|
+
|
318
|
+
state[:lines] = state[:lines][..state[:line_i]] + scan_lines
|
319
|
+
|
320
|
+
state[:line_i] += 1
|
321
|
+
|
322
|
+
state
|
323
|
+
end
|
324
|
+
},
|
325
|
+
{
|
326
|
+
keyword: "macro",
|
327
|
+
collect_operands: false,
|
328
|
+
func: lambda do |_, state|
|
329
|
+
line_i = state[:line_i]
|
330
|
+
|
331
|
+
def_line = state[:lines][line_i]
|
332
|
+
|
333
|
+
# First: collect the part after ".macro"
|
334
|
+
|
335
|
+
char_i = 0
|
336
|
+
# Skip the whitespace before .macro
|
337
|
+
while char_i < def_line.size && Kompiler::Config.whitespace_chars.include?(def_line[char_i])
|
338
|
+
char_i += 1
|
339
|
+
end
|
340
|
+
# Skip the .macro
|
341
|
+
while char_i < def_line.size && Kompiler::Config.keyword_chars.include?(def_line[char_i])
|
342
|
+
char_i += 1
|
343
|
+
end
|
344
|
+
# Skip the whitespace after .macro
|
345
|
+
while char_i < def_line.size && Kompiler::Config.whitespace_chars.include?(def_line[char_i])
|
346
|
+
char_i += 1
|
347
|
+
end
|
348
|
+
|
349
|
+
# If the end of the line was reached, throw an error
|
350
|
+
raise "Incorrect .macro definition" if char_i == def_line.size
|
351
|
+
|
352
|
+
# Now char_i contains the first index of the text after .macro
|
353
|
+
|
354
|
+
macro_def = def_line[char_i..]
|
355
|
+
|
356
|
+
# Second: extract the macro's name
|
357
|
+
|
358
|
+
macro_name = ""
|
359
|
+
|
360
|
+
while char_i < def_line.size && Kompiler::Config.keyword_chars.include?(def_line[char_i])
|
361
|
+
macro_name << def_line[char_i]
|
362
|
+
char_i += 1
|
363
|
+
end
|
364
|
+
|
365
|
+
# Third: extract the operand names (code taken from parse_instruction_line in parsers.rb)
|
366
|
+
|
367
|
+
arg_names = Kompiler::Parsers.extract_instruction_operands(def_line[char_i..])
|
368
|
+
|
369
|
+
# Make sure that the arg names are unique
|
370
|
+
raise "Macro definition error - arguments cannot have the same name: Program build not possible" if arg_names.size != arg_names.uniq.size
|
371
|
+
|
372
|
+
# Extract the macro inside definition
|
373
|
+
|
374
|
+
line_i = state[:line_i] + 1
|
375
|
+
def_lines = []
|
376
|
+
|
377
|
+
whitespace_regexp = /[#{Kompiler::Config.whitespace_chars.join("|")}]*/
|
378
|
+
|
379
|
+
endmacro_regexp = /\A#{whitespace_regexp}\.?endmacro#{whitespace_regexp}\z/
|
380
|
+
|
381
|
+
while line_i < state[:lines].size
|
382
|
+
break if state[:lines][line_i].match? endmacro_regexp # Check if it's an end macro instruction
|
383
|
+
def_lines << state[:lines][line_i]
|
384
|
+
line_i += 1
|
385
|
+
end
|
386
|
+
|
387
|
+
|
388
|
+
# Find insert indexes for each argument
|
389
|
+
arg_insert_locations = arg_names.map{|arg_name| [arg_name, []]}.to_h
|
390
|
+
|
391
|
+
def_lines.each_with_index do |line, line_i|
|
392
|
+
|
393
|
+
start_i = 0
|
394
|
+
|
395
|
+
# Loop through each character starting position
|
396
|
+
while start_i < line.size
|
397
|
+
|
398
|
+
# Skip whitespace characters
|
399
|
+
if Kompiler::Config.whitespace_chars.include?(line[start_i])
|
400
|
+
start_i += 1
|
401
|
+
next
|
402
|
+
end
|
403
|
+
|
404
|
+
# Skip string definitions
|
405
|
+
if ['"', "'"].include? line[start_i]
|
406
|
+
str, parsed_length = Kompiler::Parsers.parse_str line[start_i..]
|
407
|
+
start_i += parsed_length
|
408
|
+
next
|
409
|
+
end
|
410
|
+
|
411
|
+
cut_line = line[start_i..]
|
412
|
+
|
413
|
+
arg_found = false
|
414
|
+
|
415
|
+
# Check if one of the argument names works
|
416
|
+
arg_names.each do |arg_name|
|
417
|
+
next if !cut_line.start_with?(arg_name) # Skip the argument if the line doesn't begin with it
|
418
|
+
next if Kompiler::Config.keyword_chars.include?(cut_line[arg_name.size]) # Skip if the argument is a partial word. So, for the argument 'arg', this will skip in the case of 'arg1'
|
419
|
+
# Here if the argument name should be replaced with the contents
|
420
|
+
arg_found = true # Indicate that a replacement was found
|
421
|
+
|
422
|
+
arg_insert_locations[arg_name] << [line_i, start_i] # Add the insert location to the list
|
423
|
+
|
424
|
+
# start_i += arg_name.size
|
425
|
+
def_lines[line_i] = def_lines[line_i][...start_i] + (def_lines[line_i][(start_i+arg_name.size)..] || "")
|
426
|
+
line = def_lines[line_i]
|
427
|
+
|
428
|
+
break # Skip the arguments loop
|
429
|
+
end
|
430
|
+
|
431
|
+
# Check if an argument was found
|
432
|
+
# If not, skip the text until the next whitespace character
|
433
|
+
if !arg_found
|
434
|
+
while start_i < line.size && Kompiler::Config.keyword_chars.include?(line[start_i])
|
435
|
+
start_i += 1
|
436
|
+
end
|
437
|
+
start_i += 1 # Move one more character
|
438
|
+
end
|
439
|
+
|
440
|
+
end
|
441
|
+
|
442
|
+
end
|
443
|
+
|
444
|
+
state[:extra_state][:macros] = Array.new if !state[:extra_state].keys.include?(:macros)
|
445
|
+
|
446
|
+
state[:extra_state][:macros] << {name: macro_name, args: arg_names, def_lines: def_lines, arg_insert_locations: arg_insert_locations}
|
447
|
+
|
448
|
+
|
449
|
+
# Scan the lines after the macro for the macro call and replace it with the macro definition
|
450
|
+
|
451
|
+
scan_lines = state[:lines][(state[:line_i] + def_lines.size + 1 + 1)..]
|
452
|
+
|
453
|
+
line_i = 0
|
454
|
+
|
455
|
+
# Re-group argument insert locations by line -> [index, arg index]
|
456
|
+
|
457
|
+
arg_insert_locations_regrouped = def_lines.size.times.map{[]}
|
458
|
+
|
459
|
+
arg_insert_locations.each do |arg_name, insert_locations|
|
460
|
+
insert_locations.each do |line_i, char_i|
|
461
|
+
arg_insert_locations_regrouped[line_i] << [char_i, arg_names.index(arg_name)]
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
|
466
|
+
while line_i < scan_lines.size
|
467
|
+
keyword, operands = Kompiler::Parsers.extract_instruction_parts(scan_lines[line_i])
|
468
|
+
|
469
|
+
# If parsing failed, move on to the next line
|
470
|
+
if keyword == false
|
471
|
+
line_i += 1
|
472
|
+
next
|
473
|
+
end
|
474
|
+
|
475
|
+
# If the keyword isn't the macro's name, skip the line
|
476
|
+
if keyword != macro_name
|
477
|
+
line_i += 1
|
478
|
+
next
|
479
|
+
end
|
480
|
+
|
481
|
+
# Here when the keyword matches the macro name
|
482
|
+
|
483
|
+
# Check that the number of operands is correct
|
484
|
+
if operands.size != arg_names.size
|
485
|
+
raise "Incorrect use of the \"#{macro_name}\" macro - #{arg_names.size} operands expected, but #{operands.size} were given: Program build not possible."
|
486
|
+
end
|
487
|
+
|
488
|
+
# Build the replacement lines for the macro call
|
489
|
+
build_lines = def_lines.map{|line| line.dup} # Copying strings inside array, because array.dup doesn't work for elements
|
490
|
+
|
491
|
+
arg_insert_locations_regrouped.each_with_index do |locations, line_i|
|
492
|
+
# Sort the locations by the insert character from largest to smallest, so that the inserts are made from end to start
|
493
|
+
locations.sort_by{|el| el[0]}.reverse.each do |char_i, arg_index|
|
494
|
+
build_lines[line_i].insert char_i, operands[arg_index]
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
# Replace the macro call with the built lines
|
499
|
+
scan_lines = scan_lines[...line_i] + build_lines + scan_lines[(line_i + 1)..]
|
500
|
+
|
501
|
+
# Skip the inserted macro
|
502
|
+
line_i += build_lines.size
|
503
|
+
end
|
504
|
+
|
505
|
+
state[:lines] = state[:lines][...state[:line_i]] + scan_lines
|
506
|
+
|
507
|
+
state[:line_i] += 1
|
508
|
+
|
159
509
|
state
|
160
510
|
end
|
161
511
|
}
|
@@ -164,4 +514,4 @@ end
|
|
164
514
|
|
165
515
|
end # Kompiler::Directives
|
166
516
|
|
167
|
-
end # Kompiler
|
517
|
+
end # Kompiler
|