lc3spec 0.1.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.
- checksums.yaml +7 -0
- data/LICENSE +19 -0
- data/README.md +7 -0
- data/lib/lc3spec/base.rb +207 -0
- data/lib/lc3spec/constants.rb +17 -0
- data/lib/lc3spec/dsl.rb +108 -0
- data/lib/lc3spec/errors.rb +5 -0
- data/lib/lc3spec/expectations.rb +68 -0
- data/lib/lc3spec/helpers.rb +43 -0
- data/lib/lc3spec/lc3.rb +352 -0
- data/lib/lc3spec/main.rb +11 -0
- data/lib/lc3spec/reporter.rb +23 -0
- data/lib/lc3spec.rb +2 -0
- metadata +55 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 6d874750aeeb6b7c7659d5c68945fdf7d062d97f
|
|
4
|
+
data.tar.gz: e112c5c35204cf1e18567aaece03b4e9cc18a19a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 3589a767df9570d047bd4d7927d5cfdd78cdcf23017ec3f16e13b81366197f4d2479f247cc8a6eb46f8a97fdc693f7e1d18f6939f67c894d24fe245d7a80bdc1
|
|
7
|
+
data.tar.gz: deab9fada997b3b30cd4bbe5f8a5ea9efdd7e0fe2599899c1a9d2a1ee2c918196d45b85d7d648389466b96b7d0a8e90d4c2cd1eba3c125c5d1e7ece44442684e
|
data/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2013 Chun Yang
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
data/lib/lc3spec/base.rb
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
require 'open3'
|
|
2
|
+
require 'timeout'
|
|
3
|
+
|
|
4
|
+
require 'lc3spec/constants'
|
|
5
|
+
require 'lc3spec/errors'
|
|
6
|
+
require 'lc3spec/expectations'
|
|
7
|
+
require 'lc3spec/lc3'
|
|
8
|
+
require 'lc3spec/helpers'
|
|
9
|
+
require 'lc3spec/reporter'
|
|
10
|
+
|
|
11
|
+
module LC3Spec
|
|
12
|
+
class Test
|
|
13
|
+
include LC3Spec::Helpers
|
|
14
|
+
|
|
15
|
+
attr_accessor :pass, :reporter
|
|
16
|
+
|
|
17
|
+
def initialize(options, &block)
|
|
18
|
+
# Save src_dir
|
|
19
|
+
@src_dir = File.expand_path(Dir.pwd)
|
|
20
|
+
@reporter = Reporter.new
|
|
21
|
+
@pass = true
|
|
22
|
+
|
|
23
|
+
# Do everything inside tmp_dir
|
|
24
|
+
Dir.mktmpdir('spec') do |tmp_dir|
|
|
25
|
+
Dir.chdir(tmp_dir) do
|
|
26
|
+
@lc3 = LC3.new
|
|
27
|
+
|
|
28
|
+
begin
|
|
29
|
+
instance_eval(&block) if block_given?
|
|
30
|
+
rescue Timeout::Error => err
|
|
31
|
+
@reporter.report "Execution timed-out, likely due to an infinite loop"
|
|
32
|
+
rescue LC3Spec::DoesNotAssembleError => err
|
|
33
|
+
@reporter.report err.message
|
|
34
|
+
$stderr.puts err.message
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
@pass = false if @reporter.fail?
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def set_register(reg, val = 0)
|
|
43
|
+
case reg
|
|
44
|
+
when Hash
|
|
45
|
+
reg.each do |r, v|
|
|
46
|
+
@lc3.set_register(r, v)
|
|
47
|
+
end
|
|
48
|
+
when Array
|
|
49
|
+
reg.each_with_index do |v, i|
|
|
50
|
+
@lc3.set_register("R#{i}", v)
|
|
51
|
+
end
|
|
52
|
+
else
|
|
53
|
+
@lc3.set_register(reg, val)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
alias_method :set_registers, :set_register
|
|
58
|
+
|
|
59
|
+
def file_from_asm(asm)
|
|
60
|
+
assemble_and_load { |f| f.puts asm }
|
|
61
|
+
|
|
62
|
+
self
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def file(filename)
|
|
66
|
+
ensure_present(filename)
|
|
67
|
+
ensure_assembled(filename)
|
|
68
|
+
@lc3.file(filename)
|
|
69
|
+
|
|
70
|
+
self
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def continue(duration = 1.5)
|
|
74
|
+
Timeout.timeout(duration) do
|
|
75
|
+
@lc3.continue
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
alias_method :load_file, :file
|
|
80
|
+
|
|
81
|
+
def set_label(label, addr)
|
|
82
|
+
label = label.to_s.upcase
|
|
83
|
+
|
|
84
|
+
if @labels.include? label
|
|
85
|
+
raise "Unable to replace label #{label}"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
assemble_and_load do |f|
|
|
89
|
+
f.puts ".ORIG #{normalize_to_s(addr)}\n#{label}\n.END"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
self
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def method_missing(method_name, *arguments, &block)
|
|
96
|
+
if @lc3.respond_to? method_name
|
|
97
|
+
@lc3.send(method_name, *arguments, &block)
|
|
98
|
+
|
|
99
|
+
self
|
|
100
|
+
elsif method_name.to_s =~ /^expect_/
|
|
101
|
+
LC3Spec::Expectations.send(method_name, @lc3, @reporter, *arguments, &block)
|
|
102
|
+
|
|
103
|
+
self
|
|
104
|
+
else
|
|
105
|
+
super
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
110
|
+
if @lc3.respond_to? method_name
|
|
111
|
+
true
|
|
112
|
+
elsif method_name.to_s =~ /^expect_/
|
|
113
|
+
true
|
|
114
|
+
else
|
|
115
|
+
super
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
def assemble_and_load(name = 'lc3spec-tmp', &block)
|
|
122
|
+
return nil unless block_given?
|
|
123
|
+
|
|
124
|
+
f = Tempfile.new([name, '.asm'])
|
|
125
|
+
begin
|
|
126
|
+
yield f
|
|
127
|
+
f.close
|
|
128
|
+
|
|
129
|
+
ensure_assembled(f.path)
|
|
130
|
+
prefix = f.path.chomp(File.extname(f.path))
|
|
131
|
+
@lc3.file(prefix)
|
|
132
|
+
|
|
133
|
+
ensure
|
|
134
|
+
f.unlink
|
|
135
|
+
|
|
136
|
+
# Try not to accidentally delete all files....
|
|
137
|
+
unless prefix.nil? or prefix.empty?
|
|
138
|
+
Dir.glob(prefix + '*') do |filename|
|
|
139
|
+
File.unlink filename
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def ensure_present(filename)
|
|
146
|
+
if filename.nil? or filename.empty?
|
|
147
|
+
raise DoesNotAssembleError, 'Filename nil or empty'
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Copy file to current directory if needed
|
|
151
|
+
basename = File.basename filename
|
|
152
|
+
|
|
153
|
+
if Dir.glob(basename + '*').empty?
|
|
154
|
+
if is_absolute_path?(filename)
|
|
155
|
+
Dir.glob(filename + '') do |fn|
|
|
156
|
+
FileUtils.cp(fn, '.')
|
|
157
|
+
end
|
|
158
|
+
else
|
|
159
|
+
Dir.glob(File.join(@src_dir, filename + '*')) do |fn|
|
|
160
|
+
FileUtils.cp(fn, '.')
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Check if we have the file now
|
|
166
|
+
if Dir.glob(basename + '*').empty?
|
|
167
|
+
raise DoesNotAssembleError, "Cannot find file #{basename}"
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def ensure_assembled(filename)
|
|
172
|
+
if filename !~ /\.asm$/ and filename !~ /\.obj/
|
|
173
|
+
asm_file = filename + '.asm'
|
|
174
|
+
obj_file = filename + '.obj'
|
|
175
|
+
|
|
176
|
+
# Always prefer finding .asm over .obj file
|
|
177
|
+
if File.exist? asm_file
|
|
178
|
+
filename = asm_file
|
|
179
|
+
elsif File.exist? obj_file
|
|
180
|
+
filename = obj_file
|
|
181
|
+
else
|
|
182
|
+
raise DoesNotAssembleError, "Cannot find #{asm_file} or #{obj_file}"
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Check that the file assembles
|
|
187
|
+
case filename
|
|
188
|
+
when /\.asm$/
|
|
189
|
+
error, status = Open3.capture2e('lc3as', filename)
|
|
190
|
+
if not status.success?
|
|
191
|
+
raise DoesNotAssembleError,
|
|
192
|
+
"File does not assemble: #{filename}\n#{error}"
|
|
193
|
+
end
|
|
194
|
+
when /\.obj$/
|
|
195
|
+
if File.size? filename
|
|
196
|
+
success = true
|
|
197
|
+
else
|
|
198
|
+
raise DoesNotAssembleError,
|
|
199
|
+
"File has zero size or does not exist: #{filename}"
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
return true
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
end
|
data/lib/lc3spec/dsl.rb
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
module LC3Spec
|
|
2
|
+
module Dsl
|
|
3
|
+
def set(option, value = true)
|
|
4
|
+
@options ||= {
|
|
5
|
+
:output => $stdout,
|
|
6
|
+
:keep_score => false
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if option == :output
|
|
10
|
+
case value
|
|
11
|
+
when File
|
|
12
|
+
if value.path =~ /\.asm$/
|
|
13
|
+
raise "Not writing output to .asm file"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
@options[:output] = value
|
|
17
|
+
when String
|
|
18
|
+
if value =~ /\.asm$/
|
|
19
|
+
raise "Not writing output to .asm file"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
@options[:output] = open(value, 'w')
|
|
23
|
+
else
|
|
24
|
+
@options[:output] = value
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
return @options[:output]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
@options[option] = value
|
|
31
|
+
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def configure(&block)
|
|
35
|
+
yield if block_given?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test(description, points = 0, &block)
|
|
39
|
+
t = LC3Spec::Test.new(:options => @options,
|
|
40
|
+
:points => points,
|
|
41
|
+
&block)
|
|
42
|
+
|
|
43
|
+
output = @options[:output]
|
|
44
|
+
|
|
45
|
+
if t.pass
|
|
46
|
+
pass(description, points)
|
|
47
|
+
else
|
|
48
|
+
fail(description, points)
|
|
49
|
+
|
|
50
|
+
# FIXME: Ugly
|
|
51
|
+
errors = t.reporter.errors.join("\n")
|
|
52
|
+
|
|
53
|
+
output.puts errors.each_line.map { |line| ' ' + line }.join('')
|
|
54
|
+
output.puts
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def pass(description, points)
|
|
59
|
+
count_points(:pass, points)
|
|
60
|
+
|
|
61
|
+
if !@options[:keep_score] || (points == 0)
|
|
62
|
+
@options[:output].puts "#{description} [OK]"
|
|
63
|
+
else
|
|
64
|
+
@options[:output].puts "#{description} #{points}/#{points}"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def fail(description, points)
|
|
69
|
+
count_points(:fail, points)
|
|
70
|
+
|
|
71
|
+
if !@options[:keep_score] || (points == 0)
|
|
72
|
+
@options[:output].puts "#{description} [FAIL]"
|
|
73
|
+
else
|
|
74
|
+
@options[:output].puts "#{description} 0/#{points}"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def count_points(result, possible)
|
|
79
|
+
@possible_points ||= 0
|
|
80
|
+
@earned_points ||= 0
|
|
81
|
+
|
|
82
|
+
@earned_points += result == :pass ? possible : 0
|
|
83
|
+
@possible_points += possible
|
|
84
|
+
|
|
85
|
+
@num_tests ||= 0
|
|
86
|
+
@num_tests += 1
|
|
87
|
+
|
|
88
|
+
@num_passed ||= 0
|
|
89
|
+
@num_passed += result == :pass ? 1 : 0
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def print_score
|
|
93
|
+
return if @num_tests.nil? or (@num_tests == 0)
|
|
94
|
+
|
|
95
|
+
output = @options[:output]
|
|
96
|
+
|
|
97
|
+
if @options[:keep_score]
|
|
98
|
+
output.puts "Score: #@earned_points/#@possible_points"
|
|
99
|
+
else
|
|
100
|
+
if @num_passed == @num_tests
|
|
101
|
+
output.puts "[ALL OK]"
|
|
102
|
+
elsif @num_passed == 0
|
|
103
|
+
output.puts "[ALL FAIL]"
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
require 'lc3spec/errors'
|
|
2
|
+
require 'lc3spec/helpers'
|
|
3
|
+
|
|
4
|
+
module LC3Spec
|
|
5
|
+
module Expectations
|
|
6
|
+
extend LC3Spec::Helpers
|
|
7
|
+
|
|
8
|
+
def self.expect_register(lc3, reporter, reg, val)
|
|
9
|
+
expected = normalize_to_s(val)
|
|
10
|
+
actual = lc3.get_register(reg)
|
|
11
|
+
|
|
12
|
+
unless expected == actual
|
|
13
|
+
reporter.report "Incorrect #{reg.to_s}: #{diff(expected, actual)}"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.expect_memory(lc3, reporter, addr, val)
|
|
18
|
+
expected = normalize_to_s(val)
|
|
19
|
+
actual = lc3.get_memory(addr)
|
|
20
|
+
|
|
21
|
+
unless expected == actual
|
|
22
|
+
reporter.report "Incorrect mem[#{addr}]: #{diff(expected, actual)}"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.expect_output(lc3, reporter, expected)
|
|
27
|
+
actual = lc3.get_output
|
|
28
|
+
|
|
29
|
+
unless ignore_whitespace_equal(expected, actual)
|
|
30
|
+
reporter.report "Incorrect output:\n" +
|
|
31
|
+
" expected:\n#{strblock(expected)}\n actual:\n#{strblock(actual)}"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.diff(expected, actual)
|
|
36
|
+
"expected: #{expected}, actual: #{actual}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.strblock(str, indent = ' ')
|
|
40
|
+
return str if str.nil? or str.empty?
|
|
41
|
+
block = ''
|
|
42
|
+
str.each_line do |line|
|
|
43
|
+
block << indent + line
|
|
44
|
+
end
|
|
45
|
+
block
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.ignore_whitespace_equal(lhs, rhs)
|
|
49
|
+
lhs = lhs.each_line.map do |line|
|
|
50
|
+
if line =~ /^\s*$/
|
|
51
|
+
nil
|
|
52
|
+
else
|
|
53
|
+
line.gsub(/[ \t]+/, ' ')
|
|
54
|
+
end
|
|
55
|
+
end.compact.join('').strip
|
|
56
|
+
|
|
57
|
+
rhs = rhs.each_line.map do |line|
|
|
58
|
+
if line =~ /^\s*$/
|
|
59
|
+
nil
|
|
60
|
+
else
|
|
61
|
+
line.gsub(/[ \t]+/, ' ')
|
|
62
|
+
end
|
|
63
|
+
end.compact.join('').strip
|
|
64
|
+
|
|
65
|
+
lhs == rhs
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require 'pathname'
|
|
2
|
+
|
|
3
|
+
module LC3Spec
|
|
4
|
+
module Helpers
|
|
5
|
+
def normalize_to_s(number)
|
|
6
|
+
case number
|
|
7
|
+
when String
|
|
8
|
+
if number =~ /^(?:x|0x|X|0X)?([0-9A-Fa-f]{1,4})$/
|
|
9
|
+
"x#{$1.rjust(4, '0').upcase}"
|
|
10
|
+
else
|
|
11
|
+
raise "Unable to normalize number: #{number}"
|
|
12
|
+
end
|
|
13
|
+
when Fixnum
|
|
14
|
+
"x#{([number].pack('s>*')).unpack('H*').first.upcase}"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def normalize_to_i(number)
|
|
19
|
+
case number
|
|
20
|
+
when String
|
|
21
|
+
if number =~ /^(?:x|0x|X|0X)?([0-9A-Fa-f]{1,4})$/
|
|
22
|
+
num_part = $1
|
|
23
|
+
|
|
24
|
+
if num_part[0].to_i(16) > 7
|
|
25
|
+
num_part = num_part.rjust(4, 'F')
|
|
26
|
+
else
|
|
27
|
+
num_part = num_part.rjust(4, '0')
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
[num_part].pack('H*').unpack('s>*').first
|
|
31
|
+
else
|
|
32
|
+
raise "Unable to normalize number: #{number}"
|
|
33
|
+
end
|
|
34
|
+
when Fixnum
|
|
35
|
+
number
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def is_absolute_path?(path)
|
|
40
|
+
Pathname.new(path).absolute?
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/lc3spec/lc3.rb
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# lc3.rb
|
|
2
|
+
# Chun Yang <yang43@illinois.edu>
|
|
3
|
+
|
|
4
|
+
require 'io/wait'
|
|
5
|
+
require 'logger'
|
|
6
|
+
require 'socket'
|
|
7
|
+
require 'tempfile'
|
|
8
|
+
|
|
9
|
+
require 'lc3spec/errors'
|
|
10
|
+
require 'lc3spec/helpers'
|
|
11
|
+
|
|
12
|
+
# Class to provide access to LC-3 simulator instance
|
|
13
|
+
class LC3
|
|
14
|
+
include LC3Spec::Helpers
|
|
15
|
+
attr_accessor :logger
|
|
16
|
+
|
|
17
|
+
def initialize
|
|
18
|
+
# Logging
|
|
19
|
+
@logger = Logger.new(STDERR)
|
|
20
|
+
@logger.level = Logger::ERROR
|
|
21
|
+
#@logger.level = Logger::DEBUG
|
|
22
|
+
@logger.formatter = proc do |severity, datetime, progname, msg|
|
|
23
|
+
"#{severity}: #{msg}\n"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Registers
|
|
27
|
+
@registers = {}
|
|
28
|
+
[:R0, :R1, :R2, :R3, :R4, :R5, :R6, :R7, :PC, :IR, :PSR].each do |reg|
|
|
29
|
+
@registers[reg] = 'x0000'
|
|
30
|
+
end
|
|
31
|
+
@registers[:CC] = 'ZERO'
|
|
32
|
+
|
|
33
|
+
# Memory
|
|
34
|
+
@memory = Hash.new('x0000')
|
|
35
|
+
@labels = {}
|
|
36
|
+
|
|
37
|
+
initialize_lc3sim
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def get_register(reg)
|
|
41
|
+
@registers[reg]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def set_register(reg, val)
|
|
45
|
+
reg = reg.upcase # Don't use ! version because it doesn't work for symbols
|
|
46
|
+
|
|
47
|
+
unless @registers.keys.include? reg.to_sym
|
|
48
|
+
raise "Invalid register: #{reg.to_s}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
if val.nil?
|
|
52
|
+
raise "Invalid register value for #{reg.to_s}: #{val}"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
@io.puts "register #{reg.to_s} #{normalize_to_s(val)}"
|
|
56
|
+
|
|
57
|
+
loop do
|
|
58
|
+
msg = @io.readline
|
|
59
|
+
parse_msg msg.strip
|
|
60
|
+
|
|
61
|
+
break if msg =~ /^TOCODE/
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
self
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def get_memory(addr)
|
|
68
|
+
if addr.respond_to?(:upcase)
|
|
69
|
+
label_addr = get_address(addr)
|
|
70
|
+
addr = label_addr unless label_addr.nil?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
@memory[normalize_to_i(addr)]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def set_memory(addr, val)
|
|
77
|
+
# addr may be memory address or label
|
|
78
|
+
|
|
79
|
+
if addr.respond_to?(:upcase) and @labels.include?(addr.upcase.to_s)
|
|
80
|
+
# Is a label
|
|
81
|
+
addr = addr.upcase.to_s
|
|
82
|
+
else
|
|
83
|
+
# Not a label
|
|
84
|
+
addr = normalize_to_s(addr)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
@io.puts("memory #{addr} #{normalize_to_s(val)}")
|
|
88
|
+
|
|
89
|
+
loop do
|
|
90
|
+
msg = @io.readline
|
|
91
|
+
parse_msg msg.strip
|
|
92
|
+
|
|
93
|
+
break if msg =~ /^ERR|CODE/
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
self
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def get_address(label)
|
|
100
|
+
@labels[label.upcase.to_s]
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def file(filename)
|
|
104
|
+
@io.puts "file #{filename}"
|
|
105
|
+
|
|
106
|
+
# Need to encounter 2 TOCODEs before we're done
|
|
107
|
+
tocode_counter = 2
|
|
108
|
+
|
|
109
|
+
while tocode_counter > 0
|
|
110
|
+
msg = @io.readline
|
|
111
|
+
|
|
112
|
+
parse_msg msg.strip
|
|
113
|
+
|
|
114
|
+
if msg =~ /^TOCODE/
|
|
115
|
+
tocode_counter -= 1
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# ignore warning about no symbols
|
|
119
|
+
next if msg =~ /WARNING: No symbols/
|
|
120
|
+
|
|
121
|
+
# don't ignore other errors
|
|
122
|
+
break if msg =~ /^ERR/
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
self
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def step
|
|
129
|
+
@io.puts 'step'
|
|
130
|
+
|
|
131
|
+
parse_until_print_registers
|
|
132
|
+
|
|
133
|
+
self
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def continue
|
|
137
|
+
@io.puts 'continue'
|
|
138
|
+
|
|
139
|
+
parse_until_print_registers
|
|
140
|
+
|
|
141
|
+
self
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def set_breakpoint(addr)
|
|
145
|
+
addr = addr.upcase.to_s if addr.respond_to? :upcase
|
|
146
|
+
if @labels.include? addr
|
|
147
|
+
@io.puts "break set #{addr}"
|
|
148
|
+
else
|
|
149
|
+
@io.puts "break set #{normalize_to_s(addr)}"
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
sleep(0.01)
|
|
153
|
+
|
|
154
|
+
while @io.ready?
|
|
155
|
+
msg = @io.readline
|
|
156
|
+
parse_msg msg.strip
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
self
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def clear_breakpoint(addr)
|
|
163
|
+
if addr == :all
|
|
164
|
+
@io.puts 'break clear all'
|
|
165
|
+
return self
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
addr = addr.upcase.to_s if addr.respond_to? :upcase
|
|
169
|
+
|
|
170
|
+
if @labels.include? addr
|
|
171
|
+
@io.puts "break clear #{addr}"
|
|
172
|
+
else
|
|
173
|
+
@io.puts "break clear #{normalize_to_s(addr)}"
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
sleep(0.01)
|
|
177
|
+
|
|
178
|
+
while @io.ready?
|
|
179
|
+
msg = @io.readline
|
|
180
|
+
parse_msg msg.strip
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
self
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def clear_breakpoints
|
|
187
|
+
clear_breakpoint :all
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def get_output
|
|
191
|
+
out = ''
|
|
192
|
+
|
|
193
|
+
# There is no signal that tells the GUI that output is ready...
|
|
194
|
+
# FIXME: This is a bug waiting to happen
|
|
195
|
+
until @output.ready?
|
|
196
|
+
sleep(0.01)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
while @output.ready?
|
|
200
|
+
out << @output.readpartial(1024)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
out.gsub("\n\n--- halting the LC-3 ---\n\n", '')
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def initialize_lc3sim
|
|
207
|
+
# Start lc3sim instance
|
|
208
|
+
@io = IO.popen(%w(lc3sim -gui), 'r+')
|
|
209
|
+
|
|
210
|
+
begin
|
|
211
|
+
# Port for output server
|
|
212
|
+
@port = (rand * 1000 + 5000).to_i
|
|
213
|
+
|
|
214
|
+
# Start server to get output
|
|
215
|
+
@server = TCPServer.new @port
|
|
216
|
+
rescue Errno::EADDRINUSE
|
|
217
|
+
retry
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
th = Thread.new do
|
|
221
|
+
@output = @server.accept
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Initialize lc3sim with port number
|
|
225
|
+
@io.puts @port
|
|
226
|
+
|
|
227
|
+
# Wait for lc3sim to connect with our server
|
|
228
|
+
th.join
|
|
229
|
+
|
|
230
|
+
# Read LC-3 initialization messages (mostly loading lc3os)
|
|
231
|
+
parse_until_print_registers
|
|
232
|
+
|
|
233
|
+
# Clear all the welcome messages from lc3sim output
|
|
234
|
+
while @output.ready?
|
|
235
|
+
@output.readpartial 1024
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def close
|
|
240
|
+
@output.close
|
|
241
|
+
@server.close
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def inspect
|
|
245
|
+
registers = @registers.map { |k, v| "#{k}=#{v}" }.join(' ')
|
|
246
|
+
|
|
247
|
+
addr_to_label = @labels.invert
|
|
248
|
+
|
|
249
|
+
memory_header = "%18s ADDR VALUE" % "label"
|
|
250
|
+
memory = @memory.map do |addr, value|
|
|
251
|
+
"%18s %s %s" % [(addr_to_label[addr] or ''),
|
|
252
|
+
normalize_to_s(addr), value]
|
|
253
|
+
end.join("\n")
|
|
254
|
+
|
|
255
|
+
#[memory_header, memory, registers].join("\n")
|
|
256
|
+
registers
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# def to_s
|
|
260
|
+
# @registers.map { |k, v| "#{k}=#{v}" }.join(' ')
|
|
261
|
+
# end
|
|
262
|
+
|
|
263
|
+
private
|
|
264
|
+
|
|
265
|
+
def parse_until_print_registers
|
|
266
|
+
loop do
|
|
267
|
+
msg = @io.readline
|
|
268
|
+
parse_msg msg.strip
|
|
269
|
+
|
|
270
|
+
break if msg =~ /^REG R11/
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Parse messages from lc3sim
|
|
275
|
+
def parse_msg(msg)
|
|
276
|
+
@logger.debug msg
|
|
277
|
+
|
|
278
|
+
tokens = msg.split(/ +/)
|
|
279
|
+
|
|
280
|
+
cmd = tokens.shift
|
|
281
|
+
|
|
282
|
+
if cmd =~ /^CODEP/
|
|
283
|
+
tokens.unshift(cmd.gsub('CODEP', ''))
|
|
284
|
+
cmd = 'CODEP'
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
case cmd
|
|
288
|
+
when 'BCLEAR'
|
|
289
|
+
when 'BREAK'
|
|
290
|
+
when 'CODE', 'CODEP' # Line of code
|
|
291
|
+
# Address
|
|
292
|
+
addr = tokens.shift.to_i - 1
|
|
293
|
+
|
|
294
|
+
# Note: if there is a breakpoint at the current address, the last
|
|
295
|
+
# token.shift produces a string like "12289B". Luckily, #to_i just
|
|
296
|
+
# ignores the B at the end.
|
|
297
|
+
|
|
298
|
+
# Label, if present
|
|
299
|
+
if tokens.first != "x%04X" % addr
|
|
300
|
+
label = tokens.shift.upcase
|
|
301
|
+
|
|
302
|
+
# Don't overwrite label if one already exists
|
|
303
|
+
@labels[label] ||= addr
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Discard hex address
|
|
307
|
+
tokens.shift
|
|
308
|
+
|
|
309
|
+
# Value
|
|
310
|
+
val = tokens.shift
|
|
311
|
+
|
|
312
|
+
@memory[addr] = val
|
|
313
|
+
|
|
314
|
+
# Disassembly info
|
|
315
|
+
# info = tokens.join(' ')
|
|
316
|
+
|
|
317
|
+
when 'CONT'
|
|
318
|
+
when 'ERR'
|
|
319
|
+
if msg =~ /WARNING/
|
|
320
|
+
@logger.warn msg
|
|
321
|
+
else
|
|
322
|
+
@logger.error msg
|
|
323
|
+
end
|
|
324
|
+
when 'REG' # Register value
|
|
325
|
+
# Register number
|
|
326
|
+
reg = tokens.shift
|
|
327
|
+
|
|
328
|
+
# Register value
|
|
329
|
+
val = tokens.shift
|
|
330
|
+
|
|
331
|
+
case reg
|
|
332
|
+
when /^R[0-7]$/
|
|
333
|
+
@registers[reg.to_sym] = val
|
|
334
|
+
when 'R8'
|
|
335
|
+
@registers[:PC] = val
|
|
336
|
+
when 'R9'
|
|
337
|
+
@registers[:IR] = val
|
|
338
|
+
when 'R10'
|
|
339
|
+
@registers[:PSR] = val
|
|
340
|
+
when 'R11'
|
|
341
|
+
@registers[:CC] = val
|
|
342
|
+
end
|
|
343
|
+
when 'TOCODE'
|
|
344
|
+
when 'TRANS' # Label address translation
|
|
345
|
+
addr = normalize_to_i(tokens.shift)
|
|
346
|
+
val = tokens.shift
|
|
347
|
+
else
|
|
348
|
+
@logger.debug "Unexpected message: #{msg}"
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
end
|
data/lib/lc3spec/main.rb
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
require 'lc3spec/base'
|
|
2
|
+
require 'lc3spec/dsl'
|
|
3
|
+
require 'lc3spec/constants'
|
|
4
|
+
|
|
5
|
+
#require 'lc3spec/expectations/syntax'
|
|
6
|
+
|
|
7
|
+
extend LC3Spec::Dsl
|
|
8
|
+
include LC3Spec::Constants
|
|
9
|
+
|
|
10
|
+
#include LC3Spec::Expectations::Syntax
|
|
11
|
+
#LC3Spec::Expectations::Syntax.enable_should
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module LC3Spec
|
|
2
|
+
class Reporter
|
|
3
|
+
def initialize
|
|
4
|
+
@reports = []
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def report(msg)
|
|
8
|
+
@reports << msg
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def pass?
|
|
12
|
+
@reports.empty?
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def fail?
|
|
16
|
+
not @reports.empty?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def errors
|
|
20
|
+
@reports.dup
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/lc3spec.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lc3spec
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Chun Yang
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2013-02-27 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: DSL for testing LC-3 assembly programs
|
|
14
|
+
email: x@cyang.info
|
|
15
|
+
executables: []
|
|
16
|
+
extensions: []
|
|
17
|
+
extra_rdoc_files: []
|
|
18
|
+
files:
|
|
19
|
+
- lib/lc3spec.rb
|
|
20
|
+
- lib/lc3spec/base.rb
|
|
21
|
+
- lib/lc3spec/constants.rb
|
|
22
|
+
- lib/lc3spec/dsl.rb
|
|
23
|
+
- lib/lc3spec/errors.rb
|
|
24
|
+
- lib/lc3spec/expectations.rb
|
|
25
|
+
- lib/lc3spec/helpers.rb
|
|
26
|
+
- lib/lc3spec/lc3.rb
|
|
27
|
+
- lib/lc3spec/main.rb
|
|
28
|
+
- lib/lc3spec/reporter.rb
|
|
29
|
+
- LICENSE
|
|
30
|
+
- README.md
|
|
31
|
+
homepage: http://github.com/chunyang/lc3spec
|
|
32
|
+
licenses:
|
|
33
|
+
- MIT
|
|
34
|
+
metadata: {}
|
|
35
|
+
post_install_message:
|
|
36
|
+
rdoc_options: []
|
|
37
|
+
require_paths:
|
|
38
|
+
- lib
|
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
40
|
+
requirements:
|
|
41
|
+
- - '>='
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: '0'
|
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - '>='
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '0'
|
|
49
|
+
requirements: []
|
|
50
|
+
rubyforge_project:
|
|
51
|
+
rubygems_version: 2.0.0
|
|
52
|
+
signing_key:
|
|
53
|
+
specification_version: 4
|
|
54
|
+
summary: Testing and grading suite for LC-3 assembly programs
|
|
55
|
+
test_files: []
|