├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── BUG_TEMPLATE.md │ ├── FEATURE_TEMPLATE.md │ └── QUESTION_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── build.gradle.kts ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── kotlin-js-store └── yarn.lock ├── settings.gradle.kts └── src ├── commonMain └── kotlin │ └── dev │ └── rhovas │ └── interpreter │ ├── Interpreter.kt │ ├── analyzer │ ├── AnalyzeException.kt │ ├── Analyzer.kt │ └── rhovas │ │ ├── DeclarationPhase.kt │ │ ├── DefinitionPhase.kt │ │ ├── RegistrationPhase.kt │ │ ├── RhovasAnalyzer.kt │ │ └── RhovasIr.kt │ ├── environment │ ├── Component.kt │ ├── Function.kt │ ├── Method.kt │ ├── Modifiers.kt │ ├── Object.kt │ ├── Property.kt │ ├── Scope.kt │ ├── Variable.kt │ └── type │ │ ├── Type.kt │ │ ├── isInvariantSubtypeOf.kt │ │ ├── isSubtypeOf.kt │ │ └── unify.kt │ ├── evaluator │ ├── EvaluateException.kt │ ├── Evaluator.kt │ └── StackFrame.kt │ ├── library │ ├── AnyInitializer.kt │ ├── AtomInitializer.kt │ ├── BooleanInitializer.kt │ ├── ComparableInitializer.kt │ ├── DecimalInitializer.kt │ ├── DynamicInitializer.kt │ ├── EquatableInitializer.kt │ ├── ExceptionInitializer.kt │ ├── HashableInitializer.kt │ ├── IntegerInitializer.kt │ ├── IterableInitializer.kt │ ├── IteratorInitializer.kt │ ├── KernelInitializer.kt │ ├── LambdaInitializer.kt │ ├── Library.kt │ ├── ListInitializer.kt │ ├── MapInitializer.kt │ ├── MathInitializer.kt │ ├── NullableInitializer.kt │ ├── RegexInitializer.kt │ ├── ResultInitializer.kt │ ├── SetInitializer.kt │ ├── StringInitializer.kt │ ├── StructInitializer.kt │ ├── TupleInitializer.kt │ ├── TypeInitializer.kt │ └── VoidInitializer.kt │ └── parser │ ├── Input.kt │ ├── Lexer.kt │ ├── ParseException.kt │ ├── Parser.kt │ ├── Token.kt │ ├── dsl │ ├── DslAst.kt │ ├── DslLexer.kt │ ├── DslParser.kt │ └── DslTokenType.kt │ └── rhovas │ ├── RhovasAst.kt │ ├── RhovasLexer.kt │ ├── RhovasParser.kt │ └── RhovasTokenType.kt ├── commonTest ├── kotlin │ └── dev │ │ └── rhovas │ │ └── interpreter │ │ ├── Platform.kt │ │ ├── RhovasSpec.kt │ │ ├── analyzer │ │ └── rhovas │ │ │ └── RhovasAnalyzerTests.kt │ │ ├── environment │ │ └── type │ │ │ └── TypeTests.kt │ │ ├── evaluator │ │ └── EvaluatorTests.kt │ │ ├── parser │ │ ├── dsl │ │ │ ├── DslLexerTests.kt │ │ │ └── DslParserTests.kt │ │ └── rhovas │ │ │ ├── RhovasLexerTests.kt │ │ │ └── RhovasParserTests.kt │ │ └── programs │ │ ├── ProgramTests.kt │ │ └── rosettacode │ │ └── RosettaCodeTests.kt └── resources │ └── dev │ └── rhovas │ └── interpreter │ └── programs │ └── rosettacode │ ├── Classes.rho │ ├── Factorial.rho │ ├── Fibonacci_sequence.rho │ ├── FizzBuzz.rho │ ├── Hello_world │ └── Text.rho │ └── Palindrome_detection.rho ├── jsMain └── kotlin │ └── dev.rhovas.interpreter │ ├── Main.kt │ └── eval.kt ├── jsTest └── kotlin │ └── dev │ └── rhovas │ └── interpreter │ └── Platform.kt ├── jvmMain └── kotlin │ └── dev │ └── rhovas │ └── interpreter │ └── Main.kt └── jvmTest └── kotlin └── dev └── rhovas └── interpreter └── Platform.kt /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ["WillBAnders"] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Bug Report" 3 | about: "Report a bug or other unintended behavior" 4 | title: "" 5 | labels: "bug" 6 | assignees: "" 7 | --- 8 | 9 | 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Feature Request" 3 | about: "Suggest a new feature or other addition" 4 | title: "" 5 | labels: "feature" 6 | assignees: "" 7 | --- 8 | 9 | 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/QUESTION_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "NOTICE: Questions should NOT be asked through GitHub" 3 | about: "Please ask questions through our Discord server" 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | # NOTICE: Questions should NOT be asked through GitHub 10 | 11 | Questions about Rhovas should be directed to our [Discord Server](https://discord.gg/gm96xd8). 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the project lead at `WillBAnders@gmail.com`. All complaints will be 63 | reviewed and investigated promptly and fairly. 64 | 65 | All community leaders are obligated to respect the privacy and security of the 66 | reporter of any incident. 67 | 68 | ## Enforcement Guidelines 69 | 70 | Community leaders will follow these Community Impact Guidelines in determining 71 | the consequences for any action they deem in violation of this Code of Conduct: 72 | 73 | ### 1. Correction 74 | 75 | **Community Impact**: Use of inappropriate language or other behavior deemed 76 | unprofessional or unwelcome in the community. 77 | 78 | **Consequence**: A private, written warning from community leaders, providing 79 | clarity around the nature of the violation and an explanation of why the 80 | behavior was inappropriate. A public apology may be requested. 81 | 82 | ### 2. Warning 83 | 84 | **Community Impact**: A violation through a single incident or series 85 | of actions. 86 | 87 | **Consequence**: A warning with consequences for continued behavior. No 88 | interaction with the people involved, including unsolicited interaction with 89 | those enforcing the Code of Conduct, for a specified period of time. This 90 | includes avoiding interactions in community spaces as well as external channels 91 | like social media. Violating these terms may lead to a temporary or 92 | permanent ban. 93 | 94 | ### 3. Temporary Ban 95 | 96 | **Community Impact**: A serious violation of community standards, including 97 | sustained inappropriate behavior. 98 | 99 | **Consequence**: A temporary ban from any sort of interaction or public 100 | communication with the community for a specified period of time. No public or 101 | private interaction with the people involved, including unsolicited interaction 102 | with those enforcing the Code of Conduct, is allowed during this period. 103 | Violating these terms may lead to a permanent ban. 104 | 105 | ### 4. Permanent Ban 106 | 107 | **Community Impact**: Demonstrating a pattern of violation of community 108 | standards, including sustained inappropriate behavior, harassment of an 109 | individual, or aggression toward or disparagement of classes of individuals. 110 | 111 | **Consequence**: A permanent ban from any sort of public interaction within 112 | the community. 113 | 114 | ## Attribution 115 | 116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 117 | version 2.0, available at 118 | [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. 119 | 120 | Community Impact Guidelines were inspired by 121 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 122 | 123 | For answers to common questions about this code of conduct, see the FAQ at 124 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available 125 | at [https://www.contributor-covenant.org/translations][translations]. 126 | 127 | [homepage]: https://www.contributor-covenant.org 128 | [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html 129 | [Mozilla CoC]: https://github.com/mozilla/diversity 130 | [FAQ]: https://www.contributor-covenant.org/faq 131 | [translations]: https://www.contributor-covenant.org/translations 132 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for your interest in contributing! Please note that Rhovas is still a 4 | pretty new language and largely a one-person project, however there are two main 5 | ways you can contribute: 6 | 7 | - Try writing example programs in Rhovas (the [Online Editor](https://rhovas.dev/editor/) 8 | may help here) and provide feedback to help improve the language. Feel free 9 | to ask in [Discord](https://discord.gg/gm96xd8) if you're not sure how to 10 | accomplish something or come across a bug. 11 | - If you're looking for ideas we have a [RosettaCode page](https://rosettacode.org/wiki/Category:Rhovas) 12 | that's a little short on solutions. We also use these as test cases that 13 | represent practical programs, so it helps in that way too! 14 | - For code contributions, you're welcome to work on any open issue or propose 15 | something new if there's a feature you're waiting on. In general, please make 16 | sure to keep me (`@WillBAnders`) in the loop so I know what's being worked on 17 | and can provide guidance along the way - communication is key! 18 | 19 | If you have any questions, feel free to ask in our [Discord Server](https://discord.gg/gm96xd8). 20 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 WillBAnders 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rhovas 2 | 3 | Welcome! Rhovas is a programming language intended for API design and 4 | enforcement. Using Rhovas, developers can better express the contracts and 5 | intention of their code to help create maintainable, correct, and performant 6 | software. 7 | 8 | This repository is the implementation of Rhovas itself. For more information on 9 | Rhovas, see the following links: 10 | 11 | - [Rhovas Website](https://rhovas.dev): Information about Rhovas, most notably: 12 | - [Language Tour](https://rhovas.dev/learn/tour): A high-level overview of 13 | Rhovas that covers most of the important constructs/features. 14 | - [Online Editor](https://rhovas.dev/editor): An online editor for Rhovas 15 | which can be used without having to setup/build this project locally. 16 | - [Discord Server](https://discord.gg/gm96xd8): Discussion on Rhovas including 17 | language design, implementation, and anything related. 18 | 19 | ## Running Locally (Latest Changes) 20 | 21 | The [Online Editor](https://rhovas.dev/editor) runs the [latest release](https://github.com/Rhovas/Interpreter/releases) 22 | of Rhovas. To run the latest changes off of the current master branch (e.g. for 23 | testing unreleased features) you will need to build and run Rhovas locally. 24 | 25 | This process is made relatively straightforward using Gradle. You will need to 26 | have a JDK/JRE installation for Java (recommended latest LTS+), such as releases 27 | from [Adoptium](https://adoptium.net). 28 | 29 | ```sh 30 | #from the desired directory, e.g C:\dev\Rhovas: 31 | git clone https://github.com/Rhovas/Interpreter 32 | cd Interpreter 33 | ./gradlew build #eta: ~3-4m 34 | 35 | java -jar build/libs/Interpreter-${version}.jar #starts a REPL session 36 | java -jar build/libs/Interpreter-${version}.jar file.rho #executes a file 37 | ``` 38 | 39 | Note that while the REPL is convenient for quick testing, using a file is often 40 | safer as the REPL is lacking many common quality-of-life features for longer 41 | sessions (e.g. editing the last prompt to fix errors). 42 | 43 | ## Project Structure & Setup 44 | 45 | This information is for other developers to understand and contribute to the 46 | Rhovas interpreter. To try out the language itself, see the [Online Editor](https://rhovas.dev/editor). 47 | 48 | Rhovas is setup as a Kotlin Multiplatform project using Gradle. Kotlin is very 49 | similar to Rhovas, and multiplatform support allows both the online editor 50 | (`js`) and the command-line executable (`jvm`). IntelliJ is recommended for it's 51 | Gradle support (plus Kotlin); for details on how to open Gradle projects and run 52 | tasks see the [IntelliJ Gradle docs](https://www.jetbrains.com/help/idea/gradle.html). 53 | 54 | The project is structured as follows: 55 | 56 | - `commonMain`/`commonTest`: The core implementation of Rhovas shared between 57 | platforms. This is further subdivided into other packages but should be 58 | relatively easy to navigate. 59 | - `commonTest` contains a mix of unit and integration (full-program) tests. 60 | - `jsMain`/`jsTest`: Defines JavaScript bindings for use in the online editor. 61 | - `jvmMain`/`jvmTest`: Defines the Java/JVM main function for evaluating files 62 | and running the REPL. 63 | 64 | A few notable Gradle tasks are documented below. Gradle (and IntelliJ) will 65 | likely take some time to configure and index, as well as on the first build 66 | (which has to compile everything and setup nodejs for `jsTest`). 67 | 68 | - `build/build`: Builds all executables and runs all tests (`js` + `jvm`). 69 | - `js` executables are in `build/compileSync/main/productionExecutable` 70 | - `jvm` executable is in `build/libs/Interpreter-x.y.z.jar` 71 | - `build/clean`: Cleans the full `build` directory. 72 | - `other/compileDevelopmentExecutableKotlinJs`: Builds a development executable 73 | for Kotlin/JS, which minimizes most of the identifier mangling for debugging. 74 | - executable is in `build/compileSync/main/developmentExecutable` 75 | - `verification/allTests`: Runs all tests (`js` + `jvm`) 76 | 77 | For manual testing, it is often more convenient to create a run configuration 78 | which uses the JVM `Main.kt` directly rather than going through Gradle `build`. 79 | 80 | ## Sponsor Me! 81 | 82 | If you appreciate the work I do, please consider sponsoring me! Sponsors receive 83 | a `Sponsor` role on the [Discord Server](https://discord.gg/gm96xd8) and will 84 | also be listed on the Rhovas website. 85 | 86 | - [GitHub Sponsors](https://github.com/sponsors/WillBAnders) 87 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar 2 | import org.gradle.jvm.tasks.Jar 3 | import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest 4 | 5 | plugins { 6 | kotlin("multiplatform") version "2.0.0" 7 | id("io.kotest.multiplatform") version "5.9.1" 8 | id("com.github.johnrengelman.shadow") version "8.1.1" 9 | } 10 | 11 | group = "dev.rhovas.interpreter" 12 | version = "0.0.0" 13 | 14 | repositories { 15 | mavenCentral() 16 | } 17 | 18 | kotlin { 19 | js(IR) { 20 | binaries.executable() 21 | nodejs() 22 | } 23 | jvm() 24 | 25 | sourceSets { 26 | val commonMain by getting { 27 | dependencies { 28 | implementation("com.ionspin.kotlin:bignum:0.3.8") 29 | } 30 | } 31 | val commonTest by getting { 32 | dependencies { 33 | implementation(kotlin("test")) 34 | implementation("io.kotest:kotest-framework-engine:5.6.2") 35 | } 36 | } 37 | val jsMain by getting {} 38 | val jsTest by getting {} 39 | val jvmMain by getting {} 40 | val jvmTest by getting { 41 | dependencies { 42 | implementation("io.kotest:kotest-runner-junit5:5.6.2") 43 | } 44 | } 45 | } 46 | } 47 | 48 | tasks.withType { 49 | useJUnitPlatform() 50 | } 51 | 52 | val copyJsTestResources = task("copyJsTestResource") { 53 | from("/src/commonTest/resources") 54 | into("${project.buildDir}/js/packages/${project.name}-test/src/commonTest/resources") 55 | } 56 | 57 | tasks.withType { 58 | dependsOn(copyJsTestResources) 59 | } 60 | 61 | val shadowJvmJar = task("shadowJvmJar") { 62 | from(tasks.getByName("jvmJar").archiveFile) 63 | configurations.add(project.configurations.getByName("jvmRuntimeClasspath")) 64 | archiveClassifier.set("") 65 | manifest { 66 | attributes["Main-Class"] = "${project.group}.MainKt" 67 | } 68 | } 69 | 70 | tasks.build { 71 | dependsOn(shadowJvmJar) 72 | } 73 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rhovas/Interpreter/837ed37a04a5295d26cc64f16be065a41a7266aa/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-8.8-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | # Collect all arguments for the java command; 201 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 202 | # shell script including quotes and variable substitutions, so put them in 203 | # double quotes to make sure that they get re-expanded; and 204 | # * put everything else in single quotes, so that it's not re-expanded. 205 | 206 | set -- \ 207 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 208 | -classpath "$CLASSPATH" \ 209 | org.gradle.wrapper.GradleWrapperMain \ 210 | "$@" 211 | 212 | # Stop when "xargs" is not available. 213 | if ! command -v xargs >/dev/null 2>&1 214 | then 215 | die "xargs is not available" 216 | fi 217 | 218 | # Use "xargs" to parse quoted args. 219 | # 220 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 221 | # 222 | # In Bash we could simply go: 223 | # 224 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 225 | # set -- "${ARGS[@]}" "$@" 226 | # 227 | # but POSIX shell has neither arrays nor command substitution, so instead we 228 | # post-process each arg (as a line of input to sed) to backslash-escape any 229 | # character that might be a shell metacharacter, then use eval to reverse 230 | # that process (while maintaining the separation between arguments), and wrap 231 | # the whole thing up as a single "set" statement. 232 | # 233 | # This will of course break if any of these variables contains a newline or 234 | # an unmatched quote. 235 | # 236 | 237 | eval "set -- $( 238 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 239 | xargs -n1 | 240 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 241 | tr '\n' ' ' 242 | )" '"$@"' 243 | 244 | exec "$JAVACMD" "$@" 245 | -------------------------------------------------------------------------------- /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 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | rootProject.name = "Interpreter" 3 | 4 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/Interpreter.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter 2 | 3 | import dev.rhovas.interpreter.analyzer.AnalyzeException 4 | import dev.rhovas.interpreter.analyzer.rhovas.RhovasAnalyzer 5 | import dev.rhovas.interpreter.environment.Scope 6 | import dev.rhovas.interpreter.evaluator.EvaluateException 7 | import dev.rhovas.interpreter.evaluator.Evaluator 8 | import dev.rhovas.interpreter.library.Library 9 | import dev.rhovas.interpreter.parser.Input 10 | import dev.rhovas.interpreter.parser.ParseException 11 | import dev.rhovas.interpreter.parser.rhovas.RhovasAst 12 | import dev.rhovas.interpreter.parser.rhovas.RhovasParser 13 | 14 | var INTERPRETER = Interpreter() 15 | val EVALUATOR: Evaluator get() = INTERPRETER.evaluator 16 | 17 | class Interpreter( 18 | val stdin: () -> String = ::readln, 19 | val stdout: (String) -> Unit = ::println, 20 | ) { 21 | 22 | val scope = Scope.Definition(Library.SCOPE) 23 | val analyzer = RhovasAnalyzer(scope) 24 | val evaluator = Evaluator(scope) 25 | 26 | /** 27 | * Evaluates input and returns an optional string to be printed. For REPL 28 | * support, the input attempts to parse as an expression if source fails to 29 | * allow printing raw expressions. 30 | * 31 | * Note: This also updates the global `INTERPRETER` variable, since standard 32 | * library functions need access to the current evaluator and nothing better 33 | * has been set up yet. 34 | */ 35 | fun eval(input: Input): String? { 36 | val original = INTERPRETER 37 | return try { 38 | INTERPRETER = this 39 | val ast = try { 40 | RhovasParser(input).parse("source") 41 | } catch (e: ParseException) { 42 | try { 43 | RhovasParser(input).parse("expression") 44 | } catch (ignored: ParseException) { 45 | throw e //use the original exception 46 | } 47 | } 48 | val obj = evaluator.visit(analyzer.visit(ast)) 49 | when (ast) { 50 | is RhovasAst.Expression -> obj.methods.toString() 51 | else -> null 52 | } 53 | } catch (e: ParseException) { 54 | input.diagnostic(e.summary, e.details, e.range, e.context) 55 | } catch (e: AnalyzeException) { 56 | input.diagnostic(e.summary, e.details, e.range, e.context) 57 | } catch (e: EvaluateException) { 58 | input.diagnostic(e.summary, e.details, e.range, e.context) 59 | } catch (e: Evaluator.Throw) { 60 | "Uncaught exception: ${e.exception.value}" 61 | } catch (e: Exception) { 62 | "Unexpected exception: ${e.message}\n\n${e.stackTraceToString()}" 63 | } finally { 64 | INTERPRETER = original 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/analyzer/AnalyzeException.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.analyzer 2 | 3 | import dev.rhovas.interpreter.parser.Input 4 | 5 | class AnalyzeException( 6 | val summary: String, 7 | val details: String, 8 | val range: Input.Range, 9 | val context: List, 10 | ): Exception("${range.line}:${range.column}-${range.column + range.length}: ${summary}") 11 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/analyzer/Analyzer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.analyzer 2 | 3 | import dev.rhovas.interpreter.parser.Input 4 | import dev.rhovas.interpreter.parser.rhovas.RhovasAst 5 | import kotlin.contracts.ExperimentalContracts 6 | import kotlin.contracts.contract 7 | import kotlin.reflect.KClass 8 | 9 | abstract class Analyzer(internal var context: Context) { 10 | 11 | protected val Context.inputs get() = this[InputContext::class] 12 | 13 | data class Context( 14 | val items: Map>, 15 | ) { 16 | 17 | private val children = mutableListOf() 18 | 19 | operator fun , T>get(item: KClass): T { 20 | return items[item.simpleName]!!.value as T 21 | } 22 | 23 | fun child(): Context { 24 | return Context(items.mapValues { it.value.child() }).also { children.add(it) } 25 | } 26 | 27 | fun merge() { 28 | items.forEach { (k, v) -> 29 | (v as Item).merge(children.map { it.items[k]!!.value }) 30 | } 31 | children.clear() 32 | } 33 | 34 | fun with(vararg items: Item<*>): Context { 35 | return Context(this.items.toMutableMap().also { it.putAll(items.associateBy { it::class.simpleName!! }) }) 36 | } 37 | 38 | abstract class Item( 39 | val value: T, 40 | ) { 41 | 42 | abstract fun child(): Item 43 | 44 | abstract fun merge(children: List) 45 | 46 | } 47 | 48 | } 49 | 50 | data class InputContext( 51 | val inputs: ArrayDeque, 52 | ) : Context.Item>(inputs) { 53 | 54 | override fun child(): InputContext { 55 | return this 56 | } 57 | 58 | override fun merge(children: List>) {} 59 | 60 | } 61 | 62 | fun analyze(context: Context? = null, block: () -> T): T { 63 | val original = this.context 64 | this.context = context ?: original.child() 65 | try { 66 | return block() 67 | } finally { 68 | this.context = original.also { 69 | if (context == null) { 70 | it.merge() 71 | } 72 | } 73 | } 74 | } 75 | 76 | fun require(condition: Boolean, error: () -> AnalyzeException) { 77 | if (!condition) { 78 | throw error() 79 | } 80 | } 81 | 82 | fun error(ast: RhovasAst, summary: String, details: String): AnalyzeException { 83 | return error(summary, details, ast.context.firstOrNull()) 84 | } 85 | 86 | fun error(summary: String, details: String, range: Input.Range?): AnalyzeException { 87 | return AnalyzeException(summary, details, range ?: Input.Range(0, 1, 0, 0), context.inputs) 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/analyzer/rhovas/DeclarationPhase.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.analyzer.rhovas 2 | 3 | import dev.rhovas.interpreter.analyzer.Analyzer 4 | import dev.rhovas.interpreter.environment.Component 5 | import dev.rhovas.interpreter.environment.Modifiers 6 | import dev.rhovas.interpreter.environment.type.Type 7 | import dev.rhovas.interpreter.environment.Variable 8 | import dev.rhovas.interpreter.parser.rhovas.RhovasAst 9 | 10 | /** 11 | * Phase 2/4: Declares the type generics/inherits to allow proper construction 12 | * of types and subtype checking. 13 | */ 14 | class DeclarationPhase( 15 | private val analyzer: RhovasAnalyzer, 16 | ) { 17 | 18 | private val context get() = analyzer.context 19 | private val Analyzer.Context.scope get() = this[RhovasAnalyzer.ScopeContext::class] 20 | 21 | fun visit(ast: RhovasAst.Source) { 22 | ast.statements.filterIsInstance().forEach { visit(it.component) } 23 | } 24 | 25 | fun visit(ast: RhovasAst.Component) { 26 | when (ast) { 27 | is RhovasAst.Component.Struct -> visit(ast) 28 | is RhovasAst.Component.Class -> visit(ast) 29 | is RhovasAst.Component.Interface -> visit(ast) 30 | } 31 | } 32 | 33 | private fun visit(ast: RhovasAst.Component.Struct) = analyzer.analyze(ast.context) { 34 | val component = (context.scope.types[ast.name]!! as Type.Reference).component as Component.Struct 35 | ast.generics.forEach { 36 | analyzer.require(component.generics[it.first] == null) { analyzer.error(ast, 37 | "Redefined generic type.", 38 | "The generic type ${it.first} is already defined in this component.", 39 | ) } 40 | component.generics[it.first] = Type.Generic(it.first, it.second?.let { analyzer.visit(it).type } ?: Type.ANY) 41 | } 42 | val fields = mutableMapOf() 43 | analyzer.analyze { 44 | component.generics.forEach { context.scope.types.define(it.key, it.value) } 45 | ast.members.filterIsInstance().map { 46 | analyzer.require(fields[it.name] == null) { analyzer.error(ast, 47 | "Redefined field.", 48 | "The field ${it.name} is already defined in this component.", 49 | ) } 50 | fields[it.name] = Variable.Declaration(it.name, analyzer.visit(it.type).type, it.mutable) 51 | } 52 | } 53 | val (_, implements) = visitInherits(ast.inherits, component) 54 | component.inherits.add(Type.STRUCT[Type.Struct(fields)]) 55 | component.inherits.addAll(implements) 56 | } 57 | 58 | private fun visit(ast: RhovasAst.Component.Class) = analyzer.analyze(ast.context) { 59 | val component = (context.scope.types[ast.name]!! as Type.Reference).component as Component.Class 60 | ast.generics.forEach { 61 | analyzer.require(component.generics[it.first] == null) { analyzer.error(ast, 62 | "Redefined generic type.", 63 | "The generic type ${it.first} is already defined in this component.", 64 | ) } 65 | component.generics[it.first] = Type.Generic(it.first, it.second?.let { analyzer.visit(it).type } ?: Type.ANY) 66 | } 67 | val (extends, implements) = visitInherits(ast.inherits, component) 68 | component.inherits.add(extends ?: Type.ANY) 69 | component.inherits.addAll(implements) 70 | } 71 | 72 | private fun visit(ast: RhovasAst.Component.Interface) = analyzer.analyze(ast.context) { 73 | val component = (context.scope.types[ast.name]!! as Type.Reference).component as Component.Interface 74 | ast.generics.forEach { 75 | analyzer.require(component.generics[it.first] == null) { analyzer.error(ast, 76 | "Redefined generic type.", 77 | "The generic type ${it.first} is already defined in this component.", 78 | ) } 79 | component.generics[it.first] = Type.Generic(it.first, it.second?.let { analyzer.visit(it).type } ?: Type.ANY) 80 | } 81 | val (_, implements) = visitInherits(ast.inherits, component) 82 | component.inherits.addAll(implements.ifEmpty { listOf(Type.ANY) }) 83 | } 84 | 85 | private fun visitInherits(inherits: List, component: Component<*>): Pair> = analyzer.analyze { 86 | component.generics.forEach { context.scope.types.define(it.key, it.value) } 87 | var extends: Type.Reference? = null 88 | val implements = mutableListOf() 89 | inherits.forEach { 90 | val type = analyzer.visit(it).type.let { type -> 91 | type as? Type.Reference ?: throw analyzer.error(it, 92 | "Invalid inheritance type.", 93 | "The type ${type} cannot be inherited from as it is not a reference type.", 94 | ) 95 | } 96 | if (component is Component.Class && extends == null && implements.isEmpty() && type.component is Component.Class) { 97 | analyzer.require(type.component.modifiers.inheritance in listOf(Modifiers.Inheritance.VIRTUAL, Modifiers.Inheritance.ABSTRACT)) { analyzer.error(it, 98 | "Invalid class inheritance.", 99 | "The type ${type} cannot be inherited from as it is not virtual/abstract.", 100 | ) } 101 | extends = type as Type.Reference 102 | } else { 103 | analyzer.require(type.component is Component.Interface) { analyzer.error(it, 104 | "Invalid class inheritance.", 105 | "The type ${type} cannot be inherited from here as it is not an interface.", 106 | ) } 107 | implements.add(type as Type.Reference) 108 | } 109 | } 110 | Pair(extends, implements) 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/analyzer/rhovas/DefinitionPhase.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.analyzer.rhovas 2 | 3 | import dev.rhovas.interpreter.analyzer.Analyzer 4 | import dev.rhovas.interpreter.environment.Component 5 | import dev.rhovas.interpreter.environment.Function 6 | import dev.rhovas.interpreter.environment.Modifiers 7 | import dev.rhovas.interpreter.environment.Scope 8 | import dev.rhovas.interpreter.environment.type.Type 9 | import dev.rhovas.interpreter.environment.Variable 10 | import dev.rhovas.interpreter.parser.rhovas.RhovasAst 11 | 12 | /** 13 | * Phase 2/4: Declares the type methods to allow full semantic analysis. This 14 | * phase MUST be traversed in DAG order through the type hierarchy (in other 15 | * words, supertypes must be visited before subtypes). 16 | */ 17 | class DefinitionPhase( 18 | val analyzer: RhovasAnalyzer, 19 | ) { 20 | 21 | private val context get() = analyzer.context 22 | private val Analyzer.Context.scope get() = this[RhovasAnalyzer.ScopeContext::class] 23 | 24 | fun visit(ast: RhovasAst.Component): RhovasIr.DefinitionPhase.Component { 25 | return when (ast) { 26 | is RhovasAst.Component.Struct -> visit(ast) 27 | is RhovasAst.Component.Class -> visit(ast) 28 | is RhovasAst.Component.Interface -> visit(ast) 29 | } 30 | } 31 | 32 | private fun visit(ast: RhovasAst.Component.Struct): RhovasIr.DefinitionPhase.Component.Struct = analyzer.analyze(ast.context) { 33 | val component = (context.scope.types[ast.name]!! as Type.Reference).component as Component.Struct 34 | val fields = ast.members.filterIsInstance().map { visit(it, component) } 35 | component.inherits.forEach { component.inherit(it) } 36 | component.inherited.functions.define(Function.Definition(Function.Declaration("", 37 | generics = component.generics, 38 | parameters = listOf(Variable.Declaration("fields", Type.STRUCT[fields.filter { it.ast.value == null }.map { it.getter.name to it.getter.returns }])), 39 | returns = component.type, 40 | ))) 41 | component.inherited.functions.define(Function.Definition(Function.Declaration("", 42 | generics = component.generics, 43 | parameters = fields.map { Variable.Declaration(it.getter.name, it.getter.returns) }, 44 | returns = component.type, 45 | ))) 46 | val members = fields + ast.members.filter { it !is RhovasAst.Member.Property }.map { visit(it, component) } 47 | validateConcrete(ast, component) 48 | RhovasIr.DefinitionPhase.Component.Struct(ast, component, members) 49 | } 50 | 51 | private fun visit(ast: RhovasAst.Component.Class): RhovasIr.DefinitionPhase.Component.Class = analyzer.analyze(ast.context) { 52 | val component = (context.scope.types[ast.name]!! as Type.Reference).component as Component.Class 53 | component.inherits.forEach { component.inherit(it) } 54 | val members = ast.members.map { visit(it, component) }.toMutableList() 55 | if (ast.modifiers.inheritance != Modifiers.Inheritance.ABSTRACT) { 56 | validateConcrete(ast, component) 57 | } 58 | RhovasIr.DefinitionPhase.Component.Class(ast, component, component.inherits.first().takeUnless { it == Type.ANY }, members) 59 | } 60 | 61 | private fun visit(ast: RhovasAst.Component.Interface): RhovasIr.DefinitionPhase.Component.Interface = analyzer.analyze(ast.context) { 62 | val component = (context.scope.types[ast.name]!! as Type.Reference).component as Component.Interface 63 | component.inherits.forEach { component.inherit(it) } 64 | val members = ast.members.map { visit(it, component) }.toMutableList() 65 | RhovasIr.DefinitionPhase.Component.Interface(ast, component, members) 66 | } 67 | 68 | private fun validateConcrete(ast: RhovasAst.Component, component: Component<*>) { 69 | component.inherited.functions.collect().values.flatten() 70 | .filter { it.modifiers.inheritance == Modifiers.Inheritance.ABSTRACT } 71 | .forEach { 72 | analyzer.require(component.scope.functions[it.name, listOf(component.type) + it.parameters.drop(1).map { it.type }, true] != null) { analyzer.error(ast, 73 | "Missing override.", 74 | "The method ${it.name}/${it.parameters.size} is abstract and not overriden in ${component.name}, which requires a definition.", 75 | ) } 76 | } 77 | } 78 | 79 | fun visit(ast: RhovasAst.Member, component: Component<*>): RhovasIr.DefinitionPhase.Member { 80 | return when (ast) { 81 | is RhovasAst.Member.Property -> visit(ast, component) 82 | is RhovasAst.Member.Initializer -> visit(ast, component) 83 | is RhovasAst.Member.Method -> visit(ast, component) 84 | } 85 | } 86 | 87 | fun visit(ast: RhovasAst.Member.Property, component: Component<*>): RhovasIr.DefinitionPhase.Member.Property = analyzer.analyze(ast.context) { analyzer.analyze { 88 | component.generics.forEach { context.scope.types.define(it.key, it.value) } 89 | //TODO: Validate override constraints and check setter definitions 90 | analyzer.require(component.scope.functions[ast.name, 1, true].isEmpty()) { analyzer.error(ast, 91 | "Redefined property.", 92 | "The property ${ast.name} is already defined in ${component.name}.", 93 | ) } 94 | val type = analyzer.visit(ast.type).type 95 | val getter = Function.Definition(Function.Declaration(ast.name, 96 | generics = component.generics, 97 | parameters = listOf(Variable.Declaration("this", component.type)), 98 | returns = type, 99 | )) 100 | val setter = if (ast.mutable) Function.Definition(Function.Declaration(ast.name, 101 | generics = component.generics, 102 | parameters = listOf(Variable.Declaration("this", component.type), Variable.Declaration("value", type)), 103 | returns = Type.VOID, 104 | )) else null 105 | component.scope.functions.define(getter) 106 | setter?.let { component.scope.functions.define(it) } 107 | RhovasIr.DefinitionPhase.Member.Property(ast, getter, setter) 108 | } } 109 | 110 | fun visit(ast: RhovasAst.Member.Initializer, component: Component<*>): RhovasIr.DefinitionPhase.Member.Initializer = analyzer.analyze(ast.context) { analyzer.analyze { 111 | component.generics.forEach { context.scope.types.define(it.key, it.value) } 112 | val parameters = ast.parameters.mapIndexed { index, parameter -> 113 | val type = parameter.second?.let { analyzer.visit(it).type } ?: throw analyzer.error(ast, 114 | "Missing parameter type.", 115 | "The initializer init/${ast.parameters.size} requires parameter ${index} to have an defined type.", 116 | ) 117 | Variable.Declaration(parameter.first, type) 118 | } 119 | val returns = ast.returns?.let { analyzer.visit(it).type } ?: component.type 120 | val throws = ast.throws.map { analyzer.visit(it).type } 121 | val initializer = Function.Definition(Function.Declaration("", ast.modifiers, component.generics, parameters, returns, throws)) 122 | analyzer.require(component.scope.functions["", ast.parameters.size, true].all { it.isDisjointWith(initializer.declaration) }) { analyzer.error(ast, 123 | "Redefined initializer.", 124 | "The initializer init/${ast.parameters.size} overlaps with an existing function in ${component.name}.", 125 | ) } 126 | component.scope.functions.define(initializer) 127 | RhovasIr.DefinitionPhase.Member.Initializer(ast, initializer) 128 | } } 129 | 130 | fun visit(ast: RhovasAst.Member.Method, component: Component<*>): RhovasIr.DefinitionPhase.Member.Method { 131 | val function = visit(ast.function, ast.modifiers, component) 132 | return RhovasIr.DefinitionPhase.Member.Method(ast, function) 133 | } 134 | 135 | fun visit(ast: RhovasAst.Statement.Declaration.Function, modifiers: Modifiers? = null, component: Component<*>? = null): RhovasIr.DefinitionPhase.Function = analyzer.analyze(ast.context) { 136 | val scope = component?.scope ?: context.scope 137 | analyzer.analyze { 138 | analyzer.require(ast.operator == null || component != null) { analyzer.error(ast, 139 | "Invalid operator overload.", 140 | "Operator overloading can only be used within component methods, not functions.", 141 | ) } 142 | val generics = component?.generics?.takeIf { ast.parameters.firstOrNull()?.second == null }?.toMap(linkedMapOf()) ?: linkedMapOf() 143 | ast.generics.forEach { 144 | analyzer.require(generics[it.first] == null) { analyzer.error(ast, 145 | "Redefined generic type.", 146 | "The generic type ${it.first} is already defined in this ${component?.let { "component/" } ?: ""}function.", 147 | ) } 148 | generics[it.first] = Type.Generic(it.first, it.second?.let { analyzer.visit(it).type } ?: Type.ANY) 149 | } 150 | generics.forEach { context.scope.types.define(it.key, it.value) } 151 | val parameters = ast.parameters.mapIndexed { index, parameter -> 152 | val type = parameter.second?.let { analyzer.visit(it).type } ?: component?.type?.takeIf { index == 0 } ?: throw analyzer.error(ast, 153 | "Missing parameter type.", 154 | "The function ${ast.name}/${ast.parameters.size} requires parameter ${index} to have an explicit type.", 155 | ) 156 | Variable.Declaration(parameter.first, type) 157 | } 158 | val returns = ast.returns?.let { analyzer.visit(it).type } ?: Type.VOID 159 | val throws = ast.throws.map { analyzer.visit(it).type } 160 | val declaration = Function.Declaration(ast.name, modifiers ?: Modifiers(), generics, parameters, returns, throws) 161 | analyzer.require(scope.functions[ast.name, ast.parameters.size, true].all { it.isDisjointWith(declaration) }) { analyzer.error(ast, 162 | "Redefined function.", 163 | "The function ${ast.name}/${ast.parameters.size} overlaps with an existing function in ${component?.name ?: "this scope"}.", 164 | ) } 165 | val inherited = component?.inherited?.functions?.get(declaration.name, declaration.parameters.map { it.type }) 166 | analyzer.require(inherited == null || inherited.modifiers.inheritance in listOf(Modifiers.Inheritance.VIRTUAL, Modifiers.Inheritance.ABSTRACT)) { analyzer.error(ast, 167 | "Invalid override.", 168 | "The function ${ast.name}/${ast.parameters.size} overrides an inherited function that is final.", 169 | ) } 170 | analyzer.require(inherited != null || !declaration.modifiers.override) { analyzer.error(ast, 171 | "Invalid override modifier.", 172 | "The function ${ast.name}/${ast.parameters.size} does not override an inherited function but has an `override` modifier.", 173 | ) } 174 | analyzer.require(inherited == null || declaration.modifiers.override) { analyzer.error(ast, 175 | "Missing override modifier.", 176 | "The function ${ast.name}/${ast.parameters.size} overrides an inherited function and must have an `override` modifier.", 177 | ) } 178 | val method = if (component != null || scope is Scope.Definition) Function.Definition(declaration) else declaration 179 | (scope as Scope<*, *, in Function, *>).functions.define(method) 180 | ast.operator?.let { scope.functions.define(method, it) } 181 | RhovasIr.DefinitionPhase.Function(ast, method) 182 | } 183 | } 184 | 185 | } 186 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/analyzer/rhovas/RegistrationPhase.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.analyzer.rhovas 2 | 3 | import dev.rhovas.interpreter.analyzer.Analyzer 4 | import dev.rhovas.interpreter.environment.Component 5 | import dev.rhovas.interpreter.environment.Modifiers 6 | import dev.rhovas.interpreter.parser.rhovas.RhovasAst 7 | 8 | /** 9 | * Phase 1/4: Registers all types that may be referenced during analysis. 10 | */ 11 | class RegistrationPhase( 12 | private val analyzer: RhovasAnalyzer, 13 | ) { 14 | 15 | private val context get() = analyzer.context 16 | private val Analyzer.Context.scope get() = this[RhovasAnalyzer.ScopeContext::class] 17 | 18 | fun visit(ast: RhovasAst.Source) { 19 | ast.statements.filterIsInstance().forEach { visit(it.component) } 20 | } 21 | 22 | fun visit(ast: RhovasAst.Component) { 23 | when (ast) { 24 | is RhovasAst.Component.Struct -> visit(ast) 25 | is RhovasAst.Component.Class -> visit(ast) 26 | is RhovasAst.Component.Interface -> visit(ast) 27 | } 28 | } 29 | 30 | private fun visit(ast: RhovasAst.Component.Struct) = analyzer.analyze(ast.context) { 31 | analyzer.require(!context.scope.types.isDefined(ast.name, true)) { analyzer.error(ast, 32 | "Redefined type.", 33 | "The type ${ast.name} is already defined in this scope.", 34 | ) } 35 | analyzer.require(ast.modifiers.inheritance == Modifiers.Inheritance.FINAL) { analyzer.error(ast, 36 | "Invalid inheritance modifier.", 37 | "A struct cannot specify virtual/abstract modifiers as they cannot be inherited from.", 38 | ) } 39 | val component = Component.Struct(ast.name) 40 | context.scope.types.define(component.name, component.type) 41 | } 42 | 43 | private fun visit(ast: RhovasAst.Component.Class) = analyzer.analyze(ast.context) { 44 | analyzer.require(!context.scope.types.isDefined(ast.name, true)) { analyzer.error(ast, 45 | "Redefined type.", 46 | "The type ${ast.name} is already defined in this scope.", 47 | ) } 48 | val component = Component.Class(ast.name, ast.modifiers) 49 | context.scope.types.define(component.name, component.type) 50 | } 51 | 52 | private fun visit(ast: RhovasAst.Component.Interface) = analyzer.analyze(ast.context) { 53 | analyzer.require(!context.scope.types.isDefined(ast.name, true)) { analyzer.error(ast, 54 | "Redefined type.", 55 | "The type ${ast.name} is already defined in this scope.", 56 | ) } 57 | analyzer.require(ast.modifiers.inheritance == Modifiers.Inheritance.FINAL) { analyzer.error(ast, 58 | "Invalid inheritance modifier.", 59 | "An interface cannot specify virtual/abstract modifiers as they are always considered abstract.", 60 | ) } 61 | val component = Component.Interface(ast.name) 62 | context.scope.types.define(component.name, component.type) 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/environment/Component.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.environment 2 | 3 | import dev.rhovas.interpreter.environment.type.Type 4 | 5 | sealed class Component>( 6 | val name: String, 7 | val modifiers: Modifiers, 8 | ) { 9 | 10 | val generics = linkedMapOf() 11 | val inherits = mutableListOf() 12 | val type = Type.Reference(this, generics) 13 | 14 | val inherited = Scope.Declaration(null) 15 | val scope: S = when (modifiers.inheritance) { 16 | Modifiers.Inheritance.FINAL, Modifiers.Inheritance.VIRTUAL -> Scope.Definition(inherited as Scope<*, out Variable.Definition, *, out Function.Definition>) as S 17 | Modifiers.Inheritance.ABSTRACT -> Scope.Declaration(inherited) as S 18 | } 19 | 20 | open fun inherit(type: Type.Reference) { 21 | require(inherits.contains(type)) 22 | when (this) { 23 | is Struct -> require(type.component == Type.STRUCT.GENERIC.component || type.component is Interface) 24 | is Class -> require(type.component.modifiers.inheritance in listOf(Modifiers.Inheritance.VIRTUAL, Modifiers.Inheritance.ABSTRACT)) 25 | is Interface -> require(type.component == Type.ANY.component || type.component is Interface) 26 | } 27 | type.component.scope.functions.collect() 28 | .flatMap { entry -> entry.value.map { Pair(entry.key.first, it) } } 29 | .filter { (_, function) -> function.parameters.firstOrNull()?.type?.isSupertypeOf(type) ?: false } 30 | .map { (name, function) -> Pair(name, function.bind(type.component.generics.keys.zip(type.generics.values).associate { it.first to it.second })) } 31 | .filter { (name, function) -> scope.functions[name, function.parameters.size].all { it.isDisjointWith(function) } } 32 | .forEach { (name, function) -> 33 | //require(function is Function.Definition || modifiers.inheritance == Modifiers.Inheritance.ABSTRACT) 34 | inherited.functions.define(function, name) 35 | } 36 | } 37 | 38 | override fun equals(other: Any?): Boolean { 39 | return other is Component<*> && name == other.name && modifiers == other.modifiers && generics == other.generics && inherits == other.inherits 40 | } 41 | 42 | override fun toString(): String { 43 | println("Component.toString: " + name + "<" + generics + ">") 44 | return "Component(name='${name}', modifiers=${modifiers}), generics=${generics}, inherits=${inherits})" 45 | } 46 | 47 | class Struct( 48 | name: String, 49 | modifiers: Modifiers = Modifiers(), 50 | ) : Component(name, modifiers) { 51 | 52 | init { 53 | require(modifiers.inheritance == Modifiers.Inheritance.FINAL) 54 | } 55 | 56 | } 57 | 58 | class Class( 59 | name: String, 60 | modifiers: Modifiers = Modifiers(), 61 | ) : Component>(name, modifiers) 62 | 63 | class Interface( 64 | name: String, 65 | modifiers: Modifiers = Modifiers(Modifiers.Inheritance.ABSTRACT), 66 | ) : Component(name, modifiers) { 67 | 68 | init { 69 | require(modifiers.inheritance == Modifiers.Inheritance.ABSTRACT) 70 | } 71 | 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/environment/Function.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.environment 2 | 3 | import dev.rhovas.interpreter.environment.type.Type 4 | 5 | sealed interface Function { 6 | 7 | val declaration: Declaration 8 | 9 | val name get() = declaration.name 10 | val modifiers get() = declaration.modifiers 11 | val generics get() = declaration.generics 12 | val parameters get() = declaration.parameters 13 | val returns get() = declaration.returns 14 | val throws get() = declaration.throws 15 | 16 | /** 17 | * Returns true if this function is resolved by [arguments], thus it can be 18 | * invoked by arguments of that type. Bounds on generic types are stored in 19 | * the given [generics] map for later use (such as with [bind]) and should 20 | * only be considered meaningful if this method returns true. 21 | */ 22 | fun isResolvedBy(arguments: List, generics: MutableMap = mutableMapOf()): Boolean { 23 | val result = arguments.indices.all { 24 | arguments[it].isSubtypeOf(parameters[it].type, generics) 25 | } 26 | generics.mapValuesTo(generics) { (_, type) -> 27 | // Finalize variant type bounds between arguments after resolution is complete. 28 | if (type is Type.Variant) type.upper ?: type.lower ?: Type.ANY else type 29 | } 30 | return result 31 | } 32 | 33 | fun bind(bindings: Map): Function 34 | 35 | /** 36 | * Returns true if this function is disjoint with [other], aka there is no 37 | * overlap between function signatures. 38 | */ 39 | fun isDisjointWith(other: Function): Boolean { 40 | return ( 41 | name != other.name || 42 | parameters.size != other.parameters.size || 43 | parameters.zip(other.parameters).any { 44 | val type = it.first.type.bind(generics) 45 | val other = it.second.type.bind(other.generics) 46 | !type.isSubtypeOf(other) && !type.isSupertypeOf(other) 47 | } 48 | ) 49 | } 50 | 51 | data class Declaration( 52 | override val name: String, 53 | override val modifiers: Modifiers = Modifiers(), 54 | override val generics: LinkedHashMap = linkedMapOf(), 55 | override val parameters: List, 56 | override val returns: Type, 57 | override val throws: List = listOf(), 58 | ) : Function { 59 | 60 | override val declaration = this 61 | 62 | override fun bind(bindings: Map): Declaration { 63 | return Declaration( 64 | name, 65 | modifiers, 66 | generics.mapValuesTo(linkedMapOf()) { Type.Generic(it.key, it.value.bound.bind(bindings)) }, 67 | parameters.map { Variable.Declaration(it.name, it.type.bind(bindings), it.mutable) }, 68 | returns.bind(bindings), 69 | throws.map { it.bind(bindings) } 70 | ) 71 | } 72 | 73 | } 74 | 75 | data class Definition( 76 | override val declaration: Declaration, 77 | ) : Function { 78 | 79 | lateinit var implementation: (List) -> Object 80 | 81 | constructor(declaration: Declaration, implementation: (List) -> Object): this(declaration) { 82 | this.implementation = implementation 83 | } 84 | 85 | fun invoke(arguments: List): Object { 86 | return implementation.invoke(arguments) 87 | } 88 | 89 | override fun bind(bindings: Map): Definition { 90 | return Definition(declaration.bind(bindings)) { implementation.invoke(it) } 91 | } 92 | 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/environment/Method.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.environment 2 | 3 | sealed interface Method { 4 | 5 | val function: Function 6 | 7 | val name get() = function.name 8 | val generics get() = function.generics 9 | val receiver get() = function.parameters.first() 10 | val parameters get() = function.parameters.drop(1) 11 | val returns get() = function.returns 12 | val throws get() = function.throws 13 | 14 | data class Declaration( 15 | override val function: Function 16 | ) : Method 17 | 18 | data class Bound( 19 | override val function: Function.Definition, 20 | val instance: Object, 21 | ) : Method { 22 | 23 | fun invoke(arguments: List): Object { 24 | return function.invoke(listOf(instance) + arguments) 25 | } 26 | 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/environment/Modifiers.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.environment 2 | 3 | data class Modifiers( 4 | val inheritance: Inheritance = Inheritance.FINAL, 5 | val override: Boolean = false, 6 | ) { 7 | 8 | enum class Inheritance { FINAL, VIRTUAL, ABSTRACT } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/environment/Object.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.environment 2 | 3 | import com.ionspin.kotlin.bignum.integer.BigInteger 4 | import dev.rhovas.interpreter.environment.type.Type 5 | 6 | data class Object( 7 | val type: Type, 8 | val value: Any?, 9 | ) { 10 | 11 | init { 12 | require(type is Type.Reference) 13 | require(type.component.scope is Scope.Definition) 14 | } 15 | 16 | val properties = PropertiesDelegate() 17 | val methods = MethodsDelegate() 18 | 19 | operator fun get(property: Property): Property.Bound? { 20 | return properties[property.name] 21 | } 22 | 23 | operator fun get(method: Method): Method.Bound? { 24 | return methods[method.name, method.parameters.map { it.type }] 25 | } 26 | 27 | inner class PropertiesDelegate { 28 | 29 | operator fun get(name: String): Property.Bound? { 30 | return methods[name, listOf()]?.let { getter -> 31 | Property.Bound(getter, methods[name, listOf(getter.returns)]) 32 | } 33 | } 34 | 35 | } 36 | 37 | inner class MethodsDelegate { 38 | 39 | operator fun get(name: String, arguments: List): Method.Bound? { 40 | return type.functions[name, listOf(type) + arguments]?.let { 41 | Method.Bound(it as Function.Definition, this@Object) 42 | } 43 | } 44 | 45 | /** 46 | * Invokes `==(other)` on the object if defined or returns false. This 47 | * is placed here to avoid overriding the default data class `equals`, 48 | * but also makes it clearer this invokes a user method. 49 | */ 50 | fun equals(other: Object): Boolean { 51 | return get("==", listOf(other.type))?.invoke(listOf(other))?.value as Boolean? ?: false 52 | } 53 | 54 | /** 55 | * Invokes `hash` on the object. This is placed here to match `equals`/ 56 | * `toString`, but also makes it clearer this invokes a user method. 57 | */ 58 | fun hash(): Int { 59 | return (get("hash", listOf())!!.invoke(listOf()).value as BigInteger).intValue() 60 | } 61 | 62 | /** 63 | * Invokes `to(String)` on the object. This is placed here to avoid 64 | * overriding the default data class `toString`, but also makes it 65 | * clearer this invokes a user method. 66 | */ 67 | override fun toString(): String { 68 | val type = Type.TYPE[Type.STRING] 69 | return get("to", listOf(type))!!.invoke(listOf(Object(type, type))).value as String 70 | } 71 | 72 | } 73 | 74 | data class Hashable(val instance: Object) { 75 | 76 | override fun equals(other: Any?) = instance.methods.equals((other as Hashable).instance) 77 | override fun hashCode() = instance.methods.hash() 78 | override fun toString() = instance.methods.toString() 79 | 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/environment/Property.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.environment 2 | 3 | sealed interface Property { 4 | 5 | val getter: Method 6 | val setter: Method? 7 | 8 | val name get() = getter.name 9 | val type get() = getter.returns 10 | val mutable get() = setter != null 11 | 12 | data class Declaration( 13 | override val getter: Method, 14 | override val setter: Method? 15 | ) : Property 16 | 17 | data class Bound( 18 | override val getter: Method.Bound, 19 | override val setter: Method.Bound?, 20 | ) : Property 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/environment/Scope.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.environment 2 | 3 | import dev.rhovas.interpreter.environment.type.Type 4 | 5 | sealed class Scope( 6 | private val parent: Scope<*, out VO, *, out FO>?, 7 | ) { 8 | 9 | val variables = VariablesDelegate() 10 | val functions = FunctionsDelegate() 11 | val types = TypesDelegate() 12 | 13 | class Declaration(parent: Scope<*, out Variable, *, out Function>?): Scope(parent) 14 | 15 | class Definition(parent: Scope<*, out Variable.Definition, *, out Function.Definition>?): Scope(parent) 16 | 17 | inner class VariablesDelegate { 18 | 19 | private val variables = mutableMapOf() 20 | 21 | operator fun get(name: String, current: Boolean = false): VO? { 22 | return variables[name] ?: when { 23 | current -> null 24 | else -> (parent as Scope<*, out VO, *, *>?)?.variables?.get(name) 25 | } 26 | } 27 | 28 | fun define(variable: VI) { 29 | require(!variables.containsKey(variable.name)) 30 | variables[variable.name] = variable 31 | } 32 | 33 | internal fun collect(): MutableMap { 34 | val map = (parent as Scope<*, VO, *, *>?)?.variables?.collect() ?: mutableMapOf() 35 | map.putAll(variables) 36 | return map 37 | } 38 | 39 | } 40 | 41 | inner class FunctionsDelegate { 42 | 43 | private val functions = mutableMapOf, MutableList>() 44 | 45 | operator fun get(name: String, arity: Int, current: Boolean = false): List { 46 | return (functions[Pair(name, arity)] ?: listOf()) + when { 47 | current -> listOf() 48 | else -> ((parent as Scope<*, *, *, FO>?)?.functions?.get(name, arity) ?: listOf()) 49 | } 50 | } 51 | 52 | operator fun get(name: String, arguments: List, current: Boolean = false): FO? { 53 | return get(name, arguments.size, current).firstNotNullOfOrNull { function -> 54 | val generics = mutableMapOf() 55 | function.takeIf { it.isResolvedBy(arguments, generics) }?.bind(generics) as FO? 56 | } 57 | } 58 | 59 | fun define(function: FI, alias: String = function.name) { 60 | val overloads = functions.getOrPut(Pair(alias, function.parameters.size), ::mutableListOf) 61 | require(overloads.all { it.isDisjointWith(function) }) 62 | overloads.add(function) 63 | } 64 | 65 | internal fun collect(): MutableMap, List> { 66 | val map = (parent as Scope<*, *, *, FO>?)?.functions?.collect() ?: mutableMapOf() 67 | map.putAll(functions) 68 | return map 69 | } 70 | 71 | } 72 | 73 | inner class TypesDelegate { 74 | 75 | private val types = mutableMapOf() 76 | 77 | operator fun get(name: String): Type? { 78 | return types[name] ?: parent?.types?.get(name) 79 | } 80 | 81 | fun isDefined(name: String, current: Boolean): Boolean { 82 | return types.containsKey(name) || !current && parent?.types?.isDefined(name, current) ?: false 83 | } 84 | 85 | fun define(name: String, type: Type) { 86 | require(!types.containsKey(name)) 87 | types[name] = type 88 | } 89 | 90 | internal fun collect(): MutableMap { 91 | val map = parent?.types?.collect() ?: mutableMapOf() 92 | map.putAll(types) 93 | return map 94 | } 95 | 96 | } 97 | 98 | override fun toString(): String { 99 | return "Scope(" + 100 | "parent=${parent?.let { "Scope@${it.hashCode().toString(16)}" }}, " + 101 | "variables=${variables.collect()}, " + 102 | "functions=${functions.collect()}" + 103 | "types=${types.collect()}" + 104 | ")" 105 | } 106 | 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/environment/Variable.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.environment 2 | 3 | import dev.rhovas.interpreter.environment.type.Type 4 | 5 | sealed interface Variable { 6 | 7 | val declaration: Declaration 8 | 9 | val name get() = declaration.name 10 | val type get() = declaration.type 11 | val mutable get() = declaration.mutable 12 | 13 | data class Declaration( 14 | override val name: String, 15 | override val type: Type, 16 | override val mutable: Boolean = false, 17 | ) : Variable { 18 | 19 | override val declaration = this 20 | 21 | } 22 | 23 | data class Definition( 24 | override val declaration: Declaration, 25 | ) : Variable { 26 | 27 | lateinit var value: Object 28 | 29 | constructor(declaration: Declaration, value: Object) : this(declaration) { 30 | this.value = value 31 | } 32 | 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/environment/type/isInvariantSubtypeOf.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.environment.type 2 | 3 | fun isInvariantSubtypeOf(type: Type, other: Type, bindings: MutableMap): Boolean = when(type) { 4 | is Type.Reference -> isInvariantSubtypeOf(type, other, bindings) 5 | is Type.Tuple -> isInvariantSubtypeOf(type, other, bindings) 6 | is Type.Struct -> isInvariantSubtypeOf(type, other, bindings) 7 | is Type.Generic -> isInvariantSubtypeOf(type, other, bindings) 8 | is Type.Variant -> isInvariantSubtypeOf(type, other, bindings) 9 | } 10 | 11 | private fun isInvariantSubtypeOf(type: Type.Reference, other: Type, bindings: MutableMap): Boolean = when (other) { 12 | is Type.Reference -> when { 13 | type.component.name == "Dynamic" || other.component.name == "Dynamic" -> true 14 | type.component.name == other.component.name -> type.generics.values.zip(other.generics.values).all { isInvariantSubtypeOf(it.first, it.second, bindings) } 15 | else -> false 16 | } 17 | is Type.Tuple -> isInvariantSubtypeOf(type, Type.TUPLE[other], bindings) 18 | is Type.Struct -> isInvariantSubtypeOf(type, Type.STRUCT[other], bindings) 19 | is Type.Generic -> when { 20 | bindings.containsKey(other.name) -> isInvariantSubtypeOf(type, bindings[other.name]!!, bindings).also { bindings[other.name] = type } 21 | type.component.name == "Dynamic" -> true.also { bindings[other.name] = Type.DYNAMIC } 22 | else -> isSubtypeOf(type, other.bound, bindings.also { it[other.name] = type }) 23 | } 24 | is Type.Variant -> isSubtypeOf(type, other, bindings) 25 | } 26 | 27 | private fun isInvariantSubtypeOf(type: Type.Tuple, other: Type, bindings: MutableMap): Boolean = when (other) { 28 | is Type.Tuple -> type.elements.size == other.elements.size && type.elements.zip(other.elements).all { 29 | val typeInvariantSubtype = isInvariantSubtypeOf(it.first.type, it.second.type, bindings) 30 | val mutableInvariantSubtype = it.first.mutable == it.second.mutable 31 | typeInvariantSubtype && mutableInvariantSubtype 32 | } 33 | else -> isInvariantSubtypeOf(Type.TUPLE[type], other, bindings) 34 | } 35 | 36 | private fun isInvariantSubtypeOf(type: Type.Struct, other: Type, bindings: MutableMap): Boolean = when (other) { 37 | is Type.Struct -> type.fields.keys == other.fields.keys && type.fields.map { it.value to other.fields[it.key]!! }.all { 38 | val typeInvariantSubtype = isInvariantSubtypeOf(it.first.type, it.second.type, bindings) 39 | val mutableInvariantSubtype = it.first.mutable == it.second.mutable 40 | typeInvariantSubtype && mutableInvariantSubtype 41 | } 42 | else -> isInvariantSubtypeOf(Type.STRUCT[type], other, bindings) 43 | } 44 | 45 | private fun isInvariantSubtypeOf(type: Type.Generic, other: Type, bindings: MutableMap): Boolean = when { 46 | other is Type.Generic -> when { 47 | bindings.containsKey(other.name) -> isInvariantSubtypeOf(type, bindings[other.name]!!, bindings.also { it[other.name] = type }) 48 | else -> type.name == other.name 49 | } 50 | else -> isInvariantSubtypeOf(type.bound, other, bindings) 51 | } 52 | 53 | private fun isInvariantSubtypeOf(type: Type.Variant, other: Type, bindings: MutableMap): Boolean = when (other) { 54 | is Type.Variant -> isSubtypeOf(type, other, bindings) 55 | else -> false 56 | } 57 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/environment/type/isSubtypeOf.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.environment.type 2 | 3 | fun isSubtypeOf(type: Type, other: Type, bindings: MutableMap): Boolean = when(type) { 4 | is Type.Reference -> isSubtypeOf(type, other, bindings) 5 | is Type.Tuple -> isSubtypeOf(type, other, bindings) 6 | is Type.Struct -> isSubtypeOf(type, other, bindings) 7 | is Type.Generic -> isSubtypeOf(type, other, bindings) 8 | is Type.Variant -> isSubtypeOf(type, other, bindings) 9 | } 10 | 11 | private fun isSubtypeOf(type: Type.Reference, other: Type, bindings: MutableMap): Boolean = when (other) { 12 | is Type.Reference -> when { 13 | type.component.name == "Dynamic" || other.component.name == "Dynamic" || other.component.name == "Any" -> true 14 | type.component.name == other.component.name -> type.generics.values.zip(other.generics.values).all { when { 15 | type.component.name == "Tuple" || type.component.name == "Struct" -> it.first.isSubtypeOf(it.second, bindings) 16 | else -> isInvariantSubtypeOf(it.first, it.second, bindings) 17 | } } 18 | else -> type.component.inherits.any { isSubtypeOf(it.bind(type.generics), other, bindings) } 19 | } 20 | is Type.Tuple -> isSubtypeOf(type, Type.TUPLE[other], bindings) 21 | is Type.Struct -> isSubtypeOf(type, Type.STRUCT[other], bindings) 22 | is Type.Generic -> when { 23 | bindings.containsKey(other.name) -> isSubtypeOfBinding(type, other.name, bindings) 24 | type.component.name == "Dynamic" -> true.also { bindings[other.name] = Type.DYNAMIC } 25 | else -> isSubtypeOf(type, other.bound, bindings.also { it[other.name] = Type.Variant(type, null) }) 26 | } 27 | is Type.Variant -> ( 28 | type.isSupertypeOf(other.lower ?: Type.DYNAMIC, bindings) && 29 | isSubtypeOf(type, other.upper ?: Type.DYNAMIC, bindings) 30 | ) 31 | } 32 | 33 | private fun isSubtypeOf(type: Type.Tuple, other: Type, bindings: MutableMap): Boolean = when (other) { 34 | is Type.Tuple -> other.elements.withIndex().all { (index, other) -> 35 | type.elements.getOrNull(index)?.let { 36 | val typeSubtype = isSubtypeOf(it.type, other.type, bindings) 37 | val mutableSubtype = !other.mutable || it.mutable && it.type.isSupertypeOf(other.type, bindings) 38 | typeSubtype && mutableSubtype 39 | } ?: false 40 | } 41 | else -> isSubtypeOf(Type.TUPLE[type], other, bindings) 42 | } 43 | 44 | private fun isSubtypeOf(type: Type.Struct, other: Type, bindings: MutableMap): Boolean = when (other) { 45 | is Type.Struct -> other.fields.all { (key, other) -> 46 | type.fields[key]?.let { 47 | val typeSubtype = isSubtypeOf(it.type, other.type, bindings) 48 | val mutableSubtype = !other.mutable || it.mutable && it.type.isSupertypeOf(other.type, bindings) 49 | typeSubtype && mutableSubtype 50 | } ?: false 51 | } 52 | else -> isSubtypeOf(Type.STRUCT[type], other, bindings) 53 | } 54 | 55 | private fun isSubtypeOf(type: Type.Generic, other: Type, bindings: MutableMap): Boolean = when { 56 | other is Type.Generic -> when { 57 | bindings.containsKey(other.name) -> isSubtypeOfBinding(type, other.name, bindings) 58 | else -> type.name == other.name 59 | } 60 | else -> isSubtypeOf(type.bound, other, bindings) 61 | } 62 | 63 | private fun isSubtypeOf(type: Type.Variant, other: Type, bindings: MutableMap): Boolean = when (other) { 64 | is Type.Variant -> ( 65 | type.lower?.isSupertypeOf(other.lower ?: Type.DYNAMIC, bindings) ?: (other.lower == null) && 66 | isSubtypeOf(type.upper ?: Type.ANY, other.upper ?: Type.DYNAMIC, bindings) 67 | ) 68 | else -> isSubtypeOf(type.upper ?: Type.ANY, other, bindings) 69 | } 70 | 71 | private fun isSubtypeOfBinding(type: Type, name: String, bindings: MutableMap): Boolean { 72 | val subtype = isSubtypeOf(type, bindings[name]!!, bindings) 73 | if (subtype && bindings[name] is Type.Variant) { 74 | bindings[name] = Type.Variant(type, (bindings[name]!! as Type.Variant).upper) 75 | } 76 | return subtype 77 | } 78 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/environment/type/unify.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.environment.type 2 | 3 | import dev.rhovas.interpreter.environment.Variable 4 | 5 | fun unify(type: Type, other: Type, bindings: MutableMap): Type = when(type) { 6 | is Type.Reference -> unify(type, other, bindings) 7 | is Type.Tuple -> unify(type, other, bindings) 8 | is Type.Struct -> unify(type, other, bindings) 9 | is Type.Generic -> unify(type, other, bindings) 10 | is Type.Variant -> unify(type, other, bindings) 11 | } 12 | 13 | private fun unify(type: Type.Reference, other: Type, bindings: MutableMap): Type = when (other) { 14 | is Type.Reference -> when { 15 | type.component.name == "Dynamic" || other.component.name == "Dynamic" -> Type.DYNAMIC 16 | type.component.name == "Any" || other.component.name == "Any" -> Type.ANY 17 | type.component.name == other.component.name -> Type.Reference(type.component, type.generics.keys.associateWith { unify(type.generics[it]!!, other.generics[it]!!, bindings) }) 18 | else -> { 19 | var top: Type.Reference = other 20 | while (!isSubtypeOf(type, top, bindings)) { 21 | top = top.component.inherits.first().bind(type.generics) 22 | } 23 | unify(top, type, bindings) 24 | } 25 | } 26 | is Type.Tuple -> unify(type, Type.TUPLE[other], bindings) 27 | is Type.Struct -> unify(type, Type.STRUCT[other], bindings) 28 | is Type.Generic -> when { 29 | bindings.containsKey(other.name) -> unify(type, bindings[other.name]!!, bindings) 30 | type.component.name == "Dynamic" -> Type.DYNAMIC.also { bindings[other.name] = Type.DYNAMIC } 31 | else -> unify(type, other.bound, bindings).also { bindings[other.name] = it } 32 | } 33 | is Type.Variant -> unify(type, other.upper ?: Type.ANY, bindings) 34 | } 35 | 36 | private fun unify(type: Type.Tuple, other: Type, bindings: MutableMap): Type = when (other) { 37 | is Type.Tuple -> Type.Tuple(type.elements.zip(other.elements).mapIndexed { index, pair -> 38 | Variable.Declaration(index.toString(), unify(pair.first.type, pair.second.type, bindings), pair.first.mutable && pair.second.mutable) 39 | }) 40 | else -> unify(Type.TUPLE[type], other, bindings) 41 | } 42 | 43 | private fun unify(type: Type.Struct, other: Type, bindings: MutableMap): Type = when (other) { 44 | is Type.Struct -> Type.Struct(type.fields.keys.intersect(other.fields.keys).associateWith { 45 | Variable.Declaration(it, unify(type.fields[it]!!.type, other.fields[it]!!.type, bindings), type.fields[it]!!.mutable && other.fields[it]!!.mutable,) 46 | }) 47 | else -> unify(Type.STRUCT[type], other, bindings) 48 | } 49 | 50 | private fun unify(type: Type.Generic, other: Type, bindings: MutableMap): Type = when { 51 | type === other -> type //short-circuit for recursive generics 52 | bindings.containsKey(type.name) -> unify(bindings[type.name]!!, other, bindings) 53 | other is Type.Generic -> if (type.name == other.name) type else unify(type.bound, other.bound, bindings) 54 | else -> unify(type.bound, other, bindings).also { bindings[type.name] = other } 55 | } 56 | 57 | private fun unify(type: Type.Variant, other: Type, bindings: MutableMap): Type = when (other) { 58 | is Type.Variant -> Type.Variant( 59 | if (type.lower != null && other.lower != null) unify(type.lower, other.lower, bindings) else null, 60 | unify(type.upper ?: Type.ANY, other.upper ?: Type.ANY, bindings), 61 | ) 62 | else -> unify(type.upper ?: Type.ANY, other, bindings) 63 | } 64 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/evaluator/EvaluateException.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.evaluator 2 | 3 | import dev.rhovas.interpreter.parser.Input 4 | 5 | data class EvaluateException( 6 | val summary: String, 7 | val details: String, 8 | val range: Input.Range, 9 | val context: List, 10 | ): Exception("${range.line}:${range.column}-${range.column + range.length}: ${summary}") 11 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/evaluator/StackFrame.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.evaluator 2 | 3 | import dev.rhovas.interpreter.analyzer.rhovas.RhovasIr 4 | import dev.rhovas.interpreter.parser.Input 5 | 6 | data class StackFrame( 7 | val ir: RhovasIr?, 8 | val source: String, 9 | val range: Input.Range, 10 | ) 11 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/library/AnyInitializer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.library 2 | 3 | import dev.rhovas.interpreter.environment.Component 4 | import dev.rhovas.interpreter.environment.Modifiers 5 | import dev.rhovas.interpreter.environment.Object 6 | import dev.rhovas.interpreter.environment.type.Type 7 | import dev.rhovas.interpreter.evaluator.Evaluator 8 | 9 | object AnyInitializer: Library.ComponentInitializer(Component.Class("Any", Modifiers(Modifiers.Inheritance.ABSTRACT))) { 10 | 11 | override fun declare() {} 12 | 13 | override fun define() { 14 | function("do", 15 | generics = listOf(generic("T"), generic("R")), 16 | parameters = listOf("instance" to generic("T"), "lambda" to Type.LAMBDA[Type.TUPLE[listOf(generic("T"))], generic("R"), Type.DYNAMIC]), 17 | returns = generic("R"), 18 | ) { (instance, lambda): T2 -> 19 | val returnsType = instance.type.generic("R", Type.LAMBDA.GENERIC)!! 20 | lambda.invoke(listOf(instance), returnsType) 21 | } 22 | 23 | function("if", 24 | generics = listOf(generic("T"), generic("E")), 25 | parameters = listOf("instance" to generic("T"), "lambda" to Type.LAMBDA[Type.TUPLE[listOf(generic("T"))], Type.BOOLEAN, Type.DYNAMIC]), 26 | returns = Type.NULLABLE[generic("T")], 27 | ) { (instance, lambda): T2 -> 28 | val result = instance.takeIf { lambda.invoke(listOf(instance), Type.BOOLEAN).value as Boolean } 29 | Object(Type.NULLABLE[generics["T"]!!], result?.let { Pair(it, null) }) 30 | } 31 | 32 | method("is", 33 | parameters = listOf("type" to Type.TYPE[generic("T")]), 34 | returns = Type.BOOLEAN, 35 | ) { (instance, type): T2 -> 36 | Object(Type.BOOLEAN, instance.type.isSubtypeOf(type)) 37 | } 38 | 39 | method("as", 40 | parameters = listOf("type" to Type.TYPE[generic("T")]), 41 | returns = Type.NULLABLE[generic("T")], 42 | ) { (instance, type): T2 -> 43 | val result = instance.takeIf { it.type.isSubtypeOf(type) } 44 | Object(Type.NULLABLE[type], result?.let { Pair(it, null) }) 45 | } 46 | 47 | method("to", 48 | modifiers = Modifiers(Modifiers.Inheritance.VIRTUAL), 49 | parameters = listOf("type" to Type.TYPE[Type.STRING]), 50 | returns = Type.STRING, 51 | ) { (instance, _type): T2 -> 52 | fun toString(value: Any?): String { 53 | return when (value) { 54 | is Object -> value.methods.toString() 55 | is Map<*, *> -> value.mapValues { toString(it.value) }.toString() 56 | is Collection<*> -> value.map { toString(it) }.toString() 57 | else -> value.toString() 58 | } 59 | } 60 | val prefix = (instance.type as? Type.Reference)?.component?.name?.takeIf { it != "Struct" && instance.value is Map<*, *> } ?: "" 61 | Object(Type.STRING, prefix + toString(instance.value)) 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/library/AtomInitializer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.library 2 | 3 | import com.ionspin.kotlin.bignum.integer.BigInteger 4 | import dev.rhovas.interpreter.environment.Component 5 | import dev.rhovas.interpreter.environment.Object 6 | import dev.rhovas.interpreter.environment.type.Type 7 | import dev.rhovas.interpreter.parser.rhovas.RhovasAst 8 | 9 | object AtomInitializer : Library.ComponentInitializer(Component.Class("Atom")) { 10 | 11 | override fun declare() { 12 | inherits.add(Type.COMPARABLE[Type.ATOM]) 13 | inherits.add(Type.HASHABLE[Type.ATOM]) 14 | } 15 | 16 | override fun define() { 17 | method("name", 18 | parameters = listOf(), 19 | returns = Type.STRING, 20 | ) { (instance): T1 -> 21 | Object(Type.STRING, instance.name) 22 | } 23 | 24 | method("compare", operator = "<=>", 25 | parameters = listOf("other" to Type.ATOM), 26 | returns = Type.INTEGER, 27 | ) { (instance, other): T2 -> 28 | Object(Type.INTEGER, BigInteger.fromInt(instance.name.compareTo(other.name))) 29 | } 30 | 31 | method("to", 32 | parameters = listOf("type" to Type.TYPE[Type.STRING]), 33 | returns = Type.STRING, 34 | ) { (instance, _type): T2 -> 35 | Object(Type.STRING, ":${instance.name}") 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/library/BooleanInitializer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.library 2 | 3 | import dev.rhovas.interpreter.environment.Component 4 | import dev.rhovas.interpreter.environment.Object 5 | import dev.rhovas.interpreter.environment.type.Type 6 | 7 | object BooleanInitializer : Library.ComponentInitializer(Component.Class("Boolean")) { 8 | 9 | override fun declare() { 10 | inherits.add(Type.HASHABLE[Type.BOOLEAN]) 11 | } 12 | 13 | override fun define() { 14 | method("negate", operator = "!", 15 | parameters = listOf(), 16 | returns = Type.BOOLEAN, 17 | ) { (instance): T1 -> 18 | Object(Type.BOOLEAN, !instance) 19 | } 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/library/ComparableInitializer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.library 2 | 3 | import com.ionspin.kotlin.bignum.integer.BigInteger 4 | import dev.rhovas.interpreter.environment.Component 5 | import dev.rhovas.interpreter.environment.Modifiers 6 | import dev.rhovas.interpreter.environment.Object 7 | import dev.rhovas.interpreter.environment.type.Type 8 | 9 | object ComparableInitializer : Library.ComponentInitializer(Component.Interface("Comparable", Modifiers(Modifiers.Inheritance.ABSTRACT))) { 10 | 11 | override fun declare() { 12 | generics.add(Type.Generic("T") { Type.COMPARABLE[it] }) 13 | inherits.add(Type.EQUATABLE[Type.Generic("T") { Type.COMPARABLE[it] }]) 14 | } 15 | 16 | override fun define() { 17 | method("compare", operator = "<=>", 18 | modifiers = Modifiers(Modifiers.Inheritance.VIRTUAL), 19 | parameters = listOf("other" to Type.COMPARABLE[Type.Generic("T") { Type.COMPARABLE[it] }]), 20 | returns = Type.INTEGER, 21 | ) { (instance, other): T2 -> 22 | require(instance is Comparable<*> && instance::class.isInstance(other)) 23 | val result = (instance as Comparable).compareTo(other) 24 | Object(Type.INTEGER, BigInteger.fromInt(result)) 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/library/DecimalInitializer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.library 2 | 3 | import com.ionspin.kotlin.bignum.decimal.BigDecimal 4 | import com.ionspin.kotlin.bignum.decimal.DecimalMode 5 | import com.ionspin.kotlin.bignum.decimal.RoundingMode 6 | import dev.rhovas.interpreter.environment.Component 7 | import dev.rhovas.interpreter.environment.Object 8 | import dev.rhovas.interpreter.environment.type.Type 9 | 10 | object DecimalInitializer : Library.ComponentInitializer(Component.Class("Decimal")) { 11 | 12 | override fun declare() { 13 | inherits.add(Type.COMPARABLE[Type.DECIMAL]) 14 | inherits.add(Type.HASHABLE[Type.DECIMAL]) 15 | } 16 | 17 | override fun define() { 18 | method("abs", 19 | parameters = listOf(), 20 | returns = Type.DECIMAL, 21 | ) { (instance): T1 -> 22 | Object(Type.DECIMAL, instance.abs()) 23 | } 24 | 25 | method("negate", operator = "-", 26 | parameters = listOf(), 27 | returns = Type.DECIMAL, 28 | ) { (instance): T1 -> 29 | Object(Type.DECIMAL, instance.negate()) 30 | } 31 | 32 | method("add", operator = "+", 33 | parameters = listOf("other" to Type.DECIMAL), 34 | returns = Type.DECIMAL, 35 | ) { (instance, other): T2 -> 36 | Object(Type.DECIMAL, instance.add(other)) 37 | } 38 | 39 | method("subtract", operator = "-", 40 | parameters = listOf("instance" to Type.DECIMAL), 41 | returns = Type.DECIMAL, 42 | ) { (instance, other): T2 -> 43 | Object(Type.DECIMAL, instance.subtract(other)) 44 | } 45 | 46 | method("multiply", operator = "*", 47 | parameters = listOf("other" to Type.DECIMAL), 48 | returns = Type.DECIMAL, 49 | ) { (instance, other): T2 -> 50 | Object(Type.DECIMAL, instance.multiply(other)) 51 | } 52 | 53 | method("divide", operator = "/", 54 | parameters = listOf("other" to Type.DECIMAL), 55 | returns = Type.DECIMAL, 56 | ) { (instance, other): T2 -> 57 | Object(Type.DECIMAL, instance.divide(other, DecimalMode(other.precision, RoundingMode.TOWARDS_ZERO, other.scale))) 58 | } 59 | 60 | method("rem", 61 | parameters = listOf("other" to Type.DECIMAL), 62 | returns = Type.DECIMAL, 63 | ) { (instance, other): T2 -> 64 | Object(Type.DECIMAL, instance.rem(other)) 65 | } 66 | 67 | method("mod", 68 | parameters = listOf("other" to Type.DECIMAL), 69 | returns = Type.DECIMAL, 70 | ) { (instance, other): T2 -> 71 | val rem = instance.rem(other) 72 | val mod = if (rem.isNegative) rem + other else rem 73 | Object(Type.DECIMAL, mod) 74 | } 75 | 76 | method("to", 77 | parameters = listOf("type" to Type.TYPE[Type.INTEGER]), 78 | returns = Type.INTEGER, 79 | ) { (instance, _type): T2 -> 80 | Object(Type.INTEGER, instance.toBigInteger()) 81 | } 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/library/DynamicInitializer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.library 2 | 3 | import dev.rhovas.interpreter.environment.Component 4 | 5 | object DynamicInitializer : Library.ComponentInitializer(Component.Interface("Dynamic")) { 6 | 7 | override fun declare() {} 8 | 9 | override fun define() {} 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/library/EquatableInitializer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.library 2 | 3 | import dev.rhovas.interpreter.environment.Component 4 | import dev.rhovas.interpreter.environment.Modifiers 5 | import dev.rhovas.interpreter.environment.Object 6 | import dev.rhovas.interpreter.environment.type.Type 7 | 8 | object EquatableInitializer : Library.ComponentInitializer(Component.Interface("Equatable")) { 9 | 10 | override fun declare() { 11 | generics.add(Type.Generic("T") { Type.EQUATABLE[it] }) 12 | inherits.add(Type.ANY) 13 | } 14 | 15 | override fun define() { 16 | method("equals", operator = "==", 17 | modifiers = Modifiers(Modifiers.Inheritance.VIRTUAL), 18 | parameters = listOf("other" to Type.EQUATABLE.DYNAMIC), 19 | returns = Type.BOOLEAN, 20 | ) { (instance, other): T2 -> 21 | fun equals(value: Any?, other: Any?): Boolean { 22 | return when { 23 | value is Object && other is Object -> value.methods.equals(other) 24 | value is Map<*, *> && other is Map<*, *> -> value.keys == other.keys && value.all { equals(it.value, other[it.key]!!) } 25 | value is Collection<*> && other is Collection<*> -> value.size == other.size && value.zip(other).all { equals(it.first, it.second) } 26 | else -> value == other 27 | } 28 | } 29 | Object(Type.BOOLEAN, equals(instance, other)) 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/library/ExceptionInitializer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.library 2 | 3 | import dev.rhovas.interpreter.environment.Component 4 | import dev.rhovas.interpreter.environment.Modifiers 5 | import dev.rhovas.interpreter.environment.Object 6 | import dev.rhovas.interpreter.environment.type.Type 7 | 8 | object ExceptionInitializer : Library.ComponentInitializer(Component.Class("Exception", Modifiers(Modifiers.Inheritance.VIRTUAL))) { 9 | 10 | override fun declare() { 11 | inherits.add(Type.HASHABLE[Type.EXCEPTION]) 12 | } 13 | 14 | override fun define() { 15 | function("", 16 | parameters = listOf("message" to Type.STRING), 17 | returns = Type.EXCEPTION, 18 | ) { (message): T1 -> 19 | Object(Type.EXCEPTION, message) 20 | } 21 | 22 | method("message", 23 | parameters = listOf(), 24 | returns = Type.STRING, 25 | ) { (instance): T1 -> 26 | Object(Type.STRING, instance) 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/library/HashableInitializer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.library 2 | 3 | import com.ionspin.kotlin.bignum.integer.BigInteger 4 | import dev.rhovas.interpreter.environment.Component 5 | import dev.rhovas.interpreter.environment.Modifiers 6 | import dev.rhovas.interpreter.environment.Object 7 | import dev.rhovas.interpreter.environment.type.Type 8 | 9 | object HashableInitializer : Library.ComponentInitializer(Component.Interface("Hashable")) { 10 | 11 | override fun declare() { 12 | generics.add(Type.Generic("T") { Type.HASHABLE[it] }) 13 | inherits.add(Type.EQUATABLE[Type.Generic("T") { Type.HASHABLE[it] }]) 14 | } 15 | 16 | override fun define() { 17 | method("hash", 18 | modifiers = Modifiers(Modifiers.Inheritance.VIRTUAL), 19 | parameters = listOf(), 20 | returns = Type.INTEGER, 21 | ) { (instance): T1 -> 22 | Object(Type.INTEGER, BigInteger.fromInt(instance.hashCode())) 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/library/IntegerInitializer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.library 2 | 3 | import com.ionspin.kotlin.bignum.decimal.BigDecimal 4 | import com.ionspin.kotlin.bignum.integer.BigInteger 5 | import dev.rhovas.interpreter.environment.Component 6 | import dev.rhovas.interpreter.environment.Object 7 | import dev.rhovas.interpreter.environment.type.Type 8 | 9 | object IntegerInitializer : Library.ComponentInitializer(Component.Class("Integer")) { 10 | 11 | override fun declare() { 12 | inherits.add(Type.COMPARABLE[Type.INTEGER]) 13 | inherits.add(Type.HASHABLE[Type.INTEGER]) 14 | } 15 | 16 | override fun define() { 17 | method("abs", 18 | parameters = listOf(), 19 | returns = Type.INTEGER, 20 | ) { (instance): T1 -> 21 | Object(Type.INTEGER, instance.abs()) 22 | } 23 | 24 | method("negate", operator = "-", 25 | parameters = listOf(), 26 | returns = Type.INTEGER, 27 | ) { (instance): T1 -> 28 | Object(Type.INTEGER, instance.negate()) 29 | } 30 | 31 | method("add", operator = "+", 32 | parameters = listOf("other" to Type.INTEGER), 33 | returns = Type.INTEGER, 34 | ) { (instance, other): T2 -> 35 | Object(Type.INTEGER, instance.add(other)) 36 | } 37 | 38 | method("subtract", operator = "-", 39 | parameters = listOf("other" to Type.INTEGER), 40 | returns = Type.INTEGER, 41 | ) { (instance, other): T2 -> 42 | Object(Type.INTEGER, instance.subtract(other)) 43 | } 44 | 45 | method("multiply", operator = "*", 46 | parameters = listOf("other" to Type.INTEGER), 47 | returns = Type.INTEGER, 48 | ) { (instance, other): T2 -> 49 | Object(Type.INTEGER, instance.multiply(other)) 50 | } 51 | 52 | method("divide", operator = "/", 53 | parameters = listOf("other" to Type.INTEGER), 54 | returns = Type.INTEGER, 55 | ) { (instance, other): T2 -> 56 | Object(Type.INTEGER, instance.divide(other)) 57 | } 58 | 59 | method("rem", 60 | parameters = listOf("other" to Type.INTEGER), 61 | returns = Type.INTEGER, 62 | ) { (instance, other): T2 -> 63 | Object(Type.INTEGER, instance.rem(other)) 64 | } 65 | 66 | method("mod", 67 | parameters = listOf("other" to Type.INTEGER), 68 | returns = Type.INTEGER, 69 | ) { (instance, other): T2 -> 70 | Object(Type.INTEGER, instance.mod(other)) 71 | } 72 | 73 | method("to", 74 | parameters = listOf("type" to Type.TYPE[Type.DECIMAL]), 75 | returns = Type.DECIMAL, 76 | ) { (instance): T1 -> 77 | Object(Type.DECIMAL, BigDecimal.fromBigInteger(instance)) 78 | } 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/library/IterableInitializer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.library 2 | 3 | import dev.rhovas.interpreter.environment.Component 4 | import dev.rhovas.interpreter.environment.Modifiers 5 | import dev.rhovas.interpreter.environment.Object 6 | import dev.rhovas.interpreter.environment.type.Type 7 | import dev.rhovas.interpreter.evaluator.Evaluator 8 | 9 | object IterableInitializer : Library.ComponentInitializer(Component.Interface("Iterable")) { 10 | 11 | override fun declare() { 12 | generics.add(generic("T")) 13 | inherits.add(Type.ANY) 14 | } 15 | 16 | override fun define() { 17 | method("iterator", 18 | modifiers = Modifiers(Modifiers.Inheritance.VIRTUAL), 19 | parameters = listOf(), 20 | returns = Type.ITERATOR[generic("T")], 21 | ) { (instance): T1> -> 22 | Object(Type.ITERATOR[generics["T"]!!], instance.iterator()) 23 | } 24 | 25 | method("for", 26 | parameters = listOf("lambda" to Type.LAMBDA[Type.TUPLE[listOf(generic("T"))], Type.VOID, Type.DYNAMIC]), 27 | returns = Type.VOID, 28 | ) { (instance, lambda): T2, Evaluator.Lambda> -> 29 | instance.forEach { lambda.invoke(listOf(it), Type.VOID) } 30 | Object(Type.VOID, Unit) 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/library/IteratorInitializer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.library 2 | 3 | import dev.rhovas.interpreter.environment.Component 4 | import dev.rhovas.interpreter.environment.Modifiers 5 | import dev.rhovas.interpreter.environment.Object 6 | import dev.rhovas.interpreter.environment.type.Type 7 | 8 | object IteratorInitializer : Library.ComponentInitializer(Component.Class("Iterator", Modifiers(Modifiers.Inheritance.VIRTUAL))) { 9 | 10 | override fun declare() { 11 | generics.add(generic("T")) 12 | inherits.add(Type.ANY) 13 | } 14 | 15 | override fun define() { 16 | method("next", 17 | modifiers = Modifiers(Modifiers.Inheritance.VIRTUAL), 18 | parameters = listOf(), 19 | returns = Type.NULLABLE[generic("T")], 20 | ) { (instance): T1> -> 21 | val result = if (instance.hasNext()) instance.next() else null 22 | Object(Type.NULLABLE[generics["T"]!!], result?.let { Pair(it, null) }) 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/library/KernelInitializer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.library 2 | 3 | import com.ionspin.kotlin.bignum.integer.BigInteger 4 | import dev.rhovas.interpreter.INTERPRETER 5 | import dev.rhovas.interpreter.environment.Component 6 | import dev.rhovas.interpreter.environment.Object 7 | import dev.rhovas.interpreter.environment.type.Type 8 | import dev.rhovas.interpreter.parser.rhovas.RhovasAst 9 | 10 | object KernelInitializer: Library.ComponentInitializer(Component.Class("Kernel")) { 11 | 12 | override fun declare() {} 13 | 14 | override fun define() { 15 | function("input", 16 | parameters = listOf(), 17 | returns = Type.STRING, 18 | ) { _: Unit -> 19 | Object(Type.STRING, INTERPRETER.stdin()) 20 | } 21 | 22 | function("input", 23 | parameters = listOf("prompt" to Type.STRING), 24 | returns = Type.STRING, 25 | ) { (prompt): T1 -> 26 | INTERPRETER.stdout(prompt) 27 | Object(Type.STRING, INTERPRETER.stdin()) 28 | } 29 | 30 | function("print", 31 | parameters = listOf("object" to Type.ANY), 32 | returns = Type.VOID, 33 | ) { (obj): T1 -> 34 | INTERPRETER.stdout(obj.methods.toString()) 35 | Object(Type.VOID, null) 36 | } 37 | 38 | function("range", 39 | parameters = listOf("lower" to Type.INTEGER, "upper" to Type.INTEGER, "bound" to Type.ATOM), 40 | returns = Type.LIST[Type.INTEGER], 41 | ) { (lower, upper, bound): T3 -> 42 | val start = if (bound.name in listOf("incl", "incl_excl")) lower else lower.add(BigInteger.ONE) 43 | val end = if (bound.name in listOf("incl", "excl_incl")) upper.add(BigInteger.ONE) else upper 44 | Object(Type.LIST[Type.INTEGER], generateSequence(start.takeIf { it < end }) { 45 | it.add(BigInteger.ONE).takeIf { it < end } 46 | }.toList().map { Object(Type.INTEGER, it) }) 47 | } 48 | 49 | function("lambda", 50 | generics = listOf(generic("T", Type.TUPLE.DYNAMIC), generic("R")), 51 | parameters = listOf("lambda" to Type.LAMBDA[generic("T"), generic("R"), Type.DYNAMIC]), 52 | returns = Type.LAMBDA[generic("T"), generic("R"), Type.DYNAMIC], 53 | ) { (lambda): T1 -> 54 | lambda 55 | } 56 | 57 | function("regex", 58 | parameters = listOf("literals" to Type.LIST[Type.STRING], "arguments" to Type.LIST.DYNAMIC), 59 | returns = Type.REGEX, 60 | ) { (literals, arguments): T2, List> -> 61 | val pattern = literals.zip(arguments + listOf(null)).mapIndexed { index, (literal, argument) -> 62 | //TODO(#16): Union type for String | Regex 63 | literal.value as String + when { 64 | argument == null -> "" 65 | argument.type.isSubtypeOf(Type.STRING) -> Regex.escape(argument.value as String) 66 | argument.type.isSubtypeOf(Type.REGEX) -> (argument.value as Regex).pattern 67 | else -> throw error( 68 | "Invalid argument.", 69 | "The native function #regex requires argument ${index} to be type String | Regex, but received ${argument.type}.", 70 | ) 71 | } 72 | }.joinToString("").trim(' ').removeSurrounding("/") 73 | Object(Type.REGEX, Regex(pattern)) 74 | } 75 | 76 | function("typeof", 77 | parameters = listOf("element" to Type.ANY), 78 | returns = Type.TYPE.DYNAMIC, 79 | ) { (element): T1 -> 80 | Object(Type.TYPE[element.type], element.type) 81 | } 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/library/LambdaInitializer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.library 2 | 3 | import dev.rhovas.interpreter.environment.Component 4 | import dev.rhovas.interpreter.environment.Object 5 | import dev.rhovas.interpreter.environment.type.Type 6 | import dev.rhovas.interpreter.evaluator.Evaluator 7 | 8 | object LambdaInitializer : Library.ComponentInitializer(Component.Class("Lambda")) { 9 | 10 | override fun declare() { 11 | generics.add(generic("T", Type.TUPLE.DYNAMIC)) 12 | generics.add(generic("R")) 13 | generics.add(generic("E", Type.EXCEPTION)) 14 | inherits.add(Type.ANY) 15 | } 16 | 17 | override fun define() { 18 | method("invoke", 19 | parameters = listOf("arguments" to generic("T", Type.TUPLE.DYNAMIC)), 20 | throws = listOf(generic("E", Type.EXCEPTION)), 21 | returns = generic("R"), 22 | ) { (instance, arguments): T2> -> 23 | instance.invoke(arguments, generics["R"]!!) 24 | } 25 | 26 | method("to", 27 | parameters = listOf("type" to Type.TYPE[Type.STRING]), 28 | returns = Type.STRING, 29 | ) { (instance): T1 -> 30 | Object(Type.STRING, "Lambda/${instance.ir.parameters.size}#${instance.hashCode()}") 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/library/Library.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.library 2 | 3 | import dev.rhovas.interpreter.EVALUATOR 4 | import dev.rhovas.interpreter.environment.Component 5 | import dev.rhovas.interpreter.environment.Function 6 | import dev.rhovas.interpreter.environment.Modifiers 7 | import dev.rhovas.interpreter.environment.Object 8 | import dev.rhovas.interpreter.environment.Scope 9 | import dev.rhovas.interpreter.environment.type.Type 10 | import dev.rhovas.interpreter.environment.Variable 11 | import dev.rhovas.interpreter.evaluator.EvaluateException 12 | import kotlin.reflect.typeOf 13 | 14 | object Library { 15 | 16 | val SCOPE = Scope.Definition(null) 17 | val TYPES get() = SCOPE.types 18 | 19 | init { 20 | // This list is intended to be in initialization order following DAG 21 | // requirements for inheritance (aka, no type should inherit from a type 22 | // defined later in the initialization order). 23 | val initializers = listOf( 24 | DynamicInitializer, 25 | AnyInitializer, 26 | EquatableInitializer, 27 | ComparableInitializer, 28 | HashableInitializer, 29 | VoidInitializer, 30 | BooleanInitializer, 31 | IntegerInitializer, 32 | DecimalInitializer, 33 | StringInitializer, 34 | AtomInitializer, 35 | TupleInitializer, 36 | IterableInitializer, 37 | IteratorInitializer, 38 | ListInitializer, 39 | SetInitializer, 40 | MapInitializer, 41 | StructInitializer, 42 | TypeInitializer, 43 | ExceptionInitializer, 44 | ResultInitializer, 45 | NullableInitializer, 46 | LambdaInitializer, 47 | RegexInitializer, 48 | KernelInitializer, 49 | MathInitializer, 50 | ) 51 | initializers.forEach { initializer -> 52 | TYPES.define(initializer.component.name, initializer.component.type) 53 | } 54 | initializers.forEach { initializer -> 55 | initializer.declare() 56 | initializer.generics.associateByTo(initializer.component.generics) { it.name } 57 | } 58 | initializers.forEach { initializer -> 59 | initializer.component.inherits.addAll(initializer.inherits) 60 | initializer.inherits.forEach { initializer.component.inherit(it) } 61 | initializer.define() 62 | } 63 | (KernelInitializer.component.scope as Scope.Definition).functions.collect().values.flatten().forEach { 64 | SCOPE.functions.define(it) 65 | } 66 | } 67 | 68 | fun type(name: String, generics: Map = mapOf()): Type.Reference { 69 | val type = TYPES[name]!! as Type.Reference 70 | return if (generics.isEmpty()) type else Type.Reference(type.component, generics) 71 | } 72 | 73 | abstract class ComponentInitializer( 74 | val component: Component<*>, 75 | ) { 76 | 77 | val generics = mutableListOf() 78 | val inherits = mutableListOf() 79 | 80 | abstract fun declare() 81 | 82 | abstract fun define() 83 | 84 | fun variable( 85 | name: String, 86 | type: Type, 87 | value: Any? 88 | ) { 89 | val variable = Variable.Definition(Variable.Declaration(name, type), Object(type, value)) 90 | component.scope.variables.define(variable) 91 | } 92 | 93 | inline fun function( 94 | name: String, 95 | operator: String? = null, 96 | modifiers: Modifiers = Modifiers(), 97 | generics: List = listOf(), 98 | parameters: List>, 99 | returns: Type, 100 | throws: List = listOf(), 101 | crossinline implementation: Context.(T) -> Object, 102 | ) { 103 | val declaration = Function.Declaration(name, modifiers, generics.associateByTo(linkedMapOf()) { it.name }, parameters.map { Variable.Declaration(it.first, it.second) }, returns, throws) 104 | val function = Function.Definition(declaration) { arguments -> 105 | val generics = mutableMapOf() 106 | EVALUATOR.require(declaration.isResolvedBy(arguments.map { it.type }, generics)) 107 | val transform = arguments.indices.map { 108 | when (typeOf().arguments[it].type?.classifier) { 109 | Object::class -> arguments[it] 110 | else -> arguments[it].value 111 | } 112 | } 113 | val wrapper = when (T::class) { 114 | Unit::class -> Unit as T 115 | T1::class -> T1(transform[0]) as T 116 | T2::class -> T2(transform[0], transform[1]) as T 117 | T3::class -> T3(transform[0], transform[1], transform[2]) as T 118 | T4::class -> T4(transform[0], transform[1], transform[2], transform[3]) as T 119 | T5::class -> T5(transform[0], transform[1], transform[2], transform[3], transform[4]) as T 120 | else -> transform as T 121 | } 122 | val result = implementation.invoke(Context(generics), wrapper) 123 | EVALUATOR.require(result.type.isSubtypeOf(returns, generics)) 124 | result 125 | } 126 | component.scope.functions.define(function) 127 | operator?.let { component.scope.functions.define(function, it) } 128 | } 129 | 130 | inline fun method( 131 | name: String, 132 | operator: String? = null, 133 | modifiers: Modifiers = Modifiers(), 134 | generics: List = listOf(), 135 | parameters: List>, 136 | returns: Type, 137 | throws: List = listOf(), 138 | crossinline implementation: Context.(T) -> Object, 139 | ) { 140 | function(name, operator, modifiers, this.generics + generics, listOf("instance" to component.type) + parameters, returns, throws, implementation) 141 | } 142 | 143 | fun generic(name: String, bound: Type = Type.ANY) = Type.Generic(name, bound) 144 | 145 | data class Context( 146 | val generics: Map, 147 | ) { 148 | 149 | fun require(condition: Boolean, error: () -> EvaluateException) = EVALUATOR.require(condition, error) 150 | fun error(summary: String, details: String) = EVALUATOR.error(summary, details) 151 | 152 | } 153 | 154 | data class T1(val a: A) 155 | data class T2(val a: A, val b: B) 156 | data class T3(val a: A, val b: B, val c: C) 157 | data class T4(val a: A, val b: B, val c: C, val d: D) 158 | data class T5(val a: A, val b: B, val c: C, val d: D, val e: E) 159 | 160 | } 161 | 162 | } 163 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/library/ListInitializer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.library 2 | 3 | import com.ionspin.kotlin.bignum.integer.BigInteger 4 | import dev.rhovas.interpreter.environment.Component 5 | import dev.rhovas.interpreter.environment.Object 6 | import dev.rhovas.interpreter.environment.type.Type 7 | import dev.rhovas.interpreter.evaluator.Evaluator 8 | 9 | object ListInitializer : Library.ComponentInitializer(Component.Class("List")) { 10 | 11 | override fun declare() { 12 | generics.add(generic("T")) 13 | inherits.add(Type.ITERABLE[generic("T")]) 14 | inherits.add(Type.EQUATABLE[Type.LIST.DYNAMIC]) 15 | } 16 | 17 | override fun define() { 18 | method("size", 19 | parameters = listOf(), 20 | returns = Type.INTEGER, 21 | ) { (instance): T1> -> 22 | Object(Type.INTEGER, BigInteger.fromInt(instance.size)) 23 | } 24 | 25 | method("empty", 26 | parameters = listOf(), 27 | returns = Type.BOOLEAN, 28 | ) { (instance): T1> -> 29 | Object(Type.BOOLEAN, instance.isEmpty()) 30 | } 31 | 32 | method("first", 33 | parameters = listOf(), 34 | returns = Type.NULLABLE[generic("T")], 35 | ) { (instance): T1> -> 36 | Object(Type.NULLABLE[generics["T"]!!], instance.firstOrNull()?.let { Pair(it, null) }) 37 | } 38 | 39 | method("last", 40 | parameters = listOf(), 41 | returns = Type.NULLABLE[generic("T")], 42 | ) { (instance): T1> -> 43 | Object(Type.NULLABLE[generics["T"]!!], instance.lastOrNull()?.let { Pair(it, null) }) 44 | } 45 | 46 | method("get", operator = "[]", 47 | parameters = listOf("index" to Type.INTEGER), 48 | returns = generic("T") 49 | ) { (instance, index): T2, BigInteger> -> 50 | require(index >= BigInteger.ZERO && index < BigInteger.fromInt(instance.size)) { error( 51 | "Invalid list index.", 52 | "Expected an index in range [0, ${instance.size}), but received ${index}.", 53 | ) } 54 | instance[index.intValue()] 55 | } 56 | 57 | method("set", operator = "[]=", 58 | parameters = listOf("index" to Type.INTEGER, "value" to generic("T")), 59 | returns = Type.VOID, 60 | ) { (instance, index, value): T3, BigInteger, Object> -> 61 | require(index >= BigInteger.ZERO && index < BigInteger.fromInt(instance.size)) { error( 62 | "Invalid list index.", 63 | "Expected an index in range [0, ${instance.size}), but received ${index}.", 64 | ) } 65 | instance[index.intValue()] = value 66 | Object(Type.VOID, null) 67 | } 68 | 69 | method("slice", 70 | parameters = listOf("start" to Type.INTEGER), 71 | returns = Type.LIST[generic("T")], 72 | ) { (instance, start): T2, BigInteger> -> 73 | require(start >= BigInteger.ZERO && start <= BigInteger.fromInt(instance.size)) { error( 74 | "Invalid index.", 75 | "Expected a start index in range [0, ${instance.size}), but received ${start}.", 76 | ) } 77 | Object(Type.LIST[generics["T"]!!], instance.subList(start.intValue(), instance.size)) 78 | } 79 | 80 | method("slice", "[]", 81 | parameters = listOf("start" to Type.INTEGER, "end" to Type.INTEGER), 82 | returns = Type.LIST[generic("T")], 83 | ) { (instance, start, end): T3, BigInteger, BigInteger> -> 84 | require(start >= BigInteger.ZERO && start <= BigInteger.fromInt(instance.size)) { error( 85 | "Invalid index.", 86 | "Expected a start index in range [0, ${instance.size}), but received ${start}.", 87 | ) } 88 | require(end >= start && end <= BigInteger.fromInt(instance.size)) { error( 89 | "Invalid index.", 90 | "Expected an end index in range [start = ${start}, ${instance.size}), but received ${end}.", 91 | ) } 92 | Object(Type.LIST[generics["T"]!!], instance.subList(start.intValue(), end.intValue())) 93 | } 94 | 95 | method("contains", 96 | parameters = listOf("value" to generic("T")), 97 | returns = Type.BOOLEAN, 98 | ) { (instance, value): T2, Object> -> 99 | Object(Type.BOOLEAN, instance.any { it.methods.equals(value) }) 100 | } 101 | 102 | method("indexOf", 103 | parameters = listOf("value" to generic("T")), 104 | returns = Type.NULLABLE[Type.INTEGER], 105 | ) { (instance, other): T2, Object> -> 106 | val result = instance.indexOfFirst { other.methods.equals(it) }.takeIf { it != -1 }?.let { BigInteger.fromInt(it) } 107 | Object(Type.NULLABLE[Type.INTEGER], result?.let { Pair(Object(Type.INTEGER, it), null) }) 108 | } 109 | 110 | method("find", 111 | parameters = listOf("lambda" to Type.LAMBDA[Type.TUPLE[listOf(generic("T"))], Type.BOOLEAN, Type.DYNAMIC]), 112 | returns = generic("T"), 113 | ) { (instance, lambda): T2, Evaluator.Lambda> -> 114 | Object(generics["T"]!!, instance.find { lambda.invoke(listOf(it), Type.BOOLEAN).value as Boolean }) 115 | } 116 | 117 | method("concat", operator = "+", 118 | parameters = listOf("other" to Type.LIST[generic("T")]), 119 | returns = Type.LIST[generic("T")], 120 | ) { (instance, other): T2, List> -> 121 | Object(Type.LIST[generics["T"]!!], instance + other) 122 | } 123 | 124 | method("repeat", operator = "*", 125 | parameters = listOf("times" to Type.INTEGER), 126 | returns = Type.LIST[generic("T")], 127 | ) { (instance, times): T2, BigInteger> -> 128 | Object(Type.LIST[generics["T"]!!], (0 until times.intValue()).flatMap { instance }) 129 | } 130 | 131 | method("reverse", 132 | parameters = listOf(), 133 | returns = Type.LIST[generic("T")], 134 | ) { (instance): T1> -> 135 | Object(Type.LIST[generics["T"]!!], instance.reversed()) 136 | } 137 | 138 | method("map", 139 | parameters = listOf("lambda" to Type.LAMBDA[Type.TUPLE[listOf(generic("T"))], generic("R"), Type.DYNAMIC]), 140 | returns = Type.LIST[generic("R")], 141 | ) { (instance, lambda): T2, Evaluator.Lambda> -> 142 | Object(Type.LIST[generics["R"]!!], instance.map { lambda.invoke(listOf(it), generics["R"]!!) }) 143 | } 144 | 145 | method("filter", 146 | parameters = listOf("lambda" to Type.LAMBDA[Type.TUPLE[listOf(generic("T"))], Type.BOOLEAN, Type.DYNAMIC]), 147 | returns = Type.LIST[generic("T")], 148 | ) { (instance, lambda): T2, Evaluator.Lambda> -> 149 | Object(Type.LIST[generics["T"]!!], instance.filter { lambda.invoke(listOf(it), Type.BOOLEAN).value as Boolean }) 150 | } 151 | 152 | method("reduce", 153 | parameters = listOf("lambda" to Type.LAMBDA[Type.TUPLE[listOf(generic("T"), generic("T"))], generic("T"), Type.DYNAMIC]), 154 | returns = Type.NULLABLE[generic("T")], 155 | ) { (instance, lambda): T2, Evaluator.Lambda> -> 156 | val result = instance.reduceOrNull { result, element -> lambda.invoke(listOf(result, element), generics["T"]!!) } 157 | Object(Type.NULLABLE[generics["T"]!!], result?.let { Pair(it, null) }) 158 | } 159 | 160 | method("reduce", 161 | generics = listOf(generic("R")), 162 | parameters = listOf("initial" to generic("R"), "lambda" to Type.LAMBDA[Type.TUPLE[listOf(generic("T"), generic("T"))], generic("T"), Type.DYNAMIC]), 163 | returns = generic("R"), 164 | ) { (instance, initial, lambda): T3, Object, Evaluator.Lambda> -> 165 | instance.fold(initial) { result, element -> lambda.invoke(listOf(result, element), generics["R"]!!) } 166 | } 167 | } 168 | 169 | } 170 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/library/MapInitializer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.library 2 | 3 | import com.ionspin.kotlin.bignum.integer.BigInteger 4 | import dev.rhovas.interpreter.environment.Component 5 | import dev.rhovas.interpreter.environment.Object 6 | import dev.rhovas.interpreter.environment.type.Type 7 | 8 | object MapInitializer : Library.ComponentInitializer(Component.Class("Map")) { 9 | 10 | override fun declare() { 11 | generics.add(Type.Generic("K") { Type.HASHABLE[it] }) 12 | generics.add(generic("V")) 13 | inherits.add(Type.EQUATABLE[Type.MAP[Type.Generic("K") { Type.HASHABLE[it] }, generic("V")]]) 14 | } 15 | 16 | override fun define() { 17 | function("", 18 | generics = listOf(Type.Generic("K") { Type.HASHABLE[it] }, generic("V")), 19 | parameters = listOf("initial" to Type.MAP[Type.Generic("K") { Type.HASHABLE[it] }, generic("V")]), 20 | returns = Type.MAP[Type.Generic("K") { Type.HASHABLE[it] }, generic("V")], 21 | ) { (initial): T1> -> 22 | Object(Type.MAP[generics["K"]!!, generics["V"]!!], initial.toMutableMap()) 23 | } 24 | 25 | method("size", 26 | parameters = listOf(), 27 | returns = Type.INTEGER, 28 | ) { (instance): T1> -> 29 | Object(Type.INTEGER, BigInteger.fromInt(instance.size)) 30 | } 31 | 32 | method("empty", 33 | parameters = listOf(), 34 | returns = Type.BOOLEAN, 35 | ) { (instance): T1> -> 36 | Object(Type.BOOLEAN, instance.isEmpty()) 37 | } 38 | 39 | method("keys", 40 | parameters = listOf(), 41 | returns = Type.SET[Type.Generic("K") { Type.HASHABLE[it] }], 42 | ) { (instance): T1> -> 43 | Object(Type.SET[generics["K"]!!], instance.keys) 44 | } 45 | 46 | method("values", 47 | parameters = listOf(), 48 | returns = Type.LIST[generic("V")], 49 | ) { (instance): T1> -> 50 | Object(Type.LIST[generics["V"]!!], instance.values.toList()) 51 | } 52 | 53 | method("entries", 54 | parameters = listOf(), 55 | returns = Type.LIST[Type.TUPLE[listOf(Type.Generic("K") { Type.HASHABLE[it] }, generic("V"))]], 56 | ) { (instance): T1> -> 57 | val entryType = Type.TUPLE[listOf(generics["K"]!!, generics["V"]!!)] 58 | Object(Type.LIST[entryType], instance.entries.map { Object(entryType, listOf(it.key.instance, it.value)) }) 59 | } 60 | 61 | method("get", operator = "[]", 62 | parameters = listOf("key" to Type.Generic("K") { Type.HASHABLE[it] }), 63 | returns = Type.NULLABLE[generic("V")], 64 | ) { (instance, key): T2, Object> -> 65 | Object(Type.NULLABLE[generics["V"]!!], instance[Object.Hashable(key)]?.let { Pair(it, null) }) 66 | } 67 | 68 | method("set", operator = "[]=", 69 | parameters = listOf("key" to Type.Generic("K") { Type.HASHABLE[it] }, "value" to generic("V")), 70 | returns = Type.VOID, 71 | ) { (instance, key, value): T3, Object, Object> -> 72 | instance[Object.Hashable(key)] = value 73 | Object(Type.VOID, Unit) 74 | } 75 | 76 | method("remove", 77 | parameters = listOf("key" to Type.Generic("K") { Type.HASHABLE[it] }), 78 | returns = Type.VOID, 79 | ) { (instance, key): T2, Object> -> 80 | instance.remove(Object.Hashable(key)) 81 | Object(Type.VOID, Unit) 82 | } 83 | 84 | method("contains", 85 | parameters = listOf("key" to Type.Generic("K") { Type.HASHABLE[it] }), 86 | returns = Type.BOOLEAN, 87 | ) { (instance, key): T2, Object> -> 88 | Object(Type.BOOLEAN, instance.containsKey(Object.Hashable(key))) 89 | } 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/library/MathInitializer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.library 2 | 3 | import com.ionspin.kotlin.bignum.decimal.BigDecimal 4 | import com.ionspin.kotlin.bignum.integer.BigInteger 5 | import dev.rhovas.interpreter.environment.Component 6 | import dev.rhovas.interpreter.environment.Object 7 | import dev.rhovas.interpreter.environment.type.Type 8 | import kotlin.math.pow 9 | import kotlin.random.Random 10 | 11 | object MathInitializer : Library.ComponentInitializer(Component.Class("Math")) { 12 | 13 | override fun declare() {} 14 | 15 | override fun define() { 16 | variable("pi", Type.DECIMAL, BigDecimal.fromDouble(kotlin.math.PI)) 17 | variable("e", Type.DECIMAL, BigDecimal.fromDouble(kotlin.math.E)) 18 | 19 | function("random", 20 | parameters = listOf(), 21 | returns = Type.DECIMAL, 22 | ) { _: Unit -> 23 | Object(Type.DECIMAL, BigDecimal.fromDouble(Random.nextDouble())) 24 | } 25 | 26 | function("random", 27 | parameters = listOf("until" to Type.INTEGER), 28 | returns = Type.DECIMAL 29 | ) { (until): T1 -> 30 | Object(Type.DECIMAL, BigInteger.fromInt(Random.nextInt(until.intValue(false)))) 31 | } 32 | 33 | function("pow", 34 | parameters = listOf("base" to Type.INTEGER, "exponent" to Type.INTEGER), 35 | returns = Type.INTEGER, 36 | ) { (base, exponent): T2 -> 37 | Object(Type.INTEGER, base.pow(exponent.longValue(false))) 38 | } 39 | 40 | function("pow", 41 | parameters = listOf("base" to Type.DECIMAL, "exponent" to Type.DECIMAL), 42 | returns = Type.DECIMAL, 43 | ) { (base, exponent): T2 -> 44 | Object(Type.DECIMAL, BigDecimal.fromDouble(base.doubleValue().pow(exponent.doubleValue(false)))) 45 | } 46 | 47 | function("sqrt", 48 | parameters = listOf("num" to Type.DECIMAL), 49 | returns = Type.DECIMAL, 50 | ) { (num): T1 -> 51 | Object(Type.DECIMAL, BigDecimal.fromDouble(kotlin.math.sqrt(num.doubleValue(false)))) 52 | } 53 | 54 | function("cbrt", 55 | parameters = listOf("num" to Type.DECIMAL), 56 | returns = Type.DECIMAL, 57 | ) { (num): T1 -> 58 | Object(Type.DECIMAL, BigDecimal.fromDouble(kotlin.math.cbrt(num.doubleValue(false)))) 59 | } 60 | 61 | function("log", 62 | parameters = listOf("num" to Type.DECIMAL, "base" to Type.DECIMAL), 63 | returns = Type.DECIMAL, 64 | ) { (num, base): T2 -> 65 | Object(Type.DECIMAL, BigDecimal.fromDouble(kotlin.math.log(num.doubleValue(false), base.doubleValue(false)))) 66 | } 67 | 68 | function("sin", 69 | parameters = listOf("num" to Type.DECIMAL), 70 | returns = Type.DECIMAL, 71 | ) { (num): T1 -> 72 | Object(Type.DECIMAL, BigDecimal.fromDouble(kotlin.math.sin(num.doubleValue(false)))) 73 | } 74 | 75 | function("cos", 76 | parameters = listOf("num" to Type.DECIMAL), 77 | returns = Type.DECIMAL, 78 | ) { (num): T1 -> 79 | Object(Type.DECIMAL, BigDecimal.fromDouble(kotlin.math.cos(num.doubleValue(false)))) 80 | } 81 | 82 | function("tan", 83 | parameters = listOf("num" to Type.DECIMAL), 84 | returns = Type.DECIMAL, 85 | ) { (num): T1 -> 86 | Object(Type.DECIMAL, BigDecimal.fromDouble(kotlin.math.tan(num.doubleValue(false)))) 87 | } 88 | 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/library/NullableInitializer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.library 2 | 3 | import dev.rhovas.interpreter.environment.Component 4 | import dev.rhovas.interpreter.environment.Object 5 | import dev.rhovas.interpreter.environment.type.Type 6 | 7 | object NullableInitializer : Library.ComponentInitializer(Component.Class("Nullable")) { 8 | 9 | override fun declare() { 10 | generics.add(generic("T")) 11 | inherits.add(Type.RESULT[generic("T"), Type.EXCEPTION]) 12 | } 13 | 14 | override fun define() { 15 | function("", 16 | generics = listOf(generic("T")), 17 | parameters = listOf("value" to generic("T")), 18 | returns = Type.NULLABLE[generic("T")], 19 | ) { (value): T1 -> 20 | Object(Type.NULLABLE[generics["T"]!!], Pair(value, null)) 21 | } 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/library/RegexInitializer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.library 2 | 3 | import com.ionspin.kotlin.bignum.integer.BigInteger 4 | import dev.rhovas.interpreter.environment.Component 5 | import dev.rhovas.interpreter.environment.Object 6 | import dev.rhovas.interpreter.environment.type.Type 7 | 8 | object RegexInitializer : Library.ComponentInitializer(Component.Class("Regex")) { 9 | 10 | private val MATCH_TYPE get() = Type.STRUCT[listOf( 11 | "index" to Type.INTEGER, 12 | "value" to Type.INTEGER, 13 | "groups" to Type.LIST[Type.NULLABLE[Type.STRING]], 14 | )] 15 | 16 | override fun declare() { 17 | inherits.add(Type.ANY) 18 | } 19 | 20 | override fun define() { 21 | function("", 22 | parameters = listOf("regex" to Type.STRING), 23 | returns = Type.REGEX, 24 | ) { (pattern): T1 -> 25 | Object(Type.REGEX, Regex(pattern)) 26 | } 27 | 28 | method("match", 29 | parameters = listOf("value" to Type.STRING), 30 | returns = Type.LIST[MATCH_TYPE] 31 | ) { (instance, value): T2 -> 32 | Object(Type.LIST[MATCH_TYPE], instance.findAll(value).toList().map { 33 | Object(MATCH_TYPE, mapOf( 34 | "index" to Object(Type.INTEGER, BigInteger.fromInt(it.range.first)), 35 | "value" to Object(Type.STRING, it.value), 36 | "groups" to Object(Type.LIST[Type.NULLABLE[Type.STRING]], it.groups.drop(1).map { 37 | Object(Type.NULLABLE[Type.STRING], it?.let { Pair(Object(Type.STRING, it.value), null) }) 38 | }) 39 | )) 40 | }) 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/library/ResultInitializer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.library 2 | 3 | import dev.rhovas.interpreter.environment.Component 4 | import dev.rhovas.interpreter.environment.Modifiers 5 | import dev.rhovas.interpreter.environment.Object 6 | import dev.rhovas.interpreter.environment.type.Type 7 | import dev.rhovas.interpreter.evaluator.Evaluator 8 | 9 | object ResultInitializer : Library.ComponentInitializer(Component.Class("Result", Modifiers(Modifiers.Inheritance.VIRTUAL))) { 10 | 11 | override fun declare() { 12 | generics.add(generic("T")) 13 | generics.add(generic("E")) 14 | inherits.add(Type.HASHABLE[Type.RESULT[Type.EQUATABLE[Type.Generic("T") { Type.EQUATABLE[it] }], Type.EQUATABLE[Type.Generic("E") { Type.EQUATABLE[it] }]]]) 15 | } 16 | 17 | override fun define() { 18 | method("value", 19 | parameters = listOf(), 20 | returns = Type.NULLABLE[generic("T")], 21 | ) { (instance): T1?> -> 22 | Object(Type.NULLABLE[generics["T"]!!], instance?.first?.let { Pair(it, null) }) 23 | } 24 | 25 | method("value!", 26 | parameters = listOf(), 27 | returns = generic("T"), 28 | ) { (instance): T1?> -> 29 | instance?.first ?: throw Evaluator.Throw(instance?.second ?: Object(Type.EXCEPTION, "Invalid null access.")) 30 | } 31 | 32 | method("error", 33 | parameters = listOf(), 34 | returns = Type.NULLABLE[generic("E")], 35 | ) { (instance): T1?> -> 36 | Object(Type.NULLABLE[generics["E"]!!], instance?.second?.let { Pair(it, null) }) 37 | } 38 | 39 | method("map", 40 | generics = listOf(generic("R")), 41 | parameters = listOf("lambda" to Type.LAMBDA[Type.TUPLE[listOf(generic("T"))], generic("R"), Type.DYNAMIC]), 42 | returns = Type.RESULT[generic("R"), generic("E")], 43 | ) { (instance, lambda): T2?, Evaluator.Lambda> -> 44 | val value = instance?.takeIf { instance.first != null }?.let { 45 | Pair(lambda.invoke(listOf(it.first!!), generics["R"]!!), null as Object?) 46 | } 47 | Object(Type.RESULT[generics["R"]!!, generics["E"]!!], value) 48 | } 49 | 50 | method("or", 51 | parameters = listOf("lambda" to Type.LAMBDA[Type.TUPLE[listOf()], Type.RESULT[generic("T"), generic("E")], Type.DYNAMIC]), 52 | returns = Type.RESULT[generic("T"), generic("E")], 53 | ) { (instance, lambda): T2?, Evaluator.Lambda> -> 54 | val returnsType = Type.RESULT[generics["T"]!!, generics["E"]!!] 55 | val value = instance?.takeIf { instance.first != null } ?: run { 56 | lambda.invoke(listOf(), returnsType).value as Pair? 57 | } 58 | Object(returnsType, value) 59 | } 60 | 61 | method("else", 62 | parameters = listOf("lambda" to Type.LAMBDA[Type.TUPLE[listOf()], generic("T"), Type.DYNAMIC]), 63 | returns = generic("T"), 64 | ) { (instance, lambda): T2?, Evaluator.Lambda> -> 65 | instance?.first ?: lambda.invoke(listOf(), generics["T"]!!) 66 | } 67 | 68 | method("equals", operator = "==", 69 | parameters = listOf("other" to Type.RESULT[generic("T"), generic("E")]), 70 | returns = Type.BOOLEAN, 71 | ) { (instance, other): T2?, Pair?> -> 72 | val result = listOf(instance?.first to other?.first, instance?.second to other?.second).all { (instance, other) -> 73 | if (instance == null || other == null) instance == other else instance.methods.equals(other) 74 | } 75 | Object(Type.BOOLEAN, result) 76 | } 77 | 78 | method("to", 79 | parameters = listOf("type" to Type.TYPE[Type.STRING]), 80 | returns = Type.STRING, 81 | ) { (instance, _type): T2?, Type> -> 82 | when { 83 | instance?.first != null -> Object(Type.STRING, instance.first!!.methods.toString()) 84 | instance?.second != null -> Object(Type.STRING, instance.second!!.methods.toString()) 85 | else -> Object(Type.STRING, "null") 86 | } 87 | } 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/library/SetInitializer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.library 2 | 3 | import com.ionspin.kotlin.bignum.integer.BigInteger 4 | import dev.rhovas.interpreter.environment.Component 5 | import dev.rhovas.interpreter.environment.Object 6 | import dev.rhovas.interpreter.environment.type.Type 7 | 8 | object SetInitializer : Library.ComponentInitializer(Component.Class("Set")) { 9 | 10 | override fun declare() { 11 | generics.add(generic("T")) 12 | inherits.add(Type.ITERABLE[generic("T")]) 13 | inherits.add(Type.EQUATABLE[Type.SET.DYNAMIC]) 14 | } 15 | 16 | override fun define() { 17 | function("", 18 | generics = listOf(generic("T")), 19 | parameters = listOf("initial" to Type.LIST[generic("T")]), 20 | returns = Type.SET[generic("T")], 21 | ) { (initial): T1> -> 22 | Object(Type.SET[generics["T"]!!], initial.map { Object.Hashable(it) }.toMutableSet()) 23 | } 24 | 25 | method("size", 26 | parameters = listOf(), 27 | returns = Type.INTEGER, 28 | ) { (instance): T1> -> 29 | Object(Type.INTEGER, BigInteger.fromInt(instance.size)) 30 | } 31 | 32 | method("empty", 33 | parameters = listOf(), 34 | returns = Type.BOOLEAN, 35 | ) { (instance): T1> -> 36 | Object(Type.BOOLEAN, instance.isEmpty()) 37 | } 38 | 39 | method("contains", operator = "[]", 40 | parameters = listOf("element" to generic("T")), 41 | returns = Type.BOOLEAN, 42 | ) { (instance, element): T2, Object> -> 43 | Object(Type.BOOLEAN, instance.contains(Object.Hashable(element))) 44 | } 45 | 46 | method("add", 47 | parameters = listOf("element" to generic("T")), 48 | returns = Type.VOID, 49 | ) { (instance, element): T2, Object> -> 50 | instance.add(Object.Hashable(element)) 51 | Object(Type.VOID, Unit) 52 | } 53 | 54 | method("remove", 55 | parameters = listOf("element" to generic("T")), 56 | returns = Type.VOID, 57 | ) { (instance, element): T2, Object> -> 58 | instance.remove(Object.Hashable(element)) 59 | Object(Type.VOID, Unit) 60 | } 61 | 62 | method("union", 63 | parameters = listOf("other" to Type.SET[generic("T")]), 64 | returns = Type.SET[generic("T")], 65 | ) { (instance, other): T2, Set> -> 66 | Object(Type.SET[generics["T"]!!], instance.union(other).toMutableSet()) 67 | } 68 | 69 | method("intersection", 70 | parameters = listOf("other" to Type.SET[generic("T")]), 71 | returns = Type.SET[generic("T")], 72 | ) { (instance, other): T2, Set> -> 73 | Object(Type.SET[generics["T"]!!], instance.intersect(other).toMutableSet()) 74 | } 75 | 76 | method("difference", 77 | parameters = listOf("other" to Type.SET[generic("T")]), 78 | returns = Type.SET[generic("T")], 79 | ) { (instance, other): T2, Set> -> 80 | Object(Type.SET[generics["T"]!!], instance.minus(other).toMutableSet()) 81 | } 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/library/StringInitializer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.library 2 | 3 | import com.ionspin.kotlin.bignum.decimal.BigDecimal 4 | import com.ionspin.kotlin.bignum.integer.BigInteger 5 | import dev.rhovas.interpreter.environment.Component 6 | import dev.rhovas.interpreter.environment.Object 7 | import dev.rhovas.interpreter.environment.type.Type 8 | import dev.rhovas.interpreter.parser.Input 9 | import dev.rhovas.interpreter.parser.ParseException 10 | import dev.rhovas.interpreter.parser.rhovas.RhovasAst 11 | import dev.rhovas.interpreter.parser.rhovas.RhovasLexer 12 | import dev.rhovas.interpreter.parser.rhovas.RhovasTokenType 13 | 14 | object StringInitializer : Library.ComponentInitializer(Component.Class("String")) { 15 | 16 | override fun declare() { 17 | inherits.add(Type.COMPARABLE[Type.STRING]) 18 | inherits.add(Type.HASHABLE[Type.STRING]) 19 | } 20 | 21 | override fun define() { 22 | method("size", 23 | parameters = listOf(), 24 | returns = Type.INTEGER, 25 | ) { (instance): T1 -> 26 | Object(Type.INTEGER, BigInteger.fromInt(instance.length)) 27 | } 28 | 29 | method("empty", 30 | parameters = listOf(), 31 | returns = Type.BOOLEAN, 32 | ) { (instance): T1 -> 33 | Object(Type.BOOLEAN, instance.isEmpty()) 34 | } 35 | 36 | method("chars", 37 | parameters = listOf(), 38 | returns = Type.LIST[Type.STRING], 39 | ) { (instance): T1 -> 40 | Object(Type.LIST[Type.STRING], instance.toCharArray().map { Object(Type.STRING, it.toString()) }) 41 | } 42 | 43 | method("get", "[]", 44 | parameters = listOf("index" to Type.INTEGER), 45 | returns = Type.STRING, 46 | ) { (instance, index): T2 -> 47 | require(index >= BigInteger.ZERO && index <= BigInteger.fromInt(instance.length)) { error( 48 | "Invalid index.", 49 | "Expected an index in range [0, ${instance.length}), but received ${index}.", 50 | ) } 51 | Object(Type.STRING, instance[index.intValue()].toString()) 52 | } 53 | 54 | method("slice", 55 | parameters = listOf("start" to Type.INTEGER), 56 | returns = Type.STRING, 57 | ) { (instance, start): T2 -> 58 | require(start >= BigInteger.ZERO && start <= BigInteger.fromInt(instance.length)) { error( 59 | "Invalid index.", 60 | "Expected a start index in range [0, ${instance.length}), but received ${start}.", 61 | ) } 62 | Object(Type.STRING, instance.substring(start.intValue())) 63 | } 64 | 65 | method("slice", "[]", 66 | parameters = listOf("start" to Type.INTEGER, "end" to Type.INTEGER), 67 | returns = Type.STRING, 68 | ) { (instance, start, end): T3 -> 69 | require(start >= BigInteger.ZERO && start <= BigInteger.fromInt(instance.length)) { error( 70 | "Invalid index.", 71 | "Expected a start index in range [0, ${instance.length}], but received ${start}.", 72 | ) } 73 | require(end >= start && end <= BigInteger.fromInt(instance.length)) { error( 74 | "Invalid index.", 75 | "Expected an end index in range [start = ${start}, ${instance.length}], but received ${end}.", 76 | ) } 77 | Object(Type.STRING, instance.substring(start.intValue(), end.intValue())) 78 | } 79 | 80 | method("contains", 81 | parameters = listOf("other" to Type.STRING), 82 | returns = Type.BOOLEAN, 83 | ) { (instance, other): T2 -> 84 | Object(Type.BOOLEAN, instance.contains(other)) 85 | } 86 | 87 | method("matches", 88 | parameters = listOf("regex" to Type.REGEX), 89 | returns = Type.BOOLEAN, 90 | ) { (instance, regex): T2 -> 91 | Object(Type.BOOLEAN, instance.matches(regex)) 92 | } 93 | 94 | method("indexOf", 95 | parameters = listOf("other" to Type.STRING), 96 | returns = Type.NULLABLE[Type.INTEGER], 97 | ) { (instance, other): T2 -> 98 | val result = instance.indexOf(other).takeIf { it != -1 }?.let { BigInteger.fromInt(it) } 99 | Object(Type.NULLABLE[Type.INTEGER], result?.let { Pair(Object(Type.INTEGER, it), null) }) 100 | } 101 | 102 | method("replace", 103 | parameters = listOf("original" to Type.STRING, "other" to Type.STRING), 104 | returns = Type.STRING, 105 | ) { (instance, original, other): T3 -> 106 | Object(Type.STRING, instance.replace(original, other)) 107 | } 108 | 109 | method("concat", operator = "+", 110 | parameters = listOf("other" to Type.STRING), 111 | returns = Type.STRING, 112 | ) { (instance, other): T2 -> 113 | Object(Type.STRING, instance + other) 114 | } 115 | 116 | method("repeat", operator = "*", 117 | parameters = listOf("times" to Type.INTEGER), 118 | returns = Type.STRING, 119 | ) { (instance, times): T2 -> 120 | Object(Type.STRING, instance.repeat(times.intValue())) 121 | } 122 | 123 | method("reverse", 124 | parameters = listOf(), 125 | returns = Type.STRING, 126 | ) { (instance): T1 -> 127 | Object(Type.STRING, instance.reversed()) 128 | } 129 | 130 | method("lowercase", 131 | parameters = listOf(), 132 | returns = Type.STRING, 133 | ) { (instance): T1 -> 134 | Object(Type.STRING, instance.lowercase()) 135 | } 136 | 137 | method("uppercase", 138 | parameters = listOf(), 139 | returns = Type.STRING, 140 | ) { (instance): T1 -> 141 | Object(Type.STRING, instance.uppercase()) 142 | } 143 | 144 | method("split", 145 | parameters = listOf("other" to Type.STRING), 146 | returns = Type.LIST[Type.STRING] 147 | ) { (instance, other): T2 -> 148 | Object(Type.LIST[Type.STRING], instance.split(other).map { Object(Type.STRING, it) }) 149 | } 150 | 151 | method("to", 152 | parameters = listOf("type" to Type.TYPE[Type.INTEGER]), 153 | returns = Type.NULLABLE[Type.INTEGER], 154 | ) { (instance, _type): T2 -> 155 | val result = try { 156 | val lexer = RhovasLexer(Input("String.to(Integer)", instance)) 157 | lexer.lexToken()?.takeIf { it.type == RhovasTokenType.INTEGER && lexer.lexToken() == null }?.value as BigInteger? 158 | } catch (e: ParseException) { null } 159 | Object(Type.NULLABLE[Type.INTEGER], result?.let { Pair(Object(Type.INTEGER, it), null) }) 160 | } 161 | 162 | method("to", 163 | parameters = listOf("type" to Type.TYPE[Type.INTEGER], "base" to Type.INTEGER), 164 | returns = Type.NULLABLE[Type.INTEGER], 165 | ) { (instance, _type, base): T3 -> 166 | require(base >= BigInteger.TWO && base <= BigInteger.fromInt(36)) { error( 167 | "Invalid index.", 168 | "Expected a base in range [2, 36], but received ${base}.", 169 | ) } 170 | val result = try { 171 | BigInteger.parseString(instance, base.intValue()) 172 | } catch (e: ArithmeticException) { null } 173 | Object(Type.NULLABLE[Type.INTEGER], result?.let { Pair(Object(Type.INTEGER, it), null) }) 174 | } 175 | 176 | method("to", 177 | parameters = listOf("type" to Type.TYPE[Type.DECIMAL]), 178 | returns = Type.NULLABLE[Type.DECIMAL], 179 | ) { (instance, _type): T2 -> 180 | val input = if (instance.contains(".")) instance else "${instance}.0" 181 | val result = try { 182 | val lexer = RhovasLexer(Input("String.to(Decimal)", input)) 183 | lexer.lexToken()?.takeIf { it.type == RhovasTokenType.DECIMAL && lexer.lexToken() == null }?.value as BigDecimal? 184 | } catch (e: ParseException) { null } 185 | Object(Type.NULLABLE[Type.DECIMAL], result?.let { Pair(Object(Type.DECIMAL, it), null) }) 186 | } 187 | 188 | method("to", 189 | parameters = listOf("type" to Type.TYPE[Type.ATOM]), 190 | returns = Type.ATOM, 191 | ) { (instance, _type): T2 -> 192 | Object(Type.ATOM, RhovasAst.Atom(instance)) 193 | } 194 | } 195 | 196 | } 197 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/library/StructInitializer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.library 2 | 3 | import dev.rhovas.interpreter.environment.Component 4 | import dev.rhovas.interpreter.environment.Modifiers 5 | import dev.rhovas.interpreter.environment.type.Type 6 | 7 | object StructInitializer : Library.ComponentInitializer(Component.Class("Struct", Modifiers(Modifiers.Inheritance.VIRTUAL))) { 8 | 9 | override fun declare() { 10 | generics.add(generic("T", Type.STRUCT.DYNAMIC)) 11 | inherits.add(Type.EQUATABLE[Type.STRUCT.DYNAMIC]) 12 | } 13 | 14 | override fun define() {} 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/library/TupleInitializer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.library 2 | 3 | import dev.rhovas.interpreter.environment.Component 4 | import dev.rhovas.interpreter.environment.Object 5 | import dev.rhovas.interpreter.environment.type.Type 6 | 7 | object TupleInitializer : Library.ComponentInitializer(Component.Class("Tuple")) { 8 | 9 | override fun declare() { 10 | generics.add(generic("T", Type.TUPLE.DYNAMIC)) 11 | inherits.add(Type.EQUATABLE[Type.TUPLE.DYNAMIC]) 12 | } 13 | 14 | override fun define() { 15 | function("", 16 | generics = listOf(generic("T", Type.TUPLE.DYNAMIC)), 17 | parameters = listOf("initial" to generic("T", Type.TUPLE.DYNAMIC)), 18 | returns = generic("T", Type.TUPLE.DYNAMIC), 19 | ) { (initial): T1> -> 20 | Object(generics["T"]!!, initial.toMutableList()) 21 | } 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/library/TypeInitializer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.library 2 | 3 | import dev.rhovas.interpreter.environment.Component 4 | import dev.rhovas.interpreter.environment.type.Type 5 | 6 | object TypeInitializer : Library.ComponentInitializer(Component.Class("Type")) { 7 | 8 | override fun declare() { 9 | generics.add(generic("T")) 10 | inherits.add(Type.HASHABLE[Type.TYPE.DYNAMIC]) 11 | } 12 | 13 | override fun define() {} 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/library/VoidInitializer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.library 2 | 3 | import dev.rhovas.interpreter.environment.Component 4 | import dev.rhovas.interpreter.environment.Object 5 | import dev.rhovas.interpreter.environment.type.Type 6 | 7 | object VoidInitializer : Library.ComponentInitializer(Component.Class("Void")) { 8 | 9 | override fun declare() { 10 | inherits.add(Type.HASHABLE[Type.VOID]) 11 | } 12 | 13 | override fun define() { 14 | method("equals", operator = "==", 15 | parameters = listOf("other" to Type.VOID), 16 | returns = Type.BOOLEAN, 17 | ) { (_instance, _other): T2 -> 18 | Object(Type.BOOLEAN, true) 19 | } 20 | 21 | method("to", 22 | parameters = listOf("type" to Type.TYPE[Type.STRING]), 23 | returns = Type.STRING, 24 | ) { (_instance, _type): T2 -> 25 | Object(Type.STRING, "void") 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/parser/Input.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.parser 2 | 3 | data class Input( 4 | val source: String, 5 | val content: String, 6 | ) { 7 | 8 | data class Range( 9 | val index: Int, 10 | val line: Int, 11 | val column: Int, 12 | val length: Int, 13 | ) 14 | 15 | /** 16 | * Builds a diagnostic error message that includes relevant lines from the 17 | * [context]. The exact format of this message is subject to change. 18 | */ 19 | fun diagnostic(summary: String, details: String, range: Range, context: List): String { 20 | val builder = StringBuilder() 21 | .append("${source}:${range.line}:${range.column}-${range.column + range.length}") 22 | .append("\nError: ${summary}") 23 | val ranges = mutableMapOf(range.line to range).also { map -> 24 | context.forEach { map.getOrPut(it.line) { it } } 25 | }.toList().sortedBy { it.first }.map { it.second } 26 | val digits = ranges.last().line.toString().length 27 | ranges.forEach { 28 | val start = it.index - it.column 29 | val end = content.indexOfAny(charArrayOf('\n', '\r'), it.index).takeIf { it != -1 } ?: content.length 30 | builder.append("\n ${it.line.toString().padStart(digits)} | ${content.substring(start, end)}") 31 | if (it.line == range.line) { 32 | builder.append("\n ${" ".repeat(digits)} | ${" ".repeat(range.column)}${"^".repeat(range.length)}") 33 | } 34 | } 35 | if (details.isNotEmpty()) { 36 | builder.append("\n${details}") 37 | } 38 | return builder.toString() 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/parser/Lexer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.parser 2 | 3 | /** 4 | * Abstraction over the core structure for implementing a lexer, primarily: 5 | * 6 | * - [lexToken]: Abstract method implementing the [Lexer] 7 | * - [peek]/[match]: Helpers for checking/advancing the [CharStream] 8 | * - [require]/[error]: Helpers for checking conditions and generating errors 9 | * - [CharStream]: Manages input/tokenization state 10 | */ 11 | abstract class Lexer(val input: Input) { 12 | 13 | protected val chars = CharStream() 14 | private val context = ArrayDeque() 15 | 16 | /** 17 | * The current lexer mode (implementation-defined), commonly required for 18 | * contextual lexing between tokens as with string interpolation. 19 | */ 20 | var mode = "" 21 | 22 | /** 23 | * The current lexer state, consisting of the input location from 24 | * [CharStream.range] and the active [context]. This state is publicly 25 | * mutable to allow sharing state between lexers, as required for DSLs. 26 | */ 27 | var state 28 | get() = Pair(chars.range, context) 29 | set(value) { 30 | chars.range = value.first 31 | context.clear() 32 | context.addAll(value.second) 33 | } 34 | 35 | /** 36 | * Lexes and returns the next token, or `null` if the [CharStream] is empty. 37 | * Can be used with [generateSequence] to easily retrieve a token list. 38 | * 39 | * @throws ParseException 40 | */ 41 | abstract fun lexToken(): Token? 42 | 43 | /** 44 | * Returns `true` if the next sequence of characters match the given 45 | * arguments, which is either a [Char] or [String] regex. 46 | */ 47 | protected fun peek(vararg objects: Any): Boolean { 48 | return objects.withIndex().all { o -> 49 | chars[o.index]?.let { test(o.value, it) } == true 50 | } 51 | } 52 | 53 | /** 54 | * Returns `true` in the same way as [peek], but also advances past the 55 | * matched characters if [peek] returns `true`. 56 | */ 57 | protected fun match(vararg objects: Any): Boolean { 58 | return peek(*objects).also { 59 | if (it) { 60 | repeat(objects.size) { chars.advance() } 61 | } 62 | } 63 | } 64 | 65 | private fun test(obj: Any, char: Char): Boolean { 66 | return when(obj) { 67 | is Char -> obj == char 68 | is String -> char.toString().matches(obj.toRegex()) 69 | else -> throw AssertionError() 70 | } 71 | } 72 | 73 | /** 74 | * Throws a default [ParseException] unless the given [condition] is `true`. 75 | * This method is intended for internal assertions, hence the message 76 | * `"Broken lexer invariant."`. 77 | */ 78 | protected fun require(condition: Boolean) { 79 | require(condition) { error("Broken lexer invariant.", """ 80 | This is an internal compiler error, please report this! 81 | 82 | ${Exception().printStackTrace()} 83 | """.trimIndent()) } 84 | } 85 | 86 | /** 87 | * Throws a custom [ParseException] unless the given [condition] is `true`. 88 | */ 89 | protected fun require(condition: Boolean, error: () -> ParseException) { 90 | if (!condition) { 91 | throw error() 92 | } 93 | } 94 | 95 | /** 96 | * Returns a [ParseException] with the given arguments. The default [range] 97 | * is the current [CharStream.range]. 98 | */ 99 | protected fun error(summary: String, details: String, range: Input.Range = chars.range): ParseException { 100 | return ParseException(summary, details, range, context) 101 | } 102 | 103 | /** 104 | * Manages the current state of the lexing/tokenization process for the 105 | * [Lexer]. 106 | */ 107 | inner class CharStream { 108 | 109 | private var index = 0 110 | private var line = 1 111 | private var column = 0 112 | private var length = 0 113 | 114 | var range: Input.Range 115 | get() = Input.Range(index, line, column, length) 116 | set(value) { 117 | index = value.index 118 | line = value.line 119 | column = value.column 120 | length = value.length 121 | } 122 | 123 | /** 124 | * Returns the input character at the relative [offset] from the current 125 | * position, or `null` if the [CharStream] is empty. An offset of `0` 126 | * corresponds to the next, unconsumed character, while `-1` can be used 127 | * for the previous character. 128 | */ 129 | operator fun get(offset: Int): Char? { 130 | return input.content.getOrNull(index + length + offset) 131 | } 132 | 133 | /** 134 | * Advances the [CharStream] past the next character, including that 135 | * character in the in-progress token. 136 | */ 137 | fun advance() { 138 | require(this[0] != null) 139 | length++ 140 | } 141 | 142 | /** 143 | * Consumes the in-progress token, starting a new token at the current 144 | * position. 145 | */ 146 | fun consume() { 147 | index += length 148 | column += length 149 | length = 0 150 | } 151 | 152 | /** 153 | * Advances the [CharStream] to the next line, consuming the in-progress 154 | * token (if any). This method *must* be called by the implementing 155 | * [Lexer] to ensure accurate [line]/[column] values. 156 | */ 157 | fun newline() { 158 | require(this[-1] == '\n' || this[-1] == '\r') 159 | consume() 160 | line++ 161 | column = 0 162 | } 163 | 164 | /** 165 | * Returns the literal for the in-progress token. 166 | */ 167 | fun literal(): String { 168 | return input.content.substring(index, index + length) 169 | } 170 | 171 | /** 172 | * Returns a new [Token], consuming the in-progress token. 173 | */ 174 | fun emit(type: T, value: Any? = null): Token { 175 | return Token(type, literal(), value, range).also { consume() } 176 | } 177 | 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/parser/ParseException.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.parser 2 | 3 | data class ParseException( 4 | val summary: String, 5 | val details: String, 6 | val range: Input.Range, 7 | val context: List 8 | ): Exception("${range.line}:${range.column}-${range.column + range.length}: ${summary}") 9 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/parser/Parser.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.parser 2 | 3 | /** 4 | * Abstraction over the core structure for implementing a lexer, primarily: 5 | * 6 | * - [parse]: Abstract method implementing the [Parser] 7 | * - [peek]/[match]: Helpers for checking/advancing the [TokenStream] 8 | * - [require]/[error]: Helpers for checking conditions and generating errors 9 | * - [TokenStream]: Manages lexer/token state 10 | */ 11 | abstract class Parser(val lexer: Lexer) { 12 | 13 | protected val tokens = TokenStream() 14 | protected val context = lexer.state.second 15 | 16 | /** 17 | * Parses and returns the AST corresponding to the given [rule] 18 | * (implementation-defined). All parsers should support a `"source"` rule 19 | * that acts as the entry-point for the language. 20 | * 21 | * @throws ParseException 22 | */ 23 | abstract fun parse(rule: String): Any 24 | 25 | /** 26 | * Returns `true` if the next sequence of characters match the given 27 | * arguments, which is either a [Token.Type], [String] literal, or [List] of 28 | * potential patterns. 29 | */ 30 | protected fun peek(vararg objects: Any): Boolean { 31 | return objects.withIndex().all { o -> 32 | tokens[o.index]?.let { test(o.value, it) } == true 33 | } 34 | } 35 | 36 | /** 37 | * Returns `true` in the same way as [peek], but also advances past the 38 | * matched tokens if [peek] returns `true`. 39 | */ 40 | protected fun match(vararg objects: Any): Boolean { 41 | return peek(*objects).also { 42 | if (it) { 43 | repeat(objects.size) { tokens.advance() } 44 | } 45 | } 46 | } 47 | 48 | private fun test(obj: Any, token: Token): Boolean { 49 | return when(obj) { 50 | is Token.Type -> obj == token.type 51 | is String -> obj == token.literal 52 | is List<*> -> obj.any { test(it!!, token) } 53 | else -> throw AssertionError() 54 | } 55 | } 56 | 57 | /** 58 | * Throws a default [ParseException] unless the given [condition] is `true`. 59 | * This method is intended for internal assertions, hence the message 60 | * `"Broken parser invariant."`. 61 | */ 62 | protected fun require(condition: Boolean) { 63 | require(condition) { error("Broken parser invariant.", """ 64 | This is an internal compiler error, please report this! 65 | 66 | ${Exception().stackTraceToString()} 67 | """.trimIndent()) } 68 | } 69 | 70 | /** 71 | * Throws a custom [ParseException] unless the given [condition] is `true`. 72 | */ 73 | protected fun require(condition: Boolean, error: () -> ParseException) { 74 | if (!condition) { 75 | throw error() 76 | } 77 | } 78 | 79 | /** 80 | * Returns a [ParseException] with the given arguments. The default [range] 81 | * is the range of the next token or, if `null`, the previous token. 82 | */ 83 | protected fun error(summary: String, details: String, range: Input.Range = (tokens[0] ?: tokens[-1])!!.range): ParseException { 84 | return ParseException(summary, details, range, context + listOfNotNull(tokens[0]?.range, tokens[-1]?.range)) 85 | } 86 | 87 | /** 88 | * Manages the current state of the lexing/tokenization process for the 89 | * [Parser]. 90 | */ 91 | inner class TokenStream { 92 | 93 | private val tokens = mutableListOf?>() 94 | private var index = 0 95 | 96 | /** 97 | * Returns the [Token] at the relative [offset] from the current 98 | * position, or `null` if the [TokenStream] is empty. An offset of `0` 99 | * corresponds to the next, unconsumed token, while `-1` can be used 100 | * for the previous token. 101 | */ 102 | operator fun get(offset: Int): Token? { 103 | while (tokens.size <= index + offset) { 104 | tokens.add(lexer.lexToken()) 105 | } 106 | return tokens.getOrNull(index + offset) 107 | } 108 | 109 | /** 110 | * Advances the [TokenStream] past the next token. 111 | */ 112 | fun advance() { 113 | require(this[0] != null) 114 | index++ 115 | } 116 | 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/parser/Token.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.parser 2 | 3 | data class Token( 4 | val type: T, 5 | val literal: String, 6 | val value: Any?, 7 | val range: Input.Range, 8 | ) { 9 | 10 | interface Type 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/parser/dsl/DslAst.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.parser.dsl 2 | 3 | import dev.rhovas.interpreter.parser.Input 4 | 5 | sealed class DslAst { 6 | 7 | lateinit var context: List internal set 8 | 9 | /** 10 | * DSLs are represented using a combination of string literals and 11 | * interpolated arguments. There should always be one more literal than 12 | * arguments since each argument is surrounded by potentially-empty strings. 13 | */ 14 | data class Source( 15 | val literals: List, 16 | val arguments: List, 17 | ) : DslAst() 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/parser/dsl/DslLexer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.parser.dsl 2 | 3 | import dev.rhovas.interpreter.parser.Input 4 | import dev.rhovas.interpreter.parser.Lexer 5 | import dev.rhovas.interpreter.parser.Token 6 | 7 | class DslLexer(input: Input) : Lexer(input) { 8 | 9 | override fun lexToken(): Token? { 10 | return when { 11 | chars[0] == null -> null 12 | peek("[\n\r]") -> lexIndent() 13 | peek("[\${}]") -> lexOperator() 14 | else -> lexText() 15 | } 16 | } 17 | 18 | /** 19 | * Lexes the indentation following a newline (used for the indentation-based 20 | * approach by the parser to avoid grammar restrictions). 21 | * 22 | * - `indent = (?<= "\n" "\r"? | "\r" "\n"?) [ \t]*` 23 | */ 24 | private fun lexIndent(): Token { 25 | require(match("[\n\r]")) 26 | match(if (chars[-1] == '\n') '\r' else '\n') 27 | chars.newline() 28 | while (match("[ \t]")) {} 29 | return chars.emit(DslTokenType.INDENT) 30 | } 31 | 32 | /** 33 | * - `operator = [\${}]` 34 | */ 35 | private fun lexOperator(): Token { 36 | require(match("[\${}]")) 37 | return chars.emit(DslTokenType.OPERATOR) 38 | } 39 | 40 | /** 41 | * - `text := [^\n\r\${}]*` 42 | */ 43 | private fun lexText(): Token { 44 | require(chars[0] != null) 45 | while (match("[^\n\r\${}]")) {} 46 | return chars.emit(DslTokenType.TEXT) 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/parser/dsl/DslParser.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.parser.dsl 2 | 3 | import dev.rhovas.interpreter.parser.Input 4 | import dev.rhovas.interpreter.parser.Parser 5 | import dev.rhovas.interpreter.parser.rhovas.RhovasParser 6 | 7 | class DslParser(input: Input) : Parser(DslLexer(input)) { 8 | 9 | override fun parse(rule: String): DslAst { 10 | return when (rule) { 11 | "source" -> parseSource() 12 | else -> throw AssertionError() 13 | } 14 | } 15 | 16 | /** 17 | * Parses a DSL using an indentation-based approach to identify the 18 | * start/end without grammar restrictions. This is either inline (without 19 | * newlines/interpolation) or multiline (with newlines/interpolation). 20 | * 21 | * - `inline = "{" (text | "$")* "}"` 22 | * - `multiline = "{" (indent[n] line indent[0]*)* indent[() 30 | val arguments = mutableListOf() 31 | if (match(DslTokenType.INDENT)) { 32 | val indent = tokens[-1]!!.literal 33 | while (true) { 34 | if (match(DslTokenType.INDENT)) { 35 | val literal = tokens[-1]!!.literal 36 | if (literal.length < indent.length && !peek(DslTokenType.INDENT)) { 37 | break 38 | } 39 | builder.append("\n").append(literal.removePrefix(indent)) 40 | } else if (match("$", "{")) { 41 | context.addLast(tokens[-1]!!.range) 42 | val parser = RhovasParser(lexer.input) 43 | parser.lexer.state = lexer.state.let { 44 | Pair(it.first.copy(index = it.first.index - 2, column = it.first.column - 2, length = 0), it.second) 45 | } 46 | val argument = parser.parse("interpolation") 47 | lexer.state = parser.lexer.state.let { 48 | Pair(it.first.copy(index = it.first.index - 1, column = it.first.column - 1, length = 0), it.second) 49 | } 50 | require(match("}")) 51 | literals.add(builder.toString()) 52 | arguments.add(argument) 53 | builder.clear() 54 | context.removeLast() 55 | } else { 56 | require(tokens[0] != null) { error( 57 | "Expected input.", 58 | "A DSL expression is terminated by a closing brace `}` (if defined inline) or a closing brace `}` at a lower indentation than the start (if defined multiline).", 59 | ) } 60 | tokens.advance() 61 | builder.append(tokens[-1]!!.literal) 62 | } 63 | } 64 | } else { 65 | while (tokens[0] != null && !peek(listOf(DslTokenType.INDENT, "{", "}"))) { 66 | tokens.advance() 67 | builder.append(tokens[-1]!!.literal) 68 | } 69 | require(peek("}")) { error( 70 | "Unterminated DSL.", 71 | "An inline DSL must end with a closing brace and cannot contain newlines or opening braces (such as for interpolation).", 72 | ) } 73 | } 74 | literals.add(builder.toString()) 75 | require(match("}")) { error( 76 | "Expected closing brace.", 77 | "A DSL expression requires braces around the source, as in `#dsl { source }`.", 78 | ) } 79 | return DslAst.Source(literals, arguments).also { 80 | it.context = listOf(context.removeLast(), tokens[-1]!!.range) 81 | } 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/parser/dsl/DslTokenType.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.parser.dsl 2 | 3 | import dev.rhovas.interpreter.parser.Token 4 | 5 | enum class DslTokenType : Token.Type { 6 | INDENT, 7 | OPERATOR, 8 | TEXT, 9 | } 10 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/parser/rhovas/RhovasLexer.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.parser.rhovas 2 | 3 | import com.ionspin.kotlin.bignum.decimal.BigDecimal 4 | import com.ionspin.kotlin.bignum.integer.BigInteger 5 | import dev.rhovas.interpreter.parser.Input 6 | import dev.rhovas.interpreter.parser.Lexer 7 | import dev.rhovas.interpreter.parser.Token 8 | 9 | class RhovasLexer(input: Input) : Lexer(input) { 10 | 11 | /** 12 | * Main lexer method, which dispatches to the correct `lex` method and 13 | * also skips comments/newlines. 14 | * 15 | * - `comment = "//" [^\n\r]+` 16 | * - `newline = "\n" "\r"? | "\r" "\n"?` 17 | */ 18 | override fun lexToken(): Token? { 19 | if (mode == "string") { 20 | return lexStringMode() 21 | } 22 | while (match("/", "/") || match("[ \t\n\r]")) { 23 | when (chars[-1]) { 24 | '/' -> while (match("[^\n\r]")) {} 25 | '\n', '\r' -> { 26 | match(if (chars[-1] == '\n') '\r' else '\n') 27 | chars.newline() 28 | } 29 | } 30 | } 31 | chars.consume() 32 | return when { 33 | chars[0] == null -> null 34 | peek("[A-Za-z_]") -> lexIdentifier() 35 | peek(":", "[A-Za-z_]") -> lexAtom() 36 | peek("[0-9]") -> lexNumber() 37 | else -> lexOperator() 38 | } 39 | } 40 | 41 | /** 42 | * - `identifier = [A-Za-z_] [A-Za-z0-9_]* [!]?` 43 | */ 44 | private fun lexIdentifier(): Token { 45 | require(match("[A-Za-z_]")) 46 | while (match("[A-Za-z0-9_]")) {} 47 | match("!") 48 | return chars.emit(RhovasTokenType.IDENTIFIER) 49 | } 50 | 51 | 52 | /** 53 | * - `atom = ":" identifier` 54 | */ 55 | private fun lexAtom(): Token { 56 | require(match(":")) 57 | val identifier = lexIdentifier() 58 | return identifier.copy(type = RhovasTokenType.ATOM, value = RhovasAst.Atom(identifier.literal.substring(1))) 59 | } 60 | 61 | /** 62 | * - `number = integer | decimal` 63 | * - `integer = "0" ("b" [0-1] | "o" [0-7] | "x" [0-9A-F]) | [0-9]+` 64 | * - `decimal = [0-9]+ "." [0-9]+ ("e" [0-9]+)` 65 | */ 66 | private fun lexNumber(): Token { 67 | require(match("[0-9]")) 68 | if (chars[-1] == '0' && peek("[box]")) { 69 | fun lexBase(base: Int, digits: String): Token { 70 | while (match(digits)) {} 71 | return chars.emit(RhovasTokenType.INTEGER, BigInteger.parseString(chars.literal().substring(2), base)) 72 | } 73 | when { 74 | match('b', "[0-1]") -> return lexBase(2, "[0-1]") 75 | match('o', "[0-7]") -> return lexBase(8, "[0-7]") 76 | match('x', "[0-9A-F]") -> return lexBase(16, "[0-9A-F]") 77 | } 78 | } 79 | while (match("[0-9]")) {} 80 | return if (match('.', "[0-9]")) { 81 | while (match("[0-9]")) {} 82 | if (match("e", "[0-9]") || match("e", "[+\\-]", "[0-9]")) { 83 | while (match("[0-9]")) {} 84 | } 85 | chars.emit(RhovasTokenType.DECIMAL, BigDecimal.parseString(chars.literal())) 86 | } else { 87 | chars.emit(RhovasTokenType.INTEGER, BigInteger.parseString(chars.literal())) 88 | } 89 | } 90 | 91 | /** 92 | * - `operator = [^A-Za-z_0-9]` 93 | */ 94 | private fun lexOperator(): Token { 95 | require(chars[0] != null) 96 | chars.advance() 97 | return chars.emit(RhovasTokenType.OPERATOR) 98 | } 99 | 100 | /** 101 | * Lexer mode for within string literals to support interpolation. Mode 102 | * switches and other validation occur in the parser 103 | * 104 | * - `operator = [\"\n\r] | "${"` 105 | * - `string = ([^\"\n\r\\] & (?! "${") | escape)*` 106 | * - `escape = "\" ([nrt\"\$\\] | "u" [0-9A-F]{4})` 107 | */ 108 | private fun lexStringMode(): Token? { 109 | return when { 110 | chars[0] == null -> null 111 | match("[\"\n\r]") || match('$', '{') -> chars.emit(RhovasTokenType.OPERATOR) 112 | else -> { 113 | val builder = StringBuilder() 114 | while (!peek('$', '{') && match("[^\"\n\r]")) { 115 | if (chars[-1] == '\\') { 116 | val start = chars.range.let { 117 | it.copy( 118 | index = it.index + it.length - 1, 119 | column = it.column + it.length - 1, 120 | length = 1 121 | ) 122 | } 123 | require(match("[nrtu\"\$\\\\]")) { 124 | error( 125 | "Invalid character escape.", 126 | "A character escape is in the form \\char, where char is one of [nrtu\'\"\\]. If a literal backslash is desired, use an escape as in \"abc\\\\123\".", 127 | start.copy(length = if (chars[0] != null) 2 else 1) 128 | ) 129 | } 130 | builder.append( 131 | when (chars[-1]!!) { 132 | 'n' -> '\n' 133 | 'r' -> '\r' 134 | 't' -> '\t' 135 | 'u' -> Char((1..4).fold(0) { codepoint, index -> 136 | require(match("[0-9A-F]")) { 137 | error( 138 | "Invalid unicode escape.", 139 | "A unicode escape is in the form \\uXXXX, where X is a hexadecimal digit (one of [0-9A-F]). If a literal backslash is desired, use an escape as in \"abc\\\\123\".", 140 | start.copy(length = index + (if (chars[0] != null) 2 else 1)) 141 | ) 142 | } 143 | 16 * codepoint + chars[-1]!!.digitToInt(16) 144 | }) 145 | else -> chars[-1]!! 146 | } 147 | ) 148 | } else { 149 | builder.append(chars[-1]!!) 150 | } 151 | } 152 | chars.emit(RhovasTokenType.STRING, builder.toString()) 153 | } 154 | } 155 | } 156 | 157 | } 158 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/dev/rhovas/interpreter/parser/rhovas/RhovasTokenType.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.parser.rhovas 2 | 3 | import dev.rhovas.interpreter.parser.Token 4 | 5 | enum class RhovasTokenType: Token.Type { 6 | IDENTIFIER, 7 | INTEGER, 8 | DECIMAL, 9 | STRING, 10 | ATOM, 11 | OPERATOR, 12 | } 13 | -------------------------------------------------------------------------------- /src/commonTest/kotlin/dev/rhovas/interpreter/Platform.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter 2 | 3 | enum class Target { JS, JVM } 4 | 5 | expect object Platform { 6 | 7 | val TARGET: Target 8 | 9 | fun readFile(path: String): String 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/commonTest/kotlin/dev/rhovas/interpreter/RhovasSpec.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter 2 | 3 | import io.kotest.core.spec.style.ShouldSpec 4 | import io.kotest.core.spec.style.scopes.ShouldSpecContainerScope 5 | import io.kotest.core.spec.style.scopes.ShouldSpecRootScope 6 | import io.kotest.core.test.TestScope 7 | 8 | /** 9 | * Custom Kotest Spec implementation that allows suites to work with Kotlin/JS 10 | * by transforming them into prefixed top-level tests. Miscellaneous notes: 11 | * 12 | * - We extend [ShouldSpec] as Kotest searches specific classes to run tests. 13 | * - We use [spec] and [suite] to avoid overlapping with other test functions. 14 | * - We disable `suspend` within suites as Kotlin/JS cannot support nested 15 | * promises (root cause of the original limitation). This makes registering 16 | * all tests synchronous, which is not an issue for our current usage. 17 | * - We delay test registration as [ContainerScope.registerJvm] must be a 18 | * `suspend` function to register nested tests/contexts. 19 | */ 20 | // open, not abstract: https://youtrack.jetbrains.com/issue/KT-63399 21 | open class RhovasSpec : ShouldSpec() { 22 | 23 | fun spec(name: String, test: suspend TestScope.() -> Unit) = should(name, test) 24 | 25 | fun suite(name: String, test: ContainerScope.() -> Unit) = when(Platform.TARGET) { 26 | Target.JS -> ContainerScope().also(test).registerJs(this, name) 27 | Target.JVM -> context(name) { ContainerScope().also(test).registerJvm(this) } 28 | } 29 | 30 | fun suite(name: String, tests: List>, test: suspend (T) -> Unit) = suite(name) { 31 | tests.forEach { (name, test) -> spec(name) { test(test) } } 32 | } 33 | 34 | class ContainerScope { 35 | 36 | private val specs: MutableMap Unit> = mutableMapOf() 37 | private val suites: MutableMap = mutableMapOf() 38 | 39 | fun spec(name: String, test: suspend TestScope.() -> Unit) = specs.put(name, test) 40 | fun suite(name: String, test: ContainerScope.() -> Unit) = suites.put(name, ContainerScope().also(test)) 41 | 42 | fun suite(name: String, tests: List>, test: suspend (T) -> Unit) = suite(name) { 43 | tests.forEach { (name, test) -> spec(name) { test(test) } } 44 | } 45 | 46 | fun registerJs(scope: ShouldSpecRootScope, prefix: String) { 47 | fun format(prefix: String, name: String): String { 48 | val skip = "!".takeIf { name.startsWith("!") && !prefix.startsWith("!") } ?: "" 49 | val focus = "f:".takeIf { name.startsWith("f:") && !prefix.startsWith("f:") } ?: "" 50 | return "${skip}${focus}${prefix} | ${name.removePrefix("!").removePrefix("f:")}" 51 | } 52 | specs.forEach { scope.should(format(prefix, it.key), it.value) } 53 | suites.forEach { it.value.registerJs(scope, format(prefix, it.key)) } 54 | } 55 | 56 | suspend fun registerJvm(scope: ShouldSpecContainerScope) { 57 | specs.forEach { scope.should(it.key, it.value) } 58 | suites.forEach { scope.context(it.key) { it.value.registerJvm(this) } } 59 | } 60 | 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/commonTest/kotlin/dev/rhovas/interpreter/parser/dsl/DslLexerTests.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.parser.dsl 2 | 3 | import dev.rhovas.interpreter.RhovasSpec 4 | import dev.rhovas.interpreter.parser.Input 5 | import dev.rhovas.interpreter.parser.ParseException 6 | import dev.rhovas.interpreter.parser.Token 7 | import kotlin.test.assertEquals 8 | import kotlin.test.assertNotEquals 9 | import kotlin.test.fail 10 | 11 | class DslLexerTests: RhovasSpec() { 12 | 13 | data class Test(val source: String, val argument: T) 14 | 15 | init { 16 | suite("Indent", listOf( 17 | "Newline" to Test("\n ", " "), 18 | "Carriage Return" to Test("\r ", " "), 19 | "Newline & Carriage Return" to Test("\n\r ", " "), 20 | "Tab" to Test("\n\t", "\t"), 21 | "Spaces & Tabs" to Test("\n \t", " \t"), 22 | "Newline Only" to Test("\n", ""), 23 | "Missing Newline" to Test(" ", null), 24 | )) { 25 | val index = Regex("^[\n\r]*").find(it.source)!!.value.length 26 | val token = Token(DslTokenType.INDENT, it.argument ?: "", null, Input.Range(index, if (index > 0) 2 else 1, 0, it.argument?.length ?: 0)) 27 | test(it.source, listOf(token), it.argument != null) 28 | } 29 | 30 | suite("Operator", listOf( 31 | "Dollar Sign" to Test("\$", true), 32 | "Opening Brace" to Test("{", true), 33 | "Closing Brace" to Test("}", true), 34 | "Backslash" to Test("\\", false), 35 | "Whitespace" to Test(" ", false), 36 | )) { test(it.source, listOf(Token(DslTokenType.OPERATOR, it.source, null, Input.Range(0, 1, 0, it.source.length))), it.argument) } 37 | 38 | suite("Text", listOf( 39 | "Letters" to Test("abc", true), 40 | "Numbers" to Test("123", true), 41 | "Symbols" to Test("!@#", true), 42 | "Unicode" to Test("ρ⚡♖", true), 43 | "Whitespace" to Test(" \t", true), 44 | "Leading Whitespace" to Test(" text", true), 45 | "Trailing Whitespace" to Test("text ", true), 46 | "Escape" to Test("\\\\", true), 47 | )) { test(it.source, listOf(Token(DslTokenType.TEXT, it.source, null, Input.Range(0, 1, 0, it.source.length))), it.argument) } 48 | 49 | suite("Interaction", listOf( 50 | //indent 51 | "Inner Indent" to Test("first\n second", listOf( 52 | Token(DslTokenType.TEXT, "first", null, Input.Range(0, 1, 0, 5)), 53 | Token(DslTokenType.INDENT, " ", null, Input.Range(6, 2, 0, 4)), 54 | Token(DslTokenType.TEXT, "second", null, Input.Range(10, 2, 4, 6)), 55 | )), 56 | "Leading Indent" to Test("\n token", listOf( 57 | Token(DslTokenType.INDENT, " ", null, Input.Range(1, 2, 0, 4)), 58 | Token(DslTokenType.TEXT, "token", null, Input.Range(5, 2, 4, 5)), 59 | )), 60 | "Trailing Indent" to Test("token\n ", listOf( 61 | Token(DslTokenType.TEXT, "token", null, Input.Range(0, 1, 0, 5)), 62 | Token(DslTokenType.INDENT, " ", null, Input.Range(6, 2, 0, 4)), 63 | )), 64 | "Empty Line" to Test("first\n\nsecond", listOf( 65 | Token(DslTokenType.TEXT, "first", null, Input.Range(0, 1, 0, 5)), 66 | Token(DslTokenType.INDENT, "", null, Input.Range(6, 2, 0, 0)), 67 | Token(DslTokenType.INDENT, "", null, Input.Range(7, 3, 0, 0)), 68 | Token(DslTokenType.TEXT, "second", null, Input.Range(7, 3, 0, 6)), 69 | )), 70 | //operator 71 | "Interpolation" to Test("\${value}", listOf( 72 | Token(DslTokenType.OPERATOR, "\$", null, Input.Range(0, 1, 0, 1)), 73 | Token(DslTokenType.OPERATOR, "{", null, Input.Range(1, 1, 1, 1)), 74 | Token(DslTokenType.TEXT, "value", null, Input.Range(2, 1, 2, 5)), 75 | Token(DslTokenType.OPERATOR, "}", null, Input.Range(7, 1, 7, 1)), 76 | )), 77 | "Inner Operator" to Test("first\$second", listOf( 78 | Token(DslTokenType.TEXT, "first", null, Input.Range(0, 1, 0, 5)), 79 | Token(DslTokenType.OPERATOR, "\$", null, Input.Range(5, 1, 5, 1)), 80 | Token(DslTokenType.TEXT, "second", null, Input.Range(6, 1, 6, 6)), 81 | )), 82 | )) { test(it.source, it.argument, true) } 83 | 84 | suite("Program", listOf( 85 | "Regex" to Test(""" 86 | { /\d+(\.\d+)?/ } 87 | """.trimIndent(), listOf( 88 | Token(DslTokenType.OPERATOR, "{", null, Input.Range(0, 1, 0, 1)), 89 | Token(DslTokenType.TEXT, " /\\d+(\\.\\d+)?/ ", null, Input.Range(1, 1, 1, 15)), 90 | Token(DslTokenType.OPERATOR, "}", null, Input.Range(16, 1, 16, 1)), 91 | )), 92 | "SQL" to Test(""" 93 | { 94 | SELECT * FROM users 95 | WHERE name = ${"\$"}{user.name} 96 | } 97 | """.trimIndent(), listOf( 98 | Token(DslTokenType.OPERATOR, "{", null, Input.Range(0, 1, 0, 1)), 99 | Token(DslTokenType.INDENT, " ", null, Input.Range(2, 2, 0, 4)), 100 | Token(DslTokenType.TEXT, "SELECT * FROM users", null, Input.Range(6, 2, 4, 19)), 101 | Token(DslTokenType.INDENT, " ", null, Input.Range(26, 3, 0, 4)), 102 | Token(DslTokenType.TEXT, "WHERE name = ", null, Input.Range(30, 3, 4, 13)), 103 | Token(DslTokenType.OPERATOR, "\$", null, Input.Range(43, 3, 17, 1)), 104 | Token(DslTokenType.OPERATOR, "{", null, Input.Range(44, 3, 18, 1)), 105 | Token(DslTokenType.TEXT, "user.name", null, Input.Range(45, 3, 19, 9)), 106 | Token(DslTokenType.OPERATOR, "}", null, Input.Range(54, 3, 28, 1)), 107 | Token(DslTokenType.INDENT, "", null, Input.Range(56, 4, 0, 0)), 108 | Token(DslTokenType.OPERATOR, "}", null, Input.Range(56, 4, 0, 1)), 109 | )), 110 | )) { test(it.source, it.argument, true) } 111 | } 112 | 113 | private fun test(source: String, expected: List>, success: Boolean) { 114 | val input = Input("Test", source) 115 | val lexer = DslLexer(input) 116 | try { 117 | val tokens = generateSequence { lexer.lexToken() }.toList() 118 | if (success) { 119 | assertEquals(expected, tokens) 120 | } else { 121 | assertNotEquals(expected, tokens) 122 | } 123 | } catch (e: ParseException) { 124 | if (success || e.summary == "Broken lexer invariant.") { 125 | fail(input.diagnostic(e.summary, e.details, e.range, e.context), e) 126 | } 127 | } 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /src/commonTest/kotlin/dev/rhovas/interpreter/parser/dsl/DslParserTests.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.parser.dsl 2 | 3 | import dev.rhovas.interpreter.RhovasSpec 4 | import dev.rhovas.interpreter.parser.Input 5 | import dev.rhovas.interpreter.parser.ParseException 6 | import dev.rhovas.interpreter.parser.rhovas.RhovasAst 7 | import kotlin.test.assertEquals 8 | import kotlin.test.assertTrue 9 | import kotlin.test.fail 10 | 11 | class DslParserTests: RhovasSpec() { 12 | 13 | data class Test(val source: String, val expected: (() -> T)?) 14 | 15 | init { 16 | suite("Inline", listOf( 17 | "Empty" to Test(""" 18 | {} 19 | """.trimIndent()) { 20 | DslAst.Source(listOf(""), listOf()) 21 | }, 22 | "Text" to Test(""" 23 | {text} 24 | """.trimIndent()) { 25 | DslAst.Source(listOf("text"), listOf()) 26 | }, 27 | "Surrounding Whitespace" to Test(""" 28 | { text } 29 | """.trimIndent()) { 30 | DslAst.Source(listOf(" text "), listOf()) 31 | }, 32 | "Dollar Sign" to Test(""" 33 | {first${"$"}second} 34 | """.trimIndent()) { 35 | DslAst.Source(listOf("first\$second"), listOf()) 36 | }, 37 | "Newline" to Test(""" 38 | {first 39 | second} 40 | """.trimIndent(), null), 41 | "Braces" to Test(""" 42 | {text{}second} 43 | """.trimIndent(), null), 44 | )) { test("source", it.source, it.expected?.invoke()) } 45 | 46 | suite("Multiline", listOf( 47 | "Text" to Test(""" 48 | { 49 | text 50 | } 51 | """.trimIndent()) { 52 | DslAst.Source(listOf("text"), listOf()) 53 | }, 54 | "Multiline Text" to Test(""" 55 | { 56 | first 57 | second 58 | third 59 | } 60 | """.trimIndent()) { 61 | DslAst.Source(listOf("first\nsecond\nthird"), listOf()) 62 | }, 63 | "Multiline Indented" to Test(""" 64 | { 65 | first 66 | second 67 | third 68 | } 69 | """.trimIndent()) { 70 | DslAst.Source(listOf("first\n second\nthird"), listOf()) 71 | }, 72 | "Multiline Empty" to Test(""" 73 | { 74 | first 75 | 76 | second 77 | } 78 | """.trimIndent()) { 79 | DslAst.Source(listOf("first\n\nsecond"), listOf()) 80 | }, 81 | "Dollar Sign" to Test(""" 82 | { 83 | first${"\$"}second 84 | } 85 | """.trimIndent()) { 86 | DslAst.Source(listOf("first\$second"), listOf()) 87 | }, 88 | "Braces" to Test(""" 89 | { 90 | {} 91 | } 92 | """.trimIndent()) { 93 | DslAst.Source(listOf("{}"), listOf()) 94 | }, 95 | "Interpolation" to Test(""" 96 | { 97 | ${"\$"}{value} 98 | } 99 | """.trimIndent()) { 100 | DslAst.Source(listOf("", ""), listOf(RhovasAst.Expression.Access.Variable(null, "value"))) 101 | }, 102 | "Empty" to Test(""" 103 | { 104 | } 105 | """.trimIndent(), null), 106 | "Double Leading Indentation" to Test(""" 107 | { 108 | first 109 | second 110 | } 111 | """.trimIndent(), null), 112 | )) { test("source", it.source, it.expected?.invoke()) } 113 | } 114 | 115 | private fun test(rule: String, source: String, expected: DslAst?) { 116 | val input = Input("Test", source) 117 | try { 118 | val ast = DslParser(input).parse(rule) 119 | assertEquals(expected, ast) 120 | assertTrue(ast.context.isNotEmpty() || source.isBlank()) 121 | } catch (e: ParseException) { 122 | if (expected != null || e.summary == "Broken parser invariant.") { 123 | fail(input.diagnostic(e.summary, e.details, e.range, e.context), e) 124 | } 125 | } 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /src/commonTest/kotlin/dev/rhovas/interpreter/programs/ProgramTests.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.programs 2 | 3 | import dev.rhovas.interpreter.Interpreter 4 | import dev.rhovas.interpreter.parser.Input 5 | import kotlin.test.assertEquals 6 | import kotlin.test.fail 7 | 8 | object ProgramTests { 9 | 10 | fun test(input: Input, stdin: String, stdout: String) { 11 | val lines = stdin.lineSequence().iterator() 12 | val builder = StringBuilder() 13 | val interpreter = Interpreter( 14 | stdin = { lines.next() }, 15 | stdout = { builder.append(it).append('\n') }, 16 | ) 17 | interpreter.eval(input)?.let { fail("Unexpected evaluation response: ${it}") } 18 | assertEquals(stdout, builder.toString()) 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/commonTest/kotlin/dev/rhovas/interpreter/programs/rosettacode/RosettaCodeTests.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter.programs.rosettacode 2 | 3 | import dev.rhovas.interpreter.Platform 4 | import dev.rhovas.interpreter.programs.ProgramTests 5 | import dev.rhovas.interpreter.RhovasSpec 6 | import dev.rhovas.interpreter.parser.Input 7 | 8 | class RosettaCodeTests: RhovasSpec() { 9 | 10 | data class Test( 11 | val name: String, 12 | val stdin: String = "", 13 | val stdout: String = "", 14 | ) 15 | 16 | init { 17 | listOf( 18 | Test("Classes"), 19 | Test("Factorial"), 20 | Test("Fibonacci_sequence"), 21 | Test("FizzBuzz", stdout = "1\n2\nFizz\n4\nBuzz\nFizz\n7\n8\nFizz\nBuzz\n11\nFizz\n13\n14\nFizzBuzz\n"), 22 | Test("Hello_world/Text", stdout = "Hello world!\n"), 23 | Test("Palindrome_detection"), 24 | ).forEach { spec(it.name) { 25 | println("https://www.rosettacode.org/wiki/${it.name}#Rhovas") 26 | val input = Input(it.name, Platform.readFile("src/commonTest/resources/dev/rhovas/interpreter/programs/rosettacode/${it.name}.rho")) 27 | ProgramTests.test(input, it.stdin, it.stdout) 28 | } } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/commonTest/resources/dev/rhovas/interpreter/programs/rosettacode/Classes.rho: -------------------------------------------------------------------------------- 1 | struct Vector { 2 | var x: Decimal; 3 | var y: Decimal; 4 | 5 | func magnitude(this): Decimal { 6 | return Math.sqrt(this.x * this.x + this.y * this.y); 7 | } 8 | } 9 | 10 | class UnitVector { 11 | val x: Decimal; 12 | val y: Decimal; 13 | 14 | init(direction: Vector) { 15 | require direction.x != 0.0 || direction.y != 0.0; 16 | val magnitude = direction.magnitude(); 17 | this { x: direction.x / magnitude, y: direction.y / magnitude }; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/commonTest/resources/dev/rhovas/interpreter/programs/rosettacode/Factorial.rho: -------------------------------------------------------------------------------- 1 | func iterFactorial(num: Integer): Integer { 2 | require num >= 0; 3 | var result = 1; 4 | for (val i in range(2, num, :incl)) { 5 | result = result * i; 6 | } 7 | return result; 8 | } 9 | 10 | assert iterFactorial(0) == 1; 11 | assert iterFactorial(1) == 1; 12 | assert iterFactorial(5) == 120; 13 | assert iterFactorial(10) == 3628800; 14 | assert iterFactorial(20) == 2432902008176640000; 15 | 16 | func recFactorial(num: Integer): Integer { 17 | require num >= 0; 18 | match { 19 | num == 0: return 1; 20 | else: return num * recFactorial(num - 1); 21 | } 22 | } 23 | 24 | assert recFactorial(0) == 1; 25 | assert recFactorial(1) == 1; 26 | assert recFactorial(5) == 120; 27 | assert recFactorial(10) == 3628800; 28 | assert recFactorial(20) == 2432902008176640000; 29 | -------------------------------------------------------------------------------- /src/commonTest/resources/dev/rhovas/interpreter/programs/rosettacode/Fibonacci_sequence.rho: -------------------------------------------------------------------------------- 1 | func iterFibonacci(num: Integer): Integer { 2 | require num >= 0; 3 | var previous = 1; 4 | var current = 0; 5 | for (val _ in range(1, num, :incl)) { 6 | val next = current + previous; 7 | previous = current; 8 | current = next; 9 | } 10 | return current; 11 | } 12 | 13 | assert iterFibonacci(0) == 0; 14 | assert iterFibonacci(1) == 1; 15 | assert iterFibonacci(5) == 5; 16 | assert iterFibonacci(10) == 55; 17 | 18 | func recFibonacci(num: Integer): Integer { 19 | require num >= 0; 20 | match { 21 | num == 0: return 0; 22 | num == 1: return 1; 23 | else: return recFibonacci(num - 1) + recFibonacci(num - 2); 24 | } 25 | } 26 | 27 | assert recFibonacci(0) == 0; 28 | assert recFibonacci(1) == 1; 29 | assert recFibonacci(5) == 5; 30 | assert recFibonacci(10) == 55; 31 | 32 | func negFibonacci(num: Integer): Integer { 33 | match { 34 | num >= 0: return iterFibonacci(num); 35 | num.mod(2) != 0: return iterFibonacci(-num); 36 | else: return -iterFibonacci(-num); 37 | } 38 | } 39 | 40 | assert negFibonacci(0) == 0; 41 | assert negFibonacci(1) == 1; 42 | assert negFibonacci(-5) == 5; 43 | assert negFibonacci(-10) == -55; 44 | -------------------------------------------------------------------------------- /src/commonTest/resources/dev/rhovas/interpreter/programs/rosettacode/FizzBuzz.rho: -------------------------------------------------------------------------------- 1 | range(1, 15, :incl).for { 2 | match ([val.mod(3), val.mod(5)]) { 3 | [0, 0]: print("FizzBuzz"); 4 | [0, _]: print("Fizz"); 5 | [_, 0]: print("Buzz"); 6 | else: print(val); 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /src/commonTest/resources/dev/rhovas/interpreter/programs/rosettacode/Hello_world/Text.rho: -------------------------------------------------------------------------------- 1 | print("Hello world!"); 2 | -------------------------------------------------------------------------------- /src/commonTest/resources/dev/rhovas/interpreter/programs/rosettacode/Palindrome_detection.rho: -------------------------------------------------------------------------------- 1 | func isPalindromeReverse(string: String): Boolean { 2 | return string == string.reverse(); 3 | } 4 | 5 | func isPalindromeChars(chars: List): Boolean { 6 | match (chars) { 7 | []: return true; 8 | [elem]: return true; 9 | [first, middle*, last]: return first == last && isPalindromeChars(middle); 10 | } 11 | } 12 | 13 | func isPalindrome(string: String): Boolean { 14 | return isPalindromeReverse(string) && isPalindromeChars(string.chars); 15 | } 16 | 17 | assert isPalindrome(""); 18 | assert isPalindrome("f"); 19 | assert isPalindrome("noon"); 20 | assert isPalindrome("kayak"); 21 | assert isPalindrome("step on no pets"); 22 | assert !isPalindrome("palindrome"); 23 | assert !isPalindrome("A man, a plan, a canal - Panama!"); //inexact 24 | 25 | assert isPalindrome("§★♖★§"); //single utf16 code points 26 | assert isPalindromeReverse("🗲"); //string reverse handles surrogates 27 | assert !isPalindromeChars("🗲".chars); //.chars splits surrogates into two chars 28 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/dev.rhovas.interpreter/Main.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter 2 | 3 | import dev.rhovas.interpreter.environment.Function 4 | import dev.rhovas.interpreter.environment.Object 5 | import dev.rhovas.interpreter.environment.type.Type 6 | import dev.rhovas.interpreter.environment.Variable 7 | import dev.rhovas.interpreter.library.Library 8 | 9 | fun main() { 10 | val js = Function.Definition(Function.Declaration("js", 11 | parameters = listOf(Variable.Declaration("literals", Type.LIST[Type.STRING]), Variable.Declaration("arguments", Type.LIST[Type.DYNAMIC])), 12 | returns = Type.VOID, 13 | )) { 14 | val literals = (it[0].value as List).map { it.value as String } 15 | val arguments = (it[1].value as List) 16 | val source = literals.withIndex().joinToString("") { 17 | it.value + (arguments.getOrNull(it.index)?.let { JSON.stringify(it) } ?: "") 18 | } 19 | try { 20 | kotlin.js.eval("eval?.(" + JSON.stringify(source) + ")") 21 | } catch (error: Exception) { 22 | throw EVALUATOR.error( 23 | null, 24 | "DSL Evaluation Error", 25 | "Failed to eval JavaScript source: ${error.message ?: error}", 26 | ) 27 | } 28 | Object(Type.VOID, null) 29 | } 30 | Library.SCOPE.functions.define(js) 31 | } 32 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/dev.rhovas.interpreter/eval.kt: -------------------------------------------------------------------------------- 1 | @file:OptIn(ExperimentalJsExport::class) 2 | @file:JsExport 3 | 4 | package dev.rhovas.interpreter 5 | 6 | import dev.rhovas.interpreter.parser.Input 7 | 8 | fun eval(source: String, stdin: () -> String = ::readln, stdout: (String) -> Unit = ::println) { 9 | val input = Input("eval", source) 10 | val print = Interpreter(stdin, stdout).eval(input) 11 | print?.let(stdout) 12 | } 13 | -------------------------------------------------------------------------------- /src/jsTest/kotlin/dev/rhovas/interpreter/Platform.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter 2 | 3 | actual object Platform { 4 | 5 | actual val TARGET: Target = Target.JS 6 | 7 | actual fun readFile(path: String): String { 8 | val fs = js("require('fs')") 9 | return fs.readFileSync(path, "utf8") as String 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/jvmMain/kotlin/dev/rhovas/interpreter/Main.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter 2 | 3 | import dev.rhovas.interpreter.parser.Input 4 | import java.io.File 5 | import kotlin.system.exitProcess 6 | 7 | /** 8 | * Rhovas JVM main. If given an argument, the argument is the path of a file to 9 | * be evaluated. Without arguments, enters a REPL environment. 10 | */ 11 | fun main(args: Array) { 12 | if (args.isNotEmpty()) { 13 | val input = Input(args[0], File(args[0]).readText()) 14 | val print = INTERPRETER.eval(input) 15 | print?.let(INTERPRETER.stdout) 16 | } else { 17 | while (true) { 18 | val input = read() 19 | val print = INTERPRETER.eval(input) 20 | print?.let(INTERPRETER.stdout) 21 | } 22 | } 23 | } 24 | 25 | /** 26 | * Reads input from stdin. This hackily supports multiline input by tracking 27 | * indentation based on leading/trailing parenthesis/braces/brackets. 28 | */ 29 | fun read(): Input { 30 | val builder = StringBuilder() 31 | var indent = 0 32 | do { 33 | val line = readLine() ?: exitProcess(0) 34 | if (line.firstOrNull { it != ' ' } in listOf(')', '}', ']')) { 35 | indent-- 36 | } 37 | if (line.lastOrNull { it != ' ' } in listOf('(', '{', '[')) { 38 | indent++ 39 | } 40 | builder.append(line).append('\n') 41 | } while (indent != 0) 42 | return Input("REPL", builder.toString()) 43 | } 44 | -------------------------------------------------------------------------------- /src/jvmTest/kotlin/dev/rhovas/interpreter/Platform.kt: -------------------------------------------------------------------------------- 1 | package dev.rhovas.interpreter 2 | 3 | import java.io.File 4 | 5 | actual object Platform { 6 | 7 | actual val TARGET: Target = Target.JVM 8 | 9 | actual fun readFile(path: String): String { 10 | return File(path).readText() 11 | } 12 | 13 | } 14 | --------------------------------------------------------------------------------