├── .gitignore ├── .idea ├── .gitignore ├── compiler.xml ├── discord.xml ├── gradle.xml ├── jarRepositories.xml ├── misc.xml ├── modules │ └── oasiskt.main.iml ├── uiDesigner.xml └── vcs.xml ├── .readthedocs.yaml ├── LICENSE ├── README.md ├── build.gradle.kts ├── docs ├── Conditionals.rst ├── Makefile ├── OasisLexer.py ├── Prototypes.rst ├── _static │ ├── Hermit-Regular.otf │ └── font.css ├── conf.py ├── functions.rst ├── getting-started.rst ├── index.rst ├── make.bat ├── requirements.txt └── syntax.rst ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src ├── main ├── kotlin │ └── me │ │ └── snwy │ │ └── oasis │ │ ├── AbstractSyntaxTree.kt │ │ ├── ContextVisitor.kt │ │ ├── Creator.kt │ │ ├── Environment.kt │ │ ├── InternalException.kt │ │ ├── Interpreter.kt │ │ ├── KotlinFunction.kt │ │ ├── LanguageServer.kt │ │ ├── Main.kt │ │ ├── OasisCallable.kt │ │ ├── OasisException.kt │ │ ├── OasisFunction.kt │ │ ├── OasisPrototype.kt │ │ ├── OasisTuple.kt │ │ ├── Optimizer.kt │ │ ├── ParseException.kt │ │ ├── Parser.kt │ │ ├── PartialFunc.kt │ │ ├── RelativeExpression.kt │ │ ├── Return.kt │ │ ├── RuntimeError.kt │ │ ├── Scanner.kt │ │ ├── Token.kt │ │ ├── TokenType.kt │ │ ├── experimental │ │ └── PythonTranspiler.kt │ │ └── standardLibrary │ │ ├── Api.kt │ │ ├── Async.kt │ │ ├── Func.kt │ │ ├── HashMap.kt │ │ ├── Import.kt │ │ ├── Io.kt │ │ ├── Json.kt │ │ ├── List.kt │ │ ├── Math.kt │ │ ├── Module.kt │ │ ├── Panic.kt │ │ ├── Plot.kt │ │ ├── Prototype.kt │ │ ├── PrototypeDeserializer.kt │ │ ├── PrototypeSerializer.kt │ │ ├── RangeAndMap.kt │ │ ├── Sockets.kt │ │ ├── StandardLibrary.kt │ │ ├── String.kt │ │ ├── Sys.kt │ │ ├── Time.kt │ │ └── Type.kt └── resources │ └── libpy.oa └── test └── kotlin └── me └── snwy └── oasis └── OasisEnvironmentTest.kt /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build 3 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/discord.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | -------------------------------------------------------------------------------- /.idea/modules/oasiskt.main.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the version of Python and other tools you might need 9 | build: 10 | os: ubuntu-20.04 11 | tools: 12 | python: "3.9" 13 | # You can also specify other tool versions: 14 | # nodejs: "16" 15 | # rust: "1.55" 16 | # golang: "1.17" 17 | 18 | # Build documentation in the docs/ directory with Sphinx 19 | sphinx: 20 | configuration: docs/conf.py 21 | 22 | # If using Sphinx, optionally build your docs in additional formats such as PDF 23 | # formats: 24 | # - pdf 25 | 26 | # Optionally declare the Python requirements required to build your docs 27 | python: 28 | install: 29 | - requirements: docs/requirements.txt -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # oasis 2 | the Oasis programming language 3 | 4 | Oasis is a prototype-based scripting language inspired by Lua, Python, and Self. 5 | The main Oasis interpreter, oasiskt, is written in Kotlin. 6 | 7 | To use Oasis, you must have Java (>= 16) installed. 8 | 9 | [Examples](https://github.com/oasis-lang/examples) 10 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 | 3 | plugins { 4 | kotlin("jvm") version "1.6.10" 5 | application 6 | } 7 | 8 | group = "me.snwy" 9 | version = "1.0-SNAPSHOT" 10 | 11 | repositories { 12 | mavenCentral() 13 | } 14 | 15 | dependencies { 16 | implementation("com.google.code.gson:gson:2.9.0") 17 | implementation("org.jline:jline-builtins:3.21.0") 18 | implementation("org.jline:jline-reader:3.21.0") 19 | implementation("org.jline:jline-terminal:3.21.0") 20 | implementation("org.knowm.xchart:xchart:3.8.1") 21 | implementation("org.eclipse.lsp4j:org.eclipse.lsp4j:0.14.0") 22 | implementation("com.fazecast:jSerialComm:2.9.1") 23 | implementation("org.junit.jupiter:junit-jupiter:5.8.1") 24 | testImplementation(kotlin("test")) 25 | } 26 | 27 | tasks.withType { 28 | kotlinOptions.jvmTarget = "11" 29 | } 30 | 31 | application { 32 | mainClass.set("oasis.MainKt") 33 | } 34 | 35 | tasks.test { 36 | useJUnitPlatform() 37 | } 38 | 39 | tasks.jar { 40 | manifest { 41 | attributes["Main-Class"] = "me.snwy.oasis.MainKt" 42 | } 43 | configurations["compileClasspath"].forEach { file: File -> 44 | from(zipTree(file.absoluteFile)) { 45 | exclude("META-INF/*.RSA", "META-INF/*.DSA", "META-INF/*.SF", "META-INF/*.EC") 46 | } 47 | } 48 | duplicatesStrategy = DuplicatesStrategy.INCLUDE 49 | } 50 | -------------------------------------------------------------------------------- /docs/Conditionals.rst: -------------------------------------------------------------------------------- 1 | ###### 2 | Conditionals and Loops 3 | ###### 4 | 5 | ******** 6 | If Statements 7 | ******** 8 | 9 | An if statement in Oasis is pretty traditional. 10 | 11 | .. code-block:: oasis 12 | 13 | if condition 14 | //do something 15 | end 16 | 17 | if condition 18 | //do something 19 | else 20 | //do something else 21 | end 22 | 23 | if condition 24 | //do something 25 | else if condition 26 | //do something else 27 | else 28 | //do something else 29 | end 30 | 31 | These can also be inlined. 32 | 33 | .. code-block:: oasis 34 | 35 | // You might do this: 36 | let a = nil 37 | if 1 == 1 38 | a = 25 39 | else 40 | a = 42 41 | end 42 | 43 | // However, this is more readable: 44 | let a = if 1 == 1 => 25 else 42 45 | 46 | ******** 47 | 'Is' Statements 48 | ******** 49 | 50 | An ``is`` statement is similar to a switch statement in other languages. 51 | 52 | .. code-block:: oasis 53 | 54 | let x = 3 55 | is x 56 | 1 => 57 | io:print("x is 1") 58 | end 59 | 2 => 60 | io:print("x is 2") 61 | end 62 | 3 => 63 | io:print("x is 3") 64 | end 65 | end 66 | 67 | // prints "x is 3" 68 | 69 | ******** 70 | While Loops 71 | ******** 72 | 73 | While loops are also pretty simple. 74 | 75 | .. code-block:: oasis 76 | 77 | let i = 0 78 | while i < 10 79 | io:print(i) 80 | i = i + 1 81 | end 82 | 83 | // prints 0 1 2 3 4 5 6 7 8 9 84 | 85 | You can use break and continue to control the loop. 86 | 87 | .. code-block:: oasis 88 | 89 | let i = 0 90 | while i < 10 91 | if i == 5 92 | break 93 | end 94 | io:print(i) 95 | i = i + 1 96 | end 97 | 98 | // prints 0 1 2 3 4 99 | 100 | ******** 101 | For Loops 102 | ******** 103 | 104 | The for loop has two forms. 105 | 106 | .. code-block:: oasis 107 | 108 | for i in range(0, 10) 109 | io:print(i) 110 | end 111 | 112 | // prints 0 1 2 3 4 5 6 7 8 9 113 | 114 | for let i = 0 | i < 10 | i = i + 1 115 | io:print(i) 116 | end 117 | 118 | // prints 0 1 2 3 4 5 6 7 8 9 119 | 120 | In the first form, the loop iterates over a prototype that implements ``__iterator``. The second form is a more traditional for loop. 121 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/OasisLexer.py: -------------------------------------------------------------------------------- 1 | from pygments.lexer import RegexLexer 2 | from pygments.token import Text, Comment, Keyword, Name, String, Number, Punctuation, Operator, Generic 3 | from sphinx.highlighting import lexers 4 | 5 | class OasisLexer(RegexLexer): 6 | name = 'Oasis' 7 | aliases = ['oasis'] 8 | filenames = ['*.oa'] 9 | 10 | tokens = { 11 | 'root': [ 12 | (r'//.*?$', Comment.Single), 13 | (r'#.*?$', Comment.Single), 14 | (r'\b(if)\b', Keyword), 15 | (r'\b(else)\b', Keyword), 16 | (r'\b(while)\b', Keyword), 17 | (r'\b(for)\b', Keyword), 18 | (r'\b(break)\b', Keyword), 19 | (r'\b(continue)\b', Keyword), 20 | (r'\b(return)\b', Keyword), 21 | (r'\b(true|false)\b', Keyword.Type), 22 | (r'\b(nil)\b', Keyword.Constant), 23 | (r'\b(let|const|rel)\b', Keyword.Declaration), 24 | (r'\b(proto)\b', Keyword), 25 | (r'\b(fn)\b', Keyword), 26 | (r'\b(end)\b', Keyword), 27 | (r'\b(and)\b', Operator.Word), 28 | (r'\b(or)\b', Operator.Word), 29 | (r'\b(not)\b', Operator.Word), 30 | (r'\b(in)\b', Keyword), 31 | (r'\b(of)\b', Operator.Word), 32 | (r'\b(clone)\b', Operator.Word), 33 | (r'\b(is)\b', Keyword), 34 | (r'\b(test)\b', Keyword), 35 | (r'\b(error)\b', Keyword), 36 | (r'\b(break)\b', Keyword), 37 | (r'\b(continue)\b', Keyword), 38 | (r'\b(do)\b', Keyword), 39 | (r'"[^"]*"', String.Double), 40 | (r'\'.\'', String.Char), 41 | (r'(\+|\-|\*|\/|\%|\=|\=\=|\!\=|\,|\.|\>|\>\=|\<|\<\=|\=\>|\||\|\>|\<\||\?|\:)', Operator), 42 | (r'(\d+\.\d*|\d*\.\d+)([eE][+-]?\d+)?', Number.Float), 43 | (r'\d+([eE][+-]?\d+)?', Number.Integer), 44 | (r'\$[a-zA-Z0-9_]+', Name.Variable), 45 | (r'[a-zA-Z_][a-zA-Z0-9_]*', Name), 46 | (r'(\(|\)|\[|\]|\{|\})', Punctuation), 47 | (r'\s+', Text), 48 | 49 | ], 50 | } 51 | 52 | lexers['oasis'] = OasisLexer() 53 | -------------------------------------------------------------------------------- /docs/Prototypes.rst: -------------------------------------------------------------------------------- 1 | ###### 2 | Prototypes 3 | ###### 4 | 5 | Prototypes are similar to classes, but they are not types, they are literal instances. A prototype is somewhat equivalent to a static class. 6 | A common misconception is that prototypes are classes and that they are types. 7 | For example: 8 | 9 | .. code-block:: oasis 10 | 11 | let foo = proto 12 | a = 1 13 | b = "hello!" 14 | c = fn(x) 15 | return this:a + x 16 | end 17 | end 18 | 19 | io:print(foo:a) // prints 1 20 | io:print(foo:b) // prints "hello!" 21 | io:print(foo:c(2)) // prints 3 22 | 23 | This prototype contains three members: the number 1, the string "hello!", and a function that adds the value of the member "a" to the argument. 24 | 25 | ******** 26 | Inheritance 27 | ******** 28 | 29 | Prototypes can be inherited from. For example: 30 | 31 | .. code-block:: oasis 32 | 33 | let bar = proto : foo 34 | d = "world!" 35 | end 36 | 37 | io:print(bar:a) // prints 1 38 | io:print(bar:b) // prints "hello!" 39 | io:print(bar:c(2)) // prints 3 40 | io:print(bar:d) // prints "world!" 41 | 42 | Now, let's change something in foo: 43 | 44 | .. code-block:: oasis 45 | 46 | foo:a = 2 47 | io:print(bar:a) // prints 2 48 | 49 | This is because foo is the prototype of bar. Bar is a child of foo, so it inherits the changes made to foo. 50 | Bar is not an instance of foo, as prototypes cannot be instantiated, because they are not types. 51 | 52 | You can change the prototype of a prototype. Let's say we have 3 prototypes, A, B, and C. 53 | 54 | .. code-block:: oasis 55 | 56 | let A = proto 57 | a_value = 1 58 | end 59 | 60 | let B = proto 61 | b_value = 2 62 | end 63 | 64 | let C = proto 65 | c_value = 3 66 | end 67 | 68 | All of these prototypes are currently independent of each other. 69 | 70 | We can change the prototype of C to A: 71 | 72 | .. code-block:: oasis 73 | 74 | prototype:setPrototypeOf(C, A) 75 | 76 | io:print(C:a_value) // prints 1 77 | io:print(C:b_value) // undefined, as B is not the prototype of C 78 | io:print(C:c_value) // prints 3 79 | 80 | And, the changes made to A are also applied to C. 81 | 82 | .. code-block:: oasis 83 | 84 | A:another_value = 4 85 | io:print(C:another_value) // prints 4 86 | 87 | Now, we can change the prototype of C to B: 88 | 89 | .. code-block:: oasis 90 | 91 | prototype:setPrototypeOf(C, B) 92 | 93 | io:print(C:another_value) // undefined, as A is no longer the prototype of C 94 | io:print(C:a_value) // undefined, as A is no longer the prototype of C 95 | io:print(C:b_value) // prints 2 96 | io:print(C:c_value) // prints 3 97 | 98 | The rule is that if a prototype is modified, all of its children are also modified. However, if a child is modified, its parent is not modified. 99 | When the parent of a prototype is changed, all of the values in the parent are removed from the child, and all of the values in the new parent are added to the child. 100 | 101 | ******** 102 | Cloning 103 | ******** 104 | 105 | Prototypes can be cloned. The clone of a prototype is entirely detached from the original. Any changes made to the clone will not affect the original, and vice versa. 106 | 107 | .. code-block:: oasis 108 | 109 | let foo = proto 110 | a = 1 111 | b = "hello!" 112 | end 113 | 114 | let bar = clone foo 115 | bar:a = 2 116 | io:print(bar:a) // prints 2 117 | 118 | io:print(foo:a) // prints 1 119 | 120 | ******** 121 | Overloads 122 | ******** 123 | 124 | Prototypes can have overloaded functions. For example: 125 | 126 | .. code-block:: oasis 127 | 128 | let foo = proto 129 | a = 5 130 | __add = fn(x) => this:a + x 131 | end 132 | 133 | io:print(foo + 2) // prints 7 134 | 135 | ******** 136 | Overloadable Members 137 | ******** 138 | 139 | **__add, __sub, __mul, __div, __mod, __and, __or** 140 | 141 | Add, subtract, multiply, divide, modulus, logical and, and logical or. 142 | 143 | **__serialize** 144 | 145 | This function is called when the prototype is serialized. It must return a hashmap. 146 | 147 | .. code-block:: oasis 148 | 149 | let foo = proto 150 | a = 5 151 | __serialize = fn 152 | return { 153 | "a" | this:a, 154 | "b" | this:a + 2 155 | } 156 | end 157 | end 158 | 159 | io:print(json:dump(foo)) // prints "{ "a": 5, "b": 7 }" 160 | 161 | **__index** 162 | 163 | This function is called when the prototype is indexed. 164 | 165 | .. code-block:: oasis 166 | 167 | let foo = proto 168 | a = [1, 2, 3] 169 | __index = fn(x) 170 | return this:a:(x) 171 | end 172 | end 173 | 174 | io:print(foo:a) // prints "[1, 2, 3]" 175 | io:print(foo:(2)) // prints "3" 176 | 177 | **__setIndex** 178 | 179 | This function is called when an index of the prototype is assigned. 180 | 181 | .. code-block:: oasis 182 | 183 | let foo = proto 184 | a = [1, 2, 3] 185 | __setIndex = fn(x, y) 186 | this:a:(x) = y 187 | end 188 | end 189 | 190 | io:print(foo:a) // prints "[1, 2, 3]" 191 | foo:(2) = 4 192 | io:print(foo:a) // prints "[1, 2, 4]" 193 | 194 | **__iterator** 195 | 196 | This function is called when the prototype is iterated. It accepts a numeric value for index. 197 | Once the index is out of range, the function must call the `iteratorExhausted` function. 198 | 199 | .. code-block:: oasis 200 | 201 | let foo = proto 202 | a = [1, 2, 3] 203 | __iterator = fn(x) 204 | if x < this:a:size() 205 | return this:a:(x) 206 | else 207 | iteratorExhausted() 208 | end 209 | end 210 | end 211 | 212 | for i in foo 213 | io:print(i) 214 | end 215 | 216 | **toString** 217 | 218 | This function is called when the prototype is converted to a string. 219 | It must return a string. 220 | 221 | .. code-block:: oasis 222 | 223 | let foo = proto 224 | a = 5 225 | toString = fn 226 | return "a value is: " + this:a 227 | end 228 | end 229 | 230 | io:print(foo) // prints "a value is: 5" -------------------------------------------------------------------------------- /docs/_static/Hermit-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oasis-lang/oasis/9c3bed49471267272c9558b8f8b96b6d87262630/docs/_static/Hermit-Regular.otf -------------------------------------------------------------------------------- /docs/_static/font.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: Hermit-Regular; 3 | src: url("Hermit-Regular.otf") format("opentype"); 4 | } 5 | 6 | pre { 7 | font-family: "Hermit-Regular", monospace; 8 | } -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'Oasis' 21 | copyright = '2022, snwy' 22 | author = 'snwy' 23 | 24 | # The full version, including alpha/beta/rc tags 25 | release = '1.0' 26 | 27 | 28 | # -- General configuration --------------------------------------------------- 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = [ 34 | 'OasisLexer' 35 | ] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ['_templates'] 39 | 40 | # List of patterns, relative to source directory, that match files and 41 | # directories to ignore when looking for source files. 42 | # This pattern also affects html_static_path and html_extra_path. 43 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 44 | 45 | 46 | # -- Options for HTML output ------------------------------------------------- 47 | 48 | # The theme to use for HTML and HTML Help pages. See the documentation for 49 | # a list of builtin themes. 50 | # 51 | 52 | html_theme = 'press' 53 | 54 | # Add any paths that contain custom static files (such as style sheets) here, 55 | # relative to this directory. They are copied after the builtin static files, 56 | # so a file named "default.css" will overwrite the builtin "default.css". 57 | #html_static_path = ['_static'] 58 | html_css_files = [ 59 | 'font.css', 60 | ] 61 | html_extra_path = ['/_static/Hermit-Regular.otf'] 62 | def setup(app): 63 | app.add_stylesheet('font.css') 64 | -------------------------------------------------------------------------------- /docs/functions.rst: -------------------------------------------------------------------------------- 1 | ###### 2 | Functions 3 | ###### 4 | 5 | ******** 6 | Basics 7 | ******** 8 | 9 | Functions in Oasis are first-class. That means they can be passed as arguments to other functions, and they can be returned by other functions. 10 | They are just like any other value. 11 | 12 | Numeric and boolean arguments and return values are passed by value; but prototypes, lists, maps, strings, and functions are passed by reference. 13 | 14 | .. code-block:: oasis 15 | 16 | let foo = 12 17 | 18 | foo = fn 19 | io:print("foo function") 20 | end 21 | 22 | let f = fn(g, x) 23 | g(x) 24 | end 25 | 26 | ******** 27 | Forms 28 | ******** 29 | 30 | Functions can be expressed in multiple ways. 31 | 32 | A standard function: 33 | 34 | .. code-block:: oasis 35 | 36 | let foo = fn(x, y) 37 | io:print("I was given " + x + " and " + y + ".") 38 | end 39 | 40 | foo("hello", "world") 41 | 42 | An argumentless function: 43 | 44 | .. code-block:: oasis 45 | 46 | let foo = fn 47 | io:print("I was called.") 48 | end 49 | 50 | foo() 51 | 52 | A lambda function: 53 | 54 | .. code-block:: oasis 55 | 56 | let foo = fn(x, y) => x + y 57 | // these can also take no arguments 58 | let foo = fn => "hello" 59 | 60 | ******** 61 | Blocks 62 | ******** 63 | 64 | If a function's last operand takes a function, you can use block syntax when calling it. 65 | 66 | .. code-block:: oasis 67 | 68 | let foo = fn(x, y, z) 69 | io:print(x + y) 70 | z() 71 | end 72 | 73 | foo(1, 2) do 74 | io:print("hello") 75 | end 76 | 77 | // prints "3 hello" 78 | // One argument is supported, too 79 | 80 | let infinitely = fn(x) 81 | while true 82 | infinitely() 83 | end 84 | end 85 | 86 | infinitely do 87 | io:print("hello") // will forever print "hello" 88 | end 89 | 90 | -------------------------------------------------------------------------------- /docs/getting-started.rst: -------------------------------------------------------------------------------- 1 | Getting Started 2 | =============== 3 | 4 | Welcome to the Oasis programming language! Oasis is a prototype-based, scripting language inspired by Lua, Python, and Self. 5 | The main Oasis interpreter, ``oasiskt``, is written in Kotlin. 6 | 7 | To use Oasis, you must have Java (>= 11) installed. 8 | 9 | ******** 10 | To Install 11 | ******** 12 | 13 | TODO: Windows installer, MacOS installer, Linux packages 14 | 15 | ******** 16 | To Build 17 | ******** 18 | 19 | Clone the repository: 20 | 21 | .. code-block:: 22 | 23 | git clone https://github.com/oasis-lang/oasis 24 | 25 | Enter the Oasis directory and build: 26 | 27 | .. code-block:: 28 | 29 | cd oasis/ 30 | ./gradlew build 31 | 32 | After a little while, you should have a built Jarfile in ``build/libs``. 33 | To run the Jarfile: 34 | 35 | .. code-block:: 36 | 37 | java -jar oasiskt.jar 38 | 39 | You should be prompted with an ``oasis ->`` prompt. Try running something: 40 | 41 | .. code-block:: 42 | 43 | oasis -> 1 + 1 44 | 2 45 | oasis -> io:print("Hello, world!") 46 | Hello, world! 47 | oasis -> 48 | 49 | To use various Oasis tooling, such as the language server, you must define the OASIS_HOME environment variable. 50 | This is a path to the Oasis jarfile. 51 | 52 | * Windows 53 | 54 | See `this page (external site) `_ for more information. OASIS_HOME is a user variable. 55 | 56 | * Linux and MacOS 57 | 58 | See `this page (external site) `_ for more information. -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Oasis documentation master file, created by 2 | sphinx-quickstart on Tue Apr 5 22:50:51 2022. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Oasis's documentation! 7 | ================================= 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | getting-started 14 | syntax 15 | Conditionals 16 | Prototypes 17 | functions 18 | 19 | Indices and tables 20 | ================== 21 | 22 | * :ref:`genindex` 23 | * :ref:`search` 24 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx-press-theme -------------------------------------------------------------------------------- /docs/syntax.rst: -------------------------------------------------------------------------------- 1 | ###### 2 | Syntax 3 | ###### 4 | 5 | The syntax of Oasis is very simple. 6 | 7 | ******** 8 | Variable Definitons 9 | ******** 10 | 11 | To define a variable, use the ``let`` keyword. 12 | 13 | .. code-block:: oasis 14 | 15 | let foo = 1 16 | 17 | To make a constant, use the ``const`` keyword. 18 | 19 | .. code-block:: oasis 20 | 21 | let const foo = 1 22 | 23 | To assign a new value to a defined variable, no keyword is needed. 24 | 25 | .. code-block:: oasis 26 | 27 | let foo = 1 28 | foo = 2 29 | 30 | You can also modify a variable's value by using the different forms of the ``=`` operator. 31 | 32 | .. code-block:: oasis 33 | 34 | let foo = 1 35 | foo = 2 36 | foo += 1 37 | foo -= 1 38 | 39 | **Relative Expressions** 40 | A relative expression is an expression that is evaluated whenever it's used. 41 | It's titled so because it's an expression relative to another. 42 | 43 | .. code-block:: oasis 44 | 45 | let foo = 12 46 | rel bar = foo * 2 47 | 48 | io:print(bar) // 24 49 | foo = 13 50 | io:print(bar) // 26 51 | 52 | A relative expression cannot share a name with a variable. 53 | Relative expressions are 54 | 55 | ******** 56 | Indexing 57 | ******** 58 | 59 | To get a property of something, use ``:``. 60 | For example: 61 | 62 | .. code-block:: oasis 63 | 64 | // JavaScript: 65 | foo.bar // property bar of foo 66 | 67 | // oasis: 68 | foo:bar // property bar of foo 69 | 70 | To get the index of an array, use ``:(index)``. 71 | For example: 72 | 73 | .. code-block:: oasis 74 | 75 | // JavaScript: 76 | foo[12] // element at index 12 of foo 77 | 78 | // oasis: 79 | foo:(12) // element at index 12 of foo 80 | 81 | ******** 82 | Block Statements 83 | ******** 84 | 85 | For most block statements, a marker for the beginning of a block is not necessary. All blocks must end with the ``end`` keyword. 86 | 87 | .. code-block:: oasis 88 | 89 | if 1 == 1 90 | io:print("woah! 1 is equal to 1!!") 91 | end 92 | 93 | if 2 == 2 94 | io:print("woah! 2 is equal to 2!!") 95 | else 96 | io:print("woah! 2 is not equal to 2!!") 97 | end 98 | 99 | // block passed to a function 100 | 5:times do 101 | io:print("hello") // prints "hello" 5 times 102 | end 103 | 104 | ******** 105 | Literals 106 | ******** 107 | 108 | Oasis has string literals, number literals, boolean literals, list literals, dictionary literals, and char literals. 109 | 110 | .. code-block:: oasis 111 | 112 | let foo = "hello" 113 | let bar = 1 114 | let baz = true 115 | let qux = [1, 2, 3] 116 | let quux = {"foo" | "hello", "bar" | 1} 117 | let corge = 'a' 118 | 119 | ******** 120 | Functions 121 | ******** 122 | 123 | Functions only exist in the form of `function literals.` These are practically lambdas. 124 | 125 | .. code-block:: oasis 126 | 127 | let foo = fn(x) 128 | return x * x 129 | end 130 | 131 | // Function with no parameters 132 | 133 | let fooBar = fn 134 | // does something or other 135 | end 136 | 137 | foo(2) // 4 138 | 139 | // You can also pass functions to functions! 140 | 141 | let bar = fn(x, y) 142 | return x(y) 143 | end 144 | 145 | bar(fn(n) return 1 / n end, 5) // 1/5 146 | // alternatively 147 | bar(fn(n) => 1 / n, 5) 148 | 149 | // There is a function shorthand, for single-expression functions. 150 | let square = fn(x) => x * x 151 | 152 | ******** 153 | Prototypes 154 | ******** 155 | 156 | Prototypes also only exist in literal form. 157 | 158 | .. code-block:: oasis 159 | 160 | let foo = proto 161 | x = 2 162 | y = fn(n) => this:x * n 163 | end 164 | 165 | io:print(foo:x) // 2 166 | io:print(foo:n(4)) // 8 167 | 168 | // Prototypes can also inherit 169 | 170 | let bar = proto : foo 171 | z = 5 172 | end 173 | 174 | io:print(bar:x) // 2 175 | io:print(bar:y(3)) // 6 176 | io:print(bar:z) // 5 177 | 178 | You can clone a prototype with the ``clone`` keyword. 179 | 180 | .. code-block:: oasis 181 | 182 | let foo = proto 183 | x = 1 184 | end 185 | 186 | let bar = foo 187 | foo:x = 3 188 | io:print(bar:x) // 3 189 | 190 | let baz = clone foo 191 | foo:x = 5 192 | io:print(foo:x) // 5 193 | io:print(bar:x) // 5 194 | io:print(baz:x) // 3 195 | 196 | ******** 197 | Exceptions 198 | ******** 199 | 200 | To run a block of code and catch any exceptions, use the ``test`` keyword. 201 | The catch code goes in the ``error`` block. 202 | 203 | .. code-block:: oasis 204 | 205 | test 206 | let foo = 1 / 0 207 | error(e) // you can use '_' to ignore the exception 208 | io:print("woah! I caught an exception!") 209 | end 210 | 211 | ******** 212 | Loops 213 | ******** 214 | 215 | For loops have two different forms. 216 | The first one is the traditional for loop. 217 | 218 | .. code-block:: oasis 219 | 220 | for let i = 0 | i < 10 | i += 1 221 | io:print(i) 222 | end 223 | 224 | The second one is the iterator for loop. This is a more modern form of for loop. 225 | 226 | .. code-block:: oasis 227 | 228 | for i in range(0, 10) 229 | io:print(i) 230 | end 231 | 232 | There is also the while loop. 233 | 234 | .. code-block:: oasis 235 | 236 | while true 237 | io:print("woah! I'm in a loop!") 238 | end 239 | 240 | You can also use the ``break`` keyword to break out of a loop. 241 | 242 | .. code-block:: oasis 243 | 244 | while true 245 | io:print("woah! I'm in a loop!") 246 | break 247 | end 248 | 249 | You can also use the ``continue`` keyword to skip the rest of the loop. 250 | 251 | .. code-block:: oasis 252 | 253 | while true 254 | if true 255 | continue 256 | end 257 | io:print("woah! I'm in a loop!") // this will never print 258 | end 259 | 260 | ******** 261 | List Comprehensions 262 | ******** 263 | 264 | List comprehensions are syntatic sugar for mapping a function over a list. 265 | 266 | .. code-block:: oasis 267 | 268 | let foo = [1, 2, 3, 4, 5] 269 | let bar = {fn(i) => i * 2 of foo} // [2, 4, 6, 8, 10] 270 | 271 | ******** 272 | Operators 273 | ******** 274 | 275 | Here's a rundown of all of Oasis's operators. 276 | 277 | **Arithmetic** 278 | 279 | .. code-block:: oasis 280 | 281 | 1 + 2 // addition: 3 282 | 1 - 2 // subtraction: -1 283 | 1 * 2 // multiplication: 2 284 | 1 / 2 // division: 0.5 285 | 1 % 2 // modulus: 1 286 | 287 | **Directional evaluation** 288 | 289 | These are the directional evaluation operators. 290 | They are used to evaluate expressions in a specific direction. 291 | They are always evaluated left-to-right, but depending on the direction of the arrow, it will return the first or last expression. 292 | These are identical to the comma operator in C, but with direction. 293 | 294 | .. code-block:: oasis 295 | 296 | 1 |> 2 |> 3 // right evaluation: 3 297 | 1 <| 2 <| 3 // left evaluation: 1 298 | 299 | **Comparison** 300 | 301 | .. code-block:: oasis 302 | 303 | 1 == 2 // equality: false 304 | 1 != 2 // inequality: true 305 | 1 < 2 // less than: true 306 | 1 > 2 // greater than: false 307 | 1 <= 2 // less than or equal to: true 308 | 1 >= 2 // greater than or equal to: false 309 | 310 | true and true // logical and: true 311 | true or false // logical or: true 312 | not true // logical not: false 313 | 314 | null ? 1 // null coalescing: 1 315 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oasis-lang/oasis/9c3bed49471267272c9558b8f8b96b6d87262630/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.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or 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 UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MSYS* | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /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%" == "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%"=="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 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | rootProject.name = "oasiskt" 3 | 4 | -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/AbstractSyntaxTree.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis 2 | 3 | abstract class Expr(var line: Int, var column: Int) { 4 | interface Visitor { 5 | fun visitLiteral(literal: Literal): T 6 | fun visitAssignment(assignment: AssignmentExpr): T 7 | fun visitProperty(property: Property): T 8 | fun visitFunc(func: Func): T 9 | fun visitFcall(fcall: FCallExpr): T 10 | fun visitBinOp(binop: BinOp): T 11 | fun visitGroup(group: Group): T 12 | fun visitVariable(variable: Variable): T 13 | fun vistPrecomputed(precomputed: Precomputed): T 14 | fun visitProto(proto: Proto): T 15 | fun visitIndexer(indexer: Indexer): T 16 | fun visitList(list: OasisList): T 17 | fun visitNegate(negate: Negate): T 18 | fun visitNew(ref: New): T 19 | fun visitNot(not: Not): T 20 | fun visitListComprehension(listComprehension: ListComprehension): T 21 | fun visitMapLiteral(mapLiteral: MapLiteral): T 22 | fun visitIfExpression(ifExpression: IfExpression): T 23 | fun visitTuple(tuple: Tuple): T 24 | } 25 | 26 | abstract fun accept(visitor: Visitor): T 27 | } 28 | 29 | class Precomputed(@JvmField val hash: Int, line: Int, column: Int) : Expr(line, column) { 30 | override fun accept(visitor: Visitor): T = visitor.vistPrecomputed(this) 31 | override fun toString(): String { 32 | return "Precomputed($hash)" 33 | } 34 | } 35 | 36 | abstract class Stmt(var line: Int, var column: Int) { 37 | interface Visitor { 38 | fun visitLet(let: Let): T 39 | fun visitIfStmt(ifstmt: IfStmt): T 40 | fun visitWhileStmt(whilestmt: WhileStmt): T 41 | fun visitStmtList(stmtlist: StmtList): T 42 | fun visitReturnStmt(retstmt: RetStmt): T 43 | fun visitExprStmt(exprStmt: ExprStmt): T 44 | fun visitIs(is_: Is): T 45 | fun visitTest(test: Test): T 46 | fun visitForLoopTriad(forLoopTriad: ForLoopTriad): T 47 | fun visitForLoopIterator(forLoopIterator: ForLoopIterator): T 48 | fun visitBreakStmt(break_: BreakStmt): T 49 | fun visitContinueStmt(continue_: ContinueStmt): T 50 | fun visitRelStmt(relstmt: RelStmt): T 51 | fun visitDoBlock(doblock: DoBlock): T 52 | } 53 | 54 | abstract fun accept(visitor: Visitor): T 55 | } 56 | 57 | class ExprStmt(@JvmField var expr: Expr, line: Int, column: Int) : Stmt(line, column) { 58 | override fun accept(visitor: Visitor): T { 59 | return visitor.visitExprStmt(this) 60 | } 61 | 62 | override fun toString(): String { 63 | return "ExprStmt($expr) at $line" 64 | } 65 | } 66 | 67 | class BinOp(@JvmField var left: Expr, @JvmField val operator: Token, @JvmField var right: Expr, line: Int, column: Int) : Expr(line, column) { 68 | override fun accept(visitor: Visitor): T { 69 | return visitor.visitBinOp(this) 70 | } 71 | 72 | override fun toString(): String { 73 | return "BinOp($left ${operator.lexeme} $right) at $line" 74 | } 75 | } 76 | 77 | class Literal(@JvmField val value: Any?, line: Int, column: Int) : Expr(line, column) { 78 | override fun accept(visitor: Visitor): T { 79 | return visitor.visitLiteral(this) 80 | } 81 | 82 | override fun toString(): String { 83 | return "Literal($value) at $line" 84 | } 85 | } 86 | 87 | class AssignmentExpr(@JvmField var left: Expr, @JvmField var value: Expr, line: Int, column: Int) : Expr(line, column) { 88 | override fun accept(visitor: Visitor): T { 89 | return visitor.visitAssignment(this) 90 | } 91 | 92 | override fun toString(): String { 93 | return "Assign($left = $value) at $line" 94 | } 95 | } 96 | 97 | class Let(@JvmField val left: ArrayList, @JvmField var value: Expr, line: Int, column: Int, @JvmField val immutable: Boolean = false) : 98 | Stmt(line, column) { 99 | override fun accept(visitor: Visitor): T { 100 | return visitor.visitLet(this) 101 | } 102 | 103 | override fun toString(): String { 104 | return "Let(${left.joinToString(", ") { it.lexeme }} = $value) at $line" 105 | } 106 | } 107 | 108 | class StmtList(@JvmField var stmts: List, line: Int, column: Int) : Stmt(line, column) { 109 | override fun accept(visitor: Visitor): T { 110 | return visitor.visitStmtList(this) 111 | } 112 | 113 | override fun toString(): String { 114 | return "StmtList($stmts) at $line" 115 | } 116 | } 117 | 118 | class Property(@JvmField var obj: Expr, @JvmField val indexer: Token, line: Int, column: Int) : Expr(line, column) { 119 | override fun accept(visitor: Visitor): T { 120 | return visitor.visitProperty(this) 121 | } 122 | 123 | override fun toString(): String { 124 | return "Property($obj : ${indexer.lexeme}) at $line" 125 | } 126 | } 127 | 128 | class Func(@JvmField val operands: List, @JvmField var body: StmtList, line: Int, column: Int) : Expr(line, column) { 129 | override fun accept(visitor: Visitor): T { 130 | return visitor.visitFunc(this) 131 | } 132 | 133 | override fun toString(): String { 134 | return "Func(${operands} : $body) at $line" 135 | } 136 | } 137 | 138 | class FCallExpr(@JvmField var func: Expr, @JvmField var operands: ArrayList, line: Int, column: Int, @JvmField var splat: Boolean = false) : 139 | Expr(line, column) { 140 | override fun accept(visitor: Visitor): T { 141 | return visitor.visitFcall(this) 142 | } 143 | 144 | override fun toString(): String { 145 | return "Call(${func} : ${operands}) at $line" 146 | } 147 | } 148 | 149 | class Group(@JvmField var expr: Expr, line: Int, column: Int) : Expr(line, column) { 150 | override fun accept(visitor: Visitor): T { 151 | return visitor.visitGroup(this) 152 | } 153 | 154 | override fun toString(): String { 155 | return "Group($expr) at $line" 156 | } 157 | } 158 | 159 | class IfStmt(@JvmField var expr: Expr, @JvmField var stmtlist: StmtList, @JvmField var elseBody: StmtList?, line: Int, column: Int) : 160 | Stmt(line, column) { 161 | override fun accept(visitor: Visitor): T { 162 | return visitor.visitIfStmt(this) 163 | } 164 | } 165 | 166 | class WhileStmt(@JvmField var expr: Expr, @JvmField var body: StmtList, line: Int, column: Int) : Stmt(line, column) { 167 | override fun accept(visitor: Visitor): T { 168 | return visitor.visitWhileStmt(this) 169 | } 170 | } 171 | 172 | class Variable(@JvmField val name: Token, line: Int, column: Int) : Expr(line, column) { 173 | override fun accept(visitor: Visitor): T { 174 | return visitor.visitVariable(this) 175 | } 176 | 177 | override fun toString(): String { 178 | return "Variable($name) at $line" 179 | } 180 | } 181 | 182 | class Proto(@JvmField var base: Expr?, @JvmField val body: StmtList, line: Int, column: Int) : Expr(line, column) { 183 | override fun accept(visitor: Visitor): T { 184 | return visitor.visitProto(this) 185 | } 186 | 187 | override fun toString(): String { 188 | return "Variable($base : $body) at $line" 189 | } 190 | } 191 | 192 | class RetStmt(@JvmField var expr: Expr?, line: Int, column: Int) : Stmt(line, column) { 193 | override fun accept(visitor: Visitor): T { 194 | return visitor.visitReturnStmt(this) 195 | } 196 | 197 | override fun toString(): String { 198 | return "Return($expr) at $line" 199 | } 200 | } 201 | 202 | class Indexer(@JvmField var expr: Expr, @JvmField var index: Expr, line: Int, column: Int) : Expr(line, column) { 203 | override fun accept(visitor: Visitor): T { 204 | return visitor.visitIndexer(this) 205 | } 206 | 207 | override fun toString(): String { 208 | return "Index($expr : $index) at $line" 209 | } 210 | } 211 | 212 | class OasisList(@JvmField var exprs: ArrayList, line: Int, column: Int) : Expr(line, column) { 213 | override fun accept(visitor: Visitor): T { 214 | return visitor.visitList(this) 215 | } 216 | } 217 | 218 | class Negate(@JvmField var value: Expr, line: Int, column: Int) : Expr(line, column) { 219 | override fun accept(visitor: Visitor): T { 220 | return visitor.visitNegate(this) 221 | } 222 | } 223 | 224 | class New(@JvmField var expr: Expr, line: Int, column: Int) : Expr(line, column) { 225 | override fun accept(visitor: Visitor): T { 226 | return visitor.visitNew(this) 227 | } 228 | 229 | override fun toString(): String { 230 | return "New(${expr}) at $line" 231 | } 232 | } 233 | 234 | class Not(@JvmField var expr: Expr, line: Int, column: Int) : Expr(line, column) { 235 | override fun accept(visitor: Visitor): T { 236 | return visitor.visitNot(this) 237 | } 238 | 239 | override fun toString(): String { 240 | return "Not(${expr}) at $line" 241 | } 242 | } 243 | 244 | class MapLiteral(@JvmField var exprs: ArrayList>, line: Int, column: Int) : Expr(line, column) { 245 | override fun accept(visitor: Visitor): T { 246 | return visitor.visitMapLiteral(this) 247 | } 248 | } 249 | 250 | class ListComprehension(@JvmField var expr: Expr, @JvmField var inVal: Expr, line: Int, column: Int) : Expr(line, column) { 251 | override fun accept(visitor: Visitor): T { 252 | return visitor.visitListComprehension(this) 253 | } 254 | } 255 | 256 | class IfExpression(@JvmField var expr: Expr, @JvmField var thenExpr: Expr, @JvmField var elseExpr: Expr, line: Int, column: Int) : 257 | Expr(line, column) { 258 | override fun accept(visitor: Visitor): T { 259 | return visitor.visitIfExpression(this) 260 | } 261 | } 262 | 263 | class Tuple(@JvmField var exprs: ArrayList, line: Int, column: Int) : Expr(line, column) { 264 | override fun accept(visitor: Visitor): T { 265 | return visitor.visitTuple(this) 266 | } 267 | } 268 | 269 | class Is(@JvmField var expr: Expr, @JvmField val cases: StmtList, @JvmField val else_: StmtList?, line: Int, column: Int) : Stmt(line, column) { 270 | override fun accept(visitor: Visitor): T { 271 | return visitor.visitIs(this) 272 | } 273 | } 274 | 275 | class Test(@JvmField var block: StmtList, @JvmField var errorBlock: StmtList, @JvmField var errorVar: Token, line: Int, column: Int) : 276 | Stmt(line, column) { 277 | override fun accept(visitor: Visitor): T { 278 | return visitor.visitTest(this) 279 | } 280 | } 281 | 282 | class ForLoopTriad(@JvmField var init: Stmt, @JvmField var cond: Expr, @JvmField var step: Stmt, @JvmField var body: StmtList, line: Int, column: Int) : 283 | Stmt(line, column) { 284 | override fun accept(visitor: Visitor): T { 285 | return visitor.visitForLoopTriad(this) 286 | } 287 | } 288 | 289 | class ForLoopIterator(@JvmField var varName: Expr, @JvmField var iterable: Expr, @JvmField var body: StmtList, line: Int, column: Int) : 290 | Stmt(line, column) { 291 | override fun accept(visitor: Visitor): T { 292 | return visitor.visitForLoopIterator(this) 293 | } 294 | } 295 | 296 | class BreakStmt(line: Int, column: Int) : Stmt(line, column) { 297 | override fun accept(visitor: Visitor): T { 298 | return visitor.visitBreakStmt(this) 299 | } 300 | } 301 | 302 | class ContinueStmt(line: Int, column: Int) : Stmt(line, column) { 303 | override fun accept(visitor: Visitor): T { 304 | return visitor.visitContinueStmt(this) 305 | } 306 | } 307 | 308 | class RelStmt(@JvmField var name: Token, @JvmField var expr: Expr, line: Int, column: Int) : Stmt(line, column) { 309 | override fun accept(visitor: Visitor): T { 310 | return visitor.visitRelStmt(this) 311 | } 312 | } 313 | 314 | class DoBlock(@JvmField var body: StmtList, line: Int, column: Int) : Stmt(line, column) { 315 | override fun accept(visitor: Visitor): T { 316 | return visitor.visitDoBlock(this) 317 | } 318 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/ContextVisitor.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis 2 | 3 | import org.eclipse.lsp4j.Diagnostic 4 | import org.eclipse.lsp4j.DiagnosticSeverity 5 | import org.eclipse.lsp4j.Position 6 | import org.eclipse.lsp4j.Range 7 | 8 | data class Or(val a: A?, val b: B?) 9 | 10 | class PrototypeContext(prototype: Proto, visitor: ContextVisitor) { 11 | val map = mutableMapOf() 12 | 13 | init { 14 | map.putAll(prototype.accept(visitor).a!!) 15 | } 16 | 17 | fun get(key: String): Any? = map[key] 18 | } 19 | 20 | object StdMap { 21 | val map = mutableMapOf() 22 | 23 | init { 24 | map.putAll( 25 | mapOf( 26 | "io" to Unit, 27 | "string" to Unit, 28 | "time" to Unit, 29 | "math" to Unit, 30 | "type" to Unit, 31 | "list" to Unit, 32 | "range" to Unit, 33 | "sys" to Unit, 34 | "map" to Unit, 35 | "json" to Unit, 36 | "prototype" to Unit, 37 | "async" to Unit, 38 | "api" to Unit, 39 | "import" to Unit, 40 | "func" to Unit, 41 | "panic" to Unit, 42 | "plot" to Unit, 43 | ) 44 | ) 45 | } 46 | } 47 | 48 | class ContextVisitor : Expr.Visitor, Unit>>, Stmt.Visitor { 49 | 50 | var variables = hashMapOf() 51 | private val immutables = ArrayList() 52 | private val rels = ArrayList() 53 | 54 | private val prototypes = hashMapOf() 55 | 56 | val diagnostics = arrayListOf() 57 | 58 | private val NOVALUE = Or, Unit>(null, Unit) 59 | 60 | init { 61 | variables.putAll(StdMap.map) 62 | } 63 | 64 | override fun visitLiteral(literal: Literal): Or, Unit> { 65 | return Or(null, Unit) 66 | } 67 | 68 | override fun visitAssignment(assignment: AssignmentExpr): Or, Unit> { 69 | when (assignment.left) { 70 | is Variable -> { 71 | if (immutables.contains((assignment.left as Variable).name.lexeme)) { 72 | diagnostics.add( 73 | Diagnostic( 74 | Range(Position((assignment.left as Variable).name.line, (assignment.left as Variable).name.column), Position( 75 | (assignment.left as Variable).name.line, (assignment.left as Variable).name.column + (assignment.left as Variable).name.lexeme.length)), 76 | "Variable is marked const", 77 | DiagnosticSeverity.Error, 78 | "oasis" 79 | ) 80 | ) 81 | } 82 | if (rels.contains((assignment.left as Variable).name.lexeme)) { 83 | diagnostics.add( 84 | Diagnostic( 85 | Range(Position(0, 0), Position(0, 0)), 86 | "Can't reassign relative expression", 87 | DiagnosticSeverity.Error, 88 | "oasis" 89 | ) 90 | ) 91 | } 92 | assignment.left.accept(this) 93 | when (assignment.value) { 94 | is Proto -> { 95 | prototypes[(assignment.left as Variable).name.lexeme] = 96 | PrototypeContext(assignment.value as Proto, this) 97 | } 98 | } 99 | } 100 | is Property, is Indexer -> { 101 | // nothing to do, all is good ^v^ 102 | } 103 | else -> { 104 | diagnostics.add( 105 | Diagnostic( 106 | Range(Position(0, 0), Position(0, 0)), 107 | "Unsupported assignment", 108 | DiagnosticSeverity.Error, 109 | "oasis" 110 | ) 111 | ) 112 | } 113 | } 114 | return Or(null, Unit) 115 | } 116 | 117 | override fun visitProperty(property: Property): Or, Unit> = NOVALUE 118 | 119 | override fun visitFunc(func: Func): Or, Unit> { 120 | func.operands.forEach { 121 | if (variables.containsKey(it.lexeme)) { 122 | diagnostics.add( 123 | Diagnostic( 124 | Range( 125 | Position(it.line - 1, it.column - 1), 126 | Position(it.line - 1, it.column - 1 + it.lexeme.length) 127 | ), "Variable ${it.lexeme} already defined", DiagnosticSeverity.Warning, "oasis" 128 | ), 129 | ) 130 | } else { 131 | variables[it.lexeme] = object { 132 | override fun toString() = "DefinedToken" 133 | } 134 | } 135 | } 136 | func.body.accept(this) 137 | func.operands.forEach { 138 | if (variables[it.lexeme].toString() == "DefinedToken") { 139 | variables.remove(it.lexeme) 140 | } 141 | } 142 | return NOVALUE 143 | } 144 | 145 | override fun visitFcall(fcall: FCallExpr): Or, Unit> { 146 | fcall.func.accept(this) 147 | fcall.operands.forEach { 148 | it.accept(this) 149 | } 150 | return NOVALUE 151 | } 152 | 153 | override fun visitBinOp(binop: BinOp): Or, Unit> { 154 | binop.left.accept(this) 155 | binop.right.accept(this) 156 | return NOVALUE 157 | } 158 | 159 | override fun visitGroup(group: Group): Or, Unit> { 160 | group.expr.accept(this) 161 | return NOVALUE 162 | } 163 | 164 | override fun visitVariable(variable: Variable): Or, Unit> { 165 | if (!variables.containsKey(variable.name.lexeme)) { 166 | diagnostics.add( 167 | Diagnostic( 168 | Range( 169 | Position(variable.line - 1, variable.column - 1), 170 | Position(variable.line - 1, variable.column - 1 + variable.name.lexeme.length) 171 | ), "Undefined variable ${variable.name.lexeme}", DiagnosticSeverity.Error, "oasis" 172 | ) 173 | ) 174 | } 175 | return NOVALUE 176 | } 177 | 178 | override fun vistPrecomputed(precomputed: Precomputed) = NOVALUE 179 | 180 | override fun visitProto(proto: Proto): Or, Unit> { 181 | val map = hashMapOf() 182 | proto.body.stmts.forEach { 183 | when (it) { 184 | is Let -> { 185 | if (it.left.size > 0) { 186 | diagnostics.add( 187 | Diagnostic( 188 | Range( 189 | Position(it.left[0].line - 1, it.left.last().column - 1), 190 | Position(it.left[0].line - 1, 191 | (it.left.last().column - 1) + it.left.last().lexeme.length 192 | ) 193 | ), "Multiple let definitions not allowed in prototype", DiagnosticSeverity.Error, "oasis" 194 | ) 195 | ) 196 | } 197 | when (it.value) { 198 | is Proto -> { 199 | map[it.left[0].lexeme] = PrototypeContext(it.value as Proto, this) 200 | } 201 | else -> { 202 | map[it.left[0].lexeme] = it.value 203 | } 204 | } 205 | } 206 | is ExprStmt -> { 207 | when (it.expr) { 208 | is AssignmentExpr -> { 209 | when ((it.expr as AssignmentExpr).left) { 210 | is Variable -> { 211 | when ((it.expr as AssignmentExpr).value) { 212 | is Proto -> { 213 | map[((it.expr as AssignmentExpr).left as Variable).name.lexeme] = 214 | PrototypeContext((it.expr as AssignmentExpr).value as Proto, this) 215 | } 216 | else -> { 217 | map[((it.expr as AssignmentExpr).left as Variable).name.lexeme] = 218 | (it.expr as AssignmentExpr).value 219 | } 220 | } 221 | } 222 | } 223 | } 224 | } 225 | } 226 | } 227 | } 228 | return Or(map, null) 229 | } 230 | 231 | override fun visitIndexer(indexer: Indexer): Or, Unit> { 232 | indexer.expr.accept(this) 233 | indexer.index.accept(this) 234 | return NOVALUE 235 | } 236 | 237 | override fun visitList(list: OasisList): Or, Unit> { 238 | list.exprs.forEach { 239 | it.accept(this) 240 | } 241 | return NOVALUE 242 | } 243 | 244 | override fun visitNegate(negate: Negate): Or, Unit> { 245 | negate.value.accept(this) 246 | return NOVALUE 247 | } 248 | 249 | override fun visitNew(ref: New): Or, Unit> { 250 | ref.expr.accept(this) 251 | return NOVALUE 252 | } 253 | 254 | override fun visitNot(not: Not): Or, Unit> { 255 | not.expr.accept(this) 256 | return NOVALUE 257 | } 258 | 259 | override fun visitListComprehension(listComprehension: ListComprehension): Or, Unit> { 260 | listComprehension.expr.accept(this) 261 | listComprehension.inVal.accept(this) 262 | return NOVALUE 263 | } 264 | 265 | override fun visitMapLiteral(mapLiteral: MapLiteral): Or, Unit> { 266 | mapLiteral.exprs.forEach { 267 | it.first.accept(this) 268 | it.second.accept(this) 269 | } 270 | return NOVALUE 271 | } 272 | 273 | override fun visitIfExpression(ifExpression: IfExpression): Or, Unit> { 274 | ifExpression.expr.accept(this) 275 | ifExpression.elseExpr.accept(this) 276 | return NOVALUE 277 | } 278 | 279 | override fun visitTuple(tuple: Tuple): Or, Unit> { 280 | tuple.exprs.forEach { 281 | it.accept(this) 282 | } 283 | return NOVALUE 284 | } 285 | 286 | override fun visitLet(let: Let) { 287 | let.left.forEach { 288 | if (it.lexeme in variables) { 289 | diagnostics.add( 290 | Diagnostic( 291 | Range( 292 | Position(let.line - 1, let.column - 1), 293 | Position(let.value.line - 1, it.column + it.lexeme.length - 1) 294 | ), "Variable already defined", DiagnosticSeverity.Error, "oasis" 295 | ) 296 | ) 297 | } 298 | 299 | } 300 | if (let.immutable) { 301 | let.left.forEach { 302 | immutables.add(it.lexeme) 303 | } 304 | } 305 | let.left.forEach { 306 | variables[it.lexeme] = if (let.value is Proto) { 307 | PrototypeContext(let.value as Proto, this) 308 | } else { 309 | let.value 310 | } 311 | } 312 | } 313 | 314 | override fun visitIfStmt(ifstmt: IfStmt) { 315 | ifstmt.stmtlist.accept(this) 316 | ifstmt.elseBody?.accept(this) 317 | ifstmt.expr.accept(this) 318 | } 319 | 320 | override fun visitWhileStmt(whilestmt: WhileStmt) { 321 | whilestmt.body.accept(this) 322 | whilestmt.expr.accept(this) 323 | } 324 | 325 | override fun visitStmtList(stmtlist: StmtList) { 326 | val oldVariables = variables 327 | variables = hashMapOf() 328 | variables.putAll(oldVariables) 329 | stmtlist.stmts.forEach { 330 | it.accept(this) 331 | } 332 | variables = oldVariables 333 | } 334 | 335 | override fun visitReturnStmt(retstmt: RetStmt) { 336 | retstmt.expr?.accept(this) 337 | } 338 | 339 | override fun visitExprStmt(exprStmt: ExprStmt) { 340 | exprStmt.expr.accept(this) 341 | } 342 | 343 | override fun visitIs(is_: Is) { 344 | is_.expr.accept(this) 345 | is_.cases.accept(this) 346 | is_.else_?.accept(this) 347 | } 348 | 349 | override fun visitTest(test: Test) { 350 | if (variables.containsKey(test.errorVar.lexeme)) { 351 | diagnostics.add( 352 | Diagnostic( 353 | Range( 354 | Position(test.errorVar.line - 1, test.errorVar.column - 1), 355 | Position(test.errorVar.line - 1, test.errorVar.column - 1 + test.errorVar.lexeme.length) 356 | ), "Variable already defined", DiagnosticSeverity.Warning, "oasis" 357 | ) 358 | ) 359 | } else { 360 | variables[test.errorVar.lexeme] = object { 361 | override fun toString() = "DefinedToken" 362 | } 363 | } 364 | test.block.accept(this) 365 | test.errorBlock.accept(this) 366 | if (variables.containsKey(test.errorVar.lexeme)) { 367 | if (variables[test.errorVar.lexeme].toString() == "DefinedToken") { 368 | variables.remove(test.errorVar.lexeme) 369 | } 370 | } 371 | } 372 | 373 | override fun visitForLoopTriad(forLoopTriad: ForLoopTriad) { 374 | forLoopTriad.init.accept(this) 375 | forLoopTriad.cond.accept(this) 376 | forLoopTriad.step.accept(this) 377 | forLoopTriad.body.accept(this) 378 | } 379 | 380 | override fun visitForLoopIterator(forLoopIterator: ForLoopIterator) { 381 | if ((forLoopIterator.varName as Variable).name.lexeme in variables) { 382 | diagnostics.add( 383 | Diagnostic( 384 | Range( 385 | Position(forLoopIterator.line - 1, forLoopIterator.column - 1), 386 | Position( 387 | forLoopIterator.line - 1, 388 | (forLoopIterator.varName as Variable).column - 1 + (forLoopIterator.varName as Variable).name.lexeme.length 389 | ) 390 | ), "Variable already defined", DiagnosticSeverity.Warning, "oasis" 391 | ) 392 | ) 393 | } else { 394 | variables[(forLoopIterator.varName as Variable).name.lexeme] = object { 395 | override fun toString() = "DefinedToken" 396 | } 397 | } 398 | forLoopIterator.body.accept(this) 399 | if (variables[(forLoopIterator.varName as Variable).name.lexeme].toString() == "DefinedToken") { 400 | variables.remove((forLoopIterator.varName as Variable).name.lexeme) 401 | } 402 | } 403 | 404 | override fun visitBreakStmt(break_: BreakStmt) { 405 | // nothing 406 | } 407 | 408 | override fun visitContinueStmt(continue_: ContinueStmt) { 409 | // nothing 410 | } 411 | 412 | override fun visitRelStmt(relstmt: RelStmt) { 413 | if (relstmt.name.lexeme in variables) { 414 | diagnostics.add( 415 | Diagnostic( 416 | Range( 417 | Position(relstmt.line - 1, relstmt.column - 1), 418 | Position(relstmt.line - 1, relstmt.name.column - 1 + relstmt.name.lexeme.length) 419 | ), "Relative expression can't share name with variable", DiagnosticSeverity.Error, "oasis" 420 | ) 421 | ) 422 | } else { 423 | variables[relstmt.name.lexeme] = relstmt.expr 424 | rels.add(relstmt.name.lexeme) 425 | } 426 | } 427 | 428 | override fun visitDoBlock(doblock: DoBlock) { 429 | doblock.body.accept(this) 430 | } 431 | 432 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/Creator.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis 2 | 3 | class OasisEnvironment(var interpreter: Interpreter = Interpreter(), var ast: StmtList? = null) { 4 | var parser: Parser? = null 5 | var scanner: Scanner? = null 6 | 7 | fun eval(code: String) { 8 | val scanner = Scanner(code) 9 | this.scanner = scanner 10 | val tokens = scanner.scanTokens() 11 | val parser = Parser(tokens) 12 | this.parser = parser 13 | val ast = parser.parse() 14 | val program = Optimizer().optimize(ast) 15 | this.ast = program as StmtList 16 | } 17 | fun doExpr(code: String): Any? { 18 | val scanner = Scanner(code) 19 | this.scanner = scanner 20 | val tokens = scanner.scanTokens() 21 | val parser = Parser(tokens) 22 | this.parser = parser 23 | val ast = parser.expression() 24 | val program = Optimizer().optimize(ast) 25 | return interpreter.eval(program) 26 | } 27 | fun define(name: String, value: Any) { 28 | interpreter.environment.define(name.hashCode(), value) 29 | } 30 | fun get(name: String): Any? { 31 | return interpreter.environment.get(name.hashCode()) 32 | } 33 | fun run() { 34 | ast!!.stmts.forEach { 35 | interpreter.execute(it) 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/Environment.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis 2 | 3 | import java.util.* 4 | 5 | class Environment(private val enclosing: Environment? = null) { 6 | @JvmField 7 | var values: MutableMap = HashMap() 8 | 9 | fun define(name: Int, value: Any?) { 10 | if (values.containsKey(name)) { 11 | throw RuntimeError(line, "Variable with this name already defined") 12 | } 13 | values[name] = value 14 | } 15 | 16 | fun define(name: String, value: Any?) { 17 | if (values.containsKey(name.hashCode())) { 18 | throw RuntimeError(line, "Variable with this name already defined") 19 | } 20 | values[name.hashCode()] = value 21 | } 22 | 23 | fun get(name: Int): Any? { 24 | if (name in values) { 25 | return values[name] 26 | } 27 | if (enclosing != null) { 28 | return enclosing.get(name) 29 | } 30 | throw RuntimeError(line, "Undefined variable '${Optimizer.nameMap[name]}'") 31 | } 32 | 33 | fun assign(name: Int, value: Any?) { 34 | if (name in Optimizer.immutables) { 35 | throw RuntimeError(line, "Cannot assign to immutable variable") 36 | } 37 | if (values.containsKey(name)) { 38 | values[name] = value 39 | return 40 | } 41 | if (enclosing != null) { 42 | enclosing.assign(name, value) 43 | return 44 | } 45 | throw RuntimeError(line, "Undefined variable '${Optimizer.nameMap[name]}'") 46 | } 47 | 48 | private fun assign(name: String, value: Any?) { 49 | if (name.hashCode() in Optimizer.immutables) { 50 | throw RuntimeError(line, "Cannot assign to immutable variable") 51 | } 52 | if (values.containsKey(name.hashCode())) { 53 | values[name.hashCode()] = value 54 | return 55 | } 56 | if (enclosing != null) { 57 | enclosing.assign(name, value) 58 | return 59 | } 60 | throw RuntimeError(line, "Undefined variable '${name}'") 61 | } 62 | 63 | override fun toString(): String { 64 | return values.toString() 65 | } 66 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/InternalException.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis 2 | 3 | enum class ExceptionType { 4 | ITERATOR_EMPTY, 5 | BREAK, 6 | CONTINUE, 7 | } 8 | 9 | data class InternalException(val type: ExceptionType) : Exception() -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/Interpreter.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis 2 | 3 | import me.snwy.oasis.standardLibrary.StandardLibrary 4 | import me.snwy.oasis.standardLibrary.base 5 | import me.snwy.oasis.standardLibrary.createHashMap 6 | 7 | var line: Int = 0 8 | 9 | class Interpreter : Expr.Visitor, Stmt.Visitor { 10 | @JvmField 11 | var environment: Environment = Environment() 12 | @JvmField 13 | var retInstance = Return(null) 14 | 15 | init { 16 | StandardLibrary.generateLib(environment, this) 17 | } 18 | 19 | inline fun eval(expr: Expr): Any? { 20 | return expr.accept(this) 21 | } 22 | 23 | inline fun execute(stmt: Stmt) { 24 | line = stmt.line 25 | stmt.accept(this) 26 | } 27 | 28 | private inline fun execute(stmtList: StmtList) { 29 | executeBlock(stmtList, Environment(environment)) 30 | } 31 | 32 | inline fun executeBlock(stmtList: StmtList, env: Environment) { 33 | val previous: Environment = environment 34 | try { 35 | environment = env 36 | stmtList.stmts.forEach { 37 | execute(it) 38 | } 39 | } finally { 40 | environment = previous 41 | } 42 | } 43 | 44 | override fun visitLiteral(literal: Literal): Any? { 45 | return literal.value 46 | } 47 | 48 | override fun visitAssignment(assignment: AssignmentExpr): Any? { 49 | var a: Any? = null 50 | when (assignment.left) { 51 | is Property -> { 52 | a = eval(assignment.value); (eval((assignment.left as Property).obj) as OasisPrototype).set( 53 | (assignment.left as Property).indexer.lexeme, 54 | a 55 | ) 56 | } 57 | is Variable -> { 58 | a = eval(assignment.value) 59 | environment.assign(assignment.left.hashCode(), a) 60 | } 61 | is Precomputed -> { 62 | val left = environment.get((assignment.left as Precomputed).hash) 63 | if (left is RelativeExpression) { 64 | left.expr = assignment.value 65 | } else { 66 | a = eval(assignment.value) 67 | environment.assign((assignment.left as Precomputed).hash, a) 68 | } 69 | } 70 | is Indexer -> { 71 | a = eval(assignment.value) 72 | when (val indexer = eval((assignment.left as Indexer).expr)) { 73 | is ArrayList<*> -> (indexer as ArrayList) [ 74 | (eval((assignment.left as Indexer).index) as Number).toInt() 75 | ] = a 76 | is OasisPrototype -> { 77 | (indexer.get("__setIndex") as OasisCallable).call( 78 | this, 79 | listOf(eval((assignment.left as Indexer).index), eval(assignment.value)) 80 | ) 81 | } 82 | else -> throw RuntimeError(assignment.line, "Cannot index") 83 | } 84 | } 85 | is Tuple -> { 86 | if(!(assignment.left as Tuple).exprs.all { it is Variable || it is Precomputed }) { 87 | throw RuntimeError(assignment.line, "Cannot assign to tuple") 88 | } 89 | val right = eval(assignment.value) 90 | if (right !is Iterable<*>) { 91 | throw RuntimeError(assignment.line, "Right side of tuple assignment must be an iterable") 92 | } 93 | for((i, expr) in (assignment.left as Tuple).exprs.withIndex()) { 94 | environment.assign((expr as? Precomputed)?.hash ?: (expr as Variable).name.lexeme.hashCode(), right.elementAt(i)) 95 | } 96 | } 97 | else -> { 98 | throw RuntimeError(assignment.line, "Cannot assign") 99 | } 100 | } 101 | return a 102 | } 103 | 104 | 105 | override fun visitProperty(property: Property): Any? { 106 | return when (val propertyVal = eval(property.obj)) { 107 | is String -> { 108 | PartialFunc( 109 | (environment.get("string".hashCode()) as OasisPrototype) 110 | .get(property.indexer.lexeme) as OasisCallable, 111 | arrayListOf(propertyVal) 112 | ) 113 | } 114 | is ArrayList<*> -> { 115 | PartialFunc( 116 | (environment.get("list".hashCode()) as OasisPrototype) 117 | .get(property.indexer.lexeme) as OasisCallable, 118 | arrayListOf(propertyVal) 119 | ) 120 | } 121 | is Func -> { 122 | PartialFunc( 123 | (environment.get("func".hashCode()) as OasisPrototype) 124 | .get(property.indexer.lexeme) as OasisCallable, 125 | arrayListOf(propertyVal) 126 | ) 127 | } 128 | is Number -> { 129 | PartialFunc( 130 | (environment.get("math".hashCode()) as OasisPrototype) 131 | .get(property.indexer.lexeme) as OasisCallable, 132 | arrayListOf(propertyVal) 133 | ) 134 | } 135 | is OasisPrototype -> propertyVal.get(property.indexer.lexeme) 136 | else -> throw RuntimeError(line, "$propertyVal is not a fielded object") 137 | } 138 | } 139 | 140 | override fun visitFunc(func: Func): Any { 141 | return OasisFunction(func, environment) 142 | } 143 | 144 | override fun visitFcall(fcall: FCallExpr): Any? { 145 | val callee = (eval(fcall.func) ?: throw RuntimeError(fcall.line, "cannot call null function")) as OasisCallable 146 | val arguments = ArrayList() 147 | if (fcall.splat) { 148 | val splat = eval(fcall.operands[0]) as Iterable<*> 149 | arguments.addAll(splat) 150 | } else { 151 | arguments.addAll(fcall.operands.map { eval(it) }) 152 | } 153 | return callee.call(this, arguments) 154 | } 155 | 156 | private fun isTruthy(thing: Any?): Boolean { 157 | if (thing == null) return false 158 | if (thing == 0.0 || thing == 0) return false 159 | if (thing == false) return false 160 | return true 161 | } 162 | 163 | override fun visitBinOp(binop: BinOp): Any? { 164 | val left = eval(binop.left) 165 | val right = eval(binop.right) 166 | return when (binop.operator.type) { 167 | TokenType.PLUS -> { 168 | when (left) { 169 | is OasisPrototype -> (left.get("__plus") as OasisCallable).call(this, listOf(right)) 170 | is Double -> left + (right as Number).toDouble() 171 | is Int -> left + (right as Number).toInt() 172 | is String -> left + right.toString() 173 | else -> throw RuntimeError(binop.line, "Cannot add") 174 | } 175 | } 176 | TokenType.MINUS -> { 177 | when (left) { 178 | is OasisPrototype -> (left.get("__sub") as OasisCallable).call(this, listOf(right)) 179 | is Double -> left - (right as Number).toDouble() 180 | is Int -> left - (right as Number).toInt() 181 | else -> throw RuntimeError(binop.line, "Cannot subtract") 182 | } 183 | } 184 | TokenType.STAR -> { 185 | when (left) { 186 | is OasisPrototype -> (left.get("__mul") as OasisCallable).call(this, listOf(right)) 187 | is Double -> left * (right as Number).toDouble() 188 | is Int -> left * (right as Number).toInt() 189 | else -> throw RuntimeError(binop.line, "Cannot multiply") 190 | } 191 | } 192 | TokenType.SLASH -> { 193 | when (left) { 194 | is OasisPrototype -> (left.get("__div") as OasisCallable).call(this, listOf(right)) 195 | is Double -> left / (right as Number).toDouble() 196 | is Int -> left / (right as Number).toInt() 197 | else -> { 198 | throw RuntimeError(binop.line, "Cannot divide") 199 | } 200 | } 201 | } 202 | TokenType.EQUAL_EQUAL -> when (left) { 203 | is Class<*> -> left.isAssignableFrom(right!! as Class<*>) || (right as Class<*>).isAssignableFrom(left) 204 | else -> if (left != null) { 205 | left == right 206 | } else { 207 | if (right == null) { 208 | return true 209 | } 210 | false 211 | } 212 | } 213 | TokenType.BANG_EQUAL -> { 214 | if (left != null) { 215 | left != right 216 | } else { 217 | if (right != null) { 218 | return true 219 | } 220 | false 221 | } 222 | } 223 | TokenType.GREATER -> { 224 | when (left) { 225 | is Double -> left > (right as Number).toDouble() 226 | is Int -> left > (right as Number).toInt() 227 | else -> throw RuntimeError(binop.line, "Cannot greater") 228 | } 229 | } 230 | TokenType.GREATER_EQUAL -> { 231 | when (left) { 232 | is Double -> left >= (right as Number).toDouble() 233 | is Int -> left >= (right as Number).toInt() 234 | else -> throw RuntimeError(binop.line, "Cannot greater equal") 235 | } 236 | } 237 | TokenType.LESS -> { 238 | when (left) { 239 | is Double -> left < (right as Number).toDouble() 240 | is Int -> left < (right as Number).toInt() 241 | else -> throw RuntimeError(binop.line, "Cannot less") 242 | } 243 | } 244 | TokenType.LESS_EQUAL -> { 245 | when (left) { 246 | is Double -> left <= (right as Number).toDouble() 247 | is Int -> left <= (right as Number).toInt() 248 | else -> throw RuntimeError(binop.line, "Cannot less equal") 249 | } 250 | } 251 | TokenType.MOD -> { 252 | when (left) { 253 | is Number -> oasisMod(left.toInt(), (right as Number).toInt()) 254 | else -> throw RuntimeError(binop.line, "Cannot mod") 255 | } 256 | } 257 | TokenType.AND -> { 258 | isTruthy(left) && isTruthy(right) 259 | } 260 | TokenType.OR -> { 261 | isTruthy(left) || isTruthy(right) 262 | } 263 | TokenType.LEFT_PIPE -> { 264 | return left 265 | } 266 | TokenType.RIGHT_PIPE -> { 267 | return right 268 | } 269 | TokenType.QUESTION -> { 270 | return left ?: right 271 | } 272 | else -> throw RuntimeError(binop.line, "Invalid operator") 273 | } 274 | } 275 | 276 | override fun visitGroup(group: Group): Any? { 277 | return group.expr.accept(this) 278 | } 279 | 280 | override fun visitVariable(variable: Variable): Any? { 281 | val result = environment.get(variable.name.lexeme.hashCode()) 282 | if (result is RelativeExpression) { 283 | return result.expr.accept(this) 284 | } 285 | return result 286 | } 287 | 288 | override fun visitProto(proto: Proto): Any { 289 | val protoType = 290 | OasisPrototype((if (proto.base != null) eval(proto.base!!) else base) as OasisPrototype?, proto.line, this) 291 | proto.body.stmts.map { 292 | if (it is Let) { 293 | if (it.left.size > 1) throw RuntimeError(it.line, "Multiple let definitions not allowed in prototype") 294 | protoType.set(it.left[0].lexeme, eval(it.value)) 295 | } else 296 | protoType.set( 297 | ((((it as ExprStmt).expr as AssignmentExpr).left as? Variable) ?: throw RuntimeError(it.line, "Invalid prototype definition")).name.lexeme, 298 | eval((it.expr as AssignmentExpr).value) 299 | ) 300 | } 301 | return protoType 302 | } 303 | 304 | override fun visitLet(let: Let) { 305 | if (let.left.size > 1) { 306 | val right = eval(let.value) 307 | if (right !is Iterable<*>) { 308 | throw RuntimeError(let.line, "Right side of multiple let must be iterable") 309 | } 310 | for (i in 0 until let.left.size) { 311 | environment.define(let.left[i].lexeme.hashCode(), right.elementAt(i)) 312 | } 313 | } else { 314 | environment.define((let.left[0].lexeme).hashCode(), eval(let.value)) 315 | } 316 | } 317 | 318 | override fun visitIfStmt(ifstmt: IfStmt): Boolean { 319 | return if (isTruthy(eval(ifstmt.expr))) { 320 | ifstmt.stmtlist.accept(this) 321 | true 322 | } else { 323 | ifstmt.elseBody?.accept(this) 324 | false 325 | } 326 | } 327 | 328 | override fun visitWhileStmt(whilestmt: WhileStmt) { 329 | while (isTruthy(eval(whilestmt.expr))) { 330 | try { 331 | whilestmt.body.accept(this) 332 | } catch (internalException: InternalException) { 333 | when (internalException.type) { 334 | ExceptionType.BREAK -> break 335 | ExceptionType.CONTINUE -> continue 336 | ExceptionType.ITERATOR_EMPTY -> throw internalException 337 | } 338 | } 339 | } 340 | } 341 | 342 | override fun visitStmtList(stmtlist: StmtList) { 343 | execute(stmtlist) 344 | } 345 | 346 | override fun visitReturnStmt(retstmt: RetStmt) { 347 | retInstance.value = if (retstmt.expr != null) eval(retstmt.expr!!) else null 348 | throw retInstance 349 | } 350 | 351 | override fun visitExprStmt(exprStmt: ExprStmt) { 352 | if (repl) { 353 | repl = false 354 | eval(exprStmt.expr).let { if (it !is Unit) println(it) } 355 | } else { 356 | exprStmt.expr.accept(this) 357 | } 358 | } 359 | 360 | override fun visitIndexer(indexer: Indexer): Any? { 361 | return when (val x = eval(indexer.expr)) { 362 | is String -> x[(eval(indexer.index) as Number).toInt()] 363 | is ArrayList<*> -> x[(eval(indexer.index) as Number).toInt()] 364 | is OasisPrototype -> (x.get("__index") as OasisCallable).call(this, listOf(eval(indexer.index))) 365 | else -> throw RuntimeError(indexer.line, "Cannot index") 366 | } 367 | } 368 | 369 | override fun visitList(list: OasisList): Any { 370 | val ev = ArrayList() 371 | list.exprs.map { ev.add(eval(it)) } 372 | return ev 373 | } 374 | 375 | override fun visitNegate(negate: Negate): Any { 376 | return when (val x = eval(negate.value)) { 377 | is Double -> -x 378 | is Int -> -x 379 | is Long -> -x 380 | else -> (x as? Number)?.toInt() ?: throw RuntimeError(negate.line, "Cannot negate") 381 | } 382 | } 383 | 384 | override fun visitNew(ref: New): Any? { 385 | return eval(ref.expr)?.let { 386 | if (it is Cloneable) { 387 | it.javaClass.getMethod("clone").invoke(it) 388 | } else throw RuntimeError(ref.line, "Cannot clone object") 389 | } 390 | } 391 | 392 | override fun visitNot(not: Not): Any { 393 | return !isTruthy(eval(not.expr)) 394 | } 395 | 396 | override fun visitIs(is_: Is) { 397 | is_.cases.stmts.map { 398 | if ((it as IfStmt).accept(this) as Boolean) 399 | return 400 | } 401 | is_.else_?.let { 402 | it.accept(this) 403 | } 404 | } 405 | 406 | override fun visitTest(test: Test) { 407 | try { 408 | test.block.accept(this) 409 | } catch (OasisException: OasisException) { 410 | environment.define(test.errorVar.lexeme.hashCode(), OasisException.value) 411 | test.errorBlock.accept(this) 412 | } catch (RuntimeError: RuntimeError) { 413 | environment.define(test.errorVar.lexeme.hashCode(), RuntimeError.s) 414 | test.errorBlock.accept(this) 415 | } catch (Exception: Exception) { 416 | throw Exception 417 | } finally { 418 | environment.values.remove(test.errorVar.lexeme.hashCode()) 419 | } 420 | } 421 | 422 | override fun vistPrecomputed(precomputed: Precomputed): Any? { 423 | environment.get(precomputed.hash).let { 424 | if (it is RelativeExpression) { 425 | return it.expr.accept(this) 426 | } 427 | return it 428 | } 429 | } 430 | 431 | override fun visitForLoopTriad(forLoopTriad: ForLoopTriad) { 432 | forLoopTriad.init.accept(this) 433 | while (isTruthy(eval(forLoopTriad.cond))) { 434 | try { 435 | forLoopTriad.body.accept(this) 436 | forLoopTriad.step.accept(this) 437 | } catch (internalException: InternalException) { 438 | when (internalException.type) { 439 | ExceptionType.BREAK -> break 440 | ExceptionType.CONTINUE -> continue 441 | ExceptionType.ITERATOR_EMPTY -> throw internalException // keep it going, doesn't matter here 442 | } 443 | } 444 | } 445 | } 446 | 447 | override fun visitForLoopIterator(forLoopIterator: ForLoopIterator) { 448 | when (val iteratorExpr = eval(forLoopIterator.iterable)) { 449 | is OasisPrototype -> { 450 | val iterator = iteratorExpr.get("__iterator") as OasisCallable 451 | var index = 0 452 | while (true) { 453 | try { // Kill this with fire 454 | environment.values[(forLoopIterator.varName as Variable).name.lexeme.hashCode()] = 455 | iterator.call(this, listOf(index)) 456 | } catch (internalException: InternalException) { 457 | when (internalException.type) { 458 | ExceptionType.BREAK -> break 459 | ExceptionType.CONTINUE -> continue 460 | ExceptionType.ITERATOR_EMPTY -> break 461 | } 462 | } 463 | index++ 464 | } 465 | } 466 | is Iterable<*> -> { 467 | for (element in iteratorExpr) { 468 | try { 469 | environment.values[(forLoopIterator.varName as Variable).name.lexeme.hashCode()] = element 470 | forLoopIterator.body.accept(this) 471 | } catch (internalException: InternalException) { 472 | when (internalException.type) { 473 | ExceptionType.BREAK -> break 474 | ExceptionType.CONTINUE -> continue 475 | ExceptionType.ITERATOR_EMPTY -> throw internalException // keep it going, doesn't matter here 476 | } 477 | } 478 | } 479 | } 480 | else -> throw RuntimeError(forLoopIterator.line, "Cannot iterate") 481 | } 482 | } 483 | 484 | override fun visitBreakStmt(break_: BreakStmt) { 485 | throw InternalException(ExceptionType.BREAK) 486 | } 487 | 488 | override fun visitContinueStmt(continue_: ContinueStmt) { 489 | throw InternalException(ExceptionType.CONTINUE) 490 | } 491 | 492 | override fun visitListComprehension(listComprehension: ListComprehension): Any { 493 | when (val iteratorExpr = eval(listComprehension.inVal)) { 494 | is OasisPrototype -> { 495 | val iterator = iteratorExpr.get("__iterator") as OasisCallable 496 | var index = 0 497 | val list = ArrayList() 498 | while (true) { 499 | try { // Kill this with fire 500 | list.add( 501 | (eval(listComprehension.expr) as OasisCallable).call( 502 | this, 503 | listOf(iterator.call(this, listOf(index))) 504 | ) 505 | ) 506 | } catch (internalException: InternalException) { 507 | when (internalException.type) { 508 | ExceptionType.BREAK -> break 509 | ExceptionType.CONTINUE -> continue 510 | ExceptionType.ITERATOR_EMPTY -> break 511 | } 512 | } 513 | index++ 514 | } 515 | return list 516 | } 517 | is Iterable<*> -> { 518 | val list = ArrayList() 519 | for (element in iteratorExpr) { 520 | list.add((eval(listComprehension.expr) as OasisCallable).call(this, listOf(element))) 521 | } 522 | return list 523 | } 524 | else -> throw RuntimeError(listComprehension.line, "Cannot iterate") 525 | } 526 | } 527 | 528 | override fun visitMapLiteral(mapLiteral: MapLiteral): Any { 529 | val map = HashMap() 530 | mapLiteral.exprs.map { 531 | map[eval(it.first)] = eval(it.second) 532 | } 533 | return createHashMap(map, this) 534 | } 535 | 536 | override fun visitIfExpression(ifExpression: IfExpression): Any? { 537 | return if (isTruthy(eval(ifExpression.expr))) { 538 | eval(ifExpression.thenExpr) 539 | } else { 540 | eval(ifExpression.elseExpr) 541 | } 542 | } 543 | 544 | override fun visitTuple(tuple: Tuple): Any { 545 | return OasisTuple(tuple.exprs.size, ArrayList(tuple.exprs.map { eval(it) })) 546 | } 547 | 548 | override fun visitRelStmt(relstmt: RelStmt) { 549 | environment.define(relstmt.name.lexeme.hashCode(), RelativeExpression(relstmt.expr)) 550 | } 551 | 552 | override fun visitDoBlock(doblock: DoBlock) { 553 | doblock.body.accept(this) 554 | } 555 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/KotlinFunction.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis 2 | 3 | class KotlinFunction0(@JvmField val function: (interpreter: Interpreter) -> T) : OasisCallable { 4 | constructor(function: () -> T) : this({ interpreter -> function() }) 5 | 6 | override fun call(interpreter: Interpreter, arguments: List): Any? { 7 | if (arguments.size < arity()) throw RuntimeError(line, "Expected ${arity()} arguments, got ${arguments.size}") 8 | return function(interpreter) 9 | } 10 | 11 | override fun arity(): Int { 12 | return 0 13 | } 14 | 15 | override fun toString(): String { 16 | return "NativeFunction(${arity()})" 17 | } 18 | } 19 | 20 | class KotlinFunction1(@JvmField val function: (interpreter: Interpreter, A) -> T) : OasisCallable { 21 | constructor(function: (A) -> T) : this({ interpreter, x -> function(x) }) 22 | 23 | override fun call(interpreter: Interpreter, arguments: List): Any? { 24 | if (arguments.size < arity()) throw RuntimeError(line, "Expected ${arity()} arguments, got ${arguments.size}") 25 | try { 26 | return function(interpreter, arguments[0] as A) 27 | } catch (e: ClassCastException) { 28 | throw RuntimeError(line, "Invalid argument type") 29 | } 30 | } 31 | 32 | override fun arity(): Int { 33 | return 1 34 | } 35 | 36 | override fun toString(): String { 37 | return "NativeFunction(${arity()})" 38 | } 39 | } 40 | 41 | class KotlinFunction2(@JvmField val function: (interpreter: Interpreter, a: A, b: B) -> T) : OasisCallable { 42 | constructor(function: (A, B) -> T) : this({ interpreter, x, y -> function(x, y) }) 43 | 44 | override fun call(interpreter: Interpreter, arguments: List): Any? { 45 | if (arguments.size < arity()) throw RuntimeError(line, "Expected ${arity()} arguments, got ${arguments.size}") 46 | try { 47 | return function(interpreter, arguments[0] as A, arguments[1] as B) 48 | } catch (e: ClassCastException) { 49 | throw RuntimeError(line, "Invalid argument type") 50 | } 51 | } 52 | 53 | override fun arity(): Int { 54 | return 2 55 | } 56 | 57 | override fun toString(): String { 58 | return "NativeFunction(${arity()})" 59 | } 60 | } 61 | 62 | class KotlinFunction3(@JvmField val function: (interpreter: Interpreter, a: A, b: B, c: C) -> T) : OasisCallable { 63 | constructor(function: (A, B, C) -> T) : this({ interpreter, x, y, z -> function(x, y, z) }) 64 | 65 | override fun call(interpreter: Interpreter, arguments: List): Any? { 66 | if (arguments.size < arity()) throw RuntimeError(line, "Expected ${arity()} arguments, got ${arguments.size}") 67 | try { 68 | return function(interpreter, arguments[0] as A, arguments[1] as B, arguments[2] as C) 69 | } catch (e: ClassCastException) { 70 | throw RuntimeError(line, "Invalid argument type") 71 | } 72 | } 73 | 74 | override fun arity(): Int { 75 | return 3 76 | } 77 | 78 | override fun toString(): String { 79 | return "NativeFunction(${arity()})" 80 | } 81 | } 82 | 83 | class KotlinFunction4(@JvmField val function: (interpreter: Interpreter, a: A, b: B, c: C, D) -> T) : 84 | OasisCallable { 85 | constructor(function: (A, B, C, D) -> T) : this({ interpreter, w, x, y, z -> function(w, x, y, z) }) 86 | 87 | override fun call(interpreter: Interpreter, arguments: List): Any? { 88 | if (arguments.size < arity()) throw RuntimeError(line, "Expected ${arity()} arguments, got ${arguments.size}") 89 | try { 90 | return function(interpreter, arguments[0] as A, arguments[1] as B, arguments[2] as C, arguments[3] as D) 91 | } catch (e: ClassCastException) { 92 | throw RuntimeError(line, "Invalid argument type") 93 | } 94 | } 95 | 96 | override fun arity(): Int { 97 | return 4 98 | } 99 | 100 | override fun toString(): String { 101 | return "NativeFunction(${arity()})" 102 | } 103 | } 104 | 105 | class KotlinFunction5(@JvmField val function: (interpreter: Interpreter, a: A, b: B, c: C, d: D, e: E) -> T) : 106 | OasisCallable { 107 | constructor(function: (A, B, C, D, E) -> T) : this({ interpreter, v, w, x, y, z -> function(v, w, x, y, z) }) 108 | 109 | override fun call(interpreter: Interpreter, arguments: List): Any? { 110 | if (arguments.size < arity()) throw RuntimeError(line, "Expected ${arity()} arguments, got ${arguments.size}") 111 | try { 112 | return function( 113 | interpreter, 114 | arguments[0] as A, 115 | arguments[1] as B, 116 | arguments[2] as C, 117 | arguments[3] as D, 118 | arguments[4] as E 119 | ) 120 | } catch (e: ClassCastException) { 121 | throw RuntimeError(line, "Invalid argument type") 122 | } 123 | } 124 | 125 | override fun arity(): Int { 126 | return 5 127 | } 128 | 129 | override fun toString(): String { 130 | return "NativeFunction(${arity()})" 131 | } 132 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/LanguageServer.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis 2 | 3 | import org.eclipse.lsp4j.* 4 | import org.eclipse.lsp4j.jsonrpc.Launcher 5 | import org.eclipse.lsp4j.jsonrpc.messages.Either 6 | import org.eclipse.lsp4j.services.* 7 | import java.io.InputStream 8 | import java.io.OutputStream 9 | import java.util.* 10 | import java.util.concurrent.CompletableFuture 11 | import java.util.concurrent.Future 12 | import kotlin.system.exitProcess 13 | 14 | 15 | class OasisLanguageServer : LanguageServer, LanguageClientAware { 16 | private val textDocumentService: TextDocumentService 17 | override fun getTextDocumentService(): TextDocumentService { 18 | return textDocumentService 19 | } 20 | 21 | private val workspaceService: WorkspaceService 22 | override fun getWorkspaceService(): WorkspaceService { 23 | return workspaceService 24 | } 25 | 26 | private lateinit var clientCapabilities: ClientCapabilities 27 | 28 | lateinit var languageClient: LanguageClient 29 | 30 | private var shutdown = 1 31 | 32 | init { 33 | textDocumentService = OasisTextDocumentService(this) 34 | workspaceService = OasisWorkspaceService(this) 35 | } 36 | 37 | override fun initialize(params: InitializeParams): CompletableFuture { 38 | val response = InitializeResult(ServerCapabilities()) 39 | response.capabilities.textDocumentSync = Either.forLeft(TextDocumentSyncKind.Full) 40 | clientCapabilities = params.capabilities 41 | if (!isDynamicCompletionRegistration()) { 42 | response.capabilities.completionProvider = CompletionOptions() 43 | } 44 | return CompletableFuture.supplyAsync { response } 45 | } 46 | 47 | override fun initialized(params: InitializedParams?) { 48 | if (isDynamicCompletionRegistration()) { 49 | val completionRegistrationOptions = CompletionRegistrationOptions() 50 | val completionRegistration = Registration( 51 | UUID.randomUUID().toString(), 52 | "textDocument/completion", completionRegistrationOptions 53 | ) 54 | languageClient.registerCapability(RegistrationParams(listOf(completionRegistration))) 55 | } 56 | } 57 | 58 | override fun shutdown(): CompletableFuture { 59 | shutdown = 0 60 | return CompletableFuture.supplyAsync { 61 | println("Shutdown") 62 | } 63 | } 64 | 65 | override fun exit() { 66 | exitProcess(shutdown) 67 | } 68 | 69 | override fun connect(client: LanguageClient?) { 70 | languageClient = client!! 71 | } 72 | 73 | private fun isDynamicCompletionRegistration(): Boolean { 74 | val textDocumentCapabilities = clientCapabilities.textDocument 75 | return textDocumentCapabilities != null && textDocumentCapabilities.completion != null && java.lang.Boolean.FALSE == textDocumentCapabilities.completion.dynamicRegistration 76 | } 77 | } 78 | 79 | class OasisWorkspaceService(private val languageServer: OasisLanguageServer) : WorkspaceService { 80 | override fun didChangeConfiguration(params: DidChangeConfigurationParams) { 81 | languageServer.languageClient.logMessage(MessageParams(MessageType.Info, "DidChangeConfiguration")) 82 | } 83 | 84 | override fun didChangeWatchedFiles(params: DidChangeWatchedFilesParams?) { 85 | languageServer.languageClient.logMessage(MessageParams(MessageType.Info, "DidChangeWatchedFiles")) 86 | } 87 | 88 | override fun didRenameFiles(params: RenameFilesParams?) { 89 | languageServer.languageClient.logMessage(MessageParams(MessageType.Info, "DidRenameFiles")) 90 | } 91 | } 92 | 93 | class OasisTextDocumentService(private val languageServer: OasisLanguageServer) : TextDocumentService { 94 | 95 | private lateinit var text: String 96 | private var variables: Map = mutableMapOf() 97 | 98 | private fun fromLineColToIndex(line: Int, col: Int): Int { 99 | var index = 0 100 | for (i in 0 until line) { 101 | index += text.lines()[i].length + 1 102 | } 103 | index += col 104 | return index 105 | } 106 | 107 | override fun didOpen(params: DidOpenTextDocumentParams?) { 108 | languageServer.languageClient.logMessage(MessageParams(MessageType.Info, "DidOpen")) 109 | text = params!!.textDocument.text 110 | } 111 | 112 | override fun didChange(params: DidChangeTextDocumentParams?) { 113 | // update text with the changes 114 | languageServer.languageClient.logMessage(MessageParams(MessageType.Info, "DidChange 1: $text")) 115 | params?.contentChanges?.forEach { 116 | text = if (it.range == null) { 117 | it.text 118 | } else { 119 | text.replaceRange( 120 | fromLineColToIndex(it.range.start.line, it.range.start.character), 121 | fromLineColToIndex(it.range.end.line, it.range.end.character), 122 | it.text 123 | ) 124 | } 125 | } 126 | 127 | val diagnostics = mutableListOf() 128 | var parsed: Stmt? = null 129 | val parser: Parser? 130 | try { 131 | parser = Parser(Scanner(text).scanTokens()) 132 | parsed = parser.parse() 133 | } catch (e: ParseException) { 134 | diagnostics.add( 135 | Diagnostic( 136 | Range(Position(e.line - 1, e.column), Position(e.line - 1, e.column)), 137 | e.parseMessage, 138 | DiagnosticSeverity.Error, 139 | "oasis parser" 140 | ) 141 | ) 142 | } 143 | val contextVisitor = ContextVisitor() 144 | parsed?.accept(contextVisitor) 145 | variables = contextVisitor.variables 146 | diagnostics.addAll(contextVisitor.diagnostics) 147 | languageServer.languageClient.publishDiagnostics( 148 | PublishDiagnosticsParams( 149 | params!!.textDocument.uri, 150 | diagnostics 151 | ) 152 | ) 153 | languageServer.languageClient.logMessage(MessageParams(MessageType.Info, "DidChange 2: $text")) 154 | } 155 | 156 | override fun didClose(params: DidCloseTextDocumentParams?) { 157 | languageServer.languageClient.logMessage(MessageParams(MessageType.Info, "DidClose")) 158 | } 159 | 160 | override fun didSave(params: DidSaveTextDocumentParams?) { 161 | languageServer.languageClient.logMessage(MessageParams(MessageType.Info, "DidSave")) 162 | } 163 | 164 | override fun completion(position: CompletionParams?): CompletableFuture, CompletionList>> { 165 | languageServer.languageClient.logMessage(MessageParams(MessageType.Info, text)) 166 | if (text.isEmpty()) { 167 | return CompletableFuture.supplyAsync { Either.forLeft(mutableListOf()) } 168 | } 169 | val completions = mutableListOf() 170 | if (position?.position?.line!! > text.lines().size - 1) { 171 | return CompletableFuture.supplyAsync { Either.forLeft(mutableListOf()) } 172 | } 173 | val line = text.lines()[position.position?.line ?: 0] 174 | languageServer.languageClient.logMessage(MessageParams(MessageType.Info, "Completion A: $line")) 175 | languageServer.languageClient.logMessage( 176 | MessageParams( 177 | MessageType.Info, 178 | "Completion B: ${position.position?.character} of ${line.length} with $line" 179 | ) 180 | ) 181 | val lineStart = if ((position.position?.character ?: 0) > line.length - 1) { 182 | line 183 | } else { 184 | line.substring(0, position.position?.character ?: 0) 185 | } 186 | languageServer.languageClient.logMessage(MessageParams(MessageType.Info, "Completion C: $line : $lineStart")) 187 | 188 | if (lineStart.endsWith(":")) { 189 | completions.add(CompletionItem("toString").also { 190 | it.detail = "toString" 191 | it.insertText = "toString()" 192 | it.kind = CompletionItemKind.Method 193 | }) 194 | completions.add(CompletionItem("hashCode").also { 195 | it.detail = "hashCode" 196 | it.insertText = "hashCode()" 197 | it.kind = CompletionItemKind.Method 198 | }) 199 | val attemptedCompletion = lineStart.substring(0, lineStart.length - 2).split(" ").last() 200 | val candidates = ArrayList() 201 | variables.forEach { pair -> 202 | if (pair.key.startsWith(attemptedCompletion)) { 203 | if (pair.value is PrototypeContext) { 204 | (pair.value as PrototypeContext).map.forEach { protoPair -> 205 | candidates.add(CompletionItem(protoPair.key).also { 206 | it.detail = protoPair.key 207 | it.insertText = protoPair.key 208 | it.kind = when (protoPair.value) { 209 | is Proto -> CompletionItemKind.Struct 210 | is Func -> CompletionItemKind.Function 211 | else -> CompletionItemKind.Variable 212 | } 213 | }) 214 | } 215 | } 216 | } 217 | } 218 | completions.addAll(candidates) 219 | } else if (lineStart.endsWith("fn")) { 220 | completions.add(CompletionItem("fn =>").also { 221 | it.detail = "fn (lambda)" 222 | it.insertText = "fn(/* args */) => /* expression */" 223 | it.kind = CompletionItemKind.Snippet 224 | }) 225 | completions.add(CompletionItem("fn").also { 226 | it.detail = "fn (body)" 227 | it.insertText = "fn(/* args */)\n\t/* body */\nend" 228 | it.kind = CompletionItemKind.Snippet 229 | }) 230 | } else if (lineStart.endsWith("proto")) { 231 | completions.add(CompletionItem("proto").also { 232 | it.detail = "proto" 233 | it.insertText = "proto\n\t/* body */\nend" 234 | it.kind = CompletionItemKind.Snippet 235 | }) 236 | completions.add(CompletionItem("proto < /* superclass */").also { 237 | it.detail = "proto (inherits)" 238 | it.insertText = "proto < /* superclass */\n\t/* body */\nend" 239 | it.kind = CompletionItemKind.Snippet 240 | }) 241 | } else if (lineStart.endsWith("is")) { 242 | completions.add(CompletionItem("is").also { 243 | it.detail = "is" 244 | it.insertText = "is /* expression */\n\t/* value */ =>\n\t\t/* body */\n\tend\nend" 245 | it.kind = CompletionItemKind.Keyword 246 | }) 247 | } 248 | val attemptedCompletion = lineStart.split(" ", "(", ")", "[", "]", "{", "}").last() 249 | val candidates = ArrayList() 250 | variables.forEach { pair -> 251 | if (attemptedCompletion.isEmpty() || pair.key.startsWith(attemptedCompletion)) { 252 | candidates.add(CompletionItem(pair.key).also { 253 | it.detail = pair.key 254 | it.insertText = pair.key 255 | it.kind = when (pair.value) { 256 | is PrototypeContext -> CompletionItemKind.Struct 257 | is Func -> CompletionItemKind.Function 258 | else -> CompletionItemKind.Variable 259 | } 260 | }) 261 | } 262 | } 263 | completions.addAll(candidates) 264 | return CompletableFuture.supplyAsync { 265 | return@supplyAsync Either.forLeft(completions) 266 | } 267 | } 268 | } 269 | 270 | class OasisLanguageServiceLauncher { 271 | fun main() { 272 | startServer(System.`in`, System.out) 273 | } 274 | 275 | private fun startServer(`in`: InputStream?, out: OutputStream?) { 276 | val server = OasisLanguageServer() 277 | val launcher = Launcher.createLauncher(server, LanguageClient::class.java, `in`, out) 278 | val client = launcher.remoteProxy 279 | server.connect(client) 280 | val startListening: Future<*> = launcher.startListening() 281 | startListening.get() 282 | } 283 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/Main.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis 2 | 3 | import me.snwy.oasis.experimental.PythonTranspiler 4 | import org.jline.reader.LineReaderBuilder 5 | import org.jline.reader.impl.history.DefaultHistory 6 | import org.jline.terminal.TerminalBuilder 7 | import java.nio.file.Files 8 | import java.nio.file.Path 9 | import kotlin.system.exitProcess 10 | 11 | var repl = false 12 | fun main(args: Array) { 13 | val env = OasisEnvironment() 14 | val program = args.find { 15 | Files.exists(Path.of(it)) 16 | } 17 | val compile = args.find { 18 | it == "--compile" || it == "-c" 19 | } != null 20 | val languageServer = args.find { 21 | it == "--language-server" || it == "-l" 22 | } != null 23 | if (languageServer) { 24 | val server = OasisLanguageServiceLauncher() 25 | server.main() 26 | } 27 | val console = LineReaderBuilder.builder() 28 | .terminal(TerminalBuilder.terminal()) 29 | .history(DefaultHistory()) 30 | .build() 31 | if (compile) { 32 | var scanner = Scanner(Files.readString(program?.let { Path.of(it) })) 33 | var tokens = scanner.scanTokens() 34 | var parser = Parser(tokens) 35 | val ast: Stmt? 36 | try { 37 | ast = parser.parse() 38 | } catch (e: ParseException) { 39 | exitProcess(1) 40 | } 41 | scanner = object {}.javaClass.classLoader.getResource("libpy.oa")?.readText()?.let { Scanner(it) }!! 42 | tokens = scanner.scanTokens() 43 | parser = Parser(tokens) 44 | Files.writeString( 45 | Path.of("$program.py"), 46 | "${PythonTranspiler().transpile(parser.parse())}\n${PythonTranspiler().transpile(ast)}\n" 47 | ) 48 | } else if (program != null) { 49 | (env.get("sys") as OasisPrototype).set("argv", args.copyOfRange(1, args.size).toCollection(ArrayList())) 50 | try { 51 | env.eval(Files.readString(Path.of(program))) 52 | env.run() 53 | } catch (e: RuntimeError) { 54 | error(e.line, e.s) 55 | if (e.line > 0) { 56 | println("| ${Files.readString(Path.of(program)).split('\n')[e.line - 1]}") 57 | } 58 | exitProcess(1) 59 | } catch (e: ParseException) { 60 | error(e.line, e.parseMessage) 61 | println( 62 | "| ${ 63 | Files.readString(Path.of(program)).split('\n')[env.parser?.tokens?.get(env.parser!!.current)?.line!! - 1] 64 | }" 65 | ) 66 | exitProcess(1) 67 | } 68 | } else while (true) { 69 | repl = true 70 | try { 71 | console.readLine("oasis -> ")?.let { 72 | env.eval(it) 73 | env.run() 74 | } 75 | } catch (e: RuntimeError) { 76 | error(e.line, e.s) 77 | } catch (e: ParseException) { 78 | error(e.line, e.parseMessage) 79 | } 80 | } 81 | } 82 | 83 | fun oasisMod(a: Int, b: Int): Int { 84 | return (a % b + b) % b 85 | } 86 | 87 | fun error(line: Int, reason: String) { 88 | println("On line $line: $reason") 89 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/OasisCallable.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis 2 | 3 | interface OasisCallable { 4 | fun call(interpreter: Interpreter, arguments: List): Any? 5 | 6 | fun arity(): Int 7 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/OasisException.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis 2 | 3 | data class OasisException(val value: Any?) : Exception() { 4 | override fun toString(): String { 5 | return value.toString() 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/OasisFunction.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis 2 | 3 | class OasisFunction(@JvmField val declaration: Func, @JvmField val closure: Environment) : OasisCallable { 4 | override fun call(interpreter: Interpreter, arguments: List): Any? { 5 | val environment = Environment(closure) 6 | if (arguments.size < arity()) throw RuntimeError(line, "Expected ${arity()} arguments, got ${arguments.size}") 7 | (0 until declaration.operands.size).map { 8 | environment.define( 9 | declaration.operands[it].lexeme.hashCode(), 10 | arguments[it] 11 | ) 12 | } 13 | try { 14 | interpreter.executeBlock(declaration.body, environment) 15 | } catch (returnValue: Return) { 16 | return returnValue.value 17 | } 18 | return null 19 | } 20 | 21 | override fun arity(): Int { 22 | return declaration.operands.size 23 | } 24 | 25 | override fun toString(): String { 26 | return "me.snwy.OasisFunction(${declaration.operands.map { x -> x.lexeme }})" 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/OasisPrototype.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis 2 | 3 | import me.snwy.oasis.standardLibrary.base 4 | import java.util.* 5 | 6 | class OasisPrototype(@JvmField var inherit: OasisPrototype? = base, @JvmField val line: Int, @JvmField var interpreter: Interpreter? = null) : 7 | Cloneable { 8 | @JvmField 9 | var body: MutableMap = TreeMap() 10 | 11 | fun get(name: String): Any? { 12 | return body[name] ?: (inherit?.get(name)) 13 | } 14 | 15 | fun set(name: String, value: Any?) { 16 | body[name] = value 17 | if (value is OasisFunction) 18 | value.closure.define("this".hashCode(), this) 19 | } 20 | 21 | override fun toString(): String { 22 | return (interpreter?.let { 23 | (get("toString") as OasisCallable).call(it, listOf()) 24 | } ?: return "OasisPrototype <${hashCode()}>") as String 25 | } 26 | 27 | override fun hashCode(): Int { 28 | return interpreter?.let { 29 | ((get("hashCode") as OasisCallable).call(it, listOf()) as Double).toInt() 30 | } ?: return super.hashCode() 31 | } 32 | 33 | override fun clone(): Any { 34 | return OasisPrototype(inherit, line, interpreter).let { body.map { x -> it.set(x.key, x.value) }; it } 35 | } 36 | 37 | override fun equals(other: Any?): Boolean { 38 | if (get("__equals") != null) { 39 | return (get("__equals") as OasisCallable).call(interpreter!!, listOf(other)) as Boolean 40 | } 41 | return super.equals(other) 42 | } 43 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/OasisTuple.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis 2 | 3 | class OasisTuple(override val size: Int, private val values: ArrayList) : List { 4 | override fun contains(element: T): Boolean { 5 | return values.contains(element) 6 | } 7 | 8 | override fun containsAll(elements: Collection): Boolean { 9 | return values.containsAll(elements) 10 | } 11 | 12 | override fun get(index: Int): T { 13 | return values[index] 14 | } 15 | 16 | override fun isEmpty(): Boolean { 17 | return values.isEmpty() 18 | } 19 | 20 | override fun iterator(): Iterator { 21 | return values.iterator() 22 | } 23 | 24 | override fun listIterator(): ListIterator { 25 | return values.listIterator() 26 | } 27 | 28 | override fun listIterator(index: Int): ListIterator { 29 | return values.listIterator(index) 30 | } 31 | 32 | override fun subList(fromIndex: Int, toIndex: Int): List { 33 | return values.subList(fromIndex, toIndex) 34 | } 35 | 36 | override fun lastIndexOf(element: T): Int { 37 | return values.lastIndexOf(element) 38 | } 39 | 40 | override fun indexOf(element: T): Int { 41 | return values.indexOf(element) 42 | } 43 | 44 | override fun toString(): String { 45 | return "(${values.joinToString(", ")})" 46 | } 47 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/ParseException.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis 2 | 3 | class ParseException(val line: Int, val column: Int, val parseMessage: String) : Exception() -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/PartialFunc.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis 2 | 3 | class PartialFunc(val func: OasisCallable, private val partialArgs: ArrayList) : OasisCallable { 4 | override fun call(interpreter: Interpreter, arguments: List): Any? { 5 | val list: ArrayList = ArrayList() 6 | list.addAll(partialArgs) 7 | list.addAll(arguments) 8 | if (list.size < arity()) throw RuntimeError(line, "Expected ${arity()} arguments, got ${list.size}") 9 | return func.call(interpreter, list) 10 | } 11 | 12 | override fun arity(): Int { 13 | return func.arity() 14 | } 15 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/RelativeExpression.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis 2 | 3 | class RelativeExpression(var expr: Expr) -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/Return.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis 2 | 3 | data class Return(var value: Any?) : Throwable() -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/RuntimeError.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis 2 | 3 | class RuntimeError(var line: Int, var s: String) : Exception() -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/Scanner.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis 2 | 3 | import me.snwy.oasis.TokenType.* 4 | 5 | class Scanner(private val source: String) { 6 | private var tokens: ArrayList = ArrayList() 7 | private var start: Int = 0 8 | private var current: Int = 0 9 | private var line: Int = 1 10 | private var column: Int = 0 11 | 12 | private var keywords: Map = mapOf( 13 | "let" to LET, 14 | "proto" to PROTO, 15 | "fn" to FN, 16 | "for" to FOR, 17 | "if" to IF, 18 | "nil" to NIL, 19 | "return" to RETURN, 20 | "true" to TRUE, 21 | "false" to FALSE, 22 | "while" to WHILE, 23 | "end" to END, 24 | "else" to ELSE, 25 | "and" to AND, 26 | "or" to OR, 27 | "not" to NOT, 28 | "clone" to NEW, 29 | "is" to IS, 30 | "const" to IMMUTABLE, 31 | "test" to TEST, 32 | "error" to ERROR, 33 | "break" to BREAK, 34 | "continue" to CONTINUE, 35 | "in" to IN, 36 | "of" to OF, 37 | "rel" to REL, 38 | "do" to BEGIN, 39 | ) 40 | 41 | private fun error(line: Int, column: Int, msg: String) { 42 | throw ParseException(line, column, msg) 43 | } 44 | 45 | fun scanTokens(): List { 46 | while (!isAtEnd()) { 47 | start = current 48 | scanToken() 49 | } 50 | tokens.add(Token(EOF, "", null, line, column)) 51 | return tokens 52 | } 53 | 54 | private fun isAtEnd(): Boolean { 55 | return current >= source.length 56 | } 57 | 58 | private fun scanToken() { 59 | when (val c: Char = advance()) { 60 | '(' -> addToken(LEFT_PAREN) 61 | ')' -> addToken(RIGHT_PAREN) 62 | ',' -> addToken(COMMA) 63 | ':' -> addToken(COLON) 64 | '-' -> addToken(if (match('=')) MINUS_EQUAL else MINUS) 65 | '+' -> addToken(if (match('=')) PLUS_EQUAL else PLUS) 66 | '*' -> addToken(if (match('=')) STAR_EQUAL else STAR) 67 | '!' -> addToken(if (match('=')) BANG_EQUAL else BANG) 68 | '=' -> addToken(if (match('=')) EQUAL_EQUAL else if (match('>')) LAMBDA_ARROW else EQUAL) 69 | '<' -> addToken(if (match('=')) LESS_EQUAL else if (match('|')) LEFT_PIPE else LESS) 70 | '>' -> addToken(if (match('=')) GREATER_EQUAL else GREATER) 71 | '/' -> if (match('/')) 72 | while (peek() != '\n' && !isAtEnd()) 73 | advance() 74 | else 75 | if (match('*')) { 76 | while (peek() != '*' && !isAtEnd()) 77 | advance() 78 | if (match('*')) { 79 | if (!match('/')) { 80 | error(line, column, "Expected '/' after '*'") 81 | } 82 | } 83 | } else 84 | addToken(if (match('=')) SLASH_EQUAL else SLASH) 85 | '[' -> addToken(LBRAC) 86 | ']' -> addToken(RBRAC) 87 | ' ', '\r', '\t' -> null 88 | '\n' -> { 89 | line++; column = 0 90 | } 91 | '#' -> while (peek() != '\n' && !isAtEnd() && line == 1) 92 | advance() 93 | '"' -> string() 94 | '\'' -> char() 95 | '%' -> addToken(MOD) 96 | '|' -> addToken(if (match('>')) RIGHT_PIPE else PIPE) 97 | '?' -> addToken(QUESTION) 98 | '{' -> addToken(LBRACE) 99 | '}' -> addToken(RBRACE) 100 | '0' -> if (match('x')) hex() else number() 101 | else -> if (c.isDigit()) 102 | number() 103 | else if (c.isLetter() || c == '_') 104 | identifier() 105 | else 106 | error(line, column, "Unexpected character '$c'.") 107 | } 108 | } 109 | 110 | private fun hex() { 111 | val column = this.column 112 | while (CharRange('0', '9').contains(peek()) || CharRange('a', 'f').contains(peek()) || CharRange( 113 | 'A', 114 | 'F' 115 | ).contains(peek()) 116 | ) 117 | advance() 118 | addToken(BYTE, Integer.parseInt(source.substring(start + 2, current), 16).toUByte(), column) 119 | } 120 | 121 | private fun identifier() { 122 | val column = this.column 123 | while (peek().isDigit() || peek().isLetter() || peek() == '_') advance() 124 | val text: String = source.substring(start, current) 125 | var type: TokenType? = keywords[text] 126 | if (type == null) type = IDENTIFIER 127 | addToken(type, null, column) 128 | } 129 | 130 | private fun string() { 131 | var column = this.column 132 | while (peek() != '"' && !isAtEnd()) { 133 | if (peek() == '\n') { 134 | line++; column = 0 135 | } 136 | if (peek() == '\\') { 137 | advance() 138 | when (peek()) { 139 | '"', '\'', 'n', 'r', 't' -> null 140 | else -> error(line, column, "Invalid escape.") 141 | } 142 | } 143 | advance() 144 | } 145 | if (isAtEnd()) { 146 | error(line, column, "String without end.") 147 | return 148 | } 149 | advance() 150 | val value: String = source.substring(start + 1, current - 1) 151 | .replace("\\r", "\r") 152 | .replace("\\n", "\n") 153 | .replace("\\t", "\t") 154 | .replace("\\\"", "\"") 155 | addToken(STRING, value, column) 156 | } 157 | 158 | private fun char() { 159 | val column = this.column 160 | val c: Char = advance() 161 | if (advance() != '\'') { 162 | error(line, column, "Unterminated char literal.") 163 | return 164 | } 165 | addToken(CHAR, c, column) 166 | } 167 | 168 | private fun peek(): Char { 169 | if (isAtEnd()) return Char(0) 170 | return source[current] 171 | } 172 | 173 | private fun peekNext(): Char { 174 | if (current + 1 >= source.length) return Char(0) 175 | return source[current + 1] 176 | } 177 | 178 | private fun number(negative: Boolean = false) { 179 | val column = this.column 180 | while (peek().isDigit()) advance() 181 | if (peek() == '.' && peekNext().isDigit()) { 182 | advance() 183 | while (peek().isDigit()) advance() 184 | } 185 | addToken( 186 | NUMBER, 187 | if (!negative) source.substring(start, current).toDouble() else -(source.substring(start, current) 188 | .toDouble()), 189 | column 190 | ) 191 | } 192 | 193 | private fun match(expected: Char): Boolean { 194 | if (isAtEnd()) return false 195 | if (source[current] != expected) return false 196 | current++ 197 | return true 198 | } 199 | 200 | private fun advance(): Char { 201 | column++ 202 | return source[current++] 203 | } 204 | 205 | private fun addToken(type: TokenType) { 206 | addToken(type, null) 207 | } 208 | 209 | private fun addToken(type: TokenType, literal: Any?) { 210 | val text: String = source.substring(start, current) 211 | tokens.add(Token(type, text, literal, line, column)) 212 | } 213 | 214 | private fun addToken(type: TokenType, literal: Any?, column: Int) { 215 | val text: String = source.substring(start, current) 216 | tokens.add(Token(type, text, literal, line, column)) 217 | } 218 | 219 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/Token.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis 2 | 3 | class Token(val type: TokenType, val lexeme: String, val literal: Any?, val line: Int, val column: Int) { 4 | companion object { 5 | fun create(type: TokenType, lexeme: String, literal: Any?, line: Int, column: Int): Token { 6 | return Token(type, lexeme, literal, line, column) 7 | } 8 | 9 | fun create(type: TokenType, lexeme: String, line: Int, column: Int): Token { 10 | return Token(type, lexeme, null, line, column) 11 | } 12 | 13 | fun create(type: TokenType, line: Int, column: Int): Token { 14 | return Token(type, "", null, line, column) 15 | } 16 | } 17 | 18 | override fun toString(): String { 19 | return "$type $lexeme $literal" 20 | } 21 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/TokenType.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis 2 | 3 | enum class TokenType { 4 | LEFT_PAREN, RIGHT_PAREN, 5 | COMMA, COLON, MINUS, PLUS, SLASH, STAR, 6 | LBRAC, RBRAC, LBRACE, RBRACE, 7 | 8 | // One or two character tokens. 9 | BANG, BANG_EQUAL, PIPE, 10 | EQUAL, EQUAL_EQUAL, 11 | GREATER, GREATER_EQUAL, 12 | LESS, LESS_EQUAL, MOD, 13 | PLUS_EQUAL, MINUS_EQUAL, SLASH_EQUAL, STAR_EQUAL, 14 | RIGHT_PIPE, LEFT_PIPE, QUESTION, LAMBDA_ARROW, 15 | 16 | // Literals. 17 | IDENTIFIER, STRING, NUMBER, CHAR, BYTE, 18 | 19 | LET, PROTO, FN, FOR, IF, NIL, RETURN, TRUE, FALSE, WHILE, END, 20 | ELSE, AND, NOT, OR, NEW, IS, IMMUTABLE, TEST, ERROR, IN, BREAK, CONTINUE, OF, 21 | REL, BEGIN, 22 | 23 | EOF 24 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/experimental/PythonTranspiler.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis.experimental 2 | 3 | import me.snwy.oasis.* 4 | 5 | class PythonTranspiler : Expr.Visitor, Stmt.Visitor { 6 | 7 | fun transpile(stmt: Stmt): String { 8 | val code = stmt.accept(this) 9 | return globalStatements.joinToString("\n") + "\n" + code 10 | } 11 | 12 | 13 | private val globalStatements = mutableListOf() 14 | private var indentLevel = 0 15 | 16 | private var inProto = false 17 | 18 | private fun indent(subtract: Boolean = false) = 19 | if (indentLevel == 0) "" else "\t".repeat(if (subtract) indentLevel - 1 else indentLevel) 20 | 21 | override fun visitLiteral(literal: Literal): String { 22 | when (literal.value) { 23 | is Int -> return literal.value.toString() 24 | is Double -> return if (literal.value.toString().contains(".")) 25 | literal.value.toString().replace(Regex("0*\$"), "").replace(Regex("\\.\$"), "") 26 | else literal.value.toString() 27 | is String -> return "\"${literal.value}\"" 28 | is Boolean -> { 29 | return if (literal.value) { 30 | "True" 31 | } else { 32 | "False" 33 | } 34 | } 35 | null -> return "None" 36 | else -> throw RuntimeException("Unsupported literal") 37 | } 38 | } 39 | 40 | override fun visitAssignment(assignment: AssignmentExpr): String { 41 | return "${assignment.left.accept(this)} = ${assignment.value.accept(this)}" 42 | } 43 | 44 | override fun visitProperty(property: Property): String { 45 | return "${property.obj.accept(this)}.${property.indexer.lexeme}" 46 | } 47 | 48 | override fun visitFunc(func: Func): String { 49 | val tempIndent = indentLevel 50 | indentLevel = 1 51 | globalStatements.add("def f_${ 52 | func.hashCode().toUInt() 53 | }(${if (inProto) "self, " else ""}${func.operands.joinToString(", ") { it.lexeme }}):\n" 54 | + (func.body.stmts.map { "${indent()}${it.accept(this)}\n" }).joinToString("") 55 | ) 56 | indentLevel = tempIndent 57 | return "f_${func.hashCode().toUInt()}" 58 | } 59 | 60 | override fun visitFcall(fcall: FCallExpr): String { 61 | return "${fcall.func.accept(this)}(${fcall.operands.joinToString(", ") { it.accept(this) }})" 62 | } 63 | 64 | override fun visitBinOp(binop: BinOp): String { 65 | return "${binop.left.accept(this)} ${binop.operator.lexeme} ${binop.right.accept(this)}" 66 | } 67 | 68 | override fun visitGroup(group: Group): String { 69 | return "(${group.expr.accept(this)})" 70 | } 71 | 72 | override fun visitVariable(variable: Variable): String { 73 | return if (variable.name.lexeme == "import") "__import__" else variable.name.lexeme 74 | } 75 | 76 | override fun vistPrecomputed(precomputed: Precomputed): String { 77 | throw RuntimeException("Unsupported precomputed") 78 | } 79 | 80 | override fun visitProto(proto: Proto): String { 81 | inProto = true 82 | var proto_s = "type('proto_${proto.hashCode()}', (${proto.base?.accept(this) ?: "object"},), {" 83 | proto.body.stmts.forEach { 84 | proto_s += "\"${(it as Let).left[0].lexeme}\": ${it.value.accept(this)}, " 85 | } 86 | proto_s += "})" 87 | inProto = false 88 | return proto_s 89 | } 90 | 91 | override fun visitIndexer(indexer: Indexer): String { 92 | return "${indexer.expr.accept(this)}[${indexer.index.accept(this)}]" 93 | } 94 | 95 | override fun visitList(list: OasisList): String { 96 | return "[${list.exprs.joinToString(", ") { it.accept(this) }}]" 97 | } 98 | 99 | override fun visitNegate(negate: Negate): String { 100 | return "-${negate.value.accept(this)}" 101 | } 102 | 103 | override fun visitNew(ref: New): String { 104 | return "__import__(\"copy\").deepcopy(${ref.expr.accept(this)})" 105 | } 106 | 107 | override fun visitNot(not: Not): String { 108 | return "not ${not.expr.accept(this)}" 109 | } 110 | 111 | override fun visitLet(let: Let): String { 112 | if (let.immutable) 113 | println("Warning: Immutable values aren't supported in Python translation") 114 | return "${let.left[0].lexeme} = ${let.value.accept(this)}" 115 | } 116 | 117 | override fun visitIfStmt(ifstmt: IfStmt): String { 118 | indentLevel++ 119 | var ifstmt_s = 120 | "if ${ifstmt.expr.accept(this)}:\n" + (ifstmt.stmtlist.stmts.map { "${indent()}${it.accept(this)}\n" }).joinToString( 121 | "" 122 | ) 123 | if (ifstmt.elseBody != null) { 124 | ifstmt_s += "${indent(true)}else:\n" + (ifstmt.elseBody!!.stmts.map { "${indent()}${it.accept(this)}\n" }).joinToString( 125 | "" 126 | ) 127 | } 128 | indentLevel-- 129 | return ifstmt_s 130 | } 131 | 132 | override fun visitWhileStmt(whilestmt: WhileStmt): String { 133 | indentLevel++ 134 | val while_c = 135 | "while ${whilestmt.expr.accept(this)}:\n" + (whilestmt.body.stmts.map { "${indent()}${it.accept(this)}\n" }).joinToString( 136 | "" 137 | ) 138 | indentLevel-- 139 | return while_c 140 | } 141 | 142 | override fun visitStmtList(stmtlist: StmtList): String { 143 | return stmtlist.stmts.joinToString("\n") { it.accept(this) } 144 | } 145 | 146 | override fun visitReturnStmt(retstmt: RetStmt): String { 147 | return "return ${retstmt.expr?.accept(this)}" 148 | } 149 | 150 | override fun visitExprStmt(exprStmt: ExprStmt): String { 151 | return exprStmt.expr.accept(this) 152 | } 153 | 154 | override fun visitIs(is_: Is): String { 155 | TODO("Fix this shit") 156 | } 157 | 158 | override fun visitTest(test: Test): String { 159 | TODO("exception handling") 160 | } 161 | 162 | override fun visitForLoopTriad(forLoopTriad: ForLoopTriad): String { 163 | TODO("Not yet implemented") 164 | } 165 | 166 | override fun visitForLoopIterator(forLoopIterator: ForLoopIterator): String { 167 | TODO("Not yet implemented") 168 | } 169 | 170 | override fun visitBreakStmt(break_: BreakStmt): String { 171 | TODO("Not yet implemented") 172 | } 173 | 174 | override fun visitContinueStmt(continue_: ContinueStmt): String { 175 | TODO("Not yet implemented") 176 | } 177 | 178 | override fun visitListComprehension(listComprehension: ListComprehension): String { 179 | TODO("Not yet implemented") 180 | } 181 | 182 | override fun visitMapLiteral(mapLiteral: MapLiteral): String { 183 | TODO("Not yet implemented") 184 | } 185 | 186 | override fun visitIfExpression(ifExpression: IfExpression): String { 187 | TODO("Not yet implemented") 188 | } 189 | 190 | override fun visitTuple(tuple: Tuple): String { 191 | TODO("Not yet implemented") 192 | } 193 | 194 | override fun visitRelStmt(relstmt: RelStmt): String { 195 | TODO("Not yet implemented") 196 | } 197 | 198 | override fun visitDoBlock(doblock: DoBlock): String { 199 | TODO("Not yet implemented") 200 | } 201 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/standardLibrary/Api.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis.standardLibrary 2 | 3 | import com.sun.net.httpserver.HttpServer 4 | import me.snwy.oasis.* 5 | import java.net.InetSocketAddress 6 | 7 | var api = Module("api") { it, _ -> 8 | val api = KotlinFunction1 { interpreter, apiIt -> 9 | val api = OasisPrototype(base, line, interpreter).apply { 10 | set("port", apiIt.toInt()) 11 | set("__server", HttpServer.create(InetSocketAddress(get("port") as Int), 0)) 12 | set("get", KotlinFunction2 { interpreter, x, y -> 13 | (get("__server") as HttpServer).createContext(x) { 14 | if ("GET" == it.requestMethod) { 15 | val response = interpreter.let { it1 -> 16 | y.call( 17 | it1, 18 | listOf( 19 | it.requestHeaders, 20 | String(it.requestBody.readBytes()) 21 | ) 22 | ) 23 | } as? ArrayList<*> ?: throw OasisException("API: get: response is not an array") 24 | val responseCode = (response[0] as? Number)?.toInt() ?: throw OasisException("API: get: first response item is not a status code") 25 | val responseText = response[1] as? String ?: throw OasisException("API: get: second response item is not a response body") 26 | it.sendResponseHeaders(responseCode, responseText.toByteArray().size.toLong()) 27 | it.responseBody.write(responseText.toByteArray()) 28 | it.responseBody.flush() 29 | } else { 30 | it.sendResponseHeaders(405, -1) 31 | } 32 | it.close() 33 | } 34 | }) 35 | set("post", KotlinFunction2 { interpreter, x, y -> 36 | (get("__server") as HttpServer).createContext(x) { 37 | if ("POST" == it.requestMethod) { 38 | val response = interpreter.let { it1 -> 39 | y.call( 40 | it1, 41 | listOf( 42 | it.requestHeaders, 43 | String(it.requestBody.readBytes()) 44 | ) 45 | ) 46 | } as? ArrayList<*> ?: throw OasisException("API: get: response is not an array") 47 | val responseCode = (response[0] as? Number)?.toInt() ?: throw OasisException("API: get: first response item is not a status code") 48 | val responseText = response[1] as? String ?: throw OasisException("API: get: second response item is not a response body") 49 | it.sendResponseHeaders(responseCode, responseText.toByteArray().size.toLong()) 50 | it.responseBody.write(responseText.toByteArray()) 51 | it.responseBody.flush() 52 | } else { 53 | it.sendResponseHeaders(405, -1) 54 | } 55 | it.close() 56 | } 57 | }) 58 | set("start", KotlinFunction0((get("__server") as HttpServer)::start)) 59 | set("stop", KotlinFunction1((get("__server") as HttpServer)::stop)) 60 | } 61 | api 62 | } 63 | it.define("api", api) 64 | } 65 | -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/standardLibrary/Async.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis.standardLibrary 2 | 3 | import me.snwy.oasis.* 4 | import kotlin.concurrent.thread 5 | 6 | val async = Module("async") { it, interpreter -> 7 | val async = OasisPrototype(base, -1, interpreter).apply { 8 | set("run", KotlinFunction2> { interpreter, x, y -> 9 | OasisPrototype(base, -1, interpreter).apply { 10 | set("__thread", thread { 11 | interpreter.let { it1 -> x.call(it1, y) } 12 | }) 13 | set("run", KotlinFunction0((get("__thread") as Thread)::run)) 14 | set("stop", KotlinFunction0((get("__thread") as Thread)::interrupt)) 15 | set("alive", KotlinFunction0((get("__thread") as Thread)::isAlive)) 16 | } 17 | }) 18 | set("sleep", KotlinFunction1 { it -> 19 | Thread.sleep(it.toLong()) 20 | }) 21 | } 22 | it.define("async", async) 23 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/standardLibrary/Func.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis.standardLibrary 2 | 3 | import me.snwy.oasis.KotlinFunction2 4 | import me.snwy.oasis.OasisCallable 5 | import me.snwy.oasis.OasisPrototype 6 | import me.snwy.oasis.PartialFunc 7 | 8 | val func = Module("func") { it, interpreter -> 9 | val func = OasisPrototype(base, -1, interpreter).apply { 10 | set("partial", KotlinFunction2> { x, y -> 11 | return@KotlinFunction2 PartialFunc(x, y) 12 | }) 13 | set("call", KotlinFunction2> { interpreter, x, y -> 14 | return@KotlinFunction2 x.call(interpreter, y) 15 | }) 16 | 17 | } 18 | it.define("func", func) 19 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/standardLibrary/HashMap.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis.standardLibrary 2 | 3 | import me.snwy.oasis.* 4 | 5 | val hashmap = Module("map") { it, _ -> 6 | val mapFactory = KotlinFunction0 { interpreter -> 7 | createHashMap(HashMap(), interpreter) 8 | } 9 | it.define("map", mapFactory) 10 | } 11 | 12 | fun createHashMap(hashMap: HashMap, interpreter: Interpreter): OasisPrototype { 13 | val map = OasisPrototype(base, line, interpreter) 14 | map.set("__map", hashMap) 15 | map.apply { 16 | set("set", KotlinFunction2 { x, y -> 17 | (get("__map") as HashMap)[x] = y 18 | return@KotlinFunction2 this 19 | }) 20 | set("del", KotlinFunction1 { x -> 21 | (get("__map") as HashMap).remove(x) 22 | return@KotlinFunction1 this 23 | }) 24 | set("__index", KotlinFunction1 { x -> 25 | (get("__map") as HashMap)[x] 26 | }) 27 | set("__setIndex", KotlinFunction2 { x, y -> 28 | (get("__map") as HashMap)[x] = y 29 | return@KotlinFunction2 this 30 | }) 31 | set("toString", KotlinFunction0 { _ -> 32 | (get("__map").toString()) 33 | }) 34 | set("__serialize", KotlinFunction0 { _ -> 35 | get("__map") as HashMap 36 | }) 37 | set("__iterator", KotlinFunction1 { index -> 38 | val map = get("__map") as HashMap 39 | map.iterator().withIndex().forEach { 40 | if (it.index == index.toInt()) { 41 | return@KotlinFunction1 it.value 42 | } 43 | } 44 | }) 45 | } 46 | return map 47 | } 48 | -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/standardLibrary/Import.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis.standardLibrary 2 | 3 | import me.snwy.oasis.* 4 | import me.snwy.oasis.Optimizer.Companion.nameMap 5 | import java.nio.file.Files 6 | import java.nio.file.Path 7 | import kotlin.io.path.exists 8 | 9 | var import = Module("import") { it, _ -> 10 | it.define("import", KotlinFunction1 { ginterpreter, it -> 11 | val module = Files.readString( 12 | if (Path.of("modules/$it").exists()) 13 | Path.of("modules/$it") 14 | else 15 | Path.of(it) 16 | ) 17 | val interpreter = Interpreter() 18 | Scanner(module).scanTokens().also { tokens -> 19 | interpreter.execute(Parser(tokens).parse()) 20 | }.let { 21 | val modProto = OasisPrototype(base, -1, ginterpreter) 22 | interpreter.environment.values.map { 23 | nameMap[it.key]?.let { it1 -> modProto.set(it1, it.value) } 24 | } 25 | modProto 26 | } 27 | }) 28 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/standardLibrary/Io.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis.standardLibrary 2 | 3 | import com.fazecast.jSerialComm.SerialPort 4 | import me.snwy.oasis.* 5 | import java.io.RandomAccessFile 6 | import java.nio.charset.Charset 7 | 8 | val io = Module("io") { it, interpreter -> 9 | val io = OasisPrototype(base, -1, interpreter) 10 | val socket = OasisPrototype(base, -1, interpreter) 11 | 12 | io.set("print", KotlinFunction1 { z -> println(z.toString()) }) 13 | io.set("read", KotlinFunction0(::readLine)) 14 | io.set("readc", KotlinFunction0 { _ -> return@KotlinFunction0 Char(System.`in`.read()) }) 15 | io.set("open", KotlinFunction2 { interpreter, s, r -> 16 | return@KotlinFunction2 OasisPrototype(base, -1, interpreter).apply { 17 | set("__file", RandomAccessFile(s, r)) 18 | set("readStr", KotlinFunction0 { _ -> 19 | val result = ByteArray((get("__file") as RandomAccessFile).length().toInt()) 20 | (get("__file") as RandomAccessFile).readFully(result) 21 | result.toString(Charset.defaultCharset()) 22 | }) 23 | set("readBytes", KotlinFunction0 { _ -> 24 | val result = ByteArray((get("__file") as RandomAccessFile).length().toInt()) 25 | (get("__file") as RandomAccessFile).readFully(result) 26 | result 27 | }) 28 | set("seek", KotlinFunction1 { x -> 29 | (get("__file") as RandomAccessFile).seek(x.toLong()) 30 | }) 31 | set("readAtHead", KotlinFunction0 { _ -> 32 | (get("__file") as RandomAccessFile).read().toUByte() 33 | }) 34 | set("writeAtHead", KotlinFunction1 { x -> 35 | (get("__file") as RandomAccessFile).write(x.toInt()) 36 | }) 37 | set("writeStr", KotlinFunction1 { x -> 38 | (get("__file") as RandomAccessFile).write(x.toByteArray(Charset.defaultCharset())) 39 | }) 40 | set("writeBytes", KotlinFunction1 { x -> 41 | (get("__file") as RandomAccessFile).write(x) 42 | }) 43 | set("close", KotlinFunction0 { _ -> 44 | (get("__file") as RandomAccessFile).close() 45 | }) 46 | } 47 | }) 48 | io.set("printf", KotlinFunction2 { interpreter, z, y -> 49 | print(interpreter.let { it1 -> 50 | y.call( 51 | it1, listOf(z) 52 | ) 53 | }) 54 | }) 55 | io.set("serial", KotlinFunction0 { interpreter -> 56 | val result = arrayListOf() 57 | SerialPort.getCommPorts().forEach { 58 | result.add(OasisPrototype(base, line, interpreter).apply { 59 | set("__port", it) 60 | set("name", it.systemPortName) 61 | set("description", it.portDescription) 62 | set("isOpen", KotlinFunction0(it::isOpen)) 63 | set("open", KotlinFunction0(it::openPort)) 64 | set("close", KotlinFunction0(it::closePort)) 65 | set( 66 | "writeStr", 67 | KotlinFunction1 { x -> 68 | it.writeBytes( 69 | x.toByteArray(Charset.defaultCharset()), 70 | x.length.toLong() 71 | ) 72 | }) 73 | set("writeBytes", KotlinFunction1 { x -> it.writeBytes(x, x.size.toLong()) }) 74 | set("readBytes", KotlinFunction1 { x -> 75 | val result = ByteArray(x.toInt()) 76 | it.readBytes(result, x.toLong()) 77 | result 78 | }) 79 | set("readStr", KotlinFunction1 { x -> 80 | val result = ByteArray(x.toInt()) 81 | it.readBytes(result, x.toLong()) 82 | result.toString(Charset.defaultCharset()) 83 | }) 84 | set("writeByte", KotlinFunction1 { x -> it.writeBytes(byteArrayOf(x.toByte()), 1) }) 85 | set("readByte", KotlinFunction0 { _ -> 86 | val result = ByteArray(1) 87 | it.readBytes(result, 1) 88 | result[0].toUByte() 89 | }) 90 | }) 91 | } 92 | result 93 | }) 94 | socket.set("open", KotlinFunction1(::constructSocket)) 95 | socket.set("connect", KotlinFunction2(::socketConnect)) 96 | io.set("socket", socket) 97 | 98 | 99 | it.define("io", io) 100 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/standardLibrary/Json.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis.standardLibrary 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.GsonBuilder 5 | import me.snwy.oasis.KotlinFunction1 6 | import me.snwy.oasis.OasisPrototype 7 | 8 | var json = Module("json") { it, interpreter -> 9 | val json = OasisPrototype(base, -1, interpreter).apply { 10 | set("parse", KotlinFunction1 { interpreter, it -> 11 | createHashMap(Gson().fromJson(it, HashMap::class.java) as HashMap, interpreter) 12 | }) 13 | set("parseProto", KotlinFunction1 { interpreter, it -> 14 | GsonBuilder() 15 | .registerTypeAdapter(OasisPrototype::class.java, PrototypeDeserializer(interpreter)) 16 | .create() 17 | .fromJson(it, OasisPrototype::class.java) 18 | }) 19 | set("dump", KotlinFunction1 { it -> 20 | GsonBuilder() 21 | .registerTypeAdapter(OasisPrototype::class.java, PrototypeSerializer()) 22 | .setPrettyPrinting() 23 | .create() 24 | .toJson(it) 25 | }) 26 | } 27 | it.define("json", json) 28 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/standardLibrary/List.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis.standardLibrary 2 | 3 | import me.snwy.oasis.* 4 | 5 | val list = Module("list") { it, interpreter -> 6 | val list = OasisPrototype(base, -1, interpreter) 7 | list.set("add", KotlinFunction2, Any?> { z, y -> z.add(y) }) 8 | list.set("size", KotlinFunction1> { z -> z.size.toDouble() }) 9 | list.set("remove", 10 | KotlinFunction2, Double> { z, y -> 11 | return@KotlinFunction2 z.removeAt(y.toInt()) 12 | }) 13 | list.set("slice", KotlinFunction3, ArrayList, Double, Double> { x, y, z -> 14 | ArrayList(x.subList(y.toInt(), z.toInt())) 15 | }) 16 | list.set("sliceFrom", KotlinFunction2, ArrayList, Double> { x, y -> 17 | ArrayList(x.subList(y.toInt(), x.size)) 18 | }) 19 | list.set("sliceTo", KotlinFunction2, ArrayList, Double> { x, y -> 20 | ArrayList(x.subList(0, y.toInt())) 21 | }) 22 | list.set("last", KotlinFunction1> { z -> z.last() }) 23 | list.set("first", KotlinFunction1> { z -> z.first() }) 24 | list.set("indexOf", KotlinFunction2, Any?> { z, y -> 25 | z.indexOf(y).toDouble() 26 | }) 27 | list.set("contains", KotlinFunction2, Any?> { z, y -> 28 | z.contains(y) 29 | }) 30 | list.set("clear", KotlinFunction1> { z -> z.clear() }) 31 | list.set("isEmpty", KotlinFunction1> { z -> z.isEmpty() }) 32 | list.set("filter", KotlinFunction2, Collection, OasisCallable> { interpreter, x, y -> 33 | x.filter { z -> 34 | y.call(interpreter, listOf(z)) as? Boolean 35 | ?: throw RuntimeException("'filter' function must return boolean") 36 | } 37 | }) 38 | list.set("find", KotlinFunction2, OasisCallable> { interpreter, x, y -> 39 | x.find { z -> 40 | y.call(interpreter, listOf(z)) as? Boolean ?: throw RuntimeException("'find' function must return boolean") 41 | } 42 | }) 43 | list.set("findIndex", KotlinFunction2, OasisCallable> { interpreter, x, y -> 44 | x.indexOfFirst { z -> 45 | y.call(interpreter, listOf(z)) as? Boolean 46 | ?: throw RuntimeException("'findIndex' function must return boolean") 47 | } 48 | }) 49 | list.set("findLast", KotlinFunction2, OasisCallable> { interpreter, x, y -> 50 | x.findLast { z -> 51 | y.call(interpreter, listOf(z)) as? Boolean 52 | ?: throw RuntimeException("'findLast' function must return boolean") 53 | } 54 | }) 55 | list.set("findLastIndex", KotlinFunction2, OasisCallable> { interpreter, x, y -> 56 | x.indexOfLast { z -> 57 | y.call(interpreter, listOf(z)) as? Boolean 58 | ?: throw RuntimeException("'findLastIndex' function must return boolean") 59 | } 60 | }) 61 | list.set("map", KotlinFunction2, Collection, OasisCallable> { interpreter, x, y -> 62 | x.map { z -> 63 | y.call(interpreter, listOf(z)) 64 | } 65 | }) 66 | list.set("reduce", KotlinFunction2, OasisCallable> { interpreter, x, y -> 67 | x.reduce { a, b -> 68 | y.call(interpreter, listOf(a, b)) 69 | } 70 | }) 71 | list.set("every", KotlinFunction2, OasisCallable> { interpreter, x, y -> 72 | x.all { z -> 73 | y.call(interpreter, listOf(z)) as? Boolean ?: throw RuntimeException("'every' function must return boolean") 74 | } 75 | }) 76 | list.set("some", KotlinFunction2, OasisCallable> { interpreter, x, y -> 77 | x.any { z -> 78 | y.call(interpreter, listOf(z)) as? Boolean ?: throw RuntimeException("'some' function must return boolean") 79 | } 80 | }) 81 | it.define("list", list) 82 | } 83 | -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/standardLibrary/Math.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis.standardLibrary 2 | 3 | import me.snwy.oasis.KotlinFunction0 4 | import me.snwy.oasis.KotlinFunction1 5 | import me.snwy.oasis.KotlinFunction2 6 | import me.snwy.oasis.OasisPrototype 7 | 8 | val math = Module("math") { it, interpreter -> 9 | val math = OasisPrototype(base, -1, interpreter).apply { 10 | set("pi", Math.PI) 11 | set("sin", KotlinFunction1(Math::sin)) 12 | set("abs", KotlinFunction1(Math::abs)) 13 | set("cos", KotlinFunction1(Math::cos)) 14 | set("ceil", KotlinFunction1(Math::ceil)) 15 | set("floor", KotlinFunction1(Math::floor)) 16 | set("sqrt", KotlinFunction1(Math::sqrt)) 17 | set("exp", KotlinFunction1(Math::exp)) 18 | set("log", KotlinFunction1(Math::log)) 19 | set("pow", KotlinFunction2(Math::pow)) 20 | set("sq", KotlinFunction1 { it -> it * it }) 21 | set("cb", KotlinFunction1 { it -> it * it * it }) 22 | set("cbrt", KotlinFunction1(Math::cbrt)) 23 | set("tan", KotlinFunction1(Math::tan)) 24 | set("atan", KotlinFunction1(Math::atan)) 25 | set("atan2", KotlinFunction2(Math::atan2)) 26 | set("sinh", KotlinFunction1(Math::sinh)) 27 | set("cosh", KotlinFunction1(Math::cosh)) 28 | set("tanh", KotlinFunction1(Math::tanh)) 29 | set("asin", KotlinFunction1(Math::asin)) 30 | set("acos", KotlinFunction1(Math::acos)) 31 | set("round", KotlinFunction1(Math::round)) 32 | set("random", KotlinFunction0(Math::random)) 33 | set("toDegrees", KotlinFunction1(Math::toDegrees)) 34 | set("toRadians", KotlinFunction1(Math::toRadians)) 35 | set("max", KotlinFunction2(Math::max)) 36 | set("min", KotlinFunction2(Math::min)) 37 | set("hypot", KotlinFunction2(Math::hypot)) 38 | set("signum", KotlinFunction1(Math::signum)) 39 | set("nextAfter", KotlinFunction2(Math::nextAfter)) 40 | set("ulp", KotlinFunction1(Math::ulp)) 41 | set("getExponent", KotlinFunction1(Math::getExponent)) 42 | set("getExponent", KotlinFunction1(Math::getExponent)) 43 | set("nextUp", KotlinFunction1(Math::nextUp)) 44 | set("nextDown", KotlinFunction1(Math::nextDown)) 45 | set("copySign", KotlinFunction2(Math::copySign)) 46 | set("expm1", KotlinFunction1(Math::expm1)) 47 | set("log1p", KotlinFunction1(Math::log1p)) 48 | } 49 | it.define("math", math) 50 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/standardLibrary/Module.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis.standardLibrary 2 | 3 | import me.snwy.oasis.Environment 4 | import me.snwy.oasis.Interpreter 5 | 6 | data class Module(val name: String, val func: (Environment, Interpreter) -> Unit) -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/standardLibrary/Panic.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis.standardLibrary 2 | 3 | import me.snwy.oasis.* 4 | 5 | val panic = Module("panic") { it, _ -> 6 | it.define("panic", KotlinFunction1 { it -> 7 | throw OasisException(it) 8 | }) 9 | it.define("iteratorExhausted", KotlinFunction0 { _ -> 10 | throw InternalException(ExceptionType.ITERATOR_EMPTY) 11 | }) 12 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/standardLibrary/Plot.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis.standardLibrary 2 | 3 | import me.snwy.oasis.* 4 | import org.knowm.xchart.QuickChart 5 | import org.knowm.xchart.SwingWrapper 6 | import javax.swing.JFrame 7 | 8 | val plot = Module("Plot") { env, interpreter -> 9 | val plot = OasisPrototype(base, -1, interpreter).apply { 10 | set( 11 | "function", 12 | KotlinFunction4 { interpreter, x, y, precision, z -> 13 | val xData = 0.until(y.toInt() * precision.toInt()).map { it.toDouble() / precision } 14 | val yData = xData.map { 15 | z.call(interpreter, listOf(it)) as? Double ?: throw RuntimeError( 16 | line, 17 | "Plot function must return numeric value" 18 | ) 19 | } 20 | val chart = QuickChart.getChart(x, "X", "Y", "f(x)", xData.toDoubleArray(), yData.toDoubleArray()) 21 | val frame = SwingWrapper(chart).displayChart() 22 | javax.swing.SwingUtilities.invokeLater { 23 | frame.defaultCloseOperation = JFrame.DISPOSE_ON_CLOSE 24 | } 25 | }) 26 | set( 27 | "functionWithRange", 28 | KotlinFunction5 { interpreter, x, range, y, precision, z -> 29 | val xData = range.toInt().until(y.toInt() * precision.toInt() + 1).map { it.toDouble() / precision } 30 | val yData = xData.map { 31 | z.call(interpreter, listOf(it)) as? Double ?: throw RuntimeError( 32 | line, 33 | "Plot function must return numeric value" 34 | ) 35 | } 36 | val chart = QuickChart.getChart(x, "X", "Y", "f(x)", xData.toDoubleArray(), yData.toDoubleArray()) 37 | val frame = SwingWrapper(chart).displayChart() 38 | javax.swing.SwingUtilities.invokeLater { 39 | frame.defaultCloseOperation = JFrame.DISPOSE_ON_CLOSE 40 | } 41 | }) 42 | set("data", KotlinFunction2> { x, y -> 43 | val data = if (y.all { it is Number }) { 44 | ArrayList(y.map { it as Double }) 45 | } else { 46 | throw RuntimeError(line, "data must be numeric") 47 | } 48 | val xAxis = 0.until(data.size).map { it.toDouble() } 49 | val chart = QuickChart.getChart(x, "X", "Y", "data", xAxis.toDoubleArray(), data.toDoubleArray()) 50 | val frame = SwingWrapper(chart).displayChart() 51 | javax.swing.SwingUtilities.invokeLater { 52 | frame.defaultCloseOperation = JFrame.DISPOSE_ON_CLOSE 53 | } 54 | }) 55 | } 56 | env.define("plot", plot) 57 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/standardLibrary/Prototype.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis.standardLibrary 2 | 3 | import me.snwy.oasis.KotlinFunction1 4 | import me.snwy.oasis.KotlinFunction2 5 | import me.snwy.oasis.OasisPrototype 6 | 7 | var prototype = Module("prototype") { it, interpreter -> 8 | val prototype = OasisPrototype(base, -1, interpreter).apply { 9 | set("setPrototypeOf", KotlinFunction2 { x, y -> 10 | x.inherit = y 11 | }) 12 | set("getPrototypeOf", KotlinFunction1 { it -> 13 | it.inherit!! 14 | }) 15 | set("toMap", KotlinFunction1 { interpreter, it -> 16 | createHashMap(it.body as HashMap, interpreter) 17 | }) 18 | set("fromMap", KotlinFunction1 { interpreter, it -> 19 | OasisPrototype(base, line, interpreter).apply { 20 | body = it.get("__map") as HashMap 21 | } 22 | }) 23 | } 24 | it.define("prototype", prototype) 25 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/standardLibrary/PrototypeDeserializer.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis.standardLibrary 2 | 3 | import com.google.gson.JsonDeserializationContext 4 | import com.google.gson.JsonDeserializer 5 | import com.google.gson.JsonElement 6 | import me.snwy.oasis.Interpreter 7 | import me.snwy.oasis.OasisPrototype 8 | import me.snwy.oasis.line 9 | import java.lang.reflect.Type 10 | 11 | class PrototypeDeserializer(var interpreter: Interpreter) : JsonDeserializer { 12 | override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): OasisPrototype { 13 | val result = OasisPrototype(base, line, interpreter) 14 | json?.asJsonObject?.entrySet()?.map { 15 | result.set(it.key, it.value) 16 | } 17 | return result 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/standardLibrary/PrototypeSerializer.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis.standardLibrary 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.JsonElement 5 | import com.google.gson.JsonSerializationContext 6 | import com.google.gson.JsonSerializer 7 | import me.snwy.oasis.OasisCallable 8 | import me.snwy.oasis.OasisPrototype 9 | import me.snwy.oasis.RuntimeError 10 | import me.snwy.oasis.line 11 | import java.lang.reflect.Type 12 | 13 | class PrototypeSerializer : JsonSerializer { 14 | override fun serialize(src: OasisPrototype?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement { 15 | if (src != null) { 16 | return if (src.body.containsKey("__serialize")) { 17 | Gson().toJsonTree(src.interpreter?.let { 18 | (src.get("__serialize") as OasisCallable).call( 19 | it, 20 | listOf() 21 | ) 22 | }) 23 | } else { 24 | Gson().toJsonTree(src.body) 25 | } 26 | } else { 27 | throw RuntimeError(line, "Can't serialize null.") 28 | } 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/standardLibrary/RangeAndMap.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis.standardLibrary 2 | 3 | import me.snwy.oasis.KotlinFunction2 4 | 5 | val range = Module("range") { it, _ -> 6 | it.define("range", KotlinFunction2(::rangeFn)) 7 | } 8 | 9 | fun rangeFn(base: Number, ceil: Number): ArrayList { 10 | return if (base.toInt() > ceil.toInt()) { 11 | ArrayList( 12 | (ceil.toInt()..base.toInt()) 13 | .iterator() 14 | .asSequence() 15 | .toList() 16 | .reversed() 17 | ) 18 | } else { 19 | ArrayList( 20 | (base.toInt()..ceil.toInt()) 21 | .iterator() 22 | .asSequence() 23 | .toList() 24 | ) 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/standardLibrary/Sockets.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis.standardLibrary 2 | 3 | import me.snwy.oasis.Interpreter 4 | import me.snwy.oasis.KotlinFunction0 5 | import me.snwy.oasis.KotlinFunction1 6 | import me.snwy.oasis.OasisPrototype 7 | import java.net.ServerSocket 8 | import java.net.Socket 9 | 10 | fun constructSocket(interpreter: Interpreter, port: Double): OasisPrototype { 11 | val proto = OasisPrototype(base, -1, interpreter) 12 | proto.set("__socket", ServerSocket(port.toInt())) 13 | proto.set("port", port) 14 | proto.set("accepting", true) 15 | proto.set("waitForConnection", KotlinFunction0 { interpreter -> 16 | val clientSocket = (proto.get("__socket") as ServerSocket).accept() 17 | val connectionProto = OasisPrototype(base, -1, interpreter).apply { 18 | set("address", clientSocket.inetAddress.toString()) 19 | set("bsend", KotlinFunction1(clientSocket.getOutputStream()::write)) 20 | set("send", KotlinFunction1 { it -> clientSocket.getOutputStream().write(it.toByteArray()) }) 21 | set("read", KotlinFunction0 { _ -> clientSocket.getInputStream().readBytes().asList() }) 22 | set("sread", KotlinFunction0 { _ -> String(clientSocket.getInputStream().readBytes()) }) 23 | set("close", KotlinFunction0 { _ -> clientSocket.close(); set("accepting", false) }) 24 | } 25 | connectionProto 26 | }) 27 | return proto 28 | } 29 | 30 | fun socketConnect(interpreter: Interpreter, port: Double, ip: String): OasisPrototype { 31 | val connectionProto = OasisPrototype(base, -1, interpreter) 32 | connectionProto.set("__socket", Socket(ip, port.toInt())) 33 | connectionProto.set("accepting", true) 34 | val sock = connectionProto.get("__socket") as Socket 35 | connectionProto.apply { 36 | set("address", sock.inetAddress.toString()) 37 | set("bsend", KotlinFunction1(sock.getOutputStream()::write)) 38 | set("send", KotlinFunction1 { it -> sock.getOutputStream().write(it.toByteArray()) }) 39 | set("read", KotlinFunction0 { _ -> sock.getInputStream().readBytes().asList() }) 40 | set("sread", KotlinFunction0 { _ -> String(sock.getInputStream().readBytes()) }) 41 | set("close", KotlinFunction0 { _ -> sock.close(); connectionProto.set("accepting", false) }) 42 | } 43 | return connectionProto 44 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/standardLibrary/StandardLibrary.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis.standardLibrary 2 | 3 | import me.snwy.oasis.Environment 4 | import me.snwy.oasis.Interpreter 5 | import me.snwy.oasis.KotlinFunction0 6 | import me.snwy.oasis.OasisPrototype 7 | 8 | var base = OasisPrototype(null, -1) 9 | 10 | object StandardLibrary { 11 | private var modules = arrayListOf( 12 | // Base modules 13 | io, 14 | string, 15 | time, 16 | type, 17 | list, 18 | range, 19 | sys, 20 | hashmap, 21 | json, 22 | prototype, 23 | math, 24 | async, 25 | api, 26 | import, 27 | func, 28 | panic, 29 | plot, 30 | ) 31 | 32 | fun addModule(x: Module) = modules.add(x) 33 | 34 | fun generateLib(x: Environment, y: Interpreter) { 35 | base.let { it.set("toString", KotlinFunction0 { _ -> return@KotlinFunction0 "" }) } 36 | modules.map { it.func(x, y) } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/standardLibrary/String.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis.standardLibrary 2 | 3 | import me.snwy.oasis.KotlinFunction1 4 | import me.snwy.oasis.KotlinFunction2 5 | import me.snwy.oasis.KotlinFunction3 6 | import me.snwy.oasis.OasisPrototype 7 | import java.util.* 8 | 9 | val string = Module("string") { it, interpreter -> 10 | val string = OasisPrototype(base, -1, interpreter) 11 | string.set("size", KotlinFunction1 { z -> z.length.toDouble() }) 12 | string.set( 13 | "substring", 14 | KotlinFunction3 { x, y, z -> x.substring(y.toInt(), z.toInt()) }) 15 | string.set("toUpperCase", KotlinFunction1 { z -> z.uppercase(Locale.getDefault()) }) 16 | string.set("toLowerCase", KotlinFunction1 { z -> z.lowercase(Locale.getDefault()) }) 17 | string.set("trim", KotlinFunction1 { z -> z.trim() }) 18 | string.set("trimStart", KotlinFunction1 { z -> z.trimStart() }) 19 | string.set("trimEnd", KotlinFunction1 { z -> z.trimEnd() }) 20 | string.set("replace", KotlinFunction3 { x, y, z -> x.replace(y, z) }) 21 | string.set( 22 | "split", 23 | KotlinFunction2, String, String> { x, y -> x.split(y).filter { it != y } as ArrayList }) 24 | string.set("contains", KotlinFunction2 { x, y -> x.contains(y) }) 25 | string.set("startsWith", KotlinFunction2 { x, y -> x.startsWith(y) }) 26 | string.set("endsWith", KotlinFunction2 { x, y -> x.endsWith(y) }) 27 | string.set( 28 | "toCharArray", 29 | KotlinFunction1, String> { z -> z.toCharArray().toList() as ArrayList }) 30 | string.set("isNum", KotlinFunction1 { x -> 31 | try { 32 | val num = x.toDouble() 33 | return@KotlinFunction1 true 34 | } catch (e: NumberFormatException) { 35 | return@KotlinFunction1 false 36 | } 37 | }) 38 | it.define("string", string) 39 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/standardLibrary/Sys.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis.standardLibrary 2 | 3 | import me.snwy.oasis.KotlinFunction0 4 | import me.snwy.oasis.KotlinFunction1 5 | import me.snwy.oasis.OasisPrototype 6 | import me.snwy.oasis.Optimizer 7 | import java.io.BufferedReader 8 | import java.io.InputStreamReader 9 | import kotlin.system.exitProcess 10 | 11 | val sys = Module("sys") { it, interpreter -> 12 | val sys = OasisPrototype(base, -1, interpreter) 13 | sys.set("exit", KotlinFunction1(::exitProcess)) 14 | sys.set("exec", KotlinFunction1 { cmd -> 15 | val process = Runtime.getRuntime().exec(cmd) 16 | val reader = BufferedReader(InputStreamReader(process.inputStream)) 17 | var line: String 18 | while (reader.readLine().also { line = it ?: "" } != null) { 19 | println(line) 20 | } 21 | }) 22 | sys.set("vars", KotlinFunction0> { interpreter -> 23 | ArrayList(Optimizer.nameMap.values) 24 | }) 25 | it.define("sys", sys) 26 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/standardLibrary/Time.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis.standardLibrary 2 | 3 | import me.snwy.oasis.KotlinFunction0 4 | import me.snwy.oasis.OasisPrototype 5 | import java.time.LocalDateTime 6 | import java.util.* 7 | 8 | val time = Module("time") { it, interpreter -> 9 | val time = OasisPrototype(base, -1, interpreter) 10 | time.set("clock", KotlinFunction0 { _ -> Date().time.toDouble() }) 11 | time.set("now", KotlinFunction0 { interpreter -> 12 | val currentTime = OasisPrototype(base, -1, interpreter) 13 | val time = LocalDateTime.now() 14 | currentTime.set("year", time.year) 15 | currentTime.set("month", time.monthValue) 16 | currentTime.set("day", time.dayOfMonth) 17 | currentTime.set("hour", time.hour) 18 | currentTime.set("min", time.minute) 19 | currentTime.set("sec", time.second) 20 | currentTime.set("toString", KotlinFunction0 { _ -> 21 | "${currentTime.get("hour")}:${currentTime.get("min")}:${currentTime.get("sec")} ${currentTime.get("day")}/${ 22 | currentTime.get( 23 | "month" 24 | ) 25 | }/${currentTime.get("year")}" 26 | }) 27 | return@KotlinFunction0 currentTime 28 | }) 29 | it.define("time", time) 30 | } 31 | -------------------------------------------------------------------------------- /src/main/kotlin/me/snwy/oasis/standardLibrary/Type.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis.standardLibrary 2 | 3 | import me.snwy.oasis.* 4 | 5 | val type = Module("type") { it, interpreter -> 6 | val type = OasisPrototype(base, -1, interpreter) 7 | type.set("string", KotlinFunction1 { z -> z.toString() }) 8 | type.set("char", KotlinFunction1 { z -> z as Char }) 9 | type.set("num", KotlinFunction1 { z -> z.toString().toDouble() }) 10 | type.set("bytes", KotlinFunction1 { z -> 11 | when (z) { 12 | is List<*> -> { 13 | val r = ByteArray(z.size) 14 | var li = 0 15 | z.map { v -> 16 | r[li] = when (v) { 17 | is Double -> v.toRawBits().toByte() 18 | is Int -> v.toByte() 19 | is Boolean -> if (v) 0x1 else 0x0 20 | is Char -> v.code.toByte() 21 | else -> throw RuntimeError(line, "'${v} cannot be converted to byte") 22 | }; li += 1 23 | } 24 | return@KotlinFunction1 r 25 | } 26 | is String -> { 27 | val r = ByteArray(z.length) 28 | var li = 0 29 | z.map { v -> r[li] = v.code.toByte(); li += 1 } 30 | return@KotlinFunction1 r 31 | } 32 | else -> { 33 | throw RuntimeError(line, "'${z.toString()}' cannot be represented as a bytes-object") 34 | } 35 | } 36 | }) 37 | type.set("isString", KotlinFunction1 { x -> 38 | return@KotlinFunction1 x is String 39 | }) 40 | type.set("isNum", KotlinFunction1 { x -> 41 | return@KotlinFunction1 x is Number 42 | }) 43 | type.set("isChar", KotlinFunction1 { x -> 44 | return@KotlinFunction1 x is Char 45 | }) 46 | type.set("isBool", KotlinFunction1 { x -> 47 | return@KotlinFunction1 x is Boolean 48 | }) 49 | type.set("isList", KotlinFunction1 { x -> 50 | return@KotlinFunction1 x is List<*> || x is ArrayList<*> || x is MutableList<*> 51 | }) 52 | type.set("isBytes", KotlinFunction1 { x -> 53 | return@KotlinFunction1 x is ByteArray 54 | }) 55 | type.set("isNull", KotlinFunction1 { x -> 56 | return@KotlinFunction1 x == null 57 | }) 58 | type.set("isObject", KotlinFunction1 { x -> 59 | return@KotlinFunction1 x is OasisPrototype 60 | }) 61 | type.set("isFunction", KotlinFunction1 { x -> 62 | return@KotlinFunction1 x is OasisCallable 63 | }) 64 | type.set("typeName", KotlinFunction1 { x -> 65 | if (x != null) { 66 | return@KotlinFunction1 x.javaClass.simpleName 67 | } else { 68 | return@KotlinFunction1 "null" 69 | } 70 | }) 71 | it.define("typeof", KotlinFunction1 { x -> 72 | return@KotlinFunction1 if (x != null) { 73 | x::class.java 74 | } else { 75 | Nothing::class.java 76 | } 77 | }) 78 | it.define("type", type) 79 | it.define("numType", Number::class.java) 80 | it.define("stringType", String::class.java) 81 | it.define("charType", Char::class.java) 82 | it.define("boolType", Boolean::class.java) 83 | it.define("listType", ArrayList::class.java) 84 | it.define("bytesType", ByteArray::class.java) 85 | it.define("nullType", Nothing::class.java) 86 | it.define("objectType", OasisPrototype::class.java) 87 | it.define("functionType", OasisCallable::class.java) 88 | } -------------------------------------------------------------------------------- /src/main/resources/libpy.oa: -------------------------------------------------------------------------------- 1 | // libpy: bindings from standard Oasis library to Python 2 | 3 | let io = proto 4 | let print = print 5 | let read = input 6 | let open = open 7 | end -------------------------------------------------------------------------------- /src/test/kotlin/me/snwy/oasis/OasisEnvironmentTest.kt: -------------------------------------------------------------------------------- 1 | package me.snwy.oasis 2 | 3 | import org.junit.jupiter.api.Test 4 | 5 | import org.junit.jupiter.api.Assertions.* 6 | 7 | internal class OasisEnvironmentTest { 8 | 9 | private val testEnvironment = OasisEnvironment() 10 | 11 | @Test 12 | fun eval() { 13 | println("string literals") 14 | assertEquals( 15 | "Hello, world!", 16 | testEnvironment.doExpr("\"Hello, world!\"") 17 | ) 18 | println("integer literals") 19 | assertEquals( 20 | 42, 21 | testEnvironment.doExpr("42") 22 | ) 23 | println("float literals") 24 | assertEquals( 25 | 3.14, 26 | testEnvironment.doExpr("3.14") 27 | ) 28 | println("boolean literals") 29 | assertEquals( 30 | true, 31 | testEnvironment.doExpr("true") 32 | ) 33 | assertEquals( 34 | false, 35 | testEnvironment.doExpr("false") 36 | ) 37 | println("null literals") 38 | assertEquals( 39 | null, 40 | testEnvironment.doExpr("nil") 41 | ) 42 | println("basic operators") 43 | assertEquals( 44 | 1, 45 | testEnvironment.doExpr("1 + 0") 46 | ) 47 | assertEquals( 48 | 0, 49 | testEnvironment.doExpr("1 - 1") 50 | ) 51 | assertEquals( 52 | 2, 53 | testEnvironment.doExpr("1 * 2") 54 | ) 55 | assertEquals( 56 | 2, 57 | testEnvironment.doExpr("4 / 2") 58 | ) 59 | assertEquals( 60 | 0, 61 | testEnvironment.doExpr("4 % 2") 62 | ) 63 | println("unary operators") 64 | assertEquals( 65 | -1, 66 | testEnvironment.doExpr("-1") 67 | ) 68 | assertEquals( 69 | false, 70 | testEnvironment.doExpr("not true") 71 | ) 72 | println("binary operators") 73 | assertEquals( 74 | true, 75 | testEnvironment.doExpr("1 < 2") 76 | ) 77 | assertEquals( 78 | true, 79 | testEnvironment.doExpr("1 <= 1") 80 | ) 81 | assertEquals( 82 | true, 83 | testEnvironment.doExpr("2 > 1") 84 | ) 85 | assertEquals( 86 | true, 87 | testEnvironment.doExpr("2 >= 1") 88 | ) 89 | assertEquals( 90 | true, 91 | testEnvironment.doExpr("1 == 1") 92 | ) 93 | assertEquals( 94 | true, 95 | testEnvironment.doExpr("1 != 0") 96 | ) 97 | assertEquals( 98 | true, 99 | testEnvironment.doExpr("true and true") 100 | ) 101 | assertEquals( 102 | false, 103 | testEnvironment.doExpr("true and false") 104 | ) 105 | assertEquals( 106 | true, 107 | testEnvironment.doExpr("true or false") 108 | ) 109 | assertEquals( 110 | false, 111 | testEnvironment.doExpr("false or false") 112 | ) 113 | println("conditional operators") 114 | assertEquals( 115 | true, 116 | testEnvironment.doExpr("if true => true else false") 117 | ) 118 | assertEquals( 119 | false, 120 | testEnvironment.doExpr("if false => true else false") 121 | ) 122 | println("variable assignment") 123 | assertEquals( 124 | 1, 125 | testEnvironment.eval("let a = 1").run { testEnvironment.run(); testEnvironment.doExpr("a") } 126 | ) 127 | assertEquals( 128 | 2, 129 | testEnvironment.doExpr("a = 2") 130 | ) 131 | println("functions") 132 | assertEquals( 133 | 3, 134 | testEnvironment.eval("let add = fn(x, y) => x + y ").run{testEnvironment.run(); testEnvironment.doExpr("add(1, 2)")}, 135 | ) 136 | assertEquals( 137 | 1, 138 | testEnvironment.eval("let fn1 = fn => 1").run { testEnvironment.run(); testEnvironment.doExpr("fn1()") } 139 | ) 140 | assertEquals( 141 | 5, 142 | testEnvironment.eval("let fn2 = fn(x, y) " + 143 | "return x + y " + 144 | "end" 145 | ).run { 146 | testEnvironment.run() 147 | testEnvironment.doExpr("fn2(2, 3)") 148 | } 149 | ) 150 | println("destructuring") 151 | assertEquals( 152 | listOf(1, 2, 3), 153 | testEnvironment.eval("let x, y, z = (1, 2, 3)").run { testEnvironment.run(); testEnvironment.doExpr("[x, y, z]") } 154 | ) 155 | assertEquals( 156 | listOf(4, 5, 6), 157 | testEnvironment.eval("(x, y, z) = (4, 5, 6)").run { testEnvironment.run(); testEnvironment.doExpr("[x, y, z]") } 158 | ) 159 | assertEquals( 160 | listOf(1, 2, 3), 161 | testEnvironment.eval("let foo = fn(x, y, z) => [x, y, z]").run { testEnvironment.run(); testEnvironment.doExpr("foo(*[1, 2, 3])") } 162 | ) 163 | } 164 | } --------------------------------------------------------------------------------