lc3spec 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 702d428ff4102c7ad87b31a65490bbee2e07af52
4
- data.tar.gz: a96200247b660e91c42642b8a085bd7609fccf23
3
+ metadata.gz: 5f5b656483154d73d7f647eab19d47e71b190eb4
4
+ data.tar.gz: 047edfaf5379bacca219828cac075a2c7c94dd7a
5
5
  SHA512:
6
- metadata.gz: 22cf5b67474d74a44a70ec88c9f87062009963176bce69968311446598ac2bcdecd0331fa3289eb4f6482152ace6bf618ba511161b2109cd4f7e6c272f8a8a7f
7
- data.tar.gz: 300ce687d57e631811f47c636fcd35492358c306a90afd9df8ac938c7f6b7478eeb97d9cd713a639d2775badc7bace67cead4c68215801ed27d8e4c44bb6138e
6
+ metadata.gz: 5b4128a0a93a20af60494639a03365a6a83e6cd4b8ba034e809494980a06a76ddc8cebf1fcabd8fb03b0c433346602a30ddcf13ba2f902019822edbb238beb24
7
+ data.tar.gz: 0921354e30fd9779f6bd694e29fccc8d0ab83676dedd92e9c1e97f46e6942bbec44affa380374ed07f7c1ed407a7a1cb1293a9ace383461b3d065428e3d17f56
data/README.md CHANGED
@@ -28,10 +28,6 @@ set registers R0, R1, R2, R3, and R4 to 0, 1, 2, 3, and 4, respectively:
28
28
  We can write a spec, spec.rb, to test it:
29
29
 
30
30
  ```ruby
31
- #!/usr/bin/env ruby
32
-
33
- require 'lc3spec'
34
-
35
31
  test 'Register Expectations' do
36
32
  file 'regs'
37
33
  set_breakpoint 'TRAP_HALT'
@@ -45,7 +41,7 @@ test 'Register Expectations' do
45
41
  end
46
42
  ```
47
43
 
48
- Running with `ruby spec.rb`:
44
+ Running with `lc3spec spec.rb`:
49
45
 
50
46
  ```
51
47
  Register Expectations [FAIL]
@@ -77,18 +73,14 @@ end
77
73
 
78
74
  There are currently two supported options:
79
75
 
80
- * :output - filename or file to write output to, default is $stdout
81
- * :keep_score - whether or not to display score for each test. If false,
76
+ * `:output` - filename or file to write output to, default is `$stdout`
77
+ * `:keep_score` - whether or not to display score for each test. If false,
82
78
  only `[OK]` or `[FAIL]` are displayed, if true, a fractional score
83
79
  is displayed, e.g., `0/1` or `4/4`
84
80
 
85
81
  An example:
86
82
 
87
83
  ```ruby
88
- #!/usr/bin/env ruby
89
-
90
- require 'lc3spec'
91
-
92
84
  configure do
93
85
  set :output, 'feedback.txt'
94
86
  set :keep_score, true
@@ -137,6 +129,7 @@ gem install lc3spec
137
129
 
138
130
  * Documentation
139
131
  * Tests
132
+ * Better expectations, e.g., for arrays or strings in memory
140
133
 
141
134
  # Bugs
142
135
 
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ RSpec::Core::RakeTask.new(:spec)
4
+
5
+ task :default => :spec
6
+ task :test => :spec
@@ -8,10 +8,12 @@ module LC3Spec
8
8
  if number =~ /^(?:x|0x|X|0X)?([0-9A-Fa-f]{1,4})$/
9
9
  "x#{$1.rjust(4, '0').upcase}"
10
10
  else
11
- raise "Unable to normalize number: #{number}"
11
+ raise ArgumentError, "Unable to normalize number: #{number}"
12
12
  end
13
13
  when Fixnum
14
14
  "x#{([number].pack('s>*')).unpack('H*').first.upcase}"
15
+ else
16
+ raise ArgumentError, "Expecting String or Fixnum, got #{number.class}"
15
17
  end
16
18
  end
17
19
 
@@ -29,10 +31,12 @@ module LC3Spec
29
31
 
30
32
  [num_part].pack('H*').unpack('s>*').first
31
33
  else
32
- raise "Unable to normalize number: #{number}"
34
+ raise ArgumentError, "Unable to normalize number: #{number}"
33
35
  end
34
36
  when Fixnum
35
37
  number
38
+ else
39
+ raise ArgumentError, "Expecting String or Fixnum, got #{number.class}"
36
40
  end
37
41
  end
38
42
 
data/lib/lc3spec/lc3.rb CHANGED
@@ -40,20 +40,36 @@ class LC3
40
40
  initialize_lc3sim
41
41
  end
42
42
 
43
+ # Get the value of a register
44
+ #
45
+ # @param [Symbol] reg the register, one of :R0 through :R7,
46
+ # :PC, :IR, :PSR, or :CC
47
+ # @return [String, nil] the register value in hex format (e.g., 'x0000') or
48
+ # nil if the register does not exist.
43
49
  def get_register(reg)
44
50
  reg = reg.to_s.upcase.to_sym # Ruby 1.8 doesn't support Symbol#upcase
45
51
  @registers[reg]
46
52
  end
47
53
 
54
+ # Set the value of a register
55
+ #
56
+ # @param (see #get_register)
57
+ # @param [String] val the register value in hex format (e.g., 'xF1D0')
58
+ # @raise [ArgumentError] if the register is invalid or the value is nil,
59
+ # @return [self]
48
60
  def set_register(reg, val)
49
- reg = reg.to_s.upcase # Don't use ! version because it doesn't work for symbols
61
+ reg = reg.upcase
50
62
 
51
63
  unless @registers.keys.include? reg.to_sym
52
- raise "Invalid register: #{reg.to_s}"
64
+ raise ArgumentError, "Invalid register: #{reg.to_s}"
53
65
  end
54
66
 
55
67
  if val.nil?
56
- raise "Invalid register value for #{reg.to_s}: #{val}"
68
+ raise ArgumentError, "Invalid register value for #{reg.to_s}: #{val}"
69
+ end
70
+
71
+ if reg.to_sym == :CC and not ['POSITIVE', 'NEGATIVE', 'ZERO'].include? val
72
+ raise ArgumentError, "CC can only be set to NEGATIVE, ZERO, or POSITIVE"
57
73
  end
58
74
 
59
75
  @io.puts "register #{reg.to_s} #{normalize_to_s(val)}"
@@ -62,21 +78,37 @@ class LC3
62
78
  msg = @io.readline
63
79
  parse_msg msg.strip
64
80
 
65
- break if msg =~ /^TOCODE/
81
+ break if msg =~ /^ERR|TOCODE/
66
82
  end
67
83
 
68
84
  self
69
85
  end
70
86
 
87
+ # Return value in memory at the given address or label
88
+ #
89
+ # @param [String] addr an address in hex format (e.g., 'xADD4') or a label
90
+ # @raise [ArgumentError] if the argument is not a existing label or is an
91
+ # invalid address
92
+ # @return [String] the value in memory in hex format
71
93
  def get_memory(addr)
72
94
  if addr.respond_to?(:upcase)
73
- label_addr = get_address(addr)
74
- addr = label_addr unless label_addr.nil?
95
+ label_addr = get_address(addr.upcase)
96
+
97
+ return @memory[label_addr] unless label_addr.nil?
75
98
  end
76
99
 
77
- @memory[normalize_to_i(addr)]
100
+ @memory[normalize_to_s(addr)]
78
101
  end
79
102
 
103
+ # Set memory at the given address or label
104
+ #
105
+ # If val is a label, mem[addr] will be set to the address of val
106
+ #
107
+ # @param [String] addr an address or a label
108
+ # @param [String] val a value or a label
109
+ # @raise [ArgumentError] if addr is not an existing label, addr is an
110
+ # invalid address, val is not an existing label, or val is invalid
111
+ # @return [self]
80
112
  def set_memory(addr, val)
81
113
  # addr may be memory address or label
82
114
 
@@ -88,7 +120,15 @@ class LC3
88
120
  addr = normalize_to_s(addr)
89
121
  end
90
122
 
91
- @io.puts("memory #{addr} #{normalize_to_s(val)}")
123
+ # Value can be a label too
124
+ if val.respond_to?(:upcase) and @labels.include?(val.upcase.to_s)
125
+ # Is a label
126
+ else
127
+ # Is a value
128
+ val = normalize_to_s(val)
129
+ end
130
+
131
+ @io.puts("memory #{addr} #{val}")
92
132
 
93
133
  loop do
94
134
  msg = @io.readline
@@ -100,10 +140,21 @@ class LC3
100
140
  self
101
141
  end
102
142
 
143
+ # Get the address of a label
144
+ #
145
+ # @param [String] label
146
+ # @return [String, nil] the address of the label, or nil if the label does
147
+ # not exist
103
148
  def get_address(label)
104
149
  @labels[label.upcase.to_s]
105
150
  end
106
151
 
152
+ # Load a file given a path
153
+ #
154
+ # @param [String] filename the path of a file to load into lc3sim. The
155
+ # argument should have .obj extension or no extension at all, in which
156
+ # case .obj is assumed
157
+ # @return [self]
107
158
  def file(filename)
108
159
  @io.puts "file #{filename}"
109
160
 
@@ -129,6 +180,9 @@ class LC3
129
180
  self
130
181
  end
131
182
 
183
+ # Execute one instruction
184
+ #
185
+ # @return [self]
132
186
  def step
133
187
  @io.puts 'step'
134
188
 
@@ -137,6 +191,9 @@ class LC3
137
191
  self
138
192
  end
139
193
 
194
+ # Execute instructions until halt or breakpoint
195
+ #
196
+ # @return [self]
140
197
  def continue
141
198
  @io.puts 'continue'
142
199
 
@@ -145,6 +202,11 @@ class LC3
145
202
  self
146
203
  end
147
204
 
205
+ # Set a breakpoint at an address or label
206
+ #
207
+ # @param (see #get_memory)
208
+ # @raise (see #get_memory)
209
+ # @return [self]
148
210
  def set_breakpoint(addr)
149
211
  addr = addr.upcase.to_s if addr.respond_to? :upcase
150
212
  if @labels.include? addr
@@ -163,6 +225,11 @@ class LC3
163
225
  self
164
226
  end
165
227
 
228
+ # Clear a breakpoint at an address or label
229
+ #
230
+ # @param (see #set_breakpoint)
231
+ # @raise (see #set_breakpoint)
232
+ # @return [self]
166
233
  def clear_breakpoint(addr)
167
234
  if addr == :all
168
235
  @io.puts 'break clear all'
@@ -187,30 +254,84 @@ class LC3
187
254
  self
188
255
  end
189
256
 
257
+ # Clear all breakpoints
258
+ #
259
+ # @return [self]
190
260
  def clear_breakpoints
191
261
  clear_breakpoint :all
192
262
  end
193
263
 
264
+ # Return output from the LC-3
265
+ #
266
+ # The LC-3 welcome messages and halt messages are stripped from the output,
267
+ # so that it can easily compared to expected output.
268
+ #
269
+ # WARNING: This is potentially buggy as there is no signal from lc3sim when
270
+ # the output is ready, so this function just waits until output appears and
271
+ # reads what is available. This may not work if the output is very long.
272
+ #
273
+ # @return [String] the output from the LC-3, or an empty string
274
+ # if there is no output
194
275
  def get_output
195
276
  out = ''
196
277
 
278
+ # Do something to wait for output to be ready
279
+ set_register(:PSR, @registers[:PSR])
280
+
197
281
  # There is no signal that tells the GUI that output is ready...
198
282
  # FIXME: This is a bug waiting to happen
199
- retries = 10
200
- until @output.ready?
201
- sleep(0.1)
202
-
203
- retries -= 1
204
- break if retries <= 0
205
- end
206
-
207
- while @output.ready?
208
- out << @output.readpartial(1024)
283
+ catch(:done) do
284
+ loop do
285
+ retries = 5
286
+ until @output.ready?
287
+ sleep(0.1)
288
+
289
+ retries -= 1
290
+ throw :done if retries <= 0
291
+ end
292
+
293
+ while @output.ready?
294
+ out << @output.readpartial(1024)
295
+ end
296
+ end
209
297
  end
210
298
 
211
299
  out.gsub("\n\n--- halting the LC-3 ---\n\n", '')
212
300
  end
213
301
 
302
+ # Close open file descriptors for communicating with lc3sim
303
+ #
304
+ # @return [nil]
305
+ def close
306
+ @io.close
307
+ @output.close
308
+ @server.close
309
+ end
310
+
311
+ # Return a string containing a human-readable representation of the current
312
+ # state of the LC-3
313
+ #
314
+ # @return [String]
315
+ def inspect
316
+ registers = @registers.map { |k, v| "#{k}=#{v}" }.join(' ')
317
+
318
+ addr_to_label = @labels.invert
319
+
320
+ memory_header = "%18s ADDR VALUE" % "label"
321
+ memory = @memory.map do |addr, value|
322
+ "%18s %s %s" % [(addr_to_label[addr] or ''), addr, value]
323
+ end.join("\n")
324
+
325
+ #[memory_header, memory, registers].join("\n")
326
+ registers
327
+ end
328
+
329
+ # def to_s
330
+ # @registers.map { |k, v| "#{k}=#{v}" }.join(' ')
331
+ # end
332
+
333
+ private
334
+
214
335
  def initialize_lc3sim
215
336
  # Start lc3sim instance
216
337
  @io = IO.popen(%w(lc3sim -gui), 'r+')
@@ -244,32 +365,6 @@ class LC3
244
365
  end
245
366
  end
246
367
 
247
- def close
248
- @output.close
249
- @server.close
250
- end
251
-
252
- def inspect
253
- registers = @registers.map { |k, v| "#{k}=#{v}" }.join(' ')
254
-
255
- addr_to_label = @labels.invert
256
-
257
- memory_header = "%18s ADDR VALUE" % "label"
258
- memory = @memory.map do |addr, value|
259
- "%18s %s %s" % [(addr_to_label[addr] or ''),
260
- normalize_to_s(addr), value]
261
- end.join("\n")
262
-
263
- #[memory_header, memory, registers].join("\n")
264
- registers
265
- end
266
-
267
- # def to_s
268
- # @registers.map { |k, v| "#{k}=#{v}" }.join(' ')
269
- # end
270
-
271
- private
272
-
273
368
  def parse_until_print_registers
274
369
  loop do
275
370
  msg = @io.readline
@@ -296,23 +391,21 @@ class LC3
296
391
  when 'BCLEAR'
297
392
  when 'BREAK'
298
393
  when 'CODE', 'CODEP' # Line of code
299
- # Address
300
- addr = tokens.shift.to_i - 1
394
+ # Numeric address
395
+ num_addr = tokens.shift.to_i - 1
301
396
 
302
397
  # Note: if there is a breakpoint at the current address, the last
303
- # token.shift produces a string like "12289B". Luckily, #to_i just
304
- # ignores the B at the end.
398
+ # token.shift produces a string like "12289B". Luckily, to_i just
399
+ # reads whatever it can and discards the rest.
305
400
 
306
401
  # Label, if present
307
- if tokens.first != "x%04X" % addr
308
- label = tokens.shift.upcase
402
+ label = tokens.first != "x%04X" % num_addr ? tokens.shift.upcase : nil
309
403
 
310
- # Don't overwrite label if one already exists
311
- @labels[label] ||= addr
312
- end
404
+ # hex address
405
+ addr = tokens.shift
313
406
 
314
- # Discard hex address
315
- tokens.shift
407
+ # Insert label, but don't overwrite if it already exists
408
+ @labels[label] ||= addr if not label.nil?
316
409
 
317
410
  # Value
318
411
  val = tokens.shift
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lc3spec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chun Yang
@@ -9,7 +9,21 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
  date: 2013-02-27 00:00:00.000000000 Z
12
- dependencies: []
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
13
27
  description: DSL for testing LC-3 assembly programs
14
28
  email: x@cyang.info
15
29
  executables:
@@ -30,6 +44,7 @@ files:
30
44
  - bin/lc3spec
31
45
  - LICENSE
32
46
  - README.md
47
+ - Rakefile
33
48
  homepage: http://github.com/chunyang/lc3spec
34
49
  licenses:
35
50
  - MIT