├── part13 ├── semanticerror01.pas ├── symtab01.py ├── symtab02.py ├── symtab03.py ├── symtab04.py ├── symtab05.py ├── symtab06.py └── genastdot.py ├── part15 ├── lexerror.pas ├── idnotfound.pas ├── parsererror.pas └── duplicateid.pas ├── part11 └── python │ ├── nameerror1.pas │ ├── nameerror2.pas │ ├── part11.pas │ └── test_interpreter.py ├── part14 ├── nestedscopes01.pas ├── nestedscopes02.pas ├── dupiderror.pas ├── nestedscopes03.pas ├── nestedscopes04.pas ├── scope01.py ├── scope02.py ├── scope03a.py ├── scope03b.py ├── scope03c.py ├── scope04a.py ├── scope04b.py ├── scope05.py └── genastdot.py ├── part17 ├── part17.pas └── genastdot.py ├── part7 ├── rust │ ├── spi │ │ └── Cargo.toml │ └── README.md └── python │ ├── ex1.py │ ├── ex2.py │ ├── genastdot.py │ ├── test_interpreter.py │ └── genptdot.py ├── part9 └── python │ ├── assignments.txt │ ├── genastdot.py │ └── test_interpreter.py ├── part10 └── python │ ├── part10ast.pas │ ├── part10.pas │ ├── test_interpreter.py │ └── genastdot.py ├── part16 ├── part16.pas ├── solutions.txt └── genastdot.py ├── part18 ├── part18.pas └── genastdot.py ├── part1 ├── factorial.pas └── calc1.py ├── part12 └── python │ ├── part12.pas │ ├── test_interpreter.py │ └── genastdot.py ├── part8 └── python │ ├── testunary.pas │ ├── genastdot.py │ └── test_interpreter.py ├── part19 └── part19.pas ├── part4 ├── test_parser.py ├── test_interpreter.py ├── parser.py └── calc4.py ├── LICENSE ├── README.md ├── part5 ├── test_interpreter.py └── calc5.py ├── part2 ├── test_calc2.py └── calc2.py ├── part6 ├── test_interpreter.py └── calc6.py └── part3 └── calc3.py /part13/semanticerror01.pas: -------------------------------------------------------------------------------- 1 | program Main; 2 | var x : integer; 3 | 4 | begin 5 | x := y; 6 | end. 7 | -------------------------------------------------------------------------------- /part15/lexerror.pas: -------------------------------------------------------------------------------- 1 | program Main; 2 | 3 | begin { Main } 4 | > { lexical error } 5 | end. { Main } 6 | -------------------------------------------------------------------------------- /part11/python/nameerror1.pas: -------------------------------------------------------------------------------- 1 | PROGRAM NameError1; 2 | VAR 3 | a : INTEGER; 4 | 5 | BEGIN 6 | a := 2 + b; 7 | END. 8 | -------------------------------------------------------------------------------- /part14/nestedscopes01.pas: -------------------------------------------------------------------------------- 1 | program Main; 2 | var x, y: integer; 3 | begin { Main } 4 | x := x + y; 5 | end. { Main } 6 | 7 | -------------------------------------------------------------------------------- /part11/python/nameerror2.pas: -------------------------------------------------------------------------------- 1 | PROGRAM NameError2; 2 | VAR 3 | b : INTEGER; 4 | 5 | BEGIN 6 | b := 1; 7 | a := b + 2; 8 | END. 9 | -------------------------------------------------------------------------------- /part17/part17.pas: -------------------------------------------------------------------------------- 1 | program Main; 2 | var x, y : integer; 3 | begin { Main } 4 | y := 7; 5 | x := (y + 3) * 3; 6 | end. { Main } 7 | -------------------------------------------------------------------------------- /part7/rust/spi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "spi" 3 | version = "0.1.0" 4 | authors = ["Ruslan Spivak "] 5 | -------------------------------------------------------------------------------- /part15/idnotfound.pas: -------------------------------------------------------------------------------- 1 | program Main; 2 | var 3 | a : integer; 4 | 5 | begin { Main } 6 | a := b; { semantic error } 7 | end. { Main } 8 | -------------------------------------------------------------------------------- /part15/parsererror.pas: -------------------------------------------------------------------------------- 1 | program Main; 2 | var 3 | a : integer; 4 | 5 | begin { Main } 6 | a := 5 + ; { syntax error} 7 | end. { Main } 8 | -------------------------------------------------------------------------------- /part15/duplicateid.pas: -------------------------------------------------------------------------------- 1 | program Main; 2 | var 3 | a : integer; 4 | a : real; { semantic error } 5 | 6 | begin { Main } 7 | a := 5; 8 | end. { Main } 9 | -------------------------------------------------------------------------------- /part9/python/assignments.txt: -------------------------------------------------------------------------------- 1 | BEGIN 2 | 3 | BEGIN 4 | number := 2; 5 | a := number; 6 | b := 10 * a + 10 * number / 4; 7 | c := a - - b 8 | END; 9 | 10 | x := 11; 11 | END. 12 | -------------------------------------------------------------------------------- /part10/python/part10ast.pas: -------------------------------------------------------------------------------- 1 | PROGRAM Part10AST; 2 | VAR 3 | a, b : INTEGER; 4 | y : REAL; 5 | 6 | BEGIN {Part10AST} 7 | a := 2; 8 | b := 10 * a + 10 * a DIV 4; 9 | y := 20 / 7 + 3.14; 10 | END. {Part10AST} 11 | -------------------------------------------------------------------------------- /part14/nestedscopes02.pas: -------------------------------------------------------------------------------- 1 | program Main; 2 | var x, y: real; 3 | 4 | procedure Alpha(a : integer); 5 | var y : integer; 6 | begin 7 | x := a + x + y; 8 | end; 9 | 10 | begin { Main } 11 | 12 | end. { Main } 13 | 14 | -------------------------------------------------------------------------------- /part16/part16.pas: -------------------------------------------------------------------------------- 1 | program Main; 2 | 3 | procedure Alpha(a : integer; b : integer); 4 | var x : integer; 5 | begin 6 | x := (a + b ) * 2; 7 | end; 8 | 9 | begin { Main } 10 | 11 | Alpha(3 + 5, 7); { procedure call } 12 | 13 | end. { Main } 14 | -------------------------------------------------------------------------------- /part18/part18.pas: -------------------------------------------------------------------------------- 1 | program Main; 2 | 3 | procedure Alpha(a : integer; b : integer); 4 | var x : integer; 5 | begin 6 | x := (a + b ) * 2; 7 | end; 8 | 9 | begin { Main } 10 | 11 | Alpha(3 + 5, 7); { procedure call } 12 | 13 | end. { Main } 14 | -------------------------------------------------------------------------------- /part11/python/part11.pas: -------------------------------------------------------------------------------- 1 | PROGRAM Part11; 2 | VAR 3 | number : INTEGER; 4 | a, b : INTEGER; 5 | y : REAL; 6 | 7 | BEGIN {Part11} 8 | number := 2; 9 | a := number ; 10 | b := 10 * a + 10 * number DIV 4; 11 | y := 20 / 7 + 3.14 12 | END. {Part11} 13 | -------------------------------------------------------------------------------- /part14/dupiderror.pas: -------------------------------------------------------------------------------- 1 | program Main; 2 | var x, y: real; 3 | 4 | procedure Alpha(a : integer); 5 | var y : integer; 6 | var a : real; { ERROR here! } 7 | begin 8 | x := a + x + y; 9 | end; 10 | 11 | begin { Main } 12 | 13 | end. { Main } 14 | -------------------------------------------------------------------------------- /part7/rust/README.md: -------------------------------------------------------------------------------- 1 | Rust implementation of a simple Pascal interpreter: [Let's Build A Simple Interpreter. Part 7.](http://ruslanspivak.com/lsbasi-part7/) 2 | 3 | To run the interpreter: 4 | 5 | $ cd spi 6 | $ cargo run 7 | 8 | To run the tests: 9 | 10 | $ cd spi 11 | $ cargo test -------------------------------------------------------------------------------- /part1/factorial.pas: -------------------------------------------------------------------------------- 1 | program factorial; 2 | 3 | function factorial(n: integer): longint; 4 | begin 5 | if n = 0 then 6 | factorial := 1 7 | else 8 | factorial := n * factorial(n - 1); 9 | end; 10 | 11 | var 12 | n: integer; 13 | 14 | begin 15 | for n := 0 to 16 do 16 | writeln(n, '! = ', factorial(n)); 17 | end. 18 | -------------------------------------------------------------------------------- /part12/python/part12.pas: -------------------------------------------------------------------------------- 1 | PROGRAM Part12; 2 | VAR 3 | a : INTEGER; 4 | 5 | PROCEDURE P1; 6 | VAR 7 | a : REAL; 8 | k : INTEGER; 9 | 10 | PROCEDURE P2; 11 | VAR 12 | a, z : INTEGER; 13 | BEGIN {P2} 14 | z := 777; 15 | END; {P2} 16 | 17 | BEGIN {P1} 18 | 19 | END; {P1} 20 | 21 | BEGIN {Part12} 22 | a := 10; 23 | END. {Part12} 24 | -------------------------------------------------------------------------------- /part8/python/testunary.pas: -------------------------------------------------------------------------------- 1 | { Install Free Pascal: http://www.freepascal.org/ } 2 | { Compile and run: $ fpc testunary.pas && ./testunary } 3 | program testunary; 4 | 5 | begin 6 | writeln('Expected -3. Got ', - 3); 7 | writeln('Expected 3. Got ', + 3); 8 | writeln('Expected 8. Got ', 5 - - - + - 3); 9 | writeln('Expected 10. Got ', 5 - - - + - (3 + 4) - +2); 10 | end. 11 | -------------------------------------------------------------------------------- /part14/nestedscopes03.pas: -------------------------------------------------------------------------------- 1 | program Main; 2 | var x, y : real; 3 | var z : integer; 4 | 5 | procedure AlphaA(a : integer); 6 | var y : integer; 7 | begin { AlphaA } 8 | x := a + x + y; 9 | end; { AlphaA } 10 | 11 | procedure AlphaB(a : integer); 12 | var b : integer; 13 | begin { AlphaB } 14 | end; { AlphaB } 15 | 16 | begin { Main } 17 | end. { Main } 18 | -------------------------------------------------------------------------------- /part19/part19.pas: -------------------------------------------------------------------------------- 1 | program Main; 2 | 3 | procedure Alpha(a : integer; b : integer); 4 | var x : integer; 5 | 6 | procedure Beta(a : integer; b : integer); 7 | var x : integer; 8 | begin 9 | x := a * 10 + b * 2; 10 | end; 11 | 12 | begin 13 | x := (a + b ) * 2; 14 | 15 | Beta(5, 10); { procedure call } 16 | end; 17 | 18 | begin { Main } 19 | 20 | Alpha(3 + 5, 7); { procedure call } 21 | 22 | end. { Main } 23 | -------------------------------------------------------------------------------- /part10/python/part10.pas: -------------------------------------------------------------------------------- 1 | PROGRAM Part10; 2 | VAR 3 | number : INTEGER; 4 | a, b, c, x : INTEGER; 5 | y : REAL; 6 | 7 | BEGIN {Part10} 8 | BEGIN 9 | number := 2; 10 | a := number; 11 | b := 10 * a + 10 * number DIV 4; 12 | c := a - - b 13 | END; 14 | x := 11; 15 | y := 20 / 7 + 3.14; 16 | { writeln('a = ', a); } 17 | { writeln('b = ', b); } 18 | { writeln('c = ', c); } 19 | { writeln('number = ', number); } 20 | { writeln('x = ', x); } 21 | { writeln('y = ', y); } 22 | END. {Part10} 23 | -------------------------------------------------------------------------------- /part14/nestedscopes04.pas: -------------------------------------------------------------------------------- 1 | program Main; 2 | var b, x, y : real; 3 | var z : integer; 4 | 5 | procedure AlphaA(a : integer); 6 | var b : integer; 7 | 8 | procedure Beta(c : integer); 9 | var y : integer; 10 | 11 | procedure Gamma(c : integer); 12 | var x : integer; 13 | begin { Gamma } 14 | x := a + b + c + x + y + z; 15 | end; { Gamma } 16 | 17 | begin { Beta } 18 | 19 | end; { Beta } 20 | 21 | begin { AlphaA } 22 | 23 | end; { AlphaA } 24 | 25 | procedure AlphaB(a : integer); 26 | var c : real; 27 | begin { AlphaB } 28 | c := a + b; 29 | end; { AlphaB } 30 | 31 | begin { Main } 32 | end. { Main } 33 | -------------------------------------------------------------------------------- /part4/test_parser.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class ParserTestCase(unittest.TestCase): 5 | def makeParser(self, text): 6 | from parser import Lexer, Parser 7 | lexer = Lexer(text) 8 | parser = Parser(lexer) 9 | return parser 10 | 11 | def test_expression1(self): 12 | parser = self.makeParser('7') 13 | parser.parse() 14 | 15 | def test_expression2(self): 16 | parser = self.makeParser('7 * 4 / 2') 17 | parser.parse() 18 | 19 | def test_expression3(self): 20 | parser = self.makeParser('7 * 4 / 2 * 3') 21 | parser.parse() 22 | 23 | def test_expression4(self): 24 | parser = self.makeParser('10 * 4 * 2 * 3 / 8') 25 | parser.parse() 26 | 27 | def test_expression_invalid_syntax(self): 28 | parser = self.makeParser('10 *') 29 | with self.assertRaises(Exception): 30 | parser.parse() 31 | 32 | 33 | if __name__ == '__main__': 34 | unittest.main() 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015 Ruslan Spivak 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 | -------------------------------------------------------------------------------- /part16/solutions.txt: -------------------------------------------------------------------------------- 1 | Exercise: Add a check to the semantic analyzer that verifies that the number of 2 | arguments (actual parameters) passed to a procedure call equals the number of 3 | formal parameters defined in the corresponding procedure declaration. 4 | 5 | Solution to the Exercise: 6 | 7 | 1. Add a new error code called WRONG_PARAMS_NUM to the ErrorCode class: 8 | 9 | class ErrorCode(Enum): 10 | UNEXPECTED_TOKEN = 'Unexpected token' 11 | ID_NOT_FOUND = 'Identifier not found' 12 | DUPLICATE_ID = 'Duplicate id found' 13 | WRONG_PARAMS_NUM = 'Wrong number of arguments' 14 | 15 | 16 | 2. Extend the visit_ProcedureCall method with the following check: 17 | 18 | class SemanticAnalyzer(NodeVisitor): 19 | ... 20 | def visit_ProcedureCall(self, node): 21 | proc_symbol = self.current_scope.lookup(node.proc_name) 22 | formal_params = proc_symbol.params 23 | actual_params = node.actual_params 24 | 25 | if len(actual_params) != len(formal_params): 26 | self.error( 27 | error_code=ErrorCode.WRONG_PARAMS_NUM, 28 | token=node.token, 29 | ) 30 | 31 | for param_node in node.actual_params: 32 | self.visit(param_node) 33 | -------------------------------------------------------------------------------- /part13/symtab01.py: -------------------------------------------------------------------------------- 1 | from spi import Lexer, Parser, BuiltinTypeSymbol, VarSymbol 2 | 3 | 4 | class SymbolTable(object): 5 | def __init__(self): 6 | self._symbols = {} 7 | 8 | def __str__(self): 9 | symtab_header = 'Symbol table contents' 10 | lines = ['\n', symtab_header, '_' * len(symtab_header)] 11 | lines.extend( 12 | ('%7s: %r' % (key, value)) 13 | for key, value in self._symbols.items() 14 | ) 15 | lines.append('\n') 16 | s = '\n'.join(lines) 17 | return s 18 | 19 | __repr__ = __str__ 20 | 21 | def insert(self, symbol): 22 | print('Insert: %s' % symbol.name) 23 | self._symbols[symbol.name] = symbol 24 | 25 | 26 | 27 | if __name__ == '__main__': 28 | text = """ 29 | program SymTab1; 30 | var x, y : integer; 31 | 32 | begin 33 | 34 | end. 35 | """ 36 | lexer = Lexer(text) 37 | parser = Parser(lexer) 38 | tree = parser.parse() 39 | 40 | symtab = SymbolTable() 41 | int_type = BuiltinTypeSymbol('INTEGER') 42 | symtab.insert(int_type) 43 | 44 | var_x_symbol = VarSymbol('x', int_type) 45 | symtab.insert(var_x_symbol) 46 | 47 | var_y_symbol = VarSymbol('y', int_type) 48 | symtab.insert(var_y_symbol) 49 | print(symtab) 50 | -------------------------------------------------------------------------------- /part7/python/ex1.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Exercise 1: Infix to Postfix Translator # 3 | ############################################################################### 4 | import unittest 5 | 6 | from spi import Lexer, Parser, NodeVisitor 7 | 8 | 9 | class Infix2PostfixTranslator(NodeVisitor): 10 | def __init__(self, tree): 11 | self.tree = tree 12 | 13 | def visit_BinOp(self, node): 14 | left_val = self.visit(node.left) 15 | right_val = self.visit(node.right) 16 | return '{left} {right} {op}'.format( 17 | left=left_val, 18 | right=right_val, 19 | op=node.op.value, 20 | ) 21 | 22 | def visit_Num(self, node): 23 | return node.value 24 | 25 | def translate(self): 26 | return self.visit(self.tree) 27 | 28 | 29 | def infix2postfix(s): 30 | lexer = Lexer(s) 31 | parser = Parser(lexer) 32 | tree = parser.parse() 33 | translator = Infix2PostfixTranslator(tree) 34 | translation = translator.translate() 35 | return translation 36 | 37 | 38 | class Infix2PostfixTestCase(unittest.TestCase): 39 | 40 | def test_1(self): 41 | self.assertEqual(infix2postfix('2 + 3'), '2 3 +') 42 | 43 | def test_2(self): 44 | self.assertEqual(infix2postfix('2 + 3 * 5'), '2 3 5 * +') 45 | 46 | def test_3(self): 47 | self.assertEqual( 48 | infix2postfix('5 + ((1 + 2) * 4) - 3'), 49 | '5 1 2 + 4 * + 3 -', 50 | ) 51 | 52 | def test_4(self): 53 | self.assertEqual( 54 | infix2postfix('(5 + 3) * 12 / 3'), 55 | '5 3 + 12 * 3 /', 56 | ) 57 | 58 | 59 | if __name__ == '__main__': 60 | unittest.main() 61 | -------------------------------------------------------------------------------- /part4/test_interpreter.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class LexerTestCase(unittest.TestCase): 5 | def makeLexer(self, text): 6 | from calc4 import Lexer 7 | lexer = Lexer(text) 8 | return lexer 9 | 10 | def test_lexer_integer(self): 11 | from calc4 import INTEGER 12 | lexer = self.makeLexer('234') 13 | token = lexer.get_next_token() 14 | self.assertEqual(token.type, INTEGER) 15 | self.assertEqual(token.value, 234) 16 | 17 | def test_lexer_mul(self): 18 | from calc4 import MUL 19 | lexer = self.makeLexer('*') 20 | token = lexer.get_next_token() 21 | self.assertEqual(token.type, MUL) 22 | self.assertEqual(token.value, '*') 23 | 24 | def test_lexer_div(self): 25 | from calc4 import DIV 26 | lexer = self.makeLexer(' / ') 27 | token = lexer.get_next_token() 28 | self.assertEqual(token.type, DIV) 29 | self.assertEqual(token.value, '/') 30 | 31 | 32 | class InterpreterTestCase(unittest.TestCase): 33 | def makeInterpreter(self, text): 34 | from calc4 import Lexer, Interpreter 35 | lexer = Lexer(text) 36 | interpreter = Interpreter(lexer) 37 | return interpreter 38 | 39 | def test_expression1(self): 40 | interpreter = self.makeInterpreter('7 * 4 / 2') 41 | result = interpreter.expr() 42 | self.assertEqual(result, 14) 43 | 44 | def test_expression2(self): 45 | interpreter = self.makeInterpreter('7 * 4 / 2 * 3') 46 | result = interpreter.expr() 47 | self.assertEqual(result, 42) 48 | 49 | def test_expression3(self): 50 | interpreter = self.makeInterpreter('10 * 4 * 2 * 3 / 8') 51 | result = interpreter.expr() 52 | self.assertEqual(result, 30) 53 | 54 | 55 | if __name__ == '__main__': 56 | unittest.main() 57 | -------------------------------------------------------------------------------- /part7/python/ex2.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Exercise 2: Infix to LISP style Translator # 3 | ############################################################################### 4 | import unittest 5 | 6 | from spi import Lexer, Parser, NodeVisitor 7 | 8 | 9 | class Infix2LispTranslator(NodeVisitor): 10 | def __init__(self, tree): 11 | self.tree = tree 12 | 13 | def visit_BinOp(self, node): 14 | left_val = self.visit(node.left) 15 | right_val = self.visit(node.right) 16 | return '({op} {left} {right})'.format( 17 | left=left_val, 18 | right=right_val, 19 | op=node.op.value, 20 | ) 21 | 22 | def visit_Num(self, node): 23 | return node.value 24 | 25 | def translate(self): 26 | return self.visit(self.tree) 27 | 28 | 29 | def infix2lisp(s): 30 | lexer = Lexer(s) 31 | parser = Parser(lexer) 32 | tree = parser.parse() 33 | translator = Infix2LispTranslator(tree) 34 | translation = translator.translate() 35 | return translation 36 | 37 | 38 | class Infix2LispTestCase(unittest.TestCase): 39 | 40 | def test_1(self): 41 | self.assertEqual(infix2lisp('1 + 2'), '(+ 1 2)') 42 | 43 | def test_2(self): 44 | self.assertEqual(infix2lisp('2 * 7'), '(* 2 7)') 45 | 46 | def test_3(self): 47 | self.assertEqual(infix2lisp('2 * 7 + 3'), '(+ (* 2 7) 3)') 48 | 49 | def test_4(self): 50 | self.assertEqual(infix2lisp('2 + 3 * 5'), '(+ 2 (* 3 5))') 51 | 52 | def test_5(self): 53 | self.assertEqual(infix2lisp('7 + 5 * 2 - 3'), '(- (+ 7 (* 5 2)) 3)') 54 | 55 | def test_6(self): 56 | self.assertEqual( 57 | infix2lisp('1 + 2 + 3 + 4 + 5'), 58 | '(+ (+ (+ (+ 1 2) 3) 4) 5)' 59 | ) 60 | 61 | 62 | if __name__ == '__main__': 63 | unittest.main() 64 | -------------------------------------------------------------------------------- /part13/symtab02.py: -------------------------------------------------------------------------------- 1 | from spi import Lexer, Parser, NodeVisitor, BuiltinTypeSymbol, VarSymbol 2 | 3 | 4 | class SymbolTable(object): 5 | def __init__(self): 6 | self._symbols = {} 7 | 8 | def __str__(self): 9 | symtab_header = 'Symbol table contents' 10 | lines = ['\n', symtab_header, '_' * len(symtab_header)] 11 | lines.extend( 12 | ('%7s: %r' % (key, value)) 13 | for key, value in self._symbols.items() 14 | ) 15 | lines.append('\n') 16 | s = '\n'.join(lines) 17 | return s 18 | 19 | __repr__ = __str__ 20 | 21 | def insert(self, symbol): 22 | print('Insert: %s' % symbol.name) 23 | self._symbols[symbol.name] = symbol 24 | 25 | 26 | class SemanticAnalyzer(NodeVisitor): 27 | def __init__(self): 28 | self.symtab = SymbolTable() 29 | 30 | def visit_Block(self, node): 31 | for declaration in node.declarations: 32 | self.visit(declaration) 33 | self.visit(node.compound_statement) 34 | 35 | def visit_Program(self, node): 36 | self.visit(node.block) 37 | 38 | def visit_Compound(self, node): 39 | for child in node.children: 40 | self.visit(child) 41 | 42 | def visit_NoOp(self, node): 43 | pass 44 | 45 | def visit_VarDecl(self, node): 46 | # For now, manually create a symbol for the INTEGER built-in type 47 | # and insert the type symbol in the symbol table. 48 | type_symbol = BuiltinTypeSymbol('INTEGER') 49 | self.symtab.insert(type_symbol) 50 | 51 | # We have all the information we need to create a variable symbol. 52 | # Create the symbol and insert it into the symbol table. 53 | var_name = node.var_node.value 54 | var_symbol = VarSymbol(var_name, type_symbol) 55 | self.symtab.insert(var_symbol) 56 | 57 | 58 | if __name__ == '__main__': 59 | text = """ 60 | program SymTab2; 61 | var x, y : integer; 62 | 63 | begin 64 | 65 | end. 66 | """ 67 | lexer = Lexer(text) 68 | parser = Parser(lexer) 69 | tree = parser.parse() 70 | 71 | semantic_analyzer = SemanticAnalyzer() 72 | semantic_analyzer.visit(tree) 73 | 74 | print(semantic_analyzer.symtab) 75 | -------------------------------------------------------------------------------- /part7/python/genastdot.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # AST visualizer - generates a DOT file for Graphviz. # 3 | # # 4 | # To generate an image from the DOT file run $ dot -Tpng -o ast.png ast.dot # 5 | # # 6 | ############################################################################### 7 | import argparse 8 | import textwrap 9 | 10 | from spi import Lexer, Parser, NodeVisitor 11 | 12 | 13 | class ASTVisualizer(NodeVisitor): 14 | def __init__(self, parser): 15 | self.parser = parser 16 | self.ncount = 1 17 | self.dot_header = [textwrap.dedent("""\ 18 | digraph astgraph { 19 | node [shape=circle, fontsize=12, fontname="Courier", height=.1]; 20 | ranksep=.3; 21 | edge [arrowsize=.5] 22 | 23 | """)] 24 | self.dot_body = [] 25 | self.dot_footer = ['}'] 26 | 27 | def visit_Num(self, node): 28 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.token.value) 29 | self.dot_body.append(s) 30 | node._num = self.ncount 31 | self.ncount += 1 32 | 33 | def visit_BinOp(self, node): 34 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.op.value) 35 | self.dot_body.append(s) 36 | node._num = self.ncount 37 | self.ncount += 1 38 | 39 | self.visit(node.left) 40 | self.visit(node.right) 41 | 42 | for child_node in (node.left, node.right): 43 | s = ' node{} -> node{}\n'.format(node._num, child_node._num) 44 | self.dot_body.append(s) 45 | 46 | def gendot(self): 47 | tree = self.parser.parse() 48 | self.visit(tree) 49 | return ''.join(self.dot_header + self.dot_body + self.dot_footer) 50 | 51 | 52 | def main(): 53 | argparser = argparse.ArgumentParser( 54 | description='Generate an AST DOT file.' 55 | ) 56 | argparser.add_argument( 57 | 'text', 58 | help='Arithmetic expression (in quotes): "1 + 2 * 3"' 59 | ) 60 | args = argparser.parse_args() 61 | text = args.text 62 | 63 | lexer = Lexer(text) 64 | parser = Parser(lexer) 65 | viz = ASTVisualizer(parser) 66 | content = viz.gendot() 67 | print(content) 68 | 69 | 70 | if __name__ == '__main__': 71 | main() 72 | -------------------------------------------------------------------------------- /part13/symtab03.py: -------------------------------------------------------------------------------- 1 | from spi import Lexer, Parser, NodeVisitor, BuiltinTypeSymbol, VarSymbol 2 | 3 | 4 | class SymbolTable(object): 5 | def __init__(self): 6 | self._symbols = {} 7 | self._init_builtins() 8 | 9 | def _init_builtins(self): 10 | self.insert(BuiltinTypeSymbol('INTEGER')) 11 | self.insert(BuiltinTypeSymbol('REAL')) 12 | 13 | def __str__(self): 14 | symtab_header = 'Symbol table contents' 15 | lines = ['\n', symtab_header, '_' * len(symtab_header)] 16 | lines.extend( 17 | ('%7s: %r' % (key, value)) 18 | for key, value in self._symbols.items() 19 | ) 20 | lines.append('\n') 21 | s = '\n'.join(lines) 22 | return s 23 | 24 | __repr__ = __str__ 25 | 26 | def insert(self, symbol): 27 | print('Insert: %s' % symbol.name) 28 | self._symbols[symbol.name] = symbol 29 | 30 | def lookup(self, name): 31 | print('Lookup: %s' % name) 32 | symbol = self._symbols.get(name) 33 | # 'symbol' is either an instance of the Symbol class or None 34 | return symbol 35 | 36 | 37 | class SemanticAnalyzer(NodeVisitor): 38 | def __init__(self): 39 | self.symtab = SymbolTable() 40 | 41 | def visit_Block(self, node): 42 | for declaration in node.declarations: 43 | self.visit(declaration) 44 | self.visit(node.compound_statement) 45 | 46 | def visit_Program(self, node): 47 | self.visit(node.block) 48 | 49 | def visit_Compound(self, node): 50 | for child in node.children: 51 | self.visit(child) 52 | 53 | def visit_NoOp(self, node): 54 | pass 55 | 56 | def visit_VarDecl(self, node): 57 | type_name = node.type_node.value 58 | type_symbol = self.symtab.lookup(type_name) 59 | 60 | # We have all the information we need to create a variable symbol. 61 | # Create the symbol and insert it into the symbol table. 62 | var_name = node.var_node.value 63 | var_symbol = VarSymbol(var_name, type_symbol) 64 | self.symtab.insert(var_symbol) 65 | 66 | 67 | if __name__ == '__main__': 68 | text = """ 69 | program SymTab3; 70 | var x, y : integer; 71 | 72 | begin 73 | 74 | end. 75 | """ 76 | lexer = Lexer(text) 77 | parser = Parser(lexer) 78 | tree = parser.parse() 79 | 80 | semantic_analyzer = SemanticAnalyzer() 81 | semantic_analyzer.visit(tree) 82 | 83 | print(semantic_analyzer.symtab) 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Let's Build A Simple Interpreter 2 | 3 | This repository contains the source code and examples for the blog series *Let's Build A Simple Interpreter*. 4 | The series takes you step by step through creating a basic interpreter in Python, introducing fundamental ideas behind interpreters such as lexing, parsing, abstract syntax trees, and evaluation. 5 | 6 | The goal is to make the concepts approachable and practical, so you can follow along with the blog posts, run the code yourself, and understand how interpreters work under the hood. 7 | Each part builds on the previous one, gradually adding new language features and complexity. 8 | 9 | 10 | + [Let's Build A Simple Interpreter. Part 1.](https://ruslanspivak.com/lsbasi-part1/) 11 | + [Let's Build A Simple Interpreter. Part 2.](https://ruslanspivak.com/lsbasi-part2/) 12 | + [Let's Build A Simple Interpreter. Part 3.](https://ruslanspivak.com/lsbasi-part3/) 13 | + [Let's Build A Simple Interpreter. Part 4.](https://ruslanspivak.com/lsbasi-part4/) 14 | + [Let's Build A Simple Interpreter. Part 5.](https://ruslanspivak.com/lsbasi-part5/) 15 | + [Let's Build A Simple Interpreter. Part 6.](https://ruslanspivak.com/lsbasi-part6/) 16 | + [Let's Build A Simple Interpreter. Part 7: Abstract Syntax Trees](https://ruslanspivak.com/lsbasi-part7/) 17 | + [Let's Build A Simple Interpreter. Part 8.](https://ruslanspivak.com/lsbasi-part8/) 18 | + [Let's Build A Simple Interpreter. Part 9.](https://ruslanspivak.com/lsbasi-part9/) 19 | + [Let's Build A Simple Interpreter. Part 10.](https://ruslanspivak.com/lsbasi-part10/) 20 | + [Let's Build A Simple Interpreter. Part 11.](https://ruslanspivak.com/lsbasi-part11/) 21 | + [Let's Build A Simple Interpreter. Part 12.](https://ruslanspivak.com/lsbasi-part12/) 22 | + [Let's Build A Simple Interpreter. Part 13: Semantic Analysis](https://ruslanspivak.com/lsbasi-part13/) 23 | + [Let's Build A Simple Interpreter. Part 14: Nested Scopes and a Source-to-Source Compiler](https://ruslanspivak.com/lsbasi-part14/) 24 | + [Let's Build A Simple Interpreter. Part 15.](https://ruslanspivak.com/lsbasi-part15/) 25 | + [Let's Build A Simple Interpreter. Part 16: Recognizing Procedure Calls](https://ruslanspivak.com/lsbasi-part16/) 26 | + [Let's Build A Simple Interpreter. Part 17: Call Stack and Activation Records](https://ruslanspivak.com/lsbasi-part17/) 27 | + [Let's Build A Simple Interpreter. Part 18: Executing Procedure Calls](https://ruslanspivak.com/lsbasi-part18/) 28 | + [Let's Build A Simple Interpreter. Part 19: Nested Procedure Calls](https://ruslanspivak.com/lsbasi-part19/) 29 | -------------------------------------------------------------------------------- /part5/test_interpreter.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class LexerTestCase(unittest.TestCase): 5 | def makeLexer(self, text): 6 | from calc5 import Lexer 7 | lexer = Lexer(text) 8 | return lexer 9 | 10 | def test_lexer_integer(self): 11 | from calc5 import INTEGER 12 | lexer = self.makeLexer('234') 13 | token = lexer.get_next_token() 14 | self.assertEqual(token.type, INTEGER) 15 | self.assertEqual(token.value, 234) 16 | 17 | def test_lexer_mul(self): 18 | from calc5 import MUL 19 | lexer = self.makeLexer('*') 20 | token = lexer.get_next_token() 21 | self.assertEqual(token.type, MUL) 22 | self.assertEqual(token.value, '*') 23 | 24 | def test_lexer_div(self): 25 | from calc5 import DIV 26 | lexer = self.makeLexer(' / ') 27 | token = lexer.get_next_token() 28 | self.assertEqual(token.type, DIV) 29 | self.assertEqual(token.value, '/') 30 | 31 | def test_lexer_plus(self): 32 | from calc5 import PLUS 33 | lexer = self.makeLexer('+') 34 | token = lexer.get_next_token() 35 | self.assertEqual(token.type, PLUS) 36 | self.assertEqual(token.value, '+') 37 | 38 | def test_lexer_minus(self): 39 | from calc5 import MINUS 40 | lexer = self.makeLexer('-') 41 | token = lexer.get_next_token() 42 | self.assertEqual(token.type, MINUS) 43 | self.assertEqual(token.value, '-') 44 | 45 | 46 | class InterpreterTestCase(unittest.TestCase): 47 | def makeInterpreter(self, text): 48 | from calc5 import Lexer, Interpreter 49 | lexer = Lexer(text) 50 | interpreter = Interpreter(lexer) 51 | return interpreter 52 | 53 | def test_expression1(self): 54 | interpreter = self.makeInterpreter('3') 55 | result = interpreter.expr() 56 | self.assertEqual(result, 3) 57 | 58 | def test_expression2(self): 59 | interpreter = self.makeInterpreter('2 + 7 * 4') 60 | result = interpreter.expr() 61 | self.assertEqual(result, 30) 62 | 63 | def test_expression3(self): 64 | interpreter = self.makeInterpreter('7 - 8 / 4') 65 | result = interpreter.expr() 66 | self.assertEqual(result, 5) 67 | 68 | def test_expression4(self): 69 | interpreter = self.makeInterpreter('14 + 2 * 3 - 6 / 2') 70 | result = interpreter.expr() 71 | self.assertEqual(result, 17) 72 | 73 | def test_expression_invalid_syntax(self): 74 | interpreter = self.makeInterpreter('10 *') 75 | with self.assertRaises(Exception): 76 | interpreter.expr() 77 | 78 | 79 | if __name__ == '__main__': 80 | unittest.main() 81 | -------------------------------------------------------------------------------- /part8/python/genastdot.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # AST visualizer - generates a DOT file for Graphviz. # 3 | # # 4 | # To generate an image from the DOT file run $ dot -Tpng -o ast.png ast.dot # 5 | # # 6 | ############################################################################### 7 | import argparse 8 | import textwrap 9 | 10 | from spi import Lexer, Parser, NodeVisitor 11 | 12 | 13 | class ASTVisualizer(NodeVisitor): 14 | def __init__(self, parser): 15 | self.parser = parser 16 | self.ncount = 1 17 | self.dot_header = [textwrap.dedent("""\ 18 | digraph astgraph { 19 | node [shape=circle, fontsize=12, fontname="Courier", height=.1]; 20 | ranksep=.3; 21 | edge [arrowsize=.5] 22 | 23 | """)] 24 | self.dot_body = [] 25 | self.dot_footer = ['}'] 26 | 27 | def visit_Num(self, node): 28 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.token.value) 29 | self.dot_body.append(s) 30 | node._num = self.ncount 31 | self.ncount += 1 32 | 33 | def visit_BinOp(self, node): 34 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.op.value) 35 | self.dot_body.append(s) 36 | node._num = self.ncount 37 | self.ncount += 1 38 | 39 | self.visit(node.left) 40 | self.visit(node.right) 41 | 42 | for child_node in (node.left, node.right): 43 | s = ' node{} -> node{}\n'.format(node._num, child_node._num) 44 | self.dot_body.append(s) 45 | 46 | def visit_UnaryOp(self, node): 47 | s = ' node{} [label="unary {}"]\n'.format(self.ncount, node.op.value) 48 | self.dot_body.append(s) 49 | node._num = self.ncount 50 | self.ncount += 1 51 | 52 | self.visit(node.expr) 53 | s = ' node{} -> node{}\n'.format(node._num, node.expr._num) 54 | self.dot_body.append(s) 55 | 56 | def gendot(self): 57 | tree = self.parser.parse() 58 | self.visit(tree) 59 | return ''.join(self.dot_header + self.dot_body + self.dot_footer) 60 | 61 | 62 | def main(): 63 | argparser = argparse.ArgumentParser( 64 | description='Generate an AST DOT file.' 65 | ) 66 | argparser.add_argument( 67 | 'text', 68 | help='Arithmetic expression (in quotes): "1 + 2 * 3"' 69 | ) 70 | args = argparser.parse_args() 71 | text = args.text 72 | 73 | lexer = Lexer(text) 74 | parser = Parser(lexer) 75 | viz = ASTVisualizer(parser) 76 | content = viz.gendot() 77 | print(content) 78 | 79 | 80 | if __name__ == '__main__': 81 | main() 82 | -------------------------------------------------------------------------------- /part13/symtab04.py: -------------------------------------------------------------------------------- 1 | from spi import Lexer, Parser, NodeVisitor, BuiltinTypeSymbol, VarSymbol 2 | 3 | 4 | class SymbolTable(object): 5 | def __init__(self): 6 | self._symbols = {} 7 | self._init_builtins() 8 | 9 | def _init_builtins(self): 10 | self.insert(BuiltinTypeSymbol('INTEGER')) 11 | self.insert(BuiltinTypeSymbol('REAL')) 12 | 13 | def __str__(self): 14 | symtab_header = 'Symbol table contents' 15 | lines = ['\n', symtab_header, '_' * len(symtab_header)] 16 | lines.extend( 17 | ('%7s: %r' % (key, value)) 18 | for key, value in self._symbols.items() 19 | ) 20 | lines.append('\n') 21 | s = '\n'.join(lines) 22 | return s 23 | 24 | __repr__ = __str__ 25 | 26 | def insert(self, symbol): 27 | print('Insert: %s' % symbol.name) 28 | self._symbols[symbol.name] = symbol 29 | 30 | def lookup(self, name): 31 | print('Lookup: %s' % name) 32 | symbol = self._symbols.get(name) 33 | # 'symbol' is either an instance of the Symbol class or None 34 | return symbol 35 | 36 | 37 | class SemanticAnalyzer(NodeVisitor): 38 | def __init__(self): 39 | self.symtab = SymbolTable() 40 | 41 | def visit_Block(self, node): 42 | for declaration in node.declarations: 43 | self.visit(declaration) 44 | self.visit(node.compound_statement) 45 | 46 | def visit_Program(self, node): 47 | self.visit(node.block) 48 | 49 | def visit_Compound(self, node): 50 | for child in node.children: 51 | self.visit(child) 52 | 53 | def visit_NoOp(self, node): 54 | pass 55 | 56 | def visit_BinOp(self, node): 57 | self.visit(node.left) 58 | self.visit(node.right) 59 | 60 | def visit_VarDecl(self, node): 61 | type_name = node.type_node.value 62 | type_symbol = self.symtab.lookup(type_name) 63 | 64 | # We have all the information we need to create a variable symbol. 65 | # Create the symbol and insert it into the symbol table. 66 | var_name = node.var_node.value 67 | var_symbol = VarSymbol(var_name, type_symbol) 68 | self.symtab.insert(var_symbol) 69 | 70 | def visit_Assign(self, node): 71 | # right-hand side 72 | self.visit(node.right) 73 | # left-hand side 74 | self.visit(node.left) 75 | 76 | def visit_Var(self, node): 77 | var_name = node.value 78 | var_symbol = self.symtab.lookup(var_name) 79 | 80 | 81 | if __name__ == '__main__': 82 | text = """ 83 | program SymTab4; 84 | var x, y : integer; 85 | 86 | begin 87 | x := x + y; 88 | end. 89 | """ 90 | lexer = Lexer(text) 91 | parser = Parser(lexer) 92 | tree = parser.parse() 93 | 94 | semantic_analyzer = SemanticAnalyzer() 95 | semantic_analyzer.visit(tree) 96 | 97 | print(semantic_analyzer.symtab) 98 | -------------------------------------------------------------------------------- /part13/symtab05.py: -------------------------------------------------------------------------------- 1 | from spi import Lexer, Parser, NodeVisitor, BuiltinTypeSymbol, VarSymbol 2 | 3 | 4 | class SymbolTable(object): 5 | def __init__(self): 6 | self._symbols = {} 7 | self._init_builtins() 8 | 9 | def _init_builtins(self): 10 | self.insert(BuiltinTypeSymbol('INTEGER')) 11 | self.insert(BuiltinTypeSymbol('REAL')) 12 | 13 | def __str__(self): 14 | symtab_header = 'Symbol table contents' 15 | lines = ['\n', symtab_header, '_' * len(symtab_header)] 16 | lines.extend( 17 | ('%7s: %r' % (key, value)) 18 | for key, value in self._symbols.items() 19 | ) 20 | lines.append('\n') 21 | s = '\n'.join(lines) 22 | return s 23 | 24 | __repr__ = __str__ 25 | 26 | def insert(self, symbol): 27 | print('Insert: %s' % symbol.name) 28 | self._symbols[symbol.name] = symbol 29 | 30 | def lookup(self, name): 31 | print('Lookup: %s' % name) 32 | symbol = self._symbols.get(name) 33 | # 'symbol' is either an instance of the Symbol class or None 34 | return symbol 35 | 36 | 37 | class SemanticAnalyzer(NodeVisitor): 38 | def __init__(self): 39 | self.symtab = SymbolTable() 40 | 41 | def visit_Block(self, node): 42 | for declaration in node.declarations: 43 | self.visit(declaration) 44 | self.visit(node.compound_statement) 45 | 46 | def visit_Program(self, node): 47 | self.visit(node.block) 48 | 49 | def visit_Compound(self, node): 50 | for child in node.children: 51 | self.visit(child) 52 | 53 | def visit_NoOp(self, node): 54 | pass 55 | 56 | def visit_BinOp(self, node): 57 | self.visit(node.left) 58 | self.visit(node.right) 59 | 60 | def visit_VarDecl(self, node): 61 | type_name = node.type_node.value 62 | type_symbol = self.symtab.lookup(type_name) 63 | 64 | # We have all the information we need to create a variable symbol. 65 | # Create the symbol and insert it into the symbol table. 66 | var_name = node.var_node.value 67 | var_symbol = VarSymbol(var_name, type_symbol) 68 | self.symtab.insert(var_symbol) 69 | 70 | def visit_Assign(self, node): 71 | # right-hand side 72 | self.visit(node.right) 73 | # left-hand side 74 | self.visit(node.left) 75 | 76 | def visit_Var(self, node): 77 | var_name = node.value 78 | var_symbol = self.symtab.lookup(var_name) 79 | if var_symbol is None: 80 | raise Exception( 81 | "Error: Symbol(identifier) not found '%s'" % var_name 82 | ) 83 | 84 | 85 | if __name__ == '__main__': 86 | text = """ 87 | program SymTab5; 88 | var x : integer; 89 | 90 | begin 91 | x := y; 92 | end. 93 | """ 94 | lexer = Lexer(text) 95 | parser = Parser(lexer) 96 | tree = parser.parse() 97 | 98 | semantic_analyzer = SemanticAnalyzer() 99 | try: 100 | semantic_analyzer.visit(tree) 101 | except Exception as e: 102 | print(e) 103 | 104 | print(semantic_analyzer.symtab) 105 | -------------------------------------------------------------------------------- /part2/test_calc2.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class CalcTestCase(unittest.TestCase): 5 | 6 | def makeInterpreter(self, text): 7 | from calc2 import Interpreter 8 | interpreter = Interpreter(text) 9 | return interpreter 10 | 11 | def test_lexer_integer(self): 12 | from calc2 import INTEGER 13 | lexer = self.makeInterpreter('234') 14 | token = lexer.get_next_token() 15 | self.assertEqual(token.type, INTEGER) 16 | self.assertEqual(token.value, 234) 17 | 18 | def test_lexer_plus(self): 19 | from calc2 import PLUS 20 | lexer = self.makeInterpreter('+') 21 | token = lexer.get_next_token() 22 | self.assertEqual(token.type, PLUS) 23 | self.assertEqual(token.value, '+') 24 | 25 | def test_lexer_minus(self): 26 | from calc2 import MINUS 27 | lexer = self.makeInterpreter('-') 28 | token = lexer.get_next_token() 29 | self.assertEqual(token.type, MINUS) 30 | self.assertEqual(token.value, '-') 31 | 32 | def test_lexer_eof(self): 33 | from calc2 import EOF 34 | lexer = self.makeInterpreter('-') 35 | token = lexer.get_next_token() 36 | token = lexer.get_next_token() 37 | self.assertEqual(token.type, EOF) 38 | 39 | def test_lexer_whitespace(self): 40 | from calc2 import INTEGER 41 | lexer = self.makeInterpreter(' 23') 42 | token = lexer.get_next_token() 43 | self.assertEqual(token.type, INTEGER) 44 | self.assertEqual(token.value, 23) 45 | 46 | def test_lexer_addition(self): 47 | from calc2 import INTEGER, PLUS, EOF 48 | lexer = self.makeInterpreter('2+3') 49 | 50 | token = lexer.get_next_token() 51 | self.assertEqual(token.type, INTEGER) 52 | self.assertEqual(token.value, 2) 53 | 54 | token = lexer.get_next_token() 55 | self.assertEqual(token.type, PLUS) 56 | self.assertEqual(token.value, '+') 57 | 58 | token = lexer.get_next_token() 59 | self.assertEqual(token.type, INTEGER) 60 | self.assertEqual(token.value, 3) 61 | 62 | token = lexer.get_next_token() 63 | self.assertEqual(token.type, EOF) 64 | 65 | def test_lexer_subtraction(self): 66 | from calc2 import INTEGER, MINUS, EOF 67 | lexer = self.makeInterpreter(' 27 - 7 ') 68 | 69 | token = lexer.get_next_token() 70 | self.assertEqual(token.type, INTEGER) 71 | self.assertEqual(token.value, 27) 72 | 73 | token = lexer.get_next_token() 74 | self.assertEqual(token.type, MINUS) 75 | self.assertEqual(token.value, '-') 76 | 77 | token = lexer.get_next_token() 78 | self.assertEqual(token.type, INTEGER) 79 | self.assertEqual(token.value, 7) 80 | 81 | token = lexer.get_next_token() 82 | self.assertEqual(token.type, EOF) 83 | 84 | def test_interpreter_addition(self): 85 | interpreter = self.makeInterpreter(' 23 + 7') 86 | result = interpreter.expr() 87 | self.assertEqual(result, 30) 88 | 89 | def test_interpreter_subtraction(self): 90 | interpreter = self.makeInterpreter(' 27 - 7 ') 91 | result = interpreter.expr() 92 | self.assertEqual(result, 20) 93 | 94 | 95 | if __name__ == '__main__': 96 | unittest.main() 97 | -------------------------------------------------------------------------------- /part13/symtab06.py: -------------------------------------------------------------------------------- 1 | from spi import Lexer, Parser, NodeVisitor, BuiltinTypeSymbol, VarSymbol 2 | 3 | 4 | class SymbolTable(object): 5 | def __init__(self): 6 | self._symbols = {} 7 | self._init_builtins() 8 | 9 | def _init_builtins(self): 10 | self.insert(BuiltinTypeSymbol('INTEGER')) 11 | self.insert(BuiltinTypeSymbol('REAL')) 12 | 13 | def __str__(self): 14 | symtab_header = 'Symbol table contents' 15 | lines = ['\n', symtab_header, '_' * len(symtab_header)] 16 | lines.extend( 17 | ('%7s: %r' % (key, value)) 18 | for key, value in self._symbols.items() 19 | ) 20 | lines.append('\n') 21 | s = '\n'.join(lines) 22 | return s 23 | 24 | __repr__ = __str__ 25 | 26 | def insert(self, symbol): 27 | print('Insert: %s' % symbol.name) 28 | self._symbols[symbol.name] = symbol 29 | 30 | def lookup(self, name): 31 | print('Lookup: %s' % name) 32 | symbol = self._symbols.get(name) 33 | # 'symbol' is either an instance of the Symbol class or None 34 | return symbol 35 | 36 | 37 | class SemanticAnalyzer(NodeVisitor): 38 | def __init__(self): 39 | self.symtab = SymbolTable() 40 | 41 | def visit_Block(self, node): 42 | for declaration in node.declarations: 43 | self.visit(declaration) 44 | self.visit(node.compound_statement) 45 | 46 | def visit_Program(self, node): 47 | self.visit(node.block) 48 | 49 | def visit_Compound(self, node): 50 | for child in node.children: 51 | self.visit(child) 52 | 53 | def visit_NoOp(self, node): 54 | pass 55 | 56 | def visit_BinOp(self, node): 57 | self.visit(node.left) 58 | self.visit(node.right) 59 | 60 | def visit_VarDecl(self, node): 61 | type_name = node.type_node.value 62 | type_symbol = self.symtab.lookup(type_name) 63 | 64 | # We have all the information we need to create a variable symbol. 65 | # Create the symbol and insert it into the symbol table. 66 | var_name = node.var_node.value 67 | var_symbol = VarSymbol(var_name, type_symbol) 68 | 69 | # Signal an error if the table alrady has a symbol 70 | # with the same name 71 | if self.symtab.lookup(var_name) is not None: 72 | raise Exception( 73 | "Error: Duplicate identifier '%s' found" % var_name 74 | ) 75 | 76 | self.symtab.insert(var_symbol) 77 | 78 | def visit_Assign(self, node): 79 | # right-hand side 80 | self.visit(node.right) 81 | # left-hand side 82 | self.visit(node.left) 83 | 84 | def visit_Var(self, node): 85 | var_name = node.value 86 | var_symbol = self.symtab.lookup(var_name) 87 | if var_symbol is None: 88 | raise Exception( 89 | "Error: Symbol(identifier) not found '%s'" % var_name 90 | ) 91 | 92 | 93 | if __name__ == '__main__': 94 | text = """ 95 | program SymTab6; 96 | var x, y : integer; 97 | var y : real; 98 | begin 99 | x := x + y; 100 | end. 101 | """ 102 | lexer = Lexer(text) 103 | parser = Parser(lexer) 104 | tree = parser.parse() 105 | 106 | semantic_analyzer = SemanticAnalyzer() 107 | try: 108 | semantic_analyzer.visit(tree) 109 | except Exception as e: 110 | print(e) 111 | 112 | print(semantic_analyzer.symtab) 113 | -------------------------------------------------------------------------------- /part14/scope01.py: -------------------------------------------------------------------------------- 1 | from spi import Lexer, Parser, NodeVisitor, BuiltinTypeSymbol, VarSymbol 2 | 3 | 4 | class ScopedSymbolTable(object): 5 | def __init__(self, scope_name, scope_level): 6 | self._symbols = {} 7 | self.scope_name = scope_name 8 | self.scope_level = scope_level 9 | self._init_builtins() 10 | 11 | def _init_builtins(self): 12 | self.insert(BuiltinTypeSymbol('INTEGER')) 13 | self.insert(BuiltinTypeSymbol('REAL')) 14 | 15 | def __str__(self): 16 | h1 = 'SCOPE (SCOPED SYMBOL TABLE)' 17 | lines = ['\n', h1, '=' * len(h1)] 18 | for header_name, header_value in ( 19 | ('Scope name', self.scope_name), 20 | ('Scope level', self.scope_level), 21 | ): 22 | lines.append('%-15s: %s' % (header_name, header_value)) 23 | h2 = 'Scope (Scoped symbol table) contents' 24 | lines.extend([h2, '-' * len(h2)]) 25 | lines.extend( 26 | ('%7s: %r' % (key, value)) 27 | for key, value in self._symbols.items() 28 | ) 29 | lines.append('\n') 30 | s = '\n'.join(lines) 31 | return s 32 | 33 | __repr__ = __str__ 34 | 35 | def insert(self, symbol): 36 | print('Insert: %s' % symbol.name) 37 | self._symbols[symbol.name] = symbol 38 | 39 | def lookup(self, name): 40 | print('Lookup: %s' % name) 41 | symbol = self._symbols.get(name) 42 | # 'symbol' is either an instance of the Symbol class or None 43 | return symbol 44 | 45 | 46 | class SemanticAnalyzer(NodeVisitor): 47 | def __init__(self): 48 | self.scope = ScopedSymbolTable(scope_name='global', scope_level=1) 49 | 50 | def visit_Block(self, node): 51 | for declaration in node.declarations: 52 | self.visit(declaration) 53 | self.visit(node.compound_statement) 54 | 55 | def visit_Program(self, node): 56 | self.visit(node.block) 57 | 58 | def visit_Compound(self, node): 59 | for child in node.children: 60 | self.visit(child) 61 | 62 | def visit_NoOp(self, node): 63 | pass 64 | 65 | def visit_BinOp(self, node): 66 | self.visit(node.left) 67 | self.visit(node.right) 68 | 69 | def visit_VarDecl(self, node): 70 | type_name = node.type_node.value 71 | type_symbol = self.scope.lookup(type_name) 72 | 73 | # We have all the information we need to create a variable symbol. 74 | # Create the symbol and insert it into the symbol table. 75 | var_name = node.var_node.value 76 | var_symbol = VarSymbol(var_name, type_symbol) 77 | 78 | self.scope.insert(var_symbol) 79 | 80 | def visit_Assign(self, node): 81 | # right-hand side 82 | self.visit(node.right) 83 | # left-hand side 84 | self.visit(node.left) 85 | 86 | def visit_Var(self, node): 87 | var_name = node.value 88 | var_symbol = self.scope.lookup(var_name) 89 | if var_symbol is None: 90 | raise Exception( 91 | "Error: Symbol(identifier) not found '%s'" % var_name 92 | ) 93 | 94 | 95 | if __name__ == '__main__': 96 | text = """ 97 | program Main; 98 | var x, y : integer; 99 | begin 100 | x := x + y; 101 | end. 102 | """ 103 | lexer = Lexer(text) 104 | parser = Parser(lexer) 105 | tree = parser.parse() 106 | semantic_analyzer = SemanticAnalyzer() 107 | semantic_analyzer.visit(tree) 108 | 109 | print(semantic_analyzer.scope) 110 | -------------------------------------------------------------------------------- /part6/test_interpreter.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class LexerTestCase(unittest.TestCase): 5 | def makeLexer(self, text): 6 | from calc6 import Lexer 7 | lexer = Lexer(text) 8 | return lexer 9 | 10 | def test_lexer_integer(self): 11 | from calc6 import INTEGER 12 | lexer = self.makeLexer('234') 13 | token = lexer.get_next_token() 14 | self.assertEqual(token.type, INTEGER) 15 | self.assertEqual(token.value, 234) 16 | 17 | def test_lexer_mul(self): 18 | from calc6 import MUL 19 | lexer = self.makeLexer('*') 20 | token = lexer.get_next_token() 21 | self.assertEqual(token.type, MUL) 22 | self.assertEqual(token.value, '*') 23 | 24 | def test_lexer_div(self): 25 | from calc6 import DIV 26 | lexer = self.makeLexer(' / ') 27 | token = lexer.get_next_token() 28 | self.assertEqual(token.type, DIV) 29 | self.assertEqual(token.value, '/') 30 | 31 | def test_lexer_plus(self): 32 | from calc6 import PLUS 33 | lexer = self.makeLexer('+') 34 | token = lexer.get_next_token() 35 | self.assertEqual(token.type, PLUS) 36 | self.assertEqual(token.value, '+') 37 | 38 | def test_lexer_minus(self): 39 | from calc6 import MINUS 40 | lexer = self.makeLexer('-') 41 | token = lexer.get_next_token() 42 | self.assertEqual(token.type, MINUS) 43 | self.assertEqual(token.value, '-') 44 | 45 | def test_lexer_lparen(self): 46 | from calc6 import LPAREN 47 | lexer = self.makeLexer('(') 48 | token = lexer.get_next_token() 49 | self.assertEqual(token.type, LPAREN) 50 | self.assertEqual(token.value, '(') 51 | 52 | def test_lexer_rparen(self): 53 | from calc6 import RPAREN 54 | lexer = self.makeLexer(')') 55 | token = lexer.get_next_token() 56 | self.assertEqual(token.type, RPAREN) 57 | self.assertEqual(token.value, ')') 58 | 59 | 60 | class InterpreterTestCase(unittest.TestCase): 61 | def makeInterpreter(self, text): 62 | from calc6 import Lexer, Interpreter 63 | lexer = Lexer(text) 64 | interpreter = Interpreter(lexer) 65 | return interpreter 66 | 67 | def test_expression1(self): 68 | interpreter = self.makeInterpreter('3') 69 | result = interpreter.expr() 70 | self.assertEqual(result, 3) 71 | 72 | def test_expression2(self): 73 | interpreter = self.makeInterpreter('2 + 7 * 4') 74 | result = interpreter.expr() 75 | self.assertEqual(result, 30) 76 | 77 | def test_expression3(self): 78 | interpreter = self.makeInterpreter('7 - 8 / 4') 79 | result = interpreter.expr() 80 | self.assertEqual(result, 5) 81 | 82 | def test_expression4(self): 83 | interpreter = self.makeInterpreter('14 + 2 * 3 - 6 / 2') 84 | result = interpreter.expr() 85 | self.assertEqual(result, 17) 86 | 87 | def test_expression5(self): 88 | interpreter = self.makeInterpreter('7 + 3 * (10 / (12 / (3 + 1) - 1))') 89 | result = interpreter.expr() 90 | self.assertEqual(result, 22) 91 | 92 | def test_expression6(self): 93 | interpreter = self.makeInterpreter( 94 | '7 + 3 * (10 / (12 / (3 + 1) - 1)) / (2 + 3) - 5 - 3 + (8)' 95 | ) 96 | result = interpreter.expr() 97 | self.assertEqual(result, 10) 98 | 99 | def test_expression7(self): 100 | interpreter = self.makeInterpreter('7 + (((3 + 2)))') 101 | result = interpreter.expr() 102 | self.assertEqual(result, 12) 103 | 104 | def test_expression_invalid_syntax(self): 105 | interpreter = self.makeInterpreter('10 *') 106 | with self.assertRaises(Exception): 107 | interpreter.expr() 108 | 109 | 110 | if __name__ == '__main__': 111 | unittest.main() 112 | -------------------------------------------------------------------------------- /part9/python/genastdot.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # AST visualizer - generates a DOT file for Graphviz. # 3 | # # 4 | # To generate an image from the DOT file run $ dot -Tpng -o ast.png ast.dot # 5 | # # 6 | ############################################################################### 7 | import argparse 8 | import textwrap 9 | 10 | from spi import Lexer, Parser, NodeVisitor 11 | 12 | 13 | class ASTVisualizer(NodeVisitor): 14 | def __init__(self, parser): 15 | self.parser = parser 16 | self.ncount = 1 17 | self.dot_header = [textwrap.dedent("""\ 18 | digraph astgraph { 19 | node [shape=circle, fontsize=12, fontname="Courier", height=.1]; 20 | ranksep=.3; 21 | edge [arrowsize=.5] 22 | 23 | """)] 24 | self.dot_body = [] 25 | self.dot_footer = ['}'] 26 | 27 | def visit_Num(self, node): 28 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.token.value) 29 | self.dot_body.append(s) 30 | node._num = self.ncount 31 | self.ncount += 1 32 | 33 | def visit_BinOp(self, node): 34 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.op.value) 35 | self.dot_body.append(s) 36 | node._num = self.ncount 37 | self.ncount += 1 38 | 39 | self.visit(node.left) 40 | self.visit(node.right) 41 | 42 | for child_node in (node.left, node.right): 43 | s = ' node{} -> node{}\n'.format(node._num, child_node._num) 44 | self.dot_body.append(s) 45 | 46 | def visit_UnaryOp(self, node): 47 | s = ' node{} [label="unary {}"]\n'.format(self.ncount, node.op.value) 48 | self.dot_body.append(s) 49 | node._num = self.ncount 50 | self.ncount += 1 51 | 52 | self.visit(node.expr) 53 | s = ' node{} -> node{}\n'.format(node._num, node.expr._num) 54 | self.dot_body.append(s) 55 | 56 | def visit_Compound(self, node): 57 | s = ' node{} [label="Compound"]\n'.format(self.ncount) 58 | self.dot_body.append(s) 59 | node._num = self.ncount 60 | self.ncount += 1 61 | 62 | for child in node.children: 63 | self.visit(child) 64 | s = ' node{} -> node{}\n'.format(node._num, child._num) 65 | self.dot_body.append(s) 66 | 67 | def visit_Assign(self, node): 68 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.op.value) 69 | self.dot_body.append(s) 70 | node._num = self.ncount 71 | self.ncount += 1 72 | 73 | self.visit(node.left) 74 | self.visit(node.right) 75 | 76 | for child_node in (node.left, node.right): 77 | s = ' node{} -> node{}\n'.format(node._num, child_node._num) 78 | self.dot_body.append(s) 79 | 80 | def visit_Var(self, node): 81 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.value) 82 | self.dot_body.append(s) 83 | node._num = self.ncount 84 | self.ncount += 1 85 | 86 | def visit_NoOp(self, node): 87 | s = ' node{} [label="NoOp"]\n'.format(self.ncount) 88 | self.dot_body.append(s) 89 | node._num = self.ncount 90 | self.ncount += 1 91 | 92 | def gendot(self): 93 | tree = self.parser.parse() 94 | self.visit(tree) 95 | return ''.join(self.dot_header + self.dot_body + self.dot_footer) 96 | 97 | 98 | def main(): 99 | argparser = argparse.ArgumentParser( 100 | description='Generate an AST DOT file.' 101 | ) 102 | argparser.add_argument( 103 | 'fname', 104 | help='Pascal source file' 105 | ) 106 | args = argparser.parse_args() 107 | fname = args.fname 108 | text = open(fname, 'r').read() 109 | 110 | lexer = Lexer(text) 111 | parser = Parser(lexer) 112 | viz = ASTVisualizer(parser) 113 | content = viz.gendot() 114 | print(content) 115 | 116 | 117 | if __name__ == '__main__': 118 | main() 119 | -------------------------------------------------------------------------------- /part7/python/test_interpreter.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class LexerTestCase(unittest.TestCase): 5 | def makeLexer(self, text): 6 | from spi import Lexer 7 | lexer = Lexer(text) 8 | return lexer 9 | 10 | def test_lexer_integer(self): 11 | from spi import INTEGER 12 | lexer = self.makeLexer('234') 13 | token = lexer.get_next_token() 14 | self.assertEqual(token.type, INTEGER) 15 | self.assertEqual(token.value, 234) 16 | 17 | def test_lexer_mul(self): 18 | from spi import MUL 19 | lexer = self.makeLexer('*') 20 | token = lexer.get_next_token() 21 | self.assertEqual(token.type, MUL) 22 | self.assertEqual(token.value, '*') 23 | 24 | def test_lexer_div(self): 25 | from spi import DIV 26 | lexer = self.makeLexer(' / ') 27 | token = lexer.get_next_token() 28 | self.assertEqual(token.type, DIV) 29 | self.assertEqual(token.value, '/') 30 | 31 | def test_lexer_plus(self): 32 | from spi import PLUS 33 | lexer = self.makeLexer('+') 34 | token = lexer.get_next_token() 35 | self.assertEqual(token.type, PLUS) 36 | self.assertEqual(token.value, '+') 37 | 38 | def test_lexer_minus(self): 39 | from spi import MINUS 40 | lexer = self.makeLexer('-') 41 | token = lexer.get_next_token() 42 | self.assertEqual(token.type, MINUS) 43 | self.assertEqual(token.value, '-') 44 | 45 | def test_lexer_lparen(self): 46 | from spi import LPAREN 47 | lexer = self.makeLexer('(') 48 | token = lexer.get_next_token() 49 | self.assertEqual(token.type, LPAREN) 50 | self.assertEqual(token.value, '(') 51 | 52 | def test_lexer_rparen(self): 53 | from spi import RPAREN 54 | lexer = self.makeLexer(')') 55 | token = lexer.get_next_token() 56 | self.assertEqual(token.type, RPAREN) 57 | self.assertEqual(token.value, ')') 58 | 59 | 60 | class InterpreterTestCase(unittest.TestCase): 61 | def makeInterpreter(self, text): 62 | from spi import Lexer, Parser, Interpreter 63 | lexer = Lexer(text) 64 | parser = Parser(lexer) 65 | interpreter = Interpreter(parser) 66 | return interpreter 67 | 68 | def test_expression1(self): 69 | interpreter = self.makeInterpreter('3') 70 | result = interpreter.interpret() 71 | self.assertEqual(result, 3) 72 | 73 | def test_expression2(self): 74 | interpreter = self.makeInterpreter('2 + 7 * 4') 75 | result = interpreter.interpret() 76 | self.assertEqual(result, 30) 77 | 78 | def test_expression3(self): 79 | interpreter = self.makeInterpreter('7 - 8 / 4') 80 | result = interpreter.interpret() 81 | self.assertEqual(result, 5) 82 | 83 | def test_expression4(self): 84 | interpreter = self.makeInterpreter('14 + 2 * 3 - 6 / 2') 85 | result = interpreter.interpret() 86 | self.assertEqual(result, 17) 87 | 88 | def test_expression5(self): 89 | interpreter = self.makeInterpreter('7 + 3 * (10 / (12 / (3 + 1) - 1))') 90 | result = interpreter.interpret() 91 | self.assertEqual(result, 22) 92 | 93 | def test_expression6(self): 94 | interpreter = self.makeInterpreter( 95 | '7 + 3 * (10 / (12 / (3 + 1) - 1)) / (2 + 3) - 5 - 3 + (8)' 96 | ) 97 | result = interpreter.interpret() 98 | self.assertEqual(result, 10) 99 | 100 | def test_expression7(self): 101 | interpreter = self.makeInterpreter('7 + (((3 + 2)))') 102 | result = interpreter.interpret() 103 | self.assertEqual(result, 12) 104 | 105 | def test_expression_invalid_syntax1(self): 106 | interpreter = self.makeInterpreter('10 *') 107 | with self.assertRaises(Exception): 108 | interpreter.interpret() 109 | 110 | def test_expression_invalid_syntax2(self): 111 | interpreter = self.makeInterpreter('1 (1 + 2)') 112 | with self.assertRaises(Exception): 113 | interpreter.interpret() 114 | 115 | 116 | if __name__ == '__main__': 117 | unittest.main() 118 | -------------------------------------------------------------------------------- /part1/calc1.py: -------------------------------------------------------------------------------- 1 | # Token types 2 | # 3 | # EOF (end-of-file) token is used to indicate that 4 | # there is no more input left for lexical analysis 5 | INTEGER, PLUS, EOF = 'INTEGER', 'PLUS', 'EOF' 6 | 7 | 8 | class Token(object): 9 | def __init__(self, type, value): 10 | # token type: INTEGER, PLUS, or EOF 11 | self.type = type 12 | # token value: 0, 1, 2. 3, 4, 5, 6, 7, 8, 9, '+', or None 13 | self.value = value 14 | 15 | def __str__(self): 16 | """String representation of the class instance. 17 | 18 | Examples: 19 | Token(INTEGER, 3) 20 | Token(PLUS '+') 21 | """ 22 | return 'Token({type}, {value})'.format( 23 | type=self.type, 24 | value=repr(self.value) 25 | ) 26 | 27 | def __repr__(self): 28 | return self.__str__() 29 | 30 | 31 | class Interpreter(object): 32 | def __init__(self, text): 33 | # client string input, e.g. "3+5" 34 | self.text = text 35 | # self.pos is an index into self.text 36 | self.pos = 0 37 | # current token instance 38 | self.current_token = None 39 | 40 | def error(self): 41 | raise Exception('Error parsing input') 42 | 43 | def get_next_token(self): 44 | """Lexical analyzer (also known as scanner or tokenizer) 45 | 46 | This method is responsible for breaking a sentence 47 | apart into tokens. One token at a time. 48 | """ 49 | text = self.text 50 | 51 | # is self.pos index past the end of the self.text ? 52 | # if so, then return EOF token because there is no more 53 | # input left to convert into tokens 54 | if self.pos > len(text) - 1: 55 | return Token(EOF, None) 56 | 57 | # get a character at the position self.pos and decide 58 | # what token to create based on the single character 59 | current_char = text[self.pos] 60 | 61 | # if the character is a digit then convert it to 62 | # integer, create an INTEGER token, increment self.pos 63 | # index to point to the next character after the digit, 64 | # and return the INTEGER token 65 | if current_char.isdigit(): 66 | token = Token(INTEGER, int(current_char)) 67 | self.pos += 1 68 | return token 69 | 70 | if current_char == '+': 71 | token = Token(PLUS, current_char) 72 | self.pos += 1 73 | return token 74 | 75 | self.error() 76 | 77 | def eat(self, token_type): 78 | # compare the current token type with the passed token 79 | # type and if they match then "eat" the current token 80 | # and assign the next token to the self.current_token, 81 | # otherwise raise an exception. 82 | if self.current_token.type == token_type: 83 | self.current_token = self.get_next_token() 84 | else: 85 | self.error() 86 | 87 | def expr(self): 88 | """expr -> INTEGER PLUS INTEGER""" 89 | # set current token to the first token taken from the input 90 | self.current_token = self.get_next_token() 91 | 92 | # we expect the current token to be a single-digit integer 93 | left = self.current_token 94 | self.eat(INTEGER) 95 | 96 | # we expect the current token to be a '+' token 97 | op = self.current_token 98 | self.eat(PLUS) 99 | 100 | # we expect the current token to be a single-digit integer 101 | right = self.current_token 102 | self.eat(INTEGER) 103 | # after the above call the self.current_token is set to 104 | # EOF token 105 | 106 | # at this point INTEGER PLUS INTEGER sequence of tokens 107 | # has been successfully found and the method can just 108 | # return the result of adding two integers, thus 109 | # effectively interpreting client input 110 | result = left.value + right.value 111 | return result 112 | 113 | 114 | def main(): 115 | while True: 116 | try: 117 | text = input('calc> ') 118 | except EOFError: 119 | break 120 | if not text: 121 | continue 122 | interpreter = Interpreter(text) 123 | result = interpreter.expr() 124 | print(result) 125 | 126 | 127 | if __name__ == '__main__': 128 | main() 129 | -------------------------------------------------------------------------------- /part4/parser.py: -------------------------------------------------------------------------------- 1 | # Token types 2 | # 3 | # EOF (end-of-file) token is used to indicate that 4 | # there is no more input left for lexical analysis 5 | INTEGER, MUL, DIV, EOF = 'INTEGER', 'MUL', 'DIV', 'EOF' 6 | 7 | 8 | class Token(object): 9 | def __init__(self, type, value): 10 | # token type: INTEGER, MUL, DIV, or EOF 11 | self.type = type 12 | # token value: non-negative integer value, '*', '/', or None 13 | self.value = value 14 | 15 | def __str__(self): 16 | """String representation of the class instance. 17 | 18 | Examples: 19 | Token(INTEGER, 3) 20 | Token(MUL, '*') 21 | """ 22 | return 'Token({type}, {value})'.format( 23 | type=self.type, 24 | value=repr(self.value) 25 | ) 26 | 27 | def __repr__(self): 28 | return self.__str__() 29 | 30 | 31 | class Lexer(object): 32 | def __init__(self, text): 33 | # client string input, e.g. "3 * 5", "12 / 3 * 4", etc 34 | self.text = text 35 | # self.pos is an index into self.text 36 | self.pos = 0 37 | self.current_char = self.text[self.pos] 38 | 39 | def error(self): 40 | raise Exception('Invalid character') 41 | 42 | def advance(self): 43 | """Advance the `pos` pointer and set the `current_char` variable.""" 44 | self.pos += 1 45 | if self.pos > len(self.text) - 1: 46 | self.current_char = None # Indicates end of input 47 | else: 48 | self.current_char = self.text[self.pos] 49 | 50 | def skip_whitespace(self): 51 | while self.current_char is not None and self.current_char.isspace(): 52 | self.advance() 53 | 54 | def integer(self): 55 | """Return a (multidigit) integer consumed from the input.""" 56 | result = '' 57 | while self.current_char is not None and self.current_char.isdigit(): 58 | result += self.current_char 59 | self.advance() 60 | return int(result) 61 | 62 | def get_next_token(self): 63 | """Lexical analyzer (also known as scanner or tokenizer) 64 | 65 | This method is responsible for breaking a sentence 66 | apart into tokens. One token at a time. 67 | """ 68 | while self.current_char is not None: 69 | 70 | if self.current_char.isspace(): 71 | self.skip_whitespace() 72 | continue 73 | 74 | if self.current_char.isdigit(): 75 | return Token(INTEGER, self.integer()) 76 | 77 | if self.current_char == '*': 78 | self.advance() 79 | return Token(MUL, '*') 80 | 81 | if self.current_char == '/': 82 | self.advance() 83 | return Token(DIV, '/') 84 | 85 | self.error() 86 | 87 | return Token(EOF, None) 88 | 89 | 90 | class Parser(object): 91 | def __init__(self, lexer): 92 | self.lexer = lexer 93 | # set current token to the first token taken from the input 94 | self.current_token = self.lexer.get_next_token() 95 | 96 | def error(self): 97 | raise Exception('Invalid syntax') 98 | 99 | def eat(self, token_type): 100 | # compare the current token type with the passed token 101 | # type and if they match then "eat" the current token 102 | # and assign the next token to the self.current_token, 103 | # otherwise raise an exception. 104 | if self.current_token.type == token_type: 105 | self.current_token = self.lexer.get_next_token() 106 | else: 107 | self.error() 108 | 109 | def factor(self): 110 | """Parse integer. 111 | 112 | factor : INTEGER 113 | """ 114 | self.eat(INTEGER) 115 | 116 | def expr(self): 117 | """Arithmetic expression parser. 118 | 119 | Grammar: 120 | 121 | expr : factor ((MUL | DIV) factor)* 122 | factor : INTEGER 123 | """ 124 | self.factor() 125 | 126 | while self.current_token.type in (MUL, DIV): 127 | token = self.current_token 128 | if token.type == MUL: 129 | self.eat(MUL) 130 | self.factor() 131 | elif token.type == DIV: 132 | self.eat(DIV) 133 | self.factor() 134 | 135 | def parse(self): 136 | self.expr() 137 | 138 | 139 | def main(): 140 | while True: 141 | try: 142 | text = input('calc> ') 143 | except EOFError: 144 | break 145 | if not text: 146 | continue 147 | 148 | parser = Parser(Lexer(text)) 149 | parser.parse() 150 | 151 | 152 | if __name__ == '__main__': 153 | main() 154 | -------------------------------------------------------------------------------- /part9/python/test_interpreter.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class LexerTestCase(unittest.TestCase): 5 | def makeLexer(self, text): 6 | from spi import Lexer 7 | lexer = Lexer(text) 8 | return lexer 9 | 10 | def test_lexer_integer(self): 11 | from spi import INTEGER 12 | lexer = self.makeLexer('234') 13 | token = lexer.get_next_token() 14 | self.assertEqual(token.type, INTEGER) 15 | self.assertEqual(token.value, 234) 16 | 17 | def test_lexer_mul(self): 18 | from spi import MUL 19 | lexer = self.makeLexer('*') 20 | token = lexer.get_next_token() 21 | self.assertEqual(token.type, MUL) 22 | self.assertEqual(token.value, '*') 23 | 24 | def test_lexer_div(self): 25 | from spi import DIV 26 | lexer = self.makeLexer(' / ') 27 | token = lexer.get_next_token() 28 | self.assertEqual(token.type, DIV) 29 | self.assertEqual(token.value, '/') 30 | 31 | def test_lexer_plus(self): 32 | from spi import PLUS 33 | lexer = self.makeLexer('+') 34 | token = lexer.get_next_token() 35 | self.assertEqual(token.type, PLUS) 36 | self.assertEqual(token.value, '+') 37 | 38 | def test_lexer_minus(self): 39 | from spi import MINUS 40 | lexer = self.makeLexer('-') 41 | token = lexer.get_next_token() 42 | self.assertEqual(token.type, MINUS) 43 | self.assertEqual(token.value, '-') 44 | 45 | def test_lexer_lparen(self): 46 | from spi import LPAREN 47 | lexer = self.makeLexer('(') 48 | token = lexer.get_next_token() 49 | self.assertEqual(token.type, LPAREN) 50 | self.assertEqual(token.value, '(') 51 | 52 | def test_lexer_rparen(self): 53 | from spi import RPAREN 54 | lexer = self.makeLexer(')') 55 | token = lexer.get_next_token() 56 | self.assertEqual(token.type, RPAREN) 57 | self.assertEqual(token.value, ')') 58 | 59 | def test_lexer_new_tokens(self): 60 | from spi import ASSIGN, DOT, ID, SEMI, BEGIN, END 61 | records = ( 62 | (':=', ASSIGN, ':='), 63 | ('.', DOT, '.'), 64 | ('number', ID, 'number'), 65 | (';', SEMI, ';'), 66 | ('BEGIN', BEGIN, 'BEGIN'), 67 | ('END', END, 'END'), 68 | ) 69 | for text, tok_type, tok_val in records: 70 | lexer = self.makeLexer(text) 71 | token = lexer.get_next_token() 72 | self.assertEqual(token.type, tok_type) 73 | self.assertEqual(token.value, tok_val) 74 | 75 | 76 | class InterpreterTestCase(unittest.TestCase): 77 | def makeInterpreter(self, text): 78 | from spi import Lexer, Parser, Interpreter 79 | lexer = Lexer(text) 80 | parser = Parser(lexer) 81 | interpreter = Interpreter(parser) 82 | return interpreter 83 | 84 | def test_arithmetic_expressions(self): 85 | for expr, result in ( 86 | ('3', 3), 87 | ('2 + 7 * 4', 30), 88 | ('7 - 8 / 4', 5), 89 | ('14 + 2 * 3 - 6 / 2', 17), 90 | ('7 + 3 * (10 / (12 / (3 + 1) - 1))', 22), 91 | ('7 + 3 * (10 / (12 / (3 + 1) - 1)) / (2 + 3) - 5 - 3 + (8)', 10), 92 | ('7 + (((3 + 2)))', 12), 93 | ('- 3', -3), 94 | ('+ 3', 3), 95 | ('5 - - - + - 3', 8), 96 | ('5 - - - + - (3 + 4) - +2', 10), 97 | ): 98 | interpreter = self.makeInterpreter('BEGIN a := %s END.' % expr) 99 | interpreter.interpret() 100 | globals = interpreter.GLOBAL_SCOPE 101 | self.assertEqual(globals['a'], result) 102 | 103 | def test_expression_invalid_syntax1(self): 104 | interpreter = self.makeInterpreter('BEGIN a := 10 * ; END.') 105 | with self.assertRaises(Exception): 106 | interpreter.interpret() 107 | 108 | def test_expression_invalid_syntax2(self): 109 | interpreter = self.makeInterpreter('BEGIN a := 1 (1 + 2); END.') 110 | with self.assertRaises(Exception): 111 | interpreter.interpret() 112 | 113 | def test_statements(self): 114 | text = """\ 115 | BEGIN 116 | 117 | BEGIN 118 | number := 2; 119 | a := number; 120 | b := 10 * a + 10 * number / 4; 121 | c := a - - b 122 | END; 123 | 124 | x := 11; 125 | END. 126 | """ 127 | interpreter = self.makeInterpreter(text) 128 | interpreter.interpret() 129 | 130 | globals = interpreter.GLOBAL_SCOPE 131 | self.assertEqual(len(globals.keys()), 5) 132 | self.assertEqual(globals['number'], 2) 133 | self.assertEqual(globals['a'], 2) 134 | self.assertEqual(globals['b'], 25) 135 | self.assertEqual(globals['c'], 27) 136 | self.assertEqual(globals['x'], 11) 137 | 138 | 139 | if __name__ == '__main__': 140 | unittest.main() 141 | -------------------------------------------------------------------------------- /part4/calc4.py: -------------------------------------------------------------------------------- 1 | # Token types 2 | # 3 | # EOF (end-of-file) token is used to indicate that 4 | # there is no more input left for lexical analysis 5 | INTEGER, MUL, DIV, EOF = 'INTEGER', 'MUL', 'DIV', 'EOF' 6 | 7 | 8 | class Token(object): 9 | def __init__(self, type, value): 10 | # token type: INTEGER, MUL, DIV, or EOF 11 | self.type = type 12 | # token value: non-negative integer value, '*', '/', or None 13 | self.value = value 14 | 15 | def __str__(self): 16 | """String representation of the class instance. 17 | 18 | Examples: 19 | Token(INTEGER, 3) 20 | Token(MUL, '*') 21 | """ 22 | return 'Token({type}, {value})'.format( 23 | type=self.type, 24 | value=repr(self.value) 25 | ) 26 | 27 | def __repr__(self): 28 | return self.__str__() 29 | 30 | 31 | class Lexer(object): 32 | def __init__(self, text): 33 | # client string input, e.g. "3 * 5", "12 / 3 * 4", etc 34 | self.text = text 35 | # self.pos is an index into self.text 36 | self.pos = 0 37 | self.current_char = self.text[self.pos] 38 | 39 | def error(self): 40 | raise Exception('Invalid character') 41 | 42 | def advance(self): 43 | """Advance the `pos` pointer and set the `current_char` variable.""" 44 | self.pos += 1 45 | if self.pos > len(self.text) - 1: 46 | self.current_char = None # Indicates end of input 47 | else: 48 | self.current_char = self.text[self.pos] 49 | 50 | def skip_whitespace(self): 51 | while self.current_char is not None and self.current_char.isspace(): 52 | self.advance() 53 | 54 | def integer(self): 55 | """Return a (multidigit) integer consumed from the input.""" 56 | result = '' 57 | while self.current_char is not None and self.current_char.isdigit(): 58 | result += self.current_char 59 | self.advance() 60 | return int(result) 61 | 62 | def get_next_token(self): 63 | """Lexical analyzer (also known as scanner or tokenizer) 64 | 65 | This method is responsible for breaking a sentence 66 | apart into tokens. One token at a time. 67 | """ 68 | while self.current_char is not None: 69 | 70 | if self.current_char.isspace(): 71 | self.skip_whitespace() 72 | continue 73 | 74 | if self.current_char.isdigit(): 75 | return Token(INTEGER, self.integer()) 76 | 77 | if self.current_char == '*': 78 | self.advance() 79 | return Token(MUL, '*') 80 | 81 | if self.current_char == '/': 82 | self.advance() 83 | return Token(DIV, '/') 84 | 85 | self.error() 86 | 87 | return Token(EOF, None) 88 | 89 | 90 | class Interpreter(object): 91 | def __init__(self, lexer): 92 | self.lexer = lexer 93 | # set current token to the first token taken from the input 94 | self.current_token = self.lexer.get_next_token() 95 | 96 | def error(self): 97 | raise Exception('Invalid syntax') 98 | 99 | def eat(self, token_type): 100 | # compare the current token type with the passed token 101 | # type and if they match then "eat" the current token 102 | # and assign the next token to the self.current_token, 103 | # otherwise raise an exception. 104 | if self.current_token.type == token_type: 105 | self.current_token = self.lexer.get_next_token() 106 | else: 107 | self.error() 108 | 109 | def factor(self): 110 | """Return an INTEGER token value. 111 | 112 | factor : INTEGER 113 | """ 114 | token = self.current_token 115 | self.eat(INTEGER) 116 | return token.value 117 | 118 | def expr(self): 119 | """Arithmetic expression parser / interpreter. 120 | 121 | expr : factor ((MUL | DIV) factor)* 122 | factor : INTEGER 123 | """ 124 | result = self.factor() 125 | 126 | while self.current_token.type in (MUL, DIV): 127 | token = self.current_token 128 | if token.type == MUL: 129 | self.eat(MUL) 130 | result = result * self.factor() 131 | elif token.type == DIV: 132 | self.eat(DIV) 133 | result = result // self.factor() 134 | 135 | return result 136 | 137 | 138 | def main(): 139 | while True: 140 | try: 141 | text = input('calc> ') 142 | except EOFError: 143 | break 144 | if not text: 145 | continue 146 | lexer = Lexer(text) 147 | interpreter = Interpreter(lexer) 148 | result = interpreter.expr() 149 | print(result) 150 | 151 | 152 | if __name__ == '__main__': 153 | main() 154 | -------------------------------------------------------------------------------- /part3/calc3.py: -------------------------------------------------------------------------------- 1 | # Token types 2 | # 3 | # EOF (end-of-file) token is used to indicate that 4 | # there is no more input left for lexical analysis 5 | INTEGER, PLUS, MINUS, EOF = 'INTEGER', 'PLUS', 'MINUS', 'EOF' 6 | 7 | 8 | class Token(object): 9 | def __init__(self, type, value): 10 | # token type: INTEGER, PLUS, MINUS, or EOF 11 | self.type = type 12 | # token value: non-negative integer value, '+', '-', or None 13 | self.value = value 14 | 15 | def __str__(self): 16 | """String representation of the class instance. 17 | 18 | Examples: 19 | Token(INTEGER, 3) 20 | Token(PLUS, '+') 21 | """ 22 | return 'Token({type}, {value})'.format( 23 | type=self.type, 24 | value=repr(self.value) 25 | ) 26 | 27 | def __repr__(self): 28 | return self.__str__() 29 | 30 | 31 | class Interpreter(object): 32 | def __init__(self, text): 33 | # client string input, e.g. "3 + 5", "12 - 5 + 3", etc 34 | self.text = text 35 | # self.pos is an index into self.text 36 | self.pos = 0 37 | # current token instance 38 | self.current_token = None 39 | self.current_char = self.text[self.pos] 40 | 41 | ########################################################## 42 | # Lexer code # 43 | ########################################################## 44 | def error(self): 45 | raise Exception('Invalid syntax') 46 | 47 | def advance(self): 48 | """Advance the `pos` pointer and set the `current_char` variable.""" 49 | self.pos += 1 50 | if self.pos > len(self.text) - 1: 51 | self.current_char = None # Indicates end of input 52 | else: 53 | self.current_char = self.text[self.pos] 54 | 55 | def skip_whitespace(self): 56 | while self.current_char is not None and self.current_char.isspace(): 57 | self.advance() 58 | 59 | def integer(self): 60 | """Return a (multidigit) integer consumed from the input.""" 61 | result = '' 62 | while self.current_char is not None and self.current_char.isdigit(): 63 | result += self.current_char 64 | self.advance() 65 | return int(result) 66 | 67 | def get_next_token(self): 68 | """Lexical analyzer (also known as scanner or tokenizer) 69 | 70 | This method is responsible for breaking a sentence 71 | apart into tokens. One token at a time. 72 | """ 73 | while self.current_char is not None: 74 | 75 | if self.current_char.isspace(): 76 | self.skip_whitespace() 77 | continue 78 | 79 | if self.current_char.isdigit(): 80 | return Token(INTEGER, self.integer()) 81 | 82 | if self.current_char == '+': 83 | self.advance() 84 | return Token(PLUS, '+') 85 | 86 | if self.current_char == '-': 87 | self.advance() 88 | return Token(MINUS, '-') 89 | 90 | self.error() 91 | 92 | return Token(EOF, None) 93 | 94 | ########################################################## 95 | # Parser / Interpreter code # 96 | ########################################################## 97 | def eat(self, token_type): 98 | # compare the current token type with the passed token 99 | # type and if they match then "eat" the current token 100 | # and assign the next token to the self.current_token, 101 | # otherwise raise an exception. 102 | if self.current_token.type == token_type: 103 | self.current_token = self.get_next_token() 104 | else: 105 | self.error() 106 | 107 | def term(self): 108 | """Return an INTEGER token value.""" 109 | token = self.current_token 110 | self.eat(INTEGER) 111 | return token.value 112 | 113 | def expr(self): 114 | """Arithmetic expression parser / interpreter.""" 115 | # set current token to the first token taken from the input 116 | self.current_token = self.get_next_token() 117 | 118 | result = self.term() 119 | while self.current_token.type in (PLUS, MINUS): 120 | token = self.current_token 121 | if token.type == PLUS: 122 | self.eat(PLUS) 123 | result = result + self.term() 124 | elif token.type == MINUS: 125 | self.eat(MINUS) 126 | result = result - self.term() 127 | 128 | return result 129 | 130 | 131 | def main(): 132 | while True: 133 | try: 134 | text = input('calc> ') 135 | except EOFError: 136 | break 137 | if not text: 138 | continue 139 | interpreter = Interpreter(text) 140 | result = interpreter.expr() 141 | print(result) 142 | 143 | 144 | if __name__ == '__main__': 145 | main() 146 | -------------------------------------------------------------------------------- /part14/scope02.py: -------------------------------------------------------------------------------- 1 | from spi import ( 2 | Lexer, 3 | Parser, 4 | NodeVisitor, 5 | BuiltinTypeSymbol, 6 | VarSymbol, 7 | ProcedureSymbol 8 | ) 9 | 10 | 11 | class ScopedSymbolTable(object): 12 | def __init__(self, scope_name, scope_level): 13 | self._symbols = {} 14 | self.scope_name = scope_name 15 | self.scope_level = scope_level 16 | self._init_builtins() 17 | 18 | def _init_builtins(self): 19 | self.insert(BuiltinTypeSymbol('INTEGER')) 20 | self.insert(BuiltinTypeSymbol('REAL')) 21 | 22 | def __str__(self): 23 | h1 = 'SCOPE (SCOPED SYMBOL TABLE)' 24 | lines = ['\n', h1, '=' * len(h1)] 25 | for header_name, header_value in ( 26 | ('Scope name', self.scope_name), 27 | ('Scope level', self.scope_level), 28 | ): 29 | lines.append('%-15s: %s' % (header_name, header_value)) 30 | h2 = 'Scope (Scoped symbol table) contents' 31 | lines.extend([h2, '-' * len(h2)]) 32 | lines.extend( 33 | ('%7s: %r' % (key, value)) 34 | for key, value in self._symbols.items() 35 | ) 36 | lines.append('\n') 37 | s = '\n'.join(lines) 38 | return s 39 | 40 | __repr__ = __str__ 41 | 42 | def insert(self, symbol): 43 | print('Insert: %s' % symbol.name) 44 | self._symbols[symbol.name] = symbol 45 | 46 | def lookup(self, name): 47 | print('Lookup: %s' % name) 48 | symbol = self._symbols.get(name) 49 | # 'symbol' is either an instance of the Symbol class or None 50 | return symbol 51 | 52 | 53 | class SemanticAnalyzer(NodeVisitor): 54 | def __init__(self): 55 | self.current_scope = None 56 | 57 | def visit_Block(self, node): 58 | for declaration in node.declarations: 59 | self.visit(declaration) 60 | self.visit(node.compound_statement) 61 | 62 | def visit_Program(self, node): 63 | print('ENTER scope: global') 64 | global_scope = ScopedSymbolTable( 65 | scope_name='global', 66 | scope_level=1, 67 | ) 68 | self.current_scope = global_scope 69 | 70 | # visit subtree 71 | self.visit(node.block) 72 | 73 | print(global_scope) 74 | print('LEAVE scope: global') 75 | 76 | def visit_Compound(self, node): 77 | for child in node.children: 78 | self.visit(child) 79 | 80 | def visit_NoOp(self, node): 81 | pass 82 | 83 | def visit_BinOp(self, node): 84 | self.visit(node.left) 85 | self.visit(node.right) 86 | 87 | def visit_ProcedureDecl(self, node): 88 | proc_name = node.proc_name 89 | proc_symbol = ProcedureSymbol(proc_name) 90 | self.current_scope.insert(proc_symbol) 91 | 92 | print('ENTER scope: %s' % proc_name) 93 | # Scope for parameters and local variables 94 | procedure_scope = ScopedSymbolTable( 95 | scope_name=proc_name, 96 | scope_level=2, 97 | ) 98 | self.current_scope = procedure_scope 99 | 100 | # Insert parameters into the procedure scope 101 | for param in node.params: 102 | param_type = self.current_scope.lookup(param.type_node.value) 103 | param_name = param.var_node.value 104 | var_symbol = VarSymbol(param_name, param_type) 105 | self.current_scope.insert(var_symbol) 106 | proc_symbol.params.append(var_symbol) 107 | 108 | self.visit(node.block_node) 109 | 110 | print(procedure_scope) 111 | print('LEAVE scope: %s' % proc_name) 112 | 113 | def visit_VarDecl(self, node): 114 | type_name = node.type_node.value 115 | type_symbol = self.current_scope.lookup(type_name) 116 | 117 | # We have all the information we need to create a variable symbol. 118 | # Create the symbol and insert it into the symbol table. 119 | var_name = node.var_node.value 120 | var_symbol = VarSymbol(var_name, type_symbol) 121 | 122 | self.current_scope.insert(var_symbol) 123 | 124 | def visit_Assign(self, node): 125 | # right-hand side 126 | self.visit(node.right) 127 | # left-hand side 128 | self.visit(node.left) 129 | 130 | def visit_Var(self, node): 131 | var_name = node.value 132 | var_symbol = self.current_scope.lookup(var_name) 133 | if var_symbol is None: 134 | raise Exception( 135 | "Error: Symbol(identifier) not found '%s'" % var_name 136 | ) 137 | 138 | 139 | if __name__ == '__main__': 140 | text = """ 141 | program Main; 142 | var x, y: real; 143 | 144 | procedure Alpha(a : integer); 145 | var y : integer; 146 | begin 147 | 148 | end; 149 | 150 | begin { Main } 151 | 152 | end. { Main } 153 | """ 154 | 155 | lexer = Lexer(text) 156 | parser = Parser(lexer) 157 | tree = parser.parse() 158 | semantic_analyzer = SemanticAnalyzer() 159 | semantic_analyzer.visit(tree) 160 | -------------------------------------------------------------------------------- /part11/python/test_interpreter.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class LexerTestCase(unittest.TestCase): 5 | def makeLexer(self, text): 6 | from spi import Lexer 7 | lexer = Lexer(text) 8 | return lexer 9 | 10 | def test_tokens(self): 11 | from spi import ( 12 | INTEGER_CONST, REAL_CONST, MUL, INTEGER_DIV, FLOAT_DIV, PLUS, MINUS, LPAREN, RPAREN, 13 | ASSIGN, DOT, ID, SEMI, BEGIN, END 14 | ) 15 | records = ( 16 | ('234', INTEGER_CONST, 234), 17 | ('3.14', REAL_CONST, 3.14), 18 | ('*', MUL, '*'), 19 | ('DIV', INTEGER_DIV, 'DIV'), 20 | ('/', FLOAT_DIV, '/'), 21 | ('+', PLUS, '+'), 22 | ('-', MINUS, '-'), 23 | ('(', LPAREN, '('), 24 | (')', RPAREN, ')'), 25 | (':=', ASSIGN, ':='), 26 | ('.', DOT, '.'), 27 | ('number', ID, 'number'), 28 | (';', SEMI, ';'), 29 | ('BEGIN', BEGIN, 'BEGIN'), 30 | ('END', END, 'END'), 31 | ) 32 | for text, tok_type, tok_val in records: 33 | lexer = self.makeLexer(text) 34 | token = lexer.get_next_token() 35 | self.assertEqual(token.type, tok_type) 36 | self.assertEqual(token.value, tok_val) 37 | 38 | 39 | class InterpreterTestCase(unittest.TestCase): 40 | def makeInterpreter(self, text): 41 | from spi import Lexer, Parser, SymbolTableBuilder, Interpreter 42 | lexer = Lexer(text) 43 | parser = Parser(lexer) 44 | tree = parser.parse() 45 | symtab_builder = SymbolTableBuilder() 46 | symtab_builder.visit(tree) 47 | 48 | 49 | interpreter = Interpreter(tree) 50 | return interpreter 51 | 52 | def test_integer_arithmetic_expressions(self): 53 | for expr, result in ( 54 | ('3', 3), 55 | ('2 + 7 * 4', 30), 56 | ('7 - 8 DIV 4', 5), 57 | ('14 + 2 * 3 - 6 DIV 2', 17), 58 | ('7 + 3 * (10 DIV (12 DIV (3 + 1) - 1))', 22), 59 | ('7 + 3 * (10 DIV (12 DIV (3 + 1) - 1)) DIV (2 + 3) - 5 - 3 + (8)', 10), 60 | ('7 + (((3 + 2)))', 12), 61 | ('- 3', -3), 62 | ('+ 3', 3), 63 | ('5 - - - + - 3', 8), 64 | ('5 - - - + - (3 + 4) - +2', 10), 65 | ): 66 | interpreter = self.makeInterpreter( 67 | """PROGRAM Test; 68 | VAR 69 | a : INTEGER; 70 | BEGIN 71 | a := %s 72 | END. 73 | """ % expr 74 | ) 75 | interpreter.interpret() 76 | globals = interpreter.GLOBAL_MEMORY 77 | self.assertEqual(globals['a'], result) 78 | 79 | def test_float_arithmetic_expressions(self): 80 | for expr, result in ( 81 | ('3.14', 3.14), 82 | ('2.14 + 7 * 4', 30.14), 83 | ('7.14 - 8 / 4', 5.14), 84 | ): 85 | interpreter = self.makeInterpreter( 86 | """PROGRAM Test; 87 | VAR 88 | a : REAL; 89 | BEGIN 90 | a := %s 91 | END. 92 | """ % expr 93 | ) 94 | interpreter.interpret() 95 | globals = interpreter.GLOBAL_MEMORY 96 | self.assertEqual(globals['a'], result) 97 | 98 | def test_expression_invalid_syntax_01(self): 99 | with self.assertRaises(Exception): 100 | self.makeInterpreter( 101 | """ 102 | PROGRAM Test; 103 | VAR 104 | a : INTEGER; 105 | BEGIN 106 | a := 10 * ; {Invalid syntax} 107 | END. 108 | """ 109 | ) 110 | 111 | def test_expression_invalid_syntax_02(self): 112 | with self.assertRaises(Exception): 113 | self.makeInterpreter( 114 | """ 115 | PROGRAM Test; 116 | VAR 117 | a : INTEGER; 118 | BEGIN 119 | a := 1 (1 + 2); {Invalid syntax} 120 | END. 121 | """ 122 | ) 123 | 124 | def test_program(self): 125 | text = """\ 126 | PROGRAM Part11; 127 | VAR 128 | number : INTEGER; 129 | a, b : INTEGER; 130 | y : REAL; 131 | 132 | BEGIN {Part11} 133 | number := 2; 134 | a := number ; 135 | b := 10 * a + 10 * number DIV 4; 136 | y := 20 / 7 + 3.14 137 | END. {Part11} 138 | """ 139 | interpreter = self.makeInterpreter(text) 140 | interpreter.interpret() 141 | 142 | globals = interpreter.GLOBAL_MEMORY 143 | self.assertEqual(len(globals.keys()), 4) 144 | self.assertEqual(globals['number'], 2) 145 | self.assertEqual(globals['a'], 2) 146 | self.assertEqual(globals['b'], 25) 147 | self.assertAlmostEqual(globals['y'], float(20) / 7 + 3.14) # 5.9971... 148 | 149 | 150 | if __name__ == '__main__': 151 | unittest.main() 152 | -------------------------------------------------------------------------------- /part10/python/test_interpreter.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class LexerTestCase(unittest.TestCase): 5 | def makeLexer(self, text): 6 | from spi import Lexer 7 | lexer = Lexer(text) 8 | return lexer 9 | 10 | def test_tokens(self): 11 | from spi import ( 12 | INTEGER_CONST, REAL_CONST, MUL, INTEGER_DIV, FLOAT_DIV, PLUS, MINUS, LPAREN, RPAREN, 13 | ASSIGN, DOT, ID, SEMI, BEGIN, END 14 | ) 15 | records = ( 16 | ('234', INTEGER_CONST, 234), 17 | ('3.14', REAL_CONST, 3.14), 18 | ('*', MUL, '*'), 19 | ('DIV', INTEGER_DIV, 'DIV'), 20 | ('/', FLOAT_DIV, '/'), 21 | ('+', PLUS, '+'), 22 | ('-', MINUS, '-'), 23 | ('(', LPAREN, '('), 24 | (')', RPAREN, ')'), 25 | (':=', ASSIGN, ':='), 26 | ('.', DOT, '.'), 27 | ('number', ID, 'number'), 28 | (';', SEMI, ';'), 29 | ('BEGIN', BEGIN, 'BEGIN'), 30 | ('END', END, 'END'), 31 | ) 32 | for text, tok_type, tok_val in records: 33 | lexer = self.makeLexer(text) 34 | token = lexer.get_next_token() 35 | self.assertEqual(token.type, tok_type) 36 | self.assertEqual(token.value, tok_val) 37 | 38 | 39 | class InterpreterTestCase(unittest.TestCase): 40 | def makeInterpreter(self, text): 41 | from spi import Lexer, Parser, Interpreter 42 | lexer = Lexer(text) 43 | parser = Parser(lexer) 44 | interpreter = Interpreter(parser) 45 | return interpreter 46 | 47 | def test_integer_arithmetic_expressions(self): 48 | for expr, result in ( 49 | ('3', 3), 50 | ('2 + 7 * 4', 30), 51 | ('7 - 8 DIV 4', 5), 52 | ('14 + 2 * 3 - 6 DIV 2', 17), 53 | ('7 + 3 * (10 DIV (12 DIV (3 + 1) - 1))', 22), 54 | ('7 + 3 * (10 DIV (12 DIV (3 + 1) - 1)) DIV (2 + 3) - 5 - 3 + (8)', 10), 55 | ('7 + (((3 + 2)))', 12), 56 | ('- 3', -3), 57 | ('+ 3', 3), 58 | ('5 - - - + - 3', 8), 59 | ('5 - - - + - (3 + 4) - +2', 10), 60 | ): 61 | interpreter = self.makeInterpreter( 62 | """PROGRAM Test; 63 | VAR 64 | a : INTEGER; 65 | BEGIN 66 | a := %s 67 | END. 68 | """ % expr 69 | ) 70 | interpreter.interpret() 71 | globals = interpreter.GLOBAL_SCOPE 72 | self.assertEqual(globals['a'], result) 73 | 74 | def test_float_arithmetic_expressions(self): 75 | for expr, result in ( 76 | ('3.14', 3.14), 77 | ('2.14 + 7 * 4', 30.14), 78 | ('7.14 - 8 / 4', 5.14), 79 | ): 80 | interpreter = self.makeInterpreter( 81 | """PROGRAM Test; 82 | VAR 83 | a : REAL; 84 | BEGIN 85 | a := %s 86 | END. 87 | """ % expr 88 | ) 89 | interpreter.interpret() 90 | globals = interpreter.GLOBAL_SCOPE 91 | self.assertEqual(globals['a'], result) 92 | 93 | def test_expression_invalid_syntax_01(self): 94 | interpreter = self.makeInterpreter( 95 | """ 96 | PROGRAM Test; 97 | BEGIN 98 | a := 10 * ; {Invalid syntax} 99 | END. 100 | """ 101 | ) 102 | with self.assertRaises(Exception): 103 | interpreter.interpret() 104 | 105 | def test_expression_invalid_syntax_02(self): 106 | interpreter = self.makeInterpreter( 107 | """ 108 | PROGRAM Test; 109 | BEGIN 110 | a := 1 (1 + 2); {Invalid syntax} 111 | END. 112 | """ 113 | ) 114 | with self.assertRaises(Exception): 115 | interpreter.interpret() 116 | 117 | def test_program(self): 118 | text = """\ 119 | PROGRAM Part10; 120 | VAR 121 | number : INTEGER; 122 | a, b, c, x : INTEGER; 123 | y : REAL; 124 | 125 | BEGIN {Part10} 126 | BEGIN 127 | number := 2; 128 | a := number; 129 | b := 10 * a + 10 * number DIV 4; 130 | c := a - - b 131 | END; 132 | x := 11; 133 | y := 20 / 7 + 3.14; 134 | END. {Part10} 135 | """ 136 | interpreter = self.makeInterpreter(text) 137 | interpreter.interpret() 138 | 139 | globals = interpreter.GLOBAL_SCOPE 140 | self.assertEqual(len(globals.keys()), 6) 141 | self.assertEqual(globals['number'], 2) 142 | self.assertEqual(globals['a'], 2) 143 | self.assertEqual(globals['b'], 25) 144 | self.assertEqual(globals['c'], 27) 145 | self.assertEqual(globals['x'], 11) 146 | self.assertAlmostEqual(globals['y'], float(20) / 7 + 3.14) # 5.9971... 147 | 148 | 149 | if __name__ == '__main__': 150 | unittest.main() 151 | -------------------------------------------------------------------------------- /part8/python/test_interpreter.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class LexerTestCase(unittest.TestCase): 5 | def makeLexer(self, text): 6 | from spi import Lexer 7 | lexer = Lexer(text) 8 | return lexer 9 | 10 | def test_lexer_integer(self): 11 | from spi import INTEGER 12 | lexer = self.makeLexer('234') 13 | token = lexer.get_next_token() 14 | self.assertEqual(token.type, INTEGER) 15 | self.assertEqual(token.value, 234) 16 | 17 | def test_lexer_mul(self): 18 | from spi import MUL 19 | lexer = self.makeLexer('*') 20 | token = lexer.get_next_token() 21 | self.assertEqual(token.type, MUL) 22 | self.assertEqual(token.value, '*') 23 | 24 | def test_lexer_div(self): 25 | from spi import DIV 26 | lexer = self.makeLexer(' / ') 27 | token = lexer.get_next_token() 28 | self.assertEqual(token.type, DIV) 29 | self.assertEqual(token.value, '/') 30 | 31 | def test_lexer_plus(self): 32 | from spi import PLUS 33 | lexer = self.makeLexer('+') 34 | token = lexer.get_next_token() 35 | self.assertEqual(token.type, PLUS) 36 | self.assertEqual(token.value, '+') 37 | 38 | def test_lexer_minus(self): 39 | from spi import MINUS 40 | lexer = self.makeLexer('-') 41 | token = lexer.get_next_token() 42 | self.assertEqual(token.type, MINUS) 43 | self.assertEqual(token.value, '-') 44 | 45 | def test_lexer_lparen(self): 46 | from spi import LPAREN 47 | lexer = self.makeLexer('(') 48 | token = lexer.get_next_token() 49 | self.assertEqual(token.type, LPAREN) 50 | self.assertEqual(token.value, '(') 51 | 52 | def test_lexer_rparen(self): 53 | from spi import RPAREN 54 | lexer = self.makeLexer(')') 55 | token = lexer.get_next_token() 56 | self.assertEqual(token.type, RPAREN) 57 | self.assertEqual(token.value, ')') 58 | 59 | 60 | class InterpreterTestCase(unittest.TestCase): 61 | def makeInterpreter(self, text): 62 | from spi import Lexer, Parser, Interpreter 63 | lexer = Lexer(text) 64 | parser = Parser(lexer) 65 | interpreter = Interpreter(parser) 66 | return interpreter 67 | 68 | def test_expression1(self): 69 | interpreter = self.makeInterpreter('3') 70 | result = interpreter.interpret() 71 | self.assertEqual(result, 3) 72 | 73 | def test_expression2(self): 74 | interpreter = self.makeInterpreter('2 + 7 * 4') 75 | result = interpreter.interpret() 76 | self.assertEqual(result, 30) 77 | 78 | def test_expression3(self): 79 | interpreter = self.makeInterpreter('7 - 8 / 4') 80 | result = interpreter.interpret() 81 | self.assertEqual(result, 5) 82 | 83 | def test_expression4(self): 84 | interpreter = self.makeInterpreter('14 + 2 * 3 - 6 / 2') 85 | result = interpreter.interpret() 86 | self.assertEqual(result, 17) 87 | 88 | def test_expression5(self): 89 | interpreter = self.makeInterpreter('7 + 3 * (10 / (12 / (3 + 1) - 1))') 90 | result = interpreter.interpret() 91 | self.assertEqual(result, 22) 92 | 93 | def test_expression6(self): 94 | interpreter = self.makeInterpreter( 95 | '7 + 3 * (10 / (12 / (3 + 1) - 1)) / (2 + 3) - 5 - 3 + (8)' 96 | ) 97 | result = interpreter.interpret() 98 | self.assertEqual(result, 10) 99 | 100 | def test_expression7(self): 101 | interpreter = self.makeInterpreter('7 + (((3 + 2)))') 102 | result = interpreter.interpret() 103 | self.assertEqual(result, 12) 104 | 105 | def test_expression8(self): 106 | interpreter = self.makeInterpreter('- 3') 107 | result = interpreter.interpret() 108 | self.assertEqual(result, -3) 109 | 110 | def test_expression9(self): 111 | interpreter = self.makeInterpreter('+ 3') 112 | result = interpreter.interpret() 113 | self.assertEqual(result, 3) 114 | 115 | def test_expression10(self): 116 | interpreter = self.makeInterpreter('5 - - - + - 3') 117 | result = interpreter.interpret() 118 | self.assertEqual(result, 8) 119 | 120 | def test_expression11(self): 121 | interpreter = self.makeInterpreter('5 - - - + - (3 + 4) - +2') 122 | result = interpreter.interpret() 123 | self.assertEqual(result, 10) 124 | 125 | def test_no_expression(self): 126 | interpreter = self.makeInterpreter(' ') 127 | result = interpreter.interpret() 128 | self.assertEqual(result, '') 129 | 130 | def test_expression_invalid_syntax1(self): 131 | interpreter = self.makeInterpreter('10 *') 132 | with self.assertRaises(Exception): 133 | interpreter.interpret() 134 | 135 | def test_expression_invalid_syntax2(self): 136 | interpreter = self.makeInterpreter('1 (1 + 2)') 137 | with self.assertRaises(Exception): 138 | interpreter.interpret() 139 | 140 | 141 | if __name__ == '__main__': 142 | unittest.main() 143 | -------------------------------------------------------------------------------- /part2/calc2.py: -------------------------------------------------------------------------------- 1 | # Token types 2 | # EOF (end-of-file) token is used to indicate that 3 | # there is no more input left for lexical analysis 4 | INTEGER, PLUS, MINUS, EOF = 'INTEGER', 'PLUS', 'MINUS', 'EOF' 5 | 6 | 7 | class Token(object): 8 | def __init__(self, type, value): 9 | # token type: INTEGER, PLUS, MINUS, or EOF 10 | self.type = type 11 | # token value: non-negative integer value, '+', '-', or None 12 | self.value = value 13 | 14 | def __str__(self): 15 | """String representation of the class instance. 16 | 17 | Examples: 18 | Token(INTEGER, 3) 19 | Token(PLUS '+') 20 | """ 21 | return 'Token({type}, {value})'.format( 22 | type=self.type, 23 | value=repr(self.value) 24 | ) 25 | 26 | def __repr__(self): 27 | return self.__str__() 28 | 29 | 30 | class Interpreter(object): 31 | def __init__(self, text): 32 | # client string input, e.g. "3 + 5", "12 - 5", etc 33 | self.text = text 34 | # self.pos is an index into self.text 35 | self.pos = 0 36 | # current token instance 37 | self.current_token = None 38 | self.current_char = self.text[self.pos] 39 | 40 | def error(self): 41 | raise Exception('Error parsing input') 42 | 43 | def advance(self): 44 | """Advance the 'pos' pointer and set the 'current_char' variable.""" 45 | self.pos += 1 46 | if self.pos > len(self.text) - 1: 47 | self.current_char = None # Indicates end of input 48 | else: 49 | self.current_char = self.text[self.pos] 50 | 51 | def skip_whitespace(self): 52 | while self.current_char is not None and self.current_char.isspace(): 53 | self.advance() 54 | 55 | def integer(self): 56 | """Return a (multidigit) integer consumed from the input.""" 57 | result = '' 58 | while self.current_char is not None and self.current_char.isdigit(): 59 | result += self.current_char 60 | self.advance() 61 | return int(result) 62 | 63 | def get_next_token(self): 64 | """Lexical analyzer (also known as scanner or tokenizer) 65 | 66 | This method is responsible for breaking a sentence 67 | apart into tokens. 68 | """ 69 | while self.current_char is not None: 70 | 71 | if self.current_char.isspace(): 72 | self.skip_whitespace() 73 | continue 74 | 75 | if self.current_char.isdigit(): 76 | return Token(INTEGER, self.integer()) 77 | 78 | if self.current_char == '+': 79 | self.advance() 80 | return Token(PLUS, '+') 81 | 82 | if self.current_char == '-': 83 | self.advance() 84 | return Token(MINUS, '-') 85 | 86 | self.error() 87 | 88 | return Token(EOF, None) 89 | 90 | def eat(self, token_type): 91 | # compare the current token type with the passed token 92 | # type and if they match then "eat" the current token 93 | # and assign the next token to the self.current_token, 94 | # otherwise raise an exception. 95 | if self.current_token.type == token_type: 96 | self.current_token = self.get_next_token() 97 | else: 98 | self.error() 99 | 100 | def expr(self): 101 | """Parser / Interpreter 102 | 103 | expr -> INTEGER PLUS INTEGER 104 | expr -> INTEGER MINUS INTEGER 105 | """ 106 | # set current token to the first token taken from the input 107 | self.current_token = self.get_next_token() 108 | 109 | # we expect the current token to be an integer 110 | left = self.current_token 111 | self.eat(INTEGER) 112 | 113 | # we expect the current token to be either a '+' or '-' 114 | op = self.current_token 115 | if op.type == PLUS: 116 | self.eat(PLUS) 117 | else: 118 | self.eat(MINUS) 119 | 120 | # we expect the current token to be an integer 121 | right = self.current_token 122 | self.eat(INTEGER) 123 | # after the above call the self.current_token is set to 124 | # EOF token 125 | 126 | # at this point either the INTEGER PLUS INTEGER or 127 | # the INTEGER MINUS INTEGER sequence of tokens 128 | # has been successfully found and the method can just 129 | # return the result of adding or subtracting two integers, 130 | # thus effectively interpreting client input 131 | if op.type == PLUS: 132 | result = left.value + right.value 133 | else: 134 | result = left.value - right.value 135 | return result 136 | 137 | 138 | def main(): 139 | while True: 140 | try: 141 | text = input('calc> ') 142 | except EOFError: 143 | break 144 | if not text: 145 | continue 146 | interpreter = Interpreter(text) 147 | result = interpreter.expr() 148 | print(result) 149 | 150 | 151 | if __name__ == '__main__': 152 | main() 153 | -------------------------------------------------------------------------------- /part12/python/test_interpreter.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class LexerTestCase(unittest.TestCase): 5 | def makeLexer(self, text): 6 | from spi import Lexer 7 | lexer = Lexer(text) 8 | return lexer 9 | 10 | def test_tokens(self): 11 | from spi import ( 12 | INTEGER_CONST, REAL_CONST, MUL, INTEGER_DIV, FLOAT_DIV, PLUS, MINUS, LPAREN, RPAREN, 13 | ASSIGN, DOT, ID, SEMI, BEGIN, END, PROCEDURE 14 | ) 15 | records = ( 16 | ('234', INTEGER_CONST, 234), 17 | ('3.14', REAL_CONST, 3.14), 18 | ('*', MUL, '*'), 19 | ('DIV', INTEGER_DIV, 'DIV'), 20 | ('/', FLOAT_DIV, '/'), 21 | ('+', PLUS, '+'), 22 | ('-', MINUS, '-'), 23 | ('(', LPAREN, '('), 24 | (')', RPAREN, ')'), 25 | (':=', ASSIGN, ':='), 26 | ('.', DOT, '.'), 27 | ('number', ID, 'number'), 28 | (';', SEMI, ';'), 29 | ('BEGIN', BEGIN, 'BEGIN'), 30 | ('END', END, 'END'), 31 | ('PROCEDURE', PROCEDURE, 'PROCEDURE'), 32 | ) 33 | for text, tok_type, tok_val in records: 34 | lexer = self.makeLexer(text) 35 | token = lexer.get_next_token() 36 | self.assertEqual(token.type, tok_type) 37 | self.assertEqual(token.value, tok_val) 38 | 39 | 40 | class InterpreterTestCase(unittest.TestCase): 41 | def makeInterpreter(self, text): 42 | from spi import Lexer, Parser, SymbolTableBuilder, Interpreter 43 | lexer = Lexer(text) 44 | parser = Parser(lexer) 45 | tree = parser.parse() 46 | symtab_builder = SymbolTableBuilder() 47 | symtab_builder.visit(tree) 48 | 49 | 50 | interpreter = Interpreter(tree) 51 | return interpreter 52 | 53 | def test_integer_arithmetic_expressions(self): 54 | for expr, result in ( 55 | ('3', 3), 56 | ('2 + 7 * 4', 30), 57 | ('7 - 8 DIV 4', 5), 58 | ('14 + 2 * 3 - 6 DIV 2', 17), 59 | ('7 + 3 * (10 DIV (12 DIV (3 + 1) - 1))', 22), 60 | ('7 + 3 * (10 DIV (12 DIV (3 + 1) - 1)) DIV (2 + 3) - 5 - 3 + (8)', 10), 61 | ('7 + (((3 + 2)))', 12), 62 | ('- 3', -3), 63 | ('+ 3', 3), 64 | ('5 - - - + - 3', 8), 65 | ('5 - - - + - (3 + 4) - +2', 10), 66 | ): 67 | interpreter = self.makeInterpreter( 68 | """PROGRAM Test; 69 | VAR 70 | a : INTEGER; 71 | BEGIN 72 | a := %s 73 | END. 74 | """ % expr 75 | ) 76 | interpreter.interpret() 77 | globals = interpreter.GLOBAL_MEMORY 78 | self.assertEqual(globals['a'], result) 79 | 80 | def test_float_arithmetic_expressions(self): 81 | for expr, result in ( 82 | ('3.14', 3.14), 83 | ('2.14 + 7 * 4', 30.14), 84 | ('7.14 - 8 / 4', 5.14), 85 | ): 86 | interpreter = self.makeInterpreter( 87 | """PROGRAM Test; 88 | VAR 89 | a : REAL; 90 | BEGIN 91 | a := %s 92 | END. 93 | """ % expr 94 | ) 95 | interpreter.interpret() 96 | globals = interpreter.GLOBAL_MEMORY 97 | self.assertEqual(globals['a'], result) 98 | 99 | def test_expression_invalid_syntax_01(self): 100 | with self.assertRaises(Exception): 101 | self.makeInterpreter( 102 | """ 103 | PROGRAM Test; 104 | VAR 105 | a : INTEGER; 106 | BEGIN 107 | a := 10 * ; {Invalid syntax} 108 | END. 109 | """ 110 | ) 111 | 112 | def test_expression_invalid_syntax_02(self): 113 | with self.assertRaises(Exception): 114 | self.makeInterpreter( 115 | """ 116 | PROGRAM Test; 117 | VAR 118 | a : INTEGER; 119 | BEGIN 120 | a := 1 (1 + 2); {Invalid syntax} 121 | END. 122 | """ 123 | ) 124 | 125 | def test_program(self): 126 | text = """\ 127 | PROGRAM Part12; 128 | VAR 129 | number : INTEGER; 130 | a, b : INTEGER; 131 | y : REAL; 132 | 133 | PROCEDURE P1; 134 | VAR 135 | a : REAL; 136 | k : INTEGER; 137 | PROCEDURE P2; 138 | VAR 139 | a, z : INTEGER; 140 | BEGIN {P2} 141 | z := 777; 142 | END; {P2} 143 | BEGIN {P1} 144 | 145 | END; {P1} 146 | 147 | BEGIN {Part12} 148 | number := 2; 149 | a := number ; 150 | b := 10 * a + 10 * number DIV 4; 151 | y := 20 / 7 + 3.14 152 | END. {Part12} 153 | """ 154 | interpreter = self.makeInterpreter(text) 155 | interpreter.interpret() 156 | 157 | globals = interpreter.GLOBAL_MEMORY 158 | self.assertEqual(len(globals.keys()), 4) 159 | self.assertEqual(globals['number'], 2) 160 | self.assertEqual(globals['a'], 2) 161 | self.assertEqual(globals['b'], 25) 162 | self.assertAlmostEqual(globals['y'], float(20) / 7 + 3.14) # 5.9971... 163 | 164 | 165 | if __name__ == '__main__': 166 | unittest.main() 167 | -------------------------------------------------------------------------------- /part14/scope03a.py: -------------------------------------------------------------------------------- 1 | from spi import ( 2 | Lexer, 3 | Parser, 4 | NodeVisitor, 5 | BuiltinTypeSymbol, 6 | VarSymbol, 7 | ProcedureSymbol 8 | ) 9 | 10 | 11 | class ScopedSymbolTable(object): 12 | def __init__(self, scope_name, scope_level, enclosing_scope=None): 13 | self._symbols = {} 14 | self.scope_name = scope_name 15 | self.scope_level = scope_level 16 | self.enclosing_scope = enclosing_scope 17 | self._init_builtins() 18 | 19 | def _init_builtins(self): 20 | self.insert(BuiltinTypeSymbol('INTEGER')) 21 | self.insert(BuiltinTypeSymbol('REAL')) 22 | 23 | def __str__(self): 24 | h1 = 'SCOPE (SCOPED SYMBOL TABLE)' 25 | lines = ['\n', h1, '=' * len(h1)] 26 | for header_name, header_value in ( 27 | ('Scope name', self.scope_name), 28 | ('Scope level', self.scope_level), 29 | ('Enclosing scope', 30 | self.enclosing_scope.scope_name if self.enclosing_scope else None 31 | ) 32 | ): 33 | lines.append('%-15s: %s' % (header_name, header_value)) 34 | h2 = 'Scope (Scoped symbol table) contents' 35 | lines.extend([h2, '-' * len(h2)]) 36 | lines.extend( 37 | ('%7s: %r' % (key, value)) 38 | for key, value in self._symbols.items() 39 | ) 40 | lines.append('\n') 41 | s = '\n'.join(lines) 42 | return s 43 | 44 | __repr__ = __str__ 45 | 46 | def insert(self, symbol): 47 | print('Insert: %s' % symbol.name) 48 | self._symbols[symbol.name] = symbol 49 | 50 | def lookup(self, name): 51 | print('Lookup: %s' % name) 52 | symbol = self._symbols.get(name) 53 | # 'symbol' is either an instance of the Symbol class or None 54 | return symbol 55 | 56 | 57 | class SemanticAnalyzer(NodeVisitor): 58 | def __init__(self): 59 | self.current_scope = None 60 | 61 | def visit_Block(self, node): 62 | for declaration in node.declarations: 63 | self.visit(declaration) 64 | self.visit(node.compound_statement) 65 | 66 | def visit_Program(self, node): 67 | print('ENTER scope: global') 68 | global_scope = ScopedSymbolTable( 69 | scope_name='global', 70 | scope_level=1, 71 | enclosing_scope=self.current_scope, # None 72 | ) 73 | self.current_scope = global_scope 74 | 75 | # visit subtree 76 | self.visit(node.block) 77 | 78 | print(global_scope) 79 | 80 | self.current_scope = self.current_scope.enclosing_scope 81 | print('LEAVE scope: global') 82 | 83 | def visit_Compound(self, node): 84 | for child in node.children: 85 | self.visit(child) 86 | 87 | def visit_NoOp(self, node): 88 | pass 89 | 90 | def visit_BinOp(self, node): 91 | self.visit(node.left) 92 | self.visit(node.right) 93 | 94 | def visit_ProcedureDecl(self, node): 95 | proc_name = node.proc_name 96 | proc_symbol = ProcedureSymbol(proc_name) 97 | self.current_scope.insert(proc_symbol) 98 | 99 | print('ENTER scope: %s' % proc_name) 100 | # Scope for parameters and local variables 101 | procedure_scope = ScopedSymbolTable( 102 | scope_name=proc_name, 103 | scope_level=self.current_scope.scope_level + 1, 104 | enclosing_scope=self.current_scope 105 | ) 106 | self.current_scope = procedure_scope 107 | 108 | # Insert parameters into the procedure scope 109 | for param in node.params: 110 | param_type = self.current_scope.lookup(param.type_node.value) 111 | param_name = param.var_node.value 112 | var_symbol = VarSymbol(param_name, param_type) 113 | self.current_scope.insert(var_symbol) 114 | proc_symbol.params.append(var_symbol) 115 | 116 | self.visit(node.block_node) 117 | 118 | print(procedure_scope) 119 | 120 | self.current_scope = self.current_scope.enclosing_scope 121 | print('LEAVE scope: %s' % proc_name) 122 | 123 | def visit_VarDecl(self, node): 124 | type_name = node.type_node.value 125 | type_symbol = self.current_scope.lookup(type_name) 126 | 127 | # We have all the information we need to create a variable symbol. 128 | # Create the symbol and insert it into the symbol table. 129 | var_name = node.var_node.value 130 | var_symbol = VarSymbol(var_name, type_symbol) 131 | 132 | self.current_scope.insert(var_symbol) 133 | 134 | def visit_Assign(self, node): 135 | # right-hand side 136 | self.visit(node.right) 137 | # left-hand side 138 | self.visit(node.left) 139 | 140 | def visit_Var(self, node): 141 | var_name = node.value 142 | var_symbol = self.current_scope.lookup(var_name) 143 | if var_symbol is None: 144 | raise Exception( 145 | "Error: Symbol(identifier) not found '%s'" % var_name 146 | ) 147 | 148 | 149 | if __name__ == '__main__': 150 | text = """ 151 | program Main; 152 | var x, y: real; 153 | 154 | procedure Alpha(a : integer); 155 | var y : integer; 156 | begin 157 | 158 | end; 159 | 160 | begin { Main } 161 | 162 | end. { Main } 163 | """ 164 | 165 | lexer = Lexer(text) 166 | parser = Parser(lexer) 167 | tree = parser.parse() 168 | semantic_analyzer = SemanticAnalyzer() 169 | semantic_analyzer.visit(tree) 170 | -------------------------------------------------------------------------------- /part14/scope03b.py: -------------------------------------------------------------------------------- 1 | from spi import ( 2 | Lexer, 3 | Parser, 4 | NodeVisitor, 5 | BuiltinTypeSymbol, 6 | VarSymbol, 7 | ProcedureSymbol 8 | ) 9 | 10 | 11 | class ScopedSymbolTable(object): 12 | def __init__(self, scope_name, scope_level, enclosing_scope=None): 13 | self._symbols = {} 14 | self.scope_name = scope_name 15 | self.scope_level = scope_level 16 | self.enclosing_scope = enclosing_scope 17 | self._init_builtins() 18 | 19 | def _init_builtins(self): 20 | self.insert(BuiltinTypeSymbol('INTEGER')) 21 | self.insert(BuiltinTypeSymbol('REAL')) 22 | 23 | def __str__(self): 24 | h1 = 'SCOPE (SCOPED SYMBOL TABLE)' 25 | lines = ['\n', h1, '=' * len(h1)] 26 | for header_name, header_value in ( 27 | ('Scope name', self.scope_name), 28 | ('Scope level', self.scope_level), 29 | ('Enclosing scope', 30 | self.enclosing_scope.scope_name if self.enclosing_scope else None 31 | ) 32 | ): 33 | lines.append('%-15s: %s' % (header_name, header_value)) 34 | h2 = 'Scope (Scoped symbol table) contents' 35 | lines.extend([h2, '-' * len(h2)]) 36 | lines.extend( 37 | ('%7s: %r' % (key, value)) 38 | for key, value in self._symbols.items() 39 | ) 40 | lines.append('\n') 41 | s = '\n'.join(lines) 42 | return s 43 | 44 | __repr__ = __str__ 45 | 46 | def insert(self, symbol): 47 | print('Insert: %s' % symbol.name) 48 | self._symbols[symbol.name] = symbol 49 | 50 | def lookup(self, name): 51 | print('Lookup: %s' % name) 52 | symbol = self._symbols.get(name) 53 | # 'symbol' is either an instance of the Symbol class or None 54 | return symbol 55 | 56 | 57 | class SemanticAnalyzer(NodeVisitor): 58 | def __init__(self): 59 | self.current_scope = None 60 | 61 | def visit_Block(self, node): 62 | for declaration in node.declarations: 63 | self.visit(declaration) 64 | self.visit(node.compound_statement) 65 | 66 | def visit_Program(self, node): 67 | print('ENTER scope: global') 68 | global_scope = ScopedSymbolTable( 69 | scope_name='global', 70 | scope_level=1, 71 | enclosing_scope=self.current_scope, # None 72 | ) 73 | self.current_scope = global_scope 74 | 75 | # visit subtree 76 | self.visit(node.block) 77 | 78 | print(global_scope) 79 | 80 | # self.current_scope = self.current_scope.enclosing_scope 81 | print('LEAVE scope: global') 82 | 83 | def visit_Compound(self, node): 84 | for child in node.children: 85 | self.visit(child) 86 | 87 | def visit_NoOp(self, node): 88 | pass 89 | 90 | def visit_BinOp(self, node): 91 | self.visit(node.left) 92 | self.visit(node.right) 93 | 94 | def visit_ProcedureDecl(self, node): 95 | proc_name = node.proc_name 96 | proc_symbol = ProcedureSymbol(proc_name) 97 | self.current_scope.insert(proc_symbol) 98 | 99 | print('ENTER scope: %s' % proc_name) 100 | # Scope for parameters and local variables 101 | procedure_scope = ScopedSymbolTable( 102 | scope_name=proc_name, 103 | scope_level=self.current_scope.scope_level + 1, 104 | enclosing_scope=self.current_scope 105 | ) 106 | self.current_scope = procedure_scope 107 | 108 | # Insert parameters into the procedure scope 109 | for param in node.params: 110 | param_type = self.current_scope.lookup(param.type_node.value) 111 | param_name = param.var_node.value 112 | var_symbol = VarSymbol(param_name, param_type) 113 | self.current_scope.insert(var_symbol) 114 | proc_symbol.params.append(var_symbol) 115 | 116 | self.visit(node.block_node) 117 | 118 | print(procedure_scope) 119 | 120 | # self.current_scope = self.current_scope.enclosing_scope 121 | print('LEAVE scope: %s' % proc_name) 122 | 123 | def visit_VarDecl(self, node): 124 | type_name = node.type_node.value 125 | type_symbol = self.current_scope.lookup(type_name) 126 | 127 | # We have all the information we need to create a variable symbol. 128 | # Create the symbol and insert it into the symbol table. 129 | var_name = node.var_node.value 130 | var_symbol = VarSymbol(var_name, type_symbol) 131 | 132 | self.current_scope.insert(var_symbol) 133 | 134 | def visit_Assign(self, node): 135 | # right-hand side 136 | self.visit(node.right) 137 | # left-hand side 138 | self.visit(node.left) 139 | 140 | def visit_Var(self, node): 141 | var_name = node.value 142 | var_symbol = self.current_scope.lookup(var_name) 143 | if var_symbol is None: 144 | raise Exception( 145 | "Error: Symbol(identifier) not found '%s'" % var_name 146 | ) 147 | 148 | 149 | if __name__ == '__main__': 150 | text = """ 151 | program Main; 152 | var x, y : real; 153 | 154 | procedure AlphaA(a : integer); 155 | var y : integer; 156 | begin { AlphaA } 157 | 158 | end; { AlphaA } 159 | 160 | procedure AlphaB(a : integer); 161 | var b : integer; 162 | begin { AlphaB } 163 | 164 | end; { AlphaB } 165 | 166 | begin { Main } 167 | 168 | end. { Main } 169 | """ 170 | 171 | lexer = Lexer(text) 172 | parser = Parser(lexer) 173 | tree = parser.parse() 174 | semantic_analyzer = SemanticAnalyzer() 175 | semantic_analyzer.visit(tree) 176 | -------------------------------------------------------------------------------- /part14/scope03c.py: -------------------------------------------------------------------------------- 1 | from spi import ( 2 | Lexer, 3 | Parser, 4 | NodeVisitor, 5 | BuiltinTypeSymbol, 6 | VarSymbol, 7 | ProcedureSymbol 8 | ) 9 | 10 | 11 | class ScopedSymbolTable(object): 12 | def __init__(self, scope_name, scope_level, enclosing_scope=None): 13 | self._symbols = {} 14 | self.scope_name = scope_name 15 | self.scope_level = scope_level 16 | self.enclosing_scope = enclosing_scope 17 | self._init_builtins() 18 | 19 | def _init_builtins(self): 20 | self.insert(BuiltinTypeSymbol('INTEGER')) 21 | self.insert(BuiltinTypeSymbol('REAL')) 22 | 23 | def __str__(self): 24 | h1 = 'SCOPE (SCOPED SYMBOL TABLE)' 25 | lines = ['\n', h1, '=' * len(h1)] 26 | for header_name, header_value in ( 27 | ('Scope name', self.scope_name), 28 | ('Scope level', self.scope_level), 29 | ('Enclosing scope', 30 | self.enclosing_scope.scope_name if self.enclosing_scope else None 31 | ) 32 | ): 33 | lines.append('%-15s: %s' % (header_name, header_value)) 34 | h2 = 'Scope (Scoped symbol table) contents' 35 | lines.extend([h2, '-' * len(h2)]) 36 | lines.extend( 37 | ('%7s: %r' % (key, value)) 38 | for key, value in self._symbols.items() 39 | ) 40 | lines.append('\n') 41 | s = '\n'.join(lines) 42 | return s 43 | 44 | __repr__ = __str__ 45 | 46 | def insert(self, symbol): 47 | print('Insert: %s' % symbol.name) 48 | self._symbols[symbol.name] = symbol 49 | 50 | def lookup(self, name): 51 | print('Lookup: %s' % name) 52 | symbol = self._symbols.get(name) 53 | # 'symbol' is either an instance of the Symbol class or None 54 | return symbol 55 | 56 | 57 | class SemanticAnalyzer(NodeVisitor): 58 | def __init__(self): 59 | self.current_scope = None 60 | 61 | def visit_Block(self, node): 62 | for declaration in node.declarations: 63 | self.visit(declaration) 64 | self.visit(node.compound_statement) 65 | 66 | def visit_Program(self, node): 67 | print('ENTER scope: global') 68 | global_scope = ScopedSymbolTable( 69 | scope_name='global', 70 | scope_level=1, 71 | enclosing_scope=self.current_scope, # None 72 | ) 73 | self.current_scope = global_scope 74 | 75 | # visit subtree 76 | self.visit(node.block) 77 | 78 | print(global_scope) 79 | 80 | self.current_scope = self.current_scope.enclosing_scope 81 | print('LEAVE scope: global') 82 | 83 | def visit_Compound(self, node): 84 | for child in node.children: 85 | self.visit(child) 86 | 87 | def visit_NoOp(self, node): 88 | pass 89 | 90 | def visit_BinOp(self, node): 91 | self.visit(node.left) 92 | self.visit(node.right) 93 | 94 | def visit_ProcedureDecl(self, node): 95 | proc_name = node.proc_name 96 | proc_symbol = ProcedureSymbol(proc_name) 97 | self.current_scope.insert(proc_symbol) 98 | 99 | print('ENTER scope: %s' % proc_name) 100 | # Scope for parameters and local variables 101 | procedure_scope = ScopedSymbolTable( 102 | scope_name=proc_name, 103 | scope_level=self.current_scope.scope_level + 1, 104 | enclosing_scope=self.current_scope 105 | ) 106 | self.current_scope = procedure_scope 107 | 108 | # Insert parameters into the procedure scope 109 | for param in node.params: 110 | param_type = self.current_scope.lookup(param.type_node.value) 111 | param_name = param.var_node.value 112 | var_symbol = VarSymbol(param_name, param_type) 113 | self.current_scope.insert(var_symbol) 114 | proc_symbol.params.append(var_symbol) 115 | 116 | self.visit(node.block_node) 117 | 118 | print(procedure_scope) 119 | 120 | self.current_scope = self.current_scope.enclosing_scope 121 | print('LEAVE scope: %s' % proc_name) 122 | 123 | def visit_VarDecl(self, node): 124 | type_name = node.type_node.value 125 | type_symbol = self.current_scope.lookup(type_name) 126 | 127 | # We have all the information we need to create a variable symbol. 128 | # Create the symbol and insert it into the symbol table. 129 | var_name = node.var_node.value 130 | var_symbol = VarSymbol(var_name, type_symbol) 131 | 132 | self.current_scope.insert(var_symbol) 133 | 134 | def visit_Assign(self, node): 135 | # right-hand side 136 | self.visit(node.right) 137 | # left-hand side 138 | self.visit(node.left) 139 | 140 | def visit_Var(self, node): 141 | var_name = node.value 142 | var_symbol = self.current_scope.lookup(var_name) 143 | if var_symbol is None: 144 | raise Exception( 145 | "Error: Symbol(identifier) not found '%s'" % var_name 146 | ) 147 | 148 | 149 | if __name__ == '__main__': 150 | text = """ 151 | program Main; 152 | var x, y : real; 153 | 154 | procedure AlphaA(a : integer); 155 | var y : integer; 156 | begin { AlphaA } 157 | 158 | end; { AlphaA } 159 | 160 | procedure AlphaB(a : integer); 161 | var b : integer; 162 | begin { AlphaB } 163 | 164 | end; { AlphaB } 165 | 166 | begin { Main } 167 | 168 | end. { Main } 169 | """ 170 | 171 | lexer = Lexer(text) 172 | parser = Parser(lexer) 173 | tree = parser.parse() 174 | semantic_analyzer = SemanticAnalyzer() 175 | semantic_analyzer.visit(tree) 176 | -------------------------------------------------------------------------------- /part5/calc5.py: -------------------------------------------------------------------------------- 1 | # Token types 2 | # 3 | # EOF (end-of-file) token is used to indicate that 4 | # there is no more input left for lexical analysis 5 | INTEGER, PLUS, MINUS, MUL, DIV, EOF = ( 6 | 'INTEGER', 'PLUS', 'MINUS', 'MUL', 'DIV', 'EOF' 7 | ) 8 | 9 | 10 | class Token(object): 11 | def __init__(self, type, value): 12 | # token type: INTEGER, PLUS, MINUS, MUL, DIV, or EOF 13 | self.type = type 14 | # token value: non-negative integer value, '+', '-', '*', '/', or None 15 | self.value = value 16 | 17 | def __str__(self): 18 | """String representation of the class instance. 19 | 20 | Examples: 21 | Token(INTEGER, 3) 22 | Token(PLUS, '+') 23 | Token(MUL, '*') 24 | """ 25 | return 'Token({type}, {value})'.format( 26 | type=self.type, 27 | value=repr(self.value) 28 | ) 29 | 30 | def __repr__(self): 31 | return self.__str__() 32 | 33 | 34 | class Lexer(object): 35 | def __init__(self, text): 36 | # client string input, e.g. "3 * 5", "12 / 3 * 4", etc 37 | self.text = text 38 | # self.pos is an index into self.text 39 | self.pos = 0 40 | self.current_char = self.text[self.pos] 41 | 42 | def error(self): 43 | raise Exception('Invalid character') 44 | 45 | def advance(self): 46 | """Advance the `pos` pointer and set the `current_char` variable.""" 47 | self.pos += 1 48 | if self.pos > len(self.text) - 1: 49 | self.current_char = None # Indicates end of input 50 | else: 51 | self.current_char = self.text[self.pos] 52 | 53 | def skip_whitespace(self): 54 | while self.current_char is not None and self.current_char.isspace(): 55 | self.advance() 56 | 57 | def integer(self): 58 | """Return a (multidigit) integer consumed from the input.""" 59 | result = '' 60 | while self.current_char is not None and self.current_char.isdigit(): 61 | result += self.current_char 62 | self.advance() 63 | return int(result) 64 | 65 | def get_next_token(self): 66 | """Lexical analyzer (also known as scanner or tokenizer) 67 | 68 | This method is responsible for breaking a sentence 69 | apart into tokens. One token at a time. 70 | """ 71 | while self.current_char is not None: 72 | 73 | if self.current_char.isspace(): 74 | self.skip_whitespace() 75 | continue 76 | 77 | if self.current_char.isdigit(): 78 | return Token(INTEGER, self.integer()) 79 | 80 | if self.current_char == '+': 81 | self.advance() 82 | return Token(PLUS, '+') 83 | 84 | if self.current_char == '-': 85 | self.advance() 86 | return Token(MINUS, '-') 87 | 88 | if self.current_char == '*': 89 | self.advance() 90 | return Token(MUL, '*') 91 | 92 | if self.current_char == '/': 93 | self.advance() 94 | return Token(DIV, '/') 95 | 96 | self.error() 97 | 98 | return Token(EOF, None) 99 | 100 | 101 | class Interpreter(object): 102 | def __init__(self, lexer): 103 | self.lexer = lexer 104 | # set current token to the first token taken from the input 105 | self.current_token = self.lexer.get_next_token() 106 | 107 | def error(self): 108 | raise Exception('Invalid syntax') 109 | 110 | def eat(self, token_type): 111 | # compare the current token type with the passed token 112 | # type and if they match then "eat" the current token 113 | # and assign the next token to the self.current_token, 114 | # otherwise raise an exception. 115 | if self.current_token.type == token_type: 116 | self.current_token = self.lexer.get_next_token() 117 | else: 118 | self.error() 119 | 120 | def factor(self): 121 | """factor : INTEGER""" 122 | token = self.current_token 123 | self.eat(INTEGER) 124 | return token.value 125 | 126 | def term(self): 127 | """term : factor ((MUL | DIV) factor)*""" 128 | result = self.factor() 129 | 130 | while self.current_token.type in (MUL, DIV): 131 | token = self.current_token 132 | if token.type == MUL: 133 | self.eat(MUL) 134 | result = result * self.factor() 135 | elif token.type == DIV: 136 | self.eat(DIV) 137 | result = result // self.factor() 138 | 139 | return result 140 | 141 | def expr(self): 142 | """Arithmetic expression parser / interpreter. 143 | 144 | calc> 14 + 2 * 3 - 6 / 2 145 | 17 146 | 147 | expr : term ((PLUS | MINUS) term)* 148 | term : factor ((MUL | DIV) factor)* 149 | factor : INTEGER 150 | """ 151 | result = self.term() 152 | 153 | while self.current_token.type in (PLUS, MINUS): 154 | token = self.current_token 155 | if token.type == PLUS: 156 | self.eat(PLUS) 157 | result = result + self.term() 158 | elif token.type == MINUS: 159 | self.eat(MINUS) 160 | result = result - self.term() 161 | 162 | return result 163 | 164 | 165 | def main(): 166 | while True: 167 | try: 168 | text = input('calc> ') 169 | except EOFError: 170 | break 171 | if not text: 172 | continue 173 | lexer = Lexer(text) 174 | interpreter = Interpreter(lexer) 175 | result = interpreter.expr() 176 | print(result) 177 | 178 | 179 | if __name__ == '__main__': 180 | main() 181 | -------------------------------------------------------------------------------- /part14/scope04a.py: -------------------------------------------------------------------------------- 1 | from spi import ( 2 | Lexer, 3 | Parser, 4 | NodeVisitor, 5 | BuiltinTypeSymbol, 6 | VarSymbol, 7 | ProcedureSymbol 8 | ) 9 | 10 | 11 | class ScopedSymbolTable(object): 12 | def __init__(self, scope_name, scope_level, enclosing_scope=None): 13 | self._symbols = {} 14 | self.scope_name = scope_name 15 | self.scope_level = scope_level 16 | self.enclosing_scope = enclosing_scope 17 | 18 | def _init_builtins(self): 19 | self.insert(BuiltinTypeSymbol('INTEGER')) 20 | self.insert(BuiltinTypeSymbol('REAL')) 21 | 22 | def __str__(self): 23 | h1 = 'SCOPE (SCOPED SYMBOL TABLE)' 24 | lines = ['\n', h1, '=' * len(h1)] 25 | for header_name, header_value in ( 26 | ('Scope name', self.scope_name), 27 | ('Scope level', self.scope_level), 28 | ('Enclosing scope', 29 | self.enclosing_scope.scope_name if self.enclosing_scope else None 30 | ) 31 | ): 32 | lines.append('%-15s: %s' % (header_name, header_value)) 33 | h2 = 'Scope (Scoped symbol table) contents' 34 | lines.extend([h2, '-' * len(h2)]) 35 | lines.extend( 36 | ('%7s: %r' % (key, value)) 37 | for key, value in self._symbols.items() 38 | ) 39 | lines.append('\n') 40 | s = '\n'.join(lines) 41 | return s 42 | 43 | __repr__ = __str__ 44 | 45 | def insert(self, symbol): 46 | print('Insert: %s' % symbol.name) 47 | self._symbols[symbol.name] = symbol 48 | 49 | def lookup(self, name): 50 | print('Lookup: %s. (Scope name: %s)' % (name, self.scope_name)) 51 | # 'symbol' is either an instance of the Symbol class or None 52 | symbol = self._symbols.get(name) 53 | 54 | if symbol is not None: 55 | return symbol 56 | 57 | # recursively go up the chain and lookup the name 58 | if self.enclosing_scope is not None: 59 | return self.enclosing_scope.lookup(name) 60 | 61 | 62 | class SemanticAnalyzer(NodeVisitor): 63 | def __init__(self): 64 | self.current_scope = None 65 | 66 | def visit_Block(self, node): 67 | for declaration in node.declarations: 68 | self.visit(declaration) 69 | self.visit(node.compound_statement) 70 | 71 | def visit_Program(self, node): 72 | print('ENTER scope: global') 73 | global_scope = ScopedSymbolTable( 74 | scope_name='global', 75 | scope_level=1, 76 | enclosing_scope=self.current_scope, # None 77 | ) 78 | global_scope._init_builtins() 79 | self.current_scope = global_scope 80 | 81 | # visit subtree 82 | self.visit(node.block) 83 | 84 | print(global_scope) 85 | 86 | self.current_scope = self.current_scope.enclosing_scope 87 | print('LEAVE scope: global') 88 | 89 | def visit_Compound(self, node): 90 | for child in node.children: 91 | self.visit(child) 92 | 93 | def visit_NoOp(self, node): 94 | pass 95 | 96 | def visit_BinOp(self, node): 97 | self.visit(node.left) 98 | self.visit(node.right) 99 | 100 | def visit_ProcedureDecl(self, node): 101 | proc_name = node.proc_name 102 | proc_symbol = ProcedureSymbol(proc_name) 103 | self.current_scope.insert(proc_symbol) 104 | 105 | print('ENTER scope: %s' % proc_name) 106 | # Scope for parameters and local variables 107 | procedure_scope = ScopedSymbolTable( 108 | scope_name=proc_name, 109 | scope_level=self.current_scope.scope_level + 1, 110 | enclosing_scope=self.current_scope 111 | ) 112 | self.current_scope = procedure_scope 113 | 114 | # Insert parameters into the procedure scope 115 | for param in node.params: 116 | param_type = self.current_scope.lookup(param.type_node.value) 117 | param_name = param.var_node.value 118 | var_symbol = VarSymbol(param_name, param_type) 119 | self.current_scope.insert(var_symbol) 120 | proc_symbol.params.append(var_symbol) 121 | 122 | self.visit(node.block_node) 123 | 124 | print(procedure_scope) 125 | 126 | self.current_scope = self.current_scope.enclosing_scope 127 | print('LEAVE scope: %s' % proc_name) 128 | 129 | def visit_VarDecl(self, node): 130 | type_name = node.type_node.value 131 | type_symbol = self.current_scope.lookup(type_name) 132 | 133 | # We have all the information we need to create a variable symbol. 134 | # Create the symbol and insert it into the symbol table. 135 | var_name = node.var_node.value 136 | var_symbol = VarSymbol(var_name, type_symbol) 137 | 138 | self.current_scope.insert(var_symbol) 139 | 140 | def visit_Assign(self, node): 141 | # right-hand side 142 | self.visit(node.right) 143 | # left-hand side 144 | self.visit(node.left) 145 | 146 | def visit_Var(self, node): 147 | var_name = node.value 148 | var_symbol = self.current_scope.lookup(var_name) 149 | if var_symbol is None: 150 | raise Exception( 151 | "Error: Symbol(identifier) not found '%s'" % var_name 152 | ) 153 | 154 | 155 | if __name__ == '__main__': 156 | text = """ 157 | program Main; 158 | var x, y: real; 159 | 160 | procedure Alpha(a : integer); 161 | var y : integer; 162 | begin 163 | x := a + x + y; 164 | end; 165 | 166 | begin { Main } 167 | 168 | end. { Main } 169 | """ 170 | 171 | lexer = Lexer(text) 172 | parser = Parser(lexer) 173 | tree = parser.parse() 174 | semantic_analyzer = SemanticAnalyzer() 175 | try: 176 | semantic_analyzer.visit(tree) 177 | except Exception as e: 178 | print(e) 179 | -------------------------------------------------------------------------------- /part14/scope04b.py: -------------------------------------------------------------------------------- 1 | from spi import ( 2 | Lexer, 3 | Parser, 4 | NodeVisitor, 5 | BuiltinTypeSymbol, 6 | VarSymbol, 7 | ProcedureSymbol 8 | ) 9 | 10 | 11 | class ScopedSymbolTable(object): 12 | def __init__(self, scope_name, scope_level, enclosing_scope=None): 13 | self._symbols = {} 14 | self.scope_name = scope_name 15 | self.scope_level = scope_level 16 | self.enclosing_scope = enclosing_scope 17 | 18 | def _init_builtins(self): 19 | self.insert(BuiltinTypeSymbol('INTEGER')) 20 | self.insert(BuiltinTypeSymbol('REAL')) 21 | 22 | def __str__(self): 23 | h1 = 'SCOPE (SCOPED SYMBOL TABLE)' 24 | lines = ['\n', h1, '=' * len(h1)] 25 | for header_name, header_value in ( 26 | ('Scope name', self.scope_name), 27 | ('Scope level', self.scope_level), 28 | ('Enclosing scope', 29 | self.enclosing_scope.scope_name if self.enclosing_scope else None 30 | ) 31 | ): 32 | lines.append('%-15s: %s' % (header_name, header_value)) 33 | h2 = 'Scope (Scoped symbol table) contents' 34 | lines.extend([h2, '-' * len(h2)]) 35 | lines.extend( 36 | ('%7s: %r' % (key, value)) 37 | for key, value in self._symbols.items() 38 | ) 39 | lines.append('\n') 40 | s = '\n'.join(lines) 41 | return s 42 | 43 | __repr__ = __str__ 44 | 45 | def insert(self, symbol): 46 | print('Insert: %s' % symbol.name) 47 | self._symbols[symbol.name] = symbol 48 | 49 | def lookup(self, name): 50 | print('Lookup: %s. (Scope name: %s)' % (name, self.scope_name)) 51 | # 'symbol' is either an instance of the Symbol class or None 52 | symbol = self._symbols.get(name) 53 | 54 | if symbol is not None: 55 | return symbol 56 | 57 | # recursively go up the chain and lookup the name 58 | if self.enclosing_scope is not None: 59 | return self.enclosing_scope.lookup(name) 60 | 61 | 62 | class SemanticAnalyzer(NodeVisitor): 63 | def __init__(self): 64 | self.current_scope = None 65 | 66 | def visit_Block(self, node): 67 | for declaration in node.declarations: 68 | self.visit(declaration) 69 | self.visit(node.compound_statement) 70 | 71 | def visit_Program(self, node): 72 | print('ENTER scope: global') 73 | global_scope = ScopedSymbolTable( 74 | scope_name='global', 75 | scope_level=1, 76 | enclosing_scope=self.current_scope, # None 77 | ) 78 | global_scope._init_builtins() 79 | self.current_scope = global_scope 80 | 81 | # visit subtree 82 | self.visit(node.block) 83 | 84 | print(global_scope) 85 | 86 | self.current_scope = self.current_scope.enclosing_scope 87 | print('LEAVE scope: global') 88 | 89 | def visit_Compound(self, node): 90 | for child in node.children: 91 | self.visit(child) 92 | 93 | def visit_NoOp(self, node): 94 | pass 95 | 96 | def visit_BinOp(self, node): 97 | self.visit(node.left) 98 | self.visit(node.right) 99 | 100 | def visit_ProcedureDecl(self, node): 101 | proc_name = node.proc_name 102 | proc_symbol = ProcedureSymbol(proc_name) 103 | self.current_scope.insert(proc_symbol) 104 | 105 | print('ENTER scope: %s' % proc_name) 106 | # Scope for parameters and local variables 107 | procedure_scope = ScopedSymbolTable( 108 | scope_name=proc_name, 109 | scope_level=self.current_scope.scope_level + 1, 110 | enclosing_scope=self.current_scope 111 | ) 112 | self.current_scope = procedure_scope 113 | 114 | # Insert parameters into the procedure scope 115 | for param in node.params: 116 | param_type = self.current_scope.lookup(param.type_node.value) 117 | param_name = param.var_node.value 118 | var_symbol = VarSymbol(param_name, param_type) 119 | self.current_scope.insert(var_symbol) 120 | proc_symbol.params.append(var_symbol) 121 | 122 | self.visit(node.block_node) 123 | 124 | print(procedure_scope) 125 | 126 | self.current_scope = self.current_scope.enclosing_scope 127 | print('LEAVE scope: %s' % proc_name) 128 | 129 | def visit_VarDecl(self, node): 130 | type_name = node.type_node.value 131 | type_symbol = self.current_scope.lookup(type_name) 132 | 133 | # We have all the information we need to create a variable symbol. 134 | # Create the symbol and insert it into the symbol table. 135 | var_name = node.var_node.value 136 | var_symbol = VarSymbol(var_name, type_symbol) 137 | 138 | self.current_scope.insert(var_symbol) 139 | 140 | def visit_Assign(self, node): 141 | # right-hand side 142 | self.visit(node.right) 143 | # left-hand side 144 | self.visit(node.left) 145 | 146 | def visit_Var(self, node): 147 | var_name = node.value 148 | var_symbol = self.current_scope.lookup(var_name) 149 | if var_symbol is None: 150 | raise Exception( 151 | "Error: Symbol(identifier) not found '%s'" % var_name 152 | ) 153 | 154 | 155 | if __name__ == '__main__': 156 | text = """ 157 | program Main; 158 | var x, y: real; 159 | 160 | procedure Alpha(a : integer); 161 | var y : integer; 162 | begin 163 | x := b + x + y; { ERROR here! } 164 | end; 165 | 166 | begin { Main } 167 | 168 | end. { Main } 169 | """ 170 | 171 | lexer = Lexer(text) 172 | parser = Parser(lexer) 173 | tree = parser.parse() 174 | semantic_analyzer = SemanticAnalyzer() 175 | try: 176 | semantic_analyzer.visit(tree) 177 | except Exception as e: 178 | print(e) 179 | -------------------------------------------------------------------------------- /part10/python/genastdot.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # AST visualizer - generates a DOT file for Graphviz. # 3 | # # 4 | # To generate an image from the DOT file run $ dot -Tpng -o ast.png ast.dot # 5 | # # 6 | ############################################################################### 7 | import argparse 8 | import textwrap 9 | 10 | from spi import Lexer, Parser, NodeVisitor 11 | 12 | 13 | class ASTVisualizer(NodeVisitor): 14 | def __init__(self, parser): 15 | self.parser = parser 16 | self.ncount = 1 17 | self.dot_header = [textwrap.dedent("""\ 18 | digraph astgraph { 19 | node [shape=circle, fontsize=12, fontname="Courier", height=.1]; 20 | ranksep=.3; 21 | edge [arrowsize=.5] 22 | 23 | """)] 24 | self.dot_body = [] 25 | self.dot_footer = ['}'] 26 | 27 | def visit_Program(self, node): 28 | s = ' node{} [label="Program"]\n'.format(self.ncount) 29 | self.dot_body.append(s) 30 | node._num = self.ncount 31 | self.ncount += 1 32 | 33 | self.visit(node.block) 34 | 35 | s = ' node{} -> node{}\n'.format(node._num, node.block._num) 36 | self.dot_body.append(s) 37 | 38 | def visit_Block(self, node): 39 | s = ' node{} [label="Block"]\n'.format(self.ncount) 40 | self.dot_body.append(s) 41 | node._num = self.ncount 42 | self.ncount += 1 43 | 44 | for declaration in node.declarations: 45 | self.visit(declaration) 46 | self.visit(node.compound_statement) 47 | 48 | for decl_node in node.declarations: 49 | s = ' node{} -> node{}\n'.format(node._num, decl_node._num) 50 | self.dot_body.append(s) 51 | 52 | s = ' node{} -> node{}\n'.format( 53 | node._num, 54 | node.compound_statement._num 55 | ) 56 | self.dot_body.append(s) 57 | 58 | def visit_VarDecl(self, node): 59 | s = ' node{} [label="VarDecl"]\n'.format(self.ncount) 60 | self.dot_body.append(s) 61 | node._num = self.ncount 62 | self.ncount += 1 63 | 64 | self.visit(node.var_node) 65 | s = ' node{} -> node{}\n'.format(node._num, node.var_node._num) 66 | self.dot_body.append(s) 67 | 68 | self.visit(node.type_node) 69 | s = ' node{} -> node{}\n'.format(node._num, node.type_node._num) 70 | self.dot_body.append(s) 71 | 72 | def visit_Type(self, node): 73 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.token.value) 74 | self.dot_body.append(s) 75 | node._num = self.ncount 76 | self.ncount += 1 77 | 78 | def visit_Num(self, node): 79 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.token.value) 80 | self.dot_body.append(s) 81 | node._num = self.ncount 82 | self.ncount += 1 83 | 84 | def visit_BinOp(self, node): 85 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.op.value) 86 | self.dot_body.append(s) 87 | node._num = self.ncount 88 | self.ncount += 1 89 | 90 | self.visit(node.left) 91 | self.visit(node.right) 92 | 93 | for child_node in (node.left, node.right): 94 | s = ' node{} -> node{}\n'.format(node._num, child_node._num) 95 | self.dot_body.append(s) 96 | 97 | def visit_UnaryOp(self, node): 98 | s = ' node{} [label="unary {}"]\n'.format(self.ncount, node.op.value) 99 | self.dot_body.append(s) 100 | node._num = self.ncount 101 | self.ncount += 1 102 | 103 | self.visit(node.expr) 104 | s = ' node{} -> node{}\n'.format(node._num, node.expr._num) 105 | self.dot_body.append(s) 106 | 107 | def visit_Compound(self, node): 108 | s = ' node{} [label="Compound"]\n'.format(self.ncount) 109 | self.dot_body.append(s) 110 | node._num = self.ncount 111 | self.ncount += 1 112 | 113 | for child in node.children: 114 | self.visit(child) 115 | s = ' node{} -> node{}\n'.format(node._num, child._num) 116 | self.dot_body.append(s) 117 | 118 | def visit_Assign(self, node): 119 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.op.value) 120 | self.dot_body.append(s) 121 | node._num = self.ncount 122 | self.ncount += 1 123 | 124 | self.visit(node.left) 125 | self.visit(node.right) 126 | 127 | for child_node in (node.left, node.right): 128 | s = ' node{} -> node{}\n'.format(node._num, child_node._num) 129 | self.dot_body.append(s) 130 | 131 | def visit_Var(self, node): 132 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.value) 133 | self.dot_body.append(s) 134 | node._num = self.ncount 135 | self.ncount += 1 136 | 137 | def visit_NoOp(self, node): 138 | s = ' node{} [label="NoOp"]\n'.format(self.ncount) 139 | self.dot_body.append(s) 140 | node._num = self.ncount 141 | self.ncount += 1 142 | 143 | def gendot(self): 144 | tree = self.parser.parse() 145 | self.visit(tree) 146 | return ''.join(self.dot_header + self.dot_body + self.dot_footer) 147 | 148 | 149 | def main(): 150 | argparser = argparse.ArgumentParser( 151 | description='Generate an AST DOT file.' 152 | ) 153 | argparser.add_argument( 154 | 'fname', 155 | help='Pascal source file' 156 | ) 157 | args = argparser.parse_args() 158 | fname = args.fname 159 | text = open(fname, 'r').read() 160 | 161 | lexer = Lexer(text) 162 | parser = Parser(lexer) 163 | viz = ASTVisualizer(parser) 164 | content = viz.gendot() 165 | print(content) 166 | 167 | 168 | if __name__ == '__main__': 169 | main() 170 | -------------------------------------------------------------------------------- /part14/scope05.py: -------------------------------------------------------------------------------- 1 | from spi import ( 2 | Lexer, 3 | Parser, 4 | NodeVisitor, 5 | BuiltinTypeSymbol, 6 | VarSymbol, 7 | ProcedureSymbol 8 | ) 9 | 10 | 11 | class ScopedSymbolTable(object): 12 | def __init__(self, scope_name, scope_level, enclosing_scope=None): 13 | self._symbols = {} 14 | self.scope_name = scope_name 15 | self.scope_level = scope_level 16 | self.enclosing_scope = enclosing_scope 17 | 18 | def _init_builtins(self): 19 | self.insert(BuiltinTypeSymbol('INTEGER')) 20 | self.insert(BuiltinTypeSymbol('REAL')) 21 | 22 | def __str__(self): 23 | h1 = 'SCOPE (SCOPED SYMBOL TABLE)' 24 | lines = ['\n', h1, '=' * len(h1)] 25 | for header_name, header_value in ( 26 | ('Scope name', self.scope_name), 27 | ('Scope level', self.scope_level), 28 | ('Enclosing scope', 29 | self.enclosing_scope.scope_name if self.enclosing_scope else None 30 | ) 31 | ): 32 | lines.append('%-15s: %s' % (header_name, header_value)) 33 | h2 = 'Scope (Scoped symbol table) contents' 34 | lines.extend([h2, '-' * len(h2)]) 35 | lines.extend( 36 | ('%7s: %r' % (key, value)) 37 | for key, value in self._symbols.items() 38 | ) 39 | lines.append('\n') 40 | s = '\n'.join(lines) 41 | return s 42 | 43 | __repr__ = __str__ 44 | 45 | def insert(self, symbol): 46 | print('Insert: %s' % symbol.name) 47 | self._symbols[symbol.name] = symbol 48 | 49 | def lookup(self, name, current_scope_only=False): 50 | print('Lookup: %s. (Scope name: %s)' % (name, self.scope_name)) 51 | # 'symbol' is either an instance of the Symbol class or None 52 | symbol = self._symbols.get(name) 53 | 54 | if symbol is not None: 55 | return symbol 56 | 57 | if current_scope_only: 58 | return None 59 | 60 | # recursively go up the chain and lookup the name 61 | if self.enclosing_scope is not None: 62 | return self.enclosing_scope.lookup(name) 63 | 64 | 65 | class SemanticAnalyzer(NodeVisitor): 66 | def __init__(self): 67 | self.current_scope = None 68 | 69 | def visit_Block(self, node): 70 | for declaration in node.declarations: 71 | self.visit(declaration) 72 | self.visit(node.compound_statement) 73 | 74 | def visit_Program(self, node): 75 | print('ENTER scope: global') 76 | global_scope = ScopedSymbolTable( 77 | scope_name='global', 78 | scope_level=1, 79 | enclosing_scope=self.current_scope, # None 80 | ) 81 | global_scope._init_builtins() 82 | self.current_scope = global_scope 83 | 84 | # visit subtree 85 | self.visit(node.block) 86 | 87 | print(global_scope) 88 | 89 | self.current_scope = self.current_scope.enclosing_scope 90 | print('LEAVE scope: global') 91 | 92 | def visit_Compound(self, node): 93 | for child in node.children: 94 | self.visit(child) 95 | 96 | def visit_NoOp(self, node): 97 | pass 98 | 99 | def visit_BinOp(self, node): 100 | self.visit(node.left) 101 | self.visit(node.right) 102 | 103 | def visit_ProcedureDecl(self, node): 104 | proc_name = node.proc_name 105 | proc_symbol = ProcedureSymbol(proc_name) 106 | self.current_scope.insert(proc_symbol) 107 | 108 | print('ENTER scope: %s' % proc_name) 109 | # Scope for parameters and local variables 110 | procedure_scope = ScopedSymbolTable( 111 | scope_name=proc_name, 112 | scope_level=self.current_scope.scope_level + 1, 113 | enclosing_scope=self.current_scope 114 | ) 115 | self.current_scope = procedure_scope 116 | 117 | # Insert parameters into the procedure scope 118 | for param in node.params: 119 | param_type = self.current_scope.lookup(param.type_node.value) 120 | param_name = param.var_node.value 121 | var_symbol = VarSymbol(param_name, param_type) 122 | self.current_scope.insert(var_symbol) 123 | proc_symbol.params.append(var_symbol) 124 | 125 | self.visit(node.block_node) 126 | 127 | print(procedure_scope) 128 | 129 | self.current_scope = self.current_scope.enclosing_scope 130 | print('LEAVE scope: %s' % proc_name) 131 | 132 | def visit_VarDecl(self, node): 133 | type_name = node.type_node.value 134 | type_symbol = self.current_scope.lookup(type_name) 135 | 136 | # We have all the information we need to create a variable symbol. 137 | # Create the symbol and insert it into the symbol table. 138 | var_name = node.var_node.value 139 | var_symbol = VarSymbol(var_name, type_symbol) 140 | 141 | # Signal an error if the table alrady has a symbol 142 | # with the same name 143 | if self.current_scope.lookup(var_name, current_scope_only=True): 144 | raise Exception( 145 | "Error: Duplicate identifier '%s' found" % var_name 146 | ) 147 | 148 | self.current_scope.insert(var_symbol) 149 | 150 | def visit_Assign(self, node): 151 | # right-hand side 152 | self.visit(node.right) 153 | # left-hand side 154 | self.visit(node.left) 155 | 156 | def visit_Var(self, node): 157 | var_name = node.value 158 | var_symbol = self.current_scope.lookup(var_name) 159 | if var_symbol is None: 160 | raise Exception( 161 | "Error: Symbol(identifier) not found '%s'" % var_name 162 | ) 163 | 164 | 165 | if __name__ == '__main__': 166 | import sys 167 | text = open(sys.argv[1], 'r').read() 168 | 169 | lexer = Lexer(text) 170 | parser = Parser(lexer) 171 | tree = parser.parse() 172 | semantic_analyzer = SemanticAnalyzer() 173 | try: 174 | semantic_analyzer.visit(tree) 175 | except Exception as e: 176 | print(e) 177 | -------------------------------------------------------------------------------- /part7/python/genptdot.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # # 3 | # Parse Tree visualizer # 4 | # # 5 | # To generate an image from the DOT file run: # 6 | # $ dot -Tpng -o parsetree.png parsetree.dot # 7 | # # 8 | ############################################################################### 9 | import argparse 10 | import textwrap 11 | 12 | from spi import PLUS, MINUS, MUL, DIV, INTEGER, LPAREN, RPAREN, Lexer 13 | 14 | 15 | class Node(object): 16 | def __init__(self, name): 17 | self.name = name 18 | self.children = [] 19 | 20 | def add(self, node): 21 | self.children.append(node) 22 | 23 | 24 | class RuleNode(Node): 25 | pass 26 | 27 | 28 | class TokenNode(Node): 29 | pass 30 | 31 | 32 | class Parser(object): 33 | """Parses the input and builds a parse tree.""" 34 | 35 | def __init__(self, lexer): 36 | self.lexer = lexer 37 | # set current token to the first token taken from the input 38 | self.current_token = self.lexer.get_next_token() 39 | 40 | # Parse tree root 41 | self.root = None 42 | self.current_node = None 43 | 44 | def error(self): 45 | raise Exception('Invalid syntax') 46 | 47 | def eat(self, token_type): 48 | # compare the current token type with the passed token 49 | # type and if they match then "eat" the current token 50 | # and assign the next token to the self.current_token, 51 | # otherwise raise an exception. 52 | if self.current_token.type == token_type: 53 | self.current_node.add(TokenNode(self.current_token.value)) 54 | self.current_token = self.lexer.get_next_token() 55 | else: 56 | self.error() 57 | 58 | def factor(self): 59 | """factor : INTEGER | LPAREN expr RPAREN""" 60 | node = RuleNode('factor') 61 | self.current_node.add(node) 62 | _save = self.current_node 63 | self.current_node = node 64 | 65 | token = self.current_token 66 | if token.type == INTEGER: 67 | self.eat(INTEGER) 68 | elif token.type == LPAREN: 69 | self.eat(LPAREN) 70 | self.expr() 71 | self.eat(RPAREN) 72 | 73 | self.current_node = _save 74 | 75 | def term(self): 76 | """term : factor ((MUL | DIV) factor)*""" 77 | node = RuleNode('term') 78 | self.current_node.add(node) 79 | _save = self.current_node 80 | self.current_node = node 81 | 82 | self.factor() 83 | 84 | while self.current_token.type in (MUL, DIV): 85 | token = self.current_token 86 | if token.type == MUL: 87 | self.eat(MUL) 88 | elif token.type == DIV: 89 | self.eat(DIV) 90 | 91 | self.factor() 92 | 93 | self.current_node = _save 94 | 95 | def expr(self): 96 | """ 97 | expr : term ((PLUS | MINUS) term)* 98 | term : factor ((MUL | DIV) factor)* 99 | factor : INTEGER | LPAREN expr RPAREN 100 | """ 101 | node = RuleNode('expr') 102 | if self.root is None: 103 | self.root = node 104 | else: 105 | self.current_node.add(node) 106 | 107 | _save = self.current_node 108 | self.current_node = node 109 | 110 | self.term() 111 | 112 | while self.current_token.type in (PLUS, MINUS): 113 | token = self.current_token 114 | if token.type == PLUS: 115 | self.eat(PLUS) 116 | elif token.type == MINUS: 117 | self.eat(MINUS) 118 | 119 | self.term() 120 | 121 | self.current_node = _save 122 | 123 | def parse(self): 124 | self.expr() 125 | return self.root 126 | 127 | 128 | class ParseTreeVisualizer(object): 129 | def __init__(self, parser): 130 | self.parser = parser 131 | self.ncount = 1 132 | self.dot_header = [textwrap.dedent("""\ 133 | digraph astgraph { 134 | node [shape=none, fontsize=12, fontname="Courier", height=.1]; 135 | ranksep=.3; 136 | edge [arrowsize=.5] 137 | 138 | """)] 139 | self.dot_body = [] 140 | self.dot_footer = ['}'] 141 | 142 | def bfs(self, node): 143 | ncount = 1 144 | queue = [] 145 | queue.append(node) 146 | s = ' node{} [label="{}"]\n'.format(ncount, node.name) 147 | self.dot_body.append(s) 148 | node._num = ncount 149 | ncount += 1 150 | 151 | while queue: 152 | node = queue.pop(0) 153 | for child_node in node.children: 154 | s = ' node{} [label="{}"]\n'.format(ncount, child_node.name) 155 | self.dot_body.append(s) 156 | child_node._num = ncount 157 | ncount += 1 158 | s = ' node{} -> node{}\n'.format(node._num, child_node._num) 159 | self.dot_body.append(s) 160 | queue.append(child_node) 161 | 162 | def gendot(self): 163 | tree = self.parser.parse() 164 | self.bfs(tree) 165 | return ''.join(self.dot_header + self.dot_body + self.dot_footer) 166 | 167 | 168 | def main(): 169 | argparser = argparse.ArgumentParser( 170 | description='Generate a Parse Tree DOT file.' 171 | ) 172 | argparser.add_argument( 173 | 'text', 174 | help='Arithmetic expression (in quotes): "1 + 2 * 3"' 175 | ) 176 | args = argparser.parse_args() 177 | text = args.text 178 | 179 | lexer = Lexer(text) 180 | parser = Parser(lexer) 181 | 182 | viz = ParseTreeVisualizer(parser) 183 | content = viz.gendot() 184 | print(content) 185 | 186 | 187 | if __name__ == '__main__': 188 | main() 189 | -------------------------------------------------------------------------------- /part6/calc6.py: -------------------------------------------------------------------------------- 1 | # Token types 2 | # 3 | # EOF (end-of-file) token is used to indicate that 4 | # there is no more input left for lexical analysis 5 | INTEGER, PLUS, MINUS, MUL, DIV, LPAREN, RPAREN, EOF = ( 6 | 'INTEGER', 'PLUS', 'MINUS', 'MUL', 'DIV', '(', ')', 'EOF' 7 | ) 8 | 9 | 10 | class Token(object): 11 | def __init__(self, type, value): 12 | self.type = type 13 | self.value = value 14 | 15 | def __str__(self): 16 | """String representation of the class instance. 17 | 18 | Examples: 19 | Token(INTEGER, 3) 20 | Token(PLUS, '+') 21 | Token(MUL, '*') 22 | """ 23 | return 'Token({type}, {value})'.format( 24 | type=self.type, 25 | value=repr(self.value) 26 | ) 27 | 28 | def __repr__(self): 29 | return self.__str__() 30 | 31 | 32 | class Lexer(object): 33 | def __init__(self, text): 34 | # client string input, e.g. "4 + 2 * 3 - 6 / 2" 35 | self.text = text 36 | # self.pos is an index into self.text 37 | self.pos = 0 38 | self.current_char = self.text[self.pos] 39 | 40 | def error(self): 41 | raise Exception('Invalid character') 42 | 43 | def advance(self): 44 | """Advance the `pos` pointer and set the `current_char` variable.""" 45 | self.pos += 1 46 | if self.pos > len(self.text) - 1: 47 | self.current_char = None # Indicates end of input 48 | else: 49 | self.current_char = self.text[self.pos] 50 | 51 | def skip_whitespace(self): 52 | while self.current_char is not None and self.current_char.isspace(): 53 | self.advance() 54 | 55 | def integer(self): 56 | """Return a (multidigit) integer consumed from the input.""" 57 | result = '' 58 | while self.current_char is not None and self.current_char.isdigit(): 59 | result += self.current_char 60 | self.advance() 61 | return int(result) 62 | 63 | def get_next_token(self): 64 | """Lexical analyzer (also known as scanner or tokenizer) 65 | 66 | This method is responsible for breaking a sentence 67 | apart into tokens. One token at a time. 68 | """ 69 | while self.current_char is not None: 70 | 71 | if self.current_char.isspace(): 72 | self.skip_whitespace() 73 | continue 74 | 75 | if self.current_char.isdigit(): 76 | return Token(INTEGER, self.integer()) 77 | 78 | if self.current_char == '+': 79 | self.advance() 80 | return Token(PLUS, '+') 81 | 82 | if self.current_char == '-': 83 | self.advance() 84 | return Token(MINUS, '-') 85 | 86 | if self.current_char == '*': 87 | self.advance() 88 | return Token(MUL, '*') 89 | 90 | if self.current_char == '/': 91 | self.advance() 92 | return Token(DIV, '/') 93 | 94 | if self.current_char == '(': 95 | self.advance() 96 | return Token(LPAREN, '(') 97 | 98 | if self.current_char == ')': 99 | self.advance() 100 | return Token(RPAREN, ')') 101 | 102 | self.error() 103 | 104 | return Token(EOF, None) 105 | 106 | 107 | class Interpreter(object): 108 | def __init__(self, lexer): 109 | self.lexer = lexer 110 | # set current token to the first token taken from the input 111 | self.current_token = self.lexer.get_next_token() 112 | 113 | def error(self): 114 | raise Exception('Invalid syntax') 115 | 116 | def eat(self, token_type): 117 | # compare the current token type with the passed token 118 | # type and if they match then "eat" the current token 119 | # and assign the next token to the self.current_token, 120 | # otherwise raise an exception. 121 | if self.current_token.type == token_type: 122 | self.current_token = self.lexer.get_next_token() 123 | else: 124 | self.error() 125 | 126 | def factor(self): 127 | """factor : INTEGER | LPAREN expr RPAREN""" 128 | token = self.current_token 129 | if token.type == INTEGER: 130 | self.eat(INTEGER) 131 | return token.value 132 | elif token.type == LPAREN: 133 | self.eat(LPAREN) 134 | result = self.expr() 135 | self.eat(RPAREN) 136 | return result 137 | 138 | def term(self): 139 | """term : factor ((MUL | DIV) factor)*""" 140 | result = self.factor() 141 | 142 | while self.current_token.type in (MUL, DIV): 143 | token = self.current_token 144 | if token.type == MUL: 145 | self.eat(MUL) 146 | result = result * self.factor() 147 | elif token.type == DIV: 148 | self.eat(DIV) 149 | result = result // self.factor() 150 | 151 | return result 152 | 153 | def expr(self): 154 | """Arithmetic expression parser / interpreter. 155 | 156 | calc> 7 + 3 * (10 / (12 / (3 + 1) - 1)) 157 | 22 158 | 159 | expr : term ((PLUS | MINUS) term)* 160 | term : factor ((MUL | DIV) factor)* 161 | factor : INTEGER | LPAREN expr RPAREN 162 | """ 163 | result = self.term() 164 | 165 | while self.current_token.type in (PLUS, MINUS): 166 | token = self.current_token 167 | if token.type == PLUS: 168 | self.eat(PLUS) 169 | result = result + self.term() 170 | elif token.type == MINUS: 171 | self.eat(MINUS) 172 | result = result - self.term() 173 | 174 | return result 175 | 176 | 177 | def main(): 178 | while True: 179 | try: 180 | text = input('calc> ') 181 | except EOFError: 182 | break 183 | if not text: 184 | continue 185 | lexer = Lexer(text) 186 | interpreter = Interpreter(lexer) 187 | result = interpreter.expr() 188 | print(result) 189 | 190 | 191 | if __name__ == '__main__': 192 | main() 193 | -------------------------------------------------------------------------------- /part12/python/genastdot.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # AST visualizer - generates a DOT file for Graphviz. # 3 | # # 4 | # To generate an image from the DOT file run $ dot -Tpng -o ast.png ast.dot # 5 | # # 6 | ############################################################################### 7 | import argparse 8 | import textwrap 9 | 10 | from spi import Lexer, Parser, NodeVisitor 11 | 12 | 13 | class ASTVisualizer(NodeVisitor): 14 | def __init__(self, parser): 15 | self.parser = parser 16 | self.ncount = 1 17 | self.dot_header = [textwrap.dedent("""\ 18 | digraph astgraph { 19 | node [shape=circle, fontsize=12, fontname="Courier", height=.1]; 20 | ranksep=.3; 21 | edge [arrowsize=.5] 22 | 23 | """)] 24 | self.dot_body = [] 25 | self.dot_footer = ['}'] 26 | 27 | def visit_Program(self, node): 28 | s = ' node{} [label="Program"]\n'.format(self.ncount) 29 | self.dot_body.append(s) 30 | node._num = self.ncount 31 | self.ncount += 1 32 | 33 | self.visit(node.block) 34 | 35 | s = ' node{} -> node{}\n'.format(node._num, node.block._num) 36 | self.dot_body.append(s) 37 | 38 | def visit_Block(self, node): 39 | s = ' node{} [label="Block"]\n'.format(self.ncount) 40 | self.dot_body.append(s) 41 | node._num = self.ncount 42 | self.ncount += 1 43 | 44 | for declaration in node.declarations: 45 | self.visit(declaration) 46 | self.visit(node.compound_statement) 47 | 48 | for decl_node in node.declarations: 49 | s = ' node{} -> node{}\n'.format(node._num, decl_node._num) 50 | self.dot_body.append(s) 51 | 52 | s = ' node{} -> node{}\n'.format( 53 | node._num, 54 | node.compound_statement._num 55 | ) 56 | self.dot_body.append(s) 57 | 58 | def visit_VarDecl(self, node): 59 | s = ' node{} [label="VarDecl"]\n'.format(self.ncount) 60 | self.dot_body.append(s) 61 | node._num = self.ncount 62 | self.ncount += 1 63 | 64 | self.visit(node.var_node) 65 | s = ' node{} -> node{}\n'.format(node._num, node.var_node._num) 66 | self.dot_body.append(s) 67 | 68 | self.visit(node.type_node) 69 | s = ' node{} -> node{}\n'.format(node._num, node.type_node._num) 70 | self.dot_body.append(s) 71 | 72 | def visit_ProcedureDecl(self, node): 73 | s = ' node{} [label="ProcDecl:{}"]\n'.format( 74 | self.ncount, 75 | node.proc_name 76 | ) 77 | self.dot_body.append(s) 78 | node._num = self.ncount 79 | self.ncount += 1 80 | 81 | self.visit(node.block_node) 82 | s = ' node{} -> node{}\n'.format(node._num, node.block_node._num) 83 | self.dot_body.append(s) 84 | 85 | def visit_Type(self, node): 86 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.token.value) 87 | self.dot_body.append(s) 88 | node._num = self.ncount 89 | self.ncount += 1 90 | 91 | def visit_Num(self, node): 92 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.token.value) 93 | self.dot_body.append(s) 94 | node._num = self.ncount 95 | self.ncount += 1 96 | 97 | def visit_BinOp(self, node): 98 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.op.value) 99 | self.dot_body.append(s) 100 | node._num = self.ncount 101 | self.ncount += 1 102 | 103 | self.visit(node.left) 104 | self.visit(node.right) 105 | 106 | for child_node in (node.left, node.right): 107 | s = ' node{} -> node{}\n'.format(node._num, child_node._num) 108 | self.dot_body.append(s) 109 | 110 | def visit_UnaryOp(self, node): 111 | s = ' node{} [label="unary {}"]\n'.format(self.ncount, node.op.value) 112 | self.dot_body.append(s) 113 | node._num = self.ncount 114 | self.ncount += 1 115 | 116 | self.visit(node.expr) 117 | s = ' node{} -> node{}\n'.format(node._num, node.expr._num) 118 | self.dot_body.append(s) 119 | 120 | def visit_Compound(self, node): 121 | s = ' node{} [label="Compound"]\n'.format(self.ncount) 122 | self.dot_body.append(s) 123 | node._num = self.ncount 124 | self.ncount += 1 125 | 126 | for child in node.children: 127 | self.visit(child) 128 | s = ' node{} -> node{}\n'.format(node._num, child._num) 129 | self.dot_body.append(s) 130 | 131 | def visit_Assign(self, node): 132 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.op.value) 133 | self.dot_body.append(s) 134 | node._num = self.ncount 135 | self.ncount += 1 136 | 137 | self.visit(node.left) 138 | self.visit(node.right) 139 | 140 | for child_node in (node.left, node.right): 141 | s = ' node{} -> node{}\n'.format(node._num, child_node._num) 142 | self.dot_body.append(s) 143 | 144 | def visit_Var(self, node): 145 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.value) 146 | self.dot_body.append(s) 147 | node._num = self.ncount 148 | self.ncount += 1 149 | 150 | def visit_NoOp(self, node): 151 | s = ' node{} [label="NoOp"]\n'.format(self.ncount) 152 | self.dot_body.append(s) 153 | node._num = self.ncount 154 | self.ncount += 1 155 | 156 | def gendot(self): 157 | tree = self.parser.parse() 158 | self.visit(tree) 159 | return ''.join(self.dot_header + self.dot_body + self.dot_footer) 160 | 161 | 162 | def main(): 163 | argparser = argparse.ArgumentParser( 164 | description='Generate an AST DOT file.' 165 | ) 166 | argparser.add_argument( 167 | 'fname', 168 | help='Pascal source file' 169 | ) 170 | args = argparser.parse_args() 171 | fname = args.fname 172 | text = open(fname, 'r').read() 173 | 174 | lexer = Lexer(text) 175 | parser = Parser(lexer) 176 | viz = ASTVisualizer(parser) 177 | content = viz.gendot() 178 | print(content) 179 | 180 | 181 | if __name__ == '__main__': 182 | main() 183 | -------------------------------------------------------------------------------- /part13/genastdot.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # AST visualizer - generates a DOT file for Graphviz. # 3 | # # 4 | # To generate an image from the DOT file run $ dot -Tpng -o ast.png ast.dot # 5 | # # 6 | ############################################################################### 7 | import argparse 8 | import textwrap 9 | 10 | from spi import Lexer, Parser, NodeVisitor 11 | 12 | 13 | class ASTVisualizer(NodeVisitor): 14 | def __init__(self, parser): 15 | self.parser = parser 16 | self.ncount = 1 17 | self.dot_header = [textwrap.dedent("""\ 18 | digraph astgraph { 19 | node [shape=circle, fontsize=12, fontname="Courier", height=.1]; 20 | ranksep=.3; 21 | edge [arrowsize=.5] 22 | 23 | """)] 24 | self.dot_body = [] 25 | self.dot_footer = ['}'] 26 | 27 | def visit_Program(self, node): 28 | s = ' node{} [label="Program"]\n'.format(self.ncount) 29 | self.dot_body.append(s) 30 | node._num = self.ncount 31 | self.ncount += 1 32 | 33 | self.visit(node.block) 34 | 35 | s = ' node{} -> node{}\n'.format(node._num, node.block._num) 36 | self.dot_body.append(s) 37 | 38 | def visit_Block(self, node): 39 | s = ' node{} [label="Block"]\n'.format(self.ncount) 40 | self.dot_body.append(s) 41 | node._num = self.ncount 42 | self.ncount += 1 43 | 44 | for declaration in node.declarations: 45 | self.visit(declaration) 46 | self.visit(node.compound_statement) 47 | 48 | for decl_node in node.declarations: 49 | s = ' node{} -> node{}\n'.format(node._num, decl_node._num) 50 | self.dot_body.append(s) 51 | 52 | s = ' node{} -> node{}\n'.format( 53 | node._num, 54 | node.compound_statement._num 55 | ) 56 | self.dot_body.append(s) 57 | 58 | def visit_VarDecl(self, node): 59 | s = ' node{} [label="VarDecl"]\n'.format(self.ncount) 60 | self.dot_body.append(s) 61 | node._num = self.ncount 62 | self.ncount += 1 63 | 64 | self.visit(node.var_node) 65 | s = ' node{} -> node{}\n'.format(node._num, node.var_node._num) 66 | self.dot_body.append(s) 67 | 68 | self.visit(node.type_node) 69 | s = ' node{} -> node{}\n'.format(node._num, node.type_node._num) 70 | self.dot_body.append(s) 71 | 72 | def visit_ProcedureDecl(self, node): 73 | s = ' node{} [label="ProcDecl:{}"]\n'.format( 74 | self.ncount, 75 | node.proc_name 76 | ) 77 | self.dot_body.append(s) 78 | node._num = self.ncount 79 | self.ncount += 1 80 | 81 | for param_node in node.params: 82 | self.visit(param_node) 83 | s = ' node{} -> node{}\n'.format(node._num, param_node._num) 84 | self.dot_body.append(s) 85 | 86 | self.visit(node.block_node) 87 | s = ' node{} -> node{}\n'.format(node._num, node.block_node._num) 88 | self.dot_body.append(s) 89 | 90 | def visit_Param(self, node): 91 | s = ' node{} [label="Param"]\n'.format(self.ncount) 92 | self.dot_body.append(s) 93 | node._num = self.ncount 94 | self.ncount += 1 95 | 96 | self.visit(node.var_node) 97 | s = ' node{} -> node{}\n'.format(node._num, node.var_node._num) 98 | self.dot_body.append(s) 99 | 100 | self.visit(node.type_node) 101 | s = ' node{} -> node{}\n'.format(node._num, node.type_node._num) 102 | self.dot_body.append(s) 103 | 104 | 105 | def visit_Type(self, node): 106 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.token.value) 107 | self.dot_body.append(s) 108 | node._num = self.ncount 109 | self.ncount += 1 110 | 111 | def visit_Num(self, node): 112 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.token.value) 113 | self.dot_body.append(s) 114 | node._num = self.ncount 115 | self.ncount += 1 116 | 117 | def visit_BinOp(self, node): 118 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.op.value) 119 | self.dot_body.append(s) 120 | node._num = self.ncount 121 | self.ncount += 1 122 | 123 | self.visit(node.left) 124 | self.visit(node.right) 125 | 126 | for child_node in (node.left, node.right): 127 | s = ' node{} -> node{}\n'.format(node._num, child_node._num) 128 | self.dot_body.append(s) 129 | 130 | def visit_UnaryOp(self, node): 131 | s = ' node{} [label="unary {}"]\n'.format(self.ncount, node.op.value) 132 | self.dot_body.append(s) 133 | node._num = self.ncount 134 | self.ncount += 1 135 | 136 | self.visit(node.expr) 137 | s = ' node{} -> node{}\n'.format(node._num, node.expr._num) 138 | self.dot_body.append(s) 139 | 140 | def visit_Compound(self, node): 141 | s = ' node{} [label="Compound"]\n'.format(self.ncount) 142 | self.dot_body.append(s) 143 | node._num = self.ncount 144 | self.ncount += 1 145 | 146 | for child in node.children: 147 | self.visit(child) 148 | s = ' node{} -> node{}\n'.format(node._num, child._num) 149 | self.dot_body.append(s) 150 | 151 | def visit_Assign(self, node): 152 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.op.value) 153 | self.dot_body.append(s) 154 | node._num = self.ncount 155 | self.ncount += 1 156 | 157 | self.visit(node.left) 158 | self.visit(node.right) 159 | 160 | for child_node in (node.left, node.right): 161 | s = ' node{} -> node{}\n'.format(node._num, child_node._num) 162 | self.dot_body.append(s) 163 | 164 | def visit_Var(self, node): 165 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.value) 166 | self.dot_body.append(s) 167 | node._num = self.ncount 168 | self.ncount += 1 169 | 170 | def visit_NoOp(self, node): 171 | s = ' node{} [label="NoOp"]\n'.format(self.ncount) 172 | self.dot_body.append(s) 173 | node._num = self.ncount 174 | self.ncount += 1 175 | 176 | def gendot(self): 177 | tree = self.parser.parse() 178 | self.visit(tree) 179 | return ''.join(self.dot_header + self.dot_body + self.dot_footer) 180 | 181 | 182 | def main(): 183 | argparser = argparse.ArgumentParser( 184 | description='Generate an AST DOT file.' 185 | ) 186 | argparser.add_argument( 187 | 'fname', 188 | help='Pascal source file' 189 | ) 190 | args = argparser.parse_args() 191 | fname = args.fname 192 | text = open(fname, 'r').read() 193 | 194 | lexer = Lexer(text) 195 | parser = Parser(lexer) 196 | viz = ASTVisualizer(parser) 197 | content = viz.gendot() 198 | print(content) 199 | 200 | 201 | if __name__ == '__main__': 202 | main() 203 | -------------------------------------------------------------------------------- /part14/genastdot.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # AST visualizer - generates a DOT file for Graphviz. # 3 | # # 4 | # To generate an image from the DOT file run $ dot -Tpng -o ast.png ast.dot # 5 | # # 6 | ############################################################################### 7 | import argparse 8 | import textwrap 9 | 10 | from spi import Lexer, Parser, NodeVisitor 11 | 12 | 13 | class ASTVisualizer(NodeVisitor): 14 | def __init__(self, parser): 15 | self.parser = parser 16 | self.ncount = 1 17 | self.dot_header = [textwrap.dedent("""\ 18 | digraph astgraph { 19 | node [shape=circle, fontsize=12, fontname="Courier", height=.1]; 20 | ranksep=.3; 21 | edge [arrowsize=.5] 22 | 23 | """)] 24 | self.dot_body = [] 25 | self.dot_footer = ['}'] 26 | 27 | def visit_Program(self, node): 28 | s = ' node{} [label="Program"]\n'.format(self.ncount) 29 | self.dot_body.append(s) 30 | node._num = self.ncount 31 | self.ncount += 1 32 | 33 | self.visit(node.block) 34 | 35 | s = ' node{} -> node{}\n'.format(node._num, node.block._num) 36 | self.dot_body.append(s) 37 | 38 | def visit_Block(self, node): 39 | s = ' node{} [label="Block"]\n'.format(self.ncount) 40 | self.dot_body.append(s) 41 | node._num = self.ncount 42 | self.ncount += 1 43 | 44 | for declaration in node.declarations: 45 | self.visit(declaration) 46 | self.visit(node.compound_statement) 47 | 48 | for decl_node in node.declarations: 49 | s = ' node{} -> node{}\n'.format(node._num, decl_node._num) 50 | self.dot_body.append(s) 51 | 52 | s = ' node{} -> node{}\n'.format( 53 | node._num, 54 | node.compound_statement._num 55 | ) 56 | self.dot_body.append(s) 57 | 58 | def visit_VarDecl(self, node): 59 | s = ' node{} [label="VarDecl"]\n'.format(self.ncount) 60 | self.dot_body.append(s) 61 | node._num = self.ncount 62 | self.ncount += 1 63 | 64 | self.visit(node.var_node) 65 | s = ' node{} -> node{}\n'.format(node._num, node.var_node._num) 66 | self.dot_body.append(s) 67 | 68 | self.visit(node.type_node) 69 | s = ' node{} -> node{}\n'.format(node._num, node.type_node._num) 70 | self.dot_body.append(s) 71 | 72 | def visit_ProcedureDecl(self, node): 73 | s = ' node{} [label="ProcDecl:{}"]\n'.format( 74 | self.ncount, 75 | node.proc_name 76 | ) 77 | self.dot_body.append(s) 78 | node._num = self.ncount 79 | self.ncount += 1 80 | 81 | for param_node in node.params: 82 | self.visit(param_node) 83 | s = ' node{} -> node{}\n'.format(node._num, param_node._num) 84 | self.dot_body.append(s) 85 | 86 | self.visit(node.block_node) 87 | s = ' node{} -> node{}\n'.format(node._num, node.block_node._num) 88 | self.dot_body.append(s) 89 | 90 | def visit_Param(self, node): 91 | s = ' node{} [label="Param"]\n'.format(self.ncount) 92 | self.dot_body.append(s) 93 | node._num = self.ncount 94 | self.ncount += 1 95 | 96 | self.visit(node.var_node) 97 | s = ' node{} -> node{}\n'.format(node._num, node.var_node._num) 98 | self.dot_body.append(s) 99 | 100 | self.visit(node.type_node) 101 | s = ' node{} -> node{}\n'.format(node._num, node.type_node._num) 102 | self.dot_body.append(s) 103 | 104 | 105 | def visit_Type(self, node): 106 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.token.value) 107 | self.dot_body.append(s) 108 | node._num = self.ncount 109 | self.ncount += 1 110 | 111 | def visit_Num(self, node): 112 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.token.value) 113 | self.dot_body.append(s) 114 | node._num = self.ncount 115 | self.ncount += 1 116 | 117 | def visit_BinOp(self, node): 118 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.op.value) 119 | self.dot_body.append(s) 120 | node._num = self.ncount 121 | self.ncount += 1 122 | 123 | self.visit(node.left) 124 | self.visit(node.right) 125 | 126 | for child_node in (node.left, node.right): 127 | s = ' node{} -> node{}\n'.format(node._num, child_node._num) 128 | self.dot_body.append(s) 129 | 130 | def visit_UnaryOp(self, node): 131 | s = ' node{} [label="unary {}"]\n'.format(self.ncount, node.op.value) 132 | self.dot_body.append(s) 133 | node._num = self.ncount 134 | self.ncount += 1 135 | 136 | self.visit(node.expr) 137 | s = ' node{} -> node{}\n'.format(node._num, node.expr._num) 138 | self.dot_body.append(s) 139 | 140 | def visit_Compound(self, node): 141 | s = ' node{} [label="Compound"]\n'.format(self.ncount) 142 | self.dot_body.append(s) 143 | node._num = self.ncount 144 | self.ncount += 1 145 | 146 | for child in node.children: 147 | self.visit(child) 148 | s = ' node{} -> node{}\n'.format(node._num, child._num) 149 | self.dot_body.append(s) 150 | 151 | def visit_Assign(self, node): 152 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.op.value) 153 | self.dot_body.append(s) 154 | node._num = self.ncount 155 | self.ncount += 1 156 | 157 | self.visit(node.left) 158 | self.visit(node.right) 159 | 160 | for child_node in (node.left, node.right): 161 | s = ' node{} -> node{}\n'.format(node._num, child_node._num) 162 | self.dot_body.append(s) 163 | 164 | def visit_Var(self, node): 165 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.value) 166 | self.dot_body.append(s) 167 | node._num = self.ncount 168 | self.ncount += 1 169 | 170 | def visit_NoOp(self, node): 171 | s = ' node{} [label="NoOp"]\n'.format(self.ncount) 172 | self.dot_body.append(s) 173 | node._num = self.ncount 174 | self.ncount += 1 175 | 176 | def gendot(self): 177 | tree = self.parser.parse() 178 | self.visit(tree) 179 | return ''.join(self.dot_header + self.dot_body + self.dot_footer) 180 | 181 | 182 | def main(): 183 | argparser = argparse.ArgumentParser( 184 | description='Generate an AST DOT file.' 185 | ) 186 | argparser.add_argument( 187 | 'fname', 188 | help='Pascal source file' 189 | ) 190 | args = argparser.parse_args() 191 | fname = args.fname 192 | text = open(fname, 'r').read() 193 | 194 | lexer = Lexer(text) 195 | parser = Parser(lexer) 196 | viz = ASTVisualizer(parser) 197 | content = viz.gendot() 198 | print(content) 199 | 200 | 201 | if __name__ == '__main__': 202 | main() 203 | -------------------------------------------------------------------------------- /part16/genastdot.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # AST visualizer - generates a DOT file for Graphviz. # 3 | # # 4 | # To generate an image from the DOT file run $ dot -Tpng -o ast.png ast.dot # 5 | # # 6 | ############################################################################### 7 | import argparse 8 | import textwrap 9 | 10 | from spi import Lexer, Parser, NodeVisitor 11 | 12 | 13 | class ASTVisualizer(NodeVisitor): 14 | def __init__(self, parser): 15 | self.parser = parser 16 | self.ncount = 1 17 | self.dot_header = [textwrap.dedent("""\ 18 | digraph astgraph { 19 | node [shape=circle, fontsize=12, fontname="Courier", height=.1]; 20 | ranksep=.3; 21 | edge [arrowsize=.5] 22 | 23 | """)] 24 | self.dot_body = [] 25 | self.dot_footer = ['}'] 26 | 27 | def visit_Program(self, node): 28 | s = ' node{} [label="Program"]\n'.format(self.ncount) 29 | self.dot_body.append(s) 30 | node._num = self.ncount 31 | self.ncount += 1 32 | 33 | self.visit(node.block) 34 | 35 | s = ' node{} -> node{}\n'.format(node._num, node.block._num) 36 | self.dot_body.append(s) 37 | 38 | def visit_Block(self, node): 39 | s = ' node{} [label="Block"]\n'.format(self.ncount) 40 | self.dot_body.append(s) 41 | node._num = self.ncount 42 | self.ncount += 1 43 | 44 | for declaration in node.declarations: 45 | self.visit(declaration) 46 | self.visit(node.compound_statement) 47 | 48 | for decl_node in node.declarations: 49 | s = ' node{} -> node{}\n'.format(node._num, decl_node._num) 50 | self.dot_body.append(s) 51 | 52 | s = ' node{} -> node{}\n'.format( 53 | node._num, 54 | node.compound_statement._num 55 | ) 56 | self.dot_body.append(s) 57 | 58 | def visit_VarDecl(self, node): 59 | s = ' node{} [label="VarDecl"]\n'.format(self.ncount) 60 | self.dot_body.append(s) 61 | node._num = self.ncount 62 | self.ncount += 1 63 | 64 | self.visit(node.var_node) 65 | s = ' node{} -> node{}\n'.format(node._num, node.var_node._num) 66 | self.dot_body.append(s) 67 | 68 | self.visit(node.type_node) 69 | s = ' node{} -> node{}\n'.format(node._num, node.type_node._num) 70 | self.dot_body.append(s) 71 | 72 | def visit_ProcedureDecl(self, node): 73 | s = ' node{} [label="ProcDecl:{}"]\n'.format( 74 | self.ncount, 75 | node.proc_name 76 | ) 77 | self.dot_body.append(s) 78 | node._num = self.ncount 79 | self.ncount += 1 80 | 81 | for param_node in node.params: 82 | self.visit(param_node) 83 | s = ' node{} -> node{}\n'.format(node._num, param_node._num) 84 | self.dot_body.append(s) 85 | 86 | self.visit(node.block_node) 87 | s = ' node{} -> node{}\n'.format(node._num, node.block_node._num) 88 | self.dot_body.append(s) 89 | 90 | def visit_Param(self, node): 91 | s = ' node{} [label="Param"]\n'.format(self.ncount) 92 | self.dot_body.append(s) 93 | node._num = self.ncount 94 | self.ncount += 1 95 | 96 | self.visit(node.var_node) 97 | s = ' node{} -> node{}\n'.format(node._num, node.var_node._num) 98 | self.dot_body.append(s) 99 | 100 | self.visit(node.type_node) 101 | s = ' node{} -> node{}\n'.format(node._num, node.type_node._num) 102 | self.dot_body.append(s) 103 | 104 | def visit_Type(self, node): 105 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.token.value) 106 | self.dot_body.append(s) 107 | node._num = self.ncount 108 | self.ncount += 1 109 | 110 | def visit_Num(self, node): 111 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.token.value) 112 | self.dot_body.append(s) 113 | node._num = self.ncount 114 | self.ncount += 1 115 | 116 | def visit_BinOp(self, node): 117 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.op.value) 118 | self.dot_body.append(s) 119 | node._num = self.ncount 120 | self.ncount += 1 121 | 122 | self.visit(node.left) 123 | self.visit(node.right) 124 | 125 | for child_node in (node.left, node.right): 126 | s = ' node{} -> node{}\n'.format(node._num, child_node._num) 127 | self.dot_body.append(s) 128 | 129 | def visit_UnaryOp(self, node): 130 | s = ' node{} [label="unary {}"]\n'.format(self.ncount, node.op.value) 131 | self.dot_body.append(s) 132 | node._num = self.ncount 133 | self.ncount += 1 134 | 135 | self.visit(node.expr) 136 | s = ' node{} -> node{}\n'.format(node._num, node.expr._num) 137 | self.dot_body.append(s) 138 | 139 | def visit_Compound(self, node): 140 | s = ' node{} [label="Compound"]\n'.format(self.ncount) 141 | self.dot_body.append(s) 142 | node._num = self.ncount 143 | self.ncount += 1 144 | 145 | for child in node.children: 146 | self.visit(child) 147 | s = ' node{} -> node{}\n'.format(node._num, child._num) 148 | self.dot_body.append(s) 149 | 150 | def visit_Assign(self, node): 151 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.op.value) 152 | self.dot_body.append(s) 153 | node._num = self.ncount 154 | self.ncount += 1 155 | 156 | self.visit(node.left) 157 | self.visit(node.right) 158 | 159 | for child_node in (node.left, node.right): 160 | s = ' node{} -> node{}\n'.format(node._num, child_node._num) 161 | self.dot_body.append(s) 162 | 163 | def visit_Var(self, node): 164 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.value) 165 | self.dot_body.append(s) 166 | node._num = self.ncount 167 | self.ncount += 1 168 | 169 | def visit_NoOp(self, node): 170 | s = ' node{} [label="NoOp"]\n'.format(self.ncount) 171 | self.dot_body.append(s) 172 | node._num = self.ncount 173 | self.ncount += 1 174 | 175 | def visit_ProcedureCall(self, node): 176 | s = ' node{} [label="ProcCall:{}"]\n'.format( 177 | self.ncount, 178 | node.proc_name 179 | ) 180 | self.dot_body.append(s) 181 | node._num = self.ncount 182 | self.ncount += 1 183 | 184 | for param_node in node.actual_params: 185 | self.visit(param_node) 186 | s = ' node{} -> node{}\n'.format(node._num, param_node._num) 187 | self.dot_body.append(s) 188 | 189 | def gendot(self): 190 | tree = self.parser.parse() 191 | self.visit(tree) 192 | return ''.join(self.dot_header + self.dot_body + self.dot_footer) 193 | 194 | 195 | def main(): 196 | argparser = argparse.ArgumentParser( 197 | description='Generate an AST DOT file.' 198 | ) 199 | argparser.add_argument( 200 | 'fname', 201 | help='Pascal source file' 202 | ) 203 | args = argparser.parse_args() 204 | fname = args.fname 205 | text = open(fname, 'r').read() 206 | 207 | lexer = Lexer(text) 208 | parser = Parser(lexer) 209 | viz = ASTVisualizer(parser) 210 | content = viz.gendot() 211 | print(content) 212 | 213 | 214 | if __name__ == '__main__': 215 | main() 216 | -------------------------------------------------------------------------------- /part17/genastdot.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # AST visualizer - generates a DOT file for Graphviz. # 3 | # # 4 | # To generate an image from the DOT file run $ dot -Tpng -o ast.png ast.dot # 5 | # # 6 | ############################################################################### 7 | import argparse 8 | import textwrap 9 | 10 | from spi import Lexer, Parser, NodeVisitor 11 | 12 | 13 | class ASTVisualizer(NodeVisitor): 14 | def __init__(self, parser): 15 | self.parser = parser 16 | self.ncount = 1 17 | self.dot_header = [textwrap.dedent("""\ 18 | digraph astgraph { 19 | node [shape=circle, fontsize=12, fontname="Courier", height=.1]; 20 | ranksep=.3; 21 | edge [arrowsize=.5] 22 | 23 | """)] 24 | self.dot_body = [] 25 | self.dot_footer = ['}'] 26 | 27 | def visit_Program(self, node): 28 | s = ' node{} [label="Program"]\n'.format(self.ncount) 29 | self.dot_body.append(s) 30 | node._num = self.ncount 31 | self.ncount += 1 32 | 33 | self.visit(node.block) 34 | 35 | s = ' node{} -> node{}\n'.format(node._num, node.block._num) 36 | self.dot_body.append(s) 37 | 38 | def visit_Block(self, node): 39 | s = ' node{} [label="Block"]\n'.format(self.ncount) 40 | self.dot_body.append(s) 41 | node._num = self.ncount 42 | self.ncount += 1 43 | 44 | for declaration in node.declarations: 45 | self.visit(declaration) 46 | self.visit(node.compound_statement) 47 | 48 | for decl_node in node.declarations: 49 | s = ' node{} -> node{}\n'.format(node._num, decl_node._num) 50 | self.dot_body.append(s) 51 | 52 | s = ' node{} -> node{}\n'.format( 53 | node._num, 54 | node.compound_statement._num 55 | ) 56 | self.dot_body.append(s) 57 | 58 | def visit_VarDecl(self, node): 59 | s = ' node{} [label="VarDecl"]\n'.format(self.ncount) 60 | self.dot_body.append(s) 61 | node._num = self.ncount 62 | self.ncount += 1 63 | 64 | self.visit(node.var_node) 65 | s = ' node{} -> node{}\n'.format(node._num, node.var_node._num) 66 | self.dot_body.append(s) 67 | 68 | self.visit(node.type_node) 69 | s = ' node{} -> node{}\n'.format(node._num, node.type_node._num) 70 | self.dot_body.append(s) 71 | 72 | def visit_ProcedureDecl(self, node): 73 | s = ' node{} [label="ProcDecl:{}"]\n'.format( 74 | self.ncount, 75 | node.proc_name 76 | ) 77 | self.dot_body.append(s) 78 | node._num = self.ncount 79 | self.ncount += 1 80 | 81 | for param_node in node.params: 82 | self.visit(param_node) 83 | s = ' node{} -> node{}\n'.format(node._num, param_node._num) 84 | self.dot_body.append(s) 85 | 86 | self.visit(node.block_node) 87 | s = ' node{} -> node{}\n'.format(node._num, node.block_node._num) 88 | self.dot_body.append(s) 89 | 90 | def visit_Param(self, node): 91 | s = ' node{} [label="Param"]\n'.format(self.ncount) 92 | self.dot_body.append(s) 93 | node._num = self.ncount 94 | self.ncount += 1 95 | 96 | self.visit(node.var_node) 97 | s = ' node{} -> node{}\n'.format(node._num, node.var_node._num) 98 | self.dot_body.append(s) 99 | 100 | self.visit(node.type_node) 101 | s = ' node{} -> node{}\n'.format(node._num, node.type_node._num) 102 | self.dot_body.append(s) 103 | 104 | def visit_Type(self, node): 105 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.token.value) 106 | self.dot_body.append(s) 107 | node._num = self.ncount 108 | self.ncount += 1 109 | 110 | def visit_Num(self, node): 111 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.token.value) 112 | self.dot_body.append(s) 113 | node._num = self.ncount 114 | self.ncount += 1 115 | 116 | def visit_BinOp(self, node): 117 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.op.value) 118 | self.dot_body.append(s) 119 | node._num = self.ncount 120 | self.ncount += 1 121 | 122 | self.visit(node.left) 123 | self.visit(node.right) 124 | 125 | for child_node in (node.left, node.right): 126 | s = ' node{} -> node{}\n'.format(node._num, child_node._num) 127 | self.dot_body.append(s) 128 | 129 | def visit_UnaryOp(self, node): 130 | s = ' node{} [label="unary {}"]\n'.format(self.ncount, node.op.value) 131 | self.dot_body.append(s) 132 | node._num = self.ncount 133 | self.ncount += 1 134 | 135 | self.visit(node.expr) 136 | s = ' node{} -> node{}\n'.format(node._num, node.expr._num) 137 | self.dot_body.append(s) 138 | 139 | def visit_Compound(self, node): 140 | s = ' node{} [label="Compound"]\n'.format(self.ncount) 141 | self.dot_body.append(s) 142 | node._num = self.ncount 143 | self.ncount += 1 144 | 145 | for child in node.children: 146 | self.visit(child) 147 | s = ' node{} -> node{}\n'.format(node._num, child._num) 148 | self.dot_body.append(s) 149 | 150 | def visit_Assign(self, node): 151 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.op.value) 152 | self.dot_body.append(s) 153 | node._num = self.ncount 154 | self.ncount += 1 155 | 156 | self.visit(node.left) 157 | self.visit(node.right) 158 | 159 | for child_node in (node.left, node.right): 160 | s = ' node{} -> node{}\n'.format(node._num, child_node._num) 161 | self.dot_body.append(s) 162 | 163 | def visit_Var(self, node): 164 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.value) 165 | self.dot_body.append(s) 166 | node._num = self.ncount 167 | self.ncount += 1 168 | 169 | def visit_NoOp(self, node): 170 | s = ' node{} [label="NoOp"]\n'.format(self.ncount) 171 | self.dot_body.append(s) 172 | node._num = self.ncount 173 | self.ncount += 1 174 | 175 | def visit_ProcedureCall(self, node): 176 | s = ' node{} [label="ProcCall:{}"]\n'.format( 177 | self.ncount, 178 | node.proc_name 179 | ) 180 | self.dot_body.append(s) 181 | node._num = self.ncount 182 | self.ncount += 1 183 | 184 | for param_node in node.actual_params: 185 | self.visit(param_node) 186 | s = ' node{} -> node{}\n'.format(node._num, param_node._num) 187 | self.dot_body.append(s) 188 | 189 | def gendot(self): 190 | tree = self.parser.parse() 191 | self.visit(tree) 192 | return ''.join(self.dot_header + self.dot_body + self.dot_footer) 193 | 194 | 195 | def main(): 196 | argparser = argparse.ArgumentParser( 197 | description='Generate an AST DOT file.' 198 | ) 199 | argparser.add_argument( 200 | 'fname', 201 | help='Pascal source file' 202 | ) 203 | args = argparser.parse_args() 204 | fname = args.fname 205 | text = open(fname, 'r').read() 206 | 207 | lexer = Lexer(text) 208 | parser = Parser(lexer) 209 | viz = ASTVisualizer(parser) 210 | content = viz.gendot() 211 | print(content) 212 | 213 | 214 | if __name__ == '__main__': 215 | main() 216 | -------------------------------------------------------------------------------- /part18/genastdot.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # AST visualizer - generates a DOT file for Graphviz. # 3 | # # 4 | # To generate an image from the DOT file run $ dot -Tpng -o ast.png ast.dot # 5 | # # 6 | ############################################################################### 7 | import argparse 8 | import textwrap 9 | 10 | from spi import Lexer, Parser, NodeVisitor 11 | 12 | 13 | class ASTVisualizer(NodeVisitor): 14 | def __init__(self, parser): 15 | self.parser = parser 16 | self.ncount = 1 17 | self.dot_header = [textwrap.dedent("""\ 18 | digraph astgraph { 19 | node [shape=circle, fontsize=12, fontname="Courier", height=.1]; 20 | ranksep=.3; 21 | edge [arrowsize=.5] 22 | 23 | """)] 24 | self.dot_body = [] 25 | self.dot_footer = ['}'] 26 | 27 | def visit_Program(self, node): 28 | s = ' node{} [label="Program"]\n'.format(self.ncount) 29 | self.dot_body.append(s) 30 | node._num = self.ncount 31 | self.ncount += 1 32 | 33 | self.visit(node.block) 34 | 35 | s = ' node{} -> node{}\n'.format(node._num, node.block._num) 36 | self.dot_body.append(s) 37 | 38 | def visit_Block(self, node): 39 | s = ' node{} [label="Block"]\n'.format(self.ncount) 40 | self.dot_body.append(s) 41 | node._num = self.ncount 42 | self.ncount += 1 43 | 44 | for declaration in node.declarations: 45 | self.visit(declaration) 46 | self.visit(node.compound_statement) 47 | 48 | for decl_node in node.declarations: 49 | s = ' node{} -> node{}\n'.format(node._num, decl_node._num) 50 | self.dot_body.append(s) 51 | 52 | s = ' node{} -> node{}\n'.format( 53 | node._num, 54 | node.compound_statement._num 55 | ) 56 | self.dot_body.append(s) 57 | 58 | def visit_VarDecl(self, node): 59 | s = ' node{} [label="VarDecl"]\n'.format(self.ncount) 60 | self.dot_body.append(s) 61 | node._num = self.ncount 62 | self.ncount += 1 63 | 64 | self.visit(node.var_node) 65 | s = ' node{} -> node{}\n'.format(node._num, node.var_node._num) 66 | self.dot_body.append(s) 67 | 68 | self.visit(node.type_node) 69 | s = ' node{} -> node{}\n'.format(node._num, node.type_node._num) 70 | self.dot_body.append(s) 71 | 72 | def visit_ProcedureDecl(self, node): 73 | s = ' node{} [label="ProcDecl:{}"]\n'.format( 74 | self.ncount, 75 | node.proc_name 76 | ) 77 | self.dot_body.append(s) 78 | node._num = self.ncount 79 | self.ncount += 1 80 | 81 | for param_node in node.params: 82 | self.visit(param_node) 83 | s = ' node{} -> node{}\n'.format(node._num, param_node._num) 84 | self.dot_body.append(s) 85 | 86 | self.visit(node.block_node) 87 | s = ' node{} -> node{}\n'.format(node._num, node.block_node._num) 88 | self.dot_body.append(s) 89 | 90 | def visit_Param(self, node): 91 | s = ' node{} [label="Param"]\n'.format(self.ncount) 92 | self.dot_body.append(s) 93 | node._num = self.ncount 94 | self.ncount += 1 95 | 96 | self.visit(node.var_node) 97 | s = ' node{} -> node{}\n'.format(node._num, node.var_node._num) 98 | self.dot_body.append(s) 99 | 100 | self.visit(node.type_node) 101 | s = ' node{} -> node{}\n'.format(node._num, node.type_node._num) 102 | self.dot_body.append(s) 103 | 104 | def visit_Type(self, node): 105 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.token.value) 106 | self.dot_body.append(s) 107 | node._num = self.ncount 108 | self.ncount += 1 109 | 110 | def visit_Num(self, node): 111 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.token.value) 112 | self.dot_body.append(s) 113 | node._num = self.ncount 114 | self.ncount += 1 115 | 116 | def visit_BinOp(self, node): 117 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.op.value) 118 | self.dot_body.append(s) 119 | node._num = self.ncount 120 | self.ncount += 1 121 | 122 | self.visit(node.left) 123 | self.visit(node.right) 124 | 125 | for child_node in (node.left, node.right): 126 | s = ' node{} -> node{}\n'.format(node._num, child_node._num) 127 | self.dot_body.append(s) 128 | 129 | def visit_UnaryOp(self, node): 130 | s = ' node{} [label="unary {}"]\n'.format(self.ncount, node.op.value) 131 | self.dot_body.append(s) 132 | node._num = self.ncount 133 | self.ncount += 1 134 | 135 | self.visit(node.expr) 136 | s = ' node{} -> node{}\n'.format(node._num, node.expr._num) 137 | self.dot_body.append(s) 138 | 139 | def visit_Compound(self, node): 140 | s = ' node{} [label="Compound"]\n'.format(self.ncount) 141 | self.dot_body.append(s) 142 | node._num = self.ncount 143 | self.ncount += 1 144 | 145 | for child in node.children: 146 | self.visit(child) 147 | s = ' node{} -> node{}\n'.format(node._num, child._num) 148 | self.dot_body.append(s) 149 | 150 | def visit_Assign(self, node): 151 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.op.value) 152 | self.dot_body.append(s) 153 | node._num = self.ncount 154 | self.ncount += 1 155 | 156 | self.visit(node.left) 157 | self.visit(node.right) 158 | 159 | for child_node in (node.left, node.right): 160 | s = ' node{} -> node{}\n'.format(node._num, child_node._num) 161 | self.dot_body.append(s) 162 | 163 | def visit_Var(self, node): 164 | s = ' node{} [label="{}"]\n'.format(self.ncount, node.value) 165 | self.dot_body.append(s) 166 | node._num = self.ncount 167 | self.ncount += 1 168 | 169 | def visit_NoOp(self, node): 170 | s = ' node{} [label="NoOp"]\n'.format(self.ncount) 171 | self.dot_body.append(s) 172 | node._num = self.ncount 173 | self.ncount += 1 174 | 175 | def visit_ProcedureCall(self, node): 176 | s = ' node{} [label="ProcCall:{}"]\n'.format( 177 | self.ncount, 178 | node.proc_name 179 | ) 180 | self.dot_body.append(s) 181 | node._num = self.ncount 182 | self.ncount += 1 183 | 184 | for param_node in node.actual_params: 185 | self.visit(param_node) 186 | s = ' node{} -> node{}\n'.format(node._num, param_node._num) 187 | self.dot_body.append(s) 188 | 189 | def gendot(self): 190 | tree = self.parser.parse() 191 | self.visit(tree) 192 | return ''.join(self.dot_header + self.dot_body + self.dot_footer) 193 | 194 | 195 | def main(): 196 | argparser = argparse.ArgumentParser( 197 | description='Generate an AST DOT file.' 198 | ) 199 | argparser.add_argument( 200 | 'fname', 201 | help='Pascal source file' 202 | ) 203 | args = argparser.parse_args() 204 | fname = args.fname 205 | text = open(fname, 'r').read() 206 | 207 | lexer = Lexer(text) 208 | parser = Parser(lexer) 209 | viz = ASTVisualizer(parser) 210 | content = viz.gendot() 211 | print(content) 212 | 213 | 214 | if __name__ == '__main__': 215 | main() 216 | --------------------------------------------------------------------------------