├── .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 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
16 |
17 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
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