├── Examples
├── Hello.strs
└── SimpleHTML.strs
├── StarShip.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── xcuserdata
│ └── larsaugustin.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
└── project.pbxproj
├── StarShip
└── Sources
│ ├── Lexer
│ ├── Token.swift
│ └── Lexer.swift
│ ├── main.swift
│ ├── Execution
│ ├── Instruction.swift
│ ├── ExecHelpers.swift
│ ├── StandardLibrary.swift
│ └── Execution.swift
│ ├── Helpers
│ └── Global.swift
│ └── Parser
│ └── Parser.swift
├── LICENSE
├── Docs
├── StandardLibrary.md
├── Architecture.md
└── Syntax.md
└── README.md
/Examples/Hello.strs:
--------------------------------------------------------------------------------
1 | -- prints out "hello world" ten times
2 | hello :: -> Bool
3 | "hello world"
4 | return True
5 | end
6 |
7 | let _ = for 10 hello
8 |
--------------------------------------------------------------------------------
/StarShip.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/StarShip.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Examples/SimpleHTML.strs:
--------------------------------------------------------------------------------
1 | -- This script generates a super simple website
2 |
3 | let title = "Hello World!"
4 | let body = "This is a super simple web page generated with StarShip."
5 |
6 | generateWebsite :: String & String -> String
7 | "
"
8 | arg1
9 | "
"
10 | arg2
11 | "
"
12 | return ""
13 | end
14 |
15 | let _ = generateWebsite title body
16 | _
17 |
--------------------------------------------------------------------------------
/StarShip.xcodeproj/xcuserdata/larsaugustin.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | StarShip.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/StarShip/Sources/Lexer/Token.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Token.swift
3 | // StarShip
4 | //
5 |
6 | import Foundation
7 |
8 | public struct Token {
9 | let type: TokenType
10 | let val: String
11 | }
12 |
13 | public enum TokenType {
14 | case equals
15 | case `let`
16 | case funcAssignment
17 | case name
18 | case amp
19 | case arrow
20 | case end
21 | case `return`
22 |
23 | case number
24 | case string
25 | case bool
26 |
27 | case eol
28 | }
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2019 Lars Augustin
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/StarShip/Sources/main.swift:
--------------------------------------------------------------------------------
1 | //
2 | // main.swift
3 | // StarShip
4 | //
5 |
6 | import Foundation
7 |
8 | print("🚀 Welcome to StarShip")
9 |
10 | var inputData: Data?
11 |
12 | if CommandLine.arguments.count != 2 {
13 | // no input file
14 | print("Usage (if installed):")
15 | print("-> starship [path to file]")
16 | exit(0)
17 | } else {
18 | let arguments = CommandLine.arguments
19 |
20 | if !arguments[1].hasSuffix("strs") {
21 | print("Fatal Error: Can only execute StarShip (strs) files.")
22 | exit(0)
23 | }
24 |
25 | if arguments[1].hasPrefix("/") {
26 | // absolute path input
27 | inputData = FileManager.default.contents(atPath: arguments[1])
28 | } else if arguments[1].hasPrefix("~") {
29 | // user path
30 | let userPath = FileManager.default.homeDirectoryForCurrentUser.relativePath
31 | let pathToFile = String(arguments[1].dropFirst())
32 | inputData = FileManager.default.contents(atPath: userPath + pathToFile)
33 | } else {
34 | // plain input file
35 | let completePath = FileManager.default.currentDirectoryPath + "/" + CommandLine.arguments[1]
36 | inputData = FileManager.default.contents(atPath: completePath)
37 | }
38 | }
39 |
40 | if inputData == nil {
41 | print("Fatal error: Could not find file")
42 | exit(0)
43 | }
44 |
45 | guard let program = String(data: inputData!, encoding: .utf8) else {
46 | print("Fatal error: Could not open file as text")
47 | exit(0)
48 | }
49 |
50 | var vm = Execution()
51 | vm.execute(Parser().Run(Lexer().Run(program)))
52 |
--------------------------------------------------------------------------------
/Docs/StandardLibrary.md:
--------------------------------------------------------------------------------
1 | # Standard Library
2 | - Comparisons
3 | - `eql` Two Arguments → Bool
4 | - Checks if two values are equal. Returns the result of the comparison.
5 | - `big` Two Numbers → Bool
6 | - Checks if the first number is bigger than the second one. Returns the result of the comparison.
7 | - `sml` Two Numbers → Bool
8 | - Checks if the first number is smaller than the second one. Returns the result of the comparison.
9 | - Number operations
10 | - `inc` One Number → Number
11 | - Returns the number incremented by one.
12 | - `dec` One Number → Number
13 | - Returns the number decremented by one.
14 | - `add` Multiple Numbers → Number
15 | - Adds numbers. Can take multiple arguments, but requires two. Returns the result of the addition.
16 | - `sub` Multiple Numbers → Number
17 | - Subtracts numbers. Can take multiple arguments, but requires two. Returns the result of the subtraction.
18 | - `mul` Multiple Numbers → Number
19 | - Multiplies numbers. Can take multiple arguments, but requires two. Returns the result of the multiplication.
20 | - `div` Multiple Numbers → Number
21 | - Divides numbers. Can take multiple arguments, but requires two. Returns the result of the division.
22 | - Others
23 | - `for` Number & Function Name → Number
24 | - Calls a function multiple times. How many times the function will be executed is determined by the first argument. Can't take any other arguments (for now) and returns the first argument.
25 | - `if` Bool & Function Name → Bool
26 | - Calls a function if the first argument is `True`. Can't take any other arguments (for now) and returns the first argument.
27 | - `exit` No Arguments → Exit
28 | - Exits the program
29 |
--------------------------------------------------------------------------------
/StarShip/Sources/Execution/Instruction.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Instruction.swift
3 | // StarShip
4 | //
5 |
6 | import Foundation
7 |
8 | public protocol Instruction {
9 | var type: InstructionType { get }
10 | }
11 |
12 | public enum InstructionType {
13 | case absoluteVariable
14 | case functionVariable
15 | case functionDefinition
16 | case functionCall
17 | case printVariable
18 | case printValue
19 | case removeAndReplace
20 | case returnVariable
21 | case returnAbsoluteValue
22 | case removeAndValue
23 | }
24 |
25 | public enum DataType {
26 | case string
27 | case number
28 | case bool
29 | }
30 |
31 | public struct absoluteVariable: Instruction {
32 | public var type = InstructionType.absoluteVariable
33 | var name: String
34 | var value: Any
35 | var dataType: DataType
36 | }
37 |
38 | struct returnVariable: Instruction {
39 | var type = InstructionType.returnVariable
40 | var name: String
41 | }
42 |
43 | struct returnAbsoluteValue: Instruction {
44 | var type = InstructionType.returnAbsoluteValue
45 | var value: Any
46 | var dataType: DataType
47 | }
48 |
49 | struct functionVariable: Instruction {
50 | var type = InstructionType.functionVariable
51 | var name: String
52 | var functionName: String
53 | var arguments: [Token]
54 | }
55 |
56 | struct functionDefinition: Instruction {
57 | var type = InstructionType.functionDefinition
58 | var name: String
59 | var functionArguments: [Token]
60 | var returningTypes: [Token]
61 | var innerCode: [Token] // code is interpreted when the function gets called
62 | }
63 |
64 | struct functionCall: Instruction {
65 | var type = InstructionType.functionCall
66 | var name: String
67 | var arguments: [Token]
68 | }
69 |
70 | struct printVariable: Instruction {
71 | var type = InstructionType.printVariable
72 | var name: String
73 | }
74 |
75 | struct printValue: Instruction {
76 | var type = InstructionType.printValue
77 | var value: Any
78 | var dataType: DataType
79 | }
80 |
81 | struct removeAndReplace: Instruction {
82 | var type = InstructionType.removeAndReplace
83 | var name: String
84 | var functionName: String
85 | var arguments: [Token]
86 | }
87 |
88 | struct removeAndValue: Instruction {
89 | var type = InstructionType.removeAndValue
90 | var name: String
91 | var value: Any
92 | var dataType: DataType
93 | }
94 |
--------------------------------------------------------------------------------
/Docs/Architecture.md:
--------------------------------------------------------------------------------
1 | # Architecture
2 | StarShip source code gets processed in three steps to produce the expected result.
3 |
4 | ## Overview
5 | Source code → Lexer → Parser → Execution → Result
6 |
7 | *Each step is self-contained and can be used independently. This can be useful for testing and makes the code very reusable.*
8 |
9 | ## Lexer
10 | Once the source code is loaded from the input file, the lexer starts breaking up the source code into tokens. It starts at the beginning of the input string by looking for keywords like "let". If the string doesn't start with any particular keyword, the lexer checks if a string or comment is about to begin. If not, it assumes a name is about to start. Once it has recognized any of these elements it generates a token and trims the input string. This repeats until the input string is empty.
11 |
12 | ## Parser
13 | The parser receives an array of tokens. Like the lexer, the parser starts at the beginning of the array. It looks for familiar patterns, like variable or function definitions and print statements. Once the parser recognized a pattern, it generates an instruction for the interpreter to execute. Instructions for function definitions include tokens for the code they contain. The tokens get parsed when the function is called. Like the lexer, the parser looks for patterns, until the input array is empty.
14 |
15 | *Since this programming language doesn't have if-statements or for-loops, we don't need to pay attention to hierarchy. For a different kind of syntax, a more complex parser would be needed.*
16 |
17 | ## Interpreter
18 | The main interpreter (called "Execution" in this project), executes the instructions from the parser. It has access to a variable stack to store values. In this project, it's also responsible for most errors. Ideally, most errors would be identified before running the program. When a function definition calls a function, a new execution unit gets started. The parser parses the function body and then sends the instructions to the new execution unit. This process of executing and removing instructions gets repeated until there are no instructions left to execute. Then, the interpreter is done and the program has been executed.
19 |
20 | ## Building a similar architecture
21 | If you want to build your own interpreter, you could use the same architecture. For better results, adding an intermediate byte code format would be a good idea. In case you don't want to have a simple syntax like the one used in this project, you might also need a more complex parser.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # StarShip
2 |
3 |
4 |
5 |
6 |
7 |
8 | StarShip is an interpreted, strongly typed, and functional programming language written in Swift. The project includes a lexer, parser, and interpreter.
9 |
10 | Since it's still in the early stages of development, it's pretty simple. Basic types like strings, booleans, and numbers are available. Arrays are planned, but not yet implemented.
11 |
12 | Even when fully developed, this language shouldn't be used in a production environment. It is intended for having fun while playing around with the syntax and demonstrating the architecture of a simple interpreter.
13 |
14 | *If you're interested in the architecture please read [this document](Docs/Architecture.md)*
15 |
16 | ## Example
17 | The following code prints "hello world" ten times.
18 |
19 | ```
20 | hello :: -> Bool
21 | "hello world"
22 | return True
23 | end
24 |
25 | let _ = for 10 hello
26 | ```
27 |
28 | *The syntax is explained [here](Docs/Syntax.md)*
29 |
30 | ## Features
31 | - Minimal, functional syntax
32 | - A strong type system
33 | - Basic types like bools, numbers, and strings
34 | - Function definitions with arguments and return types
35 | - Functions for conditional statements and repeated function calls
36 | - A standard library with basic operations for comparing and simple numerical operations
37 |
38 | ## Building and running the CLI
39 | Since StarShip doesn't use any dependencies, you *should* be able to open up the project in Xcode and create an archive by selecting "Archive" in the "Product" menu. You might have to change the default development team to your own one. If simply opening the project doesn't work, please open an issue.
40 |
41 | After exporting the binary, you can drag it into a terminal window to use it immediately or copy the binary to `/usr/local/bin/`.
42 |
43 | ## Roadmap
44 | - [ ] Arguments for if function
45 | - [ ] Arrays
46 | - [ ] Compile to C/JS or LLVM bitcode
47 | - [ ] Linux support
48 | - [ ] Automated unit tests
49 |
50 | ## IDE
51 | I'm currently in the process of writing a small IDE for writing and running StarShip code on macOS and iOS. The IDE will be completely portable (you won't need to have the CLI installed) and will (probably) be released on the App Store.
52 |
53 | ## License
54 | This project is released under the MIT license. See the `LICENSE` file for more info.
55 |
--------------------------------------------------------------------------------
/StarShip/Sources/Lexer/Lexer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Lexer.swift
3 | // StarShip
4 | //
5 |
6 | import Foundation
7 |
8 | public class Lexer {
9 |
10 | struct Keyword {
11 | var value: String
12 | var type: TokenType
13 | }
14 | let keywords: [Keyword] = [
15 | Keyword(value: "= ", type: .equals),
16 | Keyword(value: ":: ", type: .funcAssignment),
17 | Keyword(value: "& ", type: .amp),
18 | Keyword(value: "->", type: .arrow),
19 | Keyword(value: "end", type: .end),
20 | Keyword(value: "let ", type: .let),
21 | Keyword(value: "return ", type: .return),
22 | Keyword(value: "True", type: .bool),
23 | Keyword(value: "False", type: .bool),
24 | Keyword(value: "\n", type: .eol)]
25 |
26 | func Run(_ input: String) -> [Token] {
27 | var tokens = [Token]()
28 | var program = input
29 |
30 | program = program.replacingOccurrences(of: "\n", with: " \n ") // direct line breaks can confuse the lexer
31 |
32 | while program.count > 0 {
33 | program = program.trimmingCharacters(in: .whitespaces) // remove whitespace at the beginning of the program
34 | if let firstKeyword = keywords.first(where: { keyword -> Bool in
35 | program.hasPrefix(keyword.value)
36 | }) {
37 | let newToken = Token(type: firstKeyword.type, val: firstKeyword.value)
38 | tokens.append(newToken)
39 | program.removeFirst(firstKeyword.value.count)
40 | } else if program.hasPrefix("--") { // comment, starts with "--" ends at new line
41 | let parts = String(program.dropFirst()).split(separator: "\n")
42 | program = (parts.count != 1) ? "\n" + String(parts.dropFirst().joined(separator: "\n")) : "\n" // no new token; skipping the comment
43 | } else if program.hasPrefix("\"") { // string literal, enclosed value
44 | let parts = String(program.dropFirst()).split(separator: "\"")
45 | let newToken = Token(type: .string, val: String(parts[0]))
46 | tokens.append(newToken)
47 | program = (parts.count != 1) ? String(parts.dropFirst().joined(separator: "\"")) : "\n"
48 | } else {
49 | let seperatedItems = program.split(separator: " ")
50 | if let _ = Float(seperatedItems[0]) { // if a float can be made from the token, it's a number
51 | let newToken = Token(type: .number, val: String(seperatedItems[0]))
52 | tokens.append(newToken)
53 | program.removeFirst(seperatedItems[0].count)
54 | } else { // otherwise it's a name
55 | let newToken = Token(type: .name, val: String(seperatedItems[0]))
56 | tokens.append(newToken)
57 | program.removeFirst(seperatedItems[0].count)
58 | }
59 | }
60 | }
61 |
62 | return tokens
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Docs/Syntax.md:
--------------------------------------------------------------------------------
1 | # Syntax
2 | ## Introduction
3 | StarShip uses a functional syntax and only has a few patterns. This makes the syntax easy to learn and parse but might need some adjusting when coming from a language like Swift or Python.
4 |
5 | ## Function definitions
6 | Functions are the most important and common pattern in StarShip. You define a function by giving it a name, followed by two double points (`::`). Then, you define the data types of the arguments you expect. These need to be separated by an ampersand (&). These arguments will be available as `arg1`, `arg2` and so on. This means the value of the first argument will be available as `arg1`.
7 |
8 | After defining the arguments, you'll need to add an arrow (`->`) to indicate the following type is the data type you'll return. Right now, every function has to return and can only have one return type.
9 |
10 | A function definition will have to be ended with the keyword `end`
11 |
12 | Here is an example of a function definition:
13 | ```
14 | functionName :: String, Bool -> String
15 | -- code
16 | -- the first argument (string) is available as arg1
17 | end
18 | ```
19 |
20 | ## Return
21 | Return is written as `return`. After the keyword, you can either insert an absolute value or reference a variable.
22 |
23 | Here is an example of returning an absolute value …
24 | ```
25 | return "hello world"
26 | ```
27 |
28 | … and here one of returning a variable
29 | ```
30 | return variableName
31 | ```
32 |
33 | ## Comments
34 | If you want to document your code, you'll need comments. A comment starts with two hyphens (`--`). The comment spans to the end of the line.
35 |
36 | Here is an example of some comments:
37 | ```
38 | -- comment
39 | code -- comment
40 | ```
41 |
42 | ## Variable definitions
43 | If a variable is not yet defined, you can define it with an absolute value or by calling a function. Since you can't call functions without assigning the return value to a variable, you'll define lots of variables. This also helps to document the progress of a program. When defining a variable with an absolute value, use this syntax:
44 |
45 | ```
46 | let variableName = 42
47 | ```
48 |
49 | Here the keyword is `let`. It lets the interpreter know, you want to define a new variable. If you want to call a function to define a variable, you put the function name directly after the equals sign. After the name, you list all arguments. These shouldn't be separated by an ampersand.
50 |
51 | Here is an example:
52 | ```
53 | let variableName = functionName "hello" "world"
54 | ```
55 |
56 | If you want to reassign a variable, you can omit the `let` keyword. The data type of new value will have to match the one of the original value. Right now, you can't use the value of a variable when redefining it. For example:
57 |
58 | ```
59 | variableName = inc variableName -- won't work
60 | ```
61 |
62 | ## Basic data types
63 | Right now, StarShip has three data types:
64 |
65 | - Strings can be written in quotes ("String value")
66 | - Numbers can be written as numbers (42)
67 | - Bools have the values `True` and `False`
68 |
69 | ## That's it!
70 | StarShip is a pretty simplistic language. It doesn't have many features, but in combination with the [standard library](StandardLibrary.md), it can do some pretty cool stuff. Install the CLI, and give it a try!
71 |
--------------------------------------------------------------------------------
/StarShip/Sources/Helpers/Global.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Global.swift
3 | // StarShip
4 | //
5 |
6 | import Foundation
7 |
8 | // global helper functions
9 | public class Helpers {
10 | public func ArrayBetween(_ first: Int, and second: Int, on input: Array) -> Array {
11 | Array(input.dropLast(input.count - second).dropFirst(first - 1))
12 | }
13 |
14 | // translating a string description of a data type into a data type
15 | public func getDataType(fromString string: String) -> DataType {
16 | switch string {
17 | case "String":
18 | return .string
19 | case "Bool":
20 | return .bool
21 | case "Number":
22 | return .number
23 | default:
24 | return .string
25 | }
26 | }
27 |
28 | // getting a string value from an absolute variable
29 | public func getStringValue(_ variable: absoluteVariable) -> String {
30 | switch variable.dataType {
31 | case .string:
32 | return variable.value as! String
33 | case .bool:
34 | return String(variable.value as! Bool)
35 | case .number:
36 | return String(variable.value as! Float)
37 | }
38 | }
39 |
40 | // getting the token type from a string
41 | public func getTokenTypeFromString(_ input: String) -> TokenType {
42 | if input == "String" {
43 | return .string
44 | } else if input == "Bool" {
45 | return .bool
46 | } else if input == "Number" {
47 | return .number
48 | } else {
49 | print("undefined type")
50 | exit(0)
51 | }
52 | }
53 |
54 | // checking if a value matches a data type
55 | public func checkIf(any input: Any, matches dataType: DataType) -> Bool {
56 | if dataType == .bool {
57 | return input as? Bool != nil
58 | } else if dataType == .number {
59 | return input as? Float != nil
60 | } else if dataType == .string {
61 | return input as? String != nil
62 | } else {
63 | return false
64 | }
65 | }
66 |
67 | // translating a data type into a token type
68 | public func getTokenType(fromDataType dataType: DataType) -> TokenType {
69 | switch dataType {
70 | case .bool:
71 | return .bool
72 | case .number:
73 | return .number
74 | case .string:
75 | return .string
76 | }
77 | }
78 |
79 | // returns the real value of a token as any
80 | public func getValueFromToken(_ token: Token) -> Any {
81 | if token.type == .bool {
82 | return Bool(token.val.lowercased())!
83 | } else if token.type == .number {
84 | return Float(token.val)!
85 | } else {
86 | return token.val
87 | }
88 | }
89 |
90 | // returns the data type for the value of a token
91 | public func getDataType(_ token: Token) -> DataType {
92 | if token.type == .bool {
93 | return .bool
94 | } else if token.type == .number {
95 | return .number
96 | } else {
97 | return .string
98 | }
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/StarShip/Sources/Execution/ExecHelpers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExecHelpers.swift
3 | // StarShip
4 | //
5 |
6 | import Foundation
7 |
8 | extension Execution {
9 |
10 | // getting a number value from a token
11 | func resolveNumberForToken(_ token: Token) -> Float {
12 | if token.type == .name {
13 | if let absVar = variableStack.last(where: { variable -> Bool in
14 | variable.name == token.val
15 | }) {
16 | if absVar.dataType == .number {
17 | return absVar.value as! Float
18 | } else {
19 | print("Fatal error: Variable in not a number")
20 | exit(0)
21 | }
22 | } else {
23 | print("Fatal error: Could not resolve variable")
24 | exit(0)
25 | }
26 | } else if token.type == .number {
27 | return Float(token.val)!
28 | } else {
29 | print("Fatal error: Could not resolve variable as name")
30 | exit(0)
31 | }
32 | }
33 |
34 | // getting a bool value for from a token
35 | func resolveBoolForToken(_ token: Token) -> Bool {
36 | if token.type == .name {
37 | if let absVar = variableStack.last(where: { variable -> Bool in
38 | variable.name == token.val
39 | }) {
40 | if absVar.dataType == .bool {
41 | return absVar.value as! Bool
42 | } else {
43 | print("Fatal error: Variable in not a bool")
44 | exit(0)
45 | }
46 | } else {
47 | print("could not resolve variable")
48 | exit(0)
49 | }
50 | } else if token.type == .bool {
51 | return Bool(token.val.lowercased())!
52 | } else {
53 | print("could not resolve as name")
54 | exit(0)
55 | }
56 | }
57 |
58 | // translating a token into an absolute variable
59 | func getAbsoluteVarForToken(_ token: Token) -> absoluteVariable {
60 | switch token.type {
61 | case .name:
62 | guard let retunVar = variableStack.last(where: { absVar -> Bool in
63 | absVar.name == token.val
64 | }) else {
65 | print("variable for equal not found")
66 | exit(0)
67 | }
68 | return retunVar
69 | case .bool:
70 | return absoluteVariable(name: "absoluteVariable", value: Bool(token.val.lowercased())!, dataType: .bool)
71 | case .string:
72 | return absoluteVariable(name: "absoluteVariable", value: token.val, dataType: .string)
73 | case .number:
74 | return absoluteVariable(name: "absoluteVariable", value: Float(token.val) ?? 0, dataType: .number)
75 | default:
76 | print("unresolvable")
77 | exit(0)
78 | }
79 | }
80 |
81 | // get an absolute variable
82 | func getAbsoluteVariable(_ token: Token, named name: String) -> absoluteVariable? {
83 | switch token.type {
84 | case .number:
85 | return absoluteVariable(name: name, value: Float(token.val)!, dataType: .number)
86 | case .string:
87 | return absoluteVariable(name: name, value: token.val, dataType: .string)
88 | case .bool:
89 | return absoluteVariable(name: name, value: Bool(token.val.lowercased())!, dataType: .bool)
90 | default:
91 | return nil
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/StarShip/Sources/Execution/StandardLibrary.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StandardLibrary.swift
3 | // StarShip
4 | //
5 |
6 | import Foundation
7 |
8 | extension Execution {
9 |
10 | // MARK: Add
11 | func stdAdd(arguments: [absoluteVariable]) -> Any {
12 | if arguments.count != 0 {
13 | if arguments[0].dataType == .number {
14 | var returningNumber = arguments[0].value as! Float
15 | for argument in arguments.dropFirst() {
16 | if argument.dataType == .number {
17 | returningNumber += argument.value as! Float
18 | } else {
19 | print("Fatal error: can't add anything but numbers")
20 | exit(0)
21 | }
22 | }
23 | return returningNumber
24 | } else {
25 | print("Fatal error: can't add anything but numbers")
26 | exit(0)
27 | }
28 | }
29 | return Float.zero
30 | }
31 |
32 | // MARK: Sub
33 | func stdSub(arguments: [absoluteVariable]) -> Any {
34 | if arguments.count != 0 {
35 | if arguments[0].dataType == .number {
36 | var returningNumber = arguments[0].value as! Float
37 | for argument in arguments.dropFirst() {
38 | if argument.dataType == .number {
39 | returningNumber -= argument.value as! Float
40 | } else {
41 | print("Fatal error: Can't subtract anything but numbers")
42 | exit(0)
43 | }
44 | }
45 | return returningNumber
46 | } else {
47 | print("Fatal error: Can't subtract anything but numbers")
48 | exit(0)
49 | }
50 | }
51 | return Float.zero
52 | }
53 |
54 | // MARK: Mul
55 | func stdMul(arguments: [absoluteVariable]) -> Any {
56 | if arguments.count != 0 {
57 | if arguments[0].dataType == .number {
58 | var returningNumber = arguments[0].value as! Float
59 | for argument in arguments.dropFirst() {
60 | if argument.dataType == .number {
61 | returningNumber *= argument.value as! Float
62 | } else {
63 | print("Fatal error: Can't mulitply anything but numbers")
64 | exit(0)
65 | }
66 | }
67 | return returningNumber
68 | } else {
69 | print("Fatal error: Can't mulitply anything but numbers")
70 | exit(0)
71 | }
72 | }
73 | return Float.zero
74 | }
75 |
76 | // MARK: Div
77 | func stdDiv(arguments: [absoluteVariable]) -> Any {
78 | if arguments.count != 0 {
79 | if arguments[0].dataType == .number {
80 | var returningNumber = arguments[0].value as! Float
81 | for argument in arguments.dropFirst() {
82 | if argument.dataType == .number {
83 | returningNumber /= argument.value as! Float
84 | } else {
85 | print("Fatal error: Can't divide anything but numbers")
86 | exit(0)
87 | }
88 | }
89 | return returningNumber
90 | } else {
91 | print("Fatal error: Can't divide anything but numbers")
92 | exit(0)
93 | }
94 | }
95 | return Float.zero
96 | }
97 |
98 | // MARK: Inc
99 | func stdInc(arguments: [absoluteVariable]) -> Any {
100 | if arguments.count == 1 {
101 | if arguments[0].dataType == .number {
102 | return arguments[0].value as! Float + 1
103 | } else {
104 | print("Fatal error: Can't inc anything but a number")
105 | exit(0)
106 | }
107 | } else {
108 | print("Fatal error: Inc can only take one argument")
109 | exit(0)
110 | }
111 | }
112 |
113 | // MARK: Dec
114 | func stdDec(arguments: [absoluteVariable]) -> Any {
115 | if arguments.count == 1 {
116 | if arguments[0].dataType == .number {
117 | return arguments[0].value as! Float - 1
118 | } else {
119 | print("Fatal error: Can't dec anything but a number")
120 | exit(0)
121 | }
122 | } else {
123 | print("Fatal error: Dec can only take one argument")
124 | exit(0)
125 | }
126 | }
127 |
128 | // MARK: For
129 | func stdFor(count: Float, functionCode: [Token]) -> Any {
130 | let parser = Parser().Run(functionCode)
131 | for _ in 0...Int(count) {
132 | let vm = Execution()
133 | vm.variableStack = variableStack
134 | vm.functionStack = functionStack
135 | vm.execute(parser)
136 | }
137 | return count
138 | }
139 |
140 | // MARK: If
141 | func stdIf(variable: Bool, functionCode: [Token]) -> Any {
142 | let parser = Parser().Run(functionCode)
143 | if variable {
144 | let vm = Execution()
145 | vm.variableStack = variableStack
146 | vm.functionStack = functionStack
147 | vm.execute(parser)
148 | }
149 | return variable
150 | }
151 |
152 | // MARK: Equal
153 | func stdEqual(first firstVar: absoluteVariable, second secondVar: absoluteVariable) -> Any {
154 | if firstVar.dataType == .string && secondVar.dataType == .string {
155 | return firstVar.value as! String == secondVar.value as! String
156 | } else if firstVar.dataType == .number && secondVar.dataType == .number {
157 | return firstVar.value as! Float == secondVar.value as! Float
158 | } else if firstVar.dataType == .bool && secondVar.dataType == .bool {
159 | return firstVar.value as! Bool == secondVar.value as! Bool
160 | } else {
161 | print("Fatal error: Different types can't be compared")
162 | exit(0)
163 | }
164 | }
165 |
166 | // MARK: Bigger
167 | func stdBigger(first firstVar: absoluteVariable, second secondVar: absoluteVariable) -> Any {
168 | if firstVar.dataType == .number && secondVar.dataType == .number {
169 | return firstVar.value as! Float > secondVar.value as! Float
170 | } else {
171 | print("Fatal error: Only number can be compared")
172 | exit(0)
173 | }
174 | }
175 |
176 | // MARK: Smaller
177 | func stdSmaller(first firstVar: absoluteVariable, second secondVar: absoluteVariable) -> Any {
178 | if firstVar.dataType == .number && secondVar.dataType == .number {
179 | return secondVar.value as! Float > firstVar.value as! Float
180 | } else {
181 | print("Fatal error: Only number can be compared")
182 | exit(0)
183 | }
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/StarShip/Sources/Parser/Parser.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Parser.swift
3 | // StarShip
4 | //
5 |
6 | import Foundation
7 |
8 | public class Parser {
9 | public func Run(_ input: [Token]) -> [Instruction] {
10 | var instructions: [Instruction] = []
11 | var tokens = input
12 |
13 | tokens.append(Token(type: .eol, val: "\n")) // many parts of the compiler rely on the eol signal; if a program ends without an eol, the parser will crash
14 |
15 | while tokens.count > 0 {
16 | if tokens.first!.type == .let {
17 | /* Possible patterns:
18 | - "let" name "=" function arguments?
19 | - "let" name "=" value
20 | - 0 1 2 3 4
21 | */
22 | if tokens[3].type == .string || tokens[3].type == .number || tokens[3].type == .bool {
23 | // MARK: Absolute variable
24 | // "let" name "=" value
25 | let newInstruction = absoluteVariable(name: tokens[1].val, value: Helpers().getValueFromToken(tokens[3]), dataType: Helpers().getDataType(tokens[3]))
26 | instructions.append(newInstruction)
27 | tokens.removeFirst(4)
28 | } else if tokens[3].type == .name {
29 | // MARK: Function variable
30 | // "let" name "=" functionName arguments
31 | let firstLineBreak = tokens.firstIndex { token -> Bool in
32 | token.type == .eol
33 | }!
34 | // arguments are tokens between index 5 and the first line break
35 | // when a function gets called, there won't be amp tokens between the arguments
36 | let arguments = Helpers().ArrayBetween(5, and: firstLineBreak, on: tokens) as! [Token]
37 |
38 | let newInstruction = functionVariable(name: tokens[1].val, functionName: tokens[3].val, arguments: arguments)
39 | instructions.append(newInstruction)
40 | tokens.removeFirst(4 + arguments.count)
41 | } else {
42 | print("Fatal error: Unknown pattern")
43 | exit(0)
44 | }
45 |
46 | } else if tokens.first!.type == .string || tokens.first!.type == .number || tokens.first!.type == .bool {
47 | // MARK: Print absolute value
48 | // just the value on a line
49 | let newInstruction = printValue(value: Helpers().getValueFromToken(tokens.first!), dataType: Helpers().getDataType(tokens.first!))
50 | instructions.append(newInstruction)
51 | tokens.removeFirst()
52 |
53 | } else if tokens.first!.type == .name {
54 | // check if the next one is an eol or function definition
55 | // if the next token is an eol, the value of a variable will be printed, otherwise the next token is a function assignment (::)
56 | // there will always be two tokens left (that's why the final eol was added), so checking index 1 is okay
57 | if tokens[1].type == .funcAssignment {
58 | // MARK: Function definition
59 | // find the first eol to "measure" the function
60 | let firstEolIndex = tokens.firstIndex { token -> Bool in
61 | token.type == .eol
62 | }
63 | // a function can look pretty different, depending on the definition:
64 | // an example: function :: argument & argument & argument -> return
65 | // could also be function :: -> return
66 | // or function :: argument
67 |
68 | // if the function doesn't end with and end token, the parser ends the program
69 | let firstEndIndex = tokens.firstIndex { token -> Bool in
70 | token.type == .end
71 | }
72 | if firstEndIndex == nil {
73 | print("Fatal error: Function definition did not end")
74 | exit(0)
75 |
76 | } else {
77 | let relevantRange = Helpers().ArrayBetween(3, and: firstEolIndex!, on: tokens) as! [Token] // relevant range is the relevant range for searching return statements and arguments
78 | let returnStatements = relevantRange.filter { token -> Bool in
79 | token.type == .arrow
80 | }
81 | let mainContent = Helpers().ArrayBetween(firstEolIndex! + 1, and: firstEndIndex!, on: tokens) as! [Token]
82 | if relevantRange.count == 0 {
83 | // the function has no arguments and doesn't return anything
84 | // right now a function has to return a value, but that might change in the future
85 |
86 | let newInstruction = functionDefinition(name: tokens[0].val, functionArguments: [], returningTypes: [], innerCode: mainContent)
87 | instructions.append(newInstruction)
88 | } else if returnStatements.count == 0 {
89 | // the function has arguments, but doesn't return anything
90 | // right now a function has to return a value, but that might change in the future
91 |
92 | // remove the amp tokens, which separate the argument types
93 | let allArguments = relevantRange.filter { token -> Bool in
94 | token.type != .amp
95 | }
96 |
97 | let newInstruction = functionDefinition(name: tokens[0].val, functionArguments: allArguments, returningTypes: [], innerCode: mainContent)
98 | instructions.append(newInstruction)
99 | } else if returnStatements.count == 1 {
100 | // right now only one return type is supported
101 |
102 | // the arrow indicates a return type
103 | let parts = relevantRange.split { token -> Bool in
104 | token.type == .arrow
105 | }
106 | // before the arrow are all the arguments, after the arrow the return types
107 | let allArguments = parts.first!.filter { token -> Bool in
108 | token.type != .amp
109 | }
110 |
111 | // this is in preparation for multiple return types, which might be coming in the future
112 | let returnTypes = parts.last!.filter { token -> Bool in
113 | token.type != .amp
114 | }
115 |
116 | if tokens[2].type != .arrow {
117 | // the function returns and has arguments
118 | let newInstruction = functionDefinition(name: tokens[0].val, functionArguments: allArguments, returningTypes: returnTypes, innerCode: mainContent)
119 | instructions.append(newInstruction)
120 | } else {
121 | // the function returns, but has no arguments
122 | let newInstruction = functionDefinition(name: tokens[0].val, functionArguments: [], returningTypes: returnTypes, innerCode: mainContent)
123 | instructions.append(newInstruction)
124 | }
125 | }
126 |
127 | tokens.removeFirst(firstEndIndex! + 1)
128 | }
129 | } else if tokens[1].type == .eol {
130 | // MARK: Variable print
131 | let newInstruction = printVariable(name: tokens.first!.val)
132 | instructions.append(newInstruction)
133 | tokens.removeFirst()
134 | } else if tokens[1].type == .equals {
135 | // this is a variable reassignment:
136 | // name = value
137 | // name = functionName arguments
138 | if tokens[2].type == .string || tokens[2].type == .number || tokens[2].type == .bool {
139 | // MARK: Function value redefinition
140 | // "removeAndValue" removes the variable from the stack and then adds it back to the stack;
141 | // more info in the execution module
142 | let newInstruction = removeAndValue(name: tokens[0].val, value: Helpers().getValueFromToken(tokens[2]), dataType: Helpers().getDataType(tokens[2]))
143 | instructions.append(newInstruction)
144 | tokens.removeFirst(3)
145 |
146 | } else if tokens[2].type == .name {
147 | // MARK: Function variable redefinition
148 | let firstLineBreak = tokens.firstIndex { token -> Bool in
149 | token.type == .eol
150 | }!
151 |
152 | // get the arguments
153 | let arguments = Helpers().ArrayBetween(4, and: firstLineBreak, on: tokens) as! [Token]
154 |
155 | let newInstruction = removeAndReplace(name: tokens[0].val, functionName: tokens[2].val, arguments: arguments)
156 | instructions.append(newInstruction)
157 |
158 | tokens.removeFirst(3 + arguments.count)
159 | } else {
160 | print("Fatal error: Unknown pattern")
161 | exit(0)
162 | }
163 | } else {
164 | print("Fatal error: Unknown pattern")
165 | exit(0)
166 | }
167 | } else if tokens.first!.type == .return {
168 | // MARK: Return statement
169 | // a variable or a value can be returned
170 | if tokens[1].type == .name {
171 | let newInstruction = returnVariable(name: tokens[1].val)
172 | instructions.append(newInstruction)
173 | } else if tokens[1].type == .string || tokens[1].type == .number || tokens[1].type == .bool {
174 | let newInstruction = returnAbsoluteValue(value: Helpers().getValueFromToken(tokens[1]),
175 | dataType: Helpers().getDataType(tokens[1]))
176 | instructions.append(newInstruction)
177 | } else {
178 | print("Fatal error: Unknown pattern")
179 | exit(0)
180 | }
181 | tokens.removeFirst(2)
182 | } else {
183 | // MARK: Unknown pattern
184 | if tokens.first!.type != .eol { // eol will be resolved later
185 | print("Fatal error: Unknown pattern")
186 | exit(0)
187 | }
188 | }
189 |
190 | // resolving eol
191 | if tokens.first!.type == .eol {
192 | tokens = Array(tokens.dropFirst())
193 | }
194 | }
195 | return instructions
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/StarShip.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 99448BE123B6729D00A20E8B /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99448BE023B6729D00A20E8B /* main.swift */; };
11 | 99448BEC23B6740B00A20E8B /* Execution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99448BEB23B6740B00A20E8B /* Execution.swift */; };
12 | 99448BEE23B6743700A20E8B /* StandardLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99448BED23B6743700A20E8B /* StandardLibrary.swift */; };
13 | 99448BF023B6745A00A20E8B /* Instruction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99448BEF23B6745A00A20E8B /* Instruction.swift */; };
14 | 99448BF223B6748100A20E8B /* ExecHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99448BF123B6748100A20E8B /* ExecHelpers.swift */; };
15 | 99448BF523B674AF00A20E8B /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99448BF423B674AF00A20E8B /* Parser.swift */; };
16 | 99448BFA23B6750400A20E8B /* Lexer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99448BF923B6750400A20E8B /* Lexer.swift */; };
17 | 99448BFC23B6752D00A20E8B /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99448BFB23B6752D00A20E8B /* Token.swift */; };
18 | 99448C0723B792B400A20E8B /* LICENSE in Sources */ = {isa = PBXBuildFile; fileRef = 99448C0623B792B400A20E8B /* LICENSE */; };
19 | 996DC06023CA6DEA000E4BB8 /* Global.swift in Sources */ = {isa = PBXBuildFile; fileRef = 996DC05F23CA6DEA000E4BB8 /* Global.swift */; };
20 | /* End PBXBuildFile section */
21 |
22 | /* Begin PBXCopyFilesBuildPhase section */
23 | 99448BDB23B6729D00A20E8B /* CopyFiles */ = {
24 | isa = PBXCopyFilesBuildPhase;
25 | buildActionMask = 2147483647;
26 | dstPath = /usr/share/man/man1/;
27 | dstSubfolderSpec = 0;
28 | files = (
29 | );
30 | runOnlyForDeploymentPostprocessing = 1;
31 | };
32 | /* End PBXCopyFilesBuildPhase section */
33 |
34 | /* Begin PBXFileReference section */
35 | 99448BDD23B6729D00A20E8B /* StarShip */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = StarShip; sourceTree = BUILT_PRODUCTS_DIR; };
36 | 99448BE023B6729D00A20E8B /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; };
37 | 99448BEB23B6740B00A20E8B /* Execution.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Execution.swift; sourceTree = ""; };
38 | 99448BED23B6743700A20E8B /* StandardLibrary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandardLibrary.swift; sourceTree = ""; };
39 | 99448BEF23B6745A00A20E8B /* Instruction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Instruction.swift; sourceTree = ""; };
40 | 99448BF123B6748100A20E8B /* ExecHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExecHelpers.swift; sourceTree = ""; };
41 | 99448BF423B674AF00A20E8B /* Parser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = ""; };
42 | 99448BF923B6750400A20E8B /* Lexer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Lexer.swift; sourceTree = ""; };
43 | 99448BFB23B6752D00A20E8B /* Token.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = ""; };
44 | 99448C0023B675B700A20E8B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
45 | 99448C0123B67CC300A20E8B /* SimpleHTML.strs */ = {isa = PBXFileReference; lastKnownFileType = text; path = SimpleHTML.strs; sourceTree = ""; };
46 | 99448C0223B67F3100A20E8B /* Hello.strs */ = {isa = PBXFileReference; lastKnownFileType = text; path = Hello.strs; sourceTree = ""; };
47 | 99448C0323B682FE00A20E8B /* StandardLibrary.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = StandardLibrary.md; sourceTree = ""; };
48 | 99448C0423B69BEE00A20E8B /* Architecture.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Architecture.md; sourceTree = ""; };
49 | 99448C0523B69C7100A20E8B /* Syntax.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Syntax.md; sourceTree = ""; };
50 | 99448C0623B792B400A20E8B /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; };
51 | 996DC05F23CA6DEA000E4BB8 /* Global.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Global.swift; sourceTree = ""; };
52 | /* End PBXFileReference section */
53 |
54 | /* Begin PBXFrameworksBuildPhase section */
55 | 99448BDA23B6729D00A20E8B /* Frameworks */ = {
56 | isa = PBXFrameworksBuildPhase;
57 | buildActionMask = 2147483647;
58 | files = (
59 | );
60 | runOnlyForDeploymentPostprocessing = 0;
61 | };
62 | /* End PBXFrameworksBuildPhase section */
63 |
64 | /* Begin PBXGroup section */
65 | 99448BD423B6729D00A20E8B = {
66 | isa = PBXGroup;
67 | children = (
68 | 99448C0023B675B700A20E8B /* README.md */,
69 | 99448C0623B792B400A20E8B /* LICENSE */,
70 | 99448BE723B672D800A20E8B /* Docs */,
71 | 99448BE923B672E600A20E8B /* Examples */,
72 | 99448BDF23B6729D00A20E8B /* StarShip */,
73 | 99448BDE23B6729D00A20E8B /* Products */,
74 | );
75 | sourceTree = "";
76 | };
77 | 99448BDE23B6729D00A20E8B /* Products */ = {
78 | isa = PBXGroup;
79 | children = (
80 | 99448BDD23B6729D00A20E8B /* StarShip */,
81 | );
82 | name = Products;
83 | sourceTree = "";
84 | };
85 | 99448BDF23B6729D00A20E8B /* StarShip */ = {
86 | isa = PBXGroup;
87 | children = (
88 | 99448BE823B672E000A20E8B /* Sources */,
89 | );
90 | path = StarShip;
91 | sourceTree = "";
92 | };
93 | 99448BE723B672D800A20E8B /* Docs */ = {
94 | isa = PBXGroup;
95 | children = (
96 | 99448C0523B69C7100A20E8B /* Syntax.md */,
97 | 99448C0323B682FE00A20E8B /* StandardLibrary.md */,
98 | 99448C0423B69BEE00A20E8B /* Architecture.md */,
99 | );
100 | path = Docs;
101 | sourceTree = "";
102 | };
103 | 99448BE823B672E000A20E8B /* Sources */ = {
104 | isa = PBXGroup;
105 | children = (
106 | 99448BF823B674FA00A20E8B /* Lexer */,
107 | 99448BF323B6749A00A20E8B /* Parser */,
108 | 99448BEA23B673F700A20E8B /* Execution */,
109 | 99448BFD23B6756700A20E8B /* Helpers */,
110 | 99448BE023B6729D00A20E8B /* main.swift */,
111 | );
112 | path = Sources;
113 | sourceTree = "";
114 | };
115 | 99448BE923B672E600A20E8B /* Examples */ = {
116 | isa = PBXGroup;
117 | children = (
118 | 99448C0123B67CC300A20E8B /* SimpleHTML.strs */,
119 | 99448C0223B67F3100A20E8B /* Hello.strs */,
120 | );
121 | path = Examples;
122 | sourceTree = "";
123 | };
124 | 99448BEA23B673F700A20E8B /* Execution */ = {
125 | isa = PBXGroup;
126 | children = (
127 | 99448BEF23B6745A00A20E8B /* Instruction.swift */,
128 | 99448BEB23B6740B00A20E8B /* Execution.swift */,
129 | 99448BED23B6743700A20E8B /* StandardLibrary.swift */,
130 | 99448BF123B6748100A20E8B /* ExecHelpers.swift */,
131 | );
132 | path = Execution;
133 | sourceTree = "";
134 | };
135 | 99448BF323B6749A00A20E8B /* Parser */ = {
136 | isa = PBXGroup;
137 | children = (
138 | 99448BF423B674AF00A20E8B /* Parser.swift */,
139 | );
140 | path = Parser;
141 | sourceTree = "";
142 | };
143 | 99448BF823B674FA00A20E8B /* Lexer */ = {
144 | isa = PBXGroup;
145 | children = (
146 | 99448BF923B6750400A20E8B /* Lexer.swift */,
147 | 99448BFB23B6752D00A20E8B /* Token.swift */,
148 | );
149 | path = Lexer;
150 | sourceTree = "";
151 | };
152 | 99448BFD23B6756700A20E8B /* Helpers */ = {
153 | isa = PBXGroup;
154 | children = (
155 | 996DC05F23CA6DEA000E4BB8 /* Global.swift */,
156 | );
157 | path = Helpers;
158 | sourceTree = "";
159 | };
160 | /* End PBXGroup section */
161 |
162 | /* Begin PBXNativeTarget section */
163 | 99448BDC23B6729D00A20E8B /* StarShip */ = {
164 | isa = PBXNativeTarget;
165 | buildConfigurationList = 99448BE423B6729D00A20E8B /* Build configuration list for PBXNativeTarget "StarShip" */;
166 | buildPhases = (
167 | 99448BD923B6729D00A20E8B /* Sources */,
168 | 99448BDA23B6729D00A20E8B /* Frameworks */,
169 | 99448BDB23B6729D00A20E8B /* CopyFiles */,
170 | );
171 | buildRules = (
172 | );
173 | dependencies = (
174 | );
175 | name = StarShip;
176 | productName = StarShip;
177 | productReference = 99448BDD23B6729D00A20E8B /* StarShip */;
178 | productType = "com.apple.product-type.tool";
179 | };
180 | /* End PBXNativeTarget section */
181 |
182 | /* Begin PBXProject section */
183 | 99448BD523B6729D00A20E8B /* Project object */ = {
184 | isa = PBXProject;
185 | attributes = {
186 | LastSwiftUpdateCheck = 1130;
187 | LastUpgradeCheck = 1130;
188 | ORGANIZATIONNAME = "Lars Augustin";
189 | TargetAttributes = {
190 | 99448BDC23B6729D00A20E8B = {
191 | CreatedOnToolsVersion = 11.3;
192 | };
193 | };
194 | };
195 | buildConfigurationList = 99448BD823B6729D00A20E8B /* Build configuration list for PBXProject "StarShip" */;
196 | compatibilityVersion = "Xcode 9.3";
197 | developmentRegion = en;
198 | hasScannedForEncodings = 0;
199 | knownRegions = (
200 | en,
201 | Base,
202 | );
203 | mainGroup = 99448BD423B6729D00A20E8B;
204 | productRefGroup = 99448BDE23B6729D00A20E8B /* Products */;
205 | projectDirPath = "";
206 | projectRoot = "";
207 | targets = (
208 | 99448BDC23B6729D00A20E8B /* StarShip */,
209 | );
210 | };
211 | /* End PBXProject section */
212 |
213 | /* Begin PBXSourcesBuildPhase section */
214 | 99448BD923B6729D00A20E8B /* Sources */ = {
215 | isa = PBXSourcesBuildPhase;
216 | buildActionMask = 2147483647;
217 | files = (
218 | 99448BFC23B6752D00A20E8B /* Token.swift in Sources */,
219 | 99448BEE23B6743700A20E8B /* StandardLibrary.swift in Sources */,
220 | 99448BF223B6748100A20E8B /* ExecHelpers.swift in Sources */,
221 | 99448BF523B674AF00A20E8B /* Parser.swift in Sources */,
222 | 99448BEC23B6740B00A20E8B /* Execution.swift in Sources */,
223 | 99448BF023B6745A00A20E8B /* Instruction.swift in Sources */,
224 | 99448BFA23B6750400A20E8B /* Lexer.swift in Sources */,
225 | 99448BE123B6729D00A20E8B /* main.swift in Sources */,
226 | 996DC06023CA6DEA000E4BB8 /* Global.swift in Sources */,
227 | 99448C0723B792B400A20E8B /* LICENSE in Sources */,
228 | );
229 | runOnlyForDeploymentPostprocessing = 0;
230 | };
231 | /* End PBXSourcesBuildPhase section */
232 |
233 | /* Begin XCBuildConfiguration section */
234 | 99448BE223B6729D00A20E8B /* Debug */ = {
235 | isa = XCBuildConfiguration;
236 | buildSettings = {
237 | ALWAYS_SEARCH_USER_PATHS = NO;
238 | CLANG_ANALYZER_NONNULL = YES;
239 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
240 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
241 | CLANG_CXX_LIBRARY = "libc++";
242 | CLANG_ENABLE_MODULES = YES;
243 | CLANG_ENABLE_OBJC_ARC = YES;
244 | CLANG_ENABLE_OBJC_WEAK = YES;
245 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
246 | CLANG_WARN_BOOL_CONVERSION = YES;
247 | CLANG_WARN_COMMA = YES;
248 | CLANG_WARN_CONSTANT_CONVERSION = YES;
249 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
250 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
251 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
252 | CLANG_WARN_EMPTY_BODY = YES;
253 | CLANG_WARN_ENUM_CONVERSION = YES;
254 | CLANG_WARN_INFINITE_RECURSION = YES;
255 | CLANG_WARN_INT_CONVERSION = YES;
256 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
257 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
258 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
259 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
260 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
261 | CLANG_WARN_STRICT_PROTOTYPES = YES;
262 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
263 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
264 | CLANG_WARN_UNREACHABLE_CODE = YES;
265 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
266 | COPY_PHASE_STRIP = NO;
267 | DEBUG_INFORMATION_FORMAT = dwarf;
268 | ENABLE_STRICT_OBJC_MSGSEND = YES;
269 | ENABLE_TESTABILITY = YES;
270 | GCC_C_LANGUAGE_STANDARD = gnu11;
271 | GCC_DYNAMIC_NO_PIC = NO;
272 | GCC_NO_COMMON_BLOCKS = YES;
273 | GCC_OPTIMIZATION_LEVEL = 0;
274 | GCC_PREPROCESSOR_DEFINITIONS = (
275 | "DEBUG=1",
276 | "$(inherited)",
277 | );
278 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
279 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
280 | GCC_WARN_UNDECLARED_SELECTOR = YES;
281 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
282 | GCC_WARN_UNUSED_FUNCTION = YES;
283 | GCC_WARN_UNUSED_VARIABLE = YES;
284 | MACOSX_DEPLOYMENT_TARGET = 10.15;
285 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
286 | MTL_FAST_MATH = YES;
287 | ONLY_ACTIVE_ARCH = YES;
288 | SDKROOT = macosx;
289 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
290 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
291 | };
292 | name = Debug;
293 | };
294 | 99448BE323B6729D00A20E8B /* Release */ = {
295 | isa = XCBuildConfiguration;
296 | buildSettings = {
297 | ALWAYS_SEARCH_USER_PATHS = NO;
298 | CLANG_ANALYZER_NONNULL = YES;
299 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
300 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
301 | CLANG_CXX_LIBRARY = "libc++";
302 | CLANG_ENABLE_MODULES = YES;
303 | CLANG_ENABLE_OBJC_ARC = YES;
304 | CLANG_ENABLE_OBJC_WEAK = YES;
305 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
306 | CLANG_WARN_BOOL_CONVERSION = YES;
307 | CLANG_WARN_COMMA = YES;
308 | CLANG_WARN_CONSTANT_CONVERSION = YES;
309 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
310 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
311 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
312 | CLANG_WARN_EMPTY_BODY = YES;
313 | CLANG_WARN_ENUM_CONVERSION = YES;
314 | CLANG_WARN_INFINITE_RECURSION = YES;
315 | CLANG_WARN_INT_CONVERSION = YES;
316 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
317 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
318 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
319 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
320 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
321 | CLANG_WARN_STRICT_PROTOTYPES = YES;
322 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
323 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
324 | CLANG_WARN_UNREACHABLE_CODE = YES;
325 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
326 | COPY_PHASE_STRIP = NO;
327 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
328 | ENABLE_NS_ASSERTIONS = NO;
329 | ENABLE_STRICT_OBJC_MSGSEND = YES;
330 | GCC_C_LANGUAGE_STANDARD = gnu11;
331 | GCC_NO_COMMON_BLOCKS = YES;
332 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
333 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
334 | GCC_WARN_UNDECLARED_SELECTOR = YES;
335 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
336 | GCC_WARN_UNUSED_FUNCTION = YES;
337 | GCC_WARN_UNUSED_VARIABLE = YES;
338 | MACOSX_DEPLOYMENT_TARGET = 10.15;
339 | MTL_ENABLE_DEBUG_INFO = NO;
340 | MTL_FAST_MATH = YES;
341 | SDKROOT = macosx;
342 | SWIFT_COMPILATION_MODE = wholemodule;
343 | SWIFT_OPTIMIZATION_LEVEL = "-O";
344 | };
345 | name = Release;
346 | };
347 | 99448BE523B6729D00A20E8B /* Debug */ = {
348 | isa = XCBuildConfiguration;
349 | buildSettings = {
350 | CODE_SIGN_STYLE = Automatic;
351 | DEVELOPMENT_TEAM = "";
352 | ENABLE_HARDENED_RUNTIME = YES;
353 | PRODUCT_NAME = "$(TARGET_NAME)";
354 | SWIFT_VERSION = 5.0;
355 | };
356 | name = Debug;
357 | };
358 | 99448BE623B6729D00A20E8B /* Release */ = {
359 | isa = XCBuildConfiguration;
360 | buildSettings = {
361 | CODE_SIGN_STYLE = Automatic;
362 | DEVELOPMENT_TEAM = "";
363 | ENABLE_HARDENED_RUNTIME = YES;
364 | PRODUCT_NAME = "$(TARGET_NAME)";
365 | SWIFT_VERSION = 5.0;
366 | };
367 | name = Release;
368 | };
369 | /* End XCBuildConfiguration section */
370 |
371 | /* Begin XCConfigurationList section */
372 | 99448BD823B6729D00A20E8B /* Build configuration list for PBXProject "StarShip" */ = {
373 | isa = XCConfigurationList;
374 | buildConfigurations = (
375 | 99448BE223B6729D00A20E8B /* Debug */,
376 | 99448BE323B6729D00A20E8B /* Release */,
377 | );
378 | defaultConfigurationIsVisible = 0;
379 | defaultConfigurationName = Release;
380 | };
381 | 99448BE423B6729D00A20E8B /* Build configuration list for PBXNativeTarget "StarShip" */ = {
382 | isa = XCConfigurationList;
383 | buildConfigurations = (
384 | 99448BE523B6729D00A20E8B /* Debug */,
385 | 99448BE623B6729D00A20E8B /* Release */,
386 | );
387 | defaultConfigurationIsVisible = 0;
388 | defaultConfigurationName = Release;
389 | };
390 | /* End XCConfigurationList section */
391 | };
392 | rootObject = 99448BD523B6729D00A20E8B /* Project object */;
393 | }
394 |
--------------------------------------------------------------------------------
/StarShip/Sources/Execution/Execution.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Execution.swift
3 | // StarShip
4 | //
5 |
6 | import Foundation
7 |
8 | public class Execution {
9 | var variableStack: [absoluteVariable] = [] // all variables with their values
10 | var functionStack: [functionDefinition] = [] // all functions with their tokens
11 | var returningValue: Any? = nil // every execution unit has a return value
12 | // since a new function uses a new execution
13 | // unit. This way the returned value can be
14 | // used after a unit has executed
15 |
16 | let stdFuncs = ["add", // add two numbers
17 | "sub", // subtract one number from another
18 | "mul", // multiplies two numbers
19 | "div", // divides two numbers
20 | "exit", // exit a function
21 | "inc", // add one to a number
22 | "dec", // subtract one from a number
23 | "for", // repeat a function x times
24 | "if", // if a bool is true, run a function
25 | "eql", // checks if two values are the same
26 | "big", // checks if first value is bigger than the second one
27 | "sml", // checks if first value is smaller than the second one
28 | "not"] // flips a bool
29 |
30 | var variablesToReplace: [DataType] = [] // saves a data type between removal of a variable
31 | // and adding it back on.
32 | // more info in "removeAndReplace"
33 |
34 | public func execute(_ input: [Instruction]) {
35 | var instructions = input
36 |
37 | // first, we filter out all function definitions and add them to the function stack
38 | // this way functions defined at the bottom of the document can be called from the top of the document
39 |
40 | let functions = instructions.filter { instruction -> Bool in
41 | instruction.type == .functionDefinition
42 | }
43 | for function in functions {
44 | functionStack.append(function as! functionDefinition)
45 | instructions.remove(at: instructions.firstIndex(where: { inst -> Bool in
46 | let instFunc = inst as? functionDefinition
47 | let funcFunc = function as! functionDefinition
48 | if instFunc != nil {
49 | return instFunc!.name == funcFunc.name
50 | } else {
51 | return false
52 | }
53 | })!)
54 | }
55 |
56 | while instructions.count > 0 {
57 | if instructions.first!.type == .absoluteVariable {
58 | // MARK: Absolute variable definition
59 | variableStack.append(instructions.first! as! absoluteVariable)
60 | instructions.removeFirst()
61 | } else if instructions.first!.type == .functionDefinition {
62 | // MARK: Function definition
63 | // A function gets defined with all of its inner tokens; the tokens get parsed when the function is called; then the function will be executed in a new execution unit
64 | functionStack.append(instructions.first! as! functionDefinition)
65 | instructions.removeFirst()
66 | } else if instructions.first!.type == .printValue {
67 | // MARK: Print value
68 | print((instructions.first! as! printValue).value)
69 | instructions.removeFirst()
70 | } else if instructions.first!.type == .printVariable {
71 | // MARK: Print variable
72 | // the last variable with the name will be used, since an old variable with the same name can still be in the stack in certain situations
73 | // this is mostly just a bug waiting to be fixed
74 | let printingVariable = variableStack.last(where: { variable -> Bool in
75 | variable.name == (instructions.first! as! printVariable).name
76 | })
77 | if printingVariable != nil {
78 | print(printingVariable!.value)
79 | } else {
80 | print("Fatal error: Variable to print not found")
81 | exit(0)
82 | }
83 | instructions.removeFirst()
84 | } else if instructions.first!.type == .functionVariable {
85 | // MARK: Function variable definition
86 | // warning: Nested mess ahead
87 |
88 | if let functionToCall = functionStack.first(where: { definition -> Bool in
89 | definition.name == (instructions.first! as! functionVariable).functionName &&
90 | definition.functionArguments.count == (instructions.first! as! functionVariable).arguments.count
91 | }) {
92 | // found function to call
93 | let functionVar = (instructions.first! as! functionVariable)
94 |
95 | // For defining the arguments as arg1, arg2, arg3, … we generate variable definition code, which will then be parsed and executed in the execution unit of the function
96 | var addititonalDefinitions = ""
97 | for index in 0.. Bool in
103 | variable.name == argument.val
104 | }) {
105 | // check if the proposed type matches the type of the input
106 | if Helpers().getTokenType(fromDataType: argValue.dataType) != Helpers().getTokenTypeFromString(functionToCall.functionArguments[index].val) {
107 | print("Fatal error: Argument types didn't match")
108 | exit(0)
109 | }
110 | argumentValue = "\"" + Helpers().getStringValue(argValue) + "\""
111 | }
112 | } else {
113 | // using an absolute value as input
114 | if argument.type != Helpers().getTokenTypeFromString(functionToCall.functionArguments[index].val) {
115 | print("Fatal error: Argument types didn't match")
116 | exit(0)
117 | }
118 | argumentValue = argument.type != .string ? argument.val : "\"" + argument.val + "\""
119 | }
120 | // now define generate the variable definition
121 | addititonalDefinitions.append(contentsOf: "let arg" + String(index + 1) + " = " + argumentValue + " \n")
122 | }
123 |
124 | if variablesToReplace.count == 1 {
125 | if Helpers().getDataType(fromString: functionToCall.returningTypes.first!.val) != variablesToReplace[0] {
126 | print("variable that has to be replaced didn't match original type")
127 | exit(0)
128 | } else {
129 | variablesToReplace.removeAll()
130 | }
131 | }
132 | if functionToCall.returningTypes.count != 1 {
133 | print("functions can only have one return argument for now")
134 | }
135 |
136 | // now the additional code gets tokenized and appended to the tokenized function code
137 | let additionalCode = Lexer().Run(addititonalDefinitions)
138 | var functionCode = functionToCall.innerCode
139 | functionCode.insert(contentsOf: additionalCode, at: 0)
140 | let parser = Parser().Run(functionCode)
141 | // the function will be run in a new execution unit
142 | let execUnit = Execution()
143 | // the variable stack will be added to the new unit
144 | execUnit.variableStack = variableStack
145 | execUnit.functionStack = functionStack
146 | execUnit.execute(parser)
147 | // check if the unit returned
148 | if (execUnit.returningValue != nil) {
149 | let newVariable = absoluteVariable(name: functionVar.name, value: execUnit.returningValue!, dataType: Helpers().getDataType(fromString: functionToCall.returningTypes.first!.val))
150 | if Helpers().checkIf(any: execUnit.returningValue!, matches: Helpers().getDataType(fromString: functionToCall.returningTypes.first!.val)) {
151 | variableStack.append(newVariable)
152 | } else {
153 | // Exit if the function returned a different value from what it initially proposed
154 | print("Fatal error: Retured value doesn't match return requirement")
155 | exit(0)
156 | }
157 | } else {
158 | // The function has to return right now
159 | print("Fatal error: The function did not return")
160 | exit(0)
161 | }
162 | instructions.removeFirst()
163 | } else if let functionToCall = stdFuncs.first(where: { definition -> Bool in
164 | definition == (instructions.first! as! functionVariable).functionName
165 | }) {
166 | // if the function isn't in the function stack, it might be a std function
167 | let functionVar = (instructions.first! as! functionVariable)
168 |
169 | var argumentVars: [absoluteVariable] = []
170 | for index in 0.. Bool in
174 | variable.name == argument.val
175 | }) {
176 | argumentVars.append(argValue)
177 | } // only say the variable doesn't exist if it isn't an if or for function
178 | } else {
179 | guard let absVar = getAbsoluteVariable(argument, named: functionVar.name) else { return }
180 | argumentVars.append(absVar)
181 | }
182 | }
183 |
184 | var newVariable: absoluteVariable?
185 |
186 | // switch to switch between the standard functions
187 | // this code is pretty messy; might clean it up later
188 | switch functionToCall {
189 | case "add":
190 | newVariable = absoluteVariable(name: functionVar.name, value: stdAdd(arguments: argumentVars), dataType: .number)
191 | case "sub":
192 | newVariable = absoluteVariable(name: functionVar.name, value: stdSub(arguments: argumentVars), dataType: .number)
193 | case "mul":
194 | newVariable = absoluteVariable(name: functionVar.name, value: stdMul(arguments: argumentVars), dataType: .number)
195 | case "div":
196 | newVariable = absoluteVariable(name: functionVar.name, value: stdDiv(arguments: argumentVars), dataType: .number)
197 | case "exit":
198 | print("Program exited")
199 | exit(0) // 1 is the code for an in-program exit
200 | case "inc":
201 | newVariable = absoluteVariable(name: functionVar.name, value: stdInc(arguments: argumentVars), dataType: .number)
202 | case "dec":
203 | newVariable = absoluteVariable(name: functionVar.name, value: stdDec(arguments: argumentVars), dataType: .number)
204 | case "for":
205 | // some checking
206 | if (functionVar.arguments.count == 2) {
207 | let count = resolveNumberForToken(functionVar.arguments[0])
208 | let function = functionStack.last { def -> Bool in
209 | def.name == functionVar.arguments[1].val
210 | }
211 | if function == nil {
212 | print("Fatal error: Function to repeat not found")
213 | exit(0)
214 | }
215 | let result = stdFor(count: count, functionCode: function!.innerCode)
216 | newVariable = absoluteVariable(name: functionVar.name, value: result, dataType: .number)
217 | }
218 | case "if":
219 | // some checking
220 | if (functionVar.arguments.count == 2) {
221 | let isTrue = resolveBoolForToken(functionVar.arguments[0])
222 | let function = functionStack.last { def -> Bool in
223 | def.name == functionVar.arguments[1].val
224 | }
225 | if function == nil {
226 | print("Fatal error: Function for condition not found")
227 | exit(0)
228 | }
229 | let result = stdIf(variable: isTrue, functionCode: function!.innerCode)
230 | newVariable = absoluteVariable(name: functionVar.name, value: result, dataType: .bool)
231 | }
232 | case "eql", "big", "sml":
233 | if (functionVar.arguments.count == 2) {
234 | let first = getAbsoluteVarForToken(functionVar.arguments[0]) // get absolute var before calling stdEqual
235 | let second = getAbsoluteVarForToken(functionVar.arguments[1]) // get absolute var before calling stdEqual
236 | var result: Any?
237 | if functionToCall == "eql" {
238 | result = stdEqual(first: first, second: second)
239 | } else if functionToCall == "big" {
240 | result = stdBigger(first: first, second: second)
241 | } else {
242 | // sml
243 | result = stdSmaller(first: first, second: second)
244 | }
245 | newVariable = absoluteVariable(name: functionVar.name, value: result!, dataType: .bool)
246 | }
247 | case "not":
248 | // just flip the bool
249 | if functionVar.arguments.count == 1 {
250 | var result = resolveBoolForToken(functionVar.arguments[0])
251 | result.toggle()
252 | newVariable = absoluteVariable(name: functionVar.name, value: result, dataType: .bool)
253 | }
254 | default:
255 | return
256 | }
257 |
258 | // check for type
259 | if variablesToReplace.count == 1 {
260 | if newVariable?.dataType != variablesToReplace[0] {
261 | print("variable that has to be replaced didn't match original type")
262 | return
263 | } else {
264 | variablesToReplace.removeAll()
265 | }
266 | }
267 |
268 | // random errors
269 | newVariable != nil ? variableStack.append(newVariable!) : print("Couldn't invoke standard function")
270 | instructions.removeFirst()
271 | } else if let absVar = variableStack.first(where: { variable -> Bool in
272 | variable.name == (instructions.first! as! functionVariable).functionName
273 | }) {
274 | // defining a new variable from an existing variable
275 | let newVariable = absoluteVariable(name: (instructions.first! as! functionVariable).name,
276 | value: absVar.value,
277 | dataType: absVar.dataType)
278 | variableStack.append(newVariable)
279 | instructions.removeFirst()
280 | } else {
281 | // if the function isn't a std func and not in the function stack, it doesn't exist
282 | print("Fatal error: Function isn't yet defined")
283 | exit(0)
284 | }
285 | } else if instructions.first!.type == .removeAndReplace {
286 | // MARK: Remove and replace variable
287 | // This is a hack to not have to write a second variable definition function
288 | // Essentially, we remove the variable from the variable stack, save its type and then add an instruction to re-add it to the variable stack
289 | // This isn't the best way to do this, but it works for now
290 |
291 | let firstIndex = variableStack.lastIndex { variable -> Bool in
292 | variable.name == (instructions.first! as! removeAndReplace).name
293 | }
294 | if firstIndex != nil {
295 | variablesToReplace.append(variableStack[firstIndex!].dataType) // add the datatype from the original variable
296 | let removeAndRep = instructions.first! as! removeAndReplace
297 | // New instruction to re-add the variable to the stack
298 | let newInstruction = functionVariable(name: removeAndRep.name, functionName: removeAndRep.functionName, arguments: removeAndRep.arguments)
299 | variableStack.remove(at: firstIndex!)
300 | instructions.removeFirst()
301 | instructions.insert(newInstruction, at: 0)
302 | } else {
303 | print("Fatal error: Variable not yet defined")
304 | exit(0)
305 | }
306 | } else if instructions.first!.type == .removeAndValue {
307 | // replace with value
308 | let removeAndVal = instructions.first! as! removeAndValue
309 | guard let firstVariable = variableStack.lastIndex(where: { variable -> Bool in
310 | variable.name == removeAndVal.name
311 | }) else {
312 | print("Fatal error: Could not find variable for reassignment")
313 | exit(0)
314 | }
315 | if variableStack[firstVariable].dataType == removeAndVal.dataType {
316 | variableStack[firstVariable].value = removeAndVal.value
317 | instructions.removeFirst()
318 | } else {
319 | print("Fatal error: Data types did not match")
320 | exit(0)
321 | }
322 | } else if instructions.first!.type == .returnAbsoluteValue {
323 | // MARK: Return value
324 | let returnInstruction = instructions.first! as! returnAbsoluteValue
325 | returningValue = returnInstruction.value
326 | instructions.removeFirst()
327 | } else if instructions.first!.type == .returnVariable {
328 | // MARK: Return variable
329 | // the last variable with the name will be used, since an old variable with the same name can still be in the stack in certain situations
330 | // this is mostly just a bug waiting to be fixed
331 | let returnVariable = variableStack.last(where: { variable -> Bool in
332 | variable.name == (instructions.first! as! returnVariable).name
333 | })
334 | if returnVariable != nil {
335 | returningValue = returnVariable?.value
336 | } else {
337 | print("Fatal error: Variable not found")
338 | exit(0)
339 | }
340 | instructions.removeFirst()
341 | }
342 | }
343 | }
344 | }
345 |
--------------------------------------------------------------------------------