diff --git a/Examples/factorial.pas b/Examples/factorial.pas index 0bd67e9..c337dad 100644 --- a/Examples/factorial.pas +++ b/Examples/factorial.pas @@ -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 diff --git a/Examples/game.pas b/Examples/game.pas new file mode 100644 index 0000000..0a30efb --- /dev/null +++ b/Examples/game.pas @@ -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. \ No newline at end of file diff --git a/PascalInterpreter/PascalInterpreter/Interpreter/Interpreter+Standard.swift b/PascalInterpreter/PascalInterpreter/Interpreter/Interpreter+Standard.swift index b57cd43..5abf812 100644 --- a/PascalInterpreter/PascalInterpreter/Interpreter/Interpreter+Standard.swift +++ b/PascalInterpreter/PascalInterpreter/Interpreter/Interpreter+Standard.swift @@ -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 { diff --git a/PascalInterpreter/PascalInterpreter/Interpreter/Interpreter.swift b/PascalInterpreter/PascalInterpreter/Interpreter/Interpreter.swift index dae2b5c..05078fe 100644 --- a/PascalInterpreter/PascalInterpreter/Interpreter/Interpreter.swift +++ b/PascalInterpreter/PascalInterpreter/Interpreter/Interpreter.swift @@ -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 } @@ -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)'") diff --git a/PascalInterpreter/PascalInterpreter/Lexer/Lexer.swift b/PascalInterpreter/PascalInterpreter/Lexer/Lexer.swift index 049c7b9..8da8e65 100644 --- a/PascalInterpreter/PascalInterpreter/Lexer/Lexer.swift +++ b/PascalInterpreter/PascalInterpreter/Lexer/Lexer.swift @@ -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) { diff --git a/PascalInterpreter/PascalInterpreter/Lexer/Token+Extensions.swift b/PascalInterpreter/PascalInterpreter/Lexer/Token+Extensions.swift index 1d30ae6..d3223da 100644 --- a/PascalInterpreter/PascalInterpreter/Lexer/Token+Extensions.swift +++ b/PascalInterpreter/PascalInterpreter/Lexer/Token+Extensions.swift @@ -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 } @@ -191,6 +195,10 @@ extension Token: CustomStringConvertible { return "FUNCTION" case .apostrophe: return "APOSTROPHE" + case .repeat: + return "REPEAT" + case .until: + return "UNTIL" } } } diff --git a/PascalInterpreter/PascalInterpreter/Lexer/Token.swift b/PascalInterpreter/PascalInterpreter/Lexer/Token.swift index 60aa7c4..0bf2e29 100644 --- a/PascalInterpreter/PascalInterpreter/Lexer/Token.swift +++ b/PascalInterpreter/PascalInterpreter/Lexer/Token.swift @@ -60,4 +60,6 @@ public enum Token { case greaterThan case function case apostrophe + case `repeat` + case until } diff --git a/PascalInterpreter/PascalInterpreter/Parser/AST+Extensions.swift b/PascalInterpreter/PascalInterpreter/Parser/AST+Extensions.swift index 7e42527..a7a4e1c 100644 --- a/PascalInterpreter/PascalInterpreter/Parser/AST+Extensions.swift +++ b/PascalInterpreter/PascalInterpreter/Parser/AST+Extensions.swift @@ -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)") } @@ -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)") } diff --git a/PascalInterpreter/PascalInterpreter/Parser/AST.swift b/PascalInterpreter/PascalInterpreter/Parser/AST.swift index f2bb117..6cbffcd 100644 --- a/PascalInterpreter/PascalInterpreter/Parser/AST.swift +++ b/PascalInterpreter/PascalInterpreter/Parser/AST.swift @@ -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 + } +} diff --git a/PascalInterpreter/PascalInterpreter/Parser/Parser.swift b/PascalInterpreter/PascalInterpreter/Parser/Parser.swift index e119549..2b381bb 100644 --- a/PascalInterpreter/PascalInterpreter/Parser/Parser.swift +++ b/PascalInterpreter/PascalInterpreter/Parser/Parser.swift @@ -276,6 +276,7 @@ public class Parser { | procedure_call | if_else_statement | assignment_statement + | repeat_until | empty */ private func statement() -> AST { @@ -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() @@ -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: @@ -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 { @@ -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) } @@ -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: diff --git a/PascalInterpreter/PascalInterpreter/Semantic analyzer/SymbolTable.swift b/PascalInterpreter/PascalInterpreter/Semantic analyzer/SymbolTable.swift index 40e7ecf..66eb470 100644 --- a/PascalInterpreter/PascalInterpreter/Semantic analyzer/SymbolTable.swift +++ b/PascalInterpreter/PascalInterpreter/Semantic analyzer/SymbolTable.swift @@ -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) { diff --git a/PascalInterpreter/PascalInterpreter/Semantic analyzer/Visitor.swift b/PascalInterpreter/PascalInterpreter/Semantic analyzer/Visitor.swift index d94fba9..fcdffa4 100644 --- a/PascalInterpreter/PascalInterpreter/Semantic analyzer/Visitor.swift +++ b/PascalInterpreter/PascalInterpreter/Semantic analyzer/Visitor.swift @@ -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 { @@ -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)") } @@ -160,4 +163,9 @@ extension Visitor { visit(node: falseExpression) } } + + func visit(repeatUntil: RepeatUntil) { + visit(node: repeatUntil.statement) + visit(node: repeatUntil.condition) + } } diff --git a/PascalInterpreter/PascalInterpreterTests/InterpreterTests.swift b/PascalInterpreter/PascalInterpreterTests/InterpreterTests.swift index 04aacc7..6f42572 100644 --- a/PascalInterpreter/PascalInterpreterTests/InterpreterTests.swift +++ b/PascalInterpreter/PascalInterpreterTests/InterpreterTests.swift @@ -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 @@ -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 @@ -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 @@ -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 == [:]) + } } diff --git a/PascalInterpreter/PascalInterpreterTests/LexerTests.swift b/PascalInterpreter/PascalInterpreterTests/LexerTests.swift index 12eef50..c37f5de 100644 --- a/PascalInterpreter/PascalInterpreterTests/LexerTests.swift +++ b/PascalInterpreter/PascalInterpreterTests/LexerTests.swift @@ -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) + } } diff --git a/PascalInterpreter/PascalInterpreterTests/ParserTests.swift b/PascalInterpreter/PascalInterpreterTests/ParserTests.swift index 086679f..93762a2 100644 --- a/PascalInterpreter/PascalInterpreterTests/ParserTests.swift +++ b/PascalInterpreter/PascalInterpreterTests/ParserTests.swift @@ -276,7 +276,7 @@ class ParserTests: XCTestCase { begin { Main } y := 5; - if (y = 5) then + if y = 5 then x:=2 else x:= 3 @@ -299,7 +299,7 @@ class ParserTests: XCTestCase { begin { Main } y := 5; - if (y = 5) then + if y = 5 then x:=2 end. { Main } """ @@ -361,7 +361,7 @@ class ParserTests: XCTestCase { function Factorial(number: Integer): Integer; begin - if (number > 1) then + if number > 1 then Factorial := number * Factorial(number-1) else Factorial := 1 @@ -404,4 +404,29 @@ class ParserTests: XCTestCase { let node = Program(name: "Main", block: block) XCTAssertEqual(result, node) } + + func testProgramWithRepeatUntil() { + let program = + """ + program Main; + var x: integer; + + begin + x:=0; + repeat + x:=x+1; + until x = 6; + writeln(x); + end. { Main } + """ + + let parser = Parser(program) + let result = parser.parse() + let xDec = VariableDeclaration(variable: Variable(name: "x"), type: VariableType(type: .integer)) + let compound = Compound(children: [Assignment(left: Variable(name: "x"), right: Number.integer(0)), RepeatUntil(statement: Assignment(left: Variable(name: "x"), right: BinaryOperation(left: Variable(name: "x"), operation: BinaryOperationType.plus, right: Number.integer(1))), condition: Condition(type: .equals, leftSide: Variable(name: "x"), rightSide: Number.integer(6))), FunctionCall(name: "writeln", actualParameters: [Variable(name: "x")]), NoOp()]) + let block = Block(declarations: [xDec], compound: compound) + let node = Program(name: "Main", block: block) + XCTAssertEqual(result, node) + } + } diff --git a/PascalInterpreter/PascalInterpreterTests/XCTestCase+FatalError.swift b/PascalInterpreter/PascalInterpreterTests/XCTestCase+FatalError.swift index 06ef4a7..71144a4 100644 --- a/PascalInterpreter/PascalInterpreterTests/XCTestCase+FatalError.swift +++ b/PascalInterpreter/PascalInterpreterTests/XCTestCase+FatalError.swift @@ -134,6 +134,9 @@ func XCTAssertEqual(_ left: AST, _ right: AST) { XCTAssert(left == right) case let (left as Bool, right as Bool): XCTAssert(left == right) + case let (left as RepeatUntil, right as RepeatUntil): + XCTAssertEqual(left.condition, right.condition) + XCTAssertEqual(left.statement, right.statement) default: XCTFail("\(left) and \(right) are not equal") } diff --git a/README.md b/README.md index 4ccb7d7..fe715a1 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ Simple Swift interpreter for the Pascal language inspired by the [Let’s Build ![Playground](Images/cli.gif) +There are a few sample Pascal programs in the [Examples directory](Examples), like a simple [number guessing game](Examples/game.pas) and a [factorial computation](Examples/factorial.pas). + ## Scructure ### Lexer @@ -51,7 +53,7 @@ var result: integer; function Factorial(number: Integer): Integer; begin -if (number > 1) then +if number > 1 then Factorial := number * Factorial(number-1) else Factorial := 1 diff --git a/grammar.md b/grammar.md index e759667..888a010 100644 --- a/grammar.md +++ b/grammar.md @@ -23,9 +23,10 @@ statement_list : statement | statement SEMI statement_list statement : compound_statement - | procedure_call + | procedure_call | if_else_statement | assignment_statement + | repeat_until | empty function_call : id LPAREN (factor (factor COLON)* )* RPAREN (COLON type_spec){0,1} @@ -33,6 +34,11 @@ function_call : id LPAREN (factor (factor COLON)* )* RPAREN (COLON type_spec){0 if_else_statement : IF condition statement | IF condition THEN statement ELSE statement +condition : expr (= | < | >) expr + | LPAREN expr (= | < | >) expr RPAREN + +repeat_until : REPEAT statement UNTIL condition + assignment_statement : variable ASSIGN expr empty :