Skip to content

Commit e58da11

Browse files
committed
Update Interpreter and UI
1 parent 626ad73 commit e58da11

38 files changed

+1494
-517
lines changed

README.md

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ A simple Pascal interpreter based on [Let's Build a Simple Interpreter](https://
1212

1313
## Current state
1414

15+
- Implemented all parts of the series (part 19 implemented in part 18)
16+
- Additionally implemented:
17+
- Procedures can access non-local variables
18+
- UI with following output:
19+
- Error messages
20+
- Table with scopes
21+
- Activation records for each scope
22+
1523
![img.png](src/img.png)
1624

1725
## Grammar
@@ -27,7 +35,9 @@ A simple Pascal interpreter based on [Let's Build a Simple Interpreter](https://
2735
2836
# Declaration
2937
30-
<declarations> ::= [ <VAR> { <variable_declaration> <SEMI> }+ ]
38+
<declarations> ::= <VAR> { <variable_declaration> <SEMI> }+
39+
| { <PROCEDURE> <ID> <SEMI> <block> <SEMI> }*
40+
| <empty>
3141
3242
<variable_declaration> ::= <ID> { <COMMA> <ID> }* <COLON> <type_spec>
3343
@@ -40,11 +50,14 @@ A simple Pascal interpreter based on [Let's Build a Simple Interpreter](https://
4050
<statement_list> ::= <statement> { <SEMI> <statement_list> }*
4151
4252
<statement> ::= <compound_statement>
53+
| <procedure_call_statement>
4354
| <assignment_statement>
4455
| <empty>
4556
4657
<assignment_statement> ::= <variable> <ASSIGN> <expression>
4758
59+
<procedure_call_statement> ::= <ID> <LPAREN> [ <expression> { <COMMA> <expression> }* ] <RPAREN>
60+
4861
<empty> ::= ''
4962
5063
# Mathemathical Expression
@@ -139,22 +152,26 @@ A simple Pascal interpreter based on [Let's Build a Simple Interpreter](https://
139152

140153
![](src/diagram9.svg)
141154

142-
### Empty Statement
155+
### Procedure Call Statement
143156

144157
![](src/diagram10.svg)
145158

146-
### Expression
159+
### Empty Statement
147160

148161
![](src/diagram11.svg)
149162

150-
### Term
163+
### Expression
151164

152165
![](src/diagram12.svg)
153166

154-
### Factor
167+
### Term
155168

156169
![](src/diagram13.svg)
157170

158-
### Variable
171+
### Factor
159172

160173
![](src/diagram14.svg)
174+
175+
### Variable
176+
177+
![](src/diagram15.svg)

base/ARNode.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from base.ActivationRecord import ActivationRecord
2+
from base.ScopedSymbolTable import ScopedSymbolTable
3+
4+
5+
class ARNode:
6+
def __init__(self, scope):
7+
self.scope: ScopedSymbolTable = scope
8+
self.ar_records: list[ActivationRecord] = []
9+
self.children: list[ARNode] = []
10+
11+
def __str__(self):
12+
return f'{self.scope.scope_name} {self.scope.scope_level} <- {self.scope.enclosing_scope.scope_name if self.scope.enclosing_scope else None}'

base/ARTree.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
from base.ActivationRecord import ActivationRecord
2+
from base.ARNode import ARNode
3+
4+
5+
class ARTree:
6+
def __init__(self):
7+
self.root: ARNode
8+
self._size: int = 0
9+
10+
def build_tree(self, scopes):
11+
"""
12+
Builds the tree from the list of scopes. The first scope in the list is
13+
built-in scope and the root of the tree. Its only child is second scope in the list
14+
- the global scope. Other scopes are children or ancestors of the global scope. It
15+
is guaranteed that that each scope is unique and that at least built-in and global
16+
scopes are present in the list.
17+
"""
18+
self.root = ARNode(scopes[0])
19+
self._size = 1
20+
21+
for scope in scopes[1:]:
22+
node = ARNode(scope)
23+
parent = self._find_parent(scope, self.root)
24+
parent.children.append(node)
25+
self._size += 1
26+
27+
def _find_parent(self, scope, root: ARNode) -> ARNode:
28+
"""
29+
Finds the parent of the given scope. It is guaranteed that the scope has a parent.
30+
"""
31+
32+
if scope.enclosing_scope == root.scope and scope.scope_level == root.scope.scope_level + 1:
33+
return root
34+
else:
35+
for child in root.children:
36+
node = self._find_parent(scope, child)
37+
if node:
38+
return node
39+
40+
return ARNode(None)
41+
42+
def _find_node(self, name: str, level: int, root: ARNode) -> ARNode:
43+
"""
44+
Finds the node with the given name and level. It is guaranteed that the node exists.
45+
"""
46+
if root.scope.scope_name == name and root.scope.scope_level == level:
47+
return root
48+
else:
49+
for child in root.children:
50+
node = self._find_node(name, level, child)
51+
if node:
52+
return node
53+
54+
return ARNode(None)
55+
56+
def find(self, name: str, level: int) -> list[ActivationRecord]:
57+
"""
58+
Finds the activation record with the given name and level. It is guaranteed that the
59+
record exists.
60+
"""
61+
node = self._find_node(name, level, self.root)
62+
return node.ar_records
63+
64+
def push(self, AR: ActivationRecord) -> None:
65+
node = self._find_node(AR.scope_name, AR.nesting_level, self.root)
66+
node.ar_records.append(AR)
67+
68+
def __str__(self):
69+
return '\n'.join([i.__str__() for i in self._bf_traverse(self.root)])
70+
71+
def preorder_traverse(self, root: ARNode) -> list[ARNode]:
72+
"""
73+
Performs a pre-order traversal of the tree.
74+
Returns a list of nodes in the order they were visited.
75+
"""
76+
nodes = [root]
77+
78+
for child in root.children:
79+
nodes += self.preorder_traverse(child)
80+
81+
return nodes
82+
83+
def bf_traverse(self) -> list[ARNode]:
84+
return self._bf_traverse(self.root)
85+
86+
def _bf_traverse(self, root: ARNode) -> list[ARNode]:
87+
"""
88+
Performs a breadth-first traversal of the tree.
89+
Returns a list of nodes in the order they were visited.
90+
"""
91+
nodes = [root]
92+
queue = [root]
93+
94+
while queue:
95+
node = queue.pop(0)
96+
for child in node.children:
97+
nodes.append(child)
98+
queue.append(child)
99+
100+
return nodes
101+
102+
def __len__(self) -> int:
103+
return self._size
104+
105+
@property
106+
def size(self) -> int:
107+
return self._size

base/ARType.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from enum import Enum
2+
3+
4+
class ARType(Enum):
5+
PROGRAM = 'PROGRAM'
6+
PROCEDURE = 'PROCEDURE'

base/AST.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ def __init__(self, op, expr):
2222

2323

2424
class Compound(AST):
25+
"""Represents a 'BEGIN ... END' block"""
26+
2527
def __init__(self):
2628
self.children = []
2729

@@ -34,6 +36,8 @@ def __init__(self, left, op, right):
3436

3537

3638
class Var(AST):
39+
"""The Var node is constructed out of ID token."""
40+
3741
def __init__(self, token):
3842
self.token = token
3943
self.value = token.value
@@ -65,3 +69,24 @@ class Type(AST):
6569
def __init__(self, token):
6670
self.token = token
6771
self.value = token.value
72+
73+
74+
class ProcedureDecl(AST):
75+
def __init__(self, proc_name, params, block_node):
76+
self.proc_name = proc_name
77+
self.params = params
78+
self.block_node = block_node
79+
80+
81+
class Param(AST):
82+
def __init__(self, var_node, type_node):
83+
self.var_node = var_node
84+
self.type_node = type_node
85+
86+
87+
class ProcedureCall(AST):
88+
def __init__(self, proc_name, actual_params, token):
89+
self.proc_name = proc_name
90+
self.actual_params = actual_params
91+
self.token = token
92+
self.proc_symbol = None

base/ActivationRecord.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
class ActivationRecord:
2+
def __init__(self, name, scope_name, type, nesting_level, execution_order, outer_scope):
3+
self.name = name
4+
self.scope_name = scope_name
5+
self.type = type
6+
self.nesting_level = nesting_level
7+
self.execution_order = execution_order
8+
self.members = {}
9+
self.outer_scope = outer_scope
10+
11+
def __setitem__(self, key, value):
12+
self.members[key] = value
13+
14+
def __getitem__(self, key):
15+
val = self.members.get(key)
16+
17+
if not val:
18+
val = self.outer_scope[key]
19+
20+
return val
21+
22+
def __str__(self):
23+
lines = [
24+
'{level}: {type} {name} (execution order: {execution_order})'.format(
25+
level=self.nesting_level,
26+
type=self.type.name,
27+
name=self.name,
28+
execution_order=self.execution_order,
29+
),
30+
]
31+
for name, val in self.members.items():
32+
lines.append(f' {name:<20}: {val}')
33+
34+
s = '\n'.join(lines)
35+
return s
36+
37+
def __repr__(self):
38+
return self.__str__()

base/CallStack.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
class CallStack:
2+
def __init__(self):
3+
self._records = []
4+
5+
def push(self, ar):
6+
self._records.append(ar)
7+
8+
def pop(self):
9+
return self._records.pop()
10+
11+
def peek(self):
12+
return self._records[-1]
13+
14+
def __len__(self):
15+
return len(self._records)
16+
17+
@property
18+
def size(self):
19+
return self.__len__()
20+
21+
def __str__(self):
22+
s = '\n'.join(repr(ar) for ar in reversed(self._records))
23+
s = f'CALL STACK\n{s}\n'
24+
return s
25+
26+
def __repr__(self):
27+
return self.__str__()

base/Error.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from enum import Enum
2+
3+
4+
class ErrorCode(Enum):
5+
UNEXPECTED_TOKEN = 'Unexpected token'
6+
ID_NOT_FOUND = 'Identifier not found'
7+
DUPLICATE_ID = 'Duplicate id found'
8+
WRONG_ARGUMENTS_NUMBER = 'Wrong number of arguments'
9+
MAX_RECURSION_DEPTH_REACHED = 'RecursionError'
10+
11+
12+
class Error(Exception):
13+
def __init__(self, error_code=None, token=None, message=None):
14+
self.error_code = error_code
15+
self.token = token
16+
self.message = f'{self.__class__.__name__}: {message}'
17+
18+
def __str__(self):
19+
return self.message
20+
21+
22+
class LexerError(Error):
23+
pass
24+
25+
26+
class ParserError(Error):
27+
pass
28+
29+
30+
class SemanticError(Error):
31+
pass
32+
33+
34+
class InterpreterError(Error):
35+
pass

0 commit comments

Comments
 (0)