├── .gitignore ├── LICENSE ├── README.md ├── examples ├── CountdownLoop.c ├── CountdownLoop.tb ├── HelloWorld.c ├── HelloWorld.tb ├── YouAreAwesome.c └── YouAreAwesome.tb ├── requirements.txt ├── tiny_basic.py └── tiny_basic ├── __init__.py ├── compiler.py ├── interpreter.py └── parser.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac 2 | .DS_Store 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 hachibu 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tiny_basic 2 | 3 | [Tiny BASIC](https://en.wikipedia.org/wiki/Tiny_BASIC) compiler and interpreter. 4 | 5 | ## Requirements 6 | 7 | - [Python 3](https://www.python.org/downloads) 8 | 9 | ## Installation 10 | 11 | pip3 install -r requirements.txt 12 | 13 | ## Usage 14 | 15 | ### Run Program 16 | 17 | ./tiny_basic.py examples/HelloWorld.tb 18 | 19 | ### Compile Program to C 20 | 21 | ./tiny_basic.py examples/HelloWorld.tb --compile 22 | 23 | ### Start Interactive Console 24 | 25 | ./tiny_basic.py 26 | -------------------------------------------------------------------------------- /examples/CountdownLoop.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main(void) { 6 | int A; 7 | // A PROGRAM THAT COUNTS DOWN FROM 10 TO 0 8 | label_10: 9 | A = 10; 10 | label_20: 11 | if (A<0) { 12 | goto label_60; 13 | } 14 | label_30: 15 | printf("%d\n", A); 16 | label_40: 17 | A = A-1; 18 | label_50: 19 | goto label_20; 20 | label_60: 21 | printf("%s\n", "BLAST OFF!"); 22 | label_70: 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /examples/CountdownLoop.tb: -------------------------------------------------------------------------------- 1 | REM "A PROGRAM THAT COUNTS DOWN FROM 10 TO 0" 2 | 3 | 10 LET A = 10 4 | 20 IF A < 0 THEN GOTO 60 5 | 30 PRINT A 6 | 40 LET A = A - 1 7 | 50 GOTO 20 8 | 60 PRINT "BLAST OFF!" 9 | 70 END 10 | 11 | RUN 12 | -------------------------------------------------------------------------------- /examples/HelloWorld.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main(void) { 6 | char* A; 7 | // A VERY SIMPLE PROGRAM 8 | label_10: 9 | A = "Hello World!"; 10 | label_20: 11 | printf("%s\n", A); 12 | label_30: 13 | return 0; 14 | } 15 | -------------------------------------------------------------------------------- /examples/HelloWorld.tb: -------------------------------------------------------------------------------- 1 | REM "A VERY SIMPLE PROGRAM" 2 | 3 | 10 LET A = "Hello World!" 4 | 20 PRINT A 5 | 30 END 6 | 7 | RUN 8 | -------------------------------------------------------------------------------- /examples/YouAreAwesome.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main(void) { 6 | char* A; 7 | // A PROGRAM THAT SAYS YOU'RE AWESOME 8 | label_10: 9 | printf("%s\n", "What's your name?"); 10 | label_20: 11 | A = malloc(sizeof(char) * 50); 12 | fgets(A, 50, stdin); 13 | if (A[strlen(A) - 1] == '\n') { 14 | A[strlen(A) - 1] = '\0'; 15 | } 16 | label_30: 17 | printf("%s %s\n", A, "is awesome!"); 18 | label_40: 19 | free(A); 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /examples/YouAreAwesome.tb: -------------------------------------------------------------------------------- 1 | REM "A PROGRAM THAT SAYS YOU'RE AWESOME" 2 | 3 | 10 PRINT "What's your name?" 4 | 20 INPUT A 5 | 30 PRINT A, "is awesome!" 6 | 40 END 7 | 8 | RUN 9 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Peglet==0.1.1 2 | -------------------------------------------------------------------------------- /tiny_basic.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from tiny_basic import compiler, interpreter 4 | import argparse 5 | import io 6 | import os 7 | 8 | argParser = argparse.ArgumentParser() 9 | 10 | argParser.add_argument("input", nargs="?") 11 | argParser.add_argument("--compile", action="store_true") 12 | 13 | args = argParser.parse_args() 14 | interpreter = interpreter.Interpreter() 15 | compiler = compiler.Compiler() 16 | 17 | if not args.input: 18 | interpreter.repl() 19 | elif not os.path.isfile(args.input): 20 | print("Input file not found.") 21 | exit(1) 22 | else: 23 | with io.open(args.input, "r") as f: 24 | program = f.read() 25 | if args.compile: 26 | compiler.compile(program) 27 | else: 28 | interpreter.interpret(program) 29 | -------------------------------------------------------------------------------- /tiny_basic/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hachibu/tiny_basic/ab356772e5995e4b06bf70bff90499ebee2f0cb9/tiny_basic/__init__.py -------------------------------------------------------------------------------- /tiny_basic/compiler.py: -------------------------------------------------------------------------------- 1 | from .parser import Parser 2 | import re 3 | 4 | class Compiler(object): 5 | def __init__(self): 6 | self.parser = Parser() 7 | self.parse_tree = None 8 | self.symbols = {} 9 | self.malloc_symbols = {} 10 | self.level = 0 11 | 12 | def write(self, string): 13 | print((self.level * "\t") + string) 14 | 15 | def compile(self, program): 16 | self.parse_tree = self.parser(program) 17 | 18 | self.write("#include ") 19 | self.write("#include ") 20 | self.write("#include \n") 21 | self.write("int main(void) {") 22 | 23 | self.level += 1 24 | 25 | for line in self.parse_tree: 26 | if "LET" in line: 27 | id = line[2] 28 | if id not in self.symbols: 29 | self.compile_stmt(line[1:]) 30 | elif "INPUT" in line: 31 | id = line[2] 32 | if id not in self.symbols: 33 | self.compile_var((id, '""')) 34 | 35 | for line in self.parse_tree: 36 | self.compile_stmt(line) 37 | 38 | self.level -= 1 39 | 40 | self.write("}") 41 | 42 | def compile_stmt(self, stmt): 43 | head, tail = stmt[0], stmt[1:] 44 | if tail: 45 | if head == "IF": 46 | self.compile_if(tail) 47 | elif head == "LET": 48 | self.compile_var(tail) 49 | elif head == "REM": 50 | self.compile_comment(tail) 51 | elif head == "GOTO": 52 | self.compile_goto(tail) 53 | elif head == "PRINT": 54 | self.compile_printf(tail) 55 | elif head == "INPUT": 56 | self.compile_input(tail) 57 | else: 58 | self.compile_label(head) 59 | self.level += 1 60 | self.compile_stmt(tail) 61 | self.level -= 1 62 | else: 63 | if head == "END": 64 | self.compile_return() 65 | 66 | def compile_input(self, xs): 67 | id, buffer = xs[0], 50 68 | self.malloc_symbols[id] = buffer 69 | self.write("{0} = malloc(sizeof(char) * {1});".format(id, buffer)) 70 | self.write("fgets({0}, {1}, stdin);".format(id, buffer)) 71 | self.write("if ({0}[strlen({0}) - 1] == '\\n') {{".format(id, buffer)) 72 | self.level += 1 73 | self.write("{0}[strlen({0}) - 1] = '\\0';".format(id, buffer)) 74 | self.level -= 1 75 | self.write("}") 76 | 77 | def compile_if(self, xs): 78 | cond, stmt = xs[0], xs[2:] 79 | self.write("if (%s) {" % (cond)) 80 | self.level += 1 81 | self.compile_stmt(stmt) 82 | self.level -= 1 83 | self.write("}") 84 | 85 | def compile_goto(self, xs): 86 | self.write("goto label_%s;" % xs[0]) 87 | 88 | def compile_var(self, xs): 89 | id = xs[0] 90 | if id in self.symbols: 91 | self.compile_var_set(xs) 92 | else: 93 | self.compile_var_dec(xs) 94 | 95 | def compile_var_dec(self, xs): 96 | t, id, v = None, xs[0], xs[1] 97 | if self.is_quoted(v): 98 | t = "char" 99 | else: 100 | t = "int" 101 | self.symbols[id] = (t, v) 102 | if t == "char": 103 | self.write("%s* %s;" % (t, id)) 104 | elif t == "int": 105 | self.write("%s %s;" % (t, id)) 106 | 107 | def compile_var_set(self, xs): 108 | id, nv = xs[0], xs[1] 109 | t, ov = self.symbols[id] 110 | self.symbols[id] = (t, nv) 111 | self.write("%s = %s;" % (id, nv)) 112 | 113 | def compile_comment(self, xs): 114 | self.write("// %s" % xs[0].replace('"', "")) 115 | 116 | def compile_label(self, n): 117 | self.write("label_%s:" % n) 118 | 119 | def compile_printf(self, xs): 120 | fmt, args = [], [] 121 | for x in xs: 122 | if x in self.symbols: 123 | t, v = self.symbols[x] 124 | if t == "char": 125 | fmt.append("%s") 126 | elif t == "int": 127 | fmt.append("%d") 128 | args.append(x) 129 | else: 130 | try: 131 | x = int(eval(x)) 132 | fmt.append("%d") 133 | args.append(str(x)) 134 | except: 135 | fmt.append("%s") 136 | args.append(x) 137 | if fmt and args: 138 | fmt = " ".join(fmt) 139 | args = ", ".join(args) 140 | self.write('printf("{0}\\n", {1});'.format(fmt, args)) 141 | 142 | def compile_return(self): 143 | for id in self.malloc_symbols: 144 | self.write("free(%s);" % id) 145 | self.write("return 0;") 146 | 147 | def is_quoted(self, s): 148 | return re.match('^".*"$', s) 149 | 150 | -------------------------------------------------------------------------------- /tiny_basic/interpreter.py: -------------------------------------------------------------------------------- 1 | from .parser import Parser 2 | import re 3 | 4 | class Interpreter(object): 5 | def __init__(self): 6 | self.curr = 0 7 | self.memory = {} 8 | self.symbols = {} 9 | self.parse_tree = None 10 | self.parser = Parser() 11 | 12 | def interpret(self, program): 13 | self.parse_tree = self.parser(program) 14 | for line in self.parse_tree: 15 | if len(line) > 1: 16 | head, tail = line[0], line[1:] 17 | self.memory[head] = tail 18 | for line in self.parse_tree: 19 | if len(line) == 1: 20 | self.stmt(line) 21 | self.curr = 0 22 | 23 | def stmt(self, stmt): 24 | head, tail = stmt[0], stmt[1:] 25 | if head == "PRINT": 26 | self.print_stmt(tail) 27 | elif head == "LET": 28 | self.let_stmt(tail) 29 | elif head == "INPUT": 30 | self.input_stmt(tail) 31 | elif head == "IF": 32 | self.if_stmt(tail) 33 | elif head == "GOTO": 34 | self.goto_stmt(tail) 35 | elif head == "CLEAR": 36 | self.clear_stmt() 37 | elif head == "LIST": 38 | self.list_stmt() 39 | elif head == "RUN": 40 | self.run_stmt() 41 | elif head == "END": 42 | self.end_stmt() 43 | 44 | def print_stmt(self, xs): 45 | print(" ".join(self.expr_list(xs))) 46 | 47 | def let_stmt(self, xs): 48 | head, tail = xs[0], xs[1] 49 | self.symbols[head] = self.expr(tail) 50 | 51 | def input_stmt(self, xs): 52 | for x in xs: 53 | self.symbols[x] = str(input("? ")) 54 | 55 | def if_stmt(self, xs): 56 | head, tail = xs[0], xs[2:] 57 | if self.expr(head) == "True": 58 | self.stmt(tail) 59 | 60 | def goto_stmt(self, xs): 61 | n = self.expr(xs[0]) 62 | self.curr = n 63 | self.run_stmt() 64 | 65 | def run_stmt(self): 66 | stmts = self.gen_stmts() 67 | while(stmts): 68 | try: 69 | self.curr, line = next(stmts) 70 | self.stmt(line) 71 | except StopIteration: 72 | break 73 | 74 | def gen_stmts(self): 75 | for k in sorted(self.memory): 76 | c = int(self.curr) 77 | if c != -1 and int(k) >= c: 78 | yield (k, self.memory[k]) 79 | 80 | def is_int_str(self, s): 81 | return re.search("^[0-9]+$", s) != None 82 | 83 | def end_stmt(self): 84 | self.curr = -1 85 | 86 | def list_stmt(self): 87 | for k in sorted(self.memory): 88 | print(" ".join(list(self.memory[k]))) 89 | 90 | def clear_stmt(self): 91 | self.memory = {} 92 | 93 | def expr_list(self, xs): 94 | return [self.expr(x) for x in xs] 95 | 96 | def expr(self, x): 97 | if re.match("^\".*\"$", x): 98 | return x.replace("\"", "") 99 | else: 100 | vs = re.findall("[A-Z]", x) 101 | if vs: 102 | for v in vs: 103 | x = x.replace(v, self.var(v)) 104 | try: 105 | return str(eval(x)) 106 | except: 107 | return x.replace("\"", "") 108 | 109 | def var(self, x): 110 | return self.symbols[x] 111 | 112 | def repl(self): 113 | try: 114 | line = str(input("tiny_basic> ")).upper() 115 | if line == "QUIT": 116 | exit(0) 117 | try: 118 | self.interpret(line) 119 | except: 120 | if line: 121 | print('PARSE ERROR "{}"'.format(line)) 122 | self.repl() 123 | except: 124 | pass 125 | -------------------------------------------------------------------------------- /tiny_basic/parser.py: -------------------------------------------------------------------------------- 1 | import re 2 | import peglet 3 | 4 | class Parser(object): 5 | grammar = r""" 6 | lines = _ line _ lines 7 | | _ line 8 | line = num _ stmt hug 9 | | stmt hug 10 | stmt = print_stmt 11 | | let_stmt 12 | | input_stmt 13 | | if_stmt 14 | | goto_stmt 15 | | clear_stmt 16 | | list_stmt 17 | | run_stmt 18 | | end_stmt 19 | | rem_stmt 20 | 21 | print_stmt = (PRINT\b) _ expr_list 22 | let_stmt = (LET\b) _ var _ (?:=) _ expr 23 | | (LET\b) _ var _ (?:=) _ str 24 | input_stmt = (INPUT\b) _ var_list 25 | if_stmt = (IF\b) _ expr _ (THEN\b) _ stmt 26 | goto_stmt = (GOTO\b) _ expr 27 | clear_stmt = (CLEAR\b) 28 | list_stmt = (LIST\b) 29 | run_stmt = (RUN\b) 30 | end_stmt = (END\b) 31 | rem_stmt = (REM\b) _ str 32 | 33 | expr_list = expr _ , _ expr_list 34 | | expr 35 | | str _ , _ expr_list 36 | | str 37 | expr = term _ binop _ expr join 38 | | term _ relop _ expr join 39 | | term 40 | term = var 41 | | num 42 | | l_paren _ expr _ r_paren join 43 | var_list = var _ , _ var_list 44 | | var 45 | var = ([A-Z]) 46 | 47 | str = " chars " _ join quote 48 | | ' sqchars ' _ join 49 | chars = char chars 50 | | 51 | char = ([^\x00-\x1f"\\]) 52 | | esc_char 53 | sqchars = sqchar sqchars 54 | | 55 | sqchar = ([^\x00-\x1f'\\]) 56 | | esc_char 57 | esc_char = \\(['"/\\]) 58 | | \\([bfnrt]) escape 59 | num = (\-) num 60 | | (\d+) 61 | relop = (<>|><|<=|<|>=|>|=) repop 62 | binop = (\+|\-|\*|\/) 63 | l_paren = (\() 64 | r_paren = (\)) 65 | _ = \s* 66 | """ 67 | 68 | def __init__(self): 69 | kwargs = {"hug" : peglet.hug, 70 | "join" : peglet.join, 71 | "repop" : self.repop, 72 | "quote" : self.quote, 73 | "escape" : re.escape} 74 | self.parser = peglet.Parser(self.grammar, **kwargs) 75 | 76 | def __call__(self, program): 77 | return self.parser(program) 78 | 79 | def repop(self, token): 80 | if token == "<>" or token == "><": 81 | return "!=" 82 | return token 83 | 84 | def quote(self, token): 85 | return '"%s"' % token 86 | --------------------------------------------------------------------------------