├── .gitignore ├── LICENSE.txt ├── README.md ├── compilerTest.py ├── ozopyc.py ├── ozopython ├── __init__.py ├── colorLanguageTranslator.py ├── compiler.py └── ozopython.py └── test.ozopy /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Kaarel Maidre 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python-like language for Ozobot 2 | 3 | This is a Python-like language compiler that can be used to program the Ozobot bit robot. 4 | None of this would have been possible without [this](https://github.com/AshleyF/ozobot) 5 | 6 | ## Language description 7 | Below is a description of the given Python-like language which due to using the Python parser is a subset of Python with some added builtin methods to control the Ozobot bit with. 8 | 9 | ### Built in constants 10 | 11 | 8 color constants marking the 8 different colors Ozobot can undestand: 12 | `BLACK`, `WHITE`, `GREEN`, `RED`, `BLUE`, `YELLOW`, `CYAN` and `MAGENTA` 13 | 14 | 4 direction constants used with the `there_is_way(direction)` and `pick_direction(direction)` functions described under line navigation: 15 | `STRAIGHT`, `LEFT`, `RIGHT` and `BACK` 16 | 17 | 3 termination constants used by the `terminate(mode)` function: 18 | `OFF`, `FOLLOW` and `IDLE` 19 | 20 | ### Values, variables and math 21 | 22 | Only integer value in the range -127 to 127 and boolean values `True` and `False` are supported 23 | 24 | Assigning variables is standard e.g `x = 5`. Multiple variable assignment is also possible e.g `x = y = 5` sets x and y to 5. 25 | 26 | 5 arithmetic operations are supprted: `+`, `-`, `*`, `/` and `%` 27 | 28 | There are also 2 mathematical functions: 29 | `random(low, high)` - generate a random value wihtin the given range 30 | `abs(value)` - absolute value of the given parameter 31 | 32 | ### Movement 33 | 34 | There are 3 built in functions to control the movement of the robot outside of line navigation: 35 | 36 | `move(distance, speed)` - move forward by given distance (mm) and speed (mm/s). 37 | `rotate(angle, speed)` - rotate by given angle (degrees) and speed y (mm/s). 38 | `wheels(left, right)` - set the left and right wheel speeds (mm/s). 39 | 40 | Stopping the robot is just `wheels(0, 0)` 41 | 42 | Reading the surface color below the robot is `get_surface_color()` 43 | 44 | ### Line navigation 45 | 46 | There are 4 built in functions to control the robot on line navigation: 47 | 48 | `follow_line_to_intersect_or_end()` - follows a line to intersectsion or line end 49 | `move_straight_until_line(speed)` - move forward at a given speed (mm/s) until a line is found 50 | `there_is_way(direction)` - checks if there is a way(line) in the given direction. 51 | `pick_direction(direction)` - picks a given direction at an intersection. 52 | 53 | It is also possible to set and read the line following speed: 54 | `set_line_speed(speed)` - sets the line following speed to the given value 55 | `get_line_speed()` - gets the current line following speed 56 | 57 | There is also `get_intersect_or_line_end_color()` which is similar to `get_surface_color()`, but is specifically meant for reading the color of the line the robot is navigating. 58 | 59 | ### Top LED 60 | 61 | `color(red, green, blue)` - sets the top led color by the given red, green and blue values. 62 | 63 | Turning the LED off is simply `color(0, 0, 0)` 64 | 65 | ### Timing and termination 66 | 67 | `wait(seconds, centiseconds)` - waits for the given time 68 | `terminate(mode)` - end program excecution on leave leave the robot in one of three modes: off, line following or idle. If this function is not called at the end of the programm a `terminate(OFF)` is implicitly added by the compiler at the end of the program. 69 | 70 | ### Conditional statements and logic 71 | 72 | Standard Python `if` and `else` blocks are supported but the `elif` keyword is not. 73 | Also the standard Python boolean operations `and`, `or` and `not` and comparison operators `<`, `>`, `==`, `<=`, `>=` and `!=` are supported. 74 | 75 | ### Loops 76 | 77 | Only the standard `while` block is supported. 78 | 79 | ### Functions 80 | 81 | It is possible to create functions wihtout parameters and return values with the standard `def` keyword. 82 | Functions do not have their own scope and have to be declared before being called. 83 | 84 | ## Loading programs 85 | 86 | See compilerTest.py or use the Thonny plugin 87 | 88 | ## Thonny plugin 89 | 90 | I also created a thonny plugin which can be found [here](https://bitbucket.org/kaarel94/thonny-ozobot) 91 | -------------------------------------------------------------------------------- /compilerTest.py: -------------------------------------------------------------------------------- 1 | import ozopython 2 | 3 | ozopython.run("test.ozopy") -------------------------------------------------------------------------------- /ozopyc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import sys 4 | from pathlib import Path 5 | import ozopython 6 | 7 | if (len(sys.argv) != 2): 8 | sys.stderr.write("needs 1 argument - source file (without the .ozopy extension)\n") 9 | quit() 10 | 11 | src = sys.argv[1] + ".ozopy" 12 | 13 | if (Path(src).exists()): 14 | ozopython.run(src) 15 | else: 16 | sys.stderr.write("file "+src+" not found\n") 17 | -------------------------------------------------------------------------------- /ozopython/__init__.py: -------------------------------------------------------------------------------- 1 | from tkinter import ttk 2 | 3 | from ozopython.colorLanguageTranslator import ColorLanguageTranslator 4 | from .ozopython import * 5 | from tkinter import * 6 | 7 | def run(filename): 8 | code = ozopython.compile(filename) 9 | colorcode = ColorLanguageTranslator.translate(code) 10 | 11 | def load(prog, prog_bar): 12 | colormap = { 13 | 'K': "#000000", 14 | 'R': "#ff0000", 15 | 'G': "#00ff00", 16 | 'Y': "#ffff00", 17 | 'B': "#0000ff", 18 | 'M': "#ff00ff", 19 | 'C': "#00ffff", 20 | 'W': "#ffffff" 21 | } 22 | 23 | head, *tail = prog 24 | canvas.itemconfig(circle, fill=colormap[head]) 25 | prog = tail 26 | prog_bar["value"] = len(colorcode) - len(prog) 27 | if len(prog) != 0: 28 | canvas.after(50, lambda: load(prog, prog_bar)) 29 | 30 | window = Tk() 31 | 32 | progress = ttk.Progressbar(window, orient="horizontal", length='5c', mode="determinate") 33 | progress["value"] = 0 34 | progress["maximum"] = len(colorcode) 35 | 36 | button = Button(window, text="Load", command=lambda: load(colorcode, progress)) 37 | button.pack(pady=5) 38 | 39 | exit = Button(window, text="Exit", command=lambda: quit()) 40 | exit.pack(side="bottom",pady=5) 41 | 42 | progress.pack() 43 | 44 | canvas = Canvas(window, height='6c', width='6c') 45 | circle = canvas.create_oval('0.5c', '0.5c', '5.5c', '5.5c', fill="white") 46 | 47 | canvas.pack() 48 | 49 | window.mainloop() 50 | 51 | -------------------------------------------------------------------------------- /ozopython/colorLanguageTranslator.py: -------------------------------------------------------------------------------- 1 | class ColorLanguageTranslator: 2 | START = "CRYCYMCRW" 3 | END = "CMW" 4 | 5 | @staticmethod 6 | def base7(num): 7 | if num == 0: 8 | return '0' 9 | 10 | new_num_string = '' 11 | current = num 12 | while current != 0: 13 | remainder = current % 7 14 | remainder_string = str(remainder) 15 | new_num_string = remainder_string + new_num_string 16 | current //= 7 17 | return new_num_string 18 | 19 | # Function for converting a base-7 number(given as a string) to a 3 digit color code: 20 | @staticmethod 21 | def base7_to_color_code(num): 22 | colorDict = { 23 | '0': 'K', 24 | '1': 'R', 25 | '2': 'G', 26 | '3': 'Y', 27 | '4': 'B', 28 | '5': 'M', 29 | '6': 'C', 30 | } 31 | 32 | if len(num) == 1: 33 | num = "00" + num 34 | elif len(num) == 2: 35 | num = "0" + num 36 | 37 | chars = list(num) 38 | 39 | return colorDict[chars[0]] + colorDict[chars[1]] + colorDict[chars[2]] 40 | 41 | @staticmethod 42 | def translate(byte_array): 43 | color_sequence = "".join([ColorLanguageTranslator.base7_to_color_code(ColorLanguageTranslator.base7(x)) for x in byte_array]) 44 | 45 | sequence_with_repetition = ColorLanguageTranslator.START + color_sequence + ColorLanguageTranslator.END 46 | 47 | end_sequence = "" 48 | for i, c in enumerate(sequence_with_repetition): 49 | if i == 0 or sequence_with_repetition[i - 1] != c or end_sequence[i - 1] == 'W': 50 | end_sequence += c 51 | else: 52 | end_sequence += 'W' 53 | 54 | return end_sequence -------------------------------------------------------------------------------- /ozopython/compiler.py: -------------------------------------------------------------------------------- 1 | from ast import * 2 | 3 | builtins = [ 4 | 'color', 5 | 'wait', 6 | 'move', 7 | 'rotate', 8 | 'wheels', 9 | 'random', 10 | 'get_surface_color', 11 | 'terminate', 12 | 'abs', 13 | 'follow_line_to_intersect_or_end', 14 | 'set_line_speed', 15 | 'pick_direction', 16 | 'move_straight_until_line', 17 | 'there_is_way', 18 | 'get_line_speed', 19 | 'get_intersect_or_line_end_color', 20 | ] 21 | 22 | colors = { 23 | 'BLACK': 0, 24 | 'RED': 1, 25 | 'GREEN': 2, 26 | 'YELLOW': 3, 27 | 'BLUE': 4, 28 | 'MAGENTA': 5, 29 | 'CYAN': 6, 30 | 'WHITE': 7, 31 | } 32 | 33 | directions = { 34 | 'STRAIGHT': 1, 35 | 'LEFT': 2, 36 | 'RIGHT': 4, 37 | 'BACK': 8, 38 | } 39 | 40 | terminate = { 41 | 'OFF': 0, 42 | 'FOLLOW': 1, 43 | 'IDLE': 2, 44 | } 45 | 46 | VERSION = [0x01, 0x03] 47 | KILL = [0x00, 0xAE] 48 | 49 | class CompileException(BaseException): 50 | def __init__(self, msg, node = None): 51 | if node is None: 52 | super(CompileException, self).__init__(msg) 53 | else: 54 | super(CompileException, self).__init__("{0}:{1}".format(node.lineno - 1, node.col_offset), msg) 55 | 56 | class Compiler: 57 | def __init__(self): 58 | self.bytecode = [] 59 | self.variable_counter = 0x2a 60 | self.variables = {} 61 | self.functions = {} 62 | self.compiled_functions = {} 63 | 64 | def calc_checksum(self): 65 | result = 0 66 | 67 | for byte in self.bytecode: 68 | result -= byte 69 | if result < 0: 70 | result += 256 71 | 72 | self.bytecode.append(result) 73 | 74 | def get_length_bytes(self): 75 | div = len(self.bytecode) // 256 76 | remainder = (len(self.bytecode)) % 256 77 | first_byte = 3 78 | second_byte = 219 - len(self.bytecode) 79 | 80 | while second_byte < 0: 81 | first_byte -= 1 82 | second_byte += 256 83 | 84 | if first_byte < 0: 85 | raise CompileException('Maximum bytecode length exceeded') 86 | 87 | # return [(219 - len(self.bytecode)) % 256, len(self.bytecode) // 256, (len(self.bytecode)) % 256] 88 | return [first_byte, second_byte, div, remainder] 89 | 90 | def compile(self, root): 91 | self.compile_stmt(root) 92 | 93 | if len(self.bytecode) == 0: 94 | return [] 95 | 96 | if self.bytecode[-1] != 0xae: 97 | self.bytecode.extend(KILL) 98 | 99 | # compile functions 100 | for index, value in enumerate(self.bytecode): 101 | if type(value) == str: 102 | if value in self.compiled_functions.keys(): 103 | jump_index = self.compiled_functions[value] 104 | self.bytecode[index] = 0x90 105 | self.bytecode[index + 1] = jump_index // 256 106 | self.bytecode[index + 2] = jump_index % 256 107 | else: 108 | self.bytecode[index] = 0x90 109 | self.bytecode[index + 1] = len(self.bytecode) // 256 110 | self.bytecode[index + 2] = len(self.bytecode) % 256 111 | 112 | self.compiled_functions[value] = len(self.bytecode) 113 | 114 | for n in self.functions[value]: 115 | self.compile_stmt(n) 116 | 117 | self.push(0x91) 118 | 119 | 120 | 121 | self.bytecode = [0x01] + self.get_length_bytes() + self.bytecode 122 | self.calc_checksum() 123 | 124 | return self.bytecode 125 | 126 | def compile_stmt(self, node): 127 | if type(node) == Module: 128 | for n in node.body: 129 | self.compile_stmt(n) 130 | elif type(node) == Expr: 131 | self.compile_expr(node.value) 132 | elif type(node) == Assign: 133 | self.assign(node.targets, node.value) 134 | elif type(node) == If: 135 | self.if_stmt(node) 136 | elif type(node) == While: 137 | self.while_loop(node) 138 | elif type(node) == FunctionDef: 139 | self.function_def(node) 140 | else: 141 | raise CompileException('Unsupported statement type %s.\n%s' % (str(type(node)), str(vars(node))), node) 142 | 143 | def compile_expr(self, node): 144 | if type(node) == Call: 145 | self.call(node) 146 | elif type(node) == Num: 147 | self.num(node) 148 | elif type(node) == Name: 149 | self.get_var(node) 150 | elif type(node) == NameConstant: 151 | self.name_constant(node) 152 | elif type(node) == BoolOp: 153 | self.bool_op(node) 154 | elif type(node) == Compare: 155 | self.compare(node) 156 | elif type(node) == UnaryOp: 157 | self.unary_op(node) 158 | elif type(node) == BinOp: 159 | self.bin_op(node) 160 | else: 161 | raise CompileException('Unsupported expression type %s.\n%s' % (str(type(node)), str(vars(node))), node) 162 | 163 | def assign(self, targets, value): 164 | for target in targets: 165 | if type(target) != Name: 166 | raise CompileException('Values can only be assigned to variables', target) 167 | 168 | if target.id in colors.keys(): 169 | raise CompileException('Variable name cannot be one of the built in colors', target) 170 | 171 | if target.id in directions.keys(): 172 | raise CompileException('Variable name cannot be one of the built in directions', target) 173 | 174 | if target.id in self.variables: 175 | key = self.variables[target.id] 176 | else: 177 | key = self.variable_counter 178 | self.variables[target.id] = key 179 | self.variable_counter += 1 180 | 181 | self.compile_expr(value) 182 | self.bytecode.extend([key, 0x93]) 183 | 184 | def call(self, node): 185 | if node.func.id in builtins: 186 | getattr(self, node.func.id)(*node.args) 187 | elif node.func.id in self.functions.keys(): 188 | self.push(node.func.id) 189 | self.push(0x00) 190 | self.push(0x00) 191 | else: 192 | raise CompileException("Unknown function call %s" % node.func.id, node) 193 | 194 | def num(self, node): 195 | value = node.n 196 | 197 | if value > 127: 198 | raise CompileException("Value %s outside of valid range" % value, node) 199 | 200 | self.push(value) 201 | 202 | def get_var(self, node): 203 | if node.id in colors.keys(): 204 | self.push(colors[node.id]) 205 | elif node.id in directions.keys(): 206 | self.push(directions[node.id]) 207 | elif node.id in terminate.keys(): 208 | self.push(terminate[node.id]) 209 | else: 210 | if node.id not in self.variables: 211 | raise CompileException('Undefined variable %s.' % node.id, node) 212 | 213 | key = self.variables[node.id] 214 | 215 | self.bytecode.extend([key, 0x92]) 216 | 217 | def if_stmt(self, node): 218 | self.compile_expr(node.test) 219 | self.push(0x80) 220 | self.push(0) 221 | index = len(self.bytecode) - 1 222 | self.push(0x97) 223 | 224 | for n in node.body: 225 | self.compile_stmt(n) 226 | 227 | self.bytecode[index] = len(self.bytecode[index:]) + 1 228 | 229 | if len(node.orelse) > 0: 230 | self.bytecode[index] += 3 231 | self.push(0xba) 232 | self.push(0) 233 | index = len(self.bytecode) - 1 234 | self.push(0x97) 235 | 236 | for n in node.orelse: 237 | self.compile_stmt(n) 238 | 239 | self.bytecode[index] = len(self.bytecode[index:]) + 1 240 | 241 | def name_constant(self, node): 242 | if type(node.value) != bool: 243 | raise CompileException('Only boolean constant type is supported. %s' % type(node.value), node) 244 | self.push(1 if node.value else 0) 245 | 246 | def bool_op(self, node): 247 | self.compile_expr(node.values[0]) 248 | for i in range(1, len(node.values)): 249 | self.compile_expr(node.values[i]) 250 | if type(node.op) == And: 251 | self.push(0xa2) 252 | elif type(node.op) == Or: 253 | self.push(0xa3) 254 | else: 255 | raise CompileException("Unknown operator %s" % type(node.op), node.op) 256 | 257 | def compare(self, node): 258 | self.compile_expr(node.left) 259 | for i in range(len(node.ops)): 260 | self.compile_expr(node.comparators[i]) 261 | self.compare_ops(node.ops[i]) 262 | 263 | def compare_ops(self, op): 264 | if type(op) == Eq: 265 | self.push(0xa4) 266 | elif type(op) == NotEq: 267 | self.push(0xa4) 268 | self.push(0x8a) 269 | elif type(op) == Lt: 270 | self.push(0x9c) 271 | self.push(0x8a) 272 | elif type(op) == LtE: 273 | self.push(0x9d) 274 | self.push(0x8a) 275 | elif type(op) == Gt: 276 | self.push(0x9d) 277 | elif type(op) == GtE: 278 | self.push(0x9c) 279 | else: 280 | raise CompileException('Unsupported operator', op) 281 | 282 | def unary_op(self, node): 283 | if type(node.op) == Not: 284 | self.compile_expr(node.operand) 285 | self.push(0x8a) 286 | elif type(node.op) == USub: 287 | self.compile_expr(node.operand) 288 | # self.bytecode[-1] -= 1 289 | self.push(0x8b) 290 | else: 291 | raise CompileException('Unsupported operator', node.op) 292 | 293 | def bin_op(self, node): 294 | self.compile_expr(node.left) 295 | self.compile_expr(node.right) 296 | if type(node.op) == Add: 297 | self.push(0x85) 298 | elif type(node.op) == Sub: 299 | self.push(0x86) 300 | elif type(node.op) == Mult: 301 | self.push(0x87) 302 | elif type(node.op) == Div: 303 | self.push(0x88) 304 | elif type(node.op) == Mod: 305 | self.push(0x89) 306 | else: 307 | raise CompileException('Unsupported operator', node.op) 308 | 309 | def while_loop(self, node): 310 | # Infinite loop 311 | if type(node.test) == NameConstant and node.test.value: 312 | jump_index = len(self.bytecode) 313 | for n in node.body: 314 | self.compile_stmt(n) 315 | self.push(0xba) 316 | self.push(256 - len(self.bytecode[jump_index:]) + 1) 317 | elif type(node.test) == NameConstant and not node.test.value: 318 | return 319 | else: 320 | jump_back_index = len(self.bytecode) 321 | self.compile_expr(node.test) 322 | self.push(0x80) 323 | self.push(0) 324 | jump_index = len(self.bytecode) - 1 325 | self.push(0x97) 326 | 327 | for n in node.body: 328 | self.compile_stmt(n) 329 | 330 | self.push(0xba) 331 | self.push(256 - len(self.bytecode[jump_back_index:]) + 1) 332 | 333 | self.bytecode[jump_index] = len(self.bytecode[jump_index:]) + 1 334 | 335 | def function_def(self, node): 336 | self.functions[node.name] = node.body 337 | 338 | 339 | def move(self, distance, speed): 340 | self.compile_expr(distance) 341 | self.compile_expr(speed) 342 | self.push(0x9e) 343 | 344 | def wait(self, seconds, centisec): 345 | self.compile_expr(seconds) 346 | 347 | if self.bytecode[-1] == 0: 348 | del self.bytecode[-1] 349 | else: 350 | self.bytecode.extend([0x64, 0x9b, 0x1, 0x86, 0x94, 0x0, 0x9d, 0x8a, 0x80, 0xf8, 0x97, 0x96]) 351 | 352 | self.compile_expr(centisec) 353 | self.push(0x9b) 354 | 355 | def color(self, red, green, blue): 356 | self.compile_expr(red) 357 | self.compile_expr(green) 358 | self.compile_expr(blue) 359 | self.push(0xb8) 360 | 361 | def rotate(self, degree, speed): 362 | self.compile_expr(degree) 363 | self.compile_expr(speed) 364 | self.push(0x98) 365 | 366 | def wheels(self, left, right): 367 | self.compile_expr(left) 368 | self.compile_expr(right) 369 | self.push(0x9f) 370 | 371 | def random(self, low, high): 372 | self.compile_expr(high) 373 | self.compile_expr(low) 374 | self.push(0x8c) 375 | 376 | def get_surface_color(self): 377 | self.push(0x0e) 378 | self.push(0x92) 379 | 380 | def terminate(self, value): 381 | self.compile_expr(value) 382 | self.push(0xae) 383 | 384 | def abs(self, value): 385 | self.compile_expr(value) 386 | self.push(0xa8) 387 | 388 | def follow_line_to_intersect_or_end(self): 389 | self.bytecode.extend([0x01, 0xa0, 0xac, 0xad, 0x9a, 0x10, 0xa4, 0x80, 0xfd, 0x00, 0xa0, 0x01, 0x29, 0x93]) 390 | 391 | def set_line_speed(self, speed): 392 | self.compile_expr(speed) 393 | self.push(0x18) 394 | self.push(0x93) 395 | 396 | def move_straight_until_line(self, speed): 397 | self.compile_expr(speed) 398 | self.bytecode.extend([0x94, 0x94, 0x9f, 0xac, 0x08, 0x92, 0x80, 0xfa, 0x97, 0x96, 0x00, 0x00, 0x9f, 0xc6, 0x01, 0xa0, 0xac, 0xad, 0x9a, 0x10, 0xa4, 0x80, 0xfd, 0x97, 0x00, 0xa0, 0x01, 0x29, 0x93]) 399 | 400 | def pick_direction(self, direction): 401 | if type(direction) != Name and direction.id not in directions.keys(): 402 | raise CompileException('Unsupported direction', direction) 403 | 404 | self.compile_expr(direction) 405 | self.bytecode.extend([0x94, 0x10, 0x92, 0x81, 0x8a, 0xb7, 0x29, 0x92, 0x8a, 0xb7, 0x1f, 0x93, 0x01, 0xa0, 0xad, 0x9a, 0x14, 0xa4, 0x80, 0xfd, 0x00, 0xa0, 0x00, 0x29, 0x93]) 406 | 407 | def there_is_way(self, direction): 408 | if type(direction) != Name and direction.id not in directions.keys(): 409 | raise CompileException('Unsupported direction', direction) 410 | 411 | self.push(0x10) 412 | self.push(0x92) 413 | self.compile_expr(direction) 414 | self.push(0x81) 415 | 416 | def get_line_speed(self): 417 | self.push(0x18) 418 | self.push(0x92) 419 | 420 | def get_intersect_or_line_end_color(self): 421 | self.push(0x0f) 422 | self.push(0x92) 423 | 424 | def push(self, byte): 425 | self.bytecode.append(byte) 426 | -------------------------------------------------------------------------------- /ozopython/ozopython.py: -------------------------------------------------------------------------------- 1 | import ast 2 | from .compiler import Compiler 3 | 4 | def compile(file): 5 | compiler = Compiler() 6 | return compiler.compile(ast.parse("\n".join(open(file).readlines()))) -------------------------------------------------------------------------------- /test.ozopy: -------------------------------------------------------------------------------- 1 | def f(): 2 | if get_surface_color() == BLACK: 3 | color(0, 0, 0) 4 | if get_surface_color() == RED: 5 | color(127, 0, 0) 6 | if get_surface_color() == GREEN: 7 | color(0, 127, 0) 8 | if get_surface_color() == BLUE: 9 | color(0, 0, 127) 10 | if get_surface_color() == WHITE: 11 | color(127, 127, 127) 12 | if get_surface_color() == YELLOW: 13 | color(127, 127, 0) 14 | if get_surface_color() == CYAN: 15 | color(0, 127, 127) 16 | if get_surface_color() == MAGENTA: 17 | color(127, 0, 127) 18 | 19 | while True: 20 | f() 21 | --------------------------------------------------------------------------------