kompiler 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,262 @@
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
+ class CompilerFunctions
7
+
8
+ def self.parse_includes(lines, loaded_files=[])
9
+
10
+ final_lines = lines.dup
11
+
12
+ line_i = 0
13
+
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
82
+
83
+ end
84
+
85
+
86
+
87
+ def self.parse_code(lines)
88
+
89
+ parsed_lines = []
90
+
91
+ instr_adr = 0
92
+
93
+ lines.each_with_index do |line, line_i|
94
+
95
+ # Check if line is not just whitespace
96
+ is_char_whitespace = line.each_char.map{|c| [" ", "\t"].include? c}
97
+ if !is_char_whitespace.include?(false) # If only whitespace
98
+ next # Skip
99
+ end
100
+
101
+ #
102
+ # Label definitions are now a directive, so this isn't needed
103
+ #
104
+
105
+ # is_label, label_name = check_label(line)
106
+ # if is_label
107
+ # # labels[label_name] = instr_adr
108
+ # parsed_lines << {type: "label", label_name: label_name, label_address: instr_adr, address: instr_adr}
109
+ # next
110
+ # end
111
+
112
+ is_instruction, exec_instruction = Kompiler::Parsers.check_instruction(line)
113
+ if is_instruction
114
+ parsed_lines << {type: "instruction", instruction: exec_instruction[:instruction], operands: exec_instruction[:operands], address: instr_adr}
115
+ instr_adr += exec_instruction[:instruction][:bitsize] / 8
116
+
117
+ next # Go to the next line
118
+ end
119
+
120
+
121
+ is_directive, directive_hash = Kompiler::Parsers.check_directive(line)
122
+ if is_directive
123
+ directive = directive_hash[:directive]
124
+ operands = directive_hash[:operands]
125
+
126
+ state = {current_address: instr_adr, parsed_lines: parsed_lines}
127
+
128
+ state = directive[:func].call(operands, state)
129
+
130
+ instr_adr = state[:current_address]
131
+ parsed_lines = state[:parsed_lines]
132
+
133
+ next # Skip to the next lime
134
+ end
135
+
136
+
137
+ # Line wasn't classified
138
+ # Throw an error
139
+
140
+ raise "\"#{line}\" - Unknown syntax: Program build not possible"
141
+
142
+ end
143
+
144
+ parsed_lines
145
+ end
146
+
147
+
148
+ def self.get_labels(parsed_lines)
149
+
150
+ label_definitions = parsed_lines.filter{|line| line[:type] == "label"}
151
+
152
+ labels_hash = Hash.new
153
+
154
+ label_definitions.each do |label_def|
155
+ if labels_hash.keys.include?(label_def[:label_name])
156
+ puts "Warning: Label #{label_def[:label_name]} was aleady defined. Label is now re-defined"
157
+ end
158
+
159
+ labels_hash[label_def[:label_name]] = label_def[:label_address]
160
+ end
161
+
162
+ labels_hash
163
+ end
164
+
165
+
166
+ def self.construct_program_mc(parsed_lines, labels)
167
+
168
+ lines_bytes = ""
169
+
170
+
171
+ program_state = {labels: labels, current_address: 0}
172
+
173
+ parsed_lines.each do |line|
174
+ case line[:type]
175
+ when "instruction"
176
+ program_state[:operands] = line[:operands]
177
+ program_state[:current_address] = line[:address]
178
+
179
+ mc_constructor = line[:instruction][:mc_constructor]
180
+
181
+ instr_bits = Kompiler::MachineCode_AST.build_mc(mc_constructor, program_state)
182
+
183
+ instr_bytes = bits_to_bytes(instr_bits)
184
+
185
+ lines_bytes += instr_bytes.map(&:chr).join
186
+ when "insert"
187
+ if line[:bits]
188
+ lines_bytes += bits_to_bytes(line[:bits]).map(&:chr).join
189
+ elsif line[:bytes]
190
+ lines_bytes += line[:bytes].map(&:chr).join
191
+ elsif line[:byte_string]
192
+ lines_bytes += line[:byte_string]
193
+ end
194
+ end
195
+ end
196
+
197
+ lines_bytes
198
+ end
199
+
200
+
201
+ def self.bits_to_bytes(bits)
202
+
203
+ bit_byte_groups = (0...(bits.size / 8)).map{|byte_i| bits[(byte_i * 8)...(byte_i * 8 + 8)] }
204
+
205
+ bytes = []
206
+
207
+
208
+ bit_byte_groups.each do |byte_bits|
209
+ byte_val = 0
210
+ byte_bits.each_with_index do |bit, bit_i|
211
+ byte_val += bit * 2 ** bit_i
212
+ end
213
+ bytes << byte_val
214
+ end
215
+
216
+ bytes
217
+ end
218
+
219
+
220
+ def self.bit_lines_to_bytes(bit_lines)
221
+
222
+ bits_flat = bit_lines.flatten
223
+
224
+ bit_byte_groups = (0...(bits_flat.size / 8)).map{|byte_i| bits_flat[(byte_i * 8)...(byte_i * 8 + 8)] }
225
+
226
+ bytes = []
227
+
228
+ bit_byte_groups.each do |byte_bits|
229
+ byte_val = 0
230
+
231
+ byte_bits.each_with_index do |bit, bit_index|
232
+ byte_val += bit * 2 ** bit_index
233
+ end
234
+
235
+ bytes << byte_val
236
+ end
237
+
238
+ bytes
239
+ end
240
+
241
+
242
+ def self.compile(code, included_files=[])
243
+
244
+ lines = Kompiler::Parsers.get_code_lines(code)
245
+
246
+ final_lines = parse_includes(lines, included_files.map{|fname| File.expand_path(fname)})
247
+
248
+ parsed_lines = parse_code(final_lines)
249
+
250
+ labels = get_labels(parsed_lines)
251
+
252
+ # machine_code_bit_lines = construct_program_mc(parsed_lines, labels)
253
+ #
254
+ # machine_code_bytes = bit_lines_to_bytes(machine_code_bit_lines)
255
+
256
+ machine_code_bytes = construct_program_mc(parsed_lines, labels)
257
+ end
258
+
259
+
260
+ end # Kompiler::CompilerFunctions
261
+
262
+ end # Kompiler
@@ -0,0 +1,167 @@
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
+ class Directives
7
+
8
+ def self.directives
9
+ @@DIRECTIVES
10
+ end
11
+
12
+ @@DIRECTIVES = [
13
+ {
14
+ keyword: "zeros",
15
+ func: lambda do |operands, state|
16
+ raise "Incorrect use of the \"zeros\" directive." if operands.size > 1 || (operands[0] && operands[0][:type] != "immediate")
17
+ n_zeros = operands[0][:value]
18
+ state[:current_address] += n_zeros
19
+ state[:parsed_lines] << {type: "insert", bits: (n_zeros*8).times.map{0} }
20
+ state
21
+ end
22
+ },
23
+ {
24
+ keyword: "ascii",
25
+ func: lambda do |operands, state|
26
+ raise "Incorrect use of the \"ascii\" directive." if operands.size > 1 || (operands[0] && operands[0][:type] != "string")
27
+
28
+ insert_bytes = operands[0][:string].encode("ascii").bytes
29
+ insert_bits = insert_bytes.map{|byte| 8.times.map{|bit_i| byte[bit_i]}}.flatten
30
+
31
+ state[:parsed_lines] << {type: "insert", bits: insert_bits, address: state[:current_address]}
32
+ state[:current_address] += insert_bytes.size
33
+
34
+ state
35
+ end
36
+ },
37
+ {
38
+ keyword: "align",
39
+ func: lambda do |operands, state|
40
+ raise "Incorrect use of the \"align\" directive." if operands.size > 1 || (operands[0] && operands[0][:type] != "immediate")
41
+
42
+ alignment = operands[0][:value]
43
+ to_add = alignment - (state[:current_address] % alignment)
44
+
45
+ # If aligned, do nothing
46
+ return state if to_add == alignment
47
+
48
+ # Else add stuff
49
+
50
+ state[:current_address] += to_add
51
+ state[:parsed_lines] << {type: "insert", bits: (to_add * 8).times.map{0} }
52
+
53
+ state
54
+ end
55
+ },
56
+ {
57
+ keyword: "label",
58
+ func: lambda do |operands, state|
59
+
60
+ raise "Incorrect use of the \"label\" directive." if (operands.size < 1 || operands.size > 2) || (operands[0] && operands[0][:type] != "label")
61
+
62
+ raise "Incorrect use of the \"label\" directive: second argument must be an immediate value" if operands[1] && operands[1][:type] != "immediate"
63
+
64
+ label_name = operands[0][:value]
65
+
66
+ # If a second argument is provided, use it as the label value; otherwise use the current instruction address (PC)
67
+ if operands[1] && operands[1][:type] == "immediate"
68
+ label_value = operands[1][:value]
69
+ else
70
+ label_value = state[:current_address]
71
+ end
72
+
73
+ # Add the label definition
74
+ state[:parsed_lines] << {type: "label", label_name: label_name, label_address: label_value, address: state[:current_address]}
75
+
76
+ state
77
+ end
78
+ },
79
+ {
80
+ keyword: "8byte",
81
+ func: lambda do |operands, state|
82
+
83
+ raise "Incorrect use of the \"8byte\" directive." if (operands.size != 1) || (operands[0] && operands[0][:type] != "immediate")
84
+
85
+ value = operands[0][:value]
86
+
87
+ value_bits = (0...(8 * 8)).map{|bit_i| value[bit_i]}
88
+
89
+ # Insert 64 bits of the value into the program
90
+ state[:current_address] += 8
91
+ state[:parsed_lines] << {type: "insert", bits: value_bits}
92
+
93
+ state
94
+ end
95
+ },
96
+ {
97
+ keyword: "4byte",
98
+ func: lambda do |operands, state|
99
+
100
+ raise "Incorrect use of the \"4byte\" directive." if (operands.size != 1) || (operands[0] && operands[0][:type] != "immediate")
101
+
102
+ value = operands[0][:value]
103
+
104
+ value_bits = (0...(4 * 8)).map{|bit_i| value[bit_i]}
105
+
106
+ # Insert 64 bits of the value into the program
107
+ state[:current_address] += 4
108
+ state[:parsed_lines] << {type: "insert", bits: value_bits}
109
+
110
+ state
111
+ end
112
+ },
113
+ {
114
+ keyword: "bytes",
115
+ func: lambda do |operands, state|
116
+
117
+ raise "Incorrect use of the \"bytes\" directive." if (operands.size != 2) || (operands[0][:type] != "immediate" && operands[1][:type] != "immediate")
118
+
119
+ n_bytes = operands[0][:value]
120
+ value = operands[1][:value]
121
+
122
+ value_bits = (0...(n_bytes * 8)).map{|bit_i| value[bit_i]}
123
+
124
+ # Insert the input amount of bytes of the value into the program
125
+ state[:current_address] += n_bytes
126
+ state[:parsed_lines] << {type: "insert", bits: value_bits}
127
+
128
+ state
129
+ end
130
+ },
131
+ {
132
+ keyword: "set_pc",
133
+ func: lambda do |operands, state|
134
+
135
+ raise "Incorrect use of the \"set_pc\" directive." if (operands.size != 1) || (operands[0][:type] != "immediate")
136
+
137
+ new_pc = operands[0][:value]
138
+
139
+ state[:current_address] = new_pc
140
+
141
+ state
142
+ end
143
+ },
144
+ {
145
+ keyword: "insert_file",
146
+ func: lambda do |operands, state|
147
+ raise "Incorrect use of the \"insert_file\" directive" if (operands.size != 1) || (operands[0][:type] != "string")
148
+
149
+ filename = operands[0][:string]
150
+
151
+ file_content = ""
152
+ File.open(filename, "rb") do |f|
153
+ file_content = f.read()
154
+ end
155
+
156
+ state[:current_address] += file_content.bytes.size
157
+ state[:parsed_lines] << {type: "insert", byte_string: file_content}
158
+
159
+ state
160
+ end
161
+ }
162
+ ]
163
+
164
+
165
+ end # Kompiler::Directives
166
+
167
+ end # Kompiler
@@ -0,0 +1,88 @@
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
+ class MachineCode_AST
7
+
8
+ MC_AST_NODES = [
9
+ {name: "get_operand", n_args: 1, func: lambda {|args, state| state[:operands][args[0]][:value]} },
10
+ {name: "get_bits", n_args: 3, func: lambda {|args, state| (args[1]...(args[1] + args[2])).map{|bit_i| args[0][bit_i]} } },
11
+ {name: "get_bits_signed", n_args: 3, func: lambda do |args, state|
12
+ if args[1] == 0
13
+ # If sign should be included
14
+ (args[0] >= 0 ? [0] : [1]) + (0...(args[2] - 1)).map{|bit_i| args[0].abs[bit_i]}
15
+ else
16
+ # If sign shouldn't be included, since the bit range omits it
17
+ ((args[1] - 1)...(args[1] + args[2] - 1)).map{|bit_i| args[0].abs[bit_i]}
18
+ end
19
+ end},
20
+ {name: "reverse", n_args: 1, func: lambda {|args, state| args[0].reverse } },
21
+ {name: "encode_gp_register", n_args: 1, func: lambda {|args, state| args[0][:reg_value] } },
22
+ {name: "add", n_args: 2, func: lambda {|args, state| args[0] + args[1] } },
23
+ {name: "subtract", n_args: 2, func: lambda {|args, state| args[0] - args[1] } },
24
+ {name: "multiply", n_args: 2, func: lambda {|args, state| args[0] * args[1] } },
25
+ {name: "divide", n_args: 2, func: lambda {|args, state| args[0] / args[1] } },
26
+ {name: "modulo", n_args: 2, func: lambda {|args, state| args[0] % args[1] } },
27
+ {name: "get_current_address", n_args: 0, func: lambda {|args, state| state[:current_address] } },
28
+ {name: "get_label_address", n_args: 1, func: lambda {|args, state| state[:labels].include?(args[0]) ? state[:labels][args[0]] : raise("Label \"#{args[0]}\" not found: Program build not possible") } },
29
+ {name: "bits", n_args: "any", func: lambda {|args, state| args } },
30
+ {name: "if_eq_else", n_args: 4, eval_args: false, func: lambda {|args, state| (eval_mc_node_arg(args[0], state) == eval_mc_node_arg(args[1], state)) ? eval_mc_node_arg(args[2], state) : eval_mc_node_arg(args[3], state) }},
31
+ {name: "raise_error", n_args: 1, func: lambda {|args, state| raise args[0] } },
32
+ ]
33
+
34
+ def self.is_ast_node(val)
35
+ val.is_a?(Array) && (val.size >= 1) && val[0].is_a?(String)
36
+ end
37
+
38
+ # If an argument is a node, evaluates it. Otherwise just returns the argument
39
+ def self.eval_mc_node_arg(arg, state)
40
+ is_ast_node(arg) ? run_mc_ast(arg, state) : arg
41
+ end
42
+
43
+ def self.run_mc_ast(node, state)
44
+
45
+ node_name = node[0]
46
+ node_args = node[1..]
47
+
48
+ node_logic = MC_AST_NODES.filter{|any_node| any_node[:name] == node_name}[0]
49
+
50
+ if !node_logic
51
+ raise "MC Node \"#{node_name}\" wasn't found. Cannot build the program"
52
+ end
53
+
54
+ if !node_logic.keys.include?(:eval_args) || node_logic[:eval_args] != false
55
+ node_args.map!{|arg| eval_mc_node_arg(arg, state) }
56
+ end
57
+
58
+ raise "Undefined node \"#{node_name}\"" if !node_logic
59
+
60
+ # Check if the amount of arguments is correct for the node
61
+ raise "Incorrect node use for \"#{node_name}\": Expected #{node_logic[:n_args]} operands, but received #{node_args.size}" if (node_logic[:n_args] != "any") && (node_logic[:n_args] != node_args.size)
62
+
63
+ node_logic[:func].call(node_args, state)
64
+ end
65
+
66
+
67
+ def self.build_mc(mc_constructor, state)
68
+ final = []
69
+ mc_constructor.each do |ast_node|
70
+
71
+ ast_result = run_mc_ast(ast_node, state)
72
+
73
+ # Check if ast_result is only zeros and ones
74
+ is_element_zero_or_one = ast_result.map{|el| [0, 1].include?(el)}
75
+ if is_element_zero_or_one.include?(false)
76
+ raise "MC AST Build resulted in a non-bit value (#{ast_result}): Cannot build the program"
77
+ end
78
+
79
+ final += ast_result
80
+ end
81
+
82
+ return final
83
+ end
84
+
85
+
86
+ end # Kompiler::MC_AST
87
+
88
+ end # Kompiler