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