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

1from typing import List, Iterable, Dict, Optional 

2import sys 

3import random 

4import argparse 

5from bdb import BdbQuit 

6 

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} 

48 

49 

50class Debug: 

51 def __init__(self): 

52 self.ins: List[tuple] = {} 

53 self.mem: Dict[int, int] 

54 

55 def show_ins(self): 

56 for address, it in enumerate(self.ins): 

57 print(f"{decode_value(address)}: {disassemble_instruction(it)}") 

58 

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]) 

69 

70 

71def _show_value(address: int, value: int): 

72 print(f"{decode_value(address)}: {decode_value(value, show_ascii=True)}") 

73 

74 

75DEBUG = Debug() 

76show_ins = DEBUG.show_ins 

77show_mem = DEBUG.show_mem 

78 

79 

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) 

85 

86 instructions = compile_instructions(parsed_args.filename) 

87 execute_instructions(instructions, parsed_args.debug) 

88 

89 

90def compile_instructions(filename: str) -> List[tuple]: 

91 with open(filename, "r", encoding="utf-8") as f: 

92 program = f.read() 

93 

94 byte_code = convert_to_byte_code(program) 

95 return parse_statements(byte_code) 

96 

97 

98def convert_to_byte_code(program: str) -> str: 

99 return "".join(UNICAT_EMOJIS.get(ch, "") for ch in program) 

100 

101 

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) 

107 

108 return instructions 

109 

110 

111def parse_statement(byte_code_iter: Iterable[str]) -> tuple: 

112 try: 

113 instruction_code = next(byte_code_iter) 

114 except StopIteration: 

115 return () 

116 

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 

136 

137 return instruction 

138 

139 

140def parse_number(byte_code_iter: Iterable[str]) -> int: 

141 value = "" 

142 try: 

143 while (digit := next(byte_code_iter)) != "8": 

144 value += digit 

145 

146 digit = next(byte_code_iter) 

147 if digit == "7": 

148 value = f"-{value}" 

149 

150 if value in ("", "-"): 

151 value = "0" 

152 

153 return int(value, 8) 

154 except StopIteration: 

155 return 1337 

156 

157 

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 

164 

165Here are the commands: 

166 

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 

172 

173Everything else is just a pdb command. 

174See https://docs.python.org/3/library/pdb.html for details. 

175""" 

176 ) 

177 

178 DEBUG.mem = mem 

179 DEBUG.ins = ins 

180 

181 while True: 

182 try: 

183 mem[-1] += 1 

184 try: 

185 it = ins[mem[-1]] 

186 except IndexError: 

187 it = ("asgnlit", -1, -1) 

188 

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 

195 

196 if it[0] == "diepgrm": 

197 return 

198 

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) 

223 

224 mem[it[1] + len(inp)] = 0 

225 except BdbQuit: 

226 return 

227 

228 

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 

236 

237 return f"{value_str})" 

238 

239 

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)