Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Examples/factorial.pas
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

function Factorial(number: Integer): Integer;
begin
if (number > 1) then
if number > 1 then
Factorial := number * Factorial(number-1)
else
Factorial := 1
Expand Down
25 changes: 25 additions & 0 deletions Examples/game.pas
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Program Game;
Var
target, guess: Integer;

Begin
guess := 10;
target := random(guess);

Writeln('Guess a number between 0 and 10:');
Read(guess);

repeat
if guess > target then
begin
Writeln('Too much, try again');
Read(guess);
end
else
begin
Writeln('Too low, try again');
Read(guess);
end
until target = guess;
Writeln('You won!')
End.
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,22 @@ extension Interpreter {
case "READLN":
read(params: params, frame: frame)
return .none
case "RANDOM":
return random(params: params)
default:
fatalError("Implement built in procedure \(procedure)")
}
}

private func random(params: [AST]) -> Value {
guard params.count == 1, let first = params.first, case let .number(.integer(l)) = eval(node: first) else {
fatalError("Random called with invalid parameters")
}

let value = arc4random_uniform(UInt32(l))
return .number(.integer(Int(value)))
}

private func write(params: [AST], newLine: Bool) {
var s = ""
for param in params {
Expand Down
14 changes: 14 additions & 0 deletions PascalInterpreter/PascalInterpreter/Interpreter/Interpreter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ public class Interpreter {
return eval(condition: condition)
case let ifElse as IfElse:
return eval(ifElse: ifElse)
case let repeatUntil as RepeatUntil:
return eval(repeatUntil: repeatUntil)
default:
return .none
}
Expand Down Expand Up @@ -165,6 +167,18 @@ public class Interpreter {
}
}

func eval(repeatUntil: RepeatUntil) -> Value {
eval(node: repeatUntil.statement)
var value = eval(condition: repeatUntil.condition)

while case .boolean(false) = value {
eval(node: repeatUntil.statement)
value = eval(condition: repeatUntil.condition)
}

return .none
}

private func callFunction(function: String, params: [AST], frame: Frame) -> Value {
guard let symbol = frame.scope.lookup(function), let procedureSymbol = symbol as? ProcedureSymbol else {
fatalError("Symbol(procedure) not found '\(function)'")
Expand Down
4 changes: 3 additions & 1 deletion PascalInterpreter/PascalInterpreter/Lexer/Lexer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ public class Lexer {
"IF": .if,
"ELSE": .else,
"THEN": .then,
"FUNCTION": .function
"FUNCTION": .function,
"REPEAT": .repeat,
"UNTIL": .until
]

public init(_ text: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ extension Token: Equatable {
return true
case (.apostrophe, .apostrophe):
return true
case (.repeat, .repeat):
return true
case (.until, .until):
return true
default:
return false
}
Expand Down Expand Up @@ -191,6 +195,10 @@ extension Token: CustomStringConvertible {
return "FUNCTION"
case .apostrophe:
return "APOSTROPHE"
case .repeat:
return "REPEAT"
case .until:
return "UNTIL"
}
}
}
2 changes: 2 additions & 0 deletions PascalInterpreter/PascalInterpreter/Lexer/Token.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,6 @@ public enum Token {
case greaterThan
case function
case apostrophe
case `repeat`
case until
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ extension AST {
return string
case let boolean as Bool:
return boolean ? "TRUE" : "FALSE"
case is RepeatUntil:
return "REPEAT"
default:
fatalError("Missed AST case \(self)")
}
Expand Down Expand Up @@ -179,6 +181,8 @@ extension AST {
return []
case is Bool:
return []
case let repeatUntil as RepeatUntil:
return [repeatUntil.condition, repeatUntil.statement]
default:
fatalError("Missed AST case \(self)")
}
Expand Down
10 changes: 10 additions & 0 deletions PascalInterpreter/PascalInterpreter/Parser/AST.swift
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,13 @@ class IfElse: AST {
self.falseExpression = falseExpression
}
}

class RepeatUntil: AST {
let statement: AST
let condition: Condition

init(statement: AST, condition: Condition) {
self.statement = statement
self.condition = condition
}
}
37 changes: 33 additions & 4 deletions PascalInterpreter/PascalInterpreter/Parser/Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ public class Parser {
| procedure_call
| if_else_statement
| assignment_statement
| repeat_until
| empty
*/
private func statement() -> AST {
Expand All @@ -284,6 +285,8 @@ public class Parser {
return compoundStatement()
case .if:
return ifElseStatement()
case .repeat:
return repeatUntilLoop()
case .id:
if nextToken == .parenthesis(.left) {
return functionCall()
Expand All @@ -295,6 +298,27 @@ public class Parser {
}
}

/**
Rule:

repeat_until : REPEAT statement UNTIL condition
*/
private func repeatUntilLoop() -> RepeatUntil {
eat(.repeat)

var statements: [AST] = []

while currentToken != .until {
statements.append(statement())
if currentToken == .semi {
eat(.semi)
}
}
eat(.until)
let condition = self.condition()
return RepeatUntil(statement: statements.count == 1 ? statements[0] : Compound(children: statements), condition: condition)
}

/**
Rule:

Expand Down Expand Up @@ -346,9 +370,12 @@ public class Parser {
/**
Rule:
condition: expr (= | < | >) expr
| LPAREN expr (= | < | >) expr RPAREN
*/
private func condition() -> Condition {
eat(.parenthesis(.left))
if currentToken == .parenthesis(.left) {
eat(.parenthesis(.left))
}
let left = expr()
var type: ConditionType = .equals
switch currentToken {
Expand All @@ -365,7 +392,9 @@ public class Parser {
fatalError("Invalid condition type \(type)")
}
let right = expr()
eat(.parenthesis(.right))
if currentToken == .parenthesis(.right) {
eat(.parenthesis(.right))
}
return Condition(type: type, leftSide: left, rightSide: right)
}

Expand Down Expand Up @@ -483,10 +512,10 @@ public class Parser {
let result = expr()
eat(.parenthesis(.right))
return result
case .constant(.string(let value)):
case let .constant(.string(value)):
eat(.constant(.string(value)))
return value
case .constant(.boolean(let value)):
case let .constant(.boolean(value)):
eat(.constant(.boolean(value)))
return value
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class ScopedSymbolTable {
symbols["WRITE"] = BuiltInProcedureSymbol(name: "WRITE", parameters: [], hasVariableParameters: true)
symbols["READ"] = BuiltInProcedureSymbol(name: "READ", parameters: [], hasVariableParameters: true)
symbols["READLN"] = BuiltInProcedureSymbol(name: "READLN", parameters: [], hasVariableParameters: true)
symbols["RANDOM"] = BuiltInProcedureSymbol(name: "RANDOM", parameters: [BuiltInTypeSymbol.integer], hasVariableParameters: false)
}

func insert(_ symbol: Symbol) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ protocol Visitor: class {
func visit(call: FunctionCall)
func visit(condition: Condition)
func visit(ifElse: IfElse)
func visit(repeatUntil: RepeatUntil)
}

extension Visitor {
Expand Down Expand Up @@ -66,6 +67,8 @@ extension Visitor {
visit(condition: condition)
case let ifElse as IfElse:
visit(ifElse: ifElse)
case let repeatUntil as RepeatUntil:
visit(repeatUntil: repeatUntil)
default:
fatalError("Unsupported node type \(node)")
}
Expand Down Expand Up @@ -160,4 +163,9 @@ extension Visitor {
visit(node: falseExpression)
}
}

func visit(repeatUntil: RepeatUntil) {
visit(node: repeatUntil.statement)
visit(node: repeatUntil.condition)
}
}
32 changes: 28 additions & 4 deletions PascalInterpreter/PascalInterpreterTests/InterpreterTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ class InterpreterTests: XCTestCase {

function Factorial(number: Integer): Integer;
begin
if (number > 1) then
if number > 1 then
Factorial := number * Factorial(number-1)
else
Factorial := 1
Expand Down Expand Up @@ -172,7 +172,7 @@ class InterpreterTests: XCTestCase {

function Factorial(number: Integer): Integer;
begin
if (number > 1) then
if number > 1 then
Factorial := number * Factorial(number-1)
else
Factorial := 1
Expand All @@ -195,13 +195,13 @@ class InterpreterTests: XCTestCase {

func testProgramWithRecursiveFunctionsAndParameterTheSameName() {
let program =
"""
"""
program Main;
var number, result: integer;

function Factorial(number: Integer): Integer;
begin
if (number > 1) then
if number > 1 then
Factorial := number * Factorial(number-1)
else
Factorial := 1
Expand All @@ -221,4 +221,28 @@ class InterpreterTests: XCTestCase {
XCTAssert(boolState == [:])
XCTAssert(stringState == [:])
}

func testProgramWithRepeatUntil() {
let program =
"""
program Main;
var x: integer;

begin
x:=0;
repeat
x:=x+1;
until x = 6;
writeln(x);
end. { Main }
"""

let interpeter = Interpreter(program)
interpeter.interpret()
let (integerState, realState, boolState, stringState) = interpeter.getState()
XCTAssert(realState == [:])
XCTAssert(integerState == ["x": 6])
XCTAssert(boolState == [:])
XCTAssert(stringState == [:])
}
}
15 changes: 15 additions & 0 deletions PascalInterpreter/PascalInterpreterTests/LexerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -434,4 +434,19 @@ class LexerTests: XCTestCase {
XCTAssert(lexer.getNextToken() == .constant(.string("Test string")))
XCTAssert(lexer.getNextToken() == .eof)
}

func testLoopTokens() {
let lexer = Lexer("repeat x:= x+1 until x=5")
XCTAssert(lexer.getNextToken() == .repeat)
XCTAssert(lexer.getNextToken() == .id("x"))
XCTAssert(lexer.getNextToken() == .assign)
XCTAssert(lexer.getNextToken() == .id("x"))
XCTAssert(lexer.getNextToken() == .operation(.plus))
XCTAssert(lexer.getNextToken() == .constant(.integer(1)))
XCTAssert(lexer.getNextToken() == .until)
XCTAssert(lexer.getNextToken() == .id("x"))
XCTAssert(lexer.getNextToken() == .equals)
XCTAssert(lexer.getNextToken() == .constant(.integer(5)))
XCTAssert(lexer.getNextToken() == .eof)
}
}
Loading