├── .gitignore ├── LICENSE ├── README.md ├── examples ├── bubble_sort.mb ├── factorial.mb ├── filestat.mb ├── guess.mb ├── hello.mb ├── limited.mb └── pass_statement.mb ├── mamba ├── __init__.py ├── __main__.py ├── ast.py ├── environment.py ├── exceptions.py ├── lexer.py ├── parser.py └── symbol_table.py ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | __pycache__ 3 | *.py[cod] 4 | parser.out 5 | parsetab.py 6 | scripts 7 | *.egg-info 8 | .venv 9 | venv 10 | env 11 | dist/ 12 | build/ 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jens Reidel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Mamba Programming Language 2 | 3 | Mamba is a simple dynamic typed programming language. 4 | Thanks to [maldoinc](https://github.com/maldoinc/mamba) for a great code base to start with and modify! 5 | 6 | Sadly, he's no longer working on it, so I made this fork and will continue updating it. 7 | 8 | I am working on this right now and there are lots of features to add, e.g. modules and limitation on loops and HTTP operations or modules. 9 | 10 | ### Installation requirements ### 11 | 12 | * Python 3.x 13 | * ply 14 | 15 | Install Mamba using `pip3 install git+https://github.com/Gelbpunkt/mamba-lang`. 16 | 17 | It ships with a command line tool to execute files, the recommended ending is `.mb`. 18 | 19 | `mamba my-script.mb` 20 | 21 | The tool features `-h` for help and `-l` or `--limited` for a safe mode that limits memory usage, execution time and I/O. 22 | 23 | `-v (--verbose)` toggles the verbose mode and it will send the AST. 24 | 25 | `--version` will show the mamba version. 26 | 27 | ### Features ### 28 | * Variables 29 | * Functions 30 | * Flow control statements 31 | * Loops (for in, for, inf loop, while) 32 | * Loop exit statement 33 | * Compound operators 34 | * Pythonic sequence (array, string) slicing 35 | 36 | ### Data types ### 37 | * Integer 38 | * Float 39 | * String 40 | * Boolean 41 | * Arrays 42 | 43 | ### Syntax ### 44 | 45 | #### Variables #### 46 | 47 | Variables are dynamically typed, and immediately declared. The syntax is `foo = "bar";` 48 | 49 | ### Operators ### 50 | 51 | Logic: `and` `or` `not` `in` `not in` `>` `>=` `<` `<=` `==` `!=` 52 | 53 | Arithmetic: `+` `-` `*` `/` `**` 54 | 55 | Binary: `~` `^` `|` `&` `>>` `<<` 56 | 57 | Ternary: `test ? true_value : false_value` 58 | 59 | ### Pass Statement 60 | 61 | Doing nothing can be accomplished by leaving it simply out. 62 | 63 | function do_nothing() { 64 | } 65 | 66 | 67 | #### Functions #### 68 | 69 | Functions are declared via the following grammar: 70 | 71 | function func_name( [,] ){ 72 | < statements > 73 | } 74 | 75 | function random(){ 76 | return 4; 77 | } 78 | 79 | The return value is specified with the `return` keyword which, as expected, immediately halts function execution upon being called. Functions can have their private functions which are inaccessible to the outer scope. 80 | 81 | #### Flow Control #### 82 | 83 | Mamba supports `if` statements for flow control via the following syntax 84 | 85 | if < expression > { 86 | < statements > 87 | } 88 | 89 | NB: Brackets are mandatory, while parentheses on the expression are optional. 90 | 91 | 92 | ### Loops ### 93 | 94 | Mamba supports `for` and `while` loops. 95 | 96 | **for syntax** 97 | 98 | for variable in sequence { 99 | < statements > 100 | } 101 | 102 | NB: The sequence field accepts arrays and strings: 103 | 104 | for variable in low -> high { 105 | < statements > 106 | } 107 | 108 | Down to loops are constructed as: 109 | 110 | for variable in high <- low { 111 | < statements > 112 | } 113 | 114 | NB: Loop indexes are inclusive (0-3 is 0, 1, 2, and 3) 115 | 116 | **while syntax** 117 | 118 | while < expression > { 119 | < statements > 120 | } 121 | 122 | There is also the alternative `for` syntax: 123 | 124 | for { 125 | < statements > 126 | } 127 | 128 | Which acts as an infinite loop (internally expressed as a `while true {}` statement). 129 | 130 | All loops can be prematurely exited via the `exit` statement when necessary 131 | 132 | 133 | ### Arrays ### 134 | 135 | Arrays have dynamic length and can be declared via the `[ ... ]` expression 136 | 137 | 138 | ### Printing ### 139 | 140 | Printing is supported via the `say` keyword which accepts a list of values to print. 141 | 142 | ### Standard library ### 143 | 144 | #### 1. Constants ### 145 | 146 | * `e` 147 | * `pi` 148 | 149 | #### 2. Globals 150 | 151 | * `argv` 152 | 153 | #### 3. Functions 154 | 155 | * `ask(prompt)` *shows the prompt and returns the result as a string* 156 | * `int(x [, base])` 157 | * `float(x)` 158 | * `round(value, precision)` 159 | * `abs(x)` 160 | * `log(x)` 161 | * `rand` 162 | * `randrange(lo, hi)` 163 | * `randint(lo, hi)` 164 | * `range(lo, hi, inc)` *behaves like the python 2.x range()* 165 | * `sin(x)` 166 | * `cos(x)` 167 | * `tan(x)` 168 | * `atan(x)` 169 | * `str(x)` 170 | * `substr(str, start, length)` 171 | * `len(str)` 172 | * `pos(substr, str)` 173 | * `upper(str)` 174 | * `lower(str)` 175 | * `replace(str, find, replace)` 176 | * `format(string [, ... ])` 177 | * `chr(x)` 178 | * `ord(x)` 179 | * `time` 180 | * `array_insert(array, index, value)` 181 | * `array_pop(array)` *returns removed value and modifies array* 182 | * `array_push(array, value)` 183 | * `array_remove(array, index)` *returns removed value and modifies array* 184 | * `array_reverse(array)` *reverses array without returning it* 185 | * `array_sort(array)` *sorts the array without returning it* 186 | * `file(filename, mode)` *opens a file and returns the handle* 187 | * `file_close(handle)` 188 | * `file_write(handle, data)` 189 | * `file_read(handle [,size])` 190 | * `file_seek(handle, position)` 191 | * `file_pos(handle)` 192 | * `file_exists(filename)` 193 | -------------------------------------------------------------------------------- /examples/bubble_sort.mb: -------------------------------------------------------------------------------- 1 | function bubble_sort(a){ 2 | l = len(a) - 1; 3 | for i in 0 -> l { 4 | for j in l <- i { 5 | if a[j] < a[j - 1] { 6 | temp = a[j]; 7 | a[j] = a[j - 1]; 8 | a[j - 1] = temp; 9 | } 10 | } 11 | } 12 | 13 | return a; 14 | } 15 | 16 | arr = []; 17 | for i in 1 -> 100 { 18 | array_push(arr, randrange(1, 500)); 19 | } 20 | 21 | say(bubble_sort(arr)); 22 | -------------------------------------------------------------------------------- /examples/factorial.mb: -------------------------------------------------------------------------------- 1 | function fac_i(n){ 2 | r = 1; 3 | 4 | for i in 1 -> n { 5 | r *= i; 6 | } 7 | 8 | return r; 9 | } 10 | 11 | function fac_r(n){ 12 | if n == 0 { 13 | return 1; 14 | } 15 | 16 | return n * fac_r(n - 1); 17 | } 18 | 19 | say(fac_i(6), " ", fac_r(6)); 20 | -------------------------------------------------------------------------------- /examples/filestat.mb: -------------------------------------------------------------------------------- 1 | // returns the file size 2 | function file_size(filename) { 3 | f = file(filename, "r"); 4 | file_read(f); 5 | 6 | return file_pos(f); 7 | } 8 | 9 | function filestat(files) { 10 | for file in files { 11 | say(format("%s is %.2fkb\n", file, file_size(file) / 1024)); 12 | } 13 | } 14 | 15 | filestat(["mamba/lexer.py", "mamba/parser.py", "mamba/ast.py"]); 16 | -------------------------------------------------------------------------------- /examples/guess.mb: -------------------------------------------------------------------------------- 1 | num = randrange(1, 100); 2 | tries = 0; 3 | 4 | say("Guess the number (1 - 100)\n\n"); 5 | 6 | for { 7 | tries += 1; 8 | guess = int(ask(format("Guess (attempt: %d): ", tries))); 9 | 10 | if guess == num { 11 | say(format("You guessed right with %d attempts", tries)); 12 | exit; 13 | } else if guess > num { 14 | say("You guessed too high"); 15 | } else { 16 | say("You guessed too low"); 17 | } 18 | 19 | say("\n"); 20 | } 21 | -------------------------------------------------------------------------------- /examples/hello.mb: -------------------------------------------------------------------------------- 1 | function helloWorld() { 2 | for i in "Hello World" { 3 | say(i); 4 | } 5 | } 6 | helloWorld(); 7 | -------------------------------------------------------------------------------- /examples/limited.mb: -------------------------------------------------------------------------------- 1 | for i in 1 -> 1000000000000000 { 2 | say(i); 3 | } 4 | -------------------------------------------------------------------------------- /examples/pass_statement.mb: -------------------------------------------------------------------------------- 1 | function do_nothing() { 2 | } 3 | 4 | for i in range(5) { 5 | } 6 | 7 | do_nothing(); 8 | -------------------------------------------------------------------------------- /mamba/__init__.py: -------------------------------------------------------------------------------- 1 | import time 2 | import mamba.parser as p 3 | import mamba.ast 4 | import mamba.environment 5 | import mamba.exceptions 6 | import pprint 7 | import sys 8 | import resource 9 | import signal 10 | 11 | __version__ = "0.2.0" 12 | 13 | 14 | def execute( 15 | source, show_ast: bool = False, disable_warnings: bool = True, limited: bool = False 16 | ): 17 | p.disable_warnings = disable_warnings 18 | 19 | if limited: 20 | 21 | def handler(num, frame): 22 | raise TimeoutError("The code execution took too long") 23 | 24 | resource.setrlimit( 25 | resource.RLIMIT_AS, (300000000, 300000000) 26 | ) # limit the memory usage, it's high because mamba uses a lot for parsing 27 | 28 | signal.signal(signal.SIGALRM, handler) 29 | signal.alarm(5) # allow up to 5 seconds of execution 30 | 31 | try: 32 | res = p.get_parser().parse(source) 33 | environment.declare_env(mamba.ast.symbols, limited=limited) 34 | 35 | for node in res.children: 36 | node.eval() 37 | 38 | if show_ast: 39 | print("\n\n" + "=" * 80, " == Syntax tree ==") 40 | 41 | pp = pprint.PrettyPrinter() 42 | pp.pprint(res.children) 43 | pp.pprint(mamba.ast.symbols.table()) 44 | except Exception as e: 45 | print(e.__class__.__name__ + ": " + str(e), file=sys.stderr) 46 | if not disable_warnings: 47 | raise e 48 | -------------------------------------------------------------------------------- /mamba/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import mamba 5 | import sys 6 | import argparse 7 | 8 | 9 | def str2bool(v): 10 | if v.lower() in ("yes", "true", "t", "y", "1"): 11 | return True 12 | elif v.lower() in ("no", "false", "f", "n", "0"): 13 | return False 14 | else: 15 | raise argparse.ArgumentTypeError("Boolean value expected.") 16 | 17 | 18 | parser = argparse.ArgumentParser(description="The Mamba programming language") 19 | parser.add_argument("file", help="The file to execute") 20 | parser.add_argument( 21 | "-l", 22 | "--limited", 23 | type=str2bool, 24 | dest="limited", 25 | help="Start in limited mode", 26 | required=False, 27 | const=True, 28 | default=False, 29 | nargs="?", 30 | ) 31 | parser.add_argument( 32 | "-v", 33 | "--verbose", 34 | type=str2bool, 35 | dest="verbose", 36 | help="Shows the AST", 37 | required=False, 38 | const=True, 39 | default=False, 40 | nargs="?", 41 | ) 42 | parser.add_argument( 43 | "--version", 44 | action="version", 45 | help="Shows the mamba version", 46 | version=f"%(prog)s {mamba.__version__}", 47 | ) 48 | 49 | 50 | def main(): 51 | args = vars(parser.parse_args()) 52 | with open(args["file"]) as f: 53 | mamba.execute(f.read(), limited=args["limited"], show_ast=args["verbose"]) 54 | 55 | 56 | if __name__ == "__main__": 57 | main() 58 | -------------------------------------------------------------------------------- /mamba/ast.py: -------------------------------------------------------------------------------- 1 | import operator 2 | from types import LambdaType 3 | from mamba.exceptions import * 4 | import mamba.symbol_table 5 | 6 | symbols = mamba.symbol_table.SymbolTable() 7 | 8 | 9 | class InstructionList: 10 | def __init__(self, children=None): 11 | if children is None: 12 | children = [] 13 | self.children = children 14 | 15 | def __len__(self): 16 | return len(self.children) 17 | 18 | def __iter__(self): 19 | return iter(self.children) 20 | 21 | def __repr__(self): 22 | return "".format(self.children) 23 | 24 | def eval(self): 25 | """ 26 | Evaluates all the class children and returns the result 27 | of their eval method in a list or returns an ExitStatement 28 | in case one is found 29 | """ 30 | 31 | ret = [] 32 | for n in self: 33 | if isinstance(n, ExitStatement): 34 | return n 35 | 36 | res = n.eval() 37 | 38 | if isinstance(res, ExitStatement): 39 | return res 40 | elif res is not None: 41 | ret.append(res) 42 | 43 | return ret 44 | 45 | 46 | class BaseExpression: 47 | def eval(self): 48 | raise NotImplementedError() 49 | 50 | 51 | class ExitStatement(BaseExpression): 52 | def __iter__(self): 53 | return [] 54 | 55 | def eval(self): 56 | pass 57 | 58 | 59 | class PassStatement(BaseExpression): 60 | def eval(self): 61 | pass 62 | 63 | 64 | class ReturnStatement(ExitStatement): 65 | def __init__(self, expr: BaseExpression): 66 | self.expr = expr 67 | 68 | def __repr__(self): 69 | return "".format(self.expr) 70 | 71 | def eval(self): 72 | return full_eval(self.expr) 73 | 74 | 75 | def full_eval(expr: BaseExpression): 76 | """ 77 | Fully evaluates the passed expression returning it's value 78 | """ 79 | 80 | while isinstance(expr, BaseExpression): 81 | expr = expr.eval() 82 | 83 | return expr 84 | 85 | 86 | class Primitive(BaseExpression): 87 | def __init__(self, value): 88 | self.value = value 89 | 90 | def __repr__(self): 91 | return ''.format(self.value, self.value.__class__) 92 | 93 | def eval(self): 94 | return self.value 95 | 96 | 97 | class Identifier(BaseExpression): 98 | is_function = False 99 | 100 | def __init__(self, name): 101 | self.name = name 102 | 103 | def __repr__(self): 104 | return "".format(self.name) 105 | 106 | def assign(self, val): 107 | if self.is_function: 108 | symbols.set_func(self.name, val) 109 | else: 110 | symbols.set_sym(self.name, val) 111 | 112 | def eval(self): 113 | if self.is_function: 114 | return symbols.get_func(self.name) 115 | 116 | return symbols.get_sym(self.name) 117 | 118 | 119 | class Array(BaseExpression): 120 | def __init__(self, values: InstructionList): 121 | self.values = values 122 | 123 | def __repr__(self): 124 | return "".format( 125 | len(self.values.children), self.values 126 | ) 127 | 128 | def eval(self): 129 | return self.values.eval() 130 | 131 | 132 | class ArrayAccess(BaseExpression): 133 | def __init__(self, array: Identifier, index: BaseExpression): 134 | self.array = array 135 | self.index = index 136 | 137 | def __repr__(self): 138 | return "".format(self.index) 139 | 140 | def eval(self): 141 | return self.array.eval()[self.index.eval()] 142 | 143 | 144 | class ArrayAssign(BaseExpression): 145 | def __init__(self, array: Identifier, index: BaseExpression, value: BaseExpression): 146 | self.array = array 147 | self.index = index 148 | self.value = value 149 | 150 | def __repr__(self): 151 | return "".format( 152 | self.array, self.index, self.value 153 | ) 154 | 155 | def eval(self): 156 | self.array.eval()[self.index.eval()] = self.value.eval() 157 | 158 | 159 | class ArraySlice(BaseExpression): 160 | def __init__( 161 | self, 162 | array: Identifier, 163 | start: BaseExpression = None, 164 | end: BaseExpression = None, 165 | ): 166 | self.array = array 167 | self.start = start 168 | self.end = end 169 | 170 | def __repr__(self): 171 | return "".format( 172 | self.array, self.start, self.end 173 | ) 174 | 175 | def eval(self): 176 | if self.start is not None and self.end is not None: 177 | # access [start : end] 178 | return self.array.eval()[self.start.eval() : self.end.eval()] 179 | 180 | elif self.start is None and self.end is not None: 181 | # access [: end] 182 | return self.array.eval()[: self.end.eval()] 183 | 184 | elif self.start is not None and self.end is None: 185 | # access [start :] 186 | return self.array.eval()[self.start.eval() :] 187 | else: 188 | return self.array.eval()[:] 189 | 190 | 191 | class Assignment(BaseExpression): 192 | def __init__(self, identifier: Identifier, val): 193 | self.identifier = identifier 194 | self.val = val 195 | 196 | def __repr__(self): 197 | return "".format(self.identifier, self.val) 198 | 199 | def eval(self): 200 | if self.identifier.is_function: 201 | self.identifier.assign(self.val) 202 | else: 203 | self.identifier.assign(self.val.eval()) 204 | 205 | 206 | class BinaryOperation(BaseExpression): 207 | __operations = { 208 | "+": operator.add, 209 | "-": operator.sub, 210 | "*": operator.mul, 211 | "**": operator.pow, 212 | "/": operator.truediv, 213 | "%": operator.mod, 214 | ">": operator.gt, 215 | ">=": operator.ge, 216 | "<": operator.lt, 217 | "<=": operator.le, 218 | "==": operator.eq, 219 | "!=": operator.ne, 220 | "and": lambda a, b: a.eval() and b.eval(), 221 | "or": lambda a, b: a.eval() or b.eval(), 222 | "&": operator.and_, 223 | "|": operator.or_, 224 | "^": operator.xor, 225 | ">>": operator.rshift, 226 | "<<": operator.lshift, 227 | } 228 | 229 | def __repr__(self): 230 | return ''.format( 231 | self.left, self.right, self.op 232 | ) 233 | 234 | def __init__(self, left, right, op): 235 | self.left = left 236 | self.right = right 237 | self.op = op 238 | 239 | def eval(self): 240 | left = None 241 | right = None 242 | 243 | try: 244 | # find the operation that needs to be performed 245 | op = self.__operations[self.op] 246 | 247 | # The only lambda operations are logical and/or 248 | # Pass the arguments unevaluated as they will be during the lambda execution 249 | # This implements short circuit boolean evaluation 250 | if isinstance(op, LambdaType): 251 | return op(self.left, self.right) 252 | 253 | # otherwise, straight up call the operation, also save the variables 254 | # in case they are to be used for the exception block 255 | left = self.left.eval() 256 | right = self.right.eval() 257 | return op(left, right) 258 | except TypeError: 259 | fmt = ( 260 | left.__class__.__name__, 261 | left, 262 | self.op, 263 | right.__class__.__name__, 264 | right, 265 | ) 266 | raise InterpreterRuntimeError( 267 | "Unable to apply operation (%s: %s) %s (%s: %s)" % fmt 268 | ) 269 | 270 | 271 | class CompoundOperation(BaseExpression): 272 | __operations = { 273 | "+=": operator.iadd, 274 | "-=": operator.isub, 275 | "/=": operator.itruediv, 276 | "*=": operator.imul, 277 | "%=": operator.imod, 278 | "**=": operator.ipow, 279 | } 280 | 281 | def __init__( 282 | self, identifier: Identifier, modifier: BaseExpression, operation: str 283 | ): 284 | self.identifier = identifier 285 | self.modifier = modifier 286 | self.operation = operation 287 | 288 | def __repr__(self): 289 | fmt = "" 290 | return fmt.format(self.identifier, self.modifier, self.operation) 291 | 292 | def eval(self): 293 | # Express the compound operation as a 'simplified' binary op 294 | # does not return anything as compound operations 295 | # are statements and not expressions 296 | 297 | l = self.identifier.eval() 298 | r = self.modifier.eval() 299 | 300 | try: 301 | self.identifier.assign(self.__operations[self.operation](l, r)) 302 | except TypeError: 303 | fmt = (l.__class__.__name__, l, self.operation, r.__class__.__name__, r) 304 | raise InterpreterRuntimeError( 305 | "Unable to apply operation (%s: %s) %s (%s: %s)" % fmt 306 | ) 307 | 308 | 309 | class UnaryOperation(BaseExpression): 310 | __operations = { 311 | "+": operator.pos, 312 | "-": operator.neg, 313 | "~": operator.inv, 314 | "not": operator.not_, 315 | } 316 | 317 | def __repr__(self): 318 | return "".format( 319 | self.operation, self.expr 320 | ) 321 | 322 | def __init__(self, operation, expr: BaseExpression): 323 | self.operation = operation 324 | self.expr = expr 325 | 326 | def eval(self): 327 | return self.__operations[self.operation](self.expr.eval()) 328 | 329 | 330 | class If(BaseExpression): 331 | def __init__( 332 | self, condition: BaseExpression, truepart: InstructionList, elsepart=None 333 | ): 334 | self.condition = condition 335 | self.truepart = truepart 336 | self.elsepart = elsepart 337 | 338 | def __repr__(self): 339 | return "".format( 340 | self.condition, self.truepart, self.elsepart 341 | ) 342 | 343 | def eval(self): 344 | if self.condition.eval(): 345 | return self.truepart.eval() 346 | elif self.elsepart is not None: 347 | return self.elsepart.eval() 348 | 349 | 350 | class For(BaseExpression): 351 | def __init__( 352 | self, 353 | variable: Identifier, 354 | start: Primitive, 355 | end: Primitive, 356 | asc: bool, 357 | body: InstructionList, 358 | ): 359 | self.variable = variable 360 | self.start = start 361 | self.end = end 362 | self.asc = asc # ascending order 363 | self.body = body 364 | 365 | def __repr__(self): 366 | fmt = "" 367 | return fmt.format( 368 | self.start, "asc" if self.asc else "desc", self.end, self.body 369 | ) 370 | 371 | def eval(self): 372 | if self.asc: 373 | lo = self.start.eval() 374 | hi = self.end.eval() + 1 375 | sign = 1 376 | else: 377 | lo = self.start.eval() 378 | hi = self.end.eval() - 1 379 | sign = -1 380 | 381 | for i in range(lo, hi, sign): 382 | self.variable.assign(i) 383 | 384 | # in case of exit statement prematurely break the loop 385 | if isinstance(self.body.eval(), ExitStatement): 386 | break 387 | 388 | 389 | class ForIn(BaseExpression): 390 | def __init__( 391 | self, variable: Identifier, sequence: BaseExpression, body: InstructionList 392 | ): 393 | self.variable = variable 394 | self.sequence = sequence 395 | self.body = body 396 | 397 | def __repr__(self): 398 | return "".format( 399 | self.variable, self.sequence, self.body 400 | ) 401 | 402 | def eval(self): 403 | for i in self.sequence.eval(): 404 | self.variable.assign(i) 405 | if isinstance(self.body.eval(), ExitStatement): 406 | break 407 | 408 | 409 | class While(BaseExpression): 410 | def __init__(self, condition, body): 411 | self.condition = condition 412 | self.body = body 413 | 414 | def __repr__(self): 415 | return "".format(self.condition, self.body) 416 | 417 | def eval(self): 418 | while self.condition.eval(): 419 | if isinstance(self.body.eval(), ExitStatement): 420 | break 421 | 422 | 423 | class PrintStatement(BaseExpression): 424 | def __init__(self, items: InstructionList): 425 | self.items = items 426 | 427 | def __repr__(self): 428 | return "".format(self.items) 429 | 430 | def eval(self): 431 | # Alternative, but not the default behavior 432 | # print(*self.items.eval(), end='', sep='') 433 | print(*self.items.eval()) 434 | 435 | 436 | class FunctionCall(BaseExpression): 437 | def __init__(self, name: Identifier, params: InstructionList): 438 | self.name = name 439 | self.params = params 440 | 441 | def __repr__(self): 442 | return "".format(self.name, self.params) 443 | 444 | def __eval_builtin_func(self): 445 | func = self.name.eval() 446 | args = [] 447 | 448 | for p in self.params: 449 | args.append(full_eval(p)) 450 | 451 | return func.eval(args) 452 | 453 | def __eval_udf(self): 454 | func = self.name.eval() 455 | args = {} 456 | 457 | # check param count 458 | l1 = len(func.params) 459 | l2 = len(self.params) 460 | 461 | if l1 != l2: 462 | msg = "Invalid number of arguments for function {0}. Expected {1} got {2}" 463 | raise InvalidParamCount(msg.format(self.name.name, l1, l2)) 464 | 465 | # pair the defined parameters in the function signature with 466 | # whatever is being passed on. 467 | # 468 | # On the parameters we only need the name rather than fully evaluating them 469 | for p, v in zip(func.params, self.params): 470 | args[p.name] = full_eval(v) 471 | 472 | return func.eval(args) 473 | 474 | def eval(self): 475 | if isinstance(self.name.eval(), BuiltInFunction): 476 | return self.__eval_builtin_func() 477 | 478 | return self.__eval_udf() 479 | 480 | 481 | class Function(BaseExpression): 482 | def __init__(self, params: InstructionList, body: InstructionList): 483 | self.params = params 484 | self.body = body 485 | 486 | def __repr__(self): 487 | return "".format(self.params, self.body) 488 | 489 | def eval(self, args): 490 | symbols.set_local(True) 491 | 492 | for k, v in args.items(): 493 | symbols.set_sym(k, v) 494 | 495 | try: 496 | ret = self.body.eval() 497 | 498 | if isinstance(ret, ReturnStatement): 499 | return ret.eval() 500 | finally: 501 | symbols.set_local(False) 502 | 503 | return None 504 | 505 | 506 | class BuiltInFunction(BaseExpression): 507 | def __init__(self, func): 508 | self.func = func 509 | 510 | def __repr__(self): 511 | return "".format(self.func) 512 | 513 | def eval(self, args): 514 | return self.func(*args) 515 | 516 | 517 | class InExpression(BaseExpression): 518 | def __init__(self, a: BaseExpression, b: BaseExpression, not_in: bool = False): 519 | self.a = a 520 | self.b = b 521 | self.not_in = not_in 522 | 523 | def __repr__(self): 524 | return "".format(self.a, self.b) 525 | 526 | def eval(self): 527 | if self.not_in: 528 | return self.a.eval() not in self.b.eval() 529 | return self.a.eval() in self.b.eval() 530 | 531 | 532 | class TernaryOperator(BaseExpression): 533 | def __init__( 534 | self, cond: BaseExpression, trueval: BaseExpression, falseval: BaseExpression 535 | ): 536 | self.cond = cond 537 | self.trueval = trueval 538 | self.falseval = falseval 539 | 540 | def __repr__(self): 541 | return "".format( 542 | self.cond, self.trueval, self.falseval 543 | ) 544 | 545 | def eval(self): 546 | return self.trueval.eval() if self.cond.eval() else self.falseval.eval() 547 | -------------------------------------------------------------------------------- /mamba/environment.py: -------------------------------------------------------------------------------- 1 | import os 2 | from timeit import default_timer 3 | import mamba 4 | import mamba.ast as ast 5 | import mamba.symbol_table 6 | import math 7 | import random 8 | import sys 9 | 10 | 11 | def substr(s: str, start: int, length: int): 12 | return s[start : start + length] 13 | 14 | 15 | def str_pos(sub: str, string: str): 16 | return string.index(sub) 17 | 18 | 19 | def str_format(string, *args): 20 | return string % tuple(args) 21 | 22 | 23 | def array_push(arr: list, value): 24 | arr.append(value) 25 | 26 | 27 | def array_pop(arr: list): 28 | return arr.pop() 29 | 30 | 31 | def array_insert(arr: list, i: int, x): 32 | arr.insert(i, x) 33 | 34 | 35 | def array_remove(arr: list, i: int): 36 | return arr.pop(i) 37 | 38 | 39 | def array_reverse(arr: list): 40 | arr.reverse() 41 | 42 | 43 | def array_sort(arr: list): 44 | arr.sort() 45 | 46 | 47 | def file_close(f): 48 | f.close() 49 | 50 | 51 | def file_write(f, data): 52 | f.write(data) 53 | 54 | 55 | def file_read(f, size=None): 56 | return f.read(size) 57 | 58 | 59 | def file_seek(f, offset): 60 | return f.seek(offset) 61 | 62 | 63 | def file_pos(f): 64 | return f.tell() 65 | 66 | 67 | def file_exists(f): 68 | return os.path.isfile(f) 69 | 70 | 71 | def list_range(*args): 72 | return list(range(*args)) 73 | 74 | 75 | def declare_env(s: mamba.symbol_table.SymbolTable, limited: bool): 76 | f = ast.BuiltInFunction 77 | 78 | # "constants" 79 | s.set_sym("pi", math.pi) 80 | s.set_sym("e", math.e) 81 | 82 | # globals 83 | s.set_sym("argv", sys.argv) 84 | 85 | # Built in functions 86 | 87 | # math 88 | s.set_func("int", f(int)) 89 | s.set_func("float", f(float)) 90 | s.set_func("round", f(round)) 91 | s.set_func("abs", f(abs)) 92 | s.set_func("log", f(math.log)) 93 | s.set_func("log2", f(math.log)) 94 | s.set_func("sqrt", f(math.sqrt)) 95 | s.set_func("rand", f(random.random)) 96 | s.set_func("randrange", f(random.randrange)) 97 | s.set_func("randint", f(random.randint)) 98 | s.set_func("range", f(list_range)) 99 | 100 | s.set_func("sin", f(math.sin)) 101 | s.set_func("cos", f(math.cos)) 102 | s.set_func("tan", f(math.tan)) 103 | s.set_func("atan", f(math.atan)) 104 | 105 | # string 106 | s.set_func("substr", f(substr)) 107 | s.set_func("len", f(len)) 108 | s.set_func("pos", f(str_pos)) 109 | s.set_func("upper", f(str.upper)) 110 | s.set_func("lower", f(str.lower)) 111 | s.set_func("replace", f(str.replace)) 112 | s.set_func("format", f(str_format)) 113 | s.set_func("str", f(str)) 114 | 115 | # misc 116 | s.set_func("chr", f(chr)) 117 | s.set_func("ord", f(ord)) 118 | s.set_func("time", f(default_timer)) 119 | 120 | # arrays 121 | s.set_func("array_insert", f(array_insert)) 122 | s.set_func("array_pop", f(array_pop)) 123 | s.set_func("array_push", f(array_push)) 124 | s.set_func("array_remove", f(array_remove)) 125 | s.set_func("array_reverse", f(array_reverse)) 126 | s.set_func("array_sort", f(array_sort)) 127 | 128 | if not limited: 129 | # file 130 | s.set_func("file", f(open)) 131 | s.set_func("file_close", f(file_close)) 132 | s.set_func("file_write", f(file_write)) 133 | s.set_func("file_read", f(file_read)) 134 | s.set_func("file_seek", f(file_seek)) 135 | s.set_func("file_pos", f(file_pos)) 136 | s.set_func("file_exists", f(file_exists)) 137 | 138 | # input 139 | s.set_func("ask", f(input)) 140 | -------------------------------------------------------------------------------- /mamba/exceptions.py: -------------------------------------------------------------------------------- 1 | class InterpreterException(Exception): 2 | def __init__(self, message): 3 | self.message = message 4 | 5 | def __str__(self): 6 | return self.message 7 | 8 | 9 | class SymbolNotFound(InterpreterException): 10 | pass 11 | 12 | 13 | class UnexpectedCharacter(InterpreterException): 14 | pass 15 | 16 | 17 | class ParserSyntaxError(InterpreterException): 18 | pass 19 | 20 | 21 | class DuplicateSymbol(InterpreterException): 22 | pass 23 | 24 | 25 | class InterpreterRuntimeError(InterpreterException): 26 | pass 27 | 28 | 29 | class InvalidParamCount(InterpreterRuntimeError): 30 | pass 31 | -------------------------------------------------------------------------------- /mamba/lexer.py: -------------------------------------------------------------------------------- 1 | import ply.lex as lex 2 | import mamba.exceptions 3 | 4 | reserved = { 5 | "if": "IF", 6 | "else": "ELSE", 7 | "for": "FOR", 8 | "in": "IN", 9 | "while": "WHILE", 10 | "exit": "EXIT", 11 | "function": "FUNCTION", 12 | "return": "RETURN", 13 | "say": "PRINT", 14 | "and": "AND", 15 | "or": "OR", 16 | "not": "NOT", 17 | } 18 | 19 | tokens = [ 20 | "KEYWORD", 21 | "STMT_END", 22 | "EQUALS", 23 | "IDENTIFIER", 24 | "NUM_INT", 25 | "NUM_FLOAT", 26 | "LPAREN", 27 | "RPAREN", 28 | "LBRACK", 29 | "RBRACK", 30 | "COMMA", 31 | "STRING", 32 | "NEWLINE", 33 | "LSQBRACK", 34 | "RSQBRACK", 35 | "COLON", 36 | "QUESTION_MARK", 37 | "PLUS", 38 | "EXP", 39 | "MINUS", 40 | "MUL", 41 | "DIV", 42 | "MOD", 43 | "LSHIFT", 44 | "RSHIFT", 45 | "BIT_AND", 46 | "BIT_OR", 47 | "BIT_XOR", 48 | "BIT_NEG", 49 | "DOUBLE_PLUS", 50 | "DOUBLE_MINUS", 51 | "PLUS_EQ", 52 | "MINUS_EQ", 53 | "MUL_EQ", 54 | "DIV_EQ", 55 | "MOD_EQ", 56 | "EXP_EQ", 57 | "TRUE", 58 | "FALSE", 59 | "EQ", 60 | "NEQ", 61 | "GT", 62 | "GTE", 63 | "LT", 64 | "LTE", 65 | "ARROW_LTR", 66 | "ARROW_RTL", 67 | ] + list(reserved.values()) 68 | 69 | t_COMMA = "," 70 | t_PLUS = r"\+" 71 | t_EXP = r"\*\*" 72 | t_MINUS = "-" 73 | t_MUL = r"\*" 74 | t_DIV = r"/" 75 | t_MOD = "%" 76 | t_STMT_END = ";" 77 | t_QUESTION_MARK = r"\?" 78 | t_EQUALS = "=" 79 | t_ignore_WS = r"\s+" 80 | t_COLON = ":" 81 | t_LPAREN = r"\(" 82 | t_RPAREN = r"\)" 83 | t_LBRACK = "{" 84 | t_RBRACK = "}" 85 | t_LSQBRACK = r"\[" 86 | t_RSQBRACK = r"\]" 87 | t_EQ = "==" 88 | t_NEQ = "!=" 89 | t_GT = ">" 90 | t_GTE = ">=" 91 | t_LT = "<" 92 | t_LTE = "<=" 93 | t_ARROW_LTR = "->" 94 | t_ARROW_RTL = "<-" 95 | t_ignore_COMMENTS = r"//.+" 96 | t_PLUS_EQ = r"\+=" 97 | t_MINUS_EQ = r"-=" 98 | t_MUL_EQ = r"\*=" 99 | t_DIV_EQ = r"/=" 100 | t_MOD_EQ = "%=" 101 | t_EXP_EQ = "\*\*=" 102 | 103 | t_RSHIFT = ">>" 104 | t_LSHIFT = "<<" 105 | t_BIT_AND = r"\&" 106 | t_BIT_OR = r"\|" 107 | t_BIT_XOR = r"\^" 108 | t_BIT_NEG = r"~" 109 | 110 | t_DOUBLE_PLUS = r"\+\+" 111 | t_DOUBLE_MINUS = "--" 112 | 113 | 114 | def t_NEWLINE(t): 115 | r"\n" 116 | t.lexer.lineno += 1 117 | t.lexer.linepos = 0 118 | pass 119 | 120 | 121 | def t_TRUE(t): 122 | "true" 123 | t.value = True 124 | return t 125 | 126 | 127 | def t_FALSE(t): 128 | "false" 129 | t.value = False 130 | return t 131 | 132 | 133 | def t_IDENTIFIER(t): 134 | r"[\$_a-zA-Z]\w*" 135 | 136 | t.type = reserved.get(t.value, t.type) 137 | 138 | return t 139 | 140 | 141 | def t_NUM_FLOAT(t): 142 | r"\d*\.\d+" 143 | t.value = float(t.value) 144 | return t 145 | 146 | 147 | def t_NUM_INT(t): 148 | r"\d+" 149 | t.value = int(t.value) 150 | return t 151 | 152 | 153 | def t_STRING(t): 154 | r'("(?:\\"|.)*?"|\'(?:\\\'|.)*?\')' 155 | 156 | # let's escape the shit 157 | 158 | # make multiple quotes possible like this 159 | t.value = t.value[1:-1] 160 | t.value = bytes(t.value, "utf-8").decode("unicode_escape") 161 | 162 | return t 163 | 164 | 165 | def t_error(t): 166 | raise mamba.exceptions.UnexpectedCharacter( 167 | "Unexpected character '%s' at line %d" % (t.value[0], t.lineno) 168 | ) 169 | 170 | 171 | lexer = lex.lex() 172 | -------------------------------------------------------------------------------- /mamba/parser.py: -------------------------------------------------------------------------------- 1 | import ply.yacc as yacc 2 | import mamba.ast as ast 3 | from mamba.lexer import * 4 | from mamba.exceptions import * 5 | 6 | disable_warnings = False 7 | 8 | precedence = ( 9 | ("left", "NOT"), 10 | ("left", "PLUS", "MINUS"), 11 | ("left", "MUL", "DIV"), 12 | ("left", "EXP", "MOD"), 13 | ("right", "UMINUS"), 14 | ("right", "UPLUS"), 15 | ) 16 | 17 | 18 | def p_statement_list(p): 19 | """ 20 | statement_list : statement 21 | | statement_list statement 22 | | 23 | """ 24 | if len(p) == 2: 25 | p[0] = ast.InstructionList([p[1]]) 26 | elif len(p) == 1: 27 | p[0] = ast.InstructionList([ast.PassStatement()]) 28 | else: 29 | p[1].children.append(p[2]) 30 | p[0] = p[1] 31 | 32 | 33 | def p_statement(p): 34 | """ 35 | statement : identifier 36 | | expression 37 | | if_statement 38 | """ 39 | p[0] = p[1] 40 | 41 | 42 | def p_identifier(p): 43 | """ 44 | identifier : IDENTIFIER 45 | """ 46 | p[0] = ast.Identifier(p[1]) 47 | 48 | 49 | def p_exit_stmt(p): 50 | """ 51 | statement : EXIT STMT_END 52 | """ 53 | p[0] = ast.ExitStatement() 54 | 55 | 56 | def p_primitive(p): 57 | """ 58 | primitive : NUM_INT 59 | | NUM_FLOAT 60 | | STRING 61 | | boolean 62 | """ 63 | if isinstance(p[1], ast.BaseExpression): 64 | p[0] = p[1] 65 | else: 66 | p[0] = ast.Primitive(p[1]) 67 | 68 | 69 | def p_indexable(p): 70 | """ 71 | indexable : identifier 72 | | STRING 73 | | array 74 | """ 75 | if isinstance(p[1], ast.BaseExpression): 76 | p[0] = p[1] 77 | else: # we need to handle the strings 78 | p[0] = ast.Primitive(p[1]) 79 | 80 | 81 | def p_indexable_expression(p): 82 | """ 83 | expression : indexable 84 | """ 85 | p[0] = p[1] 86 | 87 | 88 | def p_binary_op(p): 89 | """ 90 | expression : expression PLUS expression %prec PLUS 91 | | expression MINUS expression %prec MINUS 92 | | expression MUL expression %prec MUL 93 | | expression DIV expression %prec DIV 94 | | expression EXP expression %prec EXP 95 | | expression MOD expression %prec MOD 96 | 97 | | expression BIT_AND expression 98 | | expression BIT_OR expression 99 | | expression BIT_XOR expression 100 | | expression LSHIFT expression 101 | | expression RSHIFT expression 102 | """ 103 | p[0] = ast.BinaryOperation(p[1], p[3], p[2]) 104 | 105 | 106 | def p_boolean_operators(p): 107 | """ 108 | boolean : expression EQ expression 109 | | expression NEQ expression 110 | | expression GT expression 111 | | expression GTE expression 112 | | expression LT expression 113 | | expression LTE expression 114 | | expression AND expression 115 | | expression OR expression 116 | """ 117 | p[0] = ast.BinaryOperation(p[1], p[3], p[2]) 118 | 119 | 120 | def p_unary_operation(p): 121 | """ 122 | expression : MINUS expression %prec UMINUS 123 | | PLUS expression %prec UPLUS 124 | | BIT_NEG expression 125 | | NOT expression 126 | """ 127 | p[0] = ast.UnaryOperation(p[1], p[2]) 128 | 129 | 130 | def p_paren(p): 131 | """ 132 | expression : LPAREN expression RPAREN 133 | """ 134 | p[0] = p[2] if isinstance(p[2], ast.BaseExpression) else ast.Primitive(p[2]) 135 | 136 | 137 | def p_boolean(p): 138 | """ 139 | boolean : TRUE 140 | | FALSE 141 | """ 142 | p[0] = ast.Primitive(p[1]) 143 | 144 | 145 | def p_assignable(p): 146 | """ 147 | assignable : primitive 148 | | expression 149 | | indexable 150 | """ 151 | p[0] = p[1] 152 | 153 | 154 | def p_comma_separated_expr(p): 155 | """ 156 | arguments : arguments COMMA expression 157 | | expression 158 | | 159 | """ 160 | if len(p) == 2: 161 | p[0] = ast.InstructionList([p[1]]) 162 | elif len(p) == 1: 163 | p[0] = ast.InstructionList() 164 | else: 165 | p[1].children.append(p[3]) 166 | p[0] = p[1] 167 | 168 | 169 | def p_ternary_op(p): 170 | """ 171 | expression : expression QUESTION_MARK expression COLON expression 172 | """ 173 | p[0] = ast.TernaryOperator(p[1], p[3], p[5]) 174 | 175 | 176 | def p_array(p): 177 | """ 178 | array : LSQBRACK arguments RSQBRACK 179 | """ 180 | p[0] = ast.Array(p[2]) 181 | 182 | 183 | def p_array_access(p): 184 | """ 185 | expression : indexable LSQBRACK expression RSQBRACK 186 | """ 187 | p[0] = ast.ArrayAccess(p[1], p[3]) 188 | 189 | 190 | def p_slice(p): 191 | """ 192 | expression : indexable LSQBRACK expression COLON expression RSQBRACK 193 | | indexable LSQBRACK COLON expression RSQBRACK 194 | | indexable LSQBRACK expression COLON RSQBRACK 195 | | indexable LSQBRACK COLON RSQBRACK 196 | """ 197 | if len(p) == 7: 198 | p[0] = ast.ArraySlice(p[1], p[3], p[5]) 199 | elif len(p) == 5: 200 | p[0] = ast.ArraySlice(p[1]) 201 | elif p[3] == ":": 202 | # accessing [:expr] 203 | p[0] = ast.ArraySlice(p[1], end=p[4]) 204 | else: 205 | # accessing [expr:] 206 | p[0] = ast.ArraySlice(p[1], start=p[3]) 207 | 208 | 209 | def p_array_access_assign(p): 210 | """ 211 | statement : indexable LSQBRACK expression RSQBRACK EQUALS expression STMT_END 212 | """ 213 | p[0] = ast.ArrayAssign(p[1], p[3], p[6]) 214 | 215 | 216 | def p_assign(p): 217 | """ 218 | expression : identifier EQUALS assignable STMT_END 219 | """ 220 | p[0] = ast.Assignment(p[1], p[3]) 221 | 222 | 223 | def p_ifstatement(p): 224 | """ 225 | if_statement : IF expression LBRACK statement_list RBRACK 226 | """ 227 | p[0] = ast.If(p[2], p[4]) 228 | 229 | 230 | def p_ifstatement_else(p): 231 | """ 232 | if_statement : IF expression LBRACK statement_list RBRACK ELSE LBRACK statement_list RBRACK 233 | """ 234 | p[0] = ast.If(p[2], p[4], p[8]) 235 | 236 | 237 | def p_ifstatement_else_if(p): 238 | """ 239 | if_statement : IF expression LBRACK statement_list RBRACK ELSE if_statement 240 | """ 241 | p[0] = ast.If(p[2], p[4], p[7]) 242 | 243 | 244 | def p_in_expression(p): 245 | """ 246 | expression : expression IN expression 247 | | expression NOT IN expression 248 | """ 249 | if len(p) == 4: 250 | p[0] = ast.InExpression(p[1], p[3]) 251 | else: 252 | p[0] = ast.InExpression(p[1], p[4], True) 253 | 254 | 255 | def p_print_statement(p): 256 | """ 257 | statement : PRINT LPAREN arguments RPAREN STMT_END 258 | """ 259 | p[0] = ast.PrintStatement(p[3]) 260 | 261 | 262 | def p_compound_operations(p): 263 | """ 264 | statement : identifier PLUS_EQ expression STMT_END 265 | | identifier MINUS_EQ expression STMT_END 266 | | identifier MUL_EQ expression STMT_END 267 | | identifier DIV_EQ expression STMT_END 268 | | identifier EXP_EQ expression STMT_END 269 | | identifier MOD_EQ expression STMT_END 270 | """ 271 | p[0] = ast.CompoundOperation(p[1], p[3], p[2]) 272 | 273 | 274 | def p_increment_decrement_identifiers(p): 275 | """ 276 | expression : identifier DOUBLE_PLUS 277 | | identifier DOUBLE_MINUS 278 | """ 279 | if p[2] == "++": 280 | p[0] = ast.BinaryOperation(p[1], ast.Primitive(1), "+") 281 | else: 282 | p[0] = ast.BinaryOperation(p[1], ast.Primitive(1), "-") 283 | 284 | 285 | def p_expression(p): 286 | """ 287 | expression : primitive 288 | | STRING 289 | | identifier 290 | """ 291 | p[0] = p[1] 292 | 293 | 294 | def p_for_loop(p): 295 | """ 296 | statement : FOR identifier IN expression ARROW_LTR expression LBRACK statement_list RBRACK 297 | | FOR identifier IN expression ARROW_RTL expression LBRACK statement_list RBRACK 298 | """ 299 | p[0] = ast.For(p[2], p[4], p[6], p[5] == "->", p[8]) 300 | 301 | 302 | def p_for_in_loop(p): 303 | """ 304 | statement : FOR identifier IN expression LBRACK statement_list RBRACK 305 | """ 306 | p[0] = ast.ForIn(p[2], p[4], p[6]) 307 | 308 | 309 | def p_while_loop(p): 310 | """ 311 | statement : WHILE expression LBRACK statement_list RBRACK 312 | """ 313 | p[0] = ast.While(p[2], p[4]) 314 | 315 | 316 | def p_for_loop_infinite(p): 317 | """ 318 | statement : FOR LBRACK statement_list RBRACK 319 | """ 320 | p[0] = ast.While(ast.Primitive(True), p[3]) 321 | 322 | 323 | def p_function_declaration(p): 324 | """ 325 | statement : FUNCTION identifier LPAREN arguments RPAREN LBRACK statement_list RBRACK 326 | | FUNCTION identifier LBRACK statement_list RBRACK 327 | """ 328 | p[2].is_function = True 329 | 330 | if len(p) == 9: 331 | p[0] = ast.Assignment(p[2], ast.Function(p[4], p[7])) 332 | else: 333 | p[0] = ast.Assignment(p[2], ast.Function(ast.InstructionList(), p[4])) 334 | 335 | 336 | def p_return(p): 337 | """ 338 | statement : RETURN expression STMT_END 339 | """ 340 | p[0] = ast.ReturnStatement(p[2]) 341 | 342 | 343 | def p_function_call(p): 344 | """ 345 | expression : identifier LPAREN arguments RPAREN 346 | statement : identifier LPAREN arguments RPAREN STMT_END 347 | 348 | """ 349 | p[1].is_function = True 350 | p[0] = ast.FunctionCall(p[1], p[3]) 351 | 352 | 353 | def p_error(p): 354 | if p is not None: 355 | raise ParserSyntaxError( 356 | "Syntax error at line %d, illegal token '%s' found" % (p.lineno, p.value) 357 | ) 358 | 359 | raise ParserSyntaxError("Unexpected end of input") 360 | 361 | 362 | def get_parser(): 363 | return yacc.yacc(errorlog=yacc.NullLogger()) if disable_warnings else yacc.yacc() 364 | -------------------------------------------------------------------------------- /mamba/symbol_table.py: -------------------------------------------------------------------------------- 1 | from mamba.exceptions import * 2 | 3 | 4 | class SymbolTable: 5 | __func = "functions" 6 | __sym = "symbols" 7 | __local = "local" 8 | 9 | __table = {__func: {}, __sym: {}, __local: []} 10 | 11 | def __is_local(self): 12 | """ 13 | Returns true if symbol table is being called from inside 14 | a function rather than the global scope 15 | 16 | :return: bool 17 | """ 18 | return len(self.__table[self.__local]) > 0 19 | 20 | def table(self): 21 | return self.__table 22 | 23 | def get_local_table(self): 24 | """ 25 | Returns the active local symbol table (the last one on the stack) 26 | """ 27 | 28 | t = self.__table[self.__local] 29 | 30 | return t[len(t) - 1] 31 | 32 | def set_local(self, flag): 33 | if flag: 34 | self.__table[self.__local].append({}) 35 | else: 36 | self.__table[self.__local].pop() 37 | 38 | def get_sym(self, sym): 39 | if self.__is_local(): 40 | # Check all the local symbol tables starting from the current one 41 | for tab in reversed(self.__table[self.__local]): 42 | if sym in tab: 43 | return tab[sym] 44 | 45 | # if not found check the global scope 46 | if sym in self.__table[self.__sym]: 47 | return self.__table[self.__sym][sym] 48 | 49 | # nope... sorry :( 50 | raise SymbolNotFound("Undefined variable '%s'" % sym) 51 | 52 | def set_sym(self, sym, val): 53 | if self.__is_local(): 54 | self.get_local_table()[sym] = val 55 | else: 56 | self.__table[self.__sym][sym] = val 57 | 58 | def get_func(self, name): 59 | if name in self.__table[self.__func]: 60 | return self.__table[self.__func][name] 61 | 62 | raise SymbolNotFound("Undefined function '%s'" % name) 63 | 64 | def set_func(self, name, val): 65 | if name in self.__table[self.__func]: 66 | raise DuplicateSymbol("Cannot redeclare function '%s'" % name) 67 | 68 | self.__table[self.__func][name] = val 69 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ply 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("README.md", "r") as fh: 4 | long_description = fh.read() 5 | 6 | 7 | requirements = [] 8 | with open("requirements.txt") as f: 9 | requirements = f.read().splitlines() 10 | 11 | setuptools.setup( 12 | name="mamba", 13 | version="0.2.0", 14 | author="Jens Reidel", 15 | author_email="jens.reidel@gmail.com", 16 | description="A language built on top of python, with JS flavours", 17 | long_description=long_description, 18 | long_description_content_type="text/markdown", 19 | url="https://github.com/Gelbpunkt/mamba-lang", 20 | packages=setuptools.find_packages(), 21 | entry_points={"console_scripts": "mamba = mamba.__main__:main"}, 22 | license="MIT", 23 | install_requires=requirements, 24 | classifiers=[ 25 | "Programming Language :: Python :: 3", 26 | "License :: OSI Approved :: MIT License", 27 | "Operating System :: OS Independent", 28 | ], 29 | ) 30 | --------------------------------------------------------------------------------