scasm 0.0.1
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/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +69 -0
- data/Rakefile +2 -0
- data/bin/scasm +45 -0
- data/bin/scasm-disassemble +112 -0
- data/examples/spec.scasm +29 -0
- data/lib/scasm/assembler.rb +152 -0
- data/lib/scasm/isa.rb +37 -0
- data/lib/scasm/statement.rb +74 -0
- data/lib/scasm/value.rb +178 -0
- data/lib/scasm/version.rb +3 -0
- data/scasm.gemspec +19 -0
- data/test/foo.scasm +14 -0
- data/test/test_assembler.rb +133 -0
- metadata +76 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Rich Lane
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
SCASM
|
2
|
+
=====
|
3
|
+
|
4
|
+
Introduction
|
5
|
+
------------
|
6
|
+
|
7
|
+
SCASM is an assembler for the DCPU-16 architecture used in the game 0x10c.
|
8
|
+
See the game's [official website](http://0x10c.com/) for details on the
|
9
|
+
instruction set. This assembler is different and powerful because the
|
10
|
+
code you write is actually Ruby, meaning you can use all the capabilities
|
11
|
+
of a high-level programming language to help generate the final machine code.
|
12
|
+
|
13
|
+
Installation
|
14
|
+
------------
|
15
|
+
|
16
|
+
gem install scasm
|
17
|
+
|
18
|
+
Example
|
19
|
+
-------
|
20
|
+
|
21
|
+
foo = A
|
22
|
+
bar = B
|
23
|
+
offset = 42
|
24
|
+
add [foo, offset], bar
|
25
|
+
|
26
|
+
See the examples directory for more sample code.
|
27
|
+
|
28
|
+
Syntax
|
29
|
+
------
|
30
|
+
|
31
|
+
SCASM input is Ruby code, but you won't need a deep understanding of Ruby to
|
32
|
+
get started. Simple statements like `add A, 1` work just like you expect. This
|
33
|
+
section will cover the SCASM-specific syntax.
|
34
|
+
|
35
|
+
### Registers
|
36
|
+
|
37
|
+
Ruby variables named `A`, `B`, `C`, `X`, `Y`, `Z`, `I`, and `J` are provided to refer to the
|
38
|
+
processor registers.
|
39
|
+
|
40
|
+
### Memory
|
41
|
+
|
42
|
+
Memory references are of the form `[register, offset]`. Either `register` or
|
43
|
+
`offset` can be omitted.
|
44
|
+
|
45
|
+
Examples:
|
46
|
+
|
47
|
+
* `[A]`
|
48
|
+
* `[0x100]`
|
49
|
+
* `[A, 2]`
|
50
|
+
|
51
|
+
### Literals
|
52
|
+
|
53
|
+
Literal values are just given as integers. Example: `42` or `0x200`.
|
54
|
+
|
55
|
+
### Miscellaneous
|
56
|
+
|
57
|
+
These values have the same meaning as in the spec:
|
58
|
+
|
59
|
+
* `pop`
|
60
|
+
* `peek`
|
61
|
+
* `push`
|
62
|
+
* `sp`
|
63
|
+
* `pc`
|
64
|
+
* `o`
|
65
|
+
|
66
|
+
Contributing
|
67
|
+
------------
|
68
|
+
|
69
|
+
Fork the project on GitHub and send me a pull request.
|
data/Rakefile
ADDED
data/bin/scasm
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'trollop'
|
3
|
+
require 'scasm/assembler'
|
4
|
+
|
5
|
+
opts = Trollop.options do
|
6
|
+
banner <<-EOS
|
7
|
+
scasm - the Ship Computer Assembler
|
8
|
+
|
9
|
+
SCASM is an assembler for the DCPU-16 computer used in the game 0x10c.
|
10
|
+
CPU specs: http://0x10c.com/doc/dcpu-16.txt
|
11
|
+
|
12
|
+
Usage:
|
13
|
+
scasm [options] [file]
|
14
|
+
|
15
|
+
where [options] are:
|
16
|
+
EOS
|
17
|
+
|
18
|
+
opt :output, "Output filename", :short => 'o', :type => :string
|
19
|
+
opt :help, "Show this message", :short => 'h'
|
20
|
+
|
21
|
+
text <<-EOS
|
22
|
+
|
23
|
+
If no input file is given input is read from stdin. If no output file is given
|
24
|
+
the output will be written to filename.bin or stdout.
|
25
|
+
EOS
|
26
|
+
end
|
27
|
+
|
28
|
+
if ARGV[0]
|
29
|
+
input_io = File.open(ARGV[0])
|
30
|
+
else
|
31
|
+
input_io = $stdin
|
32
|
+
end
|
33
|
+
|
34
|
+
if opts[:output]
|
35
|
+
output_io = File.open(opts[:output], 'w')
|
36
|
+
elsif input_io != $stdin
|
37
|
+
filename = File.join(File.dirname(input_io.path), File.basename(input_io.path, '.scasm') + '.bin')
|
38
|
+
output_io = File.open(filename, 'w')
|
39
|
+
else
|
40
|
+
output_io = $stdout
|
41
|
+
end
|
42
|
+
|
43
|
+
as = SCASM::Assembler.new
|
44
|
+
as.eval input_io.read
|
45
|
+
output_io.write as.assemble
|
@@ -0,0 +1,112 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'trollop'
|
3
|
+
require 'scasm/isa'
|
4
|
+
require 'scasm/statement'
|
5
|
+
require 'scasm/value'
|
6
|
+
|
7
|
+
include SCASM
|
8
|
+
|
9
|
+
opts = Trollop.options do
|
10
|
+
banner <<-EOS
|
11
|
+
scasm-disassembler - Disassemble DCPU-16 binaries.
|
12
|
+
|
13
|
+
Usage:
|
14
|
+
scasm-disassembler [options] [file]
|
15
|
+
|
16
|
+
where [options] are:
|
17
|
+
EOS
|
18
|
+
end
|
19
|
+
|
20
|
+
mem = ARGF.read.unpack("v*")
|
21
|
+
addr = 0
|
22
|
+
basic_opcode_map = SCASM::BASIC_OPCODES.invert
|
23
|
+
extended_opcode_map = SCASM::EXTENDED_OPCODES.invert
|
24
|
+
register_map = SCASM::REGISTERS.invert
|
25
|
+
|
26
|
+
next_word = lambda do
|
27
|
+
word = mem[addr]
|
28
|
+
addr += 1
|
29
|
+
word
|
30
|
+
end
|
31
|
+
|
32
|
+
decode_value = lambda do |value|
|
33
|
+
if value <= 0x07
|
34
|
+
Register.new register_map[value]
|
35
|
+
elsif value <= 0x0f
|
36
|
+
RegisterMemory.new register_map[value-0x08]
|
37
|
+
elsif value <= 0x17
|
38
|
+
OffsetRegisterMemory.new register_map[value-0x10], next_word[]
|
39
|
+
elsif value == 0x18
|
40
|
+
Pop.new
|
41
|
+
elsif value == 0x19
|
42
|
+
Peek.new
|
43
|
+
elsif value == 0x1a
|
44
|
+
Push.new
|
45
|
+
elsif value == 0x1b
|
46
|
+
SP.new
|
47
|
+
elsif value == 0x1c
|
48
|
+
PC.new
|
49
|
+
elsif value == 0x1d
|
50
|
+
O.new
|
51
|
+
elsif value == 0x1e
|
52
|
+
ImmediateMemory.new next_word[]
|
53
|
+
elsif value == 0x1f
|
54
|
+
Immediate.new next_word[]
|
55
|
+
elsif value < 0x3f
|
56
|
+
Immediate.new(value-0x20)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Create statements for each instruction
|
61
|
+
stmts = []
|
62
|
+
jumps = Hash.new { |h,k| h[k] = [] }
|
63
|
+
while addr < mem.size
|
64
|
+
start_addr = addr
|
65
|
+
word = next_word[]
|
66
|
+
opcode = word & 0xf
|
67
|
+
code_a = (word >> 4) & 0x3f
|
68
|
+
code_b = (word >> 10) & 0x3f
|
69
|
+
|
70
|
+
if opcode != 0 then
|
71
|
+
opsym = basic_opcode_map[opcode] or fail "unknown opcode #{opcode} at #{start_addr}"
|
72
|
+
a = decode_value[code_a]
|
73
|
+
b = decode_value[code_b]
|
74
|
+
else
|
75
|
+
opsym = extended_opcode_map[code_a] or fail "unknown extended opcode #{code_a} at #{start_addr}"
|
76
|
+
a = decode_value[code_b]
|
77
|
+
b = nil
|
78
|
+
end
|
79
|
+
|
80
|
+
if opsym == :set and a.is_a? PC and b.is_a? Immediate
|
81
|
+
imm = b.value
|
82
|
+
b = ImmediateLabel.new "L#{b.value}"
|
83
|
+
b.resolve imm
|
84
|
+
jumps[imm] << b
|
85
|
+
elsif opsym == :jsr and a.is_a? Immediate
|
86
|
+
imm = a.value
|
87
|
+
a = ImmediateLabel.new "L#{a.value}"
|
88
|
+
a.resolve imm
|
89
|
+
jumps[imm] << a
|
90
|
+
end
|
91
|
+
|
92
|
+
inst = Instruction.new(opsym, a, b)
|
93
|
+
inst.addr = start_addr
|
94
|
+
stmts << inst
|
95
|
+
end
|
96
|
+
|
97
|
+
# Create labels and fixup jumpers
|
98
|
+
labelled_stmts = []
|
99
|
+
stmts.each do |stmt|
|
100
|
+
if jumps.member? stmt.addr
|
101
|
+
jumpers = jumps[stmt.addr]
|
102
|
+
lbl = Label.new("L#{stmt.addr}")
|
103
|
+
lbl.addr = stmt.addr
|
104
|
+
labelled_stmts << lbl
|
105
|
+
end
|
106
|
+
labelled_stmts << stmt
|
107
|
+
end
|
108
|
+
|
109
|
+
# Output each statement
|
110
|
+
labelled_stmts.each do |stmt|
|
111
|
+
printf "%-40s # pc=%d\n" % [stmt, stmt.addr]
|
112
|
+
end
|
data/examples/spec.scasm
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Translation of the example code from the DPU-16 spec.
|
2
|
+
|
3
|
+
# Try some basic stuff
|
4
|
+
set A, 48
|
5
|
+
set [4096], 32
|
6
|
+
sub A, [4096]
|
7
|
+
ifn A, 16
|
8
|
+
set pc, "crash"
|
9
|
+
|
10
|
+
# Do a loopy thing
|
11
|
+
set I, 10
|
12
|
+
set A, 8192
|
13
|
+
label "loop"
|
14
|
+
set [I, 8192], [A]
|
15
|
+
sub I, 1
|
16
|
+
ifn I, 0
|
17
|
+
set pc, "loop"
|
18
|
+
|
19
|
+
# Call a subroutine
|
20
|
+
set X, 4
|
21
|
+
jsr "testsub"
|
22
|
+
set pc, "crash"
|
23
|
+
label "testsub"
|
24
|
+
shl X, 4
|
25
|
+
set pc, pop
|
26
|
+
|
27
|
+
# Hang forever. X should now be 0x40 if everything went right.
|
28
|
+
label "crash"
|
29
|
+
set pc, "crash"
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
require 'scasm/isa'
|
3
|
+
require 'scasm/statement'
|
4
|
+
require 'scasm/value'
|
5
|
+
|
6
|
+
module SCASM
|
7
|
+
|
8
|
+
class Assembler < BasicObject
|
9
|
+
def initialize
|
10
|
+
@stmts = []
|
11
|
+
@relocations = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def eval code
|
15
|
+
instance_eval code
|
16
|
+
end
|
17
|
+
|
18
|
+
def assemble
|
19
|
+
resolve_labels
|
20
|
+
io = ::StringIO.new
|
21
|
+
@stmts.each { |stmt| stmt.assemble io }
|
22
|
+
io.string
|
23
|
+
end
|
24
|
+
|
25
|
+
def inst opsym, a, b
|
26
|
+
a = parse_value a
|
27
|
+
b = parse_value b
|
28
|
+
@stmts << Instruction.new(opsym, a, b)
|
29
|
+
end
|
30
|
+
|
31
|
+
def label name
|
32
|
+
::Kernel.raise "label names must be strings" unless name.is_a? ::String
|
33
|
+
@stmts << Label.new(name)
|
34
|
+
end
|
35
|
+
|
36
|
+
def reg regsym
|
37
|
+
Register.new regsym
|
38
|
+
end
|
39
|
+
|
40
|
+
def regmem regsym
|
41
|
+
RegisterMemory.new regsym
|
42
|
+
end
|
43
|
+
|
44
|
+
def iregmem regsym, imm
|
45
|
+
OffsetRegisterMemory.new regsym, imm
|
46
|
+
end
|
47
|
+
|
48
|
+
def pop
|
49
|
+
Pop.new
|
50
|
+
end
|
51
|
+
|
52
|
+
def peek
|
53
|
+
Peek.new
|
54
|
+
end
|
55
|
+
|
56
|
+
def push
|
57
|
+
Push.new
|
58
|
+
end
|
59
|
+
|
60
|
+
def sp
|
61
|
+
SP.new
|
62
|
+
end
|
63
|
+
|
64
|
+
def pc
|
65
|
+
PC.new
|
66
|
+
end
|
67
|
+
|
68
|
+
def o
|
69
|
+
O.new
|
70
|
+
end
|
71
|
+
|
72
|
+
def imem imm
|
73
|
+
ImmediateMemory.new imm
|
74
|
+
end
|
75
|
+
|
76
|
+
def imm imm
|
77
|
+
Immediate.new imm
|
78
|
+
end
|
79
|
+
|
80
|
+
def l name
|
81
|
+
::Kernel.raise "label names must be strings" unless name.is_a? ::String
|
82
|
+
ImmediateLabel.new(name).tap { |x| @relocations << x }
|
83
|
+
end
|
84
|
+
|
85
|
+
# Add a method for each instruction
|
86
|
+
BASIC_OPCODES.each do |opsym,opcode|
|
87
|
+
define_method(opsym) { |a,b| inst opsym, a, b }
|
88
|
+
end
|
89
|
+
|
90
|
+
EXTENDED_OPCODES.each do |opsym,opcode|
|
91
|
+
define_method(opsym) { |a| inst opsym, a, nil }
|
92
|
+
end
|
93
|
+
|
94
|
+
# Add a constant for each register
|
95
|
+
REGISTERS.each do |regsym,regnum|
|
96
|
+
const_set regsym, regsym
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def resolve_labels
|
102
|
+
label_addrs = {}
|
103
|
+
|
104
|
+
addr = 0
|
105
|
+
@stmts.each do |stmt|
|
106
|
+
if stmt.is_a? Label
|
107
|
+
label_addrs[stmt.name] = addr
|
108
|
+
end
|
109
|
+
addr += stmt.length
|
110
|
+
end
|
111
|
+
|
112
|
+
@relocations.each do |x|
|
113
|
+
addr = label_addrs[x.name] or ::Kernel.raise "undefined label #{x.name.inspect}"
|
114
|
+
x.resolve addr
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Shorter notation for values
|
119
|
+
def parse_value x
|
120
|
+
case x
|
121
|
+
when Value, ::NilClass
|
122
|
+
x
|
123
|
+
when ::Array
|
124
|
+
x1, x2, = x
|
125
|
+
if x1.is_a? ::Symbol and x2 == nil
|
126
|
+
# [reg]
|
127
|
+
regmem x1
|
128
|
+
elsif x1.is_a? ::Symbol and x2.is_a? ::Integer
|
129
|
+
# [reg, imm]
|
130
|
+
iregmem x1, x2
|
131
|
+
elsif x1.is_a? ::Integer
|
132
|
+
# [imm]
|
133
|
+
imem x1
|
134
|
+
else
|
135
|
+
::Kernel.fail "invalid memory access syntax"
|
136
|
+
end
|
137
|
+
when ::Symbol
|
138
|
+
# register
|
139
|
+
reg x
|
140
|
+
when ::String
|
141
|
+
# label
|
142
|
+
l x
|
143
|
+
when ::Integer
|
144
|
+
# immediate
|
145
|
+
imm x
|
146
|
+
else
|
147
|
+
::Kernel.raise "unexpected value class #{x.class}"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
data/lib/scasm/isa.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module SCASM
|
2
|
+
|
3
|
+
BASIC_OPCODES = {
|
4
|
+
:ext => 0x0,
|
5
|
+
:set => 0x1,
|
6
|
+
:add => 0x2,
|
7
|
+
:sub => 0x3,
|
8
|
+
:mul => 0x4,
|
9
|
+
:div => 0x5,
|
10
|
+
:mod => 0x6,
|
11
|
+
:shl => 0x7,
|
12
|
+
:shr => 0x8,
|
13
|
+
:and_ => 0x9, # "and" is a reserved word
|
14
|
+
:bor => 0xa,
|
15
|
+
:xor => 0xb,
|
16
|
+
:ife => 0xc,
|
17
|
+
:ifn => 0xd,
|
18
|
+
:ifg => 0xe,
|
19
|
+
:ifb => 0xf,
|
20
|
+
}
|
21
|
+
|
22
|
+
EXTENDED_OPCODES = {
|
23
|
+
:jsr => 0x01,
|
24
|
+
}
|
25
|
+
|
26
|
+
REGISTERS = {
|
27
|
+
:A => 0,
|
28
|
+
:B => 1,
|
29
|
+
:C => 2,
|
30
|
+
:X => 3,
|
31
|
+
:Y => 4,
|
32
|
+
:Z => 5,
|
33
|
+
:I => 6,
|
34
|
+
:J => 7,
|
35
|
+
}
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module SCASM
|
4
|
+
|
5
|
+
class Statement
|
6
|
+
attr_accessor :addr
|
7
|
+
|
8
|
+
def assemble io
|
9
|
+
fail 'unimplemented'
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
fail 'unimplemented'
|
14
|
+
end
|
15
|
+
|
16
|
+
# XXX HACK
|
17
|
+
def length
|
18
|
+
io = StringIO.new
|
19
|
+
assemble io
|
20
|
+
io.length/2
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Instruction < Statement
|
25
|
+
attr_reader :opsym, :a, :b
|
26
|
+
attr_writer :b # for disassembler
|
27
|
+
|
28
|
+
def initialize opsym, a, b
|
29
|
+
@opsym = opsym
|
30
|
+
@a = a
|
31
|
+
@b = b
|
32
|
+
end
|
33
|
+
|
34
|
+
def assemble io
|
35
|
+
if opcode = BASIC_OPCODES[@opsym]
|
36
|
+
code_a, imm_a = @a.assemble
|
37
|
+
code_b, imm_b = @b.assemble
|
38
|
+
code = opcode | (code_a<<4) | (code_b<<10)
|
39
|
+
io.write [code, imm_a, imm_b].compact.pack('v*')
|
40
|
+
elsif opcode = EXTENDED_OPCODES[@opsym]
|
41
|
+
code_a, imm_a = @a.assemble
|
42
|
+
fail unless @b == nil
|
43
|
+
code = (opcode<<4) | (code_a<<10)
|
44
|
+
io.write [code, imm_a].compact.pack('v*')
|
45
|
+
else
|
46
|
+
fail "unknown opsym #{@opsym.inspect}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_s
|
51
|
+
"%s %s, %s" % [@opsym, @a, @b]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class Data < Statement
|
56
|
+
end
|
57
|
+
|
58
|
+
class Label < Statement
|
59
|
+
attr_reader :name
|
60
|
+
|
61
|
+
def initialize name
|
62
|
+
@name = name
|
63
|
+
end
|
64
|
+
|
65
|
+
def assemble io
|
66
|
+
# nop
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_s
|
70
|
+
"label #{@name.inspect}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
data/lib/scasm/value.rb
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
module SCASM
|
2
|
+
|
3
|
+
class Value
|
4
|
+
def assemble
|
5
|
+
fail "not implemented"
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_s
|
9
|
+
fail "not implemented"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Register < Value
|
14
|
+
def initialize regsym
|
15
|
+
fail "invalid register #{regsym.inspect}" unless REGISTERS.member? regsym
|
16
|
+
@regsym = regsym
|
17
|
+
end
|
18
|
+
|
19
|
+
def assemble
|
20
|
+
return REGISTERS[@regsym]
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
"reg(#@regsym)"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class RegisterMemory < Value
|
29
|
+
def initialize regsym
|
30
|
+
fail "invalid register #{regsym.inspect}" unless REGISTERS.member? regsym
|
31
|
+
@regsym = regsym
|
32
|
+
end
|
33
|
+
|
34
|
+
def assemble
|
35
|
+
return 0x08 + REGISTERS[@regsym]
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_s
|
39
|
+
"regmem(#@regsym)"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class OffsetRegisterMemory < Value
|
44
|
+
def initialize regsym, imm
|
45
|
+
fail "invalid register #{regsym.inspect}" unless REGISTERS.member? regsym
|
46
|
+
@regsym = regsym
|
47
|
+
@imm = imm
|
48
|
+
end
|
49
|
+
|
50
|
+
def assemble
|
51
|
+
return (0x10 + REGISTERS[@regsym]), @imm
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_s
|
55
|
+
"iregmem(#@regsym, #@imm)"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class Pop < Value
|
60
|
+
def assemble
|
61
|
+
return 0x18
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_s
|
65
|
+
'pop'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class Peek < Value
|
70
|
+
def assemble
|
71
|
+
return 0x19
|
72
|
+
end
|
73
|
+
|
74
|
+
def to_s
|
75
|
+
'peek'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class Push < Value
|
80
|
+
def assemble
|
81
|
+
return 0x1a
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_s
|
85
|
+
'push'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class SP < Value
|
90
|
+
def assemble
|
91
|
+
return 0x1b
|
92
|
+
end
|
93
|
+
|
94
|
+
def to_s
|
95
|
+
'sp'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class PC < Value
|
100
|
+
def assemble
|
101
|
+
return 0x1c
|
102
|
+
end
|
103
|
+
|
104
|
+
def to_s
|
105
|
+
'pc'
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class O < Value
|
110
|
+
def assemble
|
111
|
+
return 0x1d
|
112
|
+
end
|
113
|
+
|
114
|
+
def to_s
|
115
|
+
'o'
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
class ImmediateMemory < Value
|
120
|
+
def initialize imm
|
121
|
+
@imm = imm
|
122
|
+
end
|
123
|
+
|
124
|
+
def assemble
|
125
|
+
return 0x1e, @imm
|
126
|
+
end
|
127
|
+
|
128
|
+
def to_s
|
129
|
+
"imem(#@imm)"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
class Immediate < Value
|
134
|
+
def initialize imm
|
135
|
+
@imm = imm
|
136
|
+
end
|
137
|
+
|
138
|
+
def assemble
|
139
|
+
if @imm <= 0x1f
|
140
|
+
return 0x20 + @imm
|
141
|
+
else
|
142
|
+
return 0x1f, @imm
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def to_s
|
147
|
+
"imm(#@imm)"
|
148
|
+
end
|
149
|
+
|
150
|
+
def value
|
151
|
+
@imm
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
class ImmediateLabel < Value
|
156
|
+
attr_reader :name
|
157
|
+
|
158
|
+
def initialize name
|
159
|
+
@name = name
|
160
|
+
@imm = nil
|
161
|
+
end
|
162
|
+
|
163
|
+
def resolve imm
|
164
|
+
fail if @imm
|
165
|
+
@imm = imm
|
166
|
+
end
|
167
|
+
|
168
|
+
def assemble
|
169
|
+
#fail unless @imm
|
170
|
+
return 0x1f, (@imm||0) # HACK
|
171
|
+
end
|
172
|
+
|
173
|
+
def to_s
|
174
|
+
"l(#{@name.inspect})"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
data/scasm.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/scasm/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Rich Lane"]
|
6
|
+
gem.email = ["rlane@club.cc.cmu.edu"]
|
7
|
+
gem.description = %q{}
|
8
|
+
gem.summary = %q{A Ruby DSL for DCPU-16 assembly code}
|
9
|
+
gem.homepage = "https://github.com/rlane/scasm"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "scasm"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Scasm::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency 'trollop'
|
19
|
+
end
|
data/test/foo.scasm
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
set reg(A), reg(B)
|
2
|
+
add reg(A), regmem(C)
|
3
|
+
sub reg(A), iregmem(X, 1)
|
4
|
+
div reg(A), pop
|
5
|
+
mod reg(A), peek
|
6
|
+
shl reg(A), push
|
7
|
+
shr reg(A), sp
|
8
|
+
and_ reg(A), pc
|
9
|
+
bor reg(A), o
|
10
|
+
xor reg(A), imem(5)
|
11
|
+
ife reg(A), imm(1)
|
12
|
+
ifn reg(A), imm(32)
|
13
|
+
ifg reg(I), reg(Y)
|
14
|
+
ifb reg(J), reg(Z)
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'scasm/assembler'
|
3
|
+
|
4
|
+
class AssemblerTest < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@expected = nil
|
7
|
+
end
|
8
|
+
|
9
|
+
def teardown
|
10
|
+
end
|
11
|
+
|
12
|
+
def expect words
|
13
|
+
@expected = words
|
14
|
+
end
|
15
|
+
|
16
|
+
def check code
|
17
|
+
as = SCASM::Assembler.new
|
18
|
+
as.eval code
|
19
|
+
result = as.assemble
|
20
|
+
result_words = result.unpack('v*')
|
21
|
+
assert_equal @expected, result_words
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_empty
|
25
|
+
expect []
|
26
|
+
check ''
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_reg
|
30
|
+
expect [
|
31
|
+
0x0401,
|
32
|
+
0x0c21,
|
33
|
+
0x1441,
|
34
|
+
0x1c61,
|
35
|
+
]
|
36
|
+
|
37
|
+
check <<-EOS
|
38
|
+
set reg(A), reg(B)
|
39
|
+
set reg(C), reg(X)
|
40
|
+
set reg(Y), reg(Z)
|
41
|
+
set reg(I), reg(J)
|
42
|
+
EOS
|
43
|
+
|
44
|
+
check <<-EOS
|
45
|
+
set A, B
|
46
|
+
set C, X
|
47
|
+
set Y, Z
|
48
|
+
set I, J
|
49
|
+
EOS
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_regmem
|
53
|
+
expect [0x0cb1]
|
54
|
+
check "set regmem(X), reg(X)"
|
55
|
+
check "set [X], X"
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_iregmem
|
59
|
+
expect [0x0d31, 0x002a]
|
60
|
+
check 'set iregmem(X, 42), reg(X)'
|
61
|
+
check 'set [X,42], X'
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_misc
|
65
|
+
expect [0x6181]
|
66
|
+
check 'set pop, pop'
|
67
|
+
|
68
|
+
expect [0x6591]
|
69
|
+
check 'set peek, peek'
|
70
|
+
|
71
|
+
expect [0x69a1]
|
72
|
+
check 'set push, push'
|
73
|
+
|
74
|
+
expect [0x6db1]
|
75
|
+
check 'set sp, sp'
|
76
|
+
|
77
|
+
expect [0x71c1]
|
78
|
+
check 'set pc, pc'
|
79
|
+
|
80
|
+
expect [0x75d1]
|
81
|
+
check 'set o, o'
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_imem
|
85
|
+
expect [0x7801, 0x1000]
|
86
|
+
check 'set reg(A), imem(0x1000)'
|
87
|
+
check 'set A, [0x1000]'
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_imm
|
91
|
+
expect [0xfc01]
|
92
|
+
check "set reg(A), imm(31)"
|
93
|
+
check "set A, 31"
|
94
|
+
|
95
|
+
expect [0x7c01, 0x0020]
|
96
|
+
check "set reg(A), imm(32)"
|
97
|
+
check "set A, 32"
|
98
|
+
|
99
|
+
expect [0x7df1, 0xffff, 0x0020]
|
100
|
+
check "set imm(65535), imm(32)"
|
101
|
+
check "set 65535, 32"
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_label
|
105
|
+
expect [
|
106
|
+
0xb401,
|
107
|
+
0x8402,
|
108
|
+
0x7dc1, 0x0001,
|
109
|
+
]
|
110
|
+
|
111
|
+
check <<-EOS
|
112
|
+
set reg(A), imm(13)
|
113
|
+
label 'loop'
|
114
|
+
add reg(A), imm(1)
|
115
|
+
set pc, l('loop')
|
116
|
+
EOS
|
117
|
+
|
118
|
+
check <<-EOS
|
119
|
+
set A, 13
|
120
|
+
label 'loop'
|
121
|
+
add A, 1
|
122
|
+
set pc, 'loop'
|
123
|
+
EOS
|
124
|
+
end
|
125
|
+
|
126
|
+
def test_ext_insts
|
127
|
+
expect [0x0010]
|
128
|
+
check 'jsr A'
|
129
|
+
|
130
|
+
expect [0x7c10, 0x0020]
|
131
|
+
check 'jsr 32'
|
132
|
+
end
|
133
|
+
end
|
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: scasm
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Rich Lane
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-05 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: trollop
|
16
|
+
requirement: &15992680 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *15992680
|
25
|
+
description: ''
|
26
|
+
email:
|
27
|
+
- rlane@club.cc.cmu.edu
|
28
|
+
executables:
|
29
|
+
- scasm
|
30
|
+
- scasm-disassemble
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- .gitignore
|
35
|
+
- Gemfile
|
36
|
+
- LICENSE
|
37
|
+
- README.md
|
38
|
+
- Rakefile
|
39
|
+
- bin/scasm
|
40
|
+
- bin/scasm-disassemble
|
41
|
+
- examples/spec.scasm
|
42
|
+
- lib/scasm/assembler.rb
|
43
|
+
- lib/scasm/isa.rb
|
44
|
+
- lib/scasm/statement.rb
|
45
|
+
- lib/scasm/value.rb
|
46
|
+
- lib/scasm/version.rb
|
47
|
+
- scasm.gemspec
|
48
|
+
- test/foo.scasm
|
49
|
+
- test/test_assembler.rb
|
50
|
+
homepage: https://github.com/rlane/scasm
|
51
|
+
licenses: []
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
requirements: []
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 1.8.17
|
71
|
+
signing_key:
|
72
|
+
specification_version: 3
|
73
|
+
summary: A Ruby DSL for DCPU-16 assembly code
|
74
|
+
test_files:
|
75
|
+
- test/foo.scasm
|
76
|
+
- test/test_assembler.rb
|