View difference between Paste ID: wS9r2e2R and edAEkfYa
SHOW: | | - or go back to the newest paste.
1
#!/usr/bin/env python3
2
3
"""tptasm.py
4
5
TPTASM is an assembly language for The Powder Toy's "8-bit processor v1.0" (see http://powdertoy.co.uk/Discussions/Thread/View.html?Thread=17358). Assembly languages make low-level programming a lot easier by getting rid of the bit manipulation and replacing it with friendlier words and numbers that actually show what the code does.
6
7
Here is an overview of its syntax:
8
9
* Comments start with semicolons (";") e.g. "; This is a comment"
10
* RAM addresses are represented with square brackets e.g.
11
  "[6]" = RAM SLOT 6.
12
* To set A, B, C, RAM addresses or STDOUT to something
13
  use "SET" e.g. "SET A, [6]" (sets A to the value at RAM
14
  SLOT 7) or "SET [15], 5" (sets RAM SLOT 15 to 5) or "SET
15
  OUT, [54]" (outputs RAM SLOT 54 to STDOUT)
16
* To utilise the ALU use "ADD", "AND" etc. e.g. "ADD [4]"
17
  (adds A + B and puts the result in RAM SLOT 4) or
18
  "OR C" (puts A OR B in C)
19
* To jump to a line use "JMP" e.g. "JMP 3" (jump to ROM SLOT 3)
20
* To conditionally jump to a line use "IFE" e.g. "IFE 5" (if
21
  A doesn't equal B, jump to line 5)
22
* To quit use END by itself on a line.
23
"""
24
25
import re
26
import sys
27
28
# The regex for a TPTASM number
29
number_re = r"(?:[0-9]+|0[xX][0-9a-fA-F]+|0[bB][0-1]+)"
30
31
# The regex for a TPTASM name
32
name_re = r"(?:[A-Za-z_][A-Za-z0-9_]*)"
33
34
# The regex for a line of TPTASM code
35-
    (?P<line>
35+
36-
        (?P<name>SET|ADD|AND|OR|IFE|JMP|END)
36+
37-
        (   (?<=SET)\s+(?P<set1>A|B|C|OUT|\[{number}\])\s*,\s*(?P<set2>A|B|C|\[{number}\]|{number})
37+
38-
          | (?<=ADD)\s+(?P<add1>\[{number}\]|C)
38+
    (?:(?P<label>{name}):)? # label
39-
          | (?<=AND)\s+(?P<and1>\[{number}\]|C)
39+
40-
          | (?<= OR)\s+(?P<or1>\[{number}\]|C)
40+
    (?P<thingy>
41-
          | (?<=IFE)\s+(?P<ife1>{number})
41+
        (?P<line>
42-
          | (?<=JMP)\s+(?P<jmp1>{number})
42+
            (?P<name>SET|ADD|SUB|AND|OR|IFE|IFG|IFL|JMP|CLR|END)
43-
          | (?<=END)
43+
            (   (?<=SET) \s+(?P<set1>A|B|C|OUT|\[{number}\]|\${name}|\[B\])\s*,\s*(?P<set2>A|B|C|\[{number}\]|\${name}|{number}|INP)
44
              | (?<=ADD) \s+(?P<add1>\[{number}\]|\${name}|C)
45
              | (?<=SUB) \s+(?P<sub1>\[{number}\]|\${name}|C)
46
              | (?<=AND) \s+(?P<and1>\[{number}\]|\${name}|C)
47
              | (?<= OR) \s+(?P<or1>\[{number}\]|\${name}|C)
48
              | (?<=IFE) \s+(?P<ife1>{number}|{name})
49-
    """.format(number=number_re), re.X)
49+
              | (?<=IFG) \s+(?P<ifg1>{number}|{name})
50
              | (?<=IFL) \s+(?P<ifl1>{number}|{name})
51
              | (?<=JMP) \s+(?P<jmp1>{number}|{name})
52-
    """Check if a string is a PTASM memory address."""
52+
              | (?<=CLR) \s+(?P<clr1>\[(?:A|B|{number})\]|\${name})
53-
    return s.startswith('[')
53+
              | (?<=END)
54
            )
55
        )
56
    )?
57
    \s*
58
    (?P<comment>;.*)? # comment
59
    $
60
    """.format(number=number_re, name=name_re), re.X)
61
62
def isaddr(s):
63
    """Check if a string is a TPTASM memory address."""
64
    return s.startswith('[') and isnum(s[1:-1])
65
66
def isvar(s):
67
    """Check if a string is a TPTASM variable."""
68
    return s.startswith("$")
69
70
def isnum(s):
71
    """Check if a string is a valid Python integer."""
72
    try:
73
        int(s, 0)
74
    except:
75
        return False
76
    return True
77
78
def num(s):
79
    """Turn a number string or RAM address into a Python integer."""
80
    if s.startswith('['):
81
        return int(s[1:-1], 0)
82
    return int(s, 0)
83
84
def binary(s, width):
85
    """Make a number binary. Also restrict it to a certain width by adding
86
    preceding zeros (when necessary)."""
87
    # YAY double string formatting :)
88
    return ("{:>0%s}" % width).format(bin(num(s))[2:])
89
90-
                out.append("{}0000000000000001".format(binary(set2, 8)))
90+
def getvar(name, varmap):
91
    """Get a variable's address from a varmap.
92-
                out.append("{}0000000000000010".format(binary(set2, 8)))
92+
    If the variable does not exist, create it and give it an address."""
93
    if name not in varmap:
94-
                out.append("{}0000000000000011".format(binary(set2, 8)))
94+
        if len(varmap) == 0:
95
            n = 1
96-
                out.append("{}0{}0000100".format(binary(set1, 7), binary(set2, 8)))
96+
97
            n = sorted(varmap.values())[-1] + 1
98-
                out.append("111111110{}00000101".format(binary(set2, 7)))
98+
        varmap[name] = n
99
        #print("Added var {} [{}]".format(name, n))
100-
                out.append("111111110{}00000110".format(binary(set2, 7)))
100+
        if n > 100:
101
            print("WARNING: RAM all used up!")
102-
                out.append("111111110{}00000111".format(binary(set2, 7)))
102+
    return "[" + str(varmap[name]) + "]"
103
104-
                out.append("000000000000000000001000")
104+
105
    """Parse multiple lines of code and output the compiled program in
106-
                out.append("000000000000000000001001")
106+
107
    lines = text.upper().split('\n')
108-
                out.append("000000000{}00001010".format(binary(set1, 7)))
108+
109
    labelmap = {}
110-
                out.append("111111110{}00001101".format(binary(set2, 7)))
110+
    varmap = {}
111
    instrno = -1
112-
                raise Exception("unsupported SET operation")
112+
113
        line2 = line_re.match(line)
114
        if line2 is None:
115-
                out.append("000000000000000000011011")
115+
116
        if line2.groupdict()['label'] is not None:
117
            labelmap[line2.groupdict()['label']] = instrno + 2
118-
                out.append("000000000{:>07}00011100".format(number))
118+
        if line2.groupdict()['line'] is None:
119
            continue
120
        instrno += 1
121-
                out.append("000000000000000000101011")
121+
    instrno = -1
122
    for lineno, line in enumerate(lines):
123
        line2 = line_re.match(line)
124-
                out.append("000000000{:>07}00101100".format(number))
124+
125
        if gd['line'] is None:
126
            continue
127-
                out.append("000000000000000000111011")
127+
        instrno += 1
128
        if gd['name'] == 'SET':
129
            set1 = gd['set1']
130-
                out.append("000000000{:>07}00111100".format(number))
130+
131
            if isvar(set1):
132-
            number = bin(int(gd['ife1'], 0))[2:]  # convert to binary
132+
                set1 = getvar(set1, varmap)
133-
            out.append("000000000{:>07}01000000".format(number))
133+
            if isvar(set2):
134
                set2 = getvar(set2, varmap)
135-
            number = bin(int(gd['jmp1'], 0))[2:]  # convert to binary
135+
136-
            out.append("000000000{:>07}01010000".format(number))
136+
                out.append("{}00000000000000001".format(binary(set2, 8)))
137
            elif set1 == 'B' and isnum(set2):
138-
            out.append("000000001000000000000000")
138+
                out.append("{}00000000000000010".format(binary(set2, 8)))
139
            elif set1 == 'C' and isnum(set2):
140
                out.append("{}00000000000000011".format(binary(set2, 8)))
141
            elif isaddr(set1) and isnum(set2):
142
                out.append("{}0{}000000100".format(binary(set2, 8), binary(set1, 7)))
143
            elif set1 == 'A' and isaddr(set2):
144
                out.append("111111110{}000000101".format(binary(set2, 7)))
145
            elif set1 == 'B' and isaddr(set2):
146-
    print(
146+
                out.append("111111110{}000000110".format(binary(set2, 7)))
147
            elif set1 == 'C' and isaddr(set2):
148
                out.append("111111110{}000000111".format(binary(set2, 7)))
149
            elif set1 == 'A' and set2 == 'C':
150
                out.append("0000000000000000000001000")
151
            elif set1 == 'B' and set2 == 'C':
152
                out.append("0000000000000000000001001")
153
            elif isaddr(set1) and set2 == 'C':
154
                out.append("000000000{}000001010".format(binary(set1, 7)))
155
            elif set1 == 'OUT' and isaddr(set2):
156
                out.append("111111110{}000001101".format(binary(set2, 7)))
157
            elif set1 == '[B]' and set2 == 'A':
158
                out.append("0000000000000000000001110")
159
            elif isaddr(set1) and set2 == "INP":
160
                out.append("000000000{}000010010".format(binary(set1, 7)))
161
            else:
162
                raise Exception("unsupported SET operation on line {}".format(lineno + 1))
163
        elif gd['name'] == 'ADD':
164
            if isvar(gd['add1']):
165
                gd['add1'] = getvar(gd['add1'], varmap)
166-
    print("Enter code (press {} to finish):".format(("Ctrl-D", "Ctrl-Z")[sys.platform == "win32"]))
166+
167-
    program = sys.stdin.read()
167+
                out.append("0000000000000000000101011")
168
            else:  # RAM value
169-
    print("Here is your program! Enjoy!")
169+
170
                out.append("000000000{:>07}000101100".format(number))
171-
        print(i)
171+
172-
    sys.stdin.readline()
172+
            if isvar(gd['and1']):
173
                gd['and1'] = getvar(gd['and1'], varmap)
174
            if gd['and1'] == 'C':  # register C
175
                out.append("0000000000000000001001011")
176
            else:  # RAM value
177
                number = bin(int(gd['and1'][1:-1], 0))[2:]  # convert to binary
178
                out.append("000000000{:>07}001001100".format(number))
179
        elif gd['name'] == 'OR':
180
            if isvar(gd['or1']):
181
                gd['or1'] = getvar(gd['or1'], varmap)
182
            if gd['or1'] == 'C':  # register C
183
                out.append("0000000000000000001101011")
184
            else:  # RAM value
185
                number = bin(int(gd['or1'][1:-1], 0))[2:]  # convert to binary
186
                out.append("000000000{:>07}001101100".format(number))
187
        elif gd['name'] == 'IFE':
188
            if isnum(gd['ife1']):  # line number
189
                number = bin(int(gd['ife1'], 0))[2:]  # convert to binary
190
            else:  # label
191
                number = bin(labelmap[gd['ife1']])[2:]
192
            out.append("000000000{:>07}010001011".format(number))
193
        elif gd['name'] == 'JMP':
194
            if isnum(gd['jmp1']):  # line number
195
                number = bin(int(gd['jmp1'], 0))[2:]  # convert to binary
196
            else:  # label
197
                number = bin(labelmap[gd['jmp1']])[2:]
198
            out.append("000000000{:>07}010101011".format(number))
199
        elif gd['name'] == 'IFG':
200
            if isnum(gd['ifg1']):  # line number
201
                number = bin(int(gd['ifg1'], 0))[2:]  # convert to binary
202
            else:  # label
203
                number = bin(labelmap[gd['ifg1']])[2:]
204
            out.append("000000000{:>07}011001011".format(number))  # made up
205
        elif gd['name'] == 'IFL':
206
            if isnum(gd['ifl1']):  # line number
207
                number = bin(int(gd['ifl1'], 0))[2:]  # convert to binary
208
            else:  # label
209
                number = bin(labelmap[gd['ifl1']])[2:]
210
            out.append("000000000{:>07}011101011".format(number))  # made up
211
        elif gd['name'] == 'SUB':
212
            if isvar(gd['sub1']):
213
                gd['sub1'] = getvar(gd['sub1'], varmap)
214
            if gd['add1'] == 'C':  # register C
215
                out.append("0000000000000000100001011")  # made up
216
            else:  # RAM value
217
                number = bin(int(gd['sub1'][1:-1], 0))[2:]  # convert to binary
218
                out.append("000000000{:>07}100001100".format(number))  # made up
219
        elif gd['name'] == 'CLR':
220
            if isvar(gd['clr1']):
221
                gd['clr1'] = getvar(gd['clr1'], varmap)
222
            if gd['clr1'] == '[A]':
223
                out.append("0000000000000000000010000")
224
            elif gd['clr1'] == '[B]':
225
                out.append("0000000000000000000010001")
226
            else:  # RAM address
227
                out.append("000000000{}000001111".format(binary(gd['clr1'], 7)))
228
        elif gd['name'] == 'END':
229
            out.append("0000000010000000000000000")
230
        else:
231
            raise Exception("something strange happened! Please report this bug!")
232
233
    if not all([len(i) == 25 for i in out]):
234
        print(out)
235
        for i, v in enumerate(out):
236
            if len(v) != 25:
237
                print(i, v)
238
        raise Exception("uh-oh, something bad happened! Please report this bug!")
239
240
    return out
241
242
def print_(s):
243
    print("[{}][{}][{}][{}][{}]".format(s[:8], s[8:9], s[9:16], s[16:20], s[20:]))
244
245
def main(argv):
246
    f = sys.stdin
247
    if len(argv) > 1:
248
        f = open(argv[1])
249
    if f == sys.stdin:
250
        print(
251
"""TPTASM is an assembly language for The Powder Toy's "8-bit processor v1.0" (see http://powdertoy.co.uk/Discussions/Thread/View.html?Thread=17358). Assembly languages make low-level programming a lot easier by getting rid of the bit manipulation and replacing it with friendlier words and numbers that actually show what the code does.
252
253
Here is an overview of its syntax:
254
255
* Comments start with semicolons (";") e.g. "; This is a comment"
256
* RAM addresses are represented with square brackets e.g.
257
  "[6]" = RAM SLOT 6.
258
* To set A, B, C, RAM addresses or STDOUT to something
259
  use "SET" e.g. "SET A, [6]" (sets A to the value at RAM
260
  SLOT 7) or "SET [15], 5" (sets RAM SLOT 15 to 5) or "SET
261
  OUT, [54]" (outputs RAM SLOT 54 to STDOUT)
262
* To utilise the ALU use "ADD", "AND" etc. e.g. "ADD [4]"
263
  (adds A + B and puts the result in RAM SLOT 4) or
264
  "OR C" (puts A OR B in C)
265
* To make a label use colons e.g. "mainloop:"
266
* To jump to a line use "JMP" e.g. "JMP 3" (jump to ROM SLOT 3)
267
  or use labels e.g. "JMP mainloop" (jump to the label "mainloop")
268
* To conditionally jump to a line use "IFE" e.g. "IFE 5" (if
269
  A doesn't equal B, jump to line 5)
270
* To quit use END by itself on a line.
271
""")
272
        print("Enter code (press {} to finish):".format(("Ctrl-D", "Ctrl-Z")[sys.platform == "win32"]))
273
    program = f.read()
274
    result = parse(program)
275
    if f == sys.stdin:
276
        print("Here is your program! Enjoy!")
277
    for i in result:
278
        yield i
279
    if len(result) > 100:
280
        print("WARNING: program too long! ({})".format(len(result)))
281
    if f == sys.stdin:
282
        sys.stdin.readline()
283
284
if __name__ == "__main__":
285
    for i in main(sys.argv):
286
        print_(i)