Coverage for unicat_esolang/unicat.py: 100%
139 statements
« prev ^ index » next coverage.py v7.3.0, created at 2023-09-09 23:13 +0000
« prev ^ index » next coverage.py v7.3.0, created at 2023-09-09 23:13 +0000
1from typing import List, Iterable, Dict, Optional
2import sys
3import random
4import argparse
5from bdb import BdbQuit
7UNICAT_EMOJIS = {
8 "😸": "0",
9 "😹": "1",
10 "😺": "2",
11 "😻": "3",
12 "😼": "4",
13 "😽": "5",
14 "😾": "6",
15 "😿": "7",
16 "🙀": "8",
17}
18MNEMONICS = {
19 "31": ("asgnlit", 2),
20 "57": ("jumpif>", 2),
21 "54": ("echovar", 1),
22 "44": ("echoval", 1),
23 "46": ("pointer", 1),
24 "83": ("randomb", 1),
25 "24": ("inputst", 1),
26 "78": ("applop", 0),
27 "88": ("diepgrm", 0),
28}
29APPLOPS = {
30 "2": "-",
31 "8": "*",
32 "7": "/",
33}
34DISASSEMBLY = {
35 "asgnlit": (False, True),
36 "jumpif>": (False, False),
37 "echovar": (False,),
38 "echoval": (False,),
39 "pointer": (False,),
40 "randomb": (False,),
41 "inputst": (False,),
42 "applop+": (False, False),
43 "applop-": (False, False),
44 "applop*": (False, False),
45 "applop/": (False, False),
46 "diepgrm": (),
47}
50class Debug:
51 def __init__(self):
52 self.ins: List[tuple] = {}
53 self.mem: Dict[int, int]
55 def show_ins(self):
56 for address, it in enumerate(self.ins):
57 print(f"{decode_value(address)}: {disassemble_instruction(it)}")
59 def show_mem(self, start: Optional[int] = None, end: Optional[int] = None):
60 if start is None:
61 for address, value in sorted(self.mem.items()):
62 _show_value(address, value)
63 elif end is None:
64 _show_value(start, self.mem.get(start, 0))
65 else:
66 for address in range(start, end + 1):
67 if address in self.mem:
68 _show_value(address, self.mem[address])
71def _show_value(address: int, value: int):
72 print(f"{decode_value(address)}: {decode_value(value, show_ascii=True)}")
75DEBUG = Debug()
76show_ins = DEBUG.show_ins
77show_mem = DEBUG.show_mem
80def main(argv=None):
81 parser = argparse.ArgumentParser()
82 parser.add_argument("-d", "--debug", action="store_true", help="Debug")
83 parser.add_argument("filename", help="Name of unicat file to execute")
84 parsed_args = parser.parse_args(argv)
86 instructions = compile_instructions(parsed_args.filename)
87 execute_instructions(instructions, parsed_args.debug)
90def compile_instructions(filename: str) -> List[tuple]:
91 with open(filename, "r", encoding="utf-8") as f:
92 program = f.read()
94 byte_code = convert_to_byte_code(program)
95 return parse_statements(byte_code)
98def convert_to_byte_code(program: str) -> str:
99 return "".join(UNICAT_EMOJIS.get(ch, "") for ch in program)
102def parse_statements(byte_code: str) -> List[tuple]:
103 instructions: List[tuple] = []
104 byte_code_iter: Iterable[str] = iter(byte_code)
105 while instruction := parse_statement(byte_code_iter):
106 instructions.append(instruction)
108 return instructions
111def parse_statement(byte_code_iter: Iterable[str]) -> tuple:
112 try:
113 instruction_code = next(byte_code_iter)
114 except StopIteration:
115 return ()
117 instruction = ("asgnlit", -1, -1)
118 try:
119 instruction_code += next(byte_code_iter)
120 mnemonic, num_numbers = MNEMONICS.get(instruction_code, ("", 0))
121 numbers = tuple(parse_number(byte_code_iter) for _ in range(num_numbers))
122 if not mnemonic:
123 pass
124 elif mnemonic == "applop":
125 opcode = next(byte_code_iter)
126 mnemonic += APPLOPS.get(opcode, "+")
127 instruction = (
128 mnemonic,
129 parse_number(byte_code_iter),
130 parse_number(byte_code_iter),
131 )
132 else:
133 instruction = (mnemonic,) + numbers
134 except StopIteration:
135 pass
137 return instruction
140def parse_number(byte_code_iter: Iterable[str]) -> int:
141 value = ""
142 try:
143 while (digit := next(byte_code_iter)) != "8":
144 value += digit
146 digit = next(byte_code_iter)
147 if digit == "7":
148 value = f"-{value}"
150 if value in ("", "-"):
151 value = "0"
153 return int(value, 8)
154 except StopIteration:
155 return 1337
158def execute_instructions(ins: List[tuple], debug: bool = False):
159 mem: Dict[int, int] = {-1: -1}
160 if debug:
161 print(
162 """\
163Welcome to the Unicat debugger
165Here are the commands:
167- show_ins() - Show instructions
168- show_mem() - Show all memory
169- show_mem(start) - Show memory address <start>
170- show_mem(start, end) - Show memory address <start> to <end>
171- c - Execute next instruction
173Everything else is just a pdb command.
174See https://docs.python.org/3/library/pdb.html for details.
175"""
176 )
178 DEBUG.mem = mem
179 DEBUG.ins = ins
181 while True:
182 try:
183 mem[-1] += 1
184 try:
185 it = ins[mem[-1]]
186 except IndexError:
187 it = ("asgnlit", -1, -1)
189 if debug:
190 print(
191 f"Current instruction:\nAddress {decode_value(mem[-1])}: "
192 + disassemble_instruction(it)
193 )
194 breakpoint() # pylint: disable=forgotten-debug-statement
196 if it[0] == "diepgrm":
197 return
199 if it[0] == "pointer":
200 mem[it[1]] = mem.get(mem.get(it[1], 0), 0)
201 elif it[0] == "randomb":
202 mem[it[1]] = random.choice([0, 1])
203 elif it[0] == "asgnlit":
204 mem[it[1]] = it[2]
205 elif it[0] == "jumpif>" and mem.get(it[1], 0) > 0:
206 mem[-1] = it[2]
207 elif it[0] == "applop+":
208 mem[it[1]] = mem.get(it[1], 0) + mem.get(it[2], 0)
209 elif it[0] == "applop-":
210 mem[it[1]] = mem.get(it[1], 0) - mem.get(it[2], 0)
211 elif it[0] == "applop/":
212 mem[it[1]] = mem.get(it[1], 0) // mem.get(it[2], 0)
213 elif it[0] == "applop*":
214 mem[it[1]] = mem.get(it[1], 0) * mem.get(it[2], 0)
215 elif it[0] == "echovar":
216 sys.stdout.write(chr(mem.get(it[1], 0)))
217 elif it[0] == "echoval":
218 sys.stdout.write(str(mem.get(it[1], 0)))
219 elif it[0] == "inputst":
220 inp = sys.stdin.readline()
221 for k, ch in enumerate(inp, start=it[1]):
222 mem[k] = ord(ch)
224 mem[it[1] + len(inp)] = 0
225 except BdbQuit:
226 return
229def decode_value(value: int, show_ascii=False):
230 value_str = f"{value} ({oct(value)}"
231 if show_ascii:
232 try:
233 value_str += f" = {repr(chr(value))}"
234 except ValueError:
235 pass
237 return f"{value_str})"
240def disassemble_instruction(instruction: tuple):
241 instruction_list = [instruction[0]]
242 instruction_list += [
243 decode_value(operand, show_ascii=show_ascii)
244 for operand, show_ascii in zip(instruction[1:], DISASSEMBLY[instruction[0]])
245 ] or []
246 return ", ".join(instruction_list).replace(",", "", 1)