kompiler 0.0.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,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