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.
@@ -0,0 +1,23 @@
1
+ # Copyright 2024 Kyrylo Shyshko
2
+ # Licensed under the Apache License, Version 2.0. See LICENSE file for details.
3
+
4
+ module Kompiler
5
+
6
+ module ARMv8A
7
+
8
+ def self.simd_fp_registers
9
+ @simd_fp_registers
10
+ end
11
+
12
+ @simd_fp_registers = [
13
+
14
+ ]
15
+
16
+ (0..31).each do |reg_i|
17
+ @simd_fp_registers << {reg_name: "q#{reg_i}", reg_type: "simd_fp_reg", re_num: reg_i}
18
+ end
19
+
20
+
21
+ end # Kompiler::ARMv8A
22
+
23
+ end # Kompiler
@@ -54,6 +54,9 @@ end
54
54
 
55
55
  {reg_name: "ISR_EL1", reg_size: 64, reg_type: "sr", reg_encoding: {"op0"=>0b11, "op1"=>0b000, "CRn"=>0b1100, "CRm"=>0b0001, "op2"=>0b000}},
56
56
 
57
+ {reg_name: "CPACR_EL1", reg_size: 64, reg_type: "sr", reg_encoding: {"op0"=>0b11, "op1"=>0b000, "CRn"=>0b0001, "CRm"=>0b0000, "op2"=>0b010}},
58
+ {reg_name: "CPACR_EL12", reg_size: 64, reg_type: "sr", reg_encoding: {"op0"=>0b11, "op1"=>0b101, "CRn"=>0b0001, "CRm"=>0b0000, "op2"=>0b010}},
59
+
57
60
  # Special registers for the MSR (immediate) instruction (some of them were previously defined already)
58
61
  {reg_name: "SPSel", reg_type: "pstate_reg"},
59
62
  {reg_name: "DAIFSet", reg_type: "pstate_reg"},
@@ -1,226 +1,49 @@
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 main logic happening during compilation.
6
+ # Main functions:
7
+ # parse_code - main code parser that turns all lines into an abstract structure (determining types of lines and connecting them into a program)
8
+ # construct_program_mc - transforms the parse_code's AST into machine code (MC)
9
+ # detailed_compile - stitches parse_code and construct_program_mc to fully compile a program with a detailed output
10
+ # compile - calls detailed_compile and removes the extra output information
11
+ #
12
+
13
+
4
14
  module Kompiler
5
15
 
6
16
  module CompilerFunctions
7
17
 
8
- def self.parse_includes(lines, loaded_files=[])
9
-
10
- final_lines = lines.dup
18
+ def self.parse_code(lines)
11
19
 
12
- line_i = 0
20
+ parsed_lines = []
13
21
 
14
- loop do
15
- break if line_i >= final_lines.size
16
-
17
- line = final_lines[line_i]
18
-
19
- keyword, operands = Kompiler::Parsers.parse_instruction_line(line)
20
-
21
- if keyword == false
22
- line_i += 1
23
- next
24
- end
25
-
26
- if keyword.start_with? "."
27
- keyword = keyword[1..]
28
- end
29
-
30
- if !["load", "include", "load_end", "include_end"].include?(keyword)
31
- line_i += 1
32
- next
33
- end
34
-
35
- raise "Incorrect use of the \"#{keyword}\" directive: requires a filename in a string." if operands.size != 1 || (operands.size == 1 && operands[0][:type] != "string")
36
-
37
- load_file_name_selector = operands[0][:string]
38
-
39
-
40
- # Remove the include code line from the lines array
41
- final_lines.delete_at line_i
42
-
43
- # If ends with _end, that means that the file contents should be appended at the end of the current lines
44
- if keyword.end_with? "_end"
45
- next_i_insert = final_lines.size
46
- else
47
- # If doesn't end with _end, files should be loaded in-place of the current line
48
- next_i_insert = line_i
49
- end
50
-
51
-
52
- Dir[load_file_name_selector].each do |load_file_name|
53
- # Get the absolute path filename
54
- full_file_name = File.expand_path(load_file_name)
55
-
56
- raise "#{keyword} \"#{load_file_name}\": File not found." if !File.exist?(full_file_name)
57
-
58
- # Check if the file was already loaded (stop recursive loading)
59
- if loaded_files.include?(full_file_name)
60
- next
61
- end
62
-
63
- # Read the file to load
64
- include_code = File.read(full_file_name)
65
-
66
- # Separate the lines inside it
67
- include_code_lines = Kompiler::Parsers.get_code_lines(include_code)
68
-
69
- # Add the lines from the load file to the lines array at the line_i index, effectively replacing the load command with the content of the load file
70
- final_lines.insert next_i_insert, *include_code_lines
71
-
72
- next_i_insert += include_code_lines.size
73
-
74
- # Add the filename (absolute path) to the list of included files
75
- loaded_files << full_file_name
76
- end
77
-
78
- # Don't increment line_i, since the new loop will now start include-parsing the newly loaded file in the same program
79
- end
80
-
81
- final_lines
22
+ instr_adr = 0
82
23
 
83
- end
84
-
85
-
86
- def self.parse_macros(lines)
87
-
88
- macros = []
89
-
90
24
  line_i = 0
91
-
92
- loop do
93
- break if line_i >= lines.size
94
-
95
- line = lines[line_i]
96
-
97
- keyword, operands = Kompiler::Parsers.parse_instruction_line line
98
-
99
- keyword = keyword[1..] if keyword.start_with? "."
100
-
101
- if keyword == 'macro'
102
- raise "Macro definition error: Expected two operands to be provided, while #{operands.size} were given" if operands.size != 2
103
- raise "Macro definition error: The macro name must either be a word or a string" if !["label", "string"].include?(operands[0][:type])
104
-
105
- case operands[0][:type]
106
- when "label"
107
- macro_name = operands[0][:value]
108
- when "string"
109
- macro_name = operands[0][:value]
110
- end
111
-
112
- macro_definition = operands[1][:definition]
113
-
114
- macros << {name: macro_name, definition: macro_definition}
115
-
116
- lines.delete_at line_i
117
- else
118
- line_i += 1
119
- end
120
- end
121
-
122
- # Apply the macros to all lines, and then the lines that were changed, and than again, and so on (iterative loop until none of the lines need to be checked)
123
-
124
- lines_to_check = (0...lines.size).to_a
125
-
126
- while lines_to_check.size != 0 # Repeat until no more lines to check
127
-
128
- # Define restricted separators characters (characters that CAN'T separate a macro from everything else)
129
- restricted_separator_chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a + ["_", "."]
130
-
131
- # Create an array for the lines to check once again
132
- new_lines_to_check = []
133
-
134
- lines_to_check.each do |line_i|
135
- line = lines[line_i]
136
-
137
- char_i = 0
138
-
139
- # Create a variable to indicate if a macro was used on the current line
140
- line_macro_used = false
141
-
142
- loop do
143
- break if char_i >= line.size
144
-
145
- if ['"', "'"].include? line[char_i]
146
- # If a string, skip it
147
- str, parsed_length = Kompiler::Parsers.parse_str line[char_i..]
148
- char_i += parsed_length
149
- next
150
- end
151
-
152
- cut_line = line[char_i..]
153
-
154
- # Create a variable to indicate if a macro was used in the current position
155
- macro_used = false
156
-
157
- macros.each do |macro|
158
- macro_name = macro[:name]
159
- macro_def = macro[:definition]
160
- next if !cut_line.start_with?(macro_name) # Skip if doesn't begin with the macro's name
161
- next if (cut_line.size > macro_name.size) && restricted_separator_chars.include?(cut_line[macro_name.size]) # Skip if there is a character after the macro name, and the character is not permitted
162
- next if (char_i > 0) && restricted_separator_chars.include?(line[char_i - 1]) # Skip if there is a character before the checking sequence, and the character is not permitted
163
-
164
- # Here if the macro is 'accepted'
165
- line = line[...char_i] + macro_def + line[(char_i + macro_name.size)..]
166
- # Indicate that a macro was used
167
- macro_used = true
168
- end
169
-
170
- if macro_used
171
- # If a macro was used, indicate that on line level
172
- line_macro_used = true
173
- else
174
- # If a macro wasn't used, proceed to the next character
175
- char_i += 1
176
- end
177
- end # line characters loop
178
-
179
- if line_macro_used
180
- # If a macro was used on the current line, update the lines array, and add the line to check for macros again
181
- lines[line_i] = line
182
- new_lines_to_check << line_i
183
- end
184
- end # lines_to_check.each loop
185
-
186
- lines_to_check = new_lines_to_check # Update lines to check with the new ones
187
-
188
- end # while lines_to_check.size != 0 loop
189
25
 
190
- lines
191
- end
192
-
193
-
194
- def self.parse_code(lines)
26
+ extra_state = Hash.new
195
27
 
196
- parsed_lines = []
28
+ add_later_directives = []
197
29
 
198
- instr_adr = 0
199
-
200
- lines.each_with_index do |line, line_i|
30
+ while line_i < lines.size
31
+ line = lines[line_i]
201
32
 
202
33
  # Check if line is not just whitespace
203
- is_char_whitespace = line.each_char.map{|c| [" ", "\t"].include? c}
34
+ is_char_whitespace = line.each_char.map{|c| Kompiler::Config.whitespace_chars.include? c}
204
35
  if !is_char_whitespace.include?(false) # If only whitespace
36
+ line_i += 1
205
37
  next # Skip
206
38
  end
207
39
 
208
- #
209
- # Label definitions are now a directive, so this isn't needed
210
- #
211
-
212
- # is_label, label_name = check_label(line)
213
- # if is_label
214
- # # labels[label_name] = instr_adr
215
- # parsed_lines << {type: "label", label_name: label_name, label_address: instr_adr, address: instr_adr}
216
- # next
217
- # end
218
-
219
40
  is_instruction, exec_instruction = Kompiler::Parsers.check_instruction(line)
220
41
  if is_instruction
221
42
  parsed_lines << {type: "instruction", instruction: exec_instruction[:instruction], operands: exec_instruction[:operands], address: instr_adr}
222
43
  instr_adr += exec_instruction[:instruction][:bitsize] / 8
223
-
44
+
45
+ line_i += 1
46
+
224
47
  next # Go to the next line
225
48
  end
226
49
 
@@ -230,12 +53,59 @@ def self.parse_code(lines)
230
53
  directive = directive_hash[:directive]
231
54
  operands = directive_hash[:operands]
232
55
 
233
- state = {current_address: instr_adr, parsed_lines: parsed_lines}
56
+ state = {current_address: instr_adr, parsed_lines: parsed_lines, lines: lines, line_i: line_i, extra_state: extra_state}
57
+
58
+ add_later_directive = false
59
+
60
+ if directive.keys.include?(:add_later_directive) && directive[:add_later_directive] == true
61
+ add_later_directive = true
62
+ end
63
+
64
+ new_operands = operands.map do |op|
65
+ if op[:type] == "run_block"
66
+ begin
67
+ block_state = state.dup
68
+ block_state[:block_args] = op[:block_args]
69
+ block_state[:labels] = self.get_labels(parsed_lines)
70
+ op = op[:block].call(block_state)
71
+ rescue
72
+ add_later_directive = true
73
+ op = {type: "immediate", value: 0, def_type: "kompiler_test_value", definition: "0"}
74
+ end
75
+ end
76
+ op
77
+ end
78
+
79
+
80
+ if add_later_directive
81
+
82
+ try_state = {current_address: instr_adr, parsed_lines: parsed_lines.dup, lines: lines.dup, line_i: line_i, extra_state: extra_state.dup}
83
+
84
+ try_state = directive[:func].call(new_operands, try_state)
85
+
86
+
87
+ instr_adr = try_state[:current_address]
88
+ line_i = try_state[:line_i]
89
+
90
+ raise "Directive error 1.1" if !(lines == try_state[:lines])
91
+ raise "Directive error 1.2" if !(extra_state == try_state[:extra_state])
92
+ raise "Directive error 1.3" if !(parsed_lines == try_state[:parsed_lines][...parsed_lines.size]) # Check that the previous parsed lines were not changed by the directive
93
+
94
+ state.delete :parsed_lines
95
+ state.delete :lines
96
+
97
+ add_later_directives << {directive_hash: directive_hash, insert_i: parsed_lines.size, run_state: state, return_line_i: line_i, return_current_address: instr_adr}
98
+
99
+ next
100
+ end
234
101
 
235
- state = directive[:func].call(operands, state)
102
+ state = directive[:func].call(new_operands, state)
236
103
 
237
104
  instr_adr = state[:current_address]
238
105
  parsed_lines = state[:parsed_lines]
106
+ lines = state[:lines]
107
+ line_i = state[:line_i]
108
+ extra_state = state[:extra_state]
239
109
 
240
110
  next # Skip to the next lime
241
111
  end
@@ -246,9 +116,43 @@ def self.parse_code(lines)
246
116
 
247
117
  raise "\"#{line}\" - Unknown syntax: Program build not possible"
248
118
 
119
+ end
120
+
121
+ add_later_directives.sort_by{|hash| hash[:insert_i]}.reverse.each do |add_later_directive|
122
+ directive_hash = add_later_directive[:directive_hash]
123
+ insert_i = add_later_directive[:insert_i]
124
+ state = add_later_directive[:run_state]
125
+
126
+ state[:parsed_lines] = parsed_lines[...insert_i]
127
+ state[:lines] = lines
128
+
129
+ directive = directive_hash[:directive]
130
+ operands = directive_hash[:operands]
131
+
132
+ new_operands = operands.map do |op|
133
+ if op[:type] == "run_block"
134
+ block_state = state.dup
135
+ block_state[:block_args] = op[:block_args]
136
+ block_state[:labels] = self.get_labels(parsed_lines)
137
+ op = op[:block].call(block_state)
138
+ end
139
+ op
140
+ end
141
+
142
+ state = directive[:func].call(new_operands, state)
143
+
144
+
145
+ raise "Directive error 2.1" if add_later_directive[:return_current_address] != state[:current_address]
146
+ raise "Directive error 2.2" if add_later_directive[:return_line_i] != state[:line_i]
147
+
148
+
149
+ parsed_lines = state[:parsed_lines] + parsed_lines[insert_i..]
249
150
  end
250
-
251
- parsed_lines
151
+
152
+
153
+ state = {parsed_lines: parsed_lines, current_address: instr_adr, lines: lines, line_i: line_i, extra_state: extra_state}
154
+
155
+ return state
252
156
  end
253
157
 
254
158
 
@@ -280,9 +184,21 @@ def self.construct_program_mc(parsed_lines, labels)
280
184
  parsed_lines.each do |line|
281
185
  case line[:type]
282
186
  when "instruction"
283
- program_state[:operands] = line[:operands]
284
187
  program_state[:current_address] = line[:address]
285
188
 
189
+ operands = line[:operands]
190
+ operands.map! do |op|
191
+ if op[:type] == "run_block"
192
+ state = program_state.dup
193
+ state[:block_args] = op[:block_args]
194
+ op = op[:block].call(state)
195
+ end
196
+ op
197
+ end
198
+
199
+ program_state[:operands] = operands
200
+
201
+
286
202
  mc_constructor = line[:instruction][:mc_constructor]
287
203
 
288
204
  instr_bits = Kompiler::MachineCode_AST.build_mc(mc_constructor, program_state)
@@ -301,7 +217,7 @@ def self.construct_program_mc(parsed_lines, labels)
301
217
  end
302
218
  end
303
219
  end
304
-
220
+
305
221
  lines_bytes
306
222
  end
307
223
 
@@ -347,23 +263,26 @@ def self.bit_lines_to_bytes(bit_lines)
347
263
  end
348
264
 
349
265
 
350
- def self.compile(code, included_files=[])
266
+ def self.compile(code)
267
+ detailed_result = detailed_compile(code)
268
+
269
+ return detailed_result[:machine_code]
270
+ end
351
271
 
272
+ def self.detailed_compile(code)
352
273
  lines = Kompiler::Parsers.get_code_lines(code)
353
-
354
- included_lines = parse_includes(lines, included_files.map{|fname| File.expand_path(fname)})
355
-
356
- macroed_lines = parse_macros(included_lines)
357
-
358
- parsed_lines = parse_code(macroed_lines)
359
-
360
- labels = get_labels(parsed_lines)
361
-
362
- # machine_code_bit_lines = construct_program_mc(parsed_lines, labels)
363
- #
364
- # machine_code_bytes = bit_lines_to_bytes(machine_code_bit_lines)
365
-
274
+
275
+ parsed_state = parse_code(lines)
276
+
277
+ # pp parsed_state
278
+
279
+ parsed_lines = parsed_state[:parsed_lines]
280
+
281
+ labels = get_labels(parsed_lines)
282
+
366
283
  machine_code_bytes = construct_program_mc(parsed_lines, labels)
284
+
285
+ return {machine_code: machine_code_bytes, labels: labels, parsed_state: parsed_state}
367
286
  end
368
287
 
369
288
 
@@ -0,0 +1,40 @@
1
+
2
+ #
3
+ # Contains config options for how Kompiler interprets different characters.
4
+ #
5
+ # Main config options:
6
+ # keyword_chars - list of characters that a keyword can contain
7
+ # label_chars - list of characters that a label name can contain
8
+ # whitespace_chars - list of characters that qualify as whitespace / separators of words
9
+ # string_delimiters - a list of characters that denote the start and end of a string
10
+ #
11
+
12
+ module Kompiler
13
+
14
+ module Config
15
+
16
+ @keyword_chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a + ["_", "."]
17
+ @label_chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a + ["_", "."]
18
+ @whitespace_chars = [" ", "\t", "\r"]
19
+ @string_delimiters = ['"', "'"]
20
+
21
+ # Returns the permittable keyword characters
22
+ def self.keyword_chars
23
+ @keyword_chars
24
+ end
25
+
26
+ # Returns the permittable label characters
27
+ def self.label_chars
28
+ @label_chars
29
+ end
30
+
31
+ def self.whitespace_chars
32
+ @whitespace_chars
33
+ end
34
+
35
+ def self.string_delimiters
36
+ @string_delimiters
37
+ end
38
+ end
39
+
40
+ end