Skip to content

Commit

Permalink
Update Interpreter and UI
Browse files Browse the repository at this point in the history
  • Loading branch information
AlimU11 committed Mar 14, 2023
1 parent 626ad73 commit e58da11
Show file tree
Hide file tree
Showing 38 changed files with 1,494 additions and 517 deletions.
29 changes: 23 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ A simple Pascal interpreter based on [Let's Build a Simple Interpreter](https://

## Current state

- Implemented all parts of the series (part 19 implemented in part 18)
- Additionally implemented:
- Procedures can access non-local variables
- UI with following output:
- Error messages
- Table with scopes
- Activation records for each scope

![img.png](src/img.png)

## Grammar
Expand All @@ -27,7 +35,9 @@ A simple Pascal interpreter based on [Let's Build a Simple Interpreter](https://
# Declaration
<declarations> ::= [ <VAR> { <variable_declaration> <SEMI> }+ ]
<declarations> ::= <VAR> { <variable_declaration> <SEMI> }+
| { <PROCEDURE> <ID> <SEMI> <block> <SEMI> }*
| <empty>
<variable_declaration> ::= <ID> { <COMMA> <ID> }* <COLON> <type_spec>
Expand All @@ -40,11 +50,14 @@ A simple Pascal interpreter based on [Let's Build a Simple Interpreter](https://
<statement_list> ::= <statement> { <SEMI> <statement_list> }*
<statement> ::= <compound_statement>
| <procedure_call_statement>
| <assignment_statement>
| <empty>
<assignment_statement> ::= <variable> <ASSIGN> <expression>
<procedure_call_statement> ::= <ID> <LPAREN> [ <expression> { <COMMA> <expression> }* ] <RPAREN>
<empty> ::= ''
# Mathemathical Expression
Expand Down Expand Up @@ -139,22 +152,26 @@ A simple Pascal interpreter based on [Let's Build a Simple Interpreter](https://

![](src/diagram9.svg)

### Empty Statement
### Procedure Call Statement

![](src/diagram10.svg)

### Expression
### Empty Statement

![](src/diagram11.svg)

### Term
### Expression

![](src/diagram12.svg)

### Factor
### Term

![](src/diagram13.svg)

### Variable
### Factor

![](src/diagram14.svg)

### Variable

![](src/diagram15.svg)
12 changes: 12 additions & 0 deletions base/ARNode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from base.ActivationRecord import ActivationRecord
from base.ScopedSymbolTable import ScopedSymbolTable


class ARNode:
def __init__(self, scope):
self.scope: ScopedSymbolTable = scope
self.ar_records: list[ActivationRecord] = []
self.children: list[ARNode] = []

def __str__(self):
return f'{self.scope.scope_name} {self.scope.scope_level} <- {self.scope.enclosing_scope.scope_name if self.scope.enclosing_scope else None}'
107 changes: 107 additions & 0 deletions base/ARTree.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
from base.ActivationRecord import ActivationRecord
from base.ARNode import ARNode


class ARTree:
def __init__(self):
self.root: ARNode
self._size: int = 0

def build_tree(self, scopes):
"""
Builds the tree from the list of scopes. The first scope in the list is
built-in scope and the root of the tree. Its only child is second scope in the list
- the global scope. Other scopes are children or ancestors of the global scope. It
is guaranteed that that each scope is unique and that at least built-in and global
scopes are present in the list.
"""
self.root = ARNode(scopes[0])
self._size = 1

for scope in scopes[1:]:
node = ARNode(scope)
parent = self._find_parent(scope, self.root)
parent.children.append(node)
self._size += 1

def _find_parent(self, scope, root: ARNode) -> ARNode:
"""
Finds the parent of the given scope. It is guaranteed that the scope has a parent.
"""

if scope.enclosing_scope == root.scope and scope.scope_level == root.scope.scope_level + 1:
return root
else:
for child in root.children:
node = self._find_parent(scope, child)
if node:
return node

return ARNode(None)

def _find_node(self, name: str, level: int, root: ARNode) -> ARNode:
"""
Finds the node with the given name and level. It is guaranteed that the node exists.
"""
if root.scope.scope_name == name and root.scope.scope_level == level:
return root
else:
for child in root.children:
node = self._find_node(name, level, child)
if node:
return node

return ARNode(None)

def find(self, name: str, level: int) -> list[ActivationRecord]:
"""
Finds the activation record with the given name and level. It is guaranteed that the
record exists.
"""
node = self._find_node(name, level, self.root)
return node.ar_records

def push(self, AR: ActivationRecord) -> None:
node = self._find_node(AR.scope_name, AR.nesting_level, self.root)
node.ar_records.append(AR)

def __str__(self):
return '\n'.join([i.__str__() for i in self._bf_traverse(self.root)])

def preorder_traverse(self, root: ARNode) -> list[ARNode]:
"""
Performs a pre-order traversal of the tree.
Returns a list of nodes in the order they were visited.
"""
nodes = [root]

for child in root.children:
nodes += self.preorder_traverse(child)

return nodes

def bf_traverse(self) -> list[ARNode]:
return self._bf_traverse(self.root)

def _bf_traverse(self, root: ARNode) -> list[ARNode]:
"""
Performs a breadth-first traversal of the tree.
Returns a list of nodes in the order they were visited.
"""
nodes = [root]
queue = [root]

while queue:
node = queue.pop(0)
for child in node.children:
nodes.append(child)
queue.append(child)

return nodes

def __len__(self) -> int:
return self._size

@property
def size(self) -> int:
return self._size
6 changes: 6 additions & 0 deletions base/ARType.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from enum import Enum


class ARType(Enum):
PROGRAM = 'PROGRAM'
PROCEDURE = 'PROCEDURE'
25 changes: 25 additions & 0 deletions base/AST.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ def __init__(self, op, expr):


class Compound(AST):
"""Represents a 'BEGIN ... END' block"""

def __init__(self):
self.children = []

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


class Var(AST):
"""The Var node is constructed out of ID token."""

def __init__(self, token):
self.token = token
self.value = token.value
Expand Down Expand Up @@ -65,3 +69,24 @@ class Type(AST):
def __init__(self, token):
self.token = token
self.value = token.value


class ProcedureDecl(AST):
def __init__(self, proc_name, params, block_node):
self.proc_name = proc_name
self.params = params
self.block_node = block_node


class Param(AST):
def __init__(self, var_node, type_node):
self.var_node = var_node
self.type_node = type_node


class ProcedureCall(AST):
def __init__(self, proc_name, actual_params, token):
self.proc_name = proc_name
self.actual_params = actual_params
self.token = token
self.proc_symbol = None
38 changes: 38 additions & 0 deletions base/ActivationRecord.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
class ActivationRecord:
def __init__(self, name, scope_name, type, nesting_level, execution_order, outer_scope):
self.name = name
self.scope_name = scope_name
self.type = type
self.nesting_level = nesting_level
self.execution_order = execution_order
self.members = {}
self.outer_scope = outer_scope

def __setitem__(self, key, value):
self.members[key] = value

def __getitem__(self, key):
val = self.members.get(key)

if not val:
val = self.outer_scope[key]

return val

def __str__(self):
lines = [
'{level}: {type} {name} (execution order: {execution_order})'.format(
level=self.nesting_level,
type=self.type.name,
name=self.name,
execution_order=self.execution_order,
),
]
for name, val in self.members.items():
lines.append(f' {name:<20}: {val}')

s = '\n'.join(lines)
return s

def __repr__(self):
return self.__str__()
27 changes: 27 additions & 0 deletions base/CallStack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
class CallStack:
def __init__(self):
self._records = []

def push(self, ar):
self._records.append(ar)

def pop(self):
return self._records.pop()

def peek(self):
return self._records[-1]

def __len__(self):
return len(self._records)

@property
def size(self):
return self.__len__()

def __str__(self):
s = '\n'.join(repr(ar) for ar in reversed(self._records))
s = f'CALL STACK\n{s}\n'
return s

def __repr__(self):
return self.__str__()
35 changes: 35 additions & 0 deletions base/Error.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from enum import Enum


class ErrorCode(Enum):
UNEXPECTED_TOKEN = 'Unexpected token'
ID_NOT_FOUND = 'Identifier not found'
DUPLICATE_ID = 'Duplicate id found'
WRONG_ARGUMENTS_NUMBER = 'Wrong number of arguments'
MAX_RECURSION_DEPTH_REACHED = 'RecursionError'


class Error(Exception):
def __init__(self, error_code=None, token=None, message=None):
self.error_code = error_code
self.token = token
self.message = f'{self.__class__.__name__}: {message}'

def __str__(self):
return self.message


class LexerError(Error):
pass


class ParserError(Error):
pass


class SemanticError(Error):
pass


class InterpreterError(Error):
pass
Loading

0 comments on commit e58da11

Please sign in to comment.