kompiler 0.3.1 → 0.3.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 18cda0db09b288b5ad14ebb5feb99e13fe03ea5a7897d603285b0aab0c8bdb77
4
- data.tar.gz: e84247276f40846adef9798d8ea6584dc3fe156caaa18495c668ee7f99506e9e
3
+ metadata.gz: f5938bafe84031bb6dfb99ab6452d66fb1da892a42520e5e05c274e2b89f0b73
4
+ data.tar.gz: 8d80c58a6d2b689fbd3bfcd69f780cb2aa258c0f02d5b905c0b16b7d1b2c8a12
5
5
  SHA512:
6
- metadata.gz: d682ae38a0d09b9ca8c584de5a3687554a97962ab227fcfb1bd7f458d0f5c48ff1ca0386db8ddcd6f80832e5d94a53b12a655164a23e58bd168128dd8a05106e
7
- data.tar.gz: 04e832aff94a2948615b3f9ca03db1cc75648673873ec9a4678aab6f09aa24b595d9a276910cf53f00de34dffebb0bfc7675b1150749bdfcbe6225bae2778401
6
+ metadata.gz: e79f4af903656d29d7d2a4b9b5b1c1e75f7bc10461d4a1e203d68b98c70420d920e13e651e263469a679e361349e7b4106b519a3712efa5185d5124cbe71c1ed
7
+ data.tar.gz: b8065b466b2b5bca9234b7ba8fd00a5560914cc819cfa9c973884f5c489eed1d797d1eb1d633a47570629133c616caedff3ce6743d69502c7069715d1c0ae1ec
data/bin/kompile CHANGED
@@ -15,7 +15,7 @@
15
15
  # limitations under the License.
16
16
 
17
17
  require 'kompiler'
18
-
18
+ require 'fileutils'
19
19
 
20
20
  arg_string = ARGV.join(" ")
21
21
 
@@ -107,20 +107,28 @@ Available wrapping formats:
107
107
  none
108
108
  elf.obj
109
109
  elf.exec
110
- mach-o.obj [not implemented yet]
111
- mach-o.exec [not implemented yet]
112
-
110
+ mach-o.obj
111
+ mach-o.exec
112
+
113
113
  Additional options for wrapping are:
114
114
  --elf-machine=<type> Specifies ELF header's e_machine to be the type provided (default is 0)
115
115
  --elf-class=<class> Specifies the ELF file's class to either 32 or 64 (default is 64)
116
116
  --mach-o-machine=<cputype.subtype> Specifies Mach-O header's cputype and subtype
117
117
  to be the type provided
118
+ --mach-o-archtype=<type> Specifies the file architecture type to either 32 or 64 (default is 64)
119
+ --exec-type=<type> Used with --wrap=mach-o.exec. Specifies whether the executable is
120
+ statically (static) or dynamically (dylink) linked (default is dylink)
121
+ --mach-o-threadstate=<type> Used with --wrap=mach-o.exec and --exec-type=static. Specifies which
122
+ thread state type to use (arm64, arm32, x86-64 or x86-32)
123
+ --codesign=<bool> Used with --wrap=mach-o.*. Specifies whether to add a basic
124
+ code signature to the Mach-O file (default is false)
118
125
 
119
126
  Available options:
120
127
  --help, -h Prints this information
121
128
  --list-architectures Lists available architectures
122
129
  --list-instructions [arch] Lists available instructions for the specified architecture
123
130
  --list-registers [arch] Lists available registers for the specified architecture
131
+ --list-lexis Lists available lexis entries
124
132
  """
125
133
  exit # Exit
126
134
  end
@@ -140,14 +148,18 @@ end
140
148
  if arg_opts == ["list-instructions"]
141
149
  arch_name = arg_words[0] || "armv8a"
142
150
  Kompiler::ArchManager.load_all_entries()
143
- arch = Kompiler::ArchManager.get_arch(arch_name)
151
+ Kompiler::ArchManager.load_arch arch_name
144
152
 
145
- require arch[:include_path]
153
+ Kompiler::Architecture.instructions.each do |instruction|
154
+ print instruction[:keyword]
146
155
 
147
- Kompiler::Architecture.instructions.each do |instr|
148
- puts "#{instr[:keyword]} (#{instr[:operands].size} operands): #{instr[:name]}"
149
- puts instr[:description]
150
- puts
156
+ print " "
157
+
158
+ puts instruction[:operands].map{"<" + (_1[:name] || _1[:type] || "") + ">"}.join(" ")
159
+
160
+ puts instruction[:description]
161
+
162
+ print "\n"
151
163
  end
152
164
 
153
165
  exit
@@ -234,6 +246,7 @@ else
234
246
  arch_name = "armv8a"
235
247
  end
236
248
 
249
+
237
250
  # Load all the architecture entries
238
251
 
239
252
  Kompiler::ArchManager.load_all_entries()
@@ -261,7 +274,7 @@ code = File.binread(in_filename)
261
274
 
262
275
  detailed_out = Kompiler::CompilerFunctions.detailed_compile(code)
263
276
 
264
- # p detailed_out
277
+ # pp detailed_out
265
278
 
266
279
  out = nil
267
280
 
@@ -271,37 +284,139 @@ labels = detailed_out[:labels]
271
284
  labels.delete "here"
272
285
 
273
286
 
287
+ def get_number str
288
+ bool, val = Kompiler::Parsers.check_immediate_operand(str)
289
+ return (bool == false) ? nil : val
290
+ end
291
+
292
+
293
+ def get_arg_key arg_keys, key, default_value=0
294
+ filtered = arg_keys.filter{_1[0] == key}[0]
295
+ value = (filtered != nil) ? filtered[1] : default_value
296
+ return value
297
+ end
298
+
299
+
300
+ class String
301
+ def to_num
302
+ status, val = Kompiler::Parsers.check_immediate_operand(self)
303
+ return (status == false) ? nil : val[:value]
304
+ end
305
+ end
306
+
307
+
308
+ add_exec_permission = false
309
+
310
+
274
311
  case wrap_opt
275
312
  when "none"
276
313
  out = code
277
314
  when "elf.obj"
278
- elf_machine = arg_keys.filter{_1[0] == "elf-machine"}[0]
279
- elf_machine = (elf_machine != nil) ? elf_machine[1].to_i : 0
280
-
281
- elf_class = arg_keys.filter{_1[0] == "elf-class"}[0]
282
- elf_class ||= ["elf-class", "64"]
283
- elf_class = elf_class[1].to_i
315
+ elf_machine = get_arg_key(arg_keys, "elf-machine", "0").to_num
316
+ elf_class = get_arg_key(arg_keys, "elf-class", "64").to_num
317
+
318
+ if ![32, 64].include?(elf_class)
319
+ puts "kompile: Invalid ELF class specified."
320
+ puts "Type \"kompile --help\" for more information."
321
+ exit
322
+ end
284
323
 
285
324
  symbols = Kompiler::Wrappers::ELF.labels_to_symbols(labels)
286
325
  out = Kompiler::Wrappers::ELF.wrap_obj(code, symbols, machine: elf_machine, elf_class: elf_class)
287
326
  when "elf.exec"
288
- elf_machine = arg_keys.filter{_1[0] == "elf-machine"}[0]
289
- elf_machine = (elf_machine != nil) ? elf_machine[1].to_i : 0
327
+ vaddr = 0x80000
290
328
 
291
- elf_class = arg_keys.filter{_1[0] == "elf-class"}[0]
292
- elf_class ||= ["elf-class", "64"]
293
- elf_class = elf_class[1].to_i
329
+ elf_machine = get_arg_key(arg_keys, "elf-machine", "0").to_num
330
+ elf_class = get_arg_key(arg_keys, "elf-class", "64").to_num
294
331
 
295
- symbols = Kompiler::Wrappers::ELF.labels_to_symbols(labels)
296
- out = Kompiler::Wrappers::ELF.wrap_exec(code, symbols, machine: elf_machine, elf_class: elf_class)
332
+ if ![32, 64].include?(elf_class)
333
+ puts "kompile: Invalid ELF class specified."
334
+ puts "Type \"kompile --help\" for more information."
335
+ exit
336
+ end
337
+
338
+
339
+ symbols = Kompiler::Wrappers::ELF.labels_to_symbols(labels, vaddr: vaddr)
340
+ out = Kompiler::Wrappers::ELF.wrap_exec(code, symbols, machine: elf_machine, elf_class: elf_class, vaddr: vaddr)
341
+
342
+ add_exec_permission = true
343
+
297
344
  when "mach-o.obj"
298
- puts "Mach-O not yet implemented."
299
- exit
345
+ macho_cpu = get_arg_key(arg_keys, "mach-o-machine", "0.0")
346
+ cputype, cpusubtype = macho_cpu.split(".").map(&:to_num)
347
+
348
+ if [cputype, cpusubtype].include? nil
349
+ puts "kompile: Invalid Mach-O machine specified."
350
+ puts "Type \"kompile --help\" for more information."
351
+ exit
352
+ end
353
+
354
+ arch_type = get_arg_key(arg_keys, "mach-o-archtype", "64").to_num
355
+
356
+ codesign = get_arg_key(arg_keys, "codesign", "false")
357
+ if !["true", "false"].include?(codesign)
358
+ puts "kompile: Invalid --codesign value."
359
+ puts "Type \"kompile --help\" for more information."
360
+ exit
361
+ end
362
+
363
+ codesign = (codesign == "true")
364
+
365
+ symbols = Kompiler::Wrappers::MachO.labels_to_symbols(labels)
366
+ out = Kompiler::Wrappers::MachO.wrap_obj(code, symbols, cputype: cputype, cpusubtype: cpusubtype, arch_type: arch_type)
300
367
  when "mach-o.exec"
301
- puts "Mach-O not yet implemented."
302
- exit
368
+ macho_cpu = get_arg_key(arg_keys, "mach-o-machine", "0.0")
369
+ cputype, cpusubtype = macho_cpu.split(".").map(&:to_num)
370
+
371
+ if [cputype, cpusubtype].include? nil
372
+ puts "kompile: Invalid Mach-O machine specified."
373
+ puts "Type \"kompile --help\" for more information."
374
+ exit
375
+ end
376
+
377
+ arch_type = get_arg_key(arg_keys, "mach-o-archtype", "64").to_num
378
+
379
+ exec_type = get_arg_key(arg_keys, "exec-type", "dylink")
380
+
381
+
382
+ codesign = get_arg_key(arg_keys, "codesign", "false")
383
+ if !["true", "false"].include?(codesign)
384
+ puts "kompile: Invalid --codesign value."
385
+ puts "Type \"kompile --help\" for more information."
386
+ exit
387
+ end
388
+
389
+ codesign = (codesign == "true")
390
+
391
+
392
+ case exec_type
393
+ when "dylink"
394
+ symbols = Kompiler::Wrappers::MachO.labels_to_symbols(labels)
395
+ out = Kompiler::Wrappers::MachO.wrap_exec_dylink(code, symbols, cputype: cputype, cpusubtype: cpusubtype, arch_type: arch_type, codesign: codesign)
396
+ when "static"
397
+ symbols = Kompiler::Wrappers::MachO.labels_to_symbols(labels)
398
+
399
+ thread_state_arch = get_arg_key(arg_keys, "mach-o-threadstate", "arm64")
400
+
401
+ entry_address = 0x1000000
402
+
403
+ thread_state = Kompiler::Wrappers::MachO.build_thread_state arch: thread_state_arch, entry_address: 0x1000000, stack_pointer: 0
404
+
405
+ out = Kompiler::Wrappers::MachO.wrap_exec_static(code, symbols, cputype: cputype, cpusubtype: cpusubtype, arch_type: arch_type, thread_state: thread_state, codesign: codesign, virtual_entry_address: entry_address)
406
+
407
+ else
408
+ puts "kompile: Invalid Mach-O executable type specified."
409
+ puts "Type \"kompile --help\" for more information."
410
+ exit
411
+ end
412
+
413
+ add_exec_permission = true
303
414
  end
304
415
 
305
416
 
306
417
  File.binwrite out_filename, out
307
418
 
419
+ if add_exec_permission
420
+ FileUtils.chmod "+x", out_filename
421
+ end
422
+
@@ -13,6 +13,8 @@
13
13
  # If false, operands will be a raw string containing the string after the keyword.
14
14
  #
15
15
 
16
+ require 'securerandom'
17
+
16
18
 
17
19
  module Kompiler
18
20
 
@@ -250,7 +252,7 @@ end
250
252
  state[:line_i] += 1
251
253
 
252
254
  # Insert the lines at the correct place
253
- state[:lines] = state[:lines][0...state[:line_i]] + total_load_lines + state[:lines][state[:line_i]..]
255
+ state[:lines] = state[:lines][0...state[:line_i]] + total_load_lines + state[:lines][state[:line_i]..]
254
256
 
255
257
  state
256
258
  end
@@ -415,7 +417,7 @@ end
415
417
  end
416
418
 
417
419
  # Skip string definitions
418
- if ['"', "'"].include? line[start_i]
420
+ if Kompiler::Config.string_delimiters.include? line[start_i]
419
421
  str, parsed_length = Kompiler::Parsers.parse_str line[start_i..]
420
422
  start_i += parsed_length
421
423
  next
@@ -515,10 +517,393 @@ end
515
517
  line_i += build_lines.size
516
518
  end
517
519
 
518
- state[:lines] = state[:lines][...state[:line_i]] + scan_lines
520
+ state[:lines] = state[:lines][...(state[:line_i] + def_lines.size + 2)] + scan_lines
521
+
522
+ state[:line_i] += def_lines.size + 2
523
+
524
+ state
525
+ end
526
+ },
527
+ {
528
+ keyword: "isomacro",
529
+ collect_operands: false,
530
+ func: lambda do |_, state|
531
+ line_i = state[:line_i]
532
+
533
+ def_line = state[:lines][line_i]
534
+
535
+ # First: collect the part after ".macro"
536
+
537
+ char_i = 0
538
+ # Skip the whitespace before .macro
539
+ while char_i < def_line.size && Kompiler::Config.whitespace_chars.include?(def_line[char_i])
540
+ char_i += 1
541
+ end
542
+ # Skip the .macro
543
+ while char_i < def_line.size && Kompiler::Config.keyword_chars.include?(def_line[char_i])
544
+ char_i += 1
545
+ end
546
+ # Skip the whitespace after .macro
547
+ while char_i < def_line.size && Kompiler::Config.whitespace_chars.include?(def_line[char_i])
548
+ char_i += 1
549
+ end
550
+
551
+ # If the end of the line was reached, throw an error
552
+ raise "Incorrect .isomacro definition" if char_i == def_line.size
553
+
554
+ # Now char_i contains the first index of the text after .macro
555
+
556
+ macro_def = def_line[char_i..]
557
+
558
+ # Second: extract the macro's name
559
+
560
+ macro_name = ""
561
+
562
+ while char_i < def_line.size && Kompiler::Config.keyword_chars.include?(def_line[char_i])
563
+ macro_name << def_line[char_i]
564
+ char_i += 1
565
+ end
566
+
567
+ # Third: extract the operand names (code taken from parse_instruction_line in parsers.rb)
568
+
569
+ arg_names = Kompiler::Parsers.extract_instruction_operands(def_line[char_i..])
570
+
571
+ # Make sure that the arg names are unique
572
+ raise "Macro definition error - arguments cannot have the same name: Program build not possible" if arg_names.size != arg_names.uniq.size
573
+
574
+ # Extract the macro inside definition
575
+
576
+ line_i = state[:line_i] + 1
577
+ def_lines = []
578
+
579
+ whitespace_regexp = /[#{Kompiler::Config.whitespace_chars.join("|")}]*/
580
+
581
+ endmacro_regexp = /\A#{whitespace_regexp}\.?endisomacro#{whitespace_regexp}\z/
582
+
583
+ while line_i < state[:lines].size
584
+ break if state[:lines][line_i].match? endmacro_regexp # Check if it's an end macro instruction
585
+ def_lines << state[:lines][line_i]
586
+ line_i += 1
587
+ end
588
+
589
+
590
+ # Find insert indexes for each argument
591
+ arg_insert_locations = arg_names.map{|arg_name| [arg_name, []]}.to_h
592
+
593
+ def_lines.each_with_index do |line, line_i|
594
+
595
+ start_i = 0
596
+
597
+ # Loop through each character starting position
598
+ while start_i < line.size
599
+
600
+ # Skip whitespace characters
601
+ if Kompiler::Config.whitespace_chars.include?(line[start_i])
602
+ start_i += 1
603
+ next
604
+ end
605
+
606
+ # Skip string definitions
607
+ if Kompiler::Config.string_delimiters.include? line[start_i]
608
+ str, parsed_length = Kompiler::Parsers.parse_str line[start_i..]
609
+ start_i += parsed_length
610
+ next
611
+ end
612
+
613
+ cut_line = line[start_i..]
614
+
615
+ arg_found = false
616
+
617
+ # Check if one of the argument names works
618
+ arg_names.each do |arg_name|
619
+ next if !cut_line.start_with?(arg_name) # Skip the argument if the line doesn't begin with it
620
+ 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'
621
+ # Here if the argument name should be replaced with the contents
622
+ arg_found = true # Indicate that a replacement was found
623
+
624
+ arg_insert_locations[arg_name] << [line_i, start_i] # Add the insert location to the list
625
+
626
+ # start_i += arg_name.size
627
+ def_lines[line_i] = def_lines[line_i][...start_i] + (def_lines[line_i][(start_i+arg_name.size)..] || "")
628
+ line = def_lines[line_i]
629
+
630
+ break # Skip the arguments loop
631
+ end
632
+
633
+ # Check if an argument was found
634
+ # If not, skip the text until the next whitespace character
635
+ if !arg_found
636
+ while start_i < line.size && Kompiler::Config.keyword_chars.include?(line[start_i])
637
+ start_i += 1
638
+ end
639
+ start_i += 1 # Move one more character
640
+ end
641
+
642
+ end
643
+
644
+ end
645
+
646
+ state[:extra_state][:isomacros] = Array.new if !state[:extra_state].keys.include?(:macros)
647
+
648
+ state[:extra_state][:isomacros] << {name: macro_name, args: arg_names, def_lines: def_lines, arg_insert_locations: arg_insert_locations}
649
+
650
+
651
+ # Scan the lines after the macro for the macro call and replace it with the macro definition
652
+
653
+ scan_lines = state[:lines][(state[:line_i] + def_lines.size + 1 + 1)..]
654
+
655
+ line_i = 0
656
+
657
+ # Re-group argument insert locations by line -> [index, arg index]
658
+
659
+ arg_insert_locations_regrouped = def_lines.size.times.map{[]}
660
+
661
+ arg_insert_locations.each do |arg_name, insert_locations|
662
+ insert_locations.each do |line_i, char_i|
663
+ arg_insert_locations_regrouped[line_i] << [char_i, arg_names.index(arg_name)]
664
+ end
665
+ end
666
+
667
+
668
+ while line_i < scan_lines.size
669
+ keyword, operands = Kompiler::Parsers.extract_instruction_parts(scan_lines[line_i])
670
+
671
+ # If parsing failed, move on to the next line
672
+ if keyword == false
673
+ line_i += 1
674
+ next
675
+ end
676
+
677
+ # If the keyword isn't the macro's name, skip the line
678
+ if keyword != macro_name
679
+ line_i += 1
680
+ next
681
+ end
682
+
683
+ # Here when the keyword matches the macro name
684
+
685
+ # Check that the number of operands is correct
686
+ if operands.size != arg_names.size
687
+ raise "Incorrect use of the \"#{macro_name}\" macro - #{arg_names.size} operands expected, but #{operands.size} were given: Program build not possible."
688
+ end
689
+
690
+ # Build the replacement lines for the macro call
691
+ build_lines = def_lines.map{|line| line.dup} # Copying strings inside array, because array.dup doesn't work for elements
692
+
693
+ arg_insert_locations_regrouped.each_with_index do |locations, line_i|
694
+ # Sort the locations by the insert character from largest to smallest, so that the inserts are made from end to start
695
+ locations.sort_by{|el| el[0]}.reverse.each do |char_i, arg_index|
696
+ build_lines[line_i].insert char_i, operands[arg_index]
697
+ end
698
+ end
699
+
700
+ build_lines.insert 0, ".namespace macro.#{macro_name}.#{SecureRandom.uuid.gsub('-', '')}"
701
+ build_lines << ".endnamespace"
702
+
703
+ # Replace the macro call with the built lines
704
+ scan_lines = scan_lines[...line_i] + build_lines + scan_lines[(line_i + 1)..]
705
+
706
+ # Skip the inserted macro
707
+ line_i += build_lines.size
708
+ end
709
+
710
+ state[:lines] = state[:lines][...(state[:line_i] + def_lines.size + 2)] + scan_lines
711
+
712
+ state[:line_i] += def_lines.size + 2
713
+
714
+ state
715
+ end
716
+ },
717
+ {
718
+ keyword: "info",
719
+ func: lambda do |operands, state|
720
+
721
+ begin
722
+ info_name = operands[0][:definition]
723
+ info_value = operands[1][:definition]
724
+ rescue
725
+ raise "Incorrect use of the \".info\" directive."
726
+ end
519
727
 
728
+
520
729
  state[:line_i] += 1
730
+
731
+ context_info = {}
732
+
733
+ context_info[:current_address] = state[:current_address]
734
+
735
+ context_info[:line_i] = state[:line_i]
736
+
737
+ state[:extra_state][:info_entries] ||= Hash.new
738
+
739
+ state[:extra_state][:info_entries][info_name] ||= Array.new
740
+
741
+ state[:extra_state][:info_entries][info_name] << {context_info: context_info, input_str: info_value, input_full: operands[1]}
742
+
743
+ state
744
+ end
745
+ },
746
+ {
747
+ keyword: "namespace",
748
+ func: lambda do |operands, state|
749
+
750
+ raise "Incorrect use of the \".namespace\" directive" if operands.size != 1
751
+
752
+ namespace_name = operands[0][:definition]
753
+
754
+
755
+ namespace_lines = []
756
+
757
+ line_i = state[:line_i] + 1
758
+
759
+ whitespace_regexp = /[#{Kompiler::Config.whitespace_chars.join("|")}]*/
760
+ end_namespace_regexp = /\A#{whitespace_regexp}\.?endnamespace#{whitespace_regexp}\z/
761
+
762
+ start_namespace_regexp = /\A#{whitespace_regexp}\.?namespace/
763
+
764
+ namespace_level = 1
765
+
766
+ while line_i < state[:lines].size
767
+ if state[:lines][line_i].match?(end_namespace_regexp)
768
+ namespace_level -= 1
769
+ end
770
+ break if namespace_level == 0
771
+
772
+ # Indicates whether a line should be looked at and changed based on whether it's on the first level
773
+ line_change = namespace_level == 1
774
+
775
+ namespace_lines << [line_change, state[:lines][line_i]]
776
+
777
+ if state[:lines][line_i].match?(start_namespace_regexp)
778
+ namespace_level += 1
779
+ end
780
+
781
+ line_i += 1
782
+ end
783
+
784
+
785
+ raise "\".endnamespace\" was not found for \".namespace\"" if line_i == state[:lines].size
786
+
787
+
788
+ # Collect the definitions that happen inside of the namespace
789
+ defs = []
790
+
791
+ namespace_lines.each do |line_info|
792
+
793
+ line_change, line = line_info
794
+
795
+ next if !line_change
796
+
797
+ keyword, operands = Kompiler::Parsers.extract_instruction_parts(line)
798
+
799
+ is_instruction = false
800
+ Kompiler::Architecture.instructions.each do |instr|
801
+ is_instruction = instr[:keyword] == keyword
802
+ break if is_instruction
803
+ end
804
+
805
+ next if is_instruction
806
+
807
+ keyword = "." + keyword if keyword[0] != "."
808
+
809
+
810
+ case keyword
811
+ when ".value"
812
+ value_name = operands[0]
813
+ defs << {type: "value", name: value_name}
814
+ when ".macro"
815
+ # Remove the ".macro"
816
+ macro_def = line.split(keyword)[1..].join(keyword)
817
+ macro_name, _ = Kompiler::Parsers.extract_instruction_parts(macro_def)
818
+ defs << {type: "macro", name: macro_name}
819
+ when ".label"
820
+ label_name = operands[0]
821
+ defs << {type: "label", name: label_name}
822
+ when ".namespace"
823
+ inside_namespace_name = operands[0]
824
+ defs << {type: "namespace", name: inside_namespace_name}
825
+ end
826
+
827
+ end
828
+
829
+
830
+ # Create replacements for each definition inside of the namespace
521
831
 
832
+ replacements = defs.map do |definition|
833
+ [definition[:name], namespace_name + "." + definition[:name]]
834
+ end.to_h
835
+ defs.each do |definition|
836
+ next if definition[:type] != "namespace"
837
+ replacements[definition[:name] + "."] = namespace_name + "." + definition[:name] + "."
838
+ end
839
+
840
+
841
+ # Replace all words in the namespace that are equal to replacement[:from] as replacement[:to]
842
+ # Logic same as .macro's
843
+
844
+ namespace_lines.each_with_index do |line_info, line_i|
845
+
846
+ line_change, line = line_info
847
+
848
+ next if !line_change
849
+
850
+ start_i = 0
851
+
852
+ # Loop through each character starting position
853
+ while start_i < line.size
854
+
855
+ # Skip whitespace characters
856
+ if Kompiler::Config.whitespace_chars.include?(line[start_i])
857
+ start_i += 1
858
+ next
859
+ end
860
+
861
+ # Skip string definitions
862
+ if Kompiler::Config.string_delimiters.include? line[start_i]
863
+ str, parsed_length = Kompiler::Parsers.parse_str line[start_i..]
864
+ start_i += parsed_length
865
+ next
866
+ end
867
+
868
+ cut_line = line[start_i..]
869
+
870
+ skip_to_next_word = true
871
+
872
+ # Check if one of the argument names works
873
+ replacements.each do |from, to|
874
+ next if !cut_line.start_with?(from) # Skip the argument if the line doesn't begin with it
875
+ next if Kompiler::Config.keyword_chars.include?(cut_line[from.size]) && from[-1] != "." # Skip if the argument is a partial word. So, for the argument 'arg', this will skip in the case of 'arg1'
876
+ # Here if the argument name should be replaced with the contents
877
+ skip_to_next_word = from[-1] == "." # Skip to the next word only if the replacement is a namespace call
878
+
879
+ new_line = line[...start_i] + to + cut_line[from.size..]
880
+
881
+
882
+ start_i += to.size
883
+
884
+ namespace_lines[line_i] = [true, new_line]
885
+ line = namespace_lines[line_i]
886
+
887
+ break # Skip the arguments loop
888
+ end
889
+
890
+ # Check if an argument was found
891
+ # If not, skip the text until the next whitespace character
892
+ if skip_to_next_word
893
+ while start_i < line.size && Kompiler::Config.keyword_chars.include?(line[start_i])
894
+ start_i += 1
895
+ end
896
+ start_i += 1 # Move one more character
897
+ end
898
+
899
+ end
900
+
901
+ end
902
+
903
+ namespace_lines.map!{_1[1]}
904
+
905
+ state[:lines] = state[:lines][...state[:line_i]] + namespace_lines + state[:lines][(state[:line_i] + namespace_lines.size + 2)..]
906
+
522
907
  state
523
908
  end
524
909
  }
@@ -77,10 +77,10 @@ module Kompiler
77
77
 
78
78
 
79
79
  # Converts a label hash of name-address pairs into an array of symbols
80
- def self.labels_to_symbols labels
80
+ def self.labels_to_symbols labels, vaddr: 0
81
81
  out = []
82
82
  labels.each do |name, value|
83
- out << {name: name, value: value, type: 0, binding: 0}
83
+ out << {name: name, value: value + vaddr, type: 0, binding: 0}
84
84
  end
85
85
  out
86
86
  end