├── .gitignore ├── LICENSE ├── README.md ├── __init__.py ├── ast.py ├── braid.py ├── bytecode.py ├── compiler.py ├── errors.py ├── examples ├── fact.bd ├── headtail.bd ├── nested.bd ├── nested2.bd └── readline.bd ├── interpreter.py ├── lexer.py ├── objects.py ├── parser.py ├── prelude.py ├── repl.py ├── requirements.txt ├── target.py ├── test.py └── test.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | *.sh 4 | target-c 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Braid 2 | 3 | **Learning to build a language interpreter with RPython and RPly** 4 | 5 | I don't really know what I'm doing but I'm interested in writing a toy language and interpreter. 6 | I've chosen RPython and RPly because I know Python quite well and the RPython EBNF parsing libs were confusing. 7 | RPly's interface is a bit higher level. 8 | 9 | *This project is no longer being actively maintained.* 10 | 11 | ## Installing 12 | 13 | `pip install -r requirements.txt` 14 | 15 | ## Running 16 | 17 | `python braid.py` for REPL, `python braid.py [filename].bd` for interpreting a file 18 | 19 | `:a` gives you the AST of the last statement, `:e` to list environment variables, `:q` or Ctrl-C to quit. The REPL now supports multi-line input too — it'll just keep appending code and trying to interpret it until it's valid (eg. you closed the block or whatever), or you break it ;) 20 | 21 | ## Status 22 | 23 | Basic arithmetic, floats, integers, booleans, and strings, variable assignment, if expressions, and a print() function. 24 | 25 | 26 | ``` 27 | >>> 5 == 5 28 | = true 29 | >>> 5 != 5 30 | = false 31 | >>> let a = 5 32 | = 5 33 | >>> print(a) 34 | 5 35 | >>> print(a + 25) 36 | 30 37 | >>> "hi" + 'hi' 38 | = hihi 39 | >>> "hi" * 5 - 1 40 | = hihihihih 41 | 42 | # if expressions 43 | >>> if false: print("no") else: print("yes") end 44 | yes 45 | >>> let a = (if true: 1 else: 5 end) 46 | = 1 47 | 48 | let a = 50 49 | if a == 50 and true: 50 | print("doing stuff") 51 | else: 52 | print("not this though") 53 | end 54 | 55 | >>> 5 >= 6 56 | = false 57 | 58 | # assignment via if 59 | >>> let a = if true: 5 end 60 | = 5 61 | >>> :a 62 | Program(BinaryOp(Variable('a'), If(Boolean(True))Then(Integer(5))Else(None))) 63 | 64 | # arrays 65 | >>> [5, 6, ["hi", 7.0]] 66 | = [5, 6, [hi, 7.0]] 67 | 68 | # functions 69 | func a(b): 70 | b + 1 71 | end 72 | 73 | >>> b(1) 74 | = 2 75 | 76 | # immutability means loops become recursion 77 | func p_message(msg, n): 78 | if n > 0: 79 | print(msg) 80 | p_message(msg, n - 1) 81 | end 82 | end 83 | 84 | >>> p_message("hellooo",2) 85 | hellooo 86 | hellooo 87 | 88 | # functions can be passed around 89 | func a(): 90 | 1 91 | end 92 | 93 | >>> let b = a 94 | >>> b() 95 | = 1 96 | ``` 97 | 98 | ## Compiling 99 | 100 | You will need pypy so you can use RPython's compiler. Then, like so: 101 | 102 | `python path/to/rpython/bin/rpython target.py` 103 | 104 | This will provide a `target-c` binary which you can use as a compiled substitute for `main.py`. 105 | 106 | ## Goals 107 | A language which can do things I find interesting, and the tools necessary to execute it. 108 | 109 | - [X] Define the language (ongoing) 110 | - [X] Lexer 111 | - [X] Parser 112 | - [X] Bytecode compiler 113 | - [X] Interpreter/VM 114 | - [X] Compiles to RPython (mostly but sometimes broken) 115 | - [ ] JIT 116 | - [X] Immutability (initial support anyway) 117 | - [X] First-class functions (sort of) 118 | - [ ] Structs and traits 119 | - [ ] FP concepts like map/reduce 120 | - [ ] Pattern matching 121 | - [ ] Concurrency via message passing 122 | - [ ] Standard library 123 | 124 | ## Status updates 125 | 126 | I've abandoned this as I'm no longer interested in building a language on RPython. You can follow me on twitter at [@joshsharp](https://twitter.com/joshsharp) if you're interested in the other stuff I'm working on. 127 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshsharp/python-braid/dc7d87668aa426e6b76a1d072cd4bd89b9b7dc3e/__init__.py -------------------------------------------------------------------------------- /ast.py: -------------------------------------------------------------------------------- 1 | 2 | from rply.token import BaseBox 3 | from errors import * 4 | 5 | # all token types inherit rply's basebox as rpython needs this 6 | # these classes represent our Abstract Syntax Tree 7 | # TODO: deprecate eval() as we move to compiling and then interpreting 8 | 9 | class Program(BaseBox): 10 | 11 | def __init__(self, statement): 12 | self.statements = [] 13 | self.statements.append(statement) 14 | 15 | def add_statement(self, statement): 16 | self.statements.insert(0,statement) 17 | 18 | def eval(self, env): 19 | #print "count: %s" % len(self.statements) 20 | result = None 21 | for statement in self.statements: 22 | result = statement.eval(env) 23 | #print result.to_string() 24 | return result 25 | 26 | def rep(self): 27 | result = 'Program(' 28 | for statement in self.statements: 29 | result += '\n\t' + statement.rep() 30 | result += '\n)' 31 | return result 32 | 33 | def get_statements(self): 34 | return self.statements 35 | 36 | 37 | class FunctionDeclaration(BaseBox): 38 | 39 | def __init__(self, name, args, block): 40 | self.name = name 41 | self.args = args 42 | self.block = block 43 | 44 | def eval(self, env): 45 | raise LogicError("Cannot assign to this") 46 | 47 | def rep(self): 48 | result = 'FunctionDeclaration %s (' % self.name 49 | if isinstance(self.args,Array): 50 | for statement in self.args.get_statements(): 51 | result += ' ' + statement.rep() 52 | result += ')' 53 | result += '\n\t(' 54 | if isinstance(self.args,Block): 55 | for statement in self.block.get_statements(): 56 | result += '\n\t' + statement.rep() 57 | result += '\n)' 58 | return result 59 | 60 | def to_string(self): 61 | return "" % self.name 62 | 63 | 64 | class Call(BaseBox): 65 | 66 | def __init__(self, name, args): 67 | self.name = name 68 | self.args = args 69 | 70 | def eval(self, env): 71 | result = Null() 72 | return result 73 | 74 | def rep(self): 75 | result = 'Call %s (' % self.name 76 | if isinstance(self.args,Array): 77 | for statement in self.args.get_statements(): 78 | result += ' ' + statement.rep() 79 | result += ')' 80 | return result 81 | 82 | def to_string(self): 83 | return "" % self.name 84 | 85 | 86 | class Block(BaseBox): 87 | 88 | def __init__(self, statement): 89 | self.statements = [] 90 | self.statements.append(statement) 91 | 92 | def add_statement(self, statement): 93 | self.statements.insert(0,statement) 94 | 95 | def get_statements(self): 96 | return self.statements 97 | 98 | def eval(self, env): 99 | #print "count: %s" % len(self.statements) 100 | result = None 101 | for statement in self.statements: 102 | result = statement.eval(env) 103 | #print result.to_string() 104 | return result 105 | 106 | def rep(self): 107 | result = 'Block(' 108 | for statement in self.statements: 109 | result += '\n\t' + statement.rep() 110 | result += '\n)' 111 | return result 112 | 113 | 114 | class InnerArray(BaseBox): 115 | """ 116 | Only used to handle array values which are passed to Array. 117 | """ 118 | 119 | def __init__(self, statements = None): 120 | self.statements = [] 121 | self.values = [] 122 | if statements: 123 | self.statements = statements 124 | 125 | def push(self, statement): 126 | self.statements.insert(0,statement) 127 | 128 | def append(self, statement): 129 | self.statements.append(statement) 130 | 131 | def extend(self, statements): 132 | self.statements.extend(statements) 133 | 134 | def get_statements(self): 135 | return self.statements 136 | 137 | 138 | class Array(BaseBox): 139 | 140 | def map(self, fun, ls): 141 | nls = [] 142 | for l in ls: 143 | nls.append(fun(l)) 144 | return nls 145 | 146 | def __init__(self, inner): 147 | self.statements = inner.get_statements() 148 | self.values = [] 149 | 150 | def get_statements(self): 151 | return self.statements 152 | 153 | def push(self, statement): 154 | self.statements.insert(0,statement) 155 | 156 | def append(self, statement): 157 | self.statements.append(statement) 158 | 159 | def index(self, i): 160 | if type(i) is Integer: 161 | return self.values[i.value] 162 | if type(i) is Float: 163 | return self.values[int(i.value)] 164 | raise LogicError("Cannot index with that value") 165 | 166 | def add(self, right): 167 | 168 | if type(right) is Array: 169 | result = Array(InnerArray()) 170 | result.values.extend(self.values) 171 | result.values.extend(right.values) 172 | return result 173 | raise LogicError("Cannot add that to array") 174 | 175 | def eval(self, env): 176 | 177 | if len(self.values) == 0: 178 | for statement in self.statements: 179 | self.values.append(statement.eval(env)) 180 | return self 181 | 182 | def rep(self): 183 | result = 'Array(' 184 | result += ",".join(self.map(lambda x: x.rep(),self.statements)) 185 | result += ')' 186 | return result 187 | 188 | def to_string(self): 189 | return '[%s]' % (", ".join(self.map(lambda x: x.to_string(),self.values))) 190 | 191 | 192 | class InnerDict(BaseBox): 193 | """ 194 | Only used to handle array values which are passed to Array. 195 | """ 196 | 197 | def __init__(self, statements = None): 198 | self.data = {} 199 | self.values = {} 200 | if statements: 201 | self.data = statements 202 | 203 | def update(self, key, val): 204 | self.data[key] = val 205 | 206 | def get_data(self): 207 | return self.data 208 | 209 | 210 | class Dict(BaseBox): 211 | 212 | def map(self, fun, ls): 213 | nls = [] 214 | for l in ls: 215 | nls.append(fun(l)) 216 | return nls 217 | 218 | def __init__(self, inner): 219 | self.data = inner.get_data() 220 | self.values = {} 221 | 222 | def get_data(self): 223 | return self.data 224 | 225 | def update(self, key, val): 226 | self.data[key] = val 227 | 228 | def eval(self, env): 229 | 230 | if len(self.values) == 0: 231 | for statement in self.statements: 232 | self.values.append(statement.eval(env)) 233 | return self 234 | 235 | def rep(self): 236 | result = 'Dict(' 237 | result += ",".join(self.map(lambda k: "%s: %s" % (k[0].rep(), k[1].rep()),self.data.iteritems())) 238 | result += ')' 239 | return result 240 | 241 | def to_string(self): 242 | return '{ %s }' % (", ".join(self.map(lambda k: "%s: %s" % (k[0].to_string(), k[1].to_string()),self.values.iteritems()))) 243 | 244 | 245 | class Null(BaseBox): 246 | 247 | def eval(self, env): 248 | return self 249 | 250 | def to_string(self): 251 | return 'null' 252 | 253 | def rep(self): 254 | return 'Null()' 255 | 256 | 257 | class Boolean(BaseBox): 258 | def __init__(self, value): 259 | self.value = bool(value) 260 | 261 | def eval(self, env): 262 | return self 263 | 264 | def rep(self): 265 | return 'Boolean(%s)' % self.value 266 | 267 | 268 | class Integer(BaseBox): 269 | def __init__(self, value): 270 | self.value = int(value) 271 | 272 | def eval(self, env): 273 | return self 274 | 275 | def to_string(self): 276 | return str(self.value) 277 | 278 | def rep(self): 279 | return 'Integer(%s)' % self.value 280 | 281 | 282 | class Float(BaseBox): 283 | def __init__(self, value): 284 | self.value = float(value) 285 | 286 | def eval(self, env): 287 | return self 288 | 289 | def to_string(self): 290 | return str(self.value) 291 | 292 | def rep(self): 293 | return 'Float(%s)' % self.value 294 | 295 | 296 | class String(BaseBox): 297 | def __init__(self, value): 298 | self.value = str(value) 299 | 300 | def eval(self, env): 301 | return self 302 | 303 | def to_string(self): 304 | return '"%s"' % str(self.value) 305 | 306 | def rep(self): 307 | return 'String("%s")' % self.value 308 | 309 | 310 | class Variable(BaseBox): 311 | def __init__(self, name): 312 | self.name = str(name) 313 | self.value = None 314 | 315 | def getname(self): 316 | return str(self.name) 317 | 318 | def eval(self, env): 319 | if env.variables.get(self.name, None) is not None: 320 | self.value = env.variables[self.name].eval(env) 321 | return self.value 322 | raise LogicError("Not yet defined") 323 | 324 | def to_string(self): 325 | return str(self.name) 326 | 327 | def rep(self): 328 | return 'Variable(%s)' % self.name 329 | 330 | 331 | class Print(BaseBox): 332 | def __init__(self, value): 333 | self.value = value 334 | 335 | def eval(self, env): 336 | print self.value.eval(env).to_string() 337 | return Null() #self.value.eval(env) 338 | 339 | def to_string(self): 340 | return "Print" 341 | 342 | def rep(self): 343 | return "Print(%s)" % self.value.rep() 344 | 345 | 346 | class If(BaseBox): 347 | def __init__(self, condition, body, else_body=Null()): 348 | self.condition = condition 349 | self.body = body 350 | self.else_body = else_body 351 | 352 | def eval(self, env): 353 | condition = self.condition.eval(env) 354 | if Boolean(True).equals(condition).value: 355 | 356 | return self.body.eval(env) 357 | else: 358 | 359 | if type(self.else_body) is not Null: 360 | return self.else_body.eval(env) 361 | return Null() 362 | 363 | def rep(self): 364 | return 'If(%s) Then(%s) Else(%s)' % (self.condition.rep(), self.body.rep(), self.else_body.rep()) 365 | 366 | 367 | class While(BaseBox): 368 | def __init__(self, condition, body): 369 | self.condition = condition 370 | self.body = body 371 | 372 | def eval(self, env): 373 | return Null() 374 | 375 | def rep(self): 376 | return 'While(%s) Then(%s)' % (self.condition.rep(), self.body.rep()) 377 | 378 | 379 | class BinaryOp(BaseBox): 380 | def __init__(self, left, right): 381 | self.left = left 382 | self.right = right 383 | 384 | def to_string(self): 385 | return 'BinaryOp' 386 | 387 | def rep(self): 388 | return 'BinaryOp(%s, %s)' % (self.left.rep(), self.right.rep()) 389 | 390 | 391 | class Equal(BinaryOp): 392 | 393 | def rep(self): 394 | return 'Equal(%s, %s)' % (self.left.rep(), self.right.rep()) 395 | 396 | def eval(self, env): 397 | return self.left.eval(env).equals(self.right.eval(env)) 398 | 399 | 400 | class NotEqual(BinaryOp): 401 | 402 | def rep(self): 403 | return 'NotEqual(%s, %s)' % (self.left.rep(), self.right.rep()) 404 | 405 | def eval(self, env): 406 | result = self.left.eval(env).equals(self.right.eval(env)) 407 | result.value = not result.value 408 | return result 409 | 410 | 411 | class GreaterThan(BinaryOp): 412 | 413 | def rep(self): 414 | return 'GreaterThan(%s, %s)' % (self.left.rep(), self.right.rep()) 415 | 416 | def eval(self, env): 417 | return self.left.eval(env).gt(self.right.eval(env)) 418 | 419 | 420 | class LessThan(BinaryOp): 421 | 422 | def rep(self): 423 | return 'LessThan(%s, %s)' % (self.left.rep(), self.right.rep()) 424 | 425 | def eval(self, env): 426 | return self.left.eval(env).lt(self.right.eval(env)) 427 | 428 | 429 | class GreaterThanEqual(BinaryOp): 430 | 431 | def rep(self): 432 | return 'GreaterThan(%s, %s)' % (self.left.rep(), self.right.rep()) 433 | 434 | def eval(self, env): 435 | return self.left.eval(env).gte(self.right.eval(env)) 436 | 437 | 438 | class LessThanEqual(BinaryOp): 439 | 440 | def rep(self): 441 | return 'LessThan(%s, %s)' % (self.left.rep(), self.right.rep()) 442 | 443 | def eval(self, env): 444 | return self.left.eval(env).lte(self.right.eval(env)) 445 | 446 | 447 | class And(BinaryOp): 448 | 449 | def rep(self): 450 | return 'And(%s, %s)' % (self.left.rep(), self.right.rep()) 451 | 452 | def eval(self, env): 453 | one = self.left.eval(env).equals(Boolean(True)) 454 | two = self.right.eval(env).equals(Boolean(True)) 455 | return Boolean(one.value and two.value) 456 | 457 | 458 | class Or(BinaryOp): 459 | 460 | def rep(self): 461 | return 'Or(%s, %s)' % (self.left.rep(), self.right.rep()) 462 | 463 | def eval(self, env): 464 | one = self.left.eval(env).equals(Boolean(True)) 465 | two = self.right.eval(env).equals(Boolean(True)) 466 | # must remember to use inner primitive values 467 | return Boolean(one.value or two.value) 468 | 469 | 470 | class Not(BaseBox): 471 | 472 | def __init__(self, value): 473 | self.value = value 474 | 475 | def rep(self): 476 | return 'Not(%s)' % (self.value.rep()) 477 | 478 | def eval(self, env): 479 | result = self.value.eval(env) 480 | if isinstance(result,Boolean): 481 | return Boolean(not result.value) 482 | raise LogicError("Cannot 'not' that") 483 | 484 | 485 | class Add(BinaryOp): 486 | 487 | def rep(self): 488 | return 'Add(%s, %s)' % (self.left.rep(), self.right.rep()) 489 | 490 | def eval(self, env): 491 | # this needs to call 'add' or something on the left, passing in the right 492 | # cannot check that types are 'primitives' eg. Float like we were doing 493 | # because compound expression like 5 + 5 + 5 will end up with 494 | # Add(Float,Add(Float)) tree. 495 | 496 | return self.left.eval(env).add(self.right.eval(env)) 497 | 498 | class Sub(BinaryOp): 499 | 500 | def rep(self): 501 | return 'Sub(%s, %s)' % (self.left.rep(), self.right.rep()) 502 | 503 | def eval(self, env): 504 | return self.left.eval(env).sub(self.right.eval(env)) 505 | 506 | 507 | class Mul(BinaryOp): 508 | 509 | def rep(self): 510 | return 'Mul(%s, %s)' % (self.left.rep(), self.right.rep()) 511 | 512 | def eval(self, env): 513 | return self.left.eval(env).mul(self.right.eval(env)) 514 | 515 | class Div(BinaryOp): 516 | def rep(self): 517 | return 'Div(%s, %s)' % (self.left.rep(), self.right.rep()) 518 | 519 | def eval(self, env): 520 | return self.left.eval(env).div(self.right.eval(env)) 521 | 522 | class Assignment(BinaryOp): 523 | 524 | def rep(self): 525 | return 'Assignment(%s, %s)' % (self.left.rep(), self.right.rep()) 526 | 527 | def eval(self, env): 528 | if isinstance(self.left,Variable): 529 | 530 | if env.variables.get(self.left.getname(), None) is None: 531 | env.variables[self.left.getname()] = self.right 532 | return self.right.eval(env) 533 | 534 | # otherwise raise error 535 | raise ImmutableError(self.left.getname()) 536 | 537 | else: 538 | raise LogicError("Cannot assign to this") 539 | 540 | 541 | class Index(BinaryOp): 542 | 543 | def rep(self): 544 | return 'Index(%s, %s)' % (self.left.rep(), self.right.rep()) 545 | 546 | def eval(self, env): 547 | 548 | left = self.left.eval(env) 549 | if type(left) is Array: 550 | return left.index(self.right.eval(env)) 551 | if type(left) is String: 552 | return left.index(self.right.eval(env)) 553 | 554 | raise LogicError("Cannot index this") 555 | -------------------------------------------------------------------------------- /braid.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | import sys 3 | import repl, interpreter, parser 4 | import os 5 | 6 | def compile_file(filename): 7 | fd = os.open(filename,os.O_RDONLY,0777) 8 | contents = '' 9 | while True: 10 | buf = os.read(fd, 16) 11 | contents += buf 12 | if buf == '': 13 | # we're done 14 | break 15 | 16 | itpr = interpreter.Interpreter() 17 | contents = contents 18 | return itpr.compile_interpret(parser.parse(contents)).to_string() 19 | 20 | 21 | def begin(args): 22 | if len(args) == 1: 23 | print args[0] 24 | print compile_file(args[0]) 25 | 26 | elif len(args) == 0: 27 | repl.main() 28 | else: 29 | print "I don't understand these arguments" 30 | 31 | 32 | if __name__ == '__main__': 33 | 34 | begin(sys.argv[1:]) 35 | -------------------------------------------------------------------------------- /bytecode.py: -------------------------------------------------------------------------------- 1 | 2 | LOAD_CONST = 1 3 | BINARY_NEQ = 2 4 | PRINT = 3 5 | BINARY_EQ = 4 6 | RETURN = 5 7 | STORE_VARIABLE = 6 8 | LOAD_VARIABLE = 7 9 | 10 | JUMP = 8 11 | JUMP_IF_NOT_ZERO = 9 12 | JUMP_IF_ZERO = 10 13 | 14 | BINARY_ADD = 11 15 | BINARY_SUB = 12 16 | BINARY_LT = 13 17 | BINARY_LTE = 14 18 | BINARY_GT = 15 19 | BINARY_GTE = 16 20 | BINARY_AND = 17 21 | BINARY_OR = 18 22 | NOT = 19 23 | BINARY_MUL = 20 24 | BINARY_DIV = 21 25 | STORE_ARRAY = 22 26 | STORE_DICT = 23 27 | INDEX = 50 28 | CALL = 90 29 | 30 | NO_ARG = -255 31 | 32 | 33 | reverse = { 34 | 1: "LOAD_CONST", 35 | 2: "BINARY_NEQ", 36 | 3: "PRINT", 37 | 4: "BINARY_EQ", 38 | 5: "RETURN", 39 | 6: "STORE_VARIABLE", 40 | 7: "LOAD_VARIABLE", 41 | 8: "JUMP", 42 | 9: "JUMP_IF_NOT_ZERO", 43 | 10: "JUMP_IF_ZERO", 44 | 11: "BINARY_ADD", 45 | 12: "BINARY_SUB", 46 | 13: "BINARY_LT", 47 | 14: "BINARY_LTE", 48 | 15: "BINARY_GT", 49 | 16: "BINARY_GTE", 50 | 17: "BINARY_AND", 51 | 18: "BINARY_OR", 52 | 19: "NOT", 53 | 20: "BINARY_MUL", 54 | 21: "BINARY_DIV", 55 | 22: "STORE_ARRAY", 56 | 23: "STORE_DICT", 57 | 50: "INDEX", 58 | 90: "CALL", 59 | 0: "NO_ARG" 60 | } 61 | 62 | class Bytecode(object): 63 | """Also plundered from Cycy""" 64 | 65 | def __init__(self, instructions, arguments, constants, variables, name): 66 | self.instructions = instructions 67 | self.name = name 68 | self.arguments = arguments or [] 69 | self.constants = constants 70 | self.variables = variables 71 | 72 | def __iter__(self): 73 | """Yield (offset, byte_code, arg) tuples. 74 | The `byte_code` will be one of the constants defined above, 75 | and `arg` may be None. `byte_code` and `arg` will be ints. 76 | """ 77 | offset = 0 78 | while offset < len(self.instructions): 79 | byte_code, arg = self.instructions[offset] 80 | 81 | yield (offset, byte_code, arg) 82 | offset += 1 83 | 84 | def to_string(self): 85 | return 'bytecode' 86 | 87 | def dump(self, pretty=True, indent=0): 88 | lines = [] 89 | lines.append("CONSTANTS:") 90 | for i, v in enumerate(self.constants): 91 | lines.append("%s: %s" % (i, v.to_string())) 92 | 93 | lines.append("VARS:") 94 | for k, v in self.variables.iteritems(): 95 | lines.append("%s: %s => %s" % (k, v.name, v.value.__class__.__name__)) 96 | 97 | 98 | lines.append("CODE:") 99 | 100 | for offset, byte_code, arg in self: 101 | 102 | name = reverse[byte_code] 103 | 104 | str_arg = "" 105 | if arg != NO_ARG: 106 | str_arg = "%s" % arg 107 | 108 | line = "%s%s %s %s" % (' ' * indent, str(offset), name, str_arg) 109 | if pretty: 110 | if byte_code == LOAD_CONST: 111 | line += " => " + self.constants[arg].dump() 112 | elif byte_code == CALL: 113 | line += " => \n" + self.variables[arg].value.dump() 114 | 115 | elif byte_code == RETURN: 116 | if arg: 117 | line += " (top of stack)" 118 | else: 119 | line += " (void return)" 120 | lines.append(line.strip()) 121 | 122 | return "\n".join(lines) 123 | 124 | -------------------------------------------------------------------------------- /compiler.py: -------------------------------------------------------------------------------- 1 | import bytecode, objects, errors 2 | import ast as ast_objects 3 | 4 | class Context(object): 5 | """Shamelessly plundered from Cycy""" 6 | 7 | def __init__(self): 8 | self.instructions = [] 9 | self.constants = [] 10 | self.variables = {} 11 | 12 | #self.NULL = self.register_constant(objects.Null()) 13 | #self.TRUE = self.register_constant(objects.Boolean(True)) 14 | #self.FALSE = self.register_constant(objects.Boolean(False)) 15 | 16 | def emit(self, byte_code, arg=bytecode.NO_ARG): 17 | assert(isinstance(byte_code,int)) 18 | assert(isinstance(arg,int)) 19 | self.instructions.append((byte_code,arg)) 20 | 21 | def register_variable(self, name): 22 | index = len(self.variables) 23 | self.variables[index] = objects.Variable(name,objects.Null()) 24 | return index 25 | 26 | def register_constant(self, constant): 27 | index = len(self.constants) 28 | self.constants.append(constant) 29 | return index 30 | 31 | #def register_function(self, function): 32 | # index = len(self.functions) 33 | # self.functions[index] = function 34 | # return index 35 | 36 | def build(self, arguments=[], name=""): 37 | 38 | if isinstance(arguments, ast_objects.Null): 39 | arguments = [] 40 | elif isinstance(arguments, ast_objects.Array): 41 | arguments = [s.getname() for s in arguments.getstatements()] 42 | 43 | return bytecode.Bytecode( 44 | instructions=self.instructions, 45 | name=name, 46 | arguments=arguments, 47 | constants=self.constants, 48 | variables=self.variables, 49 | ) 50 | 51 | 52 | def compile_program(context, ast): 53 | assert(isinstance(ast,ast_objects.Program)) 54 | for statement in ast.get_statements(): 55 | compile_any(context,statement) 56 | 57 | 58 | def compile_functiondeclaration(context, ast): 59 | assert(isinstance(ast,ast_objects.FunctionDeclaration)) 60 | # new context, but need access to outer context 61 | ctx = Context() 62 | 63 | fn_index = context.register_variable(ast.name) 64 | 65 | for v in context.constants: 66 | ctx.constants.append(v) 67 | for k, v in context.variables.iteritems(): 68 | ctx.variables[k] = v 69 | 70 | indexes = [] 71 | 72 | if type(ast.args) is not ast_objects.Null: 73 | 74 | for arg in reversed(ast.args.get_statements()): 75 | assert(isinstance(arg,ast_objects.Variable)) 76 | name = str(arg.getname()) 77 | index = ctx.register_variable(name) 78 | indexes.append(index) 79 | #context.emit(bytecode.STORE_VARIABLE, index) 80 | 81 | compile_block(ctx,ast.block) 82 | 83 | fn = ctx.build(indexes, name=ast.name) 84 | context.variables[fn_index] = objects.Variable(ast.name,objects.Function(ast.name,fn)) 85 | ctx.variables[fn_index] = objects.Variable(ast.name,objects.Function(ast.name,fn)) 86 | context.emit(bytecode.LOAD_VARIABLE,fn_index) 87 | 88 | 89 | def compile_call(context, ast): 90 | assert(isinstance(ast,ast_objects.Call)) 91 | # this is a call really 92 | 93 | if type(ast.args) is ast_objects.InnerArray: 94 | 95 | for arg in ast.args.get_statements(): 96 | compile_any(context, arg) 97 | 98 | index = -1 99 | for k, v in context.variables.iteritems(): 100 | assert(isinstance(v, objects.Variable)) 101 | #assert(isinstance(v.value, objects.Function)) 102 | if v.name == ast.name: 103 | index = k 104 | if index > -1: 105 | context.emit(bytecode.CALL, index) 106 | else: 107 | raise Exception("function %s does not exist" % ast.name) 108 | 109 | 110 | def compile_block(context, ast): 111 | assert(isinstance(ast,ast_objects.Block)) 112 | for statement in ast.get_statements(): 113 | compile_any(context,statement) 114 | 115 | 116 | def compile_innerarray(context, ast): 117 | assert(isinstance(ast,ast_objects.InnerArray)) 118 | # this is used for function args I think 119 | for statement in ast.get_statements(): 120 | compile_any(context,statement) 121 | 122 | 123 | def compile_array(context, ast): 124 | assert(isinstance(ast,ast_objects.Array)) 125 | length = len(ast.get_statements()) 126 | for statement in reversed(ast.get_statements()): 127 | compile_any(context,statement) 128 | context.emit(bytecode.STORE_ARRAY,length) 129 | 130 | 131 | def compile_innerdict(context, ast): 132 | assert(isinstance(ast,ast_objects.InnerDict)) 133 | for key, val in ast.get_data().iteritems(): 134 | compile_any(context,key) 135 | compile_any(context,val) 136 | 137 | 138 | def compile_dict(context, ast): 139 | assert(isinstance(ast,ast_objects.Dict)) 140 | length = len(ast.get_data().keys()) 141 | for key, val in ast.get_data().iteritems(): 142 | compile_any(context,key) 143 | compile_any(context,val) 144 | context.emit(bytecode.STORE_DICT,length) 145 | 146 | 147 | def compile_null(context, ast): 148 | assert(isinstance(ast,ast_objects.Null)) 149 | context.emit(bytecode.LOAD_CONST,0) 150 | 151 | 152 | def compile_boolean(context, ast): 153 | assert(isinstance(ast,ast_objects.Boolean)) 154 | value = objects.Boolean(ast.value) 155 | index = context.register_constant(value) 156 | 157 | context.emit(bytecode.LOAD_CONST,index) 158 | 159 | 160 | def compile_integer(context, ast): 161 | assert(isinstance(ast,ast_objects.Integer)) 162 | value = objects.Integer(ast.value) 163 | index = context.register_constant(value) 164 | 165 | context.emit(bytecode.LOAD_CONST,index) 166 | 167 | 168 | def compile_float(context, ast): 169 | assert(isinstance(ast,ast_objects.Float)) 170 | value = objects.Float(ast.value) 171 | index = context.register_constant(value) 172 | 173 | context.emit(bytecode.LOAD_CONST,index) 174 | 175 | 176 | def compile_string(context, ast): 177 | assert(isinstance(ast,ast_objects.String)) 178 | value = objects.String(ast.value) 179 | index = context.register_constant(value) 180 | context.emit(bytecode.LOAD_CONST,index) 181 | 182 | 183 | def compile_variable(context, ast): 184 | assert(isinstance(ast,ast_objects.Variable)) 185 | index = None 186 | for k, v in context.variables.iteritems(): 187 | assert(isinstance(v,objects.Variable)) 188 | if v.name == ast.getname(): 189 | index = k 190 | break 191 | if index is not None: 192 | context.emit(bytecode.LOAD_VARIABLE,index) 193 | else: 194 | raise Exception("Variable %s not yet defined" % ast.getname()) 195 | 196 | 197 | def compile_print(context, ast): 198 | assert(isinstance(ast,ast_objects.Print)) 199 | compile_any(context,ast.value) 200 | context.emit(bytecode.PRINT,bytecode.NO_ARG) 201 | 202 | 203 | def compile_if(context, ast): 204 | # compile the condition 205 | assert(isinstance(ast, ast_objects.If)) 206 | compile_any(context, ast.condition) 207 | # add true 208 | t = context.register_constant(objects.Boolean(True)) 209 | context.emit(bytecode.LOAD_CONST,t) 210 | # compare the condition to true 211 | context.emit(bytecode.BINARY_EQ,bytecode.NO_ARG) 212 | 213 | # condition: 214 | # jump if zero (false): false block 215 | # true block 216 | # jump to end 217 | # false block 218 | 219 | # TODO: let jump target labels, not values! store the name of the jump 220 | # in a constant and then reference that constant name, which can contain the 221 | # jump position and be updated if need be 222 | 223 | context.emit(bytecode.JUMP_IF_ZERO,0) 224 | # make a note of the instruction we'll have to change 225 | false_jump = len(context.instructions) - 1 226 | # then add the true block 227 | compile_any(context,ast.body) 228 | # then a jump from the true block to after the false block 229 | context.emit(bytecode.JUMP,0) 230 | # the start of the false block is the current length 231 | false_block = len(context.instructions) 232 | # so set the false block jump to that point 233 | context.instructions[false_jump] = (context.instructions[false_jump][0],false_block) 234 | compile_any(context,ast.else_body) 235 | # get the point we're at now 236 | after_false = len(context.instructions) 237 | # then change the true jump to point here 238 | context.instructions[false_block-1] = (context.instructions[false_block-1][0], after_false) 239 | 240 | 241 | def compile_while(context, ast): 242 | assert(isinstance(ast, ast_objects.While)) 243 | condition_pos = len(context.instructions) 244 | compile_any(context, ast.condition) 245 | # add true 246 | t = context.register_constant(objects.Boolean(True)) 247 | context.emit(bytecode.LOAD_CONST,t) 248 | # compare the condition to true 249 | context.emit(bytecode.BINARY_EQ,bytecode.NO_ARG) 250 | 251 | # condition: 252 | # jump if zero (false): after the block 253 | # block 254 | # jump to condition 255 | 256 | # this will point to after the end 257 | context.emit(bytecode.JUMP_IF_ZERO,0) 258 | # make a note of the instruction we'll have to change 259 | false_jump = len(context.instructions) - 1 260 | compile_any(context,ast.body) 261 | context.emit(bytecode.JUMP,condition_pos) 262 | after_block = len(context.instructions) 263 | context.instructions[false_jump] = (context.instructions[false_jump][0],after_block) 264 | 265 | 266 | def compile_equal(context, ast): 267 | assert(isinstance(ast,ast_objects.Equal)) 268 | compile_any(context, ast.left) 269 | compile_any(context, ast.right) 270 | context.emit(bytecode.BINARY_EQ,bytecode.NO_ARG) 271 | 272 | 273 | def compile_equal(context, ast): 274 | assert(isinstance(ast,ast_objects.Equal)) 275 | compile_any(context, ast.left) 276 | compile_any(context, ast.right) 277 | context.emit(bytecode.BINARY_EQ,bytecode.NO_ARG) 278 | 279 | 280 | def compile_notequal(context, ast): 281 | assert(isinstance(ast,ast_objects.NotEqual)) 282 | compile_any(context, ast.left) 283 | compile_any(context, ast.right) 284 | context.emit(bytecode.BINARY_NEQ,bytecode.NO_ARG) 285 | 286 | 287 | def compile_greaterthan(context, ast): 288 | assert(isinstance(ast,ast_objects.GreaterThan)) 289 | compile_any(context, ast.left) 290 | compile_any(context, ast.right) 291 | context.emit(bytecode.BINARY_GT,bytecode.NO_ARG) 292 | 293 | 294 | def compile_greaterthanequal(context, ast): 295 | assert(isinstance(ast,ast_objects.GreaterThanEqual)) 296 | compile_any(context, ast.left) 297 | compile_any(context, ast.right) 298 | context.emit(bytecode.BINARY_GTE,bytecode.NO_ARG) 299 | 300 | 301 | def compile_lessthan(context, ast): 302 | assert(isinstance(ast,ast_objects.LessThan)) 303 | compile_any(context, ast.left) 304 | compile_any(context, ast.right) 305 | context.emit(bytecode.BINARY_LT,bytecode.NO_ARG) 306 | 307 | 308 | def compile_lessthanequal(context, ast): 309 | assert(isinstance(ast,ast_objects.LessThanEqual)) 310 | compile_any(context, ast.left) 311 | compile_any(context, ast.right) 312 | context.emit(bytecode.BINARY_LTE,bytecode.NO_ARG) 313 | 314 | 315 | def compile_and(context, ast): 316 | assert(isinstance(ast,ast_objects.And)) 317 | compile_any(context, ast.left) 318 | compile_any(context, ast.right) 319 | context.emit(bytecode.BINARY_AND,bytecode.NO_ARG) 320 | 321 | 322 | def compile_or(context, ast): 323 | assert(isinstance(ast,ast_objects.Or)) 324 | compile_any(context, ast.left) 325 | compile_any(context, ast.right) 326 | context.emit(bytecode.BINARY_OR,bytecode.NO_ARG) 327 | 328 | 329 | def compile_not(context, ast): 330 | assert(isinstance(ast,ast_objects.Not)) 331 | compile_any(context, ast.value) 332 | context.emit(bytecode.NOT,bytecode.NO_ARG) 333 | 334 | 335 | def compile_add(context, ast): 336 | assert(isinstance(ast,ast_objects.Add)) 337 | compile_any(context, ast.left) 338 | compile_any(context, ast.right) 339 | context.emit(bytecode.BINARY_ADD,bytecode.NO_ARG) 340 | 341 | 342 | def compile_sub(context, ast): 343 | assert(isinstance(ast,ast_objects.Sub)) 344 | compile_any(context, ast.left) 345 | compile_any(context, ast.right) 346 | context.emit(bytecode.BINARY_SUB,bytecode.NO_ARG) 347 | 348 | 349 | def compile_mul(context, ast): 350 | assert(isinstance(ast,ast_objects.Mul)) 351 | compile_any(context, ast.left) 352 | compile_any(context, ast.right) 353 | context.emit(bytecode.BINARY_MUL,bytecode.NO_ARG) 354 | 355 | 356 | def compile_div(context, ast): 357 | assert(isinstance(ast,ast_objects.Div)) 358 | compile_any(context, ast.left) 359 | compile_any(context, ast.right) 360 | context.emit(bytecode.BINARY_DIV,bytecode.NO_ARG) 361 | 362 | 363 | def compile_assignment(context, ast): 364 | assert(isinstance(ast,ast_objects.Assignment)) 365 | assert(isinstance(ast.left,ast_objects.Variable)) 366 | 367 | name = str(ast.left.getname()) 368 | index = None 369 | for k, v in context.variables.iteritems(): 370 | if v.name == name: 371 | index = k 372 | raise errors.ImmutableError(name) 373 | 374 | if index is None: 375 | index = context.register_variable(name) 376 | compile_any(context, ast.right) 377 | context.emit(bytecode.STORE_VARIABLE, index) 378 | 379 | 380 | def compile_argument(context, name): 381 | 382 | index = context.register_variable(str(name)) 383 | context.emit(bytecode.STORE_VARIABLE, index) 384 | 385 | 386 | def compile_index(context, ast): 387 | assert(isinstance(ast,ast_objects.Index)) 388 | compile_any(context, ast.right) 389 | compile_any(context, ast.left) 390 | context.emit(bytecode.INDEX,bytecode.NO_ARG) 391 | 392 | 393 | def compile_any(context, ast): 394 | typename = ast.__class__.__name__.lower() 395 | #funcname = "compile_%s" % typename.lower() 396 | 397 | funcs = { 398 | "index":compile_index, 399 | "div":compile_div, 400 | "sub":compile_sub, 401 | "mul":compile_mul, 402 | "assignment":compile_assignment, 403 | "argument":compile_argument, 404 | "add":compile_add, 405 | "call":compile_call, 406 | "functiondeclaration":compile_functiondeclaration, 407 | "block":compile_block, 408 | "or":compile_or, 409 | "and":compile_and, 410 | "not":compile_not, 411 | #"print":compile_print, 412 | "string":compile_string, 413 | "integer":compile_integer, 414 | "float":compile_float, 415 | "boolean":compile_boolean, 416 | "array":compile_array, 417 | "innerarray":compile_innerarray, 418 | "dict":compile_dict, 419 | "innerdict":compile_dict, 420 | "program":compile_program, 421 | "null":compile_null, 422 | "variable":compile_variable, 423 | "if":compile_if, 424 | "while":compile_while, 425 | "greaterthan":compile_greaterthan, 426 | "greaterthanequal":compile_greaterthanequal, 427 | "lessthan":compile_lessthan, 428 | "lessthanequal":compile_lessthanequal, 429 | "equal":compile_equal, 430 | "notequal":compile_notequal, 431 | } 432 | 433 | func = funcs.get(typename,None) 434 | if func: 435 | func(context, ast) 436 | else: 437 | raise Exception("Cannot compile %s - cannot find it" % (typename)) 438 | 439 | 440 | def compile(ast, context=None): 441 | """ 442 | Begin here. 443 | """ 444 | if context is None: 445 | context = Context() 446 | 447 | compile_any(context, ast) 448 | context.emit(bytecode.RETURN,1) 449 | 450 | return context.build() 451 | -------------------------------------------------------------------------------- /errors.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class LogicError(Exception): 4 | 5 | def __str__(self): 6 | return self.message 7 | 8 | 9 | class UnexpectedEndError(Exception): 10 | 11 | message = 'Unexpected end of statement' 12 | 13 | def __str__(self): 14 | return "Unexpected end of statement" 15 | 16 | 17 | class UnexpectedTokenError(Exception): 18 | 19 | def __init__(self, token): 20 | self.token = token 21 | 22 | def __str__(self): 23 | return self.token 24 | 25 | 26 | class ImmutableError(Exception): 27 | 28 | message = 'Cannot assign to immutable variable %s' 29 | 30 | def __init__(self, name): 31 | self.name = name 32 | 33 | def __str__(self): 34 | return self.message % self.name 35 | -------------------------------------------------------------------------------- /examples/fact.bd: -------------------------------------------------------------------------------- 1 | func fact(a): 2 | if a == 1: 3 | 1 4 | else: 5 | a + fact(a - 1) 6 | end 7 | end 8 | 9 | fact(900) 10 | 11 | -------------------------------------------------------------------------------- /examples/headtail.bd: -------------------------------------------------------------------------------- 1 | func head(arr): 2 | arr[0] 3 | end 4 | 5 | func tail(arr): 6 | let arr2 = arr 7 | arr2 - 0 8 | end 9 | 10 | let a = [0,1,2,3] 11 | print(head(a)) 12 | print(tail(a)) 13 | print(tail(tail(a))) 14 | print("done") 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/nested.bd: -------------------------------------------------------------------------------- 1 | func f(): 2 | let a = 5 3 | func boo(): 4 | print("hi!") 5 | print(a) 6 | end 7 | print("run inner") 8 | boo() 9 | end 10 | 11 | f() # this works 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/nested2.bd: -------------------------------------------------------------------------------- 1 | print("load") 2 | 3 | func make_adder(amount): 4 | func adder(i): 5 | let con = amount 6 | con + i 7 | end 8 | end 9 | 10 | print("begin") 11 | let ad = make_adder(5) 12 | ad(1) # doesn't 13 | -------------------------------------------------------------------------------- /examples/readline.bd: -------------------------------------------------------------------------------- 1 | let a = readline("What is your name? ") 2 | if a == "": 3 | print("Boo that's no help") 4 | else: 5 | print(a + ", that's a nice name :)") 6 | 7 | end 8 | -------------------------------------------------------------------------------- /interpreter.py: -------------------------------------------------------------------------------- 1 | import parser, compiler, bytecode, objects, errors, prelude 2 | 3 | class Interpreter(object): 4 | 5 | def __init__(self): 6 | self.last_bc = '' 7 | self.context = compiler.Context() 8 | self.import_prelude() 9 | 10 | def import_prelude(self): 11 | index = self.context.register_variable("print") 12 | self.context.variables[index] = objects.Variable("print",objects.ExternalFunction("print",prelude.print_fn,1)) 13 | 14 | index = self.context.register_variable("readline") 15 | self.context.variables[index] = objects.Variable("readline",objects.ExternalFunction("readline",prelude.readline,1)) 16 | 17 | 18 | def compile_interpret(self, ast, context=None): 19 | if not context: 20 | context = self.context 21 | byte_code = compiler.compile(ast, context) 22 | self.last_bc = '' 23 | 24 | return self.interpret(byte_code) 25 | 26 | def copy_context(self, code_from, code_to): 27 | for k, v in code_from.variables.iteritems(): 28 | code_to.variables[k] = v 29 | 30 | def interpret(self, byte_code, args=[]): 31 | 32 | pc = 0 # program counter 33 | stack = [] 34 | variables = [objects.Null()] * 255 35 | 36 | assert(len(args) == len(byte_code.arguments)) 37 | 38 | #print "(running %s)" % byte_code.name 39 | 40 | # copy args into inner context 41 | for i in xrange(0,len(args)): 42 | # TODO: this doesn't make sense, indexes change I think? 43 | # Make sure these aren't getting overwritten 44 | index = byte_code.arguments[i] 45 | #print "(arg %s going into %s)" % (args[i].dump(),index) 46 | byte_code.variables[index] = objects.Variable("arg",args[i]) 47 | 48 | 49 | self.last_bc += byte_code.dump(True) 50 | 51 | while pc < len(byte_code.instructions): 52 | 53 | # the type of instruction and arg (a tuple) 54 | opcode, arg = byte_code.instructions[pc] 55 | 56 | #print "(%s %s %s)" % (pc, bytecode.reverse[opcode], arg) 57 | 58 | # then increment 59 | pc += 1 60 | 61 | if opcode == bytecode.LOAD_CONST: 62 | # grab a value from our constants and add to stack 63 | value = byte_code.constants[arg] 64 | stack.append(value) 65 | 66 | elif opcode == bytecode.LOAD_VARIABLE: 67 | var = byte_code.variables[arg] 68 | assert(isinstance(var,objects.Variable)) 69 | #print "- appending value %s" % var.value.dump() 70 | stack.append(var.value) 71 | 72 | elif opcode == bytecode.STORE_VARIABLE: 73 | value = stack.pop() 74 | oldvar = byte_code.variables.get(arg,None) 75 | if isinstance(oldvar,objects.Variable): 76 | byte_code.variables[arg] = objects.Variable(oldvar.name,value) 77 | else: 78 | byte_code.variables[arg] = objects.Variable("arg",value) 79 | stack.append(value) 80 | 81 | elif opcode == bytecode.STORE_ARRAY: 82 | values = [] 83 | for i in xrange(arg): 84 | values.append(stack.pop()) 85 | stack.append(objects.Array(values)) 86 | 87 | elif opcode == bytecode.STORE_DICT: 88 | values = objects.r_dict(objects.dict_eq,objects.dict_hash) 89 | for i in xrange(arg): 90 | values[stack.pop()] = stack.pop() 91 | stack.append(objects.Dict(values)) 92 | 93 | elif opcode == bytecode.PRINT: 94 | value = stack.pop() 95 | print value.to_string() 96 | stack.append(objects.Null()) 97 | 98 | elif opcode == bytecode.INDEX: 99 | left = stack.pop() 100 | right = stack.pop() 101 | result = left.index(right) 102 | stack.append(result) 103 | 104 | elif opcode == bytecode.BINARY_ADD: 105 | right = stack.pop() 106 | left = stack.pop() 107 | result = left.add(right) 108 | stack.append(result) 109 | 110 | elif opcode == bytecode.BINARY_SUB: 111 | right = stack.pop() 112 | left = stack.pop() 113 | result = left.sub(right) 114 | stack.append(result) 115 | 116 | elif opcode == bytecode.BINARY_MUL: 117 | right = stack.pop() 118 | left = stack.pop() 119 | result = left.mul(right) 120 | stack.append(result) 121 | 122 | elif opcode == bytecode.BINARY_DIV: 123 | right = stack.pop() 124 | left = stack.pop() 125 | result = left.div(right) 126 | stack.append(result) 127 | 128 | elif opcode == bytecode.BINARY_NEQ: 129 | right = stack.pop() 130 | left = stack.pop() 131 | result = left.equals(right) 132 | result.boolvalue = not result.boolvalue 133 | stack.append(result) 134 | 135 | elif opcode == bytecode.BINARY_EQ: 136 | right = stack.pop() 137 | left = stack.pop() 138 | result = left.equals(right) 139 | stack.append(result) 140 | 141 | elif opcode == bytecode.BINARY_GT: 142 | right = stack.pop() 143 | left = stack.pop() 144 | result = left.gt(right) 145 | stack.append(result) 146 | 147 | elif opcode == bytecode.BINARY_GTE: 148 | right = stack.pop() 149 | left = stack.pop() 150 | result = left.gte(right) 151 | stack.append(result) 152 | 153 | elif opcode == bytecode.BINARY_LT: 154 | right = stack.pop() 155 | left = stack.pop() 156 | result = left.lt(right) 157 | stack.append(result) 158 | 159 | elif opcode == bytecode.BINARY_LTE: 160 | right = stack.pop() 161 | left = stack.pop() 162 | result = left.lte(right) 163 | stack.append(result) 164 | 165 | elif opcode == bytecode.RETURN: 166 | if arg == 1: 167 | if len(stack) > 0: 168 | result = stack.pop() 169 | return result 170 | return objects.Null() 171 | 172 | elif opcode == bytecode.JUMP_IF_NOT_ZERO: 173 | val = stack.pop() 174 | assert(isinstance(val,objects.BaseBox)) 175 | 176 | result = val.equals(objects.Boolean(True)) 177 | assert(isinstance(result,objects.Boolean)) 178 | if result.value: 179 | pc = arg 180 | 181 | elif opcode == bytecode.JUMP_IF_ZERO: 182 | val = stack.pop() 183 | assert(isinstance(val,objects.BaseBox)) 184 | result = val.equals(objects.Boolean(True)) 185 | assert(isinstance(result,objects.Boolean)) 186 | if not result.value: 187 | pc = arg 188 | 189 | elif opcode == bytecode.JUMP: 190 | pc = arg 191 | 192 | elif opcode == bytecode.CALL: 193 | assert(isinstance(byte_code.variables[arg],objects.Variable)) 194 | val = byte_code.variables[arg].value 195 | if isinstance(val,objects.Function): 196 | func = val.code 197 | self.copy_context(byte_code,func) 198 | args = [] 199 | if len(func.arguments) > len(stack): 200 | raise Exception("Not enough arguments") 201 | 202 | for i in range(0,len(func.arguments)): 203 | args.append(stack.pop()) 204 | stack.append(self.interpret(func,args)) 205 | elif isinstance(val, objects.ExternalFunction): 206 | # call 207 | func = val.fn 208 | arglen = val.args 209 | args = [] 210 | for i in range(0,arglen): 211 | args.append(stack.pop()) 212 | result = func(args) 213 | stack.append(result) 214 | else: 215 | raise Exception("Not a function") 216 | return stack[len(stack) - 1] 217 | 218 | 219 | -------------------------------------------------------------------------------- /lexer.py: -------------------------------------------------------------------------------- 1 | 2 | from rply import LexerGenerator 3 | try: 4 | import rpython.rlib.rsre.rsre_re as re 5 | except: 6 | import re 7 | 8 | lg = LexerGenerator() 9 | 10 | # build up a set of token names and regexes they match 11 | lg.add('FLOAT', '-?\d+\.\d+') 12 | lg.add('INTEGER', '-?\d+') 13 | lg.add('STRING', '(""".*?""")|(".*?")|(\'.*?\')') 14 | #lg.add('PRINT', 'print(?!\w)') # put this before variable which would otherwise match 15 | lg.add('BOOLEAN', "true(?!\w)|false(?!\w)") 16 | lg.add('IF', 'if(?!\w)') 17 | lg.add('ELSE', 'else(?!\w)') 18 | lg.add('END', 'end(?!\w)') 19 | lg.add('AND', "and(?!\w)") 20 | lg.add('OR', "or(?!\w)") 21 | lg.add('NOT', "not(?!\w)") 22 | lg.add('LET', 'let(?!\w)') 23 | lg.add('FOR', 'for(?!\w)') 24 | lg.add('WHILE', 'while(?!\w)') 25 | lg.add('BREAK', 'break(?!\w)') 26 | lg.add('CONTINUE', 'continue(?!\w)') 27 | lg.add('MATCH', 'match(?!\w)') 28 | lg.add('ENUM', 'enum(?!\w)') 29 | lg.add('NEW', 'new(?!\w)') 30 | lg.add('RETURN', 'return(?!\w)') 31 | lg.add('TYPE', 'type(?!\w)') 32 | lg.add('TYPE_ARRAY', 'array(?!\w)') 33 | lg.add('TYPE_DICT', 'dict(?!\w)') 34 | lg.add('TYPE_INTEGER', 'int(?!\w)') 35 | lg.add('TYPE_STRING', 'str(?!\w)') 36 | lg.add('TYPE_FLOAT', 'float(?!\w)') 37 | lg.add('TYPE_CHAR', 'char(?!\w)') 38 | lg.add('TYPE_LONG', 'long(?!\w)') 39 | lg.add('TYPE_DOUBLE', 'double(?!\w)') 40 | lg.add('RECORD', 'record(?!\w)') 41 | lg.add('FUNCTION', 'func(?!\w)') 42 | lg.add('LAMBDA', 'fn(?!\w)') 43 | lg.add('PRIVATE', 'priv(?!\w)') 44 | lg.add('MODULE', 'mod(?!\w)') 45 | lg.add('TRAIT', 'trait(?!\w)') 46 | lg.add('IMPLEMENT', 'impl(?!\w)') 47 | lg.add('IMPORT', 'import(?!\w)') 48 | lg.add('SEND', 'send(?!\w)') 49 | lg.add('RECEIVE', 'receive(?!\w)') 50 | lg.add('IDENTIFIER', "[a-zA-Z_][a-zA-Z0-9_]*") 51 | lg.add('PLUS', '\+') 52 | lg.add('==', '==') 53 | lg.add('!=', '!=') 54 | lg.add('>=', '>=') 55 | lg.add('<=', '<=') 56 | lg.add('>', '>') 57 | lg.add('<', '<') 58 | lg.add('=', '=') 59 | lg.add('[', '\[') 60 | lg.add(']', '\]') 61 | lg.add('{', '\{') 62 | lg.add('}', '\}') 63 | lg.add('|', '\|') 64 | lg.add(',', ',') 65 | lg.add('DOT', '\.') 66 | lg.add('COLON', ':') 67 | lg.add('MINUS', '-') 68 | lg.add('MUL', '\*') 69 | lg.add('DIV', '/') 70 | lg.add('MOD', '%') 71 | lg.add('(', '\(') 72 | lg.add(')', '\)') 73 | lg.add('NEWLINE', '\n') 74 | 75 | # ignore whitespace 76 | lg.ignore('[ \t\r\f\v]+') 77 | 78 | lexer = lg.build() 79 | 80 | def lex(source): 81 | 82 | comments = r'(#.*)(?:\n|\Z)' 83 | multiline = r'([\s]+)(?:\n)' 84 | 85 | comment = re.search(comments,source) 86 | while comment is not None: 87 | start, end = comment.span(1) 88 | assert start >= 0 and end >= 0 89 | source = source[0:start] + source[end:] #remove string part that was a comment 90 | comment = re.search(comments,source) 91 | 92 | line = re.search(multiline,source) 93 | while line is not None: 94 | start, end = line.span(1) 95 | assert start >= 0 and end >= 0 96 | source = source[0:start] + source[end:] #remove string part that was an empty line 97 | line = re.search(multiline,source) 98 | 99 | #print "source is now: %s" % source 100 | 101 | return lexer.lex(source) 102 | -------------------------------------------------------------------------------- /objects.py: -------------------------------------------------------------------------------- 1 | from rpython.rlib.objectmodel import r_dict, compute_hash 2 | from rply.token import BaseBox 3 | from errors import * 4 | 5 | def dict_eq(key, other): 6 | # we need to implement rdict method to find key equality 7 | return key._eq(other) 8 | 9 | def dict_hash(key): 10 | # we need to implement rdict method to find key equality 11 | return key._hash() 12 | 13 | 14 | class Null(BaseBox): 15 | 16 | def __init__(self): 17 | pass 18 | 19 | def to_string(self): 20 | return "" 21 | 22 | def dump(self): 23 | return "" 24 | 25 | 26 | class Function(BaseBox): 27 | 28 | def __init__(self, name, code): 29 | self.name = name 30 | self.code = code 31 | 32 | def to_string(self): 33 | return "" % self.name 34 | 35 | def dump(self): 36 | return "" % self.name 37 | 38 | def add(self, right): 39 | raise Exception("Cannot add that to function %s" % self.name) 40 | 41 | 42 | class ExternalFunction(BaseBox): 43 | 44 | def __init__(self, name, fn, args): 45 | self.name = name 46 | self.fn = fn 47 | self.args = args 48 | 49 | def to_string(self): 50 | return "" % self.name 51 | 52 | def dump(self): 53 | return "" % self.name 54 | 55 | def add(self, right): 56 | raise Exception("Cannot add that to function %s" % self.name) 57 | 58 | 59 | class Array(BaseBox): 60 | 61 | def __init__(self, args): 62 | self.values = args 63 | 64 | def dump(self): 65 | return self.to_string() 66 | 67 | def map(self, fun, ls): 68 | nls = [] 69 | for l in ls: 70 | nls.append(fun(l)) 71 | return nls 72 | 73 | def push(self, statement): 74 | self.values.insert(0,statement) 75 | 76 | def append(self, statement): 77 | self.values.append(statement) 78 | 79 | def index(self, right): 80 | if isinstance(right, Integer): 81 | return self.values[right.value] 82 | raise LogicError("Cannot index with that value") 83 | 84 | def add(self, right): 85 | 86 | if isinstance(right, Array): 87 | result = Array([]) 88 | result.values.extend(self.values) 89 | result.values.extend(right.values) 90 | return result 91 | raise LogicError("Cannot add that to array") 92 | 93 | def sub(self,right): 94 | if isinstance(right,Integer): 95 | result = [val for val in self.values] 96 | 97 | del result[right.intvalue] 98 | return Array(result) 99 | raise LogicError("Cannot remove that index from array") 100 | 101 | def to_string(self): 102 | return '[%s]' % (", ".join(self.map(lambda x: x.to_string(),self.values))) 103 | 104 | 105 | class Dict(BaseBox): 106 | 107 | def __init__(self, args): 108 | self.values = args 109 | 110 | def dump(self): 111 | return self.to_string() 112 | 113 | def map(self, fun, ls): 114 | nls = [] 115 | for l in ls: 116 | nls.append(fun(l)) 117 | return nls 118 | 119 | def update(self, key, val): 120 | self.values[key] = val 121 | 122 | def index(self, right): 123 | if isinstance(right, Integer): 124 | return self.values[right] 125 | if isinstance(right, String): 126 | return self.values[right] 127 | if isinstance(right, Float): 128 | return self.values[right] 129 | if isinstance(right, Boolean): 130 | return self.values[right] 131 | raise LogicError("Cannot index with that value") 132 | 133 | def add(self, right): 134 | 135 | if isinstance(right, Dict): 136 | result = Dict(r_dict(dict_eq, dict_hash)) 137 | for key, val in self.values.iteritems(): 138 | result.values[key] = val 139 | 140 | for key, val in right.values.iteritems(): 141 | result.values[key] = val 142 | 143 | return result 144 | raise LogicError("Cannot add that to dict") 145 | 146 | def sub(self,right): 147 | result = r_dict(dict_eq, dict_hash) 148 | for key, val in self.values.iteritems(): 149 | result[key] = val 150 | 151 | del result[right] 152 | return Dict(result) 153 | 154 | def to_string(self): 155 | return '{%s}' % (", ".join(self.map(lambda k: "%s: %s" % (k[0].to_string(), k[1].to_string()),self.values.iteritems()))) 156 | 157 | 158 | class Boolean(BaseBox): 159 | 160 | def __init__(self, value): 161 | self.boolvalue = bool(value) 162 | 163 | @property 164 | def value(self): 165 | return bool(self.boolvalue) 166 | 167 | def __hash__(self): 168 | return compute_hash(self.boolvalue) 169 | 170 | def __eq__(self, other): 171 | if(isinstance(other,Boolean)): 172 | return self.boolvalue == other.boolvalue 173 | return False 174 | 175 | def _hash(self): 176 | return compute_hash(self.boolvalue) 177 | 178 | def _eq(self, other): 179 | if(isinstance(other,Boolean)): 180 | return self.boolvalue == other.boolvalue 181 | return False 182 | 183 | def equals(self, right): 184 | if isinstance(right, Boolean): 185 | return Boolean(self.value == right.value) 186 | if isinstance(right, Integer): 187 | return Boolean(self.to_int() == right.value) 188 | if isinstance(right, Float): 189 | return Boolean(self.to_int() == right.value) 190 | else: 191 | return Boolean(False) 192 | raise LogicError("Cannot compare that to boolean") 193 | 194 | def lte(self, right): 195 | if isinstance(right, Boolean): 196 | return Boolean(self.value == right.value) 197 | raise LogicError("Cannot compare that to boolean") 198 | 199 | def lt(self, right): 200 | raise LogicError("Cannot compare boolean that way") 201 | 202 | def gt(self, right): 203 | raise LogicError("Cannot compare boolean that way") 204 | 205 | def gte(self, right): 206 | if isinstance(right, Boolean): 207 | return Boolean(self.value == right.value) 208 | raise LogicError("Cannot compare that to boolean") 209 | 210 | def add(self, right): 211 | raise LogicError("Cannot add that to boolean") 212 | 213 | def sub(self, right): 214 | raise LogicError("Cannot sub that from boolean") 215 | 216 | def mul(self, right): 217 | raise LogicError("Cannot mul that to boolean") 218 | 219 | def div(self, right): 220 | raise LogicError("Cannot div that from boolean") 221 | 222 | def to_string(self): 223 | if self.value: 224 | return "true" 225 | return "false" 226 | 227 | def to_int(self): 228 | if self.value: 229 | return 1 230 | return 0 231 | 232 | def dump(self): 233 | return self.to_string() 234 | 235 | class Integer(BaseBox): 236 | 237 | def __init__(self, value): 238 | self.intvalue = int(value) 239 | 240 | @property 241 | def value(self): 242 | return int(self.intvalue) 243 | 244 | def __hash__(self): 245 | return compute_hash(self.intvalue) 246 | 247 | def __eq__(self, other): 248 | if(isinstance(other,Integer)): 249 | return (self.intvalue) == (other.intvalue) 250 | return False 251 | 252 | def _hash(self): 253 | return compute_hash(self.intvalue) 254 | 255 | def _eq(self, other): 256 | if(isinstance(other,Integer)): 257 | return self.intvalue == other.intvalue 258 | return False 259 | 260 | def to_string(self): 261 | return str(self.value) 262 | 263 | def dump(self): 264 | return str(self.value) 265 | 266 | def equals(self, right): 267 | if isinstance(right,Float): 268 | return Boolean(float(self.value) == right.value) 269 | if isinstance(right, Integer): 270 | return Boolean(self.value == right.value) 271 | if isinstance(right, Boolean): 272 | return Boolean(self.value == right.to_int()) 273 | raise LogicError("Cannot compare that to integer") 274 | 275 | def lte(self, right): 276 | if isinstance(right, Integer): 277 | return Boolean(self.value <= right.value) 278 | if isinstance(right,Float): 279 | return Boolean(float(self.value) <= right.value) 280 | raise LogicError("Cannot compare that to integer") 281 | 282 | def lt(self, right): 283 | if isinstance(right, Integer): 284 | return Boolean(self.value < right.value) 285 | if type(right) is Float: 286 | return Boolean(float(self.value) < right.value) 287 | raise LogicError("Cannot compare integer that way") 288 | 289 | def gt(self, right): 290 | if isinstance(right, Integer): 291 | return Boolean(self.value > right.value) 292 | if isinstance(right,Float): 293 | return Boolean(float(self.value) > right.value) 294 | raise LogicError("Cannot compare integer that way") 295 | 296 | def gte(self, right): 297 | if isinstance(right, Integer): 298 | return Boolean(self.value >= right.value) 299 | if isinstance(right,Float): 300 | return Boolean(float(self.value) >= right.value) 301 | raise LogicError("Cannot compare integer that way") 302 | 303 | def add(self, right): 304 | 305 | if isinstance(right, Integer): 306 | return Integer(self.value + right.value) 307 | if isinstance(right,Float): 308 | return Float(float(self.value) + right.value) 309 | raise LogicError("Cannot add %s to integer" % str(right.__class__.__name__)) 310 | 311 | def sub(self, right): 312 | if isinstance(right, Integer): 313 | return Integer(self.value - right.value) 314 | if isinstance(right,Float): 315 | return Float(float(self.value) - right.value) 316 | raise LogicError("Cannot sub from int") 317 | 318 | def mul(self, right): 319 | if isinstance(right, Integer): 320 | return Integer(self.value * right.value) 321 | if isinstance(right,Float): 322 | return Float(float(self.value) * right.value) 323 | raise LogicError("Cannot mul that to int") 324 | 325 | def div(self, right): 326 | if isinstance(right, Integer): 327 | return Integer(self.value / right.value) 328 | if isinstance(right,Float): 329 | return Float(float(self.value) / right.value) 330 | raise LogicError("Cannot div that with int") 331 | 332 | 333 | class Float(BaseBox): 334 | 335 | def __init__(self, val): 336 | self.floatvalue = float(val) 337 | 338 | @property 339 | def value(self): 340 | return float(self.floatvalue) 341 | 342 | def __hash__(self): 343 | return compute_hash(self.value) 344 | 345 | def __eq__(self, other): 346 | return (self.value) == (other.value) 347 | 348 | 349 | def _hash(self): 350 | return compute_hash(self.floatvalue) 351 | 352 | def _eq(self, other): 353 | if(isinstance(other,Float)): 354 | return self.floatvalue == other.floatvalue 355 | return False 356 | 357 | def to_string(self): 358 | return str(self.value) 359 | 360 | def equals(self, right): 361 | 362 | if isinstance(right,Float): 363 | return Boolean(self.value == right.value) 364 | if isinstance(right, Integer): 365 | return Boolean(self.value == float(right.value)) 366 | if isinstance(right, Boolean): 367 | return Boolean(self.value == float(right.to_int())) 368 | raise LogicError("Cannot compare that to float") 369 | 370 | def lte(self, right): 371 | 372 | if isinstance(right, Integer): 373 | return Boolean(self.value <= float(right.value)) 374 | if isinstance(right,Float): 375 | return Boolean(self.value <= right.value) 376 | raise LogicError("Cannot compare that to integer") 377 | 378 | def lt(self, right): 379 | 380 | if isinstance(right, Integer): 381 | return Boolean(self.value < float(right.value)) 382 | if type(right) is Float: 383 | return Boolean(self.value < right.value) 384 | raise LogicError("Cannot compare integer that way") 385 | 386 | def gt(self, right): 387 | 388 | if isinstance(right, Integer): 389 | return Boolean(self.value > float(right.value)) 390 | if isinstance(right,Float): 391 | return Boolean(self.value > right.value) 392 | raise LogicError("Cannot compare integer that way") 393 | 394 | def gte(self, right): 395 | 396 | if isinstance(right, Integer): 397 | return Boolean(self.value >= float(right.value)) 398 | if isinstance(right,Float): 399 | return Boolean(self.value >= right.value) 400 | raise LogicError("Cannot compare integer that way") 401 | 402 | def add(self, right): 403 | 404 | if isinstance(right, Integer): 405 | return Float(self.value + float(right.value)) 406 | if isinstance(right,Float): 407 | return Float(self.value + right.value) 408 | raise LogicError("Cannot add that to float") 409 | 410 | def sub(self, right): 411 | 412 | if isinstance(right,Float): 413 | return Float(self.value - right.value) 414 | if isinstance(right, Integer): 415 | return Float(self.value - float(right.value)) 416 | raise LogicError("Cannot sub string") 417 | 418 | def mul(self, right): 419 | 420 | if isinstance(right, Integer): 421 | return Float(self.value * float(right.value)) 422 | if isinstance(right,Float): 423 | return Float(self.value * right.value) 424 | raise LogicError("Cannot mul that to float") 425 | 426 | def div(self, right): 427 | 428 | if isinstance(right, Integer): 429 | return Float(self.value / float(right.value)) 430 | if isinstance(right,Float): 431 | return Float(self.value / right.value) 432 | raise LogicError("Cannot div that with float") 433 | 434 | def dump(self): 435 | return str(self.value) 436 | 437 | 438 | class String(BaseBox): 439 | 440 | def __init__(self, value): 441 | self.value = str(value) 442 | 443 | def __hash__(self): 444 | return compute_hash(self.value) 445 | 446 | def __eq__(self, other): 447 | return (self.value) == (other.value) 448 | 449 | def _hash(self): 450 | return compute_hash(self.value) 451 | 452 | def _eq(self, other): 453 | if(isinstance(other,String)): 454 | return self.value == other.value 455 | return False 456 | 457 | def to_string(self): 458 | return str(self.value) 459 | 460 | def equals(self, right): 461 | if isinstance(right, String): 462 | return Boolean(self.value == right.value) 463 | if isinstance(right, Boolean): 464 | length = int(len(self.value) != 0) 465 | return Boolean(length == right.to_int()) 466 | raise LogicError("Cannot compare that to string") 467 | 468 | def lte(self, right): 469 | if isinstance(right, String): 470 | return Boolean(self.value == right.value) 471 | raise LogicError("Cannot compare that to string") 472 | 473 | def lt(self, right): 474 | raise LogicError("Cannot compare string that way") 475 | 476 | def gt(self, right): 477 | raise LogicError("Cannot compare string that way") 478 | 479 | def gte(self, right): 480 | if isinstance(right, String): 481 | return Boolean(self.value == right.value) 482 | raise LogicError("Cannot compare that to string") 483 | 484 | def add(self, right): 485 | 486 | if isinstance(right, Integer): 487 | return String(self.value + str(right.value)) 488 | if isinstance(right,Float): 489 | return String("%s%s" % (self.value,right.value)) 490 | if isinstance(right, String): 491 | return String(self.value + right.value) 492 | raise LogicError("Cannot add that to string") 493 | 494 | def sub(self, right): 495 | if isinstance(right, Integer): 496 | 497 | sli = len(self.value) - right.value 498 | assert(sli >= 0) 499 | return String(self.value[:sli]) 500 | 501 | raise LogicError("Cannot sub string") 502 | 503 | def mul(self, right): 504 | if isinstance(right, Integer): 505 | return String(self.value * right.value) 506 | 507 | raise LogicError("Cannot multiply string with that") 508 | 509 | def div(self, right): 510 | raise LogicError("Cannot divide a string") 511 | 512 | def index(self, right): 513 | if isinstance(right, Integer): 514 | if right.value >= 0: 515 | return String(str(self.value[right.value])) 516 | raise LogicError("Cannot index with that") 517 | 518 | def dump(self): 519 | return str(self.value) 520 | 521 | 522 | class Variable(BaseBox): 523 | 524 | def __init__(self, name, value): 525 | self.name = str(name) 526 | self.value = value 527 | 528 | def dump(self): 529 | return self.value.dump() 530 | -------------------------------------------------------------------------------- /parser.py: -------------------------------------------------------------------------------- 1 | from rply import ParserGenerator 2 | from rply.token import BaseBox, Token 3 | from ast import * 4 | from errors import * 5 | import lexer 6 | import os 7 | 8 | # state instance which gets passed to parser 9 | class ParserState(object): 10 | def __init__(self): 11 | # we want to hold a dict of declared variables 12 | self.variables = {} 13 | 14 | pg = ParserGenerator( 15 | # A list of all token names, accepted by the parser. 16 | ['STRING', 'INTEGER', 'FLOAT', 'IDENTIFIER', 'BOOLEAN', 17 | 'PLUS', 'MINUS', 'MUL', 'DIV', 18 | 'IF', 'ELSE', 'COLON', 'END', 'AND', 'OR', 'NOT', 'LET','WHILE', 19 | '(', ')', '=', '==', '!=', '>=', '<=', '<', '>', '[', ']', ',', 20 | '{','}', 21 | '$end', 'NEWLINE', 'FUNCTION', 22 | 23 | ], 24 | # A list of precedence rules with ascending precedence, to 25 | # disambiguate ambiguous production rules. 26 | precedence=[ 27 | ('left', ['FUNCTION',]), 28 | ('left', ['LET',]), 29 | ('left', ['=']), 30 | ('left', ['[',']',',']), 31 | ('left', ['IF', 'COLON', 'ELSE', 'END', 'NEWLINE','WHILE',]), 32 | ('left', ['AND', 'OR',]), 33 | ('left', ['NOT',]), 34 | ('left', ['==', '!=', '>=','>', '<', '<=',]), 35 | ('left', ['PLUS', 'MINUS',]), 36 | ('left', ['MUL', 'DIV',]), 37 | 38 | ] 39 | ) 40 | 41 | @pg.production("main : program") 42 | def main_program(self, p): 43 | return p[0] 44 | 45 | @pg.production('program : statement_full') 46 | def program_statement(state, p): 47 | return Program(p[0]) 48 | 49 | @pg.production('program : statement_full program') 50 | def program_statement_program(state, p): 51 | if type(p[1]) is Program: 52 | program = p[1] 53 | else: 54 | program = Program(p[12]) 55 | 56 | program.add_statement(p[0]) 57 | return p[1] 58 | 59 | @pg.production('block : statement_full') 60 | def block_expr(state, p): 61 | return Block(p[0]) 62 | 63 | 64 | @pg.production('block : statement_full block') 65 | def block_expr_block(state, p): 66 | if type(p[1]) is Block: 67 | b = p[1] 68 | else: 69 | b = Block(p[1]) 70 | 71 | b.add_statement(p[0]) 72 | return b 73 | 74 | 75 | 76 | @pg.production('statement_full : statement NEWLINE') 77 | @pg.production('statement_full : statement $end') 78 | def statement_full(state, p): 79 | return p[0] 80 | 81 | @pg.production('statement : expression') 82 | def statement_expr(state, p): 83 | return p[0] 84 | 85 | @pg.production('statement : LET IDENTIFIER = expression') 86 | def statement_assignment(state, p): 87 | return Assignment(Variable(p[1].getstr()),p[3]) 88 | 89 | @pg.production('statement : FUNCTION IDENTIFIER ( arglist ) COLON NEWLINE block END') 90 | def statement_func(state, p): 91 | return FunctionDeclaration(p[1].getstr(), Array(p[3]), p[7]) 92 | 93 | @pg.production('statement : FUNCTION IDENTIFIER ( ) COLON NEWLINE block END') 94 | def statement_func_noargs(state, p): 95 | return FunctionDeclaration(p[1].getstr(), Null(), p[6]) 96 | 97 | @pg.production('const : FLOAT') 98 | def expression_float(state, p): 99 | # p is a list of the pieces matched by the right hand side of the rule 100 | return Float(float(p[0].getstr())) 101 | 102 | @pg.production('const : BOOLEAN') 103 | def expression_boolean(state, p): 104 | # p is a list of the pieces matched by the right hand side of the rule 105 | return Boolean(True if p[0].getstr() == 'true' else False) 106 | 107 | @pg.production('const : INTEGER') 108 | def expression_integer(state, p): 109 | return Integer(int(p[0].getstr())) 110 | 111 | @pg.production('const : STRING') 112 | def expression_string(state, p): 113 | return String(p[0].getstr().strip('"\'')) 114 | 115 | @pg.production('expression : const') 116 | def expression_const(state, p): 117 | return p[0] 118 | 119 | @pg.production('expression : [ expression ]') 120 | def expression_array_single(state, p): 121 | return Array(InnerArray([p[1]])) 122 | 123 | @pg.production('expression : [ expressionlist ]') 124 | def expression_array(state, p): 125 | return Array(p[1]) 126 | 127 | @pg.production('expressionlist : expression') 128 | @pg.production('expressionlist : expression ,') 129 | def expressionlist_single(state, p): 130 | return InnerArray([p[0]]) 131 | 132 | @pg.production('expressionlist : expression , expressionlist') 133 | def arglist(state, p): 134 | # expressionlist should already be an InnerArray 135 | p[2].push(p[0]) 136 | return p[2] 137 | 138 | @pg.production('arglist : IDENTIFIER') 139 | @pg.production('arglist : IDENTIFIER ,') 140 | def arglist_single(state, p): 141 | return InnerArray([Variable(p[0].getstr())]) 142 | 143 | @pg.production('arglist : IDENTIFIER , arglist') 144 | def arglist(state, p): 145 | # list should already be an InnerArray 146 | p[2].push(Variable(p[0].getstr())) 147 | return p[2] 148 | 149 | @pg.production('maplist : expression COLON expression') 150 | @pg.production('maplist : expression COLON expression ,') 151 | def maplist_single(state, p): 152 | return InnerDict({ p[0]: p[2] }) 153 | 154 | @pg.production('maplist : expression COLON expression , maplist') 155 | def arglist(state, p): 156 | # expressionlist should already be an InnerArray 157 | p[4].update(p[0],p[2]) 158 | return p[4] 159 | 160 | @pg.production('expression : { maplist }') 161 | def expression_dict(state, p): 162 | return Dict(p[1]) 163 | 164 | @pg.production('expression : expression [ expression ]') 165 | def expression_array_index(state, p): 166 | return Index(p[0],p[2]) 167 | 168 | @pg.production('expression : IF expression COLON statement END') 169 | def expression_if_single_line(state, p): 170 | return If(condition=p[1],body=p[3]) 171 | 172 | @pg.production('expression : IF expression COLON statement ELSE COLON statement END') 173 | def expression_if_else_single_line(state, p): 174 | return If(condition=p[1],body=p[3],else_body=p[6]) 175 | 176 | @pg.production('expression : IF expression COLON NEWLINE block END') 177 | def expression_if(state, p): 178 | return If(condition=p[1],body=p[4]) 179 | 180 | @pg.production('expression : IF expression COLON NEWLINE block ELSE COLON NEWLINE block END') 181 | def expression_if_else(state, p): 182 | return If(condition=p[1],body=p[4],else_body=p[8]) 183 | 184 | @pg.production('expression : WHILE expression COLON NEWLINE block END') 185 | def expression_while(state, p): 186 | return While(condition=p[1],body=p[4]) 187 | 188 | @pg.production('expression : IDENTIFIER') 189 | def expression_variable(state, p): 190 | # cannot return the value of a variable if it isn't yet defined 191 | return Variable(p[0].getstr()) 192 | 193 | @pg.production('expression : IDENTIFIER ( )') 194 | def expression_call_noargs(state, p): 195 | # cannot return the value of a variable if it isn't yet defined 196 | return Call(p[0].getstr(),InnerArray()) 197 | 198 | @pg.production('expression : IDENTIFIER ( expressionlist )') 199 | def expression_call_args(state, p): 200 | # cannot return the value of a variable if it isn't yet defined 201 | return Call(p[0].getstr(),p[2]) 202 | 203 | @pg.production('expression : NOT expression ') 204 | def expression_not(state, p): 205 | return Not(p[1]) 206 | 207 | @pg.production('expression : ( expression )') 208 | def expression_parens(state, p): 209 | # in this case we need parens only for precedence 210 | # so we just need to return the inner expression 211 | return p[1] 212 | 213 | @pg.production('expression : expression PLUS expression') 214 | @pg.production('expression : expression MINUS expression') 215 | @pg.production('expression : expression MUL expression') 216 | @pg.production('expression : expression DIV expression') 217 | def expression_binop(state, p): 218 | left = p[0] 219 | right = p[2] 220 | 221 | if p[1].gettokentype() == 'PLUS': 222 | return Add(left, right) 223 | elif p[1].gettokentype() == 'MINUS': 224 | return Sub(left, right) 225 | elif p[1].gettokentype() == 'MUL': 226 | return Mul(left, right) 227 | elif p[1].gettokentype() == 'DIV': 228 | return Div(left, right) 229 | else: 230 | raise LogicError('Oops, this should not be possible!') 231 | 232 | @pg.production('expression : expression != expression') 233 | @pg.production('expression : expression == expression') 234 | @pg.production('expression : expression >= expression') 235 | @pg.production('expression : expression <= expression') 236 | @pg.production('expression : expression > expression') 237 | @pg.production('expression : expression < expression') 238 | @pg.production('expression : expression AND expression') 239 | @pg.production('expression : expression OR expression') 240 | def expression_equality(state, p): 241 | left = p[0] 242 | right = p[2] 243 | check = p[1] 244 | 245 | if check.gettokentype() == '==': 246 | return Equal(left, right) 247 | elif check.gettokentype() == '!=': 248 | return NotEqual(left, right) 249 | elif check.gettokentype() == '>=': 250 | return GreaterThanEqual(left, right) 251 | elif check.gettokentype() == '<=': 252 | return LessThanEqual(left, right) 253 | elif check.gettokentype() == '>': 254 | return GreaterThan(left, right) 255 | elif check.gettokentype() == '<': 256 | return LessThan(left, right) 257 | elif check.gettokentype() == 'AND': 258 | return And(left, right) 259 | elif check.gettokentype() == 'OR': 260 | return Or(left, right) 261 | else: 262 | raise LogicError("Shouldn't be possible") 263 | 264 | @pg.error 265 | def error_handler(state, token): 266 | # we print our state for debugging porpoises 267 | #print token 268 | pos = token.getsourcepos() 269 | if pos: 270 | raise UnexpectedTokenError(token.gettokentype()) 271 | elif token.gettokentype() == '$end': 272 | raise UnexpectedEndError() 273 | else: 274 | raise UnexpectedTokenError(token.gettokentype()) 275 | 276 | parser = pg.build() 277 | state = ParserState() 278 | 279 | def parse(code, state=state): 280 | result = parser.parse(lexer.lex(code),state) 281 | return result 282 | 283 | -------------------------------------------------------------------------------- /prelude.py: -------------------------------------------------------------------------------- 1 | import os 2 | import objects 3 | 4 | def print_fn(args): 5 | 6 | print args[0].to_string() 7 | return objects.Null() 8 | 9 | 10 | def readline(args): 11 | prompt = args[0] 12 | os.write(1,prompt.to_string()) 13 | 14 | res = '' 15 | while True: 16 | buf = os.read(0, 16) 17 | if not buf: 18 | return objects.String(res) 19 | res += buf 20 | if res[-1] == '\n': 21 | #print res[:-1] == "" 22 | return objects.String(res[:-1]) 23 | -------------------------------------------------------------------------------- /repl.py: -------------------------------------------------------------------------------- 1 | import parser, compiler, interpreter, errors, objects 2 | import sys, locale, os 3 | 4 | 5 | def readline(prompt=None): 6 | 7 | if prompt: 8 | os.write(1,prompt) 9 | 10 | res = '' 11 | while True: 12 | buf = os.read(0, 16) 13 | if not buf: 14 | return res 15 | res += buf 16 | if res[-1] == '\n': 17 | return res[:-1] 18 | 19 | def printresult(result, prefix): 20 | #print type(result) 21 | if result is not None: 22 | print "%s %s" % (prefix, result.to_string()) 23 | else: 24 | print prefix 25 | 26 | def loop(): 27 | intr = interpreter.Interpreter() 28 | #context = compiler.Context() 29 | last = parser.Null() 30 | bytecode = '' 31 | 32 | opening = 0 33 | code = '' 34 | 35 | try: 36 | while True: 37 | 38 | # loop forever until KeyboardInterrupt or other break 39 | if opening > 0: 40 | code += '\n' + readline('... ') 41 | else: 42 | code = readline('>>> ') 43 | if code.strip(' \t\r\n') == '': 44 | continue 45 | if code.strip(' \t\r\n') == ':a': 46 | print last.rep() 47 | continue 48 | if code.strip(' \t\r\n') == ':b': 49 | print bytecode 50 | continue 51 | if code.strip(' \t\r\n') == ':q': 52 | os.write(1, "\n") 53 | break 54 | 55 | try: 56 | ast = parser.parse(code) # at this point we get AST 57 | last = ast # store AST for later inspection 58 | #result = ast.eval(env) 59 | #env.variables['it'] = result 60 | 61 | result = intr.compile_interpret(ast) 62 | bytecode = intr.last_bc 63 | printresult(result,"= ") 64 | 65 | intr.context.instructions = [] 66 | opening = 0 67 | 68 | except parser.UnexpectedEndError as e: 69 | # just keep ignoring this till we break or complete 70 | opening += 1 71 | continue 72 | 73 | except parser.LogicError as e: 74 | opening = 0 # reset 75 | os.write(2, "ERROR: Cannot perform that operation (%s)\n" % e) 76 | continue 77 | 78 | except parser.ImmutableError as e: 79 | opening = 0 # reset 80 | os.write(2, "ERROR: Cannot reassign that (%s)\n" % e) 81 | continue 82 | 83 | except parser.UnexpectedTokenError as e: 84 | opening = 0 # reset 85 | os.write(2, "ERROR: Unexpected '%s'\n" % e.token) 86 | continue 87 | 88 | except Exception as e: 89 | opening = 0 # reset 90 | os.write(2, "ERROR: %s %s\n" % (e.__class__.__name__, str(e))) 91 | continue 92 | 93 | except KeyboardInterrupt: 94 | os.write(1, "\n") 95 | 96 | def main(): 97 | os.write(1, "Braid interpreter\n") 98 | loop() 99 | 100 | if __name__ == '__main__': 101 | main() 102 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | py==1.4.30 2 | pytest==2.7.2 3 | rply==0.7.3 4 | rpython==0.1.4 5 | wheel==0.24.0 6 | -------------------------------------------------------------------------------- /target.py: -------------------------------------------------------------------------------- 1 | import lexer, parser, interpreter, repl, braid 2 | 3 | def entry_point(argv): 4 | braid.begin(argv[1:]) 5 | return 0 6 | 7 | def target(*args): 8 | return entry_point, None 9 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | import unittest 4 | import parser 5 | import sys 6 | from contextlib import contextmanager 7 | from StringIO import StringIO 8 | 9 | class Environment(object): 10 | 11 | def __init__(self): 12 | self.variables = {} 13 | 14 | 15 | @contextmanager 16 | def captured_output(): 17 | new_out, new_err = StringIO(), StringIO() 18 | old_out, old_err = sys.stdout, sys.stderr 19 | try: 20 | sys.stdout, sys.stderr = new_out, new_err 21 | yield sys.stdout, sys.stderr 22 | finally: 23 | sys.stdout, sys.stderr = old_out, old_err 24 | 25 | 26 | class ArithmeticTest(unittest.TestCase): 27 | 28 | def setUp(self): 29 | self.s = parser.ParserState() 30 | self.e = Environment() 31 | 32 | def test_primitives(self): 33 | result = parser.parse('5',self.s).eval(self.e) 34 | self.assertEqual(type(result),parser.Integer) 35 | self.assertEqual(result.to_string(), '5') 36 | 37 | def test_addition(self): 38 | result = parser.parse('5 + 5',self.s).eval(self.e) 39 | self.assertEqual(type(result),parser.Integer) 40 | self.assertEqual(result.to_string(), '10') 41 | 42 | def test_negatives(self): 43 | result = parser.parse('5 + -15',self.s).eval(self.e) 44 | self.assertEqual(result.to_string(), '-10') 45 | 46 | def test_subtraction(self): 47 | result = parser.parse('5 - 10',self.s).eval(self.e) 48 | self.assertEqual(result.to_string(), '-5') 49 | 50 | def test_multiplication(self): 51 | result = parser.parse('5 * 3',self.s).eval(self.e) 52 | self.assertEqual(result.to_string(), '15') 53 | 54 | def test_precedence(self): 55 | result = parser.parse('5 * 3 + 4',self.s).eval(self.e) 56 | self.assertEqual(result.to_string(), '19') 57 | result = parser.parse('5 + 3 * 4',self.s).eval(self.e) 58 | self.assertEqual(result.to_string(), '17') 59 | result = parser.parse('5 * (3 + 4)',self.s).eval(self.e) 60 | self.assertEqual(result.to_string(), '35') 61 | 62 | def test_floats(self): 63 | result = parser.parse('5 * 3.0',self.s).eval(self.e) 64 | self.assertEqual(result.to_string(), '15.0') 65 | 66 | def test_floats2(self): 67 | result = parser.parse('5.0 * -3.0',self.s).eval(self.e) 68 | self.assertEqual(result.to_string(), '-15.0') 69 | 70 | 71 | class StringTest(unittest.TestCase): 72 | 73 | def setUp(self): 74 | self.s = parser.ParserState() 75 | self.e = Environment() 76 | 77 | def test_value(self): 78 | result = parser.parse('"a"',self.s).eval(self.e) 79 | self.assertEqual(result.to_string(), '"a"') 80 | 81 | result = parser.parse("'a'",self.s).eval(self.e) 82 | self.assertEqual(result.to_string(), '"a"') 83 | 84 | result = parser.parse('"""a b"""',self.s).eval(self.e) 85 | self.assertEqual(result.to_string(), '"a b"') 86 | 87 | result = parser.parse('"""a "b" c"""',self.s).eval(self.e) 88 | self.assertEqual(result.to_string(), '"a "b" c"') 89 | 90 | def test_concat(self): 91 | result = parser.parse('"hi" + "yo"',self.s).eval(self.e) 92 | self.assertEqual(result.to_string(), '"hiyo"') 93 | 94 | 95 | class BooleanTest(unittest.TestCase): 96 | 97 | def setUp(self): 98 | self.s = parser.ParserState() 99 | self.e = Environment() 100 | 101 | def test_values(self): 102 | result = parser.parse('true',self.s).eval(self.e) 103 | self.assertEqual(result.to_string(), 'true') 104 | result = parser.parse('false',self.s).eval(self.e) 105 | self.assertEqual(result.to_string(), 'false') 106 | 107 | def test_equality(self): 108 | result = parser.parse('true == true',self.s).eval(self.e) 109 | self.assertEqual(result.to_string(), 'true') 110 | result = parser.parse('false == false',self.s).eval(self.e) 111 | self.assertEqual(result.to_string(), 'true') 112 | result = parser.parse('true == false',self.s).eval(self.e) 113 | self.assertEqual(result.to_string(), 'false') 114 | 115 | def test_inequality(self): 116 | result = parser.parse('true != true',self.s).eval(self.e) 117 | self.assertEqual(result.to_string(), 'false') 118 | result = parser.parse('false != false',self.s).eval(self.e) 119 | self.assertEqual(result.to_string(), 'false') 120 | result = parser.parse('true != false',self.s).eval(self.e) 121 | self.assertEqual(result.to_string(), 'true') 122 | 123 | def test_numbers(self): 124 | result = parser.parse('5 == 5',self.s).eval(self.e) 125 | self.assertEqual(result.to_string(), 'true') 126 | result = parser.parse('5 >= 5',self.s).eval(self.e) 127 | self.assertEqual(result.to_string(), 'true') 128 | result = parser.parse('5 >= 4',self.s).eval(self.e) 129 | self.assertEqual(result.to_string(), 'true') 130 | result = parser.parse('5 > 4',self.s).eval(self.e) 131 | self.assertEqual(result.to_string(), 'true') 132 | result = parser.parse('5 > -4',self.s).eval(self.e) 133 | self.assertEqual(result.to_string(), 'true') 134 | result = parser.parse('-5 < -4',self.s).eval(self.e) 135 | self.assertEqual(result.to_string(), 'true') 136 | result = parser.parse('-5 < 4',self.s).eval(self.e) 137 | self.assertEqual(result.to_string(), 'true') 138 | 139 | result = parser.parse('5.0 == 5.0',self.s).eval(self.e) 140 | self.assertEqual(result.to_string(), 'true') 141 | result = parser.parse('5.0 >= 5.0',self.s).eval(self.e) 142 | self.assertEqual(result.to_string(), 'true') 143 | result = parser.parse('5.0 >= 4.0',self.s).eval(self.e) 144 | self.assertEqual(result.to_string(), 'true') 145 | result = parser.parse('5.0 > 4.0',self.s).eval(self.e) 146 | self.assertEqual(result.to_string(), 'true') 147 | result = parser.parse('5.0 > -4.0',self.s).eval(self.e) 148 | self.assertEqual(result.to_string(), 'true') 149 | result = parser.parse('-5.0 < -4.0',self.s).eval(self.e) 150 | self.assertEqual(result.to_string(), 'true') 151 | result = parser.parse('-5.0 < 4.0',self.s).eval(self.e) 152 | self.assertEqual(result.to_string(), 'true') 153 | 154 | result = parser.parse('5 == 5.0',self.s).eval(self.e) 155 | self.assertEqual(result.to_string(), 'true') 156 | result = parser.parse('5.0 >= 5',self.s).eval(self.e) 157 | self.assertEqual(result.to_string(), 'true') 158 | result = parser.parse('5 >= 4.0',self.s).eval(self.e) 159 | self.assertEqual(result.to_string(), 'true') 160 | result = parser.parse('5.0 > 4',self.s).eval(self.e) 161 | self.assertEqual(result.to_string(), 'true') 162 | result = parser.parse('5 > -4.0',self.s).eval(self.e) 163 | self.assertEqual(result.to_string(), 'true') 164 | result = parser.parse('-5.0 < -4',self.s).eval(self.e) 165 | self.assertEqual(result.to_string(), 'true') 166 | result = parser.parse('-5 < 4.0',self.s).eval(self.e) 167 | self.assertEqual(result.to_string(), 'true') 168 | 169 | def test_strings(self): 170 | result = parser.parse('"5" == "5"',self.s).eval(self.e) 171 | self.assertEqual(result.to_string(), 'true') 172 | result = parser.parse('"a" >= "a"',self.s).eval(self.e) 173 | self.assertEqual(result.to_string(), 'true') 174 | result = parser.parse('"6" <= "6"',self.s).eval(self.e) 175 | self.assertEqual(result.to_string(), 'true') 176 | 177 | 178 | class VariableTest(unittest.TestCase): 179 | 180 | def setUp(self): 181 | self.s = parser.ParserState() 182 | self.e = Environment() 183 | 184 | def test_assignment(self): 185 | result = parser.parse('let a = 50',self.s).eval(self.e) 186 | self.assertEqual(result.to_string(), '50') 187 | result = parser.parse('a',self.s).eval(self.e) 188 | self.assertEqual(type(result), parser.Integer) 189 | self.assertEqual(result.to_string(), '50') 190 | 191 | def test_assignment_zero(self): 192 | result = parser.parse('let k = 0',self.s).eval(self.e) 193 | self.assertEqual(result.to_string(), '0') 194 | result = parser.parse('k',self.s).eval(self.e) 195 | self.assertEqual(result.to_string(), '0') 196 | 197 | def test_assignment_string(self): 198 | result = parser.parse('let l = "hey"',self.s).eval(self.e) 199 | self.assertEqual(result.to_string(), '"hey"') 200 | result = parser.parse('l',self.s).eval(self.e) 201 | self.assertEqual(result.to_string(), '"hey"') 202 | 203 | def test_assignment_bool(self): 204 | result = parser.parse('let o = true',self.s).eval(self.e) 205 | self.assertEqual(result.to_string(), 'true') 206 | result = parser.parse('o == true',self.s).eval(self.e) 207 | self.assertEqual(result.to_string(), "true") 208 | 209 | def test_multiples(self): 210 | result = parser.parse('let m = 50',self.s).eval(self.e) 211 | self.assertEqual(result.to_string(), '50') 212 | result = parser.parse('let n = m + 5',self.s).eval(self.e) 213 | self.assertEqual(result.to_string(), '55') 214 | result = parser.parse('n',self.s).eval(self.e) 215 | self.assertEqual(result.to_string(), '55') 216 | 217 | def test_multiline(self): 218 | code = """let one = 5 219 | let two = 10 220 | let three = one + two 221 | print(three)""" 222 | with captured_output() as (out, err): 223 | result = parser.parse(code,self.s).eval(self.e) 224 | output = out.getvalue().strip() 225 | 226 | 227 | self.assertEqual(result.to_string(), 'null') 228 | self.assertEqual(output, '15') 229 | 230 | class PrintTest(unittest.TestCase): 231 | 232 | def setUp(self): 233 | self.s = parser.ParserState() 234 | self.e = Environment() 235 | 236 | def test_print_value(self): 237 | 238 | with captured_output() as (out, err): 239 | result = parser.parse('print(3)',self.s).eval(self.e) 240 | 241 | output = out.getvalue().strip() 242 | self.assertEqual(output, '3') 243 | 244 | with captured_output() as (out, err): 245 | result = parser.parse('print(3 * 5)',self.s).eval(self.e) 246 | 247 | output = out.getvalue().strip() 248 | self.assertEqual(output, '15') 249 | 250 | def test_print_variable(self): 251 | with captured_output() as (out, err): 252 | result = parser.parse('let a = 50.0',self.s).eval(self.e) 253 | result = parser.parse('print(a)',self.s).eval(self.e) 254 | 255 | output = out.getvalue().strip() 256 | self.assertEqual(output, '50.0') 257 | 258 | 259 | class IfTest(unittest.TestCase): 260 | 261 | def setUp(self): 262 | self.s = parser.ParserState() 263 | self.e = Environment() 264 | 265 | def test_if(self): 266 | 267 | result = parser.parse('if true: true end',self.s).eval(self.e) 268 | self.assertEqual(result.to_string(), 'true') 269 | 270 | result = parser.parse('if false: true end',self.s).eval(self.e) 271 | self.assertEqual(type(result), parser.Null) 272 | 273 | def test_if_else(self): 274 | 275 | result = parser.parse('if true: true else: false end',self.s).eval(self.e) 276 | self.assertEqual(result.to_string(), 'true') 277 | 278 | result = parser.parse('if 5 == 4: true else: false end',self.s).eval(self.e) 279 | self.assertEqual(result.to_string(), 'false') 280 | 281 | def test_multiline(self): 282 | code = """if true: 283 | let g = 5 284 | print(15) 285 | end""" 286 | with captured_output() as (out, err): 287 | result = parser.parse(code,self.s).eval(self.e) 288 | output = out.getvalue().strip() 289 | 290 | self.assertEqual(result.to_string(), 'null') 291 | self.assertEqual(output, '15') 292 | 293 | def test_multiline2(self): 294 | code = """let a = 5 295 | if a == 4: 296 | print(a) 297 | else: 298 | let b = 1 299 | print("no") 300 | end""" 301 | with captured_output() as (out, err): 302 | result = parser.parse(code,self.s).eval(self.e) 303 | output = out.getvalue().strip() 304 | 305 | self.assertEqual(result.to_string(), 'null') 306 | self.assertEqual(output, '"no"') 307 | 308 | def test_assignment(self): 309 | code = """let a = 5 310 | let b = if a == 4: a else: 1 end 311 | print(b)""" 312 | with captured_output() as (out, err): 313 | result = parser.parse(code,self.s).eval(self.e) 314 | output = out.getvalue().strip() 315 | 316 | self.assertEqual(result.to_string(), 'null') 317 | self.assertEqual(output, '1') 318 | 319 | 320 | class CommentTest(unittest.TestCase): 321 | 322 | def setUp(self): 323 | self.s = parser.ParserState() 324 | self.e = Environment() 325 | 326 | def test_if(self): 327 | 328 | result = parser.parse('if true: true end #yo',self.s).eval(self.e) 329 | self.assertEqual(result.to_string(), 'true') 330 | 331 | result = parser.parse('if false: true end # all good',self.s).eval(self.e) 332 | self.assertEqual(type(result), parser.Null) 333 | 334 | code = """if true: # hi 335 | let g = 5 # yes 336 | # good 337 | print(15) 338 | 6 339 | else: 340 | 1 341 | # nah 342 | end # fine""" 343 | with captured_output() as (out, err): 344 | result = parser.parse(code,self.s).eval(self.e) 345 | output = out.getvalue().strip() 346 | 347 | self.assertEqual(result.to_string(), '6') 348 | self.assertEqual(output, '15') 349 | 350 | def test_print_value(self): 351 | 352 | with captured_output() as (out, err): 353 | result = parser.parse('print(3) #hi',self.s).eval(self.e) 354 | 355 | output = out.getvalue().strip() 356 | self.assertEqual(output, '3') 357 | 358 | with captured_output() as (out, err): 359 | result = parser.parse('print(3 * 5) # tessst',self.s).eval(self.e) 360 | 361 | output = out.getvalue().strip() 362 | self.assertEqual(output, '15') 363 | 364 | def test_assignment(self): 365 | result = parser.parse('let a = 50 #hi',self.s).eval(self.e) 366 | self.assertEqual(result.to_string(), '50') 367 | result = parser.parse('a # yes',self.s).eval(self.e) 368 | self.assertEqual(type(result), parser.Integer) 369 | self.assertEqual(result.to_string(), '50') 370 | 371 | def test_multiline(self): 372 | code = """let one = 5 373 | let two = 10 374 | # this next line is important 375 | let three = one + two # whoa 376 | print(three)""" 377 | with captured_output() as (out, err): 378 | result = parser.parse(code,self.s).eval(self.e) 379 | output = out.getvalue().strip() 380 | 381 | 382 | self.assertEqual(result.to_string(), 'null') 383 | self.assertEqual(output, '15') 384 | 385 | def test_concat(self): 386 | result = parser.parse('"hi" + "yo" # wheeee',self.s).eval(self.e) 387 | self.assertEqual(result.to_string(), '"hiyo"') 388 | 389 | def test_numbers(self): 390 | result = parser.parse('5 == 5 # nice',self.s).eval(self.e) 391 | self.assertEqual(result.to_string(), 'true') 392 | result = parser.parse('5 >= 5 # ooh',self.s).eval(self.e) 393 | self.assertEqual(result.to_string(), 'true') 394 | result = parser.parse('true != false # woop',self.s).eval(self.e) 395 | self.assertEqual(result.to_string(), 'true') 396 | 397 | 398 | class ArrayTest(unittest.TestCase): 399 | 400 | def setUp(self): 401 | self.s = parser.ParserState() 402 | self.e = Environment() 403 | 404 | def test_simple(self): 405 | result = parser.parse('[5]',self.s).eval(self.e) 406 | self.assertEqual(result.to_string(), '[5]') 407 | 408 | result = parser.parse('let b = [5,]',self.s).eval(self.e) 409 | self.assertEqual(result.to_string(), '[5]') 410 | 411 | result = parser.parse('[5,6]',self.s).eval(self.e) 412 | self.assertEqual(result.to_string(), '[5, 6]') 413 | 414 | def test_nested(self): 415 | result = parser.parse('[5, [6]]',self.s).eval(self.e) 416 | self.assertEqual(result.to_string(), '[5, [6]]') 417 | 418 | result = parser.parse('[5, [6, 7]]',self.s).eval(self.e) 419 | self.assertEqual(result.to_string(), '[5, [6, 7]]') 420 | 421 | result = parser.parse('let a = [5,[6,[7]]]',self.s).eval(self.e) 422 | self.assertEqual(result.to_string(), '[5, [6, [7]]]') 423 | 424 | 425 | if __name__ == '__main__': 426 | unittest.main() 427 | -------------------------------------------------------------------------------- /test.txt: -------------------------------------------------------------------------------- 1 | let ooh = 5 2 | let eer = "hi" 3 | 4 | func a(hi): 5 | hi 6 | end 7 | 8 | func b(bye): 9 | bye 10 | end 11 | 12 | print(a("hi") + b("bye")) 13 | print(a(5) + b(6)) 14 | 15 | func c(truthy): 16 | if truthy: 17 | print("true") 18 | else: 19 | print("not true") 20 | end 21 | end 22 | 23 | c(true) 24 | c(false) 25 | c(1) 26 | c(0) 27 | c("string") 28 | 29 | func d(one, two): 30 | if (two > one): 31 | two 32 | else: 33 | one 34 | end 35 | end 36 | 37 | print(d(5,6)) 38 | print(d(6,7)) 39 | print(d(5,4)) 40 | print(d(6,7) + d(7,5)) 41 | 42 | #let boo = 0 43 | #while boo < 5: 44 | # print(boo) 45 | # let boo = boo + 1 46 | #end 47 | 48 | "done" 49 | 50 | --------------------------------------------------------------------------------