kompiler 0.3.0.pre.3 → 0.3.0

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