├── 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 | Version: 0.1 5 | Extension: .strs 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 | --------------------------------------------------------------------------------