Skip to content

Commit e8d68e8

Browse files
committed
Add built-in procedure WRITELN
1 parent e58da11 commit e8d68e8

File tree

10 files changed

+91
-12
lines changed

10 files changed

+91
-12
lines changed

README.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,39 @@ A simple Pascal interpreter based on [Let's Build a Simple Interpreter](https://
1616
- Additionally implemented:
1717
- Procedures can access non-local variables
1818
- UI with following output:
19-
- Error messages
19+
- Standard and error output
2020
- Table with scopes
2121
- Activation records for each scope
2222

2323
![img.png](src/img.png)
24+
<details>
25+
<summary>Sample program</summary>
26+
<br>
27+
<pre>
28+
program Main;
29+
30+
procedure Alpha(a : integer; b : integer); { 4, 2 / 1, 3 }
31+
var x : integer;
32+
33+
procedure Beta(a : integer; c : integer); { 12, 2 / 8, 3 }
34+
var x : integer;
35+
begin
36+
x := a * 10 + b * 2 + c; { 66 / 49 }
37+
writeln(a);
38+
writeln(x);
39+
end;
40+
41+
begin
42+
x := (a + b ) * 2; { 12 / 8 }
43+
Beta(x, b);
44+
end;
45+
46+
begin { Main }
47+
Alpha(2 + 2, 2);
48+
Alpha(1, 3);
49+
end. { Main }
50+
</pre>
51+
</details>
2452

2553
## Grammar
2654

base/Interpreter.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ def __init__(self, text):
1515
self.call_stack = CallStack()
1616
self.ar_tree = ARTree()
1717
self.global_execution_order = 0
18+
self.stdout = []
1819

1920
def error(self, error_code, token):
2021
raise InterpreterError(error_code, token, message=f'{error_code.value} -> {token}')
@@ -101,6 +102,16 @@ def visit_ProcedureDecl(self, node):
101102
def visit_ProcedureCall(self, node):
102103
proc_name = node.proc_name
103104

105+
if proc_name == 'WRITELN':
106+
out = ''
107+
108+
for param in node.actual_params:
109+
out += str(self.visit(param))
110+
111+
self.stdout.append(out)
112+
113+
return
114+
104115
AR = ActivationRecord(
105116
name=proc_name,
106117
scope_name=proc_name,

base/Parser.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
)
1717
from base.Error import ErrorCode, ParserError
1818
from base.Lexer import Lexer
19+
from base.Token import Token
1920
from base.TokenType import TokenType
2021

2122

@@ -194,11 +195,10 @@ def statement(self):
194195
| assignment_statement
195196
| empty
196197
"""
197-
198198
if self.current_token.type == TokenType.BEGIN:
199199
node = self.compound_statement()
200200

201-
elif self.current_token.type == TokenType.ID:
201+
elif self.current_token.type == TokenType.ID or Token.RESERVED_PROCEDURES.get(self.current_token.value):
202202

203203
if self.lexer.current_char == '(':
204204
node = self.proccall_statement()
@@ -226,7 +226,12 @@ def proccall_statement(self):
226226
token = self.current_token
227227

228228
proc_name = self.current_token.value
229-
self.eat(TokenType.ID)
229+
230+
if token.type == TokenType.ID:
231+
self.eat(TokenType.ID)
232+
elif Token.RESERVED_PROCEDURES.get(token.value):
233+
self.eat(Token.RESERVED_PROCEDURES.get(token.value))
234+
230235
self.eat(TokenType.LPAREN)
231236
actual_params = []
232237
if self.current_token.type != TokenType.RPAREN:

base/ScopedSymbolTable.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from base.Symbol import BuiltinTypeSymbol
1+
from base.Symbol import BuiltInProcedureSymbol, BuiltinTypeSymbol
22

33

44
class ScopedSymbolTable:
@@ -11,6 +11,7 @@ def __init__(self, scope_name, scope_level, enclosing_scope=None):
1111
def _init_builtins(self):
1212
self.insert(BuiltinTypeSymbol('INTEGER'))
1313
self.insert(BuiltinTypeSymbol('REAL'))
14+
self.insert(BuiltInProcedureSymbol('WRITELN'))
1415

1516
def __str__(self):
1617
return self.scope_name

base/SemanticAnalyzer.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from base.NodeVisitor import NodeVisitor
33
from base.Parser import Parser
44
from base.ScopedSymbolTable import ScopedSymbolTable
5-
from base.Symbol import ProcedureSymbol, Symbol, VarSymbol
5+
from base.Symbol import BuiltInProcedureSymbol, ProcedureSymbol, Symbol, VarSymbol
66

77

88
class SemanticAnalyzer(NodeVisitor):
@@ -125,11 +125,12 @@ def visit_ProcedureCall(self, node):
125125
token=node.token,
126126
)
127127

128-
if len(node.actual_params) != len(proc_symbol.formal_params):
129-
self.error(
130-
error_code=ErrorCode.WRONG_ARGUMENTS_NUMBER,
131-
token=node.token,
132-
)
128+
if not isinstance(proc_symbol, BuiltInProcedureSymbol):
129+
if len(node.actual_params) != len(proc_symbol.formal_params):
130+
self.error(
131+
error_code=ErrorCode.WRONG_ARGUMENTS_NUMBER,
132+
token=node.token,
133+
)
133134

134135
for param_node in node.actual_params:
135136
self.visit(param_node)

base/Symbol.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,18 @@ def __str__(self):
5555

5656
def __repr__(self):
5757
return self.__str__()
58+
59+
60+
class BuiltInProcedureSymbol(ProcedureSymbol):
61+
def __init__(self, name):
62+
super().__init__(name)
63+
64+
def __str__(self):
65+
return '<{class_name}(name={name}, parameters={params})>'.format(
66+
class_name=self.__class__.__name__,
67+
name=self.name,
68+
params=self.formal_params,
69+
)
70+
71+
def __repr__(self):
72+
return self.__str__()

base/Token.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,16 @@ def __repr__(self):
2323
def _build_reserved_keywords():
2424
tokens = list(TokenType)
2525
start_idx = tokens.index(TokenType.PROGRAM)
26-
end_idx = tokens.index(TokenType.END)
26+
end_idx = tokens.index(TokenType.WRITELN)
27+
28+
return {token.value: token for token in tokens[start_idx : end_idx + 1]}
29+
30+
def _build_reserved_procedures():
31+
tokens = list(TokenType)
32+
start_idx = tokens.index(TokenType.WRITELN)
33+
end_idx = tokens.index(TokenType.WRITELN)
2734

2835
return {token.value: token for token in tokens[start_idx : end_idx + 1]}
2936

3037
RESERVED_KEYWORDS = _build_reserved_keywords()
38+
RESERVED_PROCEDURES = _build_reserved_procedures()

base/TokenType.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ class TokenType(Enum):
1515
COMMA = ','
1616

1717
# reserved keywords
18+
# it's important to have PROGRAM as the first keyword as it's affect
19+
# building RESERVED_KEYWORDS dictionary
1820
PROGRAM = 'PROGRAM'
21+
1922
INTEGER = 'INTEGER'
2023
REAL = 'REAL'
2124
INTEGER_DIV = 'DIV'
@@ -24,6 +27,9 @@ class TokenType(Enum):
2427
BEGIN = 'BEGIN'
2528
END = 'END'
2629

30+
# reserved procedures
31+
WRITELN = 'WRITELN'
32+
2733
# misc
2834
ID = 'ID'
2935
INTEGER_CONST = 'INTEGER_CONST'

main.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,11 @@ def interpret(self):
9898

9999
self.ui.scopeComboBox.addItem(node.scope.scope_name)
100100

101+
for out in self.interpreter.stdout:
102+
self.ui.output.append(out)
103+
101104
except Exception as e:
105+
102106
self.ui.output.setText(str(e))
103107
self.ui.output.setStyleSheet('color: red;')
104108

src/img.png

-73 Bytes
Loading

0 commit comments

Comments
 (0)