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