├── .gitattributes ├── README.md ├── basic_interpreter.py ├── basic_lexer.py └── basic_parser.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | howCode's Programming Language 2 | ============================== 3 | 4 | This is the source code howCode's programming language series. 5 | 6 | You can watch the videos that accompany this series here: https://www.youtube.com/playlist?list=PLBOh8f9FoHHgPEbiK-FSdSw3FiyP78fbk 7 | -------------------------------------------------------------------------------- /basic_interpreter.py: -------------------------------------------------------------------------------- 1 | from sly import Lexer 2 | from sly import Parser 3 | 4 | class BasicLexer(Lexer): 5 | tokens = { NAME, NUMBER, STRING, IF, THEN, ELSE, FOR, FUN, TO, ARROW, EQEQ } 6 | ignore = '\t ' 7 | 8 | literals = { '=', '+', '-', '/', '*', '(', ')', ',', ';' } 9 | 10 | # Define tokens 11 | IF = r'IF' 12 | THEN = r'THEN' 13 | ELSE = r'ELSE' 14 | FOR = r'FOR' 15 | FUN = r'FUN' 16 | TO = r'TO' 17 | ARROW = r'->' 18 | NAME = r'[a-zA-Z_][a-zA-Z0-9_]*' 19 | STRING = r'\".*?\"' 20 | 21 | EQEQ = r'==' 22 | 23 | @_(r'\d+') 24 | def NUMBER(self, t): 25 | t.value = int(t.value) 26 | return t 27 | 28 | @_(r'#.*') 29 | def COMMENT(self, t): 30 | pass 31 | 32 | @_(r'\n+') 33 | def newline(self,t ): 34 | self.lineno = t.value.count('\n') 35 | 36 | class BasicParser(Parser): 37 | tokens = BasicLexer.tokens 38 | 39 | precedence = ( 40 | ('left', '+', '-'), 41 | ('left', '*', '/'), 42 | ('right', 'UMINUS'), 43 | ) 44 | 45 | def __init__(self): 46 | self.env = { } 47 | 48 | @_('') 49 | def statement(self, p): 50 | pass 51 | 52 | @_('FOR var_assign TO expr THEN statement') 53 | def statement(self, p): 54 | return ('for_loop', ('for_loop_setup', p.var_assign, p.expr), p.statement) 55 | 56 | @_('IF condition THEN statement ELSE statement') 57 | def statement(self, p): 58 | return ('if_stmt', p.condition, ('branch', p.statement0, p.statement1)) 59 | 60 | @_('FUN NAME "(" ")" ARROW statement') 61 | def statement(self, p): 62 | return ('fun_def', p.NAME, p.statement) 63 | 64 | @_('NAME "(" ")"') 65 | def statement(self, p): 66 | return ('fun_call', p.NAME) 67 | 68 | @_('expr EQEQ expr') 69 | def condition(self, p): 70 | return ('condition_eqeq', p.expr0, p.expr1) 71 | 72 | @_('var_assign') 73 | def statement(self, p): 74 | return p.var_assign 75 | 76 | @_('NAME "=" expr') 77 | def var_assign(self, p): 78 | return ('var_assign', p.NAME, p.expr) 79 | 80 | @_('NAME "=" STRING') 81 | def var_assign(self, p): 82 | return ('var_assign', p.NAME, p.STRING) 83 | 84 | @_('expr') 85 | def statement(self, p): 86 | return (p.expr) 87 | 88 | @_('expr "+" expr') 89 | def expr(self, p): 90 | return ('add', p.expr0, p.expr1) 91 | 92 | @_('expr "-" expr') 93 | def expr(self, p): 94 | return ('sub', p.expr0, p.expr1) 95 | 96 | @_('expr "*" expr') 97 | def expr(self, p): 98 | return ('mul', p.expr0, p.expr1) 99 | 100 | @_('expr "/" expr') 101 | def expr(self, p): 102 | return ('div', p.expr0, p.expr1) 103 | 104 | @_('"-" expr %prec UMINUS') 105 | def expr(self, p): 106 | return p.expr 107 | 108 | @_('NAME') 109 | def expr(self, p): 110 | return ('var', p.NAME) 111 | 112 | @_('NUMBER') 113 | def expr(self, p): 114 | return ('num', p.NUMBER) 115 | 116 | 117 | 118 | class BasicExecute: 119 | 120 | def __init__(self, tree, env): 121 | self.env = env 122 | result = self.walkTree(tree) 123 | if result is not None and isinstance(result, int): 124 | print(result) 125 | if isinstance(result, str) and result[0] == '"': 126 | print(result) 127 | 128 | def walkTree(self, node): 129 | 130 | if isinstance(node, int): 131 | return node 132 | if isinstance(node, str): 133 | return node 134 | 135 | if node is None: 136 | return None 137 | 138 | if node[0] == 'program': 139 | if node[1] == None: 140 | self.walkTree(node[2]) 141 | else: 142 | self.walkTree(node[1]) 143 | self.walkTree(node[2]) 144 | 145 | if node[0] == 'num': 146 | return node[1] 147 | 148 | if node[0] == 'str': 149 | return node[1] 150 | 151 | if node[0] == 'if_stmt': 152 | result = self.walkTree(node[1]) 153 | if result: 154 | return self.walkTree(node[2][1]) 155 | return self.walkTree(node[2][2]) 156 | 157 | if node[0] == 'condition_eqeq': 158 | return self.walkTree(node[1]) == self.walkTree(node[2]) 159 | 160 | if node[0] == 'fun_def': 161 | self.env[node[1]] = node[2] 162 | 163 | if node[0] == 'fun_call': 164 | try: 165 | return self.walkTree(self.env[node[1]]) 166 | except LookupError: 167 | print("Undefined function '%s'" % node[1]) 168 | return 0 169 | 170 | if node[0] == 'add': 171 | return self.walkTree(node[1]) + self.walkTree(node[2]) 172 | elif node[0] == 'sub': 173 | return self.walkTree(node[1]) - self.walkTree(node[2]) 174 | elif node[0] == 'mul': 175 | return self.walkTree(node[1]) * self.walkTree(node[2]) 176 | elif node[0] == 'div': 177 | return self.walkTree(node[1]) / self.walkTree(node[2]) 178 | 179 | if node[0] == 'var_assign': 180 | self.env[node[1]] = self.walkTree(node[2]) 181 | return node[1] 182 | 183 | if node[0] == 'var': 184 | try: 185 | return self.env[node[1]] 186 | except LookupError: 187 | print("Undefined variable '"+node[1]+"' found!") 188 | return 0 189 | 190 | if node[0] == 'for_loop': 191 | if node[1][0] == 'for_loop_setup': 192 | loop_setup = self.walkTree(node[1]) 193 | 194 | loop_count = self.env[loop_setup[0]] 195 | loop_limit = loop_setup[1] 196 | 197 | for i in range(loop_count+1, loop_limit+1): 198 | res = self.walkTree(node[2]) 199 | if res is not None: 200 | print(res) 201 | self.env[loop_setup[0]] = i 202 | del self.env[loop_setup[0]] 203 | 204 | if node[0] == 'for_loop_setup': 205 | return (self.walkTree(node[1]), self.walkTree(node[2])) 206 | 207 | 208 | if __name__ == '__main__': 209 | lexer = BasicLexer() 210 | parser = BasicParser() 211 | env = {} 212 | while True: 213 | try: 214 | text = input('basic > ') 215 | except EOFError: 216 | break 217 | if text: 218 | tree = parser.parse(lexer.tokenize(text)) 219 | BasicExecute(tree, env) 220 | -------------------------------------------------------------------------------- /basic_lexer.py: -------------------------------------------------------------------------------- 1 | from sly import Lexer 2 | 3 | class BasicLexer(Lexer): 4 | tokens = { NAME, NUMBER, STRING, IF, THEN, ELSE, FOR, FUN, TO, ARROW, EQEQ } 5 | ignore = '\t ' 6 | 7 | literals = { '=', '+', '-', '/', '*', '(', ')', ',', ';' } 8 | 9 | # Define tokens 10 | IF = r'IF' 11 | THEN = r'THEN' 12 | ELSE = r'ELSE' 13 | FOR = r'FOR' 14 | FUN = r'FUN' 15 | TO = r'TO' 16 | ARROW = r'->' 17 | NAME = r'[a-zA-Z_][a-zA-Z0-9_]*' 18 | STRING = r'\".*?\"' 19 | 20 | EQEQ = r'==' 21 | 22 | @_(r'\d+') 23 | def NUMBER(self, t): 24 | t.value = int(t.value) 25 | return t 26 | 27 | @_(r'#.*') 28 | def COMMENT(self, t): 29 | pass 30 | 31 | @_(r'\n+') 32 | def newline(self,t ): 33 | self.lineno = t.value.count('\n') 34 | 35 | 36 | 37 | if __name__ == '__main__': 38 | lexer = BasicLexer() 39 | env = {} 40 | while True: 41 | try: 42 | text = input('basic > ') 43 | except EOFError: 44 | break 45 | if text: 46 | lex = lexer.tokenize(text) 47 | for token in lex: 48 | print(token) 49 | -------------------------------------------------------------------------------- /basic_parser.py: -------------------------------------------------------------------------------- 1 | from sly import Lexer 2 | from sly import Parser 3 | 4 | class BasicLexer(Lexer): 5 | tokens = { NAME, NUMBER, STRING, IF, THEN, ELSE, FOR, FUN, TO, ARROW, EQEQ } 6 | ignore = '\t ' 7 | 8 | literals = { '=', '+', '-', '/', '*', '(', ')', ',', ';' } 9 | 10 | # Define tokens 11 | IF = r'IF' 12 | THEN = r'THEN' 13 | ELSE = r'ELSE' 14 | FOR = r'FOR' 15 | FUN = r'FUN' 16 | TO = r'TO' 17 | ARROW = r'->' 18 | NAME = r'[a-zA-Z_][a-zA-Z0-9_]*' 19 | STRING = r'\".*?\"' 20 | 21 | EQEQ = r'==' 22 | 23 | @_(r'\d+') 24 | def NUMBER(self, t): 25 | t.value = int(t.value) 26 | return t 27 | 28 | @_(r'#.*') 29 | def COMMENT(self, t): 30 | pass 31 | 32 | @_(r'\n+') 33 | def newline(self,t ): 34 | self.lineno = t.value.count('\n') 35 | 36 | class BasicParser(Parser): 37 | tokens = BasicLexer.tokens 38 | 39 | precedence = ( 40 | ('left', '+', '-'), 41 | ('left', '*', '/'), 42 | ('right', 'UMINUS'), 43 | ) 44 | 45 | def __init__(self): 46 | self.env = { } 47 | @_('') 48 | def statement(self, p): 49 | pass 50 | 51 | @_('FOR var_assign TO expr THEN statement') 52 | def statement(self, p): 53 | return ('for_loop', ('for_loop_setup', p.var_assign, p.expr), p.statement) 54 | 55 | @_('IF condition THEN statement ELSE statement') 56 | def statement(self, p): 57 | return ('if_stmt', p.condition, ('branch', p.statement0, p.statement1)) 58 | 59 | @_('FUN NAME "(" ")" ARROW statement') 60 | def statement(self, p): 61 | return ('fun_def', p.NAME, p.statement) 62 | 63 | @_('NAME "(" ")"') 64 | def statement(self, p): 65 | return ('fun_call', p.NAME) 66 | 67 | @_('expr EQEQ expr') 68 | def condition(self, p): 69 | return ('condition_eqeq', p.expr0, p.expr1) 70 | 71 | @_('var_assign') 72 | def statement(self, p): 73 | return p.var_assign 74 | 75 | @_('NAME "=" expr') 76 | def var_assign(self, p): 77 | return ('var_assign', p.NAME, p.expr) 78 | 79 | @_('NAME "=" STRING') 80 | def var_assign(self, p): 81 | return ('var_assign', p.NAME, p.STRING) 82 | 83 | @_('expr') 84 | def statement(self, p): 85 | return (p.expr) 86 | 87 | @_('expr "+" expr') 88 | def expr(self, p): 89 | return ('add', p.expr0, p.expr1) 90 | 91 | @_('expr "-" expr') 92 | def expr(self, p): 93 | return ('sub', p.expr0, p.expr1) 94 | 95 | @_('expr "*" expr') 96 | def expr(self, p): 97 | return ('mul', p.expr0, p.expr1) 98 | 99 | @_('expr "/" expr') 100 | def expr(self, p): 101 | return ('div', p.expr0, p.expr1) 102 | 103 | @_('"-" expr %prec UMINUS') 104 | def expr(self, p): 105 | return p.expr 106 | 107 | @_('NAME') 108 | def expr(self, p): 109 | return ('var', p.NAME) 110 | 111 | @_('NUMBER') 112 | def expr(self, p): 113 | return ('num', p.NUMBER) 114 | 115 | if __name__ == '__main__': 116 | lexer = BasicLexer() 117 | parser = BasicParser() 118 | env = {} 119 | while True: 120 | try: 121 | text = input('basic > ') 122 | except EOFError: 123 | break 124 | if text: 125 | tree = parser.parse(lexer.tokenize(text)) 126 | print(tree) 127 | --------------------------------------------------------------------------------