ucisc 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
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