gasm 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +8 -0
- data/data/gasm/cpus/6502.gasm.rb +162 -0
- data/data/gasm/cpus/chip8.gasm.rb +66 -0
- data/data/gasm/cpus/chip8.gasm.yml +47 -0
- data/data/gasm/cpus/gameboy.gasm.rb +136 -0
- data/exe/gasm +77 -0
- data/gasm.gemspec +40 -0
- data/lib/gasm/asm.rb +31 -0
- data/lib/gasm/gasm_loader.rb +37 -0
- data/lib/gasm/gasm_matcher.rb +192 -0
- data/lib/gasm/instruction_section.rb +20 -0
- data/lib/gasm/op_section.rb +19 -0
- data/lib/gasm/ruby_desc.rb +17 -0
- data/lib/gasm/version.rb +5 -0
- data/lib/gasm.rb +4 -0
- metadata +77 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: bd06be7e5047c313deeb459a869c00cbf56ff8efd4d3d4190cd825ba09353eb3
|
4
|
+
data.tar.gz: da6ddd558a76a47819abe388c3cd57564c89530ec762f6493c793d6a81225a2b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 68e594afb0d28eb6b22bf9e8fb126881157f2e447edbeac877cf86e0320665534b451ce96bb239cfa976389acebe669d0d05b1c3c440cb3c348e246749e9b26d
|
7
|
+
data.tar.gz: 57f58715abe134ed443389ab2d432b454f4239b6dcf6ebd3703f80fed67da13533d9ad2fcad05930d8055a11be814b1d241c6ffa0e6659eb3b1f129954f44dd3
|
data/Gemfile
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
# 6502 instructions
|
2
|
+
# refs:
|
3
|
+
# https://llx.com/Neil/a2/opcodes.html
|
4
|
+
# http://www.6502.org/tutorials/6502opcodes.html
|
5
|
+
# https://www.cs.otago.ac.nz/cosc243/pdf/6502Poster.pdf
|
6
|
+
|
7
|
+
asm do
|
8
|
+
instructions do
|
9
|
+
def impl_type_a_instr(opcodes, addrmodes, c)
|
10
|
+
opcodes.each_with_index do |opcode, oi|
|
11
|
+
addrmodes.each_with_index do |(pattern, zeropage, operands), ai|
|
12
|
+
next if pattern.nil?
|
13
|
+
next if pattern == '___'
|
14
|
+
|
15
|
+
a = oi.to_s(2).rjust(3, '0')
|
16
|
+
b = ai.to_s(2).rjust(3, '0')
|
17
|
+
|
18
|
+
condition = Proc.new do true end
|
19
|
+
condition = Proc.new do |x|
|
20
|
+
x[:n] < 256
|
21
|
+
end if zeropage
|
22
|
+
|
23
|
+
op "#{opcode} #{pattern}", "#{a} #{b} #{c} #{'nnnn nnnn' * operands}", &condition
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def hexop(name, hex, parambytes = 0)
|
29
|
+
op name, hex.to_s(2).rjust(8, '0') + "nnnn nnnn" * parambytes
|
30
|
+
end
|
31
|
+
|
32
|
+
# addressing modes:
|
33
|
+
|
34
|
+
ADDR_ZERO_IN_X = ['(<n>,X)', true , 1] # (zero page, X)
|
35
|
+
ADDR_ZERO_PAGE = ['<n>', true , 1] # zero page absolute
|
36
|
+
ADDR_IMMEDIATE = ['#<n>', false, 1] # #immediate
|
37
|
+
ADDR_ABSOLUTE = ['<n>', false, 2] # absolute
|
38
|
+
ADDR_ZERO_IN_Y = ['(<n>),Y', true , 1] # (zero page), Y
|
39
|
+
ADDR_ZERO_X = ['<n>,X', true , 1] # zero page absolute, X
|
40
|
+
ADDR_ZERO_Y = ['<n>,Y', true , 1] # zero page absolute, Y
|
41
|
+
ADDR_ABS_Y = ['<n>,Y', false, 2] # absolute, Y
|
42
|
+
ADDR_ABS_X = ['<n>,X', false, 2] # absolute, X
|
43
|
+
ADDR_ACCUMULAT = ['A', false, 0] # accumulator
|
44
|
+
ADDR_INVALID = [] # invalid addressing mode
|
45
|
+
|
46
|
+
# single byte instructions
|
47
|
+
hexop 'brk', 0x00
|
48
|
+
hexop 'nop', 0xea
|
49
|
+
hexop 'rti', 0x40
|
50
|
+
hexop 'rts', 0x60
|
51
|
+
|
52
|
+
hexop 'php', 0x08
|
53
|
+
hexop 'plp', 0x28
|
54
|
+
hexop 'pha', 0x48
|
55
|
+
hexop 'pla', 0x68
|
56
|
+
|
57
|
+
hexop 'txs', 0x9a
|
58
|
+
hexop 'tsx', 0xba
|
59
|
+
|
60
|
+
hexop 'tay', 0xa8
|
61
|
+
hexop 'tax', 0xaa
|
62
|
+
|
63
|
+
hexop 'dex', 0xca
|
64
|
+
hexop 'dey', 0x88
|
65
|
+
|
66
|
+
hexop 'iny', 0xc8
|
67
|
+
hexop 'inx', 0xe8
|
68
|
+
|
69
|
+
hexop 'txa', 0x8a
|
70
|
+
hexop 'tya', 0x98
|
71
|
+
|
72
|
+
hexop 'clc', 0x18
|
73
|
+
hexop 'cli', 0x58
|
74
|
+
hexop 'clv', 0xb8
|
75
|
+
hexop 'cld', 0xd8
|
76
|
+
|
77
|
+
hexop 'sec', 0x38
|
78
|
+
hexop 'sei', 0x78
|
79
|
+
hexop 'sed', 0xf8
|
80
|
+
|
81
|
+
# type aaabbbcc instructions (01)
|
82
|
+
# I left sta #n in but its a nop anyway. it will generate the code without complaining
|
83
|
+
opcodes = %w[ora and eor adc sta lda cmp sbc]
|
84
|
+
|
85
|
+
addrmodes = [
|
86
|
+
ADDR_ZERO_IN_X,
|
87
|
+
ADDR_ZERO_PAGE,
|
88
|
+
ADDR_IMMEDIATE,
|
89
|
+
ADDR_ABSOLUTE,
|
90
|
+
ADDR_ZERO_IN_Y,
|
91
|
+
ADDR_ZERO_X,
|
92
|
+
ADDR_ABS_Y,
|
93
|
+
ADDR_ABS_X
|
94
|
+
]
|
95
|
+
|
96
|
+
impl_type_a_instr(opcodes, addrmodes, '01')
|
97
|
+
|
98
|
+
# type aaabbbcc instructions (10)
|
99
|
+
# %w[asl rol lsr ror stx ldx dec inc]
|
100
|
+
|
101
|
+
common = [
|
102
|
+
ADDR_ZERO_PAGE,
|
103
|
+
ADDR_ACCUMULAT,
|
104
|
+
ADDR_ABSOLUTE,
|
105
|
+
ADDR_INVALID,
|
106
|
+
]
|
107
|
+
|
108
|
+
no_a = [
|
109
|
+
ADDR_ZERO_PAGE,
|
110
|
+
ADDR_INVALID,
|
111
|
+
ADDR_ABSOLUTE,
|
112
|
+
ADDR_INVALID,
|
113
|
+
]
|
114
|
+
|
115
|
+
# zero page statements
|
116
|
+
zerox = [ADDR_ZERO_X, ADDR_INVALID]
|
117
|
+
zeroy = [ADDR_ZERO_Y, ADDR_INVALID]
|
118
|
+
|
119
|
+
# absolute statements (only available in load)
|
120
|
+
absx = [ADDR_ABS_X]
|
121
|
+
absy = [ADDR_ABS_Y]
|
122
|
+
|
123
|
+
# note: stx a and ldx a are basically txa and tax, so we allow it
|
124
|
+
# note: stx abs,Y could actually exist but doesnt because it maps to an illegal instruction that is undefined.
|
125
|
+
impl_type_a_instr(%w[___ ___ ___ ___ ___ ldx ___ ___], [ADDR_IMMEDIATE] + common + zeroy + absy, '10')
|
126
|
+
impl_type_a_instr(%w[___ ___ ___ ___ stx ___ ___ ___], [ADDR_INVALID] + common + zeroy , '10')
|
127
|
+
|
128
|
+
# note: dec a is the same as dex. so we allow it
|
129
|
+
impl_type_a_instr(%w[asl rol lsr ror ___ ___ dec ___], [ADDR_INVALID] + common + zerox + absx, '10')
|
130
|
+
|
131
|
+
# inc cannot access the accumulator. that opcode is nop
|
132
|
+
impl_type_a_instr(%w[___ ___ ___ ___ ___ ___ ___ inc], [ADDR_INVALID] + no_a + zerox + absx, '10')
|
133
|
+
|
134
|
+
# type aaabbbcc instructions (00)
|
135
|
+
# %w[___ bit ___ ___ sty ldy cpy cpx]
|
136
|
+
|
137
|
+
# note: ldy a is the same as tay so we allow it. sty a if we use the normal accumulator
|
138
|
+
# pattern unfortunately leads to dey. we put in a special entry to map it to tya
|
139
|
+
impl_type_a_instr(%w[___ ___ ___ ___ ___ ldy ___ ___], [ADDR_IMMEDIATE] + common + zerox + absx, '00')
|
140
|
+
impl_type_a_instr(%w[___ ___ ___ ___ sty ___ ___ ___], [ADDR_INVALID] + no_a + zerox , '00')
|
141
|
+
hexop 'sty A', 0x98
|
142
|
+
|
143
|
+
impl_type_a_instr(%w[___ ___ ___ ___ ___ ___ cpy cpx], [ADDR_IMMEDIATE] + no_a , '00')
|
144
|
+
|
145
|
+
impl_type_a_instr(%w[___ bit ___ ___ ___ ___ ___ ___], [ADDR_INVALID] + no_a , '00')
|
146
|
+
|
147
|
+
# control flow instructions
|
148
|
+
hexop 'jmp <n>', 0x4c, 2
|
149
|
+
hexop 'jmp (<n>)', 0x6c, 2
|
150
|
+
hexop 'jsr <n>', 0x20, 2
|
151
|
+
|
152
|
+
hexop 'bpl <n>', 0x10, 1
|
153
|
+
hexop 'bmi <n>', 0x30, 1
|
154
|
+
hexop 'bvc <n>', 0x50, 1
|
155
|
+
hexop 'bvs <n>', 0x70, 1
|
156
|
+
|
157
|
+
hexop 'bcc <n>', 0x90, 1
|
158
|
+
hexop 'bcs <n>', 0xb0, 1
|
159
|
+
hexop 'bne <n>', 0xd0, 1
|
160
|
+
hexop 'beq <n>', 0xf0, 1
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
asm do
|
2
|
+
instructions do
|
3
|
+
# Generate matchers for hex values for register
|
4
|
+
def vp(pattern, bits)
|
5
|
+
if pattern.include?('V<x>')
|
6
|
+
hex_letters = %w[a b c d e f A B C D E F]
|
7
|
+
hex_letters.each do |register_x|
|
8
|
+
pattern_a = pattern.gsub('V<x>', "V#{register_x}")
|
9
|
+
bits_a = bits.gsub('xxxx', "#{register_x.to_i(16).to_s(2)}")
|
10
|
+
|
11
|
+
if pattern_a.include?('V<y>')
|
12
|
+
hex_letters.each do |register_y|
|
13
|
+
pattern_b = pattern_a.gsub('V<y>', "V#{register_y}")
|
14
|
+
bits_b = bits_a.gsub('yyyy', "#{register_y.to_i(16).to_s(2)}")
|
15
|
+
op pattern_b, bits_b
|
16
|
+
end
|
17
|
+
end
|
18
|
+
op pattern_a, bits_a
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
op pattern, bits
|
23
|
+
end
|
24
|
+
|
25
|
+
vp "ld V<x>, [I]" , "1111 xxxx 0110 0101" # LD Vx, [I]
|
26
|
+
vp "ld [I], V<x>" , "1111 xxxx 0101 0101" # LD [I], Vx
|
27
|
+
vp "ld B, V<x>" , "1111 xxxx 0011 0011" # LD B, Vx
|
28
|
+
vp "ld F, V<x>" , "1111 xxxx 0010 1001" # LD F, Vx
|
29
|
+
vp "add I, V<x>" , "1111 xxxx 0001 1110" # ADD I, Vx
|
30
|
+
vp "ld ST, V<x>" , "1111 xxxx 0001 1000" # LD ST, Vx
|
31
|
+
vp "ld DT, V<x>" , "1111 xxxx 0001 0101" # LD DT, Vx
|
32
|
+
vp "ld V<x>, K" , "1111 xxxx 0000 1010" # LD Vx, K
|
33
|
+
vp "ld V<x>, DT" , "1111 xxxx 0000 0111" # LD Vx, DT
|
34
|
+
|
35
|
+
vp "sknp V<x>" , "1110 xxxx 1010 0001" # SKNP Vx
|
36
|
+
vp "skp V<x>" , "1110 xxxx 1001 1110" # SKP Vx
|
37
|
+
vp "drw V<x>, V<y>, <n>" , "1101 xxxx yyyy nnnn" # DRW Vx, Vy, n
|
38
|
+
vp "rnd V<x>, <k>" , "1100 xxxx kkkk kkkk" # RND Vx, k; rnd Vx & k
|
39
|
+
op "jp V0, <n>" , "1011 nnnn nnnn nnnn" # JP V0, n; jump n + V0
|
40
|
+
op "ld I, <n>" , "1010 nnnn nnnn nnnn" # LD I, n
|
41
|
+
vp "sne V<x>, V<y>" , "1001 xxxx yyyy 0000" # SNE Vx, Vy
|
42
|
+
|
43
|
+
vp "shl V<x>" , "1000 xxxx yyyy 1110" # SHL Vx
|
44
|
+
vp "subn V<x>, V<y>" , "1000 xxxx yyyy 0111" # SUBN Vx, Vy
|
45
|
+
vp "shr V<x>" , "1000 xxxx yyyy 0110" # SHR Vx
|
46
|
+
vp "sub V<x>, V<y>" , "1000 xxxx yyyy 0101" # SUB Vx, Vy
|
47
|
+
vp "add V<x>, V<y>" , "1000 xxxx yyyy 0100" # ADD Vx, Vy
|
48
|
+
vp "xor V<x>, V<y>" , "1000 xxxx yyyy 0011" # XOR Vx, Vy
|
49
|
+
vp "and V<x>, V<y>" , "1000 xxxx yyyy 0010" # AND Vx, Vy
|
50
|
+
vp "or V<x>, V<y>" , "1000 xxxx yyyy 0001" # OR Vx, Vy
|
51
|
+
vp "ld V<x>, V<y>" , "1000 xxxx yyyy 0000" # LD Vx, Vy
|
52
|
+
|
53
|
+
vp "add V<x>, <k>" , "0111 xxxx kkkk kkkk" # ADD Vx, k
|
54
|
+
vp "ld V<x>, <k>" , "0110 xxxx kkkk kkkk" # LD Vx, k
|
55
|
+
vp "se V<x>, V<y>" , "0101 xxxx yyyy 0000" # SE Vx, Vy
|
56
|
+
vp "sne V<x>, <k>" , "0100 xxxx kkkk kkkk" # SNE Vx, k
|
57
|
+
vp "se V<x>, <k>" , "0011 xxxx kkkk kkkk" # SE Vx, k
|
58
|
+
|
59
|
+
op "call <n>" , "0010 nnnn nnnn nnnn"
|
60
|
+
op "jp <n>" , "0001 nnnn nnnn nnnn"
|
61
|
+
op "ret" , "0000 0000 1110 1110"
|
62
|
+
op "cls" , "0000 0000 1110 0000"
|
63
|
+
|
64
|
+
op "nop" , "1000 0000 0000 0000" # made up. basically LD V0, V0
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# CHIP8 instruction set
|
2
|
+
# Every instruction is 16 bits long
|
3
|
+
asm:
|
4
|
+
instructions:
|
5
|
+
# This is a made-up instruction NOP, that basically does LD V0, V0
|
6
|
+
# it just wastes a cycle and does not do anything
|
7
|
+
nop : 1000 0000 0000 0000
|
8
|
+
|
9
|
+
ld V<x>, [I] : 1111 xxxx 0110 0101 # LD Vx, [I]
|
10
|
+
ld [I], V<x> : 1111 xxxx 0101 0101 # LD [I], Vx
|
11
|
+
ld B, V<x> : 1111 xxxx 0011 0011 # LD B, Vx
|
12
|
+
ld F, V<x> : 1111 xxxx 0010 1001 # LD F, Vx
|
13
|
+
add I, V<x> : 1111 xxxx 0001 1110 # ADD I, Vx
|
14
|
+
ld ST, V<x> : 1111 xxxx 0001 1000 # LD ST, Vx
|
15
|
+
ld DT, V<x> : 1111 xxxx 0001 0101 # LD DT, Vx
|
16
|
+
ld V<x>, K : 1111 xxxx 0000 1010 # LD Vx, K
|
17
|
+
ld V<x>, DT : 1111 xxxx 0000 0111 # LD Vx, DT
|
18
|
+
|
19
|
+
sknp V<x> : 1110 xxxx 1010 0001 # SKNP Vx
|
20
|
+
skp V<x> : 1110 xxxx 1001 1110 # SKP Vx
|
21
|
+
|
22
|
+
drw V<x>, V<y>, <n> : 1101 xxxx yyyy nnnn # DRW Vx, Vy, n
|
23
|
+
rnd V<x>, <k> : 1100 xxxx kkkk kkkk # RND Vx, k; rnd Vx & k
|
24
|
+
jp V0, <n> : 1011 nnnn nnnn nnnn # JP V0, n; jump n + V0
|
25
|
+
ld I, <n> : 1010 nnnn nnnn nnnn # LD I, n
|
26
|
+
|
27
|
+
sne V<x>, V<y> : 1001 xxxx yyyy 0000 # SNE Vx, Vy
|
28
|
+
|
29
|
+
shl V<x> : 1000 xxxx yyyy 1110 # SHL Vx
|
30
|
+
subn V<x>, V<y> : 1000 xxxx yyyy 0111 # SUBN Vx, Vy
|
31
|
+
shr V<x> : 1000 xxxx yyyy 0110 # SHR Vx
|
32
|
+
sub V<x>, V<y> : 1000 xxxx yyyy 0101 # SUB Vx, Vy
|
33
|
+
add V<x>, V<y> : 1000 xxxx yyyy 0100 # ADD Vx, Vy
|
34
|
+
xor V<x>, V<y> : 1000 xxxx yyyy 0011 # XOR Vx, Vy
|
35
|
+
and V<x>, V<y> : 1000 xxxx yyyy 0010 # AND Vx, Vy
|
36
|
+
or V<x>, V<y> : 1000 xxxx yyyy 0001 # OR Vx, Vy
|
37
|
+
ld V<x>, V<y> : 1000 xxxx yyyy 0000 # LD Vx, Vy
|
38
|
+
|
39
|
+
add V<x>, <k> : 0111 xxxx kkkk kkkk # ADD Vx, k
|
40
|
+
ld V<x>, <k> : 0110 xxxx kkkk kkkk # LD Vx, k
|
41
|
+
se V<x>, V<y> : 0101 xxxx yyyy 0000 # SE Vx, Vy
|
42
|
+
sne V<x>, <k> : 0100 xxxx kkkk kkkk # SNE Vx, k
|
43
|
+
se V<x>, <k> : 0011 xxxx kkkk kkkk # SE Vx, k
|
44
|
+
call <n> : 0010 nnnn nnnn nnnn
|
45
|
+
jp <n> : 0001 nnnn nnnn nnnn
|
46
|
+
ret : 0000 0000 1110 1110
|
47
|
+
cls : 0000 0000 1110 0000
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# gameboy cpu instructions
|
2
|
+
# refs:
|
3
|
+
# http://marc.rawer.de/Gameboy/Docs/GBCPUman.pdf
|
4
|
+
# https://meganesulli.com/generate-gb-opcodes/
|
5
|
+
|
6
|
+
TARGETS = %w[B C D E H L (HL) A]
|
7
|
+
CB = "1100 1011"
|
8
|
+
|
9
|
+
asm do
|
10
|
+
instructions do
|
11
|
+
|
12
|
+
def tbit(i)
|
13
|
+
i.to_s(2).rjust(3,'0')
|
14
|
+
end
|
15
|
+
|
16
|
+
def dbit(i)
|
17
|
+
i.to_s(2).rjust(2,'0')
|
18
|
+
end
|
19
|
+
|
20
|
+
def hexop(name, hex)
|
21
|
+
op name, hex.to_s(2).rjust(8, '0')
|
22
|
+
end
|
23
|
+
|
24
|
+
# reset to position
|
25
|
+
%w[00 08 10 18 20 28 30 38].each do |pos|
|
26
|
+
op "rst $#{pos}", "11 #{(pos.to_i(16)+7).to_s(2)}"
|
27
|
+
end
|
28
|
+
|
29
|
+
# conditional jumps
|
30
|
+
%w[NZ Z NC C].each_with_index do |cond, i|
|
31
|
+
op "jp #{cond}, <n>", "110 #{dbit(i)} 010 nnnn nnnn nnnn nnnn"
|
32
|
+
op "jr #{cond}, <n>", "001 #{dbit(i)} 000 nnnn nnnn"
|
33
|
+
op "call #{cond}, <n>", "110 #{dbit(i)} 100 nnnn nnnn nnnn nnnn"
|
34
|
+
op "ret #{cond}", "110 #{dbit(i)} 000"
|
35
|
+
end
|
36
|
+
|
37
|
+
# jumps
|
38
|
+
op "jp (HL)", "1110 1001"
|
39
|
+
op "jp <n>", "1100 0011 nnnn nnnn nnnn nnnn"
|
40
|
+
op "jr <n>", "0001 1000 nnnn nnnn"
|
41
|
+
op "call <n>", "1100 1101 nnnn nnnn nnnn nnnn"
|
42
|
+
|
43
|
+
# shift and rotates
|
44
|
+
%w[rlc rrc rl rr sla sra swap srl].each_with_index do |oper, opidx|
|
45
|
+
TARGETS.each_with_index do |reg, i|
|
46
|
+
op "#{oper} #{reg}" , "#{CB} 00 #{tbit(opidx)} #{tbit(i)}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# bit twiddling
|
51
|
+
TARGETS.each_with_index do |reg, i|
|
52
|
+
op "bit <b>, #{reg}" , "#{CB} 01 bbb #{tbit(i)}"
|
53
|
+
op "res <b>, #{reg}" , "#{CB} 10 bbb #{tbit(i)}"
|
54
|
+
op "set <b>, #{reg}" , "#{CB} 11 bbb #{tbit(i)}"
|
55
|
+
end
|
56
|
+
|
57
|
+
op "stop", "0001 0000 0000 0000"
|
58
|
+
|
59
|
+
# misc instructions
|
60
|
+
hexop "daa", 0x27
|
61
|
+
hexop "cpl", 0x2f
|
62
|
+
hexop "ccf", 0x3f
|
63
|
+
hexop "scf", 0x37
|
64
|
+
|
65
|
+
hexop "reti", 0xd9
|
66
|
+
hexop "ret", 0xc9
|
67
|
+
|
68
|
+
hexop "nop", 0x00
|
69
|
+
hexop "halt", 0x76
|
70
|
+
|
71
|
+
hexop "di", 0xf3
|
72
|
+
hexop "ei", 0xfb
|
73
|
+
|
74
|
+
hexop "rlca", 0x07
|
75
|
+
hexop "rla", 0x17
|
76
|
+
hexop "rrca", 0x0f
|
77
|
+
hexop "rra", 0x1f
|
78
|
+
|
79
|
+
# 16-bit arithmetic
|
80
|
+
%w[BC DE HL SP].each_with_index do |reg, i|
|
81
|
+
op "add HL, #{reg}" , "00 #{dbit(i)} 1001"
|
82
|
+
op "inc #{reg}", "00 #{dbit(i)} 0011"
|
83
|
+
op "dec #{reg}", "00 #{dbit(i)} 1011"
|
84
|
+
end
|
85
|
+
|
86
|
+
# 8-bit arithmetic
|
87
|
+
%w[add adc sub sbc and xor or cp].each_with_index do |oper, operidx|
|
88
|
+
TARGETS.each_with_index do |reg, i|
|
89
|
+
op "#{oper} A, #{reg}" , "10 #{tbit(operidx)} #{tbit(i)}"
|
90
|
+
end
|
91
|
+
|
92
|
+
op "#{oper} A, <n>" , "11 #{tbit(operidx)} 110 nnnn nnnn"
|
93
|
+
end
|
94
|
+
|
95
|
+
TARGETS.each_with_index do |reg, i|
|
96
|
+
op "inc #{reg}" , "00 #{tbit(i)} 100"
|
97
|
+
op "dec #{reg}" , "00 #{tbit(i)} 101"
|
98
|
+
end
|
99
|
+
|
100
|
+
op "add SP, <n>", "1110 1000 nnnn nnnn"
|
101
|
+
|
102
|
+
# stack
|
103
|
+
%w[BC DE HL AF].each_with_index do |reg, i|
|
104
|
+
op "pop #{reg}" , "11 #{dbit(i)} 0001"
|
105
|
+
end
|
106
|
+
|
107
|
+
%w[BC DE HL AF].each_with_index do |reg, i|
|
108
|
+
op "push #{reg}" , "11 #{dbit(i)} 0101"
|
109
|
+
end
|
110
|
+
|
111
|
+
op "ld (<n>), SP", "0000 1000 nnnn nnnn nnnn nnnn"
|
112
|
+
|
113
|
+
op "ld HL, SP + <n>", "1111 1000 nnnn nnnn nnnn nnnn"
|
114
|
+
|
115
|
+
# hl -> sp
|
116
|
+
op "ld SP, HL", "1111 1001"
|
117
|
+
|
118
|
+
# immediate 16-bit loads
|
119
|
+
%w[BC DE HL SP].each_with_index do |reg, i|
|
120
|
+
op "ld #{reg}, <n>" , "00 #{dbit(i)} 0001 nnnn nnnn nnnn nnnn"
|
121
|
+
end
|
122
|
+
|
123
|
+
# register-register 8-bit loads
|
124
|
+
TARGETS.each_with_index do |dreg, di|
|
125
|
+
TARGETS.each_with_index do |sreg, si|
|
126
|
+
op "ld #{dreg}, #{sreg}" , "01 #{tbit(di)} #{tbit(si)}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# immediate 8-bit loads
|
131
|
+
TARGETS.each_with_index do |reg, i|
|
132
|
+
op "ld #{reg}, <n>" , "00 #{tbit(i)} 110 nnnn nnnn"
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
data/exe/gasm
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'gasm'
|
4
|
+
|
5
|
+
include Gasm
|
6
|
+
|
7
|
+
asm_desc = ARGV[0]
|
8
|
+
asm_file = ARGV[1]
|
9
|
+
|
10
|
+
basedir = File.join(
|
11
|
+
Gem.loaded_specs['gasm'].full_gem_path,
|
12
|
+
'data', 'gasm', 'cpus')
|
13
|
+
|
14
|
+
if asm_desc == 'lscpu'
|
15
|
+
a = Dir[File.join(basedir, "*.gasm.rb")].map do |x|
|
16
|
+
x.sub!(basedir + '/', '')
|
17
|
+
x.sub!('.gasm.rb', '')
|
18
|
+
x
|
19
|
+
end
|
20
|
+
|
21
|
+
puts a.to_a
|
22
|
+
|
23
|
+
exit(0)
|
24
|
+
end
|
25
|
+
|
26
|
+
if asm_desc.nil? || asm_file.nil?
|
27
|
+
puts "USAGE: gasm <GASMFILE> <ASMFILE or ->"
|
28
|
+
puts "GASMFILE can be a YML or Ruby file that is a GASM description."
|
29
|
+
puts "ASMFILE is any text file that contains a series of assembly instructions separated"
|
30
|
+
puts " by newlines. You can also just write a dash and it will attempt to read from STDIN"
|
31
|
+
|
32
|
+
puts "USAGE: gasm lscpu"
|
33
|
+
puts "Lists known CPUs. See below."
|
34
|
+
|
35
|
+
puts "USAGE: gasm -<CPUNAME> <ASMFILE or ->"
|
36
|
+
puts "Assembles a known CPU."
|
37
|
+
puts "Valid values of CPUNAME can be found using 'gasm lscpu'"
|
38
|
+
exit(1)
|
39
|
+
end
|
40
|
+
|
41
|
+
if asm_desc.start_with?('-')
|
42
|
+
descfile = File.join(basedir, "#{asm_desc[1..-1]}.gasm.rb")
|
43
|
+
|
44
|
+
if !File.exist?(descfile)
|
45
|
+
STDERR.puts "'#{descfile}' is not a known CPU."
|
46
|
+
exit(1)
|
47
|
+
end
|
48
|
+
|
49
|
+
asm_desc = descfile
|
50
|
+
end
|
51
|
+
|
52
|
+
if !File.exist?(asm_desc)
|
53
|
+
STDERR.puts "'#{asm_desc}' GASM file does not exist"
|
54
|
+
exit(1)
|
55
|
+
end
|
56
|
+
|
57
|
+
begin
|
58
|
+
matcher = Gasm::GasmMatcher.new(GasmLoader.new(asm_desc).desc)
|
59
|
+
rescue => e
|
60
|
+
STDERR.puts "Maybe invalid GASM file: #{e.message}"
|
61
|
+
exit(1)
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
asm = if asm_file == '-'
|
66
|
+
Asm.new(STDIN.read, matcher)
|
67
|
+
|
68
|
+
else
|
69
|
+
if !File.exist?(asm_file)
|
70
|
+
STDERR.puts "'#{asm_file}' asm does not exist"
|
71
|
+
exit(1)
|
72
|
+
end
|
73
|
+
|
74
|
+
Asm.new(File.read(asm_file), matcher)
|
75
|
+
end
|
76
|
+
|
77
|
+
puts asm.compiled
|
data/gasm.gemspec
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'gasm/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
unless spec.respond_to?(:metadata)
|
9
|
+
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host',
|
10
|
+
# or delete this section to allow pushing this gem to any host.
|
11
|
+
raise <<-ERR
|
12
|
+
RubyGems 2.0 or newer is required to protect against public gem pushes.
|
13
|
+
ERR
|
14
|
+
end
|
15
|
+
|
16
|
+
spec.name = 'gasm'
|
17
|
+
spec.version = Gasm::VERSION
|
18
|
+
spec.authors = ['David Siaw']
|
19
|
+
spec.email = ['davidsiaw@gmail.com']
|
20
|
+
|
21
|
+
spec.summary = 'General ASsembler Maker'
|
22
|
+
spec.description = 'Generates an assembler based on an assembly description'
|
23
|
+
spec.homepage = 'https://github.com/davidsiaw/gasm'
|
24
|
+
spec.license = 'MIT'
|
25
|
+
|
26
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
27
|
+
spec.metadata['source_code_uri'] = 'https://github.com/davidsiaw/gasm'
|
28
|
+
spec.metadata['changelog_uri'] = 'https://github.com/davidsiaw/gasm'
|
29
|
+
|
30
|
+
spec.files = Dir['{data,exe,lib,bin}/**/*'] +
|
31
|
+
%w[Gemfile gasm.gemspec]
|
32
|
+
spec.test_files = Dir['{spec,features}/**/*']
|
33
|
+
spec.bindir = 'exe'
|
34
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
35
|
+
spec.require_paths = ['lib']
|
36
|
+
|
37
|
+
spec.add_development_dependency 'rspec'
|
38
|
+
|
39
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
40
|
+
end
|
data/lib/gasm/asm.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Gasm
|
2
|
+
class Asm
|
3
|
+
def initialize(contents, gasm)
|
4
|
+
@gasm = gasm
|
5
|
+
@contents = contents
|
6
|
+
end
|
7
|
+
|
8
|
+
def compiled
|
9
|
+
lines = @contents.split("\n")
|
10
|
+
offset = 0
|
11
|
+
|
12
|
+
result = []
|
13
|
+
|
14
|
+
lines.each do |line|
|
15
|
+
line.strip!
|
16
|
+
next if line.length.zero? # Skip empty lines
|
17
|
+
|
18
|
+
if line.start_with?('//') # Skip over comments
|
19
|
+
result << line
|
20
|
+
next
|
21
|
+
end
|
22
|
+
|
23
|
+
parsed = @gasm.parse(line)
|
24
|
+
result << line
|
25
|
+
result << "#{parsed}\n" if parsed
|
26
|
+
end
|
27
|
+
|
28
|
+
result.join("\n")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'gasm/ruby_desc'
|
3
|
+
|
4
|
+
module Gasm
|
5
|
+
class GasmLoader
|
6
|
+
def initialize(filename)
|
7
|
+
@filename = filename
|
8
|
+
end
|
9
|
+
|
10
|
+
def rubydesc
|
11
|
+
rbdesc = RubyDesc.new
|
12
|
+
rbdesc.instance_eval(File.read(@filename), @filename)
|
13
|
+
rbdesc.desc
|
14
|
+
end
|
15
|
+
|
16
|
+
def desc
|
17
|
+
return yamldesc if @filename.end_with?('.yml')
|
18
|
+
|
19
|
+
rubydesc
|
20
|
+
end
|
21
|
+
|
22
|
+
def yamldesc
|
23
|
+
v = YAML.load_file(@filename)
|
24
|
+
|
25
|
+
newhash = {}
|
26
|
+
v['asm']['instructions'].each do |k, v|
|
27
|
+
newhash[k] = {
|
28
|
+
bits: v,
|
29
|
+
condition: Proc.new {|x| true}
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
v['asm']['instructions'] = newhash
|
34
|
+
v
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
module Gasm
|
2
|
+
class GasmMatcher
|
3
|
+
def initialize(desc)
|
4
|
+
@desc = desc
|
5
|
+
end
|
6
|
+
|
7
|
+
def strip_spaces(line)
|
8
|
+
line.sub(/([^%\$a-z0-9]) +/, '\1')
|
9
|
+
end
|
10
|
+
|
11
|
+
def strip_comment(line)
|
12
|
+
line.split('//')[0]
|
13
|
+
end
|
14
|
+
|
15
|
+
NUMCHARS = %[0 1 2 3 4 5 6 7 8 9 a b c d e f o x b $ %]
|
16
|
+
def parse(line)
|
17
|
+
line = strip_comment(line)
|
18
|
+
line = strip_spaces(line)
|
19
|
+
line = line.strip
|
20
|
+
|
21
|
+
result = ''
|
22
|
+
values = {}
|
23
|
+
idx = 0
|
24
|
+
|
25
|
+
@desc['asm']['instructions'].each do |k, info|
|
26
|
+
result = info[:bits]
|
27
|
+
idx = 0
|
28
|
+
|
29
|
+
state = 'CHR'
|
30
|
+
varname = ''
|
31
|
+
|
32
|
+
values = {}
|
33
|
+
k.split('').each do |chr|
|
34
|
+
#p chr
|
35
|
+
if state == 'VAR'
|
36
|
+
if chr.ord >= 'a'.ord && chr.ord <= 'z'.ord
|
37
|
+
varname = chr
|
38
|
+
state = 'FIN'
|
39
|
+
else
|
40
|
+
throw "expected a-z but got #{chr}"
|
41
|
+
end
|
42
|
+
|
43
|
+
elsif state == 'FIN' &&
|
44
|
+
if chr == '>'
|
45
|
+
state = 'CHR'
|
46
|
+
value = ''
|
47
|
+
loop do
|
48
|
+
break if line[idx].nil? || !NUMCHARS.include?(line[idx])
|
49
|
+
value += line[idx]
|
50
|
+
idx += 1
|
51
|
+
end
|
52
|
+
values[varname] = value
|
53
|
+
|
54
|
+
else
|
55
|
+
throw "expected > but got #{chr}"
|
56
|
+
end
|
57
|
+
|
58
|
+
elsif state == 'CHR'
|
59
|
+
if chr == '<'
|
60
|
+
state = 'VAR'
|
61
|
+
elsif chr != line[idx]
|
62
|
+
result = nil
|
63
|
+
break
|
64
|
+
else
|
65
|
+
idx += 1
|
66
|
+
end
|
67
|
+
|
68
|
+
else
|
69
|
+
throw "unknown state #{state} #{chr}"
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# parsing was not complete, so its not this one
|
75
|
+
if line[idx] != nil
|
76
|
+
result = nil
|
77
|
+
end
|
78
|
+
|
79
|
+
#p result, values, line, k
|
80
|
+
# convert values to ints
|
81
|
+
values = values.map do |k, v|
|
82
|
+
int = 0
|
83
|
+
if v.start_with?('0x')
|
84
|
+
int = v[2..-1].to_i(16).to_s(2)
|
85
|
+
elsif v.start_with?('$')
|
86
|
+
int = v[1..-1].to_i(16).to_s(2)
|
87
|
+
elsif v.start_with?('0b')
|
88
|
+
int = v[2..-1].to_i(2).to_s(2)
|
89
|
+
elsif v.start_with?('%')
|
90
|
+
int = v[1..-1].to_i(2).to_s(2)
|
91
|
+
elsif v.to_i.to_s == v
|
92
|
+
int = v.to_i.to_s(2)
|
93
|
+
else
|
94
|
+
# we encounter a non-number or some symbol that makes no sense
|
95
|
+
# this might not be the instruction we want.
|
96
|
+
result = nil
|
97
|
+
break
|
98
|
+
end
|
99
|
+
[k, int]
|
100
|
+
end.to_h
|
101
|
+
|
102
|
+
next if result.nil?
|
103
|
+
|
104
|
+
# check the custom condition tagged on the opcode
|
105
|
+
unless info[:condition].call(values.map{|k,v| [k.to_sym, v.to_i(2)]}.to_h)
|
106
|
+
result = nil
|
107
|
+
end
|
108
|
+
|
109
|
+
break unless result.nil?
|
110
|
+
end
|
111
|
+
|
112
|
+
if result.nil?
|
113
|
+
throw "unknown instruction '#{line}'"
|
114
|
+
end
|
115
|
+
|
116
|
+
# fill in result
|
117
|
+
result = result.to_s.gsub(/\s+/, '')
|
118
|
+
idx = result.length - 1
|
119
|
+
varidx = 0
|
120
|
+
|
121
|
+
lastvar = ' '
|
122
|
+
output = ''
|
123
|
+
|
124
|
+
evalues = endiannize(values)
|
125
|
+
loop do
|
126
|
+
|
127
|
+
if result[idx] == lastvar
|
128
|
+
varidx += 1
|
129
|
+
else
|
130
|
+
varidx = 0
|
131
|
+
end
|
132
|
+
|
133
|
+
if evalues.key? result[idx]
|
134
|
+
output = (evalues[result[idx]][-1 - varidx] || '.') + output
|
135
|
+
lastvar = result[idx]
|
136
|
+
else
|
137
|
+
lastvar = ' '
|
138
|
+
varidx = 0
|
139
|
+
output = result[idx] + output
|
140
|
+
end
|
141
|
+
|
142
|
+
#puts "#{evalues}, #{lastvar} #{varidx}"
|
143
|
+
|
144
|
+
idx -= 1
|
145
|
+
break if idx < 0
|
146
|
+
end
|
147
|
+
|
148
|
+
# split array into groups of 8 bits
|
149
|
+
toks = output.split('').each_slice(8)
|
150
|
+
|
151
|
+
# rejoin the 8 bits in bsm format, and pad the end with zeros
|
152
|
+
toks = toks.map do |x|
|
153
|
+
"#{x.join('').ljust(8, '.')}"
|
154
|
+
end
|
155
|
+
|
156
|
+
# write down a byte breakdown in both dec and hex
|
157
|
+
comment_line = toks.map{|x| x.tr('.', '0').to_i(2).to_s.ljust(11)}.join('')
|
158
|
+
comment_line2 = toks.map{|x| ("0x" + x.tr('.', '0').to_i(2).to_s(16).rjust(2, '0')).ljust(11)}.join('')
|
159
|
+
comment_line3 = toks.map{|x| ("0o" + x.tr('.', '0').to_i(2).to_s(8).rjust(3, '0')).ljust(11)}.join('')
|
160
|
+
|
161
|
+
# generate the line that actually creates the bits
|
162
|
+
command_line = toks.map{|x| "<#{x}>"}.join(' ')
|
163
|
+
|
164
|
+
[
|
165
|
+
"--",
|
166
|
+
" d " + comment_line,
|
167
|
+
" h " + comment_line2,
|
168
|
+
" o " + comment_line3,
|
169
|
+
"; " + command_line
|
170
|
+
].join("\n")
|
171
|
+
end
|
172
|
+
|
173
|
+
def littleendian(bitstr)
|
174
|
+
# flips a bit string around to blocks of 8 little endian bytes
|
175
|
+
# 1. reverse the string
|
176
|
+
# 2. cut it into blocks of 8
|
177
|
+
# 3. reverse each block of 8
|
178
|
+
# 4. et voila
|
179
|
+
bitstr.split('').reverse.each_slice(8).map{|x| x.reverse}.flatten.join('')
|
180
|
+
end
|
181
|
+
|
182
|
+
def endiannize(values)
|
183
|
+
# transforms the values array into big and small endian versions
|
184
|
+
result = {}
|
185
|
+
values.each do |k,v|
|
186
|
+
result[k] = littleendian(v)
|
187
|
+
result[k.upcase] = v
|
188
|
+
end
|
189
|
+
result
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'gasm/op_section'
|
2
|
+
|
3
|
+
module Gasm
|
4
|
+
class InstructionsSection
|
5
|
+
def initialize
|
6
|
+
@ops = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def inst
|
10
|
+
{
|
11
|
+
'instructions' => @ops
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
def instructions(&block)
|
16
|
+
op = OpSection.new(@ops)
|
17
|
+
op.instance_eval(&block)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Gasm
|
2
|
+
class OpSection
|
3
|
+
attr_reader :pattern, :bits
|
4
|
+
|
5
|
+
def initialize(ops)
|
6
|
+
@ops = ops
|
7
|
+
end
|
8
|
+
|
9
|
+
def op(pattern, bits, &block)
|
10
|
+
info = {
|
11
|
+
bits: bits,
|
12
|
+
condition: lambda { |x| true }
|
13
|
+
}
|
14
|
+
info[:condition] = lambda { |x| block.call(x) } if block
|
15
|
+
|
16
|
+
@ops << [pattern, info]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'gasm/instruction_section'
|
2
|
+
|
3
|
+
module Gasm
|
4
|
+
class RubyDesc
|
5
|
+
attr_reader :desc
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@desc = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def asm(&block)
|
12
|
+
insts = InstructionsSection.new
|
13
|
+
insts.instance_eval(&block)
|
14
|
+
@desc['asm'] = insts.inst
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/gasm/version.rb
ADDED
data/lib/gasm.rb
ADDED
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gasm
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- David Siaw
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-07-18 00:00:00.000000000 Z
|
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: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: Generates an assembler based on an assembly description
|
28
|
+
email:
|
29
|
+
- davidsiaw@gmail.com
|
30
|
+
executables:
|
31
|
+
- gasm
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- Gemfile
|
36
|
+
- data/gasm/cpus/6502.gasm.rb
|
37
|
+
- data/gasm/cpus/chip8.gasm.rb
|
38
|
+
- data/gasm/cpus/chip8.gasm.yml
|
39
|
+
- data/gasm/cpus/gameboy.gasm.rb
|
40
|
+
- exe/gasm
|
41
|
+
- gasm.gemspec
|
42
|
+
- lib/gasm.rb
|
43
|
+
- lib/gasm/asm.rb
|
44
|
+
- lib/gasm/gasm_loader.rb
|
45
|
+
- lib/gasm/gasm_matcher.rb
|
46
|
+
- lib/gasm/instruction_section.rb
|
47
|
+
- lib/gasm/op_section.rb
|
48
|
+
- lib/gasm/ruby_desc.rb
|
49
|
+
- lib/gasm/version.rb
|
50
|
+
homepage: https://github.com/davidsiaw/gasm
|
51
|
+
licenses:
|
52
|
+
- MIT
|
53
|
+
metadata:
|
54
|
+
homepage_uri: https://github.com/davidsiaw/gasm
|
55
|
+
source_code_uri: https://github.com/davidsiaw/gasm
|
56
|
+
changelog_uri: https://github.com/davidsiaw/gasm
|
57
|
+
allowed_push_host: https://rubygems.org
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
requirements: []
|
73
|
+
rubygems_version: 3.1.4
|
74
|
+
signing_key:
|
75
|
+
specification_version: 4
|
76
|
+
summary: General ASsembler Maker
|
77
|
+
test_files: []
|