kompiler 0.3.0.pre.2 → 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.
@@ -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