├── .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 | [](https://opensource.org/licenses/MIT)
3 | [](https://developer.apple.com/swift)
4 | [](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 | 
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 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------