gasm 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/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: []
|