ucisc 0.1.3 → 0.1.4

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.
data/exe/ucisc CHANGED
@@ -6,14 +6,17 @@ require "byebug"
6
6
 
7
7
  if ARGV.length < 1
8
8
  puts "Usage:"
9
- puts " ucisc <file_name>"
9
+ puts " ucisc <file_name> [<file_name> ...]"
10
10
  exit(0)
11
11
  end
12
12
 
13
- file_name = ARGV.first
14
- puts "Reading #{file_name}"
15
- compiler = MicroCisc.load(file_name)
16
- puts "Running program with #{compiler.serialize.size} words"
13
+ file_names = ARGV.reject { |name| name.start_with?("-") }
14
+ puts "Reading #{file_names.join(" ")}"
15
+ compiler = MicroCisc.load(file_names)
16
+
17
+ instructions = compiler.command_count
18
+ size = compiler.serialize.size
19
+ puts "Running program with #{instructions} instructions compiled to #{size} words"
17
20
 
18
21
  begin
19
22
  MicroCisc.run(compiler.serialize)
@@ -13,25 +13,48 @@ require "micro_cisc/vm/device"
13
13
  require "micro_cisc/vm/processor"
14
14
  require "micro_cisc/vm/term_device"
15
15
  require "micro_cisc/vm/empty_device"
16
+ require "micro_cisc/vm/color_lcd_display"
16
17
 
17
18
  module MicroCisc
18
19
  class Error < StandardError; end
19
20
 
20
- def self.load(file_name)
21
- text = File.read(file_name)
21
+ def self.load(file_names)
22
+ text = ""
23
+ file_names.each do |file_name|
24
+ text += File.read(file_name)
25
+ end
22
26
  MicroCisc::Compile::Compiler.new(text)
23
27
  end
24
28
 
25
29
  def self.run(data)
26
- rom = Array.new(256).map { 0 }
27
- rom[0...data.size] = data
30
+ blocks = data.size / 256 + 1
31
+ rom_blocks = []
32
+ (0...blocks).each do |block|
33
+ rom = Array.new(256).map { 0 }
34
+ size = data.size - block * 256
35
+ size = 256 if size > 256
36
+ rom[0...size] = data[(block * 256)...((block + 1) * 256)]
37
+ rom_blocks << rom
38
+ end
28
39
  terminal = MicroCisc::Vm::TermDevice.new(5)
29
- terminal.host_device = 1
30
- devices = Array.new(16).map { MicroCisc::Vm::EmptyDevice.new }
40
+ screen = MicroCisc::Vm::EmptyDevice.new
41
+ init_screen = ARGV.include?('-s')
42
+ if(init_screen)
43
+ screen = MicroCisc::Vm::ColorLcdDisplay.new(
44
+ 6, # Device ID
45
+ 40, # 10k words of memory (40 blocks)
46
+ 128, # screen pixel width
47
+ 72, # screen pixel height
48
+ MicroCisc::Vm::ColorLcdDisplay::COLOR_MODE_12BIT
49
+ )
50
+ end
51
+ devices = Array.new(17).map { MicroCisc::Vm::EmptyDevice.new }
31
52
  devices[15] = terminal # first banked device
32
- processor = MicroCisc::Vm::Processor.new(1, 256, [rom])
53
+ devices[16] = screen
54
+ processor = MicroCisc::Vm::Processor.new(1, 256, rom_blocks)
33
55
  processor.devices = devices
34
56
  processor.start(ARGV.include?('-d'))
57
+ screen.join if(init_screen)
35
58
  processor
36
59
  end
37
60
 
@@ -1,8 +1,11 @@
1
1
  module MicroCisc
2
2
  module Compile
3
3
  class Compiler
4
+ attr_reader :command_count
5
+
4
6
  def initialize(text)
5
7
  @text = text
8
+ @command_count = 0
6
9
  parse
7
10
  end
8
11
 
@@ -11,17 +14,21 @@ module MicroCisc
11
14
  @instructions = []
12
15
  address = 0
13
16
  @labels = {}
14
- @sugar = {}
17
+ @indexed_vars = {}
18
+ @equivalents = {}
15
19
  errors = []
16
20
  lgen = MicroCisc::Compile::LabelGenerator.new
21
+ @command_count = 0
17
22
  @text.each_line do |line|
18
23
  begin
19
- statement = MicroCisc::Compile::Statement.new(lgen, line, @sugar)
24
+ statement = MicroCisc::Compile::Statement.new(lgen, line, @indexed_vars, @equivalents)
25
+ command = false
20
26
  statement.parse.each do |instruction|
21
27
  if instruction.label?
22
28
  @labels[instruction.label] = address
23
29
  elsif instruction.instruction?
24
30
  @instructions << [line_number, instruction]
31
+ command = true
25
32
  address += 1
26
33
  elsif instruction.data?
27
34
  @instructions += instruction.data.map { |d| [line_number, d] }
@@ -38,6 +45,7 @@ module MicroCisc
38
45
  address += word_counts.sum
39
46
  end
40
47
  end
48
+ @command_count += 1 if command
41
49
  rescue ArgumentError => e
42
50
  MicroCisc.logger.error("Error on line #{line_number}: #{e.message}\n #{line}")
43
51
  errors << [line_number, e, line]
@@ -1,19 +1,20 @@
1
1
  module MicroCisc
2
2
  module Compile
3
3
  class Instruction
4
- attr_reader :label, :instruction, :data, :imm, :sign, :dir, :reg, :dest, :original, :minimal
4
+ COMPONENT_WITH_QUOTES_REGEX = /(?<component>[^\s"']+|"((\\"|[^"])*)")/
5
+ attr_reader :label, :operation, :data, :immediates, :sign, :dir, :src, :dest, :original, :minimal, :inc
5
6
 
6
- def initialize(label_generator, minimal, original, sugar)
7
+ def initialize(label_generator, minimal, original, statement)
7
8
  @label_generator = label_generator
8
9
  @original = original
9
10
  @label = nil
10
11
  @operation = nil
11
12
  @sign = nil
12
13
  @dir = nil
13
- @imm = nil
14
+ @immediates = []
14
15
  @src = nil
15
16
  @dest = nil
16
- @sugar = sugar
17
+ @statement = statement
17
18
  @data = nil
18
19
  @inc = nil
19
20
  @eff = nil
@@ -38,7 +39,7 @@ module MicroCisc
38
39
  end
39
40
 
40
41
  def parse_ucisc(minimal_instruction)
41
- @components = minimal_instruction.split(/\s/)
42
+ @components = minimal_instruction.scan(COMPONENT_WITH_QUOTES_REGEX).flatten
42
43
 
43
44
  return if @components.empty?
44
45
 
@@ -57,6 +58,28 @@ module MicroCisc
57
58
 
58
59
  if @components.first == '%'
59
60
  @components.shift
61
+ @components = @components.map do |component|
62
+ if component =~ /"(\\"|[^"])*"/
63
+ # Remove surrounding quotes
64
+ component = component[1...(component.length - 1)]
65
+ component = component.gsub("\\n","\n")
66
+ component = component.gsub("\\\"","\"")
67
+ hex = []
68
+ offset = 0
69
+ while(offset < component.length)
70
+ pair = component[offset...(offset + 2)]
71
+ pair = pair.bytes
72
+ pair << 0 if pair.length < 2
73
+ word = pair.pack("C*").unpack("S>").last
74
+ hex_word = "%04X" % word
75
+ hex << hex_word
76
+ offset += 2
77
+ end
78
+ component = ["%04X" % component.length] + hex
79
+ else
80
+ component
81
+ end
82
+ end.flatten
60
83
  @data = @components.map do |component|
61
84
  if match = /(?<name>[^\s]+)\.(?<type>imm|disp)/.match(component)
62
85
  [match['name'], match['type']]
@@ -98,7 +121,7 @@ module MicroCisc
98
121
  raise ArgumentError, "Duplicate #{component.last} value" if current
99
122
  code = component.first
100
123
  unless code >= 0 && code < 16
101
- raise ArgumentError, "Value of #{component.last} must be between 0x0 and 0xF instead of #{component.first.to_s(16).upcase}"
124
+ raise ArgumentError, "Value of #{component.last} must be between 0x0 and 0xF instead of 0x#{component.first.to_s(16).upcase}"
102
125
  end
103
126
  component.first
104
127
  end
@@ -135,8 +158,10 @@ module MicroCisc
135
158
  while components.size > 0
136
159
  to_parse = components.shift
137
160
  if(to_parse.start_with?('$') || to_parse.start_with?('&'))
138
- raise ArgumentError, "Missing ref #{to_parse}" unless @sugar[to_parse]
139
- to_parse = @sugar[to_parse]
161
+ raise ArgumentError, "Missing ref #{to_parse}" unless @statement.get_var(to_parse)
162
+ to_parse = @statement.get_var(to_parse)
163
+ components = to_parse.split(/\s/) + components
164
+ to_parse = components.shift
140
165
  end
141
166
  parsed = parse_component(to_parse)
142
167
  if ['val', 'reg', 'mem'].include?(parsed.last)
@@ -198,7 +223,9 @@ module MicroCisc
198
223
  end
199
224
  end
200
225
 
201
- if @immediates.last != 0 && (!@source_is_mem || !@dest_is_mem)
226
+ if @immediates.last != 0 && @operation == 'alu'
227
+ raise ArgumentError, "Destination immediate is not allowed for compute instructions"
228
+ elsif @immediates.last != 0 && (!@source_is_mem || !@dest_is_mem)
202
229
  raise ArgumentError, "Destination immediate is only allowed when both arguments are mem args"
203
230
  end
204
231
 
@@ -208,18 +235,18 @@ module MicroCisc
208
235
 
209
236
  def validate_immediate(value, index)
210
237
  width = @bit_width
211
- width = width / 2 if @source_is_mem && @dest_is_mem
238
+ width = width / 2 if @operation == 'copy' && @source_is_mem && @dest_is_mem
212
239
  if index == 0 && @source_is_mem || index == 1 && @dest_is_mem
213
240
  min = 0
214
- max = (2 << @bit_width) - 1
241
+ max = (1 << width) - 1
215
242
  else
216
- magnitude = 1 << (@bit_width - 1)
243
+ magnitude = 1 << (width - 1)
217
244
  min = magnitude * -1
218
245
  max = magnitude - 1
219
246
  end
220
247
  if (value < min || value > max)
221
248
  signed = @source_is_mem ? 'unsigned' : 'signed'
222
- raise ArgumentError, "Immediate max bits is #{@bit_width} #{signed}; value must be between #{min} and #{max} instead of #{value}"
249
+ raise ArgumentError, "Immediate max bits is #{width} #{signed}; value must be between #{min} and #{max} instead of #{value}"
223
250
  end
224
251
  end
225
252
 
@@ -1,16 +1,17 @@
1
1
  module MicroCisc
2
2
  module Compile
3
3
  class Statement
4
- SUGAR_REGEX = /(?<name>(\$|&)[^\s\[\]]+)\s+(?<op>as|=)\s+(?<param>.+)/
4
+ SUGAR_REGEX = /(?<names>(\$|&)[^\s\[\]]+(\s*,\s*(\$|&)[^\s\[\]]+)*)\s+(?<op>as|=)\s+(?<param>.+)/
5
5
  FUNCTION_REGEX = /(?<stack>[^\s\[\]]+)\s*(\[(?<words>[0-9]+)\]){0,1}\s+<=\s+(?<label>[a-zA-Z_][a-zA-Z0-9_\-@$!%]*)\s*\(\s*(?<args>[^)]*)/
6
- IMM_REGEX = / (0x){0,1}(?<imm_val>[0-9A-Fa-f])\.imm/
6
+ IMM_REGEX = / (?<imm_val>-{0,1}(0x){0,1}[0-9A-Fa-f])\.imm/
7
7
  attr_reader :original, :minimal
8
8
 
9
- def initialize(label_generator, statement, sugar)
9
+ def initialize(label_generator, statement, indexed_vars, equivalents)
10
10
  @label_generator = label_generator
11
11
  @original = statement
12
12
  @minimal = filter_comments(statement)
13
- @sugar = sugar
13
+ @indexed_vars = indexed_vars
14
+ @equivalents = equivalents
14
15
  end
15
16
 
16
17
  def filter_comments(instruction)
@@ -24,44 +25,124 @@ module MicroCisc
24
25
  instruction.gsub(/\s+/, ' ')
25
26
  end
26
27
 
27
- def create_variable(name, arg)
28
- if arg.end_with?("mem") || arg.end_with?("reg")
28
+ def normalize(arg, delta)
29
+ imm_matches = arg.scan(IMM_REGEX).flatten
30
+ imm_val = imm_matches.map(&:to_i).sum + delta
31
+ arg = arg.gsub(IMM_REGEX, '').strip
32
+ [arg, imm_val]
33
+ end
34
+
35
+ def create_variable(name, arg, delta)
36
+ unless arg.include?("mem") || arg.include?("reg")
37
+ raise ArgumentError, "Indexed variable reference is not allowed for non-register arguments, use 'as' instead"
38
+ end
39
+
40
+ arg = normalize(arg, delta)
41
+ imm_val = arg.last
42
+ # remove mem/reg part
43
+ arg_num = arg.first[0..-4]
44
+ name = name[1..-1]
45
+
46
+ mem_name = "$#{name}"
47
+ ref_name = "&#{name}"
48
+ @equivalents.delete(mem_name)
49
+ @equivalents.delete(ref_name)
50
+ @indexed_vars[mem_name] = ["#{arg_num}mem", imm_val]
51
+ @indexed_vars[ref_name] = ["#{arg_num}reg", imm_val]
52
+ end
53
+
54
+ def create_equivalent(name, arg, delta)
55
+ arg = normalize(arg, delta)
56
+ if arg.first.include?("mem") || arg.first.include?("reg")
57
+ imm_val = arg.last
58
+ # remove mem/reg part
59
+ arg_num = arg[0][0..-4]
29
60
  name = name[1..-1]
30
- arg = arg[0..-4]
31
61
 
32
- @sugar["$#{name}"] = "#{arg}mem"
33
- @sugar["&#{name}"] = "#{arg}reg"
62
+ mem_name = "$#{name}"
63
+ ref_name = "&#{name}"
64
+ @indexed_vars.delete(mem_name)
65
+ @indexed_vars.delete(ref_name)
66
+
67
+ imm_str = " #{imm_val}.imm" if imm_val > 0
68
+ @equivalents[mem_name] = "#{arg_num}mem#{imm_str}"
69
+ @equivalents[ref_name] = "#{arg_num}reg#{imm_str}"
34
70
  else
35
- @sugar[name] = arg
71
+ @indexed_vars.delete(name)
72
+ @equivalents[name] = arg.first
36
73
  end
37
74
  end
38
75
 
76
+ def update_variable(arg, delta)
77
+ arg = arg.sub('reg', 'mem')
78
+ ['mem', 'reg'].each do |type|
79
+ variable = arg.sub('mem', type)
80
+ @indexed_vars.each do |name, value|
81
+ value[1] += delta if value.first == variable
82
+ end
83
+ end
84
+ end
85
+
86
+ def get_var(name)
87
+ val = @equivalents[name]
88
+ val ||=
89
+ begin
90
+ pair = @indexed_vars[name]
91
+ "#{pair.first} #{pair.last}.imm" if pair
92
+ end
93
+ val || name
94
+ end
95
+
39
96
  def parse
97
+ instruction = nil
98
+ names = []
99
+ op = nil
40
100
  if FUNCTION_REGEX =~ @minimal
41
- parse_function_call
101
+ return parse_function_call
42
102
  elsif SUGAR_REGEX =~ @minimal
43
103
  match = SUGAR_REGEX.match(@minimal)
44
- name = match['name']
45
- if match['op'] == 'as'
46
- create_variable(name, match['param'])
47
- []
48
- elsif match['op'] == '='
49
- @minimal = match['param']
50
- instruction = Instruction.new(@label_generator, minimal, original, @sugar)
51
- dest = instruction.dest
52
- if [1, 2, 3].include?(dest)
53
- create_variable(name, "#{dest}.mem")
54
- else
55
- dest -= 4 if dest > 4
56
- create_variable(name, "#{dest}.reg")
57
- end
58
- [instruction]
104
+ op = match['op']
105
+ names = match['names'].split(',').map(&:strip)
106
+
107
+ @minimal = match['param']
108
+ if minimal.start_with?('copy') || minimal.start_with?('compute')
109
+ instruction = Instruction.new(@label_generator, @minimal, original, self)
59
110
  else
60
- raise ArgumentError, "Invalid syntax declaration: #{@minimal}"
111
+ var = match['param']
112
+ imm_match = IMM_REGEX.match(var)
113
+ imm_val = imm_match ? imm_match['imm_val'].to_i : 0
114
+ var = var.to_s.gsub(IMM_REGEX, '').strip
115
+ @minimal = "#{get_var(var)}#{" #{imm_val}.imm" if imm_val != 0}"
61
116
  end
62
117
  else
63
- [Instruction.new(@label_generator, @minimal, original, @sugar)]
118
+ instruction = Instruction.new(@label_generator, @minimal, original, self)
64
119
  end
120
+ if instruction && instruction.instruction?
121
+ dest = instruction.dest
122
+ if instruction.inc && instruction.inc > 0
123
+ if dest > 4
124
+ # pop
125
+ update_variable("#{dest - 4}.mem", -1)
126
+ else
127
+ # push
128
+ update_variable("#{dest}.mem", 1)
129
+ end
130
+ end
131
+ if minimal.start_with?('copy') && dest > 4 && instruction.src == dest
132
+ # Manually modifying a register
133
+ update_variable("#{dest - 4}.mem", -1 * instruction.immediates.first)
134
+ end
135
+ dest -= 4 if dest > 4
136
+ @minimal = "#{dest}.mem"
137
+ end
138
+ names.each_with_index do |name, index|
139
+ if op == 'as'
140
+ create_equivalent(name, @minimal, index)
141
+ elsif op == '='
142
+ create_variable(name, @minimal, index)
143
+ end
144
+ end
145
+ [instruction].compact
65
146
  end
66
147
 
67
148
  def parse_function_call
@@ -69,7 +150,7 @@ module MicroCisc
69
150
  label = match['label']
70
151
 
71
152
  stack = match['stack']
72
- stack = @sugar[stack] if @sugar[stack]
153
+ stack = get_var(stack)
73
154
  raise ArgumentError, "Invalid stack param, mem register expected: #{stack}" unless stack =~ /^[1-3]\.mem$/
74
155
  stackp = stack.sub('mem', 'reg')
75
156
 
@@ -79,15 +160,15 @@ module MicroCisc
79
160
  instructions = []
80
161
  if return_words > 0
81
162
  instruction = "copy #{stackp} -#{return_words}.imm #{stackp}"
82
- instructions << Instruction.new(@label_generator, instruction, " #{instruction} # return vars - #{original}", @sugar)
163
+ instructions << Instruction.new(@label_generator, instruction, " #{instruction} # return vars - #{original}", self)
83
164
  end
84
165
 
85
166
  instruction = "copy 0.reg #{args.size + 2}.imm #{stack} push"
86
- instructions << Instruction.new(@label_generator, instruction, " #{instruction} # return addr - #{original}", @sugar)
167
+ instructions << Instruction.new(@label_generator, instruction, " #{instruction} # return addr - #{original}", self)
87
168
 
88
169
  stack_delta = 1 + return_words
89
170
  args = args.each do |arg|
90
- arg = arg.split(' ').map { |a| @sugar[a] || a }.join(' ')
171
+ arg = arg.split(' ').map { |a| get_var(a) || a }.join(' ')
91
172
  is_stack = arg.start_with?(stack)
92
173
  if is_stack
93
174
  offset = stack_delta
@@ -102,10 +183,13 @@ module MicroCisc
102
183
  end
103
184
  instruction = "copy #{arg} #{stack} push"
104
185
  stack_delta += 1
105
- instructions << Instruction.new(@label_generator, instruction, " #{instruction} # push arg - #{original}", @sugar)
186
+ instructions << Instruction.new(@label_generator, instruction, " #{instruction} # push arg - #{original}", self)
106
187
  end
107
188
  instruction = "copy 0.reg #{label}.disp 0.reg"
108
- instructions << Instruction.new(@label_generator, instruction, " #{instruction} # call - #{original}", @sugar)
189
+ instructions << Instruction.new(@label_generator, instruction, " #{instruction} # call - #{original}", self)
190
+ if return_words > 0
191
+ update_variable(stack, return_words)
192
+ end
109
193
  instructions
110
194
  end
111
195
  end