gasm 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
+
7
+ # Specify your gem's dependencies in gasm.gemspec
8
+ gemspec
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gasm
4
+ VERSION = '0.1.0'
5
+ end
data/lib/gasm.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'gasm/gasm_loader'
2
+ require 'gasm/ruby_desc'
3
+ require 'gasm/asm'
4
+ require 'gasm/gasm_matcher'
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: []