├── .github └── workflows │ ├── build.yml │ └── todo.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle.kts ├── documentation ├── CODE_STYLE.MD ├── OVERVIEW.MD └── STANDARD_LIBRARY.MD ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── src ├── main │ └── kotlin │ │ └── spritz │ │ ├── EvaluationResult.kt │ │ ├── SpritzEnvironment.kt │ │ ├── api │ │ ├── CallData.kt │ │ ├── Coercion.kt │ │ ├── Config.kt │ │ ├── StandardOverride.kt │ │ ├── annotations │ │ │ ├── Excluded.kt │ │ │ └── Identifier.kt │ │ └── result │ │ │ ├── Failure.kt │ │ │ ├── Result.kt │ │ │ └── Success.kt │ │ ├── builtin │ │ ├── Global.kt │ │ ├── Standard.kt │ │ ├── System.kt │ │ └── companions │ │ │ ├── BooleanCompanion.kt │ │ │ ├── ClassCompanion.kt │ │ │ ├── Companion.kt │ │ │ ├── DictionaryCompanion.kt │ │ │ ├── InstanceCompanion.kt │ │ │ ├── ListCompanion.kt │ │ │ ├── NumberCompanion.kt │ │ │ └── StringCompanion.kt │ │ ├── error │ │ ├── Error.kt │ │ ├── interpreting │ │ │ ├── CallArgumentMismatchError.kt │ │ │ ├── DualDeclarationError.kt │ │ │ ├── ExternalNotFoundError.kt │ │ │ ├── IllegalOperationError.kt │ │ │ ├── ImportError.kt │ │ │ ├── JvmError.kt │ │ │ ├── MathsError.kt │ │ │ ├── MemberNotFoundError.kt │ │ │ ├── MutabilityAssignationError.kt │ │ │ ├── NodeIntepreterNotFoundError.kt │ │ │ ├── ReturnTypeMismatchError.kt │ │ │ ├── RuntimeError.kt │ │ │ ├── TypeMismatchError.kt │ │ │ └── UndefinedReferenceError.kt │ │ ├── lexing │ │ │ └── LexingError.kt │ │ └── parsing │ │ │ └── ParsingError.kt │ │ ├── interfaces │ │ └── Cloneable.kt │ │ ├── interpreter │ │ ├── Interpreter.kt │ │ ├── RuntimeResult.kt │ │ └── context │ │ │ └── Context.kt │ │ ├── lexer │ │ ├── Lexer.kt │ │ ├── position │ │ │ ├── LinkPosition.kt │ │ │ └── Position.kt │ │ └── token │ │ │ ├── Token.kt │ │ │ └── TokenType.kt │ │ ├── parser │ │ ├── ParseResult.kt │ │ ├── Parser.kt │ │ ├── node │ │ │ └── Node.kt │ │ └── nodes │ │ │ ├── AccessNode.kt │ │ │ ├── AssignmentNode.kt │ │ │ ├── BinaryOperationNode.kt │ │ │ ├── BreakNode.kt │ │ │ ├── CatchNode.kt │ │ │ ├── ClassDefineNode.kt │ │ │ ├── ContinueNode.kt │ │ │ ├── DictionaryNode.kt │ │ │ ├── EnumDefineNode.kt │ │ │ ├── ForNode.kt │ │ │ ├── ImportNode.kt │ │ │ ├── ListNode.kt │ │ │ ├── NativeNode.kt │ │ │ ├── NumberNode.kt │ │ │ ├── ReturnNode.kt │ │ │ ├── StringNode.kt │ │ │ ├── TaskCallNode.kt │ │ │ ├── TaskDefineNode.kt │ │ │ ├── TryNode.kt │ │ │ ├── UnaryOperationNode.kt │ │ │ ├── WhileNode.kt │ │ │ └── condition │ │ │ ├── Case.kt │ │ │ └── ConditionNode.kt │ │ ├── util │ │ ├── Argument.kt │ │ ├── Misc.kt │ │ └── RequiredArgument.kt │ │ ├── value │ │ ├── NothingValue.kt │ │ ├── NullValue.kt │ │ ├── PrimitiveValue.kt │ │ ├── Value.kt │ │ ├── bool │ │ │ └── BooleanValue.kt │ │ ├── class │ │ │ ├── ClassValue.kt │ │ │ ├── DefinedClassValue.kt │ │ │ ├── DefinedInstanceValue.kt │ │ │ ├── InstanceValue.kt │ │ │ ├── JvmClassValue.kt │ │ │ └── JvmInstanceValue.kt │ │ ├── dictionary │ │ │ └── DictionaryValue.kt │ │ ├── enum │ │ │ ├── EnumValue.kt │ │ │ └── JvmEnumValue.kt │ │ ├── list │ │ │ └── ListValue.kt │ │ ├── number │ │ │ ├── ByteValue.kt │ │ │ ├── FloatValue.kt │ │ │ ├── IntValue.kt │ │ │ ├── LongValue.kt │ │ │ └── NumberValue.kt │ │ ├── string │ │ │ └── StringValue.kt │ │ ├── table │ │ │ ├── Symbol.kt │ │ │ ├── Table.kt │ │ │ ├── TableAccessor.kt │ │ │ └── result │ │ │ │ └── Result.kt │ │ └── task │ │ │ ├── DefinedTaskValue.kt │ │ │ ├── JvmTaskValue.kt │ │ │ └── TaskValue.kt │ │ └── warning │ │ └── Warning.kt └── test │ └── kotlin │ ├── MiscTests.kt │ └── jvmlink │ ├── ClassTesting.kt │ ├── ClassWithEnum.kt │ └── InstanceTesting.kt └── workspace ├── demos └── cli.sz └── examples ├── dict.sz ├── enums.sz ├── for_loop.sz ├── importing.sz ├── lambdas.sz ├── natives.sz ├── safe_call.sz └── try_catch.sz /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | 11 | - name: Set up JDK 1.8 12 | uses: actions/setup-java@v1 13 | with: 14 | java-version: 1.8 15 | 16 | - name: Cache Gradle packages 17 | uses: actions/cache@v2 18 | with: 19 | path: | 20 | ~/.gradle/caches 21 | ~/.gradle/wrapper 22 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*','**/gradle-wrapper.properties') }} 23 | restore-keys: | 24 | ${{ runner.os }}-gradle- 25 | 26 | - name: Grant execute permission for gradlew 27 | run: chmod +x gradlew 28 | 29 | - name: Clean 30 | run: ./gradlew clean 31 | 32 | - name: Build 33 | run: ./gradlew build 34 | 35 | - uses: actions/upload-artifact@v2 36 | with: 37 | name: Spritz-Latest 38 | path: build/libs/*.jar 39 | 40 | - name: Cleanup Gradle Cache 41 | run: | 42 | rm -rf ~/.gradle/caches/modules-2/modules-2.lock 43 | rm -rf ~/.gradle/caches/modules-2/gc.properties 44 | -------------------------------------------------------------------------------- /.github/workflows/todo.yml: -------------------------------------------------------------------------------- 1 | name: Create issue from TODOs 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | importAll: 7 | default: 'false' 8 | required: false 9 | type: boolean 10 | description: Enable, if you want to import all TODOs. Runs on checked out branch! Only use if you're sure what you are doing. 11 | push: 12 | branches: # do not set multiple branches, todos might be added and then get referenced by themselves in case of a merge 13 | - main 14 | - master 15 | 16 | permissions: 17 | issues: write 18 | repository-projects: read 19 | contents: read 20 | 21 | jobs: 22 | todos: 23 | runs-on: ubuntu-latest 24 | 25 | steps: 26 | - uses: actions/checkout@v3 27 | 28 | - name: Run Issue Bot 29 | uses: derjuulsn/todo-issue@main 30 | with: 31 | excludePattern: '^(node_modules/)' 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.war 15 | *.nar 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar 20 | 21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 22 | hs_err_pid* 23 | 24 | # idea files 25 | /.idea/ 26 | 27 | # generated files 28 | /build/ 29 | /.gradle/ 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spritz 2 | 3 | ![version](https://img.shields.io/github/v/release/SpritzLanguage/Spritz?style=for-the-badge) 4 | 5 | An interpreted programming language written in Kotlin, designed for interoperability with the JVM. 6 | 7 | ## Get Started 8 | Add Spritz to your project, with your build system of choice: 9 |
10 | Gradle (Groovy) 11 | 12 | ```groovy 13 | repositories { 14 | maven { url 'https://jitpack.io' } 15 | } 16 | 17 | dependencies { 18 | implementation 'com.github.SpritzLanguage:Spritz:1.0.0-alpha' 19 | } 20 | ``` 21 | 22 |
23 | 24 |
25 | Gradle (Kotlin) 26 | 27 | ```kotlin 28 | repositories { 29 | maven("https://jitpack.io") 30 | } 31 | 32 | dependencies { 33 | implementation("com.github.SpritzLanguage:Spritz:1.0.0-alpha") 34 | } 35 | ``` 36 | 37 |
38 | 39 |
40 | Maven 41 | 42 | ```xml 43 | 44 | 45 | jitpack.io 46 | https://jitpack.io 47 | 48 | 49 | 50 | 51 | com.github.SpritzLanguage 52 | Spritz 53 | 1.0.0-alpha 54 | 55 | ``` 56 | 57 |
58 | 59 | Create a `SpritzEnvironment` instance to handle scripting: 60 | 61 | ```kotlin 62 | val env = SpritzEnvironment(Config()) 63 | .setWarningHandler(::println) 64 | .setErrorHandler(::println) 65 | ``` 66 | 67 | You can then evaluate a file with the `SpritzEnvironment#evaluate` method: 68 | 69 | ```kotlin 70 | env.evaluate(File("example.sz")) 71 | ``` 72 | 73 | Please read the documentation (coming soon) for more information. 74 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | java 3 | `maven-publish` 4 | kotlin("jvm") version "1.6.20" 5 | } 6 | 7 | group = "spritz" 8 | version = "1.0.0" 9 | 10 | repositories.mavenCentral() 11 | 12 | dependencies { 13 | implementation(kotlin("stdlib-jdk8", "1.6.20")) 14 | implementation(kotlin("reflect", "1.6.20")) 15 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1") 16 | } 17 | 18 | publishing.publications.create("maven").from(components["java"]) -------------------------------------------------------------------------------- /documentation/CODE_STYLE.MD: -------------------------------------------------------------------------------- 1 | # Code Styling 2 | An overview of how Spritz code should be formatted. 3 | 4 | ## General 5 | Braces should ideally follow the `1TBS` brace style. In short, braces should 6 | never be on their own line, like various `C` style languages. 7 | 8 | ### Correct Braces 9 | ``` 10 | task main { 11 | if (true) { 12 | std::println("always gonna print...") 13 | } 14 | } 15 | ``` 16 | 17 | ### Incorrect braces 18 | ``` 19 | task main 20 | { 21 | if (true) 22 | { 23 | std::println("always gonna print...") 24 | } 25 | } 26 | ``` 27 | 28 | ## Tasks 29 | ### Syntax 30 | ``` 31 | task [return type] [name] [arguments] { 32 | [body] 33 | } 34 | ``` 35 | 36 | If you are specifying a return type, it should be encased inside `<>`: 37 | ``` 38 | task a = 2 + 2 39 | ``` 40 | 41 | A task with no parameters should not use `()`. 42 | ``` 43 | task example { 44 | std::println("hello, world!") 45 | } 46 | ``` 47 | 48 | If a task only performs and returns an expression, it should be considered an `expression` task. 49 | 50 | ### Correct 51 | ``` 52 | task expression = 2 + 4 53 | ``` 54 | 55 | ### Incorrect 56 | ``` 57 | task expression { 58 | return 2 + 4 59 | } 60 | ``` 61 | 62 | Tasks should follow the `snake_case` name formatting, and not `camelCase` or `PascalCase`. 63 | 64 | ## Lambdas 65 | You can create a lambda with the `lambda` keyword. If a lambda has no arguments, an arrow (`->`) should be 66 | directly before the opening brace. 67 | 68 | ``` 69 | lambda -> {} 70 | ``` 71 | 72 | However, if it does have arguments, the arguments should be inside the braces, followed by an arrow. 73 | 74 | ``` 75 | lambda { a, b: int, c: float -> } 76 | ``` 77 | 78 | ## Classes 79 | A class with no parameters should not use `()` and a class with no 80 | inner tasks or members should not use `{}`. 81 | ``` 82 | class Example 83 | ``` 84 | 85 | ``` 86 | class DataClass(a: int, b: int) 87 | ``` 88 | 89 | ``` 90 | class WithBody { 91 | 92 | task example { 93 | return 0 94 | } 95 | 96 | } 97 | ``` 98 | 99 | Classes should always use `PascalCase`, rather than `camelCase` or `snake_case`. 100 | 101 | ## Dictionaries 102 | Dictionaries should always be declared on multiple lines, unless its initialised with only a single value. 103 | 104 | ### Correct 105 | ``` 106 | const a = { 107 | "member1": 2, 108 | "member2": 3, 109 | "member3": 4 110 | } 111 | 112 | const b = { "member1": 2 } 113 | ``` 114 | 115 | ### Incorrect 116 | ``` 117 | const a = { "member1": 2, "member2": 3, "member3": 4 } 118 | 119 | const b = { 120 | "member1": 2 121 | } 122 | ``` 123 | 124 | ## Try-Catch statements 125 | A Try-Catch statement where nothing happens on an exception should omit the `catch` statement, as per the following: 126 | 127 | ### Correct 128 | ``` 129 | try { 130 | const a = 2 131 | const b = "2" 132 | 133 | std::println(a + b) 134 | } 135 | 136 | try -> std::println(2 + "2") 137 | ``` 138 | 139 | ### Incorrect 140 | ``` 141 | try { 142 | const a = 2 143 | const b = "2" 144 | 145 | std::println(a + b) 146 | } catch exception {} 147 | 148 | try -> std::println(2 + "2") 149 | catch exception {} 150 | ``` -------------------------------------------------------------------------------- /documentation/OVERVIEW.MD: -------------------------------------------------------------------------------- 1 | # Spritz - Overview 2 | The very basics to writing Spritz. Make sure to visit `CODE_STYLE.MD` for instructions 3 | on the ideal way to format your code, and visit `STANDARD_LIBRARY` for documentation 4 | on the `std` library and other default inclusions. 5 | 6 | # The basics 7 | Features that are needed to write any amount of Spritz. 8 | 9 | ## Variables 10 | Declare mutable variables with the `mut` keyword: 11 | `mut [name] = [value]` 12 | 13 | E.G. 14 | `mut a = 5` 15 | 16 | Declare constant variables with the `const` keyword: 17 | `const [name] = [value]` 18 | 19 | E.G. 20 | `const a = 5` 21 | 22 | ## Tasks 23 | Define a task with the `task` keyword. You can follow this with the return type in `<>`, 24 | then the name, arguments, and body. 25 | 26 | ``` 27 | task a_plus_b(a, b) { 28 | return a + b 29 | } 30 | ``` 31 | 32 | You can use `=` for single expression tasks as well: 33 | ``` 34 | task a_plus_b(a, b) = a + b 35 | ``` 36 | 37 | Arguments are optional. If the task does not have any, it should not have `()`: 38 | ``` 39 | task main = std::println("hello, world!") 40 | ``` 41 | 42 | You can specify the types of arguments with `[name]: [type]`: 43 | ``` 44 | task a_plus_b(a: int, b: int) = a + b 45 | ``` 46 | 47 | ## Classes 48 | Technically, these aren't strictly classes and do not follow true OOP practices, 49 | but they are still similar. 50 | 51 | Define a task with the `class` keyword. You follow this with the name, arguments, 52 | and then body. 53 | 54 | Like tasks, the arguments are optional, however, the body is also optional. 55 | 56 | ``` 57 | class ExampleNoBody(a: int, b: Int) 58 | 59 | class ExampleNoArgs { 60 | task out { 61 | std::println("hello!") 62 | } 63 | } 64 | ``` 65 | 66 | ## Accessing contained members 67 | You can use the `::` operator to access a member of 68 | a class or value. 69 | 70 | ``` 71 | [parent]::[child] 72 | ``` 73 | 74 | E.G. 75 | 76 | ``` 77 | std::println("println is a child task") 78 | ``` 79 | 80 | You can also add `?` after the child identifier to specify it 81 | to return `null` if the parent value is null. 82 | 83 | ``` 84 | const a = null 85 | std::println(a::length?()) // will output null 86 | ``` 87 | 88 | ## Loops 89 | Declare a `for` loop like this: 90 | 91 | ``` 92 | for ([item identifier] : [list]) { 93 | // body 94 | } 95 | ``` 96 | 97 | Or, use `->` for a single expression loop: 98 | ``` 99 | for ([item identifier] : [list]) -> // body 100 | ``` 101 | 102 | E.G. 103 | ``` 104 | for (i : std::int_range(0, 5, 1)) { 105 | std::println(i) 106 | } 107 | ``` 108 | 109 | Declare a `while` loop like this: 110 | 111 | ``` 112 | while ([condition]) { 113 | // body 114 | } 115 | ``` 116 | 117 | Or, like the `for` loop, use `->` for a single expression loop: 118 | ``` 119 | while ([condition]) -> // body 120 | ``` 121 | 122 | E.G. 123 | ``` 124 | while (true) { 125 | const input = std::readln() 126 | 127 | if (input == "quit") { 128 | break 129 | } 130 | 131 | std::println(input) 132 | } 133 | ``` 134 | 135 | `break` and `continue` also exist, and can be used to 136 | exit the loop, or continue onto the next element, respectively. 137 | 138 | ## List 139 | Declare a list with `[]` 140 | 141 | ``` 142 | const li = [1, 2, 3] 143 | ``` 144 | 145 | ## Dictionary 146 | Declare a dictionary with `{}` 147 | 148 | ``` 149 | const dict = { 150 | [key]: [value] 151 | } 152 | ``` 153 | 154 | `key` must always be a string. 155 | 156 | E.G. 157 | 158 | ``` 159 | const ages = { 160 | "John": 34, 161 | "Amy": 21, 162 | "Owen": 12 163 | } 164 | ``` 165 | 166 | # Advanced Features 167 | Features that are more advanced, for people who have already learnt the basics. 168 | 169 | ## Natives 170 | You can directly reference JVM classes with the `native` keyword. 171 | 172 | Declare references like this: 173 | ``` 174 | native "[class path]" as [identifier] 175 | ``` 176 | 177 | You can then access that reference when using the identifier. 178 | 179 | ## Try-Catch 180 | You can handle exceptions by using the `try` keyword. 181 | 182 | ``` 183 | try { 184 | // body 185 | } 186 | 187 | try -> // body 188 | ``` 189 | 190 | You can then add a `catch` block to handle the exception: 191 | 192 | ``` 193 | try { 194 | // body 195 | } catch [exception identifier] { 196 | // catch body 197 | } 198 | 199 | try -> // body 200 | catch [exception identifier] -> // catch body 201 | ``` 202 | 203 | E.G. 204 | ``` 205 | try { 206 | 2 + " " // will always throw an exception 207 | } catch exception { 208 | std::println(exception) 209 | } 210 | ``` 211 | 212 | 213 | ## Lambdas 214 | You can create an anonymous task using the `lambda` keyword. 215 | 216 | If your lambda has no arguments it should look like this: 217 | 218 | ``` 219 | lambda -> { 220 | // body 221 | } 222 | ``` 223 | 224 | Otherwise, it should look like this: 225 | ``` 226 | lambda { arg_a, arg_b -> 227 | // body 228 | } 229 | ``` 230 | 231 | It will then equate to a normal task, but without a name. 232 | 233 | E.G. 234 | ``` 235 | const lambda_variable = lambda { a, b -> 236 | return a + b 237 | } 238 | 239 | std::println(lambda_variable(3, 4)) 240 | ``` 241 | 242 | It is worth noting that lambdas can reference values that have previously been defined, 243 | so you need to name your arguments something that hasn't already been defined. 244 | 245 | ``` 246 | const a = 2 247 | 248 | // this will NOT work because `a` is already defined 249 | const l = lambda { a -> 250 | std::println(a) 251 | } 252 | 253 | l(45) 254 | ``` 255 | 256 | ## Enums 257 | Declare an enum with the `enum` keyword, followed by the name, arguments, and body. 258 | 259 | Like classes and tasks, arguments are optional, but the body is not. 260 | 261 | ``` 262 | enum [identifier] ([arguments]) { 263 | // body 264 | } 265 | ``` 266 | 267 | The body should contain the members, like this: 268 | ``` 269 | enum Example { 270 | MEMBER_ONE, 271 | MEMBER_TWO 272 | } 273 | ``` 274 | 275 | If you have provided arguments, each member should be "called". 276 | 277 | ``` 278 | enum Example(a, b) { 279 | MEMBER_ONE(1, 2), 280 | MEMBER_TWO(3, 4) 281 | } 282 | ``` 283 | 284 | If you want to give each member its own body, add `{}` inside the body: 285 | 286 | ``` 287 | enum Example(a, b) { 288 | MEMBER_ONE(1, 2), 289 | MEMBER_TWO(3, 4) 290 | 291 | { 292 | task add = a + b 293 | } 294 | } 295 | ``` -------------------------------------------------------------------------------- /documentation/STANDARD_LIBRARY.MD: -------------------------------------------------------------------------------- 1 | # Standard Library 2 | Documentation for all inbuilt tools inside Spritz. 3 | 4 | ## Globals 5 | ### `true` 6 | The boolean value of true. 7 | 8 | ### `false` 9 | The boolean value of false. 10 | 11 | ### `null` 12 | A filler for a non-existent value. 13 | 14 | ## `std` 15 | Import using: 16 | ``` 17 | import "std" as [identifier] 18 | ``` 19 | 20 | ### `print(input: any)` 21 | Prints a value (without a new line) to the console 22 | 23 | ### `printf(input: string, format: any)` 24 | Prints the given `input` to the console (without a new line), but replaces the first instance of `%` in the given `input` with the corresponding value `format`. 25 | If `format` is a `list`, instances of `%` will be replaced with the value at the corresponding instance in `format`. 26 | 27 | ### `println(input: any)` 28 | Prints a value (with a new line) to the console 29 | 30 | ### `printlnf(input: string, format: any)` 31 | Prints the given `input` to the console (with a new line), but replaces the first instance of `%` in the given `input` with the corresponding value `format`. 32 | If `format` is a `list`, instances of `%` will be replaced with the value at the corresponding instance in `format`. 33 | 34 | ### ` readln()` 35 | Waits for the console to have input, then returns it. 36 | 37 | ### ` int_range(start: int, end: int, step: int)` 38 | Generates a list of integers based on the start, end, and step. 39 | Supports backwards ranges, where `start` is bigger than `end`. 40 | 41 | ### ` float_range(start: float, end: float, step: float)` 42 | Generates a list of floats based on the start, end, and step. 43 | Supports backwards ranges, where `start` is bigger than `end`. 44 | 45 | ## Primitives 46 | Tasks included with each primitive type. 47 | 48 | Primitives: 49 |
    50 |
  • int
  • 51 |
  • float
  • 52 |
  • number
  • 53 |
  • string
  • 54 |
  • boolean
  • 55 |
  • list
  • 56 |
  • dictionary
  • 57 |
  • class
  • 58 |
  • instance
  • 59 |
  • task
  • 60 |
61 | 62 | ## String 63 | ### ` length()` 64 | Gets the length of the string 65 | 66 | ### ` is_empty()` 67 | Checks if the length of the string is 0 68 | 69 | ### ` char_at(index: int)` 70 | Gets the character at the given index 71 | 72 | ### ` replace(old: string, new: string)` 73 | Gets a string where all instances of `old` have been replaced with `new` 74 | 75 | ### ` upper()` 76 | Gets a string with the same content as the current string, but uppercase. 77 | 78 | ### ` lower()` 79 | Gets a string with the same content as the current string, but lowercase. 80 | 81 | ### ` to_char_list()` 82 | Gets a list of `string` instances, with each element being a character in the current string. 83 | 84 | ### ` split(delimiter)` 85 | Splits the string by the delimiter and returns it as a list 86 | 87 | ### ` after(index: int)` 88 | Gets a list of characters after the given index (inclusive). 89 | 90 | ### ` int()` 91 | Converts the string to an integer value. 92 | 93 | ### ` float()` 94 | Converts the string to a float value. 95 | 96 | ### ` byte()` 97 | Converts the string to a byte value. 98 | 99 | ### ` boolean()` 100 | Converts the string to a boolean value. 101 | 102 | ## Boolean 103 | ### ` binary()` 104 | Returns the binary representation of this boolean. 105 | 106 | ## List 107 | ### `add(value: any)` 108 | Adds the given `value` to the list. 109 | 110 | ### ` get(index: int)` 111 | Gets the element at the given `index`. 112 | 113 | ### ` remove(value: any)` 114 | Removes the given `value` from the list, and returns whether removing was successful. 115 | 116 | ### ` removeAt(index: int)` 117 | Removes the element at the given `index`, and returns that element. 118 | 119 | ### ` length()` 120 | Gets the length of the list. 121 | 122 | ### ` is_empty()` 123 | Checks if the list is empty. 124 | 125 | ### ` after(index: int)` 126 | Gets a list of elements after the given index (inclusive). 127 | 128 | ### ` join(separator: string, format: string)` 129 | Returns a string that represents the concatenated list values, with the element being formatted by `format`, with `%` representing the item. 130 | 131 | ## Dictionary 132 | ### `set(key: string, value: any)` 133 | Sets the element mapped to the given `key` to `value`. 134 | 135 | ### ` get(key: string)` 136 | Gets the element mapped to the given `key`, or `null`, if it wasn't found. 137 | 138 | ### ` remove(key: string)` 139 | Removes the value mapped to the given `key`, and returns the last value that was mapped to it. 140 | 141 | ### ` length()` 142 | Gets the length of the dictionary. 143 | 144 | ## Class 145 | ### `name` 146 | The name of this class. 147 | 148 | ## Instance 149 | ### ` get_parent()` 150 | Gets the parent class of this instance, or `null` if this instance is a JVM instance. -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpritzLanguage/Spritz/f50d81fa2d5aa7784ac4c76717603eea079c8db2/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Stop when "xargs" is not available. 209 | if ! command -v xargs >/dev/null 2>&1 210 | then 211 | die "xargs is not available" 212 | fi 213 | 214 | # Use "xargs" to parse quoted args. 215 | # 216 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 217 | # 218 | # In Bash we could simply go: 219 | # 220 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 221 | # set -- "${ARGS[@]}" "$@" 222 | # 223 | # but POSIX shell has neither arrays nor command substitution, so instead we 224 | # post-process each arg (as a line of input to sed) to backslash-escape any 225 | # character that might be a shell metacharacter, then use eval to reverse 226 | # that process (while maintaining the separation between arguments), and wrap 227 | # the whole thing up as a single "set" statement. 228 | # 229 | # This will of course break if any of these variables contains a newline or 230 | # an unmatched quote. 231 | # 232 | 233 | eval "set -- $( 234 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 235 | xargs -n1 | 236 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 237 | tr '\n' ' ' 238 | )" '"$@"' 239 | 240 | exec "$JAVACMD" "$@" 241 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if %ERRORLEVEL% equ 0 goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if %ERRORLEVEL% equ 0 goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | set EXIT_CODE=%ERRORLEVEL% 84 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 85 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 86 | exit /b %EXIT_CODE% 87 | 88 | :mainEnd 89 | if "%OS%"=="Windows_NT" endlocal 90 | 91 | :omega 92 | -------------------------------------------------------------------------------- /src/main/kotlin/spritz/EvaluationResult.kt: -------------------------------------------------------------------------------- 1 | package spritz 2 | 3 | import spritz.error.Error 4 | import spritz.value.Value 5 | import spritz.warning.Warning 6 | 7 | /** 8 | * Returned after an evaluation of a script has been performed. 9 | * Contains the [value] which can be supplemented at the end of a script, 10 | * as well as any [warnings] that have been produced, or an [error] that 11 | * has occurred during lexing, parsing, or interpreting. 12 | * 13 | * @author surge 14 | * @since 19/03/2023 15 | */ 16 | data class EvaluationResult(val value: Value?, val warnings: List, val error: Error?) -------------------------------------------------------------------------------- /src/main/kotlin/spritz/SpritzEnvironment.kt: -------------------------------------------------------------------------------- 1 | package spritz 2 | 3 | import spritz.api.Coercion 4 | import spritz.api.Config 5 | import spritz.api.annotations.Excluded 6 | import spritz.api.annotations.Identifier 7 | import spritz.builtin.Global 8 | import spritz.builtin.Standard 9 | import spritz.builtin.System 10 | import spritz.error.Error 11 | import spritz.interpreter.Interpreter 12 | import spritz.interpreter.context.Context 13 | import spritz.lexer.Lexer 14 | import spritz.lexer.position.LinkPosition 15 | import spritz.parser.Parser 16 | import spritz.util.coercedName 17 | import spritz.util.getAllFields 18 | import spritz.util.getAllMethods 19 | import spritz.value.`class`.JvmClassValue 20 | import spritz.value.table.Table 21 | import spritz.value.table.TableAccessor 22 | import spritz.warning.Warning 23 | import java.io.File 24 | import java.lang.reflect.Modifier 25 | import java.nio.charset.Charset 26 | 27 | /** 28 | * An environment that contains the global value table, origin context, handlers, etc. 29 | * 30 | * @author surge 31 | * @since 25/02/2023 32 | */ 33 | class SpritzEnvironment(val config: Config = Config()) { 34 | 35 | // bottom of the table hierarchy 36 | val global = Table() 37 | 38 | // bottom of the context hierarchy 39 | val origin = Context("", config = config) 40 | .givenTable(global) 41 | .also { 42 | it.environment = this 43 | } 44 | 45 | init { 46 | // load builtins 47 | if (config.loadDefaults) { 48 | this.putIntoGlobal(Global) 49 | this.addLibrary("std", Standard) 50 | this.addLibrary("system", System) 51 | } 52 | } 53 | 54 | /** 55 | * Evaluates the given [content]. [fileName] is used for error handling. 56 | * @return An [EvaluationResult], which contains the returned value, warnings, and an error, if one was produced. 57 | */ 58 | fun evaluate(fileName: String, content: String): EvaluationResult { 59 | origin.name = fileName 60 | 61 | val lexer = Lexer(fileName, content).lex() 62 | 63 | if (lexer.second != null) { 64 | config.errorStream(lexer.second!!) 65 | return EvaluationResult(null, listOf(), lexer.second) 66 | } 67 | 68 | val parser = Parser(config, lexer.first).parse() 69 | 70 | parser.warnings.forEach(config.warningStream::invoke) 71 | 72 | if (parser.error != null) { 73 | config.errorStream(parser.error!!) 74 | return EvaluationResult(null, parser.warnings, parser.error) 75 | } 76 | 77 | val interpreter = Interpreter().visit(parser.node!!, origin) 78 | 79 | if (interpreter.error != null) { 80 | config.errorStream(interpreter.error!!) 81 | return EvaluationResult(null, parser.warnings, interpreter.error) 82 | } 83 | 84 | return EvaluationResult(interpreter.value, parser.warnings, null) 85 | } 86 | 87 | /** 88 | * Evaluates the given [file]. 89 | * @see evaluate 90 | */ 91 | fun evaluate(file: File): EvaluationResult = evaluate(file.name, file.readText(Charset.defaultCharset())) 92 | 93 | /** 94 | * Adds a referencable value to the [global] value table, which can be used in a script. 95 | * @return This environment 96 | */ 97 | fun putInstance(identifier: String, instance: Any): SpritzEnvironment { 98 | TableAccessor(global) 99 | .identifier(identifier) 100 | .immutable(true) 101 | .set(Coercion.IntoSpritz.coerce(instance).linked()) 102 | 103 | return this 104 | } 105 | 106 | /** 107 | * Adds a referencable class to the [global] value table, which can be instantiated in a script. 108 | * @return This environment 109 | */ 110 | fun putClass(identifier: String, clazz: Class<*>): SpritzEnvironment { 111 | TableAccessor(global) 112 | .identifier(identifier) 113 | .immutable(true) 114 | .set(JvmClassValue(identifier, clazz).linked()) 115 | 116 | return this 117 | } 118 | 119 | /** 120 | * Directly adds the (current) fields and methods to the [global] value table. The values will never update. 121 | * @return This environment 122 | */ 123 | fun putIntoGlobal(instance: Any): SpritzEnvironment { 124 | putIntoTable(instance, global, origin) 125 | 126 | return this 127 | } 128 | 129 | /** 130 | * Adds a library to the map of imports in the origin context 131 | * @return This environment 132 | */ 133 | fun addLibrary(identifier: String, instance: Any): SpritzEnvironment { 134 | origin.putImport(identifier, Coercion.IntoSpritz.coerce(instance).linked()) 135 | return this 136 | } 137 | 138 | companion object { 139 | 140 | /** 141 | * Directly adds the (current) fields and methods from the given [instance] into the given [table]. 142 | * Values are given [context]. 143 | */ 144 | @JvmStatic 145 | fun putIntoTable(instance: Any, table: Table, context: Context) { 146 | instance::class.java.getAllFields().forEach { 147 | if (it.isAnnotationPresent(Excluded::class.java) || it.isSynthetic) { 148 | return@forEach 149 | } 150 | 151 | it.isAccessible = true 152 | 153 | TableAccessor(table) 154 | .identifier(it.coercedName()) 155 | .immutable(true) 156 | .set(Coercion.IntoSpritz.coerce(it, instance).linked(), data = Table.Data(LinkPosition(), LinkPosition(), context)) 157 | } 158 | 159 | instance::class.java.getAllMethods().forEach { 160 | if (it.isAnnotationPresent(Excluded::class.java) || it.isSynthetic) { 161 | return@forEach 162 | } 163 | 164 | it.isAccessible = true 165 | 166 | TableAccessor(table) 167 | .identifier(it.coercedName()) 168 | .immutable(true) 169 | .set(Coercion.IntoSpritz.coerce(it, instance).linked(), data = Table.Data(LinkPosition(), LinkPosition(), context)) 170 | } 171 | } 172 | 173 | /** 174 | * Loads the static fields and methods of a [clazz] into a given [table]. 175 | */ 176 | @JvmStatic 177 | fun staticLoad(clazz: Class<*>, table: Table, context: Context) { 178 | clazz.declaredFields.forEach { 179 | if (it.isAnnotationPresent(Excluded::class.java) || it.isSynthetic) { 180 | return@forEach 181 | } 182 | 183 | if (Modifier.isPublic(it.modifiers) && Modifier.isStatic(it.modifiers)) { 184 | TableAccessor(table) 185 | .identifier(it.coercedName()) 186 | .immutable(true) 187 | .set(Coercion.IntoSpritz.coerce(it, null).linked(), data = Table.Data(LinkPosition(), LinkPosition(), context)) 188 | } 189 | } 190 | 191 | clazz.declaredMethods.forEach { 192 | if (it.isAnnotationPresent(Excluded::class.java) || it.isSynthetic) { 193 | return@forEach 194 | } 195 | 196 | if (Modifier.isPublic(it.modifiers) && Modifier.isStatic(it.modifiers)) { 197 | TableAccessor(table) 198 | .identifier(it.coercedName()) 199 | .immutable(true) 200 | .set(Coercion.IntoSpritz.coerce(it, null).linked(), data = Table.Data(LinkPosition(), LinkPosition(), context)) 201 | } 202 | } 203 | 204 | clazz.declaredClasses.forEach { 205 | if (it.isAnnotationPresent(Excluded::class.java) || it.isSynthetic) { 206 | return@forEach 207 | } 208 | 209 | if (Modifier.isPublic(it.modifiers)) { 210 | TableAccessor(table) 211 | .identifier(it.coercedName()) 212 | .immutable(true) 213 | .set( 214 | Coercion.IntoSpritz.coerce(it, null).linked(), 215 | data = Table.Data(LinkPosition(), LinkPosition(), context) 216 | ) 217 | } 218 | } 219 | 220 | clazz.enumConstants?.forEach { 221 | it as Enum<*> 222 | 223 | if (it.declaringClass.getField(it.name).isAnnotationPresent(Excluded::class.java)) { 224 | return@forEach 225 | } 226 | 227 | TableAccessor(table) 228 | .identifier(it.coercedName()) 229 | .immutable(true) 230 | .set( 231 | Coercion.IntoSpritz.coerce(it, null).linked(), 232 | data = Table.Data(LinkPosition(), LinkPosition(), context) 233 | ) 234 | } 235 | } 236 | 237 | } 238 | 239 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/api/CallData.kt: -------------------------------------------------------------------------------- 1 | package spritz.api 2 | 3 | import spritz.interpreter.context.Context 4 | import spritz.lexer.position.Position 5 | import spritz.value.Value 6 | import spritz.value.task.JvmTaskValue 7 | 8 | 9 | /** 10 | * @author surge 11 | * @since 04/03/2023 12 | */ 13 | data class CallData( 14 | /** 15 | * The start of where this task was called from. 16 | */ 17 | val start: Position, 18 | 19 | /** 20 | * The end of where this task was called from. 21 | */ 22 | val end: Position, 23 | 24 | /** 25 | * The context of where this task was called from. 26 | */ 27 | val context: Context, 28 | 29 | /** 30 | * The passed arguments. 31 | */ 32 | val arguments: List, 33 | 34 | /** 35 | * An instance of the JVM task. 36 | */ 37 | val instance: JvmTaskValue 38 | ) 39 | -------------------------------------------------------------------------------- /src/main/kotlin/spritz/api/Coercion.kt: -------------------------------------------------------------------------------- 1 | package spritz.api 2 | 3 | import spritz.api.result.Failure 4 | import spritz.api.result.Success 5 | import spritz.error.interpreting.JvmError 6 | import spritz.interpreter.RuntimeResult 7 | import spritz.interpreter.context.Context 8 | import spritz.lexer.position.LinkPosition 9 | import spritz.lexer.token.Token 10 | import spritz.lexer.token.TokenType 11 | import spritz.util.RequiredArgument 12 | import spritz.util.coercedName 13 | import spritz.value.NullValue 14 | import spritz.value.Value 15 | import spritz.value.bool.BooleanValue 16 | import spritz.value.`class`.JvmClassValue 17 | import spritz.value.`class`.JvmInstanceValue 18 | import spritz.value.list.ListValue 19 | import spritz.value.number.* 20 | import spritz.value.string.StringValue 21 | import spritz.value.task.JvmTaskValue 22 | import java.lang.reflect.Field 23 | import java.lang.reflect.InvocationTargetException 24 | import java.lang.reflect.Method 25 | 26 | /** 27 | * @author surge 28 | * @since 18/03/2023 29 | */ 30 | object Coercion { 31 | 32 | object IntoSpritz { 33 | 34 | /** 35 | * Coerces [any], with the given [instance] (which defaults to [any]). 36 | * @return The coerced value. 37 | */ 38 | fun coerce(any: Any?, instance: Any? = any): Value { 39 | if (any == null) { 40 | return NullValue().position(LinkPosition(), LinkPosition()).givenContext(Context("null")) 41 | } 42 | 43 | return when (any) { 44 | is Value -> any 45 | 46 | is Class<*> -> JvmClassValue(any.coercedName(), any) 47 | is Field -> coerce(any.get(instance)) 48 | 49 | is Boolean -> BooleanValue(any) 50 | is Number -> coerceNumber(any) 51 | is String -> StringValue(any) 52 | is Method -> coerceMethod(instance, any.coercedName(), any) 53 | is List<*> -> ListValue(any.map { coerce(it) }.toMutableList()) 54 | 55 | else -> JvmInstanceValue(any) 56 | 57 | }.position(LinkPosition(), LinkPosition()).givenContext(Context(any::class.java.simpleName)) 58 | } 59 | 60 | /** 61 | * Coerces a method inside the given [instance], with the given [identifier]. 62 | * @return The coerced task value. 63 | */ 64 | fun coerceMethod(instance: Any?, identifier: String, method: Method): JvmTaskValue { 65 | val types = method.parameterTypes.filter { it != CallData::class.java } 66 | val converted = arrayListOf>() 67 | 68 | types.forEach { 69 | converted.add(getEquivalentValue(it)) 70 | } 71 | 72 | return JvmTaskValue( 73 | identifier, 74 | 75 | method, 76 | 77 | { data -> 78 | val arguments = arrayListOf() 79 | 80 | if (method.parameters.any { it.type == CallData::class.java }) { 81 | arguments.add(data) 82 | } 83 | 84 | data.arguments.forEachIndexed { index, value -> 85 | arguments.add(getEquivalentPrimitive(value, types[index])) 86 | } 87 | 88 | if (data.context.getOrigin().config?.debug == true) { 89 | println("DEBUG: JVM Task '$identifier' has parameters: ${method.parameterTypes.map { it.simpleName }}, got given ${arguments.map { (it ?: NullValue())::class.java.simpleName }}") 90 | } 91 | 92 | try { 93 | val result = method.invoke(instance, *arguments.toTypedArray()) 94 | 95 | if (result is RuntimeResult) { 96 | return@JvmTaskValue result 97 | } 98 | 99 | if (result is Success) { 100 | RuntimeResult().success(result.value) 101 | } else if (result is Failure) { 102 | RuntimeResult().failure(result.error!!) 103 | } else { 104 | RuntimeResult().success(coerce(result)) 105 | } 106 | } catch (exception: InvocationTargetException) { 107 | RuntimeResult().failure(JvmError( 108 | "JVM Exception occurred: '${exception.targetException}'", 109 | data.start, 110 | data.end, 111 | data.context 112 | )) 113 | } catch (exception: IllegalArgumentException) { 114 | RuntimeResult().failure(JvmError( 115 | "JVM Exception occurred: '${exception}'", 116 | data.start, 117 | data.end, 118 | data.context 119 | )) 120 | } 121 | }, 122 | 123 | ArrayList(method.parameters.filter { it.type != CallData::class.java }.map { RequiredArgument( 124 | Token(TokenType.IDENTIFIER, it.name, LinkPosition(), LinkPosition()), object : Value("any") { 125 | 126 | override fun asJvmValue() = null 127 | override fun toString() = it.toString() 128 | 129 | }) }.toList()) 130 | ) 131 | } 132 | 133 | /** 134 | * Coerces a given number. 135 | * @return The coerced number value. 136 | */ 137 | fun coerceNumber(number: Number): NumberValue<*> { 138 | return when (number) { 139 | is Byte -> ByteValue(number) 140 | is Long -> LongValue(number.toLong()) 141 | is Int, is Short -> IntValue(number.toInt()) 142 | else -> FloatValue(number.toFloat()) 143 | } 144 | } 145 | 146 | } 147 | 148 | fun getEquivalentValue(clazz: Class<*>): Class<*> { 149 | if (isValue(clazz)) { 150 | return clazz 151 | } 152 | 153 | when (clazz) { 154 | Int::class.java, Float::class.java, Double::class.java, Long::class.java, Short::class.java, Byte::class.java -> { 155 | return NumberValue::class.java 156 | } 157 | 158 | String::class.java -> { 159 | return StringValue::class.java 160 | } 161 | 162 | Boolean::class.java -> { 163 | return BooleanValue::class.java 164 | } 165 | 166 | else -> { 167 | return JvmInstanceValue::class.java 168 | } 169 | } 170 | } 171 | 172 | fun getEquivalentPrimitive(value: Value, clazz: Class<*>): Any? { 173 | if (isValue(clazz)) { 174 | return value 175 | } 176 | 177 | when (value) { 178 | is NumberValue<*> -> { 179 | when (clazz) { 180 | Int::class.java -> { 181 | return value.value.toInt() 182 | } 183 | 184 | Float::class.java -> { 185 | return value.value.toFloat() 186 | } 187 | 188 | Double::class.java -> { 189 | return value.value.toDouble() 190 | } 191 | 192 | Long::class.java -> { 193 | return value.value.toLong() 194 | } 195 | 196 | Short::class.java -> { 197 | return value.value.toShort() 198 | } 199 | } 200 | } 201 | 202 | else -> { 203 | return value.asJvmValue() 204 | } 205 | } 206 | 207 | throw IllegalStateException("No equivalent primitive found! (Got $value, $clazz)") 208 | } 209 | 210 | private fun isValue(clazz: Class<*>): Boolean { 211 | return clazz == Value::class.java || clazz.superclass == Value::class.java || clazz.superclass?.superclass == Value::class.java 212 | } 213 | 214 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/api/Config.kt: -------------------------------------------------------------------------------- 1 | package spritz.api 2 | 3 | import spritz.error.Error 4 | import spritz.warning.Warning 5 | 6 | /** 7 | * @author surge 8 | * @since 11/03/2023 9 | */ 10 | data class Config( 11 | /** 12 | * Whether to allow forced assignations or not 13 | */ 14 | val forcedAssignations: Boolean = true, 15 | 16 | /** 17 | * Whether to load the standard library or not 18 | */ 19 | val loadDefaults: Boolean = true, 20 | 21 | /** 22 | * Whether to allow native linking or not 23 | */ 24 | val natives: Boolean = true, 25 | 26 | /** 27 | * Log debug messages 28 | */ 29 | val debug: Boolean = false, 30 | 31 | /** 32 | * How to handle warnings 33 | */ 34 | val warningStream: (Warning) -> Unit = { 35 | println(it) 36 | }, 37 | 38 | /** 39 | * How to handle errors 40 | */ 41 | val errorStream: (Error) -> Unit = { 42 | println(it) 43 | } 44 | ) -------------------------------------------------------------------------------- /src/main/kotlin/spritz/api/StandardOverride.kt: -------------------------------------------------------------------------------- 1 | package spritz.api 2 | 3 | import kotlin.system.exitProcess 4 | 5 | /** 6 | * @author surge 7 | * @since 02/04/2023 8 | */ 9 | object StandardOverride { 10 | 11 | var input: () -> String = { 12 | readlnOrNull() ?: "" 13 | } 14 | 15 | var output: (Any) -> Unit = { 16 | print(it) 17 | } 18 | 19 | var outputln: (Any) -> Unit = { 20 | println(it) 21 | } 22 | 23 | var exit: (Int) -> Unit = { 24 | exitProcess(it) 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/api/annotations/Excluded.kt: -------------------------------------------------------------------------------- 1 | package spritz.api.annotations 2 | 3 | /** 4 | * Marks a function or field to not be processed when the class is coerced. 5 | * 6 | * @author surge 7 | * @since 04/03/2023 8 | */ 9 | @Target(AnnotationTarget.FUNCTION, AnnotationTarget.FIELD, 10 | AnnotationTarget.CLASS, 11 | AnnotationTarget.PROPERTY, 12 | AnnotationTarget.VALUE_PARAMETER 13 | ) 14 | @Retention(AnnotationRetention.RUNTIME) 15 | annotation class Excluded 16 | -------------------------------------------------------------------------------- /src/main/kotlin/spritz/api/annotations/Identifier.kt: -------------------------------------------------------------------------------- 1 | package spritz.api.annotations 2 | 3 | /** 4 | * Marks a function or field to have its identifier overridden. 5 | * 6 | * @author surge 7 | * @since 04/03/2023 8 | */ 9 | @Target(AnnotationTarget.FUNCTION, AnnotationTarget.FIELD, AnnotationTarget.CLASS) 10 | @Retention(AnnotationRetention.RUNTIME) 11 | annotation class Identifier(val identifier: String) -------------------------------------------------------------------------------- /src/main/kotlin/spritz/api/result/Failure.kt: -------------------------------------------------------------------------------- 1 | package spritz.api.result 2 | 3 | import spritz.error.Error 4 | 5 | /** 6 | * Represents a failure to execute a function. 7 | */ 8 | class Failure(error: Error) : Result(null, error) -------------------------------------------------------------------------------- /src/main/kotlin/spritz/api/result/Result.kt: -------------------------------------------------------------------------------- 1 | package spritz.api.result 2 | 3 | import spritz.error.Error 4 | import spritz.value.Value 5 | 6 | /** 7 | * Represents a result of executing a function. 8 | */ 9 | open class Result(val value: Value?, val error: Error?) -------------------------------------------------------------------------------- /src/main/kotlin/spritz/api/result/Success.kt: -------------------------------------------------------------------------------- 1 | package spritz.api.result 2 | 3 | import spritz.value.Value 4 | 5 | /** 6 | * Represents a successful execution of a function. 7 | */ 8 | class Success(value: Value? = null) : Result(value, null) -------------------------------------------------------------------------------- /src/main/kotlin/spritz/builtin/Global.kt: -------------------------------------------------------------------------------- 1 | package spritz.builtin 2 | 3 | import spritz.api.annotations.Identifier 4 | import spritz.util.NUMBERS 5 | import spritz.value.PrimitiveValue 6 | import spritz.value.bool.BooleanValue 7 | import spritz.value.`class`.DefinedClassValue 8 | import spritz.value.`class`.DefinedInstanceValue 9 | import spritz.value.`class`.JvmClassValue 10 | import spritz.value.`class`.JvmInstanceValue 11 | import spritz.value.list.ListValue 12 | import spritz.value.number.FloatValue 13 | import spritz.value.number.IntValue 14 | import spritz.value.number.LongValue 15 | import spritz.value.number.NumberValue 16 | import spritz.value.string.StringValue 17 | import spritz.value.task.TaskValue 18 | 19 | /** 20 | * @author surge 21 | * @since 04/03/2023 22 | */ 23 | object Global { 24 | 25 | @Identifier("true") const val `true` = true 26 | @Identifier("false") const val `false` = false 27 | @Identifier("null") @JvmField val `null` = null 28 | 29 | // primitive types 30 | @JvmField val any = PrimitiveValue("any") { _, required -> required.type == "any" } 31 | @JvmField val int = PrimitiveValue("int") { given, required -> given is IntValue && required.type == "int" } 32 | @JvmField val long = PrimitiveValue("long") { given, required -> given is LongValue && required.type == "long" } 33 | @JvmField val float = PrimitiveValue("float") { given, required -> given is FloatValue && required.type == "float" } 34 | @JvmField val number = PrimitiveValue("number") { given, required -> given is NumberValue<*> && required.type in NUMBERS } 35 | @JvmField val boolean = PrimitiveValue("boolean") { given, required -> given is BooleanValue && required.type == "boolean" } 36 | @JvmField val string = PrimitiveValue("string") { given, required -> given is StringValue && required.type == "string" } 37 | @JvmField val task = PrimitiveValue("task") { given, required -> given is TaskValue && required.type == "task" } 38 | @JvmField val list = PrimitiveValue("list") { given, required -> given is ListValue && required.type == "list" } 39 | @JvmField val `class` = PrimitiveValue("class") { given, required -> (given is DefinedClassValue || given is JvmClassValue) && required.type == "class" } 40 | @JvmField val instance = PrimitiveValue("instance") { given, required -> (given is DefinedInstanceValue || given is JvmInstanceValue) && required.type == "instance" } 41 | 42 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/builtin/Standard.kt: -------------------------------------------------------------------------------- 1 | package spritz.builtin 2 | 3 | import spritz.api.StandardOverride 4 | import spritz.api.annotations.Excluded 5 | import spritz.api.annotations.Identifier 6 | import spritz.api.result.Result 7 | import spritz.api.result.Success 8 | import spritz.value.Value 9 | import spritz.value.list.ListValue 10 | import spritz.value.number.FloatValue 11 | import spritz.value.number.IntValue 12 | import spritz.value.string.StringValue 13 | 14 | /** 15 | * @author surge 16 | * @since 04/03/2023 17 | */ 18 | object Standard { 19 | 20 | fun print(input: Value) { 21 | StandardOverride.output(input.toString()) 22 | } 23 | 24 | fun printf(input: StringValue, formatted: Value) { 25 | StandardOverride.output(format(input.value, if (formatted is ListValue) formatted.elements else listOf(formatted))) 26 | } 27 | 28 | fun println(input: Value) { 29 | StandardOverride.outputln(input.toString()) 30 | } 31 | 32 | fun printlnf(input: StringValue, formatted: Value) { 33 | StandardOverride.outputln(format(input.value, if (formatted is ListValue) formatted.elements else listOf(formatted))) 34 | } 35 | 36 | fun readln(): String { 37 | return StandardOverride.input() 38 | } 39 | 40 | @Identifier("int_range") 41 | fun intRange(start: IntValue, end: IntValue, step: IntValue): Result { 42 | val elements = mutableListOf() 43 | 44 | if (start.value > end.value) { 45 | var i = start.value 46 | 47 | while (i > end.value) { 48 | elements.add(IntValue((i))) 49 | i -= step.value 50 | } 51 | } else { 52 | var i = start.value 53 | 54 | while (i < end.value) { 55 | elements.add(IntValue((i))) 56 | i += step.value 57 | } 58 | } 59 | 60 | return Success(ListValue(elements.toMutableList())) 61 | } 62 | 63 | @Identifier("float_range") 64 | fun floatRange(start: FloatValue, end: FloatValue, step: FloatValue): Result { 65 | val elements = mutableListOf() 66 | 67 | if (start.value > end.value) { 68 | var i = start.value 69 | 70 | while (i > end.value) { 71 | elements.add(FloatValue((i))) 72 | i -= step.value 73 | } 74 | } else { 75 | var i = start.value 76 | 77 | while (i < end.value) { 78 | elements.add(FloatValue((i))) 79 | i += step.value 80 | } 81 | } 82 | 83 | return Success(ListValue(elements.toMutableList())) 84 | } 85 | 86 | @Identifier("exit_process") 87 | fun exitProcess(status: Int) { 88 | StandardOverride.exit(status) 89 | } 90 | 91 | // utility functions 92 | @Excluded 93 | private fun format(input: String, formatted: List): String { 94 | var result = "" 95 | 96 | var formatIndex = 0 97 | 98 | input.forEachIndexed { index, char -> 99 | if (char == '%') { 100 | if (formatIndex <= formatted.lastIndex) { 101 | result += formatted[formatIndex].toString() 102 | formatIndex++ 103 | } else { 104 | result += '%' 105 | } 106 | } else { 107 | result += char 108 | } 109 | } 110 | 111 | return result 112 | } 113 | 114 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/builtin/System.kt: -------------------------------------------------------------------------------- 1 | package spritz.builtin 2 | 3 | import spritz.api.annotations.Identifier 4 | import java.lang.System 5 | 6 | /** 7 | * @author surge 8 | * @since 26/04/2023 9 | */ 10 | object System { 11 | 12 | @Identifier("current_time_millis") 13 | fun currentTimeMillis(): Long = System.currentTimeMillis() 14 | 15 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/builtin/companions/BooleanCompanion.kt: -------------------------------------------------------------------------------- 1 | package spritz.builtin.companions 2 | 3 | import spritz.value.bool.BooleanValue 4 | 5 | /** 6 | * @author surge 7 | * @since 26/03/2023 8 | */ 9 | class BooleanCompanion(value: BooleanValue) : Companion(value) { 10 | 11 | fun binary() = if ((value as BooleanValue).value) 1 else 0 12 | 13 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/builtin/companions/ClassCompanion.kt: -------------------------------------------------------------------------------- 1 | package spritz.builtin.companions 2 | 3 | import spritz.value.`class`.ClassValue 4 | 5 | /** 6 | * @author surge 7 | * @since 26/03/2023 8 | */ 9 | class ClassCompanion(value: ClassValue) : Companion(value) { 10 | 11 | @JvmField 12 | val name = value.identifier 13 | 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/builtin/companions/Companion.kt: -------------------------------------------------------------------------------- 1 | package spritz.builtin.companions 2 | 3 | import spritz.api.annotations.Excluded 4 | import spritz.value.Value 5 | 6 | /** 7 | * This class is intended to serve as the backbone for every value, an includes any methods that may be needed for every value. 8 | * 9 | * @author surge 10 | * @since 26/03/2023 11 | */ 12 | open class Companion(@Excluded @JvmField val value: Value) -------------------------------------------------------------------------------- /src/main/kotlin/spritz/builtin/companions/DictionaryCompanion.kt: -------------------------------------------------------------------------------- 1 | package spritz.builtin.companions 2 | 3 | import spritz.api.CallData 4 | import spritz.api.annotations.Excluded 5 | import spritz.api.result.Failure 6 | import spritz.api.result.Result 7 | import spritz.api.result.Success 8 | import spritz.error.interpreting.MemberNotFoundError 9 | import spritz.util.success 10 | import spritz.value.NullValue 11 | import spritz.value.Value 12 | import spritz.value.dictionary.DictionaryValue 13 | import spritz.value.string.StringValue 14 | 15 | /** 16 | * @author surge 17 | * @since 20/03/2023 18 | */ 19 | class DictionaryCompanion(value: DictionaryValue) : Companion(value) { 20 | 21 | fun set(key: StringValue, value: Value) { 22 | (this.value as DictionaryValue).elements[key.value] = value 23 | } 24 | 25 | fun get(data: CallData, key: StringValue): Result { 26 | return (this.value as DictionaryValue).elements[key.value]?.success() ?: Failure(MemberNotFoundError( 27 | "'${key.value}' was not found", 28 | data.start, 29 | data.end, 30 | data.context 31 | )) 32 | } 33 | 34 | fun remove(data: CallData, key: StringValue): Result { 35 | return (this.value as DictionaryValue).elements.remove(key.value)?.success() ?: Failure(MemberNotFoundError( 36 | "'${key.value}' was not found", 37 | data.start, 38 | data.end, 39 | data.context 40 | )) 41 | } 42 | 43 | fun length(): Int { 44 | return (this.value as DictionaryValue).elements.size 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/builtin/companions/InstanceCompanion.kt: -------------------------------------------------------------------------------- 1 | package spritz.builtin.companions 2 | 3 | import spritz.api.annotations.Identifier 4 | import spritz.value.NullValue 5 | import spritz.value.Value 6 | import spritz.value.`class`.DefinedInstanceValue 7 | import spritz.value.`class`.InstanceValue 8 | 9 | /** 10 | * @author surge 11 | * @since 26/03/2023 12 | */ 13 | class InstanceCompanion(value: InstanceValue) : Companion(value) { 14 | 15 | @Identifier("get_parent") 16 | fun getParent(): Value { 17 | return if (value is DefinedInstanceValue) { 18 | value.parent 19 | } else { 20 | NullValue() 21 | } 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/builtin/companions/ListCompanion.kt: -------------------------------------------------------------------------------- 1 | package spritz.builtin.companions 2 | 3 | import spritz.api.annotations.Identifier 4 | import spritz.value.Value 5 | import spritz.value.list.ListValue 6 | 7 | /** 8 | * @author surge 9 | * @since 20/03/2023 10 | */ 11 | class ListCompanion(value: ListValue) : Companion(value) { 12 | 13 | fun add(value: Value) { 14 | (this.value as ListValue).elements.add(value) 15 | } 16 | 17 | fun get(index: Int): Value { 18 | return (this.value as ListValue).elements[index] 19 | } 20 | 21 | fun remove(value: Value): Boolean { 22 | return (this.value as ListValue).elements.remove(value) 23 | } 24 | 25 | fun removeAt(index: Int): Value { 26 | return (this.value as ListValue).elements.removeAt(index) 27 | } 28 | 29 | fun length(): Int { 30 | return (this.value as ListValue).elements.size 31 | } 32 | 33 | fun after(index: Int): List { 34 | val elements = mutableListOf() 35 | 36 | for (i in index until (this.value as ListValue).elements.size) { 37 | elements.add(this.value.elements[i]) 38 | } 39 | 40 | return elements 41 | } 42 | 43 | @Identifier("is_empty") 44 | fun isEmpty(): Boolean { 45 | return (this.value as ListValue).elements.isEmpty() 46 | } 47 | 48 | fun join(separator: String, format: String): String { 49 | var concat = "" 50 | 51 | (this.value as ListValue).elements.forEachIndexed { index, value -> 52 | concat += format.replace("%", value.toString()) + if (index != this.value.elements.lastIndex) separator else "" 53 | } 54 | 55 | return concat 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/builtin/companions/NumberCompanion.kt: -------------------------------------------------------------------------------- 1 | package spritz.builtin.companions 2 | 3 | import spritz.value.bool.BooleanValue 4 | import spritz.value.number.NumberValue 5 | 6 | /** 7 | * @author surge 8 | * @since 26/03/2023 9 | */ 10 | class NumberCompanion(value: NumberValue<*>) : Companion(value) -------------------------------------------------------------------------------- /src/main/kotlin/spritz/builtin/companions/StringCompanion.kt: -------------------------------------------------------------------------------- 1 | package spritz.builtin.companions 2 | 3 | import spritz.api.annotations.Identifier 4 | import spritz.value.string.StringValue 5 | 6 | /** 7 | * @author surge 8 | * @since 20/03/2023 9 | */ 10 | class StringCompanion(value: StringValue) : Companion(value) { 11 | 12 | fun length(): Int { 13 | return (this.value as StringValue).value.length 14 | } 15 | 16 | @Identifier("is_empty") 17 | fun isEmpty(): Boolean { 18 | return (this.value as StringValue).value.isEmpty() 19 | } 20 | 21 | @Identifier("char_at") 22 | fun charAt(index: Int): String { 23 | return (this.value as StringValue).value[index].toString() 24 | } 25 | 26 | fun replace(old: String, new: String): String { 27 | return (this.value as StringValue).value.replace(old, new) 28 | } 29 | 30 | fun upper(): String { 31 | return (this.value as StringValue).value.uppercase() 32 | } 33 | 34 | fun lower(): String { 35 | return (this.value as StringValue).value.lowercase() 36 | } 37 | 38 | @Identifier("to_char_list") 39 | fun toCharList(): List { 40 | return (this.value as StringValue).value.map { it.toString() } 41 | } 42 | 43 | fun split(delimiter: String): List { 44 | return (this.value as StringValue).value.split(delimiter) 45 | } 46 | 47 | fun after(index: Int): String { 48 | var result = "" 49 | 50 | for (i in index until (this.value as StringValue).value.length) { 51 | result += this.value.value[i] 52 | } 53 | 54 | return result 55 | } 56 | 57 | fun int(): Int { 58 | return (this.value as StringValue).value.toInt() 59 | } 60 | 61 | fun float(): Float { 62 | return (this.value as StringValue).value.toFloat() 63 | } 64 | 65 | fun byte(): Byte { 66 | return (this.value as StringValue).value.toByte() 67 | } 68 | 69 | fun boolean(): Boolean { 70 | return (this.value as StringValue).value.toBooleanStrict() 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/error/Error.kt: -------------------------------------------------------------------------------- 1 | package spritz.error 2 | 3 | import spritz.api.annotations.Excluded 4 | import spritz.lexer.position.LinkPosition 5 | import spritz.lexer.position.Position 6 | import spritz.util.times 7 | import java.lang.Integer.max 8 | 9 | /** 10 | * @author surge 11 | * @since 25/02/2023 12 | */ 13 | open class Error(val name: String, val details: String, val start: Position, val end: Position) { 14 | 15 | override fun toString(): String { 16 | var result = "$name, line ${start.line + 1}: $details\n\n" 17 | 18 | var endIndex = this.start.contents.indexOf('\n', this.start.index - this.start.column) 19 | 20 | if (endIndex == -1) { 21 | endIndex = this.start.contents.length 22 | } 23 | 24 | val raw = this.start.contents.substring(this.start.index - this.start.column until endIndex) 25 | val line = raw.trim() 26 | 27 | result += line.replace("\r", "") 28 | 29 | if (result.last() !in System.lineSeparator()) { 30 | result += System.lineSeparator() 31 | } 32 | 33 | if (start !is LinkPosition && end !is LinkPosition && end.column - start.column >= 0) { 34 | result += " ".repeat(start.column - countLeadingWhitespace(raw)) 35 | result += "^" 36 | } 37 | 38 | return result 39 | } 40 | 41 | @Excluded 42 | private fun countLeadingWhitespace(str: String): Int { 43 | var count = 0 44 | 45 | for (i in str.indices) { 46 | if (!str[i].isWhitespace()) { 47 | break 48 | } 49 | 50 | count++ 51 | } 52 | 53 | return count 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/error/interpreting/CallArgumentMismatchError.kt: -------------------------------------------------------------------------------- 1 | package spritz.error.interpreting 2 | 3 | import spritz.interpreter.context.Context 4 | import spritz.lexer.position.Position 5 | 6 | /** 7 | * @author surge 8 | * @since 02/03/2023 9 | */ 10 | class CallArgumentMismatchError(details: String, start: Position, end: Position, context: Context) : RuntimeError("CallArgumentMismatchError", details, start, end, context) -------------------------------------------------------------------------------- /src/main/kotlin/spritz/error/interpreting/DualDeclarationError.kt: -------------------------------------------------------------------------------- 1 | package spritz.error.interpreting 2 | 3 | import spritz.interpreter.context.Context 4 | import spritz.lexer.position.Position 5 | 6 | /** 7 | * @author surge 8 | * @since 02/03/2023 9 | */ 10 | class DualDeclarationError(details: String, start: Position, end: Position, context: Context) : RuntimeError("DualDeclarationError", details, start, end, context) -------------------------------------------------------------------------------- /src/main/kotlin/spritz/error/interpreting/ExternalNotFoundError.kt: -------------------------------------------------------------------------------- 1 | package spritz.error.interpreting 2 | 3 | import spritz.interpreter.context.Context 4 | import spritz.lexer.position.Position 5 | 6 | /** 7 | * @author surge 8 | * @since 02/03/2023 9 | */ 10 | class ExternalNotFoundError(details: String, start: Position, end: Position, context: Context) : RuntimeError("ExternalNotFoundError", details, start, end, context) -------------------------------------------------------------------------------- /src/main/kotlin/spritz/error/interpreting/IllegalOperationError.kt: -------------------------------------------------------------------------------- 1 | package spritz.error.interpreting 2 | 3 | import spritz.interpreter.context.Context 4 | import spritz.lexer.position.Position 5 | 6 | /** 7 | * @author surge 8 | * @since 02/03/2023 9 | */ 10 | class IllegalOperationError(details: String, start: Position, end: Position, context: Context) : RuntimeError("IllegalOperationError", details, start, end, context) -------------------------------------------------------------------------------- /src/main/kotlin/spritz/error/interpreting/ImportError.kt: -------------------------------------------------------------------------------- 1 | package spritz.error.interpreting 2 | 3 | import spritz.interpreter.context.Context 4 | import spritz.lexer.position.Position 5 | 6 | /** 7 | * @author surge 8 | * @since 02/03/2023 9 | */ 10 | class ImportError(details: String, start: Position, end: Position, context: Context) : RuntimeError("ImportError", details, start, end, context) -------------------------------------------------------------------------------- /src/main/kotlin/spritz/error/interpreting/JvmError.kt: -------------------------------------------------------------------------------- 1 | package spritz.error.interpreting 2 | 3 | import spritz.interpreter.context.Context 4 | import spritz.lexer.position.Position 5 | 6 | /** 7 | * @author surge 8 | * @since 02/03/2023 9 | */ 10 | class JvmError(details: String, start: Position, end: Position, context: Context) : RuntimeError("JvmError", details, start, end, context) -------------------------------------------------------------------------------- /src/main/kotlin/spritz/error/interpreting/MathsError.kt: -------------------------------------------------------------------------------- 1 | package spritz.error.interpreting 2 | 3 | import spritz.interpreter.context.Context 4 | import spritz.lexer.position.Position 5 | 6 | /** 7 | * @author surge 8 | * @since 02/03/2023 9 | */ 10 | class MathsError(details: String, start: Position, end: Position, context: Context) : RuntimeError("MathsError", details, start, end, context) -------------------------------------------------------------------------------- /src/main/kotlin/spritz/error/interpreting/MemberNotFoundError.kt: -------------------------------------------------------------------------------- 1 | package spritz.error.interpreting 2 | 3 | import spritz.interpreter.context.Context 4 | import spritz.lexer.position.Position 5 | 6 | /** 7 | * @author surge 8 | * @since 02/03/2023 9 | */ 10 | class MemberNotFoundError(details: String, start: Position, end: Position, context: Context) : RuntimeError("MemberNotFoundError", details, start, end, context) -------------------------------------------------------------------------------- /src/main/kotlin/spritz/error/interpreting/MutabilityAssignationError.kt: -------------------------------------------------------------------------------- 1 | package spritz.error.interpreting 2 | 3 | import spritz.interpreter.context.Context 4 | import spritz.lexer.position.Position 5 | 6 | /** 7 | * @author surge 8 | * @since 02/03/2023 9 | */ 10 | class MutabilityAssignationError(details: String, start: Position, end: Position, context: Context) : RuntimeError("MutabilityAssignationError", details, start, end, context) -------------------------------------------------------------------------------- /src/main/kotlin/spritz/error/interpreting/NodeIntepreterNotFoundError.kt: -------------------------------------------------------------------------------- 1 | package spritz.error.interpreting 2 | 3 | import spritz.interpreter.context.Context 4 | import spritz.lexer.position.Position 5 | 6 | /** 7 | * @author surge 8 | * @since 02/03/2023 9 | */ 10 | class NodeIntepreterNotFoundError(details: String, start: Position, end: Position, context: Context) : RuntimeError("NodeInterpreterNotFoundError", details, start, end, context) -------------------------------------------------------------------------------- /src/main/kotlin/spritz/error/interpreting/ReturnTypeMismatchError.kt: -------------------------------------------------------------------------------- 1 | package spritz.error.interpreting 2 | 3 | import spritz.interpreter.context.Context 4 | import spritz.lexer.position.Position 5 | 6 | /** 7 | * @author surge 8 | * @since 02/03/2023 9 | */ 10 | class ReturnTypeMismatchError(details: String, start: Position, end: Position, context: Context) : RuntimeError("ReturnTypeMismatchError", details, start, end, context) -------------------------------------------------------------------------------- /src/main/kotlin/spritz/error/interpreting/RuntimeError.kt: -------------------------------------------------------------------------------- 1 | package spritz.error.interpreting 2 | 3 | import spritz.error.Error 4 | import spritz.interpreter.context.Context 5 | import spritz.lexer.position.Position 6 | 7 | /** 8 | * @author surge 9 | * @since 01/03/2023 10 | */ 11 | open class RuntimeError(name: String, details: String, start: Position, end: Position, val context: Context) : Error(name, details, start, end) { 12 | 13 | private fun generateTraceback(): String { 14 | var result = "" 15 | var position: Position? = this.start 16 | var context: Context? = this.context 17 | 18 | while (context != null && position != null) { 19 | result = " File ${context.name}, line ${position.line + 1}\n$result" 20 | position = context.parentEntryPosition 21 | context = context.parent 22 | } 23 | 24 | return "Traceback (most recent call last):\n$result" 25 | } 26 | 27 | override fun toString(): String { 28 | return super.toString() + "\n" + generateTraceback() 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/error/interpreting/TypeMismatchError.kt: -------------------------------------------------------------------------------- 1 | package spritz.error.interpreting 2 | 3 | import spritz.interpreter.context.Context 4 | import spritz.lexer.position.Position 5 | 6 | /** 7 | * @author surge 8 | * @since 02/03/2023 9 | */ 10 | class TypeMismatchError(details: String, start: Position, end: Position, context: Context) : RuntimeError("TypeMismatchError", details, start, end, context) -------------------------------------------------------------------------------- /src/main/kotlin/spritz/error/interpreting/UndefinedReferenceError.kt: -------------------------------------------------------------------------------- 1 | package spritz.error.interpreting 2 | 3 | import spritz.interpreter.context.Context 4 | import spritz.lexer.position.Position 5 | 6 | /** 7 | * @author surge 8 | * @since 02/03/2023 9 | */ 10 | class UndefinedReferenceError(details: String, start: Position, end: Position, context: Context) : RuntimeError("UndefinedReferenceError", details, start, end, context) -------------------------------------------------------------------------------- /src/main/kotlin/spritz/error/lexing/LexingError.kt: -------------------------------------------------------------------------------- 1 | package spritz.error.lexing 2 | 3 | import spritz.error.Error 4 | import spritz.lexer.position.Position 5 | 6 | /** 7 | * @author surge 8 | * @since 25/02/2023 9 | */ 10 | class LexingError(details: String, start: Position, end: Position) : Error("LexingError", details, start, end) -------------------------------------------------------------------------------- /src/main/kotlin/spritz/error/parsing/ParsingError.kt: -------------------------------------------------------------------------------- 1 | package spritz.error.parsing 2 | 3 | import spritz.error.Error 4 | import spritz.lexer.position.Position 5 | 6 | /** 7 | * @author surge 8 | * @since 25/02/2023 9 | */ 10 | class ParsingError(details: String, start: Position, end: Position) : Error("ParsingError", details, start, end) -------------------------------------------------------------------------------- /src/main/kotlin/spritz/interfaces/Cloneable.kt: -------------------------------------------------------------------------------- 1 | package spritz.interfaces 2 | 3 | /** 4 | * @author surge 5 | * @since 25/02/2023 6 | */ 7 | interface Cloneable { 8 | 9 | fun clone(): Cloneable 10 | 11 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/interpreter/RuntimeResult.kt: -------------------------------------------------------------------------------- 1 | package spritz.interpreter 2 | 3 | import spritz.value.Value 4 | import spritz.error.Error 5 | 6 | /** 7 | * @author surge 8 | * @since 01/03/2023 9 | */ 10 | class RuntimeResult { 11 | 12 | var value: Value? = null 13 | var error: Error? = null 14 | var returnValue: Value? = null 15 | var shouldContinue = false 16 | var shouldBreak = false 17 | 18 | fun reset() { 19 | this.value = null 20 | this.error = null 21 | this.returnValue = null 22 | this.shouldContinue = false 23 | this.shouldBreak = false 24 | } 25 | 26 | fun register(result: RuntimeResult): Value? { 27 | this.error = result.error 28 | this.returnValue = result.returnValue 29 | this.shouldContinue = result.shouldContinue 30 | this.shouldBreak = result.shouldBreak 31 | 32 | return result.value 33 | } 34 | 35 | fun success(value: Value?): RuntimeResult { 36 | this.reset() 37 | this.value = value 38 | return this 39 | } 40 | 41 | fun successReturn(value: Value?): RuntimeResult { 42 | this.reset() 43 | this.returnValue = value 44 | return this 45 | } 46 | 47 | fun successContinue(): RuntimeResult { 48 | this.reset() 49 | this.shouldContinue = true 50 | return this 51 | } 52 | 53 | fun successBreak(): RuntimeResult { 54 | this.reset() 55 | this.shouldBreak = true 56 | return this 57 | } 58 | 59 | fun failure(error: Error): RuntimeResult { 60 | this.reset() 61 | this.error = error 62 | return this 63 | } 64 | 65 | fun shouldReturn(): Boolean = this.error != null || this.returnValue != null || this.shouldContinue || this.shouldBreak 66 | 67 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/interpreter/context/Context.kt: -------------------------------------------------------------------------------- 1 | package spritz.interpreter.context 2 | 3 | import spritz.SpritzEnvironment 4 | import spritz.api.Config 5 | import spritz.error.interpreting.ImportError 6 | import spritz.interpreter.RuntimeResult 7 | import spritz.lexer.position.Position 8 | import spritz.value.Value 9 | import spritz.value.`class`.DefinedClassValue 10 | import spritz.value.`class`.DefinedInstanceValue 11 | import spritz.value.`class`.InstanceValue 12 | import spritz.value.table.Table 13 | import java.io.File 14 | import java.nio.charset.Charset 15 | 16 | /** 17 | * @author surge 18 | * @since 01/03/2023 19 | */ 20 | data class Context(var name: String, val parent: Context? = null, val parentEntryPosition: Position? = null, val config: Config? = null) { 21 | 22 | lateinit var table: Table 23 | lateinit var environment: SpritzEnvironment 24 | 25 | private val imports = hashMapOf() 26 | 27 | fun givenTable(table: Table): Context { 28 | this.table = table 29 | return this 30 | } 31 | 32 | fun getOrigin(): Context { 33 | if (this.parent == null) { 34 | return this 35 | } 36 | 37 | return this.parent.getOrigin() 38 | } 39 | 40 | /** 41 | * This should ONLY be called from the origin 42 | */ 43 | fun getImport(identifier: String, start: Position, end: Position, context: Context): RuntimeResult { 44 | val result = RuntimeResult() 45 | 46 | var import = imports[identifier] 47 | 48 | if (import == null && identifier.endsWith(".sz")) { 49 | val file = File(identifier) 50 | 51 | if (file.exists()) { 52 | val environment = SpritzEnvironment(environment.config) 53 | 54 | val res = environment.evaluate(file.name, file.readText(Charset.defaultCharset())) 55 | 56 | if (res.error != null) { 57 | return result.failure(res.error) 58 | } 59 | 60 | import = DefinedInstanceValue(DefinedClassValue(identifier, arrayListOf(), null), environment.global) 61 | } 62 | } 63 | 64 | if (import == null) { 65 | return result.failure( 66 | ImportError( 67 | "Couldn't locate '$identifier'", 68 | start, 69 | end, 70 | context 71 | )) 72 | } 73 | 74 | return result.success(import) 75 | } 76 | 77 | fun putImport(identifier: String, value: Value) { 78 | imports[identifier] = value 79 | } 80 | 81 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/lexer/Lexer.kt: -------------------------------------------------------------------------------- 1 | package spritz.lexer 2 | 3 | import spritz.error.Error 4 | import spritz.error.lexing.LexingError 5 | import spritz.lexer.position.Position 6 | import spritz.lexer.token.Token 7 | import spritz.lexer.token.TokenType.* 8 | import spritz.util.keyword 9 | 10 | /** 11 | * @author surge 12 | * @since 25/02/2023 13 | */ 14 | class Lexer(val name: String, val contents: String) { 15 | 16 | private val position: Position = Position(name, contents, -1, 0, -1) 17 | private var currentChar: Char? = null 18 | 19 | private val digits = "0123456789" 20 | private val identifierLetters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$digits" 21 | 22 | init { 23 | advance() 24 | } 25 | 26 | /** 27 | * Generates and returns a list of [Token]s and an [Error], if the lexing failed. 28 | */ 29 | fun lex(): Pair>, Error?> { 30 | val tokens = mutableListOf>() 31 | 32 | while (this.currentChar != null) { 33 | when (this.currentChar!!) { 34 | // skip spaces and tabs 35 | in " \t;${System.lineSeparator()}" -> this.advance() 36 | 37 | in digits -> { 38 | var result = "" 39 | var dots = 0 40 | var type = INT 41 | val start = this.position.clone() 42 | 43 | while (this.currentChar != null && this.currentChar!! in "$digits.") { 44 | result += if (this.currentChar == '.') { 45 | if (dots == 1) { 46 | break 47 | } 48 | 49 | dots++ 50 | type = FLOAT 51 | 52 | '.' 53 | } else { 54 | this.currentChar 55 | } 56 | 57 | this.advance() 58 | } 59 | 60 | if (this.currentChar == 'f' || this.currentChar == 'F') { 61 | type = FLOAT 62 | 63 | if (dots == 0) { 64 | result += ".0" 65 | } 66 | 67 | this.advance() 68 | } 69 | 70 | if (dots == 1) { 71 | type = FLOAT 72 | } 73 | 74 | if (this.currentChar == 'b' || this.currentChar == 'B') { 75 | this.advance() 76 | 77 | if (dots > 0) { 78 | return Pair(mutableListOf(), LexingError("The floating point literal does not conform to type 'byte'", start, this.position.clone())) 79 | } 80 | 81 | type = BYTE 82 | } 83 | 84 | if (this.currentChar == 'l' || this.currentChar == 'L') { 85 | this.advance() 86 | 87 | if (dots > 0) { 88 | return Pair(mutableListOf(), LexingError("The floating point literal does not conform to type 'long'", start, this.position.clone())) 89 | } 90 | 91 | type = LONG 92 | } 93 | 94 | tokens.add(Token(type, result, start, this.position.clone())) 95 | } 96 | 97 | in identifierLetters -> { 98 | var identifier = "" 99 | val start = this.position.clone() 100 | 101 | while (this.currentChar != null && this.currentChar!! in identifierLetters) { 102 | identifier += this.currentChar 103 | this.advance() 104 | } 105 | 106 | tokens.add(Token(if (keyword(identifier)) KEYWORD else IDENTIFIER, identifier, start, this.position.clone())) 107 | } 108 | 109 | '+' -> { 110 | var type = PLUS 111 | val start = this.position.clone() 112 | 113 | this.advance() 114 | 115 | if (this.currentChar == '+') { 116 | type = INCREMENT 117 | this.advance() 118 | } else if (this.currentChar == '=') { 119 | type = INCREASE_BY 120 | this.advance() 121 | } 122 | 123 | tokens.add(Token(type, null, start, this.position.clone())) 124 | } 125 | 126 | '-' -> { 127 | var type = MINUS 128 | val start = this.position.clone() 129 | 130 | this.advance() 131 | 132 | when (this.currentChar) { 133 | '-' -> { 134 | type = DEINCREMENT 135 | this.advance() 136 | } 137 | 138 | '=' -> { 139 | type = DECREASE_BY 140 | this.advance() 141 | } 142 | 143 | '>' -> { 144 | type = ARROW 145 | this.advance() 146 | } 147 | } 148 | 149 | tokens.add(Token(type, null, start, this.position.clone())) 150 | } 151 | 152 | '*' -> { 153 | var type = ASTERISK 154 | val start = this.position.clone() 155 | 156 | this.advance() 157 | 158 | if (this.currentChar == '=') { 159 | type = MULTIPLY_BY 160 | this.advance() 161 | } 162 | 163 | tokens.add(Token(type, null, start, this.position.clone())) 164 | } 165 | 166 | '/' -> { 167 | var type = DIVIDE 168 | val start = this.position.clone() 169 | 170 | this.advance() 171 | 172 | if (this.currentChar == '*') { 173 | this.advance() 174 | 175 | while (this.currentChar != null) { 176 | if (this.currentChar == '*') { 177 | this.advance() 178 | 179 | if (this.currentChar == '/') { 180 | this.advance() 181 | break 182 | } 183 | } else { 184 | this.advance() 185 | } 186 | } 187 | 188 | } else if (this.currentChar == '/') { 189 | while (this.currentChar != null && this.currentChar!! !in System.lineSeparator()) { 190 | this.advance() 191 | } 192 | } else { 193 | if (this.currentChar == '=') { 194 | type = DIVIDE_BY 195 | this.advance() 196 | } 197 | 198 | tokens.add(Token(type, null, start, this.position.clone())) 199 | } 200 | } 201 | 202 | '%' -> { 203 | var type = MODULO 204 | val start = this.position.clone() 205 | 206 | this.advance() 207 | 208 | if (this.currentChar == '=') { 209 | type = MODULO_BY 210 | this.advance() 211 | } 212 | 213 | tokens.add(Token(type, null, start, this.position.clone())) 214 | this.advance() 215 | } 216 | 217 | '{' -> { 218 | tokens.add(Token(OPEN_BRACE, null, this.position.clone())) 219 | this.advance() 220 | } 221 | 222 | '}' -> { 223 | tokens.add(Token(CLOSE_BRACE, null, this.position.clone())) 224 | this.advance() 225 | } 226 | 227 | '(' -> { 228 | tokens.add(Token(OPEN_PARENTHESES, null, this.position.clone())) 229 | this.advance() 230 | } 231 | 232 | ')' -> { 233 | tokens.add(Token(CLOSE_PARENTHESES, null, this.position.clone())) 234 | this.advance() 235 | } 236 | 237 | '[' -> { 238 | tokens.add(Token(OPEN_SQUARE, null, this.position.clone())) 239 | this.advance() 240 | } 241 | 242 | ']' -> { 243 | tokens.add(Token(CLOSE_SQUARE, null, this.position.clone())) 244 | this.advance() 245 | } 246 | 247 | ':' -> { 248 | var type = COLON 249 | val start = this.position.clone() 250 | 251 | this.advance() 252 | 253 | if (this.currentChar == ':') { 254 | type = ACCESSOR 255 | this.advance() 256 | } 257 | 258 | tokens.add(Token(type, null, start, this.position.clone())) 259 | } 260 | 261 | ',' -> { 262 | tokens.add(Token(COMMA, null, this.position.clone())) 263 | this.advance() 264 | } 265 | 266 | '"' -> { 267 | var result = "" 268 | val start = this.position.clone() 269 | 270 | var escape = false 271 | this.advance() 272 | 273 | val escapeCharacters = hashMapOf( 274 | Pair('n', '\n'), 275 | Pair('t', '\t') 276 | ) 277 | 278 | while (this.currentChar != null && (this.currentChar != '"' || escape)) { 279 | if (escape) { 280 | result += escapeCharacters[this.currentChar] 281 | } else { 282 | if (this.currentChar == '\\') { 283 | escape = true 284 | } else { 285 | result += this.currentChar 286 | escape = false 287 | } 288 | } 289 | 290 | this.advance() 291 | } 292 | 293 | tokens.add(Token(STRING, result, start, this.position.clone())) 294 | this.advance() 295 | } 296 | 297 | '<' -> { 298 | var type = ARROW_LEFT 299 | val start = this.position.clone() 300 | 301 | this.advance() 302 | 303 | if (this.currentChar == '=') { 304 | type = LESS_THAN_OR_EQUAL_TO 305 | this.advance() 306 | } else if (this.currentChar == '<') { 307 | type = BIN_SHIFT_LEFT 308 | this.advance() 309 | } 310 | 311 | tokens.add(Token(type, null, start, this.position.clone())) 312 | } 313 | 314 | '>' -> { 315 | var type = ARROW_RIGHT 316 | val start = this.position.clone() 317 | 318 | this.advance() 319 | 320 | if (this.currentChar == '=') { 321 | type = GREATER_THAN_OR_EQUAL_TO 322 | this.advance() 323 | } else if (this.currentChar == '>') { 324 | type = BIN_SHIFT_RIGHT 325 | this.advance() 326 | 327 | if (this.currentChar == '>') { 328 | type = BIN_UNSIGNED_SHIFT_RIGHT 329 | this.advance() 330 | } 331 | } 332 | 333 | tokens.add(Token(type, null, start, this.position.clone())) 334 | } 335 | 336 | '=' -> { 337 | var type = ASSIGNMENT 338 | val start = this.position.clone() 339 | 340 | this.advance() 341 | 342 | if (this.currentChar == '=') { 343 | type = EQUALITY 344 | this.advance() 345 | } 346 | 347 | tokens.add(Token(type, null, start, this.position.clone())) 348 | } 349 | 350 | '!' -> { 351 | var type = NEGATE 352 | val start = this.position.clone() 353 | 354 | this.advance() 355 | 356 | if (this.currentChar == '=') { 357 | type = INEQUALITY 358 | this.advance() 359 | } 360 | 361 | tokens.add(Token(type, null, start, this.position.clone())) 362 | } 363 | 364 | '&' -> { 365 | var type = BIN_AND 366 | val start = this.position.clone() 367 | 368 | this.advance() 369 | 370 | if (this.currentChar == '&') { 371 | type = AND 372 | this.advance() 373 | } 374 | 375 | tokens.add(Token(type, null, start, this.position.clone())) 376 | } 377 | 378 | '|' -> { 379 | var type = BIN_OR 380 | val start = this.position.clone() 381 | 382 | this.advance() 383 | 384 | if (this.currentChar == '|') { 385 | type = OR 386 | this.advance() 387 | } 388 | 389 | tokens.add(Token(type, null, start, this.position.clone())) 390 | } 391 | 392 | '^' -> { 393 | tokens.add(Token(BIN_XOR, null, this.position.clone())) 394 | this.advance() 395 | } 396 | 397 | '~' -> { 398 | var type = BIN_COMPLEMENT 399 | val start = this.position.clone() 400 | 401 | this.advance() 402 | 403 | if (this.currentChar == '!') { 404 | this.advance() 405 | 406 | if (this.currentChar == '=') { 407 | type = ROUGH_INEQUALITY 408 | this.advance() 409 | } 410 | } else if (this.currentChar == '=') { 411 | type = ROUGH_EQUALITY 412 | this.advance() 413 | } 414 | 415 | tokens.add(Token(type, null, start, this.position.clone())) 416 | } 417 | 418 | '?' -> { 419 | tokens.add(Token(SAFE_CALL, null, this.position.clone())) 420 | this.advance() 421 | } 422 | 423 | else -> { 424 | val start = this.position.clone() 425 | val char = this.currentChar!! 426 | 427 | this.advance() 428 | 429 | return Pair(mutableListOf(), LexingError("Illegal Character: '$char'", start, this.position.clone())) 430 | } 431 | } 432 | } 433 | 434 | tokens.add(Token(END_OF_FILE, null, start = this.position.clone())) 435 | return Pair(tokens, null) 436 | } 437 | 438 | private fun advance(amount: Int = 1) { 439 | this.position.advance(currentChar, amount) 440 | this.currentChar = if (this.position.index < this.contents.length) this.contents[this.position.index] else null 441 | } 442 | 443 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/lexer/position/LinkPosition.kt: -------------------------------------------------------------------------------- 1 | package spritz.lexer.position 2 | 3 | /** 4 | * @author surge 5 | * @since 03/03/2023 6 | */ 7 | class LinkPosition(name: String, contents: String) : Position(name, contents, 0, 0, 0) { 8 | 9 | constructor() : this("", "") 10 | 11 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/lexer/position/Position.kt: -------------------------------------------------------------------------------- 1 | package spritz.lexer.position 2 | 3 | import spritz.interfaces.Cloneable 4 | 5 | /** 6 | * @author surge 7 | * @since 25/02/2023 8 | */ 9 | open class Position(var name: String, var contents: String, var index: Int, var line: Int, var column: Int) : Cloneable { 10 | 11 | fun advance(character: Char? = null, amount: Int = 1): Position { 12 | this.index += amount 13 | this.column += amount 14 | 15 | if (character == '\n') { 16 | this.line++ 17 | this.column = 0 18 | } 19 | 20 | return this 21 | } 22 | 23 | override fun clone(): Position { 24 | return Position(name, contents, index, line, column) 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/kotlin/spritz/lexer/token/Token.kt: -------------------------------------------------------------------------------- 1 | package spritz.lexer.token 2 | 3 | import spritz.lexer.position.Position 4 | 5 | /** 6 | * @author surge 7 | * @since 25/02/2023 8 | */ 9 | class Token(val type: TokenType, val value: T?, start: Position, end: Position? = null) { 10 | 11 | val start = start.clone() 12 | val end = end ?: start.clone().advance() 13 | 14 | fun matches(value: Any?, type: TokenType = TokenType.KEYWORD): Boolean = this.type == type && this.value == value 15 | 16 | override fun toString(): String { 17 | return "(${type.name.lowercase()}${if (value != null) ", '$value'" else ""})" 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/lexer/token/TokenType.kt: -------------------------------------------------------------------------------- 1 | package spritz.lexer.token 2 | 3 | /** 4 | * @author surge 5 | * @since 25/02/2023 6 | */ 7 | enum class TokenType { 8 | // operators 9 | PLUS, 10 | MINUS, 11 | ASTERISK, 12 | DIVIDE, 13 | MODULO, 14 | 15 | // modifiers 16 | ASSIGNMENT, 17 | INCREMENT, 18 | DEINCREMENT, 19 | INCREASE_BY, 20 | DECREASE_BY, 21 | MULTIPLY_BY, 22 | DIVIDE_BY, 23 | MODULO_BY, 24 | 25 | // binary shifts 26 | BIN_SHIFT_LEFT, 27 | BIN_SHIFT_RIGHT, 28 | BIN_UNSIGNED_SHIFT_RIGHT, 29 | BIN_OR, 30 | BIN_AND, 31 | BIN_XOR, 32 | BIN_COMPLEMENT, 33 | 34 | // comparison operators 35 | EQUALITY, 36 | INEQUALITY, 37 | ROUGH_EQUALITY, 38 | ROUGH_INEQUALITY, 39 | ARROW_LEFT, 40 | ARROW_RIGHT, 41 | LESS_THAN_OR_EQUAL_TO, 42 | GREATER_THAN_OR_EQUAL_TO, 43 | 44 | // condition operators 45 | AND, 46 | OR, 47 | NEGATE, 48 | 49 | // references 50 | KEYWORD, 51 | IDENTIFIER, 52 | 53 | // inbuilt types 54 | INT, 55 | LONG, 56 | FLOAT, 57 | BYTE, 58 | STRING, 59 | 60 | // braces 61 | OPEN_BRACE, 62 | CLOSE_BRACE, 63 | OPEN_PARENTHESES, 64 | CLOSE_PARENTHESES, 65 | OPEN_SQUARE, 66 | CLOSE_SQUARE, 67 | 68 | // other syntax 69 | COLON, 70 | COMMA, 71 | ACCESSOR, 72 | ARROW, 73 | SAFE_CALL, 74 | 75 | END_OF_FILE 76 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/parser/ParseResult.kt: -------------------------------------------------------------------------------- 1 | package spritz.parser 2 | 3 | import spritz.error.Error 4 | import spritz.parser.node.Node 5 | import spritz.warning.Warning 6 | 7 | /** 8 | * @author surge 9 | * @since 26/02/2023 10 | */ 11 | class ParseResult { 12 | 13 | var node: Node? = null 14 | var error: Error? = null 15 | 16 | var advancement = 0 17 | var reverse = 0 18 | var lastRegisteredAdvanceCount = 0 19 | 20 | val warnings = mutableListOf() 21 | 22 | fun register(result: ParseResult): Node? { 23 | this.lastRegisteredAdvanceCount = result.advancement 24 | this.advancement += result.advancement 25 | this.warnings.addAll(result.warnings) 26 | 27 | if (result.error != null) { 28 | this.error = result.error 29 | } 30 | 31 | return result.node 32 | } 33 | 34 | fun registerAdvancement() { 35 | this.advancement++ 36 | this.lastRegisteredAdvanceCount = 1 37 | } 38 | 39 | fun tryRegister(result: ParseResult): Any? { 40 | if (result.error != null) { 41 | this.reverse = result.reverse 42 | return null 43 | } 44 | 45 | return this.register(result) 46 | } 47 | 48 | fun success(node: Node): ParseResult { 49 | this.node = node 50 | return this 51 | } 52 | 53 | fun warn(warning: Warning): ParseResult { 54 | this.warnings.add(warning) 55 | return this 56 | } 57 | 58 | fun failure(error: Error): ParseResult { 59 | if (this.error == null || this.lastRegisteredAdvanceCount == 0) { 60 | this.error = error 61 | } 62 | 63 | return this 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/parser/node/Node.kt: -------------------------------------------------------------------------------- 1 | package spritz.parser.node 2 | 3 | import spritz.lexer.position.Position 4 | 5 | /** 6 | * @author surge 7 | * @since 26/02/2023 8 | */ 9 | abstract class Node(val start: Position, val end: Position) { 10 | 11 | var child: Node? = null 12 | var safe = false 13 | 14 | abstract override fun toString(): String 15 | 16 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/parser/nodes/AccessNode.kt: -------------------------------------------------------------------------------- 1 | package spritz.parser.nodes 2 | 3 | import spritz.lexer.token.Token 4 | import spritz.parser.node.Node 5 | import spritz.value.Value 6 | 7 | /** 8 | * @author surge 9 | * @since 01/03/2023 10 | */ 11 | class AccessNode(val identifier: Token<*>) : Node(identifier.start, identifier.end) { 12 | 13 | var predicate: (Value) -> Boolean = { true } 14 | 15 | fun setPredicate(predicate: (Value) -> Boolean): AccessNode { 16 | this.predicate = predicate 17 | return this 18 | } 19 | 20 | override fun toString() = "(Access: $identifier${if (safe) ", SAFE" else ""})::($child)" 21 | 22 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/parser/nodes/AssignmentNode.kt: -------------------------------------------------------------------------------- 1 | package spritz.parser.nodes 2 | 3 | import spritz.lexer.position.Position 4 | import spritz.lexer.token.Token 5 | import spritz.parser.node.Node 6 | 7 | /** 8 | * @author surge 9 | * @since 26/02/2023 10 | */ 11 | class AssignmentNode(val name: Token<*>, val value: Node, val modifier: Token<*>, val immutable: Boolean, val declaration: Boolean, val forced: Boolean, start: Position, end: Position) : Node(start, end) { 12 | 13 | override fun toString() = "(Assignment: ${name.value!!}, $value, $immutable, $forced)" 14 | 15 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/parser/nodes/BinaryOperationNode.kt: -------------------------------------------------------------------------------- 1 | package spritz.parser.nodes 2 | 3 | import spritz.lexer.position.Position 4 | import spritz.lexer.token.Token 5 | import spritz.parser.node.Node 6 | 7 | /** 8 | * @author surge 9 | * @since 26/02/2023 10 | */ 11 | class BinaryOperationNode(val left: Node, val operator: Token<*>, val right: Node) : Node(left.start, right.end) { 12 | 13 | override fun toString() = "(Binary Operation: $left, $operator, $right)" 14 | 15 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/parser/nodes/BreakNode.kt: -------------------------------------------------------------------------------- 1 | package spritz.parser.nodes 2 | 3 | import spritz.lexer.position.Position 4 | import spritz.parser.node.Node 5 | 6 | /** 7 | * @author surge 8 | * @since 28/02/2023 9 | */ 10 | class BreakNode(start: Position, end: Position) : Node(start, end) { 11 | 12 | override fun toString() = "(break)" 13 | 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/parser/nodes/CatchNode.kt: -------------------------------------------------------------------------------- 1 | package spritz.parser.nodes 2 | 3 | import spritz.lexer.position.Position 4 | import spritz.lexer.token.Token 5 | import spritz.parser.node.Node 6 | 7 | /** 8 | * @author surge 9 | * @since 07/03/2023 10 | */ 11 | class CatchNode(val exception: String, val body: Node, start: Position, end: Position) : Node(start, end) { 12 | 13 | override fun toString() = "(Catch: $exception, $body)" 14 | 15 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/parser/nodes/ClassDefineNode.kt: -------------------------------------------------------------------------------- 1 | package spritz.parser.nodes 2 | 3 | import spritz.lexer.position.Position 4 | import spritz.lexer.token.Token 5 | import spritz.parser.node.Node 6 | import spritz.util.Argument 7 | 8 | /** 9 | * @author surge 10 | * @since 04/03/2023 11 | */ 12 | class ClassDefineNode(val name: Token<*>, val constructor: List, val body: Node?, start: Position, end: Position) : Node(start, end) { 13 | 14 | override fun toString() = "(Class Define: $name, $constructor, $body)" 15 | 16 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/parser/nodes/ContinueNode.kt: -------------------------------------------------------------------------------- 1 | package spritz.parser.nodes 2 | 3 | import spritz.lexer.position.Position 4 | import spritz.parser.node.Node 5 | 6 | /** 7 | * @author surge 8 | * @since 28/02/2023 9 | */ 10 | class ContinueNode(start: Position, end: Position) : Node(start, end) { 11 | 12 | override fun toString() = "(continue)" 13 | 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/parser/nodes/DictionaryNode.kt: -------------------------------------------------------------------------------- 1 | package spritz.parser.nodes 2 | 3 | import spritz.lexer.position.Position 4 | import spritz.lexer.token.Token 5 | import spritz.parser.node.Node 6 | 7 | /** 8 | * @author surge 9 | * @since 26/02/2023 10 | */ 11 | class DictionaryNode(val elements: HashMap, start: Position, end: Position) : Node(start, end) { 12 | 13 | override fun toString(): String { 14 | var result = "(DictionaryNode: [" 15 | 16 | elements.forEach { 17 | result += "\n$it" 18 | } 19 | 20 | if (elements.isNotEmpty()) { 21 | result += '\n' 22 | } 23 | 24 | result += "])" 25 | return result 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/parser/nodes/EnumDefineNode.kt: -------------------------------------------------------------------------------- 1 | package spritz.parser.nodes 2 | 3 | import spritz.lexer.position.Position 4 | import spritz.lexer.token.Token 5 | import spritz.parser.node.Node 6 | import spritz.util.Argument 7 | 8 | /** 9 | * @author surge 10 | * @since 04/03/2023 11 | */ 12 | class EnumDefineNode(val name: Token<*>, val constructor: List, val members: LinkedHashMap, List>, val body: Node?, start: Position, end: Position) : Node(start, end) { 13 | 14 | override fun toString() = "(Enum Define: $name, $constructor, $members, $body)" 15 | 16 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/parser/nodes/ForNode.kt: -------------------------------------------------------------------------------- 1 | package spritz.parser.nodes 2 | 3 | import spritz.lexer.position.Position 4 | import spritz.lexer.token.Token 5 | import spritz.parser.node.Node 6 | 7 | /** 8 | * @author surge 9 | * @since 07/03/2023 10 | */ 11 | class ForNode(val identifier: Token<*>, val expression: Node, val body: Node, start: Position, end: Position) : Node(start, end) { 12 | 13 | override fun toString() = "(for, $identifier, $expression, $body)" 14 | 15 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/parser/nodes/ImportNode.kt: -------------------------------------------------------------------------------- 1 | package spritz.parser.nodes 2 | 3 | import spritz.lexer.position.Position 4 | import spritz.lexer.token.Token 5 | import spritz.parser.node.Node 6 | 7 | /** 8 | * @author surge 9 | * @since 26/03/2023 10 | */ 11 | class ImportNode(val name: Token<*>, val identifier: Token<*>, start: Position, end: Position) : Node(start, end) { 12 | 13 | override fun toString() = "(Import: $name, $identifier)" 14 | 15 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/parser/nodes/ListNode.kt: -------------------------------------------------------------------------------- 1 | package spritz.parser.nodes 2 | 3 | import spritz.lexer.position.Position 4 | import spritz.parser.node.Node 5 | 6 | /** 7 | * @author surge 8 | * @since 26/02/2023 9 | */ 10 | class ListNode(val elements: List, start: Position, end: Position) : Node(start, end) { 11 | 12 | override fun toString(): String { 13 | var result = "(ListNode: [" 14 | 15 | elements.forEach { 16 | result += "\n$it" 17 | } 18 | 19 | if (elements.isNotEmpty()) { 20 | result += '\n' 21 | } 22 | 23 | result += "])" 24 | return result 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/parser/nodes/NativeNode.kt: -------------------------------------------------------------------------------- 1 | package spritz.parser.nodes 2 | 3 | import spritz.lexer.position.Position 4 | import spritz.lexer.token.Token 5 | import spritz.parser.node.Node 6 | 7 | /** 8 | * @author surge 9 | * @since 26/03/2023 10 | */ 11 | class NativeNode(val clazz: Token<*>, val identifier: Token<*>, start: Position, end: Position) : Node(start, end) { 12 | 13 | override fun toString() = "(Native: $clazz, $identifier)" 14 | 15 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/parser/nodes/NumberNode.kt: -------------------------------------------------------------------------------- 1 | package spritz.parser.nodes 2 | 3 | import spritz.lexer.token.Token 4 | import spritz.parser.node.Node 5 | 6 | /** 7 | * @author surge 8 | * @since 26/02/2023 9 | */ 10 | class NumberNode(val token: Token<*>) : Node(token.start, token.end) { 11 | 12 | override fun toString() = "(Number: $token)" 13 | 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/parser/nodes/ReturnNode.kt: -------------------------------------------------------------------------------- 1 | package spritz.parser.nodes 2 | 3 | import spritz.lexer.position.Position 4 | import spritz.parser.node.Node 5 | 6 | /** 7 | * @author surge 8 | * @since 26/02/2023 9 | */ 10 | class ReturnNode(val value: Node?, start: Position, end: Position) : Node(start, end) { 11 | 12 | override fun toString() = "(Return${if (value != null) ": $value" else ""})" 13 | 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/parser/nodes/StringNode.kt: -------------------------------------------------------------------------------- 1 | package spritz.parser.nodes 2 | 3 | import spritz.lexer.position.Position 4 | import spritz.lexer.token.Token 5 | import spritz.parser.node.Node 6 | 7 | /** 8 | * @author surge 9 | * @since 01/03/2023 10 | */ 11 | class StringNode(val value: Token<*>, start: Position, end: Position) : Node(start, end) { 12 | 13 | override fun toString() = "(String: $value)" 14 | 15 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/parser/nodes/TaskCallNode.kt: -------------------------------------------------------------------------------- 1 | package spritz.parser.nodes 2 | 3 | import spritz.lexer.position.Position 4 | import spritz.parser.node.Node 5 | import spritz.value.Value 6 | 7 | /** 8 | * @author surge 9 | * @since 03/03/2023 10 | */ 11 | class TaskCallNode(val target: Node, val arguments: MutableList, start: Position, end: Position) : Node(start, end) { 12 | 13 | override fun toString() = "(Task Call: $target, $arguments)::($child)" 14 | 15 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/parser/nodes/TaskDefineNode.kt: -------------------------------------------------------------------------------- 1 | package spritz.parser.nodes 2 | 3 | import spritz.lexer.position.Position 4 | import spritz.parser.node.Node 5 | import spritz.util.Argument 6 | 7 | /** 8 | * @author surge 9 | * @since 27/02/2023 10 | */ 11 | class TaskDefineNode(val identifier: String, val expression: Boolean, val returnType: Node?, val arguments: List, val body: Node, start: Position, end: Position) : Node(start, end) { 12 | 13 | override fun toString() = "(Task Define: $identifier($arguments) -> $returnType, $body)" 14 | 15 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/parser/nodes/TryNode.kt: -------------------------------------------------------------------------------- 1 | package spritz.parser.nodes 2 | 3 | import spritz.lexer.position.Position 4 | import spritz.lexer.token.Token 5 | import spritz.parser.node.Node 6 | 7 | /** 8 | * @author surge 9 | * @since 07/03/2023 10 | */ 11 | class TryNode(val body: Node, val catch: CatchNode?, start: Position, end: Position) : Node(start, end) { 12 | 13 | override fun toString() = "(Try, $catch, $body)" 14 | 15 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/parser/nodes/UnaryOperationNode.kt: -------------------------------------------------------------------------------- 1 | package spritz.parser.nodes 2 | 3 | import spritz.lexer.token.Token 4 | import spritz.parser.node.Node 5 | 6 | /** 7 | * @author surge 8 | * @since 26/02/2023 9 | */ 10 | class UnaryOperationNode(val operator: Token<*>, val value: Node) : Node(operator.start, value.end) { 11 | 12 | override fun toString() = "(Unary Operation: $operator, $value)" 13 | 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/parser/nodes/WhileNode.kt: -------------------------------------------------------------------------------- 1 | package spritz.parser.nodes 2 | 3 | import spritz.lexer.position.Position 4 | import spritz.lexer.token.Token 5 | import spritz.parser.node.Node 6 | 7 | /** 8 | * @author surge 9 | * @since 07/03/2023 10 | */ 11 | class WhileNode(val expression: Node, val body: Node, start: Position, end: Position) : Node(start, end) { 12 | 13 | override fun toString() = "(While, $expression, $body)" 14 | 15 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/parser/nodes/condition/Case.kt: -------------------------------------------------------------------------------- 1 | package spritz.parser.nodes.condition 2 | 3 | import spritz.parser.node.Node 4 | 5 | /** 6 | * @author surge 7 | * @since 10/03/2023 8 | */ 9 | open class Case(val condition: Node?, val node: Node, val `else`: Boolean = false) { 10 | 11 | override fun toString() = "(Case: $node)" 12 | 13 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/parser/nodes/condition/ConditionNode.kt: -------------------------------------------------------------------------------- 1 | package spritz.parser.nodes.condition 2 | 3 | import spritz.parser.node.Node 4 | 5 | /** 6 | * @author surge 7 | * @since 10/03/2023 8 | */ 9 | class ConditionNode(val cases: List) : Node(cases.first().node.start, cases.last().node.end) { 10 | 11 | override fun toString() = "(Condition: $cases)" 12 | 13 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/util/Argument.kt: -------------------------------------------------------------------------------- 1 | package spritz.util 2 | 3 | import spritz.lexer.token.Token 4 | import spritz.parser.node.Node 5 | 6 | /** 7 | * Holds the [name] of this argument and it's required [type]. (Used in parsing). 8 | * 9 | * @author surge 10 | * @since 27/02/2023 11 | */ 12 | data class Argument(val name: Token<*>, val type: Node?) 13 | -------------------------------------------------------------------------------- /src/main/kotlin/spritz/util/Misc.kt: -------------------------------------------------------------------------------- 1 | package spritz.util 2 | 3 | import spritz.api.annotations.Identifier 4 | import spritz.api.result.Failure 5 | import spritz.api.result.Success 6 | import spritz.lexer.token.TokenType 7 | import spritz.lexer.token.TokenType.* 8 | import spritz.value.Value 9 | import java.lang.reflect.Field 10 | import java.lang.reflect.Method 11 | import java.lang.reflect.Modifier 12 | import kotlin.reflect.KProperty 13 | import kotlin.reflect.jvm.kotlinProperty 14 | 15 | /** 16 | * @author surge 17 | * @since 25/02/2023 18 | */ 19 | operator fun String.times(amount: Int): String { 20 | var result = "" 21 | 22 | for (i in 0..amount) { 23 | result += this 24 | } 25 | 26 | return result 27 | } 28 | 29 | fun Field.coercedName(): String = this.getAnnotation(Identifier::class.java)?.identifier ?: this.name 30 | fun Method.coercedName(): String = this.getAnnotation(Identifier::class.java)?.identifier ?: this.name 31 | fun Class<*>.coercedName(): String = this.getAnnotation(Identifier::class.java)?.identifier ?: this.simpleName 32 | fun Enum<*>.coercedName(): String = this.declaringClass.getField(this.name).getAnnotation(Identifier::class.java)?.identifier ?: this.name 33 | 34 | fun Class<*>.getAllFields(): List { 35 | val fields = mutableListOf() 36 | 37 | this.declaredFields.forEach { 38 | if (it.isSynthetic) { 39 | return@forEach 40 | } 41 | 42 | it.isAccessible = true 43 | 44 | fields.add(it) 45 | } 46 | 47 | if (this.superclass != null && this.superclass != Any::class.java) { 48 | fields.addAll(this.superclass.getAllFields()) 49 | } 50 | 51 | return fields 52 | } 53 | 54 | fun Class<*>.getAllMethods(): List { 55 | val methods = mutableListOf() 56 | 57 | this.declaredMethods.forEach { 58 | if (it.isSynthetic) { 59 | return@forEach 60 | } 61 | 62 | it.isAccessible = true 63 | 64 | methods.add(it) 65 | } 66 | 67 | if (this.superclass != null && this.superclass != Any::class.java) { 68 | methods.addAll(this.superclass.getAllMethods()) 69 | } 70 | 71 | return methods 72 | } 73 | 74 | inline fun Array.allIndexed(predicate: (Int, T) -> Boolean): Boolean { 75 | this.forEachIndexed { index, element -> 76 | if (!predicate(index, element)) { 77 | return false 78 | } 79 | } 80 | 81 | return true 82 | } 83 | 84 | fun Value.success(): Success { 85 | return Success(this) 86 | } 87 | 88 | val KEYWORDS = hashMapOf( 89 | // declaration keywords 90 | "task" to "task", 91 | "lambda" to "lambda", 92 | "class" to "class", 93 | "enum" to "enum", 94 | "mutable" to "mut", 95 | "constant" to "const", 96 | "native" to "native", 97 | "as" to "as", 98 | "is" to "is", 99 | "import" to "import", 100 | 101 | // loops 102 | "for" to "for", 103 | "while" to "while", 104 | 105 | // branch control statements 106 | "conditional" to "if", 107 | "conditional_default" to "else", 108 | "return" to "return", 109 | "continue" to "continue", 110 | "break" to "break", 111 | 112 | // try catch 113 | "try" to "try", 114 | "catch" to "catch" 115 | ) 116 | 117 | val NUMBERS = listOf( 118 | "int", 119 | "float", 120 | "number" 121 | ) 122 | 123 | val ANONYMOUS = "" 124 | 125 | fun keyword(input: String) = KEYWORDS.containsValue(input) 126 | 127 | fun unary(type: TokenType) = type == NEGATE || type == BIN_COMPLEMENT 128 | fun modifier(type: TokenType) = type == ASSIGNMENT || type == INCREMENT || type == DEINCREMENT || type == INCREASE_BY || type == DECREASE_BY || type == MULTIPLY_BY || type == DIVIDE_BY -------------------------------------------------------------------------------- /src/main/kotlin/spritz/util/RequiredArgument.kt: -------------------------------------------------------------------------------- 1 | package spritz.util 2 | 3 | import spritz.lexer.token.Token 4 | import spritz.value.Value 5 | 6 | /** 7 | * Holds the [name] of this argument and it's required [type]. (Used in interpreting). 8 | * 9 | * @author surge 10 | * @since 03/03/2023 11 | */ 12 | data class RequiredArgument(val name: Token<*>, val type: Value?) -------------------------------------------------------------------------------- /src/main/kotlin/spritz/value/NothingValue.kt: -------------------------------------------------------------------------------- 1 | package spritz.value 2 | 3 | /** 4 | * @author surge 5 | * @since 02/04/2023 6 | */ 7 | class NothingValue : Value("nothing") { 8 | 9 | override fun asJvmValue() = null 10 | override fun toString() = "nothing" 11 | 12 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/value/NullValue.kt: -------------------------------------------------------------------------------- 1 | package spritz.value 2 | 3 | /** 4 | * A value representing nothing. 5 | * 6 | * @author surge 7 | * @since 18/03/2023 8 | */ 9 | class NullValue : Value("null") { 10 | 11 | override fun asJvmValue() = null 12 | override fun toString() = "null" 13 | 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/value/PrimitiveValue.kt: -------------------------------------------------------------------------------- 1 | package spritz.value 2 | 3 | /** 4 | * Represents a primitive value. 5 | * 6 | * @author surge 7 | * @since 18/03/2023 8 | */ 9 | class PrimitiveValue(identifier: String, val test: (Value, Value) -> Boolean) : Value(identifier) { 10 | 11 | init { 12 | // add this to the companion object's list of primitives 13 | add(this) 14 | } 15 | 16 | override fun asJvmValue() = this 17 | override fun toString() = identifier 18 | 19 | companion object { 20 | 21 | // a list of primitive values present in the current environment. 22 | private val primitives = mutableListOf() 23 | 24 | /** 25 | * Adds a given [primitiveValue] to the list of primitives. 26 | */ 27 | fun add(primitiveValue: PrimitiveValue) { 28 | primitives.add(primitiveValue) 29 | } 30 | 31 | /** 32 | * Checks if a given [value] matches the [required] value. 33 | */ 34 | @JvmStatic 35 | fun check(value: Value, required: Value): Boolean { 36 | if (required.type == "any") { 37 | return true 38 | } 39 | 40 | if (required !is PrimitiveValue && value.type != required.type) { 41 | return false 42 | } 43 | 44 | return primitives.any { it.test(value, required) } 45 | } 46 | 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/value/Value.kt: -------------------------------------------------------------------------------- 1 | package spritz.value 2 | 3 | import spritz.builtin.Global 4 | import spritz.error.Error 5 | import spritz.error.interpreting.IllegalOperationError 6 | import spritz.interfaces.Cloneable 7 | import spritz.interpreter.RuntimeResult 8 | import spritz.interpreter.context.Context 9 | import spritz.lexer.position.LinkPosition 10 | import spritz.lexer.position.Position 11 | import spritz.lexer.token.Token 12 | import spritz.value.bool.BooleanValue 13 | import spritz.value.table.Table 14 | import spritz.value.table.TableAccessor 15 | import spritz.value.task.DefinedTaskValue 16 | 17 | /** 18 | * The main variable class. 19 | * 20 | * @author surge 21 | * @since 01/03/2023 22 | */ 23 | abstract class Value(val type: String, val identifier: String = type) : Cloneable { 24 | 25 | // the start, end, and context of this value 26 | lateinit var start: Position 27 | lateinit var end: Position 28 | lateinit var context: Context 29 | 30 | // a value table of all values that are contained within this value, or it's companion 31 | var table = Table() 32 | 33 | /** 34 | * @return This value, as it's JVM representation. 35 | */ 36 | abstract fun asJvmValue(): Any? 37 | 38 | /** 39 | * Attempts to check if this value and the given [other] value is true. 40 | * @return A pair of the resulting value and the error. 41 | */ 42 | open fun and(other: Value, operator: Token<*>): Pair = delegateToIllegal(this, other, operator) 43 | 44 | /** 45 | * Attempts to check if this value or the given [other] value is true. 46 | * @return A pair of the resulting value and the error. 47 | */ 48 | open fun or(other: Value, operator: Token<*>): Pair = delegateToIllegal(this, other, operator) 49 | 50 | /** 51 | * Attempts to add this value and the given [other] value. 52 | * @return A pair of the resulting value and the error. 53 | */ 54 | open fun plus(other: Value, operator: Token<*>): Pair = delegateToIllegal(this, other, operator) 55 | 56 | /** 57 | * Attempts to subtract this value from the given [other] value. 58 | * @return A pair of the resulting value and the error. 59 | */ 60 | open fun minus(other: Value, operator: Token<*>): Pair = delegateToIllegal(this, other, operator) 61 | 62 | /** 63 | * Attempts to multiply this value by the given [other] value. 64 | * @return A pair of the resulting value and the error. 65 | */ 66 | open fun multiply(other: Value, operator: Token<*>): Pair = delegateToIllegal(this, other, operator) 67 | 68 | /** 69 | * Attempts to divide this value by the given [other] value. 70 | * @return A pair of the resulting value and the error. 71 | */ 72 | open fun divide(other: Value, operator: Token<*>): Pair = delegateToIllegal(this, other, operator) 73 | 74 | /** 75 | * Attempts to modulo this value by the given [other] value. 76 | * @return A pair of the resulting value and the error. 77 | */ 78 | open fun modulo(other: Value, operator: Token<*>): Pair = delegateToIllegal(this, other, operator) 79 | 80 | /** 81 | * Attempts to left shift this value by the given [other] value. 82 | * @return A pair of the resulting value and the error. 83 | */ 84 | open fun binShl(other: Value, operator: Token<*>): Pair = delegateToIllegal(this, other, operator) 85 | 86 | /** 87 | * Attempts to right shift this value by the given [other] value. 88 | * @return A pair of the resulting value and the error. 89 | */ 90 | open fun binShr(other: Value, operator: Token<*>): Pair = delegateToIllegal(this, other, operator) 91 | 92 | /** 93 | * Attempts to (unsigned) right shift this value by the given [other] value. 94 | * @return A pair of the resulting value and the error. 95 | */ 96 | open fun binUShr(other: Value, operator: Token<*>): Pair = delegateToIllegal(this, other, operator) 97 | 98 | /** 99 | * Attempts to binary or this value by the given [other] value. 100 | * @return A pair of the resulting value and the error. 101 | */ 102 | open fun binOr(other: Value, operator: Token<*>): Pair = delegateToIllegal(this, other, operator) 103 | 104 | /** 105 | * Attempts to binary and this value by the given [other] value. 106 | * @return A pair of the resulting value and the error. 107 | */ 108 | open fun binAnd(other: Value, operator: Token<*>): Pair = delegateToIllegal(this, other, operator) 109 | 110 | /** 111 | * Attempts to binary xor this value by the given [other] value. 112 | * @return A pair of the resulting value and the error. 113 | */ 114 | open fun binXor(other: Value, operator: Token<*>): Pair = delegateToIllegal(this, other, operator) 115 | 116 | /** 117 | * Attempts to binary invert this value. 118 | * @return A pair of the resulting value and the error. 119 | */ 120 | open fun binComplement(operator: Token<*>): Pair = delegateToIllegal(this, this, operator) 121 | 122 | /** 123 | * Attempts to check if this value and the given [other] value are equal. 124 | * @return A pair of the resulting value and the error. 125 | */ 126 | open fun equality(other: Value, operator: Token<*>): Pair = Pair(BooleanValue(this == other), null) 127 | 128 | /** 129 | * Attempts to check if this value and the given [other] value are not equal. 130 | * @return A pair of the resulting value and the error. 131 | */ 132 | open fun inequality(other: Value, operator: Token<*>): Pair = Pair(BooleanValue(this != other), null) 133 | 134 | /** 135 | * Attempts to check if this value and the given [other] value are nearly equal. 136 | * @return A pair of the resulting value and the error. 137 | */ 138 | open fun roughEquality(other: Value, operator: Token<*>): Pair = delegateToIllegal(this, other, operator) 139 | 140 | /** 141 | * Attempts to check if this value and the given [other] value are not nearly equal. 142 | * @return A pair of the resulting value and the error. 143 | */ 144 | open fun roughInequality(other: Value, operator: Token<*>): Pair = delegateToIllegal(this, other, operator) 145 | 146 | /** 147 | * Attempts to check if this value is less than the given [other] value. 148 | * @return A pair of the resulting value and the error. 149 | */ 150 | open fun lessThan(other: Value, operator: Token<*>): Pair = delegateToIllegal(this, other, operator) 151 | 152 | /** 153 | * Attempts to check if this value is greater than the given [other] value. 154 | * @return A pair of the resulting value and the error. 155 | */ 156 | open fun greaterThan(other: Value, operator: Token<*>): Pair = delegateToIllegal(this, other, operator) 157 | 158 | /** 159 | * Attempts to check if this value is less than or equal to the given [other] value. 160 | * @return A pair of the resulting value and the error. 161 | */ 162 | open fun lessThanOrEqualTo(other: Value, operator: Token<*>): Pair = delegateToIllegal(this, other, operator) 163 | 164 | /** 165 | * Attempts to check if this value is greater than or equal to the given [other] value. 166 | * @return A pair of the resulting value and the error. 167 | */ 168 | open fun greaterThanOrEqualTo(other: Value, operator: Token<*>): Pair = delegateToIllegal(this, other, operator) 169 | 170 | /** 171 | * Attempts to get the opposite of this value. 172 | * @return A pair of the resulting value and the error. 173 | */ 174 | open fun negated(token: Token<*>): Pair = delegateToIllegal(this, this, token) 175 | 176 | /** 177 | * Attempts to execute this value, with the given [passed] arguments. 178 | * @return The result of executing this value. 179 | */ 180 | fun execute(passed: List): RuntimeResult = execute(passed, LinkPosition(), LinkPosition(), this.context) 181 | 182 | /** 183 | * Attempts to execute this value, with the given [passed] arguments, and setting the resulting value's [start], [end], and [context]. 184 | * @return The result of executing this value. 185 | */ 186 | open fun execute(passed: List, start: Position, end: Position, context: Context): RuntimeResult = RuntimeResult().failure(IllegalOperationError("Couldn't execute '$this'", start, end, this.context)) 187 | 188 | /** 189 | * @return A cloned value. 190 | */ 191 | override fun clone(): Value { 192 | return this 193 | } 194 | 195 | /** 196 | * Checks if this value's [table] contains a task that returns int and has 0 parameters, and executes it. 197 | * @return The result of executing task repr, or '' if it is not found. 198 | */ 199 | override fun toString(): String { 200 | return TableAccessor(this.table) 201 | 202 | // called "repr" 203 | .identifier("repr") 204 | 205 | // is a task, returns string, and has no arguments 206 | .predicate { it is DefinedTaskValue && it.returnType == Global.string && it.arguments.isEmpty() } 207 | 208 | // find and execute 209 | .find(this.start, this.end, this.context).value?.execute(arrayListOf())?.value?.toString() ?: "" 210 | } 211 | 212 | /** 213 | * Sets the [start] and [end] positions of this value. 214 | * @return This value. 215 | */ 216 | fun position(start: Position, end: Position): Value { 217 | this.start = start 218 | this.end = end 219 | 220 | return this 221 | } 222 | 223 | /** 224 | * Sets the [context] of this value. 225 | * @return This value. 226 | */ 227 | fun givenContext(context: Context): Value { 228 | this.context = context 229 | 230 | return this 231 | } 232 | 233 | /** 234 | * Sets the [start], [end], and [context] of this value to what we would assume as linked positions. 235 | * @return This value. 236 | */ 237 | fun linked(): Value { 238 | return position(LinkPosition(), LinkPosition()).givenContext(Context(this.identifier)) 239 | } 240 | 241 | companion object { 242 | 243 | /** 244 | * @return A pair of null and an [IllegalOperationError]. 245 | */ 246 | fun delegateToIllegal(value: Value, other: Value, operator: Token<*>): Pair { 247 | return Pair( 248 | null, 249 | IllegalOperationError( 250 | "Illegal operation on value of type '${value.type}': '$operator' and '$other'", 251 | operator.start, 252 | other.end, 253 | other.context 254 | ) 255 | ) 256 | } 257 | 258 | } 259 | 260 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/value/bool/BooleanValue.kt: -------------------------------------------------------------------------------- 1 | package spritz.value.bool 2 | 3 | import spritz.SpritzEnvironment 4 | import spritz.builtin.companions.BooleanCompanion 5 | import spritz.builtin.companions.ClassCompanion 6 | import spritz.error.Error 7 | import spritz.interpreter.context.Context 8 | import spritz.lexer.token.Token 9 | import spritz.value.Value 10 | 11 | /** 12 | * @author surge 13 | * @since 18/03/2023 14 | */ 15 | class BooleanValue(var value: Boolean) : Value("boolean") { 16 | 17 | init { 18 | SpritzEnvironment.putIntoTable(BooleanCompanion(this), this.table, Context("companion")) 19 | } 20 | 21 | override fun asJvmValue() = value 22 | 23 | override fun and(other: Value, operator: Token<*>): Pair { 24 | if (other is BooleanValue) { 25 | return Pair(BooleanValue(this.value && other.value), null) 26 | } 27 | 28 | return super.and(other, operator) 29 | } 30 | 31 | override fun or(other: Value, operator: Token<*>): Pair { 32 | if (other is BooleanValue) { 33 | return Pair(BooleanValue(this.value || other.value), null) 34 | } 35 | 36 | return super.and(other, operator) 37 | } 38 | 39 | override fun negated(token: Token<*>): Pair { 40 | return Pair(BooleanValue(!this.value), null) 41 | } 42 | 43 | override fun toString() = value.toString() 44 | 45 | override fun equals(other: Any?): Boolean { 46 | if (other !is BooleanValue) { 47 | return false 48 | } 49 | 50 | return value == other.value 51 | } 52 | 53 | override fun hashCode(): Int { 54 | return value.hashCode() 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/value/class/ClassValue.kt: -------------------------------------------------------------------------------- 1 | package spritz.value.`class` 2 | 3 | import spritz.value.task.TaskValue 4 | 5 | /** 6 | * @author surge 7 | * @since 26/03/2023 8 | */ 9 | abstract class ClassValue(identifier: String, type: String) : TaskValue(identifier, type) -------------------------------------------------------------------------------- /src/main/kotlin/spritz/value/class/DefinedClassValue.kt: -------------------------------------------------------------------------------- 1 | package spritz.value.`class` 2 | 3 | import spritz.SpritzEnvironment 4 | import spritz.builtin.companions.ClassCompanion 5 | import spritz.interpreter.Interpreter 6 | import spritz.interpreter.RuntimeResult 7 | import spritz.interpreter.context.Context 8 | import spritz.lexer.position.Position 9 | import spritz.parser.node.Node 10 | import spritz.util.RequiredArgument 11 | import spritz.value.Value 12 | import spritz.value.table.Table 13 | import spritz.value.table.TableAccessor 14 | 15 | /** 16 | * @author surge 17 | * @since 04/03/2023 18 | */ 19 | class DefinedClassValue(identifier: String, val constructor: List, val body: Node?) : ClassValue(identifier = identifier, identifier) { 20 | 21 | init { 22 | SpritzEnvironment.putIntoTable(ClassCompanion(this), this.table, Context("companion")) 23 | } 24 | 25 | override fun asJvmValue() = this 26 | 27 | override fun execute(passed: List, start: Position, end: Position, context: Context): RuntimeResult { 28 | val result = RuntimeResult() 29 | val interpreter = Interpreter() 30 | 31 | // generate new context 32 | val instanceContext = generateExecuteContext() 33 | 34 | // check and populate arguments 35 | result.register(this.checkAndPopulate(constructor, passed, start, end, instanceContext)) 36 | 37 | if (result.shouldReturn()) { 38 | return result 39 | } 40 | 41 | // table that holds any tasks and variables inside the instance. 42 | val table = Table() 43 | 44 | if (body != null) { 45 | result.register(interpreter.visit(this.body, instanceContext)) 46 | 47 | if (result.error != null) { 48 | return result 49 | } 50 | } 51 | 52 | // add symbols to table 53 | table.symbols.addAll(instanceContext.table.symbols) 54 | 55 | // generate instance 56 | val instance = DefinedInstanceValue(this, table) 57 | 58 | TableAccessor(instance.table) 59 | .identifier("this") 60 | .immutable(true) 61 | .set(instance, forced = true, data = Table.Data(this.start, this.end, context)) 62 | 63 | return result.success(instance.position(start, end).givenContext(instanceContext)) 64 | } 65 | 66 | override fun toString() = super.toString().ifEmpty { "($identifier)" } 67 | 68 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/value/class/DefinedInstanceValue.kt: -------------------------------------------------------------------------------- 1 | package spritz.value.`class` 2 | 3 | import spritz.SpritzEnvironment 4 | import spritz.builtin.companions.ClassCompanion 5 | import spritz.builtin.companions.InstanceCompanion 6 | import spritz.interpreter.context.Context 7 | import spritz.value.table.Table 8 | 9 | /** 10 | * @author surge 11 | * @since 04/03/2023 12 | */ 13 | class DefinedInstanceValue(val parent: DefinedClassValue, table: Table) : InstanceValue(parent.type) { 14 | 15 | init { 16 | this.table = table 17 | SpritzEnvironment.putIntoTable(InstanceCompanion(this), this.table, Context("companion")) 18 | } 19 | 20 | override fun asJvmValue() = this 21 | override fun toString() = super.toString().ifEmpty { "(Instance of $identifier)" } 22 | 23 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/value/class/InstanceValue.kt: -------------------------------------------------------------------------------- 1 | package spritz.value.`class` 2 | 3 | import spritz.value.task.TaskValue 4 | 5 | /** 6 | * @author surge 7 | * @since 26/03/2023 8 | */ 9 | abstract class InstanceValue(type: String) : TaskValue(type) -------------------------------------------------------------------------------- /src/main/kotlin/spritz/value/class/JvmClassValue.kt: -------------------------------------------------------------------------------- 1 | package spritz.value.`class` 2 | 3 | import spritz.SpritzEnvironment 4 | import spritz.builtin.companions.ClassCompanion 5 | import spritz.error.interpreting.JvmError 6 | import spritz.interpreter.RuntimeResult 7 | import spritz.interpreter.context.Context 8 | import spritz.lexer.position.Position 9 | import spritz.util.allIndexed 10 | import spritz.value.Value 11 | 12 | /** 13 | * @author surge 14 | * @since 04/03/2023 15 | */ 16 | class JvmClassValue(identifier: String, val clazz: Class<*>) : ClassValue(identifier = identifier, identifier) { 17 | 18 | init { 19 | SpritzEnvironment.putIntoTable(ClassCompanion(this), this.table, Context("companion")) 20 | SpritzEnvironment.staticLoad(clazz, this.table, Context("static")) 21 | } 22 | 23 | override fun asJvmValue() = clazz 24 | 25 | override fun execute(passed: List, start: Position, end: Position, context: Context): RuntimeResult { 26 | val constructorArgs = mutableListOf() 27 | 28 | // convert all passed arguments to their JVM representation. 29 | passed.forEachIndexed { index, it -> 30 | constructorArgs.add(it.asJvmValue()) 31 | } 32 | 33 | // find first constructor with the same parameter size as the passed arguments. 34 | val constructor = clazz.constructors.first { constructor -> 35 | constructor.parameterCount == passed.size && constructor.parameters.allIndexed { index, parameter -> 36 | parameter.type.kotlin.javaObjectType.isAssignableFrom(constructorArgs[index]?.let { it::class.java }) 37 | } 38 | } 39 | 40 | try { 41 | // instantiate class 42 | val classInstance = constructor.newInstance(*constructorArgs.toTypedArray()) 43 | 44 | val instance = JvmInstanceValue(classInstance) 45 | .position(start, end) 46 | .givenContext(context) 47 | 48 | return RuntimeResult().success(instance) 49 | } catch (exception: Exception) { 50 | return RuntimeResult().failure(JvmError( 51 | "Failed to instantiate JVM Class instance!\n\nJVM STACK TRACE WILL FOLLOW\n\n${ 52 | run { 53 | exception.printStackTrace() 54 | "" 55 | } 56 | }", 57 | start, 58 | end, 59 | context 60 | )) 61 | } 62 | } 63 | 64 | override fun toString() = "($identifier)" 65 | 66 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/value/class/JvmInstanceValue.kt: -------------------------------------------------------------------------------- 1 | package spritz.value.`class` 2 | 3 | import spritz.api.Coercion 4 | import spritz.api.annotations.Excluded 5 | import spritz.error.interpreting.JvmError 6 | import spritz.util.coercedName 7 | import spritz.util.getAllFields 8 | import spritz.util.getAllMethods 9 | import spritz.value.table.result.Result 10 | import java.lang.reflect.Field 11 | import java.lang.reflect.Modifier 12 | 13 | /** 14 | * I am not documenting this. 15 | * 16 | * @author surge 17 | * @since 18/03/2023 18 | */ 19 | class JvmInstanceValue(val instance: Any) : InstanceValue(instance::class.java.simpleName) { 20 | 21 | init { 22 | this.table.overrideGet { identifier, predicate, _, data -> 23 | try { 24 | var field: Any? = instance::class.java.getAllFields().filter { 25 | Modifier.isPublic(it.modifiers) && !Modifier.isStatic(it.modifiers) && 26 | !it.isAnnotationPresent(Excluded::class.java) && 27 | predicate(Coercion.IntoSpritz.coerce(it.get(instance))) 28 | }.firstOrNull { it.coercedName() == identifier }?.also { it.isAccessible = true } 29 | 30 | if (field == null) { 31 | field = instance::class.java.getAllMethods().filter { 32 | Modifier.isPublic(it.modifiers) && !Modifier.isStatic(it.modifiers) && 33 | !it.isAnnotationPresent(Excluded::class.java) && 34 | predicate(Coercion.IntoSpritz.coerce(it)) 35 | }.firstOrNull { it.coercedName() == identifier }?.also { it.isAccessible = true } 36 | } 37 | 38 | if (field == null) { 39 | field = instance::class.java.declaredClasses.filter { 40 | Modifier.isPublic(it.modifiers) && !Modifier.isStatic(it.modifiers) && 41 | !it.isAnnotationPresent(Excluded::class.java) && 42 | predicate(Coercion.IntoSpritz.coerce(it)) 43 | }.firstOrNull { it.name == identifier } 44 | } 45 | 46 | if (field == null) { 47 | field = instance::class.java.enumConstants?.filter { 48 | predicate(Coercion.IntoSpritz.coerce(it)) && !it::class.java.isAnnotationPresent(Excluded::class.java) 49 | }?.firstOrNull { it::class.java.coercedName() == identifier } 50 | } 51 | 52 | if (field == null) { 53 | return@overrideGet Result(null, JvmError( 54 | "'$identifier' is not present in ${instance::class.java.simpleName}", 55 | data.start, 56 | data.end, 57 | data.context 58 | )) 59 | } 60 | 61 | return@overrideGet Result(Coercion.IntoSpritz.coerce(field, instance).position(data.start, data.end).givenContext(data.context), null) 62 | } catch (exception: Exception) { 63 | return@overrideGet Result(null, JvmError( 64 | exception.message!!, 65 | data.start, 66 | data.end, 67 | data.context 68 | )) 69 | } 70 | } 71 | 72 | this.table.overrideSet { 73 | try { 74 | val field: Field = instance::class.java.declaredFields.filter { !it.isAnnotationPresent(Excluded::class.java) }.firstOrNull { field -> field.coercedName() == identifier } 75 | ?: throw Error("'$identifier' is not present in ${instance::class.java.simpleName}") 76 | 77 | field.set(instance, it.value.asJvmValue()) 78 | 79 | return@overrideSet Result(null, null) 80 | } catch (exception: Exception) { 81 | return@overrideSet Result(null, JvmError( 82 | exception.message!!, 83 | start, 84 | end, 85 | context 86 | )) 87 | } 88 | } 89 | } 90 | 91 | override fun asJvmValue() = instance 92 | override fun toString() = instance.toString() 93 | 94 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/value/dictionary/DictionaryValue.kt: -------------------------------------------------------------------------------- 1 | package spritz.value.dictionary 2 | 3 | import spritz.SpritzEnvironment 4 | import spritz.builtin.companions.DictionaryCompanion 5 | import spritz.interpreter.context.Context 6 | import spritz.value.Value 7 | 8 | /** 9 | * @author surge 10 | * @since 18/03/2023 11 | */ 12 | class DictionaryValue(val elements: HashMap) : Value("dictionary") { 13 | 14 | init { 15 | SpritzEnvironment.putIntoTable(DictionaryCompanion(this), this.table, Context("companion")) 16 | } 17 | 18 | override fun asJvmValue() = elements 19 | override fun toString() = elements.toString() 20 | 21 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/value/enum/EnumValue.kt: -------------------------------------------------------------------------------- 1 | package spritz.value.`enum` 2 | 3 | import spritz.value.Value 4 | import spritz.value.table.TableAccessor 5 | 6 | /** 7 | * @author surge 8 | * @since 26/03/2023 9 | */ 10 | class EnumValue(identifier: String, members: LinkedHashMap) : Value("enum", identifier = identifier) { 11 | 12 | init { 13 | members.forEach { (name, value) -> 14 | TableAccessor(this.table) 15 | .identifier(name) 16 | .immutable(true) 17 | .set(value, declaration = true) 18 | } 19 | } 20 | 21 | override fun asJvmValue() = this 22 | 23 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/value/enum/JvmEnumValue.kt: -------------------------------------------------------------------------------- 1 | package spritz.value.enum 2 | 3 | import spritz.util.coercedName 4 | import spritz.value.`class`.InstanceValue 5 | import spritz.value.`class`.JvmInstanceValue 6 | import spritz.value.table.TableAccessor 7 | 8 | /** 9 | * I am not documenting this. 10 | * 11 | * @author surge 12 | * @since 18/03/2023 13 | */ 14 | class JvmEnumValue(val instance: Class>) : InstanceValue(instance::class.java.simpleName) { 15 | 16 | init { 17 | instance.enumConstants.forEach { member -> 18 | val instance = JvmInstanceValue(member) 19 | 20 | TableAccessor(this.table) 21 | .identifier(member.javaClass.coercedName()) 22 | .immutable(true) 23 | } 24 | } 25 | 26 | override fun asJvmValue() = instance 27 | override fun toString() = instance.toString() 28 | 29 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/value/list/ListValue.kt: -------------------------------------------------------------------------------- 1 | package spritz.value.list 2 | 3 | import spritz.SpritzEnvironment 4 | import spritz.builtin.companions.ListCompanion 5 | import spritz.interpreter.context.Context 6 | import spritz.value.Value 7 | 8 | /** 9 | * @author surge 10 | * @since 18/03/2023 11 | */ 12 | class ListValue(val elements: MutableList) : Value("list") { 13 | 14 | init { 15 | SpritzEnvironment.putIntoTable(ListCompanion(this), this.table, Context("companion")) 16 | } 17 | 18 | override fun asJvmValue() = elements 19 | override fun toString() = elements.toString() 20 | 21 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/value/number/ByteValue.kt: -------------------------------------------------------------------------------- 1 | package spritz.value.number 2 | 3 | import spritz.SpritzEnvironment 4 | import spritz.builtin.companions.NumberCompanion 5 | import spritz.lexer.token.Token 6 | import spritz.value.Value 7 | import spritz.error.Error 8 | import spritz.interpreter.context.Context 9 | 10 | /** 11 | * @author surge 12 | * @since 03/03/2023 13 | */ 14 | class ByteValue(value: Byte) : NumberValue(value, "byte") { 15 | 16 | init { 17 | SpritzEnvironment.putIntoTable(NumberCompanion(this), this.table, Context("companion")) 18 | } 19 | 20 | override fun plus(other: Value, operator: Token<*>): Pair { 21 | if (other is FloatValue) { 22 | return delegateToIllegal(this, other, operator) 23 | } 24 | 25 | return super.plus(other, operator) 26 | } 27 | 28 | override fun minus(other: Value, operator: Token<*>): Pair { 29 | if (other is FloatValue) { 30 | return delegateToIllegal(this, other, operator) 31 | } 32 | 33 | return super.minus(other, operator) 34 | } 35 | 36 | override fun multiply(other: Value, operator: Token<*>): Pair { 37 | if (other is FloatValue) { 38 | return delegateToIllegal(this, other, operator) 39 | } 40 | 41 | return super.multiply(other, operator) 42 | } 43 | 44 | override fun divide(other: Value, operator: Token<*>): Pair { 45 | if (other is FloatValue) { 46 | return delegateToIllegal(this, other, operator) 47 | } 48 | 49 | return super.divide(other, operator) 50 | } 51 | 52 | override fun toString() = value.toString() 53 | 54 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/value/number/FloatValue.kt: -------------------------------------------------------------------------------- 1 | package spritz.value.number 2 | 3 | import spritz.SpritzEnvironment 4 | import spritz.builtin.companions.NumberCompanion 5 | import spritz.interpreter.context.Context 6 | 7 | /** 8 | * @author surge 9 | * @since 18/03/2023 10 | */ 11 | class FloatValue(value: Float) : NumberValue(value, "float") { 12 | 13 | init { 14 | SpritzEnvironment.putIntoTable(NumberCompanion(this), this.table, Context("companion")) 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/value/number/IntValue.kt: -------------------------------------------------------------------------------- 1 | package spritz.value.number 2 | 3 | import spritz.SpritzEnvironment 4 | import spritz.builtin.companions.NumberCompanion 5 | import spritz.lexer.token.Token 6 | import spritz.value.Value 7 | import spritz.error.Error 8 | import spritz.interpreter.context.Context 9 | 10 | /** 11 | * @author surge 12 | * @since 02/03/2023 13 | */ 14 | class IntValue(value: Int) : NumberValue(value, "int") { 15 | 16 | init { 17 | SpritzEnvironment.putIntoTable(NumberCompanion(this), this.table, Context("companion")) 18 | } 19 | 20 | override fun binShl(other: Value, operator: Token<*>): Pair { 21 | if (other is IntValue) { 22 | return Pair(IntValue(this.value shl other.value), null) 23 | } 24 | 25 | return super.binShl(other, operator) 26 | } 27 | 28 | override fun binShr(other: Value, operator: Token<*>): Pair { 29 | if (other is IntValue) { 30 | return Pair(IntValue(this.value shr other.value), null) 31 | } 32 | 33 | return super.binShr(other, operator) 34 | } 35 | 36 | override fun binUShr(other: Value, operator: Token<*>): Pair { 37 | if (other is IntValue) { 38 | return Pair(IntValue(this.value ushr other.value), null) 39 | } 40 | 41 | return super.binUShr(other, operator) 42 | } 43 | 44 | override fun binOr(other: Value, operator: Token<*>): Pair { 45 | if (other is IntValue) { 46 | return Pair(IntValue(this.value or other.value), null) 47 | } 48 | 49 | return super.binOr(other, operator) 50 | } 51 | 52 | override fun binAnd(other: Value, operator: Token<*>): Pair { 53 | if (other is IntValue) { 54 | return Pair(IntValue(this.value and other.value), null) 55 | } 56 | 57 | return super.binAnd(other, operator) 58 | } 59 | 60 | override fun binXor(other: Value, operator: Token<*>): Pair { 61 | if (other is IntValue) { 62 | return Pair(IntValue(this.value xor other.value), null) 63 | } 64 | 65 | return super.binXor(other, operator) 66 | } 67 | 68 | override fun binComplement(operator: Token<*>): Pair { 69 | return Pair(IntValue(this.value.toString(2).toInt().inv()), null) 70 | } 71 | 72 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/value/number/LongValue.kt: -------------------------------------------------------------------------------- 1 | package spritz.value.number 2 | 3 | import spritz.SpritzEnvironment 4 | import spritz.builtin.companions.NumberCompanion 5 | import spritz.lexer.token.Token 6 | import spritz.value.Value 7 | import spritz.error.Error 8 | import spritz.interpreter.context.Context 9 | 10 | /** 11 | * @author surge 12 | * @since 02/03/2023 13 | */ 14 | class LongValue(value: Long) : NumberValue(value, "long") { 15 | 16 | init { 17 | SpritzEnvironment.putIntoTable(NumberCompanion(this), this.table, Context("companion")) 18 | } 19 | 20 | override fun binShl(other: Value, operator: Token<*>): Pair { 21 | if (other is IntValue) { 22 | return Pair(LongValue(this.value shl other.value), null) 23 | } 24 | 25 | return super.binShl(other, operator) 26 | } 27 | 28 | override fun binShr(other: Value, operator: Token<*>): Pair { 29 | if (other is IntValue) { 30 | return Pair(LongValue(this.value shr other.value), null) 31 | } 32 | 33 | return super.binShr(other, operator) 34 | } 35 | 36 | override fun binUShr(other: Value, operator: Token<*>): Pair { 37 | if (other is IntValue) { 38 | return Pair(LongValue(this.value ushr other.value), null) 39 | } 40 | 41 | return super.binUShr(other, operator) 42 | } 43 | 44 | override fun binOr(other: Value, operator: Token<*>): Pair { 45 | if (other is LongValue) { 46 | return Pair(LongValue(this.value or other.value), null) 47 | } 48 | 49 | return super.binOr(other, operator) 50 | } 51 | 52 | override fun binAnd(other: Value, operator: Token<*>): Pair { 53 | if (other is LongValue) { 54 | return Pair(LongValue(this.value and other.value), null) 55 | } 56 | 57 | return super.binAnd(other, operator) 58 | } 59 | 60 | override fun binXor(other: Value, operator: Token<*>): Pair { 61 | if (other is LongValue) { 62 | return Pair(LongValue(this.value xor other.value), null) 63 | } 64 | 65 | return super.binXor(other, operator) 66 | } 67 | 68 | override fun binComplement(operator: Token<*>): Pair { 69 | return Pair(LongValue(this.value.toString(2).toLong().inv()), null) 70 | } 71 | 72 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/value/number/NumberValue.kt: -------------------------------------------------------------------------------- 1 | package spritz.value.number 2 | 3 | import spritz.error.Error 4 | import spritz.error.interpreting.MathsError 5 | import spritz.lexer.token.Token 6 | import spritz.value.Value 7 | import spritz.value.bool.BooleanValue 8 | 9 | /** 10 | * @author surge 11 | * @since 02/03/2023 12 | */ 13 | open class NumberValue(val value: T, type: String) : Value(type) { 14 | 15 | override fun asJvmValue() = value 16 | 17 | override fun plus(other: Value, operator: Token<*>): Pair { 18 | if (other is NumberValue<*>) { 19 | return Pair(convert(if (shouldConvert(other)) this.value.toFloat() + other.value.toFloat() else this.value.toInt() + other.value.toInt()), null) 20 | } 21 | 22 | return delegateToIllegal(this, other, operator) 23 | } 24 | 25 | override fun minus(other: Value, operator: Token<*>): Pair { 26 | if (other is NumberValue<*>) { 27 | return Pair(convert(if (shouldConvert(other)) this.value.toFloat() - other.value.toFloat() else this.value.toInt() - other.value.toInt()), null) 28 | } 29 | 30 | return delegateToIllegal(this, other, operator) 31 | } 32 | 33 | override fun multiply(other: Value, operator: Token<*>): Pair { 34 | if (other is NumberValue<*>) { 35 | return Pair(convert(if (shouldConvert(other)) this.value.toFloat() * other.value.toFloat() else this.value.toInt() * other.value.toInt()), null) 36 | } 37 | 38 | return delegateToIllegal(this, other, operator) 39 | } 40 | 41 | override fun divide(other: Value, operator: Token<*>): Pair { 42 | if (other is NumberValue<*>) { 43 | if (other.value == 0) { 44 | return Pair(null, MathsError( 45 | "Division by 0", 46 | other.start, 47 | other.end, 48 | this.context 49 | )) 50 | } 51 | 52 | return Pair(convert(if (shouldConvert(other)) this.value.toFloat() / other.value.toFloat() else this.value.toInt() / other.value.toInt()), null) 53 | } 54 | 55 | return delegateToIllegal(this, other, operator) 56 | } 57 | 58 | override fun modulo(other: Value, operator: Token<*>): Pair { 59 | if (other is NumberValue<*>) { 60 | return Pair(convert(if (shouldConvert(other)) this.value.toFloat() % other.value.toFloat() else this.value.toInt() % other.value.toInt()), null) 61 | } 62 | 63 | return delegateToIllegal(this, other, operator) 64 | } 65 | 66 | override fun equality(other: Value, operator: Token<*>): Pair { 67 | if (other is NumberValue<*>) { 68 | return Pair(BooleanValue(if (shouldConvert(other)) this.value.toFloat() == other.value.toFloat() else this.value.toInt() == other.value.toInt()), null) 69 | } 70 | 71 | return delegateToIllegal(this, other, operator) as Pair 72 | } 73 | 74 | override fun inequality(other: Value, operator: Token<*>): Pair { 75 | if (other is NumberValue<*>) { 76 | return Pair(BooleanValue(if (shouldConvert(other)) this.value.toFloat() != other.value.toFloat() else this.value.toInt() != other.value.toInt()), null) 77 | } 78 | 79 | return delegateToIllegal(this, other, operator) as Pair 80 | } 81 | 82 | override fun lessThan(other: Value, operator: Token<*>): Pair { 83 | if (other is NumberValue<*>) { 84 | return Pair(BooleanValue(if (shouldConvert(other)) this.value.toFloat() < other.value.toFloat() else this.value.toInt() < other.value.toInt()), null) 85 | } 86 | 87 | return delegateToIllegal(this, other, operator) as Pair 88 | } 89 | 90 | override fun greaterThan(other: Value, operator: Token<*>): Pair { 91 | if (other is NumberValue<*>) { 92 | return Pair(BooleanValue(if (shouldConvert(other)) this.value.toFloat() > other.value.toFloat() else this.value.toInt() > other.value.toInt()), null) 93 | } 94 | 95 | return delegateToIllegal(this, other, operator) as Pair 96 | } 97 | 98 | override fun lessThanOrEqualTo(other: Value, operator: Token<*>): Pair { 99 | if (other is NumberValue<*>) { 100 | return Pair(BooleanValue(if (shouldConvert(other)) this.value.toFloat() <= other.value.toFloat() else this.value.toInt() <= other.value.toInt()), null) 101 | } 102 | 103 | return delegateToIllegal(this, other, operator) as Pair 104 | } 105 | 106 | override fun greaterThanOrEqualTo(other: Value, operator: Token<*>): Pair { 107 | if (other is NumberValue<*>) { 108 | return Pair(BooleanValue(if (shouldConvert(other)) this.value.toFloat() >= other.value.toFloat() else this.value.toInt() >= other.value.toInt()), null) 109 | } 110 | 111 | return delegateToIllegal(this, other, operator) as Pair 112 | } 113 | 114 | override fun toString() = this.value.toString() 115 | 116 | /** 117 | * Converts a value to a float, if it needs to be converted. 118 | * @return The corrected value. 119 | */ 120 | private fun convert(result: Number): NumberValue<*> { 121 | return if (result is Float) { 122 | FloatValue(result.toFloat()) 123 | } else { 124 | IntValue(result.toInt()) 125 | } 126 | } 127 | 128 | /** 129 | * Checks if the value needs to be converted to a [FloatValue] based on this value and [other]. 130 | */ 131 | private fun shouldConvert(other: NumberValue<*>): Boolean = this.value is Float || other.value is Float 132 | 133 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/value/string/StringValue.kt: -------------------------------------------------------------------------------- 1 | package spritz.value.string 2 | 3 | import spritz.SpritzEnvironment 4 | import spritz.builtin.companions.StringCompanion 5 | import spritz.lexer.token.Token 6 | import spritz.value.Value 7 | import spritz.value.bool.BooleanValue 8 | import spritz.error.Error 9 | import spritz.interpreter.context.Context 10 | 11 | /** 12 | * @author surge 13 | * @since 18/03/2023 14 | */ 15 | class StringValue(val value: String) : Value("string") { 16 | 17 | init { 18 | SpritzEnvironment.putIntoTable(StringCompanion(this), this.table, Context("companion")) 19 | } 20 | 21 | override fun asJvmValue() = value 22 | 23 | override fun plus(other: Value, operator: Token<*>): Pair { 24 | return Pair(StringValue(value + other.toString()), null) 25 | } 26 | 27 | override fun equality(other: Value, operator: Token<*>): Pair { 28 | return if (other is StringValue) { 29 | Pair(BooleanValue(this.value == other.value), null) 30 | } else { 31 | Pair(BooleanValue(false), null) 32 | } 33 | } 34 | 35 | override fun inequality(other: Value, operator: Token<*>): Pair { 36 | return if (other is StringValue) { 37 | Pair(BooleanValue(this.value != other.value), null) 38 | } else { 39 | Pair(BooleanValue(true), null) 40 | } 41 | } 42 | 43 | override fun roughEquality(other: Value, operator: Token<*>): Pair { 44 | return if (other is StringValue) { 45 | Pair(BooleanValue(this.value.equals(other.value, true)), null) 46 | } else { 47 | Pair(BooleanValue(false), null) 48 | } 49 | } 50 | 51 | override fun roughInequality(other: Value, operator: Token<*>): Pair { 52 | return if (other is StringValue) { 53 | Pair(BooleanValue(!this.value.equals(other.value, true)), null) 54 | } else { 55 | Pair(BooleanValue(true), null) 56 | } 57 | } 58 | 59 | override fun toString() = value 60 | 61 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/value/table/Symbol.kt: -------------------------------------------------------------------------------- 1 | package spritz.value.table 2 | 3 | import spritz.lexer.position.Position 4 | import spritz.value.Value 5 | 6 | /** 7 | * Holds the [name], [value], and position of a symbol. 8 | * 9 | * @author surge 10 | * @since 17/03/2023 11 | */ 12 | data class Symbol(val name: String, var value: Value, val start: Position, val end: Position) { 13 | 14 | var immutable = false 15 | 16 | /** 17 | * Sets the [immutable] state of this symbol. 18 | * @return This instance. 19 | */ 20 | fun setImmutability(immutable: Boolean): Symbol { 21 | this.immutable = immutable 22 | 23 | return this 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/value/table/Table.kt: -------------------------------------------------------------------------------- 1 | package spritz.value.table 2 | 3 | import spritz.interpreter.context.Context 4 | import spritz.lexer.position.Position 5 | import spritz.value.Value 6 | import spritz.value.table.result.Result 7 | 8 | /** 9 | * Holds values. 10 | * 11 | * @author surge 12 | * @since 17/03/2023 13 | */ 14 | class Table(val parent: Table? = null) { 15 | 16 | // used if we don't want to get symbols the normal way 17 | var getOverride: (Table.(String?, (Value?) -> Boolean, Boolean, Data) -> Result)? = null 18 | 19 | // used if we don't want to set symbols the normal way 20 | var setOverride: (Table.(Symbol) -> Result)? = null 21 | 22 | // a list of symbols 23 | val symbols = mutableListOf() 24 | 25 | /** 26 | * Overrides the [get] method that is found in [TableAccessor]. 27 | * @return This instance. 28 | */ 29 | fun overrideGet(get: (Table.(String?, (Value?) -> Boolean, Boolean, Data) -> Result)): Table { 30 | this.getOverride = get 31 | 32 | return this 33 | } 34 | 35 | /** 36 | * Overrides the [set] method that is found in [TableAccessor]. 37 | * @return This instance. 38 | */ 39 | fun overrideSet(set: (Table.(Symbol) -> Result)): Table { 40 | this.setOverride = set 41 | 42 | return this 43 | } 44 | 45 | /** 46 | * Data class that holds [start] and [end] positions, as well as [context]. 47 | */ 48 | data class Data(val start: Position, val end: Position, val context: Context) 49 | 50 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/value/table/TableAccessor.kt: -------------------------------------------------------------------------------- 1 | package spritz.value.table 2 | 3 | import spritz.error.interpreting.DualDeclarationError 4 | import spritz.error.interpreting.UndefinedReferenceError 5 | import spritz.interpreter.context.Context 6 | import spritz.lexer.position.LinkPosition 7 | import spritz.lexer.position.Position 8 | import spritz.value.Value 9 | import spritz.value.table.result.Result 10 | 11 | /** 12 | * Used for interacting with tables. 13 | * 14 | * @author surge 15 | * @since 24/03/2023 16 | */ 17 | class TableAccessor(private val table: Table) { 18 | 19 | // the identifier we want to fetch 20 | private var identifier: String? = null 21 | 22 | // any filter we want to apply 23 | private var predicate: (Value?) -> Boolean = { true } 24 | 25 | // if we only want to get a value from the top level of symbols, and exclude parents. 26 | private var top: Boolean = false 27 | 28 | // if we want to set the value to be immutable 29 | private var immutable: Boolean = false 30 | 31 | /** 32 | * Sets the [identifier] we want to fetch. 33 | * @return This instance. 34 | */ 35 | fun identifier(identifier: String?): TableAccessor { 36 | this.identifier = identifier 37 | return this 38 | } 39 | 40 | /** 41 | * Sets the filter to apply. 42 | * @return This instance. 43 | */ 44 | fun predicate(predicate: (Value?) -> Boolean): TableAccessor { 45 | this.predicate = predicate 46 | return this 47 | } 48 | 49 | /** 50 | * Sets the [top] state. 51 | * @return This instance. 52 | */ 53 | fun top(top: Boolean): TableAccessor { 54 | this.top = top 55 | return this 56 | } 57 | 58 | /** 59 | * Sets the [immutable] state. 60 | * @return This instance. 61 | */ 62 | fun immutable(immutable: Boolean): TableAccessor { 63 | this.immutable = immutable 64 | return this 65 | } 66 | 67 | /** 68 | * Finds the value with the filters that have been applied. 69 | * @return A result which either contains a [Value] or a [spritz.error.Error]. 70 | */ 71 | fun find(start: Position = LinkPosition(), end: Position = LinkPosition(), context: Context = Context("link")): Result { 72 | if (table.getOverride != null) { 73 | return table.getOverride!!(table, identifier, predicate, top, Table.Data(start, end, context)) 74 | } 75 | 76 | val existing = table.symbols.filter { 77 | if (identifier != null && identifier != it.name) { 78 | return@filter false 79 | } 80 | 81 | return@filter predicate(it.value) 82 | }.firstOrNull() 83 | 84 | if (existing != null) { 85 | return Result(existing.value, null) 86 | } 87 | 88 | if (table.parent != null && !top) { 89 | return TableAccessor(table.parent) 90 | .identifier(this.identifier) 91 | .predicate(this.predicate) 92 | .top(this.top) 93 | .find(start, end, context) 94 | } 95 | 96 | return Result(null, UndefinedReferenceError( 97 | "'${identifier}' was not found", 98 | start, 99 | end, 100 | context 101 | )) 102 | } 103 | 104 | /** 105 | * Finds and sets the value with the filters that have been applied. 106 | * @return A result which either contains a [Value] or a [spritz.error.Error]. 107 | */ 108 | fun set(value: Value, declaration: Boolean = true, forced: Boolean = false, data: Table.Data = Table.Data(LinkPosition(), LinkPosition(), Context("link"))): Result { 109 | val symbol = Symbol(this.identifier!!, value, data.start, data.end) 110 | 111 | if (table.setOverride != null) { 112 | return table.setOverride!!(table, symbol) 113 | } 114 | 115 | if (declaration) { 116 | table.symbols.firstOrNull { it.name == symbol.name && this.predicate.invoke(it.value) }?.let { 117 | if (forced) { 118 | it.value = symbol.value 119 | return Result(symbol.value, null) 120 | } else { 121 | return Result( 122 | null, DualDeclarationError( 123 | "${it.name} was already defined", 124 | it.start, 125 | it.end, 126 | data.context 127 | ) 128 | ) 129 | } 130 | } 131 | 132 | table.symbols.add(symbol) 133 | 134 | return Result(symbol.value, null) 135 | } 136 | 137 | table.symbols.firstOrNull { it.name == symbol.name && predicate.invoke(it.value) }?.let { 138 | it.value = symbol.value 139 | return Result(symbol.value, null) 140 | } 141 | 142 | if (table.parent != null) { 143 | return TableAccessor(table.parent) 144 | .identifier(symbol.name) 145 | .predicate(this.predicate) 146 | .set(value, declaration, forced, data) 147 | } 148 | 149 | return Result( 150 | null, 151 | UndefinedReferenceError( 152 | "'${symbol.name}' was not found in the current scope", 153 | symbol.start, 154 | symbol.end, 155 | data.context 156 | ) 157 | ) 158 | } 159 | 160 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/value/table/result/Result.kt: -------------------------------------------------------------------------------- 1 | package spritz.value.table.result 2 | 3 | import spritz.error.Error 4 | import spritz.value.Value 5 | 6 | /** 7 | * Holds a [value] and an [error] of a find or set operation. 8 | * 9 | * @author surge 10 | * @since 18/03/2023 11 | */ 12 | data class Result(val value: Value?, val error: Error?) -------------------------------------------------------------------------------- /src/main/kotlin/spritz/value/task/DefinedTaskValue.kt: -------------------------------------------------------------------------------- 1 | package spritz.value.task 2 | 3 | import spritz.SpritzEnvironment 4 | import spritz.error.interpreting.TypeMismatchError 5 | import spritz.interpreter.Interpreter 6 | import spritz.interpreter.RuntimeResult 7 | import spritz.interpreter.context.Context 8 | import spritz.lexer.position.Position 9 | import spritz.parser.node.Node 10 | import spritz.util.ANONYMOUS 11 | import spritz.util.RequiredArgument 12 | import spritz.value.NothingValue 13 | import spritz.value.NullValue 14 | import spritz.value.PrimitiveValue 15 | import spritz.value.Value 16 | 17 | /** 18 | * A value representing a task that has been defined in a script. 19 | * 20 | * @author surge 21 | * @since 03/03/2023 22 | */ 23 | class DefinedTaskValue(identifier: String, val arguments: List, val body: Node, val expression: Boolean, val returnType: Value?) : TaskValue(identifier) { 24 | 25 | override fun asJvmValue() = this 26 | 27 | override fun execute(passed: List, start: Position, end: Position, context: Context): RuntimeResult { 28 | val result = RuntimeResult() 29 | val interpreter = Interpreter() 30 | 31 | // we want to allow references to variables that have been defined in the current context 32 | // if this is an anonymous function, so we just check this identifier against the [ANONYMOUS] 33 | // identifier. 34 | val execContext = if (identifier == ANONYMOUS) context else generateExecuteContext() 35 | 36 | // attempt to check and populate the arguments 37 | result.register(this.checkAndPopulate(arguments, passed, start, end, execContext)) 38 | 39 | if (result.shouldReturn()) { 40 | return result 41 | } 42 | 43 | // execute the task 44 | val value = result.register(interpreter.visit(this.body, execContext)) 45 | 46 | if (result.error != null) { 47 | return result 48 | } 49 | 50 | // get the returned value 51 | val returnValue = (if (expression) value else null) ?: (result.returnValue ?: NothingValue().position(start, end)).givenContext(context) 52 | 53 | // make sure the returned value conforms to the given return type. 54 | if (returnValue !is NullValue && returnType != null && !(PrimitiveValue.check(returnValue, returnType) || returnValue.type == returnType.type)) { 55 | return result.failure(TypeMismatchError( 56 | "Returned value did not conform to type '${returnType.type}' (got '${returnValue.type}')", 57 | returnValue.start, 58 | returnValue.end, 59 | context 60 | )) 61 | } 62 | 63 | return result.success(returnValue) 64 | } 65 | 66 | override fun clone(): DefinedTaskValue { 67 | return DefinedTaskValue(identifier, arguments, body, expression, returnType).position(this.start, this.end).givenContext(this.context) as DefinedTaskValue 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/value/task/JvmTaskValue.kt: -------------------------------------------------------------------------------- 1 | package spritz.value.task 2 | 3 | import spritz.api.CallData 4 | import spritz.interpreter.RuntimeResult 5 | import spritz.interpreter.context.Context 6 | import spritz.lexer.position.Position 7 | import spritz.util.RequiredArgument 8 | import spritz.value.Value 9 | import java.lang.reflect.Method 10 | 11 | /** 12 | * A value representing a JVM method. 13 | * 14 | * @author surge 15 | * @since 03/03/2023 16 | */ 17 | class JvmTaskValue(identifier: String, val method: Method, val invoke: (functionData: CallData) -> RuntimeResult, val arguments: List) : TaskValue(identifier, "Jvm Task") { 18 | 19 | override fun asJvmValue() = method 20 | 21 | override fun execute(passed: List, start: Position, end: Position, context: Context): RuntimeResult { 22 | val result = RuntimeResult() 23 | 24 | // check arguments 25 | result.register(this.check(this.arguments, passed, start, end, context)) 26 | 27 | if (result.shouldReturn()) { 28 | return result 29 | } 30 | 31 | // invoke method 32 | val value = result.register(invoke(CallData(this.start, this.end, context, passed, this))) 33 | 34 | if (result.shouldReturn()) { 35 | return result 36 | } 37 | 38 | return result.success(value) 39 | } 40 | 41 | override fun clone(): Value { 42 | return JvmTaskValue(this.identifier, this.method, this.invoke, this.arguments) 43 | .position(this.start, this.end) 44 | .givenContext(this.context) 45 | } 46 | 47 | override fun toString() = "(Jvm Task: $identifier)" 48 | 49 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/value/task/TaskValue.kt: -------------------------------------------------------------------------------- 1 | package spritz.value.task 2 | 3 | import spritz.error.Error 4 | import spritz.error.interpreting.CallArgumentMismatchError 5 | import spritz.error.interpreting.TypeMismatchError 6 | import spritz.interpreter.RuntimeResult 7 | import spritz.interpreter.context.Context 8 | import spritz.lexer.position.Position 9 | import spritz.util.RequiredArgument 10 | import spritz.value.NullValue 11 | import spritz.value.PrimitiveValue 12 | import spritz.value.Value 13 | import spritz.value.table.Table 14 | import spritz.value.table.TableAccessor 15 | 16 | /** 17 | * A value representing a task. 18 | * 19 | * @author surge 20 | * @since 18/03/2023 21 | */ 22 | abstract class TaskValue(identifier: String, type: String = "task") : Value(type, identifier) { 23 | 24 | abstract override fun asJvmValue(): Any 25 | 26 | /** 27 | * Generates a new context which we can execute from. 28 | */ 29 | protected fun generateExecuteContext(): Context { 30 | val context = Context(this.identifier, this.context, this.start) 31 | context.table = Table(context.parent!!.table) 32 | return context 33 | } 34 | 35 | /** 36 | * Checks whether the [given] arguments are valid for the [required] arguments. 37 | * @return A [RuntimeResult] that contains an optional error. 38 | */ 39 | protected fun check(required: List, given: List, start: Position, end: Position, context: Context): RuntimeResult { 40 | val result = RuntimeResult() 41 | 42 | // too many arguments 43 | if (given.size > required.size) { 44 | return result.failure(CallArgumentMismatchError( 45 | "Too many arguments passed when calling '$identifier', expected ${required.size}, got ${given.size}", 46 | start, 47 | end, 48 | context 49 | )) 50 | } 51 | 52 | // too little arguments 53 | if (given.size < required.size) { 54 | return result.failure(CallArgumentMismatchError( 55 | "Too little arguments passed when calling '$identifier', expected ${required.size}, got ${given.size}", 56 | start, 57 | end, 58 | context 59 | )) 60 | } 61 | 62 | // check types 63 | given.forEachIndexed { index, givenValue -> 64 | val matched = required[index] 65 | 66 | // any type 67 | if (matched.type == null || matched.type.type == "any") { 68 | return@forEachIndexed 69 | } 70 | 71 | // if the given value isn't null, and it doesn't match the correct type 72 | if (givenValue !is NullValue && !(PrimitiveValue.check(givenValue, matched.type) || givenValue.type == matched.type.type)) { 73 | return result.failure(TypeMismatchError( 74 | "Given value did not conform to type '${matched.type.type}' (got '${givenValue.type}')", 75 | givenValue.start, 76 | givenValue.end, 77 | context 78 | )) 79 | } 80 | } 81 | 82 | return result.success(null) 83 | } 84 | 85 | /** 86 | * Adds the [given] arguments to the given [context]. 87 | * @return An optional error, if we couldn't successfully set the argument. 88 | */ 89 | private fun populate(required: List, given: List, context: Context): Error? { 90 | given.forEachIndexed { index, passedArgument -> 91 | val matched = required[index] 92 | 93 | passedArgument.givenContext(context) 94 | 95 | val result = TableAccessor(context.table) 96 | .identifier(matched.name.value.toString()) 97 | .immutable(true) 98 | .set(passedArgument, declaration = true, data = Table.Data(passedArgument.start, passedArgument.end, context)) 99 | 100 | if (result.error != null) { 101 | return result.error 102 | } 103 | } 104 | 105 | return null 106 | } 107 | 108 | /** 109 | * Calls the [check] and [populate] methods. 110 | * @return A [RuntimeResult] that contains any error we may have run into. 111 | */ 112 | protected fun checkAndPopulate(required: List, given: List, start: Position, end: Position, context: Context): RuntimeResult { 113 | val result = RuntimeResult() 114 | 115 | // check arguments 116 | result.register(this.check(required, given, start, end, context)) 117 | 118 | if (result.shouldReturn()) { 119 | return result 120 | } 121 | 122 | // populate arguments 123 | val error = this.populate(required, given, context) 124 | 125 | if (error != null) { 126 | return result.failure(error) 127 | } 128 | 129 | return result.success(null) 130 | } 131 | 132 | override fun toString() = "($type: $identifier)" 133 | 134 | } -------------------------------------------------------------------------------- /src/main/kotlin/spritz/warning/Warning.kt: -------------------------------------------------------------------------------- 1 | package spritz.warning 2 | 3 | import spritz.lexer.position.Position 4 | 5 | /** 6 | * A warning that has been produced during parsing. Contains [details] of 7 | * the warning as well as the [start] of where the warning was produced. 8 | * 9 | * @author surge 10 | * @since 10/03/2023 11 | */ 12 | open class Warning(val details: String, val start: Position) { 13 | 14 | override fun toString() = "Warning: line ${start.line + 1}: $details" 15 | 16 | } -------------------------------------------------------------------------------- /src/test/kotlin/MiscTests.kt: -------------------------------------------------------------------------------- 1 | import jvmlink.ClassTesting 2 | import jvmlink.ClassWithEnum 3 | import spritz.SpritzEnvironment 4 | import spritz.api.Config 5 | import spritz.value.table.TableAccessor 6 | import spritz.value.task.DefinedTaskValue 7 | import java.io.File 8 | 9 | /** 10 | * @author surge 11 | * @since 16/03/2023 12 | */ 13 | object MiscTests { 14 | 15 | @JvmStatic 16 | fun main(args: Array) { 17 | val spritzEnvironment = SpritzEnvironment(Config(debug = false)) 18 | .putClass("ClsWithEnum", ClassWithEnum::class.java) 19 | .putInstance("ClassTesting", ClassTesting()) 20 | 21 | spritzEnvironment.evaluate(File("examples/for_loop.sz")) 22 | 23 | val main = TableAccessor(spritzEnvironment.global) 24 | .identifier("main") 25 | .predicate { it is DefinedTaskValue && it.arguments.isEmpty() } 26 | .find() 27 | .value 28 | ?.execute(arrayListOf())?.also { 29 | if (it.error != null) { 30 | println(it.error) 31 | } 32 | } ?: run { 33 | println("`main` function not found") 34 | } 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /src/test/kotlin/jvmlink/ClassTesting.kt: -------------------------------------------------------------------------------- 1 | package jvmlink 2 | 3 | import spritz.api.Coercion 4 | import spritz.api.annotations.Identifier 5 | import spritz.value.Value 6 | 7 | /** 8 | * @author surge 9 | * @since 18/03/2023 10 | */ 11 | class ClassTesting { 12 | 13 | @Identifier("test") 14 | fun test(value: String) { 15 | println(value) 16 | } 17 | 18 | @Identifier("boolean_setting") 19 | fun registerBoolean(name: String, description: String, default: Boolean): Value { 20 | return Coercion.IntoSpritz.coerce(default) 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /src/test/kotlin/jvmlink/ClassWithEnum.kt: -------------------------------------------------------------------------------- 1 | package jvmlink 2 | 3 | /** 4 | * @author surge 5 | * @since 27/03/2023 6 | */ 7 | class ClassWithEnum(val test: TestEnum) { 8 | 9 | enum class TestEnum(@JvmField val a: Int) { 10 | MEMBER_ONE(2), 11 | MEMBER_TWO(5); 12 | 13 | fun test() { 14 | println(a) 15 | } 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /src/test/kotlin/jvmlink/InstanceTesting.kt: -------------------------------------------------------------------------------- 1 | package jvmlink 2 | 3 | /** 4 | * @author surge 5 | * @since 18/03/2023 6 | */ 7 | class InstanceTesting { 8 | 9 | @JvmField val a = 5 10 | 11 | } -------------------------------------------------------------------------------- /workspace/demos/cli.sz: -------------------------------------------------------------------------------- 1 | import "std" as std 2 | 3 | class Command(name: string, arguments: list, execute: task) 4 | 5 | task main { 6 | const variables = {} 7 | 8 | const commands = [ 9 | Command("quit", ["status"]) lambda { args: list -> 10 | try -> std::exit_process(args::get(0)::int()) 11 | catch exception -> std::printlnf("Failed to execute 'quit': %", exception::details) 12 | }, 13 | 14 | Command("echo", null) lambda { args: list -> 15 | std::println(args::join(" ", "%")) 16 | }, 17 | 18 | Command("store", ["identifier", "value"]) lambda { args: list -> 19 | variables::set(args::get(0), args::get(1)) 20 | std::printlnf("Set variable '%' to '%'", [args::get(0), args::get(1)]) 21 | } 22 | ] 23 | 24 | while (true) { 25 | const input = std::readln()::split(" ") 26 | 27 | if (input::is_empty()) { 28 | continue 29 | } 30 | 31 | mut arguments = input::after(1) 32 | 33 | const new = [] 34 | const br = false 35 | 36 | for (argument : arguments) { 37 | if (argument::char_at(0) == "$") { 38 | try -> new::add(variables::get(argument::after(1))) 39 | catch exception { 40 | std::printlnf("Couldn't find variable '%'", argument::after(1)) 41 | br = true 42 | break 43 | } 44 | } else { 45 | new::add(argument) 46 | } 47 | } 48 | 49 | if (br) { 50 | continue 51 | } 52 | 53 | arguments = new 54 | 55 | mut found = false 56 | 57 | for (command : commands) { 58 | if (input::get(0) ~= command::name) { 59 | if (command::arguments == null || arguments::length() == command::arguments::length()) { 60 | command::execute(arguments) 61 | } else { 62 | std::printlnf("Invalid arguments for '%'. Expected '% %'", [command::name, command::name, command::arguments::join(" ", "[%]")]) 63 | } 64 | 65 | found = true 66 | break 67 | } 68 | } 69 | 70 | if (!found) { 71 | std::println("Command not found!") 72 | } 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /workspace/examples/dict.sz: -------------------------------------------------------------------------------- 1 | import "std" as std 2 | 3 | task main { 4 | const d = { 5 | "member_one": 2, 6 | "member_two": "abc", 7 | "member_three": { 8 | "member_three_one": { 9 | "member_three_one_one": { 10 | "member_three_one_one_one": { 11 | "member_three_one_one_one": "hiiiii :3" 12 | } 13 | } 14 | } 15 | } 16 | } 17 | 18 | std::println(d) 19 | std::println(d::get("member_one")) 20 | std::println(d::get("member_three")) 21 | return -1 22 | } -------------------------------------------------------------------------------- /workspace/examples/enums.sz: -------------------------------------------------------------------------------- 1 | enum TestEnum(a: int, b: int) { 2 | MEMBER_ONE(1, 2), 3 | MEMBER_TWO(3, 4) 4 | 5 | { 6 | task add = a + b 7 | } 8 | } -------------------------------------------------------------------------------- /workspace/examples/for_loop.sz: -------------------------------------------------------------------------------- 1 | import "std" as std 2 | import "system" as system 3 | 4 | task main { 5 | const init = system::current_time_millis() 6 | 7 | for (i : std::int_range(0, 1000, 1)) { 8 | std::println(i) 9 | } 10 | 11 | std::println("Time: " + (system::current_time_millis() - init)) 12 | 13 | return 0 14 | } -------------------------------------------------------------------------------- /workspace/examples/importing.sz: -------------------------------------------------------------------------------- 1 | import "std" as std 2 | import "examples/enums.sz" as Enums 3 | 4 | task main { 5 | std::println(Enums::TestEnum::MEMBER_ONE::add()) 6 | 7 | return 0 8 | } -------------------------------------------------------------------------------- /workspace/examples/lambdas.sz: -------------------------------------------------------------------------------- 1 | import "std" as std 2 | 3 | task execute_lambda( 4 | l: task // lambdas are considered tasks, and are executed in the same way 5 | ) { 6 | l() 7 | } 8 | 9 | task main { 10 | const t = 2 11 | 12 | const a = lambda -> { 13 | std::println("hello, world!") 14 | std::println(t) 15 | } 16 | 17 | a() 18 | 19 | execute_lambda(lambda -> { 20 | std::println("executed!") 21 | }) 22 | 23 | return 0 24 | } -------------------------------------------------------------------------------- /workspace/examples/natives.sz: -------------------------------------------------------------------------------- 1 | native "java.lang.Math" as maths 2 | native "java.lang.String" as str 3 | native "jvmlink.ClassTesting" as test 4 | 5 | import "std" as std 6 | 7 | task main { 8 | std::println(maths::sin(2)) 9 | 10 | const a = str("test") 11 | const b = test(3) 12 | 13 | return 0 14 | } -------------------------------------------------------------------------------- /workspace/examples/safe_call.sz: -------------------------------------------------------------------------------- 1 | import "std" as std 2 | 3 | task main { 4 | const a = null 5 | 6 | std::println(a::length?()) 7 | std::println("hello, world!") 8 | 9 | return 0 10 | } -------------------------------------------------------------------------------- /workspace/examples/try_catch.sz: -------------------------------------------------------------------------------- 1 | import "std" as std 2 | 3 | task main { 4 | try { 5 | std::println(2 + "t") 6 | } catch e { 7 | std::println(e::name) 8 | } 9 | 10 | try -> 2 + "t" 11 | catch exception -> std::println("Error occurred!") 12 | } --------------------------------------------------------------------------------