lc3spec 0.1.4 → 0.1.5

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.
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