├── .github └── workflows │ └── main.yml ├── .gitignore ├── Examples ├── factorial.pas ├── game.pas ├── helloworld.pas ├── name.pas └── numbers.pas ├── Images ├── cli.gif ├── lexer.png ├── parser.png └── playground.png ├── LICENSE ├── PascalInterpreter ├── .swiftlint.yml ├── PascalInterpreter.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ └── PascalInterpreter.xcscheme ├── PascalInterpreter │ ├── Extensions │ │ └── Naming+Extensions.swift │ ├── FatalError.swift │ ├── Info.plist │ ├── Interpreter │ │ ├── Arithmetics.swift │ │ ├── Frame.swift │ │ ├── Interpreter+Standard.swift │ │ ├── Interpreter.swift │ │ ├── Stack.swift │ │ ├── Value+Extensions.swift │ │ └── Value.swift │ ├── Lexer │ │ ├── Lexer.swift │ │ ├── Token+Extensions.swift │ │ └── Token.swift │ ├── Parser │ │ ├── AST+Extensions.swift │ │ ├── AST.swift │ │ └── Parser.swift │ ├── PascalInterpreter.h │ ├── Semantic analyzer │ │ ├── SemanticAnalyzer.swift │ │ ├── Symbol+Extensions.swift │ │ ├── Symbol.swift │ │ ├── SymbolTable.swift │ │ └── Visitor.swift │ └── Utils.swift └── PascalInterpreterTests │ ├── Info.plist │ ├── InterpreterTests.swift │ ├── LexerTests.swift │ ├── ParserTests.swift │ ├── SemanticAnalyzerTests.swift │ └── XCTestCase+FatalError.swift ├── Playground.playground ├── Contents.swift └── contents.xcplayground ├── README.md ├── SPI ├── SPI.xcodeproj │ └── project.pbxproj └── SPI │ └── main.swift ├── SwiftPascalInterpreter.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist └── grammar.md /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: macOS-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Install needed software 13 | run: gem install xcpretty 14 | - name: Run tests 15 | run: xcodebuild test -workspace SwiftPascalInterpreter.xcworkspace -scheme PascalInterpreter -destination 'platform=OS X,arch=x86_64' CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO | xcpretty -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | .build/ 41 | 42 | # CocoaPods 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | # Pods/ 49 | 50 | # Carthage 51 | # 52 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 53 | # Carthage/Checkouts 54 | 55 | Carthage/Build 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 63 | 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output 68 | /Examples/SPI 69 | /PascalInterpreter/.DS_Store 70 | .DS_Store 71 | -------------------------------------------------------------------------------- /Examples/factorial.pas: -------------------------------------------------------------------------------- 1 | program Main; 2 | var input, result: integer; 3 | 4 | function Factorial(number: Integer): Integer; 5 | begin 6 | if number > 1 then 7 | Factorial := number * Factorial(number-1) 8 | else 9 | Factorial := 1 10 | end; 11 | 12 | begin { Main } 13 | writeln('Factorial'); 14 | writeln(''); 15 | writeln('Enter number and press Enter'); 16 | read(input); 17 | result := Factorial(input); 18 | writeln(''); 19 | writeln(input,'! = ', result) 20 | end. { Main } -------------------------------------------------------------------------------- /Examples/game.pas: -------------------------------------------------------------------------------- 1 | Program Game; 2 | Var 3 | target, guess: Integer; 4 | 5 | Begin 6 | guess := 10; 7 | target := random(guess); 8 | 9 | Writeln('Guess a number between 0 and 10:'); 10 | Read(guess); 11 | 12 | repeat 13 | if guess > target then 14 | begin 15 | Writeln('Too much, try again'); 16 | Read(guess); 17 | end 18 | else 19 | begin 20 | Writeln('Too low, try again'); 21 | Read(guess); 22 | end 23 | until target = guess; 24 | Writeln('You won!') 25 | End. -------------------------------------------------------------------------------- /Examples/helloworld.pas: -------------------------------------------------------------------------------- 1 | Program HelloWorld; 2 | Begin 3 | Writeln('Hello World'); 4 | End. -------------------------------------------------------------------------------- /Examples/name.pas: -------------------------------------------------------------------------------- 1 | Program Name; 2 | Var name, surname: String; 3 | 4 | Begin 5 | write('Enter your name:'); 6 | read(name); 7 | write('Enter your surname:'); 8 | read(surname); 9 | writeln();{new line} 10 | writeln();{new line} 11 | writeln('Your full name is: ',name,' ',surname); 12 | End. -------------------------------------------------------------------------------- /Examples/numbers.pas: -------------------------------------------------------------------------------- 1 | Program Numbers; 2 | Var 3 | Num1, Num2, Sum : Integer; 4 | 5 | Begin {no semicolon} 6 | Write('Input number 1:'); 7 | Read(Num1); 8 | Write('Input number 2:'); 9 | Read(Num2); 10 | Sum := Num1 + Num2; {addition} 11 | Writeln(); 12 | Writeln(Num1,'+', Num2,'=', Sum); 13 | End. -------------------------------------------------------------------------------- /Images/cli.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igorkulman/SwiftPascalInterpreter/2d9cc03c48708418c08d4cb7a2ef389557eaa88f/Images/cli.gif -------------------------------------------------------------------------------- /Images/lexer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igorkulman/SwiftPascalInterpreter/2d9cc03c48708418c08d4cb7a2ef389557eaa88f/Images/lexer.png -------------------------------------------------------------------------------- /Images/parser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igorkulman/SwiftPascalInterpreter/2d9cc03c48708418c08d4cb7a2ef389557eaa88f/Images/parser.png -------------------------------------------------------------------------------- /Images/playground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igorkulman/SwiftPascalInterpreter/2d9cc03c48708418c08d4cb7a2ef389557eaa88f/Images/playground.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Igor Kulman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PascalInterpreter/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | excluded: 2 | PascalInterpreter/Utils.swift 3 | disabled_rules: 4 | - identifier_name 5 | - cyclomatic_complexity 6 | - line_length 7 | - function_body_length 8 | - type_body_length 9 | - file_length 10 | - large_tuple 11 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreter.xcodeproj/xcshareddata/xcschemes/PascalInterpreter.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 65 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 84 | 90 | 91 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreter/Extensions/Naming+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Naming+Extensions.swift 3 | // SwiftPascalInterpreter 4 | // 5 | // Created by Igor Kulman on 09/12/2017. 6 | // Copyright © 2017 Igor Kulman. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Lexer: CustomStringConvertible { 12 | public var description: String { 13 | return "Lexer" 14 | } 15 | } 16 | 17 | extension Interpreter: CustomStringConvertible { 18 | public var description: String { 19 | return "Interpreter" 20 | } 21 | } 22 | 23 | extension Parser: CustomStringConvertible { 24 | public var description: String { 25 | return "Parser" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreter/FatalError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FatalError.swift 3 | // SwiftPascalInterpreter 4 | // 5 | // Created by Igor Kulman on 10/12/2017. 6 | // Copyright © 2017 Igor Kulman. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Taken from https://medium.com/@marcosantadev/how-to-test-fatalerror-in-swift-e1be9ff11a29 13 | */ 14 | func fatalError(_ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) -> Never { 15 | FatalErrorUtil.fatalErrorClosure(message(), file, line) 16 | } 17 | 18 | struct FatalErrorUtil { 19 | 20 | // 1 21 | static var fatalErrorClosure: (String, StaticString, UInt) -> Never = defaultFatalErrorClosure 22 | 23 | // 2 24 | private static let defaultFatalErrorClosure = { Swift.fatalError($0, file: $1, line: $2) } 25 | 26 | // 3 27 | static func replaceFatalError(closure: @escaping (String, StaticString, UInt) -> Never) { 28 | fatalErrorClosure = closure 29 | } 30 | 31 | // 4 32 | static func restoreFatalError() { 33 | fatalErrorClosure = defaultFatalErrorClosure 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreter/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSHumanReadableCopyright 22 | Copyright © 2017 Igor Kulman. All rights reserved. 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreter/Interpreter/Arithmetics.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Arithmetics.swift 3 | // SwiftPascalInterpreter 4 | // 5 | // Created by Igor Kulman on 10/12/2017. 6 | // Copyright © 2017 Igor Kulman. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Binary operator representing DIV (integer division) 13 | */ 14 | infix operator ‖: MultiplicationPrecedence 15 | 16 | extension Number { 17 | 18 | /** 19 | Unary plus 20 | */ 21 | static prefix func + (left: Number) -> Number { 22 | switch left { 23 | case let .integer(value): 24 | return .integer(+value) 25 | case let .real(value): 26 | return .real(+value) 27 | } 28 | } 29 | 30 | /** 31 | Unary minus 32 | */ 33 | static prefix func - (left: Number) -> Number { 34 | switch left { 35 | case let .integer(value): 36 | return .integer(-value) 37 | case let .real(value): 38 | return .real(-value) 39 | } 40 | } 41 | 42 | /** 43 | Binary plus 44 | 45 | Int + Int -> Int 46 | Real + Real -> Real 47 | Int + Real -> Real 48 | */ 49 | static func + (left: Number, right: Number) -> Number { 50 | switch (left, right) { 51 | case let (.integer(left), .integer(right)): 52 | return .integer(left + right) 53 | case let (.real(left), .real(right)): 54 | return .real(left + right) 55 | case let (.integer(left), .real(right)): 56 | return .real(Double(left) + right) 57 | case let (.real(left), .integer(right)): 58 | return .real(left + Double(right)) 59 | } 60 | } 61 | 62 | /** 63 | Binary minus 64 | 65 | Int - Int -> Int 66 | Real - Real -> Real 67 | Int - Real -> Real 68 | */ 69 | static func - (left: Number, right: Number) -> Number { 70 | switch (left, right) { 71 | case let (.integer(left), .integer(right)): 72 | return .integer(left - right) 73 | case let (.real(left), .real(right)): 74 | return .real(left - right) 75 | case let (.integer(left), .real(right)): 76 | return .real(Double(left) - right) 77 | case let (.real(left), .integer(right)): 78 | return .real(left - Double(right)) 79 | } 80 | } 81 | 82 | /** 83 | Binary multiplication 84 | 85 | Int * Int -> Int 86 | Real * Real -> Real 87 | Int * Real -> Real 88 | */ 89 | static func * (left: Number, right: Number) -> Number { 90 | switch (left, right) { 91 | case let (.integer(left), .integer(right)): 92 | return .integer(left * right) 93 | case let (.real(left), .real(right)): 94 | return .real(left * right) 95 | case let (.integer(left), .real(right)): 96 | return .real(Double(left) * right) 97 | case let (.real(left), .integer(right)): 98 | return .real(left * Double(right)) 99 | } 100 | } 101 | 102 | /** 103 | Binary float division 104 | 105 | Int / Int -> Real 106 | Real / Real -> Real 107 | Int / Real -> Real 108 | Real / Int -> Real 109 | */ 110 | static func / (left: Number, right: Number) -> Number { 111 | switch (left, right) { 112 | case let (.integer(left), .integer(right)): 113 | return .real(Double(left) / Double(right)) 114 | case let (.real(left), .real(right)): 115 | return .real(left / right) 116 | case let (.integer(left), .real(right)): 117 | return .real(Double(left) / right) 118 | case let (.real(left), .integer(right)): 119 | return .real(left / Double(right)) 120 | } 121 | } 122 | 123 | /** 124 | Binary integer division 125 | 126 | Int ‖ Int -> Int 127 | Real ‖ Real -> error 128 | Int ‖ Real -> error 129 | Real ‖ Int -> error 130 | */ 131 | static func ‖ (left: Number, right: Number) -> Number { 132 | switch (left, right) { 133 | case let (.integer(left), .integer(right)): 134 | return .integer(left / right) 135 | default: 136 | fatalError("Integer division DIV can only be applied to two integers") 137 | } 138 | } 139 | 140 | static func < (left: Number, right: Number) -> Bool { 141 | switch (left, right) { 142 | case let (.integer(left), .integer(right)): 143 | return Double(left) < Double(right) 144 | case let (.real(left), .real(right)): 145 | return left < right 146 | case let (.integer(left), .real(right)): 147 | return Double(left) < right 148 | case let (.real(left), .integer(right)): 149 | return left < Double(right) 150 | } 151 | } 152 | 153 | static func > (left: Number, right: Number) -> Bool { 154 | switch (left, right) { 155 | case let (.integer(left), .integer(right)): 156 | return Double(left) > Double(right) 157 | case let (.real(left), .real(right)): 158 | return left > right 159 | case let (.integer(left), .real(right)): 160 | return Double(left) > right 161 | case let (.real(left), .integer(right)): 162 | return left > Double(right) 163 | } 164 | } 165 | } 166 | 167 | extension Value { 168 | static func < (lhs: Value, rhs: Value) -> Bool { 169 | switch (lhs, rhs) { 170 | case let (.number(left), .number(right)): 171 | return left < right 172 | default: 173 | fatalError("Cannot compare \(lhs) and \(rhs)") 174 | } 175 | } 176 | 177 | static func > (lhs: Value, rhs: Value) -> Bool { 178 | switch (lhs, rhs) { 179 | case let (.number(left), .number(right)): 180 | return left > right 181 | default: 182 | fatalError("Cannot compare \(lhs) and \(rhs)") 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreter/Interpreter/Frame.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Frame.swift 3 | // PascalInterpreter 4 | // 5 | // Created by Igor Kulman on 13/12/2017. 6 | // Copyright © 2017 Igor Kulman. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class Frame { 12 | var scalars: [String: Value] = [:] 13 | var arrays: [String: [Value]] = [:] 14 | let scope: ScopedSymbolTable 15 | let previousFrame: Frame? 16 | var returnValue: Value = .none 17 | 18 | init(scope: ScopedSymbolTable, previousFrame: Frame?) { 19 | self.scope = scope 20 | self.previousFrame = previousFrame 21 | 22 | for symbol in scope.symbols { 23 | guard let arraySymbol = symbol.value as? ArraySymbol, let type = arraySymbol.type as? BuiltInTypeSymbol else { 24 | continue 25 | } 26 | 27 | switch type { 28 | case .integer: 29 | arrays[symbol.key.uppercased()] = Array(repeating: .number(.integer(0)), count: arraySymbol.endIndex - arraySymbol.startIndex + 1) 30 | case .real: 31 | arrays[symbol.key.uppercased()] = Array(repeating: .number(.real(0)), count: arraySymbol.endIndex - arraySymbol.startIndex + 1) 32 | case .boolean: 33 | arrays[symbol.key.uppercased()] = Array(repeating: .boolean(false), count: arraySymbol.endIndex - arraySymbol.startIndex + 1) 34 | case .string: 35 | arrays[symbol.key.uppercased()] = Array(repeating: .string(""), count: arraySymbol.endIndex - arraySymbol.startIndex + 1) 36 | } 37 | } 38 | } 39 | 40 | func remove(variable: String) { 41 | scalars.removeValue(forKey: variable.uppercased()) 42 | } 43 | 44 | func set(variable: String, value: Value, index: Int) { 45 | if let symbol = scope.lookup(variable, currentScopeOnly: true), 46 | let arraySymbol = symbol as? ArraySymbol, 47 | let type = arraySymbol.type as? BuiltInTypeSymbol { 48 | 49 | let computedIndex = index - arraySymbol.startIndex 50 | switch (value, type) { 51 | case (.number(.integer), .integer): 52 | arrays[variable.uppercased()]![computedIndex] = value 53 | case let (.number(.integer(value)), .real): 54 | arrays[variable.uppercased()]![computedIndex] = .number(.real(Double(value))) 55 | case (.number(.real), .real): 56 | arrays[variable.uppercased()]![computedIndex] = value 57 | case (.boolean, .boolean): 58 | arrays[variable.uppercased()]![computedIndex] = value 59 | case (.string, .string): 60 | arrays[variable.uppercased()]![computedIndex] = value 61 | default: 62 | fatalError("Cannot assing \(value) to \(type)") 63 | } 64 | 65 | return 66 | } 67 | 68 | // previous scope, eg global 69 | previousFrame!.set(variable: variable, value: value, index: index) 70 | } 71 | 72 | func set(variable: String, value: Value) { 73 | // setting function return value 74 | if variable == scope.name && scope.level > 1 { 75 | returnValue = value 76 | return 77 | } 78 | 79 | // variable define in current scope (procedure declaraion, etc) 80 | if let symbol = scope.lookup(variable, currentScopeOnly: true), 81 | let variableSymbol = symbol as? VariableSymbol, 82 | let type = variableSymbol.type as? BuiltInTypeSymbol { 83 | 84 | switch (value, type) { 85 | case (.number(.integer), .integer): 86 | scalars[variable.uppercased()] = value 87 | case let (.number(.integer(value)), .real): 88 | scalars[variable.uppercased()] = .number(.real(Double(value))) 89 | case (.number(.real), .real): 90 | scalars[variable.uppercased()] = value 91 | case (.boolean, .boolean): 92 | scalars[variable.uppercased()] = value 93 | case (.string, .string): 94 | scalars[variable.uppercased()] = value 95 | default: 96 | fatalError("Cannot assing \(value) to \(type)") 97 | } 98 | 99 | return 100 | } 101 | 102 | // previous scope, eg global 103 | previousFrame!.set(variable: variable, value: value) 104 | } 105 | 106 | func get(variable: String) -> Value { 107 | // variable define in current scole (procedure declataion, etc) 108 | if scope.lookup(variable, currentScopeOnly: true) != nil { 109 | return scalars[variable.uppercased()]! 110 | } 111 | 112 | // previous scope, eg global 113 | return previousFrame!.get(variable: variable) 114 | } 115 | 116 | func get(variable: String, index: Int) -> Value { 117 | // variable define in current scole (procedure declataion, etc) 118 | if let symbol = scope.lookup(variable, currentScopeOnly: true), let arraySymbol = symbol as? ArraySymbol { 119 | let computedIndex = index - arraySymbol.startIndex 120 | return arrays[variable.uppercased()]![computedIndex] 121 | } 122 | 123 | // previous scope, eg global 124 | return previousFrame!.get(variable: variable, index: index) 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreter/Interpreter/Interpreter+Standard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Interpreter+Standard.swift 3 | // PascalInterpreter 4 | // 5 | // Created by Igor Kulman on 17/12/2017. 6 | // Copyright © 2017 Igor Kulman. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Interpreter { 12 | func callBuiltInProcedure(procedure: String, params: [AST], frame: Frame) -> Value { 13 | switch procedure.uppercased() { 14 | case "WRITE": 15 | write(params: params, newLine: false) 16 | return .none 17 | case "WRITELN": 18 | write(params: params, newLine: true) 19 | return .none 20 | case "READ": 21 | read(params: params, frame: frame) 22 | return .none 23 | case "READLN": 24 | read(params: params, frame: frame) 25 | return .none 26 | case "RANDOM": 27 | return random(params: params) 28 | case "LENGTH": 29 | return length(params: params, frame: frame) 30 | default: 31 | fatalError("Implement built in procedure \(procedure)") 32 | } 33 | } 34 | 35 | private func length(params: [AST], frame: Frame) -> Value { 36 | guard params.count == 1, let first = params.first, let variable = first as? Variable, let definition = frame.scope.lookup(variable.name) as? ArraySymbol else { 37 | fatalError("Length called with invalid parameters") 38 | } 39 | 40 | return .number(.integer(definition.endIndex - definition.startIndex + 1)) 41 | } 42 | 43 | private func random(params: [AST]) -> Value { 44 | guard params.count == 1, let first = params.first, case let .number(.integer(l)) = eval(node: first) else { 45 | fatalError("Random called with invalid parameters") 46 | } 47 | 48 | let value = Int.random(in: 0...l) 49 | return .number(.integer(value)) 50 | } 51 | 52 | private func write(params: [AST], newLine: Bool) { 53 | var s = "" 54 | for param in params { 55 | let value = eval(node: param) 56 | switch value { 57 | case let .boolean(value): 58 | s += value ? "TRUE" : "FALSE" 59 | case let .number(number): 60 | switch number { 61 | case let .integer(value): 62 | s += String(value) 63 | case let .real(value): 64 | s += String(value) 65 | } 66 | case let .string(value): 67 | s += value 68 | case .none: 69 | fatalError("Cannot use WRITE with expression without a value") 70 | } 71 | } 72 | if newLine { 73 | print(s) 74 | } else { 75 | print(s, terminator: "") 76 | } 77 | } 78 | 79 | private func read(params: [AST], frame: Frame) { 80 | guard let line = readLine() else { 81 | fatalError("Empty input") 82 | } 83 | 84 | let parts = line.components(separatedBy: CharacterSet.whitespaces) 85 | for i in 0 ... params.count - 1 { 86 | let param = params[i] 87 | guard let variable = param as? Variable else { 88 | fatalError("READ parameter must be a variable") 89 | } 90 | guard let symbol = frame.scope.lookup(variable.name), let variableSymbol = symbol as? VariableSymbol, let type = variableSymbol.type as? BuiltInTypeSymbol else { 91 | fatalError("Symbol(variable) not found '\(variable.name)'") 92 | } 93 | switch type { 94 | case .integer: 95 | frame.set(variable: variable.name, value: .number(.integer(Int(parts[i])!))) 96 | case .real: 97 | frame.set(variable: variable.name, value: .number(.real(Double(parts[i])!))) 98 | case .boolean: 99 | frame.set(variable: variable.name, value: .boolean(Bool(parts[i])!)) 100 | case .string: 101 | frame.set(variable: variable.name, value: .string((parts[i]))) 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreter/Interpreter/Interpreter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Interpreter.swift 3 | // SwiftPascalInterpreter 4 | // 5 | // Created by Igor Kulman on 06/12/2017. 6 | // Copyright © 2017 Igor Kulman. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class Interpreter { 12 | private var callStack = Stack() 13 | private let tree: AST 14 | private let scopes: [String: ScopedSymbolTable] 15 | 16 | public init(_ text: String) { 17 | let parser = Parser(text) 18 | tree = parser.parse() 19 | let semanticAnalyzer = SemanticAnalyzer() 20 | scopes = semanticAnalyzer.analyze(node: tree) 21 | } 22 | 23 | @discardableResult func eval(node: AST) -> Value { 24 | switch node { 25 | case let number as Number: 26 | return eval(number: number) 27 | case let string as String: 28 | return eval(string: string) 29 | case let unaryOperation as UnaryOperation: 30 | return eval(unaryOperation: unaryOperation) 31 | case let binaryOperation as BinaryOperation: 32 | return eval(binaryOperation: binaryOperation) 33 | case let compound as Compound: 34 | return eval(compound: compound) 35 | case let assignment as Assignment: 36 | return eval(assignment: assignment) 37 | case let variable as Variable: 38 | return eval(variable: variable) 39 | case let block as Block: 40 | return eval(block: block) 41 | case let program as Program: 42 | return eval(program: program) 43 | case let call as FunctionCall: 44 | return eval(call: call) 45 | case let condition as Condition: 46 | return eval(condition: condition) 47 | case let ifElse as IfElse: 48 | return eval(ifElse: ifElse) 49 | case let repeatUntil as RepeatUntil: 50 | return eval(repeatUntil: repeatUntil) 51 | case let whileLoop as While: 52 | return eval(whileLoop: whileLoop) 53 | case let forLoop as For: 54 | return eval(forLoop: forLoop) 55 | default: 56 | return .none 57 | } 58 | } 59 | 60 | func eval(number: Number) -> Value { 61 | return .number(number) 62 | } 63 | 64 | func eval(string: String) -> Value { 65 | return .string(string) 66 | } 67 | 68 | func eval(unaryOperation: UnaryOperation) -> Value { 69 | guard case let .number(result) = eval(node: unaryOperation.operand) else { 70 | fatalError("Cannot use unary \(unaryOperation.operation) on non number") 71 | } 72 | 73 | switch unaryOperation.operation { 74 | case .plus: 75 | return .number(+result) 76 | case .minus: 77 | return .number(-result) 78 | } 79 | } 80 | 81 | func eval(binaryOperation: BinaryOperation) -> Value { 82 | guard case let .number(leftResult) = eval(node: binaryOperation.left), case let .number(rightResult) = eval(node: binaryOperation.right) else { 83 | fatalError("Cannot use binary \(binaryOperation.operation) on non numbers") 84 | } 85 | 86 | switch binaryOperation.operation { 87 | case .plus: 88 | return .number(leftResult + rightResult) 89 | case .minus: 90 | return .number(leftResult - rightResult) 91 | case .mult: 92 | return .number(leftResult * rightResult) 93 | case .integerDiv: 94 | return .number(leftResult ‖ rightResult) 95 | case .floatDiv: 96 | return .number(leftResult / rightResult) 97 | } 98 | } 99 | 100 | func eval(compound: Compound) -> Value { 101 | for child in compound.children { 102 | eval(node: child) 103 | } 104 | return .none 105 | } 106 | 107 | func eval(assignment: Assignment) -> Value { 108 | guard let currentFrame = callStack.peek() else { 109 | fatalError("No call stack frame") 110 | } 111 | 112 | switch assignment.left { 113 | case let array as ArrayVariable: 114 | guard case let .number(.integer(index)) = eval(node: array.index) else { 115 | fatalError("Cannot use non-Integer index with array '\(array.name)'") 116 | } 117 | currentFrame.set(variable: assignment.left.name, value: eval(node: assignment.right), index: index) 118 | default: 119 | currentFrame.set(variable: assignment.left.name, value: eval(node: assignment.right)) 120 | } 121 | 122 | return .none 123 | } 124 | 125 | func eval(variable: Variable) -> Value { 126 | guard let currentFrame = callStack.peek() else { 127 | fatalError("No call stack frame") 128 | } 129 | 130 | switch variable { 131 | case let array as ArrayVariable: 132 | guard case let .number(.integer(index)) = eval(node: array.index) else { 133 | fatalError("Cannot use non-Integer index with array '\(array.name)'") 134 | } 135 | return currentFrame.get(variable: array.name, index: index) 136 | default: 137 | return currentFrame.get(variable: variable.name) 138 | } 139 | } 140 | 141 | func eval(block: Block) -> Value { 142 | for declaration in block.declarations { 143 | eval(node: declaration) 144 | } 145 | 146 | return eval(node: block.compound) 147 | } 148 | 149 | func eval(program: Program) -> Value { 150 | let frame = Frame(scope: scopes["global"]!, previousFrame: nil) 151 | callStack.push(frame) 152 | return eval(node: program.block) 153 | } 154 | 155 | func eval(call: FunctionCall) -> Value { 156 | let current = callStack.peek()! 157 | 158 | guard let symbol = current.scope.lookup(call.name), symbol is ProcedureSymbol else { 159 | return callBuiltInProcedure(procedure: call.name, params: call.actualParameters, frame: current) 160 | } 161 | 162 | return callFunction(function: call.name, params: call.actualParameters, frame: current) 163 | } 164 | 165 | func eval(condition: Condition) -> Value { 166 | let left = eval(node: condition.leftSide) 167 | let right = eval(node: condition.rightSide) 168 | 169 | switch condition.type { 170 | case .equals: 171 | return .boolean(left == right) 172 | case .greaterThan: 173 | return .boolean(left > right) 174 | case .lessThan: 175 | return .boolean(left < right) 176 | } 177 | } 178 | 179 | func eval(ifElse: IfElse) -> Value { 180 | guard case let .boolean(value) = eval(condition: ifElse.condition) else { 181 | fatalError("Condition not boolean") 182 | } 183 | 184 | if value { 185 | return eval(node: ifElse.trueExpression) 186 | } else if let falseExpression = ifElse.falseExpression { 187 | return eval(node: falseExpression) 188 | } else { 189 | return .none 190 | } 191 | } 192 | 193 | func eval(repeatUntil: RepeatUntil) -> Value { 194 | eval(node: repeatUntil.statement) 195 | var value = eval(condition: repeatUntil.condition) 196 | 197 | while case .boolean(false) = value { 198 | eval(node: repeatUntil.statement) 199 | value = eval(condition: repeatUntil.condition) 200 | } 201 | 202 | return .none 203 | } 204 | 205 | func eval(whileLoop: While) -> Value { 206 | while case .boolean(true) = eval(condition: whileLoop.condition) { 207 | eval(node: whileLoop.statement) 208 | } 209 | 210 | return .none 211 | } 212 | 213 | func eval(forLoop: For) -> Value { 214 | guard let currentFrame = callStack.peek() else { 215 | fatalError("No call stack frame") 216 | } 217 | 218 | guard case let .number(.integer(start)) = eval(node: forLoop.startValue), case let .number(.integer(end)) = eval(node: forLoop.endValue) else { 219 | fatalError("Cannot do a for loop on non integer values") 220 | } 221 | 222 | for i in start ... end { 223 | currentFrame.set(variable: forLoop.variable.name, value: .number(.integer(i))) 224 | eval(node: forLoop.statement) 225 | } 226 | currentFrame.remove(variable: forLoop.variable.name) 227 | 228 | return .none 229 | } 230 | 231 | private func callFunction(function: String, params: [AST], frame: Frame) -> Value { 232 | guard let symbol = frame.scope.lookup(function), let procedureSymbol = symbol as? ProcedureSymbol else { 233 | fatalError("Symbol(procedure) not found '\(function)'") 234 | } 235 | 236 | let newScope = frame.scope.level == 1 ? scopes[function]! : ScopedSymbolTable(name: function, level: scopes[function]!.level + 1, enclosingScope: scopes[function]!) 237 | let newFrame = Frame(scope: newScope, previousFrame: frame) 238 | 239 | if procedureSymbol.params.count > 0 { 240 | 241 | for i in 0 ... procedureSymbol.params.count - 1 { 242 | let evaluated = eval(node: params[i]) 243 | newFrame.set(variable: procedureSymbol.params[i].name, value: evaluated) 244 | } 245 | } 246 | 247 | callStack.push(newFrame) 248 | eval(node: procedureSymbol.body.block) 249 | callStack.pop() 250 | return newFrame.returnValue 251 | } 252 | 253 | public func interpret() { 254 | eval(node: tree) 255 | } 256 | 257 | func getState() -> ([String: Value], [String: [Value]]) { 258 | return (callStack.peek()!.scalars, callStack.peek()!.arrays) 259 | } 260 | 261 | public func printState() { 262 | print("Final interpreter memory state (\(callStack.peek()!.scope.name)):") 263 | print(callStack.peek()!.scalars) 264 | print(callStack.peek()!.arrays) 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreter/Interpreter/Stack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Stack.swift 3 | // PascalInterpreter 4 | // 5 | // Created by Igor Kulman on 13/12/2017. 6 | // Copyright © 2017 Igor Kulman. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Stack { 12 | fileprivate var array: [Element] = [] 13 | 14 | mutating func push(_ element: Element) { 15 | array.append(element) 16 | } 17 | 18 | @discardableResult mutating func pop() -> Element? { 19 | return array.popLast() 20 | } 21 | 22 | func peek() -> Element? { 23 | return array.last 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreter/Interpreter/Value+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Value+Extensions.swift 3 | // PascalInterpreter 4 | // 5 | // Created by Igor Kulman on 25/12/2017. 6 | // Copyright © 2017 Igor Kulman. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Value: Equatable { 12 | static func == (lhs: Value, rhs: Value) -> Bool { 13 | switch (lhs, rhs) { 14 | case let (.number(left), .number(right)): 15 | return left == right 16 | case let (.boolean(left), .boolean(right)): 17 | return left == right 18 | case let (.string(left), .string(right)): 19 | return left == right 20 | default: 21 | return false 22 | } 23 | } 24 | } 25 | 26 | extension Value: CustomStringConvertible { 27 | public var description: String { 28 | switch self { 29 | case .none: 30 | return "NIL" 31 | case let .boolean(value): 32 | return "BOOLEAN(\(value))" 33 | case let .string(value): 34 | return "STRING(\(value))" 35 | case let .number(number): 36 | switch number { 37 | case let .integer(value): 38 | return "INTEGER(\(value))" 39 | case let .real(value): 40 | return "REAL(\(value))" 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreter/Interpreter/Value.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Result.swift 3 | // SwiftPascalInterpreter 4 | // 5 | // Created by Igor Kulman on 10/12/2017. 6 | // Copyright © 2017 Igor Kulman. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum Value { 12 | case none 13 | case number(Number) 14 | case boolean(Bool) 15 | case string(String) 16 | } 17 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreter/Lexer/Lexer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Lexer.swift 3 | // SwiftPascalInterpreter 4 | // 5 | // Created by Igor Kulman on 07/12/2017. 6 | // Copyright © 2017 Igor Kulman. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Basic lexical analyzer converting program text into tokens 13 | */ 14 | public class Lexer { 15 | 16 | // MARK: - Fields 17 | 18 | private let text: String 19 | private var currentPosition: Int 20 | private var currentCharacter: Character? 21 | private var isStringStart = false 22 | private var wasStringLast = false 23 | 24 | // MARK: - Constants 25 | 26 | private let keywords: [String: Token] = [ 27 | "PROGRAM": .program, 28 | "VAR": .varDef, 29 | "DIV": .operation(.integerDiv), 30 | "INTEGER": .type(.integer), 31 | "REAL": .type(.real), 32 | "BOOLEAN": .type(.boolean), 33 | "STRING": .type(.string), 34 | "BEGIN": .begin, 35 | "END": .end, 36 | "PROCEDURE": .procedure, 37 | "TRUE": .constant(.boolean(true)), 38 | "FALSE": .constant(.boolean(false)), 39 | "IF": .if, 40 | "ELSE": .else, 41 | "THEN": .then, 42 | "FUNCTION": .function, 43 | "REPEAT": .repeat, 44 | "UNTIL": .until, 45 | "FOR": .for, 46 | "TO": .to, 47 | "DO": .do, 48 | "WHILE": .while, 49 | "OF": .of, 50 | "ARRAY": .array 51 | ] 52 | 53 | public init(_ text: String) { 54 | self.text = text 55 | currentPosition = 0 56 | currentCharacter = text.isEmpty ? nil : text[text.startIndex] 57 | } 58 | 59 | // MARK: - Stream helpers 60 | 61 | /** 62 | Skips all the whitespace 63 | */ 64 | private func skipWhitestace() { 65 | while let character = currentCharacter, CharacterSet.whitespacesAndNewlines.contains(character.unicodeScalars.first!) { 66 | advance() 67 | } 68 | } 69 | 70 | /** 71 | Skips all the commented text 72 | */ 73 | private func skipComments() { 74 | while let character = currentCharacter, character != "}" { 75 | advance() 76 | } 77 | advance() // closing bracket 78 | } 79 | 80 | /** 81 | Advances by one character forward, sets the current character (if still any available) 82 | */ 83 | private func advance() { 84 | currentPosition += 1 85 | guard currentPosition < text.count else { 86 | currentCharacter = nil 87 | return 88 | } 89 | 90 | currentCharacter = text[text.index(text.startIndex, offsetBy: currentPosition)] 91 | } 92 | 93 | /** 94 | Returns the next character without advancing 95 | 96 | Returns: Character if not at the end of the text, nil otherwise 97 | */ 98 | private func peek() -> Character? { 99 | let peekPosition = currentPosition + 1 100 | 101 | guard peekPosition < text.count else { 102 | return nil 103 | } 104 | 105 | return text[text.index(text.startIndex, offsetBy: peekPosition)] 106 | } 107 | 108 | // MARK: - Parsing helpers 109 | 110 | /** 111 | Reads a possible multidigit integer starting at the current position 112 | */ 113 | private func number() -> Token { 114 | var lexem = "" 115 | while let character = currentCharacter, CharacterSet.decimalDigits.contains(character.unicodeScalars.first!) { 116 | lexem += String(character) 117 | advance() 118 | } 119 | 120 | if let character = currentCharacter, character == ".", text[text.index(text.startIndex, offsetBy: currentPosition + 1)] != "." { 121 | lexem += "." 122 | advance() 123 | 124 | while let character = currentCharacter, CharacterSet.decimalDigits.contains(character.unicodeScalars.first!) { 125 | lexem += String(character) 126 | advance() 127 | } 128 | 129 | return .constant(.real(Double(lexem)!)) 130 | } 131 | 132 | return .constant(.integer(Int(lexem)!)) 133 | } 134 | 135 | /** 136 | Handles identifies and reserved keywords 137 | 138 | Returns: Token 139 | */ 140 | private func id() -> Token { 141 | var lexem = "" 142 | while let character = currentCharacter, CharacterSet.alphanumerics.contains(character.unicodeScalars.first!) { 143 | lexem += String(character) 144 | advance() 145 | } 146 | 147 | if let token = keywords[lexem.uppercased()] { 148 | return token 149 | } 150 | 151 | return .id(lexem) 152 | } 153 | 154 | private func string() -> Token { 155 | var lexem = "" 156 | while let character = currentCharacter, character != "'" { 157 | lexem += String(character) 158 | advance() 159 | } 160 | return .constant(.string(lexem)) 161 | } 162 | 163 | // MARK: - Public methods 164 | 165 | /** 166 | Reads the text at current position and returns next token 167 | 168 | - Returns: Next token in text 169 | */ 170 | public func getNextToken() -> Token { 171 | 172 | while let currentCharacter = currentCharacter { 173 | 174 | if wasStringLast { 175 | wasStringLast = false 176 | if currentCharacter == "'" { 177 | advance() 178 | } 179 | return .apostrophe 180 | } 181 | 182 | if isStringStart { 183 | wasStringLast = true 184 | isStringStart = false 185 | return string() 186 | } 187 | 188 | if CharacterSet.whitespacesAndNewlines.contains(currentCharacter.unicodeScalars.first!) { 189 | skipWhitestace() 190 | continue 191 | } 192 | 193 | if currentCharacter == "{" { 194 | advance() 195 | skipComments() 196 | continue 197 | } 198 | 199 | if currentCharacter == "'" { 200 | advance() 201 | isStringStart = !isStringStart 202 | return .apostrophe 203 | } 204 | 205 | // if the character is a digit, convert it to int, create an integer token and move position 206 | if CharacterSet.decimalDigits.contains(currentCharacter.unicodeScalars.first!) { 207 | return number() 208 | } 209 | 210 | if CharacterSet.alphanumerics.contains(currentCharacter.unicodeScalars.first!) { 211 | return id() 212 | } 213 | 214 | if currentCharacter == ":" && peek() == "=" { 215 | advance() 216 | advance() 217 | return .assign 218 | } 219 | 220 | if currentCharacter == "." { 221 | advance() 222 | return .dot 223 | } 224 | 225 | if currentCharacter == "," { 226 | advance() 227 | return .coma 228 | } 229 | 230 | if currentCharacter == ";" { 231 | advance() 232 | return .semi 233 | } 234 | 235 | if currentCharacter == ":" { 236 | advance() 237 | return .colon 238 | } 239 | 240 | if currentCharacter == "+" { 241 | advance() 242 | return .operation(.plus) 243 | } 244 | 245 | if currentCharacter == "-" { 246 | advance() 247 | return .operation(.minus) 248 | } 249 | 250 | if currentCharacter == "*" { 251 | advance() 252 | return .operation(.mult) 253 | } 254 | 255 | if currentCharacter == "/" { 256 | advance() 257 | return .operation(.floatDiv) 258 | } 259 | 260 | if currentCharacter == "(" { 261 | advance() 262 | return .parenthesis(.left) 263 | } 264 | 265 | if currentCharacter == ")" { 266 | advance() 267 | return .parenthesis(.right) 268 | } 269 | 270 | if currentCharacter == "[" { 271 | advance() 272 | return .bracket(.left) 273 | } 274 | 275 | if currentCharacter == "]" { 276 | advance() 277 | return .bracket(.right) 278 | } 279 | 280 | if currentCharacter == "=" { 281 | advance() 282 | return .equals 283 | } 284 | 285 | if currentCharacter == ">" { 286 | advance() 287 | return .greaterThan 288 | } 289 | 290 | if currentCharacter == "<" { 291 | advance() 292 | return .lessThan 293 | } 294 | 295 | fatalError("Unrecognized character \(currentCharacter) at position \(currentPosition)") 296 | } 297 | 298 | return .eof 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreter/Lexer/Token+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Token+Extensions.swift 3 | // SwiftPascalInterpreter 4 | // 5 | // Created by Igor Kulman on 09/12/2017. 6 | // Copyright © 2017 Igor Kulman. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Token: Equatable { 12 | public static func == (lhs: Token, rhs: Token) -> Bool { 13 | switch (lhs, rhs) { 14 | case let (.operation(left), .operation(right)): 15 | return left == right 16 | case (.eof, .eof): 17 | return true 18 | case let (.type(left), .type(right)): 19 | return left == right 20 | case let (.parenthesis(left), .parenthesis(right)): 21 | return left == right 22 | case (.dot, .dot): 23 | return true 24 | case (.semi, .semi): 25 | return true 26 | case (.assign, .assign): 27 | return true 28 | case (.begin, .begin): 29 | return true 30 | case (.end, .end): 31 | return true 32 | case let (.id(left), .id(right)): 33 | return left == right 34 | case (.program, .program): 35 | return true 36 | case (.varDef, .varDef): 37 | return true 38 | case (.colon, .colon): 39 | return true 40 | case (.coma, .coma): 41 | return true 42 | case let (.constant(left), .constant(right)): 43 | return left == right 44 | case (.procedure, .procedure): 45 | return true 46 | case (.if, .if): 47 | return true 48 | case (.else, .else): 49 | return true 50 | case (.equals, .equals): 51 | return true 52 | case (.then, .then): 53 | return true 54 | case (.lessThan, .lessThan): 55 | return true 56 | case (.greaterThan, .greaterThan): 57 | return true 58 | case (.function, .function): 59 | return true 60 | case (.apostrophe, .apostrophe): 61 | return true 62 | case (.repeat, .repeat): 63 | return true 64 | case (.until, .until): 65 | return true 66 | case (.for, .for): 67 | return true 68 | case (.to, .to): 69 | return true 70 | case (.do, .do): 71 | return true 72 | case (.while, .while): 73 | return true 74 | case (.of, .of): 75 | return true 76 | case (.array, .array): 77 | return true 78 | case let (.bracket(left), .bracket(right)): 79 | return left == right 80 | default: 81 | return false 82 | } 83 | } 84 | } 85 | 86 | extension Constant: Equatable { 87 | public static func == (lhs: Constant, rhs: Constant) -> Bool { 88 | switch (lhs, rhs) { 89 | case let (.integer(left), .integer(right)): 90 | return left == right 91 | case let (.real(left), .real(right)): 92 | return left == right 93 | case let (.boolean(left), .boolean(right)): 94 | return left == right 95 | case let (.string(left), .string(right)): 96 | return left == right 97 | default: 98 | return false 99 | } 100 | } 101 | } 102 | 103 | extension Operation: CustomStringConvertible { 104 | public var description: String { 105 | switch self { 106 | case .minus: 107 | return "MINUS" 108 | case .plus: 109 | return "PLUS" 110 | case .mult: 111 | return "MULT" 112 | case .integerDiv: 113 | return "DIV" 114 | case .floatDiv: 115 | return "FDIV" 116 | } 117 | } 118 | } 119 | 120 | extension Type: CustomStringConvertible { 121 | public var description: String { 122 | switch self { 123 | case .integer: 124 | return "INTEGER" 125 | case .real: 126 | return "REAL" 127 | case .boolean: 128 | return "BOOLEAN" 129 | case .string: 130 | return "STRING" 131 | } 132 | } 133 | } 134 | 135 | extension Parenthesis: CustomStringConvertible { 136 | public var description: String { 137 | switch self { 138 | case .left: 139 | return "LPAREN" 140 | case .right: 141 | return "RPAREN" 142 | } 143 | } 144 | } 145 | 146 | extension Bracket: CustomStringConvertible { 147 | public var description: String { 148 | switch self { 149 | case .left: 150 | return "LBRACKET" 151 | case .right: 152 | return "RBRACKET" 153 | } 154 | } 155 | } 156 | 157 | extension Constant: CustomStringConvertible { 158 | public var description: String { 159 | switch self { 160 | case let .integer(value): 161 | return "INTEGER_CONST(\(value))" 162 | case let .real(value): 163 | return "REAL_CONST(\(value))" 164 | case let .boolean(value): 165 | return "BOOLEAN_CONST(\(value))" 166 | case let .string(value): 167 | return "STRING_CONST(\(value))" 168 | } 169 | } 170 | } 171 | 172 | extension Token: CustomStringConvertible { 173 | public var description: String { 174 | switch self { 175 | case .eof: 176 | return "EOF" 177 | case let .operation(operation): 178 | return operation.description 179 | case .begin: 180 | return "BEGIN" 181 | case .end: 182 | return "END" 183 | case let .id(value): 184 | return "ID(\(value))" 185 | case .assign: 186 | return "ASSIGN" 187 | case .semi: 188 | return "SEMI" 189 | case .dot: 190 | return "DOT" 191 | case .program: 192 | return "PROGRAM" 193 | case .varDef: 194 | return "VAR" 195 | case .colon: 196 | return ":" 197 | case .coma: 198 | return "," 199 | case let .parenthesis(paren): 200 | return paren.description 201 | case let .type(type): 202 | return type.description 203 | case let .constant(constant): 204 | return constant.description 205 | case .procedure: 206 | return "PROCEDURE" 207 | case .if: 208 | return "IF" 209 | case .else: 210 | return "ELSE" 211 | case .then: 212 | return "THEN" 213 | case .equals: 214 | return "EQ" 215 | case .lessThan: 216 | return "LT" 217 | case .greaterThan: 218 | return "GT" 219 | case .function: 220 | return "FUNCTION" 221 | case .apostrophe: 222 | return "APOSTROPHE" 223 | case .repeat: 224 | return "REPEAT" 225 | case .until: 226 | return "UNTIL" 227 | case .for: 228 | return "FOR" 229 | case .to: 230 | return "TO" 231 | case .do: 232 | return "DO" 233 | case .while: 234 | return "WHILE" 235 | case .array: 236 | return "ARRAY" 237 | case .of: 238 | return "OF" 239 | case let .bracket(bracket): 240 | return bracket.description 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreter/Lexer/Token.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Token.swift 3 | // SwiftPascalInterpreter 4 | // 5 | // Created by Igor Kulman on 06/12/2017. 6 | // Copyright © 2017 Igor Kulman. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum Operation { 12 | case plus 13 | case minus 14 | case mult 15 | case integerDiv 16 | case floatDiv 17 | } 18 | 19 | public enum Parenthesis { 20 | case left 21 | case right 22 | } 23 | 24 | public enum Bracket { 25 | case left 26 | case right 27 | } 28 | 29 | public enum Constant { 30 | case integer(Int) 31 | case real(Double) 32 | case boolean(Bool) 33 | case string(String) 34 | } 35 | 36 | public enum Type { 37 | case integer 38 | case real 39 | case boolean 40 | case string 41 | } 42 | 43 | public enum Token { 44 | case operation(Operation) 45 | case eof 46 | case parenthesis(Parenthesis) 47 | case begin 48 | case end 49 | case id(String) 50 | case dot 51 | case assign 52 | case semi 53 | case program 54 | case varDef 55 | case colon 56 | case coma 57 | case type(Type) 58 | case constant(Constant) 59 | case procedure 60 | case `if` 61 | case `else` 62 | case then 63 | case equals 64 | case lessThan 65 | case greaterThan 66 | case function 67 | case apostrophe 68 | case `repeat` 69 | case until 70 | case `for` 71 | case to 72 | case `do` 73 | case `while` 74 | case array 75 | case of 76 | case bracket(Bracket) 77 | } 78 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreter/Parser/AST+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AST+Extensions.swift 3 | // SwiftPascalInterpreter 4 | // 5 | // Created by Igor Kulman on 09/12/2017. 6 | // Copyright © 2017 Igor Kulman. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension UnaryOperationType: CustomStringConvertible { 12 | public var description: String { 13 | switch self { 14 | case .minus: 15 | return "-" 16 | case .plus: 17 | return "+" 18 | } 19 | } 20 | } 21 | 22 | extension Number: CustomStringConvertible { 23 | public var description: String { 24 | switch self { 25 | case let .integer(value): 26 | return "INTEGER(\(value))" 27 | case let .real(value): 28 | return "REAL(\(value))" 29 | } 30 | } 31 | } 32 | 33 | extension Number: Equatable { 34 | public static func == (lhs: Number, rhs: Number) -> Bool { 35 | switch (lhs, rhs) { 36 | case let (.integer(left), .integer(right)): 37 | return left == right 38 | case let (.real(left), .real(right)): 39 | return left == right 40 | case let (.real(left), .integer(right)): 41 | return left == Double(right) 42 | case let (.integer(left), .real(right)): 43 | return Double(left) == right 44 | } 45 | } 46 | } 47 | 48 | extension BinaryOperationType: CustomStringConvertible { 49 | public var description: String { 50 | switch self { 51 | case .minus: 52 | return "-" 53 | case .plus: 54 | return "+" 55 | case .mult: 56 | return "*" 57 | case .floatDiv: 58 | return "//" 59 | case .integerDiv: 60 | return "/" 61 | } 62 | } 63 | } 64 | 65 | extension ConditionType: CustomStringConvertible { 66 | public var description: String { 67 | switch self { 68 | case .equals: 69 | return "=" 70 | case .greaterThan: 71 | return ">" 72 | case .lessThan: 73 | return "<" 74 | } 75 | } 76 | } 77 | 78 | extension AST { 79 | var value: String { 80 | switch self { 81 | case let number as Number: 82 | return "\(number)" 83 | case let unaryOperation as UnaryOperation: 84 | return "u\(unaryOperation.operation)" 85 | case let binaryOperation as BinaryOperation: 86 | return "\(binaryOperation.operation)" 87 | case is NoOp: 88 | return "noOp" 89 | case let variable as Variable: 90 | return variable.name 91 | case is Compound: 92 | return "compound" 93 | case is Assignment: 94 | return ":=" 95 | case is Block: 96 | return "block" 97 | case is VariableDeclaration: 98 | return "var" 99 | case let type as VariableType: 100 | return "\(type.type)" 101 | case let program as Program: 102 | return program.name 103 | case let function as Function: 104 | return "\(function.name):\(function.returnType.type)" 105 | case let procedure as Procedure: 106 | return procedure.name 107 | case let param as Param: 108 | return "param(\(param.name))" 109 | case let call as FunctionCall: 110 | return "\(call.name)()" 111 | case is IfElse: 112 | return "IF" 113 | case let condition as Condition: 114 | return condition.type.description 115 | case let string as String: 116 | return string 117 | case let boolean as Bool: 118 | return boolean ? "TRUE" : "FALSE" 119 | case is RepeatUntil: 120 | return "REPEAT" 121 | case is For: 122 | return "FOR" 123 | case is While: 124 | return "WHILE" 125 | default: 126 | fatalError("Missed AST case \(self)") 127 | } 128 | } 129 | 130 | var children: [AST] { 131 | switch self { 132 | case is Number: 133 | return [] 134 | case let unaryOperation as UnaryOperation: 135 | return [unaryOperation.operand] 136 | case let binaryOperation as BinaryOperation: 137 | return [binaryOperation.left, binaryOperation.right] 138 | case is NoOp: 139 | return [] 140 | case is Variable: 141 | return [] 142 | case let compound as Compound: 143 | return compound.children 144 | case let assignment as Assignment: 145 | return [assignment.left, assignment.right] 146 | case let block as Block: 147 | var nodes: [AST] = [] 148 | for declaration in block.declarations { 149 | nodes.append(declaration) 150 | } 151 | nodes.append(block.compound) 152 | return nodes 153 | case let variableDeclaration as VariableDeclaration: 154 | return [variableDeclaration.variable, variableDeclaration.type] 155 | case is VariableType: 156 | return [] 157 | case let program as Program: 158 | return [program.block] 159 | case let function as Function: 160 | var nodes: [AST] = [] 161 | for param in function.params { 162 | nodes.append(param) 163 | } 164 | nodes.append(function.block) 165 | return nodes 166 | case let procedure as Procedure: 167 | var nodes: [AST] = [] 168 | for param in procedure.params { 169 | nodes.append(param) 170 | } 171 | nodes.append(procedure.block) 172 | return nodes 173 | case let param as Param: 174 | return [param.type] 175 | case let call as FunctionCall: 176 | return call.actualParameters 177 | case let ifelse as IfElse: 178 | if let falseExpression = ifelse.falseExpression { 179 | return [ifelse.condition, ifelse.trueExpression, falseExpression] 180 | } 181 | return [ifelse.condition, ifelse.trueExpression] 182 | case let condition as Condition: 183 | return [condition.leftSide, condition.rightSide] 184 | case is String: 185 | return [] 186 | case is Bool: 187 | return [] 188 | case let repeatUntil as RepeatUntil: 189 | return [repeatUntil.condition, repeatUntil.statement] 190 | case let whileLoop as While: 191 | return [whileLoop.condition, whileLoop.statement] 192 | case let forLoop as For: 193 | return [forLoop.variable, forLoop.startValue, forLoop.endValue, forLoop.statement] 194 | default: 195 | fatalError("Missed AST case \(self)") 196 | } 197 | } 198 | 199 | func treeLines(_ nodeIndent: String = "", _ childIndent: String = "") -> [String] { 200 | return [nodeIndent + value] 201 | + children.enumerated().map { ($0 < children.count - 1, $1) } 202 | .flatMap { $0 ? $1.treeLines("┣╸", "┃ ") : $1.treeLines("┗╸", " ") } 203 | .map { childIndent + $0 } 204 | } 205 | 206 | public func printTree() { print(treeLines().joined(separator: "\n")) } 207 | } 208 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreter/Parser/AST.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AST.swift 3 | // SwiftPascalInterpreter 4 | // 5 | // Created by Igor Kulman on 07/12/2017. 6 | // Copyright © 2017 Igor Kulman. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum BinaryOperationType { 12 | case plus 13 | case minus 14 | case mult 15 | case floatDiv 16 | case integerDiv 17 | } 18 | 19 | public enum UnaryOperationType { 20 | case plus 21 | case minus 22 | } 23 | 24 | public enum ConditionType { 25 | case equals 26 | case lessThan 27 | case greaterThan 28 | } 29 | 30 | public enum Number: AST { 31 | case integer(Int) 32 | case real(Double) 33 | } 34 | 35 | extension String: AST { 36 | } 37 | 38 | extension Bool: AST { 39 | } 40 | 41 | public protocol AST { 42 | } 43 | 44 | public protocol Declaration: AST { 45 | } 46 | 47 | class UnaryOperation: AST { 48 | let operation: UnaryOperationType 49 | let operand: AST 50 | 51 | init(operation: UnaryOperationType, operand: AST) { 52 | self.operation = operation 53 | self.operand = operand 54 | } 55 | } 56 | 57 | class BinaryOperation: AST { 58 | let left: AST 59 | let operation: BinaryOperationType 60 | let right: AST 61 | 62 | init(left: AST, operation: BinaryOperationType, right: AST) { 63 | self.left = left 64 | self.operation = operation 65 | self.right = right 66 | } 67 | } 68 | 69 | class Compound: AST { 70 | let children: [AST] 71 | 72 | init(children: [AST]) { 73 | self.children = children 74 | } 75 | } 76 | 77 | class Assignment: AST { 78 | let left: Variable 79 | let right: AST 80 | 81 | init(left: Variable, right: AST) { 82 | self.left = left 83 | self.right = right 84 | } 85 | } 86 | 87 | class Variable: AST { 88 | let name: String 89 | 90 | init(name: String) { 91 | self.name = name 92 | } 93 | } 94 | 95 | class ArrayVariable: Variable { 96 | let index: AST 97 | 98 | init(name: String, index: AST) { 99 | self.index = index 100 | super.init(name: name) 101 | } 102 | } 103 | 104 | class NoOp: AST { 105 | } 106 | 107 | class Block: AST { 108 | let declarations: [Declaration] 109 | let compound: Compound 110 | 111 | init(declarations: [Declaration], compound: Compound) { 112 | self.declarations = declarations 113 | self.compound = compound 114 | } 115 | } 116 | 117 | class VariableType: AST { 118 | let type: Type 119 | 120 | init(type: Type) { 121 | self.type = type 122 | } 123 | } 124 | 125 | class VariableDeclaration: Declaration { 126 | let variable: Variable 127 | let type: VariableType 128 | 129 | init(variable: Variable, type: VariableType) { 130 | self.variable = variable 131 | self.type = type 132 | } 133 | } 134 | 135 | class ArrayDeclaration: VariableDeclaration { 136 | let startIndex: Int 137 | let endIndex: Int 138 | 139 | init(variable: Variable, type: VariableType, startIndex: Int, endIndex: Int) { 140 | self.startIndex = startIndex 141 | self.endIndex = endIndex 142 | 143 | super.init(variable: variable, type: type) 144 | } 145 | } 146 | 147 | class Program: AST { 148 | let name: String 149 | let block: Block 150 | 151 | init(name: String, block: Block) { 152 | self.name = name 153 | self.block = block 154 | } 155 | } 156 | 157 | class Procedure: Declaration { 158 | let name: String 159 | let params: [Param] 160 | let block: Block 161 | 162 | init(name: String, params: [Param], block: Block) { 163 | self.name = name 164 | self.block = block 165 | self.params = params 166 | } 167 | } 168 | 169 | class Function: Procedure { 170 | let returnType: VariableType 171 | 172 | init(name: String, params: [Param], block: Block, returnType: VariableType) { 173 | self.returnType = returnType 174 | super.init(name: name, params: params, block: block) 175 | } 176 | } 177 | 178 | class Param: AST { 179 | let name: String 180 | let type: VariableType 181 | 182 | init(name: String, type: VariableType) { 183 | self.name = name 184 | self.type = type 185 | } 186 | } 187 | 188 | class FunctionCall: AST { 189 | let name: String 190 | let actualParameters: [AST] 191 | 192 | init(name: String, actualParameters: [AST]) { 193 | self.name = name 194 | self.actualParameters = actualParameters 195 | } 196 | } 197 | 198 | class Condition: AST { 199 | let type: ConditionType 200 | let leftSide: AST 201 | let rightSide: AST 202 | 203 | init(type: ConditionType, leftSide: AST, rightSide: AST) { 204 | self.type = type 205 | self.leftSide = leftSide 206 | self.rightSide = rightSide 207 | } 208 | } 209 | 210 | class IfElse: AST { 211 | let condition: Condition 212 | let trueExpression: AST 213 | let falseExpression: AST? 214 | 215 | init(condition: Condition, trueExpression: AST, falseExpression: AST?) { 216 | self.condition = condition 217 | self.trueExpression = trueExpression 218 | self.falseExpression = falseExpression 219 | } 220 | } 221 | 222 | protocol Loop: AST { 223 | var statement: AST { get } 224 | var condition: Condition { get } 225 | } 226 | 227 | class RepeatUntil: Loop { 228 | let statement: AST 229 | let condition: Condition 230 | 231 | init(statement: AST, condition: Condition) { 232 | self.statement = statement 233 | self.condition = condition 234 | } 235 | } 236 | 237 | class While: Loop { 238 | let statement: AST 239 | let condition: Condition 240 | 241 | init(statement: AST, condition: Condition) { 242 | self.statement = statement 243 | self.condition = condition 244 | } 245 | } 246 | 247 | class For: AST { 248 | let statement: AST 249 | let variable: Variable 250 | let startValue: AST 251 | let endValue: AST 252 | 253 | init(statement: AST, variable: Variable, startValue: AST, endValue: AST) { 254 | self.statement = statement 255 | self.variable = variable 256 | self.startValue = startValue 257 | self.endValue = endValue 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreter/Parser/Parser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Parser.swift 3 | // SwiftPascalInterpreter 4 | // 5 | // Created by Igor Kulman on 07/12/2017. 6 | // Copyright © 2017 Igor Kulman. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class Parser { 12 | 13 | // MARK: - Fields 14 | 15 | private var tokenIndex = 0 16 | private let tokens: [Token] 17 | 18 | private var currentToken: Token { 19 | return tokens[tokenIndex] 20 | } 21 | 22 | private var nextToken: Token { 23 | return tokens[tokenIndex + 1] 24 | } 25 | 26 | public init(_ text: String) { 27 | let lexer = Lexer(text) 28 | var token = lexer.getNextToken() 29 | var all = [token] 30 | while token != .eof { 31 | token = lexer.getNextToken() 32 | all.append(token) 33 | } 34 | tokens = all 35 | } 36 | 37 | // MARK: - Helpers 38 | 39 | /** 40 | Compares the current token with the given token, if they match, the next token is read, 41 | otherwise an error is thrown 42 | 43 | - Parameter token: Expected token 44 | */ 45 | private func eat(_ token: Token) { 46 | if currentToken == token { 47 | tokenIndex += 1 48 | } else { 49 | fatalError("Syntax error, expected \(token), got \(currentToken)") 50 | } 51 | } 52 | 53 | // MARK: - Grammar rules 54 | 55 | /** 56 | Rule: 57 | 58 | program : PROGRAM variable SEMI block DOT 59 | */ 60 | private func program() -> Program { 61 | eat(.program) 62 | guard case let .id(name) = currentToken else { 63 | fatalError("Program must have a name") 64 | } 65 | eat(.id(name)) 66 | eat(.semi) 67 | let blockNode = block() 68 | let programNode = Program(name: name, block: blockNode) 69 | eat(.dot) 70 | return programNode 71 | } 72 | 73 | /** 74 | block : declarations compound_statement 75 | */ 76 | private func block() -> Block { 77 | let decs = declarations() 78 | let statement = compoundStatement() 79 | return Block(declarations: decs, compound: statement) 80 | } 81 | 82 | /** 83 | declarations : VAR (variable_declaration SEMI)+ 84 | | (PROCEDURE ID (LPAREN formal_parameter_list RPAREN)? SEMI block SEMI)* 85 | | (FUNCTION ID (LPAREN formal_parameter_list RPAREN)? COLON type_spec SEMI block SEMI)* 86 | | empty 87 | */ 88 | private func declarations() -> [Declaration] { 89 | var declarations: [Declaration] = [] 90 | 91 | if currentToken == .varDef { 92 | eat(.varDef) 93 | while case .id = currentToken { 94 | let decs = variableDeclaration() 95 | for declaration in decs { 96 | declarations.append(declaration) 97 | } 98 | eat(.semi) 99 | } 100 | } 101 | 102 | while currentToken == .procedure { 103 | eat(.procedure) 104 | guard case let .id(name) = currentToken else { 105 | fatalError("Procedure name expected, got \(currentToken)") 106 | } 107 | eat(.id(name)) 108 | 109 | var params: [Param] = [] 110 | if currentToken == .parenthesis(.left) { 111 | eat(.parenthesis(.left)) 112 | params = formalParameterList() 113 | eat(.parenthesis(.right)) 114 | } 115 | 116 | eat(.semi) 117 | let body = block() 118 | let procedure = Procedure(name: name, params: params, block: body) 119 | declarations.append(procedure) 120 | eat(.semi) 121 | } 122 | 123 | while currentToken == .function { 124 | eat(.function) 125 | guard case let .id(name) = currentToken else { 126 | fatalError("Function name expected, got \(currentToken)") 127 | } 128 | eat(.id(name)) 129 | 130 | var params: [Param] = [] 131 | if currentToken == .parenthesis(.left) { 132 | eat(.parenthesis(.left)) 133 | params = formalParameterList() 134 | eat(.parenthesis(.right)) 135 | } 136 | 137 | eat(.colon) 138 | let type = typeSpec() 139 | 140 | eat(.semi) 141 | let body = block() 142 | let function = Function(name: name, params: params, block: body, returnType: type) 143 | declarations.append(function) 144 | eat(.semi) 145 | } 146 | 147 | return declarations 148 | } 149 | 150 | /** 151 | formal_parameter_list : formal_parameters 152 | | formal_parameters SEMI formal_parameter_list 153 | */ 154 | private func formalParameterList() -> [Param] { 155 | guard case .id = currentToken else { 156 | return [] // procedure without parameters 157 | } 158 | 159 | var parameters = formalParameters() 160 | while currentToken == .semi { 161 | eat(.semi) 162 | parameters.append(contentsOf: formalParameterList()) 163 | } 164 | return parameters 165 | } 166 | 167 | /** 168 | formal_parameters : ID (COMMA ID)* COLON type_spec 169 | */ 170 | private func formalParameters() -> [Param] { 171 | guard case let .id(name) = currentToken else { 172 | fatalError("Parameter name expected, got \(currentToken)") 173 | } 174 | 175 | var parameters = [name] 176 | 177 | eat(.id(name)) 178 | while currentToken == .coma { 179 | eat(.coma) 180 | guard case let .id(name) = currentToken else { 181 | fatalError("Parameter name expected, got \(currentToken)") 182 | } 183 | eat(.id(name)) 184 | parameters.append(name) 185 | } 186 | 187 | eat(.colon) 188 | let type = typeSpec() 189 | return parameters.map({ Param(name: $0, type: type) }) 190 | } 191 | 192 | /** 193 | variable_declaration : ID (COMMA ID)* COLON type_spec 194 | | ID (COMMA ID)* COLON ARRAY LBRACKET startIndex DOT DOT endIdex RBRACKE OF type_spec 195 | */ 196 | private func variableDeclaration() -> [VariableDeclaration] { 197 | guard case let .id(name) = currentToken else { 198 | fatalError() 199 | } 200 | 201 | var variableNames = [name] 202 | eat(.id(name)) 203 | 204 | while currentToken == .coma { 205 | eat(.coma) 206 | if case let .id(value) = currentToken { 207 | variableNames.append(value) 208 | eat(.id(value)) 209 | } else { 210 | fatalError("Variable name expected, got \(currentToken)") 211 | } 212 | } 213 | 214 | eat(.colon) 215 | 216 | if currentToken == .array { 217 | eat(.array) 218 | eat(.bracket(.left)) 219 | var startIndex = 0 220 | var endIndex = 0 221 | if case let .constant(.integer(start)) = currentToken { 222 | eat(.constant(.integer(start))) 223 | startIndex = start 224 | } else { 225 | fatalError("Start index of array expected, got \(currentToken)") 226 | } 227 | eat(.dot) 228 | eat(.dot) 229 | if case let .constant(.integer(end)) = currentToken { 230 | eat(.constant(.integer(end))) 231 | endIndex = end 232 | } else { 233 | fatalError("End index of array expected, got \(currentToken)") 234 | } 235 | eat(.bracket(.right)) 236 | eat(.of) 237 | let type = typeSpec() 238 | return variableNames.map({ ArrayDeclaration(variable: Variable(name: $0), type: type, startIndex: startIndex, endIndex: endIndex) }) 239 | } else { 240 | let type = typeSpec() 241 | return variableNames.map({ VariableDeclaration(variable: Variable(name: $0), type: type) }) 242 | } 243 | } 244 | 245 | /** 246 | type_spec : INTEGER 247 | | REAL 248 | | STRING 249 | | BOOLEAN 250 | */ 251 | private func typeSpec() -> VariableType { 252 | switch currentToken { 253 | case .type(.integer): 254 | eat(.type(.integer)) 255 | return VariableType(type: .integer) 256 | case .type(.real): 257 | eat(.type(.real)) 258 | return VariableType(type: .real) 259 | case .type(.boolean): 260 | eat(.type(.boolean)) 261 | return VariableType(type: .boolean) 262 | case .type(.string): 263 | eat(.type(.string)) 264 | return VariableType(type: .string) 265 | default: 266 | fatalError("Expected type, got \(currentToken)") 267 | } 268 | } 269 | 270 | /** 271 | Rule: 272 | 273 | compound_statement: BEGIN statement_list END 274 | */ 275 | private func compoundStatement() -> Compound { 276 | eat(.begin) 277 | let nodes = statementList() 278 | eat(.end) 279 | return Compound(children: nodes) 280 | } 281 | 282 | /** 283 | Rule: 284 | 285 | statement_list : statement 286 | | statement SEMI statement_list 287 | */ 288 | private func statementList() -> [AST] { 289 | let node = statement() 290 | 291 | var statements = [node] 292 | 293 | while currentToken == .semi { 294 | eat(.semi) 295 | statements.append(statement()) 296 | } 297 | 298 | return statements 299 | } 300 | 301 | /** 302 | Rule: 303 | 304 | statement : compound_statement 305 | | procedure_call 306 | | if_else_statement 307 | | assignment_statement 308 | | repeat_until 309 | | for_loop 310 | | while_loop 311 | | empty 312 | */ 313 | private func statement() -> AST { 314 | switch currentToken { 315 | case .begin: 316 | return compoundStatement() 317 | case .if: 318 | return ifElseStatement() 319 | case .repeat: 320 | return repeatUntilLoop() 321 | case .while: 322 | return whileLoop() 323 | case .for: 324 | return forLoop() 325 | case .id: 326 | if nextToken == .parenthesis(.left) { 327 | return functionCall() 328 | } else { 329 | return assignmentStatement() 330 | } 331 | default: 332 | return empty() 333 | } 334 | } 335 | 336 | /** 337 | Rule: 338 | 339 | repeat_until : REPEAT statement UNTIL condition 340 | */ 341 | private func repeatUntilLoop() -> RepeatUntil { 342 | eat(.repeat) 343 | 344 | var statements: [AST] = [] 345 | 346 | while currentToken != .until { 347 | statements.append(statement()) 348 | if currentToken == .semi { 349 | eat(.semi) 350 | } 351 | } 352 | eat(.until) 353 | let condition = self.condition() 354 | return RepeatUntil(statement: statements.count == 1 ? statements[0] : Compound(children: statements), condition: condition) 355 | } 356 | 357 | /** 358 | Rule: 359 | 360 | for_loop : WHILE condition DO statement 361 | */ 362 | private func whileLoop() -> While { 363 | eat(.while) 364 | let condition = self.condition() 365 | eat(.do) 366 | let statement = self.statement() 367 | return While(statement: statement, condition: condition) 368 | } 369 | 370 | /** 371 | Rule: 372 | 373 | for_loop : FOR variable ASSIGN expression TO expression DO statement 374 | */ 375 | private func forLoop() -> For { 376 | eat(.for) 377 | let variable = self.variable() 378 | eat(.assign) 379 | let start = expr() 380 | eat(.to) 381 | let end = expr() 382 | eat(.do) 383 | let statement = self.statement() 384 | return For(statement: statement, variable: variable, startValue: start, endValue: end) 385 | } 386 | 387 | /** 388 | Rule: 389 | 390 | function_call : id LPAREN (factor (factor COLON)* )* RPAREN 391 | */ 392 | private func functionCall() -> FunctionCall { 393 | guard case let .id(name) = currentToken else { 394 | fatalError("Procedure name expected, got \(currentToken)") 395 | } 396 | 397 | var parameters: [AST] = [] 398 | 399 | eat(.id(name)) 400 | eat(.parenthesis(.left)) 401 | if currentToken == .parenthesis(.right) { // no parameters 402 | eat(.parenthesis(.right)) 403 | } else { 404 | parameters.append(expr()) 405 | while currentToken == .coma { 406 | eat(.coma) 407 | parameters.append(factor()) 408 | } 409 | eat(.parenthesis(.right)) 410 | } 411 | 412 | return FunctionCall(name: name, actualParameters: parameters) 413 | } 414 | 415 | /** 416 | Rule: 417 | 418 | if_else_statement : IF condition statement 419 | | IF condition THEN statement ELSE statement 420 | */ 421 | private func ifElseStatement() -> IfElse { 422 | eat(.if) 423 | let cond = condition() 424 | eat(.then) 425 | let trueStatement = statement() 426 | var falseStatement: AST? 427 | if currentToken == .else { 428 | eat(.else) 429 | falseStatement = statement() 430 | } 431 | 432 | return IfElse(condition: cond, trueExpression: trueStatement, falseExpression: falseStatement) 433 | } 434 | 435 | /** 436 | Rule: 437 | condition: expr (= | < | >) expr 438 | | LPAREN expr (= | < | >) expr RPAREN 439 | */ 440 | private func condition() -> Condition { 441 | if currentToken == .parenthesis(.left) { 442 | eat(.parenthesis(.left)) 443 | } 444 | let left = expr() 445 | var type: ConditionType = .equals 446 | switch currentToken { 447 | case .equals: 448 | eat(.equals) 449 | type = .equals 450 | case .lessThan: 451 | eat(.lessThan) 452 | type = .lessThan 453 | case .greaterThan: 454 | eat(.greaterThan) 455 | type = .greaterThan 456 | default: 457 | fatalError("Invalid condition type \(type)") 458 | } 459 | let right = expr() 460 | if currentToken == .parenthesis(.right) { 461 | eat(.parenthesis(.right)) 462 | } 463 | return Condition(type: type, leftSide: left, rightSide: right) 464 | } 465 | 466 | /** 467 | Rule: 468 | 469 | assignment_statement : variable ASSIGN expr 470 | */ 471 | private func assignmentStatement() -> Assignment { 472 | let left = variable() 473 | eat(.assign) 474 | let right = expr() 475 | return Assignment(left: left, right: right) 476 | } 477 | 478 | /** 479 | An empty production 480 | */ 481 | private func empty() -> NoOp { 482 | return NoOp() 483 | } 484 | 485 | /** 486 | Rule: 487 | 488 | expr: term ((PLUS | MINUS) term)* 489 | */ 490 | private func expr() -> AST { 491 | 492 | var node = term() 493 | 494 | while [.operation(.plus), .operation(.minus)].contains(currentToken) { 495 | let token = currentToken 496 | if token == .operation(.plus) { 497 | eat(.operation(.plus)) 498 | node = BinaryOperation(left: node, operation: .plus, right: term()) 499 | } else if token == .operation(.minus) { 500 | eat(.operation(.minus)) 501 | node = BinaryOperation(left: node, operation: .minus, right: term()) 502 | } 503 | } 504 | 505 | return node 506 | } 507 | 508 | /** 509 | Rule: 510 | 511 | term : factor ((MUL | INTEGER_DIV | FLOAT_DIV) factor)* 512 | */ 513 | private func term() -> AST { 514 | var node = factor() 515 | 516 | while [.operation(.mult), .operation(.integerDiv), .operation(.floatDiv)].contains(currentToken) { 517 | let token = currentToken 518 | if token == .operation(.mult) { 519 | eat(.operation(.mult)) 520 | node = BinaryOperation(left: node, operation: .mult, right: factor()) 521 | } else if token == .operation(.integerDiv) { 522 | eat(.operation(.integerDiv)) 523 | node = BinaryOperation(left: node, operation: .integerDiv, right: factor()) 524 | } else if token == .operation(.floatDiv) { 525 | eat(.operation(.floatDiv)) 526 | node = BinaryOperation(left: node, operation: .floatDiv, right: factor()) 527 | } 528 | } 529 | 530 | return node 531 | } 532 | 533 | /** 534 | Rule: 535 | 536 | variable : ID 537 | */ 538 | private func variable() -> Variable { 539 | switch currentToken { 540 | case let .id(value): 541 | eat(.id(value)) 542 | if currentToken == .bracket(.left) { 543 | eat(.bracket(.left)) 544 | let index = expr() 545 | eat(.bracket(.right)) 546 | return ArrayVariable(name: value, index: index) 547 | } 548 | return Variable(name: value) 549 | default: 550 | fatalError("Syntax error, expected variable, found \(currentToken)") 551 | } 552 | } 553 | 554 | /** 555 | Rule: 556 | 557 | factor : PLUS factor 558 | | MINUS factor 559 | | INTEGER_CONST 560 | | REAL_CONST 561 | | STRING_CONST 562 | | LPAREN expr RPAREN 563 | | variable 564 | | function_call 565 | */ 566 | private func factor() -> AST { 567 | let token = currentToken 568 | switch token { 569 | case .operation(.plus): 570 | eat(.operation(.plus)) 571 | return UnaryOperation(operation: .plus, operand: factor()) 572 | case .operation(.minus): 573 | eat(.operation(.minus)) 574 | return UnaryOperation(operation: .minus, operand: factor()) 575 | case let .constant(.integer(value)): 576 | eat(.constant(.integer(value))) 577 | return Number.integer(value) 578 | case let .constant(.real(value)): 579 | eat(.constant(.real(value))) 580 | return Number.real(value) 581 | case .parenthesis(.left): 582 | eat(.parenthesis(.left)) 583 | let result = expr() 584 | eat(.parenthesis(.right)) 585 | return result 586 | case .apostrophe: 587 | eat(.apostrophe) 588 | guard case let .constant(.string(value)) = currentToken else { 589 | fatalError("Expected string literal after '") 590 | } 591 | eat(.constant(.string(value))) 592 | eat(.apostrophe) 593 | return value 594 | case let .constant(.boolean(value)): 595 | eat(.constant(.boolean(value))) 596 | return value 597 | default: 598 | if nextToken == .parenthesis(.left) { 599 | return functionCall() 600 | } else { 601 | return variable() 602 | } 603 | } 604 | } 605 | 606 | // MARK: - Public methods. 607 | 608 | /** 609 | Parser for the following grammar 610 | 611 | program : PROGRAM variable SEMI block DOT 612 | 613 | block : declarations compound_statement 614 | 615 | declarations : (VAR (variable_declaration SEMI)+)* 616 | | (PROCEDURE ID (LPAREN formal_parameter_list RPAREN)? SEMI block SEMI)* 617 | | empty 618 | 619 | formal_parameter_list : formal_parameters 620 | | formal_parameters SEMI formal_parameter_list 621 | 622 | formal_parameters : ID (COMMA ID)* COLON type_spec 623 | 624 | variable_declaration : ID (COMMA ID)* COLON type_spec 625 | 626 | type_spec : INTEGER 627 | 628 | compound_statement : BEGIN statement_list END 629 | 630 | statement_list : statement 631 | | statement SEMI statement_list 632 | 633 | statement : compound_statement 634 | | assignment_statement 635 | | empty 636 | 637 | assignment_statement : variable ASSIGN expr 638 | 639 | empty : 640 | 641 | expr : term ((PLUS | MINUS) term)* 642 | 643 | term : factor ((MUL | INTEGER_DIV | FLOAT_DIV) factor)* 644 | 645 | factor : PLUS factor 646 | | MINUS factor 647 | | INTEGER_CONST 648 | | REAL_CONST 649 | | LPAREN expr RPAREN 650 | | variable 651 | 652 | variable: ID 653 | */ 654 | public func parse() -> AST { 655 | let node = program() 656 | if currentToken != .eof { 657 | fatalError("Syntax error, end of file expected") 658 | } 659 | 660 | return node 661 | } 662 | } 663 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreter/PascalInterpreter.h: -------------------------------------------------------------------------------- 1 | // 2 | // PascalInterpreter.h 3 | // PascalInterpreter 4 | // 5 | // Created by Igor Kulman on 10/12/2017. 6 | // Copyright © 2017 Igor Kulman. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for PascalInterpreter. 12 | FOUNDATION_EXPORT double PascalInterpreterVersionNumber; 13 | 14 | //! Project version string for PascalInterpreter. 15 | FOUNDATION_EXPORT const unsigned char PascalInterpreterVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreter/Semantic analyzer/SemanticAnalyzer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SymbolTableBuilder.swift 3 | // SwiftPascalInterpreter 4 | // 5 | // Created by Igor Kulman on 10/12/2017. 6 | // Copyright © 2017 Igor Kulman. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class SemanticAnalyzer: Visitor { 12 | private var currentScope: ScopedSymbolTable? 13 | private var scopes: [String: ScopedSymbolTable] = [:] 14 | 15 | public init() { 16 | } 17 | 18 | public func analyze(node: AST) -> [String: ScopedSymbolTable] { 19 | visit(node: node) 20 | return scopes 21 | } 22 | 23 | func visit(program: Program) { 24 | let globalScope = ScopedSymbolTable(name: "global", level: 1, enclosingScope: nil) 25 | scopes[globalScope.name] = globalScope 26 | currentScope = globalScope 27 | visit(node: program.block) 28 | currentScope = nil 29 | } 30 | 31 | func visit(variable: Variable) { 32 | guard let scope = currentScope else { 33 | fatalError("Cannot access a variable outside a scope") 34 | } 35 | guard scope.lookup(variable.name) != nil else { 36 | fatalError("Symbol(indetifier) not found '\(variable.name)'") 37 | } 38 | } 39 | 40 | func visit(variableDeclaration: VariableDeclaration) { 41 | guard let scope = currentScope else { 42 | fatalError("Cannot declare a variable outside a scope") 43 | } 44 | 45 | guard scope.lookup(variableDeclaration.variable.name, currentScopeOnly: true) == nil else { 46 | fatalError("Duplicate identifier '\(variableDeclaration.variable.name)' found") 47 | } 48 | 49 | guard let symbolType = scope.lookup(variableDeclaration.type.type.description) else { 50 | fatalError("Type not found '\(variableDeclaration.type.type.description)'") 51 | } 52 | 53 | switch variableDeclaration { 54 | case let arrayDeclaration as ArrayDeclaration: 55 | scope.insert(ArraySymbol(name: arrayDeclaration.variable.name, type: symbolType, startIndex: arrayDeclaration.startIndex, endIndex: arrayDeclaration.endIndex)) 56 | default: 57 | scope.insert(VariableSymbol(name: variableDeclaration.variable.name, type: symbolType)) 58 | } 59 | } 60 | 61 | func visit(function: Function) { 62 | visit(procedure: function) 63 | } 64 | 65 | func visit(procedure: Procedure) { 66 | let scope = ScopedSymbolTable(name: procedure.name, level: (currentScope?.level ?? 0) + 1, enclosingScope: currentScope) 67 | scopes[scope.name] = scope 68 | currentScope = scope 69 | 70 | var parameters: [Symbol] = [] 71 | for param in procedure.params { 72 | guard let symbol = scope.lookup(param.type.type.description) else { 73 | fatalError("Type not found '\(param.type.type.description)'") 74 | } 75 | let variable = VariableSymbol(name: param.name, type: symbol) 76 | parameters.append(variable) 77 | scope.insert(variable) 78 | } 79 | 80 | switch procedure { 81 | case let function as Function: 82 | guard let symbol = scope.lookup(function.returnType.type.description) else { 83 | fatalError("Type not found '\(function.returnType.type.description)'") 84 | } 85 | let fn = FunctionSymbol(name: procedure.name, parameters: parameters, body: procedure, returnType: symbol) 86 | scope.enclosingScope?.insert(fn) 87 | default: 88 | let proc = ProcedureSymbol(name: procedure.name, parameters: parameters, body: procedure) 89 | scope.enclosingScope?.insert(proc) 90 | } 91 | 92 | visit(node: procedure.block) 93 | currentScope = currentScope?.enclosingScope 94 | } 95 | 96 | func visit(call: FunctionCall) { 97 | guard let symbol = currentScope?.lookup(call.name), symbol is ProcedureSymbol || symbol is BuiltInProcedureSymbol else { 98 | fatalError("Symbol(procedure) not found '\(call.name)'") 99 | } 100 | 101 | switch symbol { 102 | case let procedure as ProcedureSymbol: 103 | checkFunction(call: call, procedure: procedure) 104 | case let procedure as BuiltInProcedureSymbol: 105 | checkBuiltInProcedure(call: call, procedure: procedure) 106 | default: 107 | break 108 | } 109 | } 110 | 111 | private func checkBuiltInProcedure(call _: FunctionCall, procedure _: BuiltInProcedureSymbol) { 112 | } 113 | 114 | private func checkFunction(call: FunctionCall, procedure: ProcedureSymbol) { 115 | guard procedure.params.count == call.actualParameters.count else { 116 | fatalError("Procedure called with wrong number of parameters '\(call.name)'") 117 | } 118 | 119 | guard procedure.params.count > 0 else { 120 | return 121 | } 122 | 123 | for i in 0 ... procedure.params.count - 1 { 124 | guard let variableSymbol = procedure.params[i] as? VariableSymbol, variableSymbol.type is BuiltInTypeSymbol else { 125 | fatalError("Procedure declared with wrong parameters '\(call.name)'") 126 | } 127 | 128 | switch variableSymbol.type { 129 | case let builtIn as BuiltInTypeSymbol: 130 | if let constant = call.actualParameters[i] as? Number { 131 | switch (builtIn.name, constant) { 132 | case ("INTEGER", .real): 133 | fatalError("Cannot assing Real to Integer parameter in procedure call '\(procedure.name)'") 134 | default: 135 | break 136 | } 137 | } 138 | default: 139 | fatalError("Variable type \(variableSymbol.name) in procedure \(procedure.name) cannot be of type \(variableSymbol.type)") 140 | } 141 | } 142 | } 143 | 144 | func visit(forLoop: For) { 145 | currentScope?.insert(VariableSymbol(name: forLoop.variable.name, type: currentScope!.lookup("INTEGER")!)) 146 | 147 | visit(node: forLoop.startValue) 148 | visit(node: forLoop.endValue) 149 | visit(node: forLoop.statement) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreter/Semantic analyzer/Symbol+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Symbol+Extensions.swift 3 | // SwiftPascalInterpreter 4 | // 5 | // Created by Igor Kulman on 10/12/2017. 6 | // Copyright © 2017 Igor Kulman. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension BuiltInTypeSymbol: CustomStringConvertible { 12 | public var description: String { 13 | return "" 14 | } 15 | } 16 | 17 | extension VariableSymbol: CustomStringConvertible { 18 | public var description: String { 19 | switch self { 20 | case let arraySymbol as ArraySymbol: 21 | return "" 22 | default: 23 | return "" 24 | } 25 | } 26 | } 27 | 28 | extension ProcedureSymbol: CustomStringConvertible { 29 | public var description: String { 30 | switch self { 31 | case let function as FunctionSymbol: 32 | return "" 33 | default: 34 | return "" 35 | } 36 | } 37 | } 38 | 39 | extension BuiltInProcedureSymbol: CustomStringConvertible { 40 | public var description: String { 41 | return "" 42 | } 43 | } 44 | 45 | extension Symbol { 46 | var sortOrder: Int { 47 | switch self { 48 | case is BuiltInTypeSymbol: 49 | return 0 50 | case is VariableSymbol: 51 | return 2 52 | case is FunctionSymbol: 53 | return 3 54 | case is ProcedureSymbol: 55 | return 4 56 | case is BuiltInProcedureSymbol: 57 | return 1 58 | default: 59 | fatalError("Add sort order for \(self)") 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreter/Semantic analyzer/Symbol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Symbol.swift 3 | // SwiftPascalInterpreter 4 | // 5 | // Created by Igor Kulman on 10/12/2017. 6 | // Copyright © 2017 Igor Kulman. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol Symbol { 12 | var name: String { get } 13 | } 14 | 15 | public enum BuiltInTypeSymbol: Symbol { 16 | case integer 17 | case real 18 | case boolean 19 | case string 20 | 21 | var name: String { 22 | switch self { 23 | case .integer: 24 | return "INTEGER" 25 | case .real: 26 | return "REAL" 27 | case .boolean: 28 | return "BOOLEAN" 29 | case .string: 30 | return "STRING" 31 | } 32 | } 33 | } 34 | 35 | class VariableSymbol: Symbol { 36 | let name: String 37 | let type: Symbol 38 | 39 | init(name: String, type: Symbol) { 40 | self.name = name 41 | self.type = type 42 | } 43 | } 44 | 45 | class ArraySymbol: VariableSymbol { 46 | let startIndex: Int 47 | let endIndex: Int 48 | 49 | init(name: String, type: Symbol, startIndex: Int, endIndex: Int) { 50 | self.startIndex = startIndex 51 | self.endIndex = endIndex 52 | super.init(name: name, type: type) 53 | } 54 | } 55 | 56 | class ProcedureSymbol: Symbol { 57 | let name: String 58 | let params: [Symbol] 59 | let body: Procedure 60 | 61 | init(name: String, parameters: [Symbol], body: Procedure) { 62 | self.name = name 63 | params = parameters 64 | self.body = body 65 | } 66 | } 67 | 68 | class FunctionSymbol: ProcedureSymbol { 69 | let returnType: Symbol 70 | 71 | init(name: String, parameters: [Symbol], body: Procedure, returnType: Symbol) { 72 | self.returnType = returnType 73 | super.init(name: name, parameters: parameters, body: body) 74 | } 75 | } 76 | 77 | class BuiltInProcedureSymbol: Symbol { 78 | let name: String 79 | let params: [Symbol] 80 | let hasVariableParameters: Bool 81 | 82 | init(name: String, parameters: [Symbol], hasVariableParameters: Bool) { 83 | self.name = name 84 | params = parameters 85 | self.hasVariableParameters = hasVariableParameters 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreter/Semantic analyzer/SymbolTable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SymbolTable.swift 3 | // SwiftPascalInterpreter 4 | // 5 | // Created by Igor Kulman on 10/12/2017. 6 | // Copyright © 2017 Igor Kulman. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class ScopedSymbolTable { 12 | private(set) var symbols: [String: Symbol] = [:] 13 | 14 | let name: String 15 | let level: Int 16 | let enclosingScope: ScopedSymbolTable? 17 | 18 | init(name: String, level: Int, enclosingScope: ScopedSymbolTable?) { 19 | self.name = name 20 | self.level = level 21 | self.enclosingScope = enclosingScope 22 | 23 | insertBuiltInTypes() 24 | insertBuildInProcedures() 25 | } 26 | 27 | private func insertBuiltInTypes() { 28 | insert(BuiltInTypeSymbol.integer) 29 | insert(BuiltInTypeSymbol.real) 30 | insert(BuiltInTypeSymbol.boolean) 31 | insert(BuiltInTypeSymbol.string) 32 | } 33 | 34 | private func insertBuildInProcedures() { 35 | symbols["WRITELN"] = BuiltInProcedureSymbol(name: "WRITELN", parameters: [], hasVariableParameters: true) 36 | symbols["WRITE"] = BuiltInProcedureSymbol(name: "WRITE", parameters: [], hasVariableParameters: true) 37 | symbols["READ"] = BuiltInProcedureSymbol(name: "READ", parameters: [], hasVariableParameters: true) 38 | symbols["READLN"] = BuiltInProcedureSymbol(name: "READLN", parameters: [], hasVariableParameters: true) 39 | symbols["RANDOM"] = BuiltInProcedureSymbol(name: "RANDOM", parameters: [BuiltInTypeSymbol.integer], hasVariableParameters: false) 40 | symbols["LENGTH"] = BuiltInProcedureSymbol(name: "LENGTH", parameters: [], hasVariableParameters: true) 41 | } 42 | 43 | func insert(_ symbol: Symbol) { 44 | symbols[symbol.name.uppercased()] = symbol 45 | } 46 | 47 | func lookup(_ name: String, currentScopeOnly: Bool = false) -> Symbol? { 48 | if let symbol = symbols[name.uppercased()] { 49 | return symbol 50 | } 51 | 52 | if currentScopeOnly { 53 | return nil 54 | } 55 | 56 | return enclosingScope?.lookup(name) 57 | } 58 | } 59 | 60 | extension ScopedSymbolTable: CustomStringConvertible { 61 | public var description: String { 62 | var lines = ["SCOPE (SCOPED SYMBOL TABLE)", "==========================="] 63 | lines.append("Scope name : \(name)") 64 | lines.append("Scope level : \(level)") 65 | lines.append("Scope (Scoped symbol table) contents") 66 | lines.append("------------------------------------") 67 | lines.append(contentsOf: symbols.sorted(by: { $0.value.sortOrder < $1.value.sortOrder }).map({ key, value in 68 | "\(key.padding(toLength: 12, withPad: " ", startingAt: 0)): \(value)" 69 | })) 70 | return lines.reduce("", { $0 + "\n" + $1 }) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreter/Semantic analyzer/Visitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Visitor.swift 3 | // PascalInterpreter 4 | // 5 | // Created by Igor Kulman on 14/12/2017. 6 | // Copyright © 2017 Igor Kulman. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol Visitor: AnyObject { 12 | func visit(node: AST) 13 | func visit(number: Number) 14 | func visit(unaryOperation: UnaryOperation) 15 | func visit(binaryOperation: BinaryOperation) 16 | func visit(compound: Compound) 17 | func visit(assignment: Assignment) 18 | func visit(variable: Variable) 19 | func visit(noOp: NoOp) 20 | func visit(block: Block) 21 | func visit(variableDeclaration: VariableDeclaration) 22 | func visit(type: VariableType) 23 | func visit(program: Program) 24 | func visit(procedure: Procedure) 25 | func visit(function: Function) 26 | func visit(param: Param) 27 | func visit(call: FunctionCall) 28 | func visit(condition: Condition) 29 | func visit(ifElse: IfElse) 30 | func visit(loop: Loop) 31 | func visit(forLoop: For) 32 | } 33 | 34 | extension Visitor { 35 | func visit(node: AST) { 36 | switch node { 37 | case let number as Number: 38 | visit(number: number) 39 | case let unaryOperation as UnaryOperation: 40 | visit(unaryOperation: unaryOperation) 41 | case let binaryOperation as BinaryOperation: 42 | visit(binaryOperation: binaryOperation) 43 | case let compound as Compound: 44 | visit(compound: compound) 45 | case let assignment as Assignment: 46 | visit(assignment: assignment) 47 | case let variable as Variable: 48 | visit(variable: variable) 49 | case let noOp as NoOp: 50 | visit(noOp: noOp) 51 | case let block as Block: 52 | visit(block: block) 53 | case let variableDeclaration as VariableDeclaration: 54 | visit(variableDeclaration: variableDeclaration) 55 | case let type as VariableType: 56 | visit(type: type) 57 | case let program as Program: 58 | visit(program: program) 59 | case let function as Function: 60 | visit(function: function) 61 | case let procedure as Procedure: 62 | visit(procedure: procedure) 63 | case let param as Param: 64 | visit(param: param) 65 | case let call as FunctionCall: 66 | visit(call: call) 67 | case let condition as Condition: 68 | visit(condition: condition) 69 | case let ifElse as IfElse: 70 | visit(ifElse: ifElse) 71 | case let loop as Loop: 72 | visit(loop: loop) 73 | case let forLoop as For: 74 | visit(forLoop: forLoop) 75 | default: 76 | fatalError("Unsupported node type \(node)") 77 | } 78 | } 79 | 80 | func visit(number _: Number) { 81 | } 82 | 83 | func visit(unaryOperation: UnaryOperation) { 84 | visit(node: unaryOperation.operand) 85 | } 86 | 87 | func visit(binaryOperation: BinaryOperation) { 88 | visit(node: binaryOperation.left) 89 | visit(node: binaryOperation.right) 90 | } 91 | 92 | func visit(compound: Compound) { 93 | for node in compound.children { 94 | visit(node: node) 95 | } 96 | } 97 | 98 | func visit(assignment: Assignment) { 99 | visit(node: assignment.left) 100 | visit(node: assignment.right) 101 | } 102 | 103 | func visit(variable _: Variable) { 104 | } 105 | 106 | func visit(noOp _: NoOp) { 107 | } 108 | 109 | func visit(block: Block) { 110 | for declaration in block.declarations { 111 | visit(node: declaration) 112 | } 113 | visit(node: block.compound) 114 | } 115 | 116 | func visit(variableDeclaration: VariableDeclaration) { 117 | visit(node: variableDeclaration.type) 118 | } 119 | 120 | func visit(type _: VariableType) { 121 | } 122 | 123 | func visit(program: Program) { 124 | visit(node: program.block) 125 | } 126 | 127 | func visit(procedure: Procedure) { 128 | for param in procedure.params { 129 | visit(node: param) 130 | } 131 | visit(node: procedure.block) 132 | } 133 | 134 | func visit(function: Function) { 135 | for param in function.params { 136 | visit(node: param) 137 | } 138 | visit(node: function.block) 139 | visit(node: function.returnType) 140 | } 141 | 142 | func visit(param: Param) { 143 | visit(node: param.type) 144 | } 145 | 146 | func visit(call: FunctionCall) { 147 | for parameter in call.actualParameters { 148 | visit(node: parameter) 149 | } 150 | } 151 | 152 | func visit(condition: Condition) { 153 | visit(node: condition.leftSide) 154 | visit(node: condition.rightSide) 155 | } 156 | 157 | func visit(ifElse: IfElse) { 158 | visit(node: ifElse.condition) 159 | visit(node: ifElse.trueExpression) 160 | 161 | if let falseExpression = ifElse.falseExpression { 162 | visit(node: falseExpression) 163 | } 164 | } 165 | 166 | func visit(loop: Loop) { 167 | visit(node: loop.statement) 168 | visit(node: loop.condition) 169 | } 170 | 171 | func visit(forLoop: For) { 172 | // visit(node: forLoop.variable) 173 | visit(node: forLoop.startValue) 174 | visit(node: forLoop.endValue) 175 | visit(node: forLoop.statement) 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreter/Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utils.swift 3 | // SwiftPascalInterpreter 4 | // 5 | // Created by Igor Kulman on 07/12/2017. 6 | // Copyright © 2017 Igor Kulman. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Generic tree formatting code from https://stackoverflow.com/a/43903427/581164 13 | */ 14 | func treeString(_ node: T, using nodeInfo: (T) -> (String, T?, T?)) -> String { 15 | // node value string and sub nodes 16 | let (stringValue, leftNode, rightNode) = nodeInfo(node) 17 | 18 | let stringValueWidth = stringValue.count 19 | 20 | // recurse to sub nodes to obtain line blocks on left and right 21 | let leftTextBlock = leftNode == nil ? [] 22 | : treeString(leftNode!, using: nodeInfo) 23 | .components(separatedBy: "\n") 24 | 25 | let rightTextBlock = rightNode == nil ? [] 26 | : treeString(rightNode!, using: nodeInfo) 27 | .components(separatedBy: "\n") 28 | 29 | // count common and maximum number of sub node lines 30 | let commonLines = min(leftTextBlock.count, rightTextBlock.count) 31 | let subLevelLines = max(rightTextBlock.count, leftTextBlock.count) 32 | 33 | // extend lines on shallower side to get same number of lines on both sides 34 | let leftSubLines = leftTextBlock 35 | + Array(repeating: "", count: subLevelLines - leftTextBlock.count) 36 | let rightSubLines = rightTextBlock 37 | + Array(repeating: "", count: subLevelLines - rightTextBlock.count) 38 | 39 | // compute location of value or link bar for all left and right sub nodes 40 | // * left node's value ends at line's width 41 | // * right node's value starts after initial spaces 42 | let leftLineWidths = leftSubLines.map { $0.count } 43 | let rightLineIndents = rightSubLines.map { $0.prefix { $0 == " " }.count } 44 | 45 | // top line value locations, will be used to determine position of current node & link bars 46 | let firstLeftWidth = leftLineWidths.first ?? 0 47 | let firstRightIndent = rightLineIndents.first ?? 0 48 | 49 | // width of sub node link under node value (i.e. with slashes if any) 50 | // aims to center link bars under the value if value is wide enough 51 | // 52 | // ValueLine: v vv vvvvvv vvvvv 53 | // LinkLine: / \ / \ / \ / \ 54 | // 55 | let linkSpacing = min(stringValueWidth, 2 - stringValueWidth % 2) 56 | let leftLinkBar = leftNode == nil ? 0 : 1 57 | let rightLinkBar = rightNode == nil ? 0 : 1 58 | let minLinkWidth = leftLinkBar + linkSpacing + rightLinkBar 59 | let valueOffset = (stringValueWidth - linkSpacing) / 2 60 | 61 | // find optimal position for right side top node 62 | // * must allow room for link bars above and between left and right top nodes 63 | // * must not overlap lower level nodes on any given line (allow gap of minSpacing) 64 | // * can be offset to the left if lower subNodes of right node 65 | // have no overlap with subNodes of left node 66 | let minSpacing = 2 67 | let rightNodePosition = zip(leftLineWidths, rightLineIndents[0 ..< commonLines]) 68 | .reduce(firstLeftWidth + minLinkWidth) { max($0, $1.0 + minSpacing + firstRightIndent - $1.1) } 69 | 70 | // extend basic link bars (slashes) with underlines to reach left and right 71 | // top nodes. 72 | // 73 | // vvvvv 74 | // __/ \__ 75 | // L R 76 | // 77 | let linkExtraWidth = max(0, rightNodePosition - firstLeftWidth - minLinkWidth) 78 | let rightLinkExtra = linkExtraWidth / 2 79 | let leftLinkExtra = linkExtraWidth - rightLinkExtra 80 | 81 | // build value line taking into account left indent and link bar extension (on left side) 82 | let valueIndent = max(0, firstLeftWidth + leftLinkExtra + leftLinkBar - valueOffset) 83 | let valueLine = String(repeating: " ", count: max(0, valueIndent)) 84 | + stringValue 85 | 86 | // build left side of link line 87 | let leftLink = leftNode == nil ? "" 88 | : String(repeating: " ", count: firstLeftWidth) 89 | + String(repeating: "_", count: leftLinkExtra) 90 | + "/" 91 | 92 | // build right side of link line (includes blank spaces under top node value) 93 | let rightLinkOffset = linkSpacing + valueOffset * (1 - leftLinkBar) 94 | let rightLink = rightNode == nil ? "" 95 | : String(repeating: " ", count: rightLinkOffset) 96 | + "\\" 97 | + String(repeating: "_", count: rightLinkExtra) 98 | 99 | // full link line (will be empty if there are no sub nodes) 100 | let linkLine = leftLink + rightLink 101 | 102 | // will need to offset left side lines if right side sub nodes extend beyond left margin 103 | // can happen if left subtree is shorter (in height) than right side subtree 104 | let leftIndentWidth = max(0, firstRightIndent - rightNodePosition) 105 | let leftIndent = String(repeating: " ", count: leftIndentWidth) 106 | let indentedLeftLines = leftSubLines.map { $0.isEmpty ? $0 : (leftIndent + $0) } 107 | 108 | // compute distance between left and right sublines based on their value position 109 | // can be negative if leading spaces need to be removed from right side 110 | let mergeOffsets = indentedLeftLines 111 | .map { $0.count } 112 | .map { leftIndentWidth + rightNodePosition - firstRightIndent - $0 } 113 | .enumerated() 114 | .map { rightSubLines[$0].isEmpty ? 0 : $1 } 115 | 116 | // combine left and right lines using computed offsets 117 | // * indented left sub lines 118 | // * spaces between left and right lines 119 | // * right sub line with extra leading blanks removed. 120 | let mergedSubLines = zip(mergeOffsets.enumerated(), indentedLeftLines) 121 | .map { ($0.0, $0.1, $1 + String(repeating: " ", count: max(0, $0.1))) } 122 | .map { $2 + String(rightSubLines[$0].dropFirst(max(0, -$1))) } 123 | 124 | // Assemble final result combining 125 | // * node value string 126 | // * link line (if any) 127 | // * merged lines from left and right sub trees (if any) 128 | let result = [leftIndent + valueLine] 129 | + (linkLine.isEmpty ? [] : [leftIndent + linkLine]) 130 | + mergedSubLines 131 | 132 | return result.joined(separator: "\n") 133 | } 134 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreterTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreterTests/InterpreterTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnalyzerTests.swift 3 | // SwiftPascalInterpreterTests 4 | // 5 | // Created by Igor Kulman on 06/12/2017. 6 | // Copyright © 2017 Igor Kulman. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @testable import PascalInterpreter 11 | import XCTest 12 | 13 | class InterpreterTests: XCTestCase { 14 | func testSimpleProgram() { 15 | let program = 16 | """ 17 | PROGRAM Part10AST; 18 | VAR 19 | a: INTEGER; 20 | BEGIN 21 | a := 2 22 | END. 23 | """ 24 | 25 | let interpeter = Interpreter(program) 26 | interpeter.interpret() 27 | let (scalars, arrays) = interpeter.getState() 28 | XCTAssert(scalars == ["A": Value.number(.integer(2))]) 29 | XCTAssert(arrays.count == 0) 30 | } 31 | 32 | func testMoreComplexProgram() { 33 | let program = 34 | """ 35 | PROGRAM Part10AST; 36 | VAR 37 | a, number, b, c, x: INTEGER; 38 | BEGIN 39 | BEGIN 40 | number := 2; 41 | a := number; 42 | b := 10 * a + 10 * number DIV 4; 43 | c := a - - b 44 | END; 45 | x := 11; 46 | END. 47 | """ 48 | 49 | let interpeter = Interpreter(program) 50 | interpeter.interpret() 51 | let (scalars, arrays) = interpeter.getState() 52 | XCTAssert(scalars == ["B": Value.number(.integer(25)), "NUMBER": Value.number(.integer(2)), "A": Value.number(.integer(2)), "X": Value.number(.integer(11)), "C": Value.number(.integer(27))]) 53 | XCTAssert(arrays.count == 0) 54 | } 55 | 56 | func testProgramWithDeclarations() { 57 | let program = 58 | """ 59 | PROGRAM Part10AST; 60 | VAR 61 | a, b : INTEGER; 62 | y : REAL; 63 | 64 | BEGIN {Part10AST} 65 | a := 2; 66 | b := 10 * a + 10 * a DIV 4; 67 | y := 20 / 7 + 3.14; 68 | END. {Part10AST} 69 | """ 70 | 71 | let interpeter = Interpreter(program) 72 | interpeter.interpret() 73 | let (scalars, arrays) = interpeter.getState() 74 | XCTAssert(scalars == ["B": Value.number(.integer(25)), "A": Value.number(.integer(2)), "Y": Value.number(.real(5.9971428571428573))]) 75 | XCTAssert(arrays.count == 0) 76 | } 77 | 78 | func testProgramWithProcedureCallAndNoParameters() { 79 | let program = 80 | """ 81 | program Main; 82 | var x, y: real; 83 | 84 | procedure Alpha(); 85 | var a: integer; 86 | begin 87 | a := 2; 88 | x := y + a; 89 | end; 90 | 91 | begin { Main } 92 | y := 5; 93 | Alpha(); 94 | end. { Main } 95 | """ 96 | 97 | let interpeter = Interpreter(program) 98 | interpeter.interpret() 99 | let (scalars, arrays) = interpeter.getState() 100 | XCTAssert(scalars == ["X": Value.number(.real(7)), "Y": Value.number(.real(5))]) 101 | XCTAssert(arrays.count == 0) 102 | } 103 | 104 | func testProgramWithProcedureCallAndParameters() { 105 | let program = 106 | """ 107 | program Main; 108 | var x, y: real; 109 | 110 | procedure Alpha(a: Integer); 111 | begin 112 | x := y + a; 113 | end; 114 | 115 | begin { Main } 116 | y := 3; 117 | Alpha(2); 118 | end. { Main } 119 | """ 120 | 121 | let interpeter = Interpreter(program) 122 | interpeter.interpret() 123 | let (scalars, arrays) = interpeter.getState() 124 | XCTAssert(scalars == ["X": Value.number(.real(5)), "Y": Value.number(.real(3))]) 125 | XCTAssert(arrays.count == 0) 126 | } 127 | 128 | func testProgramWithRecursiveFunction() { 129 | let program = 130 | """ 131 | program Main; 132 | var result: integer; 133 | 134 | function Factorial(number: Integer): Integer; 135 | begin 136 | if number > 1 then 137 | Factorial := number * Factorial(number-1) 138 | else 139 | Factorial := 1 140 | end; 141 | 142 | begin { Main } 143 | result := Factorial(6); 144 | end. { Main } 145 | """ 146 | 147 | let interpeter = Interpreter(program) 148 | interpeter.interpret() 149 | let (scalars, arrays) = interpeter.getState() 150 | XCTAssert(scalars == ["RESULT": Value.number(.integer(720))]) 151 | XCTAssert(arrays.count == 0) 152 | } 153 | 154 | func testProgramWithRecursiveAndBuiltInFunctions() { 155 | let program = 156 | """ 157 | program Main; 158 | var result: integer; 159 | 160 | function Factorial(number: Integer): Integer; 161 | begin 162 | if number > 1 then 163 | Factorial := number * Factorial(number-1) 164 | else 165 | Factorial := 1 166 | end; 167 | 168 | begin { Main } 169 | result := Factorial(6); 170 | writeln(result); 171 | end. { Main } 172 | """ 173 | 174 | let interpeter = Interpreter(program) 175 | interpeter.interpret() 176 | let (scalars, arrays) = interpeter.getState() 177 | XCTAssert(scalars == ["RESULT": Value.number(.integer(720))]) 178 | XCTAssert(arrays.count == 0) 179 | } 180 | 181 | func testProgramWithRecursiveFunctionsAndParameterTheSameName() { 182 | let program = 183 | """ 184 | program Main; 185 | var number, result: integer; 186 | 187 | function Factorial(number: Integer): Integer; 188 | begin 189 | if number > 1 then 190 | Factorial := number * Factorial(number-1) 191 | else 192 | Factorial := 1 193 | end; 194 | 195 | begin { Main } 196 | number := 6; 197 | result := Factorial(number); 198 | end. { Main } 199 | """ 200 | 201 | let interpeter = Interpreter(program) 202 | interpeter.interpret() 203 | let (scalars, arrays) = interpeter.getState() 204 | XCTAssert(scalars == ["RESULT": Value.number(.integer(720)), "NUMBER": Value.number(.integer(6))]) 205 | XCTAssert(arrays.count == 0) 206 | } 207 | 208 | func testProgramWithRepeatUntil() { 209 | let program = 210 | """ 211 | program Main; 212 | var x: integer; 213 | 214 | begin 215 | x:=0; 216 | repeat 217 | x:=x+1; 218 | until x = 6; 219 | writeln(x); 220 | end. { Main } 221 | """ 222 | 223 | let interpeter = Interpreter(program) 224 | interpeter.interpret() 225 | let (scalars, arrays) = interpeter.getState() 226 | XCTAssert(scalars == ["X": Value.number(.integer(6))]) 227 | XCTAssert(arrays.count == 0) 228 | } 229 | 230 | func testProgramWithForLoop() { 231 | let program = 232 | """ 233 | program Main; 234 | var x: Integer; 235 | 236 | begin 237 | for i:=1 to 6 do begin 238 | writeln(i); 239 | x:= i 240 | end; 241 | end. { Main } 242 | """ 243 | 244 | let interpeter = Interpreter(program) 245 | interpeter.interpret() 246 | let (scalars, arrays) = interpeter.getState() 247 | XCTAssert(scalars == ["X": Value.number(.integer(6))]) 248 | XCTAssert(arrays.count == 0) 249 | } 250 | 251 | func testProgramWithArray() { 252 | let program = 253 | """ 254 | program Main; 255 | var data: array [1..5] of Integer; 256 | 257 | begin 258 | for i:=1 to length(data) do 259 | begin 260 | data[i] := i; 261 | end; 262 | writeln(data[2]); 263 | end. 264 | """ 265 | 266 | let interpeter = Interpreter(program) 267 | interpeter.interpret() 268 | let (scalars, arrays) = interpeter.getState() 269 | XCTAssert(scalars == [:]) 270 | XCTAssert(arrays.count == 1) 271 | XCTAssert(arrays["DATA"]! == [Value.number(.integer(1)), Value.number(.integer(2)), Value.number(.integer(3)), Value.number(.integer(4)), Value.number(.integer(5))]) 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreterTests/LexerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TokenizerTests.swift 3 | // SwiftPascalInterpreterTests 4 | // 5 | // Created by Igor Kulman on 06/12/2017. 6 | // Copyright © 2017 Igor Kulman. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @testable import PascalInterpreter 11 | import XCTest 12 | 13 | class LexerTests: XCTestCase { 14 | 15 | func testEmptyInput() { 16 | let lexer = Lexer("") 17 | let eof = lexer.getNextToken() 18 | XCTAssert(eof == .eof) 19 | } 20 | 21 | func testWhitespaceOnlyInput() { 22 | let lexer = Lexer(" ") 23 | let eof = lexer.getNextToken() 24 | XCTAssert(eof == .eof) 25 | } 26 | 27 | func testIntegerPlusInteger() { 28 | let lexer = Lexer("3+4") 29 | let left = lexer.getNextToken() 30 | let operation = lexer.getNextToken() 31 | let right = lexer.getNextToken() 32 | let eof = lexer.getNextToken() 33 | 34 | XCTAssert(left == .constant(.integer(3))) 35 | XCTAssert(operation == .operation(.plus)) 36 | XCTAssert(right == .constant(.integer(4))) 37 | XCTAssert(eof == .eof) 38 | } 39 | 40 | func testIntegerTimesInteger() { 41 | let lexer = Lexer("3*4") 42 | let left = lexer.getNextToken() 43 | let operation = lexer.getNextToken() 44 | let right = lexer.getNextToken() 45 | let eof = lexer.getNextToken() 46 | 47 | XCTAssert(left == .constant(.integer(3))) 48 | XCTAssert(operation == .operation(.mult)) 49 | XCTAssert(right == .constant(.integer(4))) 50 | XCTAssert(eof == .eof) 51 | } 52 | 53 | func testFloatDivideInteger() { 54 | let lexer = Lexer("3/4") 55 | let left = lexer.getNextToken() 56 | let operation = lexer.getNextToken() 57 | let right = lexer.getNextToken() 58 | let eof = lexer.getNextToken() 59 | 60 | XCTAssert(left == .constant(.integer(3))) 61 | XCTAssert(operation == .operation(.floatDiv)) 62 | XCTAssert(right == .constant(.integer(4))) 63 | XCTAssert(eof == .eof) 64 | } 65 | 66 | func testIntegerDivideInteger() { 67 | let lexer = Lexer("3 DIV 4") 68 | let left = lexer.getNextToken() 69 | let operation = lexer.getNextToken() 70 | let right = lexer.getNextToken() 71 | let eof = lexer.getNextToken() 72 | 73 | XCTAssert(left == .constant(.integer(3))) 74 | XCTAssert(operation == .operation(.integerDiv)) 75 | XCTAssert(right == .constant(.integer(4))) 76 | XCTAssert(eof == .eof) 77 | } 78 | 79 | func testMinusPlusInteger() { 80 | let lexer = Lexer("3 -4") 81 | let left = lexer.getNextToken() 82 | let operation = lexer.getNextToken() 83 | let right = lexer.getNextToken() 84 | let eof = lexer.getNextToken() 85 | 86 | XCTAssert(left == .constant(.integer(3))) 87 | XCTAssert(operation == .operation(.minus)) 88 | XCTAssert(right == .constant(.integer(4))) 89 | XCTAssert(eof == .eof) 90 | } 91 | 92 | func testIntegerPlusIntegerWithWhiteSace1() { 93 | let lexer = Lexer("3 +4") 94 | let left = lexer.getNextToken() 95 | let operation = lexer.getNextToken() 96 | let right = lexer.getNextToken() 97 | let eof = lexer.getNextToken() 98 | 99 | XCTAssert(left == .constant(.integer(3))) 100 | XCTAssert(operation == .operation(.plus)) 101 | XCTAssert(right == .constant(.integer(4))) 102 | XCTAssert(eof == .eof) 103 | } 104 | 105 | func testIntegerPlusIntegerWithWhiteSace2() { 106 | let lexer = Lexer("3 + 4") 107 | let left = lexer.getNextToken() 108 | let operation = lexer.getNextToken() 109 | let right = lexer.getNextToken() 110 | let eof = lexer.getNextToken() 111 | 112 | XCTAssert(left == .constant(.integer(3))) 113 | XCTAssert(operation == .operation(.plus)) 114 | XCTAssert(right == .constant(.integer(4))) 115 | XCTAssert(eof == .eof) 116 | } 117 | 118 | func testIntegerPlusIntegerWithWhiteSace3() { 119 | let lexer = Lexer(" 3 + 4") 120 | let left = lexer.getNextToken() 121 | let operation = lexer.getNextToken() 122 | let right = lexer.getNextToken() 123 | let eof = lexer.getNextToken() 124 | 125 | XCTAssert(left == .constant(.integer(3))) 126 | XCTAssert(operation == .operation(.plus)) 127 | XCTAssert(right == .constant(.integer(4))) 128 | XCTAssert(eof == .eof) 129 | } 130 | 131 | func testIntegerPlusIntegerWithWhiteSace4() { 132 | let lexer = Lexer("3+ 4 ") 133 | let left = lexer.getNextToken() 134 | let operation = lexer.getNextToken() 135 | let right = lexer.getNextToken() 136 | let eof = lexer.getNextToken() 137 | 138 | XCTAssert(left == .constant(.integer(3))) 139 | XCTAssert(operation == .operation(.plus)) 140 | XCTAssert(right == .constant(.integer(4))) 141 | XCTAssert(eof == .eof) 142 | } 143 | 144 | func testMultiDigitStrings() { 145 | let lexer = Lexer(" 13+ 154 ") 146 | let left = lexer.getNextToken() 147 | let operation = lexer.getNextToken() 148 | let right = lexer.getNextToken() 149 | let eof = lexer.getNextToken() 150 | 151 | XCTAssert(left == .constant(.integer(13))) 152 | XCTAssert(operation == .operation(.plus)) 153 | XCTAssert(right == .constant(.integer(154))) 154 | XCTAssert(eof == .eof) 155 | } 156 | 157 | func testParentheses() { 158 | let lexer = Lexer("(1+(3*5))") 159 | 160 | XCTAssert(lexer.getNextToken() == .parenthesis(.left)) 161 | XCTAssert(lexer.getNextToken() == .constant(.integer(1))) 162 | XCTAssert(lexer.getNextToken() == .operation(.plus)) 163 | XCTAssert(lexer.getNextToken() == .parenthesis(.left)) 164 | XCTAssert(lexer.getNextToken() == .constant(.integer(3))) 165 | XCTAssert(lexer.getNextToken() == .operation(.mult)) 166 | XCTAssert(lexer.getNextToken() == .constant(.integer(5))) 167 | XCTAssert(lexer.getNextToken() == .parenthesis(.right)) 168 | XCTAssert(lexer.getNextToken() == .parenthesis(.right)) 169 | XCTAssert(lexer.getNextToken() == .eof) 170 | } 171 | 172 | func testUnaryOperators() { 173 | let lexer = Lexer("5 - - - 2") 174 | 175 | XCTAssert(lexer.getNextToken() == .constant(.integer(5))) 176 | XCTAssert(lexer.getNextToken() == .operation(.minus)) 177 | XCTAssert(lexer.getNextToken() == .operation(.minus)) 178 | XCTAssert(lexer.getNextToken() == .operation(.minus)) 179 | XCTAssert(lexer.getNextToken() == .constant(.integer(2))) 180 | XCTAssert(lexer.getNextToken() == .eof) 181 | } 182 | 183 | func testPascalStructure() { 184 | let lexer = Lexer("BEGIN a := 2; END.") 185 | 186 | XCTAssert(lexer.getNextToken() == .begin) 187 | XCTAssert(lexer.getNextToken() == .id("a")) 188 | XCTAssert(lexer.getNextToken() == .assign) 189 | XCTAssert(lexer.getNextToken() == .constant(.integer(2))) 190 | XCTAssert(lexer.getNextToken() == .semi) 191 | XCTAssert(lexer.getNextToken() == .end) 192 | XCTAssert(lexer.getNextToken() == .dot) 193 | XCTAssert(lexer.getNextToken() == .eof) 194 | } 195 | 196 | func testComment() { 197 | let lexer = Lexer("{ a := 5 }") 198 | XCTAssert(lexer.getNextToken() == .eof) 199 | } 200 | 201 | func testAssignmentAndComment() { 202 | let lexer = Lexer("a := 2 { a := 5 }") 203 | XCTAssert(lexer.getNextToken() == .id("a")) 204 | XCTAssert(lexer.getNextToken() == .assign) 205 | XCTAssert(lexer.getNextToken() == .constant(.integer(2))) 206 | XCTAssert(lexer.getNextToken() == .eof) 207 | } 208 | 209 | func testCommentAndAssignment() { 210 | let lexer = Lexer(" { a := 5 } a := 2 ") 211 | XCTAssert(lexer.getNextToken() == .id("a")) 212 | XCTAssert(lexer.getNextToken() == .assign) 213 | XCTAssert(lexer.getNextToken() == .constant(.integer(2))) 214 | XCTAssert(lexer.getNextToken() == .eof) 215 | } 216 | 217 | func testTypesAndConstants() { 218 | let lexer = Lexer(" a: INTEGER, b: REAL, a:= 2, b:= 3.14 ") 219 | XCTAssert(lexer.getNextToken() == .id("a")) 220 | XCTAssert(lexer.getNextToken() == .colon) 221 | XCTAssert(lexer.getNextToken() == .type(.integer)) 222 | XCTAssert(lexer.getNextToken() == .coma) 223 | XCTAssert(lexer.getNextToken() == .id("b")) 224 | XCTAssert(lexer.getNextToken() == .colon) 225 | XCTAssert(lexer.getNextToken() == .type(.real)) 226 | XCTAssert(lexer.getNextToken() == .coma) 227 | XCTAssert(lexer.getNextToken() == .id("a")) 228 | XCTAssert(lexer.getNextToken() == .assign) 229 | XCTAssert(lexer.getNextToken() == .constant(.integer(2))) 230 | XCTAssert(lexer.getNextToken() == .coma) 231 | XCTAssert(lexer.getNextToken() == .id("b")) 232 | XCTAssert(lexer.getNextToken() == .assign) 233 | if case let .constant(.real(value)) = lexer.getNextToken() { 234 | XCTAssert(abs(3.14 - value) < 0.01) 235 | } else { 236 | XCTFail("Given value differes from expected 3.14") 237 | } 238 | XCTAssert(lexer.getNextToken() == .eof) 239 | } 240 | 241 | func testEmptyPascalProgram() { 242 | let program = 243 | """ 244 | PROGRAM Part10AST; 245 | BEGIN {Part10AST} 246 | END. {Part10AST} 247 | """ 248 | 249 | let lexer = Lexer(program) 250 | XCTAssert(lexer.getNextToken() == .program) 251 | XCTAssert(lexer.getNextToken() == .id("Part10AST")) 252 | XCTAssert(lexer.getNextToken() == .semi) 253 | XCTAssert(lexer.getNextToken() == .begin) 254 | XCTAssert(lexer.getNextToken() == .end) 255 | XCTAssert(lexer.getNextToken() == .dot) 256 | XCTAssert(lexer.getNextToken() == .eof) 257 | } 258 | 259 | func testPascalProgram() { 260 | let program = 261 | """ 262 | PROGRAM Part10AST; 263 | VAR 264 | a, b : INTEGER; 265 | 266 | BEGIN {Part10AST} 267 | a := 2; 268 | END. {Part10AST} 269 | """ 270 | 271 | let lexer = Lexer(program) 272 | XCTAssert(lexer.getNextToken() == .program) 273 | XCTAssert(lexer.getNextToken() == .id("Part10AST")) 274 | XCTAssert(lexer.getNextToken() == .semi) 275 | XCTAssert(lexer.getNextToken() == .varDef) 276 | XCTAssert(lexer.getNextToken() == .id("a")) 277 | XCTAssert(lexer.getNextToken() == .coma) 278 | XCTAssert(lexer.getNextToken() == .id("b")) 279 | XCTAssert(lexer.getNextToken() == .colon) 280 | XCTAssert(lexer.getNextToken() == .type(.integer)) 281 | XCTAssert(lexer.getNextToken() == .semi) 282 | XCTAssert(lexer.getNextToken() == .begin) 283 | XCTAssert(lexer.getNextToken() == .id("a")) 284 | XCTAssert(lexer.getNextToken() == .assign) 285 | XCTAssert(lexer.getNextToken() == .constant(.integer(2))) 286 | XCTAssert(lexer.getNextToken() == .semi) 287 | XCTAssert(lexer.getNextToken() == .end) 288 | XCTAssert(lexer.getNextToken() == .dot) 289 | XCTAssert(lexer.getNextToken() == .eof) 290 | } 291 | 292 | func testPascalProgramWithProcedure() { 293 | let program = 294 | """ 295 | PROGRAM Part10AST; 296 | VAR 297 | a, b : INTEGER; 298 | 299 | PROCEDURE P1; 300 | BEGIN {P1} 301 | 302 | END; {P1} 303 | 304 | BEGIN {Part10AST} 305 | a := 2; 306 | END. {Part10AST} 307 | """ 308 | 309 | let lexer = Lexer(program) 310 | XCTAssert(lexer.getNextToken() == .program) 311 | XCTAssert(lexer.getNextToken() == .id("Part10AST")) 312 | XCTAssert(lexer.getNextToken() == .semi) 313 | XCTAssert(lexer.getNextToken() == .varDef) 314 | XCTAssert(lexer.getNextToken() == .id("a")) 315 | XCTAssert(lexer.getNextToken() == .coma) 316 | XCTAssert(lexer.getNextToken() == .id("b")) 317 | XCTAssert(lexer.getNextToken() == .colon) 318 | XCTAssert(lexer.getNextToken() == .type(.integer)) 319 | XCTAssert(lexer.getNextToken() == .semi) 320 | XCTAssert(lexer.getNextToken() == .procedure) 321 | XCTAssert(lexer.getNextToken() == .id("P1")) 322 | XCTAssert(lexer.getNextToken() == .semi) 323 | XCTAssert(lexer.getNextToken() == .begin) 324 | XCTAssert(lexer.getNextToken() == .end) 325 | XCTAssert(lexer.getNextToken() == .semi) 326 | XCTAssert(lexer.getNextToken() == .begin) 327 | XCTAssert(lexer.getNextToken() == .id("a")) 328 | XCTAssert(lexer.getNextToken() == .assign) 329 | XCTAssert(lexer.getNextToken() == .constant(.integer(2))) 330 | XCTAssert(lexer.getNextToken() == .semi) 331 | XCTAssert(lexer.getNextToken() == .end) 332 | XCTAssert(lexer.getNextToken() == .dot) 333 | XCTAssert(lexer.getNextToken() == .eof) 334 | } 335 | 336 | func testMixedCasePascalProgram() { 337 | let program = 338 | """ 339 | Program Part10AST; 340 | Var 341 | a, b : Integer; 342 | 343 | beGIN {Part10AST} 344 | a := 2; 345 | End. {Part10AST} 346 | """ 347 | 348 | let lexer = Lexer(program) 349 | XCTAssert(lexer.getNextToken() == .program) 350 | XCTAssert(lexer.getNextToken() == .id("Part10AST")) 351 | XCTAssert(lexer.getNextToken() == .semi) 352 | XCTAssert(lexer.getNextToken() == .varDef) 353 | XCTAssert(lexer.getNextToken() == .id("a")) 354 | XCTAssert(lexer.getNextToken() == .coma) 355 | XCTAssert(lexer.getNextToken() == .id("b")) 356 | XCTAssert(lexer.getNextToken() == .colon) 357 | XCTAssert(lexer.getNextToken() == .type(.integer)) 358 | XCTAssert(lexer.getNextToken() == .semi) 359 | XCTAssert(lexer.getNextToken() == .begin) 360 | XCTAssert(lexer.getNextToken() == .id("a")) 361 | XCTAssert(lexer.getNextToken() == .assign) 362 | XCTAssert(lexer.getNextToken() == .constant(.integer(2))) 363 | XCTAssert(lexer.getNextToken() == .semi) 364 | XCTAssert(lexer.getNextToken() == .end) 365 | XCTAssert(lexer.getNextToken() == .dot) 366 | XCTAssert(lexer.getNextToken() == .eof) 367 | } 368 | 369 | func testParsingError() { 370 | let lexer = Lexer("8 + |") 371 | XCTAssert(lexer.getNextToken() == .constant(.integer(8))) 372 | XCTAssert(lexer.getNextToken() == .operation(.plus)) 373 | expectFatalError(expectedMessage: "Unrecognized character | at position 4") { 374 | _ = lexer.getNextToken() 375 | } 376 | } 377 | 378 | func testIfElseConditions() { 379 | let lexer = Lexer("if x = 5 then y:=2 else y:= 8") 380 | XCTAssert(lexer.getNextToken() == .if) 381 | XCTAssert(lexer.getNextToken() == .id("x")) 382 | XCTAssert(lexer.getNextToken() == .equals) 383 | XCTAssert(lexer.getNextToken() == .constant(.integer(5))) 384 | XCTAssert(lexer.getNextToken() == .then) 385 | XCTAssert(lexer.getNextToken() == .id("y")) 386 | XCTAssert(lexer.getNextToken() == .assign) 387 | XCTAssert(lexer.getNextToken() == .constant(.integer(2))) 388 | XCTAssert(lexer.getNextToken() == .else) 389 | XCTAssert(lexer.getNextToken() == .id("y")) 390 | XCTAssert(lexer.getNextToken() == .assign) 391 | XCTAssert(lexer.getNextToken() == .constant(.integer(8))) 392 | } 393 | 394 | func testComparisons() { 395 | let lexer = Lexer("x = 5; a <4; 5 > 8") 396 | XCTAssert(lexer.getNextToken() == .id("x")) 397 | XCTAssert(lexer.getNextToken() == .equals) 398 | XCTAssert(lexer.getNextToken() == .constant(.integer(5))) 399 | XCTAssert(lexer.getNextToken() == .semi) 400 | XCTAssert(lexer.getNextToken() == .id("a")) 401 | XCTAssert(lexer.getNextToken() == .lessThan) 402 | XCTAssert(lexer.getNextToken() == .constant(.integer(4))) 403 | XCTAssert(lexer.getNextToken() == .semi) 404 | XCTAssert(lexer.getNextToken() == .constant(.integer(5))) 405 | XCTAssert(lexer.getNextToken() == .greaterThan) 406 | XCTAssert(lexer.getNextToken() == .constant(.integer(8))) 407 | XCTAssert(lexer.getNextToken() == .eof) 408 | } 409 | 410 | func testDataTypes() { 411 | let lexer = Lexer("a: Integer; b: Real; c: Boolean; c:= true; c:=false; d:= 'Test string'; 'Another'") 412 | XCTAssert(lexer.getNextToken() == .id("a")) 413 | XCTAssert(lexer.getNextToken() == .colon) 414 | XCTAssert(lexer.getNextToken() == .type(.integer)) 415 | XCTAssert(lexer.getNextToken() == .semi) 416 | XCTAssert(lexer.getNextToken() == .id("b")) 417 | XCTAssert(lexer.getNextToken() == .colon) 418 | XCTAssert(lexer.getNextToken() == .type(.real)) 419 | XCTAssert(lexer.getNextToken() == .semi) 420 | XCTAssert(lexer.getNextToken() == .id("c")) 421 | XCTAssert(lexer.getNextToken() == .colon) 422 | XCTAssert(lexer.getNextToken() == .type(.boolean)) 423 | XCTAssert(lexer.getNextToken() == .semi) 424 | XCTAssert(lexer.getNextToken() == .id("c")) 425 | XCTAssert(lexer.getNextToken() == .assign) 426 | XCTAssert(lexer.getNextToken() == .constant(.boolean(true))) 427 | XCTAssert(lexer.getNextToken() == .semi) 428 | XCTAssert(lexer.getNextToken() == .id("c")) 429 | XCTAssert(lexer.getNextToken() == .assign) 430 | XCTAssert(lexer.getNextToken() == .constant(.boolean(false))) 431 | XCTAssert(lexer.getNextToken() == .semi) 432 | XCTAssert(lexer.getNextToken() == .id("d")) 433 | XCTAssert(lexer.getNextToken() == .assign) 434 | XCTAssert(lexer.getNextToken() == .apostrophe) 435 | XCTAssert(lexer.getNextToken() == .constant(.string("Test string"))) 436 | XCTAssert(lexer.getNextToken() == .apostrophe) 437 | XCTAssert(lexer.getNextToken() == .semi) 438 | XCTAssert(lexer.getNextToken() == .apostrophe) 439 | XCTAssert(lexer.getNextToken() == .constant(.string("Another"))) 440 | XCTAssert(lexer.getNextToken() == .apostrophe) 441 | XCTAssert(lexer.getNextToken() == .eof) 442 | } 443 | 444 | func testRepetUntilTokens() { 445 | let lexer = Lexer("repeat x:= x+1 until x=5") 446 | XCTAssert(lexer.getNextToken() == .repeat) 447 | XCTAssert(lexer.getNextToken() == .id("x")) 448 | XCTAssert(lexer.getNextToken() == .assign) 449 | XCTAssert(lexer.getNextToken() == .id("x")) 450 | XCTAssert(lexer.getNextToken() == .operation(.plus)) 451 | XCTAssert(lexer.getNextToken() == .constant(.integer(1))) 452 | XCTAssert(lexer.getNextToken() == .until) 453 | XCTAssert(lexer.getNextToken() == .id("x")) 454 | XCTAssert(lexer.getNextToken() == .equals) 455 | XCTAssert(lexer.getNextToken() == .constant(.integer(5))) 456 | XCTAssert(lexer.getNextToken() == .eof) 457 | } 458 | 459 | func testForLoopTokens() { 460 | let lexer = Lexer("for x:= 1 to 5 do writeln(5)") 461 | XCTAssert(lexer.getNextToken() == .for) 462 | XCTAssert(lexer.getNextToken() == .id("x")) 463 | XCTAssert(lexer.getNextToken() == .assign) 464 | XCTAssert(lexer.getNextToken() == .constant(.integer(1))) 465 | XCTAssert(lexer.getNextToken() == .to) 466 | XCTAssert(lexer.getNextToken() == .constant(.integer(5))) 467 | XCTAssert(lexer.getNextToken() == .do) 468 | XCTAssert(lexer.getNextToken() == .id("writeln")) 469 | XCTAssert(lexer.getNextToken() == .parenthesis(.left)) 470 | XCTAssert(lexer.getNextToken() == .constant(.integer(5))) 471 | XCTAssert(lexer.getNextToken() == .parenthesis(.right)) 472 | XCTAssert(lexer.getNextToken() == .eof) 473 | } 474 | 475 | func testWhileTokens() { 476 | let lexer = Lexer("while x<5 do x:= x+1 ") 477 | XCTAssert(lexer.getNextToken() == .while) 478 | XCTAssert(lexer.getNextToken() == .id("x")) 479 | XCTAssert(lexer.getNextToken() == .lessThan) 480 | XCTAssert(lexer.getNextToken() == .constant(.integer(5))) 481 | XCTAssert(lexer.getNextToken() == .do) 482 | XCTAssert(lexer.getNextToken() == .id("x")) 483 | XCTAssert(lexer.getNextToken() == .assign) 484 | XCTAssert(lexer.getNextToken() == .id("x")) 485 | XCTAssert(lexer.getNextToken() == .operation(.plus)) 486 | XCTAssert(lexer.getNextToken() == .constant(.integer(1))) 487 | XCTAssert(lexer.getNextToken() == .eof) 488 | } 489 | 490 | func testArrayTokens() { 491 | let lexer = Lexer("var x: array [1..5] of Integer") 492 | XCTAssert(lexer.getNextToken() == .varDef) 493 | XCTAssert(lexer.getNextToken() == .id("x")) 494 | XCTAssert(lexer.getNextToken() == .colon) 495 | XCTAssert(lexer.getNextToken() == .array) 496 | XCTAssert(lexer.getNextToken() == .bracket(.left)) 497 | XCTAssert(lexer.getNextToken() == .constant(.integer(1))) 498 | XCTAssert(lexer.getNextToken() == .dot) 499 | XCTAssert(lexer.getNextToken() == .dot) 500 | XCTAssert(lexer.getNextToken() == .constant(.integer(5))) 501 | XCTAssert(lexer.getNextToken() == .bracket(.right)) 502 | XCTAssert(lexer.getNextToken() == .of) 503 | XCTAssert(lexer.getNextToken() == .type(.integer)) 504 | XCTAssert(lexer.getNextToken() == .eof) 505 | } 506 | } 507 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreterTests/ParserTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParserTests.swift 3 | // SwiftPascalInterpreterTests 4 | // 5 | // Created by Igor Kulman on 07/12/2017. 6 | // Copyright © 2017 Igor Kulman. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @testable import PascalInterpreter 11 | import XCTest 12 | 13 | class ParserTests: XCTestCase { 14 | func testBasicCompoundStatement() { 15 | let program = 16 | """ 17 | PROGRAM Part10AST; 18 | BEGIN 19 | a := 2 20 | END. 21 | """ 22 | 23 | let a = Variable(name: "a") 24 | let two = Number.integer(2) 25 | let assignment = Assignment(left: a, right: two) 26 | let block = Block(declarations: [], compound: Compound(children: [assignment])) 27 | let node = Program(name: "Part10AST", block: block) 28 | let parser = Parser(program) 29 | let result = parser.parse() 30 | XCTAssertEqual(result, node) 31 | } 32 | 33 | func testMoreComplexExpression() { 34 | let program = 35 | """ 36 | PROGRAM Part10AST; 37 | BEGIN 38 | BEGIN 39 | number := 2; 40 | a := number + 2; 41 | END; 42 | x := 11; 43 | END. 44 | """ 45 | 46 | let parser = Parser(program) 47 | let result = parser.parse() 48 | let empty = NoOp() 49 | let eleven = Number.integer(11) 50 | let x = Variable(name: "x") 51 | let xAssignment = Assignment(left: x, right: eleven) 52 | let two = Number.integer(2) 53 | let number = Variable(name: "number") 54 | let a = Variable(name: "a") 55 | let aAssignment = Assignment(left: a, right: BinaryOperation(left: number, operation: .plus, right: two)) 56 | let compound = Compound(children: [Assignment(left: number, right: two), aAssignment, empty]) 57 | let block = Block(declarations: [], compound: Compound(children: [compound, xAssignment, empty])) 58 | let node = Program(name: "Part10AST", block: block) 59 | XCTAssertEqual(result, node) 60 | } 61 | 62 | func testEvenMoreComplexExpression() { 63 | let program = 64 | """ 65 | PROGRAM Part10AST; 66 | BEGIN 67 | BEGIN 68 | number := 2; 69 | a := number; 70 | a := 10 * a + 10 * number / 4; 71 | END; 72 | x := 11; 73 | END. 74 | """ 75 | 76 | let parser = Parser(program) 77 | let result = parser.parse() 78 | let empty = NoOp() 79 | let eleven = Number.integer(11) 80 | let x = Variable(name: "x") 81 | let xAssignment = Assignment(left: x, right: eleven) 82 | let two = Number.integer(2) 83 | let number = Variable(name: "number") 84 | let a = Variable(name: "a") 85 | let division = BinaryOperation(left: BinaryOperation(left: Number.integer(10), operation: .mult, right: number), operation: .floatDiv, right: Number.integer(4)) 86 | let plus = BinaryOperation(left: BinaryOperation(left: Number.integer(10), operation: .mult, right: a), operation: .plus, right: division) 87 | let aAssignment = Assignment(left: a, right: plus) 88 | let compound = Compound(children: [Assignment(left: number, right: two), Assignment(left: a, right: number), aAssignment, empty]) 89 | let node = Program(name: "Part10AST", block: Block(declarations: [], compound: Compound(children: [compound, xAssignment, empty]))) 90 | XCTAssertEqual(result, node) 91 | } 92 | 93 | func testProgramWithDeclarationsExpression() { 94 | let program = 95 | """ 96 | PROGRAM Part10AST; 97 | VAR 98 | a, b : INTEGER; 99 | y : REAL; 100 | BEGIN 101 | BEGIN 102 | number := 2; 103 | a := number; 104 | a := 10 * a + 10 * number / 4; 105 | END; 106 | x := 11; 107 | END. 108 | """ 109 | 110 | let parser = Parser(program) 111 | let result = parser.parse() 112 | let empty = NoOp() 113 | let eleven = Number.integer(11) 114 | let x = Variable(name: "x") 115 | let xAssignment = Assignment(left: x, right: eleven) 116 | let two = Number.integer(2) 117 | let number = Variable(name: "number") 118 | let a = Variable(name: "a") 119 | let division = BinaryOperation(left: BinaryOperation(left: Number.integer(10), operation: .mult, right: number), operation: .floatDiv, right: Number.integer(4)) 120 | let plus = BinaryOperation(left: BinaryOperation(left: Number.integer(10), operation: .mult, right: a), operation: .plus, right: division) 121 | let aAssignment = Assignment(left: a, right: plus) 122 | let compound = Compound(children: [Assignment(left: number, right: two), Assignment(left: a, right: number), aAssignment, empty]) 123 | let aDec = VariableDeclaration(variable: Variable(name: "a"), type: VariableType(type: .integer)) 124 | let bDec = VariableDeclaration(variable: Variable(name: "b"), type: VariableType(type: .integer)) 125 | let yDec = VariableDeclaration(variable: Variable(name: "y"), type: VariableType(type: .real)) 126 | let node = Program(name: "Part10AST", block: Block(declarations: [aDec, bDec, yDec], compound: Compound(children: [compound, xAssignment, empty]))) 127 | XCTAssertEqual(result, node) 128 | } 129 | 130 | func testBasicCompoundStatementFail() { 131 | let program = 132 | """ 133 | PROGRAM Part10AST; 134 | BEGIN 135 | a := 2 136 | END 137 | """ 138 | 139 | let parser = Parser(program) 140 | expectFatalError(expectedMessage: "Syntax error, expected DOT, got EOF") { 141 | _ = parser.parse() 142 | } 143 | } 144 | 145 | func testProgramWithProcedure() { 146 | let program = 147 | """ 148 | PROGRAM Part10AST; 149 | VAR 150 | a, b : INTEGER; 151 | 152 | PROCEDURE P1; 153 | BEGIN {P1} 154 | 155 | END; {P1} 156 | 157 | BEGIN {Part10AST} 158 | a := 2; 159 | END. {Part10AST} 160 | """ 161 | 162 | let parser = Parser(program) 163 | let result = parser.parse() 164 | let empty = NoOp() 165 | let two = Number.integer(2) 166 | let a = Variable(name: "a") 167 | let compound = Compound(children: [Assignment(left: a, right: two), empty]) 168 | let aDec = VariableDeclaration(variable: Variable(name: "a"), type: VariableType(type: .integer)) 169 | let bDec = VariableDeclaration(variable: Variable(name: "b"), type: VariableType(type: .integer)) 170 | let p1 = Procedure(name: "P1", params: [], block: Block(declarations: [], compound: Compound(children: [empty]))) 171 | let node = Program(name: "Part10AST", block: Block(declarations: [aDec, bDec, p1], compound: compound)) 172 | XCTAssertEqual(result, node) 173 | } 174 | 175 | func testProgramWithNestedProcedures() { 176 | let program = 177 | """ 178 | PROGRAM Part12; 179 | VAR 180 | a : INTEGER; 181 | 182 | PROCEDURE P1; 183 | VAR 184 | a : REAL; 185 | k : INTEGER; 186 | 187 | PROCEDURE P2; 188 | VAR 189 | a, z : INTEGER; 190 | BEGIN {P2} 191 | z := 777; 192 | END; {P2} 193 | 194 | BEGIN {P1} 195 | 196 | END; {P1} 197 | 198 | BEGIN {Part12} 199 | a := 10; 200 | END. {Part12} 201 | """ 202 | 203 | let parser = Parser(program) 204 | let result = parser.parse() 205 | let empty = NoOp() 206 | let ten = Number.integer(10) 207 | let a = Variable(name: "a") 208 | let compound = Compound(children: [Assignment(left: a, right: ten), empty]) 209 | let aDec = VariableDeclaration(variable: Variable(name: "a"), type: VariableType(type: .integer)) 210 | let p2 = Procedure(name: "P2", params: [], block: Block(declarations: [VariableDeclaration(variable: Variable(name: "a"), type: VariableType(type: .integer)), VariableDeclaration(variable: Variable(name: "z"), type: VariableType(type: .integer))], compound: Compound(children: [Assignment(left: Variable(name: "z"), right: Number.integer(777)), empty]))) 211 | let p1 = Procedure(name: "P1", params: [], block: Block(declarations: [VariableDeclaration(variable: Variable(name: "a"), type: VariableType(type: .real)), VariableDeclaration(variable: Variable(name: "k"), type: VariableType(type: .integer)), p2], compound: Compound(children: [empty]))) 212 | let node = Program(name: "Part12", block: Block(declarations: [aDec, p1], compound: compound)) 213 | XCTAssertEqual(result, node) 214 | } 215 | 216 | func testProgramWithProcedureCall() { 217 | let program = 218 | """ 219 | program Main; 220 | var x, y: real; 221 | 222 | procedure Alpha(); 223 | begin 224 | x := 5; 225 | end; 226 | 227 | begin { Main } 228 | Alpha(); 229 | y := 5; 230 | Alpha(); 231 | end. { Main } 232 | """ 233 | 234 | let parser = Parser(program) 235 | let result = parser.parse() 236 | let alpha = Procedure(name: "Alpha", params: [], block: Block(declarations: [], compound: Compound(children: [Assignment(left: Variable(name: "x"), right: Number.integer(5)), NoOp()]))) 237 | let xDec = VariableDeclaration(variable: Variable(name: "x"), type: VariableType(type: .real)) 238 | let yDec = VariableDeclaration(variable: Variable(name: "y"), type: VariableType(type: .real)) 239 | let compound = Compound(children: [FunctionCall(name: "Alpha", actualParameters: []), Assignment(left: Variable(name: "y"), right: Number.integer(5)), FunctionCall(name: "Alpha", actualParameters: []), NoOp()]) 240 | let node = Program(name: "Main", block: Block(declarations: [xDec, yDec, alpha], compound: compound)) 241 | XCTAssertEqual(result, node) 242 | } 243 | 244 | func testProgramWithProcedureCallAndParameters() { 245 | let program = 246 | """ 247 | program Main; 248 | var x, y: real; 249 | 250 | procedure Alpha(a: Integer); 251 | begin 252 | x := 5 + a; 253 | end; 254 | 255 | begin { Main } 256 | y := 5; 257 | Alpha(5); 258 | end. { Main } 259 | """ 260 | 261 | let parser = Parser(program) 262 | let result = parser.parse() 263 | let alpha = Procedure(name: "Alpha", params: [Param(name: "a", type: VariableType(type: .integer))], block: Block(declarations: [], compound: Compound(children: [Assignment(left: Variable(name: "x"), right: BinaryOperation(left: Number.integer(5), operation: .plus, right: Variable(name: "a"))), NoOp()]))) 264 | let xDec = VariableDeclaration(variable: Variable(name: "x"), type: VariableType(type: .real)) 265 | let yDec = VariableDeclaration(variable: Variable(name: "y"), type: VariableType(type: .real)) 266 | let compound = Compound(children: [Assignment(left: Variable(name: "y"), right: Number.integer(5)), FunctionCall(name: "Alpha", actualParameters: [Number.integer(5)]), NoOp()]) 267 | let node = Program(name: "Main", block: Block(declarations: [xDec, yDec, alpha], compound: compound)) 268 | XCTAssertEqual(result, node) 269 | } 270 | 271 | func testProgramWithIfElseCondition() { 272 | let program = 273 | """ 274 | program Main; 275 | var x, y: real; 276 | 277 | begin { Main } 278 | y := 5; 279 | if y = 5 then 280 | x:=2 281 | else 282 | x:= 3 283 | end. { Main } 284 | """ 285 | let parser = Parser(program) 286 | let result = parser.parse() 287 | let xDec = VariableDeclaration(variable: Variable(name: "x"), type: VariableType(type: .real)) 288 | let yDec = VariableDeclaration(variable: Variable(name: "y"), type: VariableType(type: .real)) 289 | let compound = Compound(children: [Assignment(left: Variable(name: "y"), right: Number.integer(5)), IfElse(condition: Condition(type: .equals, leftSide: Variable(name: "y"), rightSide: Number.integer(5)), trueExpression: Assignment(left: Variable(name: "x"), right: Number.integer(2)), falseExpression: Assignment(left: Variable(name: "x"), right: Number.integer(3)))]) 290 | let node = Program(name: "Main", block: Block(declarations: [xDec, yDec], compound: compound)) 291 | XCTAssertEqual(result, node) 292 | } 293 | 294 | func testProgramWithIfCondition() { 295 | let program = 296 | """ 297 | program Main; 298 | var x, y: real; 299 | 300 | begin { Main } 301 | y := 5; 302 | if y = 5 then 303 | x:=2 304 | end. { Main } 305 | """ 306 | let parser = Parser(program) 307 | let result = parser.parse() 308 | let xDec = VariableDeclaration(variable: Variable(name: "x"), type: VariableType(type: .real)) 309 | let yDec = VariableDeclaration(variable: Variable(name: "y"), type: VariableType(type: .real)) 310 | let compound = Compound(children: [Assignment(left: Variable(name: "y"), right: Number.integer(5)), IfElse(condition: Condition(type: .equals, leftSide: Variable(name: "y"), rightSide: Number.integer(5)), trueExpression: Assignment(left: Variable(name: "x"), right: Number.integer(2)), falseExpression: nil)]) 311 | let node = Program(name: "Main", block: Block(declarations: [xDec, yDec], compound: compound)) 312 | XCTAssertEqual(result, node) 313 | } 314 | 315 | func testProgramWithProcedureWithVariable() { 316 | let program = 317 | """ 318 | program Main; 319 | begin { Main } 320 | Factorial(x); 321 | end. { Main } 322 | """ 323 | 324 | let parser = Parser(program) 325 | let result = parser.parse() 326 | let compound = Compound(children: [FunctionCall(name: "Factorial", actualParameters: [Variable(name: "x")]), NoOp()]) 327 | let node = Program(name: "Main", block: Block(declarations: [], compound: compound)) 328 | XCTAssertEqual(result, node) 329 | } 330 | 331 | func testProgramWithFunctionDeclaration() { 332 | let program = 333 | """ 334 | program Main; 335 | function Alpha(number: Integer): Integer; 336 | begin 337 | a := number 338 | end; 339 | 340 | begin { Main } 341 | 342 | end. { Main } 343 | """ 344 | 345 | let parser = Parser(program) 346 | let result = parser.parse() 347 | let compound = Compound(children: [NoOp()]) 348 | let a = Variable(name: "a") 349 | let number = Variable(name: "number") 350 | let block = Block(declarations: [], compound: Compound(children: [Assignment(left: a, right: number)])) 351 | let alpha = Function(name: "Alpha", params: [Param(name: "number", type: VariableType(type: .integer))], block: block, returnType: VariableType(type: .integer)) 352 | let node = Program(name: "Main", block: Block(declarations: [alpha], compound: compound)) 353 | XCTAssertEqual(result, node) 354 | } 355 | 356 | func testProgramWithFunctionCall() { 357 | let program = 358 | """ 359 | program Main; 360 | var result: integer; 361 | 362 | function Factorial(number: Integer): Integer; 363 | begin 364 | if number > 1 then 365 | Factorial := number * Factorial(number-1) 366 | else 367 | Factorial := 1 368 | end; 369 | 370 | begin { Main } 371 | result := Factorial(6); 372 | end. { Main } 373 | """ 374 | 375 | let parser = Parser(program) 376 | let result = parser.parse() 377 | let compound = Compound(children: [Assignment(left: Variable(name: "result"), right: FunctionCall(name: "Factorial", actualParameters: [Number.integer(6)])), NoOp()]) 378 | let resultDeclaration = VariableDeclaration(variable: Variable(name: "result"), type: VariableType(type: .integer)) 379 | let ifelse = IfElse(condition: Condition(type: .greaterThan, leftSide: Variable(name: "number"), rightSide: Number.integer(1)), trueExpression: Assignment(left: Variable(name: "Factorial"), right: BinaryOperation(left: Variable(name: "number"), operation: .mult, right: FunctionCall(name: "Factorial", actualParameters: [BinaryOperation(left: Variable(name: "number"), operation: .minus, right: Number.integer(1))]))), falseExpression: Assignment(left: Variable(name: "Factorial"), right: Number.integer(1))) 380 | let factorialBlock = Block(declarations: [], compound: Compound(children: [ifelse])) 381 | let factorialDeclarion = Function(name: "Factorial", params: [Param(name: "number", type: VariableType(type: .integer))], block: factorialBlock, returnType: VariableType(type: .integer)) 382 | let block = Block(declarations: [resultDeclaration, factorialDeclarion], compound: compound) 383 | let node = Program(name: "Main", block: block) 384 | XCTAssertEqual(result, node) 385 | } 386 | 387 | func testProgramWithStringConstants() { 388 | let program = 389 | """ 390 | program Main; 391 | var s: String; 392 | a: Boolean; 393 | begin { Main } 394 | s := 'Test'; 395 | a := false; 396 | writeln(s,a); 397 | end. { Main } 398 | """ 399 | 400 | let parser = Parser(program) 401 | let result = parser.parse() 402 | let compound = Compound(children: [Assignment(left: Variable(name: "s"), right: "Test"), Assignment(left: Variable(name: "a"), right: false), FunctionCall(name: "writeln", actualParameters: [Variable(name: "s"), Variable(name: "a")]), NoOp()]) 403 | let block = Block(declarations: [VariableDeclaration(variable: Variable(name: "s"), type: VariableType(type: .string)), VariableDeclaration(variable: Variable(name: "a"), type: VariableType(type: .boolean))], compound: compound) 404 | let node = Program(name: "Main", block: block) 405 | XCTAssertEqual(result, node) 406 | } 407 | 408 | func testProgramWithRepeatUntil() { 409 | let program = 410 | """ 411 | program Main; 412 | var x: integer; 413 | 414 | begin 415 | x:=0; 416 | repeat 417 | x:=x+1; 418 | until x = 6; 419 | writeln(x); 420 | end. { Main } 421 | """ 422 | 423 | let parser = Parser(program) 424 | let result = parser.parse() 425 | let xDec = VariableDeclaration(variable: Variable(name: "x"), type: VariableType(type: .integer)) 426 | 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()]) 427 | let block = Block(declarations: [xDec], compound: compound) 428 | let node = Program(name: "Main", block: block) 429 | XCTAssertEqual(result, node) 430 | } 431 | 432 | func testProgramWithForLoop() { 433 | let program = 434 | """ 435 | program Main; 436 | 437 | begin 438 | for i:=1 to 6 do writeln(i); 439 | end. { Main } 440 | """ 441 | 442 | let parser = Parser(program) 443 | let result = parser.parse() 444 | let loop = For(statement: FunctionCall(name: "writeln", actualParameters: [Variable(name: "i")]), variable: Variable(name: "i"), startValue: Number.integer(1), endValue: Number.integer(6)) 445 | let compound = Compound(children: [loop, NoOp()]) 446 | let block = Block(declarations: [], compound: compound) 447 | let node = Program(name: "Main", block: block) 448 | XCTAssertEqual(result, node) 449 | } 450 | 451 | func testProgramWithForLoopAndCompoundStatement() { 452 | let program = 453 | """ 454 | program Main; 455 | 456 | begin 457 | for i:=1 to 6 do 458 | begin 459 | writeln(i); 460 | end; 461 | end. { Main } 462 | """ 463 | 464 | let parser = Parser(program) 465 | let result = parser.parse() 466 | let loop = For(statement: Compound(children: [FunctionCall(name: "writeln", actualParameters: [Variable(name: "i")]), NoOp()]), variable: Variable(name: "i"), startValue: Number.integer(1), endValue: Number.integer(6)) 467 | let compound = Compound(children: [loop, NoOp()]) 468 | let block = Block(declarations: [], compound: compound) 469 | let node = Program(name: "Main", block: block) 470 | XCTAssertEqual(result, node) 471 | } 472 | 473 | func testProgramWithWhileLoop() { 474 | let program = 475 | """ 476 | program Main; 477 | var x: integer; 478 | 479 | begin 480 | x:=0; 481 | while x < 6 do 482 | x:=x+1; 483 | writeln(x); 484 | end. { Main } 485 | """ 486 | 487 | let parser = Parser(program) 488 | let result = parser.parse() 489 | let xDec = VariableDeclaration(variable: Variable(name: "x"), type: VariableType(type: .integer)) 490 | let compound = Compound(children: [Assignment(left: Variable(name: "x"), right: Number.integer(0)), While(statement: Assignment(left: Variable(name: "x"), right: BinaryOperation(left: Variable(name: "x"), operation: BinaryOperationType.plus, right: Number.integer(1))), condition: Condition(type: .lessThan, leftSide: Variable(name: "x"), rightSide: Number.integer(6))), FunctionCall(name: "writeln", actualParameters: [Variable(name: "x")]), NoOp()]) 491 | let block = Block(declarations: [xDec], compound: compound) 492 | let node = Program(name: "Main", block: block) 493 | XCTAssertEqual(result, node) 494 | } 495 | 496 | func testProgramWithArray() { 497 | let program = 498 | """ 499 | program Main; 500 | var data: array [1..5] of Integer; 501 | 502 | begin 503 | for i:=1 to length(data) do 504 | begin 505 | data[i] := i; 506 | end; 507 | writeln(data[2]); 508 | end. 509 | """ 510 | 511 | let parser = Parser(program) 512 | let result = parser.parse() 513 | let dataDec = ArrayDeclaration(variable: Variable(name: "data"), type: VariableType(type: .integer), startIndex: 1, endIndex: 5) 514 | let writeln = FunctionCall(name: "writeln", actualParameters: [ArrayVariable(name: "data", index: Number.integer(2))]) 515 | let loop = For(statement: Compound(children: [Assignment(left: ArrayVariable(name: "data", index: Variable(name: "i")), right: Variable(name: "i")), NoOp()]), variable: Variable(name: "i"), startValue: Number.integer(1), endValue: FunctionCall(name: "length", actualParameters: [Variable(name: "data")])) 516 | let compound = Compound(children: [loop, writeln, NoOp()]) 517 | let block = Block(declarations: [dataDec], compound: compound) 518 | let node = Program(name: "Main", block: block) 519 | XCTAssertEqual(result, node) 520 | } 521 | } 522 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreterTests/SemanticAnalyzerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SymbolTableTests.swift 3 | // SwiftPascalInterpreterTests 4 | // 5 | // Created by Igor Kulman on 10/12/2017. 6 | // Copyright © 2017 Igor Kulman. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @testable import PascalInterpreter 11 | import XCTest 12 | 13 | class SemanticAnalyzerTests: XCTestCase { 14 | func testSemanticAnalyzer() { 15 | let program = 16 | """ 17 | PROGRAM Part10AST; 18 | VAR 19 | a, b, number : INTEGER; 20 | y : REAL; 21 | BEGIN 22 | BEGIN 23 | number := 2; 24 | a := number; 25 | a := 10 * a + 10 * number / 4; 26 | END; 27 | y := 11; 28 | END. 29 | """ 30 | 31 | let parser = Parser(program) 32 | let node = parser.parse() 33 | 34 | let analyzer = SemanticAnalyzer() 35 | let state = analyzer.analyze(node: node) 36 | XCTAssert(state.keys.count == 1) 37 | XCTAssert(state["global"] != nil) 38 | XCTAssert(state["global"]!.level == 1) 39 | XCTAssert(state["global"]!.lookup("y") != nil) 40 | XCTAssert(state["global"]!.lookup("a") != nil) 41 | XCTAssert(state["global"]!.lookup("b") != nil) 42 | XCTAssert(state["global"]!.lookup("number") != nil) 43 | XCTAssert(state["global"]!.lookup("c") == nil) 44 | } 45 | 46 | func testSemanticAnalyzerAssignUndeclaredVariable() { 47 | let program = 48 | """ 49 | PROGRAM Part10AST; 50 | VAR 51 | a, b, number : INTEGER; 52 | y : REAL; 53 | BEGIN 54 | BEGIN 55 | number := 2; 56 | a := number; 57 | a := 10 * a + 10 * number / 4; 58 | END; 59 | x := 11; 60 | END. 61 | """ 62 | 63 | let parser = Parser(program) 64 | let node = parser.parse() 65 | 66 | let analyzer = SemanticAnalyzer() 67 | expectFatalError(expectedMessage: "Symbol(indetifier) not found 'x'") { 68 | _ = analyzer.analyze(node: node) 69 | } 70 | } 71 | 72 | func testSemanticAnalyzerUndeclaredVariable() { 73 | let program = 74 | """ 75 | program SymTab5; 76 | var x : integer; 77 | 78 | begin 79 | x := y; 80 | end. 81 | """ 82 | 83 | let parser = Parser(program) 84 | let node = parser.parse() 85 | 86 | let analyzer = SemanticAnalyzer() 87 | expectFatalError(expectedMessage: "Symbol(indetifier) not found 'y'") { 88 | _ = analyzer.analyze(node: node) 89 | } 90 | } 91 | 92 | func testSemanticAnalyzerMultipleDeclarations() { 93 | let program = 94 | """ 95 | program SymTab6; 96 | var x, y : integer; 97 | y : real; 98 | 99 | begin 100 | x := x + y; 101 | end. 102 | """ 103 | 104 | let parser = Parser(program) 105 | let node = parser.parse() 106 | 107 | let analyzer = SemanticAnalyzer() 108 | expectFatalError(expectedMessage: "Duplicate identifier 'y' found") { 109 | _ = analyzer.analyze(node: node) 110 | } 111 | } 112 | 113 | func testSemanticAnalyzerProcedure() { 114 | let program = 115 | """ 116 | program Main; 117 | var x, y: real; 118 | 119 | procedure Alpha(a : integer); 120 | var y : integer; 121 | begin 122 | x := a + x + y; 123 | end; 124 | 125 | begin { Main } 126 | 127 | end. { Main } 128 | """ 129 | 130 | let parser = Parser(program) 131 | let node = parser.parse() 132 | 133 | let analyzer = SemanticAnalyzer() 134 | let state = analyzer.analyze(node: node) 135 | XCTAssert(state.keys.count == 2) 136 | XCTAssert(state["global"] != nil) 137 | XCTAssert(state["global"]!.level == 1) 138 | XCTAssert(state["global"]!.lookup("x") != nil) 139 | XCTAssert(state["global"]!.lookup("y") != nil) 140 | XCTAssert(state["global"]!.lookup("a") == nil) 141 | XCTAssert(state.keys.count == 2) 142 | XCTAssert(state["Alpha"] != nil) 143 | XCTAssert(state["Alpha"]!.level == 2) 144 | XCTAssert(state["Alpha"]!.lookup("x") != nil) 145 | XCTAssert(state["Alpha"]!.lookup("y") != nil) 146 | XCTAssert(state["Alpha"]!.lookup("a") != nil) 147 | } 148 | 149 | func testSemanticAnalyzerUndeclaredProcedure() { 150 | let program = 151 | """ 152 | program Main; 153 | var x, y: real; 154 | 155 | procedure Alpha(a : integer); 156 | var y : integer; 157 | begin 158 | x := a + x + y; 159 | end; 160 | 161 | begin { Main } 162 | Beta(); 163 | end. { Main } 164 | """ 165 | 166 | let parser = Parser(program) 167 | let node = parser.parse() 168 | 169 | let analyzer = SemanticAnalyzer() 170 | expectFatalError(expectedMessage: "Symbol(procedure) not found 'Beta'") { 171 | _ = analyzer.analyze(node: node) 172 | } 173 | } 174 | 175 | func testSemanticAnalyzerProcedureCall() { 176 | let program = 177 | """ 178 | program Main; 179 | var x, y: real; 180 | 181 | procedure Alpha(); 182 | var y : integer; 183 | begin 184 | x := x + y; 185 | end; 186 | 187 | begin { Main } 188 | Alpha(); 189 | end. { Main } 190 | """ 191 | 192 | let parser = Parser(program) 193 | let node = parser.parse() 194 | 195 | let analyzer = SemanticAnalyzer() 196 | let state = analyzer.analyze(node: node) 197 | XCTAssert(state.keys.count == 2) 198 | XCTAssert(state["global"] != nil) 199 | XCTAssert(state["global"]!.level == 1) 200 | XCTAssert(state["global"]!.lookup("x") != nil) 201 | XCTAssert(state["global"]!.lookup("y") != nil) 202 | XCTAssert(state["global"]!.lookup("a") == nil) 203 | XCTAssert(state.keys.count == 2) 204 | XCTAssert(state["Alpha"] != nil) 205 | XCTAssert(state["Alpha"]!.level == 2) 206 | XCTAssert(state["Alpha"]!.lookup("x") != nil) 207 | XCTAssert(state["Alpha"]!.lookup("y") != nil) 208 | XCTAssert(state["Alpha"]!.lookup("a") == nil) 209 | } 210 | 211 | func testSemanticAnalyzerProcedureUndeclaredVariable() { 212 | let program = 213 | """ 214 | program Main; 215 | var x, y: real; 216 | 217 | procedure Alpha(a : integer); 218 | var y : integer; 219 | begin 220 | x := a + x + b; 221 | end; 222 | 223 | begin { Main } 224 | 225 | end. { Main } 226 | """ 227 | 228 | let parser = Parser(program) 229 | let node = parser.parse() 230 | 231 | let analyzer = SemanticAnalyzer() 232 | expectFatalError(expectedMessage: "Symbol(indetifier) not found 'b'") { 233 | _ = analyzer.analyze(node: node) 234 | } 235 | } 236 | 237 | func testSemanticAnalyzerProcedureCallWithoutParameter() { 238 | let program = 239 | """ 240 | program Main; 241 | var x, y: real; 242 | 243 | procedure Alpha(a : integer); 244 | var y : integer; 245 | begin 246 | x := a + x; 247 | end; 248 | 249 | begin { Main } 250 | Alpha() 251 | end. { Main } 252 | """ 253 | 254 | let parser = Parser(program) 255 | let node = parser.parse() 256 | 257 | let analyzer = SemanticAnalyzer() 258 | expectFatalError(expectedMessage: "Procedure called with wrong number of parameters 'Alpha'") { 259 | _ = analyzer.analyze(node: node) 260 | } 261 | } 262 | 263 | func testSemanticAnalyzerProcedureCallWithParameter() { 264 | let program = 265 | """ 266 | program Main; 267 | var x, y: real; 268 | 269 | procedure Alpha(a : integer); 270 | var y : integer; 271 | begin 272 | x := a + x; 273 | end; 274 | 275 | begin { Main } 276 | Alpha(5) 277 | end. { Main } 278 | """ 279 | 280 | let parser = Parser(program) 281 | let node = parser.parse() 282 | 283 | let analyzer = SemanticAnalyzer() 284 | let state = analyzer.analyze(node: node) 285 | XCTAssert(state.keys.count == 2) 286 | XCTAssert(state["global"] != nil) 287 | XCTAssert(state["global"]!.level == 1) 288 | XCTAssert(state["global"]!.lookup("x") != nil) 289 | XCTAssert(state["global"]!.lookup("y") != nil) 290 | XCTAssert(state["global"]!.lookup("a") == nil) 291 | XCTAssert(state.keys.count == 2) 292 | XCTAssert(state["Alpha"] != nil) 293 | XCTAssert(state["Alpha"]!.level == 2) 294 | XCTAssert(state["Alpha"]!.lookup("x") != nil) 295 | XCTAssert(state["Alpha"]!.lookup("y") != nil) 296 | XCTAssert(state["Alpha"]!.lookup("a") != nil) 297 | } 298 | 299 | func testSemanticAnalyzerProcedureCallWithParameterWrongType() { 300 | let program = 301 | """ 302 | program Main; 303 | var x, y: real; 304 | 305 | procedure Alpha(a : integer); 306 | var y : integer; 307 | begin 308 | x := a + x; 309 | end; 310 | 311 | begin { Main } 312 | Alpha(5.2) 313 | end. { Main } 314 | """ 315 | 316 | let parser = Parser(program) 317 | let node = parser.parse() 318 | 319 | let analyzer = SemanticAnalyzer() 320 | expectFatalError(expectedMessage: "Cannot assing Real to Integer parameter in procedure call 'Alpha'") { 321 | _ = analyzer.analyze(node: node) 322 | } 323 | } 324 | 325 | func testSemanticAnalyzerFunctionCallWithParameter() { 326 | let program = 327 | """ 328 | program Main; 329 | var x, y: real; 330 | 331 | function Alpha(a : integer): Integer; 332 | var y : integer; 333 | begin 334 | Alpha := a + X; 335 | end; 336 | 337 | begin { Main } 338 | x := Alpha(5); 339 | end. { Main } 340 | """ 341 | 342 | let parser = Parser(program) 343 | let node = parser.parse() 344 | 345 | let analyzer = SemanticAnalyzer() 346 | let state = analyzer.analyze(node: node) 347 | XCTAssert(state.keys.count == 2) 348 | XCTAssert(state["global"] != nil) 349 | XCTAssert(state["global"]!.level == 1) 350 | XCTAssert(state["global"]!.lookup("x") != nil) 351 | XCTAssert(state["global"]!.lookup("y") != nil) 352 | XCTAssert(state["global"]!.lookup("a") == nil) 353 | XCTAssert(state.keys.count == 2) 354 | XCTAssert(state["Alpha"] != nil) 355 | XCTAssert(state["Alpha"]!.level == 2) 356 | XCTAssert(state["Alpha"]!.lookup("x") != nil) 357 | XCTAssert(state["Alpha"]!.lookup("y") != nil) 358 | XCTAssert(state["Alpha"]!.lookup("a") != nil) 359 | } 360 | 361 | func testSemanticAnalyzerBuildInFunction() { 362 | let program = 363 | """ 364 | program Main; 365 | var x: Integer; 366 | 367 | begin { Main } 368 | x := 5; 369 | writeln(x); 370 | end. { Main } 371 | """ 372 | 373 | let parser = Parser(program) 374 | let node = parser.parse() 375 | let analyzer = SemanticAnalyzer() 376 | let state = analyzer.analyze(node: node) 377 | XCTAssert(state.keys.count == 1) 378 | XCTAssert(state["global"] != nil) 379 | XCTAssert(state["global"]!.level == 1) 380 | XCTAssert(state["global"]!.lookup("x") != nil) 381 | XCTAssert(state["global"]!.lookup("WRITELN") != nil) 382 | XCTAssert(state["global"]!.lookup("a") == nil) 383 | } 384 | } 385 | -------------------------------------------------------------------------------- /PascalInterpreter/PascalInterpreterTests/XCTestCase+FatalError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestCase+FatalError.swift 3 | // SwiftPascalInterpreterTests 4 | // 5 | // Created by Igor Kulman on 10/12/2017. 6 | // Copyright © 2017 Igor Kulman. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @testable import PascalInterpreter 11 | import XCTest 12 | 13 | /** 14 | Taken from https://medium.com/@marcosantadev/how-to-test-fatalerror-in-swift-e1be9ff11a29 15 | */ 16 | extension XCTestCase { 17 | func expectFatalError(expectedMessage: String, testcase: @escaping () -> Void) { 18 | 19 | let expectation = self.expectation(description: "expectingFatalError") 20 | var assertionMessage: String? 21 | 22 | FatalErrorUtil.replaceFatalError { message, _, _ in 23 | assertionMessage = message 24 | expectation.fulfill() 25 | self.unreachable() 26 | } 27 | 28 | DispatchQueue.global(qos: .userInitiated).async(execute: testcase) 29 | 30 | waitForExpectations(timeout: 5) { _ in 31 | XCTAssertEqual(assertionMessage, expectedMessage) 32 | 33 | FatalErrorUtil.restoreFatalError() 34 | } 35 | } 36 | 37 | private func unreachable() -> Never { 38 | repeat { 39 | RunLoop.current.run() 40 | } while true 41 | } 42 | } 43 | 44 | func XCTAssertEqual(_ left: AST, _ right: AST) { 45 | switch (left, right) { 46 | case let (Number.integer(left), Number.integer(right)): 47 | XCTAssert(left == right) 48 | case let (left as Program, right as Program): 49 | XCTAssert(left.name == right.name) 50 | XCTAssertEqual(left.block, right.block) 51 | case let (left as Block, right as Block): 52 | XCTAssert(left.declarations.count == right.declarations.count) 53 | if left.declarations.count > 0 && left.declarations.count == right.declarations.count { 54 | for i in 0 ... left.declarations.count - 1 { 55 | XCTAssertEqual(left.declarations[i], right.declarations[i]) 56 | } 57 | } 58 | XCTAssertEqual(left.compound, right.compound) 59 | case let (left as Compound, right as Compound): 60 | XCTAssert(left.children.count == right.children.count) 61 | if left.children.count > 0 && left.children.count == right.children.count { 62 | for i in 0 ... left.children.count - 1 { 63 | XCTAssertEqual(left.children[i], right.children[i]) 64 | } 65 | } 66 | case let (left as Assignment, right as Assignment): 67 | XCTAssertEqual(left.left, right.left) 68 | XCTAssertEqual(left.right, right.right) 69 | case let (left as Variable, right as Variable): 70 | XCTAssert(left.name == right.name) 71 | case let (left as BinaryOperation, right as BinaryOperation): 72 | XCTAssert(left.operation == right.operation) 73 | XCTAssertEqual(left.left, right.left) 74 | XCTAssertEqual(left.right, right.right) 75 | case (is NoOp, is NoOp): 76 | break 77 | case let (left as UnaryOperation, right as UnaryOperation): 78 | XCTAssert(left.operation == right.operation) 79 | XCTAssertEqual(left.operand, right.operand) 80 | case let (left as VariableDeclaration, right as VariableDeclaration): 81 | XCTAssertEqual(left.type, right.type) 82 | XCTAssertEqual(left.variable, right.variable) 83 | case let (left as VariableType, right as VariableType): 84 | XCTAssert(left.type == right.type) 85 | case let (left as Function, right as Function): 86 | XCTAssertEqual(left.returnType, right.returnType) 87 | XCTAssert(left.name == right.name) 88 | XCTAssertEqual(left.block, right.block) 89 | XCTAssert(left.params.count == right.params.count) 90 | if left.params.count > 0 && left.params.count == right.params.count { 91 | for i in 0 ... left.params.count - 1 { 92 | XCTAssertEqual(left.params[i], right.params[i]) 93 | } 94 | } 95 | case let (left as Procedure, right as Procedure): 96 | XCTAssert(left.name == right.name) 97 | XCTAssertEqual(left.block, right.block) 98 | XCTAssert(left.params.count == right.params.count) 99 | if left.params.count > 0 { 100 | for i in 0 ... left.params.count - 1 { 101 | XCTAssertEqual(left.params[i], right.params[i]) 102 | } 103 | } 104 | case let (left as FunctionCall, right as FunctionCall): 105 | XCTAssert(left.name == right.name) 106 | XCTAssert(left.actualParameters.count == right.actualParameters.count) 107 | if left.actualParameters.count > 0 && left.actualParameters.count == right.actualParameters.count { 108 | for i in 0 ... left.actualParameters.count - 1 { 109 | XCTAssertEqual(left.actualParameters[i], right.actualParameters[i]) 110 | } 111 | } 112 | case let (left as Param, right as Param): 113 | XCTAssert(left.name == right.name) 114 | XCTAssertEqual(left.type, right.type) 115 | case let (left as Condition, right as Condition): 116 | XCTAssertEqual(left.type, right.type) 117 | XCTAssertEqual(left.leftSide, right.leftSide) 118 | XCTAssertEqual(left.rightSide, right.rightSide) 119 | case let (left as IfElse, right as IfElse): 120 | XCTAssertEqual(left.trueExpression, right.trueExpression) 121 | switch (left.falseExpression == nil, right.falseExpression == nil) { 122 | case (true, false): 123 | XCTFail("\(String(describing: left.falseExpression)) and \(String(describing: right.falseExpression)) are not equal") 124 | case (false, true): 125 | XCTFail("\(String(describing: left.falseExpression)) and \(String(describing: right.falseExpression)) are not equal") 126 | default: 127 | break 128 | } 129 | XCTAssertEqual(left.condition, right.condition) 130 | if let leftFalse = left.falseExpression, let rightFalse = right.falseExpression { 131 | XCTAssertEqual(leftFalse, rightFalse) 132 | } 133 | case let (left as String, right as String): 134 | XCTAssert(left == right) 135 | case let (left as Bool, right as Bool): 136 | XCTAssert(left == right) 137 | case let (left as RepeatUntil, right as RepeatUntil): 138 | XCTAssertEqual(left.condition, right.condition) 139 | XCTAssertEqual(left.statement, right.statement) 140 | case let (left as While, right as While): 141 | XCTAssertEqual(left.condition, right.condition) 142 | XCTAssertEqual(left.statement, right.statement) 143 | case let (left as For, right as For): 144 | XCTAssertEqual(left.variable, right.variable) 145 | XCTAssertEqual(left.startValue, right.startValue) 146 | XCTAssertEqual(left.endValue, right.endValue) 147 | XCTAssertEqual(left.statement, right.statement) 148 | default: 149 | XCTFail("\(left) and \(right) are not equal") 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /Playground.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | //: Playground - noun: a place where people can play 2 | 3 | import Foundation 4 | import PascalInterpreter 5 | 6 | let lexer = Lexer("BEGIN a := 2; END.") 7 | lexer.getNextToken() 8 | lexer.getNextToken() 9 | lexer.getNextToken() 10 | lexer.getNextToken() 11 | lexer.getNextToken() 12 | lexer.getNextToken() 13 | lexer.getNextToken() 14 | lexer.getNextToken() 15 | 16 | let program = 17 | """ 18 | program Main; 19 | var result: integer; 20 | 21 | function Factorial(number: Integer): Integer; 22 | begin 23 | if number > 1 then 24 | Factorial := number * Factorial(number-1) 25 | else 26 | Factorial := 1 27 | end; 28 | 29 | begin { Main } 30 | writeln('Factorial'); 31 | result := Factorial(6); 32 | writeln(result); 33 | end. { Main } 34 | """ 35 | 36 | let parser = Parser(program) 37 | let node = parser.parse() 38 | node.printTree() 39 | print("") 40 | 41 | let analyzer = SemanticAnalyzer() 42 | let result = analyzer.analyze(node: node) 43 | print(result) 44 | 45 | let interpreter = Interpreter(program) 46 | interpreter.interpret() 47 | print("") 48 | interpreter.printState() 49 | -------------------------------------------------------------------------------- /Playground.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pascal interpreter written in Swift 2 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 3 | [![Swift Version](https://img.shields.io/badge/Swift-5-F16D39.svg?style=flat)](https://developer.apple.com/swift) 4 | [![Twitter](https://img.shields.io/badge/twitter-@igorkulman-blue.svg)](http://twitter.com/igorkulman) 5 | 6 | Simple Swift interpreter for the Pascal language inspired by the [Let’s Build A Simple Interpreter](https://ruslanspivak.com/lsbasi-part1/) article series. 7 | 8 | ![Playground](Images/cli.gif) 9 | 10 | ### What is implemented 11 | 12 | * standard types (integer, real, boolean, string) 13 | * arithmetic expressions 14 | * function calls 15 | * procedure calls 16 | * recursion 17 | * loops (for, repet until, while) 18 | * logical conditions (if) 19 | * standard Pascal functions (writeln, write, readln, read, random) 20 | * one-dimensional arrays 21 | 22 | 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). 23 | 24 | ## Scructure 25 | 26 | ### Lexer 27 | 28 | The [Lexer](PascalInterpreter/PascalInterpreter/Lexer/Lexer.swift) reads the Pascal program as `String` (a sequence of characters) and converts it into a sequence of [Tokens](PascalInterpreter/PascalInterpreter/Lexer/Token.swift). You can see the result by trying it out in the Playground or on the [unit tests](PascalInterpreter/PascalInterpreterTests/LexerTests.swift). 29 | 30 | ![Lexer](Images/lexer.png) 31 | 32 | ### Parser 33 | 34 | The [Parser](PascalInterpreter/PascalInterpreter/Parser/Parser.swift) reads the sequence of tokens produced by the Lexer and builds an [Abstract Syntax Tree representation](PascalInterpreter/PascalInterpreter/Parser/AST.swift) (AST for short) of the Pascal program according to the [grammar](grammar.md). 35 | 36 | You can see what the AST looks like in the [unit tests](PascalInterpreter/PascalInterpreterTests/ParserTests.swift) or in the Playground where you can also use the `printTree()` method on any AST to see its visual representation printed into the console. 37 | 38 | ![Parser](Images/parser.png) 39 | 40 | ### Semantic analyzer 41 | 42 | The [Semantic analyzer](PascalInterpreter/PascalInterpreter/Semantic%20analyzer/SemanticAnalyzer.swift) does static semantic checks on the Pascal program AST. It currently checks if all the used variables are declared beforehand and if there are any duplicate declarations. The result of semantic analysis is a [Symbol table](PascalInterpreter/PascalInterpreter/Semantic%20analyzer/SymbolTable.swift) that holds all the symbols used by a Pascal program, currently built in types (Integer, Real, Boolean, String) and declared variable names. 43 | 44 | Implemented checks 45 | 46 | * Check if a variable was declared with a known type (Integer, Real) 47 | * Check if a variable was declared before usage 48 | * Check if variable is not declared more than once 49 | * Check if a procedure was declared 50 | * Check if a procedure is called with the correct number of parameters 51 | 52 | ### Interpreter 53 | 54 | The [Interpreter](PascalInterpreter/PascalInterpreter/Interpreter/Interpreter.swift) reads the AST representing the Pascal program from Parser and interprets it by walking the AST recursively. It can handle basic Pascal programs. 55 | 56 | At the end of the Pascal program interpretation you can check the resulting memory state (see [unit tests](PascalInterpreter/PascalInterpreterTests/InterpreterTests.swift)) or print it in the Playground using `printState()`. 57 | 58 | ## Try it out 59 | 60 | ### CLI 61 | 62 | When you build the [SPI project](SPI) in the workspace you will get command line utility that can run any Pascal program given as argument, as shown in the GIF at the top of this README. 63 | 64 | ### Playground 65 | 66 | There is a Swift playground in the project where you can try out the lexer, parser and the interpreter. The Playground interprets then following Pascal program defining and calling a factorial function 67 | 68 | ```` Pascal 69 | program Main; 70 | var result: integer; 71 | 72 | function Factorial(number: Integer): Integer; 73 | begin 74 | if number > 1 then 75 | Factorial := number * Factorial(number-1) 76 | else 77 | Factorial := 1 78 | end; 79 | 80 | begin 81 | writeln('Factorial'); 82 | result := Factorial(6); 83 | writeln(result) 84 | end. 85 | ```` 86 | 87 | ![Playground](Images/playground.png) 88 | 89 | 90 | ## Author 91 | 92 | Igor Kulman - igor@kulman.sk 93 | 94 | ## License 95 | 96 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details 97 | -------------------------------------------------------------------------------- /SPI/SPI.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | F3D90DD91FE68E4C00FA79B3 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3D90DD81FE68E4C00FA79B3 /* main.swift */; }; 11 | F3F372692315A7C000796779 /* PascalInterpreter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F3F372682315A7C000796779 /* PascalInterpreter.framework */; }; 12 | /* End PBXBuildFile section */ 13 | 14 | /* Begin PBXCopyFilesBuildPhase section */ 15 | F3D90DD31FE68E4C00FA79B3 /* CopyFiles */ = { 16 | isa = PBXCopyFilesBuildPhase; 17 | buildActionMask = 2147483647; 18 | dstPath = /usr/share/man/man1/; 19 | dstSubfolderSpec = 0; 20 | files = ( 21 | ); 22 | runOnlyForDeploymentPostprocessing = 1; 23 | }; 24 | /* End PBXCopyFilesBuildPhase section */ 25 | 26 | /* Begin PBXFileReference section */ 27 | F3D90DD51FE68E4C00FA79B3 /* SPI */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = SPI; sourceTree = BUILT_PRODUCTS_DIR; }; 28 | F3D90DD81FE68E4C00FA79B3 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 29 | F3F372682315A7C000796779 /* PascalInterpreter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = PascalInterpreter.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 30 | /* End PBXFileReference section */ 31 | 32 | /* Begin PBXFrameworksBuildPhase section */ 33 | F3D90DD21FE68E4C00FA79B3 /* Frameworks */ = { 34 | isa = PBXFrameworksBuildPhase; 35 | buildActionMask = 2147483647; 36 | files = ( 37 | F3F372692315A7C000796779 /* PascalInterpreter.framework in Frameworks */, 38 | ); 39 | runOnlyForDeploymentPostprocessing = 0; 40 | }; 41 | /* End PBXFrameworksBuildPhase section */ 42 | 43 | /* Begin PBXGroup section */ 44 | F3D90DCC1FE68E4C00FA79B3 = { 45 | isa = PBXGroup; 46 | children = ( 47 | F3D90DD71FE68E4C00FA79B3 /* SPI */, 48 | F3D90DD61FE68E4C00FA79B3 /* Products */, 49 | F3F372672315A7C000796779 /* Frameworks */, 50 | ); 51 | sourceTree = ""; 52 | }; 53 | F3D90DD61FE68E4C00FA79B3 /* Products */ = { 54 | isa = PBXGroup; 55 | children = ( 56 | F3D90DD51FE68E4C00FA79B3 /* SPI */, 57 | ); 58 | name = Products; 59 | sourceTree = ""; 60 | }; 61 | F3D90DD71FE68E4C00FA79B3 /* SPI */ = { 62 | isa = PBXGroup; 63 | children = ( 64 | F3D90DD81FE68E4C00FA79B3 /* main.swift */, 65 | ); 66 | path = SPI; 67 | sourceTree = ""; 68 | }; 69 | F3F372672315A7C000796779 /* Frameworks */ = { 70 | isa = PBXGroup; 71 | children = ( 72 | F3F372682315A7C000796779 /* PascalInterpreter.framework */, 73 | ); 74 | name = Frameworks; 75 | sourceTree = ""; 76 | }; 77 | /* End PBXGroup section */ 78 | 79 | /* Begin PBXNativeTarget section */ 80 | F3D90DD41FE68E4C00FA79B3 /* SPI */ = { 81 | isa = PBXNativeTarget; 82 | buildConfigurationList = F3D90DDC1FE68E4C00FA79B3 /* Build configuration list for PBXNativeTarget "SPI" */; 83 | buildPhases = ( 84 | F3D90DD11FE68E4C00FA79B3 /* Sources */, 85 | F3D90DD21FE68E4C00FA79B3 /* Frameworks */, 86 | F3D90DD31FE68E4C00FA79B3 /* CopyFiles */, 87 | ); 88 | buildRules = ( 89 | ); 90 | dependencies = ( 91 | ); 92 | name = SPI; 93 | productName = SPI; 94 | productReference = F3D90DD51FE68E4C00FA79B3 /* SPI */; 95 | productType = "com.apple.product-type.tool"; 96 | }; 97 | /* End PBXNativeTarget section */ 98 | 99 | /* Begin PBXProject section */ 100 | F3D90DCD1FE68E4C00FA79B3 /* Project object */ = { 101 | isa = PBXProject; 102 | attributes = { 103 | LastSwiftUpdateCheck = 0920; 104 | LastUpgradeCheck = 1020; 105 | ORGANIZATIONNAME = "Igor Kulman"; 106 | TargetAttributes = { 107 | F3D90DD41FE68E4C00FA79B3 = { 108 | CreatedOnToolsVersion = 9.2; 109 | LastSwiftMigration = 1020; 110 | ProvisioningStyle = Automatic; 111 | }; 112 | }; 113 | }; 114 | buildConfigurationList = F3D90DD01FE68E4C00FA79B3 /* Build configuration list for PBXProject "SPI" */; 115 | compatibilityVersion = "Xcode 8.0"; 116 | developmentRegion = en; 117 | hasScannedForEncodings = 0; 118 | knownRegions = ( 119 | en, 120 | Base, 121 | ); 122 | mainGroup = F3D90DCC1FE68E4C00FA79B3; 123 | productRefGroup = F3D90DD61FE68E4C00FA79B3 /* Products */; 124 | projectDirPath = ""; 125 | projectRoot = ""; 126 | targets = ( 127 | F3D90DD41FE68E4C00FA79B3 /* SPI */, 128 | ); 129 | }; 130 | /* End PBXProject section */ 131 | 132 | /* Begin PBXSourcesBuildPhase section */ 133 | F3D90DD11FE68E4C00FA79B3 /* Sources */ = { 134 | isa = PBXSourcesBuildPhase; 135 | buildActionMask = 2147483647; 136 | files = ( 137 | F3D90DD91FE68E4C00FA79B3 /* main.swift in Sources */, 138 | ); 139 | runOnlyForDeploymentPostprocessing = 0; 140 | }; 141 | /* End PBXSourcesBuildPhase section */ 142 | 143 | /* Begin XCBuildConfiguration section */ 144 | F3D90DDA1FE68E4C00FA79B3 /* Debug */ = { 145 | isa = XCBuildConfiguration; 146 | buildSettings = { 147 | ALWAYS_SEARCH_USER_PATHS = NO; 148 | CLANG_ANALYZER_NONNULL = YES; 149 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 150 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 151 | CLANG_CXX_LIBRARY = "libc++"; 152 | CLANG_ENABLE_MODULES = YES; 153 | CLANG_ENABLE_OBJC_ARC = YES; 154 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 155 | CLANG_WARN_BOOL_CONVERSION = YES; 156 | CLANG_WARN_COMMA = YES; 157 | CLANG_WARN_CONSTANT_CONVERSION = YES; 158 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 159 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 160 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 161 | CLANG_WARN_EMPTY_BODY = YES; 162 | CLANG_WARN_ENUM_CONVERSION = YES; 163 | CLANG_WARN_INFINITE_RECURSION = YES; 164 | CLANG_WARN_INT_CONVERSION = YES; 165 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 166 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 167 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 168 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 169 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 170 | CLANG_WARN_STRICT_PROTOTYPES = YES; 171 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 172 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 173 | CLANG_WARN_UNREACHABLE_CODE = YES; 174 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 175 | CODE_SIGN_IDENTITY = "Mac Developer"; 176 | COPY_PHASE_STRIP = NO; 177 | DEBUG_INFORMATION_FORMAT = dwarf; 178 | ENABLE_STRICT_OBJC_MSGSEND = YES; 179 | ENABLE_TESTABILITY = YES; 180 | GCC_C_LANGUAGE_STANDARD = gnu11; 181 | GCC_DYNAMIC_NO_PIC = NO; 182 | GCC_NO_COMMON_BLOCKS = YES; 183 | GCC_OPTIMIZATION_LEVEL = 0; 184 | GCC_PREPROCESSOR_DEFINITIONS = ( 185 | "DEBUG=1", 186 | "$(inherited)", 187 | ); 188 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 189 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 190 | GCC_WARN_UNDECLARED_SELECTOR = YES; 191 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 192 | GCC_WARN_UNUSED_FUNCTION = YES; 193 | GCC_WARN_UNUSED_VARIABLE = YES; 194 | MACOSX_DEPLOYMENT_TARGET = 10.13; 195 | MTL_ENABLE_DEBUG_INFO = YES; 196 | ONLY_ACTIVE_ARCH = YES; 197 | SDKROOT = macosx; 198 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 199 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 200 | }; 201 | name = Debug; 202 | }; 203 | F3D90DDB1FE68E4C00FA79B3 /* Release */ = { 204 | isa = XCBuildConfiguration; 205 | buildSettings = { 206 | ALWAYS_SEARCH_USER_PATHS = NO; 207 | CLANG_ANALYZER_NONNULL = YES; 208 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 209 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 210 | CLANG_CXX_LIBRARY = "libc++"; 211 | CLANG_ENABLE_MODULES = YES; 212 | CLANG_ENABLE_OBJC_ARC = YES; 213 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 214 | CLANG_WARN_BOOL_CONVERSION = YES; 215 | CLANG_WARN_COMMA = YES; 216 | CLANG_WARN_CONSTANT_CONVERSION = YES; 217 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 218 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 219 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 220 | CLANG_WARN_EMPTY_BODY = YES; 221 | CLANG_WARN_ENUM_CONVERSION = YES; 222 | CLANG_WARN_INFINITE_RECURSION = YES; 223 | CLANG_WARN_INT_CONVERSION = YES; 224 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 225 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 226 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 227 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 228 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 229 | CLANG_WARN_STRICT_PROTOTYPES = YES; 230 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 231 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 232 | CLANG_WARN_UNREACHABLE_CODE = YES; 233 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 234 | CODE_SIGN_IDENTITY = "Mac Developer"; 235 | COPY_PHASE_STRIP = NO; 236 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 237 | ENABLE_NS_ASSERTIONS = NO; 238 | ENABLE_STRICT_OBJC_MSGSEND = YES; 239 | GCC_C_LANGUAGE_STANDARD = gnu11; 240 | GCC_NO_COMMON_BLOCKS = YES; 241 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 242 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 243 | GCC_WARN_UNDECLARED_SELECTOR = YES; 244 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 245 | GCC_WARN_UNUSED_FUNCTION = YES; 246 | GCC_WARN_UNUSED_VARIABLE = YES; 247 | MACOSX_DEPLOYMENT_TARGET = 10.13; 248 | MTL_ENABLE_DEBUG_INFO = NO; 249 | SDKROOT = macosx; 250 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 251 | }; 252 | name = Release; 253 | }; 254 | F3D90DDD1FE68E4C00FA79B3 /* Debug */ = { 255 | isa = XCBuildConfiguration; 256 | buildSettings = { 257 | CODE_SIGN_STYLE = Automatic; 258 | DEVELOPMENT_TEAM = S6EJ3ZVM4G; 259 | PRODUCT_NAME = "$(TARGET_NAME)"; 260 | SWIFT_VERSION = 5.0; 261 | }; 262 | name = Debug; 263 | }; 264 | F3D90DDE1FE68E4C00FA79B3 /* Release */ = { 265 | isa = XCBuildConfiguration; 266 | buildSettings = { 267 | CODE_SIGN_STYLE = Automatic; 268 | DEVELOPMENT_TEAM = S6EJ3ZVM4G; 269 | PRODUCT_NAME = "$(TARGET_NAME)"; 270 | SWIFT_VERSION = 5.0; 271 | }; 272 | name = Release; 273 | }; 274 | /* End XCBuildConfiguration section */ 275 | 276 | /* Begin XCConfigurationList section */ 277 | F3D90DD01FE68E4C00FA79B3 /* Build configuration list for PBXProject "SPI" */ = { 278 | isa = XCConfigurationList; 279 | buildConfigurations = ( 280 | F3D90DDA1FE68E4C00FA79B3 /* Debug */, 281 | F3D90DDB1FE68E4C00FA79B3 /* Release */, 282 | ); 283 | defaultConfigurationIsVisible = 0; 284 | defaultConfigurationName = Release; 285 | }; 286 | F3D90DDC1FE68E4C00FA79B3 /* Build configuration list for PBXNativeTarget "SPI" */ = { 287 | isa = XCConfigurationList; 288 | buildConfigurations = ( 289 | F3D90DDD1FE68E4C00FA79B3 /* Debug */, 290 | F3D90DDE1FE68E4C00FA79B3 /* Release */, 291 | ); 292 | defaultConfigurationIsVisible = 0; 293 | defaultConfigurationName = Release; 294 | }; 295 | /* End XCConfigurationList section */ 296 | }; 297 | rootObject = F3D90DCD1FE68E4C00FA79B3 /* Project object */; 298 | } 299 | -------------------------------------------------------------------------------- /SPI/SPI/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // SPI 4 | // 5 | // Created by Igor Kulman on 17/12/2017. 6 | // Copyright © 2017 Igor Kulman. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import PascalInterpreter 11 | 12 | if CommandLine.arguments.count == 2 { 13 | let program = try String(contentsOfFile: CommandLine.arguments[1], encoding: String.Encoding.utf8) 14 | let interpreter = Interpreter(program) 15 | interpreter.interpret() 16 | } else { 17 | print("Usage: SPI program.pas") 18 | } 19 | -------------------------------------------------------------------------------- /SwiftPascalInterpreter.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /SwiftPascalInterpreter.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /grammar.md: -------------------------------------------------------------------------------- 1 | ```` 2 | program : PROGRAM variable SEMI block DOT 3 | 4 | block : declarations compound_statement 5 | 6 | declarations : (VAR (variable_declaration SEMI)+)* 7 | | (PROCEDURE ID (LPAREN formal_parameter_list RPAREN)? SEMI block SEMI)* 8 | | (FUNCTION ID (LPAREN formal_parameter_list RPAREN)? COLON type_spec SEMI block SEMI)* 9 | | empty 10 | 11 | formal_parameter_list : formal_parameters 12 | | formal_parameters SEMI formal_parameter_list 13 | 14 | formal_parameters : ID (COMMA ID)* COLON type_spec 15 | 16 | variable_declaration : ID (COMMA ID)* COLON type_spec 17 | 18 | type_spec : INTEGER 19 | 20 | compound_statement : BEGIN statement_list END 21 | 22 | statement_list : statement 23 | | statement SEMI statement_list 24 | 25 | statement : compound_statement 26 | | procedure_call 27 | | if_else_statement 28 | | assignment_statement 29 | | repeat_until 30 | | for_loop 31 | | for_loop 32 | | empty 33 | 34 | function_call : id LPAREN (factor (factor COLON)* )* RPAREN (COLON type_spec){0,1} 35 | 36 | if_else_statement : IF condition statement 37 | | IF condition THEN statement ELSE statement 38 | 39 | condition : expr (= | < | >) expr 40 | | LPAREN expr (= | < | >) expr RPAREN 41 | 42 | repeat_until : REPEAT statement UNTIL condition 43 | 44 | for_loop : FOR variable ASSIGN expression TO expression DO statement 45 | 46 | for_loop : WHILE condition DO statement 47 | 48 | assignment_statement : variable ASSIGN expr 49 | 50 | empty : 51 | 52 | expr : term ((PLUS | MINUS) term)* 53 | 54 | term : factor ((MUL | INTEGER_DIV | FLOAT_DIV) factor)* 55 | 56 | factor : PLUS factor 57 | | MINUS factor 58 | | INTEGER_CONST 59 | | REAL_CONST 60 | | STRING_CONST 61 | | BOOLEAN_CONST 62 | | LPAREN expr RPAREN 63 | | variable 64 | | function_call 65 | 66 | variable: ID 67 | ```` 68 | --------------------------------------------------------------------------------