├── .gitignore ├── README.md ├── examples ├── array.uc ├── dev.uc ├── ptest.uc ├── screen.uc ├── string.uc └── test.uc └── src ├── compError.py ├── parser.py ├── pyuxncle ├── thinlib.py ├── tokenizer.py └── type.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .vscode 3 | *.rom 4 | *.tal 5 | .gitlog 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pyuxncle 2 | 3 | Pyuxncle is a single-pass compiler for a small subset of C (albeit without the std library). This compiler targets Uxntal, the assembly language of the Uxn virtual computer. The output Uxntal is not meant to be human readable, only to be directly passed to uxnasm. 4 | 5 | ## Usage 6 | 7 | To compile a source file, pass it like so: 8 | 9 | ```sh 10 | ./src/pyuxncle [SRC_FILE] [OUT_UXNTAL_FILE] 11 | ``` 12 | 13 | ## Examples 14 | 15 | 16 | ```c 17 | device Console[0x18] { 18 | char write; 19 | }; 20 | 21 | Console.write = 'H'; 22 | Console.write = 'i'; 23 | Console.write = '!'; 24 | Console.write = 0x0A; 25 | 26 | ``` 27 | > Hi! -------------------------------------------------------------------------------- /examples/array.uc: -------------------------------------------------------------------------------- 1 | device Console[0x10] { 2 | char pad[8]; /* these first few bytes contain a vector and other reserved bytes that we aren't using :P */ 3 | char write; 4 | }; 5 | 6 | void printInt(int x) { 7 | char rem; /* rem is a byte since the remainder shouldn't ever be above 10 */ 8 | char len = 0; 9 | char str[5]; 10 | 11 | if (x == 0) { 12 | str[0] = '0'; 13 | len = 1; 14 | } 15 | 16 | while (x > 0) { 17 | rem = x % 10; 18 | str[len] = '0' + rem; 19 | x = x / 10; 20 | len = len + 1; 21 | } 22 | 23 | while (len > 0) { 24 | len = len - 1; 25 | Console.write = str[len]; 26 | } 27 | 28 | /* write newline character */ 29 | Console.write = 0x0A; 30 | } 31 | 32 | int arr[10]; 33 | int i = 0; 34 | 35 | while (i < 10) { 36 | arr[i] = i; 37 | i = i + 1; 38 | } 39 | 40 | while (i > 0) { 41 | i = i - 1; 42 | printInt(arr[i]); 43 | } -------------------------------------------------------------------------------- /examples/dev.uc: -------------------------------------------------------------------------------- 1 | device Console[0x10] { 2 | char pad[9]; 3 | }; 4 | 5 | void printInt(int x) { 6 | char rem; /* rem is a byte since the remainder shouldn't ever be above 10 */ 7 | char len = 0; 8 | char str[5]; 9 | 10 | if (x == 0) { 11 | str[0] = '0'; 12 | len = 1; 13 | } 14 | 15 | while (x > 0) { 16 | rem = x % 10; 17 | str[len] = '0' + rem; 18 | x = x / 10; 19 | len = len + 1; 20 | } 21 | 22 | while (len > 0) { 23 | len = len - 1; 24 | Console.pad[8] = str[len]; 25 | } 26 | 27 | /* write newline character */ 28 | Console.pad[8] = 0x0A; 29 | } 30 | 31 | printInt(2 * 4 + 3); // should print 11? 32 | -------------------------------------------------------------------------------- /examples/ptest.uc: -------------------------------------------------------------------------------- 1 | device Console[0x10] { 2 | char pad[8]; /* these first few bytes contain a vector and other reserved bytes that we aren't using :P */ 3 | char write; 4 | }; 5 | 6 | int i; 7 | int a = 2; 8 | 9 | void print(int x) { 10 | Console.write = '0' + x; 11 | Console.write = 0x0A; 12 | } 13 | 14 | print(((int*)(&i))[1]); -------------------------------------------------------------------------------- /examples/screen.uc: -------------------------------------------------------------------------------- 1 | device System[0x08] { 2 | int r; 3 | int g; 4 | int b; 5 | }; 6 | 7 | device Screen[0x20] { 8 | void *vec; 9 | int width; 10 | int height; 11 | int padd; /* unused */ 12 | int x; 13 | int y; 14 | char *addr; /* address to pull sprite data from */ 15 | char pixel; 16 | char sprite; 17 | }; 18 | 19 | int x = 0x008; 20 | int y = 0x008; 21 | 22 | System.r = 0x2ce9; 23 | System.g = 0x01c0; 24 | System.b = 0x2ce5; 25 | Screen.y = y; 26 | 27 | /* draw a line across the top of the screen */ 28 | while (x < Screen.width - 8) { 29 | Screen.x = x; 30 | Screen.pixel = 0x41; 31 | x = x + 1; 32 | } 33 | 34 | /* draw a line down the right side of the screen */ 35 | while (y < Screen.height - 8) { 36 | Screen.y = y; 37 | Screen.pixel = 0x41; 38 | y = y + 1; 39 | } 40 | 41 | /* draw a line across the bottom of the screen */ 42 | while (x > 8) { 43 | Screen.x = x; 44 | Screen.pixel = 0x41; 45 | x = x - 1; 46 | } 47 | 48 | /* draw a line across the left side of the screen */ 49 | while (y > 8) { 50 | Screen.y = y; 51 | Screen.pixel = 0x41; 52 | y = y - 1; 53 | } -------------------------------------------------------------------------------- /examples/string.uc: -------------------------------------------------------------------------------- 1 | device Console[0x10] { 2 | char pad[8]; /* these first few bytes contain a vector and other reserved bytes that we aren't using :P */ 3 | char write; 4 | }; 5 | 6 | char *str = "Hello world!"; 7 | char i = 0; 8 | 9 | while (i < 12) { 10 | Console.write = str[i]; 11 | i = i + 1; 12 | } -------------------------------------------------------------------------------- /examples/test.uc: -------------------------------------------------------------------------------- 1 | device Console[0x10] { 2 | char pad[8]; /* these first few bytes contain a vector and other reserved bytes that we aren't using :P */ 3 | char write; 4 | }; 5 | 6 | void printInt(int x) { 7 | char rem; /* rem is a byte since the remainder shouldn't ever be above 10 */ 8 | char len = 0; 9 | char str[5]; 10 | 11 | if (x == 0) { 12 | str[0] = '0'; 13 | len = 1; 14 | } 15 | 16 | while (x > 0) { 17 | rem = x % 10; 18 | str[len] = '0' + rem; 19 | x = x / 10; 20 | len = len + 1; 21 | } 22 | 23 | while (len > 0) { 24 | len = len - 1; 25 | Console.write = str[len]; 26 | } 27 | 28 | /* write newline character */ 29 | Console.write = 0x0A; 30 | } 31 | 32 | /* basic factorial example using recursion :D */ 33 | int fact(int i) { 34 | if (i > 1) 35 | return i * fact(i-1); 36 | 37 | return 1; 38 | } 39 | 40 | int total = 0; 41 | int x = 10; 42 | 43 | while (x >= 1) { 44 | total = total + x; 45 | x = x - 1; 46 | } 47 | 48 | printInt(total); 49 | printInt(fact(8)); -------------------------------------------------------------------------------- /src/compError.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | 3 | def doCompError(err: str): 4 | #for line in traceback.format_stack(): 5 | # print(line.strip()) 6 | print(err) 7 | exit(1) -------------------------------------------------------------------------------- /src/parser.py: -------------------------------------------------------------------------------- 1 | from enum import IntEnum, auto 2 | from collections.abc import Callable 3 | import string 4 | from tokenizer import Lexer, TOKENTYPE, Token 5 | from type import * 6 | from compError import doCompError 7 | import thinlib 8 | 9 | class PRECTYPE(IntEnum): 10 | NONE = 0, 11 | ASSIGNMENT = 1, # = 12 | INDEX = 2, # [] 13 | COMPAR = 3, # == != 14 | TERM = 4, # + - 15 | FACTOR = 5, # * / 16 | CALL = 6, # ( ) 17 | PRIMARY = 7 18 | 19 | class _PrecRule: 20 | def __init__(self, prec: PRECTYPE, prefix: Callable[[DataType, bool, bool, PRECTYPE], DataType], 21 | infix: Callable[[DataType, bool, bool, PRECTYPE], DataType]): 22 | self.prec = prec 23 | self.prefix = prefix 24 | self.infix = infix 25 | 26 | class _Constant: 27 | def __init__(self, type: DataType, data: str): 28 | self.type = type 29 | self.data = data 30 | 31 | class _Scope: 32 | def __init__(self): 33 | # array of declared variables (and their respective locations in the stack) 34 | self.vars: list[Variable] = [] 35 | self.allocated = 0 36 | self.instrs = '' 37 | 38 | def addVar(self, var: Variable): 39 | self.vars.append(var) 40 | 41 | def getSize(self): 42 | totalSz = 0 43 | for var in self.vars: 44 | totalSz += var.dtype.getSize() 45 | 46 | return totalSz 47 | 48 | class Parser: 49 | def __init__(self, source: str, outFile: str): 50 | self.lexer = Lexer(source) 51 | self.current: Token = None 52 | self.previous: Token = None 53 | self.out = open(outFile, "w") 54 | self.scopeStack: list[_Scope] = [] 55 | self.pushed = 0 56 | self.jmpIDS = 0 57 | self.parseTable = { 58 | # TOKEN : _PrecRule(Precedence, prefix, infix) 59 | TOKENTYPE.SEMICOLON: _PrecRule(PRECTYPE.NONE, None, None), 60 | TOKENTYPE.LBRACE: _PrecRule(PRECTYPE.NONE, None, None), 61 | TOKENTYPE.RBRACE: _PrecRule(PRECTYPE.NONE, None, None), 62 | TOKENTYPE.LBRACKET: _PrecRule(PRECTYPE.INDEX, None, self.__index), 63 | TOKENTYPE.RBRACKET: _PrecRule(PRECTYPE.NONE, None, None), 64 | TOKENTYPE.PLUS: _PrecRule(PRECTYPE.TERM, None, self.__binOp), 65 | TOKENTYPE.MINUS: _PrecRule(PRECTYPE.TERM, None, self.__binOp), 66 | TOKENTYPE.STAR: _PrecRule(PRECTYPE.FACTOR, self.__pointer, self.__binOp), 67 | TOKENTYPE.SLASH: _PrecRule(PRECTYPE.FACTOR, None, self.__binOp), 68 | TOKENTYPE.MOD: _PrecRule(PRECTYPE.FACTOR, None, self.__binOp), 69 | TOKENTYPE.AMPER: _PrecRule(PRECTYPE.NONE, self.__ampersand, None), 70 | TOKENTYPE.EQUAL: _PrecRule(PRECTYPE.NONE, None, None), 71 | TOKENTYPE.EQUALEQUAL: _PrecRule(PRECTYPE.COMPAR, None, self.__binOp), 72 | TOKENTYPE.GRTR: _PrecRule(PRECTYPE.COMPAR, None, self.__binOp), 73 | TOKENTYPE.LESS: _PrecRule(PRECTYPE.COMPAR, None, self.__binOp), 74 | TOKENTYPE.GRTREQL: _PrecRule(PRECTYPE.COMPAR, None, self.__binOp), 75 | TOKENTYPE.LESSEQL: _PrecRule(PRECTYPE.COMPAR, None, self.__binOp), 76 | 77 | TOKENTYPE.LPAREN: _PrecRule(PRECTYPE.CALL, self.__group, self.__callSub), 78 | TOKENTYPE.RPAREN: _PrecRule(PRECTYPE.NONE, None, None), 79 | 80 | TOKENTYPE.IDENT: _PrecRule(PRECTYPE.NONE, self.__ident, None), 81 | TOKENTYPE.NUM: _PrecRule(PRECTYPE.NONE, self.__number, None), 82 | TOKENTYPE.CHARLIT: _PrecRule(PRECTYPE.NONE, self.__charlit, None), 83 | TOKENTYPE.STRINGLIT: _PrecRule(PRECTYPE.NONE, self.__stringlit, None), 84 | TOKENTYPE.EOF: _PrecRule(PRECTYPE.NONE, None, None) 85 | } 86 | 87 | self.typeTable = { 88 | TOKENTYPE.INT : IntDataType(), 89 | TOKENTYPE.BOOL : BoolDataType(), 90 | TOKENTYPE.CHAR : CharDataType(), 91 | TOKENTYPE.VOID : VoidDataType() 92 | } 93 | 94 | # compiler-related stuff (in the rewrite, this should be given to another module that takes care of building the output uxntal) 95 | self.lefthand: list[str] = [] 96 | self.entryInstr = "" 97 | self.currentSub: int = -1 98 | self.subs: list[Variable] = [] 99 | self.globals: list[Variable] = [] 100 | self.devices: list[Device] = [] 101 | self.constants: list[_Constant] = [] # holds data like strings, constant arrays, etc. [TODO] 102 | 103 | def __errorAt(self, tkn: Token, err: str): 104 | doCompError("At '%s' on line %d:\n\t%s" % (tkn.word, tkn.line, err)) 105 | 106 | def __error(self, err: str): 107 | self.__errorAt(self.previous, err) 108 | 109 | # advances to the next token received by the lexer 110 | def __advance(self): 111 | self.previous = self.current 112 | self.current = self.lexer.scanNext() 113 | return self.current 114 | 115 | def __check(self, tknType: TOKENTYPE) -> bool: 116 | return self.current.type == tknType 117 | 118 | def __match(self, tknType: TOKENTYPE) -> bool: 119 | if not self.__check(tknType): 120 | return False 121 | 122 | self.__advance() 123 | return True 124 | 125 | # creates a compiler error if the expected token isn't there, else it just consumes the token 126 | def __consume(self, tknType: TOKENTYPE, errMsg: str): 127 | if not self.__match(tknType): 128 | self.__error(errMsg) 129 | 130 | # returns true if the parser reached the end of the token list 131 | def __isEnd(self) -> bool: 132 | return self.__check(TOKENTYPE.EOF) 133 | 134 | def __addConstant(self, const: _Constant) -> int: 135 | self.constants.append(const) 136 | return len(self.constants)-1 137 | 138 | def __pushLeftHand(self): 139 | self.lefthand.append("") 140 | 141 | def __popLeftHand(self) -> str: 142 | return self.lefthand.pop() 143 | 144 | # expects LBRACKET to already be consumed 145 | def __consumeArrayType(self, dtype: DataType) -> DataType: 146 | # NOTE: VLAs are NOT supported, i cba to write support for that lol. 147 | # (it's technically an extension anyways, besides this is a 'c-like' language) 148 | 149 | # ALSO NOTE: pyuxncle has no support for compiler time constexpr combining. (aka. 2 * 4 would be optimized at compile-time to 8.) 150 | # so anything in between the '[]' should just be 1 token long, and MUST BE an int. 151 | 152 | self.__consume(TOKENTYPE.NUM, "Expected size of array!") 153 | sz = self.__grabNumber(self.previous) 154 | self.__consume(TOKENTYPE.RBRACKET, "Expected end of array definition! (missing ']')") 155 | return DataArray(dtype, sz) 156 | 157 | def __checkDataType(self) -> DataType: 158 | # current token isn't a datatype 159 | if not self.current.type in self.typeTable: 160 | return None 161 | 162 | dtype = self.typeTable[self.current.type] 163 | 164 | self.__advance() 165 | if self.__match(TOKENTYPE.STAR): # is it a pointer? 166 | dtype = Pointer(dtype) 167 | 168 | return dtype 169 | 170 | # forces a datatype, if not it errors 171 | def __matchDataType(self) -> DataType: 172 | dtype = self.__checkDataType() 173 | if dtype == None: 174 | self.__errorAt(self.current, "Expected a datatype!") 175 | return dtype 176 | 177 | # =============== formatting for uxntal output =============== 178 | 179 | def __writeOut(self, buf: str): 180 | if len(self.lefthand) > 0: # we're parsing an expression that needs to be emitted after the righthand is emitted 181 | self.lefthand[len(self.lefthand)-1] += buf 182 | elif self.currentSub == -1: # we're not currently parsing a function, write it to the entry instructions 183 | self.entryInstr += buf 184 | else: 185 | self.subs[self.currentSub].dtype.addUnxtal(buf) 186 | 187 | def __writeIntLiteral(self, num: int): 188 | self.__writeOut("#%.4x " % num) 189 | 190 | def __writeByteLiteral(self, num: int): 191 | self.__writeOut("#%.2x " % num) 192 | 193 | # dtype: the datatype of the 2 values on the stack, rtype: the datatype pushed by the instruction 194 | def __writeBinaryOp(self, op: str, dtype: DataType, rtype: DataType): 195 | if dtype.type == DTYPES.INT: 196 | self.__writeOut("%s2\n" % op) 197 | elif dtype.type == DTYPES.CHAR: 198 | self.__writeOut("%s\n" % op) 199 | else: 200 | self.__error("Cannot perform binary operation on type '%s'" % dtype.name) 201 | 202 | # we popped both values 203 | self.pushed -= dtype.getSize() * 2 204 | # we pushed the rtype 205 | self.pushed += rtype.getSize() 206 | 207 | # creates new jump ID, does NOT write the label 208 | def __newJmpLbl(self) -> int: 209 | self.jmpIDS += 1 210 | return self.jmpIDS 211 | 212 | def __declareLbl(self, id: int): 213 | self.__writeOut("&lbl%d\n" % id) 214 | 215 | # expects BOOL already on the stack 216 | def __jmpCondLbl(self, id: int): 217 | self.__writeOut(",&lbl%d JCN\n" % id) 218 | self.pushed -= 1 219 | 220 | # jumps to label 221 | def __jmpLbl(self, id: int): 222 | self.__writeOut(",&lbl%d JMP\n" % id) 223 | 224 | # ===================== scope management ===================== 225 | 226 | # TODO since this a single-pass compiler, it's kind of hard to get the whole scope size before writting the allocation. 227 | # maybe hold the index of the intliteral to patch int __popScope? the returned VarInfo is valid until __addScopeVar 228 | # or __popScope is called again 229 | def __addScopeVar(self, var: Variable) -> VarInfo: 230 | # if we're not parsing a function, define the variable as a global 231 | if self.currentSub == -1: 232 | self.globals.append(var) 233 | return VarInfo(var, -1) 234 | 235 | self.scopeStack[-1].addVar(var) # add the variable to the current scope 236 | self.__writeIntLiteral(var.dtype.getSize()) 237 | self.__writeOut(";alloc-uxncle JSR2\n") 238 | return VarInfo(var, var.dtype.getSize()) 239 | 240 | def __newScope(self): 241 | self.scopeStack.append(_Scope()) 242 | 243 | # pop scopes without actually removing anything from the scope stack 244 | def __popRawScopes(self, scopes: int): 245 | sz = 0 246 | for i in range(scopes): 247 | scope = self.scopeStack[(len(self.scopeStack)-1) - i] 248 | sz += scope.getSize() 249 | 250 | if sz != 0: # minor optimization 251 | self.__writeIntLiteral(sz) 252 | self.__writeOut(";dealloc-uxncle JSR2\n") 253 | 254 | # pop the stack and deallocate all the variables in scope from the heap 255 | def __popScope(self): 256 | self.__popRawScopes(1) 257 | self.scopeStack.pop() 258 | 259 | # pops size from the uxn stack 260 | def __pop(self, size: int): 261 | self.pushed -= size 262 | 263 | # while we can, pop 2 bytes off the stack at a time 264 | while size > 1: 265 | size -= 2 266 | self.__writeOut("POP2\n") 267 | 268 | # if we still have a leftover byte, pop that too 269 | if size == 1: 270 | self.__writeOut("POP\n") 271 | 272 | # duplicates the value currently on the stack 273 | def __dupVal(self, dtype: DataType): 274 | if dtype.getSize() == 2: 275 | self.__writeOut("DUP2\n") 276 | elif dtype.getSize() == 1: 277 | self.__writeOut("DUP\n") 278 | else: 279 | self.__error("Can't dup values greater than 2!") 280 | 281 | self.pushed += dtype.getSize() 282 | 283 | def __setDevice(self, dev: Device, indx: int, type: DataType): 284 | self.__writeOut(".%s " % dev.devname) 285 | 286 | if indx > 0: 287 | self.__writeByteLiteral(indx) 288 | self.__writeOut("ADD ") 289 | 290 | if type.getSize() == 1: 291 | self.__writeOut("DEO\n") 292 | elif type.getSize() == 2: 293 | self.__writeOut("DEO2\n") 294 | else: 295 | self.__error("Can't set value of device > 2 bytes!") 296 | 297 | self.pushed -= type.getSize() 298 | 299 | def __getDevice(self, dev: Device, indx: int, type: DataType): 300 | self.__writeOut(".%s " % dev.devname) 301 | 302 | if indx > 0: 303 | self.__writeByteLiteral(indx) 304 | self.__writeOut("ADD ") 305 | 306 | if type.getSize() == 1: 307 | self.__writeOut("DEI\n") 308 | elif type.getSize() == 2: 309 | self.__writeOut("DEI2\n") 310 | else: 311 | self.__error("Can't get value of device > 2 bytes!") 312 | 313 | self.pushed += type.getSize() 314 | 315 | # searches for the variable in the scope stack, returns the index needed to be passed to ;poke-uxncle-* if var isn't found None is returned 316 | def __findVar(self, name: str) -> VarInfo: 317 | indx = 0 318 | 319 | # walk each scope (from top) 320 | for i in range(len(self.scopeStack)): 321 | scope = self.scopeStack[(len(self.scopeStack)-1)-i] 322 | # walk each variable in the scope (from top) 323 | for var in reversed(scope.vars): 324 | indx += var.dtype.getSize() 325 | 326 | # if the variable is the one we're looking for, return the index of the variable in our heap 327 | if var.name == name: 328 | return VarInfo(var, indx) 329 | 330 | # variable wasn't found, check for it in our global array? 331 | for var in self.globals: 332 | if var.name == name: 333 | return VarInfo(var, VARINFOINDXS.GLOBAL.value) # found it! return as a global 334 | 335 | # global wasn't found, maybe its a subroutine 336 | for sub in self.subs: 337 | if sub.name == name: 338 | return VarInfo(sub, VARINFOINDXS.SUBROUTINE.value) 339 | 340 | for dev in self.devices: 341 | if dev.devname == name: 342 | return VarInfo(dev, VARINFOINDXS.DEVICE.value) 343 | 344 | return None 345 | 346 | # reads variable from heap & pushes to stack 347 | def __getVar(self, varInfo: VarInfo): 348 | dtype = varInfo.var.dtype 349 | 350 | # if array, return absolute 351 | if dtype.type == DTYPES.ARRAY: 352 | self.__getVarAddr(varInfo) 353 | return 354 | 355 | # is it a global? 356 | if varInfo.indx == VARINFOINDXS.GLOBAL.value: 357 | # read global 358 | if dtype.getSize() == 2: 359 | self.__writeOut(";globals/%s LDA2\n" % varInfo.var.name) 360 | elif dtype.getSize() == 1: 361 | self.__writeOut(";globals/%s LDA\n" % varInfo.var.name) 362 | else: 363 | self.__error("Can't set '%s': size greater than 2!" % varInfo.var.name) 364 | elif varInfo.indx == VARINFOINDXS.SUBROUTINE.value: # it's a sub, out the absolute address 365 | self.__writeOut(";SUB_%s " % varInfo.var.name) 366 | else: # it's a normal var stored in the heap 367 | # read variable from the heap 368 | if dtype.getSize() == 2: 369 | self.__writeIntLiteral(varInfo.indx) 370 | self.__writeOut(";peek-uxncle-short JSR2\n") 371 | elif dtype.getSize() == 1: 372 | self.__writeIntLiteral(varInfo.indx) 373 | self.__writeOut(";peek-uxncle JSR2\n") 374 | else: 375 | self.__error("Can't set '%s': size greater than 2!" % varInfo.var.name) 376 | 377 | # peek-uxncle pushes the value from the heap to the stack 378 | self.pushed += dtype.getSize() 379 | 380 | # pops value from stack and writes to heap 381 | def __setVar(self, varInfo: VarInfo): 382 | dtype = varInfo.var.dtype 383 | 384 | if dtype.type == DTYPES.ARRAY: 385 | self.__error("Can't set '%s': cannot set array type!") 386 | 387 | # is it a global? 388 | if varInfo.indx == VARINFOINDXS.GLOBAL.value: 389 | # set global 390 | if dtype.getSize() == 2: 391 | self.__writeOut(";globals/%s STA2\n" % varInfo.var.name) 392 | elif dtype.getSize() == 1: 393 | self.__writeOut(";globals/%s STA\n" % varInfo.var.name) 394 | else: 395 | self.__error("Can't set '%s': size greater than 2!" % varInfo.var.name) 396 | elif varInfo.indx == VARINFOINDXS.SUBROUTINE.value: # it's a sub, can't set that! 397 | self.__error("Can't set '%s': constant function!" % varInfo.var.name) 398 | else: # it's a normal var stored in the heap 399 | # set variable 400 | if dtype.getSize() == 2: 401 | self.__writeIntLiteral(varInfo.indx) 402 | self.__writeOut(";poke-uxncle-short JSR2\n") 403 | elif dtype.getSize() == 1: 404 | self.__writeIntLiteral(varInfo.indx) 405 | self.__writeOut(";poke-uxncle JSR2\n") 406 | else: 407 | self.__error("Can't set '%s': size greater than 2!" % varInfo.var.name) 408 | 409 | # poke-uxncle pops the value from the stack 410 | self.pushed -= dtype.getSize() 411 | 412 | def __getVarAddr(self, varInfo: VarInfo): 413 | dtype = varInfo.var.dtype 414 | 415 | if varInfo.indx == VARINFOINDXS.GLOBAL.value: # it's a global! our job is easy, just push the absolute address 416 | self.__writeOut(";globals/%s " % varInfo.var.name) 417 | elif varInfo.indx == VARINFOINDXS.SUBROUTINE.value: # it's a subroutine! our job is easy again, just push the absolute address (again) 418 | self.__writeOut(";%s " % dtype.subname) 419 | elif varInfo.indx == VARINFOINDXS.DEVICE.value: # it's a device! we only alow DEO/DEI to interact with the devices address directly 420 | self.__error("Can't get address of device!") 421 | else: # it's a normal variable, we'll need to push it's heap address. 422 | self.__writeOut(".uxncle/heap LDZ2 ") 423 | self.__writeIntLiteral(varInfo.indx) 424 | self.__writeOut("SUB2\n") # subtract our index from the top heap address. now the absolute address of the local var is on the stack :D 425 | 426 | self.pushed += 2 # we pushed an absolute address onto the stack 427 | 428 | # expects fromType to already be on the stack 429 | def __tryTypeCast(self, fromType: DataType, toType: DataType) -> bool: 430 | # if the types are the same, do nothing 431 | if fromType == None or toType == None or fromType.compare(toType): 432 | return True 433 | 434 | self.pushed += toType.getSize() - fromType.getSize() 435 | 436 | # convert (BOOL or CHAR) to (BOOL or CHAR) 437 | if (fromType.type == DTYPES.BOOL and toType.type == DTYPES.CHAR) or (fromType.type == DTYPES.CHAR and toType.type == DTYPES.BOOL): 438 | return True # they're basically the same datatype 439 | # convert INT to (BOOL or CHAR) 440 | elif fromType.type == DTYPES.INT and (toType.type == DTYPES.BOOL or toType.type == DTYPES.CHAR): 441 | self.__writeOut("SWP POP ") # pops the most significant byte 442 | return True 443 | # convert (BOOL or CHAR) to INT 444 | elif (fromType.type == DTYPES.BOOL or fromType.type == DTYPES.CHAR) and toType.type == DTYPES.INT: 445 | self.__writeOut("#00 SWP ") 446 | return True 447 | # convert pointer to INT, (it's already the proper size) 448 | elif fromType.type == DTYPES.POINTER and toType.type == DTYPES.INT: 449 | return True 450 | 451 | return False 452 | 453 | def __isWalkable(self, dtype: DataType): 454 | return dtype.type == DTYPES.DEV 455 | 456 | # ======================== parse rules ======================= 457 | 458 | def __getRule(self, tkn: TOKENTYPE) -> _PrecRule: 459 | if tkn.type not in self.parseTable: 460 | self.__errorAt(tkn, "Unknown Token, maybe forgot a ';'?") 461 | 462 | return self.parseTable[tkn.type] 463 | 464 | def __grabNumber(self, tkn: Token): 465 | num = int(tkn.word, 0) # python can guess the base using the 0* prefix if you pass base 0 (https://docs.python.org/3.4/library/functions.html?highlight=int#int) 466 | 467 | if num > 65536: 468 | self.__error("Number literal '%d' is too big! (> 65536)" % num) 469 | 470 | return num 471 | 472 | # parses number literal 473 | def __number(self, leftType: DataType, canAssign: bool, expectValue: bool, precLevel: PRECTYPE) -> DataType: 474 | # the expression expects nothing, return nothing (void) 475 | if not expectValue: 476 | return VoidDataType() 477 | 478 | num = self.__grabNumber(self.previous) 479 | 480 | if num > 256: 481 | self.__writeIntLiteral(num) 482 | self.pushed += 2 483 | return IntDataType() 484 | else: 485 | self.__writeByteLiteral(num) 486 | self.pushed += 1 487 | return CharDataType() 488 | 489 | # parses character literal 490 | def __charlit(self, leftType: DataType, canAssign: bool, expectValue: bool, precLevel: PRECTYPE) -> DataType: 491 | self.__writeByteLiteral(ord(self.previous.word[1])) 492 | self.pushed += 1 493 | return CharDataType() 494 | 495 | # parses string literal 496 | def __stringlit(self, leftType: DataType, canAssign: bool, expectValue: bool, precLevel: PRECTYPE) -> DataType: 497 | strng = self.previous.word[1:-1] # removes "" 498 | data = "" 499 | dtype = DataArray(CharDataType(), len(strng)) 500 | wasRaw = True 501 | 502 | for c in strng: 503 | if c in string.ascii_letters or c in string.punctuation or c in string.digits: 504 | if wasRaw: # let assembler know these are to be treated as ascii characters 505 | data += " \"" 506 | wasRaw = False 507 | data += c 508 | else: 509 | data += " %.2x" % ord(c) 510 | wasRaw = True 511 | 512 | id = self.__addConstant(_Constant(dtype, data)) 513 | 514 | # push absolute address to constant 515 | self.__writeOut(";const%d " % id) 516 | self.pushed += 2 517 | return Pointer(CharDataType()) 518 | 519 | def __index(self, leftType: DataType, canAssign: bool, expectValue: bool, precLevel: PRECTYPE) -> DataType: 520 | if leftType.type != DTYPES.POINTER and leftType.type != DTYPES.ARRAY: 521 | self.__error("ltype error! Expected lefthand expression of pointer or array type! Got '%s'!" % leftType.name) 522 | 523 | exprType = self.__expression() 524 | 525 | if not self.__tryTypeCast(exprType, IntDataType()): 526 | self.__error("Expected type of 'int' for index, got '%s'" % exprType.name) 527 | 528 | self.__consume(TOKENTYPE.RBRACKET, "Expected ']' to end index!") 529 | 530 | # get offset data from pointer (multiply pointer size with the index) 531 | self.__writeIntLiteral(leftType.pType.getPSize()) 532 | self.__writeOut("MUL2\n") 533 | 534 | # add pointer and offset together to get offset 535 | self.__writeOut("ADD2\n") 536 | 537 | # popped offset 538 | self.pushed -= 2 539 | 540 | if canAssign and self.__match(TOKENTYPE.EQUAL): 541 | # assigning to index 542 | 543 | addrInstr = self.__popLeftHand() # encapsulate the pointer expression 544 | self.__pushLeftHand() 545 | 546 | # write the value that needs to be set first 547 | valType = self.__expression() 548 | 549 | if not self.__tryTypeCast(valType, leftType.pType): 550 | self.__error("Expected expression of type '%s', got '%s'!" % (leftType.pType.name, valType.name)) 551 | 552 | if expectValue: 553 | self.__dupVal(leftType.pType) 554 | 555 | # push address onto stack 556 | self.__writeOut(addrInstr) 557 | 558 | # set value 559 | if leftType.pType.getSize() == 2: 560 | self.__writeOut("STA2\n") 561 | elif leftType.pType.getSize() == 1: 562 | self.__writeOut("STA\n") 563 | else: 564 | self.__error("Can't set value > 2 bytes!") 565 | 566 | self.pushed -= leftType.pType.getSize() # popped data on stack 567 | self.pushed -= 2 # popped address 568 | else: 569 | # load data from that address (it's an absolute address on the stack) 570 | if leftType.pType.getSize() == 2: 571 | self.__writeOut("LDA2\n") 572 | elif leftType.pType.getSize() == 1: 573 | self.__writeOut("LDA\n") 574 | else: 575 | self.__error("Can't get value > 2 bytes!") 576 | 577 | self.pushed += leftType.pType.getSize() # index is now on stack 578 | self.pushed -= 2 # popped address 579 | 580 | return leftType.pType 581 | 582 | # parses binary operators 583 | def __binOp(self, leftType: DataType, canAssign: bool, expectValue: bool, precLevel: PRECTYPE) -> DataType: 584 | tkn = self.previous 585 | 586 | # parse the righthand side of the expression (if needed) 587 | rtype = self.__parsePrecedence(PRECTYPE(int(precLevel.value) + 1), expectValue) 588 | 589 | # the expression expects nothing, return nothing (void) 590 | if not expectValue: 591 | return VoidDataType() 592 | 593 | # try to convert the expression to our expected type 594 | if not self.__tryTypeCast(rtype, leftType): 595 | self.__errorAt(tkn, "Cannot convert '%s' to '%s'!" % (rtype.name, leftType.name)) 596 | 597 | if tkn.type == TOKENTYPE.PLUS: 598 | self.__writeBinaryOp("ADD", leftType, leftType) 599 | elif tkn.type == TOKENTYPE.MINUS: 600 | self.__writeBinaryOp("SUB", leftType, leftType) 601 | elif tkn.type == TOKENTYPE.STAR: 602 | self.__writeBinaryOp("MUL", leftType, leftType) 603 | elif tkn.type == TOKENTYPE.SLASH: 604 | self.__writeBinaryOp("DIV", leftType, leftType) 605 | elif tkn.type == TOKENTYPE.MOD: 606 | self.__writeBinaryOp("DIVk", leftType, leftType) 607 | self.__writeBinaryOp("MUL", leftType, leftType) 608 | self.__writeBinaryOp("SUB", leftType, leftType) 609 | elif tkn.type == TOKENTYPE.EQUALEQUAL: 610 | self.__writeBinaryOp("EQU", leftType, BoolDataType()) 611 | leftType = BoolDataType() 612 | elif tkn.type == TOKENTYPE.GRTR: 613 | self.__writeBinaryOp("GTH", leftType, BoolDataType()) 614 | leftType = BoolDataType() 615 | elif tkn.type == TOKENTYPE.LESS: 616 | self.__writeBinaryOp("LTH", leftType, BoolDataType()) 617 | leftType = BoolDataType() 618 | elif tkn.type == TOKENTYPE.GRTREQL: 619 | # >= is just *not* <. so check if it's less than, and NOT the result 620 | self.__writeBinaryOp("LTH", leftType, BoolDataType()) 621 | self.__writeOut("#00 EQU\n") 622 | leftType = BoolDataType() 623 | elif tkn.type == TOKENTYPE.LESSEQL: 624 | # <= is just *not* >. so check if it's greater than, and NOT the result 625 | self.__writeBinaryOp("GTH", leftType, BoolDataType()) 626 | self.__writeOut("#00 EQU\n") 627 | leftType = BoolDataType() 628 | else: # should never happen 629 | self.__errorAt(tkn, "Invalid binary operator token!") 630 | 631 | return leftType 632 | 633 | def __ampersand(self, leftType: DataType, canAssign: bool, expectValue: bool, precLevel: PRECTYPE) -> DataType: 634 | self.__consume(TOKENTYPE.IDENT, "Expected identifier after '&'!") 635 | ident = self.previous 636 | 637 | # check if the identifier exists 638 | varInfo = self.__findVar(ident.word) 639 | if varInfo == None: 640 | self.__error("Unknown identifier '%s'!" % ident.word) 641 | 642 | # TODO: call __walkIdent 643 | 644 | self.__getVarAddr(varInfo) 645 | return Pointer(varInfo.var.dtype) 646 | 647 | def __pointer(self, leftType: DataType, canAssign: bool, expectValue: bool, precLevel: PRECTYPE) -> DataType: 648 | self.__pushLeftHand() # we don't want to emit the instructions immediately, we'll need to emit them after the value (if we are setting) is on the stack 649 | ltype = self.__parsePrecedence(PRECTYPE(int(PRECTYPE.ASSIGNMENT.value)+1), True) 650 | leftExpr = self.__popLeftHand() # grab the not-yet-emitted lefthand expression instructions 651 | 652 | if not ltype.type == DTYPES.POINTER: 653 | self.__error("Expected expression to evaluate to a pointer, got '%s'!", ltype.name) 654 | 655 | if canAssign and self.__match(TOKENTYPE.EQUAL): # set the address to the next expression 656 | rtype = self.__expression() 657 | 658 | # try to convert data to the expected type 659 | if not self.__tryTypeCast(rtype, ltype.pType): 660 | self.__error("Couldn't convert expression of type '%s' to '%s'!" % (rtype.name, ltype.pType.name)) 661 | 662 | if expectValue: 663 | self.__dupVal(ltype.pType) 664 | 665 | # emit lefthand expression (the address) 666 | self.__writeOut(leftExpr) 667 | 668 | # store the data at the addr 669 | if ltype.pType.getSize() == 2: 670 | self.__writeOut("STA2\n") 671 | elif ltype.pType.getSize() == 1: 672 | self.__writeOut("STA\n") 673 | 674 | # we popped the value AND the pointer 675 | self.pushed -= ltype.pType.getSize() + ltype.getSize() 676 | else: # grab the value at the address 677 | # emit the size of the data on the stack 678 | self.__writeByteLiteral(ltype.pType.getSize()) 679 | 680 | # emit lefthand expression (the address) 681 | self.__writeOut(leftExpr) 682 | 683 | # load the data from the addr 684 | if ltype.pType.getSize() == 2: 685 | self.__writeOut("LDA2\n") 686 | elif ltype.pType.getSize() == 1: 687 | self.__writeOut("LDA\n") 688 | 689 | self.pushed += ltype.pType.getSize() 690 | 691 | def __callSub(self, sub: Subroutine, canAssign: bool, expectValue: bool, precLevel: PRECTYPE): 692 | if not sub.type == DTYPES.SUB: 693 | self.__error("Expression of type '%s' is not callable!" % sub.name) 694 | 695 | subInstr = self.__popLeftHand() # grab instructions that get the sub address 696 | for i in range(len(sub.args)): 697 | arg = sub.args[i] 698 | exprType = self.__expression() 699 | 700 | if not self.__tryTypeCast(exprType, arg): 701 | self.__error("Expected expression of type '%s' for parameter #%d, got '%s'!" % (arg.name, i+1, exprType.name)) 702 | 703 | self.pushed -= arg.getSize() 704 | 705 | if i < len(sub.args)-1: 706 | self.__consume(TOKENTYPE.COMMA, "Expected ',' to start argument #d!" % i+2) 707 | 708 | self.__consume(TOKENTYPE.RPAREN, "Expected ')' to end function call!") 709 | 710 | # call subroutine 711 | self.__pushLeftHand() # push another LeftHand expression 712 | self.__writeOut("%sJSR2\n" % subInstr) 713 | self.pushed -= sub.getSize() # absolute address is popped 714 | 715 | # track pushed value on stack 716 | self.pushed += sub.retType.getSize() 717 | 718 | return sub.retType 719 | 720 | def __ident(self, leftType: DataType, canAssign: bool, expectValue: bool, precLevel: PRECTYPE) -> DataType: 721 | ident = self.previous 722 | 723 | # check if the identifier exists 724 | varInfo = self.__findVar(ident.word) 725 | if not varInfo == None: 726 | ltype: DataType = None 727 | lastType = None 728 | offset = 0 729 | 730 | if varInfo.indx == VARINFOINDXS.DEVICE.value or varInfo == VARINFOINDXS.SUBROUTINE.value: 731 | ltype = varInfo.var 732 | else: 733 | ltype = varInfo.var.dtype 734 | 735 | while self.__isWalkable(ltype): 736 | if self.__match(TOKENTYPE.DOT): 737 | lastType = ltype 738 | self.__consume(TOKENTYPE.IDENT, "Expected member identifier!") 739 | memInfo = ltype.searchMembers(self.previous.word) 740 | 741 | # check member actually exists 742 | if memInfo == None: 743 | self.__errorAt(self.previous, "Member '%s' not found!" % self.previous.word) 744 | 745 | offset += memInfo.indx 746 | ltype = memInfo.dtype 747 | 748 | # are we getting/setting to an index in an array in a device? 749 | if not lastType == None and lastType.type == DTYPES.DEV and ltype.type == DTYPES.ARRAY and self.__match(TOKENTYPE.LBRACKET): 750 | ptype = ltype.pType 751 | 752 | self.__pushLeftHand() # to encapsulate absolute address 753 | itype = self.__expression() # grab index 754 | self.__consume(TOKENTYPE.RBRACKET, "Expected ']' to end index!") 755 | 756 | # make sure it's an int 757 | if not self.__tryTypeCast(itype, CharDataType()): 758 | self.__error("Cannot index array with type '%s'!" % itype.name) 759 | 760 | # compute index offset 761 | self.__writeByteLiteral(ltype.getPSize()) 762 | self.__writeOut("MUL ") 763 | 764 | # grab offset of member 765 | self.__writeOut(".%s " % lastType.devname) 766 | self.__writeByteLiteral(memInfo.indx) 767 | self.__writeOut("ADD ") 768 | 769 | # add absolute address of member to index offset 770 | self.__writeOut("ADD ") 771 | addr = self.__popLeftHand() 772 | 773 | # getting or setting? 774 | if canAssign and self.__match(TOKENTYPE.EQUAL): 775 | rtype = self.__expression() 776 | 777 | # type cast 778 | if not self.__tryTypeCast(rtype, ptype): 779 | self.__error("Cannot convert '%s' to '%s'!" % (rtype.name, ptype.name)) 780 | 781 | # duplicate the value on the stack if it expects a value 782 | if expectValue: 783 | self.__dupVal(ptype) 784 | 785 | # now that the value to set is on the stack, push the absolute address and assign 786 | self.__writeOut(addr) 787 | 788 | if ptype.getSize() == 1: 789 | self.__writeOut("DEO\n") 790 | elif ptype.getSize() == 2: 791 | self.__writeOut("DEO2\n") 792 | else: 793 | self.__error("Can't set value of device > 2 bytes!") 794 | 795 | self.pushed -= ptype.getSize() + 1 # +1 for the index 796 | 797 | else: 798 | # push the absolute address and read from it 799 | self.__writeOut(addr) 800 | 801 | if ptype.getSize() == 1: 802 | self.__writeOut("DEI\n") 803 | elif ptype.getSize() == 2: 804 | self.__writeOut("DEI2\n") 805 | else: 806 | self.__error("Can't get value of device > 2 bytes!") 807 | 808 | self.pushed -= 1 # popped index 809 | self.pushed += ptype.getSize() 810 | 811 | return ptype 812 | elif canAssign and self.__match(TOKENTYPE.EQUAL): # it's a variable, if we *can* assign & there's an EQUAL token, handle assignment 813 | rtype = self.__expression() 814 | 815 | # try to typecast by default 816 | if not self.__tryTypeCast(rtype, ltype): 817 | self.__error("Cannot convert '%s' to '%s'!" % (rtype.name, ltype.name)) 818 | 819 | # duplicate the value on the stack if it expects a value 820 | if expectValue: 821 | self.__dupVal(ltype) 822 | 823 | # finally, set the variable 824 | if not lastType == None: # we walked through '.' or '->' 825 | if lastType.type == DTYPES.DEV: # it's setting a device 826 | self.__setDevice(lastType, memInfo.indx, ltype) 827 | else: 828 | self.__error("UNIMPL!") 829 | else: 830 | self.__setVar(varInfo) 831 | else: 832 | # we ignore expectValue since the user should know better than to write a single expression-statement that computes useless values. 833 | 834 | # finally, get the variable 835 | if not lastType == None: # we walked through '.' or '->' 836 | if lastType.type == DTYPES.DEV: # it's setting a device 837 | self.__getDevice(lastType, memInfo.indx, ltype) 838 | else: 839 | self.__error("UNIMPL!") 840 | else: 841 | self.__getVar(varInfo) 842 | 843 | return ltype 844 | self.__errorAt(ident, "Unknown identifier!") 845 | 846 | def __group(self, leftType: DataType, canAssign: bool, expectValue: bool, precLevel: PRECTYPE) -> DataType: 847 | # first, check for a typecast 848 | dtype = self.__checkDataType() 849 | if dtype != None: 850 | # it's a typecast, consume next expression 851 | self.__consume(TOKENTYPE.RPAREN, "Expected ')' to end open '('!") 852 | exprType = self.__parsePrecedence(PRECTYPE.ASSIGNMENT, expectValue) 853 | 854 | if not expectValue: # skip typecasting 855 | return exprType 856 | 857 | # else, try typecasting 858 | if not self.__tryTypeCast(exprType, dtype): 859 | self.__error("Couldn't typecast '%s' to '%s'!" % (exprType.name, dtype.name)) 860 | 861 | return dtype 862 | 863 | # parse expression 864 | exprType = self.__parsePrecedence(PRECTYPE.ASSIGNMENT, expectValue) 865 | self.__consume(TOKENTYPE.RPAREN, "Expected ')' to end open '('!") 866 | return exprType 867 | 868 | def __parsePrecedence(self, precLevel: PRECTYPE, expectValue: bool) -> DataType: 869 | self.__advance() 870 | 871 | func = self.__getRule(self.previous).prefix 872 | if func == None: 873 | self.__errorAt(self.previous, "Illegal syntax! [prefix]") 874 | 875 | canAssign: bool = precLevel.value <= PRECTYPE.ASSIGNMENT 876 | 877 | self.__pushLeftHand() # encapsulate the prefix & postfix together 878 | dtype = func(VoidDataType(), canAssign, expectValue, precLevel) 879 | while precLevel.value <= self.__getRule(self.current).prec.value: 880 | func = self.__getRule(self.current).infix 881 | if func == None: 882 | self.__errorAt(self.current, "Illegal syntax! [infix]") 883 | self.__advance() 884 | dtype = func(dtype, canAssign, expectValue, self.__getRule(self.previous).prec) 885 | 886 | self.__writeOut(self.__popLeftHand()) 887 | return dtype 888 | 889 | # ========================= statements ======================= 890 | 891 | def __readArgument(self, sub: Subroutine) -> Subroutine: 892 | # grab datatype 893 | dtype = self.__matchDataType() 894 | 895 | # grab identifier of argument 896 | self.__consume(TOKENTYPE.IDENT, "Expected identifier for argument!") 897 | ident = self.previous 898 | 899 | # add to scope and subroutine 900 | sub.addArg(dtype) 901 | self.scopeStack[-1].addVar(Variable(ident.word, dtype)) 902 | 903 | return sub 904 | 905 | def __defSub(self, retType: DataType, ident: Token): 906 | if not self.currentSub == -1: 907 | self.__error("Cannot define a function here!") 908 | 909 | ident = ident.word 910 | sub = Subroutine(retType, "SUB_%s" % ident) 911 | 912 | self.__newScope() 913 | if not self.__check(TOKENTYPE.RPAREN): # arguments are being defined 914 | sub = self.__readArgument(sub) 915 | while self.__match(TOKENTYPE.COMMA): # would be nice if python had A FUCKING DO-WHILE UGH 916 | sub = self.__readArgument(sub) 917 | 918 | self.__consume(TOKENTYPE.RPAREN, "Expected ')' to end argument list!") 919 | 920 | # define our function in our subroutine list 921 | self.subs.append(Variable(ident, sub)) 922 | self.currentSub = len(self.subs) - 1 923 | 924 | # allocate enough space on the "stack" heap for our passed parameters 925 | self.__writeIntLiteral(self.scopeStack[-1].getSize()) 926 | self.__writeOut(";alloc-uxncle JSR2\n") 927 | 928 | # in the scope, pop the parameters from the stack and set the arguments 929 | """ 930 | definition: (int a, int b, int c) 931 | call: (0, 0x1000, 0xFFFF) 932 | 933 | stack: 934 | #0000 935 | #1000 936 | #FFFF < top 937 | 938 | ^ so, we set the arguments in reverse, eg. pop & set c, pop & set b, pop & set a 939 | """ 940 | indx = self.scopeStack[-1].getSize() 941 | for i in range(len(self.scopeStack[-1].vars)): 942 | var = self.scopeStack[-1].vars[i] 943 | self.__setVar(VarInfo(var, indx)) 944 | indx -= var.dtype.getSize() 945 | 946 | # parse the subroutine scope 947 | self.__consume(TOKENTYPE.LBRACE, "Expected '{' to start function body!") 948 | self.__parseScope(True) 949 | self.__popScope() 950 | 951 | # reset our currentSub index :) 952 | self.currentSub = -1 953 | 954 | def __defArray(self, dType: DataType, ident: Token): 955 | # arrays in global scope will be put into our global table. this is part of the reason our 956 | # zero-page is unused. might reserve zero-page for constants if they'll fit? if the array 957 | # is defined in a scope, it's allocated on the heap and deallocated off the heap just like 958 | # any other variable. 959 | 960 | # NOTE: read __consumeArrayType() for some restrictions 961 | array = self.__consumeArrayType(dType) 962 | self.__addScopeVar(Variable(ident.word, array)) 963 | 964 | # TODO! 965 | '''if self.__match(TOKENTYPE.EQUAL): 966 | self.__consume(TOKENTYPE.LBRACE, "Expected '{' to start array initialization!") 967 | constID = self.__parseArray(dType) 968 | 969 | if self.constants[constID].type.size != array.size: 970 | self.__error("Type mismatch! Expected array of size %d, got %d elements!" % (array.size, self.constants[constID].type.size)) 971 | 972 | # copy constant array to variable array''' 973 | 974 | 975 | # parses constant array literal, returns constant ID 976 | def __parseArray(self, dtype: DataType) -> int: 977 | if dtype.type != DTYPES.INT and dtype.type != DTYPES.CHAR: 978 | self.__error("Cannot define const array of type '%s'" % dtype.name) 979 | 980 | array = None 981 | data = "" 982 | sz = 0 983 | 984 | while True: 985 | num = None 986 | 987 | # grab literal 988 | if self.__match(TOKENTYPE.NUM): 989 | num = self.__grabNumber(self.previous) 990 | elif self.__match(TOKENTYPE.CHARLIT): 991 | num = ord(self.previous.word[1]) 992 | else: 993 | self.__error("Expected literal value!") 994 | 995 | # add to data string 996 | if dtype.type == DTYPES.INT: 997 | data += " %.4x" % num 998 | else: 999 | data += " %.2x" % num 1000 | 1001 | sz += 1 1002 | # consume comma 1003 | if not self.__match(TOKENTYPE.COMMA): 1004 | break 1005 | 1006 | self.__consume(TOKENTYPE.RBRACE, "Expected '}' to end constant array definition!") 1007 | return self.__addConstant(_Constant(DataArray(dtype, sz), data)) 1008 | 1009 | 1010 | # returns true if it parsed a function 1011 | def __varTypeState(self, dtype: DataType): 1012 | self.__consume(TOKENTYPE.IDENT, "Expected identifier!") 1013 | ident = self.previous 1014 | 1015 | if self.__match(TOKENTYPE.LPAREN): # they're declaring a subroutine! 1016 | self.__defSub(dtype, ident) 1017 | return True 1018 | 1019 | if self.__match(TOKENTYPE.LBRACKET): # they're declaring an array! 1020 | self.__defArray(dtype, ident) 1021 | return False 1022 | 1023 | varInfo = self.__addScopeVar(Variable(ident.word, dtype)) 1024 | 1025 | if self.__match(TOKENTYPE.EQUAL): 1026 | if dtype.type == DTYPES.POINTER and self.__match(TOKENTYPE.LBRACE): # they're defining a pointer to a constant array 1027 | id = self.__parseArray(dtype.pType) 1028 | self.__writeOut(";const%d "% id) 1029 | self.pushed += 2 1030 | 1031 | self.__setVar(varInfo) 1032 | else: 1033 | rtype = self.__expression() 1034 | 1035 | if not self.__tryTypeCast(rtype, dtype): 1036 | self.__error("Expected expession of type '%s', got '%s'!" % (dtype.name, rtype.name)) 1037 | self.__setVar(varInfo) 1038 | 1039 | return False 1040 | 1041 | def __parseScope(self, expectBrace: bool): 1042 | while not self.__isEnd() and (not expectBrace or not self.__check(TOKENTYPE.RBRACE)): 1043 | self.__statement() 1044 | 1045 | if expectBrace: 1046 | self.__consume(TOKENTYPE.RBRACE, "Expected '}' to end scope!") 1047 | 1048 | def __ifState(self): 1049 | self.__consume(TOKENTYPE.LPAREN, "Expected '('!") 1050 | jmp = self.__newJmpLbl() 1051 | 1052 | # parse the expession 1053 | dtype = self.__expression() 1054 | 1055 | self.__consume(TOKENTYPE.RPAREN, "Expected ')'!") 1056 | 1057 | if not self.__tryTypeCast(dtype, BoolDataType()): 1058 | self.__error("Couldn't convert '%s' to 'bool'!" % dtype.name) 1059 | 1060 | # write comparison jump, if the flag is false, skip the true block 1061 | self.__writeOut("#00 EQU ") 1062 | self.__jmpCondLbl(jmp) 1063 | 1064 | # now parse the true branch 1065 | self.__statement() 1066 | 1067 | # there's an else branch! 1068 | if self.__match(TOKENTYPE.ELSE): 1069 | elseJmp = jmp 1070 | jmp = self.__newJmpLbl() 1071 | self.__jmpLbl(jmp) # skip the else block 1072 | self.__declareLbl(elseJmp) # if the condition is false, they jump here (the beginning of the else block!) 1073 | self.__statement() 1074 | 1075 | # define the label to jump to after the statements 1076 | self.__declareLbl(jmp) 1077 | 1078 | def __whileState(self): 1079 | self.__consume(TOKENTYPE.LPAREN, "Expected '(' to start conditional expression!") 1080 | jmp = self.__newJmpLbl() 1081 | exitJmp = self.__newJmpLbl() 1082 | 1083 | # declare the label that will be the start of the loop 1084 | self.__declareLbl(jmp) 1085 | dtype = self.__expression() 1086 | 1087 | self.__consume(TOKENTYPE.RPAREN, "Expected ')' to end conditional expression!") 1088 | 1089 | if not self.__tryTypeCast(dtype, BoolDataType()): 1090 | self.__error("Couldn't convert '%s' to 'bool'!" % dtype.name) 1091 | 1092 | # write comparison jump, if the flag is false, jump out of the loop 1093 | self.__writeOut("#00 EQU ") 1094 | self.__jmpCondLbl(exitJmp) 1095 | 1096 | # now parse the loop body 1097 | self.__statement() 1098 | 1099 | # jump back to the start of the loop 1100 | self.__jmpLbl(jmp) 1101 | self.__declareLbl(exitJmp) 1102 | 1103 | def __deviceState(self): 1104 | """ 1105 | device Console[0x10] { 1106 | char padd[8]; 1107 | char write; 1108 | }; 1109 | """ 1110 | self.__consume(TOKENTYPE.IDENT, "Expected identifier for device declaration!") 1111 | ident = self.previous 1112 | 1113 | self.__consume(TOKENTYPE.LBRACKET, "Expected '[' for start of zeropage address of device!") 1114 | self.__consume(TOKENTYPE.NUM, "Expected zeropage address for start of device!") 1115 | addr = self.__grabNumber(self.previous) 1116 | 1117 | # check it'll fit in a zeropage address 1118 | if addr > 256: 1119 | self.__error("Invalid device address! (outside of zeropage!)") 1120 | 1121 | self.__consume(TOKENTYPE.RBRACKET, "Expected ']' for end of zeropage address of device!") 1122 | self.__consume(TOKENTYPE.LBRACE, "Expected '{' to start member list!") 1123 | 1124 | dev = Device(ident.word, addr) 1125 | 1126 | # consume members 1127 | while not self.__match(TOKENTYPE.RBRACE): 1128 | dtype = self.__matchDataType() # grab datatype 1129 | 1130 | self.__consume(TOKENTYPE.IDENT, "Expected identifier!") 1131 | 1132 | ident = self.previous.word 1133 | if self.__match(TOKENTYPE.LBRACKET): # is it an array? 1134 | dtype = self.__consumeArrayType(dtype) 1135 | dev.addMember(Variable(ident, dtype)) 1136 | 1137 | self.__consume(TOKENTYPE.SEMICOLON, "Expected ';' to end member declaration!") 1138 | 1139 | self.devices.append(dev) 1140 | 1141 | def __returnState(self): 1142 | if self.currentSub == -1: # we're not currently parsing a function! 1143 | self.error("'return' not allowed in this context!") 1144 | 1145 | retType: DataType = self.subs[self.currentSub].dtype.retType 1146 | 1147 | if self.__check(TOKENTYPE.SEMICOLON): # there's no expression 1148 | if retType.compare(VoidDataType): # make sure we can actually return nothing 1149 | self.__popRawScopes(len(self.scopeStack)) 1150 | self.__writeOut("JMP2r\n") 1151 | return 1152 | else: 1153 | self.__error("Expected expression of type '%s', got 'void'!" % retType.name) 1154 | 1155 | dtype = self.__expression() 1156 | 1157 | if not self.__tryTypeCast(dtype, retType): 1158 | self.__error("Cannot convert expression of type '%s' to return type of '%s'" % (dtype.name, retType.name)) 1159 | 1160 | self.__popRawScopes(len(self.scopeStack)) 1161 | self.__writeOut("JMP2r\n") 1162 | 1163 | # we don't need to keep track of that value anymore 1164 | self.pushed -= retType.getSize() 1165 | 1166 | def __expression(self) -> DataType: 1167 | return self.__parsePrecedence(PRECTYPE.ASSIGNMENT, True) 1168 | 1169 | def __voidExpression(self): 1170 | self.__parsePrecedence(PRECTYPE.ASSIGNMENT, False) 1171 | 1172 | def __statement(self): 1173 | ttype = self.current.type 1174 | currentPushed = self.pushed 1175 | 1176 | dtype = self.__checkDataType() 1177 | if not dtype == None: # is it a variable definition? 1178 | if self.__varTypeState(dtype): # if we parsed a function, we don't expect a ';' 1179 | return 1180 | elif self.__match(TOKENTYPE.IF): # we don't expect a ';', and the stack *should* already be balanced (???) so these statements return immediately 1181 | self.__ifState() 1182 | return 1183 | elif self.__match(TOKENTYPE.WHILE): # we don't expect a ';' 1184 | self.__whileState() 1185 | return 1186 | elif self.__match(TOKENTYPE.RETURN): 1187 | self.__returnState() 1188 | elif self.__match(TOKENTYPE.DEVICE): 1189 | self.__deviceState() 1190 | elif self.__match(TOKENTYPE.LBRACE): 1191 | self.__newScope() 1192 | self.__parseScope(True) 1193 | self.__popScope() 1194 | return 1195 | else: # it's not a statement, parse it as an expression (with no value expected!) 1196 | self.__voidExpression() 1197 | 1198 | self.__pop(self.pushed - currentPushed) 1199 | self.__consume(TOKENTYPE.SEMICOLON, "Expected ';' to mark end of statement!") 1200 | 1201 | def parse(self): 1202 | self.__advance() 1203 | 1204 | self.__newScope() # no 'true' variables will be allocated here. just allows heap allocation for arrays and such 1205 | # parse until the end of the file 1206 | self.__parseScope(False) 1207 | self.__popScope() 1208 | 1209 | # write the license header 1210 | self.out.write(thinlib._LICENSE) 1211 | 1212 | # declare devices 1213 | for dev in self.devices: 1214 | self.out.write("|%.2x @%s [ &padd $%d ]\n" % (dev.addr, dev.devname, dev.getSize())) 1215 | 1216 | # write the globals into the zero area 1217 | self.out.write("|0000\n") 1218 | self.out.write(thinlib._MEMDEFS) 1219 | 1220 | # write entrypoint 1221 | self.out.write("|0100\n") 1222 | self.out.write(thinlib._MEMENTRY) 1223 | self.out.write(self.entryInstr) 1224 | self.out.write("BRK\n\n") 1225 | 1226 | # now write all globals 1227 | self.out.write("@globals [ ") 1228 | for var in self.globals: 1229 | self.out.write("&%s $%d " % (var.name, var.dtype.getSize())) 1230 | self.out.write("]\n\n") 1231 | 1232 | # write all constants 1233 | for i in range(len(self.constants)): 1234 | self.out.write("@const%d %s\n" % (i, self.constants[i].data)) 1235 | 1236 | # TODO: write subroutines 1237 | for sub in self.subs: 1238 | self.out.write("@%s\n" % sub.dtype.subname) 1239 | self.out.write(sub.dtype.instrs) 1240 | self.out.write("JMP2r\n\n") 1241 | 1242 | # write memlib 1243 | self.out.write(thinlib._MEMLIB) 1244 | -------------------------------------------------------------------------------- /src/pyuxncle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import sys 4 | from pathlib import Path 5 | from parser import Parser 6 | 7 | if __name__ == '__main__': 8 | if not len(sys.argv) == 3: 9 | print("Usage: pyuxncle [source] [out]") 10 | 11 | inFile = sys.argv[1] 12 | outFile = sys.argv[2] 13 | src = Path(inFile).read_text() 14 | parse = Parser(src, outFile) 15 | parse.parse() -------------------------------------------------------------------------------- /src/thinlib.py: -------------------------------------------------------------------------------- 1 | """ 2 | This provides some thin Uxntal for the memlib (this is needed since Uxn has no user-indexable stack) also any additional uxntal templates 3 | """ 4 | 5 | _LICENSE = """ ( This file was generated by the Pyuxncle compiler [https://github.com/CPunch/pyuxncle] )\n\n""" 6 | 7 | _MEMDEFS = "@uxncle [ &heap $2 ]\n" 8 | _MEMENTRY = ";uxncle-heap .uxncle/heap STZ2\n" # sets the heap pointer to the beginning of our free address space 9 | _MEMLIB = ''.join(( 10 | "@alloc-uxncle\n", # this subroutine handles allocating memory on the heap, expects the size (short) 11 | ".uxncle/heap LDZ2\n", # load the heap pointer onto the stack 12 | "ADD2\n", # add the size 13 | ".uxncle/heap STZ2\n", # store the new heap pointer 14 | "JMP2r\n", # return 15 | "@dealloc-uxncle\n", # this subroutine handles deallocating memory from the heap, expects the size (short) 16 | ".uxncle/heap LDZ2\n", # load the heap pointer onto the stack 17 | "SWP2\n", # move the heap pointer behind the size, so when we subtract it'll be heap - size, not size - heap 18 | "SUB2\n", # sub the size from the address 19 | ".uxncle/heap STZ2\n", # store the new heap pointer 20 | "JMP2r\n", #return 21 | "@peek-uxncle-short\n", # this subroutine handles loading a short from the heap and pushing it onto the stack, expects the offset (short) 22 | ".uxncle/heap LDZ2\n", # load the heap pointer onto the stack 23 | "SWP2\n", # move the heap pointer behind the offset 24 | "SUB2\n", 25 | "LDA2\n", # loads the short from the heap onto the stack 26 | "JMP2r\n", # return 27 | "@poke-uxncle-short\n", # this subroutine handles popping a short from the stack and saving it into the heap, expects the value (short) and the offset (short) 28 | ".uxncle/heap LDZ2\n", # load the heap pointer onto the stack 29 | "SWP2\n", # move the heap pointer behind the offset 30 | "SUB2\n", 31 | "STA2\n", # stores the value into the address 32 | "JMP2r\n", # return 33 | "@peek-uxncle\n", # this subroutine handles loading a byte from the heap and pushing it onto the stack, expects the offset (short) 34 | ".uxncle/heap LDZ2\n", # load the heap pointer onto the stack 35 | "SWP2\n", # move the heap pointer behind the offset 36 | "SUB2\n", 37 | "LDA\n", # loads the byte from the heap onto the stack 38 | "JMP2r\n", 39 | "@poke-uxncle\n", # this subroutine handles popping a byte from the stack and saving it into the heap, expects the value (byte) and the offset (short) 40 | ".uxncle/heap LDZ2\n", # load the heap pointer onto the stack 41 | "SWP2\n", # move the heap pointer behind the offset 42 | "SUB2\n", 43 | "STA\n", # stores the value into the address */ 44 | "JMP2r\n", # return 45 | "@uxncle-heap\n" # marks the location after all of user-code and our thin memory lib 46 | )) -------------------------------------------------------------------------------- /src/tokenizer.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, auto 2 | import string 3 | 4 | from compError import doCompError 5 | 6 | class TOKENTYPE(Enum): 7 | SEMICOLON = auto() # ; 8 | COMMA = auto() # , 9 | LPAREN = auto() # ( 10 | RPAREN = auto() # ) 11 | LBRACE = auto() # { 12 | RBRACE = auto() # } 13 | LBRACKET = auto() # [ 14 | RBRACKET = auto() # ] 15 | PLUS = auto() # + 16 | MINUS = auto() # - 17 | STAR = auto() # * 18 | SLASH = auto() # / 19 | MOD = auto() 20 | AMPER = auto() # & 21 | MINUSGRTR = auto() # -> 22 | DOT = auto() # . 23 | EQUAL = auto() # = 24 | EQUALEQUAL = auto() # == 25 | GRTREQL = auto() # >= 26 | GRTR = auto() # > 27 | LESSEQL = auto() # <= 28 | LESS = auto() # < 29 | IF = auto() # if 30 | ELSE = auto() # else 31 | WHILE = auto() # while 32 | RETURN = auto() # return 33 | IDENT = auto() # Identifier_123 34 | NUM = auto() # 1234567890 or 0xFF 35 | CHARLIT = auto() # 'A' 36 | STRINGLIT = auto() # "ABC" 37 | INT = auto() # int 38 | CHAR = auto() # char 39 | BOOL = auto() # bool 40 | VOID = auto() # void 41 | DEVICE = auto() # device 42 | EOF = auto() # end of file 43 | 44 | class Token: 45 | def __init__(self, type: TOKENTYPE, word: str, line: int): 46 | self.type = type 47 | self.word = word 48 | self.line = line 49 | 50 | def print(self): 51 | print("\'" + self.word + "\' : [TOKEN_" + self.type.name + "]") 52 | 53 | _ReservedWordTable = { 54 | "if" : TOKENTYPE.IF, 55 | "else" : TOKENTYPE.ELSE, 56 | "while" : TOKENTYPE.WHILE, 57 | "return": TOKENTYPE.RETURN, 58 | "int" : TOKENTYPE.INT, 59 | "char" : TOKENTYPE.CHAR, 60 | "bool" : TOKENTYPE.BOOL, 61 | "void" : TOKENTYPE.VOID, 62 | "device": TOKENTYPE.DEVICE 63 | } 64 | 65 | def _isWhitespace(c): 66 | return c in string.whitespace or c == '/' 67 | 68 | def _isNum(c): 69 | return c in string.digits 70 | 71 | def _isHex(c): 72 | return c in string.hexdigits 73 | 74 | def _isAlpha(c): 75 | return c in (string.ascii_letters + "_") 76 | 77 | class Lexer: 78 | def __init__(self, source: str): 79 | self.src = source 80 | self.size = len(source) 81 | self.cursor = 0 82 | self.start = 0 83 | self.line = 1 84 | 85 | def __error(self, err: str): 86 | doCompError("On line %d:\n\t%s" % (self.line, err)) 87 | 88 | def __isEnd(self): 89 | return self.cursor >= self.size 90 | 91 | # peeks the current character 92 | def __peek(self): 93 | if self.__isEnd(): 94 | return '\0' 95 | 96 | return self.src[self.cursor] 97 | 98 | def __peekNext(self): 99 | if self.cursor + 1 >= self.size: 100 | return '\0' 101 | 102 | return self.src[self.cursor + 1] 103 | 104 | # grabs the current character and increments the cursor 105 | def __next(self): 106 | if self.__isEnd(): 107 | return '\0' 108 | 109 | self.cursor += 1 110 | return self.src[self.cursor-1] 111 | 112 | # returns true & consumes character if next 113 | def __checkNext(self, char): 114 | if self.__peek() == char: 115 | self.__next() 116 | return True 117 | return False 118 | 119 | def __getWord(self): 120 | return self.src[self.start:self.cursor] 121 | 122 | def __makeToken(self, type: TOKENTYPE): 123 | return Token(type, self.__getWord(), self.line) 124 | 125 | def __skipWhitespace(self): 126 | while (_isWhitespace(self.__peek())): 127 | if self.__peek() == '\n': # on newlines, count the line! 128 | self.line += 1 129 | elif self.__peek() == '/': # maybe a comment? 130 | if self.__peekNext() == '/': # consume comment 131 | self.__next() # consumes '/' 132 | # consume until newline, make sure to not consume newline so that it's properly handled on the next iteration 133 | while not self.__isEnd() and not self.__peek() == '\n': 134 | self.__next() 135 | elif self.__peekNext() == '*': # muti-line comment 136 | self.__next() # consumes '*' 137 | 138 | # consume until '*/' 139 | while not self.__isEnd() and not (self.__peek() == '*' and self.__peekNext() == '/'): 140 | self.__next() 141 | 142 | # consume '*/' 143 | self.__next() 144 | else: 145 | # not a comment, return 146 | return 147 | 148 | self.__next() 149 | 150 | def __readIdentifier(self): 151 | while (_isAlpha(self.__peek()) or _isNum(self.__peek())): 152 | self.__next() 153 | 154 | # check if identifier is a reserved word 155 | if self.__getWord() in _ReservedWordTable: 156 | return self.__makeToken(_ReservedWordTable[self.__getWord()]) 157 | 158 | return self.__makeToken(TOKENTYPE.IDENT) 159 | 160 | # TODO: support special characters eg. '\n', '\r', etc. 161 | def __readCharacter(self): 162 | self.__next() 163 | 164 | if not self.__checkNext('\''): 165 | self.__error("Unended character literal!") 166 | 167 | return self.__makeToken(TOKENTYPE.CHARLIT) 168 | 169 | def __readString(self): 170 | while not self.__checkNext('"'): 171 | self.__next() 172 | 173 | return self.__makeToken(TOKENTYPE.STRINGLIT) 174 | 175 | def __readNumber(self): 176 | if self.__checkNext('x') or self.__checkNext('X'): # it's a hexadecimal digit 177 | while (_isHex(self.__peek())): 178 | self.__next() 179 | 180 | return self.__makeToken(TOKENTYPE.NUM) 181 | 182 | while (_isNum(self.__peek())): 183 | self.__next() 184 | 185 | return self.__makeToken(TOKENTYPE.NUM) 186 | 187 | # grabs the next token (skipping whitespace) 188 | def scanNext(self) -> Token: 189 | # skip all uninteresting characters 190 | self.__skipWhitespace(); 191 | 192 | self.start = self.cursor 193 | c = self.__next() 194 | 195 | # python you ugly bastard, give us syntaxical sugar and add switch statements already 196 | charLookup = { 197 | ';' : (lambda : self.__makeToken(TOKENTYPE.SEMICOLON)), 198 | ',' : (lambda : self.__makeToken(TOKENTYPE.COMMA)), 199 | '(' : (lambda : self.__makeToken(TOKENTYPE.LPAREN)), 200 | ')' : (lambda : self.__makeToken(TOKENTYPE.RPAREN)), 201 | '{' : (lambda : self.__makeToken(TOKENTYPE.LBRACE)), 202 | '}' : (lambda : self.__makeToken(TOKENTYPE.RBRACE)), 203 | '[' : (lambda : self.__makeToken(TOKENTYPE.LBRACKET)), 204 | ']' : (lambda : self.__makeToken(TOKENTYPE.RBRACKET)), 205 | '+' : (lambda : self.__makeToken(TOKENTYPE.PLUS)), 206 | '-' : (lambda : self.__makeToken(TOKENTYPE.MINUSGRTR) if self.__checkNext('>') else self.__makeToken(TOKENTYPE.MINUS)), 207 | '*' : (lambda : self.__makeToken(TOKENTYPE.STAR)), 208 | '/' : (lambda : self.__makeToken(TOKENTYPE.SLASH)), 209 | '%' : (lambda : self.__makeToken(TOKENTYPE.MOD)), 210 | '&' : (lambda : self.__makeToken(TOKENTYPE.AMPER)), 211 | '.' : (lambda : self.__makeToken(TOKENTYPE.DOT)), 212 | '=' : (lambda : self.__makeToken(TOKENTYPE.EQUALEQUAL) if self.__checkNext('=') else self.__makeToken(TOKENTYPE.EQUAL)), 213 | '>' : (lambda : self.__makeToken(TOKENTYPE.GRTREQL) if self.__checkNext('=') else self.__makeToken(TOKENTYPE.GRTR)), 214 | '<' : (lambda : self.__makeToken(TOKENTYPE.LESSEQL) if self.__checkNext('=') else self.__makeToken(TOKENTYPE.LESS)), 215 | '\'': (lambda : self.__readCharacter()), 216 | '"' : (lambda : self.__readString()), 217 | '\0': (lambda : self.__makeToken(TOKENTYPE.EOF)), 218 | } 219 | 220 | # if it's a simple token, return it. 221 | if c in charLookup: 222 | return charLookup[c]() 223 | 224 | # otherwise it's an identifier or number 225 | if _isAlpha(c): 226 | return self.__readIdentifier() 227 | elif _isNum(c): 228 | return self.__readNumber() 229 | else: 230 | return self.__error("Unrecognized token '%s'!" % self.__getWord()) 231 | 232 | -------------------------------------------------------------------------------- /src/type.py: -------------------------------------------------------------------------------- 1 | from enum import IntEnum, Enum, auto 2 | 3 | class DTYPES(Enum): 4 | INT = auto() 5 | CHAR = auto() 6 | BOOL = auto() 7 | VOID = auto() 8 | SUB = auto() 9 | DEV = auto() 10 | POINTER = auto() 11 | ARRAY = auto() 12 | 13 | class DataType: 14 | def __init__(self, name: str, type: DTYPES): 15 | self.name = name 16 | self.type = type 17 | 18 | def getSize(self) -> int: 19 | raise NotImplementedError 20 | 21 | # should be overwritten for usertypes 22 | def compare(self, type): 23 | return self.type == type.type 24 | 25 | # for pointer arithmetic 26 | def getPSize(self) -> int: 27 | return self.getSize() 28 | 29 | # for stack pushes/pops 30 | def getStackSize(self) -> int: 31 | return self.getSize() 32 | 33 | class Variable: 34 | def __init__(self, name: str, dtype: DataType): 35 | self.name = name 36 | self.dtype = dtype 37 | 38 | class VARINFOINDXS(IntEnum): 39 | GLOBAL = -1, 40 | SUBROUTINE = -2, 41 | DEVICE = -3 42 | 43 | # variable info, including the variable (name & datatype) and the index in the stack 44 | class VarInfo: 45 | def __init__(self, var: Variable, indx: int): # indx >= 0 means on the heap, < 0 means a VARINFOINDXS 46 | self.var = var 47 | self.indx = indx 48 | 49 | class Pointer(DataType): 50 | def __init__(self, pointerToType: DataType): 51 | super().__init__("*%s" % pointerToType.name, DTYPES.POINTER) 52 | self.pType = pointerToType 53 | 54 | def getSize(self) -> int: 55 | return 2 # we push the absolute address to the stack 56 | 57 | def getPSize(self) -> int: 58 | return self.pType.getSize() 59 | 60 | def compare(self, other): 61 | return other.type == DTYPES.POINTER and other.pType.compare(self.pType) 62 | 63 | class Subroutine(DataType): 64 | def __init__(self, retType: DataType, name: str): 65 | super().__init__("_func", DTYPES.SUB) 66 | self.instrs = "" 67 | self.args: list[DataType] = [] 68 | self.retType = retType 69 | self.subname = name 70 | 71 | def addArg(self, dtype: DataType): 72 | self.args.append(dtype) 73 | 74 | def addUnxtal(self, uxntal: str): 75 | self.instrs = self.instrs + uxntal 76 | 77 | def getSize(self) -> int: 78 | return 2 # the absolute address is pushed onto the stack :D 79 | 80 | def compare(self, type): 81 | return False # TODO 82 | 83 | class IndxInfo: 84 | def __init__(self, indx: int, dtype: DataType): 85 | self.indx = indx 86 | self.dtype = dtype 87 | 88 | class Device(DataType): 89 | def __init__(self, name: str, addr: int): 90 | super().__init__("DEV: %s" % name, DTYPES.DEV) 91 | self.devname = name 92 | self.members: list[DataType] = [] 93 | self.addr = addr # zeropage address 94 | 95 | def addMember(self, mem: Variable): 96 | self.members.append(mem) 97 | 98 | # returns VarInfo of member, or None if not found 99 | def searchMembers(self, ident: str) -> IndxInfo: 100 | indx = 0 101 | for mem in self.members: 102 | if mem.name == ident: 103 | return IndxInfo(indx, mem.dtype) 104 | 105 | indx += mem.dtype.getSize() 106 | 107 | return None 108 | 109 | def getSize(self): 110 | sz = 0 111 | for mem in self.members: 112 | sz += mem.dtype.getSize() 113 | return sz 114 | 115 | def compare(self, type): 116 | return False 117 | 118 | class IntDataType(DataType): 119 | def __init__(self): 120 | super().__init__("int", DTYPES.INT) 121 | 122 | def getSize(self): 123 | return 2 124 | 125 | class CharDataType(DataType): 126 | def __init__(self): 127 | super().__init__("char", DTYPES.CHAR) 128 | 129 | def getSize(self): 130 | return 1 131 | 132 | class BoolDataType(DataType): 133 | def __init__(self): 134 | super().__init__("bool", DTYPES.BOOL) 135 | 136 | def getSize(self): 137 | return 1 138 | 139 | class VoidDataType(DataType): 140 | def __init__(self): 141 | super().__init__("void", DTYPES.VOID) 142 | 143 | def getSize(self) -> int: 144 | return 0 145 | 146 | # pointer arithmetic for void* acts like it's a size of 1 147 | def getPSize(self) -> int: 148 | return 1 149 | 150 | class DataArray(DataType): 151 | def __init__(self, pType: DataType, size: int): 152 | super().__init__("%s[%d]" % (pType.name, size), DTYPES.ARRAY) 153 | self.pType = pType 154 | self.size = size 155 | 156 | def getSize(self) -> int: 157 | # size of the array is the size of the datatype * the number of elements 158 | return self.pType.getSize() * self.size 159 | 160 | def getPSize(self) -> int: 161 | return self.pType.getSize() 162 | 163 | def getStackSize(self) -> int: 164 | return 2 # an absolute address is pushed :P --------------------------------------------------------------------------------