├── .gitignore ├── fizzbuzz.png ├── examples ├── mismatch.kut ├── globals.kut ├── scopes.kut ├── auto_cast.kut ├── fizzbuzz.kut ├── nested.kut ├── opt_math_identity.kut ├── opt_dead_code.kut └── functions.kut ├── tests ├── 3_7.kut ├── 3_4.kut ├── 3_2.kut ├── 2_3.kut ├── 3_5.kut ├── 4_4.kut ├── 3_6.kut ├── 3_8.kut ├── 3_3.kut ├── 5_1.kut ├── 2_2.kut ├── 4_5.kut ├── 5_2.kut ├── 2_1.kut ├── 4_2.kut ├── 4_1.kut ├── 2_5.kut ├── 1_1.kut ├── 4_3.kut ├── 5_4.kut ├── 1_3.kut └── 1_2.kut ├── LICENSE ├── README.md ├── test.py ├── lang ├── scope.py ├── lexer.py ├── parser.py └── ast.py └── main.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | .vscode/ 3 | -------------------------------------------------------------------------------- /fizzbuzz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrek-szczygiel/kutlang/HEAD/fizzbuzz.png -------------------------------------------------------------------------------- /examples/mismatch.kut: -------------------------------------------------------------------------------- 1 | fn hello(name: str) { 2 | println("Hello " + name + "!") 3 | } 4 | 5 | hello() 6 | -------------------------------------------------------------------------------- /examples/globals.kut: -------------------------------------------------------------------------------- 1 | global := 123 2 | 3 | fn test() { 4 | println(global) 5 | } 6 | 7 | { 8 | test() 9 | } 10 | -------------------------------------------------------------------------------- /tests/3_7.kut: -------------------------------------------------------------------------------- 1 | 3_7.kut 2 | Sprawdzanie syntaktyczne instrukcji. 3 | Test przechodzi pozytywnie. 4 | ### 5 | 5 := 15 6 | ### 7 | Parsing error 8 | -------------------------------------------------------------------------------- /tests/3_4.kut: -------------------------------------------------------------------------------- 1 | 3_4.kut 2 | Instrukcja przypisania. 3 | Test przechodzi pozytywnie. 4 | ### 5 | a := 10 * 3 + .4 6 | b := 100 7 | println(a + b) 8 | ### 9 | 130.4 10 | -------------------------------------------------------------------------------- /tests/3_2.kut: -------------------------------------------------------------------------------- 1 | 3_2.kut 2 | Deklarowanie typów dla zmiennych. 3 | Test przechodzi pozytywnie. 4 | ### 5 | fn add(a: int, b: int) { 6 | a + b 7 | } 8 | 9 | println(add(1, 2)) 10 | ### 11 | 3 12 | -------------------------------------------------------------------------------- /tests/2_3.kut: -------------------------------------------------------------------------------- 1 | 2_3.kut 2 | Kontynuacja parsowania kolejnych instrukcji w przypadku błędu. 3 | Test przechodzi pozytywnie. 4 | ### 5 | print("Hello") 6 | !@#$%^&* 7 | println(" world!") 8 | ### 9 | Lexing error 10 | -------------------------------------------------------------------------------- /tests/3_5.kut: -------------------------------------------------------------------------------- 1 | 3_5.kut 2 | Przeciążanie operatorów. 3 | Testy przechodzą pozytywnie. 4 | ### 5 | println(10 + 10) 6 | println("10" + "10") 7 | println("Hello " + "world!") 8 | ### 9 | 20 10 | 1010 11 | Hello world! 12 | -------------------------------------------------------------------------------- /tests/4_4.kut: -------------------------------------------------------------------------------- 1 | 4_4.kut 2 | Instrukcja wywołania funkcji. 3 | Test przechodzi pozytywnie. 4 | ### 5 | fn hello(name: str) { 6 | println("Hello " + name + "!") 7 | } 8 | 9 | hello("world") 10 | ### 11 | Hello world! 12 | -------------------------------------------------------------------------------- /examples/scopes.kut: -------------------------------------------------------------------------------- 1 | a := "hello" 2 | 3 | println(a) 4 | 5 | { 6 | a := "shadowed" 7 | println(a) 8 | } 9 | 10 | println(a) 11 | 12 | b := { 13 | for a := 0; a <= 5; a = a + 1 { a ^ 3 } 14 | } 15 | 16 | println(b) 17 | -------------------------------------------------------------------------------- /examples/auto_cast.kut: -------------------------------------------------------------------------------- 1 | fn hello(who: str) { 2 | println("Hello " + who + "!") 3 | } 4 | 5 | hello("world") 6 | hello(123) 7 | 8 | fn add(a: float, b: float) { a + b } 9 | println(add(1, 2)) 10 | println(add(10.0, "2" + "2")) 11 | -------------------------------------------------------------------------------- /tests/3_6.kut: -------------------------------------------------------------------------------- 1 | 3_6.kut 2 | Sprawdzanie syntaktyczne deklaracji zmiennych. 3 | Test przechodzi pozytywnie. 4 | ### 5 | a := 10 + 5 6 | println(a) 7 | 8 | a := 15 + 5 9 | println(a) 10 | ### 11 | 15 12 | Identifier 'a' is already defined 13 | -------------------------------------------------------------------------------- /tests/3_8.kut: -------------------------------------------------------------------------------- 1 | 3_8.kut 2 | Konwersja typów za pomocą dodatkowego operatora. 3 | Test przechodzi pozytywnie. 4 | ### 5 | a := "10" 6 | println(10 + cast(int, a)) 7 | 8 | b := 100 9 | println("b = " + cast(str, b)) 10 | ### 11 | 20 12 | b = 100 13 | -------------------------------------------------------------------------------- /tests/3_3.kut: -------------------------------------------------------------------------------- 1 | 3_3.kut 2 | Sprawdzanie typów. 3 | Test przechodzi pozytywnie. 4 | ### 5 | fn add(a: int, b: int) { 6 | a + b 7 | } 8 | 9 | println(add(1, 2)) 10 | println(add("hello", "world")) 11 | ### 12 | 3 13 | Cannot convert 'hello' to int 14 | -------------------------------------------------------------------------------- /tests/5_1.kut: -------------------------------------------------------------------------------- 1 | 5_1.kut 2 | Zagnieżdżone wywołania funkcji. 3 | Test przechodzi pozytywnie. 4 | ### 5 | fn inner() { 6 | println("world!") 7 | } 8 | 9 | fn outer() { 10 | print("Hello ") 11 | inner() 12 | } 13 | 14 | outer() 15 | ### 16 | Hello world! 17 | -------------------------------------------------------------------------------- /tests/2_2.kut: -------------------------------------------------------------------------------- 1 | 2_2.kut 2 | Implementacja instrukcji oddzielonych średnikiem. 3 | W przypadku mojego języka nie używam średnika do oddzielania instrukcji z zachowaniem funkcjonalności. 4 | Test przechodzi pozytywnie. 5 | ### 6 | print("Hello") 7 | println(" world!") 8 | ### 9 | Hello world! 10 | -------------------------------------------------------------------------------- /tests/4_5.kut: -------------------------------------------------------------------------------- 1 | 4_5.kut 2 | Automatyczna konwersja typów. 3 | Test przechodzi pozytywnie. 4 | ### 5 | fn add_strings(a: str, b: str) { 6 | a + b 7 | } 8 | 9 | fn add_numbers(a: int, b: int) { 10 | a + b 11 | } 12 | 13 | println(add_strings("10", 10)) 14 | println(add_numbers(10.7, "10")) 15 | ### 16 | 1010 17 | 20 18 | -------------------------------------------------------------------------------- /tests/5_2.kut: -------------------------------------------------------------------------------- 1 | 5_2.kut 2 | Pomijanie zbędnych instrukcji. 3 | Test przechodzi pozytywnie. 4 | ### 5 | unused_variable := 5 6 | 7 | fn unused_function() { 8 | println("This function is never called") 9 | } 10 | 11 | println("Hello world!") 12 | ### 13 | OPTIMIZE 14 | 15 | Removing 2 unused definitions 16 | Hello world! 17 | -------------------------------------------------------------------------------- /examples/fizzbuzz.kut: -------------------------------------------------------------------------------- 1 | fn fizzbuzz(n: int) { 2 | for i := 1; i <= n; i = i + 1 { 3 | value := "" 4 | 5 | if i % 3 == 0 { value = value + "Fizz" } 6 | if i % 5 == 0 { value = value + "Buzz" } 7 | 8 | if value { 9 | println(value) 10 | } else { 11 | println(i) 12 | } 13 | } 14 | } 15 | 16 | fizzbuzz(15) 17 | -------------------------------------------------------------------------------- /examples/nested.kut: -------------------------------------------------------------------------------- 1 | fn world() { 2 | println("world!") 3 | } 4 | 5 | fn hello() { 6 | print("Hello ") 7 | world() 8 | } 9 | 10 | hello() 11 | 12 | fn function() { 13 | a := 2 14 | fn nested() { 15 | println(a) 16 | a = a ^ 3 17 | } 18 | 19 | for i := 0; i < 5; i = i + 1 { 20 | nested() 21 | } 22 | } 23 | 24 | function() 25 | -------------------------------------------------------------------------------- /tests/2_1.kut: -------------------------------------------------------------------------------- 1 | 2_1.kut 2 | Implementacja działan potęgowania, funkcji specjalnych, działań relacyjnych, zmiany znaku. 3 | Test przechodzi pozytywnie. 4 | ### 5 | println(5 ^ 2) 6 | println("sin(pi/2) = " + cast(str, sin(pi() / 2))) 7 | println(20 > (sin(0.0) + 19)) 8 | println(20 > (sin(0.0) + 21)) 9 | println(0 - cos(0)) 10 | ### 11 | 25 12 | sin(pi/2) = 1.0 13 | True 14 | False 15 | -1.0 16 | -------------------------------------------------------------------------------- /tests/4_2.kut: -------------------------------------------------------------------------------- 1 | 4_2.kut 2 | Definiowanie bloków instrukcji. 3 | Test przechodzi pozytywnie. 4 | ### 5 | a := "hello" 6 | 7 | println(a) 8 | 9 | { 10 | a := "shadowed" 11 | println(a) 12 | } 13 | 14 | println(a) 15 | 16 | b := { 17 | for a := 0; a <= 5; a = a + 1 { 18 | a ^ 3 19 | } 20 | } 21 | 22 | println(b) 23 | ### 24 | hello 25 | shadowed 26 | hello 27 | 125 28 | -------------------------------------------------------------------------------- /examples/opt_math_identity.kut: -------------------------------------------------------------------------------- 1 | a := 10 2 | b := a + 0 3 | c := 0 - a 4 | d := a * 1 5 | e := a / 1 6 | f := a ^ 2 7 | g := 2 * a 8 | h := a / 2 9 | 10 | fn show(name: str, value: int) { 11 | println(name + " = " + cast(str, value)) 12 | } 13 | 14 | { 15 | show("a", a) 16 | show("b", b) 17 | show("c", c) 18 | show("d", d) 19 | show("e", e) 20 | show("f", f) 21 | show("g", g) 22 | show("h", h) 23 | } 24 | -------------------------------------------------------------------------------- /tests/4_1.kut: -------------------------------------------------------------------------------- 1 | 4_1.kut 2 | Definiowanie funkcji. 3 | Test przechodzi pozytywnie. 4 | ### 5 | fn fib() { 6 | a := 1 7 | b := 0 8 | 9 | for i := 0; i < 15; i = i + 1 { 10 | temp := a 11 | a = a + b 12 | b = temp 13 | 14 | println(a) 15 | } 16 | } 17 | 18 | fib() 19 | ### 20 | 1 21 | 2 22 | 3 23 | 5 24 | 8 25 | 13 26 | 21 27 | 34 28 | 55 29 | 89 30 | 144 31 | 233 32 | 377 33 | 610 34 | 987 35 | -------------------------------------------------------------------------------- /tests/2_5.kut: -------------------------------------------------------------------------------- 1 | 2_5.kut 2 | Instrukcje warunkowe i pętle. 3 | Test przechodzi pozytywnie. 4 | ### 5 | println("for") 6 | for i := 0; i <= 10; i = i + 1 { 7 | if i % 2 == 0 { 8 | println(i) 9 | } 10 | } 11 | 12 | println("") 13 | println("while") 14 | 15 | a := 0 16 | while a < 5 { 17 | println(a) 18 | a = a + 1 19 | } 20 | ### 21 | for 22 | 0 23 | 2 24 | 4 25 | 6 26 | 8 27 | 10 28 | 29 | while 30 | 0 31 | 1 32 | 2 33 | 3 34 | 4 35 | -------------------------------------------------------------------------------- /examples/opt_dead_code.kut: -------------------------------------------------------------------------------- 1 | fn unused1() { 2 | println("Hello!") 3 | } 4 | 5 | unused2 := "Unused variable" 6 | 7 | a := 10 8 | b := a + 1 9 | 10 | println(b) 11 | 12 | fn function() { 13 | fn unused1() { 14 | println("World") 15 | } 16 | 17 | println("Hello") 18 | } 19 | 20 | function() 21 | 22 | { 23 | { 24 | { 25 | a := 10 26 | b := 21 27 | println(b) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/1_1.kut: -------------------------------------------------------------------------------- 1 | 1_1.kut 2 | Obsługa tokenów dla liczb całkowitych i rzeczywistych. 3 | Test przechodzi pozytywnie. 4 | ### 5 | println(1 + 2 * 3.5 / .5 ^ 10.) 6 | ### 7 | LEXER OUTPUT 8 | Token('PRINTLN', 'println') 9 | Token('LPAREN', '(') 10 | Token('VALUE_INT', '1') 11 | Token('ADD', '+') 12 | Token('VALUE_INT', '2') 13 | Token('MUL', '*') 14 | Token('VALUE_FLOAT', '3.5') 15 | Token('DIV', '/') 16 | Token('VALUE_FLOAT', '.5') 17 | Token('POW', '^') 18 | Token('VALUE_FLOAT', '10.') 19 | Token('RPAREN', ')') 20 | 21 | PROGRAM OUTPUT 22 | 7169.0 23 | -------------------------------------------------------------------------------- /tests/4_3.kut: -------------------------------------------------------------------------------- 1 | 4_3.kut 2 | Definiowanie zmiennych globalnych i lokalnych. 3 | Test przechodzi pozytywnie. 4 | ### 5 | global := "global" 6 | 7 | fn test_global() { 8 | println("I can access global variables from anywhere: " + global) 9 | } 10 | 11 | test_global() 12 | 13 | if true { 14 | a := 230 15 | println(a) 16 | 17 | if !false { 18 | println("Still can access a: " + cast(str, a)) 19 | 20 | b := 17 21 | } 22 | } 23 | 24 | println(a) 25 | ### 26 | I can access global variables from anywhere: global 27 | 230 28 | Still can access a: 230 29 | Undefined identifier 'a' 30 | -------------------------------------------------------------------------------- /tests/5_4.kut: -------------------------------------------------------------------------------- 1 | 5_4.kut 2 | Optymalizacje algebraiczne. 3 | Test przechodzi pozytywnie. 4 | ### 5 | a := 10 6 | b := a + 0 7 | c := 0 - a 8 | d := a * 1 9 | e := a / 1 10 | f := a ^ 2 11 | g := 2 * a 12 | h := a / 2 13 | 14 | fn show(name: str, value: int) { 15 | println(name + " = " + cast(str, value)) 16 | } 17 | 18 | { 19 | show("a", a) 20 | show("b", b) 21 | show("c", c) 22 | show("d", d) 23 | show("e", e) 24 | show("f", f) 25 | show("g", g) 26 | show("h", h) 27 | } 28 | ### 29 | a = 10 30 | b = 10 31 | c = -10 32 | d = 10 33 | e = 10 34 | f = 100 35 | g = 20 36 | h = 5 37 | -------------------------------------------------------------------------------- /tests/1_3.kut: -------------------------------------------------------------------------------- 1 | 1_3.kut 2 | Obsługa tokenów dla operatora potęgowania, itd. 3 | Test przechodzi pozytywnie. 4 | ### 5 | println(1 + .5 ^ 10 + 10 * 10 / .2 - 18) 6 | ### 7 | LEXER OUTPUT 8 | Token('PRINTLN', 'println') 9 | Token('LPAREN', '(') 10 | Token('VALUE_INT', '1') 11 | Token('ADD', '+') 12 | Token('VALUE_FLOAT', '.5') 13 | Token('POW', '^') 14 | Token('VALUE_INT', '10') 15 | Token('ADD', '+') 16 | Token('VALUE_INT', '10') 17 | Token('MUL', '*') 18 | Token('VALUE_INT', '10') 19 | Token('DIV', '/') 20 | Token('VALUE_FLOAT', '.2') 21 | Token('SUB', '-') 22 | Token('VALUE_INT', '18') 23 | Token('RPAREN', ')') 24 | 25 | PROGRAM OUTPUT 26 | 483.0009765625 27 | -------------------------------------------------------------------------------- /examples/functions.kut: -------------------------------------------------------------------------------- 1 | fn sqr(x: int) { x ^ 2 } 2 | fn pow(x: int, n: int) { x ^ n } 3 | 4 | fn hello(name: str) { 5 | println("Hello " + name + "!") 6 | 7 | result := 0 8 | while result < 1337 { result = result + 1 } 9 | result 10 | } 11 | 12 | fn fib() { 13 | a := 1 14 | b := 0 15 | 16 | for i := 0; i < 15; i = i + 1 { 17 | temp := a 18 | a = a + b 19 | b = temp 20 | 21 | println(a) 22 | } 23 | } 24 | 25 | println("13^2 = " + cast(str, sqr(13))) 26 | println("6^9 = " + cast(str, pow(6, 9))) 27 | println("PI = " + cast(str, pi())) 28 | 29 | result := hello("World") 30 | println(result) 31 | 32 | fib() 33 | -------------------------------------------------------------------------------- /tests/1_2.kut: -------------------------------------------------------------------------------- 1 | 1_2.kut 2 | Obsługa tokenów dla funkcji specjalnych sin, cos itd. 3 | Test przechodzi pozytywnie. 4 | ### 5 | println(1 + sin(pi() / 2) + cos(pi())) 6 | ### 7 | LEXER OUTPUT 8 | Token('PRINTLN', 'println') 9 | Token('LPAREN', '(') 10 | Token('VALUE_INT', '1') 11 | Token('ADD', '+') 12 | Token('SYMBOL', 'sin') 13 | Token('LPAREN', '(') 14 | Token('SYMBOL', 'pi') 15 | Token('LPAREN', '(') 16 | Token('RPAREN', ')') 17 | Token('DIV', '/') 18 | Token('VALUE_INT', '2') 19 | Token('RPAREN', ')') 20 | Token('ADD', '+') 21 | Token('SYMBOL', 'cos') 22 | Token('LPAREN', '(') 23 | Token('SYMBOL', 'pi') 24 | Token('LPAREN', '(') 25 | Token('RPAREN', ')') 26 | Token('RPAREN', ')') 27 | Token('RPAREN', ')') 28 | 29 | PROGRAM OUTPUT 30 | 1.0 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Piotr Szczygieł 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 | ## Requirements 2 | 3 | - `pip install rply graphviz` 4 | - [Graphviz](https://graphviz.gitlab.io/download/) executable in PATH 5 | 6 | If you want to run tests install also colorama for colorful terminal output. 7 | - `pip install colorama` 8 | 9 | ## Running REPL 10 | 11 | ```bash 12 | $ python main.py 13 | > 12 + 3.5 * 5. 14 | 29.5 15 | ``` 16 | 17 | ## Running script 18 | 19 | ```bash 20 | $ python main.py examples/functions.kut 21 | 13^2 = 169 22 | 6^9 = 10077696 23 | PI = 3.1415926 24 | Hello World! 25 | 1337 26 | 1 27 | 2 28 | 3 29 | 5 30 | 8 31 | 13 32 | 21 33 | 34 34 | 55 35 | 89 36 | 144 37 | 233 38 | 377 39 | 610 40 | 987 41 | ``` 42 | 43 | ## Running tests 44 | 45 | ```bash 46 | $ python test.py 47 | tests\1_1.kut...........................PASS 48 | tests\1_2.kut...........................PASS 49 | tests\1_3.kut...........................PASS 50 | ... 51 | ``` 52 | 53 | ## Drawing the AST 54 | 55 | ```bash 56 | $ python main.py examples/fizzbuzz.kut --ast 57 | 1 58 | 2 59 | Fizz 60 | 4 61 | Buzz 62 | Fizz 63 | 7 64 | 8 65 | Fizz 66 | Buzz 67 | 11 68 | Fizz 69 | 13 70 | 14 71 | FizzBuzz 72 | 16 73 | 17 74 | Fizz 75 | 19 76 | Buzz 77 | ``` 78 | 79 | ![ast](fizzbuzz.png) 80 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | from io import StringIO 2 | import argparse 3 | import sys 4 | import os 5 | from main import execute 6 | from lang.scope import Scope 7 | from colorama import Fore, Style, init 8 | from pathlib import Path 9 | 10 | 11 | def test(path, verbose=False): 12 | with open(path, "r") as f: 13 | _, source, expected = f.read().split("###", 2) 14 | expected = expected.strip() 15 | 16 | old_stdout = sys.stdout 17 | sys.stdout = actual = StringIO() 18 | 19 | opt = False 20 | if expected.startswith("OPTIMIZE"): 21 | opt = True 22 | expected = expected[8:].strip() 23 | 24 | lexer_output = expected.startswith("LEXER OUTPUT") 25 | execute(Scope(), source, draw=False, lexer_output=lexer_output, opt=opt) 26 | 27 | sys.stdout = old_stdout 28 | actual = actual.getvalue().strip() 29 | 30 | path = str(path) 31 | 32 | print(path + Fore.BLUE + "." * (40 - len(path)), end="") 33 | if actual == expected: 34 | print(Fore.GREEN + "PASS" + Style.RESET_ALL) 35 | else: 36 | print(Fore.RED + "FAIL" + Style.RESET_ALL) 37 | if verbose: 38 | print("=" * 10 + "Expected" + "=" * 10) 39 | print(Fore.GREEN + expected + Style.RESET_ALL) 40 | print("=" * 10 + " Actual " + "=" * 10) 41 | print(Fore.YELLOW + actual + Style.RESET_ALL) 42 | 43 | 44 | if __name__ == "__main__": 45 | init() 46 | arg_parser = argparse.ArgumentParser() 47 | arg_parser.add_argument( 48 | "-v", 49 | "--verbose", 50 | help="show actual and expected output in case of an error", 51 | action="store_true", 52 | ) 53 | args = arg_parser.parse_args() 54 | 55 | tests_dir = Path("tests") 56 | (_, _, tests) = next(os.walk(tests_dir)) 57 | for t in tests: 58 | test(tests_dir / t, verbose=args.verbose) 59 | -------------------------------------------------------------------------------- /lang/scope.py: -------------------------------------------------------------------------------- 1 | class Symbols: 2 | def __init__(self): 3 | self.symbols = {} 4 | self.used = {} 5 | 6 | def add(self, name, value): 7 | self.symbols[name] = value 8 | self.used[name] = False 9 | 10 | def set(self, name, value): 11 | symbol = self.symbols[name] 12 | if not isinstance(symbol, type(value)): 13 | ltype = symbol.__class__.__name__ 14 | rtype = value.__class__.__name__ 15 | raise ValueError( 16 | f"Cannot assign {name} of type {rtype} to variable of type {ltype}" 17 | ) 18 | self.symbols[name] = value 19 | 20 | def get(self, name): 21 | self.used[name] = True 22 | return self.symbols[name] 23 | 24 | def contains(self, name): 25 | return name in self.symbols 26 | 27 | 28 | class Scope: 29 | def __init__(self): 30 | self.symbols_stack = [] 31 | self.last_pop = None 32 | 33 | def add(self, name, value): 34 | if self.symbols_stack[0].contains(name): 35 | raise ValueError(f"Identifier '{name}' is already defined") 36 | else: 37 | self.symbols_stack[0].add(name, value) 38 | 39 | def set(self, name, value): 40 | found = False 41 | for symbols in self.symbols_stack: 42 | if symbols.contains(name): 43 | symbols.set(name, value) 44 | found = True 45 | break 46 | if not found: 47 | raise ValueError(f"Undefined identifier '{name}'") 48 | 49 | def get(self, name): 50 | for symbols in self.symbols_stack: 51 | if symbols.contains(name): 52 | return symbols.get(name) 53 | raise ValueError(f"Undefined identifier '{name}'") 54 | 55 | def push(self): 56 | self.symbols_stack.insert(0, Symbols()) 57 | 58 | def pop(self): 59 | self.last_pop = self.symbols_stack.pop(0) 60 | -------------------------------------------------------------------------------- /lang/lexer.py: -------------------------------------------------------------------------------- 1 | import rply 2 | 3 | 4 | class Lexer: 5 | def __init__(self): 6 | self.lexer, self.tokens = Lexer.create_lexer() 7 | 8 | def lex(self, input): 9 | return self.lexer.lex(input) 10 | 11 | @staticmethod 12 | def create_lexer(): 13 | lg = rply.LexerGenerator() 14 | lg.ignore(r"\s+") 15 | 16 | tokens = [ 17 | ("LPAREN", r"\("), 18 | ("RPAREN", r"\)"), 19 | ("LBRACE", r"\{"), 20 | ("RBRACE", r"\}"), 21 | ("EQ", r"=="), 22 | ("NE", r"!="), 23 | ("LE", r"<="), 24 | ("GE", r">="), 25 | ("LT", r"<"), 26 | ("GT", r">"), 27 | ("DEFINE", r":="), 28 | ("COMMA", r","), 29 | ("SC", r";"), 30 | ("COLON", r":"), 31 | ("ASSIGN", r"="), 32 | ("NOT", r"!"), 33 | ("AND", r"&&"), 34 | ("OR", r"\|\|"), 35 | ("ADD", r"\+"), 36 | ("SUB", r"-"), 37 | ("MUL", r"\*"), 38 | ("DIV", r"/"), 39 | ("POW", r"\^"), 40 | ("MOD", r"%"), 41 | ("VALUE_FLOAT", r"\d+\.\d+|\d+\.|\.\d+"), 42 | ("VALUE_INT", r"\d+"), 43 | ("VALUE_STR", r"\"(.*?)\""), 44 | ("TRUE", r"true"), 45 | ("FALSE", r"false"), 46 | ("IF", r"if"), 47 | ("ELSE", r"else"), 48 | ("FOR", r"for"), 49 | ("WHILE", r"while"), 50 | ("FN", r"fn"), 51 | # ("RETURN", r"return"), 52 | # ("BREAK", r"break"), 53 | ("INT", r"int"), 54 | ("FLOAT", r"float"), 55 | ("STR", r"str"), 56 | ("BOOL", r"bool"), 57 | ("CAST", r"cast"), 58 | ("PRINTLN", r"println"), 59 | ("PRINT", r"print"), 60 | ("SYMBOL", r"[a-zA-Z_][a-zA-Z0-9_]*"), 61 | ] 62 | 63 | token_names = [] 64 | 65 | for name, pattern in tokens: 66 | lg.add(name, pattern) 67 | token_names.append(name) 68 | 69 | return lg.build(), token_names 70 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | import copy 4 | 5 | from graphviz import Digraph 6 | from rply import LexingError, ParsingError 7 | 8 | from lang.lexer import Lexer 9 | from lang.parser import Parser 10 | from lang.scope import Scope 11 | 12 | lexer = Lexer() 13 | parser = Parser(lexer.tokens) 14 | 15 | 16 | def execute(scope, source, draw=False, lexer_output=False, opt=False): 17 | try: 18 | tokens = lexer.lex(source) 19 | 20 | if lexer_output: 21 | print("LEXER OUTPUT") 22 | for token in copy.copy(tokens): 23 | print(token) 24 | print() 25 | print("PROGRAM OUTPUT") 26 | 27 | ast = parser.parse(tokens) 28 | 29 | # Optimize 30 | if opt: 31 | ast.eval(True, scope) 32 | 33 | result = ast.eval(False, scope) 34 | 35 | # Draw AST graph 36 | if draw: 37 | g = Digraph() 38 | ast.draw(g) 39 | g.render("ast", format="png", view=True, cleanup=True) 40 | 41 | return result 42 | except ValueError as err: 43 | print(err) 44 | except LexingError: 45 | print("Lexing error") 46 | except ParsingError: 47 | print("Parsing error") 48 | 49 | 50 | def run_repl(): 51 | scope = Scope() 52 | while True: 53 | try: 54 | source = input("> ") 55 | result = execute(scope, source) 56 | if result is not None: 57 | print(result) 58 | if scope.last_pop is not None: 59 | scope.symbols_stack.insert(0, scope.last_pop) 60 | except KeyboardInterrupt: 61 | break 62 | 63 | 64 | def run_file(path, draw=False, lexer_output=False): 65 | scope = Scope() 66 | with open(path, "r") as f: 67 | source = f.read() 68 | execute(scope, source, draw=draw, lexer_output=lexer_output) 69 | 70 | 71 | if __name__ == "__main__": 72 | arg_parser = argparse.ArgumentParser() 73 | arg_parser.add_argument("file", nargs="?", help="path to script") 74 | arg_parser.add_argument( 75 | "-a", "--ast", help="draw abstract syntax tree", action="store_true" 76 | ) 77 | arg_parser.add_argument( 78 | "-l", "--lexer", help="print lexer output", action="store_true" 79 | ) 80 | args = arg_parser.parse_args() 81 | 82 | if args.file: 83 | run_file(args.file, draw=args.ast, lexer_output=args.lexer) 84 | else: 85 | run_repl() 86 | -------------------------------------------------------------------------------- /lang/parser.py: -------------------------------------------------------------------------------- 1 | import operator 2 | 3 | from rply import ParserGenerator 4 | 5 | from lang import ast 6 | 7 | 8 | class Parser: 9 | def __init__(self, tokens): 10 | self.parser = Parser.create_parser(tokens) 11 | 12 | def parse(self, input): 13 | return self.parser.parse(input) 14 | 15 | @staticmethod 16 | def create_parser(tokens): 17 | pg = ParserGenerator( 18 | tokens, 19 | precedence=[ 20 | ("left", ["OR"]), 21 | ("left", ["AND"]), 22 | ("right", ["NOT"]), 23 | ("left", ["EQ", "NE", "LE", "GE", "LT", "GT"]), 24 | ("left", ["ADD", "SUB"]), 25 | ("left", ["MUL", "DIV", "MOD"]), 26 | ("right", ["MINUS"]), 27 | ("right", ["POW"]), 28 | ("nonassoc", ["LPAREN", "RPAREN"]), 29 | ], 30 | cache_id="lang", 31 | ) 32 | 33 | @pg.production("program : block") 34 | def program(p): 35 | return ast.Program(p[0]) 36 | 37 | @pg.production("scope : LBRACE block RBRACE") 38 | def scope(p): 39 | return ast.Block(p[1].block) 40 | 41 | @pg.production("scope : LBRACE RBRACE") 42 | def scope_empty(p): 43 | return ast.Block(None) 44 | 45 | @pg.production("block : block stmt") 46 | def block(p): 47 | return ast.Block(p[0].block + [p[1]]) 48 | 49 | @pg.production("block : stmt") 50 | def block_stmt(p): 51 | return ast.Block([p[0]]) 52 | 53 | @pg.production("stmt : FN SYMBOL LPAREN fn_args RPAREN scope") 54 | def stmt_fn(p): 55 | return ast.Fn(p[1].getstr(), p[3], p[5]) 56 | 57 | @pg.production("fn_args : fn_args COMMA def_arg") 58 | def fn_args(p): 59 | return ast.FnArgs(p[0].args + [p[2]]) 60 | 61 | @pg.production("fn_args : def_arg") 62 | def def_args_arg(p): 63 | return ast.FnArgs([p[0]]) 64 | 65 | @pg.production("fn_args :") 66 | def def_args_empty(p): 67 | return ast.FnArgs([]) 68 | 69 | @pg.production("def_arg : SYMBOL COLON type") 70 | def def_arg(p): 71 | return ast.FnArg(p[0].getstr(), p[2]) 72 | 73 | @pg.production("stmt : SYMBOL DEFINE expr") 74 | def stmt_define(p): 75 | return ast.Define(p[0].getstr(), p[2]) 76 | 77 | @pg.production("stmt : SYMBOL ASSIGN expr") 78 | def stmt_assign(p): 79 | return ast.Assign(p[0].getstr(), p[2]) 80 | 81 | @pg.production("stmt : PRINTLN LPAREN expr RPAREN") 82 | @pg.production("stmt : PRINT LPAREN expr RPAREN") 83 | def stmt_print(p): 84 | if p[0].gettokentype() == "PRINTLN": 85 | return ast.Print(p[2], True) 86 | elif p[0].gettokentype() == "PRINT": 87 | return ast.Print(p[2], False) 88 | 89 | @pg.production("stmt : expr") 90 | def stmt_expr(p): 91 | return ast.Statement(p[0]) 92 | 93 | @pg.production("expr : scope") 94 | def expr_scope(p): 95 | return p[0] 96 | 97 | @pg.production("expr : SYMBOL LPAREN args RPAREN") 98 | def expr_call(p): 99 | return ast.Call(p[0].getstr(), p[2]) 100 | 101 | @pg.production("args : args COMMA expr") 102 | def args(p): 103 | return ast.Args(p[0].args + [p[2]]) 104 | 105 | @pg.production("args : expr") 106 | def args_expr(p): 107 | return ast.Args([p[0]]) 108 | 109 | @pg.production("args :") 110 | def args_empty(p): 111 | return ast.Args([]) 112 | 113 | @pg.production("expr : IF expr scope ELSE scope") 114 | def expr_if_else(p): 115 | return ast.IfElse(p[1], p[2], p[4]) 116 | 117 | @pg.production("expr : IF expr scope") 118 | def expr_if(p): 119 | return ast.If(p[1], p[2]) 120 | 121 | @pg.production("expr : WHILE expr scope") 122 | def expr_while(p): 123 | return ast.While(p[1], p[2]) 124 | 125 | @pg.production("expr : FOR stmt SC expr SC stmt scope") 126 | def expr_for(p): 127 | return ast.For(p[1], p[3], p[5], p[6]) 128 | 129 | @pg.production("expr : NOT expr") 130 | def expr_not(p): 131 | return ast.Not(p[1]) 132 | 133 | @pg.production("expr : SYMBOL") 134 | def expr_symbol(p): 135 | return ast.ValueSymbol(p[0].getstr()) 136 | 137 | @pg.production("expr : CAST LPAREN type COMMA expr RPAREN") 138 | def expr_cast(p): 139 | return ast.Cast(p[2], p[4]) 140 | 141 | @pg.production("type : INT") 142 | @pg.production("type : FLOAT") 143 | @pg.production("type : STR") 144 | @pg.production("type : BOOL") 145 | def expr_type(p): 146 | return ast.Type(p[0].gettokentype()) 147 | 148 | @pg.production("expr : VALUE_INT") 149 | def expr_number_int(p): 150 | return ast.ValueInt(int(p[0].getstr())) 151 | 152 | @pg.production("expr : VALUE_FLOAT") 153 | def expr_number_float(p): 154 | return ast.ValueFloat(float(p[0].getstr())) 155 | 156 | @pg.production("expr : VALUE_STR") 157 | def expr_str(p): 158 | return ast.ValueStr(p[0].getstr()) 159 | 160 | @pg.production("expr : TRUE") 161 | def expr_true(p): 162 | return ast.ValueTrue() 163 | 164 | @pg.production("expr : FALSE") 165 | def expr_false(p): 166 | return ast.ValueFalse() 167 | 168 | @pg.production("expr : LPAREN expr RPAREN") 169 | def expr_parens(p): 170 | return p[1] 171 | 172 | @pg.production("expr : expr ADD expr") 173 | @pg.production("expr : expr SUB expr") 174 | @pg.production("expr : expr MUL expr") 175 | @pg.production("expr : expr DIV expr") 176 | @pg.production("expr : expr POW expr") 177 | @pg.production("expr : expr MOD expr") 178 | @pg.production("expr : expr EQ expr") 179 | @pg.production("expr : expr NE expr") 180 | @pg.production("expr : expr LE expr") 181 | @pg.production("expr : expr GE expr") 182 | @pg.production("expr : expr LT expr") 183 | @pg.production("expr : expr GT expr") 184 | @pg.production("expr : expr AND expr") 185 | @pg.production("expr : expr OR expr") 186 | def expr_binop(p): 187 | left = p[0] 188 | op = p[1].gettokentype() 189 | right = p[2] 190 | 191 | def is_number(x): 192 | return isinstance(x, ast.ValueInt) or isinstance(x, ast.ValueFloat) 193 | 194 | # Mathematical identities optimziation 195 | if op == "ADD": 196 | if is_number(left) and left.value == 0: # 0 + x = x 197 | return right 198 | elif is_number(right) and right.value == 0: # x + 0 = x 199 | return left 200 | elif op == "SUB": 201 | if is_number(left) and left.value == 0: # 0 - x = -x 202 | return ast.Minus(right) 203 | elif is_number(right) and right.value == 0: # x - 0 = x 204 | return left 205 | elif op == "MUL": 206 | if is_number(left) and left.value == 1: # 1 * x = x 207 | return right 208 | elif is_number(left) and left.value == 2: # 2 * x = x + x 209 | return ast.BinaryOp(operator.add, right, right) 210 | elif is_number(right) and right.value == 1: # x * 1 = x 211 | return left 212 | elif is_number(right) and right.value == 2: # x * 2 = x + x 213 | return ast.BinaryOp(operator.add, left, left) 214 | elif op == "DIV": 215 | if is_number(right) and right.value == 1: # x / 1 = x 216 | return left 217 | elif is_number(right) and right.value == 2: # x / 2 = x * 0.5 218 | return ast.BinaryOp(operator.mul, left, ast.ValueFloat(0.5)) 219 | elif op == "POW": 220 | if is_number(right) and right.value == 2: # x ^ 2 = x * x 221 | return ast.BinaryOp(operator.mul, left, left) 222 | 223 | methods = { 224 | "ADD": operator.add, 225 | "SUB": operator.sub, 226 | "MUL": operator.mul, 227 | "DIV": operator.truediv, 228 | "POW": operator.pow, 229 | "MOD": operator.mod, 230 | "EQ": operator.eq, 231 | "NE": operator.ne, 232 | "LE": operator.le, 233 | "GE": operator.ge, 234 | "LT": operator.lt, 235 | "GT": operator.gt, 236 | "AND": operator.and_, 237 | "OR": operator.or_, 238 | } 239 | 240 | assert op in methods 241 | return ast.BinaryOp(methods[op], left, right) 242 | 243 | return pg.build() 244 | -------------------------------------------------------------------------------- /lang/ast.py: -------------------------------------------------------------------------------- 1 | import operator 2 | import math 3 | 4 | from lang.scope import Scope 5 | 6 | 7 | class Node: 8 | def id(self): 9 | return str(hash(self)) 10 | 11 | 12 | class Program(Node): 13 | def __init__(self, block): 14 | self.block = block 15 | 16 | def eval(self, opt, scope): 17 | return self.block.eval(opt, scope) 18 | 19 | def draw(self, g): 20 | g.node(self.id(), "Program") 21 | g.edge(self.id(), self.block.draw(g)) 22 | return self.id() 23 | 24 | 25 | class Block(Node): 26 | def __init__(self, block): 27 | self.block = block 28 | 29 | def eval(self, opt, scope, args={}): 30 | if self.block is None: 31 | return None 32 | scope.push() 33 | for name, value in args.items(): 34 | scope.add(name, value) 35 | value = None 36 | for stmt in self.block: 37 | value = stmt.eval(opt, scope) 38 | scope.pop() 39 | symbols = scope.last_pop 40 | 41 | if opt: 42 | unused = [] 43 | for sym, used in symbols.used.items(): 44 | if not used: 45 | unused.append(sym) 46 | to_remove = [] 47 | for stmt in self.block: 48 | if isinstance(stmt, (Define, Fn)) and stmt.symbol in unused: 49 | to_remove.append(stmt) 50 | if to_remove: 51 | print(f"Removing {len(to_remove)} unused definitions") 52 | for r in to_remove: 53 | self.block.remove(r) 54 | 55 | return value 56 | 57 | def draw(self, g): 58 | g.node(self.id(), "Block") 59 | if self.block: 60 | for stmt in self.block: 61 | g.edge(self.id(), stmt.draw(g)) 62 | return self.id() 63 | 64 | 65 | class Statement(Node): 66 | def __init__(self, stmt): 67 | self.stmt = stmt 68 | 69 | def eval(self, opt, scope): 70 | return self.stmt.eval(opt, scope) 71 | 72 | def draw(self, g): 73 | g.node(self.id(), "Statement") 74 | g.edge(self.id(), self.stmt.draw(g)) 75 | return self.id() 76 | 77 | 78 | class Fn(Node): 79 | def __init__(self, symbol, args, block): 80 | self.symbol = symbol 81 | self.args = args 82 | self.block = block 83 | self.scope = Scope() 84 | 85 | def eval(self, opt, scope): 86 | self.scope.symbols_stack = scope.symbols_stack[:] 87 | scope.add(self.symbol, self) 88 | 89 | def draw(self, g): 90 | g.node(self.id(), "Define Fn: " + self.symbol) 91 | g.edge(self.id(), self.args.draw(g), "Args") 92 | g.edge(self.id(), self.block.draw(g), "Block") 93 | return self.id() 94 | 95 | 96 | class FnArg(Node): 97 | def __init__(self, symbol, type): 98 | self.symbol = symbol 99 | self.type = type 100 | 101 | def eval(self, opt, scope): 102 | return self.symbol, self.type 103 | 104 | def draw(self, g): 105 | g.node(self.id(), "Arg: " + self.symbol) 106 | g.edge(self.id(), self.type.draw(g), "Type") 107 | return self.id() 108 | 109 | 110 | class FnArgs(Node): 111 | def __init__(self, args): 112 | self.args = args 113 | 114 | def eval(self, opt, scope): 115 | args = [] 116 | for a in self.args: 117 | args.append(a.eval(opt, scope)) 118 | return args 119 | 120 | def draw(self, g): 121 | g.node(self.id(), "FnArgs") 122 | for a in self.args: 123 | g.edge(self.id(), a.draw(g)) 124 | return self.id() 125 | 126 | 127 | class Define(Node): 128 | def __init__(self, symbol, value): 129 | self.symbol = symbol 130 | self.value = value 131 | 132 | def eval(self, opt, scope): 133 | scope.add(self.symbol, self.value.eval(opt, scope)) 134 | return None 135 | 136 | def draw(self, g): 137 | g.node(self.id(), "Define: " + self.symbol) 138 | g.edge(self.id(), self.value.draw(g)) 139 | return self.id() 140 | 141 | 142 | class Assign(Node): 143 | def __init__(self, symbol, value): 144 | self.symbol = symbol 145 | self.value = value 146 | 147 | def eval(self, opt, scope): 148 | scope.set(self.symbol, self.value.eval(opt, scope)) 149 | return None 150 | 151 | def draw(self, g): 152 | g.node(self.id(), "Assign: " + self.symbol) 153 | g.edge(self.id(), self.value.draw(g)) 154 | return self.id() 155 | 156 | 157 | class Print(Node): 158 | def __init__(self, value, newline): 159 | self.value = value 160 | self.newline = newline 161 | 162 | def eval(self, opt, scope): 163 | if opt: 164 | self.value.eval(opt, scope) 165 | else: 166 | if self.newline: 167 | print(self.value.eval(opt, scope)) 168 | else: 169 | print(self.value.eval(opt, scope), end="") 170 | return None 171 | 172 | def draw(self, g): 173 | newline = "ln" if self.newline else "" 174 | g.node(self.id(), "Print" + newline) 175 | g.edge(self.id(), self.value.draw(g)) 176 | return self.id() 177 | 178 | 179 | class ValueInt(Node): 180 | def __init__(self, value): 181 | self.value = value 182 | 183 | def eval(self, opt, scope): 184 | return self.value 185 | 186 | def draw(self, g): 187 | g.node(self.id(), "ValueInt: " + str(self.value)) 188 | return self.id() 189 | 190 | 191 | class ValueFloat(Node): 192 | def __init__(self, value): 193 | self.value = value 194 | 195 | def eval(self, opt, scope): 196 | return self.value 197 | 198 | def draw(self, g): 199 | g.node(self.id(), "ValueFloat: " + str(self.value)) 200 | return self.id() 201 | 202 | 203 | class ValueStr(Node): 204 | def __init__(self, value): 205 | self.value = value[1:-1] 206 | 207 | def eval(self, opt, scope): 208 | return self.value 209 | 210 | def draw(self, g): 211 | g.node(self.id(), "ValueStr: " + self.value) 212 | return self.id() 213 | 214 | 215 | class ValueTrue(Node): 216 | def eval(self, opt, scope): 217 | return True 218 | 219 | def draw(self, g): 220 | g.node(self.id(), "ValueTrue") 221 | return self.id() 222 | 223 | 224 | class ValueFalse(Node): 225 | def eval(self, opt, scope): 226 | return False 227 | 228 | def draw(self, g): 229 | g.node(self.id(), "ValueFalse") 230 | return self.id() 231 | 232 | 233 | class ValueSymbol(Node): 234 | def __init__(self, symbol): 235 | self.symbol = symbol 236 | 237 | def eval(self, opt, scope): 238 | return scope.get(self.symbol) 239 | 240 | def draw(self, g): 241 | g.node(self.id(), "ValueSymbol: " + self.symbol) 242 | return self.id() 243 | 244 | 245 | class Type(Node): 246 | def __init__(self, type): 247 | types = { 248 | "INT": int, 249 | "FLOAT": float, 250 | "STR": str, 251 | "BOOL": bool, 252 | } 253 | 254 | self.type = types[type] 255 | 256 | def eval(self, opt, scope): 257 | return self.type 258 | 259 | def draw(self, g): 260 | g.node(self.id(), "Type: " + self.type.__name__) 261 | return self.id() 262 | 263 | 264 | class BinaryOp(Node): 265 | def __init__(self, op, left, right): 266 | self.op = op 267 | self.left = left 268 | self.right = right 269 | 270 | def eval(self, opt, scope): 271 | left = self.left.eval(opt, scope) 272 | right = self.right.eval(opt, scope) 273 | 274 | if not isinstance(left, type(right)): 275 | if isinstance(left, int) and isinstance(right, float): 276 | return self.op(float(left), right) 277 | elif isinstance(left, float) and isinstance(right, int): 278 | return self.op(left, float(right)) 279 | ltype = self.left.__class__.__name__ 280 | rtype = self.right.__class__.__name__ 281 | raise ValueError(f"Type mismatch between {ltype} and {rtype}") 282 | elif isinstance(left, ValueStr) and self.op is not operator.add: 283 | raise ValueError("Invalid string operation") 284 | else: 285 | return self.op(left, right) 286 | 287 | def draw(self, g): 288 | g.node(self.id(), "BinaryOp: " + self.op.__name__) 289 | g.edge(self.id(), self.left.draw(g), "Left") 290 | g.edge(self.id(), self.right.draw(g), "Right") 291 | return self.id() 292 | 293 | 294 | class If(Node): 295 | def __init__(self, cond, block): 296 | self.cond = cond 297 | self.block = block 298 | 299 | def eval(self, opt, scope): 300 | scope.push() 301 | value = None 302 | if opt: 303 | self.cond.eval(opt, scope) 304 | self.block.eval(opt, scope) 305 | else: 306 | if self.cond.eval(opt, scope): 307 | value = self.block.eval(opt, scope) 308 | scope.pop() 309 | return value 310 | 311 | def draw(self, g): 312 | g.node(self.id(), "If") 313 | g.edge(self.id(), self.cond.draw(g), "Condition") 314 | g.edge(self.id(), self.block.draw(g), "Consequence") 315 | return self.id() 316 | 317 | 318 | class IfElse(Node): 319 | def __init__(self, cond, true_block, false_block): 320 | self.cond = cond 321 | self.true_block = true_block 322 | self.false_block = false_block 323 | 324 | def eval(self, opt, scope): 325 | scope.push() 326 | value = None 327 | if opt: 328 | self.cond.eval(opt, scope) 329 | self.true_block.eval(opt, scope) 330 | self.false_block.eval(opt, scope) 331 | else: 332 | if self.cond.eval(opt, scope): 333 | value = self.true_block.eval(opt, scope) 334 | else: 335 | value = self.false_block.eval(opt, scope) 336 | scope.pop() 337 | return value 338 | 339 | def draw(self, g): 340 | g.node(self.id(), "IfElse") 341 | g.edge(self.id(), self.cond.draw(g), "Condition") 342 | g.edge(self.id(), self.true_block.draw(g), "Consequence") 343 | g.edge(self.id(), self.false_block.draw(g), "Alternative") 344 | return self.id() 345 | 346 | 347 | class While(Node): 348 | def __init__(self, cond, block): 349 | self.cond = cond 350 | self.block = block 351 | 352 | def eval(self, opt, scope): 353 | scope.push() 354 | value = None 355 | if opt: 356 | self.cond.eval(opt, scope) 357 | self.block.eval(opt, scope) 358 | else: 359 | while self.cond.eval(opt, scope): 360 | value = self.block.eval(opt, scope) 361 | scope.pop() 362 | return value 363 | 364 | def draw(self, g): 365 | g.node(self.id(), "While") 366 | g.edge(self.id(), self.cond.draw(g), "Condition") 367 | g.edge(self.id(), self.block.draw(g), "Consequence") 368 | return self.id() 369 | 370 | 371 | class For(Node): 372 | def __init__(self, begin, cond, step, block): 373 | self.begin = begin 374 | self.cond = cond 375 | self.step = step 376 | self.block = block 377 | 378 | def eval(self, opt, scope): 379 | scope.push() 380 | self.begin.eval(opt, scope) 381 | value = None 382 | if opt: 383 | self.cond.eval(opt, scope) 384 | self.block.eval(opt, scope) 385 | self.step.eval(opt, scope) 386 | else: 387 | while self.cond.eval(opt, scope): 388 | value = self.block.eval(opt, scope) 389 | self.step.eval(opt, scope) 390 | scope.pop() 391 | return value 392 | 393 | def draw(self, g): 394 | g.node(self.id(), "For") 395 | g.edge(self.id(), self.begin.draw(g), "Begin") 396 | g.edge(self.id(), self.cond.draw(g), "Condition") 397 | g.edge(self.id(), self.step.draw(g), "Step") 398 | g.edge(self.id(), self.block.draw(g), "Consequnce") 399 | return self.id() 400 | 401 | 402 | class Minus(Node): 403 | def __init__(self, value): 404 | self.value = value 405 | 406 | def eval(self, opt, scope): 407 | value = self.value.eval(opt, scope) 408 | if not isinstance(value, int) and not isinstance(value, float): 409 | type = value.__class__.__name__ 410 | raise ValueError(f"Cannot negate {type}") 411 | return value * -1 412 | 413 | def draw(self, g): 414 | g.node(self.id(), "Unary minus") 415 | g.edge(self.id(), self.value.draw(g)) 416 | return self.id() 417 | 418 | 419 | class Not(Node): 420 | def __init__(self, value): 421 | self.value = value 422 | 423 | def eval(self, opt, scope): 424 | value = self.value.eval(opt, scope) 425 | if not isinstance(value, bool): 426 | type = value.__class__.__name__ 427 | raise ValueError(f"Cannot negate {type}") 428 | return not value 429 | 430 | def draw(self, g): 431 | g.node(self.id(), "Negate") 432 | g.edge(self.id(), self.value.draw(g)) 433 | return self.id() 434 | 435 | 436 | class Cast(Node): 437 | def __init__(self, type, value): 438 | self.type = type 439 | self.value = value 440 | 441 | def eval(self, opt, scope): 442 | cast = self.type.eval(opt, scope) 443 | return cast(self.value.eval(opt, scope)) 444 | 445 | def draw(self, g): 446 | g.node(self.id(), "Cast") 447 | g.edge(self.id(), self.type.draw(g), "Type") 448 | g.edge(self.id(), self.value.draw(g), "Value") 449 | return self.id() 450 | 451 | 452 | class Args(Node): 453 | def __init__(self, args): 454 | self.args = args 455 | 456 | def eval(self, opt, scope): 457 | args = [] 458 | for a in self.args: 459 | args.append(a.eval(opt, scope)) 460 | return args 461 | 462 | def draw(self, g): 463 | g.node(self.id(), "Args") 464 | for a in self.args: 465 | g.edge(self.id(), a.draw(g)) 466 | return self.id() 467 | 468 | 469 | class Call(Node): 470 | def __init__(self, symbol, args): 471 | self.symbol = symbol 472 | self.args = args 473 | 474 | def eval(self, opt, scope): 475 | builtin = {"sin": math.sin, "cos": math.cos, "pi": lambda: math.pi} 476 | 477 | evaled = self.args.eval(opt, scope) 478 | 479 | if self.symbol in builtin: 480 | return builtin[self.symbol](*evaled) 481 | 482 | fn = scope.get(self.symbol) 483 | types = fn.args.eval(opt, scope) 484 | if len(evaled) != len(types): 485 | raise ValueError(f"Invalid number of arguments passed to '{self.symbol}'") 486 | 487 | args = {} 488 | for i in range(len(types)): 489 | value = evaled[i] 490 | name, expected_type = types[i] 491 | expected_type = expected_type.eval(opt, scope) 492 | try: 493 | args[name] = expected_type(value) 494 | except ValueError: 495 | raise ValueError( 496 | f"Cannot convert '{value}' to {str(expected_type.__name__)}" 497 | ) 498 | return fn.block.eval(opt, fn.scope, args) 499 | 500 | def draw(self, g): 501 | g.node(self.id(), "Call: " + self.symbol) 502 | g.edge(self.id(), self.args.draw(g), "Args") 503 | return self.id() 504 | --------------------------------------------------------------------------------