diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index 214f9ab56..000000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,183 +0,0 @@ -# This is a basic workflow to help you get started with Actions - -name: CI - -# Controls when the action will run. Triggers the workflow on push or pull request -# events but only for the main branch -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - - avm-style: - # The type of runner that the job will run on - runs-on: ubuntu-latest - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 - - # Rust is automatically set up and included by default according to: https://github.com/actions/starter-workflows/blob/master/ci/rust.yml - # Run cargo fmt linting on the source code - - name: Run style linter - run: make avm-style - - avm-unit: - # The type of runner that the job will run on - runs-on: ubuntu-latest - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 - - # Rust is automatically set up and included by default according to: https://github.com/actions/starter-workflows/blob/master/ci/rust.yml - # Run unit tests - - name: Run unit tests - run: make avm-unit - - compiler-browser-check: - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [16.x, 18.x] - - steps: - - uses: actions/checkout@v2 - - # Set up Node.js - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - # Run the BDD tests - - name: Run Compiler Browser Validation - run: make compiler-browser-check - - compiler-style: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Run style linter - run: make compiler-style - - bdd: - # The type of runner that the job will run on - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [16.x, 18.x] - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 - - # Rust is automatically set up and included by default according to: https://github.com/actions/starter-workflows/blob/master/ci/rust.yml - - # Set up Node.js - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - # Run the BDD tests - - name: Run BDD tests - env: - ALAN_TELEMETRY_OFF: true - run: make bdd - - avm-unit-macos: - # The type of runner that the job will run on - runs-on: macos-latest - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 - - # Rust is automatically set up and included by default according to: https://github.com/actions/starter-workflows/blob/master/ci/rust.yml - # Run unit tests - - name: Run unit tests - run: make avm-unit - - bdd-macos: - # The type of runner that the job will run on - runs-on: macos-latest - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 - - # Rust is automatically set up and included by default according to: https://github.com/actions/starter-workflows/blob/master/ci/rust.yml - - # Set up Node.js - - name: Use Node.js 16.x - uses: actions/setup-node@v1 - with: - node-version: 16.x - - # Run the BDD tests - - name: Run BDD tests - env: - ALAN_TELEMETRY_OFF: true - run: make bdd - - avm-unit-windows: - # The type of runner that the job will run on - runs-on: windows-latest - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 - - # The AVM requires openssl dev files to build now, and apparently the rust openssl code can auto-acquire it through vcpkg, so set that up - - name: Set up vcpkg - run: | - git clone https://github.com/microsoft/vcpkg - cd vcpkg && bootstrap-vcpkg.bat && vcpkg integrate install && vcpkg install openssl --triplet x64-windows-static-md - - # Rust is automatically set up and included by default according to: https://github.com/actions/starter-workflows/blob/master/ci/rust.yml - # Run unit tests - - name: Run unit tests - run: make avm-unit - - bdd-windows: - # The type of runner that the job will run on - runs-on: windows-latest - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 - - # Rust is automatically set up and included by default according to: https://github.com/actions/starter-workflows/blob/master/ci/rust.yml - - # Set up Node.js - - name: Use Node.js 16.x - uses: actions/setup-node@v1 - with: - node-version: 16.x - - # The AVM requires openssl dev files to build now, and apparently the rust openssl code can auto-acquire it through vcpkg, so set that up - - name: Set up vcpkg - run: | - git clone https://github.com/microsoft/vcpkg - cd vcpkg && bootstrap-vcpkg.bat && vcpkg integrate install && vcpkg install openssl --triplet x64-windows-static-md - - # Run the BDD tests - - name: Run BDD tests - env: - ALAN_TELEMETRY_OFF: true - run: bash -c "make bdd" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 26bcd3484..0792ea86d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea +target/ shellspec/ bdd/node_modules/ bdd/temp.* diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 8fd998695..354a44563 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -10,4 +10,4 @@ Out of respect to you, as well, our code of conduct is brief and (we hope) clear ## Enforcement -It is at the discretion of Alan Technologies, Inc on how to enforce this code of conduct. People make mistakes or could learn and become better people with proper guidance, so we will tend to delete the offensive content and reach out to first-time offenders. Repeat offenders may be banned from participating. +It is at the discretion of the Alan Programming Language maintainers on how to enforce this code of conduct. People make mistakes or could learn and become better people with proper guidance, so we will tend to delete the offensive content and reach out to first-time offenders. Repeat offenders may be banned from participating. diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 000000000..524aaa547 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,315 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "alan" +version = "0.2.0" +dependencies = [ + "clap", + "nom", + "ordered_hash_map", +] + +[[package]] +name = "anstream" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "ordered_hash_map" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab0e5f22bf6dd04abd854a8874247813a8fa2c8c1260eba6fbb150270ce7c176" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "proc-macro2" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "2.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000..e75e211e2 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "alan" +description = "The Alan Compiler" +license = "MIT" +homepage = "https://alan-lang.org" +documentation = "https://docs.alan-lang.org" +repository = "https://github.com/alantech/alan" +version = "0.2.0" +authors = ["David Ellis "] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = { version = "4.4.18", features = ["derive"] } +nom = "7.1.3" +ordered_hash_map = "0.4.0" diff --git a/FAQ.md b/FAQ.md deleted file mode 100644 index 18f49d4a5..000000000 --- a/FAQ.md +++ /dev/null @@ -1,65 +0,0 @@ -## Frequently Asked Questions - -### Could you combine an ELI5 (explain it like I'm five) and a sales pitch to try and convince me? - -Computers have increasingly more CPUs/cores, as opposed to more powerful ones. Multithreaded code is hard to reason about and error prone, but it is also necessary today to take advantage of all the computing resources in a machine. The goal of Alan is to be similar to multithreaded Go or Java in performance, but similar to Python brevity and simplicity. - -### Did you originally intend to make a non-Turing-complete language? - -No, originally just pondering why existing large scale backend deployments and data processing frameworks require so much engineering effort to keep them running in production, then realizing that Turing completeness was the cause of the complexity and many distributed systems' outages and that it wouldn't be possible to solve that problem with a "better framework." That's when working on a new language started. - -### Which parts of Alan make it Turing Incomplete, specifically? - -Classical loops and recursion is not possible. This does not mean that you can't loop over data or write recursive algorithms. Alan encourages expressing iteration using [arrays](https://docs.alan-lang.org/builtins/array_api.html) when possible because it can be parallelized by the AVM. - -However, there are algorithms people need to write that are inherently recursive or sequential. For that Alan reintroduces a way of [writting Sequential Algorithms](https://docs.alan-lang.org/std_seq.html) in controlled manner that still allows the AVM to be able to plan around these algorithms and provide the guarantee of termination. - -### What would would you recommend *not* trying to write with Alan? - -Generally programs where one needs more control over how code the is parallelized, even if it is less convenient, should probably use Go, Rust or Erlang. This is akin to how you might prefer C or C++ over Go or Java if you really need the memory management to be more performant or precise. - -### Have you published any papers with the research on compile time parallelization? - -Most of it is synthesis of existing ideas that just haven't been applied yet, with a couple of things we believe are [our insights](https://alan-lang.org/alan_overview.html#parallel-computation-and-the-problem-of-turing-completeness) (but we haven't exhaustively read the literature to confirm/deny that). - -### Do you have a brief description about how Alan is different (besides syntax) from Rust and Erlang? - -**Erlang:** -Alan went with an event-based model instead of an actor-based model. They are two sides of the same coin (you can represent one with the other) but the event-based model is more well-known and understood to developers as a whole than the actor model due to its use in Javascript, VisualBasic, Logo, etc. In both Erlang, parallelism has to be done across actors which makes data-level parallelism more complex. Alan has support via the array methods to perform that sort of parallelization right now, and the compiler computes a dependency graph of all instructions making up an event handler. We hope the runtime will be able to dig up even more parallelization options from this DAG in the future. - -**Rust:** -Rust gives the full power to developers to determine how and where they want concurrency and parallelism, and gives them escape hatches from their constraints with the unsafe blocks. However Alan provides neither, but uses the constraints it has to automatically find these concurrency and parallelism opportunities for you. Alan's memory story is something in-between Rust and Java; Alan lacks GC pauses because deallocations only happen outside the event loop once an event handler has run its course. This means Alan's memory management frees up data less frequently than Rust's memory model and with a frequency similar to that of a traditional GC. - -### How does Alan get rid of array out of bounds and other common runtime errors? - -We built the Alan VM in Rust and borrow from the Rust syntax a bit too. We do [error handling](https://docs.alan-lang.org/error_handling.html) like Rust and solve array-out-of-bounds by returning Result types which forces the user to handle this at compile time. This shouldn't be too tedious due to shorthand notation around error handling and Alan supporting static [multiple dispatch](https://en.wikipedia.org/wiki/Multiple_dispatch) which allows functions to have additional implementations that accept a Result type as an argument. - -### Where do you want to go with this language? Do you want to grow a community around it? - -Our goal is use Alan to build backends in production that require concurrent or asynchronous execution. We want to work with codebases for concurrent programs that are nimbler and easier to reason about than codebases that use multithreading constructs. - -Yes, we would like to create a community of contributors to work on the Alan runtime and compiler with us, or to build a healthy ecosystem of third party libraries for the language. - -### How (do you plan to) distribute workloads on different machines? - -So the short-term answer to this question is simply fronting Alan processes with a load balancer. But we do intend to make that story better over time. First priority is working on getting @std/datastore to coordinate shared, mutable state within a cluster. This is the [RFC](https://github.com/alantech/alan/blob/main/rfcs/008%20-%20Safe%20Global%20Data%20Storage%20RFC.md) to get it working across cores in a single machine. - -Once that's done, we can add TLS support and then pull in a load balancing layer based on the same balancing logic @std/datastore uses for data balancing which would also make directing traffic to the nodes likely to have the data locally possible. We also hope to eventually be able to have the cluster move the compute automatically to the relevant nodes in a more fine-grained fashion than through load balancing. - -In the long-term, there's nothing semantically restricting the language from actually performing a single logical compute distributed across multiple machines (a map on a logical array larger than local memory) making out-of-memory bugs a thing of the past if coupled with an autoscaling backend, but there's going to be a *lot* of code to write and architecture to design to detect when that's necessary and switch to it. - -### How (do you plan to) do automatic GPGPU delegation or other heterogeneous computing cores? - -Alan already encourages writing code that can be parallelized through arrays and events, so it is possible to implement automatic GPGPU delegation in the future. That said, the state of GPGPU is such a disaster, OpenCL/CUDA/OpenGL/DirectX<=11/DirectX12/Vulkan/Metal, all with strengths and weaknesses and platform issues and no way to avoid one or more of them since GPU drivers don't allow direct access to hardware and have compilers baked into them these days... - -Still thinking about the best approach to take on the GPGPU front: do it "right" up-front but support the main backends relatively early, or prove it out with a single universal backend that might literally fudge numbers on you depending on your hardware. Also, not a priority until we've got the language really working, so post-v0.2, at least. If you have experience in this realm and want to contribute, please reach out! - -### Is there any interoperability? - -We are still scoping out [FFI support](https://github.com/alantech/alan/pull/60/files) for bindings that plays nice with the auto parallelization that happens in the VM. Alan [transpiles to Javascript](https://docs.alan-lang.org/transpile_js.html) which still offers IO concurrency but without the parallelization. - -### The "source installation" necessary to contribute to Alan requires Node.js and Rust. Why are there so many dependencies? - -We went with tools we were familiar with or believed would accelerate our ability to prove to ourselves that this could work, and that is reflected in the language implementation as it exists today, but we have always intended to rewrite the Alan compiler in Alan. Then only the runtime(s) would be in different languages. - -Currently, the compiler is written in Typescript, the main runtime in Rust and the secondary runtime in Javascript. diff --git a/Makefile b/Makefile deleted file mode 100644 index 6515ad862..000000000 --- a/Makefile +++ /dev/null @@ -1,75 +0,0 @@ -SHELL = /bin/bash - -.PHONY: build -build: env-check avm/target/release/alan build-js-runtime - @echo Done - -.PHONY: env-check -env-check: - bash -c "./.envcheck.sh" - -.PHONY: avm-style -avm-style: - cd avm && cargo fmt -- --check - -.PHONY: avm-unit -avm-unit: compiler/alan-compile - cd avm && cargo test - -.PHONY: compiler-browser-check -compiler-browser-check: - cd compiler && yarn && yarn test - -.PHONY: compiler-style -compiler-style: - cd compiler && yarn && yarn style - -# rerun if any of the source files in compiler/ changes -COMPILER_FILES=$(wildcard compiler/src/*.ts) $(wildcard compiler/src/*/*.ts) -# also consider alan's std files. -# TODO: delete the lnn part once lnn replaces the current first-stage -ALAN_STD_FILES=$(wildcard std/*.ln) $(wildcard std/*.lnn) -./compiler/alan-compile: $(COMPILER_FILES) $(ALAN_STD_FILES) - cd compiler && yarn - yarn add pkg - cd compiler && ../node_modules/.bin/pkg --targets host . - -# Issue with `rustc` and `checkinstall` means this cannot be PHONYed -./avm/target/release/alan: compiler/alan-compile - cd avm && cargo build --release - cd avm && cargo fmt - -.PHONY: build-js-runtime -build-js-runtime: - cd js-runtime && yarn - -.PHONY: bdd -bdd: shellspec node_modules - bash -c "./bdd/bdd.sh $(testfile)" - -shellspec: - git clone --depth 1 --branch 0.27.2 https://github.com/shellspec/shellspec - -node_modules: build - npm init -y - yarn add ./js-runtime - -.PHONY: clean -clean: - git clean -ffdxe .vscode - -.PHONY: install -install: avm/target/release/alan - cp ./avm/target/release/alan /usr/local/bin/alan - -.PHONY: uninstall -uninstall: - rm /usr/local/bin/alan - -.PHONY: version -version: - ./.version.sh $(version) - -.PHONY: prerelease -prerelease: - ./.prerelease.sh $(version) \ No newline at end of file diff --git a/README.md b/README.md index f31b93468..4ea1c30c3 100644 --- a/README.md +++ b/README.md @@ -7,73 +7,48 @@ [![Docs](https://img.shields.io/badge/docs-mdbook-blue)](https://docs.alan-lang.org) [![Discord](https://img.shields.io/badge/discord-alanlang-purple)](https://discord.gg/XatB9we) [![Reddit](https://img.shields.io/badge/reddit-alanlang-red)](https://www.reddit.com/r/alanlang) - + +**🚧 CONSTRUCTION IN PROGRESS** - This language is being reworked for a new purpose in a backwards-incompatible way. The currently published documentation is for the [v0.1](https://github.com/alantech/alan/tree/v0.1) iteration of Alan, which is more stable and capable than the v0.2 code on the `main` branch. **πŸ”­ Predictable runtime for all computations** - A program is represented as DAG(s) where the running time for all computations can be predicted because there is no unbounded recursion or iteration. -**β›“ Automatic IO concurrency and parallelism across events and arrays** - Alan exploits opportunities for IO concurrency or CPU parallelization across machines in a cluster via arrays and a static event loop without threads, channels, promises, futures, locks, etc. +**β›“ Transparent GPGPU programming** - Alan's restrictions on recursion and iteration allows for automatic generation of compute shaders for your code. (Not yet implemented) -**βœ… Almost no runtime errors** - No deadlocks, livelocks, undefined variables, divide-by-zero, integer under/overflow, array out-of-bounds access, etc. +**βœ… Almost no runtime errors** - No deadlocks, livelocks, undefined variables, divide-by-zero, integer under/overflow, array out-of-bounds access, etc. Due to the type system and the standard library primitives provided to you. -**⚑️ No GC pauses** - Alan’s runtime manages memory allocation, access, and deallocation for you like Java, Python, or Javascript. However, Alan’s static event system and [automatic event-oriented memory model](https://alan-lang.org/alan_overview.html#memory-management) does so without garbage collector pauses. +**⚑️ Native performance with Rust** - Alan's new compiler transforms your code into Rust before finally generating a binary for your platform, without needing to worry about memory management or GC pauses by handling Rust's borrow checker for you. ---------------------------------
-πŸ‘©β€πŸš€ Alan is a programming language that does concurrency for you and can thus separate how the software is written from how it runs. -To learn more about Alan, take a look at [runnable examples](https://docs.alan-lang.org/examples.html) or the most [Frequently Asked Questions](https://github.com/alantech/alan/blob/main/FAQ.md). +πŸ‘©β€πŸš€ Alan is a programming language that makes the power of the GPU more accessible, with a syntax similar to a dynamic language (it's typed, but 100% inferred), and restrictions on recursion and iteration to make automatic generation of multi-threaded CPU and GPGPU versions of your code for you.

Installation


-For MacOS it is recommended to install Alan via the [Homebrew](https://brew.sh) package manager. - -**MacOS** - -```bash -brew install alantech/homebrew-core/alan -``` - -For Linux and Windows it is recommended to install Alan via the [published artifacts](https://github.com/alantech/alan/releases). Simply download the zip or tar.gz file for your operating system, and extract the `alan` executable to somewhere in your `$PATH`, make sure it's marked executable (if not on Windows), and you're ready to roll. - -**Linux** +Currently, the only way to install `alan` is to have a working `rust` development environment along with `git` to clone this repo and install it manually: ```bash -wget https://github.com/alantech/alan/releases/latest/download/alan-ubuntu.tar.gz -tar -xzf alan-ubuntu.tar.gz -sudo mv alan /usr/local/bin/alan +git clone git@github.com:alantech/alan +cd alan +cargo install --path . ``` -**Windows** - -```ps1 -Invoke-WebRequest -OutFile alan-windows.zip -Uri https://github.com/alantech/alan/releases/latest/download/alan-windows.zip -Expand-Archive -Path alan-windows.zip -DestinationPath C:\windows -``` +alan v0.2 has only been tested on Linux so far, but it is expected to work fine on MacOS and fail on Windows, at the moment, but full support for all platforms is planned.

Usage


-To compile to Alan GraphCode and then run it with the AVM: - -``` -alan compile .ln .agc -alan run .agc -``` - -You can also compile-and-run a source file with a simple: +To compile, simply: ``` -alan .ln +alan compile .ln ``` -You can also [transpile Alan to Javascript](https://docs.alan-lang.org/transpile_js.html) or one of it's [intermediate representations](https://docs.alan-lang.org/compiler_internals.html). - -Note: To better understand if we are building something people want to use we currently [log an event](https://github.com/alantech/alan/blob/main/avm/src/vm/telemetry.rs) when running an Alan command. Feel free to turn this off by setting the `ALAN_TELEMETRY_OFF` environment variable to `true`, but if you do please let us know how you are using Alan and how often! +This will create a file with the name `` that you can run (or error if it fails to compile).

Contribution

@@ -81,38 +56,19 @@ Note: To better understand if we are building something people want to use we cu **Source Installation:** -If you wish to contribute to Alan, or if your operating system and/or CPU architecture do not match the above, you'll need a development environment to build Alan locally: +If you wish to contribute to Alan, you'll need a development environment to build Alan locally: * git (any recent version should work) -* Node.js >=10.20.1 -* Rust >=1.45.0 +* Rust >=1.75.0 * A complete C toolchain (gcc, clang, msvc) -Once those are installed, simply: +Once those are installed, simply follow the install instructions above, replacing `cargo install --path .` with a simple `cargo build` to compile and `cargo test` to run the test suite. -```bash -git clone https://github.com/alantech/alan -cd alan -make -sudo make install -``` +**Unit and Integration Tests:** -**Integration tests:** +The tests are included within the Rust source files. Test coverage is not yet 100%, with the majority of unit tests in the `src/parse.rs` file defining the Alan syntax parser. The unit tests directly follow the functions they test, instead of all being at the end as is standard in Rust, because it seemed easier to read that way. These tests all pass. -Integration tests are in `/bdd` and defined using [Shellspec](https://shellspec.info/). To run all integration tests: -``` -make bdd -``` - -To run a single test file: -``` -make bdd testfile=bdd/spec/001_event_spec.sh -``` - -To run a single test group use the line number corresponding to a `Describe`: -``` -make bdd testfile=bdd/spec/001_event_spec.sh:30 -``` +Beyond that are integration tests in the `src/compile.rs` file, making up the vast majority of that file (which for release is just a single function that is a small wrapper around the transpilation code in `lntors.rs`). Few of these tests currently pass, as they were inherited from the Alan v0.1 test suite. Most are planned for revival but some may be changed or dropped.

License

diff --git a/avm/.gitattributes b/avm/.gitattributes deleted file mode 100644 index 52cff8824..000000000 --- a/avm/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -Cargo.lock binary \ No newline at end of file diff --git a/avm/.gitignore b/avm/.gitignore deleted file mode 100644 index f16029f24..000000000 --- a/avm/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.idea/* -*.iml -#Added by cargo - -/target diff --git a/avm/.rustfmt.toml b/avm/.rustfmt.toml deleted file mode 100644 index 161305cb3..000000000 --- a/avm/.rustfmt.toml +++ /dev/null @@ -1,3 +0,0 @@ -hard_tabs = false -newline_style = "Unix" -tab_spaces = 2 \ No newline at end of file diff --git a/avm/Cargo.lock b/avm/Cargo.lock deleted file mode 100644 index 5b01cc62f..000000000 --- a/avm/Cargo.lock +++ /dev/null @@ -1,2265 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "addr2line" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "ahash" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217" -dependencies = [ - "const-random", -] - -[[package]] -name = "aho-corasick" -version = "0.7.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" -dependencies = [ - "memchr", -] - -[[package]] -name = "alan" -version = "0.1.45-beta3" -dependencies = [ - "ascii_table", - "async-stream", - "base64 0.13.0", - "byteorder", - "clap", - "dashmap", - "dialoguer", - "flate2", - "futures", - "futures-util", - "hyper", - "hyper-openssl", - "hyper-rustls", - "indicatif", - "lazy_static", - "num_cpus", - "once_cell", - "openssl", - "protobuf", - "protoc-bin-vendored", - "protoc-rust", - "rand 0.8.3", - "regex", - "rustls", - "rustls-pemfile", - "serde", - "serde_ini", - "serde_json", - "sysinfo", - "tempfile", - "tokio", - "tokio-rustls", - "trust-dns-resolver", - "twox-hash", - "webbrowser", -] - -[[package]] -name = "anstream" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" - -[[package]] -name = "anstyle-parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" -dependencies = [ - "anstyle", - "windows-sys", -] - -[[package]] -name = "ascii_table" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51f0a58f3b1453981b910b603a4a7346be14fccd50f8edd7c954725a9210c24f" - -[[package]] -name = "async-stream" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3670df70cbc01729f901f94c887814b3c68db038aad1329a418bae178bc5295c" -dependencies = [ - "async-stream-impl", - "futures-core", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3548b8efc9f8e8a5a0a2808c5bd8451a9031b9e5b879a79590304ae928b0a70" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.69", -] - -[[package]] -name = "async-trait" -version = "0.1.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36ea56748e10732c49404c153638a15ec3d6211ec5ff35d9bb20e13b93576adf" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.69", -] - -[[package]] -name = "autocfg" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" - -[[package]] -name = "backtrace" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" -dependencies = [ - "addr2line", - "cc", - "cfg-if 1.0.0", - "libc", - "miniz_oxide 0.7.1", - "object", - "rustc-demangle", -] - -[[package]] -name = "base64" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" - -[[package]] -name = "base64" -version = "0.21.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" - -[[package]] -name = "bitflags" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" - -[[package]] -name = "bitflags" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" - -[[package]] -name = "bumpalo" -version = "3.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "bytes" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" - -[[package]] -name = "cc" -version = "1.0.84" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856" -dependencies = [ - "libc", -] - -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "clap" -version = "4.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" -dependencies = [ - "clap_builder", -] - -[[package]] -name = "clap_builder" -version = "4.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_lex" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" - -[[package]] -name = "colorchoice" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" - -[[package]] -name = "combine" -version = "4.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "console" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45" -dependencies = [ - "encode_unicode", - "lazy_static", - "libc", - "regex", - "terminal_size", - "unicode-width", - "winapi", -] - -[[package]] -name = "const-random" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f590d95d011aa80b063ffe3253422ed5aa462af4e9867d43ce8337562bac77c4" -dependencies = [ - "const-random-macro", - "proc-macro-hack", -] - -[[package]] -name = "const-random-macro" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "615f6e27d000a2bffbc7f2f6a8669179378fa27ee4d0a509e985dfc0a7defb40" -dependencies = [ - "getrandom 0.2.11", - "lazy_static", - "proc-macro-hack", - "tiny-keccak", -] - -[[package]] -name = "core-foundation" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" - -[[package]] -name = "crc32fast" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" -dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" -dependencies = [ - "cfg-if 1.0.0", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" -dependencies = [ - "autocfg", - "cfg-if 1.0.0", - "crossbeam-utils", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "dashmap" -version = "3.11.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f260e2fc850179ef410018660006951c1b55b79e8087e87111a2c388994b9b5" -dependencies = [ - "ahash", - "cfg-if 0.1.10", - "num_cpus", -] - -[[package]] -name = "data-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" - -[[package]] -name = "dialoguer" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9dd058f8b65922819fabb4a41e7d1964e56344042c26efbccd465202c23fa0c" -dependencies = [ - "console", - "lazy_static", - "tempfile", - "zeroize", -] - -[[package]] -name = "dirs" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "either" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" - -[[package]] -name = "encode_unicode" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" - -[[package]] -name = "enum-as-inner" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.39", -] - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "errno" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e" -dependencies = [ - "libc", - "windows-sys", -] - -[[package]] -name = "fastrand" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" - -[[package]] -name = "flate2" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" -dependencies = [ - "cfg-if 1.0.0", - "crc32fast", - "libc", - "miniz_oxide 0.4.4", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d5813545e459ad3ca1bff9915e9ad7f1a47dc6a91b627ce321d5863b7dd253" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce79c6a52a299137a6013061e0cf0e688fce5d7f1bc60125f520912fdb29ec25" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "098cd1c6dda6ca01650f1a37a794245eb73181d0d4d4e955e2f3c37db7af1815" - -[[package]] -name = "futures-executor" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f6cb7042eda00f0049b1d2080aa4b93442997ee507eb3828e8bd7577f94c9d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "365a1a1fb30ea1c03a830fdb2158f5236833ac81fa0ad12fe35b29cddc35cb04" - -[[package]] -name = "futures-macro" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668c6733a182cd7deb4f1de7ba3bf2120823835b3bcfbeacf7d2c4a773c1bb8b" -dependencies = [ - "proc-macro-hack", - "proc-macro2", - "quote", - "syn 1.0.69", -] - -[[package]] -name = "futures-sink" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5629433c555de3d82861a7a4e3794a4c40040390907cfbfd7143a92a426c23" - -[[package]] -name = "futures-task" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba7aa51095076f3ba6d9a1f702f74bd05ec65f555d70d2033d55ba8d69f581bc" - -[[package]] -name = "futures-util" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c144ad54d60f23927f0a6b6d816e4271278b64f005ad65e4e35291d2de9c025" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "proc-macro-hack", - "proc-macro-nested", - "slab", -] - -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", -] - -[[package]] -name = "gimli" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" - -[[package]] -name = "h2" -version = "0.3.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "hermit-abi" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" -dependencies = [ - "libc", -] - -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi", -] - -[[package]] -name = "http" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfb77c123b4e2f72a2069aeae0b4b4949cc7e966df277813fc16347e7549737" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" - -[[package]] -name = "httpdate" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" - -[[package]] -name = "hyper" -version = "0.14.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13f67199e765030fa08fe0bd581af683f0d5bc04ea09c2b1102012c5fb90e7fd" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2 0.4.0", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper-openssl" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ee5d7a8f718585d1c3c61dfde28ef5b0bb14734b4db13f5ada856cdc6c612b" -dependencies = [ - "http", - "hyper", - "linked_hash_set", - "once_cell", - "openssl", - "openssl-sys", - "parking_lot", - "tokio", - "tokio-openssl", - "tower-layer", -] - -[[package]] -name = "hyper-rustls" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" -dependencies = [ - "futures-util", - "http", - "hyper", - "log", - "rustls", - "rustls-native-certs", - "tokio", - "tokio-rustls", -] - -[[package]] -name = "idna" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "indexmap" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "indicatif" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7baab56125e25686df467fe470785512329883aab42696d661247aca2a2896e4" -dependencies = [ - "console", - "lazy_static", - "number_prefix", - "regex", -] - -[[package]] -name = "ipconfig" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" -dependencies = [ - "socket2 0.5.5", - "widestring", - "windows-sys", - "winreg", -] - -[[package]] -name = "ipnet" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" - -[[package]] -name = "itoa" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" - -[[package]] -name = "jni" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c" -dependencies = [ - "cesu8", - "combine", - "jni-sys", - "log", - "thiserror", - "walkdir", -] - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - -[[package]] -name = "js-sys" -version = "0.3.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.150" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" - -[[package]] -name = "libredox" -version = "0.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" -dependencies = [ - "bitflags 2.4.1", - "libc", - "redox_syscall", -] - -[[package]] -name = "linked-hash-map" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" - -[[package]] -name = "linked_hash_set" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47186c6da4d81ca383c7c47c1bfc80f4b95f4720514d860a5407aaf4233f9588" -dependencies = [ - "linked-hash-map", -] - -[[package]] -name = "linux-raw-sys" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" - -[[package]] -name = "lock_api" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" -dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "lru-cache" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" -dependencies = [ - "linked-hash-map", -] - -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - -[[package]] -name = "memchr" -version = "2.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" - -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - -[[package]] -name = "miniz_oxide" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" -dependencies = [ - "adler", - "autocfg", -] - -[[package]] -name = "miniz_oxide" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" -dependencies = [ - "adler", -] - -[[package]] -name = "mio" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" -dependencies = [ - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "ntapi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" -dependencies = [ - "winapi", -] - -[[package]] -name = "num_cpus" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "number_prefix" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b02fc0ff9a9e4b35b3342880f48e896ebf69f2967921fe8646bf5b7125956a" - -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", -] - -[[package]] -name = "object" -version = "0.32.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" - -[[package]] -name = "openssl" -version = "0.10.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800" -dependencies = [ - "bitflags 2.4.1", - "cfg-if 1.0.0", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.39", -] - -[[package]] -name = "openssl-probe" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" - -[[package]] -name = "openssl-sys" -version = "0.9.96" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "redox_syscall", - "smallvec", - "windows-targets", -] - -[[package]] -name = "percent-encoding" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" - -[[package]] -name = "pin-project" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc174859768806e91ae575187ada95c91a29e96a98dc5d2cd9a1fed039501ba6" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a490329918e856ed1b083f244e3bfe2d8c4f336407e4ea9e1a9f479ff09049e5" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.69", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkg-config" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" - -[[package]] -name = "ppv-lite86" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" - -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - -[[package]] -name = "proc-macro-nested" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" - -[[package]] -name = "proc-macro2" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "protobuf" -version = "2.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45604fc7a88158e7d514d8e22e14ac746081e7a70d7690074dd0029ee37458d6" - -[[package]] -name = "protobuf-codegen" -version = "2.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb87f342b585958c1c086313dbc468dcac3edf5e90362111c26d7a58127ac095" -dependencies = [ - "protobuf", -] - -[[package]] -name = "protoc" -version = "2.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4677a99cc1f866078918c00773cbb46dd72eecad949a31981de5aad1ff9bcc8d" -dependencies = [ - "log", - "which", -] - -[[package]] -name = "protoc-bin-vendored" -version = "2.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33169b068b77489fc2c655ef358beb00a78b2090dc1e03c5395d3ed51e96c77a" - -[[package]] -name = "protoc-rust" -version = "2.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35a8c288bd8b80ab9eb660b75de7b99be75ee1a0a3920f15b4924d38da43f6c" -dependencies = [ - "protobuf", - "protobuf-codegen", - "protoc", - "tempfile", -] - -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - -[[package]] -name = "quote" -version = "1.0.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc 0.2.0", -] - -[[package]] -name = "rand" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" -dependencies = [ - "libc", - "rand_chacha 0.3.0", - "rand_core 0.6.2", - "rand_hc 0.3.0", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - -[[package]] -name = "rand_chacha" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.2", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - -[[package]] -name = "rand_core" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" -dependencies = [ - "getrandom 0.2.11", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_hc" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" -dependencies = [ - "rand_core 0.6.2", -] - -[[package]] -name = "raw-window-handle" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" - -[[package]] -name = "rayon" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.2.1", -] - -[[package]] -name = "redox_users" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" -dependencies = [ - "getrandom 0.2.11", - "libredox", - "thiserror", -] - -[[package]] -name = "regex" -version = "1.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "resolv-conf" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" -dependencies = [ - "hostname", - "quick-error", -] - -[[package]] -name = "result" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194d8e591e405d1eecf28819740abed6d719d1a2db87fc0bcdedee9a26d55560" - -[[package]] -name = "ring" -version = "0.17.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" -dependencies = [ - "cc", - "getrandom 0.2.11", - "libc", - "spin", - "untrusted", - "windows-sys", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" - -[[package]] -name = "rustix" -version = "0.38.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb93593068e9babdad10e4fce47dc9b3ac25315a72a59766ffd9e9a71996a04" -dependencies = [ - "bitflags 2.4.1", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", -] - -[[package]] -name = "rustls" -version = "0.21.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c" -dependencies = [ - "log", - "ring", - "rustls-webpki", - "sct", -] - -[[package]] -name = "rustls-native-certs" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" -dependencies = [ - "openssl-probe", - "rustls-pemfile", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.5", -] - -[[package]] -name = "rustls-webpki" -version = "0.101.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "ryu" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schannel" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" -dependencies = [ - "lazy_static", - "winapi", -] - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "security-framework" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3670b1d2fdf6084d192bc71ead7aabe6c06aa2ea3fbd9cc3ac111fa5c2b1bd84" -dependencies = [ - "bitflags 1.2.1", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3676258fd3cfe2c9a0ec99ce3038798d847ce3e4bb17746373eb9f0f1ac16339" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "serde" -version = "1.0.125" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.125" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.69", -] - -[[package]] -name = "serde_ini" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb236687e2bb073a7521c021949be944641e671b8505a94069ca37b656c81139" -dependencies = [ - "result", - "serde", - "void", -] - -[[package]] -name = "serde_json" -version = "1.0.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "signal-hook-registry" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" -dependencies = [ - "libc", -] - -[[package]] -name = "slab" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" - -[[package]] -name = "smallvec" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" - -[[package]] -name = "socket2" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e3dfc207c526015c632472a77be09cf1b6e46866581aecae5cc38fb4235dea2" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "socket2" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" -dependencies = [ - "libc", - "windows-sys", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "syn" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "syn" -version = "2.0.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sysinfo" -version = "0.29.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a18d114d420ada3a891e6bc8e96a2023402203296a47cdd65083377dad18ba5" -dependencies = [ - "cfg-if 1.0.0", - "core-foundation-sys", - "libc", - "ntapi", - "once_cell", - "rayon", - "winapi", -] - -[[package]] -name = "tempfile" -version = "3.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" -dependencies = [ - "cfg-if 1.0.0", - "fastrand", - "redox_syscall", - "rustix", - "windows-sys", -] - -[[package]] -name = "terminal_size" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ca8ced750734db02076f44132d802af0b33b09942331f4459dde8636fd2406" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "thiserror" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.69", -] - -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - -[[package]] -name = "tinyvec" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - -[[package]] -name = "tokio" -version = "1.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "num_cpus", - "pin-project-lite", - "signal-hook-registry", - "socket2 0.5.5", - "tokio-macros", - "windows-sys", -] - -[[package]] -name = "tokio-macros" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.39", -] - -[[package]] -name = "tokio-openssl" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1bec5c0a4aa71e3459802c7a12e8912c2091ce2151004f9ce95cc5d1c6124e" -dependencies = [ - "futures", - "openssl", - "pin-project", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f988a1a1adc2fb21f9c12aa96441da33a1728193ae0b95d2be22dbd17fcb4e5c" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", - "tracing", -] - -[[package]] -name = "tower-layer" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" - -[[package]] -name = "tower-service" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" - -[[package]] -name = "tracing" -version = "0.1.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.39", -] - -[[package]] -name = "tracing-core" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" -dependencies = [ - "once_cell", -] - -[[package]] -name = "trust-dns-proto" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3119112651c157f4488931a01e586aa459736e9d6046d3bd9105ffb69352d374" -dependencies = [ - "async-trait", - "cfg-if 1.0.0", - "data-encoding", - "enum-as-inner", - "futures-channel", - "futures-io", - "futures-util", - "idna", - "ipnet", - "once_cell", - "rand 0.8.3", - "rustls", - "rustls-pemfile", - "rustls-webpki", - "smallvec", - "thiserror", - "tinyvec", - "tokio", - "tokio-rustls", - "tracing", - "url", -] - -[[package]] -name = "trust-dns-resolver" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a3e6c3aff1718b3c73e395d1f35202ba2ffa847c6a62eea0db8fb4cfe30be6" -dependencies = [ - "cfg-if 1.0.0", - "futures-util", - "ipconfig", - "lru-cache", - "once_cell", - "parking_lot", - "rand 0.8.3", - "resolv-conf", - "rustls", - "smallvec", - "thiserror", - "tokio", - "tokio-rustls", - "tracing", - "trust-dns-proto", - "webpki-roots", -] - -[[package]] -name = "try-lock" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" - -[[package]] -name = "twox-hash" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f8ab788026715fa63b31960869617cba39117e520eb415b0139543e325ab59" -dependencies = [ - "cfg-if 0.1.10", - "rand 0.7.3", - "static_assertions", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "unicode-normalization" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-width" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" - -[[package]] -name = "unicode-xid" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "utf8parse" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" - -[[package]] -name = "vcpkg" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb" - -[[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" - -[[package]] -name = "walkdir" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -dependencies = [ - "log", - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9" -dependencies = [ - "cfg-if 1.0.0", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae" -dependencies = [ - "bumpalo", - "lazy_static", - "log", - "proc-macro2", - "quote", - "syn 1.0.69", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.69", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489" - -[[package]] -name = "web-sys" -version = "0.3.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webbrowser" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa61ff77f695a94d9c8558e0bb5c362a8fd1f27c74663770fbc633acbafedbb6" -dependencies = [ - "core-foundation", - "dirs", - "jni", - "log", - "ndk-context", - "objc", - "raw-window-handle", - "url", - "web-sys", - "widestring", - "winapi", -] - -[[package]] -name = "webpki-roots" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" - -[[package]] -name = "which" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55551e42cbdf2ce2bedd2203d0cc08dba002c27510f86dab6d0ce304cba3dfe" -dependencies = [ - "either", - "libc", -] - -[[package]] -name = "widestring" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if 1.0.0", - "windows-sys", -] - -[[package]] -name = "zeroize" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81a974bcdd357f0dca4d41677db03436324d45a4c9ed2d0b873a5a360ce41c36" diff --git a/avm/Cargo.toml b/avm/Cargo.toml deleted file mode 100644 index ab6b48150..000000000 --- a/avm/Cargo.toml +++ /dev/null @@ -1,52 +0,0 @@ -[package] -name = "alan" -description = "The Alan Compiler and VM" -license = "MIT" -homepage = "https://alan-lang.org" -documentation = "https://docs.alan-lang.org" -repository = "https://github.com/alantech/alan" -version = "0.1.45-beta3" -authors = ["Luis de Pombo ", "David Ellis ", "Alejandro Guillen "] -edition = "2018" -resolver = "2" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -ascii_table = "3.0" -async-stream = "0.3.0" -base64 = "0.13" -byteorder = "1.3.4" -clap = { version = "4.4.8", features = ["cargo"] } -dashmap = "3.11.10" -dialoguer = "0.8.0" -flate2 = "1.0" -futures = "0.3.8" -futures-util = "0.3.13" -hyper = { version = "0.14", features = ["client", "http1", "http2", "runtime", "server"] } -hyper-openssl = "0.9.2" -hyper-rustls = { version = "0.24.2", features = ["http2"] } # needed for HTTPS w/ hyper -indicatif = "0.15.0" -lazy_static = "1.4.0" -num_cpus = "1.0" -once_cell = "1.5.2" -openssl = "0.10.60" -protobuf = "2.23.0" -rand = "0.8.3" -regex = "1" -rustls = { version = "0.21.8", features = ["dangerous_configuration"] } -rustls-pemfile = "1.0.4" -serde = { version = "1.0", features = ["derive"] } -serde_ini = { version = "0.2" } -serde_json = { version = "1.0" } -sysinfo = "0.29.10" -tempfile = "3.8.1" -tokio = { version = "1.8", features = ["rt-multi-thread", "macros", "process", "sync", "time"] } -tokio-rustls = "0.24.1" -trust-dns-resolver = { version = "0.23.2", features = ["dns-over-rustls"] } -twox-hash = "1.5.0" -webbrowser = "0.8.3" - -[build-dependencies] -protoc-bin-vendored = "2.23.0" -protoc-rust = "2.23.0" diff --git a/avm/README.md b/avm/README.md deleted file mode 100644 index d52f02bb1..000000000 --- a/avm/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# The Alan Virtual Machine (and CLI App) - -A virtual machine in Rust to compile and run AGC or Alan Graphcode, Alan's bytecode format. - -## Install - -The AVM requires the [compiler](https://github.com/alantech/alan/tree/main/compiler) to have been built and the `alan-compile` binary to exist before this project can be built directly with: - -``` -cargo build -``` - -It is recommended to build the AVM from the root of the repository with a simple `make` call, instead. - -## Usage - -``` -cargo run -- compile .ln .agc -cargo run -- run .agc -``` - -The binary file has to be `.agc` format. -To run an optimized build: - -``` -cargo build --release -./target/release/alan run .agc -``` - -## Development - -The AVM is backed by a single-threaded, or basic, [Tokio](https://tokio.rs/) scheduler and uses a [Rayon](https://crates.io/crates/rayon) -threadpool to run cpu bound opcodes. - -## License - -MIT diff --git a/avm/build.rs b/avm/build.rs deleted file mode 100644 index 85f970a4a..000000000 --- a/avm/build.rs +++ /dev/null @@ -1,30 +0,0 @@ -extern crate protoc_bin_vendored; -extern crate protoc_rust; - -use protoc_bin_vendored::protoc_bin_path; -use protoc_rust::Customize; - -fn main() { - // if the alan-compile bin has changed, trigger recompilation - if std::env::var("TARGET").unwrap().contains("windows") { - println!("cargo:rerun-if-changed=../compiler/alan-compile.exe"); - } else { - println!("cargo:rerun-if-changed=../compiler/alan-compile"); - } - - // Tell Cargo that if the build.rs or HandlerMemory.proto files change, rerun this build script - println!("cargo:rerun-if-changed=build.rs"); - println!("cargo:rerun-if-changed=src/vm/protos/HandlerMemory.proto"); - - // Protobuf schema generation - protoc_rust::Codegen::new() - .protoc_path(protoc_bin_path().unwrap()) - .out_dir("src/vm/protos") - .inputs(&["src/vm/protos/HandlerMemory.proto"]) - .includes(&["src/vm/protos"]) - .customize(Customize { - ..Default::default() - }) - .run() - .expect("protoc"); -} diff --git a/avm/src/cloud/common/mod.rs b/avm/src/cloud/common/mod.rs deleted file mode 100644 index 445f3ca3c..000000000 --- a/avm/src/cloud/common/mod.rs +++ /dev/null @@ -1,129 +0,0 @@ -use std::env; -use std::fs::read; -use std::process::Command; - -use tempfile::tempdir; - -async fn get_file(file_name: &str, file_path: Option<&str>) -> Result, String> { - match file_path { - Some(file_path) => match read(format!("{}/{}", file_path, file_name)) { - Ok(file) => Ok(file), - Err(_) => Err(format!("No {} at {}", file_name, file_path).into()), - }, - None => { - let pwd = env::current_dir(); - match pwd { - Ok(pwd) => match read(format!("{}/{}", pwd.display(), file_name)) { - Ok(file) => Ok(file), - Err(_) => Err(format!("No {} at {}", file_name, pwd.display()).into()), - }, - Err(_) => { - warn!(InvalidPwd, "Current working directory value is invalid"); - std::process::exit(1); - } - } - } - } -} - -pub async fn get_dockerfile_b64() -> String { - match get_file("Dockerfile", None).await { - Ok(file) => base64::encode(file), - Err(err) => { - warn!(NoDockerFile, "{}", err); - std::process::exit(1); - } - } -} - -pub async fn get_env_file_b64(env_file_path: String) -> String { - match get_file(&env_file_path, None).await { - Ok(file) => base64::encode(file), - Err(err) => { - warn!(NoEnvFile, "{}", err); - std::process::exit(1); - } - } -} - -pub async fn get_agz_file_b64(agz_file_path: String) -> String { - match get_file(&agz_file_path, None).await { - Ok(file) => base64::encode(file), - Err(err) => { - warn!(NoDaemonAGZFile, "{}", err); - std::process::exit(1); - } - } -} - -pub async fn get_app_tar_gz_b64(is_temporary: bool) -> String { - git_status().await; - let file_name = "app.tar.gz"; - let tmp_dir = tempdir(); - let mut tmp_dir_path: Option<&str> = None; - if is_temporary { - tmp_dir_path = match &tmp_dir { - Ok(dir) => dir.path().to_str(), - Err(e) => { - warn!(NoTmpDir, "Error creating temporal directory. {}", e); - std::process::exit(1); - } - } - } - let file_path = match tmp_dir_path { - Some(tmp_dir) => format!("{}/{}", tmp_dir, file_name), - None => file_name.to_string(), - }; - git_archive_app_tar(&file_path).await; - let app_tar_gz = match get_file(file_name, tmp_dir_path).await { - Ok(file) => file, - Err(err) => { - warn!(NoTarballFile, "{}", err); - std::process::exit(1); - } - }; - return base64::encode(app_tar_gz); -} - -async fn git_status() { - let output = Command::new("git") - .arg("status") - .arg("--porcelain") - .output() - .unwrap(); - - let msg = String::from_utf8(output.stdout).unwrap(); - if msg.contains("M ") { - warn!( - GitChanges, - "Please stash, commit or .gitignore your changes before deploying and try again:\n\n{}", msg - ); - std::process::exit(1); - } -} - -async fn git_archive_app_tar(file_path: &str) { - let output = Command::new("git") - .arg("archive") - .arg("--format=tar.gz") - .arg("-o") - .arg(file_path) - .arg("HEAD") - .output() - .unwrap(); - if output.status.code().unwrap() != 0 { - warn!(NoGit, "Your code must be managed by git in order to deploy correctly, please run `git init && git commit -am \"Initial commit\"` and try again."); - std::process::exit(output.status.code().unwrap()); - } -} - -pub fn file_exist(file_path: &str) -> bool { - let pwd = env::current_dir(); - match pwd { - Ok(pwd) => match read(format!("{}/{}", pwd.display(), file_path)) { - Ok(_) => true, - Err(_) => false, - }, - Err(_) => false, - } -} diff --git a/avm/src/cloud/deploy/deploy_dialoguer.rs b/avm/src/cloud/deploy/deploy_dialoguer.rs deleted file mode 100644 index e509bca2d..000000000 --- a/avm/src/cloud/deploy/deploy_dialoguer.rs +++ /dev/null @@ -1,72 +0,0 @@ -use dialoguer::{theme::ColorfulTheme, Confirm, Input, Select, Validator}; - -pub fn select_with_default(prompt: &str, items: &Vec, default: usize) -> usize { - Select::with_theme(&ColorfulTheme::default()) - .with_prompt(prompt) - .items(items) - .default(default) - .interact() - .unwrap() -} - -pub fn input_with_default_and_validation( - prompt: &str, - default: String, - validator: impl Validator, -) -> String { - Input::with_theme(&ColorfulTheme::default()) - .with_prompt(prompt) - .validate_with(validator) - .default(default) - .interact_text() - .unwrap() -} - -pub fn input_with_validation(prompt: &str, validator: impl Validator) -> String { - Input::with_theme(&ColorfulTheme::default()) - .with_prompt(prompt) - .validate_with(validator) - .interact_text() - .unwrap() -} - -pub fn confirm_with_default(prompt: &str, default: bool) -> bool { - Confirm::with_theme(&ColorfulTheme::default()) - .with_prompt(prompt) - .default(default) - .interact() - .unwrap() -} - -pub fn input(prompt: &str) -> String { - Input::with_theme(&ColorfulTheme::default()) - .with_prompt(prompt) - .interact_text() - .unwrap() -} - -pub fn input_with_initial_text(prompt: &str, initial_text: String) -> String { - Input::with_theme(&ColorfulTheme::default()) - .with_prompt(prompt) - .with_initial_text(initial_text) - .interact_text() - .unwrap() -} - -pub fn input_with_default(prompt: &str, default: String) -> String { - Input::with_theme(&ColorfulTheme::default()) - .with_prompt(prompt) - .default(default) - .interact_text() - .unwrap() -} - -pub fn input_with_allow_empty_as_result( - prompt: &str, - allow_empty: bool, -) -> std::io::Result { - Input::with_theme(&ColorfulTheme::default()) - .with_prompt(prompt) - .allow_empty(allow_empty) - .interact_text() -} diff --git a/avm/src/cloud/deploy/mod.rs b/avm/src/cloud/deploy/mod.rs deleted file mode 100644 index f41be24b8..000000000 --- a/avm/src/cloud/deploy/mod.rs +++ /dev/null @@ -1,1675 +0,0 @@ -use dialoguer::console::style; -use hyper::{Request, StatusCode}; -use indicatif::ProgressBar; -use serde::{Deserialize, Serialize}; -use serde_ini; -use serde_json::{json, Value}; - -use std::collections::{HashMap, HashSet}; -use std::fmt::Display; -use std::fs::OpenOptions; -use std::future::Future; -use std::io::{BufReader, BufWriter}; -use std::time::Duration; - -use ascii_table::{AsciiTable, Column}; - -use crate::cloud::http::CLIENT; -use crate::cloud::logger::ErrorType; -use crate::cloud::oauth::{clear_token, get_token}; -use crate::cloud::CLUSTER_ID; - -mod deploy_dialoguer; - -macro_rules! warn_and_exit { - ($exitCode:expr, $errCode:ident, $($message:tt)+) => {async{ - warn!( - $errCode, - $($message)+ - ); - std::process::exit($exitCode); - }}; -} - -pub const VERSION: &'static str = env!("CARGO_PKG_VERSION"); -const REQUEST_TIMEOUT: &str = - "Operation is still in progress. It might take a few more minutes for \ - the cloud provider to finish up."; -const FORBIDDEN_OPERATION: &str = - "Please review your credentials. Make sure you have follow all the \ - configuration steps: https://docs.anycloudapp.com/"; // TODO: Fix this URL -const NAME_CONFLICT: &str = "Another application with same App ID already exists."; -const UNAUTHORIZED_OPERATION: &str = - "Invalid AnyCloud authentication credentials. Please retry and you will be asked to reauthenticate."; -const BURSTABLE_VM_TYPES: [&'static str; 43] = [ - // AWS: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/burstable-performance-instances.html - "t2.nano", - "t2.micro", - "t2.small", - "t2.medium", - "t2.large", - "t2.xlarge", - "t2.2xlarge", - "t3.nano", - "t3.micro", - "t3.small", - "t3.medium", - "t3.large", - "t3.xlarge", - "t3.2xlarge", - "t3a.nano", - "t3a.micro", - "t3a.small", - "t3a.medium", - "t3a.large", - "t3a.xlarge", - "t3a.2xlarge", - "t4g.nano", - "t4g.micro", - "t4g.small", - "t4g.medium", - "t4g.large", - "t4g.xlarge", - "t4g.2xlarge", - // GCP: https://cloud.google.com/compute/docs/machine-types#cpu-bursting - "f1-micro", - "g1-small", - "e2-micro", - "e2-small", - "e2-medium", - // Azure: https://docs.microsoft.com/en-us/azure/virtual-machines/sizes-b-series-burstable - "Standard_B1ls", - "Standard_B1s", - "Standard_B1ms", - "Standard_B2s", - "Standard_B2ms", - "Standard_B4ms", - "Standard_B8ms", - "Standard_B12ms", - "Standard_B16ms", - "Standard_B20ms", -]; -// VM types with 1GB of memory or less -// AWS: aws ec2 describe-instance-types --filters Name=memory-info.size-in-mib,Values=512,1024 | jq '.InstanceTypes[] | .InstanceType' -// GCP: gcloud compute machine-types list --filter="memoryMb:(512 1024)" --format json | jq '.[] | .name' -// Azure: az vm list-sizes -l westus | jq '.[] | if .memoryInMb <= 1024 then .name else "" end' -const SMALL_VM_TYPES: [&'static str; 13] = [ - "t4g.nano", - "t2.micro", - "t3.micro", - "t4g.micro", - "t3.nano", - "t2.nano", - "t3a.nano", - "t3a.micro", - "e2-micro", - "Standard_B1ls", - "Standard_B1s", - "Standard_A0", - "Basic_A0", -]; - -#[derive(Deserialize, Debug, Clone, Serialize)] -pub struct AWSCLICredentialsFile { - default: AWSCLICredentials, -} - -#[derive(Deserialize, Debug, Clone, Serialize)] -pub struct AWSCLICredentials { - aws_access_key_id: String, - aws_secret_access_key: String, -} - -#[allow(non_snake_case)] -#[derive(Deserialize, Debug, Clone, Serialize)] -pub struct AWSCredentials { - accessKeyId: String, - secretAccessKey: String, -} - -#[allow(non_snake_case)] -#[derive(Deserialize, Debug, Clone, Serialize)] -pub struct GCPCredentials { - privateKey: String, - clientEmail: String, - projectId: String, -} - -#[allow(non_snake_case)] -#[derive(Deserialize, Debug, Clone, Serialize)] -pub struct AzureCredentials { - applicationId: String, - secret: String, - subscriptionId: String, - directoryId: String, -} - -#[derive(Deserialize, Debug, Clone, Serialize)] -#[serde(untagged)] -pub enum CloudCredentials { - GCP(GCPCredentials), - AWS(AWSCredentials), - Azure(AzureCredentials), -} - -#[allow(non_snake_case)] -#[derive(Deserialize, Debug, Serialize)] -pub struct Credentials { - credentials: CloudCredentials, - cloudProvider: String, -} - -#[allow(non_snake_case)] -#[derive(Deserialize, Debug, Serialize)] -pub struct DeployConfig { - credentialsName: String, - #[serde(skip_serializing_if = "Option::is_none")] - region: Option, - #[serde(skip_serializing_if = "Option::is_none")] - vmType: Option, - #[serde(skip_serializing_if = "Option::is_none")] - minReplicas: Option, - #[serde(skip_serializing_if = "Option::is_none")] - maxReplicas: Option, -} - -#[allow(non_snake_case)] -#[derive(Deserialize, Debug, Serialize)] -pub struct Config { - credentials: CloudCredentials, - cloudProvider: String, - #[serde(skip_serializing_if = "Option::is_none")] - region: Option, - #[serde(skip_serializing_if = "Option::is_none")] - vmType: Option, - #[serde(skip_serializing_if = "Option::is_none")] - minReplicas: Option, - #[serde(skip_serializing_if = "Option::is_none")] - maxReplicas: Option, -} - -#[allow(non_snake_case)] -#[derive(Deserialize, Debug)] -struct App { - id: String, - url: String, - deployName: String, - status: String, - size: usize, - cloudConfigs: Vec, -} - -#[derive(Debug)] -pub enum PostV1Error { - Timeout, - Forbidden, - Conflict, - Unauthorized, - Other(String), -} - -const ALANDEPLOY_FILE: &str = "alandeploy.json"; -const CREDENTIALS_FILE: &str = ".alan/credentials.json"; - -fn get_aws_cli_creds() -> Result { - let home = std::env::var("HOME").unwrap(); - let file_name = &format!("{}/.aws/credentials", home); - let file = OpenOptions::new().read(true).open(file_name); - if let Err(err) = file { - return Err(err.to_string()); - } - let reader = BufReader::new(file.unwrap()); - match serde_ini::from_bufread(reader) { - Ok(creds) => Ok(creds), - Err(err) => Err(err.to_string()), - } -} - -pub async fn add_cred(cred_name: Option<&str>) -> String { - let mut credentials = get_creds(false).await; - let clouds = vec!["AWS".to_string(), "GCP".to_string(), "Azure".to_string()]; - let selection = - deploy_dialoguer::select_with_default("Pick cloud provider for the new Credential", &clouds, 0); - let cloud = &clouds[selection]; - let default = cred_name.unwrap_or(&cloud.to_lowercase()).to_string(); - let prompt = "Name for new Credential"; - let validator = |input: &String| -> Result<(), &str> { - if credentials.contains_key(input) { - Err("Credential name already exists") - } else { - Ok(()) - } - }; - let cred_name = if credentials.contains_key(&default) { - deploy_dialoguer::input_with_validation(prompt, validator) - } else { - deploy_dialoguer::input_with_default_and_validation(prompt, default, validator) - }; - let name = cred_name.to_string(); - match cloud.as_str() { - "AWS" => { - let aws_cli_creds = get_aws_cli_creds(); - let (access_key, secret) = if aws_cli_creds.is_ok() - && deploy_dialoguer::confirm_with_default( - "Default AWS CLI credentials found. Do you wish to use those?", - true, - ) { - let creds = aws_cli_creds.unwrap().default; - (creds.aws_access_key_id, creds.aws_secret_access_key) - } else { - let access_key: String = deploy_dialoguer::input("AWS Access Key ID"); - let secret: String = deploy_dialoguer::input("AWS Secret Access Key"); - (access_key, secret) - }; - credentials.insert( - cred_name, - Credentials { - credentials: CloudCredentials::AWS(AWSCredentials { - accessKeyId: access_key, - secretAccessKey: secret, - }), - cloudProvider: "AWS".to_owned(), - }, - ); - } - "GCP" => { - let project_id: String = deploy_dialoguer::input("GCP Project ID"); - let client_email: String = deploy_dialoguer::input("GCP Client Email"); - let private_key: String = deploy_dialoguer::input("GCP Private Key"); - let clean_private_key = private_key.replace("\\n", "\n"); - credentials.insert( - cred_name, - Credentials { - credentials: CloudCredentials::GCP(GCPCredentials { - privateKey: clean_private_key, - clientEmail: client_email, - projectId: project_id, - }), - cloudProvider: "GCP".to_owned(), - }, - ); - } - "Azure" => { - let application_id: String = deploy_dialoguer::input("Azure Application ID"); - let directory_id: String = deploy_dialoguer::input("Azure Directory ID"); - let subscription_id: String = deploy_dialoguer::input("Azure Subscription ID"); - let secret: String = deploy_dialoguer::input("Azure Secret"); - credentials.insert( - cred_name, - Credentials { - credentials: CloudCredentials::Azure(AzureCredentials { - applicationId: application_id, - subscriptionId: subscription_id, - directoryId: directory_id, - secret: secret, - }), - cloudProvider: "Azure".to_owned(), - }, - ); - } - _ => {} - } - update_cred_file(credentials).await; - println!("Successfully created {} Credential", style(&name).bold()); - name -} - -async fn update_cred_file(credentials: HashMap) { - let home = std::env::var("HOME").unwrap(); - let file_name = &format!("{}/{}", home, CREDENTIALS_FILE); - // Sets the option to create a new file, or open it if it already exists. - // Sets the option for truncating a previous file. - let file = OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(file_name); - let writer = BufWriter::new(file.unwrap()); - if let Err(err) = serde_json::to_writer_pretty(writer, &credentials) { - warn_and_exit!( - 1, - InvalidCredentialsFile, - "Failed to write to {}. Error: {}", - CREDENTIALS_FILE, - err - ) - .await - } -} - -async fn update_alandeploy_file(deploy_configs: HashMap>) { - let home = std::env::var("PWD").unwrap(); - let file_name = &format!("{}/{}", home, ALANDEPLOY_FILE); - // Sets the option to create a new file, or open it if it already exists. - // Sets the option for truncating a previous file. - let file = OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(file_name); - let writer = BufWriter::new(file.unwrap()); - if let Err(err) = serde_json::to_writer_pretty(writer, &deploy_configs) { - warn_and_exit!( - 1, - InvalidAnycloudFile, - "Failed to write to {}. Error: {}", - ALANDEPLOY_FILE, - err - ) - .await - } -} - -pub async fn edit_cred() { - let mut credentials = get_creds(false).await; - let cred_options = credentials.keys().cloned().collect::>(); - if cred_options.len() == 0 { - prompt_add_cred(true, None).await; - } - let selection = - deploy_dialoguer::select_with_default("Pick Credentials to edit", &cred_options, 0); - let name = &cred_options[selection]; - let cred = credentials.get(name).unwrap(); - let cred_name = name.to_string(); - match &cred.credentials { - CloudCredentials::AWS(cred) => { - let access_key: String = deploy_dialoguer::input_with_initial_text( - "AWS Access Key ID", - cred.accessKeyId.to_string(), - ); - let secret: String = deploy_dialoguer::input_with_initial_text( - "AWS Secret Access Key", - cred.secretAccessKey.to_string(), - ); - credentials.insert( - cred_name, - Credentials { - credentials: CloudCredentials::AWS(AWSCredentials { - accessKeyId: access_key, - secretAccessKey: secret, - }), - cloudProvider: "AWS".to_owned(), - }, - ); - } - CloudCredentials::GCP(cred) => { - let client_email: String = - deploy_dialoguer::input_with_initial_text("GCP Client Email", cred.clientEmail.to_string()); - let project_id: String = - deploy_dialoguer::input_with_initial_text("GCP Project ID", cred.projectId.to_string()); - let private_key: String = - deploy_dialoguer::input_with_initial_text("GCP Private Key", cred.privateKey.to_string()); - credentials.insert( - cred_name, - Credentials { - credentials: CloudCredentials::GCP(GCPCredentials { - privateKey: private_key, - clientEmail: client_email, - projectId: project_id, - }), - cloudProvider: "GCP".to_owned(), - }, - ); - } - CloudCredentials::Azure(cred) => { - let application_id: String = deploy_dialoguer::input_with_initial_text( - "Azure Application ID", - cred.applicationId.to_string(), - ); - let directory_id: String = deploy_dialoguer::input_with_initial_text( - "Azure Directory ID", - cred.directoryId.to_owned(), - ); - let subscription_id: String = deploy_dialoguer::input_with_initial_text( - "Azure Subscription ID", - cred.subscriptionId.to_string(), - ); - let secret: String = - deploy_dialoguer::input_with_initial_text("Azure Secret", cred.secret.to_string()); - credentials.insert( - cred_name, - Credentials { - credentials: CloudCredentials::Azure(AzureCredentials { - applicationId: application_id, - subscriptionId: subscription_id, - directoryId: directory_id, - secret: secret, - }), - cloudProvider: "Azure".to_owned(), - }, - ); - } - } - update_cred_file(credentials).await; - println!("Successfully edited {} Credentials", style(name).bold()); -} - -// prompt the user to create a deploy credentials if none exists -// or if the requested credentials name does not exists. -pub async fn prompt_add_cred(exit_on_done: bool, cred_name: Option<&str>) -> String { - let cred = match cred_name { - Some(cred_name) => { - let prompt = format!( - "No Credentials found with name {}. Let's create it?", - cred_name - ); - if deploy_dialoguer::confirm_with_default(&prompt, true) { - add_cred(Some(cred_name)).await - } else { - std::process::exit(0); - } - } - None => { - let prompt = "No Credentials have been created. Let's create one?"; - if deploy_dialoguer::confirm_with_default(prompt, true) { - add_cred(None).await - } else { - std::process::exit(0); - } - } - }; - if exit_on_done { - std::process::exit(0); - } - cred -} - -// prompt the user to create a deploy config if none exists -pub async fn prompt_add_config() { - let prompt = "No Deploy Configs have been created. Let's create one?"; - if deploy_dialoguer::confirm_with_default(prompt, true) { - add_deploy_config().await; - } - std::process::exit(0); -} - -pub async fn remove_cred() { - let mut creds = get_creds(false).await; - let cred_options = creds.keys().cloned().collect::>(); - if cred_options.len() == 0 { - prompt_add_cred(true, None).await; - }; - let selection = - deploy_dialoguer::select_with_default("Pick Credentials to remove", &cred_options, 0); - let cred_name = &cred_options[selection]; - creds.remove(cred_name).unwrap(); - update_cred_file(creds).await; - println!( - "Successfully removed {} Credentials", - style(cred_name).bold() - ); -} - -pub async fn list_creds() { - let credentials = get_creds(false).await; - if credentials.len() > 0 { - for (cred_name, cred) in credentials.into_iter() { - println!("\n{}", cred_name); - println!("{}", (0..cred_name.len()).map(|_| "-").collect::()); - match cred.credentials { - CloudCredentials::AWS(credential) => { - println!("AWS Access Key ID: {}", credential.accessKeyId); - println!("AWS Secret Access Key: {}", credential.secretAccessKey); - } - CloudCredentials::GCP(credential) => { - println!("GCP Project ID: {}", credential.projectId); - println!("GCP Client Email: {}", credential.clientEmail); - println!("GCP Private Key: {}", credential.privateKey); - } - CloudCredentials::Azure(credential) => { - println!("Azure Application ID: {}", credential.applicationId); - println!("Azure Directory ID: {}", credential.directoryId); - println!("Azure Subscription ID: {}", credential.subscriptionId); - println!("Azure Secret: {}", credential.secret); - } - } - } - } else { - prompt_add_cred(true, None).await; - } -} - -pub async fn add_deploy_config() { - let mut deploy_configs = get_deploy_configs().await; - let creds = get_creds(false).await; - let default = "staging".to_string(); - let prompt = "Name for new Deploy Config"; - let validator = |input: &String| -> Result<(), &str> { - if deploy_configs.contains_key(input) { - Err("Deploy Config name already exists") - } else { - Ok(()) - } - }; - let name = if deploy_configs.contains_key(&default) { - deploy_dialoguer::input_with_validation(prompt, validator) - } else { - deploy_dialoguer::input_with_default_and_validation(prompt, default, validator) - }; - let mut cloud_configs = Vec::new(); - if creds.len() == 0 { - prompt_add_cred(false, None).await; - } - let mut options = creds.keys().cloned().collect::>(); - let new_cred_idx = options.len(); - options.push("Create new Credentials".to_string()); - loop { - let selection = deploy_dialoguer::select_with_default("Pick Credentials to use", &options, 0); - let cred = if selection == new_cred_idx { - add_cred(None).await - } else { - options[selection].to_string() - }; - // TODO validate these fields? - let mut region = None; - if deploy_dialoguer::confirm_with_default( - "Do you want to choose a specific region for this Deploy Config?", - false, - ) { - let input_region: String = deploy_dialoguer::input("Region name"); - region = Some(input_region); - }; - let mut vm_type = None; - if deploy_dialoguer::confirm_with_default( - "Do you want to select which virtual machine type to use for this Deploy Config?", - false, - ) { - vm_type = get_some_vm_type_input(); - }; - cloud_configs.push(DeployConfig { - credentialsName: cred, - vmType: vm_type, - region, - minReplicas: None, - maxReplicas: None, - }); - let prompt = if creds.len() > 1 { - "Do you want to add another region or cloud provider to this Deploy Config?" - } else { - "Do you want to add another region to this Deploy Config?" - }; - if !deploy_dialoguer::confirm_with_default(prompt, false) { - break; - } - } - let prompt = if creds.len() > 1 { - "Minimum number of VMs per region or cloud" - } else { - "Minimum number of VMs per region" - }; - let replicas: String = deploy_dialoguer::input_with_default(prompt, "1".to_string()); - let min_replicas: Option = Some(replicas.parse::().unwrap_or_else(|_| { - eprintln!("{} is not a valid number of VMs", replicas); - std::process::exit(1); - })); - let mut max_replicas = None; - let prompt = "Would you like to define a maximum number of VMs?"; - if deploy_dialoguer::confirm_with_default(prompt, false) { - let prompt = if creds.len() > 1 { - "Maximum number of VMs per region or cloud" - } else { - "Maximum number of VMs per region" - }; - let replicas: String = deploy_dialoguer::input(prompt); - if let Ok(replicas) = replicas.parse::() { - max_replicas = Some(replicas); - } else { - eprintln!("{} is not a valid number of VMs", replicas); - std::process::exit(1); - } - } - cloud_configs = cloud_configs - .into_iter() - .map(|mut c| { - c.minReplicas = min_replicas; - c.maxReplicas = max_replicas; - c - }) - .collect(); - deploy_configs.insert(name.to_string(), cloud_configs); - update_alandeploy_file(deploy_configs).await; - println!("Successfully created {} Deploy Config.", style(name).bold()); -} - -pub async fn edit_deploy_config() { - let mut deploy_configs = get_deploy_configs().await; - let config_names = deploy_configs.keys().cloned().collect::>(); - if config_names.len() == 0 { - prompt_add_config().await; - } - let selection = - deploy_dialoguer::select_with_default("Pick Deploy Config to edit", &config_names, 0); - let config_name = config_names[selection].to_string(); - let creds = get_creds(false).await; - let cloud_configs: &Vec = deploy_configs.get(&config_name).unwrap(); - let mut new_cloud_configs = Vec::new(); - let cred_options = creds.keys().cloned().collect::>(); - for config in cloud_configs { - let index = cred_options - .iter() - .position(|r| r == &config.credentialsName) - .unwrap(); - let selection = - deploy_dialoguer::select_with_default("Pick Credentials to use", &cred_options, index); - let cred = cred_options[selection].to_string(); - let mut region = None; - let mut vm_type = None; - if let Some(reg) = &config.region { - if deploy_dialoguer::confirm_with_default( - "Do you want to edit the region to use for this Deploy Config?", - true, - ) { - let input_region: String = deploy_dialoguer::input("Region name"); - region = Some(input_region); - } else { - region = Some(reg.to_string()); - } - } else { - if deploy_dialoguer::confirm_with_default( - "Do you want to choose a specific region for this Deploy Config?", - false, - ) { - let input_region: String = deploy_dialoguer::input("Region name"); - region = Some(input_region); - }; - } - if let Some(vm_t) = &config.vmType { - if deploy_dialoguer::confirm_with_default( - "Do you want to edit the virtual machine type for this Deploy Config?", - true, - ) { - vm_type = get_some_vm_type_input(); - } else { - vm_type = Some(vm_t.to_string()); - } - } else { - if deploy_dialoguer::confirm_with_default( - "Do you want to select which virtual machine type to use for this Deploy Config?", - false, - ) { - vm_type = get_some_vm_type_input(); - }; - } - new_cloud_configs.push(DeployConfig { - credentialsName: cred, - vmType: vm_type, - region, - minReplicas: None, - maxReplicas: None, - }); - } - let prompt = if creds.len() > 1 { - "Minimum number of VMs per region or cloud" - } else { - "Minimum number of VMs per region" - }; - let replicas: String = deploy_dialoguer::input_with_default(prompt, "1".to_string()); - let min_replicas: Option = Some(replicas.parse::().unwrap_or_else(|_| { - eprintln!("{} is not a valid number of VMs", replicas); - std::process::exit(1); - })); - let mut max_replicas = None; - let prompt = "Would you like to define a maximum number of VMs?"; - if deploy_dialoguer::confirm_with_default(prompt, false) { - let prompt = if creds.len() > 1 { - "Maximum number of VMs per region or cloud" - } else { - "Maximum number of VMs per region" - }; - let replicas: String = deploy_dialoguer::input(prompt); - if let Ok(replicas) = replicas.parse::() { - max_replicas = Some(replicas); - } else { - eprintln!("{} is not a valid number of VMs", replicas); - std::process::exit(1); - } - } - new_cloud_configs = new_cloud_configs - .into_iter() - .map(|mut c| { - c.minReplicas = min_replicas; - c.maxReplicas = max_replicas; - c - }) - .collect(); - deploy_configs.insert(config_name.to_string(), new_cloud_configs); - update_alandeploy_file(deploy_configs).await; - println!( - "Successfully edited {} Deploy Config.", - style(config_name).bold() - ); -} - -pub async fn remove_deploy_config() { - let mut deploy_configs = get_deploy_configs().await; - let config_names = deploy_configs.keys().cloned().collect::>(); - if config_names.len() == 0 { - prompt_add_config().await; - } - let selection = - deploy_dialoguer::select_with_default("Pick Deploy Config to remove", &config_names, 0); - let config_name = config_names[selection].to_string(); - deploy_configs.remove(&config_name); - update_alandeploy_file(deploy_configs).await; - println!( - "Successfully removed {} Deploy Config.", - style(config_name).bold() - ); -} - -pub async fn list_deploy_configs() { - let mut table = AsciiTable::default(); - table.max_width = 140; - let configs = get_deploy_configs().await; - if configs.len() == 0 { - prompt_add_config().await; - } - let mut data: Vec> = vec![]; - for (name, config) in &mut configs.iter() { - for (i, c) in config.iter().enumerate() { - let mut display_vec: Vec<&dyn Display> = Vec::new(); - if i == 0 { - display_vec.push(name); - } else { - display_vec.push(&""); - }; - display_vec.push(&c.credentialsName); - if let Some(region) = &c.region { - display_vec.push(region); - } - if let Some(vm_type) = &c.vmType { - display_vec.push(vm_type); - } - data.push(display_vec) - } - } - - let column = Column { - header: "Name".into(), - ..Column::default() - }; - table.columns.insert(0, column); - - let column = Column { - header: "Credentials Name".into(), - ..Column::default() - }; - table.columns.insert(1, column); - - let column = Column { - header: "Region".into(), - ..Column::default() - }; - table.columns.insert(2, column); - - let column = Column { - header: "VM Type".into(), - ..Column::default() - }; - table.columns.insert(3, column); - - if configs.len() > 0 { - println!("\nDeployment configurations:\n"); - table.print(data); - } else { - println!("No Deploy Configs to display from alandeploy.json.") - } -} - -async fn get_creds(non_interactive: bool) -> HashMap { - if non_interactive { - let mut credentials = HashMap::new(); - let cred_name = match std::env::var("CREDENTIALS_NAME") { - Ok(name) => name, - Err(_) => warn_and_exit!(1, InvalidEnvVar, "No CREDENTIALS_NAME defined").await, - }; - match std::env::var("CLOUD_NAME") { - Ok(cloud) => match cloud.as_str() { - "AWS" => { - let access_key: String = std::env::var("AWS_ACCESS_KEY").unwrap_or("".to_string()); - let secret: String = std::env::var("AWS_SECRET").unwrap_or("".to_string()); - if access_key.is_empty() || secret.is_empty() { - warn_and_exit!( - 1, - InvalidEnvVar, - "No AWS environment variables defined (AWS_ACCESS_KEY, AWS_SECRET)." - ) - .await - } - credentials.insert( - cred_name, - Credentials { - credentials: CloudCredentials::AWS(AWSCredentials { - accessKeyId: access_key, - secretAccessKey: secret, - }), - cloudProvider: "AWS".to_owned(), - }, - ); - } - "GCP" => { - let project_id: String = std::env::var("GCP_PROJECT_ID").unwrap_or("".to_string()); - let client_email: String = std::env::var("GCP_CLIENT_EMAIL").unwrap_or("".to_string()); - let private_key: String = std::env::var("GCP_PRIVATE_KEY").unwrap_or("".to_string()); - if project_id.is_empty() || client_email.is_empty() || private_key.is_empty() { - warn_and_exit!(1, InvalidEnvVar, "No GCP environment variables defined (GCP_PROJECT_ID, GCP_CLIENT_EMAIL, GCP_PRIVATE_KEY).").await - } - let clean_private_key = private_key.replace("\\n", "\n"); - credentials.insert( - cred_name, - Credentials { - credentials: CloudCredentials::GCP(GCPCredentials { - privateKey: clean_private_key, - clientEmail: client_email, - projectId: project_id, - }), - cloudProvider: "GCP".to_owned(), - }, - ); - } - "Azure" => { - let application_id: String = std::env::var("AZ_APP_ID").unwrap_or("".to_string()); - let directory_id: String = std::env::var("AZ_DIRECTORY_ID").unwrap_or("".to_string()); - let subscription_id: String = - std::env::var("AZ_SUBSCRIPTION_ID").unwrap_or("".to_string()); - let secret: String = std::env::var("AZ_SECRET").unwrap_or("".to_string()); - if application_id.is_empty() - || directory_id.is_empty() - || subscription_id.is_empty() - || secret.is_empty() - { - warn_and_exit!(1, InvalidEnvVar, "No Azure environment variables defined (AZ_APP_ID, AZ_DIRECTORY_ID, AZ_SUBSCRIPTION_ID, AZ_SECRET).").await - } - credentials.insert( - cred_name, - Credentials { - credentials: CloudCredentials::Azure(AzureCredentials { - applicationId: application_id, - subscriptionId: subscription_id, - directoryId: directory_id, - secret: secret, - }), - cloudProvider: "Azure".to_owned(), - }, - ); - } - _ => {} - }, - Err(_) => { - warn_and_exit!(1, InvalidCredentialsFile, "No CLOUD_NAME defined").await; - } - } - return credentials; - } - let home = std::env::var("HOME").unwrap(); - let file_name = &format!("{}/{}", home, CREDENTIALS_FILE); - let file = OpenOptions::new().read(true).open(file_name); - if let Err(_) = file { - return HashMap::new(); - } - let reader = BufReader::new(file.unwrap()); - let creds = serde_json::from_reader(reader); - if let Err(err) = creds { - warn_and_exit!( - 1, - InvalidCredentialsFile, - "Failed to read from {}. Error: {}", - CREDENTIALS_FILE, - err - ) - .await - } else { - creds.unwrap() - } -} - -async fn get_deploy_configs() -> HashMap> { - let home = std::env::var("PWD").unwrap(); - let file_name = &format!("{}/{}", home, ALANDEPLOY_FILE); - let file = OpenOptions::new().read(true).open(file_name); - if let Err(_) = file { - return HashMap::new(); - } - let reader = BufReader::new(file.unwrap()); - let config = serde_json::from_reader(reader); - if let Err(err) = config { - warn_and_exit!( - 1, - InvalidAnycloudFile, - "Failed to read from {}. Error: {}", - ALANDEPLOY_FILE, - err - ) - .await - } else { - config.unwrap() - } -} - -// This method can be called as a binary by the end user in the CLI or as a library by the Alan daemon -// to send stats to the deploy service. We default to production so that it works as-is when it is used -// as a binary and we override the value it can have to our needs -fn get_url() -> &'static str { - let env = std::env::var("ALAN_TECH_ENV").unwrap_or("production".to_string()); - match env.as_str() { - "local" => "http://localhost:8080", - "staging" => "https://deploy-staging.alantechnologies.com", - _ => "https://deploy.alantechnologies.com", - } -} - -pub async fn get_config(config_name: &str, non_interactive: bool) -> HashMap> { - let alandeploy_prof = get_deploy_configs().await; - let mut creds = get_creds(non_interactive).await; - if creds.len() == 0 && !non_interactive { - prompt_add_cred(true, None).await; - } else if creds.len() == 0 && non_interactive { - warn_and_exit!(1, NoCredentials, "No credentials defined").await - } - if alandeploy_prof.len() == 0 && !non_interactive { - prompt_add_config().await; - } else if alandeploy_prof.len() == 0 && non_interactive { - warn_and_exit!(1, NoDeployConfig, "No configuration defined").await - } - let mut all_configs = HashMap::new(); - for (deploy_name, deploy_configs) in alandeploy_prof.into_iter() { - let mut configs = Vec::new(); - for deploy_config in deploy_configs { - let cred_name = &deploy_config.credentialsName; - let cred = match creds.get(cred_name) { - Some(cred) => cred, - None => { - if config_name.is_empty() && non_interactive { - // Case when is non interactive and there is no config name specified. - // Should be caught earlier but in case we arrive here we are not interested in ask for credentials. - continue; - } else if !config_name.is_empty() { - if non_interactive && &deploy_name != config_name { - // If it is not the one we are interested in, continue. - continue; - } else if non_interactive && &deploy_name == config_name { - // If no credentials found for the config specified in non interactive mode we warn and exit. - warn_and_exit!( - 1, - NoCredentials, - "Non interactive mode. No credentials defined for desired config {}", - config_name - ) - .await; - } - }; - let cred: &Credentials; - loop { - prompt_add_cred(false, Some(cred_name)).await; - creds = get_creds(false).await; - cred = match creds.get(cred_name) { - Some(cred) => cred, - None => continue, - }; - break; - } - cred - } - }; - configs.push(Config { - credentials: cred.credentials.clone(), - cloudProvider: cred.cloudProvider.to_string(), - region: deploy_config.region, - vmType: deploy_config.vmType, - minReplicas: deploy_config.minReplicas, - maxReplicas: deploy_config.maxReplicas, - }); - } - all_configs.insert(deploy_name, configs); - } - all_configs -} - -pub async fn post_v1(endpoint: &str, mut body: Value) -> Result { - let url = get_url(); - let mut_body = body.as_object_mut().unwrap(); - mut_body.insert(format!("accessToken"), json!(get_token())); - mut_body.insert(format!("alanVersion"), json!(format!("v{}", VERSION))); - mut_body.insert(format!("osName"), json!(std::env::consts::OS)); - let req = Request::post(format!("{}/v1/{}", url, endpoint)) - .header("Content-Type", "application/json") - .body(body.to_string().into()); - let req = match req { - Ok(req) => req, - Err(e) => return Err(PostV1Error::Other(e.to_string())), - }; - let resp = CLIENT.request(req).await; - let mut resp = match resp { - Ok(resp) => resp, - Err(e) => return Err(PostV1Error::Other(e.to_string())), - }; - let data = hyper::body::to_bytes(resp.body_mut()).await; - let data = match data { - Ok(data) => data, - Err(e) => return Err(PostV1Error::Other(e.to_string())), - }; - let data_str = String::from_utf8(data.to_vec()); - let data_str = match data_str { - Ok(data_str) => data_str, - Err(e) => return Err(PostV1Error::Other(e.to_string())), - }; - return match resp.status() { - st if st.is_success() => Ok(data_str), - StatusCode::REQUEST_TIMEOUT => Err(PostV1Error::Timeout), - StatusCode::FORBIDDEN => Err(PostV1Error::Forbidden), - StatusCode::CONFLICT => Err(PostV1Error::Conflict), - _ => Err(PostV1Error::Other(data_str.to_string())), - }; -} - -pub async fn client_error(err_code: ErrorType, message: &str, level: &str) { - let mut body = json!({ - "errorCode": err_code as u64, - "message": message, - "level": level, - }); - if let Some(cluster_id) = CLUSTER_ID.get() { - body - .as_object_mut() - .unwrap() - .insert(format!("clusterId"), json!(cluster_id)); - } - let _resp = post_v1("clientError", body).await; -} - -pub async fn terminate( - app_name: Option, - config_name: Option, - non_interactive: bool, -) { - let interactive = !non_interactive; - let app_name = if let Some(app_name) = app_name { - app_name - } else { - "".to_string() - }; - let config_name = if let Some(config_name) = config_name { - config_name - } else { - "".to_string() - }; - let mut sp = ProgressBar::new_spinner(); - sp.enable_steady_tick(10); - sp.set_message("Gathering information about Apps deployed"); - let apps = get_apps(false, &config_name, non_interactive).await; - sp.finish_and_clear(); - if apps.len() == 0 { - println!("No Apps deployed"); - std::process::exit(0); - } - let ids = apps.into_iter().map(|a| a.id).collect::>(); - let selection: usize = if app_name.is_empty() && interactive { - deploy_dialoguer::select_with_default("Pick App to terminate", &ids, 0) - } else if app_name.is_empty() && non_interactive { - warn_and_exit!( - 1, - NoAppNameDefined, - "Non interactive mode. No app name provided to terminate." - ) - .await - } else { - match ids.iter().position(|id| &app_name == id) { - Some(pos) => pos, - None => { - warn_and_exit!( - 1, - NoAppNameDefined, - "No app name found with name {}.", - app_name - ) - .await - } - } - }; - let cluster_id = &ids[selection]; - CLUSTER_ID.set(cluster_id.to_string()).unwrap(); - let styled_cluster_id = style(cluster_id).bold(); - sp = ProgressBar::new_spinner(); - sp.enable_steady_tick(10); - sp.set_message(&format!("Terminating App {}", styled_cluster_id)); - let body = json!({ - "deployConfig": get_config(&config_name, non_interactive).await, - "clusterId": cluster_id, - }); - let resp = post_v1("terminate", body).await; - let res = match resp { - Ok(_) => { - poll(&sp, || async { - get_apps(false, &config_name, non_interactive) - .await - .into_iter() - .find(|app| &app.id == cluster_id) - .is_none() - }) - .await - } - Err(err) => match err { - PostV1Error::Timeout => format!("{}", REQUEST_TIMEOUT), - PostV1Error::Forbidden => format!("{}", FORBIDDEN_OPERATION), - PostV1Error::Conflict => format!( - "Failed to terminate App {}. Error: {}", - cluster_id, NAME_CONFLICT - ), - PostV1Error::Unauthorized => { - clear_token(); - format!("{}", UNAUTHORIZED_OPERATION) - } - PostV1Error::Other(err) => format!( - "Failed to terminate App {}. Error: {}", - styled_cluster_id, err - ), - }, - }; - sp.finish_with_message(&res); -} - -pub async fn new( - agz_b64: String, - files_b64: HashMap, - app_name: Option, - config_name: Option, - non_interactive: bool, - non_http: bool, -) { - let interactive = !non_interactive; - let app_name = if let Some(app_name) = app_name { - app_name - } else { - "".to_string() - }; - let config_name = if let Some(config_name) = config_name { - config_name - } else { - "".to_string() - }; - if !app_name.is_empty() { - // Check if app exists - let apps = get_apps(false, &config_name, non_interactive).await; - let ids = apps.into_iter().map(|a| a.id).collect::>(); - let app_exists: bool = match ids.iter().position(|id| &app_name == id) { - Some(_) => true, - None => false, - }; - if app_exists { - println!("App name {} already exists. Upgrading app...", app_name); - upgrade( - agz_b64, - files_b64, - if app_name.is_empty() { - None - } else { - Some(app_name.to_string()) - }, - if config_name.is_empty() { - None - } else { - Some(config_name.to_string()) - }, - non_interactive, - non_http, - ) - .await; - return; - } - }; - let config = get_config(&config_name, non_interactive).await; - let config_names = config.keys().cloned().collect::>(); - if config_names.len() == 0 && interactive { - prompt_add_config().await; - } else if config_names.len() == 0 && non_interactive { - warn_and_exit!( - 1, - NoDeployConfig, - "Non interactive mode. No deploy configuration found." - ) - .await - } - let selection: usize = if config_name.is_empty() && interactive { - deploy_dialoguer::select_with_default("Pick Deploy Config for App", &config_names, 0) - } else if config_name.is_empty() && non_interactive { - warn_and_exit!( - 1, - NoDeployConfig, - "Non interactive mode. No deploy configuration selected." - ) - .await - } else { - match config_names.iter().position(|n| &config_name == n) { - Some(pos) => pos, - None => { - warn_and_exit!( - 1, - NoDeployConfig, - "No deploy configuration found with name {}.", - config_name - ) - .await - } - } - }; - let deploy_config = &config_names[selection]; - if app_name.is_empty() && non_interactive { - warn_and_exit!( - 1, - NoAppNameDefined, - "Non interactive mode. No app name provided to upgrade." - ) - .await; - } - let app_id = if app_name.is_empty() && interactive { - deploy_dialoguer::input_with_allow_empty_as_result("Optional App name", true).unwrap() - } else { - app_name - }; - let sp = ProgressBar::new_spinner(); - sp.enable_steady_tick(10); - sp.set_message("Creating new App"); - let body = json!({ - "agzB64": agz_b64, - "appId": app_id, - "deployConfig": config, - "deployName": deploy_config, - "filesB64": files_b64, - "nonHttp": non_http, - }); - let resp = post_v1("new", body).await; - let res = match &resp { - Ok(res) => { - // idc if it's been set before, I'm setting it now!!! - let _ = CLUSTER_ID.set(res.to_string()); - poll(&sp, || async { - get_apps(true, &config_name, non_interactive) - .await - .into_iter() - .find(|app| &app.id == res) - .map(|app| app.status == "up") - .unwrap_or(false) - }) - .await - } - Err(err) => match err { - PostV1Error::Timeout => format!("{}", REQUEST_TIMEOUT), - PostV1Error::Forbidden => format!("{}", FORBIDDEN_OPERATION), - PostV1Error::Conflict => format!("Failed to create a new App. Error: {}", NAME_CONFLICT), - PostV1Error::Unauthorized => { - clear_token(); - format!("{}", UNAUTHORIZED_OPERATION) - } - PostV1Error::Other(err) => format!("Failed to create a new App. Error: {}", err), - }, - }; - sp.finish_with_message(&res); -} - -pub async fn upgrade( - agz_b64: String, - files_b64: HashMap, - app_name: Option, - config_name: Option, - non_interactive: bool, - non_http: bool, -) { - let interactive = !non_interactive; - let app_name = if let Some(app_name) = app_name { - app_name - } else { - "".to_string() - }; - let config_name = if let Some(config_name) = config_name { - config_name - } else { - "".to_string() - }; - let mut sp = ProgressBar::new_spinner(); - sp.enable_steady_tick(10); - sp.set_message("Gathering information about Apps deployed"); - let apps = get_apps(false, &config_name, non_interactive).await; - sp.finish_and_clear(); - if apps.len() == 0 { - println!("No Apps deployed"); - std::process::exit(0); - } - let (ids, sizes): (Vec, Vec) = apps.into_iter().map(|a| (a.id, a.size)).unzip(); - let selection: usize = if app_name.is_empty() && interactive { - deploy_dialoguer::select_with_default("Pick App to upgrade", &ids, 0) - } else if app_name.is_empty() && non_interactive { - warn_and_exit!( - 1, - NoAppNameDefined, - "Non interactive mode. No app name provided to upgrade." - ) - .await - } else { - match ids.iter().position(|id| &app_name == id) { - Some(pos) => pos, - None => { - warn_and_exit!( - 1, - NoAppNameDefined, - "No app name found with name {}.", - app_name - ) - .await - } - } - }; - let cluster_id = &ids[selection]; - CLUSTER_ID.set(cluster_id.to_string()).unwrap(); - let styled_cluster_id = style(cluster_id).bold(); - let config = get_config(&config_name, non_interactive).await; - sp = ProgressBar::new_spinner(); - sp.enable_steady_tick(10); - sp.set_message(&format!("Upgrading App {}", styled_cluster_id)); - let body = json!({ - "agzB64": agz_b64, - "clusterId": cluster_id, - "deployConfig": config, - "filesB64": files_b64, - "nonHttp": non_http, - }); - let resp = post_v1("upgrade", body).await; - let res = match resp { - Ok(_) => { - // Check every 10s over 5 min if app already start upgrading - let mut counter: u8 = 0; - while counter < 30 { - let is_upgrading = get_apps(true, &config_name, non_interactive) - .await - .into_iter() - .find(|app| &app.id == cluster_id) - .map(|app| app.status == "down") - .unwrap_or(false); - if is_upgrading { - break; - } - counter += 1; - tokio::time::sleep(Duration::from_secs(10)).await; - } - if counter == 30 { - format!( - "Could not find any information related to {}, try again later.", - cluster_id - ) - } else { - poll(&sp, || async { - get_apps(false, &config_name, non_interactive) - .await - .into_iter() - .find(|app| &app.id == cluster_id) - .map(|app| app.size == sizes[selection]) - .unwrap_or(false) - }) - .await - } - } - Err(err) => match err { - PostV1Error::Timeout => format!("{}", REQUEST_TIMEOUT), - PostV1Error::Forbidden => format!("{}", FORBIDDEN_OPERATION), - PostV1Error::Conflict => format!("Failed to create a new app. Error: {}", NAME_CONFLICT), - PostV1Error::Unauthorized => { - clear_token(); - format!("{}", UNAUTHORIZED_OPERATION) - } - PostV1Error::Other(err) => format!("Failed to create a new app. Error: {}", err), - }, - }; - sp.finish_with_message(&res); -} - -async fn get_apps(status: bool, config_name: &str, non_interactive: bool) -> Vec { - let config = get_config(config_name, non_interactive).await; - let body = json!({ - "deployConfig": config, - "status": status, - }); - let response = post_v1("info", body).await; - let resp = match &response { - Ok(resp) => resp, - Err(err) => { - match err { - PostV1Error::Timeout => { - eprintln!("{}", REQUEST_TIMEOUT); - } - PostV1Error::Forbidden => { - eprintln!("{}", FORBIDDEN_OPERATION); - } - PostV1Error::Conflict => { - eprintln!( - "Displaying status for Apps failed with error: {}", - NAME_CONFLICT - ); - } - PostV1Error::Unauthorized => { - clear_token(); - eprintln!("{}", UNAUTHORIZED_OPERATION); - } - PostV1Error::Other(err) => { - eprintln!("Displaying status for Apps failed with error: {}", err); - } - } - std::process::exit(1); - } - }; - serde_json::from_str(resp).unwrap() -} - -pub async fn info() { - let sp = ProgressBar::new_spinner(); - sp.enable_steady_tick(10); - sp.set_message("Gathering information about Apps deployed"); - let mut apps = get_apps(true, "", false).await; - sp.finish_and_clear(); - if apps.len() == 0 { - println!("No Apps deployed"); - std::process::exit(0); - } - - let mut clusters = AsciiTable::default(); - clusters.max_width = 140; - - let column = Column { - header: "App ID".into(), - ..Column::default() - }; - clusters.columns.insert(0, column); - - let column = Column { - header: "Url".into(), - ..Column::default() - }; - clusters.columns.insert(1, column); - - let column = Column { - header: "Deploy Config".into(), - ..Column::default() - }; - clusters.columns.insert(2, column); - - let column = Column { - header: "Size".into(), - ..Column::default() - }; - clusters.columns.insert(3, column); - - let column = Column { - header: "Status".into(), - ..Column::default() - }; - clusters.columns.insert(4, column); - - let mut app_data: Vec> = vec![]; - let mut profile_data: Vec> = vec![]; - let mut deploy_profiles = HashSet::new(); - for app in &mut apps { - app_data.push(vec![ - &app.id, - &app.url, - &app.deployName, - &app.size, - &app.status, - ]); - if deploy_profiles.contains(&app.deployName) { - continue; - } - for (i, profile) in app.cloudConfigs.iter().enumerate() { - let mut display_vec: Vec<&dyn Display> = Vec::new(); - if i == 0 { - display_vec.push(&app.deployName); - } else { - display_vec.push(&""); - }; - if let Some(region) = &profile.region { - display_vec.push(region); - } - if let Some(vm_type) = &profile.vmType { - display_vec.push(vm_type); - } - profile_data.push(display_vec) - } - deploy_profiles.insert(&app.deployName); - } - - println!("Apps deployed:\n"); - clusters.print(app_data); - - let mut profiles = AsciiTable::default(); - profiles.max_width = 140; - - let column = Column { - header: "Deploy Config".into(), - ..Column::default() - }; - profiles.columns.insert(0, column); - - let column = Column { - header: "Region".into(), - ..Column::default() - }; - profiles.columns.insert(1, column); - - let column = Column { - header: "VM Type".into(), - ..Column::default() - }; - profiles.columns.insert(2, column); - println!("\nDeploy Configs used:\n"); - profiles.print(profile_data); -} - -pub async fn poll(sp: &ProgressBar, terminator: Callback) -> String -where - Callback: Fn() -> CallbackFut, - CallbackFut: Future, -{ - let body = json!({ - "clusterId": CLUSTER_ID.get().expect("cluster ID not set..."), - }); - let mut lines: Vec = vec![]; - let mut is_done = false; - const DEFAULT_SLEEP_DURATION: Duration = Duration::from_secs(10); - let mut sleep_override = None; - while !is_done { - is_done = terminator().await; - if is_done { - sleep_override = Some(Duration::from_secs(0)); - } - let logs = match post_v1("logs", body.clone()).await { - Ok(logs) => logs, - Err(err) => { - if let Some(last_line) = lines.get(lines.len() - 1) { - sp.println(last_line); - } - return match err { - PostV1Error::Timeout => REQUEST_TIMEOUT.to_string(), - PostV1Error::Forbidden => FORBIDDEN_OPERATION.to_string(), - PostV1Error::Unauthorized => UNAUTHORIZED_OPERATION.to_string(), - PostV1Error::Conflict => { - clear_token(); - NAME_CONFLICT.to_string() - } - PostV1Error::Other(err) => format!("Unexpected error: {}", err), - }; - } - }; - // it's ok to leave out the newline chars, since `sp.println` will insert - // those for us - let new_lines = logs.split("\n").skip(lines.len()).collect::>(); - // update the spinner and lines above the spinner - new_lines - .into_iter() - .filter(|new_line| !new_line.is_empty()) - .for_each(|new_line| { - // print latest line if any. - // Will not print multiple times the same line since here we are adding a new one - // If no new lines, this iter will not execute and will not duplicate lines as if we put this check outside - if lines.len() > 0 { - if let Some(last_line) = lines.get(lines.len() - 1) { - sp.println(last_line); - } - }; - sp.set_message(new_line); - lines.push(new_line.to_string()); - }); - tokio::time::sleep(match sleep_override.take() { - None => DEFAULT_SLEEP_DURATION, - Some(sleep_override) => sleep_override, - }) - .await; - } - lines.pop().unwrap_or_default() -} - -fn is_burstable(vm_type: &str) -> bool { - BURSTABLE_VM_TYPES.contains(&vm_type) -} - -fn is_small(vm_type: &str) -> bool { - SMALL_VM_TYPES.contains(&vm_type) -} - -fn print_vm_type_warns(vm_type: &str) -> () { - if is_burstable(vm_type) { - print_burstable_vm_warn(); - } - if is_small(vm_type) { - print_small_vm_warn(); - } -} - -fn print_burstable_vm_warn() -> () { - println!( - "WARNING: You have selected a burstable virtual machine type. \ - These virtual machine types can misbehave under heavy load and \ - do not work correctly with our automatic scale." - ) -} - -// Warn if user choose a machine type with 1GB or less memory -fn print_small_vm_warn() -> () { - println!( - "WARNING: You have selected a virtual machine type that is too small. \ - These virtual machine types can underperform and take more time to start." - ) -} - -fn get_some_vm_type_input() -> Option { - loop { - let input_vm_type: String = deploy_dialoguer::input("Virtual machine type"); - if is_burstable(&input_vm_type) || is_small(&input_vm_type) { - print_vm_type_warns(&input_vm_type); - if deploy_dialoguer::confirm_with_default( - "Are you sure you want to continue with the selected virtual machine type?", - false, - ) { - return Some(input_vm_type); - } - } else { - return Some(input_vm_type); - } - } -} diff --git a/avm/src/cloud/http/mod.rs b/avm/src/cloud/http/mod.rs deleted file mode 100644 index 830d9a639..000000000 --- a/avm/src/cloud/http/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -use hyper::{ - client::{Client, HttpConnector}, - Body, -}; -use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder}; -use once_cell::sync::Lazy; - -pub static CLIENT: Lazy>> = Lazy::new(|| { - Client::builder().build::<_, Body>( - HttpsConnectorBuilder::new() - .with_native_roots() - .https_or_http() - .enable_all_versions() - .build(), - ) -}); diff --git a/avm/src/cloud/logger/mod.rs b/avm/src/cloud/logger/mod.rs deleted file mode 100644 index 9228a0221..000000000 --- a/avm/src/cloud/logger/mod.rs +++ /dev/null @@ -1,65 +0,0 @@ -#[repr(u64)] -pub enum ErrorType { - InvalidPwd = 100, - NoEnvFile = 101, - GitChanges = 102, - NoGit = 103, - DeleteTmpAppTar = 104, - NoDockerFile = 105, - InvalidCredentialsFile = 108, - InvalidAnycloudFile = 110, - AuthFailed = 113, - NoDnsVms = 114, - NoStats = 115, - NoClusterSecret = 116, - NoDns = 117, - NoPrivateIp = 118, - NoDnsPrivateIp = 119, - ScaleFailed = 120, - PostFailed = 121, - RunAgzFailed = 122, - NoDaemonProps = 128, - DaemonStartFailed = 129, - CtrlPortStartFailed = 130, - NoSSLCert = 131, - DuplicateDnsPrivateIp = 132, - NoDaemonAGZFile = 133, - NoTarballFile = 134, - InvalidEnvVar = 135, - NoCredentials = 136, - NoDeployConfig = 137, - NoAppNameDefined = 138, - UnexpectedError = 139, - ApplicationError = 140, - NoTmpDir = 141, -} - -#[macro_export] -macro_rules! error { - ($errCode:ident, $($message:tt)+) => {async{ - let err_type = $crate::cloud::logger::ErrorType::$errCode; - eprintln!($($message)+); - $crate::cloud::deploy::client_error(err_type, &format!($($message)+), "error").await; - }}; - (metadata: $metadata:tt, $errCode:ident, $($message:tt)+) => {async{ - let err_type = $crate::cloud::logger::ErrorType::$errCode; - let value = json!($metadata); - eprintln!($($message)+); - $crate::cloud::deploy::client_error(err_type, &format!($($message)+), "error").await; - }} -} - -#[macro_export] -macro_rules! warn { - ($errCode:ident, $($message:tt)+) => { - let err_type = $crate::cloud::logger::ErrorType::$errCode; - eprintln!($($message)+); - $crate::cloud::deploy::client_error(err_type, &format!($($message)+), "warn").await; - }; - (metadata: $metadata:tt, $errCode:ident, $($message:tt)+) => { - let err_type = $crate::cloud::logger::ErrorType::$errCode; - let value = json!($metadata); - eprintln!($($message)+); - $crate::cloud::deploy::client_error(err_type, &format!($($message)+), "warn").await; - }; -} diff --git a/avm/src/cloud/mod.rs b/avm/src/cloud/mod.rs deleted file mode 100644 index e90fbd6c1..000000000 --- a/avm/src/cloud/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -use once_cell::sync::OnceCell; - -pub static CLUSTER_ID: OnceCell = OnceCell::new(); -// Macros need to be defined before used -#[macro_use] -pub mod logger; -pub mod common; -pub mod deploy; -pub mod http; -pub mod oauth; diff --git a/avm/src/cloud/oauth/mod.rs b/avm/src/cloud/oauth/mod.rs deleted file mode 100644 index 385327606..000000000 --- a/avm/src/cloud/oauth/mod.rs +++ /dev/null @@ -1,172 +0,0 @@ -use std::fs::{create_dir, read_to_string, remove_file, File}; -use std::io::prelude::*; -use std::path::Path; - -use dialoguer::{console::style, theme::ColorfulTheme, Confirm}; -use hyper::Request; -use once_cell::sync::OnceCell; -use serde_json::{json, Value}; -use tokio::time::{sleep, Duration}; -use webbrowser; - -use crate::cloud::http::CLIENT; - -const CODE_URL: &'static str = "https://github.com/login/device/code"; -const CLIENT_ID: &'static str = "f6e1ede88556627925d6"; -const GRANT_TYPE: &'static str = "urn:ietf:params:oauth:grant-type:device_code"; -const CODE_BODY: &'static str = "{\ - \"client_id\": \"f6e1ede88556627925d6\",\ - \"scope\": \"user:email\"\ -}"; -const POLL_URL: &'static str = "https://github.com/login/oauth/access_token"; -const ERR: &'static str = "Failed to perform OAuth 2.0 authentication with GitHub"; -const ALAN_DIR: &str = ".alan"; -const TOKEN_FILE: &str = ".alan/.token"; -static TOKEN: OnceCell = OnceCell::new(); - -// Get saved token -pub fn get_token() -> &'static str { - let token = TOKEN.get(); - if let Some(token) = token { - return token; - } else { - // This will happen when we are not able to authenticate the user. - // Empty token will be caught by deploy service. - return ""; - } -} - -// Get previously generated OAuth access token or generate a new one -pub async fn authenticate(non_interactive: bool) { - let token = TOKEN.get(); - if token.is_none() { - let home = std::env::var("HOME").unwrap(); - let file_name = &format!("{}/{}", home, TOKEN_FILE); - match read_to_string(file_name) { - Ok(file_token) => TOKEN.set(file_token).unwrap(), - Err(_) => match std::env::var("AUTH_TOKEN") { - Ok(token) => TOKEN.set(token).unwrap(), - Err(_) => { - if non_interactive { - warn!( - AuthFailed, - "Non interactive mode. Token need to be defined in AUTH_TOKEN environment variable." - ); - std::process::exit(1) - } - generate_token().await - } - }, - }; - }; -} - -pub fn clear_token() { - let home = std::env::var("HOME").unwrap(); - let file_name = &format!("{}/{}", home, TOKEN_FILE); - remove_file(file_name).unwrap(); -} - -// Prompts the user to authenticate with Github using the Device Flow. -// Generates the OAuth access token, stores it in a file for later use and returns it. -// https://docs.github.com/en/developers/apps/authorizing-oauth-apps#device-flow -async fn generate_token() { - let req = Request::post(CODE_URL) - .header("Content-Type", "application/json") - .header("Accept", "application/json") - .body(CODE_BODY.into()) - .unwrap(); - let resp = CLIENT.request(req).await.expect(ERR); - let data = hyper::body::to_bytes(resp.into_body()).await.expect(ERR); - let data_str = String::from_utf8(data.to_vec()).expect(ERR); - let json: Value = serde_json::from_str(&data_str).expect(ERR); - let device_code = json["device_code"].as_str().unwrap(); - let verification_uri = json["verification_uri"].as_str().unwrap(); - let user_code = json["user_code"].as_str().unwrap(); - if !Confirm::with_theme(&ColorfulTheme::default()) - .with_prompt(format!( - "{} to authenticate the AnyCloud CLI via github.com", - style("Press Enter").bold(), - )) - .default(true) - .interact() - .unwrap() - { - std::process::exit(0); - } - println!( - "{} First copy your one-time code: {}", - style("!").yellow(), - style(user_code).bold() - ); - let open_browser = Confirm::with_theme(&ColorfulTheme::default()) - .with_prompt(format!( - "{} to open github.com in your browser", - style("Press Enter").bold(), - )) - .default(true) - .interact() - .unwrap(); - if !open_browser || (open_browser && webbrowser::open(verification_uri).is_err()) { - println!( - "Open the following url in your browser: {}", - style(verification_uri).bold() - ); - } - let interval = json["interval"].as_u64().unwrap(); - let period = Duration::from_secs(interval + 1); - let body = json!({ - "client_id": CLIENT_ID, - "grant_type": GRANT_TYPE, - "device_code": device_code, - }); - loop { - let req = Request::post(POLL_URL) - .header("Content-Type", "application/json") - .header("Accept", "application/json") - .body(body.to_string().into()) - .unwrap(); - let resp = CLIENT.request(req).await.expect(ERR); - let data = hyper::body::to_bytes(resp.into_body()).await.expect(ERR); - let data_str = String::from_utf8(data.to_vec()).expect(ERR); - let json: Value = serde_json::from_str(&data_str).expect(ERR); - if let Some(token) = json["access_token"].as_str() { - let home = std::env::var("HOME").unwrap(); - let dir_name = &format!("{}/{}", home, ALAN_DIR); - let path = Path::new(dir_name); - if !path.exists() { - create_dir(path).expect(ERR); - } - let file_name = &format!("{}/{}", home, TOKEN_FILE); - let path = Path::new(file_name); - // remove old token, if it exists - if path.exists() { - remove_file(path).expect(ERR); - } - let mut file = File::create(file_name).expect(ERR); - file.write_all(token.as_bytes()).expect(ERR); - TOKEN.set(token.to_string()).unwrap(); - if !Confirm::with_theme(&ColorfulTheme::default()) - .with_prompt(format!( - "Authentication complete. {} to continue...", - style("Press Enter").bold(), - )) - .default(true) - .interact() - .unwrap() - { - std::process::exit(0); - } - return; - } else if let Some(error) = json["error"].as_str() { - if error != "authorization_pending" { - warn!( - AuthFailed, - "Authentication failed. Please try again. Err: {}", error - ); - std::process::exit(1); - } - } - sleep(period).await; - } -} diff --git a/avm/src/compile.rs b/avm/src/compile.rs deleted file mode 100644 index ce3823427..000000000 --- a/avm/src/compile.rs +++ /dev/null @@ -1,61 +0,0 @@ -use std::env; -use std::fs::File; -use std::io::prelude::*; -use std::process::{id, Command, Stdio}; - -use tempfile::tempdir; - -#[cfg(unix)] -const COMPILER: &'static [u8] = include_bytes!("../../compiler/alan-compile"); -#[cfg(windows)] -const COMPILER: &'static [u8] = include_bytes!("../../compiler/alan-compile.exe"); - -pub fn compile(source_file: &str, dest_file: &str, silent: bool) -> i32 { - let tmpdir = tempdir().unwrap(); - let alan_compile_path = if cfg!(unix) { - tmpdir.path().join("alan-compile") - } else { - tmpdir.path().join("alan-compile.exe") - }; - let mut f = File::create(&alan_compile_path).unwrap(); - f.write_all(COMPILER).unwrap(); - // on unix systems we also have to set the permissions for the file - // to mark it as executable - #[cfg(unix)] - { - use std::os::unix::prelude::PermissionsExt; - - let metadata = f.metadata().unwrap(); - let mut permissions = metadata.permissions(); - permissions.set_mode(0o744); - f.set_permissions(permissions).unwrap(); - } - drop(f); - let mut source_path = env::current_dir().unwrap(); - source_path.push(source_file); - let mut dest_path = env::current_dir().unwrap(); - dest_path.push(dest_file); - let mut cmd = if cfg!(unix) { - let mut cmd = Command::new("sh"); - cmd.arg("-c"); - cmd - } else { - let mut cmd = Command::new("cmd"); - cmd.arg("/C"); - cmd - }; - if !silent { - cmd.stdout(Stdio::inherit()); - } - cmd.stderr(Stdio::inherit()); - let output = cmd - .arg(format!( - "{} {} {}", - alan_compile_path.display(), - source_path.display(), - dest_path.display(), - )) - .output() - .unwrap(); - return output.status.code().unwrap(); -} diff --git a/avm/src/daemon/ctrl.rs b/avm/src/daemon/ctrl.rs deleted file mode 100644 index 6a3f3395c..000000000 --- a/avm/src/daemon/ctrl.rs +++ /dev/null @@ -1,1947 +0,0 @@ -use std::cmp; -use std::collections::HashMap; -use std::convert::Infallible; -use std::env; -use std::fs::{read, write}; -use std::hash::Hasher; -use std::io; -use std::net::TcpStream; -use std::path::{Path, PathBuf}; -use std::str; -use std::sync::Arc; - -use futures::future::join_all; -use hyper::{ - body, - client::{Client, HttpConnector}, - header::{HeaderName, HeaderValue}, - Body, Request, Response, StatusCode, -}; -// TODO: Restore rustls once it can connect directly by IP address -//use hyper_rustls::HttpsConnector; -use hyper_openssl::HttpsConnector; -use once_cell::sync::OnceCell; -use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; -//use rustls::ClientConfig; -use protobuf::Message; -use serde::{Deserialize, Serialize}; -use serde_json::json; -use tokio::sync::oneshot::{self, Receiver, Sender}; -use tokio::task; -use twox_hash::XxHash64; - -use crate::daemon::daemon::{ - DaemonProperties, DaemonResult, CLUSTER_SECRET, DAEMON_PROPS, NON_HTTP, -}; -use crate::daemon::dns::VMMetadata; -use crate::make_server; -use crate::vm::event::{BuiltInEvents, EventEmit, HandlerFragment}; -use crate::vm::http::{HttpType, HttpsConfig}; -use crate::vm::memory::{HandlerMemory, CLOSURE_ARG_MEM_START}; -use crate::vm::opcode::{DS, REGION_VMS}; -use crate::vm::protos; -use crate::vm::run::EVENT_TX; -use crate::vm::{VMError, VMResult}; -use crate::{error, warn}; - -pub static NAIVE_CLIENT: OnceCell>> = OnceCell::new(); -pub static CONTROL_PORT_EXTENSIONS: OnceCell = OnceCell::new(); - -#[derive(Clone, Debug)] -pub struct HashedId { - pub id: String, - pub hash: u64, - pub is_up: bool, -} - -impl HashedId { - pub fn new(id: String) -> HashedId { - let mut hasher = XxHash64::with_seed(0xa1a2); - hasher.write(id.as_bytes()); - HashedId { - id, - hash: hasher.finish(), - is_up: true, // Assume it's up and let the cron job mutate it over time - } - } -} - -// An algorithm based on the classic Rendezvous Hash but with changes to make the performance -// closer to modern Consistent Hash algorithms while retaining Rendezvous Hash's coordination-free -// routing. -#[derive(Clone, Debug)] -pub struct LogRendezvousHash { - sorted_hashes: Vec, -} - -impl LogRendezvousHash { - pub fn new(ids: Vec) -> LogRendezvousHash { - let mut sorted_hashes = Vec::with_capacity(ids.len()); - for id in ids { - sorted_hashes.push(HashedId::new(id)); - } - sorted_hashes.sort_by(|a, b| a.hash.cmp(&b.hash)); - LogRendezvousHash { sorted_hashes } - } - - /// Replaces the prior set of ids with the new set. TODO: Skip re-hashing ids that already exist - pub fn update(self: &mut LogRendezvousHash, ids: Vec) { - let mut sorted_hashes = Vec::with_capacity(ids.len()); - for id in ids { - sorted_hashes.push(HashedId::new(id)); - } - sorted_hashes.sort_by(|a, b| a.hash.cmp(&b.hash)); - self.sorted_hashes = sorted_hashes; - } - - pub fn get_leader_id(&self) -> &str { - if self.sorted_hashes.len() == 0 { - return ""; - } - let mut last_idx = self.sorted_hashes.len() - 1; - while last_idx > 0 && !self.sorted_hashes[last_idx].is_up { - last_idx = last_idx - 1; - } - &self.sorted_hashes[last_idx].id - } - - pub fn get_mut_nodes(self: &mut LogRendezvousHash) -> &mut Vec { - &mut self.sorted_hashes - } - - pub fn get_primary_node_id(&self, key: &str) -> &str { - self.get_assigned_nodes_id(key)[0] - } - - pub fn get_assigned_nodes_id(&self, key: &str) -> Vec<&str> { - let top = cmp::min(3, self.sorted_hashes.len()); - let mut idx = self.get_idx_for_key(key); - let mut ids: Vec<&str> = Vec::new(); - for _ in 0..top { - ids.push(&self.sorted_hashes[idx].id); - idx = (idx + 1) % self.sorted_hashes.len(); - } - ids - } - - // Runs a binary search for the record whose hash is closest to the key hash without - // going over. If none are found, the *last* record in the list is returned as it wraps around. - fn get_idx_for_key(&self, key: &str) -> usize { - let mut key_hasher = XxHash64::with_seed(0xa1a2); - key_hasher.write(key.as_bytes()); - let key_hash = key_hasher.finish(); - let idx = match self - .sorted_hashes - .binary_search_by(|a| a.hash.cmp(&key_hash)) - { - Ok(res) => res, - Err(res) => res, // This is actually the last index less than the hash, which is what we want - } % self.sorted_hashes.len(); - idx - } -} - -#[derive(Clone, Debug)] -pub struct ControlPort { - lrh: LogRendezvousHash, - client: Client>, - // TODO: Once the crazy type info of the server can be figured out, we can attach it to this - // struct and then make it possible to wind down the control port server - // server: &'a dyn Service, - vms: HashMap, // All VMs in the cluster. String is private IP - self_vm: Option, // This VM. Not set on initialization - region_vms: HashMap, // VMs in the same cloud and region. String is private IP - vms_up: HashMap, // VMs by private IP versus health status -} - -async fn control_port(req: Request) -> Result, Infallible> { - let cluster_secret = CLUSTER_SECRET.get().unwrap(); - if cluster_secret.is_some() && !req.headers().contains_key(cluster_secret.as_ref().unwrap()) { - // If this control port is guarded by a secret string, make sure there's a header with that - // secret as the key (we don't care about the value) and abort otherwise - return Ok(Response::builder().status(500).body("fail".into()).unwrap()); - } - match req.uri().path() { - "/health" => Ok(Response::builder().status(200).body("ok".into()).unwrap()), - "/clusterHealth" => handle_cluster_health(), - "/start" => handle_start(req).await, - "/datastore/getf" => handle_dsgetf(req).await, // TODO: How to better organize the datastore stuff? - "/datastore/getv" => handle_dsgetv(req).await, - "/datastore/getr" => handle_dsgetr(req).await, - "/datastore/has" => handle_dshas(req).await, - "/datastore/del" => handle_dsdel(req).await, - "/datastore/setf" => handle_dssetf(req).await, - "/datastore/setv" => handle_dssetv(req).await, - "/datastore/keys" => handle_keys(), - "/datastore/dsrrun" => handle_dsrrun(req).await, - "/datastore/dsmrun" => handle_dsmrun(req).await, - "/datastore/dsrwith" => handle_dsrwith(req).await, - "/datastore/dsmwith" => handle_dsmwith(req).await, - "/datastore/dsmonly" => handle_dsmonly(req).await, - "/datastore/dswonly" => handle_dswonly(req).await, - "/datastore/dsrclos" => handle_dsrclos(req).await, - "/datastore/dsmclos" => handle_dsmclos(req).await, - path => { - if *CONTROL_PORT_EXTENSIONS.get().unwrap() && path.starts_with("/app/") { - handle_extensions(req).await - } else { - Ok(Response::builder().status(404).body("fail".into()).unwrap()) - } - } - } -} - -async fn handle_extensions(req: Request) -> Result, Infallible> { - match extension_listener(req).await { - Ok(res) => Ok(res), - Err(_) => Ok(Response::builder().status(404).body("fail".into()).unwrap()), - } -} - -async fn extension_listener(req: Request) -> VMResult> { - // Stolen from the `http_listener` in the opcodes with minor modifications. TODO: DRY this out - // Create a new event handler memory to add to the event queue - let mut event = HandlerMemory::new(None, 1)?; - // Grab the method - let method_str = req.method().to_string(); - let method = HandlerMemory::str_to_fractal(&method_str); - // Grab the URL - let orig_uri = req.uri().clone(); - let orig_query = match orig_uri.query() { - Some(q) => format!("?{}", q), - None => format!(""), - }; - let url_str = format!("{}{}", orig_uri.path(), orig_query); - //let url_str = req.uri().to_string(); - let url = HandlerMemory::str_to_fractal(&url_str); - // Grab the headers - let headers = req.headers(); - let mut headers_hm = HandlerMemory::new(None, headers.len() as i64)?; - headers_hm.init_fractal(CLOSURE_ARG_MEM_START)?; - for (i, (key, val)) in headers.iter().enumerate() { - let key_str = key.as_str(); - // TODO: get rid of the potential panic here - let val_str = val.to_str().unwrap(); - headers_hm.init_fractal(i as i64)?; - headers_hm.push_fractal(i as i64, HandlerMemory::str_to_fractal(key_str))?; - headers_hm.push_fractal(i as i64, HandlerMemory::str_to_fractal(val_str))?; - headers_hm.push_register(CLOSURE_ARG_MEM_START, i as i64)?; - } - // Grab the body, if any - let body_req = match hyper::body::to_bytes(req.into_body()).await { - Ok(bytes) => bytes, - // If we error out while getting the body, just close this listener out immediately - Err(ee) => { - return Ok(Response::new( - format!("Connection terminated: {}", ee).into(), - )); - } - }; - // TODO: get rid of the potential panic here - let body_str = str::from_utf8(&body_req).unwrap().to_string(); - let body = HandlerMemory::str_to_fractal(&body_str); - // Populate the event and emit it - event.init_fractal(0)?; - event.push_fractal(0, method)?; - event.push_fractal(0, url)?; - HandlerMemory::transfer( - &headers_hm, - CLOSURE_ARG_MEM_START, - &mut event, - CLOSURE_ARG_MEM_START, - )?; - event.push_register(0, CLOSURE_ARG_MEM_START)?; - event.push_fractal(0, body)?; - // Generate a threadsafe raw ptr to the tx of a watch channel - // A ptr is unsafely created from the raw ptr in httpsend once the - // user's code has completed and sends the new HandlerMemory so we - // can resume execution of this HTTP request - let (tx, rx): (Sender>, Receiver>) = oneshot::channel(); - let tx_ptr = Box::into_raw(Box::new(tx)) as i64; - event.push_fixed(0, tx_ptr)?; - let event_emit = EventEmit { - id: i64::from(BuiltInEvents::CTRLPORT), - payload: Some(event), - }; - let event_tx = EVENT_TX.get().ok_or(VMError::ShutDown)?; - let mut err_res = Response::new("Error synchronizing `send` for HTTP request".into()); - *err_res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; - if event_tx.send(event_emit).is_err() { - return Ok(err_res); - } - // Await HTTP response from the user code - let response_hm = match rx.await { - Ok(hm) => hm, - Err(_) => { - return Ok(err_res); - } - }; - // Get the status from the user response and begin building the response object - let status = response_hm.read_fixed(0)? as u16; - let mut res = Response::builder() - .status(StatusCode::from_u16(status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)); - // Get the headers and populate the response object - // TODO: figure out how to handle this potential panic - let headers = res.headers_mut().unwrap(); - let header_hms = response_hm.read_fractal(1)?; - for i in 0..header_hms.len() { - let (h, _) = response_hm.read_from_fractal(&header_hms.clone(), i); - let (key_hm, _) = response_hm.read_from_fractal(&h, 0); - let (val_hm, _) = response_hm.read_from_fractal(&h, 1); - let key = HandlerMemory::fractal_to_string(key_hm)?; - let val = HandlerMemory::fractal_to_string(val_hm)?; - // TODO: figure out how to handle this potential panic - let name = HeaderName::from_bytes(key.as_bytes()).unwrap(); - // TODO: figure out how to handle this potential panic - let value = HeaderValue::from_str(&val).unwrap(); - headers.insert(name, value); - } - // Get the body, populate the response object, and fire it out - let body = HandlerMemory::fractal_to_string(response_hm.read_fractal(2)?)?; - // TODO: figure out how to handle this potential panic - Ok(res.body(body.into()).unwrap()) -} - -fn handle_cluster_health() -> Result, Infallible> { - let non_http = NON_HTTP.get().unwrap_or(&false); - if *non_http { - // Control port is running - Ok(Response::builder().status(200).body("ok".into()).unwrap()) - } else { - if TcpStream::connect("127.0.0.1:443").is_err() { - // If the Alan HTTPS server has not yet started, mark as a failure - Ok(Response::builder().status(500).body("fail".into()).unwrap()) - } else if Path::new("./Dockerfile").exists() - && Path::new("./app.tar.gz").exists() - && TcpStream::connect("127.0.0.1:8088").is_err() - { - // If this is an Anycloud deployment and the child process hasn't started, mark as a failure - // TODO: Any way to generalize this so we don't have special logic for Anycloud? - Ok(Response::builder().status(500).body("fail".into()).unwrap()) - } else { - // Everything passed, send an ok - Ok(Response::builder().status(200).body("ok".into()).unwrap()) - } - } -} - -async fn handle_start(req: Request) -> Result, Infallible> { - // Receive POST and save daemon properties - match get_daemon_props(req).await { - Ok(_) => Ok(Response::builder().status(200).body("ok".into()).unwrap()), - Err(err) => { - error!(DaemonStartFailed, "{:?}", err).await; - Ok(Response::builder().status(500).body("fail".into()).unwrap()) - } - } -} - -async fn get_daemon_props(req: Request) -> DaemonResult<()> { - let bytes = body::to_bytes(req.into_body()).await?; - let body: DaemonProperties = serde_json::from_slice(&bytes).unwrap(); - maybe_dump_files(&body).await?; - DAEMON_PROPS.set(body).unwrap(); - Ok(()) -} - -async fn maybe_dump_files(daemon_props: &DaemonProperties) -> DaemonResult<()> { - let pwd = env::current_dir(); - match pwd { - Ok(pwd) => { - for (file_name, content) in &daemon_props.filesB64 { - write_b64_file(&pwd, file_name, content)?; - } - } - Err(err) => { - let err = format!("{:?}", err); - return Err(err.into()); - } - } - Ok(()) -} - -fn write_b64_file(pwd: &PathBuf, file_name: &str, content: &str) -> io::Result<()> { - write( - format!("{}/{}", pwd.display(), file_name), - base64::decode(content).unwrap(), - ) -} - -#[derive(Deserialize, Debug, Serialize)] -struct DSGet { - pub nskey: String, -} - -async fn handle_dsgetf(req: Request) -> Result, Infallible> { - match dsgetf_inner(req).await { - Ok(hand_mem) => { - let mut out = vec![]; - hand_mem.to_pb().write_to_vec(&mut out).unwrap(); - Ok(Response::builder().status(200).body(out.into()).unwrap()) - } - Err(err) => { - // TODO: What error message here? Also should this also be a valid HM out of here? - eprintln!("{:?}", err); - Ok(Response::builder().status(500).body("fail".into()).unwrap()) - } - } -} - -async fn dsgetf_inner(req: Request) -> DaemonResult> { - // TODO: For now assume this was directed at the right node, later on add auto-forwarding logic - let bytes = body::to_bytes(req.into_body()).await?; - let body: DSGet = serde_json::from_slice(&bytes)?; - let maybe_hm = DS.get(&body.nskey); - let mut hand_mem = HandlerMemory::new(None, 1)?; - hand_mem.init_fractal(0)?; - hand_mem.push_fixed(0, if maybe_hm.is_some() { 1i64 } else { 0i64 })?; - match maybe_hm { - Some(hm) => hand_mem.push_fixed(0, hm.read_fixed(0)?), - None => hand_mem.push_fractal( - 0, - HandlerMemory::str_to_fractal("namespace-key pair not found"), - ), - }?; - Ok(hand_mem) -} - -async fn handle_dsgetv(req: Request) -> Result, Infallible> { - match dsgetv_inner(req).await { - Ok(hand_mem) => { - let mut out = vec![]; - hand_mem.to_pb().write_to_vec(&mut out).unwrap(); - Ok(Response::builder().status(200).body(out.into()).unwrap()) - } - Err(err) => { - // TODO: What error message here? Also should this also be a valid HM out of here? - eprintln!("{:?}", err); - Ok(Response::builder().status(500).body("fail".into()).unwrap()) - } - } -} - -async fn dsgetv_inner(req: Request) -> DaemonResult> { - let bytes = body::to_bytes(req.into_body()).await?; - let body: DSGet = serde_json::from_slice(&bytes)?; - let maybe_hm = DS.get(&body.nskey); - let mut hand_mem = HandlerMemory::new(None, 1)?; - hand_mem.init_fractal(0)?; - hand_mem.push_fixed(0, if maybe_hm.is_some() { 1i64 } else { 0i64 })?; - match maybe_hm { - Some(hm) => { - HandlerMemory::transfer(&hm, 0, &mut hand_mem, CLOSURE_ARG_MEM_START)?; - hand_mem.push_register(0, CLOSURE_ARG_MEM_START)?; - } - None => { - hand_mem.push_fractal( - 0, - HandlerMemory::str_to_fractal("namespace-key pair not found"), - )?; - } - }; - Ok(hand_mem) -} - -async fn handle_dsgetr(req: Request) -> Result, Infallible> { - match dsgetr_inner(req).await { - Ok(hand_mem) => { - let mut out = vec![]; - hand_mem.to_pb().write_to_vec(&mut out).unwrap(); - Ok(Response::builder().status(200).body(out.into()).unwrap()) - } - Err(err) => { - // TODO: What error message here? Also should this also be a valid HM out of here? - eprintln!("{:?}", err); - Ok(Response::builder().status(500).body("fail".into()).unwrap()) - } - } -} - -async fn dsgetr_inner(req: Request) -> DaemonResult> { - let bytes = body::to_bytes(req.into_body()).await?; - let body: DSGet = serde_json::from_slice(&bytes)?; - let maybe_hm = DS.get(&body.nskey); - match maybe_hm { - Some(hm) => Ok(hm.clone()), - None => Err(Box::new(VMError::Other( - "namespace-key pair not found".to_string(), - ))), - } -} - -async fn handle_dshas(req: Request) -> Result, Infallible> { - match dshas_inner(req).await { - Ok(has) => Ok( - Response::builder() - .status(200) - .body(has.to_string().into()) - .unwrap(), - ), - Err(err) => { - // TODO: What error message here? Also should this also be a valid HM out of here? - eprintln!("{:?}", err); - Ok(Response::builder().status(500).body("fail".into()).unwrap()) - } - } -} - -async fn dshas_inner(req: Request) -> DaemonResult { - let bytes = body::to_bytes(req.into_body()).await?; - let body: DSGet = serde_json::from_slice(&bytes)?; - Ok(DS.contains_key(&body.nskey)) -} - -async fn handle_dsdel(req: Request) -> Result, Infallible> { - match dsdel_inner(req).await { - Ok(del) => Ok( - Response::builder() - .status(200) - .body(del.to_string().into()) - .unwrap(), - ), - Err(err) => { - // TODO: What error message here? Also should this also be a valid HM out of here? - eprintln!("{:?}", err); - Ok(Response::builder().status(500).body("fail".into()).unwrap()) - } - } -} - -async fn dsdel_inner(req: Request) -> DaemonResult { - let bytes = body::to_bytes(req.into_body()).await?; - let body: DSGet = serde_json::from_slice(&bytes)?; - Ok(DS.remove(&body.nskey).is_some()) -} - -async fn handle_dssetf(req: Request) -> Result, Infallible> { - match dssetf_inner(req).await { - Ok(_) => Ok(Response::builder().status(200).body("true".into()).unwrap()), - Err(err) => { - // TODO: What error message here? Also should this also be a valid HM out of here? - eprintln!("{:?}", err); - Ok(Response::builder().status(500).body("fail".into()).unwrap()) - } - } -} - -async fn dssetf_inner(req: Request) -> DaemonResult<()> { - let bytes = body::to_bytes(req.into_body()).await?; - let pb = protos::HandlerMemory::HandlerMemory::parse_from_bytes(&bytes)?; - let hand_mem = HandlerMemory::from_pb(&pb)?; - let nskey = HandlerMemory::fractal_to_string(hand_mem.read_fractal(0)?)?; - let val = hand_mem.read_fixed(1)?; - let mut hm = HandlerMemory::new(None, 1)?; - hm.write_fixed(0, val)?; - DS.insert(nskey, hm); - Ok(()) -} - -async fn handle_dssetv(req: Request) -> Result, Infallible> { - match dssetv_inner(req).await { - Ok(_) => Ok(Response::builder().status(200).body("true".into()).unwrap()), - Err(err) => { - // TODO: What error message here? Also should this also be a valid HM out of here? - eprintln!("{:?}", err); - Ok(Response::builder().status(500).body("fail".into()).unwrap()) - } - } -} - -async fn dssetv_inner(req: Request) -> DaemonResult<()> { - let bytes = body::to_bytes(req.into_body()).await?; - let pb = protos::HandlerMemory::HandlerMemory::parse_from_bytes(&bytes)?; - let hand_mem = HandlerMemory::from_pb(&pb)?; - let nskey = HandlerMemory::fractal_to_string(hand_mem.read_fractal(0)?)?; - let mut hm = HandlerMemory::new(None, 1)?; - HandlerMemory::transfer(&hand_mem, 1, &mut hm, 0)?; - DS.insert(nskey, hm); - Ok(()) -} - -fn handle_keys() -> Result, Infallible> { - let keys = DS - .iter() - .map(|kvs| kvs.key().clone()) - .collect::>() - .join("\n"); - Ok(Response::builder().status(200).body(keys.into()).unwrap()) -} - -async fn handle_dsrrun(req: Request) -> Result, Infallible> { - match dsrrun_inner(req).await { - Ok(hand_mem) => { - let mut out = vec![]; - hand_mem.to_pb().write_to_vec(&mut out).unwrap(); - Ok(Response::builder().status(200).body(out.into()).unwrap()) - } - Err(err) => { - // TODO: What error message here? Also should this also be a valid HM out of here? - eprintln!("{:?}", err); - Ok(Response::builder().status(500).body("fail".into()).unwrap()) - } - } -} - -async fn dsrrun_inner(req: Request) -> DaemonResult> { - let headers = req.headers(); - let nskey = headers.get("nskey").map_or("N/A", |v| v.to_str().unwrap()); - let maybe_hm = DS.get(nskey); - let subhandler_id = headers - .get("subhandler_id") - .map_or(0, |v| v.to_str().unwrap().parse().unwrap()); - let subhandler = HandlerFragment::new(subhandler_id, 0); - let bytes = body::to_bytes(req.into_body()).await?; - let pb = protos::HandlerMemory::HandlerMemory::parse_from_bytes(&bytes)?; - let mut hm = HandlerMemory::from_pb(&pb)?; - let mut res_hm = HandlerMemory::new(None, 1)?; - res_hm.init_fractal(0)?; - match maybe_hm { - Some(ds) => { - HandlerMemory::transfer(&ds, 0, &mut hm, CLOSURE_ARG_MEM_START + 1)?; - let hm = subhandler.run(hm).await?; - res_hm.push_fixed(0, 1); - if hm.addr_to_idxs_opt(CLOSURE_ARG_MEM_START).is_some() { - // Guard against void functions - HandlerMemory::transfer(&hm, CLOSURE_ARG_MEM_START, &mut res_hm, 1); - res_hm.push_register(0, 1)?; - } - } - None => { - res_hm.push_fixed(0, 0); - res_hm.push_fractal( - 0, - HandlerMemory::str_to_fractal("namespace-key pair not found"), - )?; - } - } - Ok(res_hm) -} - -async fn handle_dsmrun(req: Request) -> Result, Infallible> { - match dsmrun_inner(req).await { - Ok(hand_mem) => { - let mut out = vec![]; - hand_mem.to_pb().write_to_vec(&mut out).unwrap(); - Ok(Response::builder().status(200).body(out.into()).unwrap()) - } - Err(err) => { - // TODO: What error message here? Also should this also be a valid HM out of here? - eprintln!("{:?}", err); - Ok(Response::builder().status(500).body("fail".into()).unwrap()) - } - } -} - -async fn dsmrun_inner(req: Request) -> DaemonResult> { - let headers = req.headers(); - let nskey = headers - .get("nskey") - .map_or("N/A", |v| v.to_str().unwrap()) - .to_string(); - let maybe_hm = DS.get(&nskey); - let subhandler_id = headers - .get("subhandler_id") - .map_or(0, |v| v.to_str().unwrap().parse().unwrap()); - let subhandler = HandlerFragment::new(subhandler_id, 0); - let bytes = body::to_bytes(req.into_body()).await?; - let pb = protos::HandlerMemory::HandlerMemory::parse_from_bytes(&bytes)?; - let mut hm = HandlerMemory::from_pb(&pb)?; - let mut res_hm = HandlerMemory::new(None, 1)?; - res_hm.init_fractal(0)?; - match maybe_hm { - Some(ds) => { - HandlerMemory::transfer(&ds, 0, &mut hm, CLOSURE_ARG_MEM_START + 1)?; - let hm = subhandler.run(hm).await?; - res_hm.push_fixed(0, 1); - if hm.addr_to_idxs_opt(CLOSURE_ARG_MEM_START).is_some() { - // Guard against void functions - HandlerMemory::transfer(&hm, CLOSURE_ARG_MEM_START, &mut res_hm, 1); - res_hm.push_register(0, 1)?; - } - // Also grab the mutation to the datastore value and re-insert it - let mut newds = HandlerMemory::new(None, 1)?; - HandlerMemory::transfer(&hm, CLOSURE_ARG_MEM_START + 1, &mut newds, 0)?; - drop(ds); - DS.insert(nskey, newds); - } - None => { - res_hm.push_fixed(0, 0); - res_hm.push_fractal( - 0, - HandlerMemory::str_to_fractal("namespace-key pair not found"), - )?; - } - } - Ok(res_hm) -} - -async fn handle_dsrwith(req: Request) -> Result, Infallible> { - match dsrwith_inner(req).await { - Ok(hand_mem) => { - let mut out = vec![]; - hand_mem.to_pb().write_to_vec(&mut out).unwrap(); - Ok(Response::builder().status(200).body(out.into()).unwrap()) - } - Err(err) => { - // TODO: What error message here? Also should this also be a valid HM out of here? - eprintln!("{:?}", err); - Ok(Response::builder().status(500).body("fail".into()).unwrap()) - } - } -} - -async fn dsrwith_inner(req: Request) -> DaemonResult> { - let headers = req.headers(); - let nskey = headers.get("nskey").map_or("N/A", |v| v.to_str().unwrap()); - let maybe_hm = DS.get(nskey); - let subhandler_id = headers - .get("subhandler_id") - .map_or(0, |v| v.to_str().unwrap().parse().unwrap()); - let subhandler = HandlerFragment::new(subhandler_id, 0); - let bytes = body::to_bytes(req.into_body()).await?; - let pb = protos::HandlerMemory::HandlerMemory::parse_from_bytes(&bytes)?; - let mut hm = HandlerMemory::from_pb(&pb)?; - let mut res_hm = HandlerMemory::new(None, 1)?; - res_hm.init_fractal(0)?; - match maybe_hm { - Some(ds) => { - HandlerMemory::transfer(&ds, 0, &mut hm, CLOSURE_ARG_MEM_START + 1)?; - let hm = subhandler.run(hm).await?; - res_hm.push_fixed(0, 1); - if hm.addr_to_idxs_opt(CLOSURE_ARG_MEM_START).is_some() { - // Guard against void functions - HandlerMemory::transfer(&hm, CLOSURE_ARG_MEM_START, &mut res_hm, 1); - res_hm.push_register(0, 1)?; - } - } - None => { - res_hm.push_fixed(0, 0); - res_hm.push_fractal( - 0, - HandlerMemory::str_to_fractal("namespace-key pair not found"), - )?; - } - } - Ok(res_hm) -} - -async fn handle_dsmwith(req: Request) -> Result, Infallible> { - match dsmwith_inner(req).await { - Ok(hand_mem) => { - let mut out = vec![]; - hand_mem.to_pb().write_to_vec(&mut out).unwrap(); - Ok(Response::builder().status(200).body(out.into()).unwrap()) - } - Err(err) => { - // TODO: What error message here? Also should this also be a valid HM out of here? - eprintln!("{:?}", err); - Ok(Response::builder().status(500).body("fail".into()).unwrap()) - } - } -} - -async fn dsmwith_inner(req: Request) -> DaemonResult> { - let headers = req.headers(); - let nskey = headers - .get("nskey") - .map_or("N/A", |v| v.to_str().unwrap()) - .to_string(); - let maybe_hm = DS.get(&nskey); - let subhandler_id = headers - .get("subhandler_id") - .map_or(0, |v| v.to_str().unwrap().parse().unwrap()); - let subhandler = HandlerFragment::new(subhandler_id, 0); - let bytes = body::to_bytes(req.into_body()).await?; - let pb = protos::HandlerMemory::HandlerMemory::parse_from_bytes(&bytes)?; - let mut hm = HandlerMemory::from_pb(&pb)?; - let mut res_hm = HandlerMemory::new(None, 2)?; - res_hm.init_fractal(0)?; - match maybe_hm { - Some(ds) => { - HandlerMemory::transfer(&ds, 0, &mut hm, CLOSURE_ARG_MEM_START + 1)?; - let hm = subhandler.run(hm).await?; - res_hm.push_fixed(0, 1); - if hm.addr_to_idxs_opt(CLOSURE_ARG_MEM_START).is_some() { - // Guard against void functions - HandlerMemory::transfer(&hm, CLOSURE_ARG_MEM_START, &mut res_hm, 1); - res_hm.push_register(0, 1)?; - } - // Also grab the mutation to the datastore value and re-insert it - let mut newds = HandlerMemory::new(None, 1)?; - HandlerMemory::transfer(&hm, CLOSURE_ARG_MEM_START + 1, &mut newds, 0)?; - drop(ds); - DS.insert(nskey, newds); - } - None => { - res_hm.push_fixed(0, 0); - res_hm.push_fractal( - 0, - HandlerMemory::str_to_fractal("namespace-key pair not found"), - )?; - } - } - Ok(res_hm) -} - -async fn handle_dsmonly(req: Request) -> Result, Infallible> { - match dsmonly_inner(req).await { - Ok(_) => Ok(Response::builder().status(200).body("ok".into()).unwrap()), - Err(err) => { - // TODO: What error message here? Also should this also be a valid HM out of here? - eprintln!("{:?}", err); - Ok(Response::builder().status(500).body("fail".into()).unwrap()) - } - } -} - -async fn dsmonly_inner(req: Request) -> DaemonResult<()> { - let headers = req.headers(); - let nskey = headers - .get("nskey") - .map_or("N/A", |v| v.to_str().unwrap()) - .to_string(); - let maybe_hm = DS.get(&nskey); - let subhandler_id = headers - .get("subhandler_id") - .map_or(0, |v| v.to_str().unwrap().parse().unwrap()); - let subhandler = HandlerFragment::new(subhandler_id, 0); - let bytes = body::to_bytes(req.into_body()).await?; - let pb = protos::HandlerMemory::HandlerMemory::parse_from_bytes(&bytes)?; - let mut hm = HandlerMemory::from_pb(&pb)?; - match maybe_hm { - Some(ds) => { - HandlerMemory::transfer(&ds, 0, &mut hm, CLOSURE_ARG_MEM_START + 1)?; - let hm = subhandler.run(hm).await?; - // Also grab the mutation to the datastore value and re-insert it - let mut newds = HandlerMemory::new(None, 1)?; - HandlerMemory::transfer(&hm, CLOSURE_ARG_MEM_START + 1, &mut newds, 0)?; - drop(ds); - DS.insert(nskey, newds); - } - None => { - // Do nothing - } - } - Ok(()) -} - -async fn handle_dswonly(req: Request) -> Result, Infallible> { - match dswonly_inner(req).await { - Ok(_) => Ok(Response::builder().status(200).body("ok".into()).unwrap()), - Err(err) => { - // TODO: What error message here? Also should this also be a valid HM out of here? - eprintln!("{:?}", err); - Ok(Response::builder().status(500).body("fail".into()).unwrap()) - } - } -} - -async fn dswonly_inner(req: Request) -> DaemonResult<()> { - let headers = req.headers(); - let nskey = headers - .get("nskey") - .map_or("N/A", |v| v.to_str().unwrap()) - .to_string(); - let maybe_hm = DS.get(&nskey); - let subhandler_id = headers - .get("subhandler_id") - .map_or(0, |v| v.to_str().unwrap().parse().unwrap()); - let subhandler = HandlerFragment::new(subhandler_id, 0); - let bytes = body::to_bytes(req.into_body()).await?; - let pb = protos::HandlerMemory::HandlerMemory::parse_from_bytes(&bytes)?; - let mut hm = HandlerMemory::from_pb(&pb)?; - match maybe_hm { - Some(ds) => { - HandlerMemory::transfer(&ds, 0, &mut hm, CLOSURE_ARG_MEM_START + 1)?; - let hm = subhandler.run(hm).await?; - // Also grab the mutation to the datastore value and re-insert it - let mut newds = HandlerMemory::new(None, 1)?; - HandlerMemory::transfer(&hm, CLOSURE_ARG_MEM_START + 1, &mut newds, 0)?; - drop(ds); - DS.insert(nskey, newds); - } - None => { - // Do nothing - } - } - Ok(()) -} - -async fn handle_dsrclos(req: Request) -> Result, Infallible> { - match dsrclos_inner(req).await { - Ok(hand_mem) => { - let mut out = vec![]; - hand_mem.to_pb().write_to_vec(&mut out).unwrap(); - Ok(Response::builder().status(200).body(out.into()).unwrap()) - } - Err(err) => { - // TODO: What error message here? Also should this also be a valid HM out of here? - eprintln!("{:?}", err); - Ok(Response::builder().status(500).body("fail".into()).unwrap()) - } - } -} - -async fn dsrclos_inner(req: Request) -> DaemonResult> { - let headers = req.headers(); - let nskey = headers.get("nskey").map_or("N/A", |v| v.to_str().unwrap()); - let maybe_hm = DS.get(nskey); - let subhandler_id = headers - .get("subhandler_id") - .map_or(0, |v| v.to_str().unwrap().parse().unwrap()); - let ret_addr = headers - .get("ret_addr") - .map_or(0, |v| v.to_str().unwrap().parse().unwrap()); - let subhandler = HandlerFragment::new(subhandler_id, 0); - let bytes = body::to_bytes(req.into_body()).await?; - let pb = protos::HandlerMemory::HandlerMemory::parse_from_bytes(&bytes)?; - let mut hand_mem = HandlerMemory::from_pb(&pb)?; - hand_mem.init_fractal(ret_addr)?; - match maybe_hm { - Some(ds) => { - HandlerMemory::transfer(&ds, 0, &mut hand_mem, CLOSURE_ARG_MEM_START + 1)?; - hand_mem = subhandler.run(hand_mem).await?; - hand_mem.push_fixed(ret_addr, 1i64)?; - hand_mem.push_register(ret_addr, CLOSURE_ARG_MEM_START)?; - } - None => { - hand_mem.push_fixed(ret_addr, 0)?; - hand_mem.push_fractal( - ret_addr, - HandlerMemory::str_to_fractal("namespace-key pair not found"), - )?; - } - } - Ok(hand_mem) -} - -async fn handle_dsmclos(req: Request) -> Result, Infallible> { - match dsmclos_inner(req).await { - Ok(hand_mem) => { - let mut out = vec![]; - hand_mem.to_pb().write_to_vec(&mut out).unwrap(); - Ok(Response::builder().status(200).body(out.into()).unwrap()) - } - Err(err) => { - // TODO: What error message here? Also should this also be a valid HM out of here? - eprintln!("{:?}", err); - Ok(Response::builder().status(500).body("fail".into()).unwrap()) - } - } -} - -async fn dsmclos_inner(req: Request) -> DaemonResult> { - let headers = req.headers(); - let nskey = headers - .get("nskey") - .map_or("N/A", |v| v.to_str().unwrap()) - .to_string(); - let maybe_hm = DS.get(&nskey); - let subhandler_id = headers - .get("subhandler_id") - .map_or(0, |v| v.to_str().unwrap().parse().unwrap()); - let ret_addr = headers - .get("ret_addr") - .map_or(0, |v| v.to_str().unwrap().parse().unwrap()); - let subhandler = HandlerFragment::new(subhandler_id, 0); - let bytes = body::to_bytes(req.into_body()).await?; - let pb = protos::HandlerMemory::HandlerMemory::parse_from_bytes(&bytes)?; - let mut hand_mem = HandlerMemory::from_pb(&pb)?; - hand_mem.init_fractal(ret_addr)?; - match maybe_hm { - Some(ds) => { - HandlerMemory::transfer(&ds, 0, &mut hand_mem, CLOSURE_ARG_MEM_START + 1)?; - hand_mem = subhandler.run(hand_mem).await?; - // Also grab the mutation to the datastore value and re-insert it - let mut newds = HandlerMemory::new(None, 1)?; - HandlerMemory::transfer(&hand_mem, CLOSURE_ARG_MEM_START + 1, &mut newds, 0)?; - drop(ds); - DS.insert(nskey, newds); - hand_mem.push_fixed(ret_addr, 1i64)?; - if hand_mem.addr_to_idxs_opt(CLOSURE_ARG_MEM_START).is_some() { - // Guard against void functions - hand_mem.push_register(ret_addr, CLOSURE_ARG_MEM_START)?; - } - } - None => { - hand_mem.push_fixed(ret_addr, 0)?; - hand_mem.push_fractal( - ret_addr, - HandlerMemory::str_to_fractal("namespace-key pair not found"), - )?; - } - } - Ok(hand_mem) -} - -// TODO: Revive once rustls supports IP addresses -/*mod naive { - use rustls; - - pub struct TLS {} - - impl rustls::ServerCertVerifier for TLS { - fn verify_server_cert( - &self, - _roots: &rustls::RootCertStore, - _presented_certs: &[rustls::Certificate], - _dns_name: tokio_rustls::webpki::DNSNameRef, - _ocsp_response: &[u8], - ) -> Result { - Ok(rustls::ServerCertVerified::assertion()) - } - } -}*/ - -impl ControlPort { - pub async fn start() -> ControlPort { - let pwd = env::current_dir(); - match pwd { - Ok(pwd) => { - let priv_key = read(format!("{}/key.pem", pwd.display())); - let cert = read(format!("{}/certificate.pem", pwd.display())); - if let (Ok(priv_key), Ok(cert)) = (priv_key, cert) { - // TODO: Make this not a side-effect - make_server!( - HttpType::HTTPS(HttpsConfig { - port: 4142, // 4 = A, 1 = L, 2 = N (sideways) => ALAN - priv_key: String::from_utf8(priv_key).unwrap(), - cert: String::from_utf8(cert).unwrap(), - }), - control_port - ); - let mut tls = SslConnector::builder(SslMethod::tls_client()).unwrap(); - tls.set_verify(SslVerifyMode::NONE); - /*let mut tls = ClientConfig::new(); - tls - .dangerous() - .set_certificate_verifier(Arc::new(naive::TLS {}));*/ - let mut http_connector = HttpConnector::new(); - http_connector.enforce_http(false); - - // This works because we only construct the control port once - let mut https = HttpsConnector::with_connector(http_connector, tls).unwrap(); - https.set_callback(|cc, _| { - cc.set_use_server_name_indication(false); - Ok(()) - }); - let client = Client::builder().build::<_, Body>(https); - //let client = Client::builder().build::<_, Body>(HttpsConnector::from((http_connector, tls))); - NAIVE_CLIENT.set(client).unwrap(); - // Make a second client. TODO: Share this? Or split into a naive-client generator function? - let mut tls = SslConnector::builder(SslMethod::tls_client()).unwrap(); - tls.set_verify(SslVerifyMode::NONE); - /*let mut tls = ClientConfig::new(); - tls - .dangerous() - .set_certificate_verifier(Arc::new(naive::TLS {}));*/ - let mut http_connector = HttpConnector::new(); - http_connector.enforce_http(false); - let mut https = HttpsConnector::with_connector(http_connector, tls).unwrap(); - https.set_callback(|cc, _| { - cc.set_use_server_name_indication(false); - Ok(()) - }); - let client = Client::builder().build::<_, Body>(https); - //let client = Client::builder().build::<_, Body>(HttpsConnector::from((http_connector, tls))); - - ControlPort { - lrh: LogRendezvousHash::new(vec![]), - client, - vms: HashMap::new(), - self_vm: None, - region_vms: HashMap::new(), - vms_up: HashMap::new(), - } - } else { - let err = "Failed getting ssl certificate or key"; - error!(NoSSLCert, "{}", err).await; - std::process::exit(1); - } - } - Err(err) => { - let err = format!("{:?}", err); - error!(CtrlPortStartFailed, "{:?}", err).await; - std::process::exit(1); - } - } - } - - pub async fn update_vms(self: &mut ControlPort, self_ip: &str, vms: Vec) { - let ips: Vec = vms - .iter() - .map(|vm| vm.private_ip_addr.to_string()) - .collect(); - // Detect changes and exit early if nothing has changed - let changed = ips.len() != self.vms.len() || ips.iter().any(|ip| !self.vms.contains_key(ip)); - if !changed { - return; - } - let self_vm_vec: Vec<&VMMetadata> = vms - .iter() - .filter(|vm| vm.private_ip_addr == self_ip) - .collect(); - if self_vm_vec.len() == 0 { - warn!( - NoDnsPrivateIp, - "Failed to find self in cluster. Maybe I am being shut down or initialize?" - ); - // TODO: Should this error propagate up to the stats loop or no? - return; - } else if self_vm_vec.len() > 1 { - // This hopefully never happens, but if it does, we need to change the daemon initialization - error!( - DuplicateDnsPrivateIp, - "Private IP address collision detected! I don't know who I really am!" - ) - .await; - // TODO: Should this error propagate up to the stats loop or no? - return; - } - let self_vm = self_vm_vec[0].clone(); - let mut region_vms = HashMap::new(); - vms - .iter() - .filter(|vm| vm.cloud == self_vm.cloud && vm.region == self_vm.region) - .for_each(|vm| { - region_vms.insert(vm.private_ip_addr.clone(), vm.clone()); - }); - let mut all_vms = HashMap::new(); - vms.iter().for_each(|vm| { - all_vms.insert(vm.private_ip_addr.clone(), vm.clone()); - }); - let mut other_region_ips: Vec = region_vms - .keys() - .filter(|ip| ip.as_str() != self_ip) - .filter(|ip| *self.vms_up.get(ip.clone()).unwrap_or(&false)) - .map(|ip| ip.clone()) - .collect(); - { - // WTF, Rust? Why is `drop(var)` not good enough when there's an `await` later on? - let region_ips = Arc::clone(®ION_VMS); - let mut region_ips_mut = region_ips.write().unwrap(); - region_ips_mut.clear(); - region_ips_mut.append(&mut other_region_ips); - } - self.vms = all_vms; - self.self_vm = Some(self_vm); - self.region_vms = region_vms; - self.lrh.update(ips); - self.rebalance_data().await; - } - - pub fn is_leader(self: &ControlPort) -> bool { - match &self.self_vm { - Some(self_vm) => self.lrh.get_leader_id() == self_vm.private_ip_addr, - None => false, - } - } - - pub fn get_leader(self: &ControlPort) -> Option<&VMMetadata> { - self.vms.get(self.lrh.get_leader_id()) - } - - pub async fn check_cluster_health(self: &mut ControlPort) { - let cluster_secret = CLUSTER_SECRET.get().unwrap().clone().unwrap(); - let mut health = vec![]; - let nodes = self.lrh.get_mut_nodes(); - for node in nodes.iter() { - let mut req = Request::builder() - .method("GET") - .uri(format!("https://{}:4142/clusterHealth", node.id)); - req = req.header(cluster_secret.as_str(), "true"); - health.push(self.client.request(req.body(Body::empty()).unwrap())); - } - let health_res = join_all(health).await; - let mut health_change = false; - for (i, res) in health_res.iter().enumerate() { - let id = nodes[i].id.clone(); - match res { - Err(_) => { - if !self.vms_up.contains_key(&id) || *self.vms_up.get(&id).unwrap() == true { - health_change = true; - } - nodes[i].is_up = false; - self.vms_up.insert(id, false); - } - Ok(res) => { - let is_up = res.status().as_u16() == 200; - if !self.vms_up.contains_key(&id) || *self.vms_up.get(&id).unwrap() != is_up { - health_change = true; - } - nodes[i].is_up = is_up; - self.vms_up.insert(id, is_up); - } - } - } - if health_change { - self.rebalance_data().await; - } - } - - pub fn is_up(self: &mut ControlPort) -> bool { - match &self.self_vm { - Some(self_vm) => match self.vms_up.get(&self_vm.private_ip_addr) { - Some(s) => *s, - None => false, - }, - None => false, - } - } - - pub fn get_vm_for_key(self: &ControlPort, key: &str) -> &VMMetadata { - &self.vms[self.lrh.get_primary_node_id(key)] - } - - pub fn get_vms_for_key(self: &ControlPort, key: &str) -> Vec<&VMMetadata> { - self - .lrh - .get_assigned_nodes_id(key) - .into_iter() - .map(|priv_ip_addr| &self.vms[&priv_ip_addr.to_string()]) - .collect() - } - - pub fn get_closest_vm_for_key(self: &ControlPort, key: &str) -> (&VMMetadata, bool) { - let vms = self.get_vms_for_key(key); - let mut close_vms = vms - .into_iter() - .filter(|vm| self.region_vms.contains_key(&vm.private_ip_addr)); - match close_vms.next() { - Some(close_vm) => (&close_vm, true), - // Nothing is close, just go with the primary node - None => (self.get_vm_for_key(key), false), - } - } - - pub fn is_key_owner(self: &ControlPort, key: &str) -> bool { - match &self.self_vm { - Some(my) => self.get_vm_for_key(key).private_ip_addr == my.private_ip_addr, - None => false, - } - } - - pub async fn dsgetf(self: &ControlPort, key: &str) -> Option> { - self.dsgetf_inner(key).await.ok() - } - - async fn dsgetf_inner(self: &ControlPort, key: &str) -> DaemonResult> { - let (vm, is_close) = self.get_closest_vm_for_key(key); - let url = if is_close { - format!("https://{}:4142/datastore/getf", vm.private_ip_addr) - } else { - format!("https://{}:4142/datastore/getf", vm.public_ip_addr) - }; - let req = Request::builder().method("POST").uri(url); - let cluster_secret = CLUSTER_SECRET.get().unwrap().clone().unwrap(); - let req = req.header(cluster_secret.as_str(), "true"); - let req_obj = req.body(Body::from( - json!(DSGet { - nskey: key.to_string() - }) - .to_string(), - ))?; - let mut res = self.client.request(req_obj).await?; - let bytes = hyper::body::to_bytes(res.body_mut()).await?; - let pb = protos::HandlerMemory::HandlerMemory::parse_from_bytes(&bytes)?; - Ok(HandlerMemory::from_pb(&pb)?) - } - - pub async fn dsgetv(self: &ControlPort, key: &str) -> Option> { - self.dsgetv_inner(key).await.ok() - } - - async fn dsgetv_inner(self: &ControlPort, key: &str) -> DaemonResult> { - let (vm, is_close) = self.get_closest_vm_for_key(key); - let url = if is_close { - format!("https://{}:4142/datastore/getv", vm.private_ip_addr) - } else { - format!("https://{}:4142/datastore/getv", vm.public_ip_addr) - }; - let req = Request::builder().method("POST").uri(url); - let cluster_secret = CLUSTER_SECRET.get().unwrap().clone().unwrap(); - let req = req.header(cluster_secret.as_str(), "true"); - let req_obj = req.body(Body::from( - json!(DSGet { - nskey: key.to_string() - }) - .to_string(), - ))?; - let mut res = self.client.request(req_obj).await?; - let bytes = hyper::body::to_bytes(res.body_mut()).await?; - let pb = protos::HandlerMemory::HandlerMemory::parse_from_bytes(&bytes)?; - Ok(HandlerMemory::from_pb(&pb)?) - } - - pub async fn dshas(self: &ControlPort, key: &str) -> bool { - self.dshas_inner(key).await.unwrap_or(false) - } - - async fn dshas_inner(self: &ControlPort, key: &str) -> DaemonResult { - let (vm, is_close) = self.get_closest_vm_for_key(key); - let url = if is_close { - format!("https://{}:4142/datastore/has", vm.private_ip_addr) - } else { - format!("https://{}:4142/datastore/has", vm.public_ip_addr) - }; - let req = Request::builder().method("POST").uri(url); - let cluster_secret = CLUSTER_SECRET.get().unwrap().clone().unwrap(); - let req = req.header(cluster_secret.as_str(), "true"); - let req_obj = req.body(Body::from( - json!(DSGet { - nskey: key.to_string() - }) - .to_string(), - ))?; - let mut res = self.client.request(req_obj).await?; - let bytes = hyper::body::to_bytes(res.body_mut()).await?; - Ok(std::str::from_utf8(&bytes)? == "true") - } - - pub async fn dsdel(self: &ControlPort, key: &str) -> bool { - let vms = self.get_vms_for_key(key); - let urls: Vec = vms - .into_iter() - .map(|vm| format!("https://{}:4142/datastore/del", vm.public_ip_addr)) - .collect(); - let calls = urls.into_iter().map(|url| self.dsdel_inner(url, key)); - let reses = join_all(calls).await; - *reses[0].as_ref().unwrap_or(&false) - } - - async fn dsdel_inner(self: &ControlPort, url: String, key: &str) -> DaemonResult { - let req = Request::builder().method("POST").uri(url); - let cluster_secret = CLUSTER_SECRET.get().unwrap().clone().unwrap(); - let req = req.header(cluster_secret.as_str(), "true"); - let req_obj = req.body(Body::from( - json!(DSGet { - nskey: key.to_string() - }) - .to_string(), - ))?; - let mut res = self.client.request(req_obj).await?; - // TODO: How to handle if the various nodes are out-of-sync - let bytes = hyper::body::to_bytes(res.body_mut()).await?; - Ok(std::str::from_utf8(&bytes)? == "true") - } - - pub async fn dssetf(self: &ControlPort, key: &str, val: &Arc) -> bool { - let vms = self.get_vms_for_key(key); - let urls: Vec = vms - .into_iter() - .map(|vm| format!("https://{}:4142/datastore/setf", vm.public_ip_addr)) - .collect(); - let calls = urls.into_iter().map(|url| self.dssetf_inner(url, key, val)); - let reses = join_all(calls).await; - *reses[0].as_ref().unwrap_or(&false) - } - - async fn dssetf_inner( - self: &ControlPort, - url: String, - key: &str, - val: &Arc, - ) -> DaemonResult { - let mut hm = HandlerMemory::new(None, 1)?; - hm.write_fractal(0, &HandlerMemory::str_to_fractal(key))?; - HandlerMemory::transfer(val, 0, &mut hm, 1)?; - let mut out = vec![]; - hm.to_pb().write_to_vec(&mut out).unwrap(); - let req = Request::builder().method("POST").uri(url); - let cluster_secret = CLUSTER_SECRET.get().unwrap().clone().unwrap(); - let req = req.header(cluster_secret.as_str(), "true"); - let req_obj = req.body(Body::from(out))?; - let mut res = self.client.request(req_obj).await?; - let bytes = hyper::body::to_bytes(res.body_mut()).await?; - Ok(std::str::from_utf8(&bytes)? == "true") - } - - pub async fn dssetv(self: &ControlPort, key: &str, val: &Arc) -> bool { - let vms = self.get_vms_for_key(key); - let urls: Vec = vms - .into_iter() - .map(|vm| format!("https://{}:4142/datastore/setv", vm.public_ip_addr)) - .collect(); - let calls = urls.into_iter().map(|url| self.dssetv_inner(url, key, val)); - let reses = join_all(calls).await; - *reses[0].as_ref().unwrap_or(&false) - } - - async fn dssetv_inner( - self: &ControlPort, - url: String, - key: &str, - val: &Arc, - ) -> DaemonResult { - let mut hm = HandlerMemory::new(None, 1)?; - hm.write_fractal(0, &HandlerMemory::str_to_fractal(key))?; - HandlerMemory::transfer(val, 0, &mut hm, 1)?; - let mut out = vec![]; - hm.to_pb().write_to_vec(&mut out).unwrap(); - let req = Request::builder().method("POST").uri(url); - let cluster_secret = CLUSTER_SECRET.get().unwrap().clone().unwrap(); - let req = req.header(cluster_secret.as_str(), "true"); - let req_obj = req.body(Body::from(out))?; - let mut res = self.client.request(req_obj).await?; - let bytes = hyper::body::to_bytes(res.body_mut()).await?; - Ok(std::str::from_utf8(&bytes)? == "true") - } - - pub async fn dskeys(self: &ControlPort, ip: &str) -> (String, Vec) { - ( - ip.to_string(), - self.dskeys_inner(ip).await.unwrap_or(Vec::new()), - ) - } - - async fn dskeys_inner(self: &ControlPort, ip: &str) -> DaemonResult> { - let url = format!("https://{}:4142/datastore/keys", ip); - let req = Request::builder().method("GET").uri(url); - let cluster_secret = CLUSTER_SECRET.get().unwrap().clone().unwrap(); - let req = req.header(cluster_secret.as_str(), "true"); - let req_obj = req.body(Body::empty())?; - let mut res = self.client.request(req_obj).await?; - let bytes = hyper::body::to_bytes(res.body_mut()).await?; - let key_str = std::str::from_utf8(&bytes)?; - if key_str.len() > 0 { - Ok(key_str.split("\n").map(|s| s.to_string()).collect()) - } else { - Err(Box::new(VMError::Other( - "No keys on remote node".to_string(), - ))) - } - } - - pub async fn dsrrun( - self: &ControlPort, - nskey: &str, - subhandler_id: i64, - hand_mem: &Arc, - ) -> Arc { - let vm = self.get_vm_for_key(nskey); - // TODO: Use private ip if possible - let url = format!("https://{}:4142/datastore/dsrrun", vm.public_ip_addr); - match self.dsrrun_inner(url, nskey, subhandler_id, hand_mem).await { - Ok(hm) => hm, - Err(_) => { - let mut err_hm = HandlerMemory::new(None, 1).expect("what"); - err_hm - .write_fractal( - CLOSURE_ARG_MEM_START, - &HandlerMemory::str_to_fractal("ERROR TODO"), - ) - .expect("what"); - err_hm - } - } - } - - async fn dsrrun_inner( - self: &ControlPort, - url: String, - nskey: &str, - subhandler_id: i64, - hand_mem: &Arc, - ) -> DaemonResult> { - let req = Request::builder().method("POST").uri(url); - let cluster_secret = CLUSTER_SECRET.get().unwrap().clone().unwrap(); - let req = req.header(cluster_secret.as_str(), "true"); - let req = req.header("nskey", nskey); - let req = req.header("subhandler_id", format!("{}", subhandler_id)); - let orphan_hm = HandlerMemory::fork(hand_mem.clone())?; // TODO: This clone is a terrible idea - let orphan_hm = orphan_hm.drop_parent()?; - let mut out = vec![]; - orphan_hm.to_pb().write_to_vec(&mut out).unwrap(); - let req_obj = req.body(Body::from(out))?; - let mut res = self.client.request(req_obj).await?; - let bytes = hyper::body::to_bytes(res.body_mut()).await?; - let pb = protos::HandlerMemory::HandlerMemory::parse_from_bytes(&bytes)?; - Ok(HandlerMemory::from_pb(&pb)?) - } - - pub async fn dsmrun( - self: &ControlPort, - nskey: &str, - subhandler_id: i64, - hand_mem: &Arc, - ) -> Arc { - let vm = self.get_vm_for_key(nskey); - // TODO: Use private ip if possible - let url = format!("https://{}:4142/datastore/dsmrun", vm.public_ip_addr); - match self.dsrrun_inner(url, nskey, subhandler_id, hand_mem).await { - Ok(hm) => hm, - Err(_) => { - let mut err_hm = HandlerMemory::new(None, 1).expect("what"); - err_hm - .write_fractal( - CLOSURE_ARG_MEM_START, - &HandlerMemory::str_to_fractal("ERROR TODO"), - ) - .expect("what"); - err_hm - } - } - } - - async fn dsmrun_inner( - self: &ControlPort, - url: String, - nskey: &str, - subhandler_id: i64, - hand_mem: &Arc, - ) -> DaemonResult> { - let req = Request::builder().method("POST").uri(url); - let cluster_secret = CLUSTER_SECRET.get().unwrap().clone().unwrap(); - let req = req.header(cluster_secret.as_str(), "true"); - let req = req.header("nskey", nskey); - let req = req.header("subhandler_id", format!("{}", subhandler_id)); - let orphan_hm = HandlerMemory::fork(hand_mem.clone())?; // TODO: This clone is a terrible idea - let orphan_hm = orphan_hm.drop_parent()?; - let mut out = vec![]; - orphan_hm.to_pb().write_to_vec(&mut out).unwrap(); - let req_obj = req.body(Body::from(out))?; - let mut res = self.client.request(req_obj).await?; - let bytes = hyper::body::to_bytes(res.body_mut()).await?; - let pb = protos::HandlerMemory::HandlerMemory::parse_from_bytes(&bytes)?; - Ok(HandlerMemory::from_pb(&pb)?) - } - - pub async fn dsrwith( - self: &ControlPort, - nskey: &str, - with_addr: i64, - subhandler_id: i64, - hand_mem: &Arc, - ) -> Arc { - let vm = self.get_vm_for_key(nskey); - // TODO: Use private ip if possible - let url = format!("https://{}:4142/datastore/dsrwith", vm.public_ip_addr); - match self - .dsrwith_inner(url, nskey, with_addr, subhandler_id, hand_mem) - .await - { - Ok(hm) => hm, - Err(_) => { - let mut err_hm = HandlerMemory::new(None, 1).expect("what"); - err_hm - .write_fractal( - CLOSURE_ARG_MEM_START, - &HandlerMemory::str_to_fractal("ERROR TODO"), - ) - .expect("what"); - err_hm - } - } - } - - async fn dsrwith_inner( - self: &ControlPort, - url: String, - nskey: &str, - with_addr: i64, - subhandler_id: i64, - hand_mem: &Arc, - ) -> DaemonResult> { - let req = Request::builder().method("POST").uri(url); - let cluster_secret = CLUSTER_SECRET.get().unwrap().clone().unwrap(); - let req = req.header(cluster_secret.as_str(), "true"); - let req = req.header("nskey", nskey); - let req = req.header("subhandler_id", format!("{}", subhandler_id)); - let mut hand_mem = HandlerMemory::fork(hand_mem.clone())?; // TODO: We need two of them!? - hand_mem.register_out(with_addr, 1, CLOSURE_ARG_MEM_START)?; - let mut out_hm = HandlerMemory::new(None, 2)?; - HandlerMemory::transfer( - &hand_mem, - CLOSURE_ARG_MEM_START, - &mut out_hm, - CLOSURE_ARG_MEM_START + 2, - )?; - let mut out = vec![]; - out_hm.to_pb().write_to_vec(&mut out).unwrap(); - let req_obj = req.body(Body::from(out))?; - let mut res = self.client.request(req_obj).await?; - let bytes = hyper::body::to_bytes(res.body_mut()).await?; - let pb = protos::HandlerMemory::HandlerMemory::parse_from_bytes(&bytes)?; - Ok(HandlerMemory::from_pb(&pb)?) - } - - pub async fn dsmwith( - self: &ControlPort, - nskey: &str, - with_addr: i64, - subhandler_id: i64, - hand_mem: &Arc, - ) -> Arc { - let vm = self.get_vm_for_key(nskey); - // TODO: Use private ip if possible - let url = format!("https://{}:4142/datastore/dsmwith", vm.public_ip_addr); - match self - .dsmwith_inner(url, nskey, with_addr, subhandler_id, hand_mem) - .await - { - Ok(hm) => hm, - Err(_) => { - let mut err_hm = HandlerMemory::new(None, 1).expect("what"); - err_hm - .write_fractal( - CLOSURE_ARG_MEM_START, - &HandlerMemory::str_to_fractal("ERROR TODO"), - ) - .expect("what"); - err_hm - } - } - } - - async fn dsmwith_inner( - self: &ControlPort, - url: String, - nskey: &str, - with_addr: i64, - subhandler_id: i64, - hand_mem: &Arc, - ) -> DaemonResult> { - let req = Request::builder().method("POST").uri(url); - let cluster_secret = CLUSTER_SECRET.get().unwrap().clone().unwrap(); - let req = req.header(cluster_secret.as_str(), "true"); - let req = req.header("nskey", nskey); - let req = req.header("subhandler_id", format!("{}", subhandler_id)); - let mut hand_mem = HandlerMemory::fork(hand_mem.clone())?; // TODO: We need two of them!? - hand_mem.register_out(with_addr, 1, CLOSURE_ARG_MEM_START)?; - let mut out_hm = HandlerMemory::new(None, 2)?; - HandlerMemory::transfer( - &hand_mem, - CLOSURE_ARG_MEM_START, - &mut out_hm, - CLOSURE_ARG_MEM_START + 2, - )?; - let mut out = vec![]; - out_hm.to_pb().write_to_vec(&mut out).unwrap(); - let req_obj = req.body(Body::from(out))?; - let mut res = self.client.request(req_obj).await?; - let bytes = hyper::body::to_bytes(res.body_mut()).await?; - let pb = protos::HandlerMemory::HandlerMemory::parse_from_bytes(&bytes)?; - Ok(HandlerMemory::from_pb(&pb)?) - } - - pub fn dsmonly( - self: &ControlPort, - nskey: &str, - subhandler_id: i64, - hand_mem: &Arc, - ) { - let vm = self.get_vm_for_key(nskey); - // TODO: Use private ip if possible - let url = format!("https://{}:4142/datastore/dsmonly", vm.public_ip_addr); - let req = Request::builder().method("POST").uri(url); - let cluster_secret = CLUSTER_SECRET.get().unwrap().clone().unwrap(); - let req = req.header(cluster_secret.as_str(), "true"); - let req = req.header("nskey", nskey); - let req = req.header("subhandler_id", format!("{}", subhandler_id)); - let orphan_hm = HandlerMemory::fork(hand_mem.clone()).expect("what"); // TODO: This clone is a terrible idea - let orphan_hm = orphan_hm.drop_parent().expect("what"); - let mut out = vec![]; - orphan_hm.to_pb().write_to_vec(&mut out).unwrap(); - let req_obj = req.body(Body::from(out)).expect("what"); - let client = self.client.clone(); - task::spawn(async move { - client.request(req_obj).await; - }); - } - - pub fn dswonly( - self: &ControlPort, - nskey: &str, - with_addr: i64, - subhandler_id: i64, - hand_mem: &Arc, - ) { - let vm = self.get_vm_for_key(nskey); - // TODO: Use private ip if possible - let url = format!("https://{}:4142/datastore/dsmonly", vm.public_ip_addr); - let req = Request::builder().method("POST").uri(url); - let cluster_secret = CLUSTER_SECRET.get().unwrap().clone().unwrap(); - let req = req.header(cluster_secret.as_str(), "true"); - let req = req.header("nskey", nskey); - let req = req.header("subhandler_id", format!("{}", subhandler_id)); - let mut hand_mem = HandlerMemory::fork(hand_mem.clone()).expect("what"); // TODO: We need two of them!? - hand_mem - .register_out(with_addr, 1, CLOSURE_ARG_MEM_START) - .expect("what"); - let mut out_hm = HandlerMemory::new(None, 2).expect("what"); - HandlerMemory::transfer( - &hand_mem, - CLOSURE_ARG_MEM_START, - &mut out_hm, - CLOSURE_ARG_MEM_START + 2, - ) - .expect("what"); - let mut out = vec![]; - out_hm.to_pb().write_to_vec(&mut out).unwrap(); - let req_obj = req.body(Body::from(out)).expect("what"); - let client = self.client.clone(); - task::spawn(async move { - client.request(req_obj).await; - }); - } - - pub async fn dsrclos( - self: &ControlPort, - nskey: &str, - subhandler_id: i64, - ret_addr: i64, - hand_mem: &Arc, - ) -> Arc { - let vm = self.get_vm_for_key(nskey); - // TODO: Use private ip if possible - let url = format!("https://{}:4142/datastore/dsrclos", vm.public_ip_addr); - match self - .dsrclos_inner(url, nskey, subhandler_id, ret_addr, hand_mem) - .await - { - Ok(hm) => hm, - Err(_) => { - let mut err_hm = HandlerMemory::new(None, 1).expect("what"); - err_hm - .write_fractal( - CLOSURE_ARG_MEM_START, - &HandlerMemory::str_to_fractal("ERROR TODO"), - ) - .expect("what"); - err_hm - } - } - } - - async fn dsrclos_inner( - self: &ControlPort, - url: String, - nskey: &str, - subhandler_id: i64, - ret_addr: i64, - hand_mem: &Arc, - ) -> DaemonResult> { - let req = Request::builder().method("POST").uri(url); - let cluster_secret = CLUSTER_SECRET.get().unwrap().clone().unwrap(); - let req = req.header(cluster_secret.as_str(), "true"); - let req = req.header("nskey", nskey); - let req = req.header("subhandler_id", format!("{}", subhandler_id)); - let req = req.header("ret_addr", format!("{}", ret_addr)); - let mut out = vec![]; - hand_mem.to_pb().write_to_vec(&mut out).unwrap(); - let req_obj = req.body(Body::from(out))?; - let mut res = self.client.request(req_obj).await?; - let bytes = hyper::body::to_bytes(res.body_mut()).await?; - let pb = protos::HandlerMemory::HandlerMemory::parse_from_bytes(&bytes)?; - Ok(HandlerMemory::from_pb(&pb)?) - } - - pub async fn dsmclos( - self: &ControlPort, - nskey: &str, - subhandler_id: i64, - ret_addr: i64, - hand_mem: &Arc, - ) -> Arc { - let vm = self.get_vm_for_key(nskey); - // TODO: Use private ip if possible - let url = format!("https://{}:4142/datastore/dsmclos", vm.public_ip_addr); - match self - .dsmclos_inner(url, nskey, subhandler_id, ret_addr, hand_mem) - .await - { - Ok(hm) => hm, - Err(_) => { - let mut err_hm = HandlerMemory::new(None, 1).expect("what"); - err_hm - .write_fractal( - CLOSURE_ARG_MEM_START, - &HandlerMemory::str_to_fractal("ERROR TODO"), - ) - .expect("what"); - err_hm - } - } - } - - async fn dsmclos_inner( - self: &ControlPort, - url: String, - nskey: &str, - subhandler_id: i64, - ret_addr: i64, - hand_mem: &Arc, - ) -> DaemonResult> { - let req = Request::builder().method("POST").uri(url); - let cluster_secret = CLUSTER_SECRET.get().unwrap().clone().unwrap(); - let req = req.header(cluster_secret.as_str(), "true"); - let req = req.header("nskey", nskey); - let req = req.header("subhandler_id", format!("{}", subhandler_id)); - let req = req.header("ret_addr", format!("{}", ret_addr)); - let mut out = vec![]; - hand_mem.to_pb().write_to_vec(&mut out).unwrap(); - let pb = protos::HandlerMemory::HandlerMemory::parse_from_bytes(&out)?; - let hand_mem_2 = HandlerMemory::from_pb(&pb)?; - eprintln!("hand_mem {:?}", hand_mem); - eprintln!("hand_mem_2 {:?}", hand_mem_2); - let req_obj = req.body(Body::from(out))?; - let mut res = self.client.request(req_obj).await?; - let bytes = hyper::body::to_bytes(res.body_mut()).await?; - let pb = protos::HandlerMemory::HandlerMemory::parse_from_bytes(&bytes)?; - Ok(HandlerMemory::from_pb(&pb)?) - } - - fn get_all_vms_by_ip(self: &ControlPort) -> Vec { - self - .vms - .iter() - .map(|(k, v)| { - if self.region_vms.contains_key(k) { - v.private_ip_addr.clone() - } else { - v.public_ip_addr.clone() - } - }) - .collect() - } - - async fn rebalance_data(self: &ControlPort) { - // 1. Get lists of keys across all live nodes in the cluster - // 2. For each list: - // 2a. Determine if this node should have the key as the new primary owner, secondary owner, or - // not at all. - // 2b. If the primary owner and this list is from the node that is the first secondary owner, - // push the key and node IP to a 'get' list. - // 2c. If the primary owner and this list is from a node that should not own the key, push the - // key and the node IP to a 'del' list. - // 2d. If a secondary owner and this list is from the node that is the primary owner, push the - // key and node IP to a 'get' list. Do not create a 'del' entry. - // 3. Iterate through the 'get' list and grab the data for each key and store it. - // 3a. On failure, expand the query to every node that should have the data and re-query again. - // 3b. On failure again, query every node, period, for the data. - // 3c. On final failure, abort. It may have been a race condition with a key that was - // explicitly deleted. TODO: Add log records to verify this? - // 4. Iterate through the 'del' list and delete the data from the node that should no longer - // have it. Failure means that something else deleted it already, so it can be ignored. - println!("Rebalancing Keys"); - let vms = self.get_all_vms_by_ip(); - let fake_vm = VMMetadata::fake_vm(); - let self_vm = self.self_vm.as_ref().unwrap_or(&fake_vm); - let key_lists = join_all(vms.iter().map(|ip| self.dskeys(ip))).await; - let mut get_list: Vec<(String, String)> = Vec::new(); // (Key, Source IP) - let mut del_list: Vec<(String, String)> = Vec::new(); // (Key, Source IP) - key_lists.iter().for_each(|(ip, key_list)| { - let ip_str = ip.to_string(); - key_list.iter().for_each(|key| { - let relevant_nodes = self.get_vms_for_key(key); - if relevant_nodes.len() > 1 { - // Don't try to do any of this if the cluster is just 1 node - let self_in_list = relevant_nodes - .iter() - .any(|node| node.public_ip_addr == self_vm.public_ip_addr); - if !self_in_list { - return; - } - let primary_node = relevant_nodes[0]; - let first_secondary = relevant_nodes[1]; - let self_primary = primary_node.public_ip_addr == self_vm.public_ip_addr; - if self_primary { - let this_list_first_secondary = - first_secondary.public_ip_addr == ip_str || first_secondary.private_ip_addr == ip_str; - if this_list_first_secondary { - get_list.push((key.to_string(), ip.to_string())); - } - let this_list_irrelevant = relevant_nodes - .iter() - .all(|node| node.public_ip_addr != ip_str && node.private_ip_addr != ip_str); - if this_list_irrelevant { - del_list.push((key.to_string(), ip.to_string())); - } - } else { - let this_list_primary = - primary_node.public_ip_addr == ip_str || primary_node.private_ip_addr == ip_str; - if this_list_primary { - get_list.push((key.to_string(), ip.to_string())); - } - } - } - }); - }); - for (key, ip) in get_list.iter() { - // For our purposes, we don't want the query result-wrapped, so we have a special raw - // endpoint to get the data from - let url = format!("https://{}:4142/datastore/getr", ip); - let req = Request::builder().method("POST").uri(url); - let cluster_secret = CLUSTER_SECRET.get().unwrap().clone().unwrap(); - let req = req.header(cluster_secret.as_str(), "true"); - let req_obj = req.body(Body::from( - json!(DSGet { - nskey: key.to_string() - }) - .to_string(), - )); - let req_obj = match req_obj { - Ok(req_obj) => req_obj, - Err(e) => { - error!(UnexpectedError, "Should be impossible {:?}", e).await; - continue; - } - }; - let mut res = match self.client.request(req_obj).await { - Ok(res) => res, - Err(e) => { - error!(UnexpectedError, "Could not talk to peer {:?}", e).await; - continue; - } - }; - let bytes = match hyper::body::to_bytes(res.body_mut()).await { - Ok(bytes) => bytes, - Err(e) => { - error!(UnexpectedError, "Could not read data from peer {:?}", e).await; - continue; - } - }; - let pb = match protos::HandlerMemory::HandlerMemory::parse_from_bytes(&bytes) { - Ok(pb) => pb, - Err(e) => { - error!(UnexpectedError, "Could not parse data from peer {:?}", e).await; - continue; - } - }; - let hm = match HandlerMemory::from_pb(&pb) { - Ok(hm) => hm, - Err(e) => { - error!(UnexpectedError, "This should be impossible {:?}", e).await; - continue; - } - }; - DS.insert(key.to_string(), hm); - } - for (key, ip) in del_list.iter() { - let url = format!("https://{}:4142/datastore/del", ip); - let req = Request::builder().method("POST").uri(url); - let cluster_secret = CLUSTER_SECRET.get().unwrap().clone().unwrap(); - let req = req.header(cluster_secret.as_str(), "true"); - let req_obj = req.body(Body::from( - json!(DSGet { - nskey: key.to_string() - }) - .to_string(), - )); - let req_obj = match req_obj { - Ok(req_obj) => req_obj, - Err(e) => { - error!(UnexpectedError, "Should be impossible {:?}", e).await; - continue; - } - }; - match self.client.request(req_obj).await { - Ok(res) => res, - Err(e) => { - error!(UnexpectedError, "Could not talk to peer {:?}", e).await; - continue; - } - }; - } - } -} diff --git a/avm/src/daemon/daemon.rs b/avm/src/daemon/daemon.rs deleted file mode 100644 index 2a64ea71e..000000000 --- a/avm/src/daemon/daemon.rs +++ /dev/null @@ -1,355 +0,0 @@ -use std::collections::HashMap; -use std::env; -use std::error::Error; -use std::fs::read; -use std::io::Read; - -use base64; -use byteorder::{LittleEndian, ReadBytesExt}; -use flate2::read::GzDecoder; -use once_cell::sync::OnceCell; -use serde::{Deserialize, Serialize}; -use serde_json::{json, Value}; -use tokio::sync::watch::{self, Receiver}; -use tokio::task; -use tokio::time::{sleep, Duration}; - -use crate::cloud::common::{file_exist, get_app_tar_gz_b64, get_dockerfile_b64}; -use crate::cloud::{deploy, CLUSTER_ID}; -use crate::daemon::ctrl::ControlPort; -use crate::daemon::dns::DNS; -use crate::daemon::stats::{get_stats_factor, get_v1_stats}; -use crate::vm::http::{HttpType, HttpsConfig}; -use crate::vm::run::run; -use crate::{error, warn}; - -pub type DaemonResult = std::result::Result>; - -pub static CLUSTER_SECRET: OnceCell> = OnceCell::new(); -pub static NON_HTTP: OnceCell = OnceCell::new(); -pub static DAEMON_PROPS: OnceCell = OnceCell::new(); -pub static CONTROL_PORT_CHANNEL: OnceCell> = OnceCell::new(); - -#[allow(non_snake_case)] -#[derive(Deserialize, Debug, Serialize)] -pub struct DaemonProperties { - pub clusterId: String, - pub agzB64: String, - pub deployToken: String, - pub domain: String, - pub filesB64: HashMap, -} - -#[cfg(target_os = "linux")] -async fn get_private_ip(is_local: bool) -> DaemonResult { - if is_local { - Ok("127.0.0.1".to_string()) - } else { - let res = tokio::process::Command::new("hostname") - .arg("-I") - .output() - .await?; - let stdout = res.stdout; - let private_ip = String::from_utf8(stdout)?; - match private_ip.trim().split_whitespace().next() { - Some(private_ip) => Ok(private_ip.to_string()), - None => Err("No ip found".into()), - } - } -} - -#[cfg(not(target_os = "linux"))] -async fn get_private_ip(is_local: bool) -> DaemonResult { - if is_local { - Ok("127.0.0.1".to_string()) - } else { - Err("`hostname` command does not exist in this OS".into()) - } -} - -pub async fn post_v1(endpoint: &str, body: Value) -> String { - let resp = deploy::post_v1(endpoint, body).await; - match resp { - Ok(res) => res, - Err(err) => { - let err = format!("{:?}", err); - error!(PostFailed, "Endpoint: {} - Error: {:?}", endpoint, err).await; - err - } - } -} - -async fn post_v1_scale( - cluster_id: &str, - agz_b64: &str, - deploy_token: &str, - factor: &str, - files_b64: &HashMap, -) -> String { - let non_http = NON_HTTP.get().unwrap_or(&false); - let scale_body = json!({ - "clusterId": cluster_id, - "agzB64": agz_b64, - "deployToken": deploy_token, - "clusterFactor": factor, - "nonHttp": *non_http, - "filesB64": files_b64, - }); - post_v1("scale", scale_body).await -} - -// acknowledge deploy service to refresh secret -async fn post_v1_ack(cluster_id: &str, deploy_token: &str) -> DaemonResult { - let mut ack_body = json!({ - "deployToken": deploy_token, - "clusterId": cluster_id, - }); - let cluster_secret = CLUSTER_SECRET.get().unwrap(); - if let Some(cluster_secret) = cluster_secret.as_ref() { - ack_body - .as_object_mut() - .unwrap() - .insert("clusterSecret".to_string(), json!(cluster_secret)); - } else { - error!(NoClusterSecret, "No cluster secret found.").await; - } - Ok(post_v1("ack", ack_body).await) -} - -async fn run_agz_b64(agz_b64: &str) -> DaemonResult<()> { - let bytes = base64::decode(agz_b64); - let non_http = NON_HTTP.get().unwrap_or(&false); - if let Ok(bytes) = bytes { - let agz = GzDecoder::new(bytes.as_slice()); - let count = agz.bytes().count(); - let mut bytecode = vec![0; count / 8]; - let mut gz = GzDecoder::new(bytes.as_slice()); - let gz_read_i64 = gz.read_i64_into::(&mut bytecode); - if *non_http { - if gz_read_i64.is_ok() { - if let Err(err) = run(bytecode, None).await { - return Err(format!("Run server has failed. {}", err).into()); - } - } else { - return Err("AGZ file appears to be corrupt.".into()); - } - } else { - let pwd = env::current_dir(); - match pwd { - Ok(pwd) => { - let priv_key = read(format!("{}/key.pem", pwd.display())); - let cert = read(format!("{}/certificate.pem", pwd.display())); - if let (Ok(priv_key), Ok(cert)) = (priv_key, cert) { - if gz_read_i64.is_ok() { - if let Err(err) = run( - bytecode, - Some(HttpType::HTTPS(HttpsConfig { - port: 443, - priv_key: String::from_utf8(priv_key).unwrap(), - cert: String::from_utf8(cert).unwrap(), - })), - ) - .await - { - return Err(format!("Run server has failed. {}", err).into()); - } - } else { - return Err("AGZ file appears to be corrupt.".into()); - } - } else { - return Err("No self-signed certificate".into()); - } - } - Err(err) => { - return Err(format!("{:?}", err).into()); - } - } - } - } else { - return Err("AGZ payload not properly base64-encoded.".into()); - } - Ok(()) -} - -async fn get_files_b64(is_local_anycloud_app: bool) -> HashMap { - // TODO: Eliminate this - let mut files_b64 = HashMap::new(); - // Check for AnyCloud files - if is_local_anycloud_app { - files_b64.insert("Dockerfile".to_string(), get_dockerfile_b64().await); - files_b64.insert("app.tar.gz".to_string(), get_app_tar_gz_b64(false).await); - } - files_b64 -} - -async fn set_local_daemon_props(is_local_anycloud_app: bool, local_agz_b64: Option) -> () { - let files_b64 = get_files_b64(is_local_anycloud_app).await; - let agz_b64 = if let Some(local_agz_b64) = local_agz_b64 { - local_agz_b64 - } else { - eprintln!( - "running the Alan Daemon in a local environment requires the \ - --agz-file argument specifying the .agz file to run" - ); - std::process::exit(1); - }; - DAEMON_PROPS - .set(DaemonProperties { - clusterId: "daemon-local-cluster".to_string(), - agzB64: agz_b64, - deployToken: "no-token-needed".to_string(), - domain: "daemon-local-cluster".to_string(), - filesB64: files_b64, - }) - .unwrap(); -} - -fn maybe_create_certs() { - if !file_exist("key.pem") && !file_exist("certificate.pem") { - // Self signed certs for local dev - // openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 365 -out certificate.pem -subj "/C=US/ST=California/O=Alan Technologies, Inc/CN=*.alandeploy.com" - let mut open_ssl = std::process::Command::new("openssl") - .stdout(std::process::Stdio::null()) - .stderr(std::process::Stdio::null()) - .arg("req") - .arg("-newkey") - .arg("rsa:2048") - .arg("-nodes") - .arg("-keyout") - .arg("key.pem") - .arg("-x509") - .arg("-days") - .arg("365") - .arg("-out") - .arg("certificate.pem") - .arg("-subj") - .arg("/C=US/ST=California/O=Alan Technologies, Inc/CN=*.alandeploy.com") - .spawn() - .expect("Error generating self signed certificate"); - open_ssl.wait().expect("Failed to wait on child"); - } -} - -async fn get_daemon_props( - is_local_anycloud_app: bool, // TODO: Eliminate this - local_agz_b64: Option, -) -> Option<&'static DaemonProperties> { - if local_agz_b64.is_some() { - set_local_daemon_props(is_local_anycloud_app, local_agz_b64).await; - return DAEMON_PROPS.get(); - } - let duration = Duration::from_secs(10); - let mut counter: u8 = 0; - // Check every 10s over 10 min if props are ready - while counter < 60 { - if let Some(props) = DAEMON_PROPS.get() { - return Some(props); - } - counter += 1; - sleep(duration).await; - } - None -} - -pub async fn start(is_local_anycloud_app: bool, local_agz_b64: Option) { - maybe_create_certs(); - let is_local = local_agz_b64.is_some(); - let mut control_port = ControlPort::start().await; - let (ctrl_tx, ctrl_rx) = watch::channel(control_port.clone()); - CONTROL_PORT_CHANNEL.set(ctrl_rx).unwrap(); - if let Some(daemon_props) = get_daemon_props(is_local_anycloud_app, local_agz_b64).await { - let agz_b64 = &daemon_props.agzB64; - let cluster_id = &daemon_props.clusterId; - let files_b64 = &daemon_props.filesB64; - CLUSTER_ID.set(String::from(cluster_id)).unwrap(); - let domain = &daemon_props.domain; - let deploy_token = &daemon_props.deployToken; - task::spawn(async move { - let period = Duration::from_secs(60); - let mut stats = Vec::new(); - let mut cluster_size = 0; - let self_ip = get_private_ip(is_local).await; - let dns = DNS::new(&domain); - if let (Ok(dns), Ok(self_ip)) = (&dns, &self_ip) { - loop { - let vms = match dns.get_vms(&cluster_id, is_local).await { - Ok(vms) => Some(vms), - Err(err) => { - // We do not retry on failure here since every minute we are updating the vms list - warn!(NoDnsVms, "{}", err); - None - } - }; - // TODO: Figure out how to avoid flushing the LogRendezvousHash table every iteration, but - // avoid bugs with misidentifying cluster changes as not-changed - if let Some(vms) = vms { - cluster_size = vms.len(); - control_port.update_vms(self_ip, vms).await; - ctrl_tx.send(control_port.clone()).unwrap(); - } - if !is_local { - if control_port.is_leader() { - // TODO: Should we keep these leader announcements in the stdout logging? - println!("I am leader!"); - // Do not collect stats until this leader vm is up. Otherwise, will have scaling issues. - if control_port.is_up() { - match get_v1_stats().await { - Ok(s) => stats.push(s), - Err(err) => error!(NoStats, "{}", err).await, - }; - } else { - println!("Leader is not ready. Do not collect stats"); - } - } else { - // Debug print for now - println!("I am NOT the leader! :("); - println!( - "Me: {} Leader: {}", - self_ip, - control_port - .get_leader() - .map(|vm| vm.private_ip_addr.clone()) - .unwrap_or("".to_string()) - ); - } - if stats.len() >= 4 { - let _ack = post_v1_ack(&cluster_id, &deploy_token).await; - let stats_factor = get_stats_factor(&stats); - stats = Vec::new(); - println!( - "VM stats sent for cluster {} of size {}. Cluster factor: {}.", - cluster_id, cluster_size, stats_factor - ); - if &stats_factor != "1" { - post_v1_scale( - &cluster_id, - &agz_b64, - &deploy_token, - &stats_factor, - &files_b64, - ) - .await; - } - } - } - control_port.check_cluster_health().await; - sleep(period).await; - } - } else if let Err(dns_err) = &dns { - error!(NoDns, "DNS error: {}", dns_err).await; - std::process::exit(1); - } else if let Err(self_ip_err) = &self_ip { - error!(NoPrivateIp, "Private ip error: {}", self_ip_err).await; - std::process::exit(1); - } - }); - if let Err(err) = run_agz_b64(&agz_b64).await { - error!(RunAgzFailed, "{:?}", err).await; - std::process::exit(1); - } - } else { - let msg = "No daemon properties defined"; - error!(NoDaemonProps, "{}", msg).await; - std::process::exit(1); - } -} diff --git a/avm/src/daemon/dns.rs b/avm/src/daemon/dns.rs deleted file mode 100644 index b3d4e93b3..000000000 --- a/avm/src/daemon/dns.rs +++ /dev/null @@ -1,111 +0,0 @@ -use std::str; - -use lazy_static::lazy_static; -use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; -use trust_dns_resolver::TokioAsyncResolver; - -use crate::daemon::daemon::DaemonResult; - -lazy_static! { - static ref LOCAL_VM_METADATA: Vec = vec![VMMetadata { - schema_version: "v1".to_string(), - alan_version: option_env!("CARGO_PKG_VERSION").unwrap().to_string(), - cloud: "LOCAL".to_string(), - private_ip_addr: "127.0.0.1".to_string(), - region: "localhost".to_string(), - public_ip_addr: "127.0.0.1".to_string(), - }]; -} - -pub struct DNS { - resolver: TokioAsyncResolver, - domain: String, -} - -#[derive(Clone, Debug)] -pub struct VMMetadata { - schema_version: String, - pub(crate) public_ip_addr: String, - pub(crate) private_ip_addr: String, - pub(crate) alan_version: String, - pub(crate) region: String, - pub(crate) cloud: String, -} - -impl VMMetadata { - fn from_txt_data(data: &[u8]) -> DaemonResult { - let txt = str::from_utf8(&*data); - match txt { - Ok(txt) => { - let err = format!( - "VM metadata in DNS TXT record has invalid schema version: `{}`", - &txt - ); - let parts: Vec<&str> = txt.split("|").collect(); - if parts.len() != 7 || parts[0] != "v1" { - return Err(err.into()); - } - Ok(VMMetadata { - schema_version: parts[0].to_string(), - alan_version: parts[1].to_string(), - public_ip_addr: parts[3].to_string(), - private_ip_addr: parts[4].to_string(), - region: parts[5].to_string(), - cloud: parts[6].to_string(), - }) - } - Err(_) => return Err("Data in TXT record is not a valid string".into()), - } - } - - pub fn fake_vm() -> VMMetadata { - let fake_data = "-1".to_string(); - VMMetadata { - schema_version: fake_data.clone(), - alan_version: fake_data.clone(), - public_ip_addr: fake_data.clone(), - private_ip_addr: fake_data.clone(), - region: fake_data.clone(), - cloud: fake_data.clone(), - } - } -} - -impl DNS { - pub fn new(domain: &str) -> DaemonResult { - let mut resolver_opts = ResolverOpts::default(); - // ignore /ect/hosts - resolver_opts.use_hosts_file = false; - // DNSSec - resolver_opts.validate = true; - // Get a new resolver with the cloudflare nameservers as the upstream recursive resolvers - let resolver = ResolverConfig::cloudflare_tls(); - let resolver = TokioAsyncResolver::tokio(resolver, resolver_opts); - Ok(DNS { - domain: domain.to_string(), - resolver: resolver, - }) - } - - pub async fn get_vms(&self, cluster_id: &str, is_local: bool) -> DaemonResult> { - if is_local { - return Ok(LOCAL_VM_METADATA.to_vec()); - }; - let name = format!("{}.{}", cluster_id, self.domain); - let err = format!("Failed to fetch TXT record with name {}", &name); - let resp = self.resolver.txt_lookup(name).await; - let mut vms = Vec::new(); - if let Ok(resp) = resp { - for rec in resp { - let data = &rec.txt_data()[0]; - let vm = VMMetadata::from_txt_data(data)?; - vms.push(vm); - } - Ok(vms) - } else if let Err(err_resp) = resp { - Err(format!("{}. {:?}", err, err_resp).into()) - } else { - Err(err.into()) - } - } -} diff --git a/avm/src/daemon/mod.rs b/avm/src/daemon/mod.rs deleted file mode 100644 index d2df95e5a..000000000 --- a/avm/src/daemon/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod ctrl; -pub mod daemon; -pub mod dns; -pub mod stats; diff --git a/avm/src/daemon/stats.rs b/avm/src/daemon/stats.rs deleted file mode 100644 index cff1c705e..000000000 --- a/avm/src/daemon/stats.rs +++ /dev/null @@ -1,186 +0,0 @@ -use serde::Serialize; -use sysinfo::{ProcessExt, System, SystemExt}; - -use crate::daemon::daemon::DaemonResult; - -#[derive(Debug, Clone, Serialize)] -struct CPUSample { - avg_cpu_core_util: f64, - max_proc_cpu_usage: f64, -} - -#[derive(Debug, Clone, Serialize)] -struct CPURates { - avg_cpu_core_util: &'static f64, - max_proc_cpu_usage: &'static f64, -} - -#[allow(non_snake_case)] -#[derive(Debug, Clone, Serialize)] -pub struct CPUSecsV1 { - user: f64, - system: f64, - idle: f64, - irq: f64, - nice: f64, - ioWait: f64, - softIrq: f64, - steal: f64, -} - -#[allow(non_snake_case)] -#[derive(Debug, Clone, Serialize)] -pub struct VMStatsV1 { - cpuSecs: Vec, - procsCpuUsage: Vec, - totalMemoryKb: u64, - availableMemoryKb: u64, - freeMemoryKb: u64, - usedMemoryKb: u64, - activeMemoryKb: u64, - totalSwapKb: u64, - usedSwapKb: u64, - freeSwapKb: u64, -} - -// calculate the cpu % usage per process using the process' -// total cpu time delta in a 100ms time window -async fn get_proc_usages(sys: &System) -> Vec { - sys - .processes() - .values() - .map(|process| process.cpu_usage() as f64) - .collect() -} - -// Cpu Times from /proc/stat are for the entire lifetime of the VM -// so generate it twice with a wait in between to generate a time window -async fn get_cores_times(sys: &System) -> DaemonResult> { - // TODO: Revive or replace this - let mut time_window = Vec::new(); - for cpu in sys.cpus() { - time_window.push(CPUSecsV1 { - user: 0.0, - system: 0.0, - idle: 0.0, - irq: 0.0, - nice: 0.0, - ioWait: 0.0, - softIrq: 0.0, - steal: 0.0, - }); - } - Ok(time_window) -} - -pub async fn get_v1_stats() -> DaemonResult { - let mut sys = System::new_all(); - sys.refresh_all(); - let core_times = get_cores_times(&sys).await?; - Ok(VMStatsV1 { - cpuSecs: core_times, - procsCpuUsage: get_proc_usages(&sys).await, - totalMemoryKb: sys.total_memory(), - availableMemoryKb: sys.available_memory(), - freeMemoryKb: sys.free_memory(), - activeMemoryKb: sys.used_memory(), // TODO: Revive this? - usedMemoryKb: sys.used_memory(), - totalSwapKb: sys.total_swap(), - usedSwapKb: sys.used_swap(), - freeSwapKb: sys.free_swap(), - }) -} - -// returns the suggested scaling factor for the cluster -// 2 prescribes doubling the cluster size -// 1 prescribes leaving the cluster as-is -// 0.5 prescribes halving the cluster size -pub fn get_stats_factor(stats: &Vec) -> String { - let samples = get_cpu_procs_samples(stats).unwrap_or(Vec::new()); - // take avg of samples - let avg_util = get_avg_cpu_util(&samples); - let avg_max_proc_cpu_usage = get_avg_max_proc_cpu_usage(&samples); - // single threaded processes like node and python are bounced around cores and - // they use cpu time across all cores that are available so we use the average - // cpu utilization across all cores which also works for multithreaded processes - if avg_util < 0.3 && avg_max_proc_cpu_usage < 0.3 { - return String::from("0.5"); - } else if avg_util > 0.8 || avg_max_proc_cpu_usage > 0.8 { - return String::from("2"); - } else { - return String::from("1"); - } -} - -fn get_cpu_procs_samples(stats: &Vec) -> DaemonResult> { - Ok( - stats - .iter() - .map(|s| CPUSample { - avg_cpu_core_util: get_avg_cpu_core_util(s), - max_proc_cpu_usage: get_max_procs_usage(s), - }) - .collect(), - ) -} - -fn get_avg_cpu_core_util(stat: &VMStatsV1) -> f64 { - let acc = stat - .cpuSecs - .iter() - .map(|t| { - let idle = t.idle + t.ioWait; - // TODO ignore t.steal for now, but once we have basic clustering - // we want to remove nodes with a high steal cpu time - let total = total(t) - t.steal; - let active = total - idle; - return active / total; - }) - .reduce(|a, b| a + b); - match acc { - Some(acc) => acc, - None => (0 as f64), - } -} - -fn total(cpu_secs: &CPUSecsV1) -> f64 { - return cpu_secs.user - + cpu_secs.system - + cpu_secs.softIrq - + cpu_secs.irq - + cpu_secs.softIrq - + cpu_secs.nice - + cpu_secs.ioWait - + cpu_secs.idle; -} - -fn get_max_procs_usage(stat: &VMStatsV1) -> f64 { - let mut sorted_procs_cpu_usage = stat.procsCpuUsage.clone(); - sorted_procs_cpu_usage.sort_by(|a, b| a.partial_cmp(b).unwrap()); - match sorted_procs_cpu_usage.last() { - Some(value) => *value, - None => 0 as f64, - } -} - -fn get_avg_cpu_util(samples: &Vec) -> f64 { - match samples - .iter() - .map(|s| s.avg_cpu_core_util) - .reduce(|a, b| a + b) - { - Some(acc) => acc / samples.len() as f64, - None => 0 as f64, - } -} - -fn get_avg_max_proc_cpu_usage(samples: &Vec) -> f64 { - match samples - .iter() - .map(|s| s.max_proc_cpu_usage) - .reduce(|a, b| a + b) - { - Some(acc) => acc / samples.len() as f64, - None => 0 as f64, - } -} diff --git a/avm/src/main.rs b/avm/src/main.rs deleted file mode 100644 index 0d22da91c..000000000 --- a/avm/src/main.rs +++ /dev/null @@ -1,293 +0,0 @@ -use std::collections::HashMap; -use std::env; -use std::fs::read; -use std::path::Path; - -use base64; -use clap::{arg, command, crate_name, crate_version, Command}; -use tokio::runtime::Builder; - -use crate::cloud::common::get_agz_file_b64; -use crate::cloud::deploy; -use crate::cloud::oauth::authenticate; -use crate::compile::compile; -use crate::daemon::daemon::{start, CLUSTER_SECRET, NON_HTTP}; -use crate::vm::run::run_file; -use crate::vm::telemetry; - -mod cloud; -mod compile; -mod daemon; -mod vm; - -fn get_agz_b64(agz_file: &str) -> String { - let agz = read(agz_file).expect(&format!("No agz file found in {}", agz_file)); - return base64::encode(agz); -} - -async fn compile_and_run(source_file: &str) -> i32 { - let dest_file = "temp.agc"; - let status_code = compile(&source_file, &dest_file, true); - if status_code == 0 { - let mut path = env::current_dir().unwrap(); - path.push(dest_file); - let fp = path.into_os_string().into_string().unwrap(); - if let Err(ee) = run_file(&fp, true).await { - eprintln!("{}", ee); - return 2; - }; - } - return status_code; -} - -fn main() { - let app = command!(crate_name!()) - .version(crate_version!()) - .about("Compile, run and deploy Alan") - .subcommand(Command::new("run") - .about("Runs compiled .agc files") - .version(crate_version!()) - .arg(arg!( "Specifies the file to load")) - ) - .subcommand(Command::new("compile") - .about("Compiles the given source file (.ln, .amm, .aga) to a new output file (.amm, .aga, .agz, .agc, .js)") - .arg(arg!( "Specifies the input file to load")) - .arg(arg!( "Specifies the output file to generate")) - ) - .subcommand(Command::new("install") - .about("Install '/dependencies' from '.dependencies.ln'") - ) - .subcommand(Command::new("deploy") - .about("Deploy .agz files to one of the Deploy Configs from alandeploy.json") - .subcommand_required(true) - .subcommand(Command::new("new") - .about("Deploys an .agz file to a new app with one of the Deploy Configs from alandeploy.json") - .arg(arg!( "Specifies the .agz file to deploy")) - .arg(arg!([NON_INTERACTIVE] -n --non-interactive "Enables non-interactive CLI mode useful for scripting.")) - .arg(arg!([NON_HTTP] -h --non-http "Enables non-http server deployments.")) - .arg(arg!(-a --app-name [APP_NAME] "Specifies an optional app name.")) - .arg(arg!(-c --config-name [CONFIG_NAME] "Specifies a config name, required only in non-interactive mode.")) - .arg(arg!(-f --files [COMMA_SEPARATED_NAMES] "Specifies a set of files to include in the same working directory as your app")) - ) - .subcommand(Command::new("list") - .about("Displays all the Apps deployed with the Deploy Configs from alandeploy.json") - ) - .subcommand(Command::new("terminate") - .about("Terminate an App hosted in one of the Deploy Configs from alandeploy.json") - ) - .subcommand(Command::new("upgrade") - .about("Deploys your repository to an existing App hosted in one of the Deploy Configs from alandeploy.json") - .arg(arg!( "Specifies the .agz file to deploy")) - .arg(arg!([NON_INTERACTIVE] -n --non-interactive "Enables non-interactive CLI mode useful for scripting.")) - .arg(arg!([NON_HTTP] -h --non-http "Enables non-http server deployments.")) - .arg(arg!(-a --app-name [APP_NAME] "Specifies an optional app name.")) - .arg(arg!(-c --config-name [CONFIG_NAME] "Specifies a config name, required only in non-interactive mode.")) - .arg(arg!(-f --files [COMMA_SEPARATED_NAMES] "Specifies a set of files to include in the same working directory as your app")) - ) - .subcommand(Command::new("config") - .about("Manage Deploy Configs used by Apps from the alandeploy.json in the current directory") - .subcommand_required(true) - .subcommand(Command::new("new") - .about("Add a new Deploy Config to the alandeploy.json in the current directory and creates the file if it doesn't exist.") - ) - .subcommand(Command::new("list") - .about("List all the Deploy Configs from the alandeploy.json in the current directory") - ) - .subcommand(Command::new("edit") - .about("Edit an existing Deploy Config from the alandeploy.json in the current directory") - ) - .subcommand(Command::new("remove") - .about("Remove an existing Deploy Config from the alandeploy.json in the current directory") - ) - ) - .subcommand(Command::new("credentials") - .about("Manage all Credentials used by Deploy Configs from the credentials file at ~/.alan/credentials.json") - .subcommand_required(true) - .subcommand(Command::new("new") - .about("Add a new Credentials") - ) - .subcommand(Command::new("list") - .about("List all the available Credentials") - ) - .subcommand(Command::new("edit") - .about("Edit an existing Credentials") - ) - .subcommand(Command::new("remove") - .about("Remove an existing Credentials") - ) - ) - ) - .subcommand(Command::new("daemon") - .about("Run an .agz file in daemon mode. Used on deploy within cloud provider VMs.") - .arg(arg!( -s --cluster-secret "A secret string to constrain access to the control port")) - .arg(arg!(-f --agz-file [AGZ_FILE] "Specifies an optional agz file relative path for local usage")) - .arg(arg!([NON_HTTP] -h --non-http "Specifies non-http agz execution.")) - .arg(arg!([ANYCLOUD_APP] -a --anycloud-app "Specifies an optional AnyCloud app flag for local usage")) // TODO: Eliminate this - ) - .arg(arg!([SOURCE] "Specifies a source ln file to compile and run")); - - let matches = app.clone().get_matches(); - - let rt = Builder::new_multi_thread() - .enable_time() - .enable_io() - .build() - .unwrap(); - - rt.block_on(async move { - match matches.subcommand() { - Some(("run", matches)) => { - let agc_file = matches.get_one::("FILE").unwrap(); - let fp = &format!( - "{:}/{:}", - env::current_dir().ok().unwrap().to_str().unwrap(), - agc_file - ); - telemetry::log("avm-run").await; - if let Err(ee) = run_file(&fp, false).await { - eprintln!("{}", ee); - std::process::exit(2); - }; - } - Some(("compile", matches)) => { - let source_file = matches.get_one::("INPUT").unwrap(); - let dest_file = matches.get_one::("OUTPUT").unwrap(); - std::process::exit(compile(&source_file, &dest_file, false)); - } - Some(("install", _)) => { - let source_file = ".dependencies.ln"; - if Path::new(source_file).exists() { - std::process::exit(compile_and_run(source_file).await); - } else { - println!( - "{} does not exist. Dependencies can only be installed for {}", - source_file, source_file - ); - std::process::exit(1); - } - } - Some(("deploy", sub_matches)) => { - match sub_matches.subcommand() { - Some(("new", matches)) => { - let non_interactive = matches.get_flag("NON_INTERACTIVE"); - let non_http = matches.get_flag("NON_HTTP"); - authenticate(non_interactive).await; - let agz_file = matches.get_one::("AGZ_FILE").unwrap(); - let app_name = matches.get_one::("app-name").map(String::from); - let config_name = matches.get_one::("config-name").map(String::from); - let files = matches.get_one::("files"); - let mut files_b64 = HashMap::new(); - if let Some(files) = files { - let names = files.split(","); - for name in names { - files_b64.insert(name.to_string(), get_agz_file_b64(name.to_string()).await); - } - } - deploy::new( - get_agz_b64(agz_file), - files_b64, - app_name, - config_name, - non_interactive, - non_http, - ) - .await; - } - Some(("terminate", matches)) => { - let non_interactive = matches.get_flag("NON_INTERACTIVE"); - authenticate(non_interactive).await; - let app_name = matches.get_one::("app-name").unwrap(); - let config_name = matches.get_one::("config-name").unwrap(); - deploy::terminate( - Some(app_name.clone()), - Some(config_name.clone()), - non_interactive, - ) - .await // TODO: Change the signature of this function to use the new types - } - Some(("upgrade", matches)) => { - let non_interactive = matches.get_flag("NON_INTERACTIVE"); - let non_http = matches.get_flag("NON_HTTP"); - authenticate(non_interactive).await; - let agz_file = matches.get_one::("AGZ_FILE").unwrap(); - let app_name = matches.get_one::("app-name").unwrap(); - let config_name = matches.get_one::("config-name").unwrap(); - let files = matches.get_one::("files"); - let mut files_b64 = HashMap::new(); - if let Some(files) = files { - let names = files.split(","); - for name in names { - files_b64.insert(name.to_string(), get_agz_file_b64(name.to_string()).await); - } - } - deploy::upgrade( - get_agz_b64(agz_file), - files_b64, - Some(app_name.clone()), // TODO: Change the signature of this function to use the new types - Some(config_name.clone()), - non_interactive, - non_http, - ) - .await; - } - Some(("list", _)) => { - authenticate(false).await; - deploy::info().await - } - Some(("credentials", sub_matches)) => { - authenticate(false).await; - match sub_matches.subcommand() { - Some(("new", _)) => { - deploy::add_cred(None).await; - } - Some(("edit", _)) => deploy::edit_cred().await, - Some(("list", _)) => deploy::list_creds().await, - Some(("remove", _)) => deploy::remove_cred().await, - // rely on AppSettings::SubcommandRequiredElseHelp - _ => {} - } - } - Some(("config", sub_matches)) => { - authenticate(false).await; - match sub_matches.subcommand() { - Some(("new", _)) => deploy::add_deploy_config().await, - Some(("list", _)) => deploy::list_deploy_configs().await, - Some(("edit", _)) => deploy::edit_deploy_config().await, - Some(("remove", _)) => deploy::remove_deploy_config().await, - // rely on AppSettings::SubcommandRequiredElseHelp - _ => {} - } - } - // rely on AppSettings::SubcommandRequiredElseHelp - _ => { - authenticate(false).await; - } - } - } - Some(("daemon", matches)) => { - let non_http = matches.get_flag("NON_HTTP"); - let cluster_secret = matches.get_one::("CLUSTER_SECRET").unwrap(); - let local_agz_b64 = match matches.get_one::("agz-file") { - Some(agz_file_path) => Some(get_agz_file_b64(agz_file_path.to_string()).await), - None => None, - }; - let is_local_anycloud_app = matches.get_flag("ANYCLOUD_APP"); - NON_HTTP.set(non_http).unwrap(); - CLUSTER_SECRET - .set(Some(cluster_secret.to_string())) - .unwrap(); - start(is_local_anycloud_app, local_agz_b64).await; - } - _ => { - // AppSettings::SubcommandRequiredElseHelp does not cut it here - if let Some(source_file) = matches.get_one::("SOURCE") { - let path = Path::new(source_file); - if path.extension().is_some() { - std::process::exit(compile_and_run(&source_file).await); - } - } - app.clone().print_help().unwrap(); - } - } - }); -} diff --git a/avm/src/vm/event.rs b/avm/src/vm/event.rs deleted file mode 100644 index 05a6e479a..000000000 --- a/avm/src/vm/event.rs +++ /dev/null @@ -1,507 +0,0 @@ -use futures::future::join_all; -use std::sync::Arc; -use tokio::task; - -use crate::vm::instruction::Instruction; -use crate::vm::memory::HandlerMemory; -use crate::vm::opcode::OpcodeFn; -use crate::vm::program::Program; -use crate::vm::InstrType; -use crate::vm::VMError; -use crate::vm::VMResult; - -pub const NOP_ID: i64 = i64::MIN; - -#[derive(PartialEq, Eq, Hash)] -#[repr(i64)] -/// Special events in alan found in standard library modules, @std. -/// The IDs for built-in events are negative to avoid collision with positive, custom event IDs. -/// The first hexadecimal byte of the ID in an integer is between 80 and FF -/// The remaining 7 bytes can be used for ASCII-like values -pub enum BuiltInEvents { - /// Alan application start - /// '"start"' in ASCII or 2273 7461 7274 22(80) - START = -9213673853036498142, - /// '__conn ' in ASCII or 5f5f 636f 6e6e 20(80) - HTTPCONN = -9214243417005793441, - /// '__ctrl ' in ASCII or 5f5f 6374 72 6c 20(80) - CTRLPORT = -9214245598765293729, - NOP = NOP_ID, -} - -impl From for i64 { - fn from(ev: BuiltInEvents) -> Self { - match ev { - BuiltInEvents::START => -9213673853036498142, - BuiltInEvents::HTTPCONN => -9214243417005793441, - BuiltInEvents::CTRLPORT => -9214245598765293729, - BuiltInEvents::NOP => NOP_ID, - } - } -} - -/// Describes an event emission received by the event loop from the thread worker -pub struct EventEmit { - /// event id - pub(crate) id: i64, - /// optional handler memory with payload. each handler will get its own to consume - pub(crate) payload: Option>, -} - -/// Describes the handler for an event -#[derive(Debug)] -pub struct EventHandler { - /// event id - pub(crate) event_id: i64, - /// number of bytes each handler call requires in memory, or -1 if it's a variable length type - pub(crate) mem_req: i64, - /// the indices of fragments that have unpredictable execution and could be moved around - movable_capstones: Vec, - /// topological order of the instructions split into fragments - /// by unpredictable or partially predictable opcodes - pub fragments: Vec>, - /// total count of instructions within fragments - ins_count: usize, -} - -impl EventHandler { - pub fn new(mem_req: i64, event_id: i64) -> EventHandler { - return EventHandler { - fragments: Vec::new(), - movable_capstones: Vec::new(), - ins_count: 0, - mem_req, - event_id, - }; - } - - pub fn add_instruction(self: &mut EventHandler, ins: Instruction) { - self.ins_count += 1; - match ins.opcode.fun { - OpcodeFn::Cpu(_) => { - let mut frag = self.fragments.pop().unwrap_or(Vec::new()); - if frag.len() > 0 && !frag[frag.len() - 1].opcode.pred_exec() { - // if last instruction in the last fragment is a (io or cpu) capstone start a new fragment - self.fragments.push(frag); - self.fragments.push(vec![ins]); - } else { - // add to last fragment - frag.push(ins); - self.fragments.push(frag); - } - } - OpcodeFn::UnpredCpu(_) => { - // always put this instruction on a new fragment - self.fragments.push(vec![ins]); - } - OpcodeFn::Io(_) => { - // io opcode is a "movable capstone" in execution - let cur_max_dep = ins.dep_ids.iter().max().unwrap_or(&-1); - // merge this capstone with an existing one if possible - for frag_idx in &self.movable_capstones { - let fragment = self.fragments.get_mut(*frag_idx).unwrap(); - let prev_max_dep = fragment - .iter() - .map(|i| i.dep_ids.iter().max().unwrap_or(&-1)) - .max() - .unwrap(); - let prev_min_id = &fragment.iter().map(|i| i.id).min().unwrap(); - // merge curr in prev if *everything* that cur needs has ran by prev. - // since poset is ranked we can check the max dep id of curr is: - // less than the min id in the prev capstone - // less than or equal to the max dep id from prev capstone - if prev_min_id > cur_max_dep && prev_max_dep >= cur_max_dep { - fragment.push(ins); - return; - } - } - // this is the first capstone or it cannot be merged - // mark it as a new capstone - self.movable_capstones.push(self.fragments.len()); - self.fragments.push(vec![ins]); - } - } - } - - pub fn len(self: &EventHandler) -> usize { - return self.ins_count; - } - - pub fn last_frag_idx(self: &EventHandler) -> usize { - return self.fragments.len() - 1; - } - - pub fn get_fragment(self: &EventHandler, idx: usize) -> &Vec { - return self.fragments.get(idx).unwrap(); - } -} - -/// Identifies an exact fragment of an event handler -#[derive(Clone, Debug)] -struct HandlerFragmentID { - event_id: i64, - handler_idx: usize, - fragment_idx: Option, -} - -/// Identifies the fragment of an event handler -#[derive(Clone, Debug)] -pub struct HandlerFragment { - /// handler stack for other handlers sequentially running within itself. - /// Required IDs to identify the event handler placed into a Vec - handlers: Vec, -} - -impl HandlerFragmentID { - /// increments or initializes fragment idx to 0 if it does not exist - fn incr_frag_idx(self: &mut HandlerFragmentID) { - if self.fragment_idx.is_none() { - return self.fragment_idx = Some(0); - } - self.fragment_idx = Some(self.fragment_idx.unwrap() + 1); - } -} - -impl HandlerFragment { - pub fn new(event_id: i64, handler_idx: usize) -> HandlerFragment { - return HandlerFragment { - handlers: vec![HandlerFragmentID { - event_id, - handler_idx, - fragment_idx: Some(0), - }], - }; - } - - pub fn get_instruction_fragment(self: &mut HandlerFragment) -> &'static Vec { - let hand_id = self.handlers.get_mut(0).unwrap(); - let handlers = Program::global() - .event_handlers - .get(&hand_id.event_id) - .unwrap(); - let handler: &EventHandler = handlers.get(hand_id.handler_idx).unwrap(); - return handler.get_fragment(hand_id.fragment_idx.unwrap()); - } - - pub fn get_next_fragment(mut self) -> Option { - let hand_id = self.handlers.get_mut(0).unwrap(); - let handlers = Program::global() - .event_handlers - .get(&hand_id.event_id) - .unwrap(); - let handler: &EventHandler = handlers.get(hand_id.handler_idx).unwrap(); - let last_frag_idx = handler.last_frag_idx(); - return if hand_id.fragment_idx.is_some() && last_frag_idx <= hand_id.fragment_idx.unwrap() { - self.handlers.remove(0); - if self.handlers.len() == 0 { - None - } else { - Some(self) - } - } else { - hand_id.incr_frag_idx(); - Some(self) - }; - } - - #[inline(always)] - async fn run_cpu( - &mut self, - mut hand_mem: Arc, - instrs: &Vec, - ) -> VMResult> { - task::block_in_place(move || { - instrs - .iter() - .map(|i| { - if let OpcodeFn::Cpu(func) = i.opcode.fun { - //eprintln!("{} {:?}", i.opcode._name, i.args); - func(i.args.as_slice(), &mut hand_mem)?; - Ok(()) - } else { - Err(VMError::UnexpectedInstruction(InstrType::CPU)) - } - }) - .collect::>>()?; - Ok(hand_mem) - }) - } - - #[inline(always)] - async fn run_unpred_cpu( - &mut self, - hand_mem: Arc, - instrs: &Vec, - ) -> VMResult> { - // These instructions are always in groups by themselves - let op = &instrs[0]; - if let OpcodeFn::UnpredCpu(func) = op.opcode.fun { - //eprintln!("{} {:?}", op.opcode._name, op.args); - return func(op.args.clone(), hand_mem).await; - } else { - return Err(VMError::UnexpectedInstruction(InstrType::UnpredictableCPU)); - } - } - - #[inline(always)] - async fn run_io( - &mut self, - mut hand_mem: Arc, - instrs: &Vec, - ) -> VMResult> { - if instrs.len() == 1 { - let op = &instrs[0]; - if let OpcodeFn::Io(func) = op.opcode.fun { - //eprintln!("{} {:?}", op.opcode._name, op.args); - return func(op.args.clone(), hand_mem).await; - } else { - return Err(VMError::UnexpectedInstruction(InstrType::IO)); - } - } else { - let futures: Vec<_> = instrs - .iter() - .map(|i| { - let hand_mem = hand_mem.clone(); - async move { - if let OpcodeFn::Io(func) = i.opcode.fun { - //eprintln!("{} {:?}", i.opcode._name, i.args); - let forked = HandlerMemory::fork(hand_mem.clone())?; - let res = func(i.args.clone(), forked).await?; - Ok(HandlerMemory::drop_parent(res)?) - // Ok(func(i.args.clone(), HandlerMemory::fork(hand_mem.clone())?) - // .then(|res| HandlerMemory::drop_parent_async).await) - } else { - Err(VMError::UnexpectedInstruction(InstrType::IO)) - } - } - }) - .collect(); - let hms = join_all(futures).await; - for hm in hms { - hand_mem.join(hm?)?; - } - } - Ok(hand_mem) - } - - /// Runs the specified handler in Tokio tasks. Tokio tasks are allocated to a threadpool bound by - /// the number of CPU cores on the machine it is executing on. Actual IO work gets scheduled into - /// an IO threadpool (unbounded, according to Tokio) while CPU work uses the special - /// `block_in_place` function to indicate to Tokio to not push new Tokio tasks to this particular - /// thread at this time. Event-level parallelism and Array-level parallelism are unified into - /// this same threadpool as tasks which should minimize contentions at the OS level on work and - /// help throughput (in theory). This could be a problem for super-IO-bound applications with a - /// very high volume of small IO requests, but as most IO operations are several orders of - /// magnitude slower than CPU operations, this is considered to be a very small minority of - /// workloads and is ignored for now. - pub async fn run( - mut self: HandlerFragment, - mut hand_mem: Arc, - ) -> VMResult> { - loop { - let instrs = self.get_instruction_fragment(); - hand_mem = match instrs[0].opcode.fun { - OpcodeFn::Cpu(_) => self.run_cpu(hand_mem, instrs).await?, - OpcodeFn::UnpredCpu(_) => self.run_unpred_cpu(hand_mem, instrs).await?, - OpcodeFn::Io(_) => self.run_io(hand_mem, instrs).await?, - }; - if let Some(frag) = self.get_next_fragment() { - self = frag; - } else { - break; - } - } - Ok(hand_mem) - } - - /// Spawns and runs a non-blocking tokio task for the fragment that can be awaited. - /// Used to provide event and array level parallelism - pub fn spawn( - self: HandlerFragment, - hand_mem: Arc, - ) -> task::JoinHandle>> { - task::spawn(async move { self.run(hand_mem).await }) - } -} - -#[cfg(test)] -mod tests { - use crate::vm::opcode::{opcode_id, OPCODES}; - - use super::*; - - fn get_io_ins(id: i64, dep_ids: Vec) -> Instruction { - return Instruction { - id, - opcode: &OPCODES.get(&opcode_id("execop")).unwrap(), - args: vec![], - dep_ids, - }; - } - - fn get_cpu_ins(id: i64, dep_ids: Vec) -> Instruction { - return Instruction { - id, - opcode: &OPCODES.get(&opcode_id("addi64")).unwrap(), - args: vec![], - dep_ids, - }; - } - - fn get_cond_ins(id: i64, dep_ids: Vec) -> Instruction { - return Instruction { - id, - opcode: &OPCODES.get(&opcode_id("condfn")).unwrap(), - args: vec![], - dep_ids, - }; - } - - // multiple io operations with no dependencies forms a single fragment - #[test] - fn test_frag_grouping_1() { - let mut hand = EventHandler::new(123, 123); - hand.add_instruction(get_io_ins(0, vec![])); - hand.add_instruction(get_io_ins(1, vec![])); - hand.add_instruction(get_io_ins(2, vec![])); - hand.add_instruction(get_io_ins(3, vec![])); - assert_eq!(hand.last_frag_idx(), 0); - } - - // chained io operations forms a fragment per io operation - #[test] - fn test_frag_grouping_2() { - let mut hand = EventHandler::new(123, 123); - hand.add_instruction(get_io_ins(0, vec![])); - hand.add_instruction(get_io_ins(1, vec![0])); - hand.add_instruction(get_io_ins(2, vec![1])); - hand.add_instruction(get_io_ins(3, vec![2])); - assert_eq!(hand.last_frag_idx(), 3); - } - - // multiple io operations and one cpu operation in between - // with no dependencies form 2 fragments - #[test] - fn test_frag_grouping_3() { - let mut hand = EventHandler::new(123, 123); - hand.add_instruction(get_io_ins(0, vec![])); - hand.add_instruction(get_io_ins(1, vec![])); - hand.add_instruction(get_cpu_ins(2, vec![])); - hand.add_instruction(get_io_ins(3, vec![])); - assert_eq!(hand.last_frag_idx(), 1); - assert_eq!(hand.get_fragment(0).len(), 3); - assert_eq!(hand.get_fragment(1).len(), 1); - } - - // independent io operations, then independent cpu operation - // and then io operation dependent on cpu operation forms 3 fragments - #[test] - fn test_frag_grouping_4() { - let mut hand = EventHandler::new(123, 123); - hand.add_instruction(get_io_ins(0, vec![])); - hand.add_instruction(get_io_ins(1, vec![])); - hand.add_instruction(get_cpu_ins(2, vec![])); - hand.add_instruction(get_io_ins(3, vec![2])); - assert_eq!(hand.last_frag_idx(), 2); - assert_eq!(hand.get_fragment(0).len(), 2); - assert_eq!(hand.get_fragment(1).len(), 1); - assert_eq!(hand.get_fragment(2).len(), 1); - } - - // independent io operations, then independent cpu operation - // and then io operation dependent on io operations forms 3 fragments - #[test] - fn test_frag_grouping_5() { - let mut hand = EventHandler::new(123, 123); - hand.add_instruction(get_io_ins(0, vec![])); - hand.add_instruction(get_io_ins(1, vec![])); - hand.add_instruction(get_cpu_ins(2, vec![])); - hand.add_instruction(get_io_ins(3, vec![1])); - assert_eq!(hand.last_frag_idx(), 2); - assert_eq!(hand.get_fragment(0).len(), 2); - assert_eq!(hand.get_fragment(1).len(), 1); - assert_eq!(hand.get_fragment(2).len(), 1); - } - - // chained cpu operations form one fragment - #[test] - fn test_frag_grouping_6() { - let mut hand = EventHandler::new(123, 123); - hand.add_instruction(get_cpu_ins(0, vec![])); - hand.add_instruction(get_cpu_ins(1, vec![0])); - hand.add_instruction(get_cpu_ins(2, vec![1])); - hand.add_instruction(get_cpu_ins(3, vec![2])); - assert_eq!(hand.last_frag_idx(), 0); - } - - // independent: io operation, then independent cpu operation - // and then independent io operation then ind cpu operation then - // dep io operation on first cpu operation forms 3 fragments - #[test] - fn test_frag_grouping_7() { - let mut hand = EventHandler::new(123, 123); - hand.add_instruction(get_io_ins(0, vec![])); - hand.add_instruction(get_io_ins(1, vec![])); - hand.add_instruction(get_cpu_ins(2, vec![])); - hand.add_instruction(get_io_ins(3, vec![])); - hand.add_instruction(get_cpu_ins(4, vec![])); - hand.add_instruction(get_io_ins(5, vec![2])); - assert_eq!(hand.last_frag_idx(), 2); - assert_eq!(hand.get_fragment(0).len(), 3); - assert_eq!(hand.get_fragment(1).len(), 2); - assert_eq!(hand.get_fragment(2).len(), 1); - } - - // independent: io operation, then independent cpu operation - // and then dep io operation then ind cpu operation then - // dep io operation on prev io operation forms 4 fragments - #[test] - fn test_frag_grouping_8() { - let mut hand = EventHandler::new(123, 123); - hand.add_instruction(get_io_ins(0, vec![])); - hand.add_instruction(get_io_ins(1, vec![])); - hand.add_instruction(get_cpu_ins(2, vec![])); - hand.add_instruction(get_io_ins(3, vec![0])); - hand.add_instruction(get_cpu_ins(4, vec![])); - hand.add_instruction(get_io_ins(5, vec![])); - assert_eq!(hand.last_frag_idx(), 3); - assert_eq!(hand.get_fragment(0).len(), 3); - assert_eq!(hand.get_fragment(1).len(), 1); - assert_eq!(hand.get_fragment(2).len(), 1); - assert_eq!(hand.get_fragment(3).len(), 1); - } - - // condfn is an unpred_cpu instruction that causes a break in fragments - #[test] - fn test_frag_grouping_9() { - let mut hand = EventHandler::new(123, 123); - hand.add_instruction(get_cpu_ins(0, vec![])); - hand.add_instruction(get_cpu_ins(1, vec![])); - hand.add_instruction(get_cond_ins(2, vec![])); - hand.add_instruction(get_cpu_ins(3, vec![])); - assert_eq!(hand.movable_capstones.len(), 0); - assert_eq!(hand.last_frag_idx(), 2); - } - - // condfn and io operations with no deps run in two fragments - #[test] - fn test_frag_grouping_10() { - let mut hand = EventHandler::new(123, 123); - hand.add_instruction(get_io_ins(0, vec![])); - hand.add_instruction(get_cond_ins(1, vec![])); - hand.add_instruction(get_io_ins(2, vec![])); - assert_eq!(hand.movable_capstones.len(), 1); - assert_eq!(hand.last_frag_idx(), 1); - } - - // multiple condfns run in the separate fragments - #[test] - fn test_frag_grouping_11() { - let mut hand = EventHandler::new(123, 123); - hand.add_instruction(get_cond_ins(0, vec![])); - hand.add_instruction(get_cond_ins(1, vec![])); - hand.add_instruction(get_cond_ins(2, vec![])); - assert_eq!(hand.movable_capstones.len(), 0); - assert_eq!(hand.last_frag_idx(), 2); - } -} diff --git a/avm/src/vm/http.rs b/avm/src/vm/http.rs deleted file mode 100644 index cd6fb87be..000000000 --- a/avm/src/vm/http.rs +++ /dev/null @@ -1,308 +0,0 @@ -use futures::task::{Context, Poll}; -use std::io; -use std::pin::Pin; - -use futures_util::stream::Stream; -use hyper::{ - client::{Client, HttpConnector}, - Body, -}; -use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder}; -use once_cell::sync::Lazy; -use tokio::net::TcpStream; -use tokio_rustls::server::TlsStream; - -#[derive(Debug)] -pub struct HttpConfig { - pub port: u16, -} - -#[derive(Debug)] -pub struct HttpsConfig { - pub port: u16, - pub priv_key: String, - pub cert: String, -} - -#[derive(Debug)] -pub enum HttpType { - HTTP(HttpConfig), - HTTPS(HttpsConfig), -} - -pub struct HyperAcceptor<'a> { - pub acceptor: Pin, io::Error>> + Send + 'a>>, -} - -impl hyper::server::accept::Accept for HyperAcceptor<'_> { - type Conn = TlsStream; - type Error = io::Error; - - fn poll_accept( - mut self: Pin<&mut Self>, - cx: &mut Context, - ) -> Poll>> { - Pin::new(&mut self.acceptor).poll_next(cx) - } -} - -pub static HTTP_CLIENT: Lazy>> = Lazy::new(|| { - Client::builder().build::<_, Body>( - HttpsConnectorBuilder::new() - .with_native_roots() - .https_or_http() - .enable_all_versions() - .build(), - ) -}); - -#[macro_export] -macro_rules! make_server { - ($config:expr, $listener:expr) => { - match $config { - crate::vm::http::HttpType::HTTP(http) => { - let port_num = http.port; - let addr = std::net::SocketAddr::from(([0, 0, 0, 0], port_num)); - let make_svc = hyper::service::make_service_fn(|_conn| async { - Ok::<_, std::convert::Infallible>(hyper::service::service_fn($listener)) - }); - - let bind = hyper::server::Server::try_bind(&addr); - match bind { - Ok(server) => { - let server = server.serve(make_svc); - tokio::spawn(async move { server.await }); - println!("HTTP server listening on port {}", port_num); - } - Err(ee) => eprintln!("HTTP server failed to listen on port {}: {}", port_num, ee), - }; - } - crate::vm::http::HttpType::HTTPS(https) => { - let port_num = https.port; - let addr = std::net::SocketAddr::from(([0, 0, 0, 0], port_num)); - let tls_cfg = { - // These *should* all have a singular Item in them. Assuming this is the case and blowing - // up if otherwise - let certs = match rustls_pemfile::read_all(&mut https.cert.as_str().as_bytes()) - .unwrap() - .pop() - .unwrap() - { - rustls_pemfile::Item::X509Certificate(cert) => vec![rustls::Certificate(cert)], - _ => panic!("Misconfigured HTTPS mode missing certificate"), - }; - let key = match rustls_pemfile::read_all(&mut https.priv_key.as_str().as_bytes()) - .unwrap() - .pop() - .unwrap() - { - rustls_pemfile::Item::PKCS8Key(priv_key) => rustls::PrivateKey(priv_key), - _ => panic!("Misconfigured HTTPS mode missing PKCS8 private key"), - }; - let cfg = rustls::ServerConfig::builder() - .with_safe_defaults() - .with_no_client_auth() - .with_single_cert(certs, key) - .unwrap(); - std::sync::Arc::new(cfg) - }; - let tcp = tokio::net::TcpListener::bind(&addr).await; - let tcp = tcp.unwrap(); - let tls_acceptor = tokio_rustls::TlsAcceptor::from(tls_cfg); - let incoming_tls_stream = async_stream::stream! { - loop { - let accept = tcp.accept().await; - if accept.is_err() { continue; } - let (socket, _) = accept.unwrap(); - let strm = tls_acceptor.accept(socket); - let strm_val = strm.await; - if strm_val.is_err() { continue; } - yield Ok(strm_val.unwrap()); - } - }; - let make_svc = hyper::service::make_service_fn(|_conn| async { - Ok::<_, std::convert::Infallible>(hyper::service::service_fn($listener)) - }); - let server = hyper::server::Server::builder(crate::vm::http::HyperAcceptor { - acceptor: Box::pin(incoming_tls_stream), - }) - .serve(make_svc); - println!("HTTPS server listening on port {}", port_num); - tokio::spawn(async move { server.await }); - } - }; - }; -} - -#[macro_export] -macro_rules! make_tunnel { - ($config:expr, $dest_port:expr) => {{ - match $config { - crate::vm::http::HttpType::HTTP(http) => { - let port_num = http.port; - let addr = std::net::SocketAddr::from(([0, 0, 0, 0], port_num)); - let bind = tokio::net::TcpListener::bind(addr).await; - match bind { - Ok(server) => { - tokio::spawn(async move { - loop { - let src_socket = server.accept().await; - match src_socket { - Ok((mut src_stream, _)) => { - tokio::spawn(async move { - let dest_socket = - tokio::net::TcpStream::connect(format!("127.0.0.1:{}", $dest_port)).await; - match dest_socket { - Ok(mut dest_stream) => { - let res = - tokio::io::copy_bidirectional(&mut src_stream, &mut dest_stream).await; - match res { - Ok(_) => { /* Do nothing */ } - Err(_) => { /* Also do nothing? */ } - } - } - Err(ee) => eprintln!( - "Tunnel failed to connect to downstream on port {}: {}", - $dest_port, ee - ), - }; - }); - } - Err(ee) => eprintln!("Tunnel failed to open the socket? {}", ee), - }; - } - }); - true - } - Err(ee) => { - eprintln!("TCP tunnel failed to listen on port {}: {}", port_num, ee); - false - } - } - } - crate::vm::http::HttpType::HTTPS(https) => { - use futures_util::StreamExt; - let port_num = https.port; - let addr = std::net::SocketAddr::from(([0, 0, 0, 0], port_num)); - let tls_cfg = { - // These *should* all have a singular Item in them. Assuming this is the case and blowing - // up if otherwise - let certs = match rustls_pemfile::read_all(&mut https.cert.as_str().as_bytes()).unwrap().pop().unwrap() { - rustls_pemfile::Item::X509Certificate(cert) => vec!(rustls::Certificate(cert)), - _ => panic!("Misconfigured HTTPS mode missing certificate"), - }; - let key = match rustls_pemfile::read_all(&mut https.priv_key.as_str().as_bytes()).unwrap().pop().unwrap() { - rustls_pemfile::Item::PKCS8Key(priv_key) => rustls::PrivateKey(priv_key), - _ => panic!("Misconfigured HTTPS mode missing PKCS8 private key"), - }; - let cfg = rustls::ServerConfig::builder() - .with_safe_defaults() - .with_no_client_auth() - .with_single_cert(certs, key).unwrap(); - std::sync::Arc::new(cfg) - }; - let tcp = tokio::net::TcpListener::bind(&addr).await; - match tcp { - Ok(tcp) => { - tokio::spawn(async move { - let tls_acceptor = tokio_rustls::TlsAcceptor::from(tls_cfg); - let incoming_tls_stream = async_stream::stream! { - use crate::vm::opcode::REGION_VMS; - use rand::{thread_rng, Rng}; - loop { - let l = REGION_VMS.read().unwrap().len(); - let i = async move { - let mut rng = thread_rng(); - rng.gen_range(0..=l) - } - .await; - let (mut socket, source_addr) = match tcp.accept().await { - Ok(accepted) => accepted, - Err(_) => continue, - }; - if i != l { - let region_vms = REGION_VMS.read().unwrap().clone(); - let source_ip = source_addr.ip().to_string(); - if region_vms.iter().any(|ip| *ip == source_ip) { - let strm = tls_acceptor.accept(socket); - yield Ok(match strm.await { - Ok(strm) => strm, - Err(_) => continue, - }); - continue; - } - tokio::spawn(async move { - let dest_socket = tokio::net::TcpStream::connect(format!("{}:443", region_vms[i])).await; - match dest_socket { - Ok(mut dest_stream) => { - let res = - tokio::io::copy_bidirectional(&mut socket, &mut dest_stream).await; - match res { - Ok(_) => { /* Do nothing */ } - Err(_) => { /* Also do nothing? */ } - } - } - Err(ee) => eprintln!( - "Tunnel failed to load balance to {}: {}", - region_vms[i], ee - ), - }; - }); - continue; - } - let strm = tls_acceptor.accept(socket); - yield Ok(match strm.await { - Ok(strm) => strm, - Err(_) => continue, - }); - if 1 == 0 { yield Err("never"); } // Make Rust type inference shut up :( - } - }; - futures_util::pin_mut!(incoming_tls_stream); - while let Some(src_socket) = incoming_tls_stream.next().await { - tokio::spawn(async move { - match src_socket { - Ok(mut src_stream) => { - let dest_socket = - tokio::net::TcpStream::connect(format!("127.0.0.1:{}", $dest_port)).await; - match dest_socket { - Ok(mut dest_stream) => { - loop { - let dest_ready = dest_stream - .ready( - tokio::io::Interest::READABLE.add(tokio::io::Interest::WRITABLE), - ) - .await; - if let Ok(_) = dest_ready { - break; - } - } - let res = - tokio::io::copy_bidirectional(&mut src_stream, &mut dest_stream).await; - match res { - Ok(_) => { /* Do nothing */ } - Err(_) => { /* Also do nothing? */ } - } - } - Err(ee) => eprintln!( - "Tunnel failed to connect to downstream on port {}: {}", - $dest_port, ee - ), - }; - } - Err(ee) => eprintln!("Tunnel failed to open the socket? {}", ee), - }; - }); - } - }); - true - } - Err(ee) => { - eprintln!("TLS tunnel failed to listen on port {}: {}", port_num, ee); - false - } - } - } - } - }}; -} diff --git a/avm/src/vm/instruction.rs b/avm/src/vm/instruction.rs deleted file mode 100644 index 763817910..000000000 --- a/avm/src/vm/instruction.rs +++ /dev/null @@ -1,10 +0,0 @@ -use crate::vm::opcode::ByteOpcode; - -#[derive(Debug)] -pub struct Instruction { - // only unique per fn/handler - pub(crate) id: i64, - pub(crate) opcode: &'static ByteOpcode, - pub(crate) args: Vec, - pub(crate) dep_ids: Vec, -} diff --git a/avm/src/vm/memory.rs b/avm/src/vm/memory.rs deleted file mode 100644 index 3f560a358..000000000 --- a/avm/src/vm/memory.rs +++ /dev/null @@ -1,1012 +0,0 @@ -use std::array::IntoIter; -use std::str; -use std::sync::Arc; - -use byteorder::{ByteOrder, NativeEndian}; - -use crate::vm::{program::Program, protos, VMError, VMResult}; - -// -2^63 -pub const CLOSURE_ARG_MEM_START: i64 = -9223372036854775808; -// The closure arg memory end has been extended to handle disambiguating nested closure arguments -// being used deep in the scope hierarchy. The quickest solution was to just increase that memory -// space to a large constant range, but the proper solution is to make this no longer a constant -// and determine the range based on the side of the global memory. -pub const CLOSURE_ARG_MEM_END: i64 = CLOSURE_ARG_MEM_START + 9001; // TODO: IT'S OVER 9000! - // Flags for the registers_ish vector. The normal address flag indicates that the data is stored - // normally in either the memory or fractal memory structures. The fixed pointer address flag - // indicates that the value in the memory structure is actually a pointer to an i64 value. The - // handlermemory pointer address flag indicates that the value in the memory structure is actually - // a pointer to a HandlerMemory object. -const NORMAL_ADDR: i8 = 0; -const GMEM_ADDR: i8 = 1; -const ARGS_ADDR: i8 = 2; - -fn addr_type(addr: i64) -> i8 { - return if addr >= 0 { - NORMAL_ADDR - } else if addr <= CLOSURE_ARG_MEM_END { - ARGS_ADDR - } else { - GMEM_ADDR - }; -} - -/// Memory representation of a fractal memory block within HandlerMemory -#[derive(Clone, Debug)] -pub struct FractalMemory { - /// address in HandlerMemory which is not present for actual data or deeply nested fractals - hm_addr: Option, - /// a memory block from HandlerMemory.mems - block: Vec<(usize, i64)>, - /// id for HandlerMemory that contains it, casted as usize from raw ptr - hm_id: usize, -} - -impl FractalMemory { - pub fn new(block: Vec<(usize, i64)>) -> FractalMemory { - return FractalMemory { - hm_addr: None, - hm_id: 0 as *const HandlerMemory as usize, // null ptr, mostly for fractal strings - block, - }; - } - - /// Determines this Fractal was read from the provided HandlerMemory - pub fn belongs(self: &FractalMemory, hm: &Arc) -> bool { - return self.hm_id == 0 || self.hm_id == Arc::as_ptr(hm) as usize; - } - - /// Length of memory block - pub fn len(self: &FractalMemory) -> usize { - return self.block.len(); - } - - /// Compare the blocks at a given index between two FractalMemory - pub fn compare_at(self: &FractalMemory, idx: usize, other: &FractalMemory) -> bool { - return self.block[idx] == other.block[idx]; - } - - /// Reads fixed data from a given address. - pub fn read_fixed(self: &FractalMemory, idx: usize) -> VMResult { - if self.block[idx].0 != usize::MAX { - Err(VMError::IllegalAccess) - } else { - Ok(self.block[idx].1) - } - } -} - -impl PartialEq for FractalMemory { - fn eq(&self, other: &Self) -> bool { - // ignore hm_addr - self.block == other.block - } -} - -/// Memory representation of a handler call -#[derive(Clone, Debug)] -pub struct HandlerMemory { - /// Optional parent pointer present in forked HandlerMemories - parent: Option>, - /// The set of memory blocks. Each representing a fractal. The first (zeroth) block hosts global memory and all blocks - /// afterwards host memory created by the handler. Each block consists of tuples of two values, - /// representing either a virtual pointer or raw data, with three classes of values: - /// 1. `(usize::MAX, any value)` - The first value indicates that the second value is actual raw - /// data - /// 2. `(< usize::MAX, usize::MAX)` - The first value indicates that this is a virtual pointer to - /// nested memory. The second value indicates that the pointer is to an entire block of - /// memory, not an explicit value. - /// 3. `(< usize::MAX, < usize::MAX)` - The first value indicates that this is a virtual pointer - /// to nested memory. The second value indicates that the pointer is to an explicit value - /// within that block of memory. - /// Virtual pointers are simply the indexes into the `mems` field. - mems: Vec>, - /// The virtual address spaces to an (optional) index pointing to mems that the handler can mutate. - /// If the index is not defined in the current Handler Memory, it is in the parent Handler Memory. - /// The first is the "normal" memory space, and the second is the args memory space. - /// Global addresses are fixed for the application and do not need a mutable vector to parse. - addr: (Vec>, Vec>), - /// Specifies which memory block to push values into. - mem_addr: usize, -} - -impl HandlerMemory { - /// Constructs a new HandlerMemory. If given another HandlerMemory it simply adjusts it to the - /// expected memory needs, otherwise constructs a new one with said memory requirements. - pub fn new( - payload_mem: Option>, - mem_req: i64, - ) -> VMResult> { - let mut hm = match payload_mem { - Some(payload) => payload, - None => Arc::new(HandlerMemory { - mems: [Program::global().gmem.clone(), Vec::new()].to_vec(), - addr: (Vec::new(), Vec::new()), - mem_addr: 1, - parent: None, - }), - }; - let handlermemory = Arc::get_mut(&mut hm).ok_or_else(|| VMError::HandMemDanglingPtr)?; - handlermemory.mems[1].reserve(mem_req as usize); - return Ok(hm); - } - - /// Grabs the relevant data for the event and constructs a new HandlerMemory with that value in - /// address 0, or returns no HandlerMemory if it is a void event. - pub fn alloc_payload( - event_id: i64, - curr_addr: i64, - curr_hand_mem: &Arc, - ) -> VMResult>> { - let pls = Program::global() - .event_pls - .get(&event_id) - .ok_or(VMError::EventNotDefined(event_id))? - .clone(); - return if pls == 0 { - // no payload, void event - Ok(None) - } else { - let mut hm = HandlerMemory::new(None, 1)?; - HandlerMemory::transfer(curr_hand_mem, curr_addr, &mut hm, 0)?; - Ok(Some(hm)) - }; - } - - /// Drop the immutable, thread-safe reference to the parent provided by `fork`. - /// Consuming forked children is a mutable operation on the parent - /// that cannot be performed on an object within an Arc. As a result, - /// all the forked children drop their reference to the parent, we take - /// the parent out of the Arc after parallel work is done and then join on its children. - pub fn drop_parent(mut self: Arc) -> VMResult> { - let hm = Arc::get_mut(&mut self).ok_or(VMError::HandMemDanglingPtr)?; - hm.parent.take(); - Ok(self) - } - - /// Returns true if the idxs are valid in self - fn is_idx_defined(self: &HandlerMemory, a: usize, b: usize) -> bool { - let is_raw = a == std::usize::MAX; - let safe_mem_space = self.mem_addr == 1 || a >= self.mem_addr || self.mems[a].len() > 0; - let is_fixed = safe_mem_space && self.mems.len() > a && self.mems[a].len() > b; - let is_fractal = safe_mem_space && self.mems.len() > a && b == std::usize::MAX; - return is_raw || is_fixed || is_fractal; - } - - /// Recursively finds the parent and returns None if the idxs - /// belong to self or otherwise a reference to the parent - fn hm_for_idxs(self: &Arc, a: usize, b: usize) -> Option> { - if self.is_idx_defined(a, b) { - return None; - } - match self.parent.as_ref() { - Some(parent) => parent.hm_for_idxs(a, b).or(Some(parent.clone())), - None => None, - } - } - - /// Takes a given address and looks up the fractal location and - /// `mems` indexes relevant to it if available - pub fn addr_to_idxs_opt(self: &HandlerMemory, addr: i64) -> Option<(usize, usize)> { - return if addr >= 0 { - *self.addr.0.get(addr as usize).unwrap_or(&None) - } else if addr <= CLOSURE_ARG_MEM_END { - *self - .addr - .1 - .get((addr - CLOSURE_ARG_MEM_START) as usize) - .unwrap_or(&None) - } else { - Some((0, ((-1 * addr - 1) / 8) as usize)) - }; - } - - /// Recursively takes a given address and looks up the fractal location and - /// `mems` indexes relevant to it. It also returns an Option that is - /// None if the address is in self or a ptr to the ancestor if `self` is forked - // TODO: determine if we have to do the following: - // if addr < self.mem_addr { copy_from_parent }; - fn addr_to_idxs( - self: &Arc, - addr: i64, - ) -> VMResult<((usize, usize), Option>)> { - return match self.addr_to_idxs_opt(addr) { - Some(res) => Ok((res, self.hm_for_idxs(res.0, res.1))), - None => { - let (idxs, hm_opt) = self - .parent - .as_ref() - // fail if no parent - .ok_or(VMError::OrphanMemory)? - .addr_to_idxs(addr)?; - let hm = match hm_opt { - Some(hm) => Some(hm), - None => self.parent.clone(), - }; - Ok((idxs, hm)) - } - }; - } - - /// Reads fixed data from a given address. - pub fn read_fixed(self: &Arc, addr: i64) -> VMResult { - let ((a, b), hm_opt) = self.addr_to_idxs(addr)?; - return if a == std::usize::MAX { - Ok(b as i64) - } else { - let hm = hm_opt.as_ref().unwrap_or(self).as_ref(); - Ok(hm.mems[a][b].1) - }; - } - - /// Reads an array of data from the given address. - pub fn read_fractal(self: &Arc, addr: i64) -> VMResult { - let ((a, b), hm_opt) = self.addr_to_idxs(addr)?; - //eprintln!("addr: {}, self?: {}, (a,b): ({},{})", addr, hm_opt.is_none(), a, b); - let hm = hm_opt.as_ref().unwrap_or(self); - // Special behavior to read strings out of global memory - let start = if addr_type(addr) == GMEM_ADDR { b } else { 0 }; - return Ok(FractalMemory { - hm_addr: Some(addr), - block: hm.mems[a][start..].to_vec(), - hm_id: Arc::as_ptr(hm) as usize, - }); - } - - /// Provides a mutable array of data from the given address. - pub fn read_mut_fractal<'mem>( - self: &'mem mut Arc, - addr: i64, - ) -> VMResult<&'mem mut Vec<(usize, i64)>> { - let ((a, _), hm_opt) = self.addr_to_idxs(addr)?; - if let Some(hm) = hm_opt { - // copy necessary data from ancestor - Arc::get_mut(self).ok_or(VMError::HandMemDanglingPtr)?.mems[a] = hm.mems[a].clone(); - } - Ok(&mut Arc::get_mut(self).ok_or(VMError::HandMemDanglingPtr)?.mems[a]) - } - - /// You better know what you're doing if you use this - pub fn read_mut_fractal_by_idx<'mem>( - self: &'mem mut Arc, - a: usize, - ) -> VMResult<&'mem mut Vec<(usize, i64)>> { - if let Some(hm) = self.hm_for_idxs(a, std::usize::MAX) { - Arc::get_mut(self).ok_or(VMError::HandMemDanglingPtr)?.mems[a] = hm.mems[a].clone(); - } - Ok(&mut Arc::get_mut(self).ok_or(VMError::HandMemDanglingPtr)?.mems[a]) - } - - /// For a given address, determines if the data is a single value or an array of values, and - /// returns that value either as a vector or the singular value wrapped in a vector, and a - /// boolean indicating if it was a fractal value or not. - pub fn read_either(self: &Arc, addr: i64) -> VMResult<(FractalMemory, bool)> { - let ((a, b), hm_opt) = self.addr_to_idxs(addr)?; - let hm = match hm_opt.as_ref() { - Some(hm) => hm, - None => self, - }; - let (block, is_fractal) = if b < std::usize::MAX { - (vec![hm.mems[a][b].clone()], false) - } else { - (hm.mems[a].clone(), true) - }; - return Ok(( - FractalMemory { - hm_addr: Some(addr), - block, - hm_id: Arc::as_ptr(hm) as usize, - }, - is_fractal, - )); - } - - /// Simply sets a given address to an explicit set of `mems` indexes. Simplifies pointer creation - /// to deeply-nested data. - fn set_addr(self: &mut Arc, addr: i64, a: usize, b: usize) -> VMResult<()> { - let hm = Arc::get_mut(self).ok_or(VMError::HandMemDanglingPtr)?; - if addr_type(addr) == NORMAL_ADDR { - let addru = addr as usize; - if hm.addr.0.len() <= addru { - hm.addr.0.resize(addru + 1, None); - } - hm.addr.0[addru] = Some((a, b)); - } else { - let addru = (addr - CLOSURE_ARG_MEM_START) as usize; - if hm.addr.1.len() <= addru { - hm.addr.1.resize(addru + 1, None); - } - hm.addr.1[addru] = Some((a, b)); - } - Ok(()) - } - - /// For the memory block(s) starting at idx in Fractal, determines if the data is a single value or an array of - /// values, and returns that value either as a vector or the singular value wrapped in a vector, - /// and a boolean indicating if it is a fractal value or not. - pub fn read_from_fractal( - self: &Arc, - fractal: &FractalMemory, - idx: usize, - ) -> (FractalMemory, bool) { - let (a, b) = fractal.block[idx]; - let b_usize = b as usize; - let hm_opt = self.hm_for_idxs(a, b_usize); - let hm = match hm_opt.as_ref() { - Some(hm) => hm, - None => self, - }; - return if a == std::usize::MAX { - // The indexes are the actual data - (FractalMemory::new(vec![(a, b)]), false) - } else { - let (block, is_fractal) = if b_usize < std::usize::MAX { - // The indexes point to fixed data - (vec![hm.mems[a][b_usize].clone()], false) - } else { - // b_usize == std::usize::MAX - // The indexes point at nested data - (hm.mems[a].clone(), true) - }; - ( - FractalMemory { - hm_addr: None, - block, - hm_id: Arc::as_ptr(hm) as usize, - }, - is_fractal, - ) - }; - } - - /// Stores a nested fractal of data in a given address. - pub fn write_fixed_in_fractal( - self: &mut Arc, - fractal: &mut FractalMemory, - idx: usize, - val: i64, - ) -> VMResult<()> { - fractal.block[idx].1 = val; - match (fractal.belongs(self), fractal.hm_addr) { - (true, Some(addr)) => self.write_fractal(addr, fractal), - _ => Err(VMError::MemoryNotOwned), - } - } - - /// Stores a fixed value in a given address. Determines where to place it based on the kind of - /// address in question. - pub fn write_fixed(self: &mut Arc, addr: i64, val: i64) -> VMResult<()> { - let a = self.mem_addr; - let hm = Arc::get_mut(self).ok_or(VMError::HandMemDanglingPtr)?; - let b = hm.mems[a].len(); - hm.mems[a].push((std::usize::MAX, val)); - self.set_addr(addr, a, b) - } - - /// Stores a nested fractal of data in a given address. - pub fn write_fractal( - self: &mut Arc, - addr: i64, - fractal: &FractalMemory, - ) -> VMResult<()> { - let a = self.mems.len(); - if !fractal.belongs(self) { - if fractal.hm_addr.is_none() { - return Err(VMError::MemoryNotOwned); - } - // copy fractal from ancestor - let addr = fractal - .hm_addr - .as_ref() - .ok_or(VMError::OrphanMemory)? - .clone(); - let ((a, _), hm_opt) = self.addr_to_idxs(addr)?; - let hm = hm_opt.ok_or(VMError::IllegalAccess)?; - Arc::get_mut(self).ok_or(VMError::HandMemDanglingPtr)?.mems[a] = hm.mems[a].clone(); - drop(hm); - todo!("Writing to a fractal that a child scope has access to mutably but never acquired mutably. Please report this error!") - } - let mut_self = Arc::get_mut(self).ok_or(VMError::HandMemDanglingPtr)?; - mut_self.mems.push(fractal.block.clone()); - self.set_addr(addr, a, std::usize::MAX) - } - - /// Stores a nested empty fractal of data in a given address. - pub fn init_fractal(self: &mut Arc, addr: i64) -> VMResult<()> { - let a = self.mems.len(); - Arc::get_mut(self) - .ok_or(VMError::HandMemDanglingPtr)? - .mems - .push(Vec::new()); - self.set_addr(addr, a, std::usize::MAX) - } - - /// Pushes a fixed value into a fractal at a given address. - pub fn push_fixed(self: &mut Arc, addr: i64, val: i64) -> VMResult<()> { - let mem = self.read_mut_fractal(addr)?; - mem.push((std::usize::MAX, val)); - Ok(()) - } - - /// Pushes a nested fractal value into a fractal at a given address. - pub fn push_fractal( - self: &mut Arc, - addr: i64, - val: FractalMemory, - ) -> VMResult<()> { - let a = self.mems.len(); - let mem = self.read_mut_fractal(addr)?; - mem.push((a, std::usize::MAX as i64)); - Arc::get_mut(self) - .ok_or(VMError::HandMemDanglingPtr)? - .mems - .push(val.block); - Ok(()) - } - - /// Pops a value off of the fractal. May be fixed data or a virtual pointer. - pub fn pop(self: &mut Arc, addr: i64) -> VMResult { - let mem = self.read_mut_fractal(addr)?; - if mem.len() > 0 { - return Ok(FractalMemory::new(vec![mem - .pop() - // if pop returned a None, that means something very bad has happened. - .ok_or(VMError::IllegalAccess)?])); - } else { - return Err(VMError::Other(format!("cannot pop empty array"))); - } - } - - /// Deletes a value off of the fractal at the given idx. May be fixed data or a virtual pointer. - pub fn delete(self: &mut Arc, addr: i64, idx: usize) -> VMResult { - let mem = self.read_mut_fractal(addr)?; - if mem.len() > 0 && mem.len() > idx { - return Ok(FractalMemory::new(vec![mem.remove(idx)])); - } else { - return Err(VMError::Other(format!( - "cannot remove idx {} from array with length {}", - idx, - mem.len() - ))); - } - } - - /* REGISTER MANIPULATION METHODS */ - - /// Creates a pointer from `orig_addr` to `addr` - pub fn register( - self: &mut Arc, - addr: i64, - orig_addr: i64, - is_variable: bool, - ) -> VMResult<()> { - let ((a, b), _) = self.addr_to_idxs(orig_addr)?; - if addr_type(orig_addr) == GMEM_ADDR && is_variable { - // Special behavior to read strings out of global memory - let string = - HandlerMemory::fractal_to_string(FractalMemory::new(self.mems[a][b..].to_vec()))?; - self.write_fractal(addr, &HandlerMemory::str_to_fractal(&string)) - } else { - self.set_addr(addr, a, b) - } - } - - /// Pushes a pointer from `orig_addr` address into the fractal at `addr`. - pub fn push_register(self: &mut Arc, addr: i64, orig_addr: i64) -> VMResult<()> { - let ((a, b), _) = self.addr_to_idxs(orig_addr)?; - // Special path for strings in global memory which is the same for parent and self - if a == 0 { - let strmem = self.mems[0][b..].to_vec().clone(); - let new_a = self.mems.len(); - Arc::get_mut(self) - .ok_or(VMError::HandMemDanglingPtr)? - .mems - .push(strmem); - let mem = self.read_mut_fractal(addr)?; - mem.push((new_a, std::usize::MAX as i64)); - } else { - let mem = self.read_mut_fractal(addr)?; - mem.push((a, b as i64)); - } - Ok(()) - } - - /// Creates a pointer from `orig_addr` to index/offset `offset_addr` of fractal in `fractal_addr` - pub fn register_in( - self: &mut Arc, - orig_addr: i64, - fractal_addr: i64, - offset_addr: i64, - ) -> VMResult<()> { - let ((a, b), _) = self.addr_to_idxs(orig_addr)?; - let mem = self.read_mut_fractal(fractal_addr)?; - mem[offset_addr as usize] = (a, b as i64); - Ok(()) - } - - /// Creates a pointer from index/offset `offset_addr` of fractal in `fractal_addr` to `out_addr` - /// The inverse of `register_in` - pub fn register_out( - self: &mut Arc, - fractal_addr: i64, - offset_addr: usize, - out_addr: i64, - ) -> VMResult<()> { - let ((arr_a, _), _) = self.addr_to_idxs(fractal_addr)?; - let fractal = self.read_fractal(fractal_addr)?; - let (a, b) = fractal.block[offset_addr]; - if a < std::usize::MAX { - self.set_addr(out_addr, a, b as usize) - } else { - self.set_addr(out_addr, arr_a, offset_addr) - } - } - - /// Creates a pointer from index/offset `idx` in FractalMemory to `out_addr` - /// Used for deeply nested fractals in which case `register_out` can't be used - pub fn register_from_fractal( - self: &mut Arc, - out_addr: i64, - fractal: &FractalMemory, - idx: usize, - ) -> VMResult<()> { - let (a, b) = fractal.block[idx]; - self.set_addr(out_addr, a, b as usize) - } - - /// Pushes a pointer from index/offset `offset_addr` of FractalMemory to fractal at `out_addr` - pub fn push_register_out( - self: &mut Arc, - out_addr: i64, - fractal: &FractalMemory, - offset_addr: usize, - ) -> VMResult<()> { - let mem = self.read_mut_fractal(out_addr)?; - mem.push(fractal.block[offset_addr]); - Ok(()) - } - - /* DATA TRANSFER, FORKING AND DUPLICATION METHODS */ - - /// Migrates data from one HandlerMemory at a given address to another HandlerMemory at another - /// address. Used by many things. - pub fn transfer( - origin: &Arc, - orig_addr: i64, - dest: &mut Arc, - dest_addr: i64, - ) -> VMResult<()> { - let ((a, b), hm_opt) = origin.addr_to_idxs(orig_addr)?; - let orig = match hm_opt.as_ref() { - Some(orig) => orig, - None => origin, - }; - return HandlerMemory::transfer_idxs(orig, a, b, dest, dest_addr); - } - - pub fn transfer_idxs( - orig: &Arc, - a: usize, - b: usize, - dest: &mut Arc, - dest_addr: i64, - ) -> VMResult<()> { - if a == 0 { - // Special behavior for global memory transfers since it may be a single value or a string - let mem_slice = &orig.mems[a][b..]; - // To make this distinction we're gonna do some tests on the memory and see if it evals as a - // string or not. There is some ridiculously small possibility that this is going to make a - // false positive though so TODO: either make global memory unambiguous or update all uses of - // this function to provide a type hint. - let len = mem_slice[0].1 as usize; - if len == 0 { - // Assume zero is not a string - return dest.write_fixed(dest_addr, mem_slice[0].1); - } - let mut s_bytes: Vec = Vec::new(); - for i in 1..mem_slice.len() { - let mut b = mem_slice[i].1.clone().to_ne_bytes().to_vec(); - s_bytes.append(&mut b); - } - if len > s_bytes.len() { - // Absolutely not correct - return dest.write_fixed(dest_addr, mem_slice[0].1); - } - match str::from_utf8(&s_bytes[0..len]) { - // Well, waddaya know! - Ok(string) => return dest.write_fractal(dest_addr, &HandlerMemory::str_to_fractal(string)), - // Also not a string - Err(_) => dest.write_fixed(dest_addr, mem_slice[0].1)?, - }; - } - return if a == std::usize::MAX { - // It's direct fixed data, just copy it over - dest.write_fixed(dest_addr, b as i64) - } else if a < std::usize::MAX && (b as usize) < std::usize::MAX { - // All pointers are made shallow, so we know this is a pointer to a fixed value and just - // grab it and de-reference it. - let (_, b_nest) = orig.mems[a][b as usize]; - dest.write_fixed(dest_addr, b_nest) - } else if a < std::usize::MAX && b as usize == std::usize::MAX { - // It's a nested array of data. This may itself contain references to other nested arrays of - // data and is relatively complex to transfer over. First create some lookup vectors and - // populate them with the nested fractal, adding more and more as each fractal is checked - // until no new ones are added - let mut check_idx = 0; - let mut orig_arr_addrs: Vec = vec![a]; - let mut orig_arr_copies: Vec> = vec![orig.mems[a].clone()]; - while check_idx < orig_arr_addrs.len() { - let arr = &orig_arr_copies[check_idx]; - let l = arr.len(); - drop(arr); - for i in 0..l { - let other_arr_idx = orig_arr_copies[check_idx][i].0.clone(); - if other_arr_idx < std::usize::MAX { - if !orig_arr_addrs.contains(&other_arr_idx) { - orig_arr_addrs.push(other_arr_idx); - orig_arr_copies.push(orig.mems[other_arr_idx].clone()); - } - } - } - check_idx = check_idx + 1; - } - // Next, get the current size of the destination mems vector to use as the offset to add to - // the index of the copied arrays, updating their interior references, if any, in the process - let dest_offset = dest.mems.len(); - for i in 0..orig_arr_copies.len() { - let arr = &mut orig_arr_copies[i]; - for j in 0..arr.len() { - let (a, b) = arr[j]; - if a < std::usize::MAX { - for k in 0..orig_arr_addrs.len() { - if orig_arr_addrs[k] == a { - arr[j] = (dest_offset + k, b); - } - } - } - } - } - Arc::get_mut(dest) - .ok_or(VMError::HandMemDanglingPtr)? - .mems - .append(&mut orig_arr_copies); - // Finally, set the destination address to point at the original, main nested array - dest.set_addr(dest_addr, dest_offset, std::usize::MAX) - } else { - Ok(()) - }; - } - - /// Creates a duplicate of data at one address in the HandlerMemory in a new address. Makes the - /// `clone` function in Alan possible. - pub fn dupe(self: &mut Arc, orig_addr: i64, dest_addr: i64) -> VMResult<()> { - // This *should be possible with something like this: - // HandlerMemory::transfer(self, orig_addr, self, dest_addr); - // But Rust's borrow checker doesn't like it, so we basically have to replicate the code here - // despite the fact that it should work just fine... - let ((a, b), _) = self.addr_to_idxs(orig_addr)?; - return if a == std::usize::MAX { - self.write_fixed(dest_addr, b as i64) - } else if a < std::usize::MAX && (b as usize) < std::usize::MAX { - // All pointers are made shallow, so we know this is a pointer to a fixed value and just - // grab it and de-reference it. - let (_, b_nest) = self.mems[a][b]; - self.write_fixed(dest_addr, b_nest) - } else if a < std::usize::MAX && b as usize == std::usize::MAX { - // It's a nested array of data. This may itself contain references to other nested arrays of - // data and is relatively complex to transfer over. First create some lookup vectors and - // populate them with the nested fractal, adding more and more as each fractal is checked - // until no new ones are added - let mut check_idx = 0; - let mut orig_arr_addrs: Vec = vec![a]; - let mut orig_arr_copies: Vec> = - vec![self.read_fractal(orig_addr)?.block.clone()]; - while check_idx < orig_arr_addrs.len() { - let arr = &orig_arr_copies[check_idx]; - let l = arr.len(); - drop(arr); - for i in 0..l { - let other_arr_idx = orig_arr_copies[check_idx][i].0.clone(); - if other_arr_idx < std::usize::MAX { - if !orig_arr_addrs.contains(&other_arr_idx) { - orig_arr_addrs.push(other_arr_idx); - orig_arr_copies.push(self.mems[other_arr_idx].clone()); - } - } - } - check_idx = check_idx + 1; - } - // Next, get the current size of the destination mems vector to use as the offset to add to - // the index of the copied arrays, updating their interior references, if any, in the process - let dest_offset = self.mems.len(); - for i in 0..orig_arr_copies.len() { - let arr = &mut orig_arr_copies[i]; - for j in 0..arr.len() { - let (a, b) = arr[j]; - if a < std::usize::MAX { - for k in 0..orig_arr_addrs.len() { - if orig_arr_addrs[k] == a { - arr[j] = (dest_offset + k, b); - } - } - } - } - } - Arc::get_mut(self) - .ok_or(VMError::HandMemDanglingPtr)? - .mems - .append(&mut orig_arr_copies); - // Finally, set the destination address to point at the original, main nested array - self.set_addr(dest_addr, dest_offset, std::usize::MAX) - } else { - Ok(()) - }; - } - - /// Returns a new HandlerMemory with a read-only reference to HandlerMemory as parent - pub fn fork(parent: Arc) -> VMResult> { - let s = parent.mems.len(); - let mut hm = HandlerMemory::new(None, 1)?; - let handmem = Arc::get_mut(&mut hm).ok_or(VMError::HandMemDanglingPtr)?; - handmem.parent = Some(parent); - handmem.mems.resize(s + 1, Vec::new()); - handmem.mem_addr = s; - return Ok(hm); - } - - /// Joins two HandlerMemory structs back together. Assumes that the passed in handler memory was - /// generated by a fork call. This process moves over the records created in the forked HandlerMemory - /// into the original and then "stitches up" the virtual memory pointers for anything pointing at - /// newly-created data. This mechanism is faster but will keep around unreachable memory for longer. - /// Whether or not this is the right trade-off will have to be seen by real-world usage. - /// - /// Since the HandlerMemory has been transfered back into the original, this method assumes that - /// the atomic reference is the *only one*, and consumes it so that it can't be used again. - pub fn join(self: &mut Arc, mut hm: Arc) -> VMResult<()> { - let s = hm.mem_addr; // The initial block that will be transferred (plus all following blocks) - let s2 = self.mems.len(); // The new address of the initial block - let offset = s2 - s; // Assuming it was made by `fork` this should be positive or zero - if let Some((a, b)) = hm.addr_to_idxs_opt(CLOSURE_ARG_MEM_START) { - // The only address that can "escape" - let off = if a < std::usize::MAX && a >= s { - offset - } else { - 0 - }; - self.set_addr(CLOSURE_ARG_MEM_START, a + off, b)?; - }; - - // println!("a: {}, b: {}, s: {}, in_fork: {}, in_parent: {}", a, b, s, hm.is_idx_defined(a, b), self.is_idx_defined(a, b)); - let hm = Arc::get_mut(&mut hm).ok_or(VMError::HandMemDanglingPtr)?; - let parent = Arc::get_mut(self).ok_or(VMError::HandMemDanglingPtr)?; - let updated_prev_mems = hm.mems.drain(..s); - for (i, updated_prev_mem) in updated_prev_mems.enumerate() { - if updated_prev_mem.len() > 0 && i > 0 { - // This was a mutated clone of parent data, switch to the mutated form - parent.mems[i] = updated_prev_mem; - } - } - // Append the relevant ones to the original HandlerMemory - parent.mems.append(&mut hm.mems); - - // Set the return address on the original HandlerMemory to the acquired indexes, potentially - // offset if it is a pointer at new data - - // Similarly "stitch up" every pointer in the moved data with a pass-through scan and update - let l = parent.mems.len(); - for i in s2..l { - let mem = &mut parent.mems[i]; - for j in 0..mem.len() { - let (a, b) = mem[j]; - if a < std::usize::MAX && a >= s { - mem[j] = (a + offset, b); - } - } - } - // Finally pull any addresses added by the old object into the new with a similar stitching - if hm.addr.0.len() > parent.addr.0.len() { - parent.addr.0.resize(hm.addr.0.len(), None); - } - for i in 0..hm.addr.0.len() { - if let Some((a, b)) = hm.addr.0[i] { - match parent.addr.0[i] { - Some((c, d)) if (a != c || b != d) => { - if a + offset >= s && a != std::usize::MAX { - parent.addr.0[i] = Some((a + offset, b)) - } else { - parent.addr.0[i] = Some((a, b)) - } - } - Some((c, _)) if a == c => parent.addr.0[i] = Some((a, b)), - Some(_) => { - // TODO: Since we now `fork`/`join` on IO opcodes, we need to figure out how to handle - // the case where all children edit the same value. Right now, we work around this - // in a couple ways: - // 1. parallelized io opcode closures will refuse to compile if they edit a variable - // 2. linear io opcodes *can* edit external variables, but since they can only be - // executed in their own batch, we work around this by only `fork`/`join`ing - // for batches with >1 IO opcode. - // However, as we start increasing efficiency in the AVM, we may eventually come to a - // point where we actually *do* have to merge the parent/children memories. Until then, - // the workarounds should be fine. - return Err(VMError::Other("Unable to merge memories".to_string())); - } - None => parent.addr.0[i] = Some((a + offset, b)), - } - } - } - Ok(()) - } - - /// Takes a UTF-8 string and converts it to fractal memory that can be stored inside of a - /// HandlerMemory. Alan stores strings as Pascal strings with a 64-bit length prefix. There is no - /// computer on the planet that has 64-bits worth of RAM, so this should work for quite a while - /// into the future. :) - pub fn str_to_fractal(s: &str) -> FractalMemory { - let mut s_mem = vec![(std::usize::MAX, s.len() as i64)]; - let mut s_bytes = s.as_bytes().to_vec(); - loop { - if s_bytes.len() % 8 != 0 { - s_bytes.push(0); - } else { - break; - } - } - let mut i = 0; - loop { - if i < s_bytes.len() { - let s_slice = &s_bytes[i..i + 8]; - s_mem.push((std::usize::MAX, NativeEndian::read_i64(s_slice))); - i = i + 8; - } else { - break; - } - } - FractalMemory::new(s_mem) - } - - /// Takes a fractal memory and treats it like a UTF-8 encoded Pascal string, and the converts it - /// to something Rust can work with. This function *may* crash if the underlying data is not a - /// UTF-8 encoded Pascal string. - pub fn fractal_to_string(f: FractalMemory) -> VMResult { - let s_len = f.block[0].1 as usize; - let s_bytes = f - .block - .iter() - .skip(1) - // TODO: `IntoIter::new` will be deprecated once `rust-lang/rust#65819` is merged - .flat_map(|(_, chars)| IntoIter::new(chars.to_ne_bytes())) - .collect::>(); - let s = str::from_utf8(&s_bytes[0..s_len]).map_err(|_| VMError::InvalidString)?; - Ok(s.to_string()) - } - - /// Returns a new Protobuf HandlerMemory from an existing HandlerMemory - pub fn to_pb(self: &Arc) -> protos::HandlerMemory::HandlerMemory { - let mut proto_hm = protos::HandlerMemory::HandlerMemory::new(); - set_pb_mems(self, &mut proto_hm); - set_pb_addr(self, &mut proto_hm); - if let Some(parent) = &self.parent { - proto_hm.set_parent(HandlerMemory::to_pb(&parent)); - } - proto_hm.set_mem_addr(self.mem_addr as u64); - proto_hm - } - - /// Returns a HandlerMemory from a new Protobuf HandlerMemory - pub fn from_pb(proto_hm: &protos::HandlerMemory::HandlerMemory) -> VMResult> { - let mut hm = HandlerMemory::new(None, 1)?; - let mut hm_mut = Arc::get_mut(&mut hm).ok_or(VMError::HandMemDanglingPtr)?; - set_mems_from_pb(&proto_hm, hm_mut); - set_addr_from_pb(&proto_hm, hm_mut); - if proto_hm.has_parent() { - let parent = proto_hm.get_parent(); - hm_mut.parent = Some(HandlerMemory::from_pb(&parent)?); - } - hm_mut.mem_addr = proto_hm.get_mem_addr() as usize; - Ok(hm) - } -} - -/// Sets mems HandlerMemory attribute from Protobuf HandlerMemory struct -fn set_mems_from_pb(proto_hm: &protos::HandlerMemory::HandlerMemory, hm: &mut HandlerMemory) { - // Set global memory first - let mut mems = [Program::global().gmem.clone()].to_vec(); - for pb_mem in proto_hm.get_mems() { - let mut mem = Vec::new(); - for mem_block in pb_mem.get_mem() { - mem.push((mem_block.get_mem_type() as usize, mem_block.get_mem_val())); - } - mems.push(mem); - } - hm.mems = mems; -} - -/// Sets mems Protobuf HandlerMemory attribute from HandlerMemory struct -fn set_pb_mems(hm: &Arc, proto_hm: &mut protos::HandlerMemory::HandlerMemory) { - let mut mem_vec: protobuf::RepeatedField = - protobuf::RepeatedField::new(); - for (i, hm_inner_vec) in hm.mems.iter().enumerate() { - // Ignore global memory - if i == 0 { - continue; - }; - let mut inner_vec: protobuf::RepeatedField = - protobuf::RepeatedField::new(); - for hm_mem_block in hm_inner_vec.iter() { - let mut mem_block = protos::HandlerMemory::HandlerMemory_MemBlock::new(); - mem_block.set_mem_type(hm_mem_block.0 as u64); - mem_block.set_mem_val(hm_mem_block.1 as i64); - inner_vec.push(mem_block); - } - let mut mem = protos::HandlerMemory::HandlerMemory_Mems::new(); - mem.set_mem(inner_vec); - mem_vec.push(mem); - } - proto_hm.set_mems(mem_vec); -} - -/// Sets addr HandlerMemory attribute from Protobuf HandlerMemory struct -fn set_addr_from_pb(proto_hm: &protos::HandlerMemory::HandlerMemory, hm: &mut HandlerMemory) { - let mut mem_space = Vec::new(); - let mut mem_space_args = Vec::new(); - complete_mem_space_from_pb(&mut mem_space, &proto_hm.get_addr().get_mem_space()); - complete_mem_space_from_pb( - &mut mem_space_args, - &proto_hm.get_addr().get_mem_space_args(), - ); - hm.addr = (mem_space, mem_space_args); -} - -/// Sets addr Protobuf HandlerMemory attribute from HandlerMemory struct -fn set_pb_addr(hm: &Arc, proto_hm: &mut protos::HandlerMemory::HandlerMemory) { - let mut addr = protos::HandlerMemory::HandlerMemory_Addr::new(); - let mut mem_space_vec: protobuf::RepeatedField = - protobuf::RepeatedField::new(); - let mut mem_space_args_vec: protobuf::RepeatedField< - protos::HandlerMemory::HandlerMemory_MemSpace, - > = protobuf::RepeatedField::new(); - complete_pb_mem_space(&mut mem_space_vec, &(hm.addr.0)); - complete_pb_mem_space(&mut mem_space_args_vec, &(hm.addr.1)); - addr.set_mem_space(mem_space_vec); - addr.set_mem_space_args(mem_space_args_vec); - proto_hm.set_addr(addr); -} - -/// Completes Protobuf HandlerMemory mem space vector from HandlerMemory -fn complete_pb_mem_space( - mem_space_vec: &mut protobuf::RepeatedField, - hm_addr: &Vec>, -) { - for hm_mem_space in hm_addr.iter() { - let mut mem_space = protos::HandlerMemory::HandlerMemory_MemSpace::new(); - if let Some(hm_mem_space) = hm_mem_space { - let mut mem_space_struct = protos::HandlerMemory::HandlerMemory_MemSpaceStruct::new(); - mem_space_struct.set_first(hm_mem_space.0 as u64); - mem_space_struct.set_second(hm_mem_space.1 as u64); - mem_space.set_memspacestruct(mem_space_struct); - } else { - mem_space.clear_memspacestruct(); - } - mem_space_vec.push(mem_space); - } -} - -/// Completes HandlerMemory mem space vector from Protobuf HandlerMemory -fn complete_mem_space_from_pb( - mem_space_vec: &mut Vec>, - proto_mem_space: &[protos::HandlerMemory::HandlerMemory_MemSpace], -) { - for mem_space in proto_mem_space.iter() { - if mem_space.has_memspacestruct() { - let mem_space_struct = mem_space.get_memspacestruct(); - mem_space_vec.push(Some(( - mem_space_struct.get_first() as usize, - mem_space_struct.get_second() as usize, - ))); - } else { - mem_space_vec.push(None); - } - } -} diff --git a/avm/src/vm/mod.rs b/avm/src/vm/mod.rs deleted file mode 100644 index 9f8f0902c..000000000 --- a/avm/src/vm/mod.rs +++ /dev/null @@ -1,82 +0,0 @@ -use std::fmt::Display; - -pub mod event; -#[macro_use] -pub mod http; -pub mod instruction; -pub mod memory; -pub mod opcode; -pub mod program; -pub mod protos; -pub mod run; -pub mod telemetry; - -pub type VMResult = Result; - -#[derive(Debug)] -pub enum VMError { - AlreadyRunning, - EventNotDefined(i64), - FileNotFound(String), // path - HandMemDanglingPtr, - InvalidFile(String), // reason - IOError(std::io::Error), - IllegalAccess, - InvalidNOP, - InvalidString, - MemoryNotOwned, - OrphanMemory, - ShutDown, - Other(String), - UnexpectedInstruction(InstrType), -} - -impl Display for VMError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - VMError::AlreadyRunning => write!(f, "A VM instance was already detected to be running"), - VMError::EventNotDefined(event_id) => { - write!(f, "Event with event id {} is not defined", event_id) - } - VMError::FileNotFound(path) => write!(f, "File not found: {}", path), - VMError::HandMemDanglingPtr => write!(f, "There is a dangling pointer to a HandlerMemory"), - VMError::InvalidFile(reason) => write!(f, "File is invalid: {}", reason), - VMError::IOError(err) => err.fmt(f), - VMError::IllegalAccess => write!(f, "Illegal access"), - VMError::InvalidNOP => write!(f, "A NOP operation was used in an illegal context"), - VMError::InvalidString => write!(f, "Invalid string"), - VMError::MemoryNotOwned => write!( - f, - "Attempting to write to memory not owned by the current handler" - ), - VMError::OrphanMemory => write!( - f, - "Memory referenced in parent, but no parent pointer defined" - ), - VMError::ShutDown => write!(f, "The AVM instance appears to be shut down"), - VMError::Other(reason) => reason.fmt(f), - VMError::UnexpectedInstruction(instr_ty) => { - write!(f, "Expected another {} instruction", instr_ty) - } - } - } -} - -impl std::error::Error for VMError {} - -#[derive(Debug)] -pub enum InstrType { - CPU, - IO, - UnpredictableCPU, -} - -impl Display for InstrType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - InstrType::CPU => write!(f, "CPU"), - InstrType::IO => write!(f, "IO"), - InstrType::UnpredictableCPU => write!(f, "Unpredictable CPU"), - } - } -} diff --git a/avm/src/vm/opcode.rs b/avm/src/vm/opcode.rs deleted file mode 100644 index 6900d5473..000000000 --- a/avm/src/vm/opcode.rs +++ /dev/null @@ -1,4848 +0,0 @@ -use futures::future::join_all; -use std::collections::HashMap; -use std::convert::Infallible; -use std::fmt::Debug; -use std::future::Future; -use std::hash::Hasher; -use std::io::{self, Write}; -use std::pin::Pin; -use std::ptr::NonNull; -use std::str; -use std::sync::{Arc, RwLock}; -use std::time::Duration; - -use byteorder::{ByteOrder, LittleEndian}; -use dashmap::DashMap; -use hyper::header::{HeaderName, HeaderValue}; -use hyper::{client::ResponseFuture, Body, Request, Response, StatusCode}; -use once_cell::sync::Lazy; -use rand::{thread_rng, Rng}; -use regex::Regex; -use serde_json::json; -use tokio::process::Command; -use tokio::sync::oneshot::{self, Receiver, Sender}; -use tokio::time::sleep; -use twox_hash::XxHash64; - -use crate::daemon::ctrl::NAIVE_CLIENT; -use crate::daemon::daemon::{post_v1, CLUSTER_SECRET, CONTROL_PORT_CHANNEL, DAEMON_PROPS}; -use crate::vm::event::{BuiltInEvents, EventEmit, HandlerFragment, NOP_ID}; -use crate::vm::http::HTTP_CLIENT; -use crate::vm::memory::{FractalMemory, HandlerMemory, CLOSURE_ARG_MEM_START}; -use crate::vm::program::Program; -use crate::vm::run::EVENT_TX; -use crate::vm::{VMError, VMResult}; - -pub static DS: Lazy>>> = - Lazy::new(|| Arc::new(DashMap::>::new())); - -// used for load balancing in the cluster -pub static REGION_VMS: Lazy>>> = - Lazy::new(|| Arc::new(RwLock::new(Vec::new()))); - -// type aliases -/// Futures implement an Unpin marker that guarantees to the compiler that the future will not move while it is running -/// so it can be polled. If it is moved, the implementation would be unsafe. We have to manually pin the future because -/// we are creating it dynamically. We must also specify that the `Box`ed Future can be moved across threads with a `+ Send`. -/// For more information see: -/// https://stackoverflow.com/questions/58354633/cannot-use-impl-future-to-store-async-function-in-a-vector -/// https://stackoverflow.com/questions/51485410/unable-to-tokiorun-a-boxed-future-because-the-trait-bound-send-is-not-satisfie -pub type HMFuture = Pin>> + Send>>; - -/// A function to be run for an opcode. -pub(crate) enum OpcodeFn { - Cpu(fn(&[i64], &mut Arc) -> VMResult<()>), - UnpredCpu(fn(Vec, Arc) -> HMFuture), - Io(fn(Vec, Arc) -> HMFuture), -} - -impl Debug for OpcodeFn { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - OpcodeFn::Cpu(_) => write!(f, "cpu"), - OpcodeFn::UnpredCpu(_) => write!(f, "unpred_cpu"), - OpcodeFn::Io(_) => write!(f, "io"), - } - } -} - -/// To allow concise definition of opcodes we have a struct that stores all the information -/// about an opcode and how to run it. -#[derive(Debug)] -pub struct ByteOpcode { - /// Opcode value as an i64 number - pub(crate) _id: i64, - /// Human readable name for id - pub(crate) _name: String, - /// The native code to execute for this opcode - pub(crate) fun: OpcodeFn, -} - -impl ByteOpcode { - /// There used to be a `pred_exec` field, but that is now dependent on the - /// kind of `OpcodeFn` that is associated with the opcode, so I made this - /// inline function to make my life easier when refactoring references :) - #[inline(always)] - pub(crate) fn pred_exec(&self) -> bool { - match self.fun { - OpcodeFn::Cpu(_) => true, - OpcodeFn::UnpredCpu(_) => false, - OpcodeFn::Io(_) => false, - } - } -} - -pub fn opcode_id(name: &str) -> i64 { - let mut ascii_name = [b' '; 8]; - // Now insert the new name characters - for (i, c) in name.chars().take(8).enumerate() { - ascii_name[i] = c as u8; - } - let id = LittleEndian::read_i64(&ascii_name); - return id; -} - -pub static OPCODES: Lazy> = Lazy::new(|| { - let mut o = HashMap::new(); - - macro_rules! cpu { - ($name:ident => fn ($args:ident , $hand_mem:ident) $body:block) => { - #[allow(non_snake_case)] - fn $name($args: &[i64], $hand_mem: &mut Arc) -> VMResult<()> { - $body - } - let id = opcode_id(stringify!($name)); - let opcode = ByteOpcode { - _id: id, - _name: stringify!($name).to_string(), - fun: OpcodeFn::Cpu($name), - }; - o.insert(id, opcode); - }; - } - macro_rules! unpred_cpu { - ($name:ident => fn ($args:ident , $hand_mem:ident) $body:block) => { - #[allow(non_snake_case)] - fn $name($args: Vec, $hand_mem: Arc) -> HMFuture { - $body - } - let id = opcode_id(stringify!($name)); - let opcode = ByteOpcode { - _id: id, - _name: stringify!($name).to_string(), - fun: OpcodeFn::UnpredCpu($name), - }; - o.insert(id, opcode); - }; - ($name:ident => fn (mut $args:ident , $hand_mem:ident) $body:block) => { - #[allow(non_snake_case)] - fn $name(mut $args: Vec, $hand_mem: Arc) -> HMFuture { - $body - } - let id = opcode_id(stringify!($name)); - let opcode = ByteOpcode { - _id: id, - _name: stringify!($name).to_string(), - fun: OpcodeFn::UnpredCpu($name), - }; - o.insert(id, opcode); - }; - ($name:ident => fn ($args:ident , mut $hand_mem:ident) $body:block) => { - #[allow(non_snake_case)] - fn $name($args: Vec, mut $hand_mem: Arc) -> HMFuture { - $body - } - let id = opcode_id(stringify!($name)); - let opcode = ByteOpcode { - _id: id, - _name: stringify!($name).to_string(), - fun: OpcodeFn::UnpredCpu($name), - }; - o.insert(id, opcode); - }; - ($name:ident => fn (mut $args:ident , mut $hand_mem:ident) $body:block) => { - #[allow(non_snake_case)] - fn $name(mut $args: Vec, mut $hand_mem: Arc) -> HMFuture { - $body - } - let id = opcode_id(stringify!($name)); - let opcode = ByteOpcode { - _id: id, - _name: stringify!($name).to_string(), - fun: OpcodeFn::UnpredCpu($name), - }; - o.insert(id, opcode); - }; - } - macro_rules! io { - ($name:ident => fn ($args:ident , $hand_mem:ident) $body:block) => { - #[allow(non_snake_case)] - fn $name($args: Vec, $hand_mem: Arc) -> HMFuture { - $body - } - let id = opcode_id(stringify!($name)); - let opcode = ByteOpcode { - _id: id, - _name: stringify!($name).to_string(), - fun: OpcodeFn::Io($name), - }; - o.insert(id, opcode); - }; - ($name:ident => fn (mut $args:ident , $hand_mem:ident) $body:block) => { - #[allow(non_snake_case)] - fn $name(mut $args: Vec, $hand_mem: Arc) -> HMFuture { - $body - } - let id = opcode_id(stringify!($name)); - let opcode = ByteOpcode { - _id: id, - _name: stringify!($name).to_string(), - fun: OpcodeFn::Io($name), - }; - o.insert(id, opcode); - }; - ($name:ident => fn ($args:ident , mut $hand_mem:ident) $body:block) => { - #[allow(non_snake_case)] - fn $name($args: Vec, mut $hand_mem: Arc) -> HMFuture { - $body - } - let id = opcode_id(stringify!($name)); - let opcode = ByteOpcode { - _id: id, - _name: stringify!($name).to_string(), - fun: OpcodeFn::Io($name), - }; - o.insert(id, opcode); - }; - ($name:ident => fn (mut $args:ident , mut $hand_mem:ident) $body:block) => { - #[allow(non_snake_case)] - fn $name(mut $args: Vec, mut $hand_mem: Arc) -> HMFuture { - $body - } - let id = opcode_id(stringify!($name)); - let opcode = ByteOpcode { - _id: id, - _name: stringify!($name).to_string(), - fun: OpcodeFn::Io($name), - }; - o.insert(id, opcode); - }; - } - - // Type conversion opcodes - cpu!(i8f64 => fn(args, hand_mem) { - let out = hand_mem.read_fixed(args[0])? as f64; - hand_mem.write_fixed(args[2], i64::from_ne_bytes(out.to_ne_bytes()))?; - Ok(()) - }); - cpu!(i16f64 => fn(args, hand_mem) { - let out = hand_mem.read_fixed(args[0])? as f64; - hand_mem.write_fixed(args[2], i64::from_ne_bytes(out.to_ne_bytes()))?; - Ok(()) - }); - cpu!(i32f64 => fn(args, hand_mem) { - let out = hand_mem.read_fixed(args[0])? as f64; - hand_mem.write_fixed(args[2], i64::from_ne_bytes(out.to_ne_bytes()))?; - Ok(()) - }); - cpu!(i64f64 => fn(args, hand_mem) { - let out = hand_mem.read_fixed(args[0])? as f64; - hand_mem.write_fixed(args[2], i64::from_ne_bytes(out.to_ne_bytes()))?; - Ok(()) - }); - cpu!(f32f64 => fn(args, hand_mem) { - let out = f32::from_ne_bytes((hand_mem.read_fixed(args[0])? as i32).to_ne_bytes()); - hand_mem.write_fixed(args[2], i32::from_ne_bytes(out.to_ne_bytes()) as i64)?; - Ok(()) - }); - cpu!(strf64 => fn(args, hand_mem) { - let s = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[0])?)?; - let out: f64 = s.parse().unwrap(); - hand_mem.write_fixed(args[2], i64::from_ne_bytes(out.to_ne_bytes()))?; - Ok(()) - }); - cpu!(boolf64 => fn(args, hand_mem) { - let out = hand_mem.read_fixed(args[0])? as f64; - hand_mem.write_fixed(args[2], i64::from_ne_bytes(out.to_ne_bytes()))?; - Ok(()) - }); - - cpu!(i8f32 => fn(args, hand_mem) { - let num = hand_mem.read_fixed(args[0])? as f32; - let out = i32::from_ne_bytes(num.to_ne_bytes()) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(i16f32 => fn(args, hand_mem) { - let num = hand_mem.read_fixed(args[0])? as f32; - let out = i32::from_ne_bytes(num.to_ne_bytes()) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(i32f32 => fn(args, hand_mem) { - let num = hand_mem.read_fixed(args[0])? as f32; - let out = i32::from_ne_bytes(num.to_ne_bytes()) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(i64f32 => fn(args, hand_mem) { - let num = hand_mem.read_fixed(args[0])? as f32; - let out = i32::from_ne_bytes(num.to_ne_bytes()) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(f64f32 => fn(args, hand_mem) { - let num = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()) as f32; - let out = i32::from_ne_bytes(num.to_ne_bytes()) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(strf32 => fn(args, hand_mem) { - let s = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[0])?)?; - let num: f32 = s.parse().unwrap(); - let out = i32::from_ne_bytes(num.to_ne_bytes()) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(boolf32 => fn(args, hand_mem) { - let num = hand_mem.read_fixed(args[0])? as f32; - let out = i32::from_ne_bytes(num.to_ne_bytes()) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - - cpu!(i8i64 => fn(args, hand_mem) { - let out = hand_mem.read_fixed(args[0])?; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(i16i64 => fn(args, hand_mem) { - let out = hand_mem.read_fixed(args[0])?; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(i32i64 => fn(args, hand_mem) { - let out = hand_mem.read_fixed(args[0])?; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(f64i64 => fn(args, hand_mem) { - let out = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(f32i64 => fn(args, hand_mem) { - let out = f32::from_ne_bytes((hand_mem.read_fixed(args[0])? as i32).to_ne_bytes()) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(stri64 => fn(args, hand_mem) { - let s = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[0])?)?; - let out: i64 = s.parse().unwrap(); - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(booli64 => fn(args, hand_mem) { - let out = hand_mem.read_fixed(args[0])?; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - - cpu!(i8i32 => fn(args, hand_mem) { - let out = hand_mem.read_fixed(args[0])?; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(i16i32 => fn(args, hand_mem) { - let out = hand_mem.read_fixed(args[0])?; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(i64i32 => fn(args, hand_mem) { - let out = (hand_mem.read_fixed(args[0])? as i32) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(f64i32 => fn(args, hand_mem) { - let out = (f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()) as i32) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(f32i32 => fn(args, hand_mem) { - let out = (f32::from_ne_bytes((hand_mem.read_fixed(args[0])? as i32).to_ne_bytes()) as i32) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(stri32 => fn(args, hand_mem) { - let s = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[0])?)?; - let num: i32 = s.parse().unwrap(); - let out = num as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(booli32 => fn(args, hand_mem) { - let out = hand_mem.read_fixed(args[0])?; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - - cpu!(i8i16 => fn(args, hand_mem) { - let out = hand_mem.read_fixed(args[0])?; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(i32i16 => fn(args, hand_mem) { - let out = (hand_mem.read_fixed(args[0])? as i16) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(i64i16 => fn(args, hand_mem) { - let out = (hand_mem.read_fixed(args[0])? as i16) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(f64i16 => fn(args, hand_mem) { - let out = (f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()) as i16) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(f32i16 => fn(args, hand_mem) { - let out = (f32::from_ne_bytes((hand_mem.read_fixed(args[0])? as i32).to_ne_bytes()) as i16) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(stri16 => fn(args, hand_mem) { - let s = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[0])?)?; - let num: i16 = s.parse().unwrap(); - let out = num as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(booli16 => fn(args, hand_mem) { - let out = hand_mem.read_fixed(args[0])?; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - - cpu!(i16i8 => fn(args, hand_mem) { - let out = (hand_mem.read_fixed(args[0])? as i8) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(i32i8 => fn(args, hand_mem) { - let out = (hand_mem.read_fixed(args[0])? as i8) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(i64i8 => fn(args, hand_mem) { - let out = (hand_mem.read_fixed(args[0])? as i8) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(f64i8 => fn(args, hand_mem) { - let out = (f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()) as i8) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(f32i8 => fn(args, hand_mem) { - let out = (f32::from_ne_bytes((hand_mem.read_fixed(args[0])? as i32).to_ne_bytes()) as i8) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(stri8 => fn(args, hand_mem) { - let s = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[0])?)?; - let num: i8 = s.parse().unwrap(); - let out = num as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(booli8 => fn(args, hand_mem) { - let out = (hand_mem.read_fixed(args[0])? as i8) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - - cpu!(i8bool => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i8; - let out = if a != 0 { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(i16bool => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i16; - let out = if a != 0 { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(i32bool => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i32; - let out = if a != 0 { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(i64bool => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let out = if a != 0 { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(f64bool => fn(args, hand_mem) { - let a = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()); - let out = if a != 0.0 { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(f32bool => fn(args, hand_mem) { - let a = f32::from_ne_bytes((hand_mem.read_fixed(args[0])? as i32).to_ne_bytes()); - let out = if a != 0.0 { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(strbool => fn(args, hand_mem) { - let s = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[0])?)?; - let out = if s == "true" { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - - cpu!(i8str => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i8; - let a_str = a.to_string(); - hand_mem.write_fractal(args[2], &HandlerMemory::str_to_fractal(&a_str))?; - Ok(()) - }); - cpu!(i16str => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i16; - let a_str = a.to_string(); - hand_mem.write_fractal(args[2], &HandlerMemory::str_to_fractal(&a_str))?; - Ok(()) - }); - cpu!(i32str => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i32; - let a_str = a.to_string(); - hand_mem.write_fractal(args[2], &HandlerMemory::str_to_fractal(&a_str))?; - Ok(()) - }); - cpu!(i64str => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let a_str = a.to_string(); - hand_mem.write_fractal(args[2], &HandlerMemory::str_to_fractal(&a_str))?; - Ok(()) - }); - cpu!(f64str => fn(args, hand_mem) { - let a = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()); - let a_str = a.to_string(); - hand_mem.write_fractal(args[2], &HandlerMemory::str_to_fractal(&a_str))?; - Ok(()) - }); - cpu!(f32str => fn(args, hand_mem) { - let a = f32::from_ne_bytes((hand_mem.read_fixed(args[0])? as i32).to_ne_bytes()); - let a_str = a.to_string(); - hand_mem.write_fractal(args[2], &HandlerMemory::str_to_fractal(&a_str))?; - Ok(()) - }); - cpu!(boolstr => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let a_str = if a == 1 { "true" } else { "false" }; - hand_mem.write_fractal(args[2], &HandlerMemory::str_to_fractal(&a_str))?; - Ok(()) - }); - - // Arithmetic opcodes - cpu!(addi8 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = ra.read_fixed(1)? as i8; - let b = rb.read_fixed(1)? as i8; - hand_mem.init_fractal(args[2])?; - if a > 0 && b > 0 && a > std::i8::MAX - b { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - return Ok(()); - } - if a < 0 && b < 0 && a < std::i8::MIN - b { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("underflow"))?; - return Ok(()); - } - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], (a + b) as i64)?; - Ok(()) - }); - cpu!(addi16 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = ra.read_fixed(1)? as i16; - let b = rb.read_fixed(1)? as i16; - hand_mem.init_fractal(args[2])?; - if a > 0 && b > 0 && a > std::i16::MAX - b { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - return Ok(()); - } - if a < 0 && b < 0 && a < std::i16::MIN - b { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("underflow"))?; - return Ok(()); - } - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], (a + b) as i64)?; - Ok(()) - }); - cpu!(addi32 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = ra.read_fixed(1)? as i32; - let b = rb.read_fixed(1)? as i32; - hand_mem.init_fractal(args[2])?; - if a > 0 && b > 0 && a > std::i32::MAX - b { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - return Ok(()); - } - if a < 0 && b < 0 && a < std::i32::MIN - b { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("underflow"))?; - return Ok(()); - } - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], (a + b) as i64)?; - Ok(()) - }); - cpu!(addi64 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = ra.read_fixed(1)? as i64; - let b = rb.read_fixed(1)? as i64; - hand_mem.init_fractal(args[2])?; - if a > 0 && b > 0 && a > std::i64::MAX - b { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - return Ok(()); - } - if a < 0 && b < 0 && a < std::i64::MIN - b { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("underflow"))?; - return Ok(()); - } - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], (a + b) as i64)?; - Ok(()) - }); - cpu!(addf32 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = f32::from_ne_bytes((ra.read_fixed(1)? as i32).to_ne_bytes()); - let b = f32::from_ne_bytes((rb.read_fixed(1)? as i32).to_ne_bytes()); - let out = a + b; - hand_mem.init_fractal(args[2])?; - if out == std::f32::INFINITY { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - return Ok(()); - } - if out == std::f32::NEG_INFINITY { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("underflow"))?; - return Ok(()); - } - let num = i32::from_ne_bytes(out.to_ne_bytes()) as i64; - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], num)?; - Ok(()) - }); - cpu!(addf64 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = f64::from_ne_bytes(ra.read_fixed(1)?.to_ne_bytes()); - let b = f64::from_ne_bytes(rb.read_fixed(1)?.to_ne_bytes()); - let out = a + b; - hand_mem.init_fractal(args[2])?; - if out == std::f64::INFINITY { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - return Ok(()); - } - if out == std::f64::NEG_INFINITY { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("underflow"))?; - return Ok(()); - } - let num = i64::from_ne_bytes(out.to_ne_bytes()); - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], num)?; - Ok(()) - }); - - cpu!(subi8 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = ra.read_fixed(1)? as i8; - let b = rb.read_fixed(1)? as i8; - hand_mem.init_fractal(args[2])?; - if a > 0 && b < 0 && a > std::i8::MAX + b { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - return Ok(()); - } - if a < 0 && b > 0 && a < std::i8::MIN + b { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("underflow"))?; - return Ok(()); - } - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], (a - b) as i64)?; - Ok(()) - }); - cpu!(subi16 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = ra.read_fixed(1)? as i16; - let b = rb.read_fixed(1)? as i16; - hand_mem.init_fractal(args[2])?; - if a > 0 && b < 0 && a > std::i16::MAX + b { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - return Ok(()); - } - if a < 0 && b > 0 && a < std::i16::MIN + b { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("underflow"))?; - return Ok(()); - } - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], (a - b) as i64)?; - Ok(()) - }); - cpu!(subi32 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = ra.read_fixed(1)? as i32; - let b = rb.read_fixed(1)? as i32; - hand_mem.init_fractal(args[2])?; - if a > 0 && b < 0 && a > std::i32::MAX + b { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - return Ok(()); - } - if a < 0 && b > 0 && a < std::i32::MIN + b { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("underflow"))?; - return Ok(()); - } - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], (a - b) as i64)?; - Ok(()) - }); - cpu!(subi64 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = ra.read_fixed(1)? as i64; - let b = rb.read_fixed(1)? as i64; - hand_mem.init_fractal(args[2])?; - if a > 0 && b < 0 && a > std::i64::MAX + b { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - return Ok(()); - } - if a < 0 && b > 0 && a < std::i64::MIN + b { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("underflow"))?; - return Ok(()); - } - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], a - b)?; - Ok(()) - }); - cpu!(subf32 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = f32::from_ne_bytes((ra.read_fixed(1)? as i32).to_ne_bytes()); - let b = f32::from_ne_bytes((rb.read_fixed(1)? as i32).to_ne_bytes()); - let out = a - b; - hand_mem.init_fractal(args[2])?; - if out == std::f32::INFINITY { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - return Ok(()); - } - if out == std::f32::NEG_INFINITY { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("underflow"))?; - return Ok(()); - } - let num = i32::from_ne_bytes(out.to_ne_bytes()) as i64; - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], num)?; - Ok(()) - }); - cpu!(subf64 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = f64::from_ne_bytes(ra.read_fixed(1)?.to_ne_bytes()); - let b = f64::from_ne_bytes(rb.read_fixed(1)?.to_ne_bytes()); - let out = a - b; - hand_mem.init_fractal(args[2])?; - if out == std::f64::INFINITY { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - return Ok(()); - } - if out == std::f64::NEG_INFINITY { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("underflow"))?; - return Ok(()); - } - let num = i64::from_ne_bytes(out.to_ne_bytes()); - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], num)?; - Ok(()) - }); - - cpu!(negi8 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - let a = ra.read_fixed(1)? as i8; - hand_mem.init_fractal(args[2])?; - if a == std::i8::MIN { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - } - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], (0 - a) as i64)?; - Ok(()) - }); - cpu!(negi16 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - let a = ra.read_fixed(1)? as i16; - hand_mem.init_fractal(args[2])?; - if a == std::i16::MIN { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - } - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], (0 - a) as i64)?; - Ok(()) - }); - cpu!(negi32 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - let a = ra.read_fixed(1)? as i32; - hand_mem.init_fractal(args[2])?; - if a == std::i32::MIN { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - } - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], (0 - a) as i64)?; - Ok(()) - }); - cpu!(negi64 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - let a = ra.read_fixed(1)?; - hand_mem.init_fractal(args[2])?; - if a == std::i64::MIN { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - } - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], 0 - a)?; - Ok(()) - }); - cpu!(negf32 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - let a = f32::from_ne_bytes((ra.read_fixed(1)? as i32).to_ne_bytes()); - hand_mem.init_fractal(args[2])?; - hand_mem.push_fixed(args[2], 1)?; - let out = i32::from_ne_bytes((0.0 - a).to_ne_bytes()) as i64; - hand_mem.push_fixed(args[2], out)?; - Ok(()) - }); - cpu!(negf64 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - let a = f64::from_ne_bytes(ra.read_fixed(1)?.to_ne_bytes()); - hand_mem.init_fractal(args[2])?; - hand_mem.push_fixed(args[2], 1)?; - let out = i64::from_ne_bytes((0.0 - a).to_ne_bytes()); - hand_mem.push_fixed(args[2], out)?; - Ok(()) - }); - - cpu!(absi8 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - let a = ra.read_fixed(1)? as i8; - hand_mem.init_fractal(args[2])?; - if a == std::i8::MIN { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - } - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], a.abs() as i64)?; - Ok(()) - }); - cpu!(absi16 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - let a = ra.read_fixed(1)? as i16; - hand_mem.init_fractal(args[2])?; - if a == std::i16::MIN { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - } - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], a.abs() as i64)?; - Ok(()) - }); - cpu!(absi32 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - let a = ra.read_fixed(1)? as i32; - hand_mem.init_fractal(args[2])?; - if a == std::i32::MIN { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - } - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], a.abs() as i64)?; - Ok(()) - }); - cpu!(absi64 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - let a = ra.read_fixed(1)?; - hand_mem.init_fractal(args[2])?; - if a == std::i64::MIN { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - } - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], a.abs())?; - Ok(()) - }); - cpu!(absf32 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - let a = f32::from_ne_bytes((ra.read_fixed(1)? as i32).to_ne_bytes()); - hand_mem.init_fractal(args[2])?; - hand_mem.push_fixed(args[2], 1)?; - let out = i32::from_ne_bytes(a.abs().to_ne_bytes()) as i64; - hand_mem.push_fixed(args[2], out)?; - Ok(()) - }); - cpu!(absf64 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - let a = f64::from_ne_bytes(ra.read_fixed(1)?.to_ne_bytes()); - hand_mem.init_fractal(args[2])?; - hand_mem.push_fixed(args[2], 1)?; - let out = i64::from_ne_bytes(a.abs().to_ne_bytes()); - hand_mem.push_fixed(args[2], out)?; - Ok(()) - }); - - cpu!(muli8 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = ra.read_fixed(1)? as i8; - let b = rb.read_fixed(1)? as i8; - hand_mem.init_fractal(args[2])?; - if a > 0 && b > 0 && (a as f64) > (std::i8::MAX as f64) / (b as f64) { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - return Ok(()); - } - if a < 0 && b < 0 && (a as f64) < (std::i8::MIN as f64) / (b as f64) { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("underflow"))?; - return Ok(()); - } - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], (a * b) as i64)?; - Ok(()) - }); - cpu!(muli16 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = ra.read_fixed(1)? as i16; - let b = rb.read_fixed(1)? as i16; - hand_mem.init_fractal(args[2])?; - if a > 0 && b > 0 && (a as f64) > (std::i16::MAX as f64) / (b as f64) { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - return Ok(()); - } - if a < 0 && b < 0 && (a as f64) < (std::i16::MIN as f64) / (b as f64) { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("underflow"))?; - return Ok(()); - } - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], (a * b) as i64)?; - Ok(()) - }); - cpu!(muli32 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = ra.read_fixed(1)? as i32; - let b = rb.read_fixed(1)? as i32; - hand_mem.init_fractal(args[2])?; - if a > 0 && b > 0 && (a as f64) > (std::i32::MAX as f64) / (b as f64) { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - return Ok(()); - } - if a < 0 && b < 0 && (a as f64) < (std::i32::MIN as f64) / (b as f64) { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("underflow"))?; - return Ok(()); - } - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], (a * b) as i64)?; - Ok(()) - }); - cpu!(muli64 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = ra.read_fixed(1)? as i64; - let b = rb.read_fixed(1)? as i64; - hand_mem.init_fractal(args[2])?; - if a > 0 && b > 0 && (a as f64) > (std::i64::MAX as f64) / (b as f64) { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - return Ok(()); - } - if a < 0 && b < 0 && (a as f64) < (std::i64::MIN as f64) / (b as f64) { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("underflow"))?; - return Ok(()); - } - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], a * b)?; - Ok(()) - }); - cpu!(mulf32 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = f32::from_ne_bytes((ra.read_fixed(1)? as i32).to_ne_bytes()); - let b = f32::from_ne_bytes((rb.read_fixed(1)? as i32).to_ne_bytes()); - let out = a * b; - hand_mem.init_fractal(args[2])?; - if out == std::f32::INFINITY { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - return Ok(()); - } - if out == std::f32::NEG_INFINITY { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("underflow"))?; - return Ok(()); - } - let num = i32::from_ne_bytes(out.to_ne_bytes()) as i64; - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], num)?; - Ok(()) - }); - cpu!(mulf64 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = f64::from_ne_bytes(ra.read_fixed(1)?.to_ne_bytes()); - let b = f64::from_ne_bytes(rb.read_fixed(1)?.to_ne_bytes()); - let out = a * b; - hand_mem.init_fractal(args[2])?; - if out == std::f64::INFINITY { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - return Ok(()); - } - if out == std::f64::NEG_INFINITY { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("underflow"))?; - return Ok(()); - } - let num = i64::from_ne_bytes(out.to_ne_bytes()); - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], num)?; - Ok(()) - }); - - cpu!(divi8 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = ra.read_fixed(1)? as i8; - let b = rb.read_fixed(1)? as i8; - hand_mem.init_fractal(args[2])?; - if b == 0 { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("divide-by-zero"))?; - return Ok(()); - } - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], (a / b) as i64)?; - Ok(()) - }); - cpu!(divi16 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = ra.read_fixed(1)? as i16; - let b = rb.read_fixed(1)? as i16; - hand_mem.init_fractal(args[2])?; - if b == 0 { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("divide-by-zero"))?; - return Ok(()); - } - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], (a / b) as i64)?; - Ok(()) - }); - cpu!(divi32 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = ra.read_fixed(1)? as i32; - let b = rb.read_fixed(1)? as i32; - hand_mem.init_fractal(args[2])?; - if b == 0 { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("divide-by-zero"))?; - return Ok(()); - } - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], (a / b) as i64)?; - Ok(()) - }); - cpu!(divi64 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = ra.read_fixed(1)? as i64; - let b = rb.read_fixed(1)? as i64; - hand_mem.init_fractal(args[2])?; - if b == 0 { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("divide-by-zero"))?; - return Ok(()); - } - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], a / b)?; - Ok(()) - }); - cpu!(divf32 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = f32::from_ne_bytes((ra.read_fixed(1)? as i32).to_ne_bytes()); - let b = f32::from_ne_bytes((rb.read_fixed(1)? as i32).to_ne_bytes()); - hand_mem.init_fractal(args[2])?; - if b == 0.0 { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("divide-by-zero"))?; - return Ok(()); - } - let out = a / b; - if out == std::f32::INFINITY { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - return Ok(()); - } - if out == std::f32::NEG_INFINITY { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("underflow"))?; - return Ok(()); - } - let num = i32::from_ne_bytes(out.to_ne_bytes()) as i64; - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], num)?; - Ok(()) - }); - cpu!(divf64 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = f64::from_ne_bytes(ra.read_fixed(1)?.to_ne_bytes()); - let b = f64::from_ne_bytes(rb.read_fixed(1)?.to_ne_bytes()); - hand_mem.init_fractal(args[2])?; - if b == 0.0 { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("divide-by-zero"))?; - return Ok(()); - } - let out = a / b; - if out == std::f64::INFINITY { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - return Ok(()); - } - if out == std::f64::NEG_INFINITY { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("underflow"))?; - return Ok(()); - } - let num = i64::from_ne_bytes(out.to_ne_bytes()); - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], num)?; - Ok(()) - }); - - cpu!(modi8 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i8; - let b = hand_mem.read_fixed(args[1])? as i8; - let out = (a % b) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(modi16 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i16; - let b = hand_mem.read_fixed(args[1])? as i16; - let out = (a % b) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(modi32 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i32; - let b = hand_mem.read_fixed(args[1])? as i32; - let out = (a % b) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(modi64 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let b = hand_mem.read_fixed(args[1])?; - let out = a % b; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - - cpu!(powi8 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = ra.read_fixed(1)? as i8; - let b = rb.read_fixed(1)? as i8; - hand_mem.init_fractal(args[2])?; - if a > 0 && b > 1 && (a as f64) > f64::powf(std::i8::MAX as f64, 1.0 / (b as f64)) { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - return Ok(()); - } - if a < 0 && b > 1 && (a as f64) < f64::powf(std::i8::MIN as f64, 1.0 / (b as f64)) { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("underflow"))?; - return Ok(()); - } - hand_mem.push_fixed(args[2], 1)?; - let out = if b < 0 { 0i64 } else { i8::pow(a, b as u32) as i64 }; - hand_mem.push_fixed(args[2], out)?; - Ok(()) - }); - cpu!(powi16 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = ra.read_fixed(1)? as i16; - let b = rb.read_fixed(1)? as i16; - hand_mem.init_fractal(args[2])?; - if a > 0 && b > 1 && (a as f64) > f64::powf(std::i16::MAX as f64, 1.0 / (b as f64)) { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - return Ok(()); - } - if a < 0 && b > 1 && (a as f64) < f64::powf(std::i16::MIN as f64, 1.0 / (b as f64)) { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("underflow"))?; - return Ok(()); - } - hand_mem.push_fixed(args[2], 1)?; - let out = if b < 0 { 0i64 } else { i16::pow(a, b as u32) as i64 }; - hand_mem.push_fixed(args[2], out)?; - Ok(()) - }); - cpu!(powi32 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = ra.read_fixed(1)? as i32; - let b = rb.read_fixed(1)? as i32; - hand_mem.init_fractal(args[2])?; - if a > 0 && b > 1 && (a as f64) > f64::powf(std::i32::MAX as f64, 1.0 / (b as f64)) { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - return Ok(()); - } - if a < 0 && b > 1 && (a as f64) < f64::powf(std::i32::MIN as f64, 1.0 / (b as f64)) { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("underflow"))?; - return Ok(()); - } - hand_mem.push_fixed(args[2], 1)?; - let out = if b < 0 { 0i64 } else { i32::pow(a, b as u32) as i64 }; - hand_mem.push_fixed(args[2], out)?; - Ok(()) - }); - cpu!(powi64 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = ra.read_fixed(1)? as i64; - let b = rb.read_fixed(1)? as i64; - hand_mem.init_fractal(args[2])?; - if a > 0 && b > 1 && (a as f64) > f64::powf(std::i64::MAX as f64, 1.0 / (b as f64)) { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - return Ok(()); - } - if a < 0 && b > 1 && (a as f64) < f64::powf(std::i64::MIN as f64, 1.0 / (b as f64)) { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("underflow"))?; - return Ok(()); - } - hand_mem.push_fixed(args[2], 1)?; - let out = if b < 0 { 0i64 } else { i64::pow(a, b as u32) as i64 }; - hand_mem.push_fixed(args[2], out)?; - Ok(()) - }); - cpu!(powf32 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = f32::from_ne_bytes((ra.read_fixed(1)? as i32).to_ne_bytes()); - let b = f32::from_ne_bytes((rb.read_fixed(1)? as i32).to_ne_bytes()); - let out = f32::powf(a, b); - hand_mem.init_fractal(args[2])?; - if out == std::f32::INFINITY { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - return Ok(()); - } - if out == std::f32::NEG_INFINITY { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("underflow"))?; - return Ok(()); - } - let num = i32::from_ne_bytes(out.to_ne_bytes()) as i64; - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], num)?; - Ok(()) - }); - cpu!(powf64 => fn(args, hand_mem) { - let ra = hand_mem.read_fractal(args[0])?; - let rb = hand_mem.read_fractal(args[1])?; - if ra.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &ra)?; - return Ok(()); - } - if rb.read_fixed(0)? == 0 { - hand_mem.write_fractal(args[2], &rb)?; - return Ok(()); - } - let a = f64::from_ne_bytes(ra.read_fixed(1)?.to_ne_bytes()); - let b = f64::from_ne_bytes(rb.read_fixed(1)?.to_ne_bytes()); - let out = f64::powf(a, b); - hand_mem.init_fractal(args[2])?; - if out == std::f64::INFINITY { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("overflow"))?; - return Ok(()); - } - if out == std::f64::NEG_INFINITY { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("underflow"))?; - return Ok(()); - } - let num = i64::from_ne_bytes(out.to_ne_bytes()); - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], num)?; - Ok(()) - }); - - cpu!(sqrtf32 => fn(args, hand_mem) { - let a = f32::from_ne_bytes((hand_mem.read_fixed(args[0])? as i32).to_ne_bytes()); - let out = i32::from_ne_bytes(f32::sqrt(a).to_ne_bytes()) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(sqrtf64 => fn(args, hand_mem) { - let a = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()); - let out = i64::from_ne_bytes(f64::sqrt(a).to_ne_bytes()); - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - - // Saturating Arithmetic opcodes - cpu!(saddi8 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i8; - let b = hand_mem.read_fixed(args[1])? as i8; - hand_mem.write_fixed(args[2], a.saturating_add(b) as i64)?; - Ok(()) - }); - cpu!(saddi16 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i16; - let b = hand_mem.read_fixed(args[1])? as i16; - hand_mem.write_fixed(args[2], a.saturating_add(b) as i64)?; - Ok(()) - }); - cpu!(saddi32 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i32; - let b = hand_mem.read_fixed(args[1])? as i32; - hand_mem.write_fixed(args[2], a.saturating_add(b) as i64)?; - Ok(()) - }); - cpu!(saddi64 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let b = hand_mem.read_fixed(args[1])?; - hand_mem.write_fixed(args[2], a.saturating_add(b))?; - Ok(()) - }); - cpu!(saddf32 => fn(args, hand_mem) { - let a = f32::from_ne_bytes((hand_mem.read_fixed(args[0])? as i32).to_ne_bytes()); - let b = f32::from_ne_bytes((hand_mem.read_fixed(args[1])? as i32).to_ne_bytes()); - let out = a + b; - let num = i32::from_ne_bytes(out.to_ne_bytes()) as i64; - hand_mem.write_fixed(args[2], num)?; - Ok(()) - }); - cpu!(saddf64 => fn(args, hand_mem) { - let a = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()); - let b = f64::from_ne_bytes(hand_mem.read_fixed(args[1])?.to_ne_bytes()); - let out = a + b; - let num = i64::from_ne_bytes(out.to_ne_bytes()); - hand_mem.write_fixed(args[2], num)?; - Ok(()) - }); - - cpu!(ssubi8 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i8; - let b = hand_mem.read_fixed(args[1])? as i8; - hand_mem.write_fixed(args[2], a.saturating_sub(b) as i64)?; - Ok(()) - }); - cpu!(ssubi16 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i16; - let b = hand_mem.read_fixed(args[1])? as i16; - hand_mem.write_fixed(args[2], a.saturating_sub(b) as i64)?; - Ok(()) - }); - cpu!(ssubi32 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i32; - let b = hand_mem.read_fixed(args[1])? as i32; - hand_mem.write_fixed(args[2], a.saturating_sub(b) as i64)?; - Ok(()) - }); - cpu!(ssubi64 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let b = hand_mem.read_fixed(args[1])?; - hand_mem.write_fixed(args[2], a.saturating_sub(b))?; - Ok(()) - }); - cpu!(ssubf32 => fn(args, hand_mem) { - let a = f32::from_ne_bytes((hand_mem.read_fixed(args[0])? as i32).to_ne_bytes()); - let b = f32::from_ne_bytes((hand_mem.read_fixed(args[1])? as i32).to_ne_bytes()); - let out = a - b; - let num = i32::from_ne_bytes(out.to_ne_bytes()) as i64; - hand_mem.write_fixed(args[2], num)?; - Ok(()) - }); - cpu!(ssubf64 => fn(args, hand_mem) { - let a = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()); - let b = f64::from_ne_bytes(hand_mem.read_fixed(args[1])?.to_ne_bytes()); - let out = a - b; - let num = i64::from_ne_bytes(out.to_ne_bytes()); - hand_mem.write_fixed(args[2], num)?; - Ok(()) - }); - - cpu!(snegi8 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i8; - let out = a.saturating_neg() as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(snegi16 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i16; - let out = a.saturating_neg() as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(snegi32 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i32; - let out = a.saturating_neg() as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(snegi64 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let out = a.saturating_neg(); - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(snegf32 => fn(args, hand_mem) { - let a = f32::from_ne_bytes((hand_mem.read_fixed(args[0])? as i32).to_ne_bytes()); - let out = i32::from_ne_bytes((0.0 - a).to_ne_bytes()) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(snegf64 => fn(args, hand_mem) { - let a = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()); - let out = i64::from_ne_bytes((0.0 - a).to_ne_bytes()); - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - - cpu!(sabsi8 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i8; - let out = a.saturating_abs() as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(sabsi16 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i16; - let out = a.saturating_abs() as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(sabsi32 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i32; - let out = a.saturating_abs() as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(sabsi64 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let out = a.saturating_abs() as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(sabsf32 => fn(args, hand_mem) { - let a = f32::from_ne_bytes((hand_mem.read_fixed(args[0])? as i32).to_ne_bytes()); - let out = i32::from_ne_bytes(a.abs().to_ne_bytes()) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(sabsf64 => fn(args, hand_mem) { - let a = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()); - let out = i64::from_ne_bytes(a.abs().to_ne_bytes()); - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - - cpu!(smuli8 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i8; - let b = hand_mem.read_fixed(args[1])? as i8; - hand_mem.write_fixed(args[2], a.saturating_mul(b) as i64)?; - Ok(()) - }); - cpu!(smuli16 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i16; - let b = hand_mem.read_fixed(args[1])? as i16; - hand_mem.write_fixed(args[2], a.saturating_mul(b) as i64)?; - Ok(()) - }); - cpu!(smuli32 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i32; - let b = hand_mem.read_fixed(args[1])? as i32; - hand_mem.write_fixed(args[2], a.saturating_mul(b) as i64)?; - Ok(()) - }); - cpu!(smuli64 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let b = hand_mem.read_fixed(args[1])?; - hand_mem.write_fixed(args[2], a.saturating_mul(b))?; - Ok(()) - }); - cpu!(smulf32 => fn(args, hand_mem) { - let a = f32::from_ne_bytes((hand_mem.read_fixed(args[0])? as i32).to_ne_bytes()); - let b = f32::from_ne_bytes((hand_mem.read_fixed(args[1])? as i32).to_ne_bytes()); - let out = a * b; - let num = i32::from_ne_bytes(out.to_ne_bytes()) as i64; - hand_mem.write_fixed(args[2], num)?; - Ok(()) - }); - cpu!(smulf64 => fn(args, hand_mem) { - let a = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()); - let b = f64::from_ne_bytes(hand_mem.read_fixed(args[1])?.to_ne_bytes()); - let out = a * b; - let num = i64::from_ne_bytes(out.to_ne_bytes()); - hand_mem.write_fixed(args[2], num)?; - Ok(()) - }); - - cpu!(sdivi8 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i8; - let b = hand_mem.read_fixed(args[1])? as i8; - if b == 0 { - let out = if a > 0 { std::i8::MAX as i64 } else { std::i8::MIN as i64 }; - hand_mem.write_fixed(args[2], out)?; - return Ok(()); - } - hand_mem.write_fixed(args[2], (a / b) as i64)?; - Ok(()) - }); - cpu!(sdivi16 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i16; - let b = hand_mem.read_fixed(args[1])? as i16; - if b == 0 { - let out = if a > 0 { std::i16::MAX as i64 } else { std::i16::MIN as i64 }; - hand_mem.write_fixed(args[2], out)?; - return Ok(()); - } - hand_mem.write_fixed(args[2], (a / b) as i64)?; - Ok(()) - }); - cpu!(sdivi32 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i32; - let b = hand_mem.read_fixed(args[1])? as i32; - if b == 0 { - let out = if a > 0 { std::i32::MAX as i64 } else { std::i32::MIN as i64 }; - hand_mem.write_fixed(args[2], out)?; - return Ok(()); - } - hand_mem.write_fixed(args[2], (a / b) as i64)?; - Ok(()) - }); - cpu!(sdivi64 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let b = hand_mem.read_fixed(args[1])?; - if b == 0 { - let out = if a > 0 { std::i64::MAX } else { std::i64::MIN }; - hand_mem.write_fixed(args[2], out)?; - return Ok(()); - } - hand_mem.write_fixed(args[2], a / b)?; - Ok(()) - }); - cpu!(sdivf32 => fn(args, hand_mem) { - let a = f32::from_ne_bytes((hand_mem.read_fixed(args[0])? as i32).to_ne_bytes()); - let b = f32::from_ne_bytes((hand_mem.read_fixed(args[1])? as i32).to_ne_bytes()); - let out = a / b; - let num = i32::from_ne_bytes(out.to_ne_bytes()) as i64; - hand_mem.write_fixed(args[2], num)?; - Ok(()) - }); - cpu!(sdivf64 => fn(args, hand_mem) { - let a = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()); - let b = f64::from_ne_bytes(hand_mem.read_fixed(args[1])?.to_ne_bytes()); - let out = a / b; - let num = i64::from_ne_bytes(out.to_ne_bytes()); - hand_mem.write_fixed(args[2], num)?; - Ok(()) - }); - - cpu!(spowi8 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i8; - let b = hand_mem.read_fixed(args[1])? as i8; - let out = if b < 0 { 0i64 } else { i8::saturating_pow(a, b as u32) as i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(spowi16 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i16; - let b = hand_mem.read_fixed(args[1])? as i16; - let out = if b < 0 { 0i64 } else { i16::saturating_pow(a, b as u32) as i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(spowi32 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i32; - let b = hand_mem.read_fixed(args[1])? as i32; - let out = if b < 0 { 0i64 } else { i32::saturating_pow(a, b as u32) as i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(spowi64 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let b = hand_mem.read_fixed(args[1])?; - let out = if b < 0 { 0i64 } else { i64::saturating_pow(a, b as u32) as i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(spowf32 => fn(args, hand_mem) { - let a = f32::from_ne_bytes((hand_mem.read_fixed(args[0])? as i32).to_ne_bytes()); - let b = f32::from_ne_bytes((hand_mem.read_fixed(args[1])? as i32).to_ne_bytes()); - let out = f32::powf(a, b); - let num = i32::from_ne_bytes(out.to_ne_bytes()) as i64; - hand_mem.write_fixed(args[2], num)?; - Ok(()) - }); - cpu!(spowf64 => fn(args, hand_mem) { - let a = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()); - let b = f64::from_ne_bytes(hand_mem.read_fixed(args[1])?.to_ne_bytes()); - let out = f64::powf(a, b); - let num = i64::from_ne_bytes(out.to_ne_bytes()); - hand_mem.write_fixed(args[2], num)?; - Ok(()) - }); - - // Boolean and bitwise opcodes - cpu!(andi8 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i8; - let b = hand_mem.read_fixed(args[1])? as i8; - let out = (a & b) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(andi16 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i16; - let b = hand_mem.read_fixed(args[1])? as i16; - let out = (a & b) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(andi32 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i32; - let b = hand_mem.read_fixed(args[1])? as i32; - let out = (a & b) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(andi64 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let b = hand_mem.read_fixed(args[1])?; - let out = a & b; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(andbool => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let b = hand_mem.read_fixed(args[1])?; - let a_bool = if a == 1 { true } else { false }; - let b_bool = if b == 1 { true } else { false }; - let out = if a_bool & b_bool { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - - cpu!(ori8 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i8; - let b = hand_mem.read_fixed(args[1])? as i8; - let out = (a | b) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(ori16 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i16; - let b = hand_mem.read_fixed(args[1])? as i16; - let out = (a | b) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(ori32 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i32; - let b = hand_mem.read_fixed(args[1])? as i32; - let out = (a | b) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(ori64 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let b = hand_mem.read_fixed(args[1])?; - let out = a | b; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(orbool => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let b = hand_mem.read_fixed(args[1])?; - let a_bool = if a == 1 { true } else { false }; - let b_bool = if b == 1 { true } else { false }; - let out = if a_bool | b_bool { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - - cpu!(xori8 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i8; - let b = hand_mem.read_fixed(args[1])? as i8; - let out = (a ^ b) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(xori16 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i16; - let b = hand_mem.read_fixed(args[1])? as i16; - let out = (a ^ b) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(xori32 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i32; - let b = hand_mem.read_fixed(args[1])? as i32; - let out = (a ^ b) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(xori64 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let b = hand_mem.read_fixed(args[1])?; - let out = a ^ b; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(xorbool => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let b = hand_mem.read_fixed(args[1])?; - let a_bool = if a == 1 { true } else { false }; - let b_bool = if b == 1 { true } else { false }; - let out = if a_bool ^ b_bool { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - - cpu!(noti8 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i8; - let out = !a as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(noti16 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i16; - let out = !a as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(noti32 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i32; - let out = !a as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(noti64 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let out = !a; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(notbool => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let a_bool = if a == 1 { true } else { false }; - let out = if !a_bool { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - - cpu!(nandi8 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i8; - let b = hand_mem.read_fixed(args[1])? as i8; - let out = !(a & b) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(nandi16 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i16; - let b = hand_mem.read_fixed(args[1])? as i16; - let out = !(a & b) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(nandi32 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i32; - let b = hand_mem.read_fixed(args[1])? as i32; - let out = !(a & b) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(nandi64 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let b = hand_mem.read_fixed(args[1])?; - let out = !(a & b); - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(nandboo => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let b = hand_mem.read_fixed(args[1])?; - let a_bool = if a == 1 { true } else { false }; - let b_bool = if b == 1 { true } else { false }; - let out = if !(a_bool & b_bool) { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - - cpu!(nori8 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i8; - let b = hand_mem.read_fixed(args[1])? as i8; - let out = !(a | b) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(nori16 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i16; - let b = hand_mem.read_fixed(args[1])? as i16; - let out = !(a | b) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(nori32 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i32; - let b = hand_mem.read_fixed(args[1])? as i32; - let out = !(a | b) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(nori64 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let b = hand_mem.read_fixed(args[1])?; - let out = !(a | b); - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(norbool => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let b = hand_mem.read_fixed(args[1])?; - let a_bool = if a == 1 { true } else { false }; - let b_bool = if b == 1 { true } else { false }; - let out = if !(a_bool | b_bool) { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - - cpu!(xnori8 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i8; - let b = hand_mem.read_fixed(args[1])? as i8; - let out = !(a ^ b) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(xnori16 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i16; - let b = hand_mem.read_fixed(args[1])? as i16; - let out = !(a ^ b) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(xnori32 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i32; - let b = hand_mem.read_fixed(args[1])? as i32; - let out = !(a ^ b) as i64; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(xnori64 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let b = hand_mem.read_fixed(args[1])?; - let out = !(a ^ b); - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(xnorboo => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let b = hand_mem.read_fixed(args[1])?; - let a_bool = if a == 1 { true } else { false }; - let b_bool = if b == 1 { true } else { false }; - let out = if !(a_bool ^ b_bool) { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - - // Equality and order opcodes - cpu!(eqi8 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i8; - let b = hand_mem.read_fixed(args[1])? as i8; - let out = if a == b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(eqi16 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i16; - let b = hand_mem.read_fixed(args[1])? as i16; - let out = if a == b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(eqi32 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i32; - let b = hand_mem.read_fixed(args[1])? as i32; - let out = if a == b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(eqi64 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let b = hand_mem.read_fixed(args[1])?; - let out = if a == b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(eqf32 => fn(args, hand_mem) { - let a = f32::from_ne_bytes((hand_mem.read_fixed(args[0])? as i32).to_ne_bytes()); - let b = f32::from_ne_bytes((hand_mem.read_fixed(args[1])? as i32).to_ne_bytes()); - let out = if a == b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(eqf64 => fn(args, hand_mem) { - let a = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()); - let b = f64::from_ne_bytes(hand_mem.read_fixed(args[1])?.to_ne_bytes()); - let out = if a == b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(eqstr => fn(args, hand_mem) { - let a_pascal_string = hand_mem.read_fractal(args[0])?; - let b_pascal_string = hand_mem.read_fractal(args[1])?; - let out = if args[0] < 0 || args[1] < 0 { - // Special path for global memory stored strings, they aren't represented the same way - let a_str = HandlerMemory::fractal_to_string(a_pascal_string)?; - let b_str = HandlerMemory::fractal_to_string(b_pascal_string)?; - if a_str == b_str { 1i64 } else { 0i64 } - } else if a_pascal_string == b_pascal_string { - 1i64 - } else { - 0i64 - }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(eqbool => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i8; - let b = hand_mem.read_fixed(args[1])? as i8; - let out = if a == b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - - cpu!(neqi8 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i8; - let b = hand_mem.read_fixed(args[1])? as i8; - let out = if a != b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(neqi16 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i16; - let b = hand_mem.read_fixed(args[1])? as i16; - let out = if a != b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(neqi32 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i32; - let b = hand_mem.read_fixed(args[1])? as i32; - let out = if a != b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(neqi64 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let b = hand_mem.read_fixed(args[1])?; - let out = if a != b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(neqf32 => fn(args, hand_mem) { - let a = f32::from_ne_bytes((hand_mem.read_fixed(args[0])? as i32).to_ne_bytes()); - let b = f32::from_ne_bytes((hand_mem.read_fixed(args[1])? as i32).to_ne_bytes()); - let out = if a != b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(neqf64 => fn(args, hand_mem) { - let a = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()); - let b = f64::from_ne_bytes(hand_mem.read_fixed(args[1])?.to_ne_bytes()); - let out = if a != b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(neqstr => fn(args, hand_mem) { - let a_pascal_string = hand_mem.read_fractal(args[0])?; - let b_pascal_string = hand_mem.read_fractal(args[1])?; - let out = if a_pascal_string != b_pascal_string { - 1i64 - } else { - 0i64 - }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(neqbool => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i8; - let b = hand_mem.read_fixed(args[1])? as i8; - let out = if a != b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - - cpu!(lti8 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i8; - let b = hand_mem.read_fixed(args[1])? as i8; - let out = if a < b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(lti16 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i16; - let b = hand_mem.read_fixed(args[1])? as i16; - let out = if a < b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(lti32 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i32; - let b = hand_mem.read_fixed(args[1])? as i32; - let out = if a < b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(lti64 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let b = hand_mem.read_fixed(args[1])?; - let out = if a < b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(ltf32 => fn(args, hand_mem) { - let a = f32::from_ne_bytes((hand_mem.read_fixed(args[0])? as i32).to_ne_bytes()); - let b = f32::from_ne_bytes((hand_mem.read_fixed(args[1])? as i32).to_ne_bytes()); - let out = if a < b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(ltf64 => fn(args, hand_mem) { - let a = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()); - let b = f64::from_ne_bytes(hand_mem.read_fixed(args[1])?.to_ne_bytes()); - let out = if a < b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(ltstr => fn(args, hand_mem) { - let a_str = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[0])?)?; - let b_str = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[1])?)?; - let out = if a_str < b_str { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - - cpu!(ltei8 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i8; - let b = hand_mem.read_fixed(args[1])? as i8; - let out = if a <= b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(ltei16 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i16; - let b = hand_mem.read_fixed(args[1])? as i16; - let out = if a <= b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(ltei32 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i32; - let b = hand_mem.read_fixed(args[1])? as i32; - let out = if a <= b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(ltei64 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let b = hand_mem.read_fixed(args[1])?; - let out = if a <= b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(ltef32 => fn(args, hand_mem) { - let a = f32::from_ne_bytes((hand_mem.read_fixed(args[0])? as i32).to_ne_bytes()); - let b = f32::from_ne_bytes((hand_mem.read_fixed(args[1])? as i32).to_ne_bytes()); - let out = if a <= b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(ltef64 => fn(args, hand_mem) { - let a = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()); - let b = f64::from_ne_bytes(hand_mem.read_fixed(args[1])?.to_ne_bytes()); - let out = if a <= b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(ltestr => fn(args, hand_mem) { - let a_str = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[0])?)?; - let b_str = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[1])?)?; - let out = if a_str <= b_str { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - - cpu!(gti8 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i8; - let b = hand_mem.read_fixed(args[1])? as i8; - let out = if a > b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(gti16 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i16; - let b = hand_mem.read_fixed(args[1])? as i16; - let out = if a > b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(gti32 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i32; - let b = hand_mem.read_fixed(args[1])? as i32; - let out = if a > b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(gti64 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let b = hand_mem.read_fixed(args[1])?; - let out = if a > b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(gtf32 => fn(args, hand_mem) { - let a = f32::from_ne_bytes((hand_mem.read_fixed(args[0])? as i32).to_ne_bytes()); - let b = f32::from_ne_bytes((hand_mem.read_fixed(args[1])? as i32).to_ne_bytes()); - let out = if a > b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(gtf64 => fn(args, hand_mem) { - let a = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()); - let b = f64::from_ne_bytes(hand_mem.read_fixed(args[1])?.to_ne_bytes()); - let out = if a > b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(gtstr => fn(args, hand_mem) { - let a_str = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[0])?)?; - let b_str = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[1])?)?; - let out = if a_str > b_str { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - - cpu!(gtei8 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i8; - let b = hand_mem.read_fixed(args[1])? as i8; - let out = if a >= b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(gtei16 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i16; - let b = hand_mem.read_fixed(args[1])? as i16; - let out = if a >= b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(gtei32 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])? as i32; - let b = hand_mem.read_fixed(args[1])? as i32; - let out = if a >= b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(gtei64 => fn(args, hand_mem) { - let a = hand_mem.read_fixed(args[0])?; - let b = hand_mem.read_fixed(args[1])?; - let out = if a >= b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(gtef32 => fn(args, hand_mem) { - let a = f32::from_ne_bytes((hand_mem.read_fixed(args[0])? as i32).to_ne_bytes()); - let b = f32::from_ne_bytes((hand_mem.read_fixed(args[1])? as i32).to_ne_bytes()); - let out = if a >= b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(gtef64 => fn(args, hand_mem) { - let a = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()); - let b = f64::from_ne_bytes(hand_mem.read_fixed(args[1])?.to_ne_bytes()); - let out = if a >= b { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(gtestr => fn(args, hand_mem) { - let a_str = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[0])?)?; - let b_str = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[1])?)?; - let out = if a_str >= b_str { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - - // String opcodes - cpu!(catstr => fn(args, hand_mem) { - let a_str = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[0])?)?; - let b_str = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[1])?)?; - let out_str = format!("{}{}", a_str, b_str); - hand_mem.write_fractal(args[2], &HandlerMemory::str_to_fractal(&out_str))?; - Ok(()) - }); - cpu!(split => fn(args, hand_mem) { - let a_str = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[0])?)?; - let b_str = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[1])?)?; - let out_hms = a_str.split(&b_str).map(|out_str| HandlerMemory::str_to_fractal(&out_str)); - hand_mem.init_fractal(args[2])?; - for out in out_hms { - hand_mem.push_fractal(args[2], out)?; - } - Ok(()) - }); - cpu!(repstr => fn(args, hand_mem) { - let a_str = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[0])?)?; - let n = hand_mem.read_fixed(args[1])?; - let out_str = a_str.repeat(n as usize); - hand_mem.write_fractal(args[2], &HandlerMemory::str_to_fractal(&out_str))?; - Ok(()) - }); - cpu!(matches => fn(args, hand_mem) { - let a_str = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[0])?)?; - let b_str = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[1])?)?; - let b_regex = Regex::new(&b_str).map_err(|regex_err| VMError::Other(format!("Bad regex construction: {}", regex_err)))?; - let out = if b_regex.is_match(&a_str) { 1i64 } else { 0i64 }; - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(indstr => fn(args, hand_mem) { - let a_str = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[0])?)?; - let b_str = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[1])?)?; - let out_option = a_str.find(&b_str); - hand_mem.init_fractal(args[2])?; - match out_option { - Some(out) => { - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_fixed(args[2], out as i64)?; - }, - None => { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.write_fractal(args[2], &HandlerMemory::str_to_fractal("substring not found"))?; - }, - } - Ok(()) - }); - cpu!(lenstr => fn(args, hand_mem) { - let pascal_string = hand_mem.read_fractal(args[0])?; - let val = pascal_string.read_fixed(0)?; - hand_mem.write_fixed(args[2], val)?; - Ok(()) - }); - cpu!(trim => fn(args, hand_mem) { - let in_str = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[0])?)?; - let out_str = in_str.trim(); - hand_mem.write_fractal(args[2], &HandlerMemory::str_to_fractal(&out_str))?; - Ok(()) - }); - - // Array opcodes - cpu!(register => fn(args, hand_mem) { - // args[2] is the register address - // args[0] point to an array in memory - // args[1] is the address within the array to register - let inner_addr = hand_mem.read_fixed(args[1])?; - hand_mem.register_out(args[0], inner_addr as usize, args[2])?; - Ok(()) - }); - cpu!(copyfrom => fn(args, hand_mem) { - // args = [arr_addr, arr_idx_addr, outer_addr] - // copy data from outer_addr to inner_addr of the array in reg_addr - // The array index instead of inner address is provided to keep interaction with the js-runtime - // sane. - let inner_addr = hand_mem.read_fixed(args[1])?; - hand_mem.register_out(args[0], inner_addr as usize, args[2])?; - Ok(()) - }); - cpu!(copytof => fn(args, hand_mem) { - // args = [arr_addr, inner_addr, outer_addr] - // copy data from outer_addr to inner_addr in arr_addr - let inner = hand_mem.read_fixed(args[1])?; - hand_mem.register_in(args[2], args[0], inner)?; - Ok(()) - }); - cpu!(copytov => fn(args, hand_mem) { - // args = [arr_addr, inner_addr, outer_addr] - // copy data from outer_addr to inner_addr in arr_addr - let inner = hand_mem.read_fixed(args[1])?; - hand_mem.register_in(args[2], args[0], inner)?; - Ok(()) - }); - cpu!(lenarr => fn(args, hand_mem) { - let arr = hand_mem.read_fractal(args[0])?; - let len = arr.len() as i64; - hand_mem.write_fixed(args[2], len)?; - Ok(()) - }); - cpu!(indarrf => fn(args, hand_mem) { - let val = hand_mem.read_fixed(args[1])?; - let mem = hand_mem.read_fractal(args[0])?; - let len = mem.len(); - let mut idx = -1i64; - for i in 0..len { - let check = mem.read_fixed(i)?; - if val == check { - idx = i as i64; - break; - } - } - hand_mem.init_fractal(args[2])?; - if idx == -1i64 { - hand_mem.push_fixed(args[2], 0i64)?; - hand_mem.write_fractal(args[2], &HandlerMemory::str_to_fractal("element not found"))?; - } else { - hand_mem.push_fixed(args[2], 1i64)?; - hand_mem.push_fixed(args[2], idx)?; - } - Ok(()) - }); - cpu!(indarrv => fn(args, hand_mem) { - let val = hand_mem.read_fractal(args[1])?; - let fractal = hand_mem.read_fractal(args[0])?; - let mut idx: Option = None; - for i in 0..fractal.len() { - if let (check, true) = hand_mem.read_from_fractal(&fractal, i) { - // TODO: equality comparisons for nested arrays, for now, assume it's string-like - if val.len() != check.len() { - continue; - } - let mut matches = true; - for j in 0..val.len() { - if !val.compare_at(j, &check) { - matches = false; - break; - } - } - if matches { - idx = Some(i as i64); - break; - } - } - // the else branch originally just had `continue` - } - hand_mem.init_fractal(args[2])?; - if let Some(idx) = idx { - hand_mem.push_fixed(args[2], 1i64)?; - hand_mem.push_fixed(args[2], idx)?; - } else { - hand_mem.push_fixed(args[2], 0i64)?; - hand_mem.write_fractal(args[2], &HandlerMemory::str_to_fractal("element not found"))?; - } - Ok(()) - }); - cpu!(join => fn(args, hand_mem) { - let sep_str = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[1])?)?; - let fractal = hand_mem.read_fractal(args[0])?; - let mut strs = Vec::with_capacity(fractal.len()); - for i in 0..fractal.len() { - match hand_mem.read_from_fractal(&fractal, i) { - (data, true) => { - let v_str = HandlerMemory::fractal_to_string(data)?; - strs.push(v_str); - }, - (_, false) => todo!("handle joining non-fractal strings I think?"), - } - } - let out_str = strs.join(&sep_str); - hand_mem.write_fractal(args[2], &HandlerMemory::str_to_fractal(&out_str))?; - Ok(()) - }); - cpu!(pusharr => fn(args, hand_mem) { - let val_size = hand_mem.read_fixed(args[2])?; - if val_size == 0 { - hand_mem.push_register(args[0], args[1])?; - } else { - let val = hand_mem.read_fixed(args[1])?; - hand_mem.push_fixed(args[0], val)?; - } - Ok(()) - }); - cpu!(pushv => fn(args, hand_mem) { - hand_mem.push_register(args[0], args[1])?; - Ok(()) - }); - cpu!(pushf => fn(args, hand_mem) { - let val = hand_mem.read_fixed(args[1])?; - hand_mem.push_fixed(args[0], val)?; - Ok(()) - }); - cpu!(poparr => fn(args, hand_mem) { - let last = hand_mem.pop(args[0]); - hand_mem.init_fractal(args[2])?; - match last { - Ok(record) => { - hand_mem.push_fixed(args[2], 1i64)?; - hand_mem.push_register_out(args[2], &record, 0)?; - }, - Err(error) => { - hand_mem.push_fixed(args[2], 0i64)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal(&format!("{}", error)))?; - }, - } - Ok(()) - }); - cpu!(delindx => fn(args, hand_mem) { - let idx = hand_mem.read_fixed(args[1])? as usize; - let el = hand_mem.delete(args[0], idx); - hand_mem.init_fractal(args[2])?; - match el { - Ok(record) => { - hand_mem.push_fixed(args[2], 1i64)?; - hand_mem.push_register_out(args[2], &record, 0)?; - }, - Err(error) => { - hand_mem.push_fixed(args[2], 0i64)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal(&format!("{}", error)))?; - }, - } - Ok(()) - }); - cpu!(newarr => fn(args, hand_mem) { - hand_mem.init_fractal(args[2])?; - Ok(()) - }); - io!(map => fn(args, mut hand_mem) { - Box::pin(async move { - let fractal = hand_mem.read_fractal(args[0])?; - let subhandler = HandlerFragment::new(args[1], 0); - let mut mappers = Vec::with_capacity(fractal.len()); - for i in 0..fractal.len() { - let mut hm = HandlerMemory::fork(hand_mem.clone())?; - hm.register_out(args[0], i, CLOSURE_ARG_MEM_START + 1)?; - hm.write_fixed(CLOSURE_ARG_MEM_START + 2, i as i64)?; - mappers.push({ - let subhandler = subhandler.clone(); - async move { - let hm = subhandler.run(hm).await?; - Ok(hm.drop_parent()?) - } - }); - } - let hms = join_all(mappers).await; - hand_mem.init_fractal(args[2])?; - for hm in hms { - hand_mem.join(hm?)?; - hand_mem.push_register(args[2], CLOSURE_ARG_MEM_START)?; - } - Ok(hand_mem) - }) - }); - unpred_cpu!(mapl => fn(args, mut hand_mem) { - Box::pin(async move { - let fractal = hand_mem.read_fractal(args[0])?; - let subhandler = HandlerFragment::new(args[1], 0); - hand_mem.init_fractal(args[2])?; - for i in 0..fractal.len() { - hand_mem.register_out(args[0], i, CLOSURE_ARG_MEM_START + 1)?; - hand_mem.write_fixed(CLOSURE_ARG_MEM_START + 2, i as i64)?; - hand_mem = subhandler.clone().run(hand_mem).await?; - hand_mem.push_register(args[2], CLOSURE_ARG_MEM_START)?; - } - Ok(hand_mem) - }) - }); - cpu!(reparr => fn(args, hand_mem) { - hand_mem.init_fractal(args[2])?; - let n = hand_mem.read_fixed(args[1])?; - if n == 0 { - return Ok(()); - } - let fractal = hand_mem.read_fractal(args[0])?; - let mut is_fixed = true; - let mut arr = Vec::with_capacity(fractal.len()); - for i in 0..fractal.len() { - let (val, is_fractal) = hand_mem.read_from_fractal(&fractal, i); - arr.push(val); - if is_fractal { - is_fixed = false; - } - } - for _ in 0..n { - for val in arr.iter() { - if is_fixed { - hand_mem.push_fixed(args[2], val.read_fixed(0)?)?; - } else { - hand_mem.push_fractal(args[2], val.clone())?; - } - } - } - Ok(()) - }); - io!(each => fn(args, hand_mem) { - Box::pin(async move { - if args[1] == NOP_ID { - // each is expected to result in purely side effects - return Ok(hand_mem); - } - let fractal = hand_mem.read_fractal(args[0])?; - let subhandler = HandlerFragment::new(args[1], 0); - let mut runners = Vec::with_capacity(fractal.len()); - for i in 0..fractal.len() { - let mut hm = HandlerMemory::fork(hand_mem.clone())?; - hm.register_out(args[0], i, CLOSURE_ARG_MEM_START + 1)?; - hm.write_fixed(CLOSURE_ARG_MEM_START + 2, i as i64)?; - runners.push({ - let subhandler = subhandler.clone(); - async move { - let hm = subhandler.run(hm).await?; - Ok(hm.drop_parent()?) - } - }); - } - let runners: Vec> = join_all(runners).await; - runners.into_iter().collect::>>()?; - Ok(hand_mem) - }) - }); - unpred_cpu!(eachl => fn(args, mut hand_mem) { - Box::pin(async move { - if args[1] == NOP_ID { - // eachl is expected to result in purely side effects - return Ok(hand_mem); - } - let n = hand_mem.read_fractal(args[0])?.len(); - let subhandler = HandlerFragment::new(args[1], 0); - for i in 0..n { - hand_mem.register_out(args[0], i, CLOSURE_ARG_MEM_START + 1)?; - hand_mem.write_fixed(CLOSURE_ARG_MEM_START + 2, i as i64)?; - hand_mem = subhandler.clone().run(hand_mem).await?; - } - Ok(hand_mem) - }) - }); - io!(find => fn(args, mut hand_mem) { - Box::pin(async move { - if args[1] == NOP_ID { - return Err(VMError::InvalidNOP); - } - let fractal = hand_mem.read_fractal(args[0])?; - let len = fractal.len(); - let subhandler = HandlerFragment::new(args[1], 0); - let mut finders = Vec::with_capacity(fractal.len()); - for i in 0..len { - let mut hm = HandlerMemory::fork(hand_mem.clone())?; - hm.register_out(args[0], i, CLOSURE_ARG_MEM_START + 1)?; - finders.push(subhandler.clone().run(hm)); - } - let hms = join_all(finders).await; - let mut idx = None; - for (i, hm) in hms.into_iter().enumerate() { - let hm = hm?; - let val = hm.read_fixed(CLOSURE_ARG_MEM_START)?; - hm.drop_parent()?; - if idx.is_none() && val == 1 { - idx = Some(i); - } - } - hand_mem.init_fractal(args[2])?; - if let Some(idx) = idx { - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_register_out(args[2], &fractal, idx)?; - } else { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("no element matches"))?; - } - Ok(hand_mem) - }) - }); - unpred_cpu!(findl => fn(args, mut hand_mem) { - Box::pin(async move { - if args[1] == NOP_ID { - return Err(VMError::InvalidNOP); - } - let fractal = hand_mem.read_fractal(args[0])?; - let subhandler = HandlerFragment::new(args[1], 0); - for i in 0..fractal.len() { - hand_mem.register_out(args[0], i, CLOSURE_ARG_MEM_START + 1)?; - hand_mem = subhandler.clone().run(hand_mem).await?; - let val = hand_mem.read_fixed(CLOSURE_ARG_MEM_START)?; - if val == 1 { - hand_mem.init_fractal(args[2])?; - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_register_out(args[2], &fractal, i)?; - return Ok(hand_mem); - } - } - hand_mem.init_fractal(args[2])?; - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("no element matches"))?; - Ok(hand_mem) - }) - }); - io!(some => fn(args, mut hand_mem) { - Box::pin(async move { - if args[1] == NOP_ID { - return Err(VMError::InvalidNOP); - } - let fractal = hand_mem.read_fractal(args[0])?; - let subhandler = HandlerFragment::new(args[1], 0); - let mut somers = Vec::with_capacity(fractal.len()); - for i in 0..fractal.len() { - let mut hm = HandlerMemory::fork(hand_mem.clone())?; - hm.register_out(args[0], i, CLOSURE_ARG_MEM_START + 1)?; - somers.push(subhandler.clone().run(hm)); - } - let hms = join_all(somers).await; - let mut ret_val = 0; - for hm in hms { - let hm = hm?; - let val = hm.read_fixed(CLOSURE_ARG_MEM_START)?; - hm.drop_parent()?; - if val == 1 { - ret_val = 1; - } - } - hand_mem.write_fixed(args[2], ret_val)?; - Ok(hand_mem) - }) - }); - unpred_cpu!(somel => fn(args, mut hand_mem) { - Box::pin(async move { - if args[1] == NOP_ID { - return Err(VMError::InvalidNOP); - } - let fractal = hand_mem.read_fractal(args[0])?; - let subhandler = HandlerFragment::new(args[1], 0); - for i in 0..fractal.len() { - hand_mem.register_out(args[0], i, CLOSURE_ARG_MEM_START + 1)?; - hand_mem = subhandler.clone().run(hand_mem).await?; - let val = hand_mem.read_fixed(CLOSURE_ARG_MEM_START)?; - if val == 1 { - hand_mem.write_fixed(args[2], 1)?; - return Ok(hand_mem); - } - } - hand_mem.write_fixed(args[2], 0)?; - Ok(hand_mem) - }) - }); - io!(every => fn(args, mut hand_mem) { - Box::pin(async move { - if args[1] == NOP_ID { - return Err(VMError::InvalidNOP); - } - let fractal = hand_mem.read_fractal(args[0])?; - let subhandler = HandlerFragment::new(args[1], 0); - let mut somers = Vec::with_capacity(fractal.len()); - for i in 0..fractal.len() { - let mut hm = HandlerMemory::fork(hand_mem.clone())?; - hm.register_out(args[0], i, CLOSURE_ARG_MEM_START + 1)?; - somers.push(subhandler.clone().run(hm)); - } - let hms = join_all(somers).await; - let mut ret_val = 1; - for hm in hms { - let hm = hm?; - let val = hm.read_fixed(CLOSURE_ARG_MEM_START)?; - hm.drop_parent()?; - if val == 0 { - ret_val = 0; - } - } - hand_mem.write_fixed(args[2], ret_val)?; - Ok(hand_mem) - }) - }); - unpred_cpu!(everyl => fn(args, mut hand_mem) { - Box::pin(async move { - if args[1] == NOP_ID { - return Err(VMError::InvalidNOP); - } - let fractal = hand_mem.read_fractal(args[0])?; - let subhandler = HandlerFragment::new(args[1], 0); - for i in 0..fractal.len() { - hand_mem.register_out(args[0], i, CLOSURE_ARG_MEM_START + 1)?; - hand_mem = subhandler.clone().run(hand_mem).await?; - let val = hand_mem.read_fixed(CLOSURE_ARG_MEM_START)?; - if val == 0 { - hand_mem.write_fixed(args[2], 0)?; - return Ok(hand_mem); - } - } - hand_mem.write_fixed(args[2], 1)?; - Ok(hand_mem) - }) - }); - cpu!(catarr => fn(args, hand_mem) { - let fractal1 = hand_mem.read_fractal(args[0])?; - let fractal2 = hand_mem.read_fractal(args[1])?; - hand_mem.init_fractal(args[2])?; - for i in 0..fractal1.len() { - hand_mem.push_register_out(args[2], &fractal1, i)?; - } - for i in 0..fractal2.len() { - hand_mem.push_register_out(args[2], &fractal2, i)?; - } - Ok(()) - }); - io!(reducep => fn(args, mut hand_mem) { - Box::pin(async move { - if args[1] == NOP_ID { - return Err(VMError::InvalidNOP); - } - let fractal = hand_mem.read_fractal(args[0])?; - let mut vals = Vec::with_capacity(fractal.len()); - for i in 0..fractal.len() { - let mut hm = HandlerMemory::new(None, 1)?; - hand_mem.register_out(args[0], i, CLOSURE_ARG_MEM_START)?; - HandlerMemory::transfer(&hand_mem, CLOSURE_ARG_MEM_START, &mut hm, 0)?; - vals.push(hm); - } - let subhandler = HandlerFragment::new(args[1], 0); - // Log-n parallelism. First n/2 in parallel, then n/4, then n/8, etc - while vals.len() > 1 { - let mut reducers = Vec::with_capacity((vals.len() / 2) + 1); - while vals.len() > 1 { - let mut hm = hand_mem.clone(); - let a = vals.remove(0); - let b = vals.remove(0); - HandlerMemory::transfer(&a, 0, &mut hm, CLOSURE_ARG_MEM_START + 1)?; - HandlerMemory::transfer(&b, 0, &mut hm, CLOSURE_ARG_MEM_START + 2)?; - reducers.push(subhandler.clone().run(hm)); - } - // Check if one of the records was skipped over this round, and if so, pop it into a - // special field - let maybe_hm = if vals.len() == 1 { Some(vals.remove(0)) } else { None }; - let hms = join_all(reducers).await; - for hm in hms { - let mut hm = hm?; - hm.register(0, CLOSURE_ARG_MEM_START, false)?; - vals.push(hm); - } - if let Some(hm) = maybe_hm { - vals.push(hm); - } - } - // There can be only one - HandlerMemory::transfer(&vals[0], 0, &mut hand_mem, args[2])?; - Ok(hand_mem) - }) - }); - unpred_cpu!(reducel => fn(args, mut hand_mem) { - Box::pin(async move { - if args[1] == NOP_ID { - return Err(VMError::InvalidNOP); - } - let fractal = hand_mem.read_fractal(args[0])?; - if fractal.len() == 0 { - return Ok(hand_mem); - } - let mut vals = Vec::with_capacity(fractal.len()); - for i in 0..fractal.len() { - let mut hm = HandlerMemory::new(None, 1)?; - hand_mem.register_out(args[0], i, CLOSURE_ARG_MEM_START)?; - HandlerMemory::transfer(&hand_mem, CLOSURE_ARG_MEM_START, &mut hm, 0)?; - vals.push(hm); - } - let subhandler = HandlerFragment::new(args[1], 0); - let mut cumulative = vals.remove(0); - for i in 0..vals.len() { - let current = &vals[i]; - HandlerMemory::transfer(&cumulative, 0, &mut hand_mem, CLOSURE_ARG_MEM_START + 1)?; - HandlerMemory::transfer(¤t, 0, &mut hand_mem, CLOSURE_ARG_MEM_START + 2)?; - hand_mem = subhandler.clone().run(hand_mem).await?; - HandlerMemory::transfer(&hand_mem, CLOSURE_ARG_MEM_START, &mut cumulative, 0)?; - } - HandlerMemory::transfer(&cumulative, 0, &mut hand_mem, args[2])?; - Ok(hand_mem) - }) - }); - // io!(foldp => fn(args, mut hand_mem) { - // todo!("foldp with the new only-`Arc`"); - // Box::pin(async move { - // let obj = hand_mem.read_fractal(args[0]); - // let (arr, _) = hand_mem.read_from_fractal(&obj, 0); - // let mut vals = Vec::with_capacity(arr.len()); - // for i in 0..arr.len() { - // let mut hm = HandlerMemory::new(None, 1); - // hand_mem.register_from_fractal(CLOSURE_ARG_MEM_START, &arr, i); - // HandlerMemory::transfer(&hand_mem, CLOSURE_ARG_MEM_START, &mut hm, 0); - // vals.push(hm); - // } - // let subhandler = HandlerFragment::new(args[1], 0); - // hand_mem.register_out(args[0], 1, CLOSURE_ARG_MEM_START); - // let mut init = HandlerMemory::new(None, 1); - // HandlerMemory::transfer(&hand_mem, CLOSURE_ARG_MEM_START, &mut init, 0); - // // We can only go up to 'n' parallel sequential computations here - // let n = num_cpus::get(); - // let l = vals.len(); - // let s = l / n; - // let mut reducers = Vec::with_capacity(n); - // for i in 0..n { - // let subvals = if i == n - 1 { - // vals[i * s..].to_vec() - // } else { - // vals[i * s..(i + 1) * s].to_vec() - // }; - // eprintln!("subvals: {:?}", subvals); - // let mem = hand_mem.clone(); - // let init2 = init.clone(); - // let subhandler2 = subhandler.clone(); - // reducers.push(task::spawn(async move { - // let mut cumulative = init2.clone(); - // for i in 0..subvals.len() { - // let current = &subvals[i]; - // let mut hm = mem.clone(); - // HandlerMemory::transfer(&cumulative, 0, &mut hm, CLOSURE_ARG_MEM_START + 1); - // HandlerMemory::transfer(current, 0, &mut hm, CLOSURE_ARG_MEM_START + 2); - // hm = subhandler2.clone().run(hm).await; - // HandlerMemory::transfer(&hm, CLOSURE_ARG_MEM_START, &mut cumulative, 0); - // } - // cumulative - // })); - // } - // let hms = join_all(reducers).await; - // hand_mem.init_fractal(args[2]); - // for i in 0..n { - // let hm = hms[i].as_ref().unwrap(); - // HandlerMemory::transfer(&hm, 0, &mut hand_mem, CLOSURE_ARG_MEM_START); - // hand_mem.push_register(args[2], CLOSURE_ARG_MEM_START); - // } - // hand_mem - // }) - // }); - unpred_cpu!(foldl => fn(args, mut hand_mem) { - Box::pin(async move { - if args[1] == NOP_ID { - return Err(VMError::InvalidNOP); - } - let obj = hand_mem.read_fractal(args[0])?; - let (arr, _) = hand_mem.read_from_fractal(&obj, 0); - let mut vals = Vec::with_capacity(arr.len()); - for i in 0..arr.len() { - let mut hm = HandlerMemory::new(None, 1)?; - hand_mem.register_from_fractal(CLOSURE_ARG_MEM_START, &arr, i)?; - HandlerMemory::transfer(&hand_mem, CLOSURE_ARG_MEM_START, &mut hm, 0)?; - vals.push(hm); - } - let subhandler = HandlerFragment::new(args[1], 0); - hand_mem.register_out(args[0], 1, CLOSURE_ARG_MEM_START)?; - let mut cumulative = HandlerMemory::new(None, 1)?; - HandlerMemory::transfer(&hand_mem, CLOSURE_ARG_MEM_START, &mut cumulative, 0)?; - for i in 0..vals.len() { - let current = &vals[i]; - HandlerMemory::transfer(&cumulative, 0, &mut hand_mem, CLOSURE_ARG_MEM_START + 1)?; - HandlerMemory::transfer(current, 0, &mut hand_mem, CLOSURE_ARG_MEM_START + 2)?; - hand_mem = subhandler.clone().run(hand_mem).await?; - HandlerMemory::transfer(&hand_mem, CLOSURE_ARG_MEM_START, &mut cumulative, 0)?; - } - hand_mem.register(args[2], CLOSURE_ARG_MEM_START, false)?; - Ok(hand_mem) - }) - }); - io!(filter => fn(args, mut hand_mem) { - Box::pin(async move { - if args[1] == NOP_ID { - return Err(VMError::InvalidNOP); - } - let fractal = hand_mem.read_fractal(args[0])?; - let len = fractal.len(); - let subhandler = HandlerFragment::new(args[1], 0); - let mut filters = Vec::with_capacity(len); - for i in 0..len { - let mut hm = HandlerMemory::fork(hand_mem.clone())?; - hm.register_out(args[0], i, CLOSURE_ARG_MEM_START + 1)?; - filters.push(subhandler.clone().run(hm)); - } - let hms = join_all(filters).await; - let mut idxs = vec![]; - for (i, hm) in hms.into_iter().enumerate() { - let hm = hm?; - let val = hm.read_fixed(CLOSURE_ARG_MEM_START)?; - hm.drop_parent()?; - if val == 1 { - idxs.push(i); - } - } - hand_mem.init_fractal(args[2])?; - for i in idxs { - hand_mem.push_register_out(args[2], &fractal, i)?; - } - Ok(hand_mem) - }) - }); - unpred_cpu!(filterl => fn(args, mut hand_mem) { - Box::pin(async move { - if args[1] == NOP_ID { - return Err(VMError::InvalidNOP); - } - let fractal = hand_mem.read_fractal(args[0])?; - let len = fractal.len(); - let subhandler = HandlerFragment::new(args[1], 0); - hand_mem.init_fractal(args[2])?; - for i in 0..len { - hand_mem.register_out(args[0], i, CLOSURE_ARG_MEM_START + 1)?; - hand_mem = subhandler.clone().run(hand_mem).await?; - let val = hand_mem.read_fixed(CLOSURE_ARG_MEM_START)?; - if val == 1 { - hand_mem.push_register_out(args[2], &fractal, i)?; - } - } - Ok(hand_mem) - }) - }); - - // Conditional opcode - unpred_cpu!(condfn => fn(args, mut hand_mem) { - Box::pin(async move { - let cond = hand_mem.read_fixed(args[0])?; - let subhandler = HandlerFragment::new(args[1], 0); - if cond == 1 { - hand_mem = subhandler.run(hand_mem).await?; - } - Ok(hand_mem) - }) - }); - - // Std opcodes - io!(execop => fn(args, mut hand_mem) { - Box::pin(async move { - let cmd = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[0])?)?; - let output = if cfg!(target_os = "windows") { - Command::new("cmd").arg("/C").arg(cmd).output().await - } else { - Command::new("bash").arg("-c").arg(cmd).output().await - }; - hand_mem.init_fractal(args[2])?; - match output { - Err(e) => { - hand_mem.push_fixed(args[2], 127)?; - hand_mem.push_fractal(args[2], FractalMemory::new(vec![(0, 0)]))?; - let error_string = e.to_string(); - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal(&error_string))?; - }, - Ok(output_res) => { - let status_code = output_res.status.code().unwrap_or(127) as i64; - hand_mem.push_fixed(args[2], status_code)?; - let stdout_str = String::from_utf8(output_res.stdout).unwrap_or("".to_string()); - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal(&stdout_str))?; - let stderr_str = String::from_utf8(output_res.stderr).unwrap_or("".to_string()); - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal(&stderr_str))?; - }, - }; - Ok(hand_mem) - }) - }); - - unpred_cpu!(waitop => fn(args, hand_mem) { - Box::pin(async move { - let ms = hand_mem.read_fixed(args[0])? as u64; - sleep(Duration::from_millis(ms)).await; - Ok(hand_mem) - }) - }); - - unpred_cpu!(syncop => fn(args, mut hand_mem) { - Box::pin(async move { - let closure = HandlerFragment::new(args[0], 0); - hand_mem.register(CLOSURE_ARG_MEM_START + 1, args[1], true)?; - hand_mem = closure.clone().run(hand_mem).await?; - hand_mem.register(args[2], CLOSURE_ARG_MEM_START, true)?; - Ok(hand_mem) - }) - }); - - // IO opcodes - fn __httpreq( - method: String, - uri: String, - headers: Vec<(String, String)>, - body: Option, - ) -> Result { - let mut req = Request::builder().method(method.as_str()).uri(uri.as_str()); - for header in headers { - req = req.header(header.0.as_str(), header.1.as_str()); - } - let req_obj = if let Some(body) = body { - req.body(Body::from(body)) - } else { - req.body(Body::empty()) - }; - match req_obj { - Ok(req) => Ok(HTTP_CLIENT.request(req)), - Err(_) => Err("Failed to construct request, invalid body provided".to_string()), - } - } - io!(httpreq => fn(args, mut hand_mem) { - Box::pin(async move { - let req = hand_mem.read_fractal(args[0])?; - let method = HandlerMemory::fractal_to_string(hand_mem.read_from_fractal(&req, 0).0)?; - let url = HandlerMemory::fractal_to_string(hand_mem.read_from_fractal(&req, 1).0)?; - let headers = hand_mem.read_from_fractal(&req, 2).0; - let mut out_headers = Vec::new(); - for i in 0..headers.len() { - let header = hand_mem.read_from_fractal(&headers, i).0; - let key = HandlerMemory::fractal_to_string(hand_mem.read_from_fractal(&header, 0).0)?; - let val = HandlerMemory::fractal_to_string(hand_mem.read_from_fractal(&header, 1).0)?; - out_headers.push((key, val)); - } - let body = HandlerMemory::fractal_to_string(hand_mem.read_from_fractal(&req, 3).0)?; - let out_body = if body.len() > 0 { Some(body) /* once told me... */ } else { None }; - hand_mem.init_fractal(args[2])?; - let res = match __httpreq(method, url, out_headers, out_body) { - Ok(res) => res, - Err(estring) => { - hand_mem.push_fixed(args[2], 0i64)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal(&estring))?; - return Ok(hand_mem); - }, - }; - let mut res = match res.await { - Ok(res) => res, - Err(ee) => { - hand_mem.push_fixed(args[2], 0i64)?; - hand_mem.push_fractal( - args[2], - HandlerMemory::str_to_fractal(format!("{}", ee).as_str()) - )?; - return Ok(hand_mem); - }, - }; - // The headers and body can fail, so check those first - let headers = res.headers(); - let mut headers_hm = HandlerMemory::new(None, headers.len() as i64)?; - headers_hm.init_fractal(CLOSURE_ARG_MEM_START)?; - for (i, (key, val)) in headers.iter().enumerate() { - let key_str = key.as_str(); - let val_str = val.to_str(); - match val_str { - Ok(val_str) => { - headers_hm.init_fractal(i as i64)?; - headers_hm.push_fractal(i as i64, HandlerMemory::str_to_fractal(key_str))?; - headers_hm.push_fractal(i as i64, HandlerMemory::str_to_fractal(val_str))?; - headers_hm.push_register(CLOSURE_ARG_MEM_START, i as i64)?; - }, - Err(_) => { - hand_mem.push_fixed(args[2], 0i64)?; - hand_mem.push_fractal( - args[2], - HandlerMemory::str_to_fractal("Malformed headers encountered") - )?; - return Ok(hand_mem); - }, - } - } - let body = match hyper::body::to_bytes(res.body_mut()).await { - Ok(body) => body, - Err(ee) => { - hand_mem.push_fixed(args[2], 0i64)?; - hand_mem.push_fractal( - args[2], - HandlerMemory::str_to_fractal(format!("{}", ee).as_str()) - )?; - return Ok(hand_mem); - }, - }; - let body_str = match String::from_utf8(body.to_vec()) { - Ok(body_str) => body_str, - Err(ee) => { - hand_mem.push_fixed(args[2], 0i64)?; - hand_mem.push_fractal( - args[2], - HandlerMemory::str_to_fractal(format!("{}", ee).as_str()) - )?; - return Ok(hand_mem); - }, - }; - hand_mem.push_fixed(args[2], 1i64)?; - let mut res_hm = HandlerMemory::new(None, 3)?; - res_hm.init_fractal(0)?; - res_hm.push_fixed(0, res.status().as_u16() as i64)?; - HandlerMemory::transfer(&headers_hm, CLOSURE_ARG_MEM_START, &mut res_hm, CLOSURE_ARG_MEM_START)?; - res_hm.push_register(0, CLOSURE_ARG_MEM_START)?; - res_hm.push_fractal(0, HandlerMemory::str_to_fractal(&body_str))?; - res_hm.push_fixed(0, 0i64)?; - HandlerMemory::transfer(&res_hm, 0, &mut hand_mem, CLOSURE_ARG_MEM_START)?; - hand_mem.push_register(args[2], CLOSURE_ARG_MEM_START)?; - Ok(hand_mem) - }) - }); - - async fn http_listener(req: Request) -> VMResult> { - // Grab the headers - let headers = req.headers(); - // Check if we should load balance this request. If the special `x-alan-rr` header is present, - // that means it was already load-balanced to us and we should process it locally. If not, then - // use a random number generator to decide if we should process this here or if we should - // distribute the load to one of our local-region peers. This adds an extra network hop, but - // within the same firewall group inside of the datacenter, so that part should be a minimal - // impact on the total latency. This is done because cloudflare's routing is "sticky" to an - // individual IP address without moving to a more expensive tier, so there's no actual load - // balancing going on, just fallbacks in case of an outage. This adds stochastic load balancing - // to the cluster even if we didn't have cloudflare fronting things. - if !headers.contains_key("x-alan-rr") { - let l = REGION_VMS.read().unwrap().len(); - let i = async move { - let mut rng = thread_rng(); - rng.gen_range(0..=l) - } - .await; - // If it's equal to the length process this request normally, otherwise, load balance this - // request to another instance - if i != l { - // Otherwise, round-robin this to another node in the cluster and increment the counter - let headers = headers.clone(); - let host = ®ION_VMS.read().unwrap()[i].clone(); - let method_str = req.method().to_string(); - let orig_uri = req.uri().clone(); - let orig_query = match orig_uri.query() { - Some(q) => format!("?{}", q), - None => format!(""), - }; - let uri_str = format!("https://{}{}{}", host, orig_uri.path(), orig_query); - // Grab the body, if any - let body_req = match hyper::body::to_bytes(req.into_body()).await { - Ok(bytes) => bytes, - // If we error out while getting the body, just close this listener out immediately - Err(ee) => { - return Ok(Response::new( - format!("Connection terminated: {}", ee).into(), - )); - } - }; - let body_str = str::from_utf8(&body_req).unwrap().to_string(); - let mut rr_req = Request::builder().method(method_str.as_str()).uri(uri_str); - let rr_headers = rr_req.headers_mut().unwrap(); - let name = HeaderName::from_bytes("x-alan-rr".as_bytes()).unwrap(); - let value = HeaderValue::from_str("true").unwrap(); - rr_headers.insert(name, value); - for (key, val) in headers.iter() { - rr_headers.insert(key, val.clone()); - } - let req_obj = if body_str.len() > 0 { - rr_req.body(Body::from(body_str)) - } else { - rr_req.body(Body::empty()) - }; - let req_obj = match req_obj { - Ok(req_obj) => req_obj, - Err(ee) => { - return Ok(Response::new( - format!("Connection terminated: {}", ee).into(), - )); - } - }; - let mut rr_res = match NAIVE_CLIENT.get().unwrap().request(req_obj).await { - Ok(res) => res, - Err(ee) => { - return Ok(Response::new( - format!("Connection terminated: {}", ee).into(), - )); - } - }; - // Get the status from the round-robin response and begin building the response object - let status = rr_res.status(); - let mut res = Response::builder().status(status); - // Get the headers and populate the response object - let headers = res.headers_mut().unwrap(); - for (key, val) in rr_res.headers().iter() { - headers.insert(key, val.clone()); - } - let body = match hyper::body::to_bytes(rr_res.body_mut()).await { - Ok(body) => body, - Err(ee) => { - return Ok(Response::new( - format!("Connection terminated: {}", ee).into(), - )); - } - }; - return Ok(res.body(body.into()).unwrap()); - } - } - // Create a new event handler memory to add to the event queue - let mut event = HandlerMemory::new(None, 1)?; - // Grab the method - let method_str = req.method().to_string(); - let method = HandlerMemory::str_to_fractal(&method_str); - // Grab the URL - let orig_uri = req.uri().clone(); - let orig_query = match orig_uri.query() { - Some(q) => format!("?{}", q), - None => format!(""), - }; - let url_str = format!("{}{}", orig_uri.path(), orig_query); - //let url_str = req.uri().to_string(); - let url = HandlerMemory::str_to_fractal(&url_str); - let mut headers_hm = HandlerMemory::new(None, headers.len() as i64)?; - headers_hm.init_fractal(CLOSURE_ARG_MEM_START)?; - for (i, (key, val)) in headers.iter().enumerate() { - let key_str = key.as_str(); - // TODO: get rid of the potential panic here - let val_str = val.to_str().unwrap(); - headers_hm.init_fractal(i as i64)?; - headers_hm.push_fractal(i as i64, HandlerMemory::str_to_fractal(key_str))?; - headers_hm.push_fractal(i as i64, HandlerMemory::str_to_fractal(val_str))?; - headers_hm.push_register(CLOSURE_ARG_MEM_START, i as i64)?; - } - // Grab the body, if any - let body_req = match hyper::body::to_bytes(req.into_body()).await { - Ok(bytes) => bytes, - // If we error out while getting the body, just close this listener out immediately - Err(ee) => { - return Ok(Response::new( - format!("Connection terminated: {}", ee).into(), - )); - } - }; - // TODO: get rid of the potential panic here - let body_str = str::from_utf8(&body_req).unwrap().to_string(); - let body = HandlerMemory::str_to_fractal(&body_str); - // Populate the event and emit it - event.init_fractal(0)?; - event.push_fractal(0, method)?; - event.push_fractal(0, url)?; - HandlerMemory::transfer( - &headers_hm, - CLOSURE_ARG_MEM_START, - &mut event, - CLOSURE_ARG_MEM_START, - )?; - event.push_register(0, CLOSURE_ARG_MEM_START)?; - event.push_fractal(0, body)?; - // Generate a threadsafe raw ptr to the tx of a watch channel - // A ptr is unsafely created from the raw ptr in httpsend once the - // user's code has completed and sends the new HandlerMemory so we - // can resume execution of this HTTP request - let (tx, rx): (Sender>, Receiver>) = oneshot::channel(); - let tx_ptr = Box::into_raw(Box::new(tx)) as i64; - event.push_fixed(0, tx_ptr)?; - let event_emit = EventEmit { - id: i64::from(BuiltInEvents::HTTPCONN), - payload: Some(event), - }; - let event_tx = EVENT_TX.get().ok_or(VMError::ShutDown)?; - let mut err_res = Response::new("Error synchronizing `send` for HTTP request".into()); - *err_res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; - if event_tx.send(event_emit).is_err() { - return Ok(err_res); - } - // Await HTTP response from the user code - let response_hm = match rx.await { - Ok(hm) => hm, - Err(_) => { - return Ok(err_res); - } - }; - // Get the status from the user response and begin building the response object - let status = response_hm.read_fixed(0)? as u16; - let mut res = Response::builder() - .status(StatusCode::from_u16(status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)); - // Get the headers and populate the response object - // TODO: figure out how to handle this potential panic - let headers = res.headers_mut().unwrap(); - let header_hms = response_hm.read_fractal(1)?; - for i in 0..header_hms.len() { - let (h, _) = response_hm.read_from_fractal(&header_hms.clone(), i); - let (key_hm, _) = response_hm.read_from_fractal(&h, 0); - let (val_hm, _) = response_hm.read_from_fractal(&h, 1); - let key = HandlerMemory::fractal_to_string(key_hm)?; - let val = HandlerMemory::fractal_to_string(val_hm)?; - // TODO: figure out how to handle this potential panic - let name = HeaderName::from_bytes(key.as_bytes()).unwrap(); - // TODO: figure out how to handle this potential panic - let value = HeaderValue::from_str(&val).unwrap(); - headers.insert(name, value); - } - // Get the body, populate the response object, and fire it out - let body = HandlerMemory::fractal_to_string(response_hm.read_fractal(2)?)?; - // TODO: figure out how to handle this potential panic - Ok(res.body(body.into()).unwrap()) - } - io!(tcptun => fn(args, mut hand_mem) { - Box::pin(async move { - let port = hand_mem.read_fixed(args[0])? as i16; - let connected = if let Some(http_config) = &Program::global().http_config { - make_tunnel!(http_config, port) - } else { - false - }; - hand_mem.write_fixed(args[2], if connected { 1 } else { 0 })?; - return Ok(hand_mem); - }) - }); - io!(httplsn => fn(_args, hand_mem) { - Box::pin(async move { - // this extra fn is so that we can just use `?` inside of http_listener instead of - // having a bunch of `match`es that call a closure - async fn listen(req: Request) -> Result, Infallible> { - match http_listener(req).await { - Ok(res) => Ok(res), - Err(_) => { - // TODO: log the error? - Ok(Response::builder().status(500).body(Body::empty()).unwrap()) - } - } - } - if let Some(http_config) = &Program::global().http_config { - make_server!(http_config, listen); - }; - return Ok(hand_mem); - }) - }); - cpu!(httpsend => fn(args, hand_mem) { - hand_mem.dupe(args[0], args[0])?; // Make sure there's no pointers involved - let mut hm = HandlerMemory::new(None, 1)?; - HandlerMemory::transfer(&hand_mem, args[0], &mut hm, CLOSURE_ARG_MEM_START)?; - let res_out = hm.read_fractal(CLOSURE_ARG_MEM_START)?; - for i in 0..res_out.len() { - hm.register_from_fractal(i as i64, &res_out, i)?; - } - // Get the oneshot channel tx from the raw ptr previously generated in http_listener - let fractal = hand_mem.read_fractal(args[0])?; - let tx_ptr = NonNull::new(fractal.read_fixed(3)? as *mut Sender>); - if let Some(tx_nonnull) = tx_ptr { - let tx = unsafe { Box::from_raw(tx_nonnull.as_ptr()) }; - let (status, string) = match tx.send(hm) { - Ok(_) => (1, "ok"), - Err(_) => (0, "could not send response to server"), - }; - hand_mem.init_fractal(args[2])?; - hand_mem.push_fixed(args[2], status)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal(string))?; - } else { - hand_mem.init_fractal(args[2])?; - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal( - args[2], - HandlerMemory::str_to_fractal("cannot call send twice for the same connection") - )?; - } - Ok(()) - }); - - // Datastore opcodes - unpred_cpu!(dssetf => fn(args, hand_mem) { - Box::pin(async move { - let val = hand_mem.read_fixed(args[2])?; - let mut hm = HandlerMemory::new(None, 1)?; - hm.write_fixed(0, val)?; - let ns = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[0])?)?; - let key = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[1])?)?; - let nskey = format!("{}:{}", ns, key); - let ctrl_port = CONTROL_PORT_CHANNEL.get(); - let ctrl_port = match ctrl_port { - Some(ctrl_port) => Some(ctrl_port.borrow().clone()), // TODO: Use thread-local storage - None => None, - }; - match ctrl_port { - Some(ref ctrl_port) => { - ctrl_port.dssetf(&nskey, &hm).await; - }, - None => { - DS.insert(nskey, hm); - }, - } - Ok(hand_mem) - }) - }); - unpred_cpu!(dssetv => fn(args, hand_mem) { - Box::pin(async move { - let mut hm = HandlerMemory::new(None, 1)?; - HandlerMemory::transfer(&hand_mem, args[2], &mut hm, 0)?; - let ns = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[0])?)?; - let key = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[1])?)?; - let nskey = format!("{}:{}", ns, key); - let ctrl_port = CONTROL_PORT_CHANNEL.get(); - let ctrl_port = match ctrl_port { - Some(ctrl_port) => Some(ctrl_port.borrow().clone()), // TODO: Use thread-local storage - None => None, - }; - match ctrl_port { - Some(ref ctrl_port) => { - ctrl_port.dssetv(&nskey, &hm).await; - }, - None => { - DS.insert(nskey, hm); - }, - } - Ok(hand_mem) - }) - }); - unpred_cpu!(dshas => fn(args, mut hand_mem) { - Box::pin(async move { - let ns = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[0])?)?; - let key = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[1])?)?; - let nskey = format!("{}:{}", ns, key); - let ctrl_port = CONTROL_PORT_CHANNEL.get(); - let ctrl_port = match ctrl_port { - Some(ctrl_port) => Some(ctrl_port.borrow().clone()), // TODO: Use thread-local storage - None => None, - }; - let has = match ctrl_port { - Some(ref ctrl_port) => { - ctrl_port.dshas(&nskey).await - }, - None => { - DS.contains_key(&nskey) - }, - }; - hand_mem.write_fixed(args[2], if has { 1i64 } else { 0i64 })?; - Ok(hand_mem) - }) - }); - unpred_cpu!(dsdel => fn(args, mut hand_mem) { - Box::pin(async move { - let ns = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[0])?)?; - let key = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[1])?)?; - let nskey = format!("{}:{}", ns, key); - // If it exists locally, remove it here, too - let removed_locally = DS.remove(&nskey).is_some(); - let ctrl_port = CONTROL_PORT_CHANNEL.get(); - let removed = match ctrl_port { - Some(ctrl_port) => { - let ctrl_port = ctrl_port.borrow().clone(); // TODO: Use thread-local storage - ctrl_port.dsdel(&nskey).await || removed_locally - }, - None => removed_locally, - }; - hand_mem.write_fixed(args[2], if removed { 1i64 } else { 0i64 })?; - Ok(hand_mem) - }) - }); - unpred_cpu!(dsgetf => fn(args, mut hand_mem) { - Box::pin(async move { - let ns = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[0])?)?; - let key = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[1])?)?; - let nskey = format!("{}:{}", ns, key); - let ctrl_port = CONTROL_PORT_CHANNEL.get(); - let ctrl_port = match ctrl_port { - Some(ctrl_port) => Some(ctrl_port.borrow().clone()), // TODO: Use thread-local storage - None => None, - }; - let is_key_owner = match ctrl_port { - Some(ref ctrl_port) => ctrl_port.is_key_owner(&nskey), - None => true, - }; - if is_key_owner { - hand_mem.init_fractal(args[2])?; - let maybe_hm = DS.get(&nskey); - match maybe_hm { - Some(hm) => { - hand_mem.push_fixed(args[2], 1i64)?; - hand_mem.push_fixed(args[2], hm.read_fixed(0)?)?; - }, - None => { - hand_mem.push_fixed(args[2], 0i64)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("namespace-key pair not found"))?; - }, - } - } else { - let maybe_hm = ctrl_port.unwrap().dsgetf(&nskey).await; - match maybe_hm { - Some(hm) => { - HandlerMemory::transfer(&hm, 0, &mut hand_mem, args[2])?; - }, - None => { - hand_mem.init_fractal(args[2])?; - hand_mem.push_fixed(args[2], 0i64)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("namespace-key pair not found"))?; - }, - } - }; - Ok(hand_mem) - }) - }); - unpred_cpu!(dsgetv => fn(args, mut hand_mem) { - Box::pin(async move { - let ns = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[0])?)?; - let key = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[1])?)?; - let nskey = format!("{}:{}", ns, key); - let ctrl_port = CONTROL_PORT_CHANNEL.get(); - let ctrl_port = match ctrl_port { - Some(ctrl_port) => Some(ctrl_port.borrow().clone()), // TODO: Use thread-local storage - None => None, - }; - let is_key_owner = match ctrl_port { - Some(ref ctrl_port) => ctrl_port.is_key_owner(&nskey), - None => true, - }; - if is_key_owner { - hand_mem.init_fractal(args[2])?; - let maybe_hm = DS.get(&nskey); - match maybe_hm { - Some(hm) => { - hand_mem.push_fixed(args[2], 1i64)?; - HandlerMemory::transfer(&hm, 0, &mut hand_mem, CLOSURE_ARG_MEM_START)?; - hand_mem.push_register(args[2], CLOSURE_ARG_MEM_START)?; - }, - None => { - hand_mem.push_fixed(args[2], 0i64)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("namespace-key pair not found"))?; - }, - } - } else { - let maybe_hm = ctrl_port.unwrap().dsgetv(&nskey).await; - match maybe_hm { - Some(hm) => { - HandlerMemory::transfer(&hm, 0, &mut hand_mem, args[2])?; - }, - None => { - hand_mem.init_fractal(args[2])?; - hand_mem.push_fixed(args[2], 0i64)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("namespace-key pair not found"))?; - }, - } - } - Ok(hand_mem) - }) - }); - unpred_cpu!(dsrrun => fn(args, mut hand_mem) { - Box::pin(async move { - let nsref = hand_mem.read_fractal(args[0])?; - let ns = HandlerMemory::fractal_to_string(hand_mem.read_from_fractal(&nsref, 0).0)?; - let key = HandlerMemory::fractal_to_string(hand_mem.read_from_fractal(&nsref, 1).0)?; - let nskey = format!("{}:{}", ns, key); - let ctrl_port = CONTROL_PORT_CHANNEL.get(); - match ctrl_port { - Some(ctrl_port) => { - let ctrl_port = ctrl_port.borrow().clone(); - let res_hm = ctrl_port.dsrrun(&nskey, args[1], &hand_mem).await; - HandlerMemory::transfer(&res_hm, 0, &mut hand_mem, args[2])?; - }, - None => { - hand_mem.init_fractal(args[2])?; - let maybe_hm = DS.get(&nskey); - match maybe_hm { - Some(ds) => { - let mut hm = HandlerMemory::fork(hand_mem.clone())?; // TODO: This clone is terrible - HandlerMemory::transfer(&ds, 0, &mut hm, CLOSURE_ARG_MEM_START + 1)?; - let subhandler = HandlerFragment::new(args[1], 0); - let hm = subhandler.run(hm).await?; - let hm = hm.drop_parent()?; - HandlerMemory::transfer(&hm, CLOSURE_ARG_MEM_START, &mut hand_mem, CLOSURE_ARG_MEM_START)?; - hand_mem.push_fixed(args[2], 1i64)?; - hand_mem.push_register(args[2], CLOSURE_ARG_MEM_START)?; - }, - None => { - hand_mem.push_fixed(args[2], 0i64)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("namespace-key pair not found"))?; - }, - } - }, - } - Ok(hand_mem) - }) - }); - unpred_cpu!(dsmrun => fn(args, mut hand_mem) { - Box::pin(async move { - let nsref = hand_mem.read_fractal(args[0])?; - let ns = HandlerMemory::fractal_to_string(hand_mem.read_from_fractal(&nsref, 0).0)?; - let key = HandlerMemory::fractal_to_string(hand_mem.read_from_fractal(&nsref, 1).0)?; - let nskey = format!("{}:{}", ns, key); - let ctrl_port = CONTROL_PORT_CHANNEL.get(); - match ctrl_port { - Some(ctrl_port) => { - let ctrl_port = ctrl_port.borrow().clone(); - let res_hm = ctrl_port.dsmrun(&nskey, args[1], &hand_mem).await; - HandlerMemory::transfer(&res_hm, 0, &mut hand_mem, args[2])?; - }, - None => { - hand_mem.init_fractal(args[2])?; - let maybe_hm = DS.get(&nskey); - match maybe_hm { - Some(ds) => { - let mut hm = HandlerMemory::fork(hand_mem.clone())?; // TODO: This clone is terrible - HandlerMemory::transfer(&ds, 0, &mut hm, CLOSURE_ARG_MEM_START + 1)?; - let subhandler = HandlerFragment::new(args[1], 0); - let hm = subhandler.run(hm).await?; - let hm = hm.drop_parent()?; - HandlerMemory::transfer(&hm, CLOSURE_ARG_MEM_START, &mut hand_mem, CLOSURE_ARG_MEM_START)?; - hand_mem.push_fixed(args[2], 1i64)?; - hand_mem.push_register(args[2], CLOSURE_ARG_MEM_START)?; - // Also grab the mutation to the datastore value and re-insert it - let mut newds = HandlerMemory::new(None, 1)?; - HandlerMemory::transfer(&hm, CLOSURE_ARG_MEM_START + 1, &mut newds, 0)?; - drop(ds); - DS.insert(nskey, newds); - }, - None => { - hand_mem.push_fixed(args[2], 0i64)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("namespace-key pair not found"))?; - }, - } - }, - } - Ok(hand_mem) - }) - }); - unpred_cpu!(dsrwith => fn(args, mut hand_mem) { - Box::pin(async move { - let with = hand_mem.read_fractal(args[0])?; - let nsref = hand_mem.read_from_fractal(&with, 0).0; - let ns = HandlerMemory::fractal_to_string(hand_mem.read_from_fractal(&nsref, 0).0)?; - let key = HandlerMemory::fractal_to_string(hand_mem.read_from_fractal(&nsref, 1).0)?; - let nskey = format!("{}:{}", ns, key); - let ctrl_port = CONTROL_PORT_CHANNEL.get(); - match ctrl_port { - Some(ctrl_port) => { - let ctrl_port = ctrl_port.borrow().clone(); - let res_hm = ctrl_port.dsrwith(&nskey, args[0], args[1], &hand_mem).await; - HandlerMemory::transfer(&res_hm, 0, &mut hand_mem, args[2])?; - }, - None => { - hand_mem.init_fractal(args[2])?; - let maybe_hm = DS.get(&nskey); - match maybe_hm { - Some(ds) => { - let mut hm = HandlerMemory::fork(hand_mem.clone())?; // TODO: This clone is terrible - HandlerMemory::transfer(&ds, 0, &mut hm, CLOSURE_ARG_MEM_START + 1)?; - hm.register_out(args[0], 1, CLOSURE_ARG_MEM_START + 2)?; - let subhandler = HandlerFragment::new(args[1], 0); - let hm = subhandler.run(hm).await?; - let hm = hm.drop_parent()?; - HandlerMemory::transfer(&hm, CLOSURE_ARG_MEM_START, &mut hand_mem, CLOSURE_ARG_MEM_START)?; - hand_mem.push_fixed(args[2], 1i64)?; - hand_mem.push_register(args[2], CLOSURE_ARG_MEM_START)?; - }, - None => { - hand_mem.push_fixed(args[2], 0i64)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("namespace-key pair not found"))?; - }, - } - }, - } - Ok(hand_mem) - }) - }); - unpred_cpu!(dsmwith => fn(args, mut hand_mem) { - Box::pin(async move { - let with = hand_mem.read_fractal(args[0])?; - let nsref = hand_mem.read_from_fractal(&with, 0).0; - let ns = HandlerMemory::fractal_to_string(hand_mem.read_from_fractal(&nsref, 0).0)?; - let key = HandlerMemory::fractal_to_string(hand_mem.read_from_fractal(&nsref, 1).0)?; - let nskey = format!("{}:{}", ns, key); - let ctrl_port = CONTROL_PORT_CHANNEL.get(); - match ctrl_port { - Some(ctrl_port) => { - let ctrl_port = ctrl_port.borrow().clone(); - let res_hm = ctrl_port.dsmwith(&nskey, args[0], args[1], &hand_mem).await; - HandlerMemory::transfer(&res_hm, 0, &mut hand_mem, args[2])?; - }, - None => { - hand_mem.init_fractal(args[2])?; - let maybe_hm = DS.get(&nskey); - match maybe_hm { - Some(ds) => { - let mut hm = HandlerMemory::fork(hand_mem.clone())?; // TODO: This clone is terrible - HandlerMemory::transfer(&ds, 0, &mut hm, CLOSURE_ARG_MEM_START + 1)?; - hm.register_out(args[0], 1, CLOSURE_ARG_MEM_START + 2)?; - let subhandler = HandlerFragment::new(args[1], 0); - let hm = subhandler.run(hm).await?; - let hm = hm.drop_parent()?; - HandlerMemory::transfer(&hm, CLOSURE_ARG_MEM_START, &mut hand_mem, CLOSURE_ARG_MEM_START)?; - hand_mem.push_fixed(args[2], 1i64)?; - hand_mem.push_register(args[2], CLOSURE_ARG_MEM_START)?; - // Also grab the mutation to the datastore value and re-insert it - let mut newds = HandlerMemory::new(None, 1)?; - HandlerMemory::transfer(&hm, CLOSURE_ARG_MEM_START + 1, &mut newds, 0)?; - drop(ds); - DS.insert(nskey, newds); - }, - None => { - hand_mem.push_fixed(args[2], 0i64)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("namespace-key pair not found"))?; - }, - } - }, - } - Ok(hand_mem) - }) - }); - unpred_cpu!(dsmonly => fn(args, mut hand_mem) { - Box::pin(async move { - let nsref = hand_mem.read_fractal(args[0])?; - let ns = HandlerMemory::fractal_to_string(hand_mem.read_from_fractal(&nsref, 0).0)?; - let key = HandlerMemory::fractal_to_string(hand_mem.read_from_fractal(&nsref, 1).0)?; - let nskey = format!("{}:{}", ns, key); - let ctrl_port = CONTROL_PORT_CHANNEL.get(); - match ctrl_port { - Some(ctrl_port) => { - let ctrl_port = ctrl_port.borrow().clone(); - ctrl_port.dsmonly(&nskey, args[1], &hand_mem); - }, - None => { - hand_mem.init_fractal(args[2])?; - let maybe_hm = DS.get(&nskey); - match maybe_hm { - Some(ds) => { - let mut hm = HandlerMemory::fork(hand_mem.clone())?; // TODO: This clone is terrible - HandlerMemory::transfer(&ds, 0, &mut hm, CLOSURE_ARG_MEM_START + 1)?; - let subhandler = HandlerFragment::new(args[1], 0); - let hm = subhandler.run(hm).await?; - let hm = hm.drop_parent()?; - // Also grab the mutation to the datastore value and re-insert it - let mut newds = HandlerMemory::new(None, 1)?; - HandlerMemory::transfer(&hm, CLOSURE_ARG_MEM_START + 1, &mut newds, 0)?; - drop(ds); - DS.insert(nskey, newds); - }, - None => { - hand_mem.push_fixed(args[2], 0i64)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("namespace-key pair not found"))?; - }, - } - }, - } - Ok(hand_mem) - }) - }); - unpred_cpu!(dswonly => fn(args, mut hand_mem) { - Box::pin(async move { - let with = hand_mem.read_fractal(args[0])?; - let nsref = hand_mem.read_from_fractal(&with, 0).0; - let ns = HandlerMemory::fractal_to_string(hand_mem.read_from_fractal(&nsref, 0).0)?; - let key = HandlerMemory::fractal_to_string(hand_mem.read_from_fractal(&nsref, 1).0)?; - let nskey = format!("{}:{}", ns, key); - let ctrl_port = CONTROL_PORT_CHANNEL.get(); - match ctrl_port { - Some(ctrl_port) => { - let ctrl_port = ctrl_port.borrow().clone(); - ctrl_port.dswonly(&nskey, args[0], args[1], &hand_mem); - }, - None => { - hand_mem.init_fractal(args[2])?; - let maybe_hm = DS.get(&nskey); - match maybe_hm { - Some(ds) => { - let mut hm = HandlerMemory::fork(hand_mem.clone())?; // TODO: This clone is terrible - HandlerMemory::transfer(&ds, 0, &mut hm, CLOSURE_ARG_MEM_START + 1)?; - hm.register_out(args[0], 1, CLOSURE_ARG_MEM_START + 2)?; - let subhandler = HandlerFragment::new(args[1], 0); - let hm = subhandler.run(hm).await?; - let hm = hm.drop_parent()?; - // Also grab the mutation to the datastore value and re-insert it - let mut newds = HandlerMemory::new(None, 1)?; - HandlerMemory::transfer(&hm, CLOSURE_ARG_MEM_START + 1, &mut newds, 0)?; - drop(ds); - DS.insert(nskey, newds); - }, - None => { - hand_mem.push_fixed(args[2], 0i64)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("namespace-key pair not found"))?; - }, - } - }, - } - Ok(hand_mem) - }) - }); - unpred_cpu!(dsrclos => fn(args, mut hand_mem) { - Box::pin(async move { - let nsref = hand_mem.read_fractal(args[0])?; - let ns = HandlerMemory::fractal_to_string(hand_mem.read_from_fractal(&nsref, 0).0)?; - let key = HandlerMemory::fractal_to_string(hand_mem.read_from_fractal(&nsref, 1).0)?; - let nskey = format!("{}:{}", ns, key); - let ctrl_port = CONTROL_PORT_CHANNEL.get(); - match ctrl_port { - Some(ctrl_port) => { - let ctrl_port = ctrl_port.borrow().clone(); - hand_mem = ctrl_port.dsrclos(&nskey, args[1], args[2], &hand_mem).await; - }, - None => { - hand_mem.init_fractal(args[2])?; - let maybe_hm = DS.get(&nskey); - match maybe_hm { - Some(ds) => { - let mut hm = HandlerMemory::fork(hand_mem.clone())?; // TODO: This clone is terrible - HandlerMemory::transfer(&ds, 0, &mut hm, CLOSURE_ARG_MEM_START + 1)?; - let subhandler = HandlerFragment::new(args[1], 0); - let hm = subhandler.run(hm).await?; - let hm = hm.drop_parent()?; - hand_mem.join(hm)?; - hand_mem.push_fixed(args[2], 1i64)?; - hand_mem.push_register(args[2], CLOSURE_ARG_MEM_START)?; - }, - None => { - hand_mem.push_fixed(args[2], 0i64)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("namespace-key pair not found"))?; - }, - } - }, - } - Ok(hand_mem) - }) - }); - unpred_cpu!(dsmclos => fn(args, mut hand_mem) { - Box::pin(async move { - let nsref = hand_mem.read_fractal(args[0])?; - let ns = HandlerMemory::fractal_to_string(hand_mem.read_from_fractal(&nsref, 0).0)?; - let key = HandlerMemory::fractal_to_string(hand_mem.read_from_fractal(&nsref, 1).0)?; - let nskey = format!("{}:{}", ns, key); - let ctrl_port = CONTROL_PORT_CHANNEL.get(); - match ctrl_port { - Some(ctrl_port) => { - let ctrl_port = ctrl_port.borrow().clone(); - hand_mem = ctrl_port.dsmclos(&nskey, args[1], args[2], &hand_mem).await; - }, - None => { - hand_mem.init_fractal(args[2])?; - let maybe_hm = DS.get(&nskey); - match maybe_hm { - Some(ds) => { - let mut hm = HandlerMemory::fork(hand_mem.clone())?; // TODO: This clone is terrible - HandlerMemory::transfer(&ds, 0, &mut hm, CLOSURE_ARG_MEM_START + 1)?; - let subhandler = HandlerFragment::new(args[1], 0); - let hm = subhandler.run(hm).await?; - // Also grab the mutation to the datastore value and re-insert it - let mut newds = HandlerMemory::new(None, 1)?; - HandlerMemory::transfer(&hm, CLOSURE_ARG_MEM_START + 1, &mut newds, 0)?; - drop(ds); - DS.insert(nskey, newds); - let hm = hm.drop_parent()?; - hand_mem.join(hm)?; - hand_mem.push_fixed(args[2], 1i64)?; - hand_mem.push_register(args[2], CLOSURE_ARG_MEM_START)?; - }, - None => { - hand_mem.push_fixed(args[2], 0i64)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("namespace-key pair not found"))?; - }, - } - }, - } - Ok(hand_mem) - }) - }); - - // cluster secret for avmdaemon - cpu!(getcs => fn(args, hand_mem) { - hand_mem.init_fractal(args[2])?; - match CLUSTER_SECRET.get().unwrap() { - Some(cluster_secret) => { - hand_mem.push_fixed(args[2], 1i64)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal(cluster_secret))?; - }, - None => { - hand_mem.push_fixed(args[2], 0i64)?; - }, - }; - Ok(()) - }); - - // seq opcodes - cpu!(newseq => fn(args, hand_mem) { - hand_mem.init_fractal(args[2])?; - hand_mem.push_fixed(args[2], 0i64)?; - hand_mem.push_fixed(args[2], hand_mem.read_fixed(args[0])?)?; - Ok(()) - }); - cpu!(seqnext => fn(args, hand_mem) { - hand_mem.init_fractal(args[2])?; - let mut seq = hand_mem.read_fractal(args[0])?; - let current = seq.read_fixed(0)?; - let limit = seq.read_fixed(1)?; - if current < limit { - hand_mem.write_fixed_in_fractal(&mut seq, 0, current + 1)?; - hand_mem.push_fixed(args[2], 1i64)?; - hand_mem.push_fixed(args[2], current)?; - } else { - hand_mem.push_fixed(args[2], 0i64)?; - let err_msg = "error: sequence out-of-bounds"; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal(&err_msg))?; - } - Ok(()) - }); - unpred_cpu!(seqeach => fn(args, mut hand_mem) { - Box::pin(async move { - if args[1] == NOP_ID { - // same as `each` - return Ok(hand_mem); - } - let mut seq = hand_mem.read_fractal(args[0])?; - let current = seq.read_fixed(0)?; - let limit = seq.read_fixed(1)?; - let subhandler = HandlerFragment::new(args[1], 0); - if current >= limit { - return Ok(hand_mem); - } - hand_mem.write_fixed_in_fractal(&mut seq, 0, limit)?; - // array of potentially many levels of nested fractals - for i in current..limit { - // array element is $1 argument of the closure memory space - hand_mem.write_fixed(CLOSURE_ARG_MEM_START + 1, i)?; - hand_mem = subhandler.clone().run(hand_mem).await?; - } - Ok(hand_mem) - }) - }); - unpred_cpu!(seqwhile => fn(args, mut hand_mem) { - Box::pin(async move { - if args[1] == NOP_ID { - return Err(VMError::InvalidNOP); - } - let seq = hand_mem.read_fractal(args[0])?; - let mut current = seq.read_fixed(0)?; - let limit = seq.read_fixed(1)?; - drop(seq); - let cond_handler = HandlerFragment::new(args[1], 0); - let body_handler = HandlerFragment::new(args[2], 0); - if current >= limit { - return Ok(hand_mem); - } - hand_mem = cond_handler.clone().run(hand_mem).await?; - while current < limit && hand_mem.read_fixed(CLOSURE_ARG_MEM_START)? > 0 { - if args[2] != NOP_ID { - hand_mem = body_handler.clone().run(hand_mem).await?; - } - current = current + 1; - hand_mem = cond_handler.clone().run(hand_mem).await?; - } - let mut seq = hand_mem.read_fractal(args[0])?; - hand_mem.write_fixed_in_fractal(&mut seq, 0, current)?; - Ok(hand_mem) - }) - }); - unpred_cpu!(seqdo => fn(args, mut hand_mem) { - Box::pin(async move { - let seq = hand_mem.read_fractal(args[0])?; - let mut current = seq.read_fixed(0)?; - let limit = seq.read_fixed(1)?; - drop(seq); - let subhandler = HandlerFragment::new(args[1], 0); - loop { - if args[1] != NOP_ID { - hand_mem = subhandler.clone().run(hand_mem).await?; - } - current = current + 1; - if current >= limit || hand_mem.read_fixed(CLOSURE_ARG_MEM_START)? == 0 { - break; - } - } - let mut seq = hand_mem.read_fractal(args[0])?; - hand_mem.write_fixed_in_fractal(&mut seq, 0, current)?; - Ok(hand_mem) - }) - }); - unpred_cpu!(selfrec => fn(args, mut hand_mem) { - Box::pin(async move { - let mut hm = HandlerMemory::fork(hand_mem.clone())?; - // MUST read these first in case the arguments are themselves closure args being overwritten - // for the recursive function. - // Since we mutate the `Self` object in this, it *must* be read as mutable *first* to make - // sure that the later registration of the `Self` object is pointing at the correct copy - let slf = hm.read_mut_fractal(args[0])?; - let recurse_fn = HandlerFragment::new(slf[1].1, 0); - let seq_addr = slf[0].0; - drop(slf); - hm.register(CLOSURE_ARG_MEM_START + 1, args[0], false)?; - hm.register(CLOSURE_ARG_MEM_START + 2, args[1], false)?; - let seq = hm.read_mut_fractal_by_idx(seq_addr)?; - let curr = seq[0].1; - if curr < seq[1].1 { - seq[0].1 = curr + 1; - hm = recurse_fn.run(hm).await?; - hm = hm.drop_parent()?; - // CANNOT `join` the memory like usual because the nested `recurse` calls have set "future" - // values in the handler and will cause weird behavior. Only transfer the Self mutation and - // the return value between iterations - HandlerMemory::transfer(&mut hm, CLOSURE_ARG_MEM_START, &mut hand_mem, args[2])?; - HandlerMemory::transfer(&mut hm, CLOSURE_ARG_MEM_START + 1, &mut hand_mem, args[0])?; - } else { - hand_mem.init_fractal(args[2])?; - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("error: sequence out-of-bounds"))?; - } - Ok(hand_mem) - }) - }); - cpu!(seqrec => fn(args, hand_mem) { - if args[1] == NOP_ID { - return Err(VMError::InvalidNOP); - } - hand_mem.init_fractal(args[2])?; - hand_mem.push_register(args[2], args[0])?; - hand_mem.push_fixed(args[2], args[1])?; - Ok(()) - }); - - // "Special" opcodes - io!(exitop => fn(args, hand_mem) { - Box::pin(async move { - io::stdout().flush().map_err(VMError::IOError)?; - io::stderr().flush().map_err(VMError::IOError)?; - if let Some(props) = DAEMON_PROPS.get() { - let terminate_body = json!({ - "clusterId": props.clusterId, - "deployToken": props.deployToken, - "daemonMode": true, - }); - post_v1("terminate", terminate_body).await; - }; - std::process::exit(hand_mem.read_fixed(args[0])? as i32); - }) - }); - cpu!(stdoutp => fn(args, hand_mem) { - let out_str = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[0])?)?; - print!("{}", out_str); - Ok(()) - }); - cpu!(stderrp => fn(args, hand_mem) { - let err_str = HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[0])?)?; - eprint!("{}", err_str); - Ok(()) - }); - - // set opcodes use args[0] directly, since the relevant value directly - // fits in i64, and write it to args[2] - cpu!(seti64 => fn(args, hand_mem) { - let data = args[0]; - hand_mem.write_fixed(args[2], data)?; - Ok(()) - }); - cpu!(seti32 => fn(args, hand_mem) { - let data = (args[0] as i32) as i64; - hand_mem.write_fixed(args[2], data)?; - Ok(()) - }); - cpu!(seti16 => fn(args, hand_mem) { - let data = (args[0] as i16) as i64; - hand_mem.write_fixed(args[2], data)?; - Ok(()) - }); - cpu!(seti8 => fn(args, hand_mem) { - let data = (args[0] as i8) as i64; - hand_mem.write_fixed(args[2], data)?; - Ok(()) - }); - cpu!(setf64 => fn(args, hand_mem) { - let data = i64::from_ne_bytes((args[0] as f64).to_ne_bytes()); - hand_mem.write_fixed(args[2], data)?; - Ok(()) - }); - cpu!(setf32 => fn(args, hand_mem) { - let data = i32::from_ne_bytes((args[0] as f32).to_ne_bytes()) as i64; - hand_mem.write_fixed(args[2], data)?; - Ok(()) - }); - cpu!(setbool => fn(args, hand_mem) { - let data = if args[0] == 0 { 0i64 } else { 1i64 }; - hand_mem.write_fixed(args[2], data)?; - Ok(()) - }); - cpu!(setestr => fn(args, hand_mem) { - let empty_str = FractalMemory::new(vec![(usize::MAX, 0)]); - hand_mem.write_fractal(args[2], &empty_str)?; - Ok(()) - }); - - // copy opcodes used for let variable reassignments - cpu!(copyi8 => fn(args, hand_mem) { - let val = hand_mem.read_fixed(args[0])?; - hand_mem.write_fixed(args[2], val)?; - Ok(()) - }); - cpu!(copyi16 => fn(args, hand_mem) { - let val = hand_mem.read_fixed(args[0])?; - hand_mem.write_fixed(args[2], val)?; - Ok(()) - }); - cpu!(copyi32 => fn(args, hand_mem) { - let val = hand_mem.read_fixed(args[0])?; - hand_mem.write_fixed(args[2], val)?; - Ok(()) - }); - cpu!(copyi64 => fn(args, hand_mem) { - let val = hand_mem.read_fixed(args[0])?; - hand_mem.write_fixed(args[2], val)?; - Ok(()) - }); - cpu!(copyvoid => fn(args, hand_mem) { - let val = hand_mem.read_fixed(args[0])?; - hand_mem.write_fixed(args[2], val)?; - Ok(()) - }); - cpu!(copyf32 => fn(args, hand_mem) { - let val = hand_mem.read_fixed(args[0])?; - hand_mem.write_fixed(args[2], val)?; - Ok(()) - }); - cpu!(copyf64 => fn(args, hand_mem) { - let val = hand_mem.read_fixed(args[0])?; - hand_mem.write_fixed(args[2], val)?; - Ok(()) - }); - cpu!(copybool => fn(args, hand_mem) { - let val = hand_mem.read_fixed(args[0])?; - hand_mem.write_fixed(args[2], val)?; - Ok(()) - }); - cpu!(copystr => fn(args, hand_mem) { - let pascal_string = hand_mem.read_fractal(args[0])?; - hand_mem.write_fractal(args[2], &pascal_string)?; - Ok(()) - }); - cpu!(copyarr => fn(args, hand_mem) { - // args = [in_addr, unused, out_addr] - hand_mem.dupe(args[0], args[2])?; - Ok(()) - }); - cpu!(zeroed => fn(args, hand_mem) { - hand_mem.write_fixed(args[2], 0)?; - Ok(()) - }); - - // Trig opcodes - cpu!(lnf64 => fn(args, hand_mem) { - let a = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()); - let out = i64::from_ne_bytes(a.ln().to_ne_bytes()); - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(logf64 => fn(args, hand_mem) { - let a = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()); - let out = i64::from_ne_bytes(a.log10().to_ne_bytes()); - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(sinf64 => fn(args, hand_mem) { - let a = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()); - let out = i64::from_ne_bytes(a.sin().to_ne_bytes()); - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(cosf64 => fn(args, hand_mem) { - let a = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()); - let out = i64::from_ne_bytes(a.cos().to_ne_bytes()); - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(tanf64 => fn(args, hand_mem) { - let a = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()); - let out = i64::from_ne_bytes(a.tan().to_ne_bytes()); - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(asinf64 => fn(args, hand_mem) { - let a = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()); - let out = i64::from_ne_bytes(a.asin().to_ne_bytes()); - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(acosf64 => fn(args, hand_mem) { - let a = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()); - let out = i64::from_ne_bytes(a.acos().to_ne_bytes()); - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(atanf64 => fn(args, hand_mem) { - let a = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()); - let out = i64::from_ne_bytes(a.atan().to_ne_bytes()); - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(sinhf64 => fn(args, hand_mem) { - let a = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()); - let out = i64::from_ne_bytes(a.sinh().to_ne_bytes()); - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(coshf64 => fn(args, hand_mem) { - let a = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()); - let out = i64::from_ne_bytes(a.cosh().to_ne_bytes()); - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - cpu!(tanhf64 => fn(args, hand_mem) { - let a = f64::from_ne_bytes(hand_mem.read_fixed(args[0])?.to_ne_bytes()); - let out = i64::from_ne_bytes(a.tanh().to_ne_bytes()); - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - - // Error, Maybe, Result, Either opcodes - cpu!(error => fn(args, hand_mem) { - hand_mem.register(args[2], args[0], true)?; - Ok(()) - }); - cpu!(refv => fn(args, hand_mem) { - hand_mem.register(args[2], args[0], true)?; - Ok(()) - }); - cpu!(reff => fn(args, hand_mem) { - hand_mem.register(args[2], args[0], false)?; - Ok(()) - }); - cpu!(noerr => fn(args, hand_mem) { - let empty_string = FractalMemory::new(vec![(0, 0)]); - hand_mem.write_fractal(args[2], &empty_string)?; - Ok(()) - }); - cpu!(errorstr => fn(args, hand_mem) { - hand_mem.register(args[2], args[0], true)?; - Ok(()) - }); - cpu!(someM => fn(args, hand_mem) { - hand_mem.init_fractal(args[2])?; - hand_mem.push_fixed(args[2], 1i64)?; - let val_size = hand_mem.read_fixed(args[1])?; - if val_size == 0 { - hand_mem.push_register(args[2], args[0])?; - } else { - let val = hand_mem.read_fixed(args[0])?; - hand_mem.push_fixed(args[2], val)?; - } - Ok(()) - }); - cpu!(noneM => fn(args, hand_mem) { - hand_mem.init_fractal(args[2])?; - hand_mem.push_fixed(args[2], 0i64)?; - Ok(()) - }); - cpu!(isSome => fn(args, hand_mem) { - hand_mem.register_out(args[0], 0, args[2])?; - Ok(()) - }); - cpu!(isNone => fn(args, hand_mem) { - let fractal = hand_mem.read_fractal(args[0])?; - let val = fractal.read_fixed(0)?; - hand_mem.write_fixed(args[2], if val == 0i64 { 1i64 } else { 0i64 })?; - Ok(()) - }); - cpu!(getOrM => fn(args, hand_mem) { - let fractal = hand_mem.read_fractal(args[0])?; - let val = fractal.read_fixed(0)?; - if val == 1i64 { - hand_mem.register_out(args[0], 1, args[2])?; - } else { - if args[1] < 0 { - let val = hand_mem.read_fixed(args[1])?; - hand_mem.write_fixed(args[2], val)?; - } else { - let (data, is_fractal) = hand_mem.read_either(args[1])?; - if is_fractal { - hand_mem.register(args[2], args[1], true)?; - } else { - hand_mem.write_fixed(args[2], data.read_fixed(0)?)?; - } - } - } - Ok(()) - }); - cpu!(getMaybe => fn(args, hand_mem) { - let fractal = hand_mem.read_fractal(args[0])?; - let variant = fractal.read_fixed(0)?; - if variant == 1 { - hand_mem.register_out(args[0], 1, args[2])?; - Ok(()) - } else { - Err(VMError::IllegalAccess) - } - }); - cpu!(okR => fn(args, hand_mem) { - hand_mem.init_fractal(args[2])?; - hand_mem.push_fixed(args[2], 1i64)?; - let val_size = hand_mem.read_fixed(args[1])?; - if val_size == 0 { - hand_mem.push_register(args[2], args[0])?; - } else { - let val = hand_mem.read_fixed(args[0])?; - hand_mem.push_fixed(args[2], val)?; - } - Ok(()) - }); - cpu!(err => fn(args, hand_mem) { - hand_mem.init_fractal(args[2])?; - hand_mem.push_fixed(args[2], 0i64)?; - hand_mem.push_register(args[2], args[0])?; - Ok(()) - }); - cpu!(isOk => fn(args, hand_mem) { - hand_mem.register_out(args[0], 0, args[2])?; - Ok(()) - }); - cpu!(isErr => fn(args, hand_mem) { - let fractal = hand_mem.read_fractal(args[0])?; - let val = fractal.read_fixed(0)?; - hand_mem.write_fixed(args[2], if val == 0i64 { 1i64 } else { 0i64 })?; - Ok(()) - }); - cpu!(getOrR => fn(args, hand_mem) { - let fractal = hand_mem.read_fractal(args[0])?; - let val = fractal.read_fixed(0)?; - if val == 1i64 { - hand_mem.register_out(args[0], 1, args[2])?; - } else { - let (data, is_fractal) = hand_mem.read_either(args[1])?; - if is_fractal { - hand_mem.register(args[2], args[1], true)?; - } else { - hand_mem.write_fixed(args[2], data.read_fixed(0)?)?; - } - } - Ok(()) - }); - cpu!(getOrRS => fn(args, hand_mem) { - let fractal = hand_mem.read_fractal(args[0])?; - let val = fractal.read_fixed(0)?; - if val == 1i64 { - hand_mem.register_out(args[0], 1, args[2])?; - } else { - let f = HandlerMemory::str_to_fractal(&HandlerMemory::fractal_to_string(hand_mem.read_fractal(args[1])?)?); - hand_mem.write_fractal(args[2], &f)?; - } - Ok(()) - }); - cpu!(getR => fn(args, hand_mem) { - let fractal = hand_mem.read_fractal(args[0])?; - let val = fractal.read_fixed(0)?; - if val == 1i64 { - hand_mem.register_out(args[0], 1, args[2])?; - Ok(()) - } else { - Err(VMError::IllegalAccess) - } - }); - cpu!(getErr => fn(args, hand_mem) { - let fractal = hand_mem.read_fractal(args[0])?; - let val = fractal.read_fixed(0)?; - if val == 0i64 { - hand_mem.register_out(args[0], 1, args[2])?; - } else { - let (data, is_fractal) = hand_mem.read_either(args[1])?; - if is_fractal { - hand_mem.register(args[2], args[1], true)?; - } else { - hand_mem.write_fixed(args[2], data.read_fixed(0)?)?; - } - } - Ok(()) - }); - cpu!(resfrom => fn(args, hand_mem) { - // args = [arr_addr, arr_idx_addr, outer_addr] - // a guarded copy of data from an array to a result object - hand_mem.init_fractal(args[2])?; - let fractal = hand_mem.read_fractal(args[1])?; - let val = fractal.read_fixed(0)?; - if val == 0 { - hand_mem.write_fractal(args[2], &fractal)?; - return Ok(()); - } - let inner_addr = fractal.read_fixed(1)? as usize; - let arr = hand_mem.read_fractal(args[0])?; - if arr.len() > inner_addr { - hand_mem.push_fixed(args[2], 1)?; - hand_mem.push_register_out(args[2], &arr, inner_addr)?; - } else { - hand_mem.push_fixed(args[2], 0)?; - hand_mem.push_fractal(args[2], HandlerMemory::str_to_fractal("out-of-bounds access"))?; - } - Ok(()) - }); - cpu!(mainE => fn(args, hand_mem) { - hand_mem.init_fractal(args[2])?; - hand_mem.push_fixed(args[2], 1i64)?; - let val_size = hand_mem.read_fixed(args[1])?; - if val_size == 0 { - hand_mem.push_register(args[2], args[0])?; - } else { - let val = hand_mem.read_fixed(args[0])?; - hand_mem.push_fixed(args[2], val)?; - } - Ok(()) - }); - cpu!(altE => fn(args, hand_mem) { - hand_mem.init_fractal(args[2])?; - hand_mem.push_fixed(args[2], 0i64)?; - let val_size = hand_mem.read_fixed(args[1])?; - if val_size == 0 { - hand_mem.push_register(args[2], args[0])?; - } else { - let val = hand_mem.read_fixed(args[0])?; - hand_mem.push_fixed(args[2], val)?; - } - Ok(()) - }); - cpu!(isMain => fn(args, hand_mem) { - hand_mem.register_out(args[0], 0, args[2])?; - Ok(()) - }); - cpu!(isAlt => fn(args, hand_mem) { - let fractal = hand_mem.read_fractal(args[0])?; - let val = fractal.read_fixed(0)?; - hand_mem.write_fixed(args[2], if val == 0i64 { 1i64 } else { 0i64 })?; - Ok(()) - }); - cpu!(mainOr => fn(args, hand_mem) { - let fractal = hand_mem.read_fractal(args[0])?; - let val = fractal.read_fixed(0)?; - if val == 1i64 { - hand_mem.register_out(args[0], 1, args[2])?; - } else { - let (data, is_fractal) = hand_mem.read_either(args[1])?; - if is_fractal { - hand_mem.register(args[2], args[1], true)?; - } else { - hand_mem.write_fixed(args[2], data.read_fixed(0)?)?; - } - } - Ok(()) - }); - cpu!(altOr => fn(args, hand_mem) { - let fractal = hand_mem.read_fractal(args[0])?; - let val = fractal.read_fixed(0)?; - if val == 0i64 { - hand_mem.register_out(args[0], 1, args[2])?; - } else { - let (data, is_fractal) = hand_mem.read_either(args[1])?; - if is_fractal { - hand_mem.register(args[2], args[1], true)?; - } else { - hand_mem.write_fixed(args[2], data.read_fixed(0)?)?; - } - } - Ok(()) - }); - cpu!(getMain => fn(args, hand_mem) { - let fractal = hand_mem.read_fractal(args[0])?; - let variant = fractal.read_fixed(0)?; - if variant == 1 { - hand_mem.register_out(args[0], 1, args[2])?; - Ok(()) - } else { - Err(VMError::IllegalAccess) - } - }); - cpu!(getAlt => fn(args, hand_mem) { - let fractal = hand_mem.read_fractal(args[0])?; - let variant = fractal.read_fixed(0)?; - if variant == 0 { - hand_mem.register_out(args[0], 1, args[2])?; - Ok(()) - } else { - Err(VMError::IllegalAccess) - } - }); - - cpu!(hashf => fn(args, hand_mem) { - let val = hand_mem.read_fixed(args[0])?; - let mut hasher = XxHash64::with_seed(0xfa57); - hasher.write_i64(val); - let out = i64::from_ne_bytes(hasher.finish().to_ne_bytes()); - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - - cpu!(hashv => fn(args, hand_mem) { - let mut hasher = XxHash64::with_seed(0xfa57); - let addr = args[0]; - if addr < 0 { - // It's a string! - let pascal_string = hand_mem.read_fractal(args[0])?; - let strlen = pascal_string.read_fixed(0)? as f64; - let intlen = 1 + (strlen / 8.0).ceil() as usize; - for i in 0..intlen { - hasher.write_i64(pascal_string.read_fixed(i)?); - } - } else { - let mut stack: Vec = vec![hand_mem.read_fractal(args[0])?]; - while stack.len() > 0 { - let fractal = stack.pop().ok_or(VMError::IllegalAccess)?; - for i in 0..fractal.len() { - let (data, is_fractal) = hand_mem.read_from_fractal(&fractal, i); - if is_fractal { - stack.push(data); - } else { - hasher.write_i64(data.read_fixed(0)?); - } - } - } - } - let out = i64::from_ne_bytes(hasher.finish().to_ne_bytes()); - hand_mem.write_fixed(args[2], out)?; - Ok(()) - }); - - // king opcode - cpu!(emit => fn(args, hand_mem) { - let event = EventEmit { - id: args[0], - payload: HandlerMemory::alloc_payload(args[0], args[1], &hand_mem)?, - }; - let event_tx = EVENT_TX.get().unwrap(); - let event_sent = event_tx.send(event); - if event_sent.is_err() { - eprintln!("Event transmission error"); - std::process::exit(2); - } - Ok(()) - }); - - o -}); - -impl From for &ByteOpcode { - fn from(v: i64) -> Self { - let opc = OPCODES.get(&v); - if opc.is_none() { - panic!( - "Illegal byte opcode {} ({})", - v, - str::from_utf8(&v.to_ne_bytes()).unwrap() - ); - } - return &opc.unwrap(); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - #[should_panic] - fn test_panic_on_invalid_mapping() { - <&ByteOpcode>::from(100); - } -} diff --git a/avm/src/vm/program.rs b/avm/src/vm/program.rs deleted file mode 100644 index 5ffe60f6c..000000000 --- a/avm/src/vm/program.rs +++ /dev/null @@ -1,231 +0,0 @@ -use std::collections::HashMap; - -use once_cell::sync::OnceCell; - -use crate::daemon::ctrl::CONTROL_PORT_EXTENSIONS; -use crate::vm::event::{BuiltInEvents, EventHandler}; -use crate::vm::http::HttpType; -use crate::vm::instruction::Instruction; -use crate::vm::opcode::{opcode_id, ByteOpcode, OPCODES}; - -// Facilitates parsing the alan graph code program -struct BytecodeParser { - // Program counter that tracks which byte is being executed - pc: usize, - // The bytecode of the program being run - bytecode: Vec, -} - -impl BytecodeParser { - // Grabs the next 64 bits (8 bytes) - fn next_64_bits(self: &mut BytecodeParser) -> i64 { - let result = self.bytecode[self.pc]; - self.pc += 1; - return result; - } -} - -#[derive(Debug)] -enum GraphOpcode { - /// event handler declaration - /// "handler:" in ASCII OR 6861 6e64 6c65 723a in hex - HANDLER, - /// statement declaration within handler - /// "lineno:" in ASCII OR 6c69 6e65 6e6f 3a20 in hex - LINENO, - /// custom event declaration - /// "eventdc:" in ASCII OR 6576 656e 7464 633a in hex - CUSTOMEVENT, -} - -impl From for GraphOpcode { - fn from(v: i64) -> Self { - let handler_num: i64 = i64::from_le_bytes([b'h', b'a', b'n', b'd', b'l', b'e', b'r', b':']); - let closure_num: i64 = i64::from_le_bytes([b'c', b'l', b'o', b's', b'u', b'r', b'e', b':']); - let line_num: i64 = i64::from_le_bytes([b'l', b'i', b'n', b'e', b'n', b'o', b':', b' ']); - let custom_num: i64 = i64::from_le_bytes([b'e', b'v', b'e', b'n', b't', b'd', b'd', b':']); - // TODO: Figure out why `match` failed here - if v == handler_num || v == closure_num { - return GraphOpcode::HANDLER; - } else if v == line_num { - return GraphOpcode::LINENO; - } else if v == custom_num { - return GraphOpcode::CUSTOMEVENT; - } else { - panic!("Illegal graph opcode {}", v); - } - } -} - -/// Representation of the alan graph code program as static, global data -#[derive(Debug)] -pub struct Program { - /// Event id to Map of handler id to handler obj - pub(crate) event_handlers: HashMap>, - /// Event id to payload size which is the number of bytes if fixed length type, - /// or -1 if it's a variable length type or 0 if the event is void - pub(crate) event_pls: HashMap, - /// Memory of the program for global variables and string literals - pub(crate) gmem: Vec<(usize, i64)>, - /// The port the http server should use - pub(crate) http_config: Option, -} - -pub static PROGRAM: OnceCell = OnceCell::new(); - -impl Program { - pub fn global() -> &'static Program { - PROGRAM.get().unwrap() - } - - // instantiate handlers and define payload sizes for built-in events - fn load_builtin(self: &mut Program) { - // START - let start = i64::from(BuiltInEvents::START); - self.event_pls.insert(start, 0); - self.event_handlers.insert(start, Vec::new()); - // HTTPCONN - let httpconn = i64::from(BuiltInEvents::HTTPCONN); - self.event_pls.insert(httpconn, 0); - self.event_handlers.insert(httpconn, Vec::new()); - // CTRLPORT - let ctrlport = i64::from(BuiltInEvents::CTRLPORT); - self.event_pls.insert(ctrlport, 0); - self.event_handlers.insert(ctrlport, Vec::new()); - // NOP - // the compiler does not allow explicitly defining handlers for this event - // and it's used internally by the compiler for closures in unused conditional branches - let nop: i64 = i64::from(BuiltInEvents::NOP); - self.event_pls.insert(nop, 0); - self.event_handlers.insert(nop, Vec::with_capacity(0)); - } - - // Parses and safely initializes the alan graph code program as static, global data - pub fn load(bytecode: Vec, http_config: Option) -> Program { - let mut parser = BytecodeParser { pc: 0, bytecode }; - let mut program = Program { - event_handlers: HashMap::new(), - event_pls: HashMap::new(), - gmem: Vec::new(), - http_config, - }; - program.load_builtin(); - // parse agc version - let _agcv = parser.next_64_bits(); - // let bytes = agcv.to_le_bytes(); - // let repr = std::str::from_utf8(&bytes).unwrap(); - // println!("using alan graph code version {}", repr); - // parse size of global memory constants and string literals in bytes - let gms = parser.next_64_bits(); - if gms > 0 && gms % 8 != 0 { - panic!("Global memory is not divisible by 8"); - } - for _ in 0..gms / 8 { - program.gmem.push((std::usize::MAX, parser.next_64_bits())); - } - // instantiate null handler - let mut cur_handler = EventHandler::new(0, 0); - // parse rest of agc through opcodes - while parser.bytecode.len() > parser.pc { - match GraphOpcode::from(parser.next_64_bits()) { - GraphOpcode::CUSTOMEVENT => { - let id = parser.next_64_bits(); - let pls = parser.next_64_bits(); // number of bytes payload consumes - program.event_pls.insert(id, pls); - program.event_handlers.insert(id, Vec::new()); - } - GraphOpcode::HANDLER => { - // save instructions for previously defined handler - if cur_handler.len() > 0 { - let handlers = program - .event_handlers - .get_mut(&cur_handler.event_id) - .unwrap(); - handlers.push(cur_handler); - } - let id = parser.next_64_bits(); - // error if event has not been defined - if !program.event_pls.contains_key(&id) || !program.event_handlers.contains_key(&id) { - eprintln!("Handler for undefined event with id: {}", id); - } - let handler_mem = parser.next_64_bits(); - cur_handler = EventHandler::new(handler_mem, id); - } - GraphOpcode::LINENO => { - let id = parser.next_64_bits(); - let num_deps = parser.next_64_bits(); - let mut dep_ids = Vec::new(); - for _ in 0..num_deps { - dep_ids.push(parser.next_64_bits()); - } - let opcode = <&ByteOpcode>::from(parser.next_64_bits()); - let mut args = Vec::new(); - args.push(parser.next_64_bits()); - args.push(parser.next_64_bits()); - args.push(parser.next_64_bits()); - let ins = Instruction { - id, - opcode, - args, - dep_ids, - }; - cur_handler.add_instruction(ins); - } - } - } - // check in last handler - let handlers = program - .event_handlers - .get_mut(&cur_handler.event_id) - .unwrap(); - handlers.push(cur_handler); - // special logic to auto-listen on the http server if it is being used - if program - .event_handlers - .get(&BuiltInEvents::HTTPCONN.into()) - .unwrap() - .len() - > 0 - { - let start_handlers = program - .event_handlers - .get_mut(&BuiltInEvents::START.into()) - .unwrap(); - if start_handlers.len() == 0 { - // Create a new handler that just executes `httplsn` - let mut listen_handler = EventHandler::new(1, BuiltInEvents::START.into()); - listen_handler.add_instruction(Instruction { - id: 0, - opcode: OPCODES.get(&opcode_id("httplsn")).unwrap(), - args: vec![0, 0, 0], - dep_ids: vec![], - }); - start_handlers.push(listen_handler); - } else { - // Append the listen opcode to the end of the first start handler found - let start_handler = &mut start_handlers[0]; - let start_handler_fragment_idx = start_handler.fragments.len() - 1; - let last_frag = &mut start_handler.fragments[start_handler_fragment_idx]; - let last_id = last_frag[last_frag.len() - 1].id; - start_handler.add_instruction(Instruction { - id: i64::MAX, // That shouldn't collide with anything - opcode: OPCODES.get(&opcode_id("httplsn")).unwrap(), - args: vec![0, 0, 0], - dep_ids: vec![last_id], // To make sure this is the last instruction run (usually) - }); - } - } - // detection of control port extensions, at least the logic is contained elsehwere! - CONTROL_PORT_EXTENSIONS - .set( - program - .event_handlers - .get(&BuiltInEvents::CTRLPORT.into()) - .unwrap() - .len() - > 0, - ) - .unwrap(); - return program; - } -} diff --git a/avm/src/vm/protos/.gitignore b/avm/src/vm/protos/.gitignore deleted file mode 100644 index 848e41e05..000000000 --- a/avm/src/vm/protos/.gitignore +++ /dev/null @@ -1 +0,0 @@ -HandlerMemory.rs \ No newline at end of file diff --git a/avm/src/vm/protos/HandlerMemory.proto b/avm/src/vm/protos/HandlerMemory.proto deleted file mode 100644 index c298a0507..000000000 --- a/avm/src/vm/protos/HandlerMemory.proto +++ /dev/null @@ -1,29 +0,0 @@ -syntax = "proto3"; - -message HandlerMemory { - // mems = 2 - message MemBlock { - uint64 mem_type = 1; - sfixed64 mem_val = 2; - } - message Mems { - repeated MemBlock mem = 1; - } - // addr = 3 - message MemSpaceStruct { - uint64 first = 1; - uint64 second = 2; - } - message MemSpace { - optional MemSpaceStruct memspacestruct = 1; - } - message Addr { - repeated MemSpace mem_space = 1; - repeated MemSpace mem_space_args = 2; - } - // Memory handler - optional HandlerMemory parent = 1; - repeated Mems mems = 2; - Addr addr = 3; - uint64 mem_addr = 4; -} diff --git a/avm/src/vm/protos/mod.rs b/avm/src/vm/protos/mod.rs deleted file mode 100644 index 2e2c47610..000000000 --- a/avm/src/vm/protos/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[rustfmt::skip] -pub mod HandlerMemory; diff --git a/avm/src/vm/run.rs b/avm/src/vm/run.rs deleted file mode 100644 index e20d1db91..000000000 --- a/avm/src/vm/run.rs +++ /dev/null @@ -1,126 +0,0 @@ -use std::convert::TryInto; -use std::fs::File; -use std::io::Read; -use std::path::Path; -use std::sync::Arc; - -use byteorder::ByteOrder; -use byteorder::LittleEndian; -use flate2::read::GzDecoder; -use once_cell::sync::OnceCell; -use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; - -use crate::vm::event::{BuiltInEvents, EventEmit, HandlerFragment}; -use crate::vm::http::{HttpConfig, HttpType}; -use crate::vm::memory::HandlerMemory; -use crate::vm::program::{Program, PROGRAM}; -use crate::vm::{VMError, VMResult}; - -pub static EVENT_TX: OnceCell> = OnceCell::new(); - -pub struct VM { - /// chan for the events queue - event_tx: UnboundedSender, - event_rx: UnboundedReceiver, -} - -impl VM { - pub fn new() -> VMResult { - let (event_tx, event_rx) = unbounded_channel(); - // Hackery relying on VM being a singleton :( TODO: Refactor such that event_tx is accessible - // outside of the opcodes and instruction scheduler for http and future IO sources - EVENT_TX - .set(event_tx.clone()) - .map_err(|_| VMError::AlreadyRunning)?; - return Ok(VM { event_tx, event_rx }); - } - - pub fn add(self: &mut VM, event: EventEmit) -> VMResult<()> { - self.event_tx.send(event).map_err(|_| VMError::ShutDown) - } - - fn sched_event(self: &mut VM, event: EventEmit) -> VMResult<()> { - // skip NOP event always, but it's not an error to receive it. - if event.id == i64::from(BuiltInEvents::NOP) { - return Ok(()); - } - - // schedule 1st fragment of each handler of this event - let handlers = Program::global() - .event_handlers - .get(&event.id) - .ok_or(VMError::EventNotDefined(event.id))?; - for (i, hand) in handlers.iter().enumerate() { - // first fragment of this handler - let frag = HandlerFragment::new(event.id, i); - let payload = match event.payload.as_ref() { - Some(upstream_mem) => Some(Arc::new(HandlerMemory::clone(upstream_mem))), - None => None, - }; - // memory frag representing the memory for each handler call - let hand_mem = HandlerMemory::new(payload, hand.mem_req)?; - frag.spawn(hand_mem); - } - Ok(()) - } - - // run the vm backed by an event loop - pub async fn run(self: &mut VM) -> VMResult<()> { - while let Some(event) = self.event_rx.recv().await { - self.sched_event(event)?; - } - Ok(()) - } -} - -pub async fn run_file(fp: &str, delete_after_load: bool) -> VMResult<()> { - let filepath = Path::new(fp); - let mut file = File::open(filepath).map_err(|_| VMError::FileNotFound(fp.to_string()))?; - let metadata = file.metadata().map_err(|_| { - VMError::InvalidFile(format!( - "Unable to get file metadata for file at {}. Are you sure it's a file?", - fp - )) - })?; - let fsize = metadata - .len() - .try_into() - .map_err(|_| VMError::InvalidFile(format!("{} is a very big file on a 32-bit system", fp)))?; - // Test if it's gzip compressed - // TODO: new_uninit is nightly-only right now, we can use it to do this and achieve gains: - // let mut bytes = Box::new_uninit_slice(fsize); - // file.read_exact(&mut bytes).or(...)?; - let mut bytes = Vec::with_capacity(fsize); - file.read_to_end(&mut bytes).map_err(VMError::IOError)?; - let mut gz = GzDecoder::new(bytes.as_slice()); - if gz.header().is_some() { - let mut uncompressed = Vec::with_capacity(fsize * 2); - let _bytes_read = gz - .read_to_end(&mut uncompressed) - .map_err(VMError::IOError)?; - bytes = uncompressed; - } - let bytecode = bytes - .as_slice() - .chunks(8) - .map(LittleEndian::read_i64) - .collect::>(); - if delete_after_load { - std::fs::remove_file(Path::new(fp)).map_err(VMError::IOError)?; - } - run(bytecode, Some(HttpType::HTTP(HttpConfig { port: 8000 }))).await -} - -pub async fn run(bytecode: Vec, http_config: Option) -> VMResult<()> { - let program = Program::load(bytecode, http_config); - PROGRAM - .set(program) - .map_err(|_| VMError::Other("A program is already loaded".to_string()))?; - let mut vm = VM::new()?; - const START: EventEmit = EventEmit { - id: BuiltInEvents::START as i64, - payload: None, - }; - vm.add(START)?; - vm.run().await -} diff --git a/avm/src/vm/telemetry.rs b/avm/src/vm/telemetry.rs deleted file mode 100644 index 583ba0da7..000000000 --- a/avm/src/vm/telemetry.rs +++ /dev/null @@ -1,47 +0,0 @@ -use hyper::{client::Client, Body, Request}; -use hyper_rustls::HttpsConnectorBuilder; - -use serde_json::json; - -const AMPLITUDE_API_KEY: &str = "ae20dafe801eddecf308c6ce643e19d1"; -const AMPLITUDE_URL: &str = "https://api.amplitude.com/2/httpapi"; -// https://doc.rust-lang.org/cargo/reference/environment-variables.html -const ALAN_VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION"); -const NO_TELEMETRY: Option<&'static str> = option_env!("ALAN_TELEMETRY_OFF"); -const OS: &str = std::env::consts::OS; - -pub async fn log(event: &str) { - let no_telemetry = NO_TELEMETRY.unwrap_or("false") == "true"; - if no_telemetry { - return; - } - let body = json!({ - "api_key": AMPLITUDE_API_KEY, - "events": [ - { - "user_id": "alancli", - "event_type": event, - "app_version": ALAN_VERSION.unwrap(), - "os_name": OS, - } - ] - }); - let client = Client::builder().build::<_, Body>( - HttpsConnectorBuilder::new() - .with_native_roots() - .https_or_http() - .enable_all_versions() - .build(), - ); - if client - .request( - Request::post(AMPLITUDE_URL) - .body(body.to_string().into()) - .unwrap(), - ) - .await - .is_ok() - { - // do nothing - } -} diff --git a/bdd/.shellspec b/bdd/.shellspec deleted file mode 100644 index d567ecf97..000000000 --- a/bdd/.shellspec +++ /dev/null @@ -1,12 +0,0 @@ ---require spec_helper - -## Default kcov (coverage) options -# --kcov-options "--include-path=. --path-strip-level=1" -# --kcov-options "--include-pattern=.sh" -# --kcov-options "--exclude-pattern=/.shellspec,/spec/,/coverage/,/report/" - -## Example: Include script "myprog" with no extension -# --kcov-options "--include-pattern=.sh,myprog" - -## Example: Only specified files/directories -# --kcov-options "--include-pattern=myprog,/lib/" diff --git a/bdd/README.md b/bdd/README.md deleted file mode 100644 index 4139edd1d..000000000 --- a/bdd/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# bdd - -The BDD test suite for the Alan Programming Language. - -As the tools for Alan are written in a few different languages, the test suite is a collection of shell scripts using [shellspec](https://shellspec.info/). - -Tests are placed in the `/bdd/spec/` directory and run in sorting order, so all tests are prefixed with a number to guarantee a predictable ordering. - -Once the Alan Programming Language reaches feature complete on all planned features and ticks over to 1.0.0, Alan will be versioned according to a system we have defined as "Semantic BDD," described below: - -## Semantic BDD - -Semantic versioning is meant to tell the developer who is using it which versions could break their existing code if they update, but users eventually stop trusting it because they library developer makes a mistake in versioning, or "doesn't like" the rapidly rising major version numbers and just doesn't increment it every time they should. So, developers shouldn't be in charge of the semantic versioning. Automated analysis should be. We want to move from Semantic Versioning to Semantic BDD. - -Semantic BDD will require BDD tests to describe the supported features. If a change makes no change to the tests, then it is a patch. If a change adds new tests then it's a minor update. If it changes the results of prior testing it's a major update. That last part is determined by running the BDD test currently registered for the library and marking it as major if the old one fails and the new one succeeds. You can't publish if these special BDD tests fail. The version of your library is not up to you, anymore. - -The publishing flow will expect a `sembdd` directory or `sembdd.ln` file. The publishing tool would perform the updating of major/minor/patch based on the results of this file and the file from the previously published version. If there is no such file, the major version is 0, and the minor version increments each time (so patch is also always 0). Once the package transitions from not having sembdd to having it, it runs it and will block publishing if sembdd fails or returns zero successes (to avoid a potential workaround of an empty sembdd file), otherwise it performs the major version bump to 1.0.0. - -After that, we a history of sembdd files through checksums and it will use the following algorithm to compares the two sembdd declarations: - -1. If they are identical (the sembdd.ln file or all files in the sembdd directory have the same checksums) then it does a patch increment after the test succeeds. -2. If they are different, then it runs both versions. If they both succeed, the minor version is bumped. If the old one fails and the new one succeeds, the major version is bumped. - -With a centralized package management flow this can be enforced because you can't get into the registry without running these tests, we could potentially run them ourselves instead of letting them run locally (where a weirdly devious developer could try to fake it), and we can compare our record of expected versions in the publish branch versus what's in their repository and freeze things if it doesn't match the expected value (or have tarballs with bad signatures). - -## License - -MIT \ No newline at end of file diff --git a/bdd/bdd.sh b/bdd/bdd.sh deleted file mode 100755 index 7c467459a..000000000 --- a/bdd/bdd.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash - -# Implement readlink which does not work on mac os https://stackoverflow.com/a/1116890 -# This gives us flexibility to change the Java code and recompile a jar that we can test via `./alan` -# without blowing away our stable, globally-installed version -TARGET_FILE=$0 - -ORIG_DIR=`pwd -P` - -cd `dirname $TARGET_FILE` -TARGET_FILE=`basename $TARGET_FILE` - -# Iterate down a (possible) chain of symlinks -while [ -L "$TARGET_FILE" ] -do - TARGET_FILE=`readlink $TARGET_FILE` - cd `dirname $TARGET_FILE` - TARGET_FILE=`basename $TARGET_FILE` -done - -# Compute the canonicalized name by finding the physical path -# for the directory we're in and appending the target file. -PHYS_DIR=`pwd -P` - -# Restore the original directory so the script referenced can be found -# cd $ORIG_DIR - -PATH="${PHYS_DIR}:${PHYS_DIR}/../shellspec:${PHYS_DIR}/../node_modules/.bin:${PHYS_DIR}/../avm/target/release:${PATH}" -export PATH - -# turn off telemetry -ALAN_TELEMETRY_OFF="true" -export ALAN_TELEMETRY_OFF - -if [ "$1" ] - then - # Run a single test file if provided - shellspec -s /bin/bash "${ORIG_DIR}/${1}" - else - if command -v nproc - then - export NPROC=`nproc` - else - export NPROC=`sysctl -n hw.logicalcpu` - fi - ls ${ORIG_DIR}/bdd/spec/*_spec.sh | xargs -P ${NPROC} -I % shellspec -s /bin/bash % -fi - -exit $? diff --git a/bdd/build_tools.sh b/bdd/build_tools.sh deleted file mode 100644 index 8e90643c8..000000000 --- a/bdd/build_tools.sh +++ /dev/null @@ -1,86 +0,0 @@ -sourceToFile() { - mkdir -p test_$$ - echo "$2" > test_$$/$1 -} - -sourceToTemp() { - mkdir -p test_$$ - echo "$1" > test_$$/temp.ln -} - -lnn_sourceToTemp() { - mkdir -p test_$$ - echo "$1" > test_$$/temp.lnn -} - -tempToAmm() { - alan compile test_$$/temp.ln test_$$/temp.amm 1>/dev/null -} - -lnn_tempToAmm() { - alan compile test_$$/temp.lnn test_$$/temp.amm 1>/dev/null -} - -sourceToAmm() { - sourceToTemp "$1" - tempToAmm -} - -lnn_sourceToAmm() { - lnn_sourceToTemp "$1" - lnn_tempToAmm -} - -tempToAgc() { - alan compile test_$$/temp.amm test_$$/temp.agc 1>/dev/null -} - -sourceToAgc() { - sourceToTemp "$1" - tempToAgc -} - -tempToJs() { - alan compile test_$$/temp.amm test_$$/temp.js 1>/dev/null -} - -sourceToJs() { - sourceToTemp "$1" - tempToJs -} - -lnn_sourceToJs() { - lnn_sourceToTemp "$1" - tempToJs -} - -sourceToAll() { - sourceToTemp "$1" - tempToAmm - tempToAgc - tempToJs -} - -lnn_sourceToAll() { - lnn_sourceToTemp "$1" - lnn_tempToAmm - tempToAgc - tempToJs -} - -test_js() { - node test_$$/temp.js -} - -test_agc() { - alan run test_$$/temp.agc -} - -cleanFile() { - rm -f "test_$$/$1" -} - -cleanTemp() { - rm -rf test_$$ -} - diff --git a/bdd/spec/001_event_lnn_spec.sh b/bdd/spec/001_event_lnn_spec.sh deleted file mode 100644 index a39525095..000000000 --- a/bdd/spec/001_event_lnn_spec.sh +++ /dev/null @@ -1,121 +0,0 @@ -Include build_tools.sh - -Describe "Events" - Describe "normal exit code" - before() { - lnn_sourceToAll " - from @std/app import start, exit - - on start { emit exit 0; } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The status should eq "0" - End - - It "runs agc" - When run test_agc - The status should eq "0" - End - End - - Describe "error exit code" - before() { - lnn_sourceToAll " - from @std/app import start, exit - - on start { emit exit 1; } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The status should eq "1" - End - - It "runs agc" - # Works because little endian "automatically" coerces to the right value if you can just trim - When run test_agc - The status should eq "1" - End - End - - Describe "no global memory exit code" - before() { - lnn_sourceToAll " - import @std/app - - on app.start { - let x: int64 = 0; - emit app.exit x.toInt8(); - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The status should eq "0" - End - - It "runs agc" - When run test_agc - The status should eq "0" - End - End - - Describe "passing integers from global memory" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - - event aNumber: int64 - - on aNumber fn(num: int64) { - ('I got a number! ' + num.toString()).print(); - emit exit 0; - } - - on start { - emit aNumber 5; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - INTOUTPUT="I got a number! 5" - - It "runs js" - When run test_js - The output should eq "$INTOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$INTOUTPUT" - End - End -End diff --git a/bdd/spec/001_event_spec.sh b/bdd/spec/001_event_spec.sh deleted file mode 100644 index f23d3a7e5..000000000 --- a/bdd/spec/001_event_spec.sh +++ /dev/null @@ -1,124 +0,0 @@ -Include build_tools.sh - -Describe "Events" - Describe "normal exit code" - before() { - sourceToAll " - from @std/app import start, exit - - on start { emit exit 0; } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The status should eq "0" - End - - It "runs agc" - When run test_agc - The status should eq "0" - End - End - - Describe "error exit code" - before() { - sourceToAll " - from @std/app import start, exit - - on start { emit exit 1; } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The status should eq "1" - End - - It "runs agc" - # Works because little endian "automatically" coerces to the right value if you can just trim - When run test_agc - The status should eq "1" - End - End - - Describe "no global memory exit code" - before() { - sourceToAll " - import @std/app - - on app.start { - let x: int64 = 0; - emit app.exit x; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The status should eq "0" - End - - It "runs agc" - When run test_agc - The status should eq "0" - End - End - - Describe "passing integers from global memory" - before() { - # TODO: sourceToAll - sourceToTemp " - from @std/app import start, print, exit - - event aNumber: int64; - - on aNumber fn(num: int64) { - print('I got a number! ' + num.toString()); - emit exit 0; - } - - on start { - emit aNumber 5; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - INTOUTPUT="I got a number! 5" - - It "runs js" - Pending integer-event-arg-from-global-memory - When run test_js - The output should eq "$INTOUTPUT" - End - - It "runs agc" - Pending integer-event-arg-from-global-memory - When run test_agc - The output should eq "$INTOUTPUT" - End - End -End diff --git a/bdd/spec/002_printing_lnn_spec.sh b/bdd/spec/002_printing_lnn_spec.sh deleted file mode 100644 index 39eb13b02..000000000 --- a/bdd/spec/002_printing_lnn_spec.sh +++ /dev/null @@ -1,60 +0,0 @@ -Include build_tools.sh - -Describe "Printing" - Describe "print function" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - print('Hello, World'); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "Hello, World" - End - - It "runs agc" - When run test_agc - The output should eq "Hello, World" - End - End - - Describe "stdout event" - before() { - lnn_sourceToAll " - from @std/app import start, stdout, exit - on start { - emit stdout 'Hello, World'; - wait(10); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "Hello, World" - End - - It "runs agc" - When run test_agc - The output should eq "Hello, World" - End - End -End diff --git a/bdd/spec/002_printing_spec.sh b/bdd/spec/002_printing_spec.sh deleted file mode 100644 index 534922934..000000000 --- a/bdd/spec/002_printing_spec.sh +++ /dev/null @@ -1,60 +0,0 @@ -Include build_tools.sh - -Describe "Printing" - Describe "print function" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print('Hello, World'); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "Hello, World" - End - - It "runs agc" - When run test_agc - The output should eq "Hello, World" - End - End - - Describe "stdout event" - before() { - sourceToAll " - from @std/app import start, stdout, exit - on start { - emit stdout 'Hello, World'; - wait(10); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "Hello, World" - End - - It "runs agc" - When run test_agc - The output should eq "Hello, World" - End - End -End diff --git a/bdd/spec/003_math_lnn_spec.sh b/bdd/spec/003_math_lnn_spec.sh deleted file mode 100644 index ccbeb23ca..000000000 --- a/bdd/spec/003_math_lnn_spec.sh +++ /dev/null @@ -1,1458 +0,0 @@ -Include build_tools.sh - -Describe "Basic Math" - Describe "int8 (not default)" - Describe "addition" - before() { - lnn_sourceToAll " - from @std/app import start, exit - on start { emit exit add(1, 2).getOr(0); } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The status should eq "3" - End - - It "runs agc" - When run test_agc - The status should eq "3" - End - End - - Describe "subtraction" - before() { - lnn_sourceToAll " - from @std/app import start, exit - on start { emit exit sub(2, 1).getOr(0); } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The status should eq "1" - End - - It "runs agc" - When run test_agc - The status should eq "1" - End - End - - Describe "multiplication" - before() { - lnn_sourceToAll " - from @std/app import start, exit - on start { emit exit mul(2, 1).getOr(0); } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The status should eq "2" - End - - It "runs agc" - When run test_agc - The status should eq "2" - End - End - - Describe "division" - before() { - lnn_sourceToAll " - from @std/app import start, exit - on start { emit exit div(6, 2).getOr(0); } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The status should eq "3" - End - - It "runs agc" - When run test_agc - The status should eq "3" - End - End - - Describe "modulus" - before() { - lnn_sourceToAll " - from @std/app import start, exit - on start { emit exit mod(6, 4); } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The status should eq "2" - End - - It "runs agc" - When run test_agc - The status should eq "2" - End - End - - Describe "exponentiation" - before() { - lnn_sourceToAll " - from @std/app import start, exit - on start { emit exit pow(6, 2).getOr(0); } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The status should eq "36" - End - - It "runs agc" - When run test_agc - The status should eq "36" - End - End - - Describe "minimum" - before() { - sourceToTemp " - from @std/app import start, print, exit - on start { - min(3.toInt8(), 5.toInt8()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - Pending conditionals - When run test_js - The output should eq "3" - End - - It "runs agc" - Pending conditionals - When run test_agc - The output should eq "3" - End - End - - Describe "maximum" - before() { - sourceToTemp " - from @std/app import start, print, exit - on start { - max(3.toInt8(), 5.toInt8()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - Pending conditionals - When run test_js - The output should eq "5" - End - - It "runs agc" - Pending conditionals - When run test_agc - The output should eq "5" - End - End - End - - Describe "int16 (not default)" - Describe "addition" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - print(add(1.toInt16(), 2).getOr(0)); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "subtraction" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - print(sub(2.toInt16(), 1).getOr(0)); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "1" - End - - It "runs agc" - When run test_agc - The output should eq "1" - End - End - - Describe "multiplication" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - print(mul(2.toInt16(), 1).getOr(0)); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "2" - End - - It "runs agc" - When run test_agc - The output should eq "2" - End - End - - Describe "division" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - print(div(6.toInt16(), 2).getOr(0)); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "modulus" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - print(mod(6.toInt16(), 4)); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "2" - End - - It "runs agc" - When run test_agc - The output should eq "2" - End - End - - Describe "exponentiation" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - print(pow(6.toInt16(), 2).getOr(0)); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "36" - End - - It "runs agc" - When run test_agc - The output should eq "36" - End - End - - Describe "minimum" - before() { - sourceToTemp " - from @std/app import start, print, exit - on start { - min(3.toInt16(), 5.toInt16()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - Pending conditionals - When run test_js - The output should eq "3" - End - - It "runs agc" - Pending conditionals - When run test_agc - The output should eq "3" - End - End - - Describe "maximum" - before() { - sourceToTemp " - from @std/app import start, print, exit - on start { - max(3.toInt16(), 5.toInt16()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - Pending conditionals - When run test_js - The output should eq "5" - End - - It "runs agc" - Pending conditionals - When run test_agc - The output should eq "5" - End - End - End - - Describe "int32 (not default)" - Describe "addition" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - add(1.toInt32(), 2).getOr(0).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "subtraction" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - sub(2.toInt32(), 1).getOr(0).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "1" - End - - It "runs agc" - When run test_agc - The output should eq "1" - End - End - - Describe "multiplication" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - mul(2.toInt32(), 1).getOr(0).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "2" - End - - It "runs agc" - When run test_agc - The output should eq "2" - End - End - - Describe "division" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - div(6.toInt32(), 2).getOr(0).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "modulus" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - mod(6.toInt32(), 4).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "2" - End - - It "runs agc" - When run test_agc - The output should eq "2" - End - End - - Describe "exponentiation" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - pow(6.toInt32(), 2).getOr(0).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "36" - End - - It "runs agc" - When run test_agc - The output should eq "36" - End - End - - Describe "minimum" - before() { - sourceToTemp " - from @std/app import start, print, exit - on start { - min(3.toInt32(), 5.toInt32()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - Pending conditionals - When run test_js - The output should eq "3" - End - - It "runs agc" - Pending conditionals - When run test_agc - The output should eq "3" - End - End - - Describe "maximum" - before() { - sourceToTemp " - from @std/app import start, print, exit - on start { - max(3.toInt32(), 5.toInt32()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - Pending conditionals - When run test_js - The output should eq "5" - End - - It "runs agc" - Pending conditionals - When run test_agc - The output should eq "5" - End - End - End - - Describe "int64 (default)" - Describe "addition" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - print((1 + 2) || 0); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "subtraction" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - print((2 - 1) || 0); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "1" - End - - It "runs agc" - When run test_agc - The output should eq "1" - End - End - - Describe "multiplication" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - print((2 * 1) || 0); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "2" - End - - It "runs agc" - When run test_agc - The output should eq "2" - End - End - - Describe "division" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - print((6 / 2) || 0); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "modulus" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - print(6 % 4); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "2" - End - - It "runs agc" - When run test_agc - The output should eq "2" - End - End - - Describe "exponentiation" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - print((6 ** 2) || 0); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "36" - End - - It "runs agc" - When run test_agc - The output should eq "36" - End - End - - Describe "minimum" - before() { - sourceToTemp " - from @std/app import start, print, exit - on start { - min(3, 5).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - Pending conditionals - When run test_js - The output should eq "3" - End - - It "runs agc" - Pending conditionals - When run test_agc - The output should eq "3" - End - End - - Describe "maximum" - before() { - sourceToTemp " - from @std/app import start, print, exit - on start { - max(3.toInt64(), 5.toInt64()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - Pending conditionals - When run test_js - The output should eq "5" - End - - It "runs agc" - Pending conditionals - When run test_agc - The output should eq "5" - End - End - End - - Describe "float32 (not default)" - Describe "addition" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - print((toFloat32(1) + 2) || 0.0); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "subtraction" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - print((toFloat32(2) - 1) || 0.0); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "1" - End - - It "runs agc" - When run test_agc - The output should eq "1" - End - End - - Describe "multiplication" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - print((toFloat32(2) * 1) || 0.0); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "2" - End - - It "runs agc" - When run test_agc - The output should eq "2" - End - End - - Describe "division" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - print((toFloat32(6) / 2) || 0.0); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "sqrt" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - print(sqrt(toFloat32(36))); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "6" - End - - It "runs agc" - When run test_agc - The output should eq "6" - End - End - - Describe "exponentiation" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - print((toFloat32(6) ** 2) || 0.0); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "36" - End - - It "runs agc" - When run test_agc - The output should eq "36" - End - End - - Describe "minimum" - before() { - sourceToTemp " - from @std/app import start, print, exit - on start { - min(3.toFloat32(), 5.toFloat32()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - Pending conditionals - When run test_js - The output should eq "3" - End - - It "runs agc" - Pending conditionals - When run test_agc - The output should eq "3" - End - End - - Describe "maximum" - before() { - sourceToTemp " - from @std/app import start, print, exit - on start { - max(3.toFloat32(), 5.toFloat32()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - Pending conditionals - When run test_js - The output should eq "5" - End - - It "runs agc" - Pending conditionals - When run test_agc - The output should eq "5" - End - End - End - - Describe "float64 (default)" - Describe "addition" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - ((1.0 + 2.0) || 0.0).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "subtraction" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - ((2.0 - 1.0) || 0.0).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "1" - End - - It "runs agc" - When run test_agc - The output should eq "1" - End - End - - Describe "multiplication" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - ((2.0 * 1.0) || 0.0).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "2" - End - - It "runs agc" - When run test_agc - The output should eq "2" - End - End - - Describe "division" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - ((6.0 / 2.0) || 0.0).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "sqrt" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - sqrt(36.0).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "6" - End - - It "runs agc" - When run test_agc - The output should eq "6" - End - End - - Describe "exponentiation" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - ((6.0 ** 2.0) || 0.0).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "36" - End - - It "runs agc" - When run test_agc - The output should eq "36" - End - End - - Describe "minimum" - before() { - sourceToTemp " - from @std/app import start, print, exit - on start { - min(3.toFloat64(), 5.toFloat64()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - Pending conditionals - When run test_js - The output should eq "3" - End - - It "runs agc" - Pending conditionals - When run test_agc - The output should eq "3" - End - End - - Describe "maximum" - before() { - sourceToTemp " - from @std/app import start, print, exit - on start { - max(3.toFloat64(), 5.toFloat64()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - Pending conditionals - When run test_js - The output should eq "5" - End - - It "runs agc" - Pending conditionals - When run test_agc - The output should eq "5" - End - End - End - - Describe "grouping" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - on start { - print((2 / (3)) || 0); - print((3 / (1 + 2)) || 0); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "0 -1" - End - - It "runs agc" - When run test_agc - The output should eq "0 -1" - End - End - - Describe "string" - Describe "minimum" - before() { - sourceToTemp " - from @std/app import start, print, exit - on start { - min(3.toString(), 5.toString()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - Pending conditionals - When run test_js - The output should eq "3" - End - - It "runs agc" - Pending conditionals - When run test_agc - The output should eq "3" - End - End - - Describe "maximum" - before() { - sourceToTemp " - from @std/app import start, print, exit - on start { - max(3.toString(), 5.toString()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - Pending conditionals - When run test_js - The output should eq "5" - End - - It "runs agc" - Pending conditionals - When run test_agc - The output should eq "5" - End - End - End -End diff --git a/bdd/spec/003_math_spec.sh b/bdd/spec/003_math_spec.sh deleted file mode 100644 index 7c248271f..000000000 --- a/bdd/spec/003_math_spec.sh +++ /dev/null @@ -1,1430 +0,0 @@ -Include build_tools.sh - -Describe "Basic Math" - Describe "int8 (not default)" - Describe "addition" - before() { - sourceToAll " - from @std/app import start, exit - on start { emit exit add(toInt8(1), toInt8(2)).getOrExit(); } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The status should eq "3" - End - - It "runs agc" - When run test_agc - The status should eq "3" - End - End - - Describe "subtraction" - before() { - sourceToAll " - from @std/app import start, exit - on start { emit exit sub(toInt8(2), toInt8(1)).getOrExit(); } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The status should eq "1" - End - - It "runs agc" - When run test_agc - The status should eq "1" - End - End - - Describe "multiplication" - before() { - sourceToAll " - from @std/app import start, exit - on start { emit exit mul(toInt8(2), toInt8(1)).getOrExit(); } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The status should eq "2" - End - - It "runs agc" - When run test_agc - The status should eq "2" - End - End - - Describe "division" - before() { - sourceToAll " - from @std/app import start, exit - on start { emit exit div(toInt8(6), toInt8(2)).getOrExit(); } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The status should eq "3" - End - - It "runs agc" - When run test_agc - The status should eq "3" - End - End - - Describe "modulus" - before() { - sourceToAll " - from @std/app import start, exit - on start { emit exit mod(toInt8(6), toInt8(4)); } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The status should eq "2" - End - - It "runs agc" - When run test_agc - The status should eq "2" - End - End - - Describe "exponentiation" - before() { - sourceToAll " - from @std/app import start, exit - on start { emit exit pow(toInt8(6), toInt8(2)).getOrExit(); } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The status should eq "36" - End - - It "runs agc" - When run test_agc - The status should eq "36" - End - End - - Describe "minimum" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - min(3.toInt8(), 5.toInt8()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "maximum" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - max(3.toInt8(), 5.toInt8()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "5" - End - - It "runs agc" - When run test_agc - The output should eq "5" - End - End - End - - Describe "int16 (not default)" - Describe "addition" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(add(toInt16(1), toInt16(2))); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "subtraction" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(sub(toInt16(2), toInt16(1))); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "1" - End - - It "runs agc" - When run test_agc - The output should eq "1" - End - End - - Describe "multiplication" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(mul(toInt16(2), toInt16(1))); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "2" - End - - It "runs agc" - When run test_agc - The output should eq "2" - End - End - - Describe "division" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(div(toInt16(6), toInt16(2))); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "modulus" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(mod(toInt16(6), toInt16(4))); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "2" - End - - It "runs agc" - When run test_agc - The output should eq "2" - End - End - - Describe "exponentiation" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(pow(toInt16(6), toInt16(2))); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "36" - End - - It "runs agc" - When run test_agc - The output should eq "36" - End - End - - Describe "minimum" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - min(3.toInt16(), 5.toInt16()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "maximum" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - max(3.toInt16(), 5.toInt16()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "5" - End - - It "runs agc" - When run test_agc - The output should eq "5" - End - End - End - - Describe "int32 (not default)" - Describe "addition" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - add(1.toInt32(), 2.toInt32()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "subtraction" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - sub(2.toInt32(), 1.toInt32()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "1" - End - - It "runs agc" - When run test_agc - The output should eq "1" - End - End - - Describe "multiplication" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - mul(2.toInt32(), 1.toInt32()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "2" - End - - It "runs agc" - When run test_agc - The output should eq "2" - End - End - - Describe "division" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - div(6.toInt32(), 2.toInt32()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "modulus" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - mod(6.toInt32(), 4.toInt32()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "2" - End - - It "runs agc" - When run test_agc - The output should eq "2" - End - End - - Describe "exponentiation" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - pow(6.toInt32(), 2.toInt32()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "36" - End - - It "runs agc" - When run test_agc - The output should eq "36" - End - End - - Describe "minimum" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - min(3.toInt32(), 5.toInt32()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "maximum" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - max(3.toInt32(), 5.toInt32()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "5" - End - - It "runs agc" - When run test_agc - The output should eq "5" - End - End - End - - Describe "int64 (default)" - Describe "addition" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(1 + 2); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "subtraction" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(2 - 1); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "1" - End - - It "runs agc" - When run test_agc - The output should eq "1" - End - End - - Describe "multiplication" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(2 * 1); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "2" - End - - It "runs agc" - When run test_agc - The output should eq "2" - End - End - - Describe "division" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(6 / 2); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "modulus" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(6 % 4); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "2" - End - - It "runs agc" - When run test_agc - The output should eq "2" - End - End - - Describe "exponentiation" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(6 ** 2); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "36" - End - - It "runs agc" - When run test_agc - The output should eq "36" - End - End - - Describe "minimum" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - min(3, 5).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "maximum" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - max(3.toInt64(), 5.toInt64()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "5" - End - - It "runs agc" - When run test_agc - The output should eq "5" - End - End - End - - Describe "float32 (not default)" - Describe "addition" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(toFloat32(1) + toFloat32(2)); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "subtraction" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(toFloat32(2) - toFloat32(1)); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "1" - End - - It "runs agc" - When run test_agc - The output should eq "1" - End - End - - Describe "multiplication" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(toFloat32(2) * toFloat32(1)); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "2" - End - - It "runs agc" - When run test_agc - The output should eq "2" - End - End - - Describe "division" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(toFloat32(6) / toFloat32(2)); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "sqrt" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(sqrt(toFloat32(36))); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "6" - End - - It "runs agc" - When run test_agc - The output should eq "6" - End - End - - Describe "exponentiation" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(toFloat32(6) ** toFloat32(2)); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "36" - End - - It "runs agc" - When run test_agc - The output should eq "36" - End - End - - Describe "minimum" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - min(3.toFloat32(), 5.toFloat32()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "maximum" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - max(3.toFloat32(), 5.toFloat32()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "5" - End - - It "runs agc" - When run test_agc - The output should eq "5" - End - End - End - - Describe "float64 (default)" - Describe "addition" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - (1.0 + 2.0).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "subtraction" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - (2.0 - 1.0).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "1" - End - - It "runs agc" - When run test_agc - The output should eq "1" - End - End - - Describe "multiplication" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - (2.0 * 1.0).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "2" - End - - It "runs agc" - When run test_agc - The output should eq "2" - End - End - - Describe "division" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - (6.0 / 2.0).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "sqrt" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - sqrt(36.0).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "6" - End - - It "runs agc" - When run test_agc - The output should eq "6" - End - End - - Describe "exponentiation" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - (6.0 ** 2.0).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "36" - End - - It "runs agc" - When run test_agc - The output should eq "36" - End - End - - Describe "minimum" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - min(3.toFloat64(), 5.toFloat64()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "maximum" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - max(3.toFloat64(), 5.toFloat64()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "5" - End - - It "runs agc" - When run test_agc - The output should eq "5" - End - End - End - - Describe "grouping" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(2 / (3)); - print(3 / (1 + 2)); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "0 -1" - End - - It "runs agc" - When run test_agc - The output should eq "0 -1" - End - End - - Describe "string" - Describe "minimum" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - min(3.toString(), 5.toString()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "maximum" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - max(3.toString(), 5.toString()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "5" - End - - It "runs agc" - When run test_agc - The output should eq "5" - End - End - End -End diff --git a/bdd/spec/004_bitwise_spec.sh b/bdd/spec/004_bitwise_spec.sh deleted file mode 100644 index 689b38dc4..000000000 --- a/bdd/spec/004_bitwise_spec.sh +++ /dev/null @@ -1,157 +0,0 @@ -Include build_tools.sh - -Describe "Bitwise" - OUTPUT="0 -3 -6 --1 --1 --4 --7" - - Describe "int8" - before() { - sourceToAll " - from @std/app import start, print, exit - - prefix toInt8 as ~ precedence 10 - - on start { - print(~1 & ~2); - print(~1 | ~3); - print(~5 ^ ~3); - print(! ~0); - print(~1 !& ~2); - print(~1 !| ~2); - print(~5 !^ ~3); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "$OUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$OUTPUT" - End - End - - Describe "int16" - before() { - sourceToAll " - from @std/app import start, print, exit - - prefix toInt16 as ~ precedence 10 - - on start { - print(~1 & ~2); - print(~1 | ~3); - print(~5 ^ ~3); - print(! ~0); - print(~1 !& ~2); - print(~1 !| ~2); - print(~5 !^ ~3); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "$OUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$OUTPUT" - End - End - - Describe "int32" - before() { - sourceToAll " - from @std/app import start, print, exit - - prefix toInt32 as ~ precedence 10 - - on start { - print(~1 & ~2); - print(~1 | ~3); - print(~5 ^ ~3); - print(! ~0); - print(~1 !& ~2); - print(~1 !| ~2); - print(~5 !^ ~3); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "$OUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$OUTPUT" - End - End - - Describe "int64" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - print(1 & 2); - print(1 | 3); - print(5 ^ 3); - print(!0); - print(1 !& 2); - print(1 !| 2); - print(5 !^ 3); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "$OUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$OUTPUT" - End - End -End diff --git a/bdd/spec/005_boolean_lnn_spec.sh b/bdd/spec/005_boolean_lnn_spec.sh deleted file mode 100644 index 3e90d16ce..000000000 --- a/bdd/spec/005_boolean_lnn_spec.sh +++ /dev/null @@ -1,183 +0,0 @@ -Include build_tools.sh - -Describe "Booleans" - before() { - lnn_sourceToAll " - from @std/app import start, stdout, exit - - on start { - emit stdout concat(true.toString(), \" <- true\n\"); - emit stdout concat(toString(false), \" <- false\n\"); - emit stdout concat(toString(toBool(1)), \" <- 1\n\"); - emit stdout concat(toString(toBool(0)), \" <- 0\n\"); - emit stdout concat(toString(toBool(15)), \" <- 15\n\"); - emit stdout concat(toString(toBool(-1)), \" <- -1\n\"); - emit stdout concat(toString(toBool(0.0)), \" <- 0.0\n\"); - emit stdout concat(toString(toBool(1.2)), \" <- 1.2\n\"); - emit stdout concat(toString(toBool('')), ' <- \"\"\n'); - emit stdout concat(toString(toBool('hi')), \" <- 'hi'\n\"); - emit stdout concat(toString(toBool('true')), \" <- 'true'\n\"); - - emit stdout toString(true && true) + ' <- \"true && true\"\n'; - emit stdout toString(and(true, false)) + ' <- \"and(true, false)\"\n'; - emit stdout (false & true).toString() + ' <- \"false & true\"\n'; - emit stdout false.and(true).toString() + ' <- \"false.and(true)\"\n'; - - emit stdout toString(true || true) + ' <- \"true || true\"\n'; - emit stdout toString(or(true, false)) + ' <- \"or(true, false)\"\n'; - emit stdout (false | true).toString() + ' <- \"false | true\"\n'; - emit stdout false.or(true).toString() + ' <- \"false.or(true)\"\n'; - - emit stdout toString(true ^ true) + ' <- \"true ^ true\"\n'; - emit stdout toString(xor(true, false)) + ' <- \"xor(true, false)\"\n'; - emit stdout (false ^ true).toString() + ' <- \"false ^ true\"\n'; - emit stdout false.xor(true).toString() + ' <- \"false.xor(true)\"\n'; - - emit stdout toString(!true) + ' <- \"!true\"\n'; - emit stdout toString(not(false)) + ' <- \"not(false)\"\n'; - - emit stdout toString(true !& true) + ' <- \"true !& true\"\n'; - emit stdout toString(nand(true, false)) + ' <- \"nand(true, false)\"\n'; - emit stdout (false !& true).toString() + ' <- \"false !& true\"\n'; - emit stdout false.nand(true).toString() + ' <- \"false.nand(true)\"\n'; - - emit stdout toString(true !| true) + ' <- \"true !| true\"\n'; - emit stdout toString(nor(true, false)) + ' <- \"nor(true, false)\"\n'; - emit stdout (false !| true).toString() + ' <- \"false !| true\"\n'; - emit stdout false.nor(true).toString() + ' <- \"false.nor(true)\"\n'; - - emit stdout toString(true !^ true) + ' <- \"true !^ true\"\n'; - emit stdout toString(xnor(true, false)) + ' <- \"xnor(true, false)\"\n'; - emit stdout (false !^ true).toString() + ' <- \"false !^ true\"\n'; - emit stdout false.xnor(true).toString() + ' <- \"false.xnor(true)\"\n'; - - wait(500); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - OUTPUT1="true <- true" - OUTPUT2="false <- false" - OUTPUT3="true <- 1" - OUTPUT4="false <- 0" - OUTPUT5="true <- 15" - OUTPUT6="true <- -1" - OUTPUT7="false <- 0.0" - OUTPUT8="true <- 1.2" - OUTPUT9="false <- \"\"" - OUTPUT10="false <- 'hi'" - OUTPUT11="true <- 'true'" - OUTPUT12="true <- \"true && true\"" - OUTPUT13="false <- \"and(true, false)" - OUTPUT14="false <- \"false & true\"" - OUTPUT15="false <- \"false.and(true)\"" - OUTPUT16="true <- \"true || true\"" - OUTPUT17="true <- \"or(true, false)\"" - OUTPUT18="true <- \"false | true\"" - OUTPUT19="true <- \"false.or(true)\"" - OUTPUT20="false <- \"true ^ true\"" - OUTPUT21="true <- \"xor(true, false)\"" - OUTPUT22="true <- \"false ^ true\"" - OUTPUT23="true <- \"false.xor(true)\"" - OUTPUT24="false <- \"!true\"" - OUTPUT25="true <- \"not(false)\"" - OUTPUT26="false <- \"true !& true\"" - OUTPUT27="true <- \"nand(true, false)\"" - OUTPUT28="true <- \"false !& true\"" - OUTPUT29="true <- \"false.nand(true)\"" - OUTPUT30="false <- \"true !| true\"" - OUTPUT31="false <- \"nor(true, false)\"" - OUTPUT32="false <- \"false !| true\"" - OUTPUT33="false <- \"false.nor(true)\"" - OUTPUT34="true <- \"true !^ true\"" - OUTPUT35="false <- \"xnor(true, false)\"" - OUTPUT36="false <- \"false !^ true\"" - OUTPUT37="false <- \"false.xnor(true)\"" - - It "runs js" - When run test_js - The output should include "$OUTPUT1" - The output should include "$OUTPUT2" - The output should include "$OUTPUT3" - The output should include "$OUTPUT4" - The output should include "$OUTPUT5" - The output should include "$OUTPUT6" - The output should include "$OUTPUT7" - The output should include "$OUTPUT8" - The output should include "$OUTPUT9" - The output should include "$OUTPUT10" - The output should include "$OUTPUT11" - The output should include "$OUTPUT12" - The output should include "$OUTPUT13" - The output should include "$OUTPUT14" - The output should include "$OUTPUT15" - The output should include "$OUTPUT16" - The output should include "$OUTPUT17" - The output should include "$OUTPUT18" - The output should include "$OUTPUT19" - The output should include "$OUTPUT20" - The output should include "$OUTPUT21" - The output should include "$OUTPUT22" - The output should include "$OUTPUT23" - The output should include "$OUTPUT24" - The output should include "$OUTPUT25" - The output should include "$OUTPUT26" - The output should include "$OUTPUT27" - The output should include "$OUTPUT28" - The output should include "$OUTPUT29" - The output should include "$OUTPUT30" - The output should include "$OUTPUT31" - The output should include "$OUTPUT33" - The output should include "$OUTPUT34" - The output should include "$OUTPUT35" - The output should include "$OUTPUT36" - The output should include "$OUTPUT37" - End - - It "runs agc" - When run test_agc - The output should include "$OUTPUT1" - The output should include "$OUTPUT2" - The output should include "$OUTPUT3" - The output should include "$OUTPUT4" - The output should include "$OUTPUT5" - The output should include "$OUTPUT6" - The output should include "$OUTPUT7" - The output should include "$OUTPUT8" - The output should include "$OUTPUT9" - The output should include "$OUTPUT10" - The output should include "$OUTPUT11" - The output should include "$OUTPUT12" - The output should include "$OUTPUT13" - The output should include "$OUTPUT14" - The output should include "$OUTPUT15" - The output should include "$OUTPUT16" - The output should include "$OUTPUT17" - The output should include "$OUTPUT18" - The output should include "$OUTPUT19" - The output should include "$OUTPUT20" - The output should include "$OUTPUT21" - The output should include "$OUTPUT22" - The output should include "$OUTPUT23" - The output should include "$OUTPUT24" - The output should include "$OUTPUT25" - The output should include "$OUTPUT26" - The output should include "$OUTPUT27" - The output should include "$OUTPUT28" - The output should include "$OUTPUT29" - The output should include "$OUTPUT30" - The output should include "$OUTPUT31" - The output should include "$OUTPUT33" - The output should include "$OUTPUT34" - The output should include "$OUTPUT35" - The output should include "$OUTPUT36" - The output should include "$OUTPUT37" - End -End diff --git a/bdd/spec/005_boolean_spec.sh b/bdd/spec/005_boolean_spec.sh deleted file mode 100644 index 2e4189195..000000000 --- a/bdd/spec/005_boolean_spec.sh +++ /dev/null @@ -1,110 +0,0 @@ -Include build_tools.sh - -Describe "Booleans" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - print(true); - print(false); - print(toBool(1)); - print(toBool(0)); - print(toBool(15)); - print(toBool(-1)); - print(toBool(0.0)); - print(toBool(1.2)); - print(toBool('')); - print(toBool('hi')); - - print(true && true); - print(and(true, false)); - print(false & true); - print(false.and(false)); - - print(true || true); - print(or(true, false)); - print(false | true); - print(false.or(false)); - - print(true ^ true); - print(xor(true, false)); - print(false ^ true); - print(false.xor(false)); - - print(!true); - print(not(false)); - - print(true !& true); - print(nand(true, false)); - print(false !& true); - false.nand(false).print(); - - print(true !| true); - print(nor(true, false)); - print(false !| true); - false.nor(false).print(); - - print(true !^ true); - print(xnor(true, false)); - print(false !^ true); - false.xnor(false).print(); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - OUTPUT="true -false -true -false -true -true -false -true -false -false -true -false -false -false -true -true -true -false -false -true -true -false -false -true -false -true -true -true -false -false -false -true -true -false -false -true" - - It "runs js" - When run test_js - The output should eq "$OUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$OUTPUT" - End -End diff --git a/bdd/spec/006_string_lnn_spec.sh b/bdd/spec/006_string_lnn_spec.sh deleted file mode 100644 index 73ff0de1e..000000000 --- a/bdd/spec/006_string_lnn_spec.sh +++ /dev/null @@ -1,84 +0,0 @@ -Include build_tools.sh - -Describe "Strings" - Describe "most operations" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - - on start { - concat('Hello, ', \"World!\").print(); - print('Hello, ' + 'World!'); - - repeat('hi ', 5).print(); - print('hey ' * 5); - - matches('foobar', 'fo.*').print(); - print('foobar' ~ 'fo.*'); - - // index('foobar', 'ba').print(); - // print('foobar' @ 'ra'); - - length('foobar').print(); - print(#'foo'); - - trim(' hi ').print(); - print(\`' hey '); - - // split('Hello, World!', ', ')[0].print(); - // print(('Hello, World!' / ', ')[1]); - - // const res = split('Hello, World!', ', '); - // res[0].print(); - - // const res2 = 'Hello, World!' / ', '; - // print(res2[1]); - - wait(1000); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - OUTPUT="Hello, World! -Hello, World! -hi hi hi hi hi -hey hey hey hey hey -true -false -6 -3 -hi -hey -" - OUTPUT1="Hello, World!" - OUTPUT2="hi hi hi hi hi" - OUTPUT3="true" - OUTPUT4="6" - OUTPUT5="hi" - - It "runs js" - When run test_js - The output should include "$OUTPUT1" - The output should include "$OUTPUT2" - The output should include "$OUTPUT3" - The output should include "$OUTPUT4" - The output should include "$OUTPUT5" - End - - It "runs agc" - When run test_agc - The output should include "$OUTPUT1" - The output should include "$OUTPUT2" - The output should include "$OUTPUT3" - The output should include "$OUTPUT4" - The output should include "$OUTPUT5" - End - End -End diff --git a/bdd/spec/006_string_spec.sh b/bdd/spec/006_string_spec.sh deleted file mode 100644 index 8ca33e433..000000000 --- a/bdd/spec/006_string_spec.sh +++ /dev/null @@ -1,186 +0,0 @@ -Include build_tools.sh - -Describe "Strings" - Describe "most operations" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - concat('Hello, ', 'World!').print(); - print('Hello, ' + 'World!'); - - repeat('hi ', 5).print(); - print('hi ' * 5); - - matches('foobar', 'fo.*').print(); - print('foobar' ~ 'fo.*'); - - index('foobar', 'ba').print(); - print('foobar' @ 'ba'); - - length('foobar').print(); - print(#'foobar'); - - trim(' hi ').print(); - print(\`' hi '); - - split('Hello, World!', ', ')[0].print(); - print(('Hello, World!' / ', ')[1]); - - const res = split('Hello, World!', ', '); - res[0].print(); - - const res2 = 'Hello, World!' / ', '; - print(res2[1]); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - STROUTPUT="Hello, World! -Hello, World! -hi hi hi hi hi -hi hi hi hi hi -true -true -3 -3 -6 -6 -hi -hi -Hello -World! -Hello -World!" - - It "runs js" - When run test_js - The output should eq "$STROUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$STROUTPUT" - End - End - - Describe "equality between global constant and 'live' string works" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - const foo = 'foo'; - print(foo.trim() == foo); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "true" - End - - It "runs agc" - When run test_agc - The output should eq "true" - End - End - - Describe "toCharArray" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - const fooCharArray = 'foo'.toCharArray(); - print(#fooCharArray); - print(fooCharArray[0]); - print(fooCharArray[1]); - print(fooCharArray[2]); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - CHARARRAYOUTPUT="3 -f -o -o" - - It "runs js" - When run test_js - The output should eq "$CHARARRAYOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$CHARARRAYOUTPUT" - End - End - - Describe "templating" - before() { - # TODO: sourceToAll - sourceToTemp " - from @std/app import start, print, exit - - on start { - template('\${greet}, \${name}!', new Map { - 'greet': 'Hello' - 'name': 'World' - }).print() - print('\${greet}, \${name}!' % new Map { - 'greet': 'Good-bye' - 'name': 'World' - }) - - emit exit 0 - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - TMPLOUTPUT="Hello, World! -Good-bye, World!" - - It "runs js" - Pending template-support - When run test_js - The output should eq "$TMPLOUTPUT" - End - - It "runs agc" - Pending template-support - When run test_agc - The output should eq "$TMPLOUTPUT" - End - End -End diff --git a/bdd/spec/007_comparator_lnn_spec.sh b/bdd/spec/007_comparator_lnn_spec.sh deleted file mode 100644 index 821602a46..000000000 --- a/bdd/spec/007_comparator_lnn_spec.sh +++ /dev/null @@ -1,483 +0,0 @@ -Include build_tools.sh - -Describe "Comparators" - Describe "Equals" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - - on start { - // constrained to an int8 from the emit exit at the bottom - const i8val = 0; - const i8val1: int8 = 0; - print(i8val == i8val1); - const i8val2: int8 = 1; - print(i8val == i8val2); - - const i16val: int16 = 0; - const i16val1: int16 = 0; - print(i16val == i16val1); - const i16val2: int16 = 1; - print(i16val == i16val2); - - const i32val: int32 = 0; - const i32val1: int32 = 0; - print(i32val == i32val1); - const i32val2: int32 = 1; - print(i32val == i32val2); - - const i64val: int64 = 0; - const i64val1: int64 = 0; - print(i64val == i64val1); - const i64val2: int64 = 1; - print(i64val == i64val2); - wait(10); - - const f32val: float32 = 0.0; - const f32val1: float32 = 0.0; - print(f32val == f32val1); - const f32val2: float32 = 1.0; - print(f32val == f32val2); - - const f64val: float64 = 0.0; - const f64val1: float64 = 0.0; - print(f64val == f64val1); - const f64val2: float64 = 1.0; - print(f64val == f64val2); - - print(true == true); - print(true == false); - - print('hello' == \"hello\"); - print('hello' == \"world\"); - - emit exit i8val; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - EQUALS="true -false -true -false -true -false -true -false -true -false -true -false -true -false -true -false" - - It "runs js" - When run test_js - The output should eq "$EQUALS" - End - - It "runs agc" - When run test_agc - The output should eq "$EQUALS" - End - End - - Describe "Not Equals" - before() { - lnn_sourceToAll " - from @std/app import start, stdout, exit - - on start { - const i8val = 0; - const i8val1: int8 = 0; - emit stdout toString(i8val != i8val1) + '\n'; - wait(10); - const i8val2: int8 = 1; - emit stdout toString(i8val != i8val2) + '\n'; - wait(10); - - const i16val: int16 = 0; - const i16val1: int16 = 0; - emit stdout toString(i16val != i16val1) + '\n'; - wait(10); - const i16val2: int16 = 1; - emit stdout toString(i16val != i16val2) + '\n'; - wait(10); - - const i32val: int32 = 0; - const i32val1: int32 = 0; - emit stdout toString(i32val != i32val1) + '\n'; - wait(10); - const i32val2: int32 = 1; - emit stdout toString(i32val != i32val2) + '\n'; - wait(10); - - const i64val: int64 = 0; - const i64val1: int64 = 0; - emit stdout toString(i64val != i64val1) + '\n'; - wait(10); - const i64val2: int64 = 1; - emit stdout toString(i64val != i64val2) + '\n'; - wait(10); - - const f32val: float32 = 0; - const f32val1: float32 = 0.0; - emit stdout toString(f32val != f32val1) + '\n'; - wait(10); - const f32val2: float32 = 1.0; - emit stdout toString(f32val != f32val2) + '\n'; - wait(10); - - const f64val: float64 = 0; - const f64val1: float64 = 0.0; - emit stdout toString(f64val != f64val1) + '\n'; - wait(10); - const f64val2: float64 = 1.0; - emit stdout toString(f64val != f64val2) + '\n'; - wait(10); - - emit stdout toString(true != true) + '\n'; - wait(10); - emit stdout toString(true != false) + '\n'; - wait(10); - - emit stdout toString(\"hello\" != \"hello\") + '\n'; - wait(10); - emit stdout toString(\"hello\" != \"world\") + '\n'; - wait(10); - - emit exit i8val; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - NOTEQUALS="false -true -false -true -false -true -false -true -false -true -false -true -false -true -false -true" - - It "runs js" - When run test_js - The output should eq "$NOTEQUALS" - End - - It "runs agc" - When run test_agc - The output should eq "$NOTEQUALS" - End - End - - Describe "Less Than" - before() { - lnn_sourceToAll " - from @std/app import start, stdout, exit - - on start { - const i8val = 0; - const otheri8: int8 = 1; - emit stdout toString(i8val < otheri8) + '\n'; - wait(10); - emit stdout toString(otheri8 < i8val) + '\n'; - wait(10); - - const i16val: int16 = 0; - const otheri16: int16 = 1; - emit stdout toString(i16val < otheri16) + '\n'; - wait(10); - emit stdout toString(otheri16 < i16val) + '\n'; - wait(10); - - const i32val: int32 = 0; - const otheri32: int32 = 1; - emit stdout toString(i32val < otheri32) + '\n'; - wait(10); - emit stdout toString(otheri32 < i32val) + '\n'; - wait(10); - - const i64val: int64 = 0; - const otheri64: int64 = 1; - emit stdout toString(i64val < otheri64) + '\n'; - wait(10); - emit stdout toString(otheri64 < i64val) + '\n'; - wait(10); - - emit stdout toString('hello' < 'world') + '\n'; - wait(10); - emit stdout toString('hello' < 'hello') + '\n'; - wait(10); - - emit exit i8val; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - LESSTHAN="true -false -true -false -true -false -true -false -true -false" - - It "runs js" - When run test_js - The output should eq "$LESSTHAN" - End - - It "runs agc" - When run test_agc - The output should eq "$LESSTHAN" - End - End - - Describe "Less Than Or Equal" - before() { - lnn_sourceToAll " - from @std/app import start, stdout, exit - - on start { - const i8val = 0; - const otheri8: int8 = 1; - emit stdout toString(i8val <= otheri8) + '\n'; - wait(10); - emit stdout toString(otheri8 <= i8val) + '\n'; - wait(10); - - const i16val: int16 = 0; - const otheri16: int16 = 1; - emit stdout toString(i16val <= otheri16) + '\n'; - wait(10); - emit stdout toString(otheri16 <= i16val) + '\n'; - wait(10); - - const i32val: int32 = 0; - const otheri32: int32 = 1; - emit stdout toString(i32val <= otheri32) + '\n'; - wait(10); - emit stdout toString(otheri32 <= i32val) + '\n'; - wait(10); - - const i64val: int64 = 0; - const otheri64: int64 = 1; - emit stdout toString(i64val <= otheri64) + '\n'; - wait(10); - emit stdout toString(otheri64 <= i64val) + '\n'; - wait(10); - - emit stdout toString('hello' <= 'world') + '\n'; - wait(10); - emit stdout toString('hello' <= 'hello') + '\n'; - wait(10); - - emit exit i8val; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - LESSTHANEQUAL="true -false -true -false -true -false -true -false -true -true" - - It "runs js" - When run test_js - The output should eq "$LESSTHANEQUAL" - End - - It "runs agc" - When run test_agc - The output should eq "$LESSTHANEQUAL" - End - End - - Describe "Greater Than" - before() { - lnn_sourceToAll " - from @std/app import start, stdout, exit - - on start { - const i8val = 0; - const otheri8: int8 = 1; - emit stdout toString(i8val > otheri8) + '\n'; - wait(10); - emit stdout toString(otheri8 > i8val) + '\n'; - wait(10); - - const i16val: int16 = 0; - const otheri16: int16 = 1; - emit stdout toString(i16val > otheri16) + '\n'; - wait(10); - emit stdout toString(otheri16 > i16val) + '\n'; - wait(10); - - const i32val: int32 = 0; - const otheri32: int32 = 1; - emit stdout toString(i32val > otheri32) + '\n'; - wait(10); - emit stdout toString(otheri32 > i32val) + '\n'; - wait(10); - - const i64val: int64 = 0; - const otheri64: int64 = 1; - emit stdout toString(i64val > otheri64) + '\n'; - wait(10); - emit stdout toString(otheri64 > i64val) + '\n'; - wait(10); - - emit stdout toString('world' > 'world') + '\n'; - wait(10); - emit stdout toString('world' > 'hello') + '\n'; - wait(10); - - emit exit i8val; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - GREATERTHAN="false -true -false -true -false -true -false -true -false -true" - - It "runs js" - When run test_js - The output should eq "$GREATERTHAN" - End - - It "runs agc" - When run test_agc - The output should eq "$GREATERTHAN" - End - End - - Describe "Greater Than Or Equal" - before() { - lnn_sourceToAll " - from @std/app import start, stdout, exit - - on start { - const i8val = 0; - const otheri8: int8 = 1; - emit stdout toString(i8val >= otheri8) + '\n'; - wait(10); - emit stdout toString(otheri8 >= i8val) + '\n'; - wait(10); - - const i16val: int16 = 0; - const otheri16: int16 = 1; - emit stdout toString(i16val >= otheri16) + '\n'; - wait(10); - emit stdout toString(otheri16 >= i16val) + '\n'; - wait(10); - - const i32val: int32 = 0; - const otheri32: int32 = 1; - emit stdout toString(i32val >= otheri32) + '\n'; - wait(10); - emit stdout toString(otheri32 >= i32val) + '\n'; - wait(10); - - const i64val: int64 = 0; - const otheri64: int64 = 1; - emit stdout toString(i64val >= otheri64) + '\n'; - wait(10); - emit stdout toString(otheri64 >= i64val) + '\n'; - wait(10); - - emit stdout toString('hello' >= 'world') + '\n'; - wait(10); - emit stdout toString('hello' >= 'hello') + '\n'; - wait(10); - - emit exit i8val; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - GREATERTHANOREQUAL="false -true -false -true -false -true -false -true -false -true" - - It "runs js" - When run test_js - The output should eq "$GREATERTHANOREQUAL" - End - - It "runs agc" - When run test_agc - The output should eq "$GREATERTHANOREQUAL" - End - End -End diff --git a/bdd/spec/007_comparator_spec.sh b/bdd/spec/007_comparator_spec.sh deleted file mode 100644 index 3dfe53d2a..000000000 --- a/bdd/spec/007_comparator_spec.sh +++ /dev/null @@ -1,459 +0,0 @@ -Include build_tools.sh - -Describe "Comparators" - Describe "Cross-type comparisons" - before() { - sourceToTemp " - from @std/app import start, print, exit - - on start { - print(true == 1); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "doesn't work" - When run alan compile test_$$/temp.ln test_$$/temp.amm - The status should not eq "0" - The error should eq "Cannot resolve operators with remaining statement -true == 1 - == " - End - End - - Describe "Equals" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - print(toInt8(0) == toInt8(0)); - print(toInt8(1).eq(toInt8(0))); - - print(toInt16(0) == toInt16(0)); - print(toInt16(1).eq(toInt16(0))); - - print(toInt32(0) == toInt32(0)); - print(toInt32(1).eq(toInt32(0))); - - print(0 == 0); - print(1.eq(0)); - - print(toFloat32(0.0) == toFloat32(0.0)); - print(toFloat32(1.2).eq(toFloat32(0.0))); - - print(0.0 == 0.0); - print(1.2.eq(0.0)); - - print(true == true); - print(true.eq(false)); - - print('hello' == 'hello'); - print('hello'.eq('world')); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - EQUALS="true -false -true -false -true -false -true -false -true -false -true -false -true -false -true -false" - - It "runs js" - When run test_js - The output should eq "$EQUALS" - End - - It "runs agc" - When run test_agc - The output should eq "$EQUALS" - End - End - - Describe "Not Equals" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - print(toInt8(0) != toInt8(0)); - print(toInt8(1).neq(toInt8(0))); - - print(toInt16(0) != toInt16(0)); - print(toInt16(1).neq(toInt16(0))); - - print(toInt32(0) != toInt32(0)); - print(toInt32(1).neq(toInt32(0))); - - print(0 != 0); - print(1.neq(0)); - - print(toFloat32(0.0) != toFloat32(0.0)); - print(toFloat32(1.2).neq(toFloat32(0.0))); - - print(0.0 != 0.0); - print(1.2.neq(0.0)); - - print(true != true); - print(true.neq(false)); - - print('hello' != 'hello'); - print('hello'.neq('world')); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - NOTEQUALS="false -true -false -true -false -true -false -true -false -true -false -true -false -true -false -true" - - It "runs js" - When run test_js - The output should eq "$NOTEQUALS" - End - - It "runs agc" - When run test_agc - The output should eq "$NOTEQUALS" - End - End - - Describe "Less Than" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - print(toInt8(0) < toInt8(1)); - print(toInt8(1).lt(toInt8(0))); - - print(toInt16(0) < toInt16(1)); - print(toInt16(1).lt(toInt16(0))); - - print(toInt32(0) < toInt32(1)); - print(toInt32(1).lt(toInt32(0))); - - print(0 < 1); - print(1.lt(0)); - - print(toFloat32(0.0) < toFloat32(1.0)); - print(toFloat32(1.2).lt(toFloat32(0.0))); - - print(0.0 < 1.0); - print(1.2.lt(0.0)); - - print('hello' < 'hello'); - print('hello'.lt('world')); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - LESSTHAN="true -false -true -false -true -false -true -false -true -false -true -false -false -true" - - It "runs js" - When run test_js - The output should eq "$LESSTHAN" - End - - It "runs agc" - When run test_agc - The output should eq "$LESSTHAN" - End - End - - Describe "Less Than Or Equal" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - print(toInt8(0) <= toInt8(1)); - print(toInt8(1).lte(toInt8(0))); - - print(toInt16(0) <= toInt16(1)); - print(toInt16(1).lte(toInt16(0))); - - print(toInt32(0) <= toInt32(1)); - print(toInt32(1).lte(toInt32(0))); - - print(0 <= 1); - print(1.lte(0)); - - print(toFloat32(0.0) <= toFloat32(1.0)); - print(toFloat32(1.2).lte(toFloat32(0.0))); - - print(0.0 <= 1.0); - print(1.2.lte(0.0)); - - print('hello' <= 'hello'); - print('hello'.lte('world')); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - LESSTHANOREQUAL="true -false -true -false -true -false -true -false -true -false -true -false -true -true" - - It "runs js" - When run test_js - The output should eq "$LESSTHANOREQUAL" - End - - It "runs agc" - When run test_agc - The output should eq "$LESSTHANOREQUAL" - End - End - - Describe "Greater Than" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - print(toInt8(0) > toInt8(1)); - print(toInt8(1).gt(toInt8(0))); - - print(toInt16(0) > toInt16(1)); - print(toInt16(1).gt(toInt16(0))); - - print(toInt32(0) > toInt32(1)); - print(toInt32(1).gt(toInt32(0))); - - print(0 > 1); - print(1.gt(0)); - - print(toFloat32(0.0) > toFloat32(1.0)); - print(toFloat32(1.2).gt(toFloat32(0.0))); - - print(0.0 > 1.0); - print(1.2.gt(0.0)); - - print('hello' > 'hello'); - print('hello'.gt('world')); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - GREATERTHAN="false -true -false -true -false -true -false -true -false -true -false -true -false -false" - - It "runs js" - When run test_js - The output should eq "$GREATERTHAN" - End - - It "runs agc" - When run test_agc - The output should eq "$GREATERTHAN" - End - End - - Describe "Greater Than Or Equal" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - print(toInt8(0) >= toInt8(1)); - print(toInt8(1).gte(toInt8(0))); - - print(toInt16(0) >= toInt16(1)); - print(toInt16(1).gte(toInt16(0))); - - print(toInt32(0) >= toInt32(1)); - print(toInt32(1).gte(toInt32(0))); - - print(0 >= 1); - print(1.gte(0)); - - print(toFloat32(0.0) >= toFloat32(1.0)); - print(toFloat32(1.2).gte(toFloat32(0.0))); - - print(0.0 >= 1.0); - print(1.2.gte(0.0)); - - print('hello' >= 'hello'); - print('hello'.gte('world')); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - GREATERTHANOREQUAL="false -true -false -true -false -true -false -true -false -true -false -true -true -false" - - It "runs js" - When run test_js - The output should eq "$GREATERTHANOREQUAL" - End - - It "runs agc" - When run test_agc - The output should eq "$GREATERTHANOREQUAL" - End - End - - Describe "Type Coercion Aliases" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - print(toInt(0) == toInt64(0)); - print(toFloat(0.0) == toFloat(0.0)); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - TYPECOERCIONALIASES="true -true" - - It "runs js" - When run test_js - The output should eq "$TYPECOERCIONALIASES" - End - - It "runs agc" - When run test_agc - The output should eq "$TYPECOERCIONALIASES" - End - End -End - diff --git a/bdd/spec/008_function_spec.sh b/bdd/spec/008_function_spec.sh deleted file mode 100644 index eacc87eaf..000000000 --- a/bdd/spec/008_function_spec.sh +++ /dev/null @@ -1,72 +0,0 @@ -Include build_tools.sh - -Describe "Functions and Custom Operators" - before() { - sourceToAll " - from @std/app import start, print, exit - - fn foo() { - print('foo'); - } - - fn bar(str: string, a: int64, b: int64): string { - return str * a + b.toString(); - } - - fn baz(pre: string, body: string): void { - print(pre + bar(body, 1, 2)); - } - - // 'int' is an alias for 'int64' - fn double(a: int) = a * 2; - - prefix double as ## precedence 10 - - /** - * It should be possible to write 'doublesum' as: - * - * fn doublesum(a: int64, b: int64) = ##a + ##b - * - * but the function definitions are all parsed before the first operator mapping is done. - */ - fn doublesum(a: int64, b: int64) = a.double() + b.double(); - - infix doublesum as #+# precedence 11 - - on start fn (): void { - foo(); - 'to bar'.bar(2, 3).print(); - '>> '.baz('text here'); - 4.double().print(); - print(##3); - 4.doublesum(1).print(); - print(2 #+# 3); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - FUNCTIONRES="foo -to barto bar3 ->> text here2 -8 -6 -10 -10" - - It "runs js" - When run test_js - The output should eq "$FUNCTIONRES" - End - - It "runs agc" - When run test_agc - The output should eq "$FUNCTIONRES" - End -End diff --git a/bdd/spec/009_conditional_spec.sh b/bdd/spec/009_conditional_spec.sh deleted file mode 100644 index bfec61efa..000000000 --- a/bdd/spec/009_conditional_spec.sh +++ /dev/null @@ -1,258 +0,0 @@ -Include build_tools.sh - -Describe "Conditionals" - Describe "Basic" - before() { - sourceToAll " - from @std/app import start, print, exit - - fn bar() { - print('bar!'); - } - - fn baz() { - print('baz!'); - } - - on start { - if 1 == 0 { - print('What!?'); - } else { - print('Math is sane...'); - } - - if 1 == 0 { - print('Not this again...'); - } else if 1 == 2 { - print('Still wrong...'); - } else { - print('Math is still sane, for now...'); - } - - const foo: bool = true == true; - if foo bar else baz - - const isTrue = true == true; - cond(isTrue, fn { - print(\"It's true!\"); - }); - cond(!isTrue, fn { - print('This should not have run'); - }); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "Math is sane... -Math is still sane, for now... -bar! -It's true!" - End - - It "runs agc" - When run test_agc - The output should eq "Math is sane... -Math is still sane, for now... -bar! -It's true!" - End - End - - Describe "Nested" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - if true { - print(1); - if 1 == 2 { - print('What?'); - } else { - print(2); - if 2 == 1 { - print('Uhh...'); - } else if 2 == 2 { - print(3); - } else { - print('Nope'); - } - } - } else { - print('Hmm'); - } - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "1 -2 -3" - End - - It "runs agc" - When run test_agc - The output should eq "1 -2 -3" - End - End - - Describe "Early Return" - before() { - sourceToAll " - from @std/app import start, print, exit - - fn nearOrFar(distance: float64): string { - if distance < 5.0 { - return 'Near!'; - } else { - return 'Far!'; - } - } - - on start { - print(nearOrFar(3.14)); - print(nearOrFar(6.28)); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - RETOUTPUT="Near! -Far!" - - It "runs js" - When run test_js - The output should eq "$RETOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$RETOUTPUT" - End - End - - Describe "Ternary" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - const options = pair(2, 4); - print(options[0]); - print(options[1]); - - const options2 = 3 : 5; - print(options2[0]); - print(options2[1]); - - const val1 = 1 == 1 ? 1 : 2; - const val2 = 1 == 0 ? 1 : 2; - print(val1); - print(val2); - - const val3 = cond(1 == 1, pair(3, 4)); - const val4 = cond(1 == 0, pair(3, 4)); - print(val3); - print(val4); - - const val5 = 1 == 0 ? options2; - print(val5); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - ADVOUTPUT="2 -4 -3 -5 -1 -2 -3 -4 -5" - - It "runs js" - When run test_js - The output should eq "$ADVOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$ADVOUTPUT" - End - End - - Describe "conditional let assignment compiles and executes" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - let a = 0; - let b = 1; - let c = 2; - - if true { - a = b; - } else { - a = c; - } - print(a); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "1" - End - - It "runs agc" - When run test_agc - The output should eq "1" - End - End -End \ No newline at end of file diff --git a/bdd/spec/010_object_literal_lnn_spec.sh b/bdd/spec/010_object_literal_lnn_spec.sh deleted file mode 100644 index 1de5a7a52..000000000 --- a/bdd/spec/010_object_literal_lnn_spec.sh +++ /dev/null @@ -1,234 +0,0 @@ -Include build_tools.sh - -Describe "Object literals" - Describe "compiler checks" - before() { - sourceToTemp " - from @std/app import start, print, exit - - type Foo { - bar: string, - baz: bool, - } - - on start { - const foo = new Foo { - bay: 1.23, - }; - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "doesn't work" - When run alan compile test_$$/temp.ln test_$$/temp.amm - The status should not eq "0" - The error should eq "Foo object literal improperly defined -Missing fields: bar, baz -Extra fields: bay -new Foo { - bay: 1.23, - } on line 2:24" - End - End - - Describe "array literals and access" - before() { - sourceToTemp " - from @std/app import start, print, exit - - on start { - const test3 = new Array [ 1, 2, 4, 8, 16, 32, 64 ]; - print(test3[0]); - print(test3[1]); - print(test3[2]); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - ARRTYPEOUTPUT="1 -2 -4" - - It "runs js" - Pending generics-and-arrays - When run test_js - The output should eq "$ARRTYPEOUTPUT" - End - - It "runs agc" - Pending generics-and-arrays - When run test_agc - The output should eq "$ARRTYPEOUTPUT" - End - End - - Describe "object literals and access" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - - type MyType { - foo: string, - bar: bool, - } - - on start { - const test = new MyType { - foo: 'foo!', - bar: true, - }; - print(test.foo); - print(test.bar); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - OBJTYPEOUTPUT="foo! -true" - - It "runs js" - When run test_js - The output should eq "$OBJTYPEOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$OBJTYPEOUTPUT" - End - End - - Describe "object and array reassignment" - before() { - sourceToTemp " - from @std/app import start, print, exit - - type Foo { - bar: bool - } - - on start { - let test = new Array [ 1, 2, 3 ]; - print(test[0]); - test.set(0, 0); - print(test[0]); - - let test2 = new Array [ - new Foo { - bar: true - }, - new Foo { - bar: false - } - ]; - let test3 = test2[0] || new Foo { - bar: false - }; - print(test3.bar); - test3.bar = false; - test2.set(0, test3); // TODO: is the a better way to do nested updates? - const test4 = test2[0] || new Foo { - bar: true - }; - print(test4.bar); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - REASSIGNTYPEOUTPUT="1 -0 -true -false" - - It "runs js" - Pending generics-and-arrays - When run test_js - The output should eq "$REASSIGNTYPEOUTPUT" - End - - It "runs agc" - Pending generics-and-arrays - When run test_agc - The output should eq "$REASSIGNTYPEOUTPUT" - End - End - - Describe "map support" - before() { - # TODO: sourceToAll - sourceToTemp " - from @std/app import start, print, exit - - on start { - const test5 = new Map { - true: 1 - false: 0 - } - - print(test5[true]) - print(test5[false]) - - let test6 = new Map { - 'foo': 'bar' - } - test6['foo'] = 'baz' - print(test6['foo']) - - emit exit 0 - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - TYPEOUTPUT="1 -0 -baz" - - It "runs js" - Pending type-support - When run test_js - The output should eq "$TYPEOUTPUT" - End - - It "runs agc" - Pending type-support - When run test_agc - The output should eq "$TYPEOUTPUT" - End - End -End diff --git a/bdd/spec/010_object_literal_spec.sh b/bdd/spec/010_object_literal_spec.sh deleted file mode 100644 index b671228a1..000000000 --- a/bdd/spec/010_object_literal_spec.sh +++ /dev/null @@ -1,230 +0,0 @@ -Include build_tools.sh - -Describe "Object literals" - Describe "compiler checks" - before() { - sourceToTemp " - from @std/app import start, print, exit - - type Foo { - bar: string, - baz: bool, - } - - on start { - const foo = new Foo { - bay: 1.23, - }; - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "doesn't work" - When run alan compile test_$$/temp.ln test_$$/temp.amm - The status should not eq "0" - The error should eq "Foo object literal improperly defined -Missing fields: bar, baz -Extra fields: bay -new Foo { - bay: 1.23, - } on line 2:24" - End - End - - Describe "array literals and access" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - const test3 = new Array [ 1, 2, 4, 8, 16, 32, 64 ]; - print(test3[0]); - print(test3[1]); - print(test3[2]); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - ARRTYPEOUTPUT="1 -2 -4" - - It "runs js" - When run test_js - The output should eq "$ARRTYPEOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$ARRTYPEOUTPUT" - End - End - - Describe "object literals and access" - before() { - sourceToAll " - from @std/app import start, print, exit - - type MyType { - foo: string, - bar: bool, - } - - on start { - const test = new MyType { - foo: 'foo!', - bar: true, - }; - print(test.foo); - print(test.bar); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - OBJTYPEOUTPUT="foo! -true" - - It "runs js" - When run test_js - The output should eq "$OBJTYPEOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$OBJTYPEOUTPUT" - End - End - - Describe "object and array reassignment" - before() { - sourceToAll " - from @std/app import start, print, exit - - type Foo { - bar: bool - } - - on start { - let test = new Array [ 1, 2, 3 ]; - print(test[0]); - test.set(0, 0); - print(test[0]); - - let test2 = new Array [ - new Foo { - bar: true - }, - new Foo { - bar: false - } - ]; - let test3 = test2[0] || new Foo { - bar: false - }; - print(test3.bar); - test3.bar = false; - test2.set(0, test3); // TODO: is the a better way to do nested updates? - const test4 = test2[0] || new Foo { - bar: true - }; - print(test4.bar); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - REASSIGNTYPEOUTPUT="1 -0 -true -false" - - It "runs js" - When run test_js - The output should eq "$REASSIGNTYPEOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$REASSIGNTYPEOUTPUT" - End - End - - Describe "map support" - before() { - # TODO: sourceToAll - sourceToTemp " - from @std/app import start, print, exit - - on start { - const test5 = new Map { - true: 1 - false: 0 - } - - print(test5[true]) - print(test5[false]) - - let test6 = new Map { - 'foo': 'bar' - } - test6['foo'] = 'baz' - print(test6['foo']) - - emit exit 0 - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - TYPEOUTPUT="1 -0 -baz" - - It "runs js" - Pending type-support - When run test_js - The output should eq "$TYPEOUTPUT" - End - - It "runs agc" - Pending type-support - When run test_agc - The output should eq "$TYPEOUTPUT" - End - End -End diff --git a/bdd/spec/011_array_spec.sh b/bdd/spec/011_array_spec.sh deleted file mode 100644 index 161ce5e1a..000000000 --- a/bdd/spec/011_array_spec.sh +++ /dev/null @@ -1,494 +0,0 @@ -Include build_tools.sh - -Describe "Arrays" - Describe "accessor syntax and length" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - print('Testing...'); - const test = '1,2,3'.split(','); - print(test.length()); - print(test[0]); - print(test[1]); - print(test[2]); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - ACCESSOROUTPUT="Testing... -3 -1 -2 -3" - - It "runs js" - When run test_js - The output should eq "$ACCESSOROUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$ACCESSOROUTPUT" - End - End - - Describe "literal syntax" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - print('Testing...'); - const test = new Array [ 1, 2, 3 ]; - print(test[0]); - print(test[1]); - print(test[2]); - const test2 = [ 4, 5, 6 ]; - print(test2[0]); - print(test2[1]); - print(test2[2]); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - LITERALOUTPUT="Testing... -1 -2 -3 -4 -5 -6" - - It "runs js" - When run test_js - The output should eq "$LITERALOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$LITERALOUTPUT" - End - End - - Describe "push to lazy-let-defined Array and pop from it" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - print('Testing...'); - let test = new Array []; - test.push(1); - test.push(2); - test.push(3); - print(test[0]); - print(test[1]); - print(test[2]); - print(test.pop()); - print(test.pop()); - print(test.pop()); - print(test.pop()); // Should print error message - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - PUSHOUTPUT="Testing... -1 -2 -3 -3 -2 -1 -cannot pop empty array" - - It "runs js" - When run test_js - The output should eq "$PUSHOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$PUSHOUTPUT" - End - End - - Describe "length, index, has and join" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - const test = new Array [ 1, 1, 2, 3, 5, 8 ]; - const test2 = new Array [ 'Hello', 'World!' ]; - print('has test'); - print(test.has(3)); - print(test.has(4)); - - print('length test'); - test.length().print(); - print(#test); - - print('index test'); - test.index(5).print(); - print(test2 @ 'Hello'); - - print('join test'); - test2.join(', ').print(); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - LIJARRAYOUTPUT="has test -true -false -length test -6 -6 -index test -4 -0 -join test -Hello, World!" - - It "runs js" - When run test_js - The output should eq "$LIJARRAYOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$LIJARRAYOUTPUT" - End - End - - Describe "ternary abuse" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - const test = '1':'1':'2':'3':'5':'8'; - print(test.join(', ')); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - TERNARRAYOUTPUT="1, 1, 2, 3, 5, 8" - - It "runs js" - When run test_js - The output should eq "$TERNARRAYOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$TERNARRAYOUTPUT" - End - End - - Describe "map function" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - const count = [1, 2, 3, 4, 5]; // Ah, ah, ahh! - const byTwos = count.map(fn (n: int64): Result = n * 2); - count.map(fn (n: int64) = toString(n)).join(', ').print(); - byTwos.map(fn (n: Result) = toString(n)).join(', ').print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - MAPOUTPUT="1, 2, 3, 4, 5 -2, 4, 6, 8, 10" - - It "runs js" - When run test_js - The output should eq "$MAPOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$MAPOUTPUT" - End - End - - Describe "repeat and mapLin" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - const arr = [1, 2, 3] * 3; - const out = arr.mapLin(fn (x: int64): string = x.toString()).join(', '); - print(out); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - MAPLOUTPUT="1, 2, 3, 1, 2, 3, 1, 2, 3" - - It "runs js" - When run test_js - The output should eq "$MAPLOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$MAPLOUTPUT" - End - End - - Describe "each and find" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - const test = [ 1, 1, 2, 3, 5, 8 ]; - test.find(fn (val: int64): bool = val % 2 == 1).getOr(0).print(); - test.each(fn (val: int64) = print('=' * val)); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - EACHFINDOUTPUT="1 -= -= -== -=== -===== -========" - - agc() { - export LC_ALL=C - alan run test_$$/temp.agc | sort - } - - It "runs js" - When run test_js - The output should eq "$EACHFINDOUTPUT" - End - - It "runs agc" - When run agc - The output should eq "$EACHFINDOUTPUT" - End - End - - Describe "every, some and del" - before() { - sourceToAll " - from @std/app import start, print, exit - - fn isOdd (val: int64): bool = val % 2 == 1; - - on start { - const test = [ 1, 1, 2, 3, 5, 8 ]; - test.every(isOdd).print(); - test.some(isOdd).print(); - print(test.length()); - print(test.delete(1)); - print(test.delete(4)); - print(test.delete(10)); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - EVERYSOMEOUTPUT="false -true -6 -1 -8 -cannot remove idx 10 from array with length 4" - - It "runs js" - When run test_js - The output should eq "$EVERYSOMEOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$EVERYSOMEOUTPUT" - End - End - - Describe "reduce, filter, and concat" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - const test = [ 1, 1, 2, 3, 5, 8 ]; - const test2 = [ 4, 5, 6 ]; - print('reduce test'); - test.reduce(fn (a: int, b: int): int = a + b || 0).print(); - test.reduce(min).print(); - test.reduce(max).print(); - - print('filter test'); - test.filter(fn (val: int64): bool { - return val % 2 == 1; - }).map(fn (val: int64): string { - return toString(val); - }).join(', ').print(); - - print('concat test'); - test.concat(test2).map(fn (val: int64): string { - return toString(val); - }).join(', ').print(); - (test + test2).map(fn (val: int64): string { - return toString(val); - }).join(', ').print(); - - print('reduce as filter and concat test'); - // TODO: Lots of improvements needed for closures passed directly to opcodes. This one-liner is ridiculous - test.reduce(fn (acc: string, i: int): string = ((acc == '') && (i % 2 == 1)) ? i.toString() : (i % 2 == 1 ? (acc + ', ' + i.toString()) : acc), '').print(); - // TODO: Even more ridiculous when you want to allow parallelism - test.reducePar(fn (acc: string, i: int): string = ((acc == '') && (i % 2 == 1)) ? i.toString() : (i % 2 == 1 ? (acc + ', ' + i.toString()) : acc), fn (acc: string, cur: string): string = ((acc != '') && (cur != '')) ? (acc + ', ' + cur) : (acc != '' ? acc : cur), '').print(); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - ADVARRAYOUTPUT="reduce test -20 -1 -8 -filter test -1, 1, 3, 5 -concat test -1, 1, 2, 3, 5, 8, 4, 5, 6 -1, 1, 2, 3, 5, 8, 4, 5, 6 -reduce as filter and concat test -1, 1, 3, 5 -1, 1, 3, 5" - - It "runs js" - When run test_js - The output should eq "$ADVARRAYOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$ADVARRAYOUTPUT" - End - End - - Describe "user-defined types in array methods work" - before() { - sourceToAll " - from @std/app import start, print, exit - - type Foo { - foo: string, - bar: bool - } - - on start { - const five = [1, 2, 3, 4, 5]; - five.map(fn (n: int64): Foo { - return new Foo { - foo: n.toString(), - bar: n % 2 == 0, - }; - }).filter(fn (f: Foo): bool = f.bar).map(fn (f: Foo): string = f.foo).join(', ').print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - USERTYPEOUTPUT="2, 4" - - It "runs js" - When run test_js - The output should eq "$USERTYPEOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$USERTYPEOUTPUT" - End - End -End diff --git a/bdd/spec/012_hash_spec.sh b/bdd/spec/012_hash_spec.sh deleted file mode 100644 index cbe061209..000000000 --- a/bdd/spec/012_hash_spec.sh +++ /dev/null @@ -1,246 +0,0 @@ -Include build_tools.sh - -Describe "Hashing" - Describe "toHash" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - print(toHash(1)); - print(toHash(3.14159)); - print(toHash(true)); - print(toHash('false')); - print(toHash([1, 2, 5, 3])); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - TOHASHOUTPUT="-1058942856030168491 --5016367128657347516 --1058942856030168491 -6288867289231076425 --1521185239552941064" - - It "runs js" - When run test_js - The output should eq "$TOHASHOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$TOHASHOUTPUT" - End - End - - Describe "HashMap (no syntactic sugar)" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - const test = newHashMap('foo', 1); - test.set('bar', 2); - test.set('baz', 99); - print(test.keyVal().map(fn (n: KeyVal): string { - return 'key: ' + n.key + \"\\nval: \" + toString(n.val); - }).join(\"\\n\")); - print(test.keys().join(', ')); - print(test.vals().map(fn (n: int64): string = n.toString()).join(', ')); - print(test.length()); - print(test.get('foo')); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - HASHMAPOUTPUT="key: foo -val: 1 -key: bar -val: 2 -key: baz -val: 99 -foo, bar, baz -1, 2, 99 -3 -1" - - It "runs js" - When run test_js - The output should eq "$HASHMAPOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$HASHMAPOUTPUT" - End - End - - Describe "KeyVal to HashMap" - before() { - sourceToAll " - from @std/app import start, print, exit - - fn kv(k: any, v: anythingElse) = new KeyVal { - key: k, - val: v - } - - on start { - const kva = [ kv(1, 'foo'), kv(2, 'bar'), kv(3, 'baz') ]; - const hm = kva.toHashMap(); - print(hm.keyVal().map(fn (n: KeyVal): string { - return 'key: ' + toString(n.key) + \"\\nval: \" + n.val; - }).join(\"\\n\")); - print(hm.get(1)); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - KVTOHMOUTPUT="key: 1 -val: foo -key: 2 -val: bar -key: 3 -val: baz -foo" - - It "runs js" - When run test_js - The output should eq "$KVTOHMOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$KVTOHMOUTPUT" - End - End - - Describe "Setting same key to hashmap twice works as expected" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - let test = newHashMap('foo', 'bar'); - test.get('foo').print(); - test.set('foo', 'baz'); - print(test.get('foo')); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - HMTWICEOUTPUT="bar -baz" - - It "runs js" - When run test_js - The output should eq "$HMTWICEOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$HMTWICEOUTPUT" - End - End - - Describe "HashMap" - before() { - # TODO: sourceToAll - sourceToTemp " - from @std/app import start, print, exit - - on start { - const test = new Map { - 'foo': 1 - 'bar': 2 - 'baz': 99 - } - - print('keyVal test') - test.keyVal().each(fn (n: KeyVal) { - print('key: ' + n.key) - print('val: ' + n.value.toString()) - }) - - print('keys test') - test.keys().each(print) - - print('values test') - test.values().each(print) - - print('length test') - test.length().print() - print(#test) - - emit exit 0 - } - - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - MAPOUTPUT="keyVal test -key: bar -val: 2 -key: foo -val: 1 -key: baz -val: 99 -keys test -bar -foo -baz -values test -2 -1 -99 -length test -3 -3" - - It "runs js" - Pending map-support - When run test_js - The output should eq "$MAPOUTPUT" - End - - It "runs agc" - Pending map-support - When run test_agc - The output should eq "$MAPOUTPUT" - End - End -End diff --git a/bdd/spec/013_generics_spec.sh b/bdd/spec/013_generics_spec.sh deleted file mode 100644 index 9dcaace4e..000000000 --- a/bdd/spec/013_generics_spec.sh +++ /dev/null @@ -1,104 +0,0 @@ -Include build_tools.sh - -Describe "Generics" - Describe "valid generic type definition" - before() { - sourceToAll " - from @std/app import start, print, exit - - type box { - set: bool, - val: V - } - - on start fn { - let int8Box = new box { - val: 8.toInt8(), - set: true - }; - print(int8Box.val); - print(int8Box.set); - - let stringBox = new box { - val: 'hello, generics!', - set: true - }; - print(stringBox.val); - print(stringBox.set); - - const stringBoxBox = new box> { - val: new box { - val: 'hello, nested generics!', - set: true - }, - set: true - }; - stringBoxBox.set.print(); - stringBoxBox.val.set.print(); - print(stringBoxBox.val.val); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - GENERICOUTPUT="8 -true -hello, generics! -true -true -true -hello, nested generics!" - - It "runs js" - When run test_js - The output should eq "$GENERICOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$GENERICOUTPUT" - End - End - - Describe "invalid generic usage" - before() { - sourceToTemp " - from @std/app import start, print, exit - - type box { - set: bool, - val: V - } - - on start fn { - let stringBox = new box { - set: true, - val: 'str' - }; - stringBox.val = 8; - - emit exit 0; - } - " - } - Before before - - after() { - cleanTemp - } - After after - - It "does not compile" - When run alan compile test_$$/temp.ln test_$$/temp.agc - The error should eq "stringBox.val is of type string but assigned a value of type int64" - The status should not eq "0" - End - End -End diff --git a/bdd/spec/014_interface_lnn_spec.sh b/bdd/spec/014_interface_lnn_spec.sh deleted file mode 100644 index b672ce194..000000000 --- a/bdd/spec/014_interface_lnn_spec.sh +++ /dev/null @@ -1,308 +0,0 @@ -Include build_tools.sh - -Describe "Interfaces" - Describe "basic matching" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - - // TODO: overridable interfaces? - // interface Stringifiable { - // toString(Stringifiable): string - // } - - fn quoteAndPrint(toQuote: Stringifiable) { - print(\"'\" + toString(toQuote) + \"'\"); - } - - on start { - quoteAndPrint('Hello, World'); - quoteAndPrint(5); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "'Hello, World' -'5'" - End - - It "runs agc" - When run test_agc - The output should eq "'Hello, World' -'5'" - End - End - - Describe "import behavior" - before() { - sourceToTemp " - from @std/app import print - - export type Year { - year: int32 - } - - export type YearMonth { - year: int32, - month: int8 - } - - export type Date { - year: int32, - month: int8, - day: int8 - } - - export type Hour { - hour: int8 - } - - export type HourMinute { - hour: int8, - minute: int8 - } - - export type Time { - hour: int8, - minute: int8, - second: float64 - } - - export type DateTime { - date: Date, - time: Time, - timezone: HourMinute - } - - export fn makeYear(year: int32): Year { - return new Year { - year: year - }; - } - - export fn makeYear(year: int64): Year { - return new Year { - year: toInt32(year) - }; - } - - export fn makeYearMonth(year: int32, month: int8): YearMonth { - return new YearMonth { - year: year, - month: month - }; - } - - export fn makeYearMonth(y: Year, month: int64): YearMonth { - return new YearMonth { - year: y.year, - month: toInt8(month), - }; - } - - export fn makeDate(year: int32, month: int8, day: int8): Date { - return new Date { - year: year, - month: month, - day: day, - }; - } - - export fn makeDate(ym: YearMonth, day: int64): Date { - return new Date { - year: ym.year, - month: ym.month, - day: toInt8(day) - }; - } - - export fn makeHour(hour: int8): Hour { - return new Hour { - hour: hour - }; - } - - export fn makeHourMinute(hour: int8, minute: int8): HourMinute { - return new HourMinute { - hour: hour, - minute: minute - }; - } - - export fn makeHourMinute(hour: int64, minute: int64): HourMinute { - return new HourMinute { - hour: toInt8(hour), - minute: toInt8(minute) - }; - } - - export fn makeHourMinute(h: Hour, minute: int8): HourMinute { - return new HourMinute { - hour: h.hour, - minute: minute - }; - } - - export fn makeTime(hour: int8, minute: int8, second: float64): Time { - return new Time { - hour: hour, - minute: minute, - second: second - }; - } - - export fn makeTime(hm: HourMinute, second: float64): Time { - return new Time { - hour: hm.hour, - minute: hm.minute, - second: second - }; - } - - export fn makeTime(hm: HourMinute, second: int64): Time { - return new Time { - hour: hm.hour, - minute: hm.minute, - second: toFloat64(second) - }; - } - - export fn makeTime(hm: Array, second: int64): Time { - return new Time { - hour: hm[0].toInt8(), - minute: hm[1].toInt8(), - second: second.toFloat64() - }; - } - - export fn makeDateTime(date: Date, time: Time, timezone: HourMinute): DateTime { - return new DateTime { - date: date, - time: time, - timezone: timezone - }; - } - - export fn makeDateTime(date: Date, time: Time): DateTime { - return new DateTime { - date: date, - time: time, - timezone: 00:00, - }; - } - - export fn makeDateTimeTimezone(dt: DateTime, timezone: HourMinute): DateTime { - return new DateTime { - date: dt.date, - time: dt.time, - timezone: timezone - }; - } - - export fn makeDateTimeTimezone(dt: DateTime, timezone: Array): DateTime { - return new DateTime { - date: dt.date, - time: dt.time, - timezone: new HourMinute { - hour: timezone[0].toInt8(), - minute: timezone[1].toInt8(), - } - }; - } - - export fn makeDateTimeTimezoneRev(dt: DateTime, timezone: HourMinute): DateTime { - return new DateTime { - date: dt.date, - time: dt.time, - timezone: new HourMinute { - hour: timezone.hour.snegate(), - minute: timezone.minute - } - }; - } - - export fn makeDateTimeTimezoneRev(dt: DateTime, timezone: Array): DateTime { - return new Datetime { - date: dt.date, - time: dt.time, - timezone: new HourMinute { - hour: toInt8(timezone[0]).snegate(), - minute: toInt8(timezone[1]) - } - }; - } - - export fn print(dt: DateTime) { - // TODO: Work on formatting stuff - const timezoneOffsetSymbol = dt.timezone.hour < toInt8(0) ? \"-\" : \"+\"; - let str = (new Array [ - toString(dt.date.year), \"-\", toString(dt.date.month), \"-\", toString(dt.date.day), \"@\", - toString(dt.time.hour), \":\", toString(dt.time.minute), \":\", toString(dt.time.second), - timezoneOffsetSymbol, sabs(dt.timezone.hour).toString(), \":\", toString(dt.timezone.minute) - ]).join(''); - print(str); - } - - export prefix makeYear as # precedence 2 - export infix makeYearMonth as - precedence 2 - export infix makeDate as - precedence 2 - export infix makeHourMinute as : precedence 7 - export infix makeTime as : precedence 7 - export infix makeDateTime as @ precedence 2 - export infix makeDateTimeTimezone as + precedence 2 - export infix makeDateTimeTimezoneRev as - precedence 2 - - export interface datetime { - # int64: Year, - Year - int64: YearMonth, - YearMonth - int64: Date, - int64 : int64: HourMinute, - HourMinute : int64: Time, - Date @ Time: DateTime, - DateTime + HourMinute: DateTime, - DateTime - HourMinute: DateTime, - print(DateTime): void, - } - " - - sourceToTemp " - from @std/app import start, print, exit - from ./datetime import datetime - - on start { - const dt = #2020 - 07 - 02@12:07:30 - 08:00; - dt.print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanFile datetime.ln - cleanTemp - } - AfterAll after - - It "runs js" - Pending generics-and-arrays - When run test_js - The output should eq "2020-7-2@12:7:30-8:0" - End - - It "runs agc" - Pending generics-and-arrays - When run test_agc - The output should eq "2020-7-2@12:7:30-8:0" - End - End -End diff --git a/bdd/spec/014_interface_spec.sh b/bdd/spec/014_interface_spec.sh deleted file mode 100644 index 56c1444e7..000000000 --- a/bdd/spec/014_interface_spec.sh +++ /dev/null @@ -1,305 +0,0 @@ -Include build_tools.sh - -Describe "Interfaces" - Describe "basic matching" - before() { - sourceToAll " - from @std/app import start, print, exit - - interface Stringifiable { - toString(Stringifiable): string - } - - fn quoteAndPrint(toQuote: Stringifiable) { - print(\"'\" + toString(toQuote) + \"'\"); - } - - on start { - quoteAndPrint('Hello, World'); - quoteAndPrint(5); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "'Hello, World' -'5'" - End - - It "runs agc" - When run test_agc - The output should eq "'Hello, World' -'5'" - End - End - - Describe "import behavior" - before() { - sourceToFile datetime.ln " - from @std/app import print - - export type Year { - year: int32 - } - - export type YearMonth { - year: int32, - month: int8 - } - - export type Date { - year: int32, - month: int8, - day: int8 - } - - export type Hour { - hour: int8 - } - - export type HourMinute { - hour: int8, - minute: int8 - } - - export type Time { - hour: int8, - minute: int8, - second: float64 - } - - export type DateTime { - date: Date, - time: Time, - timezone: HourMinute - } - - export fn makeYear(year: int32): Year { - return new Year { - year: year - }; - } - - export fn makeYear(year: int64): Year { - return new Year { - year: toInt32(year) - }; - } - - export fn makeYearMonth(year: int32, month: int8): YearMonth { - return new YearMonth { - year: year, - month: month - }; - } - - export fn makeYearMonth(y: Year, month: int64): YearMonth { - return new YearMonth { - year: y.year, - month: toInt8(month), - }; - } - - export fn makeDate(year: int32, month: int8, day: int8): Date { - return new Date { - year: year, - month: month, - day: day, - }; - } - - export fn makeDate(ym: YearMonth, day: int64): Date { - return new Date { - year: ym.year, - month: ym.month, - day: toInt8(day) - }; - } - - export fn makeHour(hour: int8): Hour { - return new Hour { - hour: hour - }; - } - - export fn makeHourMinute(hour: int8, minute: int8): HourMinute { - return new HourMinute { - hour: hour, - minute: minute - }; - } - - export fn makeHourMinute(hour: int64, minute: int64): HourMinute { - return new HourMinute { - hour: toInt8(hour), - minute: toInt8(minute) - }; - } - - export fn makeHourMinute(h: Hour, minute: int8): HourMinute { - return new HourMinute { - hour: h.hour, - minute: minute - }; - } - - export fn makeTime(hour: int8, minute: int8, second: float64): Time { - return new Time { - hour: hour, - minute: minute, - second: second - }; - } - - export fn makeTime(hm: HourMinute, second: float64): Time { - return new Time { - hour: hm.hour, - minute: hm.minute, - second: second - }; - } - - export fn makeTime(hm: HourMinute, second: int64): Time { - return new Time { - hour: hm.hour, - minute: hm.minute, - second: toFloat64(second) - }; - } - - export fn makeTime(hm: Array, second: int64): Time { - return new Time { - hour: hm[0].toInt8(), - minute: hm[1].toInt8(), - second: second.toFloat64() - }; - } - - export fn makeDateTime(date: Date, time: Time, timezone: HourMinute): DateTime { - return new DateTime { - date: date, - time: time, - timezone: timezone - }; - } - - export fn makeDateTime(date: Date, time: Time): DateTime { - return new DateTime { - date: date, - time: time, - timezone: 00:00, - }; - } - - export fn makeDateTimeTimezone(dt: DateTime, timezone: HourMinute): DateTime { - return new DateTime { - date: dt.date, - time: dt.time, - timezone: timezone - }; - } - - export fn makeDateTimeTimezone(dt: DateTime, timezone: Array): DateTime { - return new DateTime { - date: dt.date, - time: dt.time, - timezone: new HourMinute { - hour: timezone[0].toInt8(), - minute: timezone[1].toInt8(), - } - }; - } - - export fn makeDateTimeTimezoneRev(dt: DateTime, timezone: HourMinute): DateTime { - return new DateTime { - date: dt.date, - time: dt.time, - timezone: new HourMinute { - hour: timezone.hour.snegate(), - minute: timezone.minute - } - }; - } - - export fn makeDateTimeTimezoneRev(dt: DateTime, timezone: Array): DateTime { - return new Datetime { - date: dt.date, - time: dt.time, - timezone: new HourMinute { - hour: toInt8(timezone[0]).snegate(), - minute: toInt8(timezone[1]) - } - }; - } - - export fn print(dt: DateTime) { - // TODO: Work on formatting stuff - const timezoneOffsetSymbol = dt.timezone.hour < toInt8(0) ? \"-\" : \"+\"; - let str = (new Array [ - toString(dt.date.year), \"-\", toString(dt.date.month), \"-\", toString(dt.date.day), \"@\", - toString(dt.time.hour), \":\", toString(dt.time.minute), \":\", toString(dt.time.second), - timezoneOffsetSymbol, sabs(dt.timezone.hour).toString(), \":\", toString(dt.timezone.minute) - ]).join(''); - print(str); - } - - export prefix makeYear as # precedence 2 - export infix makeYearMonth as - precedence 2 - export infix makeDate as - precedence 2 - export infix makeHourMinute as : precedence 7 - export infix makeTime as : precedence 7 - export infix makeDateTime as @ precedence 2 - export infix makeDateTimeTimezone as + precedence 2 - export infix makeDateTimeTimezoneRev as - precedence 2 - - export interface datetime { - # int64: Year, - Year - int64: YearMonth, - YearMonth - int64: Date, - int64 : int64: HourMinute, - HourMinute : int64: Time, - Date @ Time: DateTime, - DateTime + HourMinute: DateTime, - DateTime - HourMinute: DateTime, - print(DateTime): void, - } - " - - sourceToAll " - from @std/app import start, print, exit - from ./datetime import datetime - - on start { - const dt = #2020 - 07 - 02@12:07:30 - 08:00; - dt.print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanFile datetime.ln - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "2020-7-2@12:7:30-8:0" - End - - It "runs agc" - When run test_agc - The output should eq "2020-7-2@12:7:30-8:0" - End - End -End diff --git a/bdd/spec/015_maybe_result_either_lnn_spec.sh b/bdd/spec/015_maybe_result_either_lnn_spec.sh deleted file mode 100644 index b2817326b..000000000 --- a/bdd/spec/015_maybe_result_either_lnn_spec.sh +++ /dev/null @@ -1,280 +0,0 @@ -Include build_tools.sh - -Describe "Maybe, Result, and Either" - Describe "basic working check" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - - on start { - let result = ok(4); - result.getOr(0).print(); - result = err(''); - result.getOr(0).print(); - result = ok(0); - result.getOr(3).print(); - - let maybe = some(4); - maybe.getOr(0).print(); - maybe = none(); - maybe.getOr(0).print(); - maybe = some(0); - maybe.getOr(3).print(); - - let either = main(4); - either.getMainOr(0).print(); - either = alt('hello world'); - either.getAltOr('').print(); - either = main(0); - either.getMainOr(3).print(); - either = alt(''); - either.getAltOr('hello world').print(); - - print('done'); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - OUTPUT="4 -0 -0 -4 -0 -0 -4 -hello world -0 - -done" - - It "runs js" - When run test_js - The output should eq "$OUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$OUTPUT" - End - End - - Describe "Maybe" - before() { - lnn_sourceToTemp " - from @std/app import start, print, exit - - fn fiver(val: float64) { - if val.toInt64() == 5 { - return some(5); - } else { - return none(); - } - } - - on start { - const maybe5 = fiver(5.5); - if maybe5.isSome() { - print(maybe5.getOr(0)); - } else { - print('what?'); - } - - const maybeNot5 = fiver(4.4); - if maybeNot5.isNone() { - print('Correctly received nothing!'); - } else { - print('uhhh'); - } - - if maybe5.isSome() { - print(maybe5 || 0); - } else { - print('what?'); - } - - if maybeNot5.isNone() { - print('Correctly received nothing!'); - } else { - print('uhhh'); - } - - maybe5.toString().print(); - maybeNot5.toString().print(); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - MAYBEOUTPUT="5 -Correctly received nothing! -5 -Correctly received nothing! -5 -none" - - It "runs js" - Pending conditionals - When run test_js - The output should eq "$MAYBEOUTPUT" - End - - It "runs agc" - Pending conditionals - When run test_agc - The output should eq "$MAYBEOUTPUT" - End - End - - Describe "Result" - before() { - lnn_sourceToTemp " - from @std/app import start, print, exit - - fn reciprocal(val: float64) { - if val == 0.0 { - return err('Divide by zero error!'); - } else { - return 1.0 / val; - } - } - - on start { - const oneFifth = reciprocal(5.0); - if oneFifth.isOk() { - print(oneFifth.getOr(0.0)); - } else { - print('what?'); - } - - const oneZeroth = reciprocal(0.0); - if oneZeroth.isErr() { - const error = oneZeroth.getErr(noerr()); - print(error); - } else { - print('uhhh'); - } - - if oneFifth.isOk() { - print(oneFifth || 0.0); - } else { - print('what?'); - } - - if oneZeroth.isErr() { - print(oneZeroth || 1.2345); - } else { - print('uhhh'); - } - - oneFifth.toString().print(); - oneZeroth.toString().print(); - - const res = ok('foo'); - print(res.getErr('there is no error')); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - RESULTOUTPUT="0.2 -Divide by zero error! -0.2 -1.2345 -0.2 -Divide by zero error! -there is no error" - - It "runs js" - Pending conditionals - When run test_js - The output should eq "$RESULTOUTPUT" - End - - It "runs agc" - Pending conditionals - When run test_agc - The output should eq "$RESULTOUTPUT" - End - End - - Describe "Either" - before() { - lnn_sourceToTemp " - from @std/app import start, print, exit - - on start { - const strOrNum = getMainOrAlt(true); - if strOrNum.isMain() { - print(strOrNum.getMainOr('')); - } else { - print('what?'); - } - - const strOrNum2 = getMainOrAlt(false); - if strOrNum2.isAlt() { - print(strOrNum2.getAltOr(0)); - } else { - print('uhhh'); - } - - strOrNum.toString().print(); - strOrNum2.toString().print(); - - emit exit 0; - } - - fn getMainOrAlt(isMain: bool) { - if isMain { - return main('string'); - } else { - return alt(2); - } - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - EITHEROUTPUT="string -2 -string -2" - - It "runs js" - Pending conditionals - When run test_js - The output should eq "$EITHEROUTPUT" - End - - It "runs agc" - Pending conditionals - When run test_agc - The output should eq "$EITHEROUTPUT" - End - End -End diff --git a/bdd/spec/015_maybe_result_either_spec.sh b/bdd/spec/015_maybe_result_either_spec.sh deleted file mode 100644 index dfa783103..000000000 --- a/bdd/spec/015_maybe_result_either_spec.sh +++ /dev/null @@ -1,210 +0,0 @@ -Include build_tools.sh - -Describe "Maybe, Result, and Either" - Describe "Maybe" - before() { - sourceToAll " - from @std/app import start, print, exit - - fn fiver(val: float64) { - if val.toInt64() == 5 { - return some(5); - } else { - return none(); - } - } - - on start { - const maybe5 = fiver(5.5); - if maybe5.isSome() { - print(maybe5.getOr(0)); - } else { - print('what?'); - } - - const maybeNot5 = fiver(4.4); - if maybeNot5.isNone() { - print('Correctly received nothing!'); - } else { - print('uhhh'); - } - - if maybe5.isSome() { - print(maybe5 || 0); - } else { - print('what?'); - } - - if maybeNot5.isNone() { - print('Correctly received nothing!'); - } else { - print('uhhh'); - } - - maybe5.toString().print(); - maybeNot5.toString().print(); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - MAYBEOUTPUT="5 -Correctly received nothing! -5 -Correctly received nothing! -5 -none" - - It "runs js" - When run test_js - The output should eq "$MAYBEOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$MAYBEOUTPUT" - End - End - - Describe "Result" - before() { - sourceToAll " - from @std/app import start, print, exit - - fn reciprocal(val: float64) { - if val == 0.0 { - return err('Divide by zero error!'); - } else { - return 1.0 / val; - } - } - - on start { - const oneFifth = reciprocal(5.0); - if oneFifth.isOk() { - print(oneFifth.getOr(0.0)); - } else { - print('what?'); - } - - const oneZeroth = reciprocal(0.0); - if oneZeroth.isErr() { - const error = oneZeroth.getErr(noerr()); - print(error); - } else { - print('uhhh'); - } - - if oneFifth.isOk() { - print(oneFifth || 0.0); - } else { - print('what?'); - } - - if oneZeroth.isErr() { - print(oneZeroth || 1.2345); - } else { - print('uhhh'); - } - - oneFifth.toString().print(); - oneZeroth.toString().print(); - - const res = ok('foo'); - print(res.getErr('there is no error')); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - RESULTOUTPUT="0.2 -Divide by zero error! -0.2 -1.2345 -0.2 -Divide by zero error! -there is no error" - - It "runs js" - When run test_js - The output should eq "$RESULTOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$RESULTOUTPUT" - End - End - - Describe "Either" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - const strOrNum = getMainOrAlt(true); - if strOrNum.isMain() { - print(strOrNum.getMainOr('')); - } else { - print('what?'); - } - - const strOrNum2 = getMainOrAlt(false); - if strOrNum2.isAlt() { - print(strOrNum2.getAltOr(0)); - } else { - print('uhhh'); - } - - strOrNum.toString().print(); - strOrNum2.toString().print(); - - emit exit 0; - } - - fn getMainOrAlt(isMain: bool) { - if isMain { - return main('string'); - } else { - return alt(2); - } - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - EITHEROUTPUT="string -2 -string -2" - - It "runs js" - When run test_js - The output should eq "$EITHEROUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$EITHEROUTPUT" - End - End -End diff --git a/bdd/spec/016_type_spec.sh b/bdd/spec/016_type_spec.sh deleted file mode 100644 index 1df767473..000000000 --- a/bdd/spec/016_type_spec.sh +++ /dev/null @@ -1,132 +0,0 @@ -Include build_tools.sh - -Describe "Types" - Describe "user types and generics" - before() { - sourceToAll " - from @std/app import start, print, exit - - type foo { - bar: A, - baz: B - } - - type foo2 = foo - - on start fn { - let a = new foo { - bar: 'bar', - baz: 0 - }; - let b = new foo { - bar: 0, - baz: true - }; - let c = new foo2 { - bar: 0, - baz: 1.23 - }; - let d = new foo { - bar: 1, - baz: 3.14 - }; - print(a.bar); - print(b.bar); - print(c.bar); - print(d.bar); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - GENTYPEOUTPUT="bar -0 -0 -1" - - It "runs js" - When run test_js - The output should eq "$GENTYPEOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$GENTYPEOUTPUT" - End - End - - Describe "using non-imported type returned by imported function" - before() { - sourceToTemp " - from @std/app import start, exit - from @std/http import fetch, Request - - on start { - arghFn('{\"test\":\"test\"}'); - emit exit 0; - } - - fn arghFn(arghStr: string) { - fetch(new Request { - method: 'POST', - url: 'https://reqbin.com/echo/post/json', - headers: newHashMap('Content-Length', arghStr.length().toString()), - body: arghStr, - }); - } - " - sourceToFile test_server.js " - const http = require('http'); - - http.createServer((req, res) => { - console.log('received'); - res.end('Hello, world!'); - }).listen(8088); - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - afterEach() { - kill $PID1 - wait $PID1 2>/dev/null - # kill $PID2 - # wait $PID2 2>/dev/null - return 0 - } - After afterEach - - It "runs js" - Pending unimported-types-returned-by-imported-functions - node test_$$/test_server.js 1>test_$$/test_server.js.out 2>/dev/null & - PID1=$! - # node test_$$/temp.js 1>/dev/null & - # PID2=$! - sleep 1 - When run cat test_$$/test_server.js.out - The output should eq "received" - End - - It "runs agc" - Pending unimported-types-returned-by-imported-functions - node test_$$/test_server.js 1>test_$$/test_server.agc.out 2>/dev/null & - PID1=$! - # alan run test_$$/temp.agc 1>/dev/null 2>/dev/null & - # PID2=$! - sleep 1 - When run cat test_$$/test_server.agc.out - The output should eq "received" - End - End -End diff --git a/bdd/spec/017_custom_event_lnn_spec.sh b/bdd/spec/017_custom_event_lnn_spec.sh deleted file mode 100644 index 4f10e0816..000000000 --- a/bdd/spec/017_custom_event_lnn_spec.sh +++ /dev/null @@ -1,151 +0,0 @@ -Include build_tools.sh - -Describe "Custom events" - Describe "loop custom event" - before() { - sourceToTemp " - from @std/app import start, print, exit - - event loop: int64 - - on loop fn looper(val: int64) { - print(val); - if val >= 10 { - emit exit 0; - } else { - emit loop val + 1 || 0; - } - } - - on start { - emit loop 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - OUTPUT="0 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10" - - It "runs js" - Pending generics-and-arithmetic - When run test_js - The output should eq "$OUTPUT" - End - - It "runs agc" - Pending generics-and-arithmetic - When run test_agc - The output should eq "$OUTPUT" - End - End - - Describe "event with user-defined type" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - - type Thing { - foo: int64, - bar: string - } - - event thing: Thing - - on thing fn (t: Thing) { - print(t.foo); - print(t.bar); - emit exit 0; - } - - on start { - emit thing new Thing { - foo: 1, - bar: 'baz' - }; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - THINGOUTPUT="1 -baz" - - It "runs js" - When run test_js - The output should eq "$THINGOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$THINGOUTPUT" - End - End - - Describe "multiple event handlers for an event" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - - event aString: string - - on aString fn(str: string) { - print('hey I got a string! ' + str); - } - - on aString fn(str: string) { - print('I also got a string! ' + str); - } - - on aString fn(ignore: string) { - wait(100); - emit exit 0; - } - - on start { - emit aString 'hi'; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - THINGOUTPUT1="hey I got a string! hi" - THINGOUTPUT2="I also got a string! hi" - - It "runs js" - When run test_js - The output should include "$THINGOUTPUT1" - The output should include "$THINGOUTPUT2" - End - - It "runs agc" - When run test_agc - The output should include "$THINGOUTPUT1" - The output should include "$THINGOUTPUT2" - End - End -End diff --git a/bdd/spec/017_custom_event_spec.sh b/bdd/spec/017_custom_event_spec.sh deleted file mode 100644 index e874e82e5..000000000 --- a/bdd/spec/017_custom_event_spec.sh +++ /dev/null @@ -1,149 +0,0 @@ -Include build_tools.sh - -Describe "Custom events" - OUTPUT="0 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10" - - Describe "loop custom event" - before() { - sourceToAll " - from @std/app import start, print, exit - - event loop: int64 - - on loop fn looper(val: int64) { - print(val); - if val >= 10 { - emit exit 0; - } else { - emit loop val + 1 || 0; - } - } - - on start { - emit loop 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "$OUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$OUTPUT" - End - End - - Describe "event with user-defined type" - before() { - sourceToAll " - from @std/app import start, print, exit - - type Thing { - foo: int64, - bar: string - } - - event thing: Thing - - on thing fn (t: Thing) { - print(t.foo); - print(t.bar); - emit exit 0; - } - - on start { - emit thing new Thing { - foo: 1, - bar: 'baz' - }; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - THINGOUTPUT="1 -baz" - - It "runs js" - When run test_js - The output should eq "$THINGOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$THINGOUTPUT" - End - End - - Describe "multiple event handlers for an event" - before() { - sourceToAll " - from @std/app import start, print, exit - - event aString: string - - on aString fn(str: string) { - print('hey I got a string! ' + str); - } - - on aString fn(str: string) { - print('I also got a string! ' + str); - } - - on aString fn(ignore: string) { - wait(100); - emit exit 0; - } - - on start { - emit aString 'hi'; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - THINGOUTPUT1="hey I got a string! hi" - THINGOUTPUT2="I also got a string! hi" - - It "runs js" - When run test_js - The output should include "$THINGOUTPUT1" - The output should include "$THINGOUTPUT2" - End - - It "runs agc" - When run test_agc - The output should include "$THINGOUTPUT1" - The output should include "$THINGOUTPUT2" - End - End -End diff --git a/bdd/spec/018_closure_spec.sh b/bdd/spec/018_closure_spec.sh deleted file mode 100644 index 3e486fb15..000000000 --- a/bdd/spec/018_closure_spec.sh +++ /dev/null @@ -1,118 +0,0 @@ -Include build_tools.sh - -Describe "Closure Functions" - Describe "closure creation and usage" - before() { - sourceToAll " - from @std/app import start, print, exit - - fn closure(): function { - let num = 0; - return fn (): int64 { - num = num + 1 || 0; - return num; - }; - } - - on start fn (): void { - const counter1 = closure(); - const counter2 = closure(); - print(counter1()); - print(counter1()); - print(counter2()); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - CLOSURERES="1 -2 -1" - - It "runs js" - When run test_js - The output should eq "$CLOSURERES" - End - - It "runs agc" - When run test_agc - The output should eq "$CLOSURERES" - End - End - - Describe "closure usage by name" - before() { - sourceToAll " - from @std/app import start, print, exit - - fn double(x: int64): int64 = x * 2 || 0; - - on start { - const numbers = [1, 2, 3, 4, 5]; - numbers.map(double).map(toString).join(', ').print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - REFOUTPUT="2, 4, 6, 8, 10" - - It "runs js" - When run test_js - The output should eq "$REFOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$REFOUTPUT" - End - End - - Describe "inlined closures with argument" - before() { - sourceToTemp " - from @std/app import start, print, exit - - on start { - const arghFn = fn(argh: string) { - print(argh); - }; - arghFn('argh'); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - OUTPUT="argh" - - It "runs js" - Pending arguments-for-inlined-closures - When run test_js - The output should eq "$OUTPUT" - End - - It "runs agc" - Pending arguments-for-inlined-closures - When run test_agc - The output should eq "$OUTPUT" - End - End -End diff --git a/bdd/spec/019_compiler_error_spec.sh b/bdd/spec/019_compiler_error_spec.sh deleted file mode 100644 index 773aa9627..000000000 --- a/bdd/spec/019_compiler_error_spec.sh +++ /dev/null @@ -1,197 +0,0 @@ -Include build_tools.sh - -Describe "Compiler Errors" - # NOTE: The error messages absolutely need improvement, but this will prevent regressions in them - Describe "Cross-type comparisons" - before() { - sourceToTemp " - from @std/app import start, print, exit - - on start { - print(true == 1); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "doesn't work" - When run alan compile test_$$/temp.ln test_$$/temp.amm - The status should not eq "0" - # TODO: What file, line and character? - The error should eq "Cannot resolve operators with remaining statement -true == 1 - == " - End - End - - Describe "Unreachable code" - before() { - sourceToTemp " - from @std/app import start, print, exit - - fn unreachable() { - return 'blah'; - print('unreachable!'); - } - - on start { - unreachable(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "doesn't work" - When run alan compile test_$$/temp.ln test_$$/temp.amm - The status should not eq "0" - # TODO: What file? - The error should eq "Unreachable code in function 'unreachable' after: -return 'blah'; on line 4:12" - End - End - - Describe "Recursive Function calls" - before() { - sourceToTemp " - from @std/app import start, print, exit - - fn fibonacci(n: int64) { - if n < 2 { - return 1; - } else { - return fibonacci(n - 1 || 0) + fibonacci(n - 2 || 0); - } - } - - on start { - print(fibonacci(0)); - print(fibonacci(1)); - print(fibonacci(2)); - print(fibonacci(3)); - print(fibonacci(4)); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "doesn't work" - When run alan compile test_$$/temp.ln test_$$/temp.amm - The status should not eq "0" - # TODO: What file, line, and character? - The error should eq "Recursive callstack detected: fibonacci -> fibonacci. Aborting." - End - End - - Describe "Direct opcode calls" - before() { - sourceToTemp " - from @std/app import start, print, exit - - on start { - print(i64str(5)); // Illegal direct opcode usage - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "doesn't work" - When run alan compile test_$$/temp.ln test_$$/temp.amm - The status should not eq "0" - # TODO: What file, line, and character? - The error should eq "i64str is not a function but used as one. -i64str on line 4:18" - End - End - - Describe "Totally broken statements" - before() { - sourceToTemp " - import @std/app - - on app.start { - app.oops - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "doesn't work" - When run alan compile test_$$/temp.ln test_$$/temp.amm - The status should not eq "0" - The error should include "Could not load" - The error should include "No match for OneOrMore (whitespace | exportsn | handlers | functions | types | constdeclaration | operatormapping | events | interfaces) in file fakeFile line 3:10" - End - End - - Describe "Importing unexported values" - before() { - sourceToFile piece.ln " - type Piece { - owner: bool - } - " - sourceToTemp " - from @std/app import start, print, exit - from ./piece import Piece - - on start { - const piece = new Piece { - owner: false - }; - print('Hello World'); - if piece.owner == true { - print('OK'); - } else { - print('False'); - } - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanFile piece.ln - cleanTemp - } - AfterAll after - - It "doesn't work" - When run alan compile test_$$/temp.ln test_$$/temp.amm - The status should not eq "0" - The error should eq "Piece is not a type -new Piece { - owner: false - } on line 2:26" - End - End -End diff --git a/bdd/spec/020_module_constants_spec.sh b/bdd/spec/020_module_constants_spec.sh deleted file mode 100644 index 907de2700..000000000 --- a/bdd/spec/020_module_constants_spec.sh +++ /dev/null @@ -1,72 +0,0 @@ -Include build_tools.sh - -Describe "Module-level Constants" - Describe "simple constant" - before() { - sourceToAll " - import @std/app - - const helloWorld = 'Hello, World!'; - - on app.start { - app.print(helloWorld); - emit app.exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "Hello, World!" - End - - It "runs agc" - When run test_agc - The output should eq "Hello, World!" - End - End - - Describe "function-defined constants" - before() { - sourceToAll " - from @std/app import start, print, exit - - const three = add(1, 2); - - fn fiver() = 5; - - const five = fiver(); - - on start { - print(three); - print(five); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3 -5" - End - - It "runs agc" - When run test_agc - The output should eq "3 -5" - End - End -End \ No newline at end of file diff --git a/bdd/spec/021_trig_spec.sh b/bdd/spec/021_trig_spec.sh deleted file mode 100644 index b840bc6c7..000000000 --- a/bdd/spec/021_trig_spec.sh +++ /dev/null @@ -1,209 +0,0 @@ -Include build_tools.sh - -Describe "@std/trig" - before() { - sourceToAll " - from @std/app import start, print, exit - import @std/trig - from @std/trig import e, pi, tau - // shouldn't be necessary, but compiler issue makes it so - - on start { - 'Logarithms and e^x'.print(); - print(trig.exp(e)); - print(trig.ln(e)); - print(trig.log(e)); - - 'Basic Trig functions'.print(); - print(trig.sin(tau / 6.0)); - print(trig.cos(tau / 6.0)); - print(trig.tan(tau / 6.0)); - print(trig.sec(tau / 6.0)); - print(trig.csc(tau / 6.0)); - print(trig.cot(tau / 6.0)); - - 'Inverse Trig functions'.print(); - print(trig.arcsine(0.0)); - print(trig.arccosine(1.0)); - print(trig.arctangent(0.0)); - print(trig.arcsecant(tau / 6.0)); - print(trig.arccosecant(tau / 6.0)); - print(trig.arccotangent(tau / 6.0)); - - 'Historic Trig functions (useful for navigation and as a teaching aid: https://en.wikipedia.org/wiki/File:Circle-trig6.svg )'.print(); - print(trig.versine(pi / 3.0)); - print(trig.vercosine(pi / 3.0)); - print(trig.coversine(pi / 3.0)); - print(trig.covercosine(pi / 3.0)); - print(trig.haversine(pi / 3.0)); - print(trig.havercosine(pi / 3.0)); - print(trig.hacoversine(pi / 3.0)); - print(trig.hacovercosine(pi / 3.0)); - print(trig.exsecant(pi / 3.0)); - print(trig.excosecant(pi / 3.0)); - print(trig.chord(pi / 3.0)); - - 'Historic Inverse Trig functions'.print(); - print(trig.aver(0.0)); - print(trig.avcs(0.5)); - print(trig.acvs(1.0)); - print(trig.acvc(1.0)); - print(trig.ahav(0.5)); - print(trig.ahvc(0.5)); - print(trig.ahcv(0.5)); - print(trig.ahcc(0.5)); - print(trig.aexs(0.5)); - print(trig.aexc(0.5)); - print(trig.acrd(0.5)); - - 'Hyperbolic Trig functions'.print(); - print(trig.sinh(tau / 6.0)); - print(trig.cosh(tau / 6.0)); - print(trig.tanh(tau / 6.0)); - print(trig.sech(tau / 6.0)); - print(trig.csch(tau / 6.0)); - print(trig.coth(tau / 6.0)); - - 'Inverse Hyperbolic Trig functions'.print(); - print(trig.hyperbolicArcsine(tau / 6.0)); - print(trig.hyperbolicArccosine(tau / 6.0)); - print(trig.hyperbolicArctangent(tau / 6.0)); - print(trig.hyperbolicArcsecant(0.5)); - print(trig.hyperbolicArccosecant(tau / 6.0)); - print(trig.hyperbolicArccotangent(tau / 6.0)); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - # I would like to have a unified output for both, but Javascript rounds things very slightly - # differently from Rust at the least significant bit for the floating point numbers - - It "runs js" - When run test_js - The output should eq "Logarithms and e^x -15.154262241479259 -1 -0.43429448190325176 -Basic Trig functions -0.8660254037844386 -0.5000000000000001 -1.7320508075688767 -1.9999999999999996 -1.1547005383792517 -0.577350269189626 -Inverse Trig functions -0 -0 -0 -0.3013736097452911 -1.2694227170496055 -0.7623475341648746 -Historic Trig functions (useful for navigation and as a teaching aid: https://en.wikipedia.org/wiki/File:Circle-trig6.svg ) -0.4999999999999999 -1.5 -0.1339745962155614 -1.8660254037844386 -0.24999999999999994 -0.75 -0.0669872981077807 -0.9330127018922193 -0.9999999999999996 -0.15470053837925168 -0.9999999999999999 -Historic Inverse Trig functions -0 -2.0943951023931957 -0 -0 -1.5707963267948966 -1.5707963267948966 -0 -0 -0.8410686705679303 -0.7297276562269663 -0.5053605102841573 -Hyperbolic Trig functions -1.2493670505239751 -1.600286857702386 -0.7807144353592677 -0.6248879662960872 -0.8004052928885931 -1.2808780710450447 -Inverse Hyperbolic Trig functions -0.9143566553928857 -0.3060421086132653 -1.8849425394276085 -1.3169578969248166 -0.849142301064006 -1.8849425394276085" - End - - It "runs agc" - When run test_agc - The output should eq "Logarithms and e^x -15.154262241479259 -1 -0.4342944819032518 -Basic Trig functions -0.8660254037844386 -0.5000000000000001 -1.7320508075688767 -1.9999999999999996 -1.1547005383792517 -0.577350269189626 -Inverse Trig functions -0 -0 -0 -0.3013736097452911 -1.2694227170496055 -0.7623475341648746 -Historic Trig functions (useful for navigation and as a teaching aid: https://en.wikipedia.org/wiki/File:Circle-trig6.svg ) -0.4999999999999999 -1.5 -0.1339745962155614 -1.8660254037844386 -0.24999999999999994 -0.75 -0.0669872981077807 -0.9330127018922193 -0.9999999999999996 -0.15470053837925168 -0.9999999999999999 -Historic Inverse Trig functions -0 -2.0943951023931957 -0 -0 -1.5707963267948966 -1.5707963267948966 -0 -0 -0.8410686705679303 -0.7297276562269663 -0.5053605102841573 -Hyperbolic Trig functions -1.2493670505239751 -1.600286857702386 -0.7807144353592677 -0.6248879662960872 -0.8004052928885931 -1.2808780710450447 -Inverse Hyperbolic Trig functions -0.9143566553928857 -0.3060421086132653 -1.8849425394276085 -1.3169578969248166 -0.8491423010640059 -1.8849425394276085" - - End -End diff --git a/bdd/spec/022_deps_spec.sh b/bdd/spec/022_deps_spec.sh deleted file mode 100644 index 5fb0d41e6..000000000 --- a/bdd/spec/022_deps_spec.sh +++ /dev/null @@ -1,135 +0,0 @@ -Include build_tools.sh - -Describe "@std/deps" - Describe "package dependency add" - before() { - sourceToAll " - from @std/deps import Package, install, add, commit, dependency, using, block, fullBlock - - on install fn (package: Package) = package - .using(['@std/app', '@std/cmd']) - .dependency('https://github.com/alantech/hellodep.git') - .add() - .block('@std/tcp') - .fullBlock('@std/httpcommon') - .commit() - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - after_each() { - rm -r ./dependencies - } - After after_each - - has_dependencies() { - test -d "./dependencies" - } - - has_alantech() { - test -d "./dependencies/alantech" - } - - has_hellodep() { - test -d "./dependencies/alantech/hellodep" - } - - has_index() { - test -f "./dependencies/alantech/hellodep/index.ln" - } - - has_nested_dependencies() { - test -d "./dependencies/alantech/hellodep/dependencies" - } - - has_nested_alantech() { - test -d "./dependencies/alantech/hellodep/dependencies/alantech" - } - - has_nested_hellodep() { - test -d "./dependencies/alantech/hellodep/dependencies/alantech/nestedhellodep" - } - - has_nested_index() { - test -f "./dependencies/alantech/hellodep/dependencies/alantech/nestedhellodep/index.ln" - } - - has_modules() { - test -d "./dependencies/modules" - } - - has_std() { - test -d "./dependencies/modules/std" - } - - has_blacklisted_module() { - test -d "./dependencies/modules/std/tcpserver" - } - - not_has_cmd() { - if [ -d ./dependencies/modules/std/cmd ]; then - return 1 - fi - return 0 - } - - has_pkg_block() { - test -d "./dependencies/modules/std/tcp" - } - - has_pkg_full_block_applied() { - test -d "./dependencies/alantech/hellodep/modules/std/httpcommon" && grep -R -q "export const mock = true" "./dependencies/alantech/hellodep/modules/std/httpcommon/index.ln" - } - - run_js() { - node test_$$/temp.js | head -1 - } - - run_agc() { - alan run test_$$/temp.agc | head -1 - } - - It "runs js" - When run run_js - The output should eq "Cloning into './dependencies/alantech/hellodep'..." - Assert has_dependencies - Assert has_alantech - Assert has_hellodep - Assert has_index - Assert has_nested_dependencies - Assert has_nested_alantech - Assert has_nested_hellodep - Assert has_nested_index - Assert has_modules - Assert has_std - Assert has_blacklisted_module - Assert not_has_cmd - Assert has_pkg_block - Assert has_pkg_full_block_applied - End - - It "runs agc" - When run run_agc - The output should eq "Cloning into './dependencies/alantech/hellodep'..." - Assert has_dependencies - Assert has_alantech - Assert has_hellodep - Assert has_index - Assert has_nested_dependencies - Assert has_nested_alantech - Assert has_nested_hellodep - Assert has_nested_index - Assert has_modules - Assert has_std - Assert has_blacklisted_module - Assert not_has_cmd - Assert has_pkg_block - Assert has_pkg_full_block_applied - End - End -End \ No newline at end of file diff --git a/bdd/spec/023_http_spec.sh b/bdd/spec/023_http_spec.sh deleted file mode 100644 index c8fd37f0f..000000000 --- a/bdd/spec/023_http_spec.sh +++ /dev/null @@ -1,266 +0,0 @@ -Include build_tools.sh - -Describe "@std/http" - Describe "basic get" - before() { - sourceToAll " - from @std/app import start, print, exit - from @std/http import get - - on start { - print(get('https://raw.githubusercontent.com/alantech/hellodep/aea1ce817a423d00107577a430a046993e4e6cad/index.ln')); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "export const comeGetMe = \"You got me!\"" - End - - It "runs agc" - When run test_agc - The output should eq "export const comeGetMe = \"You got me!\"" - End - End - -Describe "basic post" - before() { - # All my homies hate CORS... - node -e "const http = require('http'); http.createServer((req, res) => { const headers = { 'Access-Control-Allow-Origin': '*','Access-Control-Allow-Methods': 'OPTIONS, POST, GET, PUT','Access-Control-Max-Age': 2592000, 'Access-Control-Allow-Headers': '*', }; if (req.method === 'OPTIONS') { res.writeHead(204, headers); res.end(); return; } res.writeHead(200, headers); req.pipe(res); req.on('end', () => res.end()); }).listen(8765)" 1>/dev/null 2>/dev/null & - ECHO_PID=$! - disown $ECHO_PID - sourceToAll " - from @std/app import start, print, exit - from @std/http import post - - on start { - print(post('http://localhost:8765', '{\"test\":\"test\"}')); - emit exit 0; - } - " - } - BeforeAll before - - after() { - kill -9 $ECHO_PID 1>/dev/null 2>/dev/null || true - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "{\"test\":\"test\"}" - End - - It "runs agc" - When run test_agc - The output should eq "{\"test\":\"test\"}" - End -End - - Describe "fetch directly" - before() { - sourceToAll " - from @std/app import start, print, exit - from @std/http import fetch, Request, Response - - on start { - const res = fetch(new Request { - method: 'GET', - url: 'https://raw.githubusercontent.com/alantech/hellodep/aea1ce817a423d00107577a430a046993e4e6cad/index.ln', - headers: newHashMap('User-Agent', 'Alanlang'), - body: '', - }); - print(res.isOk()); - const r = res.getOrExit(); - print(r.status); - print(r.headers.length()); - print(r.body); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - # The number of headers returned in the two runtimes is slightly different. Node includes the - # "connection: close" header and Hyper.rs does not - FETCHJSOUTPUT="true -200 -25 -export const comeGetMe = \"You got me!\"" - - FETCHAGCOUTPUT="true -200 -23 -export const comeGetMe = \"You got me!\"" - - It "runs js" - When run test_js - The output should eq "$FETCHJSOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$FETCHAGCOUTPUT" - End - End - - Describe "Hello World webserver" - before() { - sourceToAll " - from @std/app import start, exit - from @std/httpserver import connection, body, send, Connection - - on connection fn (conn: Connection) { - const req = conn.req; - const res = conn.res; - set(res.headers, 'Content-Type', 'text/plain'); - if req.method == 'GET' { - res.body('Hello, World!').send(); - } else { - res.body('Hello, Failure!').send(); - } - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - afterEach() { - kill $PID - wait $PID 2>/dev/null - return 0 - } - After afterEach - - It "runs js" - node test_$$/temp.js 1>/dev/null 2>/dev/null & - PID=$! - sleep 1 - When run curl -s localhost:8000 - The output should eq "Hello, World!" - End - - It "runs agc" - alan run test_$$/temp.agc 1>/dev/null 2>/dev/null & - PID=$! - sleep 1 - When run curl -s localhost:8000 - The output should eq "Hello, World!" - End - End - - Describe "importing http get doesn't break hashmap get" - before() { - sourceToAll " - from @std/app import start, print, exit - from @std/http import get - - on start { - const str = get('https://raw.githubusercontent.com/alantech/hellodep/aea1ce817a423d00107577a430a046993e4e6cad/index.ln').getOr(''); - const kv = str.split(' = '); - const key = kv[0] || 'bad'; - const val = kv[1] || 'bad'; - const hm = newHashMap(key, val); - hm.get(key).getOr('failed').print(); - hm.get('something else').getOr('correct').print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - GETGETOUTPUT="\"You got me!\" - -correct" - - It "runs js" - When run test_js - The output should eq "${GETGETOUTPUT}" - End - - It "runs agc" - When run test_agc - The output should eq "${GETGETOUTPUT}" - End - End - - Describe "Double-send in a single connection doesn't crash" - before() { - sourceToAll " - from @std/app import print, exit - from @std/httpserver import connection, Connection, body, send - - on connection fn (conn: Connection) { - const res = conn.res; - const firstMessage = res.body('First Message').send(); - print(firstMessage); - const secondMessage = res.body('Second Message').send(); - print(secondMessage); - wait(1000); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - node test_$$/temp.js 1>./out.txt 2>/dev/null & - sleep 1 - When run curl -s localhost:8000 - The output should eq "First Message" - End - - It "response from js" - When run cat ./out.txt - The output should eq "HTTP server listening on port 8000 -ok -connection not found" - rm out.txt - End - - It "runs agc" - sleep 2 - alan run test_$$/temp.agc 1>./out.txt 2>/dev/null & - sleep 1 - When run curl -s localhost:8000 - The output should eq "First Message" - End - - It "response from agc" - When run cat ./out.txt - The output should eq "HTTP server listening on port 8000 -ok -cannot call send twice for the same connection" - rm out.txt - End - End - -End \ No newline at end of file diff --git a/bdd/spec/024_clone_spec.sh b/bdd/spec/024_clone_spec.sh deleted file mode 100644 index d35a952dd..000000000 --- a/bdd/spec/024_clone_spec.sh +++ /dev/null @@ -1,44 +0,0 @@ -Include build_tools.sh - -Describe "clone" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - let a = 3; - let b = a.clone(); - a = 4; - print(a); - print(b); - let c = [1, 2, 3]; - let d = c.clone(); - d.set(0, 2); - c.map(fn (val: int): string = val.toString()).join(', ').print(); - d.map(fn (val: int): string = val.toString()).join(', ').print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - CLONEOUTPUT="4 -3 -1, 2, 3 -2, 2, 3" - - It "runs js" - When run test_js - The output should eq "${CLONEOUTPUT}" - End - - It "runs agc" - When run test_agc - The output should eq "${CLONEOUTPUT}" - End -End \ No newline at end of file diff --git a/bdd/spec/024_clone_spec_lnn.sh b/bdd/spec/024_clone_spec_lnn.sh deleted file mode 100644 index cbed59709..000000000 --- a/bdd/spec/024_clone_spec_lnn.sh +++ /dev/null @@ -1,37 +0,0 @@ -Include build_tools.sh - -Describe "clone" - before() { - lnn_sourceToAll " - from @std/app import start, print, exit - - on start { - let a = 3; - let b = a.clone(); - a = 4; - print(a); - print(b); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - CLONEOUTPUT="4 -3" - - It "runs js" - When run test_js - The output should eq "$CLONEOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$CLONEOUTPUT" - End -End diff --git a/bdd/spec/025_runtime_error_spec.sh b/bdd/spec/025_runtime_error_spec.sh deleted file mode 100644 index 57016f05a..000000000 --- a/bdd/spec/025_runtime_error_spec.sh +++ /dev/null @@ -1,52 +0,0 @@ -Include build_tools.sh - -Describe "Runtime Errors" - # NOTE: The error messages absolutely need improvement, but this will prevent regressions in them - Describe "File Not Found" - It "doesn't work" - When run alan run nothingburger - The status should eq "2" - The error should include "File not found:" - End - End - - Describe "getOrExit" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - const xs = [0, 1, 2, 5]; - const x1 = xs[1].getOrExit(); - print(x1); - const x2 = xs[2].getOrExit(); - print(x2); - const x5 = xs[5].getOrExit(); - print(x5); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "halts js on an error" - When run test_js - The status should eq "1" - The output should include "" - The error should eq "out-of-bounds access" # TODO: errors need stacktrace-like reporting - End - - It "halts agc on an error" - When run test_agc - The status should eq "1" - The output should include "" - The error should eq "out-of-bounds access" # TODO: errors need stacktrace-like reporting - End - End -End \ No newline at end of file diff --git a/bdd/spec/026_datastore_spec.sh b/bdd/spec/026_datastore_spec.sh deleted file mode 100644 index fd4a31e8b..000000000 --- a/bdd/spec/026_datastore_spec.sh +++ /dev/null @@ -1,163 +0,0 @@ -Include build_tools.sh - -Describe "@std/datastore" - Describe "distributed kv" - before() { - sourceToAll " - from @std/app import start, print, exit - from @std/datastore import namespace, has, set, del, getOr - - on start { - const ns = namespace('foo'); - print(ns.has('bar')); - ns.set('bar', 'baz'); - print(ns.has('bar')); - print(ns.getOr('bar', '')); - ns.del('bar'); - print(ns.has('bar')); - print(ns.getOr('bar', '')); - - ns.set('inc', 0); - emit waitAndInc 100; - emit waitAndInc 200; - emit waitAndInc 300; - } - - event waitAndInc: int64 - - on waitAndInc fn (ms: int64) { - wait(ms); - let i = namespace('foo').getOr('inc', 0); - i = i + 1 || 0; - print(i); - namespace('foo').set('inc', i); - if i == 3 { - emit exit 0; - } - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - DSOUTPUT="false -true -baz -false - -1 -2 -3" - - It "runs js" - When run test_js - The output should eq "$DSOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$DSOUTPUT" - End - End - - Describe "distributed compute" - before() { - sourceToAll " - from @std/app import start, print, exit - from @std/datastore import namespace, set, ref, mut, with, run, mutOnly, closure, getOr - - on start { - // Initial setup - const ns = namespace('foo'); - ns.set('foo', 'bar'); - - // Basic remote execution - const baz = ns.ref('foo').run(fn (foo: string) = foo.length()); - print(baz); - - // Closure-based remote execution - let bar = 'bar'; - const bay = ns.ref('foo').closure(fn (foo: string): int64 { - bar = 'foobar: ' + foo + bar; - return foo.length(); - }); - print(bay); - print(bar); - - // Constrained-closure that only gets the 'with' variable - const bax = ns.ref('foo').with(bar).run(fn (foo: string, bar: string): int64 = #foo +. #bar); - print(bax); - - // Mutable closure - const baw = ns.mut('foo').run(fn (foo: string): int64 { - foo = foo + 'bar'; - return foo.length(); - }); - print(baw); - - // Mutable closure that affects the foo variable - const bav = ns.mut('foo').closure(fn (foo: string): int64 { - foo = foo + 'bar'; - bar = bar * foo.length(); - return bar.length(); - }); - print(bav); - print(bar); - - // Constrained mutable closure that affects the foo variable - const bau = ns.mut('foo').with(bar).run(fn (foo: string, bar: string): int64 { - foo = foo * #bar; - return foo.length(); - }); - print(bau); - - // 'Pure' function that only does mutation - ns.mut('foo').mutOnly(fn (foo: string) { - foo = foo + foo; - }); - print(ns.getOr('foo', 'not found')); - - // Constrained 'pure' function that only does mutation - ns.mut('foo').with(bar).mutOnly(fn (foo: string, bar: string) { - foo = foo + bar; - }); - print(ns.getOr('foo', 'not found')); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - DCOUTPUT="3 -3 -foobar: barbar -17 -6 -126 -foobar: barbarfoobar: barbarfoobar: barbarfoobar: barbarfoobar: barbarfoobar: barbarfoobar: barbarfoobar: barbarfoobar: barbar -1134 -barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar -barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarfoobar: barbarfoobar: barbarfoobar: barbarfoobar: barbarfoobar: barbarfoobar: barbarfoobar: barbarfoobar: barbarfoobar: barbar" - - It "runs js" - When run test_js - The output should eq "$DCOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$DCOUTPUT" - End - End - -End diff --git a/bdd/spec/027_seq_spec.sh b/bdd/spec/027_seq_spec.sh deleted file mode 100644 index d1fdd3847..000000000 --- a/bdd/spec/027_seq_spec.sh +++ /dev/null @@ -1,298 +0,0 @@ -Include build_tools.sh - -Describe "@std/seq" - Describe "seq and next" - before() { - sourceToAll " - from @std/app import start, print, exit - from @std/seq import seq, next - - on start { - let s = seq(2); - print(s.next()); - print(s.next()); - print(s.next()); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - NEXTOUTPUT="0 -1 -error: sequence out-of-bounds" - - It "runs js" - When run test_js - The output should eq "$NEXTOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$NEXTOUTPUT" - End - End - - Describe "each" - before() { - sourceToAll " - from @std/app import start, print, exit - from @std/seq import seq, each - - on start { - let s = seq(3); - s.each(fn (i: int64) = print(i)); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - EACHOUTPUT="0 -1 -2" - - It "runs js" - When run test_js - The output should eq "$EACHOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$EACHOUTPUT" - End - End - - Describe "while" - before() { - sourceToAll " - from @std/app import start, print, exit - from @std/seq import seq, while - - on start { - let s = seq(100); - let sum = 0; - s.while(fn = sum < 10, fn { - sum = sum + 1 || 0; - }); - print(sum); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - WHILEOUTPUT="10" - - It "runs js" - When run test_js - The output should eq "$WHILEOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$WHILEOUTPUT" - End - End - - Describe "do-while" - before() { - sourceToAll " - from @std/app import start, print, exit - from @std/seq import seq, doWhile - - on start { - let s = seq(100); - let sum = 0; - // TODO: Get automatic type inference working on anonymous multi-line functions - s.doWhile(fn (): bool { - sum = sum + 1 || 0; - return sum < 10; - }); - print(sum); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - DOWHILEOUTPUT="10" - - It "runs js" - When run test_js - The output should eq "$DOWHILEOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$DOWHILEOUTPUT" - End - End - - Describe "recurse" - before() { - sourceToAll " - from @std/app import start, print, exit - from @std/seq import seq, Self, recurse - - on start { - print(seq(100).recurse(fn fibonacci(self: Self, i: int64): Result { - if i < 2 { - return ok(1); - } else { - const prev = self.recurse(i - 1 || 0); - const prevPrev = self.recurse(i - 2 || 0); - if prev.isErr() { - return prev; - } - if prevPrev.isErr() { - return prevPrev; - } - // TODO: Get type inference inside of recurse working so we don't need to unwrap these - return (prev || 0) + (prevPrev || 0); - } - }, 8)); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - RECURSEOUTPUT="34" - - It "runs js" - When run test_js - The output should eq "$RECURSEOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$RECURSEOUTPUT" - End - End - - Describe "recurse no-op one-liner regression test" - # Reported issue -- the root cause was due to how the compiler handled one-liner functions - # differently from multi-line functions. This test is to make sure the fix for this doesn't - # regress - before() { - sourceToAll " - import @std/app - from @std/seq import seq, Self, recurse - - fn doNothing(x: int) : int = x; - - fn doNothingRec(x: int) : int = seq(x).recurse(fn (self: Self, x: int) : Result { - return ok(x); - }, x) || 0; - - on app.start { - const x = 5; - app.print(doNothing(x)); // 5 - app.print(doNothingRec(x)); // 5 - - const xs = [1, 2, 3]; - app.print(xs.map(doNothing).map(toString).join(' ')); // 1 2 3 - app.print(xs.map(doNothingRec).map(toString).join(' ')); // 1 2 3 - - emit app.exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - ONELINEROUTPUT="5 -5 -1 2 3 -1 2 3" - - It "runs js" - When run test_js - The output should eq "$ONELINEROUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$ONELINEROUTPUT" - End - End - - Describe "recurse decrement regression test and variable aliasing regression test" - # Reported issue -- the root cause was two simultaneous bugs in the AVM and the ammtoaga stage - # of the compiler. The AVM was not decrementing the seq counter correctly when inside of a - # parallel opcode environment because it was being reset accidentally when merging the memory - # changes. It was also accidentally obliterating one of its arguments but this was masked by a - # bug in variable scope aliasing logic in the ammtoaga layer of the compiler (hence why the bug - # was not seen in the JS path). This test case guards against both issues. - before() { - sourceToAll " - import @std/app - from @std/seq import seq, Self, recurse - - fn triangularRec(x: int) : int = seq(x + 1 || 0).recurse(fn (self: Self, x: int) : Result { - if x == 0 { - return ok(x); - } else { - // TODO: Get type inference inside of recurse working so we don't need to unwrap these - return x + (self.recurse(x - 1 || 0) || 0); - } - }, x) || 0 - - on app.start { - const xs = [1, 2, 3]; - app.print(xs.map(triangularRec).map(toString).join(' ')); // 1 3 6 - - emit app.exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - DECREMENTOUTPUT="1 3 6" - - It "runs js" - When run test_js - The output should eq "$DECREMENTOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$DECREMENTOUTPUT" - End - End -End \ No newline at end of file diff --git a/bdd/spec/028_tree_spec.sh b/bdd/spec/028_tree_spec.sh deleted file mode 100644 index edcc27109..000000000 --- a/bdd/spec/028_tree_spec.sh +++ /dev/null @@ -1,195 +0,0 @@ -Include build_tools.sh - -Describe "Tree" - Describe "basic construction and access" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - const myTree = newTree('foo'); - const barNode = myTree.addChild('bar'); - const bazNode = myTree.addChild('baz'); - const bayNode = barNode.addChild('bay'); - - print(myTree.getRootNode() || 'wrong'); - print(bayNode.getParent() || 'wrong'); - print(myTree.getChildren().map(fn (c: Node): string = c || 'wrong').join(', ')); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - BASICOUTPUT="foo -bar -bar, baz" - - It "runs js" - When run test_js - The output should eq "$BASICOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$BASICOUTPUT" - End - End - - Describe "user-defined types in Tree work" - before() { - sourceToAll " - from @std/app import start, print, exit - - type Foo { - foo: string, - bar: bool, - } - - on start { - const myTree = newTree(new Foo { - foo: 'myFoo', - bar: false, - }); - const wrongFoo = new Foo { - foo: 'wrongFoo', - bar: false, - }; - const myFoo = myTree.getRootNode() || wrongFoo; - print(myFoo.foo); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - BASICOUTPUT="myFoo" - - It "runs js" - When run test_js - The output should eq "$BASICOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$BASICOUTPUT" - End - End - - Describe "every, find, some, reduce and prune" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - const myTree = newTree('foo'); - const barNode = myTree.addChild('bar'); - const bazNode = myTree.addChild('baz'); - const bayNode = barNode.addChild('bay'); - - print(myTree.every(fn (c: Node): bool = (c || 'wrong').length() == 3)); - print(myTree.some(fn (c: Node): bool = (c || 'wrong').length() == 1)); - print(myTree.find(fn (c: Node): bool = (c || 'wrong') == 'bay').getOr('wrong')); - print(myTree.find(fn (c: Node): bool = (c || 'wrong') == 'asf').getOr('wrong')); - - print(myTree.length()); - myTree.getChildren().eachLin(fn (c: Node) { - const n = c || 'wrong'; - if n == 'bar' { - c.prune(); - } - }); - print(myTree.getChildren().map(fn (c: Node): string = c || 'wrong').join(', ')); - print(myTree.length()); - - myTree.reduce(fn (acc: int, i: Node): int = (i || 'wrong').length() + acc || 0, 0).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - BASICOUTPUT="true -false -bay -wrong -4 -baz -2 -6" - - It "runs js" - When run test_js - The output should eq "$BASICOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$BASICOUTPUT" - End - End - - Describe "subtree and deeply nested tree construction" - before() { - sourceToAll " - from @std/app import start, print, exit - - on start { - const bigNestedTree = newTree('foo') - .addChild('bar') - .getTree() - .addChild(newTree('baz') - .addChild('quux') - .getTree() - ).getTree(); - - const mySubtree = bigNestedTree - .getRootNode() - .getChildren()[1] - .getOr(newTree('what').getRootNode()) - .toSubtree(); - - print(bigNestedTree.getRootNode() || 'wrong'); - print(mySubtree.getRootNode() || 'wrong'); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - SUBTREEOUTPUT="foo -baz" - - It "runs js" - When run test_js - The output should eq "$SUBTREEOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$SUBTREEOUTPUT" - End - End -End diff --git a/bdd/spec/029_err_printing_spec.sh b/bdd/spec/029_err_printing_spec.sh deleted file mode 100644 index 340cd53b6..000000000 --- a/bdd/spec/029_err_printing_spec.sh +++ /dev/null @@ -1,60 +0,0 @@ -Include build_tools.sh - -Describe "Error printing" - Describe "eprint function" - before() { - sourceToAll " - from @std/app import start, eprint, exit - on start { - eprint('This is an error'); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The stderr should eq "This is an error" - End - - It "runs agc" - When run test_agc - The stderr should eq "This is an error" - End - End - - Describe "stderr event" - before() { - sourceToAll " - from @std/app import start, stderr, exit - on start { - emit stderr 'This is an error'; - wait(10); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The stderr should eq "This is an error" - End - - It "runs agc" - When run test_agc - The stderr should eq "This is an error" - End - End -End diff --git a/bdd/spec/030_cmd_spec.sh b/bdd/spec/030_cmd_spec.sh deleted file mode 100644 index 90fe1b7d1..000000000 --- a/bdd/spec/030_cmd_spec.sh +++ /dev/null @@ -1,73 +0,0 @@ -Include build_tools.sh - -Describe "@std/cmd" - Describe "exec" - before() { - sourceToAll " - import @std/app - import @std/cmd - - on app.start { - const executionResult: cmd.ExecRes = cmd.exec('echo 1'); - app.print(executionResult.stdout); - emit app.exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - EXECOUTPUT="1" - - It "runs js" - When run test_js - The output should start with "$EXECOUTPUT" - End - - It "runs agc" - When run test_agc - The output should start with "$EXECOUTPUT" - End - End - - Describe "exec runs sequentially by default" - before() { - sourceToAll " - from @std/app import start, print, exit - from @std/cmd import exec - - on start { - exec('touch test.txt'); - exec('echo foo >> test.txt'); - exec('echo bar >> test.txt'); - exec('cat test.txt').stdout.print(); - exec('rm test.txt'); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - EXECSEQPATTERN="foo*bar*" - - It "runs js" - When run test_js - The output should match pattern "$EXECSEQPATTERN" - End - - It "runs agc" - When run test_agc - The output should match pattern "$EXECSEQPATTERN" - End - End -End diff --git a/bdd/spec/031_module_import_spec.sh b/bdd/spec/031_module_import_spec.sh deleted file mode 100644 index 111703acf..000000000 --- a/bdd/spec/031_module_import_spec.sh +++ /dev/null @@ -1,49 +0,0 @@ -Include build_tools.sh - -Describe "Module imports" - Describe "can import with trailing whitespace" - before() { - sourceToFile piece.ln " - export type Piece { - owner: bool, - } - " - sourceToAll " - from @std/app import start, print, exit - // Intentionally put an extra space after the import - from ./piece import Piece - - on start { - const piece = new Piece { - owner: false, - }; - print('Hello, World!'); - if piece.owner == true { - print('OK'); - } else { - print('False'); - } - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "Hello, World! -False" - End - - It "runs agc" - When run test_agc - The output should eq "Hello, World! -False" - End - End -End \ No newline at end of file diff --git a/bdd/spec/032_json_spec.sh b/bdd/spec/032_json_spec.sh deleted file mode 100644 index 483e72631..000000000 --- a/bdd/spec/032_json_spec.sh +++ /dev/null @@ -1,83 +0,0 @@ -Include build_tools.sh - -Describe "JSON" - Describe "basic construction and printing" - before() { - sourceToAll " - from @std/app import start, print, exit - from @std/json import JSON, toJSON, toString, JSONBase, JSONNode, IsObject, Null - - on start { - 1.0.toJSON().print(); - true.toJSON().print(); - 'Hello, JSON!'.toJSON().print(); - [1.0, 2.0, 5.0].toJSON().print(); - toJSON().print(); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - BASICOUTPUT="1 -true -\"Hello, JSON!\" -[1, 2, 5] -null" - - It "runs js" - When run test_js - The output should eq "$BASICOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$BASICOUTPUT" - End - End - - Describe "complex JSON type construction and printing" - before() { - sourceToAll " - from @std/app import start, print, exit - from @std/json import JSON, toString, JSONBase, JSONNode, IsObject, Null, newJSONObject, newJSONArray, addKeyVal, push - - on start { - newJSONObject() - .addKeyVal('mixed', 'values') - .addKeyVal('work', true) - .addKeyVal('even', newJSONArray() - .push(4.0) - .push('arrays')) - .print(); - - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - COMPLEXOUTPUT="{\"mixed\": \"values\", \"work\": true, \"even\": [4, \"arrays\"]}" - - It "runs js" - When run test_js - The output should eq "$COMPLEXOUTPUT" - End - - It "runs agc" - When run test_agc - The output should eq "$COMPLEXOUTPUT" - End - End -End diff --git a/bdd/spec/033_tcp_spec.sh b/bdd/spec/033_tcp_spec.sh deleted file mode 100644 index 8d2b53406..000000000 --- a/bdd/spec/033_tcp_spec.sh +++ /dev/null @@ -1,114 +0,0 @@ -Include build_tools.sh - -Describe "@std/tcp" - Describe "webserver tunnel test" - before() { - sourceToTemp " - from @std/tcpserver import tcpConn - from @std/tcp import TcpChannel, connect, addContext, ready, chunk, TcpContext, read, write, tcpClose, close - - on tcpConn fn (channel: TcpChannel) { - const tunnel = connect('localhost', 8088); - channel.addContext(tunnel); - tunnel.addContext(channel); - channel.ready(); - tunnel.ready(); - } - - on chunk fn (ctx: TcpContext) { - ctx.context.write(ctx.channel.read()); - } - - on tcpClose fn (ctx: TcpContext) { - ctx.context.close(); - } - " - tempToAmm - tempToJs - sourceToFile test_server.js " - const http = require('http') - - http.createServer((req, res) => res.end('Hello, World!')).listen(8088) - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - afterEach() { - kill $PID1 - wait $PID1 2>/dev/null - kill $PID2 - wait $PID2 2>/dev/null - return 0 - } - After afterEach - - It "runs js" - node test_$$/test_server.js 1>/dev/null 2>/dev/null & - PID1=$! - node test_$$/temp.js 1>/dev/null 2>/dev/null & - PID2=$! - sleep 1 - When run curl -s localhost:8000 - The output should eq "Hello, World!" - End - End - - Describe "webserver tunnel function test" - before() { - sourceToAll " - from @std/tcpserver import tunnel - from @std/app import start, print - - on start { - let connected = tunnel(8088); - print(connected ? 'Tunneling to 8088' : 'Failed to establish a tunnel'); - } - " - sourceToFile test_server.js " - const http = require('http') - - http.createServer((req, res) => res.end('Hello, World!')).listen(8088) - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - afterEach() { - kill $PID1 - wait $PID1 2>/dev/null - kill $PID2 - wait $PID2 2>/dev/null - return 0 - } - After afterEach - - It "runs js" - node test_$$/test_server.js 1>/dev/null 2>/dev/null & - PID1=$! - node test_$$/temp.js 1>/dev/null 2>/dev/null & - PID2=$! - sleep 1 - When run curl -s localhost:8000 - The output should eq "Hello, World!" - End - - It "runs agc" - node test_$$/test_server.js 1>/dev/null 2>/dev/null & - PID1=$! - alan run test_$$/temp.agc 1>/dev/null 2>/dev/null & - PID2=$! - sleep 1 - When run curl -s localhost:8000 - The output should eq "Hello, World!" - End - End -End \ No newline at end of file diff --git a/bdd/spec/034_saturating_math_spec.sh b/bdd/spec/034_saturating_math_spec.sh deleted file mode 100644 index 48a8068e3..000000000 --- a/bdd/spec/034_saturating_math_spec.sh +++ /dev/null @@ -1,840 +0,0 @@ -Include build_tools.sh - -Describe "Basic Saturating Math" - Describe "int8 (not default)" - Describe "addition" - before() { - sourceToAll " - from @std/app import start, exit - on start { emit exit sadd(toInt8(1), toInt8(2)); } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The status should eq "3" - End - - It "runs agc" - When run test_agc - The status should eq "3" - End - End - - Describe "subtraction" - before() { - sourceToAll " - from @std/app import start, exit - on start { emit exit ssub(toInt8(2), toInt8(1)); } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The status should eq "1" - End - - It "runs agc" - When run test_agc - The status should eq "1" - End - End - - Describe "multiplication" - before() { - sourceToAll " - from @std/app import start, exit - on start { emit exit smul(toInt8(2), toInt8(1)); } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The status should eq "2" - End - - It "runs agc" - When run test_agc - The status should eq "2" - End - End - - Describe "division" - before() { - sourceToAll " - from @std/app import start, exit - on start { emit exit sdiv(toInt8(6), toInt8(0)); } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The status should eq "127" - End - - It "runs agc" - When run test_agc - The status should eq "127" - End - End - - Describe "exponentiation" - before() { - sourceToAll " - from @std/app import start, exit - on start { emit exit spow(toInt8(6), toInt8(2)); } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The status should eq "36" - End - - It "runs agc" - When run test_agc - The status should eq "36" - End - End - End - - Describe "int16 (not default)" - Describe "addition" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(sadd(toInt16(1), toInt16(2))); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "subtraction" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(ssub(toInt16(2), toInt16(1))); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "1" - End - - It "runs agc" - When run test_agc - The output should eq "1" - End - End - - Describe "multiplication" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(smul(toInt16(2), toInt16(1))); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "2" - End - - It "runs agc" - When run test_agc - The output should eq "2" - End - End - - Describe "division" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(sdiv(toInt16(6), toInt16(2))); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "exponentiation" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(spow(toInt16(6), toInt16(2))); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "36" - End - - It "runs agc" - When run test_agc - The output should eq "36" - End - End - End - - Describe "int32 (not default)" - Describe "addition" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - sadd(1.toInt32(), 2.toInt32()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "subtraction" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - ssub(2.toInt32(), 1.toInt32()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "1" - End - - It "runs agc" - When run test_agc - The output should eq "1" - End - End - - Describe "multiplication" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - smul(2.toInt32(), 1.toInt32()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "2" - End - - It "runs agc" - When run test_agc - The output should eq "2" - End - End - - Describe "division" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - sdiv(6.toInt32(), 2.toInt32()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "exponentiation" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - spow(6.toInt32(), 2.toInt32()).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "36" - End - - It "runs agc" - When run test_agc - The output should eq "36" - End - End - End - - Describe "int64 (default)" - Describe "addition" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(1 +. 2); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "subtraction" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(2 -. 1); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "1" - End - - It "runs agc" - When run test_agc - The output should eq "1" - End - End - - Describe "multiplication" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(2 *. 1); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "2" - End - - It "runs agc" - When run test_agc - The output should eq "2" - End - End - - Describe "division" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(6 /. 2); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "exponentiation" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(6 **. 2); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "36" - End - - It "runs agc" - When run test_agc - The output should eq "36" - End - End - End - - Describe "float32 (not default)" - Describe "addition" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(toFloat32(1) +. toFloat32(2)); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "subtraction" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(toFloat32(2) -. toFloat32(1)); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "1" - End - - It "runs agc" - When run test_agc - The output should eq "1" - End - End - - Describe "multiplication" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(toFloat32(2) *. toFloat32(1)); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "2" - End - - It "runs agc" - When run test_agc - The output should eq "2" - End - End - - Describe "division" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(toFloat32(6) /. toFloat32(2)); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "exponentiation" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - print(toFloat32(6) **. toFloat32(2)); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "36" - End - - It "runs agc" - When run test_agc - The output should eq "36" - End - End - End - - Describe "float64 (default)" - Describe "addition" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - (1.0 +. 2.0).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "subtraction" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - (2.0 -. 1.0).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "1" - End - - It "runs agc" - When run test_agc - The output should eq "1" - End - End - - Describe "multiplication" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - (2.0 *. 1.0).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "2" - End - - It "runs agc" - When run test_agc - The output should eq "2" - End - End - - Describe "division" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - (6.0 /. 2.0).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "3" - End - - It "runs agc" - When run test_agc - The output should eq "3" - End - End - - Describe "exponentiation" - before() { - sourceToAll " - from @std/app import start, print, exit - on start { - (6.0 **. 2.0).print(); - emit exit 0; - } - " - } - BeforeAll before - - after() { - cleanTemp - } - AfterAll after - - It "runs js" - When run test_js - The output should eq "36" - End - - It "runs agc" - When run test_agc - The output should eq "36" - End - End - End -End diff --git a/bdd/spec/spec_helper.sh b/bdd/spec/spec_helper.sh deleted file mode 100644 index 197e06f39..000000000 --- a/bdd/spec/spec_helper.sh +++ /dev/null @@ -1,7 +0,0 @@ -#shellcheck shell=sh - -# set -eu - -# shellspec_spec_helper_configure() { -# shellspec_import 'support/custom_matcher' -# } diff --git a/compiler/.eslintignore b/compiler/.eslintignore deleted file mode 100644 index 26c3828da..000000000 --- a/compiler/.eslintignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules -dist -**/*.js -browser/stdlibs.json diff --git a/compiler/.eslintrc.js b/compiler/.eslintrc.js deleted file mode 100644 index e92476c0d..000000000 --- a/compiler/.eslintrc.js +++ /dev/null @@ -1,20 +0,0 @@ -// eslint-disable-next-line no-undef -module.exports = { - root: true, - parser: '@typescript-eslint/parser', - plugins: ['@typescript-eslint'], - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'prettier', - 'plugin:prettier/recommended', - ], - rules: { - 'no-prototype-builtins': 'off', - 'no-case-declarations': ['warn'], - '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-var-requires': 'off', - }, -}; diff --git a/compiler/.gitattributes b/compiler/.gitattributes deleted file mode 100644 index c28da5a4e..000000000 --- a/compiler/.gitattributes +++ /dev/null @@ -1,15 +0,0 @@ -amm/Amm.interp binary -amm/AmmLexer.interp binary -amm/AmmLexer.js binary -amm/AmmLexer.tokens binary -amm/AmmListener.js binary -amm/AmmParser.js binary -amm/Amm.tokens binary -ln/Ln.interp binary -ln/LnLexer.interp binary -ln/LnLexer.js binary -ln/LnLexer.tokens binary -ln/LnListener.js binary -ln/LnParser.js binary -ln/Ln.tokens binary -yarn.lock binary \ No newline at end of file diff --git a/compiler/.gitignore b/compiler/.gitignore deleted file mode 100644 index b09000eb2..000000000 --- a/compiler/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -node_modules/ -bundle.js -browser/stdlibs.json -.idea -std/ -dist/ -out.* \ No newline at end of file diff --git a/compiler/.npmignore b/compiler/.npmignore deleted file mode 100644 index 9cf6fffa3..000000000 --- a/compiler/.npmignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules/ -.idea diff --git a/compiler/.prettierignore b/compiler/.prettierignore deleted file mode 100644 index 26c3828da..000000000 --- a/compiler/.prettierignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules -dist -**/*.js -browser/stdlibs.json diff --git a/compiler/.prettierrc.json b/compiler/.prettierrc.json deleted file mode 100644 index 6e778b4fb..000000000 --- a/compiler/.prettierrc.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "trailingComma": "all", - "singleQuote": true -} diff --git a/compiler/README.md b/compiler/README.md deleted file mode 100644 index 24302f5c2..000000000 --- a/compiler/README.md +++ /dev/null @@ -1,77 +0,0 @@ -# alan-compile - -A compiler for alan to Javascript and Alan Graphcode, the [runtime](https://github.com/alantech/alan/tree/master/runtime)'s bytecode format. - -Caveats: There should be a strict mode that make sure int64s are using [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) despite the performance loss, but the vast majority of the time falling back to regular numbers should work. - -## Install - -```sh -npm install -g alan-compile -``` - -## Usage - -```sh -alan-compile -``` - -The compiler uses the file extension to determine what compilation steps to perform. - -The supported source formats are: - -- `.ln` - The a*l*a*n* source file extension, with automatic traversal and loading of specified imports relative to this file. -- `.amm` - The *a*lan *m*inus *m*inus (`alan--`) intermediate representation. A strict subset of `alan` in a single file used for final conversion to the output formats. -- `.aga` - The *a*lan *g*raph *a*ssembler format. An intermediate representation very close to the `.agc` format (below) that the runtime operates on. It also indicates the dependency graph of operations in the format that can be used by the runtime. Useful for debugging runtime behavior issues or if you are targeting the runtime with a different language. - -The supported output formats are: - -- `.amm` - The *a*lan *m*inus *m*inus (`alan--`) intermediate representation, useful only for debugging compiler issues or if you are writing your own second stage compiler for another runtime environment. -- `.aga` - The *a*lan *g*raph *a*ssembler format. An intermediate representation very close to the `.agc` format (below) that the runtime operates on. It also indicates the dependency graph of operations in the format that can be used by the runtime. Useful for debugging runtime behavior issues or if you are targeting the runtime with a different language. -- `.agc` - The *a*lan *g*raph*c*ode bytecode format. The bytecode format of the alan [runtime](https://github.com/alantech/runtime) that also maintains a dependency graph of operations to allow quick, dynamic restructuring of the code safely depending on the data being processed and the state and capabilities of the machine it is running on. -- `.js` - The most common [ECMAScript](https://ecma-international.org/ecma-262/10.0/index.html) file extension, representing a [CommonJS](http://www.commonjs.org/) module (aka a [Node](https://nodejs.org/en/) module). - -Note: `.amm` to `.amm` is absurd and not supported. :) - -## Browser Support - -This project also uses [Browserify](http://browserify.org/) to create a version of the compiler that works directly in the browser. The browser version of the compiler does not support output to `.agc`, but does support `.js` which can be simply `eval()`ed to execute. - -To get this bundled browser version, simply run: - -```sh -yarn bundle -``` - -and copy the resulting `bundle.js` to your own project, include it in a ` -``` - -then in your own Javascript source included later, you can acquire and use the compiler in this way: - -```js -const alanCompile = require('alan-compile'); // Browserify creates a toplevel `require` function that you can use to get the modules -const helloWorld = alanCompile( - 'ln', - 'js', - ` - import @std/app - - on app.start { - app.print("Hello, World!") - emit app.exit 0 - } -`, -); // argument order is: sourceExtension, outputExtension, sourceCode -eval(helloWorld); // Execute the generated javascript code -``` - -## Development - -`alan-compile` is written in relatively standard Node.js+Typescript with the source code in `src`. Run `yarn build` to recompile (or just `tsc` if you have that installed globally). - -## License - -MIT diff --git a/compiler/browser.js b/compiler/browser.js deleted file mode 100644 index 2004c9e72..000000000 --- a/compiler/browser.js +++ /dev/null @@ -1,23 +0,0 @@ -const { default: buildPipeline, } = require('./dist/pipeline') -const ammtojs = require('./dist/ammtojs') -const lntoamm = require('./dist/lntoamm') -const ammtoaga = require('./dist/ammtoaga') - -// We won't support AGC for now because of the complexities of moving off the Buffer API -const convert = buildPipeline([ - ['ln', 'amm', lntoamm], - ['amm', 'aga', ammtoaga], - ['amm', 'js', ammtojs], -]) - -module.exports = (inFormat, outFormat, text) => { - if (convert[inFormat] && convert[inFormat][outFormat]) { - const out = convert[inFormat][outFormat].fromString(text) - if (outFormat === 'js') { // Hackery for browserify for now, will clean this up later - return out.replace(/alan-js-runtime/g, 'alan-runtime') - } - return out - } else { - throw new Error(`${inFormat} to ${outFormat} is not supported`) - } -} diff --git a/compiler/browser/Std.js b/compiler/browser/Std.js deleted file mode 100644 index 622b91d62..000000000 --- a/compiler/browser/Std.js +++ /dev/null @@ -1,43 +0,0 @@ -const stdlibs = require('./stdlibs.json') - -const Ast = require('../dist/lntoamm/Ast') -const Module = require('../dist/lntoamm/Module').default -const Scope = require('../dist/lntoamm/Scope').default -const opcodeScope = require('../dist/lntoamm/opcodes').default.exportScope - -module.exports = { - loadStdModules: (modules) => { - const stdAsts = Object.keys(stdlibs).map(n => ({ - name: n, - ast: Ast.fromString(stdlibs[n]), - })) - // Load the rootScope first, all the others depend on it - let rootModule - stdAsts.forEach((moduleAst) => { - if (moduleAst.name == 'root.ln') { - rootModule = Module.populateModule('', moduleAst.ast, opcodeScope, true) - Module.getAllModules()[''] = rootModule - } - }) - // Now load the remaining modules based on the root scope - while (stdAsts.length > 0) { - const moduleAst = stdAsts.shift(); - if (moduleAst.name !== 'root.ln') { - const currName = moduleAst.name; - try { - moduleAst.name = '@std/' + moduleAst.name.replace(/.ln$/, '') - const stdModule = Module.populateModule( - moduleAst.name, - moduleAst.ast, - rootModule.exportScope, - true - ) - Module.getAllModules()[moduleAst.name] = stdModule - } catch (e) { // Failed to load, throw it back on the list to try again - moduleAst.name = currName; - stdAsts.push(moduleAst); - } - } - } - }, -} diff --git a/compiler/browser/genstdlibs.js b/compiler/browser/genstdlibs.js deleted file mode 100755 index 627719ad0..000000000 --- a/compiler/browser/genstdlibs.js +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env node - -const fs = require('fs') -const path = require('path') - - -const outJson = {} -const stdDir = path.join(__dirname, '../../std') -const stdAsts = fs.readdirSync(stdDir).filter(n => /.ln$/.test(n)).forEach(n => { - outJson[n] = fs.readFileSync(path.join(stdDir, n), { encoding: 'utf8', }) -}) - -console.log(JSON.stringify(outJson)) diff --git a/compiler/browser/runtime.js b/compiler/browser/runtime.js deleted file mode 100644 index c90e503f4..000000000 --- a/compiler/browser/runtime.js +++ /dev/null @@ -1,11 +0,0 @@ -const r = require('alan-js-runtime') - -// Redefined stdoutp and exitop to work in the browser -module.exports = { - ...r, - stdoutp: (...args) => console.log(...args), // Lazy binding to replace `console.log` at will - stderrp: (...args) => console.error(...args), // Lazy binding to replace `console.error` at will - exitop: () => { - r.emitter.removeAllListeners() - }, // Clean up the event emitter, later we'll want a hook into the playground to show this -} diff --git a/compiler/cypress.config.ts b/compiler/cypress.config.ts deleted file mode 100644 index 88f822af5..000000000 --- a/compiler/cypress.config.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { defineConfig } from 'cypress'; - -export default defineConfig({ - execTimeout: 120000, - e2e: { - // We've imported your old cypress plugins here. - // You may want to clean this up later by importing these. - setupNodeEvents(on, config) { - return require('./cypress/plugins/index.js')(on, config); - }, - }, -}); diff --git a/compiler/cypress/e2e/bundle_check.cy.js b/compiler/cypress/e2e/bundle_check.cy.js deleted file mode 100644 index 29261be1e..000000000 --- a/compiler/cypress/e2e/bundle_check.cy.js +++ /dev/null @@ -1,61 +0,0 @@ -describe('Alan Compiler Browser Bundle', () => { - before(() => { - cy.exec('yarn run test-server') - }) - - after(() => { - cy.exec('yarn run stop-test-server', { failOnNonZeroExit: false, }) - }) - - it('has a "require" global', () => { - cy.visit('http://localhost:8080/test.html') - cy.window().then((win) => { - cy.log(JSON.stringify(Object.keys(win.document))) - }) - cy.window().should('have.property', 'require') - }) - - it('can load the "alanCompiler"', () => { - cy.visit('http://localhost:8080/test.html') - cy.window().then((win) => { - const alanCompiler = win.require('alan-compiler') - expect(alanCompiler).to.be.a('function') - win.alanCompiler = alanCompiler - }) - }) - - const helloWorldLn = ` - from @std/app import start, print, exit - - on start { - print('Hello, World!'); - emit exit 0; - } - ` - - it('can compile an "ln" file to "amm", "aga", and "js" and execute the "js" correctly', () => { - cy.visit('http://localhost:8080/test.html') - cy.window().then((win) => { - const alanCompiler = win.require('alan-compiler') - const amm = alanCompiler('ln', 'amm', helloWorldLn) - expect(amm).to.be.a('string') - cy.log(amm) - const aga = alanCompiler('ln', 'aga', helloWorldLn) - expect(aga).to.be.a('string') - cy.log(aga) - const aga2 = alanCompiler('amm', 'aga', amm) - expect(aga2).to.be.a('string') - cy.log(aga2) - const js = alanCompiler('ln', 'js', helloWorldLn) - expect(js).to.be.a('string') - expect(() => { - eval(js) - }).to.not.throw(Error) - const js2 = alanCompiler('amm', 'js', amm) - expect(js2).to.be.a('string') - expect(() => { - eval(js2) - }).to.not.throw(Error) - }) - }) -}) diff --git a/compiler/cypress/plugins/index.js b/compiler/cypress/plugins/index.js deleted file mode 100644 index aa9918d21..000000000 --- a/compiler/cypress/plugins/index.js +++ /dev/null @@ -1,21 +0,0 @@ -/// -// *********************************************************** -// This example plugins/index.js can be used to load plugins -// -// You can change the location of this file or turn off loading -// the plugins file with the 'pluginsFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/plugins-guide -// *********************************************************** - -// This function is called when a project is opened or re-opened (e.g. due to -// the project's config changing) - -/** - * @type {Cypress.PluginConfig} - */ -module.exports = (on, config) => { - // `on` is used to hook into various events Cypress emits - // `config` is the resolved Cypress config -} diff --git a/compiler/cypress/support/commands.js b/compiler/cypress/support/commands.js deleted file mode 100644 index ca4d256f3..000000000 --- a/compiler/cypress/support/commands.js +++ /dev/null @@ -1,25 +0,0 @@ -// *********************************************** -// This example commands.js shows you how to -// create various custom commands and overwrite -// existing commands. -// -// For more comprehensive examples of custom -// commands please read more here: -// https://on.cypress.io/custom-commands -// *********************************************** -// -// -// -- This is a parent command -- -// Cypress.Commands.add("login", (email, password) => { ... }) -// -// -// -- This is a child command -- -// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) -// -// -// -- This is a dual command -- -// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) -// -// -// -- This will overwrite an existing command -- -// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) diff --git a/compiler/cypress/support/e2e.js b/compiler/cypress/support/e2e.js deleted file mode 100644 index d68db96df..000000000 --- a/compiler/cypress/support/e2e.js +++ /dev/null @@ -1,20 +0,0 @@ -// *********************************************************** -// This example support/index.js is processed and -// loaded automatically before your test files. -// -// This is a great place to put global configuration and -// behavior that modifies Cypress. -// -// You can change the location of this file or turn off -// automatically serving support files with the -// 'supportFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/configuration -// *********************************************************** - -// Import commands.js using ES2015 syntax: -import './commands' - -// Alternatively you can use CommonJS syntax: -// require('./commands') diff --git a/compiler/package.json b/compiler/package.json deleted file mode 100644 index 704d6ebb6..000000000 --- a/compiler/package.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "name": "alan-compile", - "version": "0.1.45-beta3", - "description": "Compile Alan code (ln) to amm, js, aga, and agc", - "engines": { - "node": ">=10.20.1" - }, - "scripts": { - "test": "yarn run prepare && yarn run bundle && cypress run", - "build": "tsc", - "prepare": "tsc && mkdir -p std && cp -r ../std/* ./std/ && npm run bundle", - "bundle": "node ./browser/genstdlibs.js > ./browser/stdlibs.json && browserify -r alan-js-runtime -r ./browser/runtime.js:alan-runtime -r ./dist/index.js:alan-compiler > bundle.js", - "clean": "rm -f bundle.js && rm -rf dist", - "test-server": "nohup http-server -p 8080 >/dev/null 2>/dev/null /dev/null 2>/dev/null &", - "style": "yarn eslint . --ext .ts && yarn prettier --check .", - "fmt": "yarn prettier --write . && yarn eslint . --ext .ts --fix" - }, - "main": "./dist/index.js", - "bin": { - "alan-compile": "./dist/index.js" - }, - "pkg": { - "assets": "std/*" - }, - "browser": { - "./dist/index.js": "./browser.js", - "./dist/lntoamm/Std.js": "./browser/Std.js" - }, - "keywords": [ - "alan", - "compiler", - "transpiler" - ], - "author": "David Ellis ", - "license": "MIT", - "dependencies": { - "@types/node": "^16", - "@types/uuid": "^8.0.0", - "alan-js-runtime": "../js-runtime", - "commander": "^5.1.0", - "uuid": "^8.0.0" - }, - "devDependencies": { - "@typescript-eslint/eslint-plugin": "^6", - "@typescript-eslint/parser": "^6", - "browserify": "^16.5.1", - "cypress": "^13", - "eslint": "^8", - "eslint-config-prettier": "^9", - "eslint-plugin-prettier": "^5", - "http-server": "^14.1.1", - "prettier": "^3", - "prettier-eslint": "^16", - "typescript": "^5" - }, - "resolutions": { - "lodash": "^4.17.19" - } -} diff --git a/compiler/src/aga.ts b/compiler/src/aga.ts deleted file mode 100644 index 48e42fab2..000000000 --- a/compiler/src/aga.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { - And, - CharSet, - NamedAnd, - NamedOr, - Not, - OneOrMore, - Or, - Token, - ZeroOrMore, - ZeroOrOne, -} from './lp'; - -// Defining AGA Tokens -const space = Token.build(' '); -const newline = Token.build('\n'); -const whitespace = ZeroOrMore.build(Or.build([space, newline])); -const at = Token.build('@'); -const colon = Token.build(':'); -const sharp = Token.build('#'); -const under = Token.build('_'); -const negate = Token.build('-'); -const dot = Token.build('.'); -const eq = Token.build('='); -const openParen = Token.build('('); -const closeParen = Token.build(')'); -const backArrow = Token.build('<-'); -const openBracket = Token.build('['); -const closeBracket = Token.build(']'); -const comma = Token.build(','); -const base10 = CharSet.build('0', '9'); -const natural = OneOrMore.build(base10); -const integer = And.build([ZeroOrOne.build(negate), natural]); -const real = And.build([integer, ZeroOrOne.build(And.build([dot, natural]))]); -const i8 = And.build([integer, Token.build('i8')]); -const i16 = And.build([integer, Token.build('i16')]); -const i32 = And.build([integer, Token.build('i32')]); -const i64 = And.build([integer, Token.build('i64')]); -const f32 = And.build([real, Token.build('f32')]); -const f64 = And.build([real, Token.build('f64')]); -const lower = CharSet.build('a', 'z'); -const upper = CharSet.build('A', 'Z'); -const variable = And.build([ - OneOrMore.build(Or.build([under, lower, upper])), - ZeroOrMore.build(Or.build([under, lower, upper, natural])), -]); -const t = Token.build('true'); -const f = Token.build('false'); -const bool = Or.build([t, f]); -const quote = Token.build('"'); -const escapeQuote = Token.build('\\"'); -const notQuote = Not.build('"'); -const str = And.build([ - quote, - ZeroOrMore.build(Or.build([escapeQuote, notQuote])), - quote, -]); -const value = NamedOr.build({ str, bool, i8, i16, i32, i64, f32, f64 }); -const header = Token.build('Alan Graphcode Assembler v0.0.1'); -const globalMem = Token.build('globalMem'); -const memoryAddress = And.build([at, integer]); -const memoryLine = NamedAnd.build({ memoryAddress, colon, whitespace, value }); -const customEvents = Token.build('customEvents'); -const eventLine = NamedAnd.build({ variable, colon, whitespace, integer }); -const handlerFor = Or.build([ - Token.build('handler for'), - Token.build('closure for'), -]); -const withSize = Token.build('with size'); -const handlerLine = NamedAnd.build({ - handlerFor, - a: whitespace, - variable, - b: whitespace, - withSize, - c: whitespace, - integer, -}); -const arg = NamedOr.build({ variable, memoryAddress, i8, i64, f64 }); -const sep = And.build([comma, whitespace]); -const args = ZeroOrMore.build( - NamedAnd.build({ arg, sep: ZeroOrOne.build(sep) }), -); -const line = And.build([sharp, natural]); -const deps = OneOrMore.build( - NamedAnd.build({ line, sep: ZeroOrOne.build(sep) }), -); -const statement = NamedAnd.build({ - result: ZeroOrOne.build( - NamedAnd.build({ memoryAddress, a: whitespace, eq, b: whitespace }), - ), - variable, - a: whitespace, - openParen, - args, - closeParen, - b: whitespace, - line, - dependsOn: ZeroOrOne.build( - NamedAnd.build({ - a: whitespace, - backArrow, - b: whitespace, - openBracket, - deps, - closeBracket, - }), - ), -}); -const memory = NamedAnd.build({ - globalMem, - memoryLines: OneOrMore.build( - NamedAnd.build({ a: whitespace, memoryLine, b: whitespace }), - ), -}); -const events = NamedAnd.build({ - customEvents, - eventLines: OneOrMore.build( - NamedAnd.build({ a: whitespace, eventLine, b: whitespace }), - ), -}); -const handler = NamedAnd.build({ - handlerLine, - statements: OneOrMore.build( - NamedAnd.build({ a: whitespace, statement, b: whitespace }), - ), - whitespace, -}); -export const aga = NamedAnd.build({ - header, - a: whitespace, - globalMemory: ZeroOrOne.build(memory), - b: whitespace, - customEvents: ZeroOrOne.build(events), - c: whitespace, - handlers: OneOrMore.build(handler), -}); - -export default aga; diff --git a/compiler/src/agatoagc.ts b/compiler/src/agatoagc.ts deleted file mode 100644 index f1d009f8a..000000000 --- a/compiler/src/agatoagc.ts +++ /dev/null @@ -1,246 +0,0 @@ -import { LP, LPNode, LPError, NamedAnd } from './lp'; - -import aga from './aga'; - -type EventLookup = { - [name: string]: { - eventId: bigint; - }; -}; - -// This project depends on BigNum and associated support in Node's Buffer, so must be >= Node 10.20 -// and does not work in the browser. It would be possible to implement a browser-compatible version -// but there is no need for it and it would make it harder to work with. -const agcHeader = Buffer.from('agc00001', 'utf8').readBigUInt64LE(0); -const eventdd = Buffer.from('eventdd:', 'utf8').readBigUInt64LE(0); -const handlerd = Buffer.from('handler:', 'utf8').readBigUInt64LE(0); -const lineno = Buffer.from('lineno: ', 'utf8').readBigUInt64LE(0); - -const ceil8 = (n: number) => Math.ceil(n / 8) * 8; -const int64ToUint64 = (n: bigint): bigint => { - const buf = Buffer.alloc(8); - buf.writeBigInt64LE(n, 0); - return buf.readBigUInt64LE(0); -}; - -const loadGlobalMem = (globalMemAst: LPNode): bigint[] => { - const globalMem = []; - const memoryLines = globalMemAst.get('memoryLines'); - for (const globalConst of memoryLines.getAll()) { - const memoryLine = globalConst.get('memoryLine'); - const value = memoryLine.get('value'); - if (value.has('i64')) { - const val = BigInt(value.t.replace(/i64$/, '')); - globalMem.push(val); - } else if (value.has('i32')) { - const val = BigInt(value.t.replace(/i32$/, '')); - globalMem.push(val); - } else if (value.has('i16')) { - const val = BigInt(value.t.replace(/i16$/, '')); - globalMem.push(val); - } else if (value.has('i8')) { - const val = BigInt(value.t.replace(/i8$/, '')); - globalMem.push(val); - } else if (value.has('f32')) { - const buf = Buffer.alloc(8); - buf.writeFloatLE(parseFloat(value.t.replace(/f32$/, ''))); - const val = buf.readBigUInt64LE(0); - globalMem.push(val); - } else if (value.has('f64')) { - const buf = Buffer.alloc(8); - buf.writeDoubleLE(parseFloat(value.t.replace(/f64$/, ''))); - const val = buf.readBigUInt64LE(0); - globalMem.push(val); - } else if (value.has('str')) { - let str: string; - try { - str = JSON.parse(value.t); // Will fail on strings with escape chars - } catch (e) { - // Hackery to get these strings to work - str = JSON.stringify(value.t.replace(/^["']/, '').replace(/["']$/, '')); - } - const len = BigInt(ceil8(str.length) + 8); - const buf = Buffer.alloc(Number(len)); - buf.writeBigInt64LE(BigInt(str.length), 0); - for (let i = 8; i < str.length + 8; i++) { - buf.writeInt8(str.charCodeAt(i - 8), i); - } - for (let i = 0; i < Number(len) / 8; i++) { - globalMem.push(buf.readBigUInt64LE(i * 8)); - } - } else if (value.has('bool')) { - const val = value.t === 'true' ? BigInt(1) : BigInt(0); - globalMem.push(val); - } else { - throw new Error( - `Strange AST parsing error, this should be unreachable: ${value}`, - ); - } - } - return globalMem; -}; - -const loadEventDecs = ( - eventAst: LPNode, - eventLookup: EventLookup, -): bigint[] => { - const eventLines = eventAst.get('eventLines'); - let customEventIdOffset = BigInt(0); - const eventMem = []; - for (const evt of eventLines.getAll()) { - const eventLine = evt.get('eventLine'); - const evtName = eventLine.get('variable').t; - const evtSize = int64ToUint64(BigInt(eventLine.get('integer').t)); - eventMem.push(eventdd, customEventIdOffset, evtSize); - eventLookup[evtName] = { - eventId: customEventIdOffset, - }; - customEventIdOffset++; - } - return eventMem; -}; - -const fill8 = (name: string) => { - const buf = Buffer.alloc(8, ' '.charCodeAt(0)); - for (let i = 0; i < name.length; i++) { - buf.writeInt8(name.charCodeAt(i), i); - } - return buf.readBigUInt64LE(0); -}; - -const loadStatements = ( - statements: LPNode, - eventLookup: EventLookup, -): bigint[] => { - const vec = []; - for (const statementAst of statements.getAll()) { - const statement = statementAst.get('statement'); - const line = BigInt(statement.get('line').get(1).t); - const dependsOn = statement.get('dependsOn'); - const deps = dependsOn - .get('deps') - .getAll() - .map((d) => BigInt(d.get('line').get(1).t)); - const fn = fill8(statement.get('variable').t); - const args = statement - .get('args') - .getAll() - .map((a) => { - const argOpt = a.get('arg'); - let out: bigint; - if (argOpt.has('variable')) { - out = eventLookup[argOpt.get('variable').t].eventId; - } else if (argOpt.has('memoryAddress')) { - out = int64ToUint64(BigInt(argOpt.get('memoryAddress').get(1).t)); - } else if (argOpt.has('i64')) { - out = BigInt(argOpt.t.replace(/i64$/, '')); - } else if (argOpt.has('i8')) { - out = BigInt(argOpt.t.replace(/i8$/, '')); - } else if (argOpt.has('f64')) { - const buf = Buffer.alloc(8); - buf.writeDoubleLE(parseFloat(argOpt.t.replace(/f64$/, ''))); - out = buf.readBigUInt64LE(0); - } - return out; - }); - if (args.length < 3) { - const resultAddress = - statement.get('result').t === '' - ? BigInt(0) - : BigInt(statement.get('result').get('memoryAddress').get(1).t); - args.push(resultAddress); - } - vec.push(lineno, line, BigInt(deps.length), ...deps, fn, ...args); - } - return vec; -}; - -const loadHandlers = ( - handlersAst: LPNode, - eventLookup: EventLookup, -): bigint[] => { - const handlers = handlersAst.getAll(); - const vec = []; - for (let i = 0; i < handlers.length; i++) { - const handler = handlers[i]; - const handlerHead = handler.get('handlerLine'); - const { eventId } = eventLookup[handlerHead.get('variable').t]; - const memSize = BigInt(handlerHead.get('integer').t); - vec.push(handlerd, eventId, memSize); - const statementVec = loadStatements(handler.get('statements'), eventLookup); - vec.push(...statementVec); - } - return vec; -}; - -const astToAgc = (ast: NamedAnd): Buffer => { - // Declare the AGC header - const vec: bigint[] = [agcHeader]; - if (ast.has('globalMemory')) { - const globalMemoryAst = ast.get('globalMemory'); - // Get the global memory - const globalMem = loadGlobalMem(globalMemoryAst); - // Compute the global memory size and declare that and add all of the global memory - const memSize = BigInt(globalMem.length * 8); - vec.push(memSize, ...globalMem); - } else { - vec.push(BigInt(0)); - } - // Declare the event lookup table (event string to id) with the singular special event `"start"` - const eventLookup = { - _start: { - eventId: (() => { - const buf = Buffer.from('"start" ', 'utf8'); - buf.writeUInt8(0x80, 7); - return buf.readBigUInt64LE(0); - })(), - }, - __conn: { - eventId: (() => { - const buf = Buffer.from('__conn ', 'utf8'); - buf.writeUInt8(0x80, 7); - return buf.readBigUInt64LE(0); - })(), - }, - __ctrl: { - eventId: (() => { - const buf = Buffer.from('__ctrl ', 'utf8'); - buf.writeUInt8(0x80, 7); - return buf.readBigUInt64LE(0); - })(), - }, - }; - // Load the events, get the event id offset (for reuse with closures) and the event declarations - const customEvents = ast.get('customEvents'); - const eventDecs = loadEventDecs(customEvents, eventLookup); - // Then add that to the output vector - vec.push(...eventDecs); - // Load the handlers - const handlers = ast.get('handlers'); - const handlerVec = loadHandlers(handlers, eventLookup); - vec.push(...handlerVec); - // All done, convert the BigInt array to a big buffer to write to a file - const outBuf = Buffer.alloc(vec.length * 8); - vec.forEach((n, i) => { - outBuf.writeBigUInt64LE(n < 0 ? int64ToUint64(n) : n, i * 8); - }); - return outBuf; -}; - -export const fromFile = (filename: string): Buffer => { - const lp = new LP(filename); - const ast = aga.apply(lp); - if (ast instanceof LPError) { - throw new Error(ast.msg); - } - return astToAgc(ast); -}; - -export const fromString = (str: string): Buffer => { - const lp = LP.fromText(str); - const ast = aga.apply(lp); - if (ast instanceof LPError) { - throw new Error(ast.msg); - } - return astToAgc(ast); -}; diff --git a/compiler/src/agctoagz.ts b/compiler/src/agctoagz.ts deleted file mode 100644 index bf9ae7818..000000000 --- a/compiler/src/agctoagz.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { gzipSync } from 'zlib'; -import { readFileSync } from 'fs'; - -export const fromFile = (filename: string): Buffer => - gzipSync(readFileSync(filename), { level: 9 }); -export const fromString = (buf: string | Buffer): Buffer => - gzipSync(buf, { level: 9 }); diff --git a/compiler/src/amm.ts b/compiler/src/amm.ts deleted file mode 100644 index 2fa10f6d7..000000000 --- a/compiler/src/amm.ts +++ /dev/null @@ -1,208 +0,0 @@ -import { - And, - CharSet, - NamedAnd, - NamedOr, - Not, - NulLP, - OneOrMore, - Or, - Token, - ZeroOrMore, - ZeroOrOne, -} from './lp'; - -// Defining AMM Tokens -const space = Token.build(' '); -const blank = OneOrMore.build(space); -const optblank = ZeroOrOne.build(blank); -const newline = Token.build('\n'); -const whitespace = OneOrMore.build(Or.build([space, newline])); -const colon = Token.build(':'); -const under = Token.build('_'); -const negate = Token.build('-'); -const dot = Token.build('.'); -const eq = Token.build('='); -const openParen = Token.build('('); -const closeParen = Token.build(')'); -const openCurly = Token.build('{'); -const closeCurly = Token.build('}'); -const openCaret = Token.build('<'); -const closeCaret = Token.build('>'); -const comma = Token.build(','); -const optcomma = ZeroOrOne.build(comma); -const sep = And.build([optblank, comma, optblank]); -const base10 = CharSet.build('0', '9'); -const natural = OneOrMore.build(base10); -const integer = And.build([ZeroOrOne.build(negate), natural]); -const real = And.build([integer, dot, natural]); -const lower = CharSet.build('a', 'z'); -const upper = CharSet.build('A', 'Z'); -const variable = And.build([ - OneOrMore.build(Or.build([under, lower, upper])), - ZeroOrMore.build(Or.build([under, lower, upper, natural])), -]); -const exit = Token.build('return'); -const t = Token.build('true'); -const f = Token.build('false'); -const bool = Or.build([t, f]); -const voidn = Token.build('void'); -const emit = Token.build('emit'); -const letn = Token.build('let'); -const constn = Token.build('const'); -const on = Token.build('on'); -const event = Token.build('event'); -const fn = Token.build('fn'); -const quote = Token.build('"'); -const escapeQuote = Token.build('\\"'); -const notQuote = Not.build('"'); -const str = And.build([ - quote, - ZeroOrMore.build(Or.build([escapeQuote, notQuote])), - quote, -]); -const value = NamedOr.build({ str, bool, real, integer }); -const decname = variable; -const typename = variable; -const typegenerics = NamedAnd.build({ - openCaret, - a: optblank, - generics: new NulLP(), // Circular dependency trick - b: optblank, - closeCaret, -}); -const fulltypename = NamedAnd.build({ - typename, - opttypegenerics: ZeroOrOne.build(typegenerics), -}); -// Ugly hackery around circular dependency -typegenerics.and.generics = NamedAnd.build({ - fulltypename, - cdr: ZeroOrMore.build( - NamedAnd.build({ - sep, - fulltypename, - }), - ), -}); - -const emits = NamedAnd.build({ - emit, - blank, - variable, - value: ZeroOrOne.build( - NamedAnd.build({ - blank, - variable, - }), - ), -}); -const events = NamedAnd.build({ - event, - blank, - variable, - a: optblank, - colon, - b: optblank, - fulltypename, -}); -const exits = NamedAnd.build({ exit, blank, variable, a: optblank }); -const calllist = ZeroOrMore.build( - NamedAnd.build({ variable, optcomma, optblank }), -); -const calls = NamedAnd.build({ - variable, - a: optblank, - openParen, - b: optblank, - calllist, - c: optblank, - closeParen, -}); -const assignables = NamedOr.build({ - functions: new NulLP(), // Circular dep trick - calls, - value, - variable, -}); -const constdeclaration = NamedAnd.build({ - constn, - a: blank, - decname, - b: optblank, - colon, - c: optblank, - fulltypename, - d: optblank, - eq, - e: optblank, - assignables, -}); -const letdeclaration = NamedAnd.build({ - letn, - a: blank, - decname, - b: optblank, - colon, - c: optblank, - fulltypename, - d: blank, - eq, - e: blank, - assignables, -}); -const declarations = NamedOr.build({ constdeclaration, letdeclaration }); -const assignments = NamedAnd.build({ - decname, - a: blank, - eq, - b: blank, - assignables, -}); -const statements = OneOrMore.build( - NamedOr.build({ - declarations, - assignments, - calls, - emits, - exits, - whitespace, - }), -); -const functionbody = NamedAnd.build({ - openCurly, - statements, - closeCurly, -}); -const arg = NamedAnd.build({ - variable, - a: optblank, - colon, - b: optblank, - fulltypename, -}); -const functions = NamedAnd.build({ - fn, - blank, - openParen, - args: And.build([ - ZeroOrMore.build(NamedAnd.build({ arg, sep })), - ZeroOrOne.build(NamedAnd.build({ arg, optblank })), - ]), - closeParen, - a: optblank, - colon, - b: optblank, - fulltypename, - c: optblank, - functionbody, -}); -assignables.or.functions = functions; -const handler = NamedAnd.build({ on, a: blank, variable, b: blank, functions }); -const amm = NamedAnd.build({ - a: optblank, - globalMem: ZeroOrMore.build(Or.build([constdeclaration, whitespace])), - eventDec: ZeroOrMore.build(Or.build([events, whitespace])), - handlers: OneOrMore.build(Or.build([handler, whitespace])), -}); -export default amm; diff --git a/compiler/src/ammtoaga/aga.ts b/compiler/src/ammtoaga/aga.ts deleted file mode 100644 index 0f860f88d..000000000 --- a/compiler/src/ammtoaga/aga.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { DepNode } from './depgraph'; - -export class Block { - type: string; - name: string; - memSize: number; - statements: Statement[]; - deps: string[]; - - constructor( - type: string, - name: string, - memSize: number, - statements: Statement[], - deps: string[], - ) { - this.type = type; - this.name = name; - this.memSize = memSize; - this.statements = statements; - this.deps = deps; - } - - build(): string { - const dependencies = []; - const idxByNode: Map = new Map(); - for (let ii = 0; ii < this.statements.length; ii++) { - const stmt = this.statements[ii]; - if (stmt.depNode === null) continue; - idxByNode.set(stmt.depNode, ii); - for (const upstream of stmt.depNode.upstream) { - if ( - idxByNode.get(upstream) !== null && - idxByNode.get(upstream) !== undefined - ) { - stmt.deps.push(idxByNode.get(upstream)); - dependencies.push({ - in: this.name, - stmt: ii, - dependsOn: idxByNode.get(upstream), - }); - } - } - } - return JSON.stringify(dependencies); - } - - toString() { - let b = `${this.type} for ${this.name} with size ${this.memSize}\n`; - this.statements.forEach((s) => (b += ` ${s.toString()}\n`)); - return b; - } -} - -export class Statement { - fn: string; - inArgs: [string, string] | [string, string, string]; - outArg: string | null; - line: number; - deps: number[]; - depNode: DepNode; - - constructor( - fn: string, - inArgs: [string, string] | [string, string, string], - outArg: string | null, - line: number, - deps: number[], - depNode: DepNode, - ) { - this.fn = fn; - this.inArgs = inArgs; - this.outArg = outArg; - this.line = line; - this.deps = deps; - this.depNode = depNode || null; - } - - toString() { - let s = ''; - if (this.outArg !== null) { - s += `${this.outArg} = `; - } - s += `${this.fn}(${this.inArgs.join(', ')}) #${this.line}`; - if (this.deps.length > 0) { - s += ` <- [${this.deps.map((d) => `#${d}`).join(', ')}]`; - } - return s; - } -} diff --git a/compiler/src/ammtoaga/depgraph.ts b/compiler/src/ammtoaga/depgraph.ts deleted file mode 100644 index 97a441a02..000000000 --- a/compiler/src/ammtoaga/depgraph.ts +++ /dev/null @@ -1,692 +0,0 @@ -import { LPNode, NamedAnd, NulLP, ZeroOrMore } from '../lp'; - -// this is just here for debugging purposes -const unhandled = (val: any, reason?: string) => { - console.error(`========== UNHANDLED: ${reason}`); - console.error(val); - console.error(); - throw new Error(); -}; - -export class DepGraph { - /** - * Goes by order of the statements in the AMM input. Note that this ordering - * is not preserved in the AGA output, so this can't be used for AGA dependencies. - */ - byOrder: DepNode[]; - /** - * The keys are the AMM variable name. Multiple nodes can be assigned to a given key, - * since a variable can be assigned to or mutated multiple times. Note that I said - * "mutated" - all mutations are inserted into the array, with the initial declaration - * (or in-scope assignment/mutation) being the first value in the list. - */ - byVar: { [varname: string]: DepNode[] }; - /** - * Uses the `LPNode` as the key to a `DepNode`. Note that this does not act - * recursively - it'll only work for the top-level nodes of the current handler - * or function. - */ - byLP: Map; - outerGraph?: DepGraph; - outerDeps: DepNode[]; - outerMuts: string[]; - params?: { [varname: string]: DepNode }; - - get isNop(): boolean { - return ( - this.byOrder.length === 0 || - this.byOrder.every((n) => n.closure != null && n.closure.isNop) - ); - } - - constructor(fn?: LPNode, outer?: DepGraph) { - this.byOrder = []; - this.byVar = {}; - this.byLP = new Map(); - this.outerGraph = outer || null; - this.outerDeps = []; - this.outerMuts = []; - this.params = null; - - if (fn !== null && fn !== undefined) { - fn = fn.get('functions'); - - if (fn.has('args')) this.buildParams([...fn.get('args').getAll()]); - - const stmts = fn - .get('functionbody') - .get('statements') - .getAll() - .filter((s) => !s.has('whitespace')); - this.build(stmts); - } - } - - private buildParams(params: LPNode[]) { - this.params = {}; - while (params.length > 0) { - let param = params.shift(); - if (param instanceof NamedAnd) { - if (param.has('arg')) { - param = param.get('arg'); - } - - if (param.has('variable') && param.get('variable').t.trim() !== '') { - // assign a very basic `DepNode` struct to avoid having to go through - // the whole constructor, since this node is just a param declaration - // and doesn't need to go through the whole shebang. We just need it - // for when we're generating the dependencies of the aga output - this.params[param.get('variable').t.trim()] = new DepNode( - param, - this, - true, - ); - } else { - unhandled(param, 'unknown param ast'); - } - } else if (param instanceof ZeroOrMore) { - params.unshift(...param.getAll()); - } else if (!(param instanceof NulLP)) { - unhandled(param, 'unknown ast type for function parameters'); - } - } - } - - build(stmts: LPNode[]) { - for (const stmt of stmts) { - const node = new DepNode(stmt, this); - // console.log(`mutates:`) - // console.log(node.mutates) - for (const mutated of node.mutates) { - if ( - this.outerGraph !== null && - this.outerGraph.getLastMutationFor(mutated) !== null - ) { - this.outerMuts.push(mutated); - } - - if (this.byVar[mutated] === null || this.byVar[mutated] === undefined) { - this.byVar[mutated] = []; - } - this.byVar[mutated].push(node); - } - this.byOrder.push(node); - this.byLP.set(stmt, node); - } - this.outerDeps = [...new Set(this.outerDeps)]; - } - - getLastMutationFor(varName: string): DepNode { - const nodes = this.byVar[varName]; - // console.log(`------- ${varName}`) - // console.log('nodes:') - // console.log(nodes) - if (nodes !== null && nodes !== undefined && nodes.length !== 0) { - return nodes[nodes.length - 1]; - } - - // if there's no mutation, check to see if it's a variable first - if ( - this.params !== null && - this.params[varName] !== null && - this.params[varName] !== undefined - ) { - // don't even make up a node for it, it's just dependent on the param - // which is always guaranteed to be satisfied - return this.params[varName]; - } - - // console.log('og:') - // console.log(this.outerGraph) - if (this.outerGraph !== null) { - const outer = this.outerGraph.getLastMutationFor(varName); - if (outer !== null) { - // console.log(`found outer for ${varName}`) - this.outerDeps.push(outer); - return outer; - } - } - - return null; - } - - toJSON(): Record { - return { - byOrder: this.byOrder.map((n) => n.toJSON()), - byVar: Object.keys(this.byVar), - byLP: this.byLP.size, - outerGraph: this.outerGraph !== null, - outerDeps: this.outerDeps.map((n) => n.toJSON()), - outerMuts: this.outerMuts, - }; - } -} - -export class DepNode { - stmt: string; - upstream: DepNode[]; - downstream: DepNode[]; - closure?: DepGraph; - mutates: string[]; - graph: DepGraph; - isParam: boolean; - - constructor(stmt: LPNode, graph: DepGraph, isParam?: boolean) { - this.stmt = stmt.t.trim(); - this.upstream = []; - this.downstream = []; - this.closure = null; - this.mutates = []; - this.graph = graph; - this.isParam = isParam || false; // if `isParam` is `true`, retains it. Otherwise, always at least `false` - - // if this node is a parameter declaration, don't do extra work - if (this.isParam) { - return; - } - - if (stmt.has('declarations')) { - let dec = stmt.get('declarations'); - if (dec.has('constdeclaration')) { - dec = dec.get('constdeclaration'); - } else if (dec.has('letdeclaration')) { - dec = dec.get('letdeclaration'); - } else { - unhandled(dec, 'dec kind'); - } - this.fromAssignment(dec); - } else if (stmt.has('assignments')) { - this.fromAssignment(stmt.get('assignments')); - } else if (stmt.has('calls')) { - this.fromCall(stmt.get('calls')); - } else if (stmt.has('emits')) { - const upstream = graph.getLastMutationFor( - stmt.get('emits').get('value').t.trim(), - ); - if (upstream !== null) { - this.upstream.push(upstream); - upstream.downstream.push(this); - } - } else if (stmt.has('exits')) { - this.fromExit(stmt.get('exits')); - } else { - unhandled(stmt, 'node top-level'); - } - this.upstream = [...new Set(this.upstream)]; - this.mutates = [...new Set(this.mutates)]; - } - - fromExit(assign: LPNode) { - if (assign.has('variable')) { - const upstream = this.graph.getLastMutationFor( - assign.get('variable').t.trim(), - ); - if (upstream !== null) { - this.upstream.push(upstream); - upstream.downstream.push(this); - } - } - } - - fromAssignment(assign: LPNode) { - // console.log(assign) - if (!assign.has('assignables')) { - unhandled(assign, 'non-assignment assignment?'); - } - - const decname = assign.get('decname').t.trim(); - // console.log(`decname: ${decname}`) - const prev = this.graph.getLastMutationFor(decname); - if (prev !== null) { - this.upstream.push(prev); - } - - this.mutates.push(decname); - if (assign.get('fulltypename').t.trim() === 'function') { - this.closure = new DepGraph(assign.get('assignables'), this.graph); - // for closures, only add upstream since the closure isn't actually - // evaluated until its called. this just makes it so that the actual - // use-site of the closure can inherit the upstream dependencies. - this.upstream.push(...this.closure.outerDeps); - this.mutates.push(...this.closure.outerMuts); - } else if (assign.has('assignables')) { - if (prev !== null) { - prev.downstream.push(this); - } - assign = assign.get('assignables'); - if (assign.has('calls')) { - this.fromCall(assign.get('calls')); - } else if (assign.has('value')) { - // do nothing - } else { - unhandled(assign, 'assignable'); - } - } else { - unhandled(assign, 'non-assignable... assignable... ?'); - } - } - - fromCall(call: LPNode) { - const opcodeName = call.get('variable').t.trim(); - const args = call - .get('calllist') - .getAll() - .map((c) => c.get('variable')); - const mutated = []; - const opMutability = opcodeParamMutabilities[opcodeName]; - if (opMutability === undefined || opMutability === null) { - unhandled(opMutability, 'opcode ' + opcodeName); - } - for (let ii = 0; ii < opMutability.length; ii++) { - if (opMutability[ii] === true) { - mutated.push(args[ii].t.trim()); - } else if (opMutability[ii] === null) { - // null indicates that the parameter expects a closure, - // so the mutability of the overall call depends on the - // mutability of the specified closure. Because of this, - // we have to grab the node for the closure declaration - // and use its mutabilities instead - - // the closure def will be the first node in the list - const closure = this.graph.getLastMutationFor(args[ii].t.trim()); - if (closure.closure) { - if (closure === null || closure === undefined) { - unhandled( - this.graph.byVar, - `no nodes declared for ${args[ii].t.trim()}`, - ); - } - mutated.push(...closure.mutates); - } else if (closure.isParam) { - mutated.push(closure); - } else { - unhandled(closure, 'expected to inherit mutations'); - } - } - } - this.mutates.push(...mutated); - // console.log('---') - // console.log(this.stmt) - for (const arg of args) { - // console.log(arg) - const upstream = this.graph.getLastMutationFor(arg.t.trim()); - // console.log(upstream) - if (upstream !== null) { - if (upstream.closure !== null) { - // if it's a closure, inherit the upstreams - this.upstream.push(...upstream.upstream); - } else { - this.upstream.push(upstream); - upstream.downstream.push(this); - } - } - } - } - - toJSON(): Record { - let closure = null; - if (this.closure) closure = this.closure.toJSON(); - return { - stmt: this.stmt.replace(/\n/g, '\\n'), - upstream: this.upstream.length, - downstream: this.downstream.length, - closure, - mutates: this.mutates, - }; - } -} - -export const opcodeParamMutabilities = { - i8f64: [false], - i16f64: [false], - i32f64: [false], - i64f64: [false], - f32f64: [false], - strf64: [false], - boolf64: [false], - i8f32: [false], - i16f32: [false], - i32f32: [false], - i64f32: [false], - f64f32: [false], - strf32: [false], - boolf32: [false], - i8i64: [false], - i16i64: [false], - i32i64: [false], - f32i64: [false], - f64i64: [false], - stri64: [false], - booli64: [false], - i8i32: [false], - i16i32: [false], - i64i32: [false], - f32i32: [false], - f64i32: [false], - stri32: [false], - booli32: [false], - i8i16: [false], - i32i16: [false], - i64i16: [false], - f32i16: [false], - f64i16: [false], - stri16: [false], - booli16: [false], - i16i8: [false], - i32i8: [false], - i64i8: [false], - f32i8: [false], - f64i8: [false], - stri8: [false], - booli8: [false], - i8bool: [false], - i16bool: [false], - i32bool: [false], - i64bool: [false], - f32bool: [false], - f64bool: [false], - strbool: [false], - i8str: [false], - i16str: [false], - i32str: [false], - i64str: [false], - f32str: [false], - f64str: [false], - boolstr: [false], - addi8: [false, false], - addi16: [false, false], - addi32: [false, false], - addi64: [false, false], - addf32: [false, false], - addf64: [false, false], - subi8: [false, false], - subi16: [false, false], - subi32: [false, false], - subi64: [false, false], - subf32: [false, false], - subf64: [false, false], - negi8: [false, false], - negi16: [false, false], - negi32: [false, false], - negi64: [false, false], - negf32: [false, false], - negf64: [false, false], - absi8: [false, false], - absi16: [false, false], - absi32: [false, false], - absi64: [false, false], - absf32: [false, false], - absf64: [false, false], - muli8: [false, false], - muli16: [false, false], - muli32: [false, false], - muli64: [false, false], - mulf32: [false, false], - mulf64: [false, false], - divi8: [false, false], - divi16: [false, false], - divi32: [false, false], - divi64: [false, false], - divf32: [false, false], - divf64: [false, false], - modi8: [false, false], - modi16: [false, false], - modi32: [false, false], - modi64: [false, false], - powi8: [false, false], - powi16: [false, false], - powi32: [false, false], - powi64: [false, false], - powf32: [false, false], - powf64: [false, false], - sqrtf32: [false, false], - sqrtf64: [false, false], - saddi8: [false, false], - saddi16: [false, false], - saddi32: [false, false], - saddi64: [false, false], - saddf32: [false, false], - saddf64: [false, false], - ssubi8: [false, false], - ssubi16: [false, false], - ssubi32: [false, false], - ssubi64: [false, false], - ssubf32: [false, false], - ssubf64: [false, false], - snegi8: [false, false], - snegi16: [false, false], - snegi32: [false, false], - snegi64: [false, false], - snegf32: [false, false], - snegf64: [false, false], - sabsi8: [false, false], - sabsi16: [false, false], - sabsi32: [false, false], - sabsi64: [false, false], - sabsf32: [false, false], - sabsf64: [false, false], - smuli8: [false, false], - smuli16: [false, false], - smuli32: [false, false], - smuli64: [false, false], - smulf32: [false, false], - smulf64: [false, false], - sdivi8: [false, false], - sdivi16: [false, false], - sdivi32: [false, false], - sdivi64: [false, false], - sdivf32: [false, false], - sdivf64: [false, false], - spowi8: [false, false], - spowi16: [false, false], - spowi32: [false, false], - spowi64: [false, false], - spowf32: [false, false], - spowf64: [false, false], - andi8: [false, false], - andi16: [false, false], - andi32: [false, false], - andi64: [false, false], - andbool: [false, false], - ori8: [false, false], - ori16: [false, false], - ori32: [false, false], - ori64: [false, false], - orbool: [false, false], - xori8: [false, false], - xori16: [false, false], - xori32: [false, false], - xori64: [false, false], - xorbool: [false, false], - noti8: [false, false], - noti16: [false, false], - noti32: [false, false], - noti64: [false, false], - notbool: [false, false], - nandi8: [false, false], - nandi16: [false, false], - nandi32: [false, false], - nandi64: [false, false], - nandboo: [false, false], - nori8: [false, false], - nori16: [false, false], - nori32: [false, false], - nori64: [false, false], - norbool: [false, false], - xnori8: [false, false], - xnori16: [false, false], - xnori32: [false, false], - xnori64: [false, false], - xnorboo: [false, false], - eqi8: [false, false], - eqi16: [false, false], - eqi32: [false, false], - eqi64: [false, false], - eqf32: [false, false], - eqf64: [false, false], - eqbool: [false, false], - eqstr: [false, false], - neqi8: [false, false], - neqi16: [false, false], - neqi32: [false, false], - neqi64: [false, false], - neqf32: [false, false], - neqf64: [false, false], - neqbool: [false, false], - neqstr: [false, false], - lti8: [false, false], - lti16: [false, false], - lti32: [false, false], - lti64: [false, false], - ltf32: [false, false], - ltf64: [false, false], - ltstr: [false, false], - ltei8: [false, false], - ltei16: [false, false], - ltei32: [false, false], - ltei64: [false, false], - ltef32: [false, false], - ltef64: [false, false], - ltestr: [false, false], - gti8: [false, false], - gti16: [false, false], - gti32: [false, false], - gti64: [false, false], - gtf32: [false, false], - gtf64: [false, false], - gtstr: [false, false], - gtei8: [false, false], - gtei16: [false, false], - gtei32: [false, false], - gtei64: [false, false], - gtef32: [false, false], - gtef64: [false, false], - gtestr: [false, false], - httpreq: [false], - httplsn: [false], - httpsend: [false], - execop: [false], - waitop: [false], - syncop: [false, false], - catstr: [false, false], - catarr: [false, false], - split: [false, false], - repstr: [false, false], - reparr: [false, false], - matches: [false, false], - indstr: [false, false], - indarrf: [false, false], - indarrv: [false, false], - lenstr: [false], - lenarr: [false], - trim: [false], - condfn: [false, null], - pusharr: [true, false, false], - pushf: [true, false], - pushv: [true, false], - poparr: [true], - delindx: [true, false], - each: [false, null], - eachl: [false, null], - map: [false, null], - mapl: [false, null], - reducel: [false, null], - reducep: [false, null], - foldl: [false, null], - foldp: [false, null], - filter: [false, null], - filterl: [false, null], - find: [false, null], - findl: [false, null], - every: [false, null], - everyl: [false, null], - some: [false, null], - somel: [false, null], - join: [false, false], - newarr: [false], - stdoutp: [false], - stderrp: [false], - exitop: [false], - copyfrom: [false, false], - copytof: [true, false, false], - copytov: [true, false, false], - register: [false, false], - copyi8: [false], - copyi16: [false], - copyi32: [false], - copyi64: [false], - copyvoid: [false], - copyf32: [false], - copyf64: [false], - copybool: [false], - copystr: [false], - copyarr: [false], - zeroed: [], - lnf64: [false], - logf64: [false], - sinf64: [false], - cosf64: [false], - tanf64: [false], - asinf64: [false], - acosf64: [false], - atanf64: [false], - sinhf64: [false], - coshf64: [false], - tanhf64: [false], - error: [false], - reff: [false], - refv: [false], - noerr: [], - errorstr: [false], - someM: [false, false], - noneM: [], - - // TODO: RFC-12 might impact these: - isSome: [false], - isNone: [false], - getOrM: [false, false], - getMaybe: [false], - okR: [false, false], - err: [false], - isOk: [false], - isErr: [false], - getOrR: [false, false], - getOrRS: [false, false], - getR: [false], - getErr: [false, false], - resfrom: [false, false], - mainE: [false, false], - altE: [false, false], - isMain: [false], - isAlt: [false], - mainOr: [false, false], - altOr: [false, false], - getMain: [false], - getAlt: [false], - - hashf: [false], - hashv: [false], - dssetf: [true, false, false], // TODO: is this right? should i be marking the DS name as being mutated? - dssetv: [true, false, false], - dshas: [false, false], - dsdel: [true, false], - dsgetf: [false, false], - dsgetv: [false, false], - dsrrun: [false, false], - dsmrun: [false, false], - dsrwith: [false, false], - dsmwith: [false, false], - dsmonly: [false, false], - dswonly: [false, false], - dsrclos: [false, false], - dsmclos: [false, false], - getcs: [], - newseq: [false], - seqnext: [false], - seqeach: [false, null], - seqwhile: [false, null, null], // TODO: ok so i don't *want* to make the 2nd value `null`, but it's not impossible for someone to mutate a value in the second function... - seqdo: [false, null], - selfrec: [false, false], // FIXME: there should be a way to handle recursive dependencies, but this works for now. - seqrec: [false, null], - tcptun: [false], -}; diff --git a/compiler/src/ammtoaga/index.ts b/compiler/src/ammtoaga/index.ts deleted file mode 100644 index 2643c4dca..000000000 --- a/compiler/src/ammtoaga/index.ts +++ /dev/null @@ -1,841 +0,0 @@ -import { LP, LPNode, LPError, NamedAnd, NulLP } from '../lp'; - -import amm from '../amm'; -import { DepGraph } from './depgraph'; -import { Block, Statement } from './aga'; - -type AddressMap = { - [name: string]: bigint | number; -}; -type EventDecs = { - [name: string]: number; -}; -type HandlerMem = { - memSize: number; - addressMap: AddressMap; -}; - -// This project depends on BigNum and associated support in Node's Buffer, so must be >= Node 10.20 -// and does not work in the browser. It would be possible to implement a browser-compatible version -// but there is no need for it and it would make it harder to work with. -const ceil8 = (n: number) => BigInt(Math.ceil(n / 8) * 8); -const CLOSURE_ARG_MEM_START = BigInt(Math.pow(-2, 63)); - -// special closure that does nothing -const NOP_CLOSURE = BigInt('-9223372036854775808'); - -const loadGlobalMem = (globalMemAst: LPNode[], addressMap: AddressMap) => { - const suffixes = { - int64: 'i64', - int32: 'i32', - int16: 'i16', - int8: 'i8', - float64: 'f64', - float32: 'f32', - }; - - const globalMem = {}; - let currentOffset = -1n; - for (const globalConst of globalMemAst) { - const rec = globalConst.get(); - if (!(rec instanceof NamedAnd)) continue; - let offset = 8n; - let val = rec.get('assignables').t.trim(); - const typename = rec.get('fulltypename').t.trim(); - if (typename === 'string') { - let str: string; - try { - // Will fail on strings with escape chars - str = JSON.parse(val); - } catch (e) { - // Hackery to get these strings to work - str = JSON.stringify(val.replace(/^["']/, '').replace(/["']$/, '')); - } - offset = ceil8(str.length) + 8n; - } else if (suffixes.hasOwnProperty(typename)) { - val += suffixes[typename]; - } else if (typename !== 'bool') { - throw new Error(rec.get('fulltypename').t + ' not yet implemented'); - } - globalMem[`@${currentOffset}`] = val; - addressMap[rec.get('decname').t] = currentOffset; - currentOffset -= offset; - } - return globalMem; -}; - -const loadEventDecs = (eventAst: LPNode[]) => { - const eventMem = {}; - for (const evt of eventAst) { - const rec = evt.get(); - if (!(rec instanceof NamedAnd)) continue; - const evtName = rec.get('variable').t.trim(); - const evtSize = - rec.get('fulltypename').t.trim() === 'void' - ? 0 - : [ - 'int8', - 'int16', - 'int32', - 'int64', - 'float32', - 'float64', - 'bool', - ].includes(rec.get('fulltypename').t.trim()) - ? 8 - : -1; - eventMem[evtName] = evtSize; - } - return eventMem; -}; - -const getFunctionbodyMem = (functionbody: LPNode) => { - let memSize = 0; - const addressMap = {}; - for (const statement of functionbody.get('statements').getAll()) { - if (statement.has('declarations')) { - if (statement.get('declarations').has('constdeclaration')) { - if ( - statement - .get('declarations') - .get('constdeclaration') - .get('assignables') - .has('functions') - ) { - // Because closures re-use their parent memory space, their own memory needs to be included - const closureMem = getFunctionbodyMem( - statement - .get('declarations') - .get('constdeclaration') - .get('assignables') - .get('functions') - .get('functionbody'), - ); - Object.keys(closureMem.addressMap).forEach( - (name) => - (addressMap[name] = closureMem.addressMap[name] + memSize), - ); - memSize += closureMem.memSize; - } else { - addressMap[ - statement - .get('declarations') - .get('constdeclaration') - .get('decname') - .t.trim() - ] = memSize; - memSize += 1; - } - } else { - addressMap[ - statement - .get('declarations') - .get('letdeclaration') - .get('decname') - .t.trim() - ] = memSize; - memSize += 1; - } - } - } - return { - memSize, - addressMap, - }; -}; - -const getHandlersMem = (handlers: LPNode[]) => - handlers - .map((h) => h.get()) - .filter((h) => h instanceof NamedAnd) - .map((handler) => { - const handlerMem = getFunctionbodyMem( - handler.get('functions').get('functionbody'), - ); - let arg = handler.get('functions').get('args').get(0).get(0).get('arg'); - if (arg instanceof NulLP) { - arg = handler.get('functions').get('args').get(1).get('arg'); - } - if (!(arg instanceof NulLP)) { - // Increase the memory usage and shift *everything* down, then add the new address - handlerMem.memSize += 1; - Object.keys(handlerMem.addressMap).forEach( - (name) => (handlerMem.addressMap[name] += 1), - ); - handlerMem.addressMap[arg.get('variable').t.trim()] = 0; - } - return handlerMem; - }); - -const closuresFromDeclaration = ( - declaration: LPNode, - closureMem: HandlerMem, - eventDecs: EventDecs, - addressMap: AddressMap, - // For each scope branch, determine a unique argument rereference so nested scopes can access - // parent scope arguments - argRerefOffset: number, - scope: string[], - depGraph: DepGraph, -) => { - const name = declaration.get('constdeclaration').get('decname').t.trim(); - if ( - depGraph.byVar[name] === null || - depGraph.byVar[name] === undefined || - depGraph.byVar[name].length === 0 || - depGraph.byVar[name][0].closure === null - ) { - throw new Error( - 'trying to build a closure, but the dependency graph did not build a closure', - ); - } - const graph = depGraph.byVar[name][0].closure; - const fn = declaration - .get('constdeclaration') - .get('assignables') - .get('functions'); - let fnArgs = []; - fn.get('args') - .getAll()[0] - .getAll() - .forEach((argdef) => { - fnArgs.push(argdef.get('arg').get('variable').t); - }); - if (fn.get('args').getAll()[1].has()) { - fnArgs.push( - ...fn - .get('args') - .getAll()[1] - .getAll() - .map((t) => t.get('variable').t), - ); - fnArgs = fnArgs.filter((t) => t !== ''); - } - fnArgs.forEach((arg) => { - addressMap[arg + name] = CLOSURE_ARG_MEM_START + BigInt(argRerefOffset); - argRerefOffset++; - }); - const allStatements = declaration - .get('constdeclaration') - .get('assignables') - .get('functions') - .get('functionbody') - .get('statements') - .getAll(); - const statements = allStatements.filter( - (statement) => - !( - statement.has('declarations') && - statement.get('declarations').has('constdeclaration') && - statement - .get('declarations') - .get('constdeclaration') - .get('assignables') - .has('functions') - ), - ); - const otherClosures = allStatements - .filter( - (statement) => - statement.has('declarations') && - statement.get('declarations').has('constdeclaration') && - statement - .get('declarations') - .get('constdeclaration') - .get('assignables') - .has('functions'), - ) - .map((s) => - closuresFromDeclaration( - s.get('declarations'), - closureMem, - eventDecs, - addressMap, - argRerefOffset, - [name, ...scope], // Newest scope gets highest priority - graph, - ), - ) - .filter((clos) => clos !== null) - .reduce( - (obj, rec) => ({ - ...obj, - ...rec, - }), - {}, - ); - eventDecs[name] = 0; - - if (!graph.isNop) { - otherClosures[name] = { - name, - fn, - statements, - closureMem, - scope: [name, ...scope], - graph, - }; - } else { - addressMap[name] = NOP_CLOSURE; - } - - return otherClosures; -}; - -const extractClosures = ( - handlers: LPNode[], - handlerMem: HandlerMem[], - eventDecs: EventDecs, - addressMap: AddressMap, - depGraphs: DepGraph[], -) => { - let closures = {}; - const recs = handlers.filter((h) => h.get() instanceof NamedAnd); - for (let i = 0; i < recs.length; i++) { - const rec = recs[i].get(); - const closureMem = handlerMem[i]; - const handlerGraph = depGraphs[i]; - for (const statement of rec - .get('functions') - .get('functionbody') - .get('statements') - .getAll()) { - if ( - statement.has('declarations') && - statement.get('declarations').has('constdeclaration') && - statement - .get('declarations') - .get('constdeclaration') - .get('assignables') - .has('functions') - ) { - // It's a closure, first try to extract any inner closures it may have - const innerClosures = closuresFromDeclaration( - statement.get('declarations'), - closureMem, - eventDecs, - addressMap, - 5, - [], - handlerGraph, - ); - closures = { - ...closures, - ...innerClosures, - }; - } - } - } - return Object.values(closures); -}; - -const loadStatements = ( - statements: LPNode[], - localMem: AddressMap, - globalMem: AddressMap, - fn: LPNode, - fnName: string, - isClosure: boolean, - closureScope: string[], - depGraph: DepGraph, -) => { - const vec = []; - let line = 0; - const localMemToLine = {}; - statements = statements.filter((s) => !s.has('whitespace')); - let fnArgs = []; - fn.get('args') - .getAll()[0] - .getAll() - .forEach((argdef) => { - fnArgs.push(argdef.get('arg').get('variable').t); - }); - if (fn.get('args').getAll()[1].has()) { - fnArgs.push( - ...fn - .get('args') - .getAll()[1] - .getAll() - .map((t) => t.get('variable').t), - ); - fnArgs = fnArgs.filter((t) => t !== ''); - } - const mutatedArgs = {}; - fnArgs.forEach((arg, i) => { - if (globalMem.hasOwnProperty(arg + fnName)) { - mutatedArgs[arg + fnName] = false; - const resultAddress = globalMem[arg + fnName]; - const val = CLOSURE_ARG_MEM_START + BigInt(1) + BigInt(i); - const s = new Statement( - 'refv', - [`@${val}`, '@0'], - `@${resultAddress}`, - line, - [], - depGraph.params[arg] || null, - ); - vec.push(s); - line += 1; - } - }); - for (let idx = 0; idx < statements.length; idx++) { - const statement = statements[idx]; - if ( - statement.has('declarations') && - statement.get('declarations').has('constdeclaration') && - statement - .get('declarations') - .get('constdeclaration') - .get('assignables') - .has('functions') - ) { - // It's a closure, skip it - continue; - } - const node = depGraph.byLP.get(statement); - const hasClosureArgs = isClosure && fnArgs.length > 0; - let s: Statement; - if (statement.has('declarations')) { - const dec = statement.get('declarations').has('constdeclaration') - ? statement.get('declarations').get('constdeclaration') - : statement.get('declarations').get('letdeclaration'); - const resultAddress = localMem[dec.get('decname').t.trim()]; - localMemToLine[dec.get('decname').t.trim()] = line; - const assignables = dec.get('assignables'); - if (assignables.has('functions')) { - throw new Error("This shouldn't be possible!"); - } else if (assignables.has('calls')) { - const call = assignables.get('calls'); - const fnName = call.get('variable').t.trim(); - const vars = ( - call.has('calllist') ? call.get('calllist').getAll() : [] - ).map((v) => v.get('variable').t.trim()); - const args = vars - .map((v) => { - if (localMem.hasOwnProperty(v)) { - return localMem[v]; - } else if (globalMem.hasOwnProperty(v)) { - return globalMem[v]; - } else if ( - Object.keys(globalMem).some((k) => - closureScope.map((s) => v + s).includes(k), - ) - ) { - return globalMem[ - closureScope - .map((s) => v + s) - .find((k) => Object.keys(globalMem).includes(k)) - ]; - } else if (hasClosureArgs) { - return ( - CLOSURE_ARG_MEM_START + BigInt(1) + BigInt(fnArgs.indexOf(v)) - ); - } else { - return v; - } - }) - .map((a) => (typeof a === 'string' ? a : `@${a}`)); - while (args.length < 2) args.push('@0'); - s = new Statement( - fnName, - args as [string, string], - `@${resultAddress}`, - line, - [], - node, - ); - } else if (assignables.has('value')) { - // Only required for `let` statements - let fn: string; - let val: string; - switch (dec.get('fulltypename').t.trim()) { - case 'int64': - fn = 'seti64'; - val = assignables.t + 'i64'; - break; - case 'int32': - fn = 'seti32'; - val = assignables.t + 'i32'; - break; - case 'int16': - fn = 'seti16'; - val = assignables.t + 'i16'; - break; - case 'int8': - fn = 'seti8'; - val = assignables.t + 'i8'; - break; - case 'float64': - fn = 'setf64'; - val = assignables.t + 'f64'; - break; - case 'float32': - fn = 'setf32'; - val = assignables.t + 'f32'; - break; - case 'bool': - fn = 'setbool'; - val = assignables.t === 'true' ? '1i8' : '0i8'; // Bools are bytes in the runtime - break; - case 'string': - fn = 'setestr'; - val = '0i64'; - break; - default: - throw new Error( - `Unsupported variable type ${dec.get('fulltypename').t}`, - ); - } - s = new Statement(fn, [val, '@0'], `@${resultAddress}`, line, [], node); - } else if (assignables.has('variable')) { - throw new Error('This should have been squashed'); - } - } else if (statement.has('assignments')) { - const asgn = statement.get('assignments'); - const asgnName = asgn.get('decname').t.trim(); - let resultAddress; - if (localMem.hasOwnProperty(asgnName)) { - resultAddress = localMem[asgnName]; - } else if (hasClosureArgs && fnArgs.indexOf(asgnName) > -1) { - resultAddress = globalMem[asgnName + fnName]; - mutatedArgs[asgnName + fnName] = true; - } else { - throw new Error(`Could not find variable ${asgnName}.`); - } - localMemToLine[resultAddress.toString()] = line; - const assignables = asgn.get('assignables'); - if (assignables.has('functions')) { - throw new Error("This shouldn't be possible!"); - } else if (assignables.has('calls')) { - const call = assignables.get('calls'); - const fnName = call.get('variable').t.trim(); - const vars = ( - call.has('calllist') ? call.get('calllist').getAll() : [] - ).map((v) => v.get('variable').t.trim()); - const hasClosureArgs = isClosure && vars.length > 0; - const args = vars - .map((v) => { - if (localMem.hasOwnProperty(v)) { - return localMem[v]; - } else if (globalMem.hasOwnProperty(v)) { - return globalMem[v]; - } else if ( - Object.keys(globalMem).some((k) => - closureScope.map((s) => v + s).includes(k), - ) - ) { - return globalMem[ - closureScope - .map((s) => v + s) - .find((k) => Object.keys(globalMem).includes(k)) - ]; - } else if (hasClosureArgs) { - return ( - CLOSURE_ARG_MEM_START + BigInt(1) + BigInt(fnArgs.indexOf(v)) - ); - } else return v; - }) - .map((a) => (typeof a === 'string' ? a : `@${a}`)); - while (args.length < 2) args.push('@0'); - s = new Statement( - fnName, - args as [string, string], - `@${resultAddress}`, - line, - [], - node, - ); - } else if (assignables.has('value')) { - // Only required for `let` statements - let fn: string; - let val: string; - // TODO: Relying on little-endian trimming integers correctly and doesn't support float32 - // correctly. Need to find the correct type data from the original variable. - const valStr = assignables.t; - if (valStr[0] === '"' || valStr[0] === "'") { - // It's a string, which doesn't work here... - fn = 'setestr'; - val = '0i64'; - } else if (valStr[0] === 't' || valStr[0] === 'f') { - // It's a bool - fn = 'setbool'; - val = assignables.t === 'true' ? '1i8' : '0i8'; // Bools are bytes in the runtime - } else if (valStr.indexOf('.') > -1) { - // It's a floating point number, assume 64-bit - fn = 'setf64'; - val = valStr + 'f64'; - } else { - // It's an integer. i64 will "work" for now - fn = 'seti64'; - val = valStr + 'i64'; - } - s = new Statement(fn, [val, '@0'], `@${resultAddress}`, line, [], node); - } else if (assignables.has('variable')) { - throw new Error('This should have been squashed'); - } - } else if (statement.has('calls')) { - const call = statement.get('calls'); - const fnName = call.get('variable').t.trim(); - const vars = ( - call.has('calllist') ? call.get('calllist').getAll() : [] - ).map((v) => v.get('variable').t.trim()); - const hasClosureArgs = isClosure && vars.length > 0; - const args = vars - .map((v) => { - if (localMem.hasOwnProperty(v)) { - return localMem[v]; - } else if (globalMem.hasOwnProperty(v)) { - return globalMem[v]; - } else if ( - Object.keys(globalMem).some((k) => - closureScope.map((s) => v + s).includes(k), - ) - ) { - return globalMem[ - closureScope - .map((s) => v + s) - .find((k) => Object.keys(globalMem).includes(k)) - ]; - } else if (hasClosureArgs) { - return ( - CLOSURE_ARG_MEM_START + BigInt(1) + BigInt(fnArgs.indexOf(v)) - ); - } else return v; - }) - .map((a) => (typeof a === 'string' ? a : `@${a}`)); - while (args.length < 3) args.push('@0'); - s = new Statement( - fnName, - args as [string, string, string], - null, - line, - [], - node, - ); - } else if (statement.has('emits')) { - const emit = statement.get('emits'); - const evtName = emit.get('variable').t.trim(); - const payloadVar = emit.has('value') - ? emit.get('value').t.trim() - : undefined; - const payload = !payloadVar - ? 0 - : localMem.hasOwnProperty(payloadVar) - ? localMem[payloadVar] - : globalMem.hasOwnProperty(payloadVar) - ? globalMem[payloadVar] - : payloadVar; - s = new Statement( - 'emit', - [evtName, typeof payload === 'string' ? payload : `@${payload}`], - null, - line, - [], - node, - ); - } else if (statement.has('exits')) { - const exit = statement.get('exits'); - const exitVar = exit.get('variable').t.trim(); - const exitVarType = localMem.hasOwnProperty(exitVar) - ? 'variable' - : globalMem.hasOwnProperty(exitVar) && - typeof globalMem[exitVar] !== 'string' - ? 'fixed' - : 'variable'; - const vars = [exitVar]; - const args = vars - .map((v) => { - if (localMem.hasOwnProperty(v)) { - return localMem[v]; - } else if (globalMem.hasOwnProperty(v)) { - return globalMem[v]; - } else if ( - Object.keys(globalMem).some((k) => - closureScope.map((s) => v + s).includes(k), - ) - ) { - return globalMem[ - closureScope - .map((s) => v + s) - .find((k) => Object.keys(globalMem).includes(k)) - ]; - } else if (hasClosureArgs) { - return ( - CLOSURE_ARG_MEM_START + BigInt(1) + BigInt(fnArgs.indexOf(v)) - ); - } else return v; - }) - .map((a) => (typeof a === 'string' ? a : `@${a}`)); - while (args.length < 2) args.push('@0'); - const ref = exitVarType === 'variable' ? 'refv' : 'reff'; - s = new Statement( - ref, - args as [string, string], - `@${CLOSURE_ARG_MEM_START}`, - line, - [], - node, - ); - } - vec.push(s); - line += 1; - } - fnArgs.forEach((arg, i) => { - if (globalMem.hasOwnProperty(arg + fnName) && mutatedArgs[arg + fnName]) { - const resultAddress = globalMem[arg + fnName]; - const val = CLOSURE_ARG_MEM_START + BigInt(1) + BigInt(i); - const s = new Statement( - 'refv', - [`@${resultAddress}`, '@0'], - `@${val}`, - line, - [], - depGraph.params[arg] || null, - ); - vec.push(s); - line += 1; - } - }); - return vec; -}; - -const loadHandlers = ( - handlers: LPNode[], - handlerMem: HandlerMem[], - globalMem: AddressMap, - depGraphs: DepGraph[], -) => { - const vec = []; - const recs = handlers.filter((h) => h.get() instanceof NamedAnd); - for (let i = 0; i < recs.length; i++) { - const handler = recs[i].get(); - const eventName = handler.get('variable').t.trim(); - const memSize = handlerMem[i].memSize; - const localMem = handlerMem[i].addressMap; - const h = new Block( - 'handler', - eventName, - memSize, - loadStatements( - handler.get('functions').get('functionbody').get('statements').getAll(), - localMem, - globalMem, - handler.get('functions'), - eventName, - false, - [], - depGraphs[i], - ), - [], - ); - vec.push(h); - } - return vec; -}; - -const loadClosures = (closures: any[], globalMem: AddressMap) => { - const vec = []; - for (let i = 0; i < closures.length; i++) { - const closure = closures[i]; - const eventName = closure.name; - const memSize = closure.closureMem.memSize; - const localMem = closure.closureMem.addressMap; - const c = new Block( - 'closure', - eventName, - memSize, - loadStatements( - closure.statements, - localMem, - globalMem, - closure.fn, - eventName, - true, - closure.scope, - closure.graph, - ), - [], - ); - vec.push(c); - } - return vec; -}; - -const ammToAga = (amm: LPNode) => { - // Declare the AGA header - let outStr = 'Alan Graphcode Assembler v0.0.1\n\n'; - // Get the global memory and the memory address map (var name to address ID) - const addressMap = {}; - const globalMem = loadGlobalMem(amm.get('globalMem').getAll(), addressMap); - if (Object.keys(globalMem).length > 0) { - // Output the global memory - outStr += 'globalMem\n'; - Object.keys(globalMem).forEach( - (addr) => (outStr += ` ${addr}: ${globalMem[addr]}\n`), - ); - outStr += '\n'; - } - // Load the events, get the event id offset (for reuse with closures) and the event declarations - const eventDecs = loadEventDecs(amm.get('eventDec').getAll()); - // Determine the amount of memory to allocate per handler and map declarations to addresses - const handlerMem = getHandlersMem(amm.get('handlers').getAll()); - const depGraphs: DepGraph[] = []; - for (let handler of amm.get('handlers').getAll()) { - handler = handler.get(); - if (handler instanceof NamedAnd) { - depGraphs.push(new DepGraph(handler)); - } - } - // console.log(depGraphs.map(g => JSON.stringify(g.toJSON())).join(',')) - const closures = extractClosures( - amm.get('handlers').getAll(), - handlerMem, - eventDecs, - addressMap, - depGraphs, - ); - // Make sure closures are accessible as addresses for statements to use - closures.forEach((c: any) => { - if (addressMap[c.name] !== NOP_CLOSURE) { - addressMap[c.name] = c.name; - } - }); - // Then output the custom events, which may include closures, if needed - if (Object.keys(eventDecs).length > 0) { - outStr += 'customEvents\n'; - Object.keys(eventDecs).forEach( - (evt) => (outStr += ` ${evt}: ${eventDecs[evt]}\n`), - ); - outStr += '\n'; - } - // Load the handlers and load the closures (as handlers) if present - const handlerVec = loadHandlers( - amm.get('handlers').getAll(), - handlerMem, - addressMap, - depGraphs, - ); - const closureVec = loadClosures(closures, addressMap); - [...handlerVec, ...closureVec].map((b) => b.build()); - // console.log(([...handlerVec, ...closureVec]).map(b => b.build()).join(',')) - const blockVec = [...handlerVec, ...closureVec].map((b) => b.toString()); - outStr += blockVec.join('\n'); - return outStr; -}; - -export const fromFile = (filename: string) => { - const lp = new LP(filename); - const ast = amm.apply(lp); - if (ast instanceof LPError) { - throw new Error(ast.msg); - } - return ammToAga(ast); -}; -export const fromString = (str: string) => { - const lp = LP.fromText(str); - const ast = amm.apply(lp); - if (ast instanceof LPError) { - throw new Error(ast.msg); - } - return ammToAga(ast); -}; diff --git a/compiler/src/ammtojs.ts b/compiler/src/ammtojs.ts deleted file mode 100644 index 08191a52b..000000000 --- a/compiler/src/ammtojs.ts +++ /dev/null @@ -1,243 +0,0 @@ -import { asyncopcodes } from 'alan-js-runtime'; - -import { LP, LPNode, LPError, NamedAnd, NulLP } from './lp'; - -import amm from './amm'; - -const funcsToAlsoReturnArg = {}; - -const callToJsText = (call: LPNode) => { - const args = call.has('calllist') - ? call - .get('calllist') - .getAll() - .map((r) => r.get('variable').t) - .join(', ') - : ''; - const opcode = call.get('variable').t; - return asyncopcodes.includes(opcode) - ? `await r.${opcode}(${args})` - : `r.${opcode}(${args})`; -}; - -type ArgMutationDirective = [string[], boolean | undefined]; - -const functionbodyToJsText = ( - fnbody: LPNode, - indent: string, - mutationDirective: ArgMutationDirective, -) => { - let outText = ''; - for (const statement of fnbody.get('statements').getAll()) { - outText += indent + ' '; // For legibility of the output - if (statement.has('declarations')) { - if (statement.get('declarations').has('constdeclaration')) { - const dec = statement.get('declarations').get('constdeclaration'); - outText += `const ${dec.get('decname').t} = ${assignableToJsText( - dec.get('assignables'), - dec.get('fulltypename'), - indent, - funcsToAlsoReturnArg[dec.get('decname').t], - )}\n`; - } else if (statement.get('declarations').has('letdeclaration')) { - const dec = statement.get('declarations').get('letdeclaration'); - outText += `let ${dec.get('decname').t} = ${assignableToJsText( - dec.get('assignables'), - dec.get('fulltypename'), - indent, - funcsToAlsoReturnArg[dec.get('decname').t], - )}\n`; - } - } else if (statement.has('assignments')) { - const assign = statement.get('assignments'); - outText += `${assign.get('decname').t} = ${assignableToJsText( - assign.get('assignables'), - assign.get('fulltypename'), - indent, - funcsToAlsoReturnArg[assign.get('decname').t], - )}\n`; - } else if (statement.has('calls')) { - outText += `${callToJsText(statement.get('calls'))}\n`; - } else if (statement.has('emits')) { - const emit = statement.get('emits'); - const name = emit.get('variable').t; - const arg = emit.has('value') - ? emit.get('value').get('variable').t - : 'undefined'; - outText += `r.emit('${name}', ${arg})\n`; - } else if (statement.has('exits')) { - if (typeof mutationDirective[1] === 'boolean') { - const exit = statement.get('exits'); - const argName = mutationDirective[0][0]; - const retVal = exit.get('variable').t; - outText += `return [${retVal}, ${argName}]\n`; - } else { - outText += `${statement.get('exits').t.trim()}\n`; - } - } - } - if (typeof mutationDirective[1] === 'boolean' && mutationDirective[1]) { - const argName = mutationDirective[0][0]; - outText += `return ${argName}\n`; - } - return outText; -}; - -const assignableToJsText = ( - assignable: LPNode, - fulltypename: LPNode, - indent: string, - mutationDirective?: boolean, -) => { - let outText = ''; - if (assignable.has('functions')) { - const args = assignable.get('functions').get('args'); - const argnames = []; - for (const arg of args.get(0).getAll()) { - argnames.push(arg.get('arg').get('variable').t); - } - if (args.get(1)) { - argnames.push(args.get(1).get('arg').get('variable').t); - } - outText += `async (${argnames.join(', ')}) => {\n`; - outText += functionbodyToJsText( - assignable.get('functions').get('functionbody'), - indent + ' ', - [argnames, mutationDirective], - ); - outText += indent + ' }'; // End this closure - } else if (assignable.has('calls')) { - outText += callToJsText(assignable.get('calls')); - } else if (assignable.has('variable')) { - outText += assignable.get('variable').t; - } else if (assignable.has('value')) { - outText += assignable.get('value').t; - try { - const t = assignable.get('value').t; - if (!/"/.test(t) && t !== 'true' && t !== 'false' && !/\./.test(t)) { - parseInt(assignable.get('value').t); - if (fulltypename.t.trim() === 'int64') { - outText += 'n'; - } - } - } catch (e) {} // eslint-disable-line no-empty - } - return outText; -}; - -const findFuncsToAlsoReturnArg = (amm: LPNode) => { - const insertExit = { - dsmrun: false, - dsmwith: false, - dsmonly: true, - dswonly: true, - dsmclos: false, - }; - const inspectCall = (call: LPNode) => { - const opcode = call.get('variable').t; - if ( - ['dsmrun', 'dsmwith', 'dsmonly', 'dswonly', 'dsmclos'].includes(opcode) - ) { - const fnname = call.has('calllist') - ? call - .get('calllist') - .getAll() - .map((r) => r.get('variable').t)[1] - : 'WAT'; - funcsToAlsoReturnArg[fnname] = insertExit[opcode]; - } - }; - for (const handler of amm.get('handlers').getAll()) { - const functionbody = handler.get().get('functions').get('functionbody'); - for (const statement of functionbody.get('statements').getAll()) { - if (statement.has('declarations')) { - if (statement.get('declarations').has('constdeclaration')) { - const dec = statement.get('declarations').get('constdeclaration'); - const assignable = dec.get('assignables'); - if (assignable.has('calls')) { - inspectCall(assignable.get('calls')); - } - } else if (statement.get('declarations').has('letdeclaration')) { - const dec = statement.get('declarations').get('letdeclaration'); - const assignable = dec.get('assignables'); - if (assignable.has('calls')) { - inspectCall(assignable.get('calls')); - } - } - } else if (statement.has('assignments')) { - const assign = statement.get('assignments'); - const assignable = assign.get('assignables'); - if (assignable.has('calls')) { - inspectCall(assignable.get('calls')); - } - } else if (statement.has('calls')) { - inspectCall(statement.get('calls')); - } - } - } -}; - -const ammToJsText = (amm: LPNode) => { - // Preprocess the tree to find the functions that need an argument also returned and identify - // which argument, as well - findFuncsToAlsoReturnArg(amm); - let outFile = "const r = require('alan-js-runtime')\n"; - // Where we're going we don't need types, so skipping that entire section - // First convert all of the global constants to javascript - for (const globalConst of amm.get('globalMem').getAll()) { - const rec = globalConst.get(); - if (!(rec instanceof NamedAnd)) continue; - outFile += `const ${rec.get('decname').t} = ${assignableToJsText( - rec.get('assignables'), - rec.get('fulltypename'), - '', - )}\n`; - } - // We can also skip the event declarations because they are lazily bound by EventEmitter - // Now we convert the handlers to Javascript. This is the vast majority of the work - let hasConn = false; - let hasTcpConn = false; - for (const handler of amm.get('handlers').getAll()) { - const rec = handler.get(); - if (!(rec instanceof NamedAnd)) continue; - let arg = rec.get('functions').get('args').get(0).get(0).get('arg'); - if (arg instanceof NulLP) { - arg = rec.get('functions').get('args').get(1).get('arg'); - } - const eventVarName = !(arg instanceof NulLP) ? arg.get('variable').t : ''; - if (rec.get('variable').t === '__conn') hasConn = true; - if (rec.get('variable').t === '__ctrl') - throw new Error('AVM Control Port unavailable in JS'); - if (rec.get('variable').t === 'tcpConn') hasTcpConn = true; - outFile += `r.on('${ - rec.get('variable').t - }', async (${eventVarName}) => {\n`; - outFile += functionbodyToJsText( - rec.get('functions').get('functionbody'), - '', - [[], undefined], - ); - outFile += '})\n'; // End this handler - } - if (hasConn) outFile += "r.on('_start', () => r.httplsn())\n"; // Make sure a web server starts up - if (hasTcpConn) outFile += "r.on('_start', () => r.tcplsn())\n"; // Make sure a server starts up - outFile += "r.emit('_start', undefined)\n"; // Let's get it started in here - return outFile; -}; - -export const fromFile = (filename: string) => { - const lp = new LP(filename); - const ast = amm.apply(lp); - if (ast instanceof LPError) { - throw new Error(ast.msg); - } - return ammToJsText(ast); -}; -export const fromString = (str: string) => { - const lp = LP.fromText(str); - const ast = amm.apply(lp); - if (ast instanceof LPError) { - throw new Error(ast.msg); - } - return ammToJsText(ast); -}; diff --git a/compiler/src/index.ts b/compiler/src/index.ts deleted file mode 100755 index 8e1c9f9f6..000000000 --- a/compiler/src/index.ts +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env node - -import * as fs from 'fs'; - -import commander = require('commander'); - -import buildPipeline from './pipeline'; -import * as agatoagc from './agatoagc'; -import * as agctoagz from './agctoagz'; -import * as ammtoaga from './ammtoaga'; -import * as ammtojs from './ammtojs'; -import * as lntoamm from './lntoamm'; -import * as lnntoamm from './lnntoamm'; - -const start = Date.now(); - -const getFormat = (filename: string) => - filename.replace(/^.+\.([A-Za-z0-9]{2,3})$/g, '$1'); - -const formatTime = (ms: number) => { - if (ms < 1000) return `${ms}ms`; - if (ms < 60000) return `${ms / 1000.0}s`; - const minutes = Math.floor(ms / 60000); - const remaining = ms - minutes * 60000; - return `${minutes}min ${remaining / 1000.0}s`; -}; - -const convert = buildPipeline([ - ['ln', 'amm', lntoamm], - ['lnn', 'amm', lnntoamm], - ['amm', 'aga', ammtoaga], - ['amm', 'js', ammtojs], - ['aga', 'agc', agatoagc], - ['agc', 'agz', agctoagz], -]); - -let inputfile: string, outputfile: string; -commander - .name('alan-compile') - .version('0.1.0') // TODO: Try to revive getting this from package.json; it's just weird in TS - .arguments(' ') - .action((input: string, output: string) => { - inputfile = input; - outputfile = output; - }) - .description( - `Compile the specified source file to the specified output file - -The input and output formats are determined automatically by the file extensions specified - -> alan-compile myRootSourceFile.ln myApplication.agc - -The AGC format is used by the alan-runtime as its native binary format - -> alan-compile mySourceFile.ln myWebApplication.js - -The compiler can also transpile to JS for use in Node.js or the browser - -It is also possible to get the compiler's intermediate representations, AMM and AGA: - -> alan-compile mySourceFile.ln firstIntermediateLayer.amm -> alan-compile firstIntermediateLayer.amm secondIntermediateLayer.aga - -And to resume from these intermediate representations - -> alan-compile firstIntermediateLayer.amm myWebApplication.js -> alan-compile secondIntermediateLayer.aga myApplication.agc - -Supports the following input formats: -- ln (Alan source code) -- lnn (Alan source code, using the new compiler front-end) -- amm (Alan-- intermediate representation) -- aga (Alan Graphcode Assembler representation) - -Supports the following output formats -- amm (Alan-- intermediate representation) -- js (Transpilation to Javascript) -- aga (Alan Graphcode Assembler representation) -- agc (Compilation to Alan Graphcode format used by the alan-runtime) -`, - ) - .parse(process.argv); - -if ( - convert[getFormat(inputfile)] && - convert[getFormat(inputfile)][getFormat(outputfile)] -) { - try { - const output = - convert[getFormat(inputfile)][getFormat(outputfile)].fromFile(inputfile); - fs.writeFileSync(outputfile, output, { encoding: 'utf8' }); - const end = Date.now(); - console.log(`Done in ${formatTime(end - start)}`); - } catch (e) { - console.error(e.message); - process.exit(1); - } -} else { - console.error( - `${getFormat(inputfile)} to ${getFormat(outputfile)} not implemented!`, - ); - process.exit(2); -} diff --git a/compiler/src/ln.ts b/compiler/src/ln.ts deleted file mode 100644 index 27e897b51..000000000 --- a/compiler/src/ln.ts +++ /dev/null @@ -1,750 +0,0 @@ -import { - And, - CharSet, - LeftSubset, - NamedAnd, - NamedOr, - Not, - NulLP, - OneOrMore, - Or, - Token, - ZeroOrMore, - ZeroOrOne, -} from './lp'; - -// Defining LN Tokens -const space = Token.build(' '); -const blank = OneOrMore.build(space); -const optblank = ZeroOrOne.build(blank); -const newline = Or.build([Token.build('\n'), Token.build('\r')]); -const notnewline = Not.build('\n'); -const singlelinecomment = And.build([ - Token.build('//'), - ZeroOrMore.build(notnewline), - newline, -]); -const star = Token.build('*'); -const notstar = Not.build('*'); -const notslash = Not.build('/'); -const multilinecomment = And.build([ - Token.build('/*'), - ZeroOrMore.build(Or.build([notstar, And.build([star, notslash])])), - Token.build('*/'), -]); -const whitespace = OneOrMore.build( - Or.build([space, newline, singlelinecomment, multilinecomment]), -); -const optwhitespace = ZeroOrOne.build(whitespace); -const colon = Token.build(':'); -const under = Token.build('_'); -const negate = Token.build('-'); -const dot = Token.build('.'); -const par = Token.build('..'); -const eq = Token.build('='); -const openParen = Token.build('('); -const closeParen = Token.build(')'); -const openCurly = Token.build('{'); -const closeCurly = Token.build('}'); -const openCaret = Token.build('<'); -const closeCaret = Token.build('>'); -const openArr = Token.build('['); -const closeArr = Token.build(']'); -const comma = Token.build(','); -const optcomma = ZeroOrOne.build(comma); -const semicolon = Token.build(';'); -const optsemicolon = ZeroOrOne.build(semicolon); -const at = Token.build('@'); -const slash = Token.build('/'); -const base10 = CharSet.build('0', '9'); -const natural = OneOrMore.build(base10); -const integer = And.build([ZeroOrOne.build(negate), natural]); -const real = And.build([integer, ZeroOrOne.build(And.build([dot, natural]))]); -const num = NamedOr.build({ - real, - integer, -}); -const t = Token.build('true'); -const f = Token.build('false'); -const bool = Or.build([t, f]); -const lower = CharSet.build('a', 'z'); -const upper = CharSet.build('A', 'Z'); -const variable = LeftSubset.build( - And.build([ - OneOrMore.build(Or.build([under, lower, upper])), - ZeroOrMore.build(Or.build([under, lower, upper, base10])), - ]), - bool, -); -const operators = OneOrMore.build( - Or.build([ - Token.build('+'), - Token.build('-'), - Token.build('/'), - Token.build('\\'), - Token.build('*'), - Token.build('^'), - Token.build('.'), - Token.build('~'), - Token.build('`'), - Token.build('!'), - Token.build('@'), - Token.build('#'), - Token.build('$'), - Token.build('%'), - Token.build('&'), - Token.build('|'), - Token.build(':'), - Token.build('<'), - Token.build('>'), - Token.build('?'), - Token.build('='), - ]), -); -const interfacen = Token.build('interface'); -const newn = Token.build('new'); -const ifn = Token.build('if'); -const elsen = Token.build('else'); -const precedence = Token.build('precedence'); -const infix = Token.build('infix'); -const prefix = Token.build('prefix'); -const asn = Token.build('as'); -const exit = Token.build('return'); -const emit = Token.build('emit'); -const letn = Token.build('let'); -const constn = Token.build('const'); -const on = Token.build('on'); -const event = Token.build('event'); -const exportn = Token.build('export'); -const typen = Token.build('type'); -const importn = Token.build('import'); -const fromn = Token.build('from'); -const fn = Token.build('fn'); -const quote = Token.build("'"); -const doublequote = Token.build('"'); -const escapeQuote = Token.build("\\'"); -const escapeDoublequote = Token.build('\\"'); -const notQuote = Not.build("'"); -const notDoublequote = Not.build('"'); -const sep = And.build([optwhitespace, comma, optwhitespace]); -const optsep = ZeroOrOne.build(sep); -const str = Or.build([ - And.build([ - quote, - ZeroOrMore.build(Or.build([escapeQuote, notQuote])), - quote, - ]), - And.build([ - doublequote, - ZeroOrMore.build(Or.build([escapeDoublequote, notDoublequote])), - doublequote, - ]), -]); - -const arrayaccess = NamedAnd.build({ - openArr, - b: optwhitespace, - assignables: new NulLP(), // Circular dep trick, see line 305 - c: optwhitespace, - closeArr, -}); -const varsegment = NamedOr.build({ - variable, - methodsep: And.build([optwhitespace, dot, optwhitespace]), - arrayaccess, -}); -const varn = OneOrMore.build(varsegment); -const varop = NamedOr.build({ - variable, - operators, -}); -const renamed = ZeroOrOne.build( - NamedAnd.build({ - a: blank, - asn, - b: blank, - varop, - }), -); -const renameablevar = NamedAnd.build({ - varop, - renamed, -}); -const varlist = NamedAnd.build({ - renameablevar, - cdr: ZeroOrMore.build( - NamedAnd.build({ - sep, - renameablevar, - }), - ), - sep: ZeroOrOne.build(sep), -}); -const depsegment = NamedOr.build({ - variable, - slash, -}); -const pardepsegment = NamedOr.build({ - variable, - slash, - par, -}); -const localdependency = NamedOr.build({ - curDir: NamedAnd.build({ - dot, - depsegments: OneOrMore.build(depsegment), - }), - parDir: NamedAnd.build({ - par, - depsegments: OneOrMore.build(pardepsegment), - }), -}); -const globaldependency = NamedAnd.build({ - at, - depsegments: OneOrMore.build(depsegment), -}); -const dependency = NamedOr.build({ - localdependency, - globaldependency, -}); -const standardImport = NamedAnd.build({ - importn, - blank, - dependency, - renamed, - a: optblank, - newline, - b: optwhitespace, -}); -const fromImport = NamedAnd.build({ - fromn, - a: blank, - dependency, - b: blank, - importn, - c: blank, - varlist, - d: optblank, - newline, - e: optwhitespace, -}); -const imports = ZeroOrMore.build( - NamedOr.build({ - standardImport, - fromImport, - }), -); -const typename = varn; -const typegenerics = NamedAnd.build({ - openCaret, - a: optwhitespace, - generics: new NulLP(), - b: optwhitespace, - closeCaret, -}); -export const fulltypename = NamedAnd.build({ - typename, - opttypegenerics: ZeroOrOne.build(typegenerics), -}); -typegenerics.and.generics = NamedAnd.build({ - fulltypename, - cdr: ZeroOrMore.build( - NamedAnd.build({ - sep, - fulltypename, - }), - ), -}); -const typeline = NamedAnd.build({ - variable, - a: optwhitespace, - colon, - b: optwhitespace, - fulltypename, -}); -const typelist = NamedAnd.build({ - typeline, - cdr: ZeroOrMore.build( - NamedAnd.build({ - sep, - typeline, - }), - ), - optsep, -}); -const typebody = NamedAnd.build({ - openCurly, - a: optwhitespace, - typelist, - b: optwhitespace, - closeCurly, -}); -const types = NamedAnd.build({ - typen, - blank, - fulltypename, - optwhitespace, - typedef: NamedOr.build({ - typebody, - typealias: NamedAnd.build({ - eq, - blank, - fulltypename, - }), - }), -}); -const constants = NamedOr.build({ - bool, - num, - str, -}); -const baseassignable = NamedOr.build({ - objectliterals: new NulLP(), // See line 525 - functions: new NulLP(), // See line 419 - fncall: new NulLP(), // See line 533 - variable, - constants, - methodsep: And.build([optwhitespace, dot, optwhitespace]), -}); -const baseassignablelist = OneOrMore.build( - NamedAnd.build({ - baseassignable, - }), -); -const withoperators = NamedOr.build({ - baseassignablelist, - operators: And.build([optwhitespace, operators, optwhitespace]), -}); -export const assignables = OneOrMore.build( - NamedAnd.build({ - withoperators, - }), -); -arrayaccess.and.assignables = assignables; -const constdeclaration = NamedAnd.build({ - constn, - whitespace, - variable, - a: optwhitespace, - typedec: ZeroOrOne.build( - NamedAnd.build({ - colon, - a: optwhitespace, - fulltypename, - b: optwhitespace, - }), - ), - eq, - b: optwhitespace, - assignables, - semicolon, -}); -const letdeclaration = NamedAnd.build({ - letn, - whitespace, - variable, - a: optwhitespace, - typedec: ZeroOrOne.build( - NamedAnd.build({ - colon, - optwhitespace, - fulltypename, - }), - ), - b: optwhitespace, - eq, - c: optwhitespace, - assignables, - semicolon, -}); -const declarations = NamedOr.build({ - constdeclaration, - letdeclaration, -}); -const assignments = NamedAnd.build({ - varn, - a: optwhitespace, - eq, - b: optwhitespace, - assignables, - semicolon, -}); -const retval = ZeroOrOne.build( - NamedAnd.build({ - assignables, - optwhitespace, - }), -); -const exits = NamedAnd.build({ - exit, - optwhitespace, - retval, - semicolon, -}); -const emits = NamedAnd.build({ - emit, - a: optwhitespace, - eventname: varn, - b: optwhitespace, - retval, - semicolon, -}); -const arglist = ZeroOrOne.build( - NamedAnd.build({ - variable, - a: optblank, - colon, - b: optblank, - fulltypename, - cdr: ZeroOrMore.build( - NamedAnd.build({ - sep, - variable, - a: optblank, - colon, - b: optblank, - fulltypename, - }), - ), - optsep, - }), -); -const functionbody = NamedAnd.build({ - openCurly, - statements: new NulLP(), // See line 458 - optwhitespace, - closeCurly, -}); -const assignfunction = NamedAnd.build({ - eq, - optwhitespace, - assignables, - optsemicolon, -}); -const fullfunctionbody = NamedOr.build({ - functionbody, - assignfunction, -}); -export const functions = NamedAnd.build({ - fn, - a: optwhitespace, - optname: ZeroOrOne.build(variable), - b: optwhitespace, - optargs: ZeroOrOne.build( - NamedAnd.build({ - openParen, - a: optwhitespace, - arglist, - b: optwhitespace, - closeParen, - c: optwhitespace, - }), - ), - optreturntype: ZeroOrOne.build( - NamedAnd.build({ - colon, - a: optwhitespace, - fulltypename, - b: optwhitespace, - }), - ), - fullfunctionbody, -}); -baseassignable.or.functions = functions; -const blocklike = NamedOr.build({ - functions, - functionbody, - fnname: varn, -}); -const condorblock = NamedOr.build({ - conditionals: new NulLP(), // Circ dep trick, see line 442 - blocklike, -}); -const conditionals = NamedAnd.build({ - ifn, - whitespace, - assignables, - optwhitespace, - blocklike, - elsebranch: ZeroOrOne.build( - NamedAnd.build({ - whitespace, - elsen, - optwhitespace, - condorblock, - }), - ), -}); -condorblock.or.conditionals = conditionals; -export const statement = NamedOr.build({ - declarations, - exits, - emits, - assignments, - conditionals, - assignables: NamedAnd.build({ - assignables, - semicolon, - }), -}); -const statements = OneOrMore.build( - NamedAnd.build({ - optwhitespace, - statement, - }), -); -functionbody.and.statements = statements; -const literaldec = NamedAnd.build({ - newn, - blank, - fulltypename, - optblank, -}); -const assignablelist = ZeroOrOne.build( - NamedAnd.build({ - optwhitespace, - assignables, - cdr: ZeroOrMore.build( - NamedAnd.build({ - sep, - assignables, - }), - ), - optsep, - }), -); -const arraybase = NamedAnd.build({ - openArr, - a: optwhitespace, - assignablelist, - b: optwhitespace, - closeArr, -}); -const fullarrayliteral = NamedAnd.build({ - literaldec, - arraybase, -}); -const arrayliteral = NamedOr.build({ - arraybase, - fullarrayliteral, -}); -const typeassignlist = NamedAnd.build({ - variable, - a: optwhitespace, - colon, - b: optwhitespace, - assignables, - c: optwhitespace, - cdr: ZeroOrMore.build( - NamedAnd.build({ - sep, - variable, - a: optwhitespace, - colon, - b: optwhitespace, - assignables, - }), - ), - optsep, -}); -const typebase = NamedAnd.build({ - openCurly, - a: optwhitespace, - typeassignlist, - b: optwhitespace, - closeCurly, -}); -const typeliteral = NamedAnd.build({ - literaldec, - typebase, -}); -const objectliterals = NamedOr.build({ - arrayliteral, - typeliteral, -}); -baseassignable.or.objectliterals = objectliterals; -const fncall = NamedAnd.build({ - openParen, - a: optwhitespace, - assignablelist, - b: optwhitespace, - closeParen, -}); -baseassignable.or.fncall = fncall; -const fntoop = NamedAnd.build({ - fnname: variable, - a: blank, - asn, - b: blank, - operators, -}); -const opprecedence = NamedAnd.build({ - precedence, - blank, - num, -}); -const fix = NamedOr.build({ - prefix, - infix, -}); -const opmap = Or.build([ - NamedAnd.build({ - fntoop, - blank, - opprecedence, - }), - NamedAnd.build({ - opprecedence, - blank, - fntoop, - }), -]); -const operatormapping = NamedAnd.build({ - fix, - blank, - opmap, -}); -const events = NamedAnd.build({ - event, - whitespace, - variable, - a: optwhitespace, - colon, - b: optwhitespace, - fulltypename, -}); -const propertytypeline = NamedAnd.build({ - variable, - a: blank, - colon, - b: blank, - fulltypename, -}); -const operatortypeline = NamedAnd.build({ - optleftarg: ZeroOrOne.build( - NamedAnd.build({ - leftarg: fulltypename, - whitespace, - }), - ), - operators, - whitespace, - rightarg: fulltypename, - a: optwhitespace, - colon, - b: optwhitespace, - fulltypename, -}); -const functiontype = NamedAnd.build({ - openParen, - a: optwhitespace, - fulltypename, - b: optwhitespace, - cdr: ZeroOrMore.build( - NamedAnd.build({ - sep, - a: optwhitespace, - fulltypename, - b: optwhitespace, - }), - ), - optsep, - c: optwhitespace, - closeParen, - d: optwhitespace, - colon, - e: optwhitespace, - returntype: fulltypename, -}); -const functiontypeline = NamedAnd.build({ - variable, - optblank, - functiontype, -}); -const interfaceline = NamedOr.build({ - functiontypeline, - operatortypeline, - propertytypeline, -}); -const interfacelist = ZeroOrOne.build( - NamedAnd.build({ - a: optwhitespace, - interfaceline, - b: optwhitespace, - cdr: ZeroOrMore.build( - NamedAnd.build({ - sep, - a: optwhitespace, - interfaceline, - b: optwhitespace, - }), - ), - optsep, - }), -); -const interfacebody = NamedAnd.build({ - openCurly, - interfacelist, - optwhitespace, - closeCurly, -}); -const interfacealias = NamedAnd.build({ - eq, - blank, - variable, -}); -const interfacedef = NamedOr.build({ - interfacebody, - interfacealias, -}); -const interfaces = NamedAnd.build({ - interfacen, - a: optblank, - variable, - b: optblank, - interfacedef, -}); -const exportable = NamedOr.build({ - functions, - constdeclaration, - types, - interfaces, - operatormapping, - events, - ref: variable, -}); -const exportsn = NamedAnd.build({ - exportn, - blank, - exportable, -}); -const handler = NamedOr.build({ - functions, - functionbody, - fnname: variable, -}); -const handlers = NamedAnd.build({ - on, - a: whitespace, - eventname: varn, - b: whitespace, - handler, -}); -const body = OneOrMore.build( - NamedOr.build({ - whitespace, - exportsn, - handlers, - functions, - types, - constdeclaration, - operatormapping, - events, - interfaces, - }), -); -export const ln = NamedAnd.build({ - optwhitespace, - imports, - body, -}); diff --git a/compiler/src/lnntoamm/Amm.ts b/compiler/src/lnntoamm/Amm.ts deleted file mode 100644 index a27defcbc..000000000 --- a/compiler/src/lnntoamm/Amm.ts +++ /dev/null @@ -1,153 +0,0 @@ -import Type from './Types'; -import { genName } from './util'; - -const INDENT = ' '; -// keep this since this compiler is WIP - once it's finished we shouldn't need it anymore, and in fact it'll hurt performance (barely? i think?) -const DEBUG_MODE_PRINTING = false; - -export type AssignKind = '' | 'const' | 'let'; - -// TODO: a better solution would be to generate SSA: -// 1. only accept opcode calls and references -// 2. return a generated name that is guaranteed not in-scope -// 3. perform "register selection" - reassign variables based on usage and references -export default class Output { - private constants: Map; - private events: { [name: string]: Type }; - private handlers: string[]; - private indent: string; - - constructor() { - this.constants = new Map(); - this.events = {}; - this.handlers = []; - } - - toString(): string { - let res = ''; - for (const [ty, constants] of this.constants.entries()) { - for (const constVal of Object.keys(constants)) { - res = res.concat( - 'const ', - constants[constVal], - ': ', - ty.ammName, - ' = ', - constVal, - '\n', - ); - } - } - for (const eventName of Object.keys(this.events)) { - res = res.concat( - 'event ', - eventName, - ': ', - this.events[eventName].ammName, - '\n', - ); - } - for (const handler of this.handlers) { - res = res.concat(handler); - } - return res; - } - - global(kind: 'const' | 'event', ty: Type, val: string): string { - if (kind === 'const') { - const constants = this.constants.get(ty) || {}; - if (Object.keys(constants).findIndex((c) => c === val) === -1) { - constants[val] = genName(); - this.constants.set(ty, constants); - } - DEBUG_MODE_PRINTING && - console.log('-> const', constants[val], ':', ty.ammName, '=', val); - return constants[val]; - } else { - if (this.events[val]) { - throw new Error(`AMM can't handle multiple events of the same name`); - } - this.events[val] = ty; - DEBUG_MODE_PRINTING && console.log('-> event', val, ':', ty.ammName); - return val; - } - } - - addHandler(event: string, args: [string, Type][], retTy?: Type) { - let line = 'on '.concat(event, ' fn ('); - for (let ii = 0; ii < args.length; ii++) { - if (ii !== 0) { - line = line.concat(', '); - } - line = line.concat(args[ii][0], ': ', args[ii][1].ammName); - } - line = line.concat('): ', retTy ? retTy.ammName : 'void', ' {'); - DEBUG_MODE_PRINTING && console.log(line); - this.handlers.unshift(line.concat('\n')); - this.indent = INDENT; - } - - // made it as linear and DRY as possible :) - assign( - kind: '' | 'const' | 'let', - name: string, - ty: Type, - assign: string, - args: string[] | null = null, - ) { - let line = this.indent; - if (kind === '') { - line = line.concat(name, ' = '); - } else { - line = line.concat(kind, ' ', name, ': ', ty.ammName, ' = '); - } - if (args !== null) { - const fnName = assign; - line = line.concat(fnName, '('); - if (args === null) { - throw new Error( - `attempting to call opcode ${fnName} but there are no args defined`, - ); - } - for (let ii = 0; ii < args.length; ii++) { - line = line.concat(args[ii]); - if (ii !== args.length - 1) { - line = line.concat(', '); - } - } - line = line.concat(')'); - } else { - line = line.concat(assign); - } - DEBUG_MODE_PRINTING && console.log(line); - this.handlers[0] = this.handlers[0].concat(line.concat('\n')); - } - - call(opcodeName: string, args: string[]) { - const line = `${this.indent}${opcodeName}(${args.join(', ')})`; - DEBUG_MODE_PRINTING && console.log(line); - this.handlers[0] = this.handlers[0].concat(line.concat('\n')); - } - - emit(eventName: string, val?: string) { - let line = this.indent.concat('emit ', eventName); - if (val) { - line = line.concat(' ', val); - } - DEBUG_MODE_PRINTING && console.log(line); - this.handlers[0] = this.handlers[0].concat(line.concat('\n')); - } - - exit(val: string | null = null) { - if (val !== null) { - const line = this.indent.concat('return ', val, '\n'); - DEBUG_MODE_PRINTING && console.log(line); - this.handlers[0] = this.handlers[0].concat(line); - } - // only replace the first newline with nothing - this.indent = this.indent.replace(/ {2}/, ''); - const line = this.indent.concat('}'); - DEBUG_MODE_PRINTING && console.log(line); - this.handlers[0] = this.handlers[0].concat(line.concat('\n')); - } -} diff --git a/compiler/src/lnntoamm/Ast.ts b/compiler/src/lnntoamm/Ast.ts deleted file mode 100644 index df6d115c2..000000000 --- a/compiler/src/lnntoamm/Ast.ts +++ /dev/null @@ -1,297 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; - -import { LP, LPNode, LPError, NamedAnd } from '../lp'; -import * as ln from '../ln'; - -const resolve = (path: string) => { - try { - return fs.realpathSync(path); - } catch (e) { - return null; - } -}; - -export const fromString = (str: string): NamedAnd => { - const lp = LP.fromText(str); - const ast = ln.ln.apply(lp); - if (ast instanceof LPError) { - throw new Error(ast.msg); - } else if (ast.t.length !== str.length) { - const lp2 = lp.clone(); - lp2.advance(ast.t.length); - const body = ast.get('body').getAll(); - const last = body[body.length - 1]; - throw new Error( - `AST Parse error, cannot continue due to syntax error between line ${last.line}:${last.char} - ${lp2.line}:${lp2.char}`, - ); - } - - return ast; -}; - -export const fromFile = (filename: string): NamedAnd => { - const ast = fromString(fs.readFileSync(filename, { encoding: 'utf8' })); - ast.filename = filename; - return ast; -}; - -export const resolveDependency = ( - modulePath: string, - dependency: LPNode, -): string => { - // Special case path for the standard library importing itself - if (modulePath.substring(0, 4) === '@std') return dependency.t.trim(); - // For everything else... - let importPath = null; - // If the dependency is a local dependency, there's little logic in determining - // what is being imported. It's either the relative path to a file with the language - // extension, or the relative path to a directory containing an "index.ln" file - if (dependency.has('localdependency')) { - const dirPath = resolve( - path.join( - path.dirname(modulePath), - dependency.get('localdependency').t, - 'index.lnn', - ), - ); - const filePath = resolve( - path.join( - path.dirname(modulePath), - dependency.get('localdependency').t + '.lnn', - ), - ); - // It's possible for both to exist. Prefer the directory-based one, but warn the user - if (typeof dirPath === 'string' && typeof filePath === 'string') { - console.error( - dirPath + ' and ' + filePath + ' both exist. Using ' + dirPath, - ); - } - if (typeof filePath === 'string') { - importPath = filePath; - } - if (typeof dirPath === 'string') { - importPath = dirPath; - } - if (importPath === null) { - throw new Error( - `The dependency ${ - dependency.get('localdependency').t - } could not be found.`, - ); - } - } - // If the dependency is a global dependency, there's a more complicated resolution to find it. - // This is inspired by the Ruby and Node resolution mechanisms, but with some changes that - // should hopefully make some improvements so dependency-injection is effectively first-class - // and micro-libraries are discouraged (the latter will require a multi-pronged effort) - // - // Essentially, there are two recursively-found directories that global modules can be found, - // the `modules` directory and the `dependencies` directory (TBD: are these the final names?) - // The `modules` directory is recursively checked first (with a special check to make sure it - // ignores self-resolutions) and the first one found in that check, if any, is used. If not, - // there's a special check if the dependency is an `@std/...` dependency, and if so to return - // that string as-is so the built-in dependency is used. Next the same recursive check is - // performed on the `dependencies` directories until the dependency is found. If that also - // fails, then there will be a complaint and the process will exit. - // - // The idea is that the package manager will install dependencies into the `dependencies` - // directory at the root of the project (or maybe PWD, but that seems a bit too unwieldy). - // Meanwhile the `modules` directory will only exist if the developer wants it, but it can be - // useful for cross-cutting code in the same project that doesn't really need to be open- - // sourced but is annoying to always reference slightly differently in each file, eg - // `../../../util`. Instead the project can have a project-root-level `modules` directory and - // then `modules/util.ln` can be referenced simply with `import @util` anywhere in the project. - // - // Since this is also recursive, it's should make dependency injection a first-class citizen - // of the language. For instance you can put all of your models in `modules/models/`, and then - // your unit test suite can have its model mocks in `tests/modules/models/` and the dependency - // you intend to inject into can be symlinked in the `tests/` directory to cause that version - // to pull the injected code, instead. And of course, if different tests need different - // dependency injections, you can turn the test file into a directory of the same name and - // rename the file to `index.ln` within it, and then have the specific mocks that test needs - // stored in a `modules/` directory in parallel with it, which will not impact other mocks. - // - // Because these mocks also have a special exception to not import themselves, this can also - // be used for instrumentation purposes, where they override the actual module but then also - // import the real thing and add extra behavior to it. - // - // While there are certainly uses for splitting some logical piece of code into a tree of - // files and directories, it is my hope that the standard application organization path is a - // project with a root `index.ln` file and `modules` and `dependencies` directories, and little - // else. At least things like `modules/logger`, `modules/config`, etc should belong there. - if (dependency.has('globaldependency')) { - // Get the two potential dependency types, file and directory-style. - const fileModule = - dependency.get('globaldependency').t.substring(1) + '.lnn'; - const dirModule = - dependency.get('globaldependency').t.substring(1) + '/index.lnn'; - // Get the initial root to check - let pathRoot = path.dirname(modulePath); - // Search the recursively up the directory structure in the `modules` directories for the - // specified dependency, and if found, return it. - while (pathRoot != null) { - const dirPath = resolve(path.join(pathRoot, 'modules', dirModule)); - const filePath = resolve(path.join(pathRoot, 'modules', fileModule)); - // It's possible for a module to accidentally resolve to itself when the module wraps the - // actual dependency it is named for. - if (dirPath === modulePath || filePath === modulePath) { - pathRoot = path.dirname(pathRoot); - continue; - } - // It's possible for both to exist. Prefer the directory-based one, but warn the user - if (typeof dirPath === 'string' && typeof filePath === 'string') { - console.error( - dirPath + ' and ' + filePath + ' both exist. Using ' + dirPath, - ); - } - if (typeof dirPath === 'string') { - importPath = dirPath; - break; - } - if (typeof filePath === 'string') { - importPath = filePath; - break; - } - if (pathRoot === '/' || /[A-Z]:\\/.test(pathRoot)) { - pathRoot = null; - } else { - pathRoot = path.dirname(pathRoot); - } - } - if (importPath == null) { - // If we can't find it defined in a `modules` directory, check if it's an `@std/...` - // module and abort here so the built-in standard library is used. - if (dependency.get('globaldependency').t.substring(0, 5) === '@std/') { - // Not a valid path (starting with '@') to be used as signal to use built-in library) - importPath = dependency.get('globaldependency').t; - } else { - // Go back to the original point and search up the tree for `dependencies` directories - pathRoot = path.dirname(modulePath); - while (pathRoot != null) { - const dirPath = resolve( - path.join(pathRoot, 'dependencies', dirModule), - ); - const filePath = resolve( - path.join(pathRoot, 'dependencies', fileModule), - ); - // It's possible for both to exist. Prefer the directory-based one, but warn the user - if (typeof dirPath === 'string' && typeof filePath === 'string') { - console.error( - dirPath + ' and ' + filePath + ' both exist. Using ' + dirPath, - ); - } - if (typeof dirPath === 'string') { - importPath = dirPath; - break; - } - if (typeof filePath === 'string') { - importPath = filePath; - break; - } - if (pathRoot === '/' || /[A-Z]:\\/.test(pathRoot)) { - pathRoot = null; - } else { - pathRoot = path.dirname(pathRoot); - } - } - } - if (importPath == null) { - throw new Error( - `The dependency ${ - dependency.get('globaldependency').t - } could not be found.`, - ); - } - } - } - return importPath; -}; - -export const resolveImports = (modulePath: string, ast: LPNode): string[] => { - const resolvedImports = []; - const imports = ast.get('imports').getAll(); - for (let i = 0; i < imports.length; i++) { - let dependency = null; - - if (imports[i].has('standardImport')) { - dependency = imports[i].get('standardImport').get('dependency'); - } - if (imports[i].has('fromImport')) { - dependency = imports[i].get('fromImport').get('dependency'); - } - if (!dependency) { - // Should I do anything else here? - throw new Error( - 'Malformed AST, import statement without an import definition?', - ); - } - const importPath = resolveDependency(modulePath, dependency); - resolvedImports.push(importPath); - } - return resolvedImports; -}; - -export const functionAstFromString = (fn: string) => { - const lp = LP.fromText(fn); - const ast = ln.functions.apply(lp); - if (ast instanceof LPError) { - throw new Error(ast.msg); - } else if (ast.t.length !== fn.length) { - const lp2 = lp.clone(); - lp2.advance(ast.t.length); - throw new Error( - `AST Parse error, cannot continue due to syntax error ending at line ${lp2.line}:${lp2.char}`, - ); - } - - return ast; -}; - -export const statementAstFromString = (s: string) => { - const lp = LP.fromText(s); - const ast = ln.statement.apply(lp); - if (ast instanceof LPError) { - throw new Error(ast.msg); - } else if (ast.t.length !== s.length) { - const lp2 = lp.clone(); - lp2.advance(ast.t.length); - throw new Error( - `AST Parse error, cannot continue due to syntax error ending at line ${lp2.line}:${lp2.char}`, - ); - } - - return ast; -}; - -export const fulltypenameAstFromString = (s: string) => { - const lp = LP.fromText(s); - const ast = ln.fulltypename.apply(lp); - if (ast instanceof LPError) { - throw new Error(ast.msg); - } else if (ast.t.length !== s.length) { - const lp2 = lp.clone(); - lp2.advance(ast.t.length); - throw new Error( - `AST Parse error, cannot continue due to syntax error ending at line ${lp2.line}:${lp2.char}`, - ); - } - - return ast; -}; - -export const assignablesAstFromString = (s: string) => { - const lp = LP.fromText(s); - const ast = ln.assignables.apply(lp); - if (ast instanceof LPError) { - throw new Error(ast.msg); - } else if (ast.t.length !== s.length) { - const lp2 = lp.clone(); - lp2.advance(ast.t.length); - throw new Error( - `AST Parse error, cannot continue due to syntax error ending at line ${lp2.line}:${lp2.char}`, - ); - } - - return ast; -}; diff --git a/compiler/src/lnntoamm/Const.ts b/compiler/src/lnntoamm/Const.ts deleted file mode 100644 index 043332a4d..000000000 --- a/compiler/src/lnntoamm/Const.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { LPNode } from '../lp'; -import Scope from './Scope'; -import Type from './Types'; - -/* -Ugh, I forgot about this class, but it should be pretty easy to do. -I think all that needs to be done is to convert the `assignablesAst` -into an `Expr`. What most compilers then do is effectively "copy-paste" -the Expr into wherever it's referenced. However, I've seen the GH -issue that mentions the desire to do something akin to Rust's `const fn` -stuff (ie, evaluating expressions at compile time). For that, there -should probably be work here to expand on this idea, with a potential -`class ConstRef extends Expr` to be able to reference the result of -those constant values. -*/ -export default class Const { - name: string; - ty: Type; - assignablesAst: LPNode; - - constructor(name: string, ty: Type | null, assignablesAst: LPNode) { - this.name = name; - this.ty = ty !== null ? ty : Type.generate(); - this.assignablesAst = assignablesAst; - } - - static fromAst(ast: LPNode, scope: Scope): Const { - const name = ast.get('variable').t.trim(); - let constTy = null; - if (ast.get('typedec').has()) { - // TODO: gonna have to support generics (just not yet) - const tyName = ast.get('typedec').get().get('fulltypename').t.trim(); - const inScope = scope.get(tyName); - if (!(inScope instanceof Type)) { - throw new Error(`${tyName} is not a type`); - } - constTy = inScope as Type; - } - const assignablesAst = ast.get('assignables'); - return new Const(name, constTy, assignablesAst); - } -} diff --git a/compiler/src/lnntoamm/Event.ts b/compiler/src/lnntoamm/Event.ts deleted file mode 100644 index 07dcc25f5..000000000 --- a/compiler/src/lnntoamm/Event.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { LPNode } from '../lp'; -import Output from './Amm'; -import Fn from './Fn'; -import Scope from './Scope'; -import Type from './Types'; -import { genName, TODO } from './util'; - -const allEvents: Event[] = []; - -/* -Notes: -This just represents the events. Right now, since event declarations -are so basic, there's no need to do much related to Types here. - -TODO: the syntax for `on someEvent someFnName` doesn't currently work. -This is pretty simple to accomplish once FnType is done: just find the -last-defined fn that corresponds to `Function` where T is the -type of the event. -*/ -export default class Event { - ammName: string; - name: string; - eventTy: Type; - // if it's a single fn - handlers: Array; - runtimeDefined: boolean; - - static get allEvents(): Event[] { - return allEvents; - } - - constructor( - name: string, - eventTy: Type, - handlers: Array = [], - runtimeDefined = false, - ) { - this.name = name; - this.eventTy = eventTy; - this.handlers = handlers; - if (allEvents.some((event) => event.name === this.name)) { - this.ammName = genName(); - } else { - this.ammName = this.name; - } - this.runtimeDefined = runtimeDefined; - allEvents.push(this); - } - - static fromAst(ast: LPNode, scope: Scope): Event { - const name = ast.get('variable').t.trim(); - const tyName = ast.get('fulltypename').t.trim(); - const ty = scope.get(tyName); - if (ty === null) { - throw new Error(`Could not find specified type: ${tyName}`); - } else if (!(ty instanceof Type)) { - throw new Error(`${tyName} is not a type`); - } - return new Event(name, ty); - } - - compile(amm: Output) { - if (this.runtimeDefined === false) { - amm.global('event', this.eventTy, this.ammName); - } - for (const handler of this.handlers) { - if (handler instanceof Array) { - // select all of the handlers that accept `this.eventTy` and return `void`. - return TODO('Event handler selection'); - } - if (!(handler instanceof Fn)) { - throw new Error('Too many possible event handlers'); - } - handler.asHandler(amm, this.ammName); - } - } -} diff --git a/compiler/src/lnntoamm/Expr.ts b/compiler/src/lnntoamm/Expr.ts deleted file mode 100644 index d5d2e69b3..000000000 --- a/compiler/src/lnntoamm/Expr.ts +++ /dev/null @@ -1,947 +0,0 @@ -import { stdout } from 'process'; -import { LPNode, NamedAnd, NamedOr, NulLP } from '../lp'; -import Output, { AssignKind } from './Amm'; -import Fn from './Fn'; -import opcodes from './opcodes'; -import Operator from './Operator'; -import Scope from './Scope'; -import Stmt, { Dec, MetaData, VarDef } from './Stmt'; -import Type, { FunctionType } from './Types'; -import { isFnArray, isOpArray, TODO } from './util'; - -/* -This file is pretty big. The idea is that this handles anything that -represents a value. I broke this up into a few different concepts: -- AccessField: represents accessing a field of another value. -- Call: calling a function, either by name or by reference. -- Const: literal values, eg numbers, strings, or bools. -- New: creating an instance of a structural type. -- Ref: a reference to a value defined at another location. - -Every `assignable` (as the terminology is in the parsing module) is -broken up into its individual Exprs. These Exprs are referenced in -other Exprs through Refs. I don't believe there's any scenario when -an Expr should maintain a reference (as in, JS/TS reference) to -any other Expr than a Ref. There is a possibility that it can help -with inlining functions, but I'm not entirely convinced and in the -end this model is simpler. -*/ -export default abstract class Expr { - ast: LPNode; - abstract get ty(): Type; - - constructor(ast: LPNode) { - this.ast = ast; - } - - /** - * Inlines this Expr, creating the relevant statement in the AMM output. - * - * @param amm the AMM generator object for the output program - * @param kind the kind of assignment that the Expr should be assigned to - * in the AMM output - * @param name the name that this Expr is expected to be referrable as in - * the AMM output - * @param ty the expected type of the Expr output. Should only be Opaque types - */ - abstract inline(amm: Output, kind: AssignKind, name: string, ty: Type): void; - - /** - * TODO: this still needs to support: - * - constructing arrays (delegate to appropriate class) - * - closures and calling HOFs - * - * @param ast the ast, expected to be a `baseassignablelist` - * @param metadata the metadata for the current context. See more documentation - * in Stmt.ts - * @returns a tuple where the first element is all of the generated Stmts that - * are contained within the baseassignablelist; the second element is the Expr - * that represents the entire baseassignablelist - */ - private static fromBaseassignablelist( - ast: LPNode, - metadata: MetaData, - ): [Stmt[], Expr] { - const asts = ast.getAll().map((a) => a.get('baseassignable')); - const generated: Stmt[] = []; - let expr: Expr = null; - for (let ii = 0; ii < asts.length; ii++) { - const skipDotIfNext = () => { - if (ii + 1 < asts.length && asts[ii + 1].has('methodsep')) { - ii += 1; - } - }; - let work = asts[ii]; - if (work.has('objectliterals')) { - if (expr !== null) { - throw new Error(`unexpected object literal following an expression`); - } - work = work.get('objectliterals'); - if (work.has('typeliteral')) { - const [stmts, newVal] = New.fromTypeLiteral( - work.get('typeliteral'), - metadata, - ); - generated.push(...stmts); - expr = newVal; - } else { - TODO('arrays'); - } - } else if (work.has('functions')) { - TODO('functions in functions'); - } else if (work.has('variable')) { - const varName = work.get('variable').t; - const next = asts[ii + 1] || new NulLP(); - if (next.has('fncall')) { - // it's a function call - // TODO: this is broken because operators don't pass their AST yet - // let text = `${expr !== null ? expr.ast.t.trim() + '.' : ''}${varName}${next.get('fncall').t.trim()}`; - const text = `${ - expr !== null ? expr.ast.get('variable').t + '.' : '' - }${varName}${next.get('fncall').t.trim()}`; - const and: any = { - fnname: work.get('variable'), - fncall: next.get('fncall'), - }; - let accessed: Ref | null = null; - // DO NOT access `expr` past this block until it is set. - if (expr !== null) { - and.fnaccess = expr.ast; - if (!(expr instanceof Ref)) { - const dec = Dec.gen(expr, metadata); - generated.push(dec); - accessed = dec.ref(); - } else { - accessed = expr; - } - expr = null; - } - const callAst = new NamedAnd( - text, - and, - (work as NamedOr).filename, - work.line, - work.char, - ); - const [intermediates, call] = Call.fromCallAst( - callAst, - varName, - accessed, - metadata, - ); - generated.push(...intermediates); - expr = call; - ii += 1; - skipDotIfNext(); - } else if (expr !== null) { - // it's a field access - if (!(expr instanceof Ref)) { - const dec = Dec.gen(expr, metadata); - generated.push(dec); - expr = dec.ref(); - } - // ensure that the value has the field - const fieldTy = Type.generate(); - const hasField = Type.hasField(varName, fieldTy); - if (!expr.ty.compatibleWithConstraint(hasField, metadata.scope)) { - throw new Error( - `cannot access ${varName} on type ${expr.ty.name} because it doesn't have that field`, - ); - } - expr.ty.constrain(hasField, metadata.scope); - // TODO: better ast - currently only gives the ast for the field name - // (instead of giving the way the struct is accessed as well) - expr = new AccessField(asts[ii], expr as Ref, varName, fieldTy); - skipDotIfNext(); - } else { - // it's a variable reference - const val = metadata.get(varName); - if (!val) { - throw new Error(`${varName} not defined`); - } - expr = val.ref(); - ii += 1; - } - } else if (work.has('constants')) { - work = work.get('constants'); - if (expr !== null) { - throw new Error(`unexpected constant found`); - } - const [int, constant] = Const.fromConstantsAst(work, metadata); - generated.push(...int); - expr = constant; - skipDotIfNext(); - } else if (work.has('fncall')) { - work = work.get('fncall'); - if (expr === null) { - const assignableList = work.get('assignablelist'); - if ( - !assignableList.has('assignables') || - assignableList.get('cdr').has(0) - ) { - console.log( - assignableList, - assignableList.has('assignables'), - assignableList.get('cdr').has(0), - ); - throw new Error( - `unexpected token: found ${work.t.trim()} but it's not applied to a function`, - ); - } - const [intermediates, res] = Expr.fromAssignablesAst( - assignableList.get('assignables'), - metadata, - ); - generated.push(...intermediates); - expr = res; - skipDotIfNext(); - } else { - // it's probably an HOF - const text = `${expr.ast.t.trim()}${work.t.trim()}`; - const and = { - fnaccess: expr.ast, - fncall: work, - }; - const callAst = new NamedAnd( - text, - and, - (work as NamedAnd).filename, - work.line, - work.char, - ); - TODO('closures/HOFs'); - } - } else { - console.error(asts); - throw new Error( - `unexpected token: expected variable or value, found ${work.t.trim()}`, - ); - } - } - return [generated, expr]; - } - - /** - * This function is a little messy, particularly with regard to operator - * handling. I believe it works for the most part, but it currently doesn't - * work when 2 operators with the same symbol and same fixity don't have the - * same precedence. The best way I can think of to implement this is to - * create a new `class OpCall extends Expr` that'll create a permutation of - * each possible call order. This would require some changes to Types.ts - * first, though. The primary requirement would be the implementation of - * some `class TypeConxn extends Type` that would change other Types if some Type is - * inferred to be a specific type. See more info in Types.ts. - * - * @param ast the ast to load, expected to be an `assignables` - * @param metadata the metadata of the current context - * @returns a tuple containing generated intermediary statements and the - * Expr representing the entire AST - */ - static fromAssignablesAst(ast: LPNode, metadata: MetaData): [Stmt[], Expr] { - const asts = ast.getAll(); - // break it up so that we're only working on one base assignable list or operator at a time. - const operated: Array<[Stmt[], Expr] | Operator[]> = asts.map((work) => { - work = work.get('withoperators'); - if (work.has('baseassignablelist')) { - return Expr.fromBaseassignablelist( - work.get('baseassignablelist'), - metadata, - ); - } else if (work.has('operators')) { - // TODO: this won't work with operators associated with interfaces. - // Will have to iterate through all of the interfaces in-scope and collect - // the applicable types as well - const op = work.get('operators').t.trim(); - const operators = metadata.scope.get(op) as Operator[]; - if (operators === null) { - throw new Error(`can't find operator ${op}`); - } else if (!isOpArray(operators)) { - // sanity check - console.log(operators); - throw new Error(`somehow ${op} isn't an operator?`); - } - return operators; - } else { - console.error(work); - console.error(ast); - throw new Error(`unexpected assignable ast: ${work}`); - } - }); - if (operated.length === 0) { - throw new Error(`no expressions generated for ast: ${ast}`); - } else if (operated.length === 1) { - if (isOpArray(operated)) { - throw new Error(`variables can't be assigned to operators`); - } - return operated[0] as [Stmt[], Expr]; - } - // now we have to resolve operators - start by filtering out operators if they - // are in a position that must be prefix or infix - // since there are no suffix operators, this is relatively easy - operators - // immediately following an expression must be infix, while all others must be - // a prefix - const stmts: Stmt[] = []; - let infixPosition = false; - const operation = operated.map((op) => { - if (!isOpArray(op)) { - if (infixPosition) { - throw new Error( - `invalid expression: expected operator, found ${op[1].ast.t.trim()}`, - ); - } - infixPosition = true; - stmts.push(...op[0]); - return op[1]; - } else if (infixPosition) { - infixPosition = false; - return op.filter((op) => !op.isPrefix); - } else { - return op.filter((op) => op.isPrefix); - } - }); - - // Now we build the precedence table for this application - const precedences = operation.map((opOrRef) => { - if (opOrRef instanceof Expr) { - return opOrRef; - } else { - return opOrRef.reduce( - (prec, op) => - prec.set(op.precedence, [...(prec.get(op.precedence) || []), op]), - new Map(), - ); - } - }); - - // now to try to solve operators. - // TODO: this does not work if there are multiple operator precedences for - // the same symbol. To support this, we'll have to create an Expr that acts - // as a permutation over the different possible operator expansions (it can - // be done after eliminating operators that aren't compatible with the - // provided types) - // eslint-disable-next-line no-constant-condition - while (true) { - // find the highest-precedence operations - let prec = -1; - let idxs: number[] = precedences.reduce((idxs, opOrRef, ii) => { - if (opOrRef instanceof Expr) return idxs; - const precs = Array.from(opOrRef.keys()); - if (precs.length > 1) { - // TODO: this is just a stop for future cases. might have - // to revisit this whole loop, but just to remind myself - TODO('figure out multiple precedences?'); - } - const maxPrec = precs.sort((a, b) => a - b).pop(); - if (maxPrec > prec) { - prec = maxPrec; - return [ii]; - } else if (maxPrec === prec) { - return [...idxs, ii]; - } else { - return idxs; - } - }, []); - if (prec === -1 || idxs.length === 0) { - break; - } - - // all of the selected operators should be the same infix/prefix mode - // if the result is null, that means they're not - idk if that's - // ever a case so just TODO it. it can probably be done with the - // permutation class I mentioned above. - const prefixModeOf = (vals: Operator[]) => - vals.reduce((mode, op) => { - if (mode === null) return mode; - return mode === op.isPrefix ? mode : null; - }, vals[0].isPrefix); - - idxs.forEach((idx) => { - const val = precedences[idx]; - // heat-death-of-the-universe check - if (val instanceof Expr) { - throw new Error(`uh, how?`); - } - // ensure that none of the operators disagree on fixity - const mode = prefixModeOf(val.get(prec)); - if (mode === null) { - TODO('operator is both prefix and infix - how to determine?'); - } - }); - // first, prefix operators - we need to mutate idxs so no `.forEach` - // do prefix operators first to ensure that there's no operator - // ambiguity. If there's a prefix before an infix operator (not an - // expression), this still gets caught below. - for (let jj = 0; jj < idxs.length; jj++) { - const idx = idxs[jj]; - const item = precedences[idx] as Map; - const operators = [...item.get(prec)]; - const isPrefix = prefixModeOf(operators); - if (!isPrefix) continue; - // prefix operators are right-associated, so we have to go ahead - // in the indices to ensure that the right-most is handled first - const applyIdx = precedences - .slice(idx) - .findIndex((val) => val instanceof Expr); - // make sure all of the operators between are prefix operators - // with the same precedence - precedences.slice(idx + 1, applyIdx).forEach((opOrExpr, idx) => { - if (opOrExpr instanceof Expr) { - throw new Error(`this error should not be thrown`); - } else if (!idxs.includes(idx)) { - throw new Error( - `unable to resolve operators - operator precedence ambiguity`, - ); - } else if (prefixModeOf(opOrExpr.get(prec)) !== true) { - throw new Error(`unable to resolve operators - operator ambiguity`); - } - }); - // slice copies the array, so this is ok :) - for (const op of precedences.slice(idx, applyIdx).reverse()) { - if (op instanceof Expr) { - throw new Error( - `unexpected expression during computation? this error should never happen`, - ); - } - if (!(precedences[applyIdx] instanceof Ref)) { - const dec = Dec.gen(precedences[applyIdx] as Expr, metadata); - stmts.push(dec); - precedences[applyIdx] = dec.ref(); - } - const applyTo = precedences[applyIdx] as Ref; - const retTy = Type.generate(); - const [fns, paramTys, retTys] = operators.reduce( - ([fns, paramTys, retTys], op) => { - let selFns: Fn[]; - let selPTys: Type[][]; - let selRTys: Type[]; - try { - [selFns, selPTys, selRTys] = op.select( - metadata.scope, - retTy, - applyTo.ty, - ); - } catch (e) { - // this try-catch isn't great, but JS doesn't give us great - // tools for error handling that allow me to quickly implement - // a better fix. Might be better to define custom error types? - // This is here because there might be multiple Operators by the - // same name. For example, in `root.lnn` the `+` operator is - // assigned to both `add` and `concat` - `op.select` above will - // throw an error when trying to do eg `"hi" + "\n"`. This - // shouldn't cause an error, because it's possible that there's - // no `fn add(string, string): string`, but as long as there's - // a `fn concat(string, string): string`, then there's no issue. - // this still gets caught later on. - return [fns, paramTys, retTys]; - } - fns = [...fns, ...selFns]; - // assume that `selPTys[i].length === 1` - paramTys = [...paramTys, ...selPTys.map((pTys) => pTys[0])]; - retTys = [...retTys, ...selRTys]; - return [fns, paramTys, retTys]; - }, - [new Array(), new Array(), new Array()], - ); - const argConstraint = Type.oneOf(paramTys); - applyTo.ty.constrain(argConstraint, metadata.scope); - retTy.constrain(Type.oneOf(retTys), metadata.scope); - precedences[applyIdx] = new Call( - new NulLP(), - fns, - null, - [applyTo], - metadata.scope, - retTy, - ); - const rm = precedences.splice(idx, applyIdx); - // update indices - idxs = idxs.map((idx, kk) => (kk > jj ? idx - rm.length : kk)); - // remove the operators we used so they aren't looked at later - idxs.splice(jj, rm.length); - jj -= 1; - } - } - // now suffix operators - for (let jj = 0; jj < idxs.length; jj++) { - const idx = idxs[jj]; - const item = precedences[idx]; - // heat-death-of-the-universe check - if (item instanceof Expr) { - console.log('-> prec', prec); - console.log('-> idxs', idxs); - console.log('-> idx', idx); - throw new Error(`uh, how?`); - } - // prefer the last-defined operators, so we must pop() - const ops = [...item.get(prec)]; - if (prefixModeOf(ops) === true) { - throw new Error( - `prefix operators at precedence level ${prec} should've already been handled`, - ); - } - // since infix operators are left-associated, and we iterate - // left->right anyways, this impl is easy - const fns = []; - let left = precedences[idx - 1] as Ref; - let right = precedences[idx + 1] as Ref; - if (!left || !right) { - throw new Error(`operator in invalid position`); - } else if (!(left instanceof Expr) || !(right instanceof Expr)) { - throw new Error(`operator ambiguity`); - } - if (!(left instanceof Ref)) { - const dec = Dec.gen(left, metadata); - stmts.push(dec); - left = dec.ref(); - } - if (!(right instanceof Ref)) { - const dec = Dec.gen(right, metadata); - stmts.push(dec); - right = dec.ref(); - } - const argTys: [Type[], Type[]] = [[], []]; - const retTys: Type[] = []; - while (ops.length > 0) { - const op = ops.pop(); - const retTy = Type.generate(); - let selected: [Fn[], Type[][], Type[]]; - try { - selected = op.select(metadata.scope, retTy, left.ty, right.ty); - } catch (e) { - // this try-catch isn't great, but JS doesn't give us great - // tools for error handling that allow me to quickly implement - // a better fix. Might be better to define custom error types? - // This is here because there might be multiple Operators by the - // same name. For example, in `root.lnn` the `+` operator is - // assigned to both `add` and `concat` - `op.select` above will - // throw an error when trying to do eg `"hi" + "\n"`. This - // shouldn't cause an error, because it's possible that there's - // no `fn add(string, string): string`, but as long as there's - // a `fn concat(string, string): string`, then there's no issue. - // this still gets caught later on. - continue; - } - fns.push(...selected[0]); - // assume `selected[1].length === 2` - selected[1].forEach((pTys) => { - argTys[0].push(pTys[0]); - argTys[1].push(pTys[1]); - }); - retTy.constrain(Type.oneOf(selected[2]), metadata.scope); - retTys.push(retTy); - } - const retTy = Type.oneOf(retTys); - const call = new Call( - new NulLP(), - fns, - null, - [left, right], - metadata.scope, - retTy, - ); - precedences[idx - 1] = call; - precedences.splice(idx, 2); - idxs = idxs.map((idx, kk) => (kk > jj ? idx - 2 : kk)); - } - } - - if (precedences.length !== 1) { - throw new Error(`couldn't resolve operators`); - } - return [stmts, precedences.pop() as Ref]; - } - - /** - * @returns true if more cleanup might be required - */ - cleanup(expectResTy: Type): boolean { - // most implementing Exprs don't have anything they need to do. - // I just didn't want to expose any of the Expr classes except - // for Ref to prevent split handling of the classes. - return false; - } -} - -/* -Meant to represent accessing the field of a type. This can be refactored -to support accessing indices of an Array, but the inlining logic is -probably best left separate (fields of types are accessed by a name literal, -but indices of Arrays are accessed by numeric variables) -*/ -class AccessField extends Expr { - struct: Ref; - fieldName: string; - fieldTy: Type; - - get ty(): Type { - return this.fieldTy; - } - - constructor(ast: LPNode, struct: Ref, fieldName: string, fieldTy: Type) { - super(ast); - this.struct = struct; - this.fieldName = fieldName; - this.fieldTy = fieldTy; - } - - inline(amm: Output, kind: AssignKind, name: string, ty: Type): void { - const fieldIndices = this.struct.ty.fieldIndices(); - const index = fieldIndices[this.fieldName]; - const indexVal = amm.global('const', opcodes().get('int64'), `${index}`); - amm.assign(kind, name, ty, 'register', [this.struct.ammName, indexVal]); - } -} - -/* -Represents a Call to a function. Right now the type constraining logic -results in a *lot* of malloc calls, but this is unavoidable without -relatively significant work to Types.ts. Primarily, the `TypeConxn` class -mentioned above (and defined with more detail in Types.ts) would be very -beneficial for this use case. It would `OneOf` a list of connections that -connect the arguments provided to the called function to each other and -to the return type of the fn call. -*/ -class Call extends Expr { - // the list of functions in scope that apply - fns: Fn[]; - // a reference to a declaration assigned to *some* value. type constraint - // compatibility checking can be done to ensure that this is in fact a fn. - // When function selecting, the fn's type should *also* be passed to the - // matrix selection (once matrix selection is changed to use types only). - maybeClosure: VarDef | null; - args: Ref[]; - retTy: Type; - scope: Scope; - - get ty(): Type { - return this.retTy; - } - - constructor( - ast: LPNode, - fns: Fn[], - maybeClosure: VarDef | null, - args: Ref[], - scope: Scope, - retTy: Type, - ) { - super(ast); - if (fns.length === 0 && maybeClosure === null) { - throw new Error(`no function possibilities provided for ${ast}`); - } - this.fns = fns; - this.maybeClosure = maybeClosure; - this.args = args; - this.retTy = retTy; - this.scope = scope; - } - - static fromCallAst( - ast: LPNode, - fnName: string, - accessed: Ref | null, - metadata: MetaData, - ): [Stmt[], Expr] { - const stmts = []; - const argAst = ast.get('fncall').get('assignablelist'); - const argAsts: LPNode[] = []; - if (argAst.has('assignables')) { - argAsts.push(argAst.get('assignables')); - if (argAst.has('cdr')) { - argAsts.push( - ...argAst - .get('cdr') - .getAll() - .map((a) => a.get('assignables')), - ); - } - } - const args: Ref[] = []; - if (accessed !== null) { - args.push(accessed); - } - args.push( - ...argAsts.map((a) => { - const [generated, argExpr] = Expr.fromAssignablesAst(a, metadata); - stmts.push(...generated); - let arg: Ref; - if (argExpr instanceof Ref) { - arg = argExpr; - } else { - const dec = Dec.gen(argExpr, metadata); - stmts.push(dec); - arg = dec.ref(); - } - return arg; - }), - ); - let fns = metadata.scope.deepGet(fnName); - const closure = metadata.get(fnName); - if ((fns === null || !isFnArray(fns)) && closure === null) { - throw new Error(`no functions found for ${fnName}`); - } - if (fns === null || !isFnArray(fns)) { - fns = [] as Fn[]; - } - // first reduction - const argTys = args.map((arg) => arg.ty); - const retTy = Type.generate(); - const [selFns, selPTys, selRetTys] = FunctionType.matrixSelect( - fns, - argTys, - retTy, - metadata.scope, - ); - fns = selFns; - // now, constrain all of the args to their possible types - const constrainArgs = selPTys.map((selPTys) => - selPTys.length === 1 ? selPTys[0] : Type.oneOf(selPTys), - ); - argTys.forEach((ty, ii) => ty.constrain(constrainArgs[ii], metadata.scope)); - retTy.constrain(Type.oneOf(selRetTys), metadata.scope); - if (closure !== null) { - TODO('closures should also be passed into matrix selection'); - } - return [stmts, new Call(ast, fns, closure, args, metadata.scope, retTy)]; - } - - private fnSelect(): [Fn[], Type[][], Type[]] { - const ret = FunctionType.matrixSelect( - this.fns, - this.args.map((a) => a.ty), - this.retTy, - this.scope, - ); - return ret; - } - - cleanup() { - const [fns, pTys, retTys] = this.fnSelect(); - const isChanged = this.fns.length !== fns.length; - this.fns = fns; - this.args.forEach((arg, ii) => - arg.ty.constrain(Type.oneOf(pTys[ii]), this.scope), - ); - this.retTy.constrain(Type.oneOf(retTys), this.scope); - return isChanged; - } - - inline(amm: Output, kind: AssignKind, name: string, ty: Type) { - // ignore selTys because if there's a mismatch between `ty` - // and the return type of the selected function, there will - // be an error when we inline - const [selFns, _selTys] = this.fnSelect(); - if (selFns.length === 0) { - // TODO: to get better error reporting, we need to pass an ast when using - // operators - console.log('~~~ ERROR'); - console.log('selection pool:', this.fns); - console.log('args:', this.args); - console.log('kind:', kind); - console.log('expected output type:', ty); - throw new Error(`no function selected`); - } - // FunctionType.matrixSelect implements the matrix so that the most - // reasonable choice is last in the fn array. "Reasonableness" is computed - // with 2 factors: 1st is alignment with given OneOf types. If `add(1, 0)` - // is called, the literal types should prefer `int64` to `int32` etc. The - // other factor is order of declaration - Alan should always prefer using - // functions that are defined last. - const fn = selFns.pop(); - fn.inline(amm, this.args, kind, name, ty, this.scope); - } -} - -/* -Value literals. Should be able to at least provide a `OneOf` of types that -work in any given context. -*/ -class Const extends Expr { - val: string; - private detectedTy: Type; - - get ty(): Type { - return this.detectedTy; - } - - constructor(ast: LPNode, val: string, detectedTy: Type) { - super(ast); - this.val = val; - this.detectedTy = detectedTy; - } - - static fromConstantsAst(ast: LPNode, _metadata: MetaData): [Stmt[], Expr] { - let val = ast.t.trim(); - let detectedTy = null; - if (ast.has('bool')) { - detectedTy = opcodes().get('bool'); - } else if (ast.has('str')) { - detectedTy = opcodes().get('string'); - // sanitize single-quoted strings - // don't need to for double-quoted strings, since the string output - // is double-quoted - if (val.startsWith("'")) { - const sanitized = val.substring(1, val.length - 1).replace(/'/g, "\\'"); - val = `"${sanitized.replace(/"/g, '\\"')}"`; - } - } else if (ast.has('num')) { - if (val.indexOf('.') !== -1) { - detectedTy = Type.oneOf( - ['float32', 'float64'].map((t) => opcodes().get(t)), - ); - } else { - detectedTy = Type.oneOf( - ['float32', 'float64', 'int8', 'int16', 'int32', 'int64'].map((t) => - opcodes().get(t), - ), - ); - } - } else { - throw new Error(`unrecognized constants node: ${ast}`); - } - return [[], new Const(ast, val, detectedTy)]; - } - - inline(amm: Output, kind: AssignKind, name: string, ty: Type) { - const suffixes = { - int8: 'i8', - int16: 'i16', - int32: 'i32', - int64: 'i64', - float32: 'f32', - float64: 'f64', - string: 'str', - bool: 'bool', - }; - - const globalName = amm.global('const', ty, this.val); - let copyOp = 'copy'; - if (suffixes[ty.ammName]) { - copyOp += suffixes[ty.ammName]; - } else { - // sanity check - throw new Error(`unhandled const type ${ty.ammName}`); - } - amm.assign(kind, name, ty, copyOp, [globalName]); - } -} - -/* -Just like `AccessField`, this type should probably only apply to constructing -Structs, which implies renaming this class. -*/ -class New extends Expr { - valTy: Type; - fields: { [name: string]: Ref }; - - get ty(): Type { - return this.valTy; - } - - /** - * NOTE: this constructor does NOT check to make sure that the fields are - * valid. Ensure that the caller has already done validated the fields - * (fromTypeLiteral does this already). - */ - constructor(ast: LPNode, valTy: Type, fields: { [name: string]: Ref }) { - super(ast); - this.valTy = valTy; - this.fields = fields; - } - - static fromTypeLiteral(ast: LPNode, metadata: MetaData): [Stmt[], New] { - const stmts: Stmt[] = []; - - // get the constructed type - const typename = ast.get('literaldec').get('fulltypename'); - const valTy = Type.getFromTypename(typename, metadata.scope); - - const fieldsAst = ast.get('typebase').get('typeassignlist'); - const fieldAsts: LPNode[] = [fieldsAst, ...fieldsAst.get('cdr').getAll()]; - const fields: { [name: string]: Ref } = {}; - // type that we're generating to make sure that the constructed object - // has the appropriate fields. - const fieldCheck = Type.generate(); - - for (const fieldAst of fieldAsts) { - const fieldName = fieldAst.get('variable').t.trim(); - // assign the value of the field to a variable - // can't use const here but eslint doesn't like the newStmts isn't const - // eslint-disable-next-line prefer-const - let [newStmts, fieldVal] = Expr.fromAssignablesAst( - fieldAst.get('assignables'), - metadata, - ); - stmts.push(...newStmts); - if (!(fieldVal instanceof Ref)) { - const fieldDef = Dec.gen(fieldVal, metadata); - stmts.push(fieldDef); - fieldVal = fieldDef.ref(); - } - // assign the field to our pseudo-object - fields[fieldName] = fieldVal as Ref; - // add the field to our generated type - fieldCheck.constrain( - Type.hasField(fieldName, fieldVal.ty), - metadata.scope, - ); - } - - // ensure that the type we just constructed matches the type intended - // to be constructed. if our generated type isn't compatible with the - // intended type, then that means we don't have all of its fields. If - // the intended type isn't compatible with our generated type, that - // means we have some unexpected fields - // TODO: MUCH better error handling. Ideally without exposing the - // internal details of the `Type`. - if (!fieldCheck.compatibleWithConstraint(valTy, metadata.scope)) { - throw new Error( - `Constructed value doesn't have all of the fields in type ${valTy.name}`, - ); - } else if (!valTy.compatibleWithConstraint(fieldCheck, metadata.scope)) { - throw new Error( - `Constructed value has fields that don't exist in ${valTy.name}`, - ); - } - - // *new* new - return [stmts, new New(ast, valTy, fields)]; - } - - inline(amm: Output, kind: AssignKind, name: string, ty: Type): void { - const int64 = opcodes().get('int64'); - const size = amm.global('const', int64, this.ty.size().toString()); - amm.assign(kind, name, ty, 'newarr', [size]); - for (const field in this.fields) { - const fieldTy = this.fields[field].ty.instance(); - const sizeHint = amm.global('const', int64, `${fieldTy.size()}`); - const pushCall = fieldTy.isFixed() ? 'pushf' : 'pushv'; - amm.call(pushCall, [name, this.fields[field].ammName, sizeHint]); - } - } -} - -/* -Just an Expr that points to some `VarDef`. It might be possible to avoid -making the new `ConstRef` that's mentioned in `Const.ts` -*/ -export class Ref extends Expr { - def: VarDef; - - get ammName(): string { - return this.def.ammName; - } - - get ty(): Type { - return this.def.ty; - } - - constructor(def: VarDef) { - super(def.ast); - this.def = def; - } - - inline(_amm: Output, _kind: AssignKind, _name: string, _ty: Type) { - throw new Error(`did not expect to inline a variable reference`); - } -} diff --git a/compiler/src/lnntoamm/Fn.ts b/compiler/src/lnntoamm/Fn.ts deleted file mode 100644 index 7adbe9b72..000000000 --- a/compiler/src/lnntoamm/Fn.ts +++ /dev/null @@ -1,382 +0,0 @@ -import { stdout } from 'process'; -import { LPNode, NamedAnd, NulLP, Token } from '../lp'; -import Output, { AssignKind } from './Amm'; -import Expr, { Ref } from './Expr'; -import opcodes from './opcodes'; -import Scope from './Scope'; -import Stmt, { Dec, Exit, FnParam, MetaData } from './Stmt'; -import Type, { FunctionType, TempConstrainOpts } from './Types'; -import { DBG, TODO } from './util'; - -/* -This class should probably be used for constructing closures, and -inherit the `Metadata` of the containing function (if any). I think -a thin `Expr` wrapper is necessary, but otherwise it's pretty -straightforward and generic enough. -*/ -export default class Fn { - // null if it's an anonymous fn - name: string | null; - ast: LPNode; - // the scope this function is defined in is the `par` - scope: Scope; - params: FnParam[]; - retTy: Type; - body: Stmt[]; - exprFn: Expr; - // not used by this class, but used by Statements - metadata: MetaData; - ty: FunctionType; - - get argNames(): string[] { - return Object.keys(this.params); - } - - constructor( - ast: LPNode, - scope: Scope, - name: string | null, - params: FnParam[], - retTy: Type | null, - body: Stmt[], - metadata: MetaData = null, - ) { - this.ast = ast; - this.scope = scope; - this.name = name; - this.params = params; - this.retTy = retTy !== null ? retTy : Type.generate(); - this.body = body; - this.metadata = - metadata !== null ? metadata : new MetaData(scope, this.retTy); - - // sometimes a type isn't selected from a OneOf until later constraints - // are passed. This means we have to call `cleanup` *at least* twice, - // however `cleanup` tells us if more rounds are necessary so just use - // this loop - for ( - let ensureOnce = true; - this.body.reduce( - (carry, stmt) => stmt.cleanup(this.scope) || carry, - ensureOnce, - ); - ensureOnce = false - ); - - const tyAst = ((fnAst: LPNode) => { - if (fnAst instanceof NulLP) { - // assume it's for an opcode - const compilerDefinition = ''; - const makeToken = (tok: string) => - new Token(tok, compilerDefinition, -1, -1); - return new NamedAnd( - `opcode ${this.name}`, - { - opcode: makeToken('opcode'), - _whitespace: makeToken(' '), - opcodeName: makeToken(this.name), - }, - compilerDefinition, - -1, - -1, - ); - } - return null; - })(this.ast); - this.ty = new FunctionType( - tyAst, - this.params.map((param) => param.ty), - this.retTy, - ); - } - - static fromFunctionsAst(ast: LPNode, scope: Scope): Fn { - const fnSigScope = new Scope(scope); - - let retTy: Type; - if (ast.get('optreturntype').has()) { - const name = ast.get('optreturntype').get('fulltypename'); - retTy = Type.getFromTypename(name, fnSigScope, { isTyVar: true }); - if (retTy === null) { - throw new Error(`Type not in scope: ${name.t.trim()}`); - } - // TODO: I had to re-enable type erasure - previously it was done with - // `if (retTy.dup() !== null) throw new Error(...)` but this doesn't - // work for `fn none(): Maybe = noneM();` since the `any` in the - // return part *does* cause `dup` to return a non-null Type. Type - // erasure *may* be detected by seeing if the return type strictly - // represents the superset of the type returned from the internal - // `return` statement, ie given: - // ```ln - // fn foo(): any = true; - // ``` - // the `any` in the return part of the signature is strictly a - // superset of the `bool` type, so it definitely is type erasure, - // while the `any` in the return part of the signature for `fn none` - // is strictly equal to the `any` part returned by opcode `noneM`. - // More investigation is necessary to determine if this is the - // right way to detect that. - // - // If the syntax does evolve to require type parameters in functions, - // then this can be solved: simply check to make sure that the type - // doesn't `dup()` because the type variable shouldn't require a dup. - // For example, using an imaginary Alan syntax for generic functions, - // `fn foo(): Maybe` obviously doesn't contain type erasure: - // the type `T` gets assigned from the caller's point of view. Meanwhile, - // `fn foo(): Maybe` would obviously type-erased because the - // caller doesn't influence the type returned by `foo`. - } else { - retTy = Type.oneOf([Type.generate(), opcodes().get('void')]); - } - - const metadata = new MetaData(scope, retTy); - - const name = ast.get('optname').has() ? ast.get('optname').get().t : null; - const p: LPNode[] = []; - const arglist = ast.get('optargs').get('arglist'); - if (arglist.has()) { - p.push(arglist); - if (arglist.get('cdr').has()) { - p.push(...arglist.get('cdr').getAll()); - } - } - const params = p.map((paramAst) => - FnParam.fromArgAst(paramAst, metadata, fnSigScope), - ); - - let body = []; - let bodyAsts: LPNode | LPNode[] = ast.get('fullfunctionbody'); - if (bodyAsts.has('functionbody')) { - bodyAsts = bodyAsts - .get('functionbody') - .get('statements') - .getAll() - .map((s) => s.get('statement')); - bodyAsts.forEach((ast) => body.push(...Stmt.fromAst(ast, metadata))); - } else { - bodyAsts = bodyAsts.get('assignfunction').get('assignables'); - let exitVal: Expr; - [body, exitVal] = Expr.fromAssignablesAst(bodyAsts, metadata); - if (exitVal instanceof Ref) { - body.push(new Exit(bodyAsts, exitVal, retTy)); - retTy.constrain(exitVal.ty, scope); - } else { - const retVal = Dec.gen(exitVal, metadata); - body.push(retVal); - body.push(new Exit(bodyAsts, retVal.ref(), retTy)); - retTy.constrain(retVal.ty, scope); - } - } - - return new Fn(ast, new Scope(scope), name, params, retTy, body); - } - - static fromFunctionbody(ast: LPNode, scope: Scope): Fn { - scope = new Scope(scope); - const body = []; - const metadata = new MetaData(scope, opcodes().get('void')); - ast - .get('statements') - .getAll() - .map((s) => s.get('statement')) - .forEach((ast) => body.push(...Stmt.fromAst(ast, metadata))); - return new Fn( - ast, - scope, - null, - [], - // TODO: should probably just be `Type.generate()`. That'll allow - // eg `fn foo = 3;` to correctly assert that it's a function that - // returns a number. - opcodes().get('void'), - body, - metadata, - ); - } - - asHandler(amm: Output, event: string) { - const handlerParams = []; - for (const param of this.params) { - handlerParams.push([param.ammName, param.ty]); - } - amm.addHandler(event, handlerParams, this.retTy); - let isReturned = false; - for (let ii = 0; ii < this.body.length; ii++) { - const stmt = this.body[ii]; - stmt.inline(amm); - if (stmt instanceof Exit) { - isReturned = true; - if (ii !== this.body.length - 1) { - throw new Error( - `hmmmm... unreachable statements probably should've been caught earlier?`, - ); - } - } - } - if (!isReturned) { - if ( - !this.retTy.compatibleWithConstraint( - opcodes().get('void'), - this.metadata.scope, - ) - ) { - throw new Error(`event handlers should not return values`); - } - amm.exit(); - } - } - - // FIXME: it'll take a bit more work to do better inlining, but it *should* be possible - // to have `inline` load all of the amm code into a new `Stmt[]` which is then iterated - // at the handler level to do optimizations and such, similar to the `Microstatement[]` - // that was loaded but using the same JS objects(?) and the optimizations should only - // be further inlining... - // FIXME: another option is to convert to SSA form (talked a bit about in Amm.ts) and then - // perform optimizations from there. This *might* require the `Stmt[]` array from above - // *or* we can do it in Amm.ts using only strings (although that might be harder) - // FIXME: a 3rd option is to make amm itself only SSA and perform the the "register - // selection" in the ammtox stage. This might be the best solution, since it's the most - // flexible regardless of the backend, and amm is where that diverges. - // FIXME: this can also all probably be done by revamping AMM generation to use a visitor - // pattern, followed by a `cleanup` phase (as is done here and in Stmt.ts and Expr.ts). - inline( - amm: Output, - args: Ref[], - kind: AssignKind, - name: string, - ty: Type, - callScope: Scope, - ) { - if (args.length !== this.params.length) { - throw new Error(`function call argument mismatch`); - } - this.params.forEach((param, ii) => param.assign(args[ii], this.scope)); - this.retTy.tempConstrain(ty, callScope); - // console.log('----- inlining', this.name); - // stdout.write('rty: '); - // console.dir(this.retTy, { depth: 4 }); - // console.dir(this.body, { depth: 8 }); - for (let ii = 0; ii < this.body.length; ii++) { - const stmt = this.body[ii]; - if (stmt instanceof Exit) { - if (ii !== this.body.length - 1) { - throw new Error( - `got a return at a bad time (should've been caught already?)`, - ); - } - if (ty.eq(opcodes().get('void'))) { - break; - } - const refCall = ty.isFixed() ? 'reff' : 'refv'; - amm.assign(kind, name, ty, refCall, [stmt.ret.ammName]); - break; - } - stmt.inline(amm); - } - this.retTy.resetTemp(); - this.params.forEach((param) => param.unassign()); - } - - // TODO: this can be done with just FnType in Types.ts - just a refactor away :) - resultTyFor( - argTys: Type[], - expectResTy: Type, - scope: Scope, - tcOpts?: TempConstrainOpts, - ): [Type[], Type] | null { - let res: [Type[], Type] | null = null; - const isDbg = false; - try { - this.params.forEach((param, ii) => { - isDbg && stdout.write('==> constraining param ty '); - isDbg && console.dir(param.ty, { depth: 4 }); - isDbg && stdout.write('to '); - isDbg && console.dir(argTys[ii], { depth: 4 }); - param.ty.tempConstrain(argTys[ii], scope, tcOpts); - isDbg && stdout.write('now: '); - isDbg && console.dir(param.ty, { depth: 4 }); - }); - isDbg && - console.log('constraining ret ty', this.retTy, 'to', expectResTy); - this.retTy.tempConstrain(expectResTy, scope, tcOpts); - isDbg && console.log('now:', this.retTy); - isDbg && console.log('oh and expected is now', expectResTy); - const instanceOpts = { interfaceOk: true, forSameDupIface: [] }; - res = [ - this.params.map((param) => param.ty.instance(instanceOpts)), - this.retTy.instance(instanceOpts), - ]; - } catch (e) { - // do nothing: the args aren't applicable to the params so - // we return null (`res` is already `null`) and we need to - // ensure the param tys have `resetTemp` called on them. - // However, if the Error message starts with `TODO`, then - // print the error since it's for debugging purposes - const msg = e.message as string; - if (msg.startsWith('TODO')) { - // users should be encouraged to report these. Also probably - // best to just make a custom Error type for TODOs - console.log( - 'warning: came across TODO from Types.ts when getting result ty for a function:', - ); - console.group(); - console.log(msg); - console.groupEnd(); - } - } - this.params.forEach((param) => param.ty.resetTemp()); - this.retTy.resetTemp(); - argTys.map((ty) => ty.resetTemp()); - expectResTy.resetTemp(); - return res; - } -} - -// circular dependency issue when this is defined in opcodes.ts :( -export class OpcodeFn extends Fn { - constructor( - name: string, - argDecs: { [name: string]: string }, - retTyName: string, - __opcodes: Scope, - ) { - const tyScope = new Scope(__opcodes); - const params = Object.entries(argDecs).map(([name, tyName]) => { - return new FnParam( - new NulLP(), - name, - Type.getFromTypename(tyName, tyScope, { isTyVar: true }), - ); - }); - const retTy = Type.getFromTypename(retTyName, tyScope, { isTyVar: true }); - if (retTy === null || !(retTy instanceof Type)) { - throw new Error('not a type'); - } - super(new NulLP(), __opcodes, name, params, retTy, []); - __opcodes.put(name, [this]); - } - - asHandler(_amm: Output, _event: string) { - // should this be allowed? - TODO('opcodes as event listener???'); - } - - inline( - amm: Output, - args: Ref[], - kind: AssignKind, - assign: string, - ty: Type, - callScope: Scope, - ) { - this.retTy.tempConstrain(ty, callScope); - amm.assign( - kind, - assign, - ty, - this.name, - args.map((ref) => ref.ammName), - ); - this.retTy.resetTemp(); - } -} diff --git a/compiler/src/lnntoamm/Module.ts b/compiler/src/lnntoamm/Module.ts deleted file mode 100644 index b0b5e1340..000000000 --- a/compiler/src/lnntoamm/Module.ts +++ /dev/null @@ -1,310 +0,0 @@ -import { LPNode } from '../lp'; -import * as Ast from './Ast'; -import Const from './Const'; -import Event from './Event'; -import Fn from './Fn'; -import Operator from './Operator'; -import Scope from './Scope'; -import Type from './Types'; -import { isFnArray } from './util'; - -const modules: { [name: string]: Module } = {}; - -interface AstMap { - [key: string]: LPNode; -} - -class Module { - moduleScope: Scope; - exportScope: Scope; - - constructor(rootScope: Scope) { - // Thoughts on how to handle this right now: - // 1. The outermost module scope is read-only always. - // 2. Therefore anything in the export scope can simply be duplicated in both scopes - // 3. Therefore export scope needs access to the module scope so the functions function, but - // the module scope can just use its local copy - this.moduleScope = new Scope(rootScope); - this.exportScope = new Scope(this.moduleScope); - } - - static getAllModules() { - return modules; - } - - static populateModule( - path: string, - ast: LPNode, // ModuleContext - rootScope: Scope, - isStd = false, - ): Module { - // First, take the export scope of the root scope and put references to it in this module. If - // it is a built-in std module, it inherits from the root scope, otherwise it attaches all - // exported references. This way std modules get access to the opcode scope via inheritance and - // 'normal' modules do not. - const module = new Module(isStd ? rootScope : undefined); - if (!isStd) { - for (const [name, val] of Object.entries(rootScope.vals)) { - module.moduleScope.put(name, val); - } - } - - // imports - ast - .get('imports') - .getAll() - .forEach((importAst) => { - if (importAst.has('standardImport')) { - importAst = importAst.get('standardImport'); - let importName: string; - if (importAst.get('renamed').has()) { - importName = importAst.get('renamed').get('varop').t.trim(); - } else { - const nameParts = importAst.get('dependency').t.trim().split('/'); - importName = nameParts.pop(); - } - const resolved = Ast.resolveDependency( - path, - importAst.get('dependency'), - ); - const importedModule = modules[resolved]; - module.moduleScope.put(importName, importedModule.exportScope); - } else { - importAst = importAst.get('fromImport'); - const resolvedDep = Ast.resolveDependency( - path, - importAst.get('dependency'), - ); - const importedModule = modules[resolvedDep]; - const vars: LPNode[] = []; - vars.push(importAst.get('varlist').get('renameablevar')); - importAst - .get('varlist') - .get('cdr') - .getAll() - .forEach((r) => vars.push(r.get('renameablevar'))); - vars.forEach((moduleVar) => { - const exportName = moduleVar.get('varop').t.trim(); - let importName = exportName; - if (moduleVar.get('renamed').has()) { - importName = moduleVar.get('renamed').get('varop').t.trim(); - } - const thing = importedModule.exportScope.shallowGet(exportName); - if (thing === null) { - throw new Error( - `couldn't import ${exportName}: not defined in ${resolvedDep}`, - ); - } else if (isFnArray(thing)) { - const otherthing = module.moduleScope.deepGet(importName); - if (otherthing === null) { - module.moduleScope.put(importName, [...thing]); - } else if (isFnArray(otherthing)) { - // note: this was `...thing, ...otherthing` before, but that - // breaks preference for more-recently-defined things - module.moduleScope.put(importName, [...otherthing, ...thing]); - } else { - throw new Error(`incompatible imports for ${importName}`); - } - } else { - module.moduleScope.put(importName, thing); - } - }); - } - }); - - // now we're done with imports, move on to the body - const body = ast.get('body').getAll(); - body - .filter((r) => r.has('types')) - .forEach((a) => module.addTypeAst(a, false)); - body - .filter((r) => r.has('interfaces')) - .forEach((a) => module.addInterfaceAst(a, false)); - body - .filter((r) => r.has('constdeclaration')) - .forEach((a) => module.addConstAst(a, false)); - body - .filter((r) => r.has('events')) - .forEach((a) => module.addEventAst(a, false)); - body - .filter((r) => r.has('functions')) - .forEach((a) => module.addFnAst(a, false)); - body - .filter((r) => r.has('operatormapping')) - .forEach((a) => module.addOpAst(a, false)); - body - .filter((r) => r.has('exportsn')) - .forEach((node) => { - node = node.get('exportsn').get('exportable'); - if (node.has('ref')) { - const ref = node.get('ref'); - const exportVar = module.moduleScope.deepGet(ref.t.trim()); - const name = ref.t.trim().split('.').pop(); - module.moduleScope.put(name, exportVar); - module.exportScope.put(name, exportVar); - } else if (node.has('types')) { - module.addTypeAst(node, true); - } else if (node.has('interfaces')) { - module.addInterfaceAst(node, true); - } else if (node.has('constdeclaration')) { - module.addConstAst(node, true); - } else if (node.has('events')) { - module.addEventAst(node, true); - } else if (node.has('functions')) { - module.addFnAst(node, true); - } else if (node.has('operatormapping')) { - module.addOpAst(node, true); - } - }); - body - .filter((r) => r.has('handlers')) - .forEach((a) => module.addHandlerAst(a)); - - return module; - } - - static modulesFromAsts(astMap: AstMap, rootScope: Scope) { - const modulePaths = Object.keys(astMap); - while (modulePaths.length > 0) { - for (let i = 0; i < modulePaths.length; i++) { - const path = modulePaths[i]; - const moduleAst = astMap[path]; - const imports = Ast.resolveImports(path, moduleAst); - let loadable = true; - for (const importPath of imports) { - if (importPath[0] === '@') continue; - if (modules.hasOwnProperty(importPath)) continue; - loadable = false; - } - if (!loadable) continue; - modulePaths.splice(i, 1); - i--; - const module = Module.populateModule(path, moduleAst, rootScope); - modules[path] = module; - } - } - return modules; - } - - addTypeAst(typeAst: LPNode, isExport: boolean) { - typeAst = typeAst.get('types'); - const newType = Type.fromTypesAst(typeAst, this.moduleScope); - this.moduleScope.put(newType.name, newType); - if (isExport) { - this.exportScope.put(newType.name, newType); - } - } - - addInterfaceAst(interfaceAst: LPNode, isExport: boolean) { - interfaceAst = interfaceAst.get('interfaces'); - const newInterface = Type.fromInterfacesAst(interfaceAst, this.moduleScope); - const alreadyInScope = this.moduleScope.shallowGet(newInterface.name); - if (alreadyInScope !== null) { - throw new Error( - `Tried to define interface ${newInterface.name}, but that name is already in scope`, - ); - } - this.moduleScope.put(newInterface.name, newInterface); - if (isExport) { - this.exportScope.put(newInterface.name, newInterface); - } - } - - addConstAst(constAst: LPNode, isExport: boolean) { - constAst = constAst.get('constdeclaration'); - const newConst = Const.fromAst(constAst, this.moduleScope); - const alreadyInScope = this.moduleScope.shallowGet(newConst.name); - if (alreadyInScope !== null) { - console.log(this.moduleScope); - throw new Error( - `Tried to define const ${newConst.name}, but that name is already in scope`, - ); - } - this.moduleScope.put(newConst.name, newConst); - if (isExport) { - this.exportScope.put(newConst.name, newConst); - } - } - - addEventAst(eventAst: LPNode, isExport: boolean) { - eventAst = eventAst.get('events'); - const newEvent = Event.fromAst(eventAst, this.moduleScope); - const alreadyInScope = this.moduleScope.shallowGet(newEvent.name); - if (alreadyInScope !== null) { - throw new Error( - `Tried to define event ${newEvent.name}, but that name is already in scope`, - ); - } - this.moduleScope.put(newEvent.name, newEvent); - if (isExport) { - this.exportScope.put(newEvent.name, newEvent); - } - } - - addFnAst(fnAst: LPNode, isExport: boolean) { - fnAst = fnAst.get('functions'); - const newFn = Fn.fromFunctionsAst(fnAst, this.moduleScope); - if (newFn.name === null) { - throw new Error('Module-level functions must have a name'); - } - const insertScopes = [this.moduleScope]; - if (isExport) insertScopes.push(this.exportScope); - for (const scope of insertScopes) { - const otherFns = scope.shallowGet(newFn.name) || []; - if (!isFnArray(otherFns)) { - throw new Error( - `Tried to define function ${newFn.name}, but a non-function by that name is already in scope`, - ); - } - scope.put(newFn.name, [...otherFns, newFn]); - } - } - - addOpAst(opAst: LPNode, isExport: boolean) { - opAst = opAst.get('operatormapping'); - const newOp = Operator.fromAst(opAst, this.moduleScope); - // no need to validate this since only operators can have such a name - const otherOps = this.moduleScope.get(newOp.symbol) || []; - this.moduleScope.put(newOp.symbol, [...otherOps, newOp]); - if (isExport) { - const exportedOps = - (this.exportScope.shallowGet(newOp.symbol) as Operator[]) || []; - this.exportScope.put(newOp.symbol, [...exportedOps, newOp]); - } - } - - addHandlerAst(handlerAst: LPNode) { - handlerAst = handlerAst.get('handlers'); - const eventName = handlerAst.get('eventname').t; - const event = this.moduleScope.deepGet(eventName); - if (event === null) { - throw new Error(`Could not find specified event: ${eventName}`); - } else if (!(event instanceof Event)) { - throw new Error(`${eventName} is not an event`); - } - - handlerAst = handlerAst.get('handler'); - let fns: Fn | Fn[] = null; - if (handlerAst.has('fnname')) { - const fnName = handlerAst.get('fnname').t; - const inScope = this.moduleScope.deepGet(fnName); - if (inScope === null) { - throw new Error(`Could not find specified function: ${fnName}`); - } else if (!(inScope instanceof Array) || !(inScope[0] instanceof Fn)) { - throw new Error(`${fnName} is not a function`); - } - fns = inScope as Fn[]; - } else if (handlerAst.has('functions')) { - fns = Fn.fromFunctionsAst(handlerAst.get('functions'), this.moduleScope); - } else if (handlerAst.has('functionbody')) { - fns = Fn.fromFunctionbody( - handlerAst.get('functionbody'), - this.moduleScope, - ); - } - // gets type-checked later - event.handlers.push(fns); - } -} - -export default Module; diff --git a/compiler/src/lnntoamm/Operator.ts b/compiler/src/lnntoamm/Operator.ts deleted file mode 100644 index f08aa8e82..000000000 --- a/compiler/src/lnntoamm/Operator.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { LPNode } from '../lp'; -import Fn from './Fn'; -import Scope from './Scope'; -import Type, { FunctionType } from './Types'; -import { isFnArray } from './util'; - -class Operator { - ast: LPNode; - symbol: string; - precedence: number; - isPrefix: boolean; - fns: Array; - - constructor( - ast: LPNode, - symbol: string, - precedence: number, - isPrefix: boolean, - fns: Array, - ) { - this.ast = ast; - this.symbol = symbol; - this.precedence = precedence; - this.isPrefix = isPrefix; - this.fns = fns; - } - - static fromAst(ast: LPNode, scope: Scope): Operator { - const isPrefix = ast.get('fix').has('prefix'); - const opmap = ast.get('opmap').get(); - const precedence = opmap.get('opprecedence').get('num'); - const symbol = opmap.get('fntoop').get('operators'); - const fnName = opmap.get('fntoop').get('fnname'); - const scoped = scope.get(fnName.t); - if (scoped === null) { - throw new Error( - `\`${fnName.t}\` cannot be used as an operator - function not found`, - ); - } else if (!isFnArray(scoped)) { - throw new Error( - `\`${fnName.t}\` cannot be used as an operator - it's not a function`, - ); - } - const fns = scoped.filter((fn) => fn.params.length === (isPrefix ? 1 : 2)); - return new Operator( - ast, - symbol.t, - Number.parseInt(precedence.t), - isPrefix, - fns, - ); - } - - select( - scope: Scope, - expectResTy: Type, - arg1: Type, - arg2?: Type, - ): [Fn[], Type[][], Type[]] { - if ((this.isPrefix && arg2) || (!this.isPrefix && !arg2)) { - console.log('~~~ ERROR'); - console.log('for operator:', this); - console.log('arg1:', arg1); - if (arg2) { - console.log('arg2:', arg2); - } - throw new Error(`nope`); - } - const tys = [arg1, ...(arg2 ? [arg2] : [])]; - return FunctionType.matrixSelect(this.fns, tys, expectResTy, scope); - } -} - -export default Operator; diff --git a/compiler/src/lnntoamm/Scope.ts b/compiler/src/lnntoamm/Scope.ts deleted file mode 100644 index c946b5b37..000000000 --- a/compiler/src/lnntoamm/Scope.ts +++ /dev/null @@ -1,81 +0,0 @@ -import Const from './Const'; -import Event from './Event'; -import Fn from './Fn'; -import Operator from './Operator'; -import Ty from './Types'; - -// Scope instead of a Module -type Boxish = Scope | Const | Event | Fn[] | Operator[] | Ty; - -type BoxSet = { - [K: string]: Boxish; -}; - -class Scope { - vals: BoxSet; - par: Scope | null; - secondaryPar: Scope | null; - - constructor(par?: Scope) { - this.vals = {}; - this.par = par ? par : null; - this.secondaryPar = null; - } - - get(name: string) { - if (this.vals.hasOwnProperty(name)) { - return this.vals[name]; - } - if (this.par) { - const val = this.par.get(name); - if (!val && !!this.secondaryPar) { - return this.secondaryPar.get(name); - } else { - return val; - } - } - return null; - } - - shallowGet(name: string) { - if (this.vals.hasOwnProperty(name)) { - return this.vals[name]; - } - return null; - } - - deepGet(fullName: string) { - const fullVar = fullName.trim().split('.'); - let boxedVar: Boxish; - for (let i = 0; i < fullVar.length; i++) { - if (i === 0) { - boxedVar = this.get(fullVar[i]); - } else if (!boxedVar) { - return null; - } else { - if (boxedVar instanceof Scope) { - boxedVar = boxedVar.get(fullVar[i]); - } else { - return null; - } - } - } - return boxedVar; - } - - has(name: string) { - if (this.vals.hasOwnProperty(name)) { - return true; - } - if (this.par) { - return this.par.has(name); - } - return false; - } - - put(name: string, val: Boxish) { - this.vals[name.trim()] = val; - } -} - -export default Scope; diff --git a/compiler/src/lnntoamm/Std.ts b/compiler/src/lnntoamm/Std.ts deleted file mode 100644 index 09415b102..000000000 --- a/compiler/src/lnntoamm/Std.ts +++ /dev/null @@ -1,100 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; - -import * as Ast from './Ast'; -import Module from './Module'; -import opcodes from './opcodes'; -import { LPNode } from '../lp'; - -interface AstRec { - name: string; - ast: LPNode; -} - -export const loadStdModules = (stdImports: Set) => { - const stdDir = path.join(__dirname, '../../std'); - const allStdAsts = fs - .readdirSync(stdDir) - .filter((n) => /.lnn$/.test(n)) - .map((n) => ({ - name: n, - ast: Ast.fromFile(path.join(__dirname, '../../std', n)), - })); - const stdAsts = allStdAsts.filter( - (ast) => stdImports.has(ast.name) || ast.name === 'root.lnn', - ); - // Load the rootScope first, all the others depend on it - let rootModule: Module; - stdAsts.forEach((moduleAst) => { - if (moduleAst.name === 'root.lnn') { - rootModule = Module.populateModule( - '', - moduleAst.ast, - opcodes(), - true, - ); - Module.getAllModules()[''] = rootModule; - } - }); - // Put the remaining ASTs in a loadable order - const orderedAsts = []; - let i = 0; - while (stdAsts.length > 0) { - const stdAst: AstRec = stdAsts[i]; - // Just remove the root node, already processed - if (stdAst.name === 'root.lnn') { - stdAsts.splice(i, 1); - i = i % stdAsts.length; - continue; - } - // Immediately add any node with no imports and remove from this list - if (stdAst.ast.get('imports').getAll().length === 0) { - orderedAsts.push(stdAst); - stdAsts.splice(i, 1); - i = i % stdAsts.length; - continue; - } - // For everything else, check if the dependencies are already queued up - const importAsts = stdAst.ast.get('imports').getAll(); - let safeToAdd = true; - for (const importAst of importAsts) { - const depName = ( - importAst.has('standardImport') - ? importAst.get('standardImport').get('dependency').t.trim() - : importAst.get('fromImport').get('dependency').t.trim() - ) - .replace('@std/', '') - .replace(/$/, '.lnn'); - if (!orderedAsts.some((ast) => ast.name === depName)) { - // add std modules this std module imports if not present - if (!stdAsts.some((ast) => ast.name === depName)) { - stdAsts.splice(i, 0, allStdAsts.filter((a) => a.name === depName)[0]); - } - safeToAdd = false; - break; - } - } - // If it's safe, add it - if (safeToAdd) { - orderedAsts.push(stdAst); - stdAsts.splice(i, 1); - i = i % stdAsts.length; - continue; - } - // Otherwise, skip this one - i = (i + 1) % stdAsts.length; - } - // Now load the remainig modules based on the root scope - orderedAsts.forEach((moduleAst) => { - if (moduleAst.name !== 'root.lnn') { - moduleAst.name = '@std/' + moduleAst.name.replace(/.lnn$/, ''); - const stdModule = Module.populateModule( - moduleAst.name, - moduleAst.ast, - rootModule.exportScope, - true, - ); - Module.getAllModules()[moduleAst.name] = stdModule; - } - }); -}; diff --git a/compiler/src/lnntoamm/Stmt.ts b/compiler/src/lnntoamm/Stmt.ts deleted file mode 100644 index 0b95009b3..000000000 --- a/compiler/src/lnntoamm/Stmt.ts +++ /dev/null @@ -1,522 +0,0 @@ -import { LPNode } from '../lp'; -import Output from './Amm'; -import Event from './Event'; -import Expr, { Ref } from './Expr'; -import opcodes from './opcodes'; -import Scope from './Scope'; -import Type from './Types'; -import { genName, TODO } from './util'; - -/** - * Metadata for the current statement's context. This allows it to - * get references to other Stmts that are defined in the same scope - * or in outer scopes, the containing scope, and the return type of - * the containing function. This makes accessing other Stmts an O(1) - * operation instead of O(n). - */ -export class MetaData { - scope: Scope; - variables: { [name: string]: VarDef }; - retTy: Type; - - constructor( - scope: Scope, - retTy: Type, - variables: { [name: string]: VarDef } = null, - ) { - this.scope = scope; - this.retTy = retTy; - this.variables = variables !== null ? variables : {}; - } - - get(name: string): VarDef | null { - if (!this.variables.hasOwnProperty(name)) { - return null; - } - return this.variables[name]; - } - - define(dec: VarDef) { - if (this.get(dec.name) !== null) { - throw new Error(`Can't redefine value ${dec.name}`); - } - this.variables[dec.name] = dec; - } -} - -export default abstract class Stmt { - ast: LPNode; - - constructor(ast: LPNode) { - this.ast = ast; - } - - /** - * @returns true if more cleanup might be required - */ - abstract cleanup(scope: Scope): boolean; - abstract inline(amm: Output): void; - - static fromAst(ast: LPNode, metadata: MetaData): Stmt[] { - const stmts = []; - if (ast.has('assignables')) { - const [generatedStmts, expr] = Expr.fromAssignablesAst( - ast.get('assignables').get('assignables'), - metadata, - ); - stmts.push(...generatedStmts, Dec.gen(expr, metadata)); - } else if (ast.has('assignments')) { - stmts.push(...Assign.fromAssignments(ast.get('assignments'), metadata)); - } else if (ast.has('conditionals')) { - stmts.push(...Cond.fromConditionals(ast.get('conditionals'), metadata)); - } else if (ast.has('declarations')) { - stmts.push(...Dec.fromDeclarations(ast.get('declarations'), metadata)); - } else if (ast.has('emits')) { - stmts.push(...Emit.fromEmits(ast.get('emits'), metadata)); - } else if (ast.has('exits')) { - stmts.push(...Exit.fromExits(ast.get('exits'), metadata)); - } else { - throw new Error(`unrecognized statement ast: ${ast}`); - } - return stmts; - } -} - -/* -Reassignment. Honestly, this can all be moved into `Dec` - I thought there -would be some utility in keeping them separate but I don't see it. Just make -sure that if it's a reassignment then it needs *the same Type reference* as the -upstream `Dec`'s (ie `this.type = upstream.type`) - -I recommend keeping the `Assign` class name -*/ -export class Assign extends Stmt { - upstream: VarDef; - expr: Expr; - - constructor(ast: LPNode, upstream: VarDef, expr: Expr) { - super(ast); - this.upstream = upstream; - this.expr = expr; - if (this.upstream.immutable) { - throw new Error( - `cannot reassign to ${this.upstream.name} since it was declared as a const`, - ); - } - } - - static fromAssignments(ast: LPNode, metadata: MetaData): Stmt[] { - const stmts: Stmt[] = []; - const name = ast.get('varn').t; - const upstream = metadata.get(name); - const [generated, expr] = Expr.fromAssignablesAst( - ast.get('assignables'), - metadata, - ); - upstream.ty.constrain(expr.ty, metadata.scope); - stmts.push(...generated, new Assign(ast, upstream, expr)); - return stmts; - } - - cleanup(scope: Scope): boolean { - const upTy = this.upstream.ty; - const didWork = this.expr.cleanup(upTy); - upTy.constrain(this.expr.ty, scope); - return didWork; - } - - inline(amm: Output) { - this.expr.inline( - amm, - '', - this.upstream.ammName, - this.upstream.ty.instance(), - ); - } -} - -/* -A thought, before going into it: this would be a lot easier if conditionals -were expressions instead of statements. The design I made is inspired by how -they're done in FP langs, but it's difficult to achieve perfectly and has -resulted in some hacky behavior that should be investigated before attempting -to use in any AVM-based FP langs. - -TODO: the implementation of this will be much less hacky if amm generation -was converted to the visitor pattern - -Conditionals require a bit of work. Here, a few things need to happen: - -1. Create a table that matches conditions to functions/closures to execute. -``` -if foo { closure 1 } -else if bar { closure 2 } -else { closure 3 } -// above gets transformed into: -[ - [foo, closure 1], - [bar, closure 2], - [true, closure 3], -] -``` - -2. The functions for each branch must have 0 parameters and all return the -same type, which is equal to their internal type wrapped in a `Maybe`. -``` -fn foo() { - if bar() { - // closure 1 - return 0; - } else if baz() { - // closure 2 - return 1; - } else { - // closure 3 - 'hello world'.print(); - } - return 2; -} -// closure 1 gets transformed into: -fn { return some(0); } -// closure 2 gets transformed into: -fn { return some(1); } -// closure 3 gets transformed into: -fn { - 'hello world'.print(); - return none(); -} -``` - -3. Create an instance of an Opaque `CondTable` type (I created the -`condtable` opcode while working on this, which I recommend doing). - -4. For each branch, insert the opcode `condfn` with the first argument -being the CondTable. The second argument should be the condition being -used to determine if the branch gets executed. The third argument is -the closure to execute. - -5. Insert the opcode `execcond` after all branches have been passed, -with the only argument being the CondTable. The return type is the -return type of all the branches. - -6. If the surrounding function guarantees a returned value, then unwrap -the returned Maybe. - ---- -On the opcode implementation side, `condfn` should insert the closure -into the CondTable if the condition is true and the CondTable is empty. -*/ -class Cond extends Stmt { - static fromConditionals(_ast: LPNode, _metadata: MetaData): Stmt[] { - return TODO('conditionals'); - } - - cleanup(): boolean { - return TODO('conditionals'); - } - - inline(_amm: Output) { - TODO('conditionals'); - } -} - -export abstract class VarDef extends Stmt { - // this name results in double negatives :) - immutable: boolean; - name: string; - ty: Type; - - abstract get ammName(): string; - - constructor(ast: LPNode, immutable: boolean, name: string, ty: Type) { - super(ast); - this.immutable = immutable; - this.name = name; - this.ty = ty; - } - - ref(): Ref { - return new Ref(this); - } -} - -/* -Variable declaration. In order to do the intended mutation tracking, -we might actually have to associate it with the `Dec`'s type like in -Rust. This can be done, but might be fairly complex to achieve. - -This class has to do a lot of constraining to make sure that everything -agrees on types for various variables. -*/ -export class Dec extends VarDef { - private __ammName = ''; - expr: Expr; - - get ammName(): string { - return this.__ammName; - } - - constructor( - ast: LPNode, - immutable: boolean, - name: string, - defTy: Type, - expr: Expr, - ) { - super(ast, immutable, name, defTy); - this.expr = expr; - } - - static fromDeclarations(ast: LPNode, metadata: MetaData): Stmt[] { - const stmts: Stmt[] = []; - let work: LPNode; - let immutable: boolean; - if (ast.has('constdeclaration')) { - work = ast.get('constdeclaration'); - immutable = true; - } else { - work = ast.get('letdeclaration'); - immutable = false; - } - const name = work.get('variable').t; - const [generated, expr] = Expr.fromAssignablesAst( - work.get('assignables'), - metadata, - ); - let ty: Type = expr.ty; - if (work.has('typedec')) { - const tyName = work.get('typedec').get('fulltypename'); - const found = Type.getFromTypename(tyName, metadata.scope); - // if the type hint is an interface, then all we have to do - // is ensure that the expr's ty matches the interface - const duped = found.dup(); - if (duped === null) { - ty = found; - ty.constrain(expr.ty, metadata.scope); - } else { - ty.constrain(duped, metadata.scope); - } - } - const dec = new Dec(ast, immutable, name, ty, expr); - stmts.push(...generated, dec); - metadata.define(dec); - return stmts; - } - - static gen(expr: Expr, metadata: MetaData): Dec { - const dec = new Dec( - expr.ast, - false, // default to mutable in case of eg builder pattern - genName(), - expr.ty, - expr, - ); - metadata.define(dec); - return dec; - } - - cleanup(scope: Scope): boolean { - const didWork = this.expr.cleanup(this.ty); - this.ty.constrain(this.expr.ty, scope); - return didWork; - } - - inline(amm: Output) { - // refs don't escape the current scope and this only happens 1x/scope, - // so this is fine - this.__ammName = genName(); - try { - this.expr.inline( - amm, - this.immutable ? 'const' : 'let', - this.ammName, - this.ty.instance(), - ); - } catch (e) { - console.dir(this, { depth: 6 }); - throw e; - } - } -} - -/* -FnParams aren't syntactic statements, so it's kinda weird to think -of this class as being a subclass of Stmt. Despite not actually being -included in amm output (with a couple exceptions, of course), there -are enough similarities that it's convenient to keep it here. -*/ -export class FnParam extends VarDef { - private __assigned: Ref | null; - - get ammName(): string { - if (this.__assigned === null) { - return this.name; - } else { - return this.__assigned.ammName; - } - } - - constructor(ast: LPNode, name: string, ty: Type) { - super( - ast, - false, // default to mutable for fn params - name, - ty, - ); - this.__assigned = null; - } - - static fromArgAst( - ast: LPNode, - metadata: MetaData, - fnSigScope: Scope, - ): FnParam { - const name = ast.get('variable').t; - const typename = ast.get('fulltypename'); - let paramTy = Type.getFromTypename(typename, fnSigScope, { isTyVar: true }); - if (paramTy === null) { - paramTy = Type.generate(); - TODO('args with implicit types are not supported yet'); - } else if (!(paramTy instanceof Type)) { - throw new Error(`Function parameter is not a valid type: ${typename.t}`); - } - const param = new FnParam(ast, name, paramTy); - metadata.define(param); - return param; - } - - assign(to: Ref, scope: Scope) { - this.__assigned = to; - this.ty.tempConstrain(to.ty, scope); - } - - cleanup(): boolean { - return false; - } - - inline(_amm: Output) { - throw new Error(`function parameters shouldn't be inlined`); - } - - unassign() { - this.__assigned = null; - this.ty.resetTemp(); - } -} - -/* -This class just has to make sure that a variable's type matches -the emitted event's type. If the event is `void`, then there won't -be a variable to emit. -*/ -class Emit extends Stmt { - event: Event; - emitVal: Ref | null; - - constructor(ast: LPNode, event: Event, emitVal: Ref | null) { - super(ast); - this.event = event; - this.emitVal = emitVal; - } - - static fromEmits(ast: LPNode, metadata: MetaData): Stmt[] { - const stmts: Stmt[] = []; - let emitRef: Ref | null = null; - if (ast.get('retval').has()) { - const emitVal = ast.get('retval').get('assignables'); - const [generated, expr] = Expr.fromAssignablesAst(emitVal, metadata); - stmts.push(...generated); - if (expr instanceof Ref) { - emitRef = expr; - } else { - const emitDec = Dec.gen(expr, metadata); - stmts.push(emitDec); - emitRef = emitDec.ref(); - } - } - const eventName = ast.get('eventname').t; - const event = metadata.scope.deepGet(eventName); - if (event === null) { - throw new Error(`event ${eventName} not defined`); - } else if (!(event instanceof Event)) { - throw new Error( - `cannot emit to non-events (${eventName} is not an event)`, - ); - } else if (emitRef !== null) { - emitRef.ty.constrain(event.eventTy, metadata.scope); - } - stmts.push(new Emit(ast, event, emitRef)); - if (!event.eventTy.compatibleWithConstraint(emitRef.ty, metadata.scope)) { - throw new Error( - `cannot emit value of type ${emitRef.ty.name} to event ${event.name} because it requires ${event.eventTy.name}`, - ); - } - return stmts; - } - - cleanup(scope: Scope): boolean { - if (!this.event.eventTy.compatibleWithConstraint(this.emitVal.ty, scope)) { - throw new Error( - `cannot emit value of type ${this.emitVal.ty.name} to event ${this.event.name} because it requires ${this.event.eventTy.name}`, - ); - } - return false; - } - - inline(amm: Output) { - if (!this.event.eventTy.eq(opcodes().get('void'))) { - amm.emit(this.event.ammName, this.emitVal.ammName); - } else { - amm.emit(this.event.ammName); - } - } -} - -/* -This statement ensures that the given variable's type matches the type -of the containing function. -*/ -export class Exit extends Stmt { - ret: Ref | null; - fnRetTy: Type; - - constructor(ast: LPNode, ret: Ref | null, fnRetTy: Type) { - super(ast); - this.ret = ret; - this.fnRetTy = fnRetTy; - } - - static fromExits(ast: LPNode, metadata: MetaData): Stmt[] { - const stmts: Stmt[] = []; - if (ast.get('retval').has()) { - const exitValAst = ast.get('retval').get('assignables'); - const [generated, expr] = Expr.fromAssignablesAst(exitValAst, metadata); - const retVal = Dec.gen(expr, metadata); - stmts.push( - ...generated, - retVal, - new Exit(ast, retVal.ref(), metadata.retTy), - ); - metadata.retTy.constrain(expr.ty, metadata.scope); - } else { - stmts.push(new Exit(ast, null, opcodes().get('void'))); - metadata.retTy.constrain(opcodes().get('void'), metadata.scope); - } - return stmts; - } - - cleanup(scope: Scope): boolean { - this.fnRetTy.constrain(this.ret.ty, scope); - return false; - } - - inline(amm: Output) { - if ( - !this.ret.ty.compatibleWithConstraint(opcodes().get('void'), opcodes()) - ) { - amm.exit(this.ret.ammName); - } else { - amm.exit(); - } - } -} diff --git a/compiler/src/lnntoamm/Types.ts b/compiler/src/lnntoamm/Types.ts deleted file mode 100644 index 47554c0f3..000000000 --- a/compiler/src/lnntoamm/Types.ts +++ /dev/null @@ -1,2076 +0,0 @@ -import { stdout } from 'process'; -import { LPNode, NulLP } from '../lp'; -import { fulltypenameAstFromString } from './Ast'; -import Fn from './Fn'; -import Operator from './Operator'; -import Scope from './Scope'; -import { - DBG, - Equalable, - genName, - isFnArray, - isOpArray, - matrixIndices, - TODO, -} from './util'; - -type Fields = { [name: string]: Type | null }; -export type FieldIndices = { [name: string]: number }; -type GenericArgs = { [name: string]: Type | null }; -type TypeName = [string, TypeName[]]; -interface Generalizable { - generics: GenericArgs; - solidify(types: Type[]): Type; -} -const generalizable = (val: Type): val is Type & Generalizable => - 'generics' in val; - -/* -Ugh, these *Opts types are nasty. I don't like them one bit. There are some -ways to avoid some of them, as mentioned below. - -If other TS classes need a variant of `Opts`, they can be extended with eg: -export type ConstrainOpts = OneOfConstrainOpts & GeneratedOpts -*/ -export type ConstrainOpts = OneOfConstrainOpts; -interface OneOfConstrainOpts { - stopAt?: Type; -} - -export type DupOpts = InterfaceDupOpts; -interface InterfaceDupOpts { - isTyVar?: boolean; -} - -export type InstanceOpts = InterfaceInstanceOpts; -interface InterfaceInstanceOpts { - /** - * If this is `true`, also set `forSameDupIface` to an array. - */ - interfaceOk?: boolean; - /** - * If `interfaceOk` is `true`, this needs to be assigned. - */ - forSameDupIface?: [Type, Type][]; -} - -export type TempConstrainOpts = InterfaceTempConstrainOpts; -interface InterfaceTempConstrainOpts { - isTest?: boolean; -} - -const parseFulltypename = (node: LPNode): TypeName => { - const name = node.get('typename').t.trim(); - const genericTys: TypeName[] = []; - if (node.get('opttypegenerics').has()) { - const generics = node.get('opttypegenerics').get('generics'); - genericTys.push(parseFulltypename(generics.get('fulltypename'))); - genericTys.push( - ...generics - .get('cdr') - .getAll() - .map((n) => n.get('fulltypename')) - .map(parseFulltypename), - ); - } - return [name, genericTys]; -}; - -/* -An idea that I've had: - -TypeConxn ---------- - -A type that represents a connection between two types. Right now, there -is some data loss during certain implementations. For example, consider -these two functions: -``` -fn foo(x: int64, y: int64): int64 = ...; -fn foo(x: int8, y: int8): int8 = ...; -``` -Right now, the selection matrix is able to solve this by filtering out -the 2nd function if either of the values passed to `foo` is an `int64`, -or if the return type is expected to be `int64`. However, this results -in a *lot* of allocations (as there's roughly `n` `Type.oneOf` calls -per type in the function signature, including the return type) and -doesn't provide great error messages. Additionally, this doesn't signify -"if the value passed to x is an int64, then y is an int64 and the return -type is an int64", as well as the other combinations that can be generated -from the above example. TypeConxn should be able to represent this -relationship between the types of those values, which should make the -type inferrence even more accurate, as well as the other benefits listed -above. -*/ -export default abstract class Type implements Equalable { - name: string; - ast: LPNode | null; - abstract get ammName(): string; - - constructor(name: string, ast: LPNode = null) { - this.name = name; - this.ast = ast; - } - - /** - * Checks if two types/constraints are compatible with each other. - * Returns `true` if they can immediately be `constrain`ed and there - * won't be an error. - * - * @param ty the type to check compatibility with - * @param scope the scope where the type checking should take place - */ - abstract compatibleWithConstraint(ty: Type, scope: Scope): boolean; - - /** - * Sets 2 types equal to each other (if both are usable types). If - * `to` is a constraint, then verifies that the constraint is valid - * for this type. - * - * @param to the type to set equality to - * @param scope the scope where the constraining should take place - * @param opts options that might be pertinent to various classes. - * Check `ConstrainOpts` for more details. - */ - abstract constrain(to: Type, scope: Scope, opts?: ConstrainOpts): void; - - /** - * Sees if this type contains `ty` anywhere within itself. Necessary - * to avoid circular references. - * - * @param ty type to look for - */ - abstract contains(ty: Type): boolean; - - /** - * Determines if two types are *effectively* equal. - * - * @param that type to check equality with - */ - abstract eq(that: Equalable): boolean; - - /** - * "Resolves" a type - aka, provides a Type representing this Type - * most concretely. The level of concreteness might need to be - * determined by options set in `opts`. - * - * @param opts options that might be pertinent to various classes. - * Check `InstanceOpts` for more details. - */ - abstract instance(opts?: InstanceOpts): Type; - - /** - * Temporarily sets 2 types equal to each other. - * - * @param to the type to set temporary equality to - * @param scope the scope where the temporary constraining should take place - * @param opts options that might be pertinent to various classes. - * Check `TempConstrainOpts` for more details. - */ - abstract tempConstrain( - to: Type, - scope: Scope, - opts?: TempConstrainOpts, - ): void; - - /** - * Resets this Type's temporary constraints. Should not reset any other - * Type's temporary constraints (I think). - */ - abstract resetTemp(): void; - - /** - * The size of the given type in multiples of 8. - * - * eg: int64 is size 1, * int8 is size 1, int128 is size 2 - */ - abstract size(): number; - - static getFromTypename( - name: LPNode | string, - scope: Scope, - dupOpts?: DupOpts, - ): Type { - if (typeof name === 'string') { - name = fulltypenameAstFromString(name); - } - const parsed = parseFulltypename(name); - const solidify = ([name, generics]: TypeName): Type => { - const ty = scope.get(name); - if (ty === null) { - throw new Error(`Could not find type ${name}`); - } else if (!(ty instanceof Type)) { - throw new Error(`${name} is not a type`); - } - if (generalizable(ty)) { - const genericArgLen = Object.keys(ty.generics).length; - if (genericArgLen !== generics.length) { - console.log([name, generics]); - throw new Error( - `Bad typename: type ${name} expects ${genericArgLen} type arguments, but ${generics.length} were provided`, - ); - } - const solidifiedTypeArgs = generics.map(solidify); - // interfaces can't have generic type params so no need to call - // dupIfNotLocalInterface - return ty.solidify(solidifiedTypeArgs); - } else if (generics.length !== 0) { - throw new Error( - `Bad typename: type ${name} doesn't expect any type arguments, but ${generics.length} were provided`, - ); - } else { - const duped = ty.dup(dupOpts); - if (duped === null) { - return ty; - } else { - // note: if scope isn't *only* for the function's arguments, - // this'll override the module scope and that would be bad. - scope.put(name, duped); - return duped; - } - } - }; - return solidify(parsed); - } - - static builtinInterface( - name: string, - fields: HasField[], - methods: HasMethod[], - operators: HasOperator[], - ) { - return new Interface(name, new NulLP(), fields, methods, operators); - } - - static fromInterfacesAst(ast: LPNode, scope: Scope): Type { - return Interface.fromAst(ast, scope); - } - - static fromTypesAst(ast: LPNode, scope: Scope): Type { - return Struct.fromAst(ast, scope); - } - - static generate(): Type { - return new Generated(); - } - - static oneOf(tys: Type[]): OneOf { - return new OneOf(tys); - } - - static opaque(name: string, generics: string[]): Type { - return new Opaque(name, generics); - } - - static hasField(name: string, ty: Type): Type { - return new HasField(name, null, ty); - } - - static hasMethod(name: string, params: Type[], ret: Type): Type { - return new HasMethod(name, null, params, ret); - } - - static hasOperator( - name: string, - params: Type[], - ret: Type, - isPrefix: boolean, - ): Type { - return new HasOperator(name, null, params, ret, isPrefix); - } - - // TODO: generalize for struct and any other generalizable type - // This function can probably be done better, but I'm not entirely sure how. - // It might not even be necessary, but that might need some of the classes - // described above. - // - // David and I worked on this together - it works by eg flattening - // OneOf(Result, Result) into Result. - static flatten(tys: Type[]): Type { - tys = tys.reduce((allTys, ty) => [...allTys, ...ty.fnselectOptions()], []); - let outerType: Type = null; - /* - squishy means it can be flattened, lol - - squishy: - [Result, Result] - [Result, Result] - [Result, Result] - - not-squishy: - [any, Result] - [void, bool] - [int8, int16] - */ - let isSquishy = true; - const innerTys = tys.reduce((genTys, ty) => { - // shallow equality (eg Result and Result will match here) - if (outerType === null) { - outerType = ty; - } - if (generalizable(ty)) { - if (ty.name !== outerType.name) { - isSquishy = false; - } else { - return Object.values(ty.generics).map((genTy, ii) => [ - ...(genTys[ii] || []), - genTy, - ]); - } - } else { - isSquishy = false; - } - }, new Array()); - - if (isSquishy) { - return (outerType as Opaque).solidify(innerTys.map(Type.oneOf)); - } else { - return null; - } - } - - /** - * Duplicates a Type so that it's ready to receive constraints. This is to - * work around Interface-typed values - constraining an Interface that hasn't - * been duped would result in eg all Stringifiables being int64, which would - * be bad and wrong. - * - * @param opts options that might be pertinent to various classes. - * See DupOpts for more details. - * @returns the duplicated type - */ - dup(opts?: DupOpts): Type | null { - return null; - } - - /** - * @returns a map of field names to alignment from the start of a type's memory - * location. Eg, given the following struct: - * - * ``` - * type Foo { - * bar: string, - * quux: int8, - * baz: int64, - * } - * ``` - * - * the result of this method would be: - * - * ``` - * { - * bar: 0, - * quux: 1, - * bax: 2, - * } - * ``` - */ - fieldIndices(): FieldIndices { - let name: string; - try { - name = this.instance().name; - } catch (e) { - name = this.name; - } - if (name !== '') { - name = ` ${name}`; - } - throw new Error(`Type${name} does not have fields`); - } - - /** - * @returns whether this type is variably sized or not. - */ - isFixed(): boolean { - // only a handful of builtin types are fixed - return false; - } - - /** - * @returns all possible types that this Type could represent. - */ - fnselectOptions(): Type[] { - return [this]; - } -} - -/* -Opaque types by definition cannot have their internal memory inspected. -This is great for primitive types, but it also allows for other types -that the language does not want users to be able to inspect. -*/ -class Opaque extends Type implements Generalizable { - // if any values are `null` that means that this isn't an instantiable type - // and should be treated like the type needs to be duped - generics: GenericArgs; - - get ammName(): string { - let generics = ''; - if (Object.keys(this.generics).length !== 0) { - const genNames = new Array(); - for (const [tyVar, ty] of Object.entries(this.generics)) { - if (ty === null) { - genNames.push(tyVar); - } else { - genNames.push(ty.ammName); - } - } - generics = '<' + genNames.join(', ') + '>'; - } - return this.name + generics; - } - - constructor(name: string, generics: string[]) { - super(name); - this.generics = {}; - generics.forEach((g) => (this.generics[g] = null)); - } - - compatibleWithConstraint(that: Type, scope: Scope): boolean { - if (this.eq(that)) { - return true; - } - if (that instanceof Opaque) { - const thisGens = Object.values(this.generics); - const thatGens = Object.values(that.generics); - if (this.name !== that.name || thisGens.length !== thatGens.length) { - return false; - } - return ( - this.name === that.name && - thisGens.length === thatGens.length && - thisGens.every((thisGen, ii) => { - const thatGen = thatGens[ii]; - if (thisGen === null || thatGen === null) { - return true; - } else { - return thisGen.compatibleWithConstraint(thatGen, scope); - } - }) - ); - } else if (that instanceof Interface || that instanceof OneOf) { - return that.compatibleWithConstraint(this, scope); - } else if (that instanceof HasField) { - return false; - } else if (that instanceof HasOperator) { - return Has.operator(that, scope, this).length !== 0; - } else if (that instanceof HasMethod) { - return Has.method(that, scope, this)[0].length !== 0; - } else { - TODO('Opaque constraint compatibility with other types'); - } - } - - constrain(that: Type, scope: Scope, opts?: ConstrainOpts) { - if (this.eq(that)) { - return; - } - if (!this.compatibleWithConstraint(that, scope)) { - throw new Error( - `Cannot constrain type ${this.ammName} to ${that.ammName}`, - ); - } - if (that instanceof Opaque) { - if ( - Object.values(this.generics).some((g) => g === null) || - Object.values(that.generics).some((g) => g === null) - ) { - // if any values are null values, that means that we just have to check - // for constraint compatibilities which was already checked so we're good - return; - } - const thisGens = Object.keys(this.generics); - const thatGens = Object.keys(that.generics); - thisGens.forEach((genName, ii) => - this.generics[genName].constrain( - that.generics[thatGens[ii]], - scope, - opts, - ), - ); - } else if (that instanceof Interface || that instanceof OneOf) { - that.constrain(this, scope, opts); - } else if (that instanceof Struct) { - throw new Error(`Cannot constrain Opaque type to Struct type`); - } else { - console.log(this); - console.log(that); - throw 'uh'; - } - } - - // FIXME: bug here - should check to see if `this.generics` contains `that`. - contains(that: Type): boolean { - return this.eq(that); - } - - dup(): Type | null { - const genKeys = Object.keys(this.generics); - if (genKeys.length === 0) { - return null; - } - const duped = new Opaque(this.name, genKeys); - let isNothingNew = true; - genKeys.forEach((genName) => { - const thisGen = this.generics[genName]; - let tyVal: Type; - if (thisGen === null) { - tyVal = Type.generate(); - } else { - const duped = thisGen.dup(); - if (duped === null) { - tyVal = thisGen; - } else { - tyVal = duped; - isNothingNew = false; - } - } - duped.generics[genName] = tyVal; - }); - // FIXME: if (isNothingNew) return this; - return duped; - } - - eq(that: Equalable): boolean { - if (!(that instanceof Opaque) || this.name !== that.name) { - return false; - } - const thisGens = Object.values(this.generics); - const thatGens = Object.values(that.generics); - return ( - thisGens.length === thatGens.length && - thisGens.every((thisGen, ii) => { - const thatGen = thatGens[ii]; - if (thisGen === null || thatGen === null) { - return thisGen === thatGen; - } else { - return thisGen.eq(thatGen); - } - }) - ); - } - - fnselectOptions(): Type[] { - const genOptions = Object.values(this.generics).map( - (g) => g?.fnselectOptions() ?? [g], - ); - const opts = new Array(); - const toSolidify = new Opaque(this.name, Object.keys(this.generics)); - for (const indices of matrixIndices(genOptions)) { - opts.push( - toSolidify.solidify( - indices.map((optIdx, tyVarIdx) => genOptions[tyVarIdx][optIdx]), - ), - ); - } - return opts; - } - - instance(opts?: InstanceOpts): Type { - const genNames = Object.keys(this.generics); - if (genNames.length === 0) { - // minor optimization: if there's no generics then we keep the same JS - // object to reduce the number of allocs - return this; - } - const instance = new Opaque(this.name, genNames); - for (const name of genNames) { - const thisGen = this.generics[name]; - if (thisGen === null) { - throw new Error( - `Cannot get an instance of a generic Opaque type that hasn't been solidified`, - ); - } - instance.generics[name] = thisGen.instance(opts); - } - return instance; - } - - // This is the only Type that overrides this method. - isFixed(): boolean { - switch (this.name) { - case 'bool': - case 'float32': - case 'float64': - case 'int8': - case 'int16': - case 'int32': - case 'int64': - case 'void': - return true; - default: - return false; - } - } - - // This is the only Type that overrides this method. - size(): number { - switch (this.name) { - case 'void': - return 0; - case 'bool': - case 'float32': - case 'float64': - case 'int8': - case 'int16': - case 'int32': - case 'int64': - return 1; - default: - const containedTypes = Object.values(this.generics); - return containedTypes - .map((t) => { - if (t === null) { - throw new Error(`cannot compute size of ${this.ammName}`); - } else { - return t.size(); - } - }) - .reduce((s1, s2) => s1 + s2, 1); - } - } - - solidify(tys: Type[]): Type { - const genNames = Object.keys(this.generics); - if (genNames.length < tys.length) { - throw new Error( - `Cannot solidify ${this.ammName} - too many type arguments were provided`, - ); - } else if (genNames.length > tys.length) { - throw new Error( - `Cannot solidify ${this.ammName} - not enough type arguments were provided`, - ); - } else if (genNames.length === 0) { - return this; - } else { - const duped = new Opaque(this.name, genNames); - genNames.forEach((name, ii) => (duped.generics[name] = tys[ii])); - return duped; - } - } - - tempConstrain(that: Type, scope: Scope, opts?: TempConstrainOpts) { - if (!this.compatibleWithConstraint(that, scope)) { - throw new Error( - `Cannot temporarily constrain type ${this.ammName} to ${that.ammName}`, - ); - } - if (that instanceof Opaque) { - const thisGens = Object.keys(this.generics); - const thatGens = Object.keys(that.generics); - thisGens.forEach((thisGenName, ii) => { - const thisGen = this.generics[thisGenName]; - const thatGen = that.generics[thatGens[ii]]; - if (thisGen === null || thatGen === null) { - throw new Error(`Can't tempConstrain non-solidified Opaque types`); - } else { - thisGen.tempConstrain(thatGen, scope, opts); - } - }); - } else if (that instanceof Interface) { - const tcTo = that.delegate ?? that.tempDelegate; - if (tcTo !== null) { - this.tempConstrain(tcTo, scope, opts); - } - } else if (that instanceof OneOf) { - // we're happy, no need to tempConstrain - } else { - console.log(this); - console.log(that); - throw 'uh'; - } - } - - resetTemp() { - for (const generic in this.generics) { - if (this.generics[generic] === null) { - return; - } - this.generics[generic].resetTemp(); - } - } -} - -/* -This type is mostly unimplemented. Right now, the only thing it provides -that's useful is `matrixSelect`. I've sent a photo in the internal Discord -channel with a page from TAPL that is very useful for this class. That can -be used for all of the constraining, and the other methods should be -fairly easy to figure out. - -TODO: implement the rest of this class, as well as a static function for -parsing an Alan Function typename. -*/ -export class FunctionType extends Type { - params: Type[]; - retTy: Type; - - get ammName(): string { - throw new Error('Method not implemented.'); - } - - constructor(ast: LPNode, params: Type[], retTy: Type) { - super('', ast); - this.params = params; - this.retTy = retTy; - } - - /* - Original design comment (was written as a FIXME above `Expr.Call.inline`): - Currently, this only works because of the way `root.lnn` is structured - - functions that accept f32s are defined first and i64s are defined last. - However, we can't rely on function declaration order to impact type checking - or type inferrence, since that could unpredictably break users' code. Instead, - if we have `OneOf` types, we should prefer the types in its list in ascending - order. I think that the solution is to create a matrix of all of the possible - types to each other, insert functions matching the types in each dimension, - and pick the function furthest from the all-0 index. For example, given - `1 + 2`, the matrix would be: - | | float32 | float64 | int8 | int16 | int32 | int64 | - | float32 |add(f32,f32)| | | | | | - | float64 | |add(f64,f64)| | | | | - | int8 | | |add(i8,i8)| | | | - | int16 | | | |add(i16,i16)| | | - | int32 | | | | |add(i32,i32)| | - | int64 | | | | | |add(i64,i64)| - in this case, it would prefer `add(int64,int64)`. Note that constraining the - type will impact this: given the code `const x: int8 = 0; const y = x + 1;`, - the matrix would be: - | | float32 | float64 | int8 | int16 | int32 | int64 | - | int8 | | | add(i8,i8) | | | | - where the columns represent the type of the constant `1`. There's only 1 - possibility, but we'd still have to check `int8,int64`, `int8,int32`, and - `int8,int16` until it finds `int8,int8`. - */ - static matrixSelect( - fns: Fn[], - args: Type[], - expectResTy: Type, - scope: Scope, - ): [Fn[], Type[][], Type[]] { - // super useful when debugging matrix selection - const isDbg = false; - isDbg && console.log('STARTING', fns); - const original = [...fns]; - // remove any fns that shouldn't apply - const callTy = new FunctionType(new NulLP(), args, expectResTy); - isDbg && stdout.write('callTy: ') && console.dir(callTy, { depth: 6 }); - fns = fns.filter((fn) => fn.ty.compatibleWithConstraint(callTy, scope)); - isDbg && console.log('filtered:', fns); - // if it's 0-arity then all we have to do is grab the retTy of the fn - if (args.length === 0) { - isDbg && console.log('nothin'); - return fns.reduce( - ([fns, _pTys, retTys], fn) => { - const alreadyFn = fns.findIndex((alreadyFn) => alreadyFn === fn); - if (alreadyFn === -1) { - return [ - [...fns, fn], - _pTys, - [ - ...retTys, - fn.retTy.instance({ interfaceOk: true, forSameDupIface: [] }), - ], - ]; - } else { - return [fns, _pTys, retTys]; - } - }, - [new Array(), new Array(), new Array()], - ); - } - // and now to generate the matrix - // every argument is a dimension within the matrix, but we're - // representing each dimension _d_ as an index in the matrix - const matrix: Array = args.map((arg) => { - return arg.fnselectOptions(); - }); - isDbg && stdout.write('matrix: '); - isDbg && console.dir(matrix, { depth: 4 }); - // TODO: this weight system feels like it can be inaccurate - // the weight of a particular function is computed by the sum - // of the indices in each dimension, with the highest sum - // having the greatest preference - const fnsByWeight = new Map(); - // keep it as for instead of while for debugging reasons - for (const indices of matrixIndices(matrix)) { - const weight = indices.reduce((w, c) => w + c); - isDbg && console.log('weight', weight); - const argTys = matrix.map((options, ii) => options[indices[ii]]); - isDbg && console.log('argtys', argTys); - const fnsForWeight = fnsByWeight.get(weight) || []; - isDbg && console.log('for weight', fnsForWeight); - fnsForWeight.push( - ...fns.reduce((fns, fn) => { - isDbg && console.log('getting result ty'); - const tys = fn.resultTyFor(argTys, expectResTy, scope, { - isTest: true, - }); - isDbg && stdout.write('signature is: '); - isDbg && console.dir(tys, { depth: 4 }); - if (tys === null) { - return fns; - } else { - return [...fns, [fn, ...tys] as [Fn, Type[], Type]]; - } - }, new Array<[Fn, Type[], Type]>()), - ); - isDbg && console.log('for weight', weight, 'now', fnsForWeight); - fnsByWeight.set(weight, fnsForWeight); - } - const weights = Array.from(fnsByWeight.keys()).sort((a, b) => a - b); - isDbg && console.log('assigned weights:', weights); - // weights is ordered lowest->highest - const ret: [Fn[], Type[][], Type[]] = weights.reduce( - ([fns, pTys, retTys], weight) => { - fnsByWeight - .get(weight) - .forEach(([weightedFn, weightedPTys, weightedRetTy]) => { - const alreadyIdx = fns.findIndex((fn) => fn === weightedFn); - if (alreadyIdx === -1) { - fns.push(weightedFn); - } else { - // push to the end of the fns since technically the weight for the function - // is indeed higher than what it was before - fns.push(fns.splice(alreadyIdx, 1)[0]); - } - pTys = weightedPTys.map((pTy, ii) => [...(pTys[ii] || []), pTy]); - retTys.push(weightedRetTy); - }); - return [fns, pTys, retTys]; - }, - [new Array(), new Array(), new Array()] as [ - Fn[], - Type[][], - Type[], - ], - ); - if (ret[0].length > original.length || ret[0].length === 0) { - // these make debugging easier :) - if (isDbg) { - console.log('~~~ ERROR'); - console.log('original: ', original); - console.log('ret: ', ret); - console.log('retLength:', ret[0].length); - console.log('args: ', args); - stdout.write('matrix: '); - console.dir(matrix, { depth: 4 }); - console.log('byweight: ', fnsByWeight); - } - if (ret[0].length === 0) { - throw new Error('no more functions left'); - } else { - throw new Error('somehow got more options when fn selecting'); - } - } - if (isDbg) { - // const getStack = { stack: '' }; - // Error.captureStackTrace(getStack); - // console.log('returning from', getStack.stack); - console.dir(ret, { depth: 4 }); - } - return ret; - } - - compatibleWithConstraint(that: Type, scope: Scope): boolean { - if (this.eq(that)) { - return true; - } - // console.log('fn.compat', this, ty); - if (that instanceof FunctionType) { - return ( - this.params.length === that.params.length && - this.params.every((param, ii) => { - // console.log('comparing my param', param, 'to', ty.params[ii]); - return param.compatibleWithConstraint(that.params[ii], scope); - }) && - this.retTy.compatibleWithConstraint(that.retTy, scope) - ); - } else if (that instanceof OneOf || that instanceof Generated) { - return that.compatibleWithConstraint(this, scope); - } else { - return false; - } - } - - constrain(to: Type, scope: Scope, opts?: ConstrainOpts): void { - console.log(to); - TODO('figure out what it means to constrain a function type'); - } - - contains(that: Type): boolean { - return this.eq(that); - } - - eq(that: Equalable): boolean { - if (that instanceof FunctionType) { - return ( - this.params.length === that.params.length && - this.params.every((param, ii) => param.eq(that.params[ii])) && - this.retTy.eq(that.retTy) - ); - } else if (that instanceof Generated || that instanceof OneOf) { - return that.eq(this); - } else { - return false; - } - } - - instance(opts?: InstanceOpts): Type { - return new FunctionType( - this.ast, - this.params.map((param) => param.instance(opts)), - this.retTy.instance(opts), - ); - } - - tempConstrain(to: Type, scope: Scope, opts?: TempConstrainOpts): void { - console.log(to); - TODO('temp constraints on a function type?'); - } - - resetTemp(): void { - TODO('temp constraints on a function type?'); - } - - size(): number { - throw new Error('Size should not be requested for function types...'); - } -} - -/* -Types with fields. - -TODO: still needs generics support. This can be done similarly to how -generics are done in Opaque, except all uses of the type variable should -be used in the fields as necessary. This might require custom duping -logic. -*/ -class Struct extends Type { - args: GenericArgs; - fields: Fields; - order: FieldIndices; - - get ammName(): string { - return this.name; - } - - constructor( - name: string, - ast: LPNode | null, - args: GenericArgs, - fields: Fields, - ) { - super(name, ast); - this.args = args; - this.fields = fields; - this.order = {}; - let sizeTracker = 0; - for (const fieldName in this.fields) { - this.order[fieldName] = sizeTracker; - sizeTracker += this.fields[fieldName].size(); - } - } - - static fromAst(ast: LPNode, scope: Scope): Type { - let work = ast; - const names = parseFulltypename(work.get('fulltypename')); - if (names[1].some((ty) => ty[1].length !== 0)) { - throw new Error( - `Generic type variables can't have generic type arguments`, - ); - } - const typeName = names[0]; - const genericArgs: GenericArgs = {}; - names[1].forEach((n) => (genericArgs[n[0]] = null)); - - work = ast.get('typedef'); - if (work.has('typebody')) { - work = work.get('typebody').get('typelist'); - const lines = [ - work.get('typeline'), - ...work - .get('cdr') - .getAll() - .map((n) => n.get('typeline')), - ]; - const fields: Fields = {}; - lines.forEach((line) => { - const fieldName = line.get('variable').t; - const fieldTy = Type.getFromTypename(line.get('fulltypename'), scope); - if (fieldTy instanceof Interface) { - throw new Error(`type fields can't be interfaces (I think)`); - } - fields[fieldName] = fieldTy; - }); - return new Struct(typeName, ast, genericArgs, fields); - } else { - ast = ast.get('typealias'); - TODO('type aliases'); - } - } - - compatibleWithConstraint(that: Type, scope: Scope): boolean { - if (this.eq(that)) { - return true; - } - if (that instanceof Struct) { - return this.eq(that); - } else if (that instanceof HasField) { - return ( - this.fields.hasOwnProperty(that.name) && - this.fields[that.name].compatibleWithConstraint(that.ty, scope) - ); - } else if (that instanceof HasMethod) { - TODO( - 'get methods and operators for types? (probably during fn selection fix?)', - ); - } else if (that instanceof Interface || that instanceof OneOf) { - return that.compatibleWithConstraint(this, scope); - } else { - return false; - } - } - - constrain(that: Type, scope: Scope, opts?: ConstrainOpts) { - // for every type T, when constraining with some `OneOf` U, T just needs - // to ensure that TβŠ†U - if (that instanceof OneOf) { - that.constrain(this, scope, opts); - return; - } - if (this.eq(that)) { - return; - } - if (!this.compatibleWithConstraint(that, scope)) { - throw new Error( - `incompatible types: ${this.name} is not compatible with ${that.name}`, - ); - } - if (that instanceof HasField) { - that.ty.constrain(this.fields[that.name], scope, opts); - } - } - - contains(that: Type): boolean { - return this.eq(that); - } - - eq(that: Equalable): boolean { - // TODO: more generic && more complex structs - return that instanceof Struct && this === that; - } - - fieldIndices(): FieldIndices { - return this.order; - } - - instance(): Type { - return this; // TODO: this right? - } - - tempConstrain(to: Type, scope: Scope, opts?: TempConstrainOpts) { - // TODO: can structs have temp constraints? - // TODO: should be separate implementation when generics are implemented, pass opts - this.constrain(to, scope); - } - - resetTemp() { - // TODO: can structs have temp constraints? - } - - size(): number { - // by lazily calculating, should be able to avoid having `OneOf` select - // issues in ducked types - return Object.values(this.fields) - .map((ty) => ty.size()) - .reduce((l, r) => l + r); - } -} - -/** - * The `Has` classes are *only* for constraining types. I've thought a lot about - * this - there should probably be more uses of these constraints. For example, - * whenever a function is called, there should be a `FnType` that's generic - * enough to apply to interfaces that gets inserted into a `HasMethod`, and then - * pass that constraint. This would vastly improve type checking beyond just - * "matrixSelect ran out of functions". - */ -abstract class Has extends Type { - get ammName(): string { - throw new Error( - 'None of the `Has` constraints should have their ammName requested...', - ); - } - - constructor(name: string, ast: LPNode | null) { - super(name, ast); - } - - static field(field: HasField, ty: Type): boolean { - // TODO: structs - return false; - } - - static method( - method: HasMethod, - scope: Scope, - ty: Type, - ): [Fn[], Type[][], Type[]] { - const fns = scope.get(method.name); - if (!isFnArray(fns)) { - return [[], [], []]; - } - return FunctionType.matrixSelect( - fns, - method.params.map((p) => (p === null ? ty : p)), - Type.generate(), // accept any type, it doesn't matter - scope, - ); - } - - static operator(operator: HasOperator, scope: Scope, ty: Type): Operator[] { - let ops: Operator[] = scope.get(operator.name); - // if there is no op by that name, RIP - if (!isOpArray(ops)) { - return []; - } - // filter out ops that aren't the same fixity - ops = ops.filter((op) => op.isPrefix === operator.isPrefix); - if (operator.isPrefix) { - return ops.filter( - (op) => - op.select(scope, Type.generate(), operator.params[0] || ty).length > - 0, - ); - } else { - return ops.filter( - (op) => - op.select( - scope, - Type.generate(), - operator.params[0] || ty, - operator.params[1] || ty, - ).length > 0, - ); - } - } - - // convenience for `Type.hasX(...).compatibleWithConstraint(ty)` - compatibleWithConstraint(that: Type, scope: Scope): boolean { - return this.eq(that) || that.compatibleWithConstraint(this, scope); - } - - // convenience for `Type.hasX(...).constrain(ty)` - constrain(that: Type, scope: Scope, opts?: ConstrainOpts) { - if (this.eq(that)) { - return; - } - that.constrain(this, scope); - } - - contains(that: Type): boolean { - return false; - } - - eq(that: Equalable): boolean { - return that instanceof Has && that.name === this.name; - } - - // it returns `any` to make the type system happy - private nope(msg: string): any { - throw new Error( - `Has constraints ${msg} (this error should never be thrown)`, - ); - } - - instance(): Type { - return this.nope('cannot represent a compilable type'); - } - - // there should never be a case where `Type.hasX(...).tempConstrain(...)` - tempConstrain(_t: Type) { - this.nope('cannot be temporarily constrained'); - } - - // there can never be temp constraints - resetTemp() { - this.nope('cannot have temporary constraints'); - } - - size(): number { - return this.nope('do not have a size'); - } - - fnselectOptions(): Type[] { - return this.nope( - 'should not be used as a Type when computing function selection', - ); - } -} - -class HasField extends Has { - ty: Type; - - constructor(name: string, ast: LPNode | null, ty: Type) { - super(name, ast); - this.ty = ty; - } - - static fromPropertyTypeLine(ast: LPNode, scope: Scope): HasField { - const name = ast.get('variable').t.trim(); - const ty = Type.getFromTypename(ast.get('fulltypename'), scope); - return new HasField(name, ast, ty); - } - - eq(that: Equalable): boolean { - return super.eq(that) && that instanceof HasField && that.ty.eq(this.ty); - } -} - -class HasMethod extends Has { - // null if it refers to the implementor's type. Only used when - // working on interfaces - params: (Type | null)[]; - ret: Type | null; - - constructor( - name: string, - ast: LPNode | null, - params: (Type | null)[], - ret: Type | null, - ) { - super(name, ast); - this.params = params; - this.ret = ret; - } - - static fromFunctionTypeLine( - ast: LPNode, - scope: Scope, - ifaceName: string, - ): HasMethod { - const name = ast.get('variable').t.trim(); - const work = ast.get('functiontype'); - const params: (Type | null)[] = [ - work.get('fulltypename'), - ...work - .get('cdr') - .getAll() - .map((cdr) => cdr.get('fulltypename')), - ].map((tyNameAst) => - tyNameAst.t.trim() === ifaceName - ? null - : Type.getFromTypename(tyNameAst, scope), - ); - const ret = - work.get('returntype').t.trim() === ifaceName - ? null - : Type.getFromTypename(work.get('returntype'), scope); - return new HasMethod(name, ast, params, ret); - } - - eq(that: Equalable): boolean { - return ( - super.eq(that) && - that instanceof HasMethod && - this.params.reduce( - (eq, param, ii) => - eq && - (param === null - ? that.params[ii] === null - : param.eq(that.params[ii])), - true, - ) && - this.ret.eq(that.ret) - ); - } -} - -class HasOperator extends HasMethod { - isPrefix: boolean; - - constructor( - name: string, - ast: LPNode | null, - params: (Type | null)[], - ret: Type | null, - isPrefix: boolean, - ) { - super(name, ast, params, ret); - this.isPrefix = isPrefix; - } - - static fromOperatorTypeLine( - ast: LPNode, - scope: Scope, - ifaceName: string, - ): HasOperator { - let isPrefix = true; - const params: (Type | null)[] = []; - if (ast.get('optleftarg').has()) { - const leftTypename = ast.get('optleftarg').get('leftarg'); - const leftTy = - leftTypename.t.trim() === ifaceName - ? null - : Type.getFromTypename(leftTypename, scope); - params.push(leftTy); - isPrefix = false; - } - const op = ast.get('operators').t.trim(); - const rightTypename = ast.get('rightarg'); - const rightTy = - rightTypename.t.trim() === ifaceName - ? null - : Type.getFromTypename(rightTypename, scope); - params.push(rightTy); - const retTypename = ast.get('fulltypename'); - const retTy = - retTypename.t.trim() === ifaceName - ? null - : Type.getFromTypename(retTypename, scope); - return new HasOperator(op, ast, params, retTy, isPrefix); - } - - eq(that: Equalable): boolean { - return ( - super.eq(that) && - that instanceof HasOperator && - this.isPrefix === that.isPrefix - ); - } -} - -/* -I realized a bit too late that `Interface`s in Alan are just ways of -declaring subtypes. I'm not too familiar with the theory on subtypes -since I didn't have the realization until it was too late, but there -is some literature available that can improve this class. - -This class is a bit too much and should probably be broken up into a -few classes. For example, there can be a `class TyVar extends Type` -that is used for function signatures (and possibly Generics in other -Types) that can remove the necessity for some of the Opts types used -within. Likewise, it'd probably simplify some of the logic within. -*/ -class Interface extends Type { - // TODO: it's more optimal to have fields, methods, and operators in - // maps so we can cut down searching and such. - fields: HasField[]; - methods: HasMethod[]; - operators: HasOperator[]; - delegate: Type | null; - tempDelegate: Type | null; - private __isDuped: DupOpts; - - get ammName(): string { - if (this.delegate !== null) { - return this.delegate.ammName; - } else if (this.tempDelegate !== null) { - return this.tempDelegate.ammName; - } else { - throw new Error(`Could not determine ammName for ${this.name}`); - } - } - - get isDuped(): boolean { - return this.__isDuped !== null; - } - - constructor( - name: string, - ast: LPNode | null, - fields: HasField[], - methods: HasMethod[], - operators: HasOperator[], - ) { - super(name, ast); - this.fields = fields; - this.methods = methods; - this.operators = operators; - this.delegate = null; - this.tempDelegate = null; - this.__isDuped = null; - } - - static fromAst(ast: LPNode, scope: Scope): Interface { - const name = ast.get('variable').t.trim(); - let work = ast.get('interfacedef'); - if (work.has('interfacebody')) { - work = work.get('interfacebody').get('interfacelist'); - const lines = [ - work.get('interfaceline'), - ...work - .get('cdr') - .getAll() - .map((cdr) => cdr.get('interfaceline')), - ]; - const fields: HasField[] = []; - const methods: HasMethod[] = []; - const operators: HasOperator[] = []; - lines.forEach((line) => { - if (line.has('propertytypeline')) { - fields.push( - HasField.fromPropertyTypeLine(line.get('propertytypeline'), scope), - ); - } else if (line.has('functiontypeline')) { - methods.push( - HasMethod.fromFunctionTypeLine( - line.get('functiontypeline'), - scope, - name, - ), - ); - } else if (line.has('operatortypeline')) { - operators.push( - HasOperator.fromOperatorTypeLine( - line.get('operatortypeline'), - scope, - name, - ), - ); - } else { - throw new Error(`invalid ast: ${work}`); - } - }); - return new Interface(name, ast, fields, methods, operators); - } else if (work.has('interfacealias')) { - TODO('interface aliases'); - } else { - throw new Error(`invalid ast: ${work}`); - } - } - - compatibleWithConstraint(that: Type, scope: Scope): boolean { - if (this.eq(that)) { - return true; - } - if (that instanceof Has) { - if (this.isDuped) { - const checkFor = this.delegate ?? this.tempDelegate; - if (checkFor !== null) { - return checkFor.compatibleWithConstraint(that, scope); - } else { - // assume true for now, let later constraints give - return true; - } - } else { - if (that instanceof HasField) { - return Has.field(that, this); - } else if (that instanceof HasOperator) { - return Has.operator(that, scope, this).length !== 0; - } else if (that instanceof HasMethod) { - return Has.method(that, scope, this)[0].length !== 0; - } else { - throw new Error(`unrecognized Has`); - } - } - } else if (that instanceof Generated) { - return that.compatibleWithConstraint(this, scope); - } - // always check all interface constraints first - if ( - !( - this.fields.every((f) => Has.field(f, that)) && - this.methods.every((f) => Has.method(f, scope, that)[0].length !== 0) && - this.operators.every((f) => Has.operator(f, scope, that).length !== 0) - ) - ) { - return false; - } - - if (this.delegate !== null) { - return this.delegate.compatibleWithConstraint(that, scope); - } else if (this.tempDelegate !== null) { - return this.tempDelegate.compatibleWithConstraint(that, scope); - } else { - return true; - } - } - - constrain(that: Type, scope: Scope, opts?: ConstrainOpts) { - const isDbg = false; - if (this.eq(that) || that.contains(this)) { - isDbg && console.log('quitting early'); - return; - } - // if it's a `Has`, it's easy enough to process. Generated types should - // handle the `Has` first before calling this method - if (that instanceof Has) { - const toCheck = this.delegate ?? this; - const errorBase = `${toCheck.ammName} doesn't have`; - if (that instanceof HasField && !Has.field(that, toCheck)) { - throw new Error(`${errorBase} field ${that.name}`); - } else if ( - that instanceof HasOperator && - Has.operator(that, scope, toCheck).length !== 0 - ) { - const opString = - that.params.length === 1 - ? `${that.name} ${that.params[0].ammName}` - : `${that.params[0].ammName} ${that.name} ${that.params[1].ammName}`; - throw new Error(`${errorBase} operator \`${opString}\``); - } else if ( - that instanceof HasMethod && - Has.method(that, scope, toCheck)[0].length !== 0 - ) { - const paramsString = `(${that.params - .map((p) => p.ammName) - .join(', ')})`; - throw new Error(`${errorBase} method \`${that.name}${paramsString}\``); - } - // none of the other checks apply - return; - } - - const baseErrorString = `type ${that.name} was constrained to interface ${this.name} but doesn't have`; - this.fields.forEach((f) => { - if (!that.compatibleWithConstraint(f, scope)) { - throw new Error(`${baseErrorString} field ${f.name} with type ${f.ty}`); - } - }); - this.methods.forEach((m) => { - if (!Has.method(m, scope, that)) { - throw new Error( - `${baseErrorString} method ${m.name}(${m.params - .map((p) => (p === null ? that : p)) - .map((t) => t.name) - .join(', ')})`, - ); - } - }); - this.operators.forEach((o) => { - if (Has.operator(o, scope, that)) return; - if (o.isPrefix) { - throw new Error( - `${baseErrorString} prefix operator \`${o.name} ${that.name}\``, - ); - } else { - throw new Error( - `${baseErrorString} infix operator \`${o.params[0] || that.name} ${ - o.name - } ${o.params[1] || that.name}\``, - ); - } - }); - - if ( - this.__isDuped.isTyVar && - !(that instanceof Interface || that instanceof OneOf) - ) { - return; - } - if (this.delegate !== null) { - isDbg && console.log('delegating to', this.delegate); - this.delegate.constrain(that, scope, opts); - } else if (this.isDuped) { - if (that.contains(this)) { - isDbg && console.log('constraining that to this'); - that.constrain(this, scope, opts); - } else { - isDbg && console.log('setting delegate'); - this.delegate = that; - } - if (this.tempDelegate !== null) { - this.delegate.constrain(this.tempDelegate, scope, opts); - this.tempDelegate = null; - } - } - // if not duped, then don't set delegate - the interface was just being - // used to ensure that the type of the `that` matches this interface - } - - contains(that: Type): boolean { - if (this.eq(that)) { - return true; - } else if (this.delegate !== null) { - return this.delegate.contains(that); - } else if (this.tempDelegate !== null) { - return this.tempDelegate.contains(that); - } else { - // i don't think tempDelegate needs to be checked since theoretically - // no other constraints happen - return false; - } - } - - eq(that: Equalable): boolean { - if (that instanceof Generated) { - return that.eq(this); - } else if (that instanceof Interface) { - // FIXME: this is technically wrong, but there's no other way - // to get the current generic params working without depending - // on `eq` returning this. Ideally, we would be checking to - // make sure all of the constraints match - return this === that; - } else if (this.delegate !== null) { - return this.delegate.eq(that); - } else if (this.tempDelegate !== null) { - return this.tempDelegate.eq(that); - } else { - return false; - } - } - - instance(opts?: InstanceOpts): Type { - if (this.delegate !== null) { - return this.delegate.instance(opts); - } else if (this.tempDelegate !== null) { - return this.tempDelegate.instance(opts); - } else if ( - opts && - opts.interfaceOk && - this.__isDuped && - this.__isDuped.isTyVar && - opts.forSameDupIface - ) { - const already = opts.forSameDupIface.find( - ([iface, _duped]) => iface === this, - ); - if (already) { - return already[1]; - } else { - const duped = this.dup(); - opts.forSameDupIface.push([this, duped]); - return duped; - } - } else if (opts && opts.interfaceOk && this.__isDuped) { - return this; - } else { - // console.log(this); - throw new Error(`Could not resolve type ${this.name}`); - } - } - - tempConstrain(that: Type, scope: Scope, opts?: TempConstrainOpts) { - if (this === that) { - throw new Error('huh?'); - } else if (this.delegate !== null) { - this.delegate.tempConstrain(that, scope, opts); - } else if (this.tempDelegate !== null) { - // ensure that `this.tempDelegate` is equal to `that` - if (!this.tempDelegate.eq(that)) { - if (opts?.isTest) { - if (!this.tempDelegate.compatibleWithConstraint(that, scope)) { - throw new Error( - `${this.tempDelegate.ammName} is not compatible with ${that.ammName}`, - ); - } - this.tempDelegate.tempConstrain(that, scope, opts); - } else { - // TODO: if more ConstrainOpts are added, make TempConstrainOpts - // be `& ConstrainOpts` and pass the opts through - that.tempConstrain(this.tempDelegate, scope, opts); - } - } - } else if (!that.contains(this)) { - this.tempDelegate = that; - } - } - - resetTemp() { - if (this.delegate !== null) { - this.delegate.resetTemp(); - if (this.tempDelegate !== null) { - throw new Error(`somehow, tempDelegate and delegate are both set`); - } - } else if (this.tempDelegate !== null) { - // this.tempDelegate.resetTemp(); - this.tempDelegate = null; - } - } - - dup(dupOpts: DupOpts = {}): Type | null { - if (this.isDuped && (!this.__isDuped.isTyVar || dupOpts.isTyVar)) { - return null; - } - const dup = new Interface( - // name isn't really used for anything in Interfaces - // (not used for ammName, not used for equality check, etc) - genName(this.name), - this.ast, - [...this.fields], - [...this.methods], - [...this.operators], - ); - dup.__isDuped = dupOpts; - return dup; - } - - size(): number { - if (this.delegate !== null) { - return this.delegate.size(); - } else if (this.tempDelegate !== null) { - return this.tempDelegate.size(); - } else { - throw new Error(`Non-concrete interface types can't have a size`); - } - } - - fnselectOptions(): Type[] { - const del = this.delegate ?? this.tempDelegate; - if (del !== null) { - return del.fnselectOptions(); - } else { - return [this]; - } - } -} - -// technically, generated types are a kind of interface - we just get to build up -// the interface through type constraints instead of through explicit requirements. -// this'll make untyped fn parameters easier once they're implemented. -// If Interface is broken up, this class might be better free-standing. It's just -// got too much in common right now to do otherwise. -class Generated extends Interface { - // don't override `get ammName` since its Error output is unique but generic - // over both Generated and Interface types - get isDuped(): boolean { - return true; - } - - constructor() { - // Generated types are just Interface types that are more - // lenient when handling `Has` constraints - super(genName('Generated'), new NulLP(), [], [], []); - } - - compatibleWithConstraint(that: Type, scope: Scope): boolean { - if (this.eq(that)) { - return true; - } - if (that instanceof Has) { - // if it's a field, make sure there's no other field by the - // HasField's name, otherwise there's no conflict since - // functions and operators can have the same symbol but be - // selected by params and return type - if (that instanceof HasField) { - return !this.fields.some((field) => { - if (field.name !== that.name) return false; - if (field.eq(that)) return false; - field.name === that.name && !field.eq(that); - }); - } else { - return true; - } - } - if (this.delegate !== null) { - return this.delegate.compatibleWithConstraint(that, scope); - } else if (this.tempDelegate !== null) { - return this.tempDelegate.compatibleWithConstraint(that, scope); - } else { - return true; - } - } - - // FIXME: I'm not entirely sure this method is correct. I haven't seen - // any problems in all the time I've spend testing, but it's not entirely - // clear if that's by coincidence or if the rest of the logic in this - // compiler is dependent on the behavior here. - // It's also quite large, which makes me feel like it's doing too much, - // it doesn't need to be tackled right away but it's a possibility to - // consider if problems arise. - constrain(that: Type, scope: Scope, opts?: ConstrainOpts) { - if (this.eq(that)) { - return; - } - // if `tempDelegate` is set, something is *very* wrong because - // all permanent constraints should already be processed... - // if we need to allow `tempConstrain`s to get processed `constrain`s, - // then this check should be at the end of this method and pass the - // removed `tempDelegate` to the new permanent delegate's `tempConstrain` - if (this.tempDelegate !== null) { - throw new Error( - `cannot process temporary type constraints before permanent type constraints`, - ); - } else if (that instanceof Interface) { - if (this.delegate ?? that.delegate === null) { - const oFields = [...this.fields]; - const oMethods = [...this.methods]; - const oOperators = [...this.operators]; - this.fields.push(...that.fields); - this.methods.push(...that.methods); - this.operators.push(...that.operators); - if (that.isDuped) { - that.fields.push(...oFields); - that.methods.push(...oMethods); - that.operators.push(...oOperators); - } - this.delegate = that; - } else if (this.tempDelegate ?? that.tempDelegate !== null) { - console.log('-------------'); - console.dir(this, { depth: 4 }); - console.dir(that, { depth: 4 }); - TODO('figure out what to do here'); - } else if (this.delegate !== null) { - if (that.delegate === null) { - that.delegate = this; - } else if (that.isDuped) { - this.delegate.constrain(that.delegate, scope, opts); - } else { - that.fields.forEach((f) => this.delegate.constrain(f, scope, opts)); - that.methods.forEach((m) => this.delegate.constrain(m, scope, opts)); - that.operators.forEach((o) => - this.delegate.constrain(o, scope, opts), - ); - } - } else if (that.delegate !== null) { - this.delegate = that.delegate; - this.fields.forEach((f) => this.delegate.constrain(f, scope, opts)); - this.methods.forEach((m) => this.delegate.constrain(m, scope, opts)); - this.operators.forEach((o) => this.delegate.constrain(o, scope, opts)); - } else { - console.log('-------------'); - console.dir(this, { depth: 4 }); - console.dir(that, { depth: 4 }); - TODO('i thought i covered all the branches here?'); - } - } else if (that instanceof Has) { - if (this.delegate !== null) { - this.delegate.constrain(that, scope, opts); - } else if (that instanceof HasField) { - const already = - this.fields.find((field) => field.name === that.name) ?? null; - if (already !== null) { - if ( - !already.eq(that) && - !already.ty.compatibleWithConstraint(that.ty, scope) - ) { - throw new Error( - `generated type ${this.name} already has a field called`, - ); - } - } else { - this.fields.push(that); - } - } else if (that instanceof HasOperator) { - if (!this.operators.some((o) => o.eq(that))) { - this.operators.push(that); - } - } else if (that instanceof HasMethod) { - if (!this.methods.some((m) => m.eq(that))) { - this.methods.push(that); - } - } - } else if (this.delegate !== null) { - this.delegate.constrain(that, scope, opts); - } else { - this.delegate = that; - this.fields.forEach((f) => that.constrain(f, scope, opts)); - this.methods.forEach((m) => that.constrain(m, scope, opts)); - this.operators.forEach((o) => that.constrain(o, scope, opts)); - } - } - - eq(that: Equalable): boolean { - if (that instanceof Generated) { - return ( - this.delegate !== null && - that.delegate !== null && - this.delegate.eq(that.delegate) - ); - } else if (that instanceof Interface) { - const mine = this.delegate ?? this.tempDelegate; - const other = that.delegate ?? that.tempDelegate; - if (mine === null || other === null) { - return mine === other; - } else { - return mine.eq(other); - } - } else { - return this.delegate !== null && this.delegate.eq(that); - } - } - - tempConstrain(to: Type, scope: Scope, opts?: TempConstrainOpts) { - if (this.delegate !== null) { - this.delegate.tempConstrain(to, scope, opts); - } else if (this.tempDelegate !== null) { - TODO('temp constraints to a temporary constraint???'); - } else if (to instanceof Has) { - TODO("i'm not sure"); - } else { - this.tempDelegate = to; - } - } -} - -/* -Represents a Set of types that could work given the initial context. -Further constraining should reduce the number of possibilities, always -resulting in an intersection to whatever it's being constrained to. - -It's a mathematical set - operations should be based on such. -*/ -class OneOf extends Type { - selection: Type[]; - tempSelect: Type[] | null; - - get ammName(): string { - return this.select().ammName; - } - - constructor(selection: Type[], tempSelect: Type[] = null) { - super(genName('OneOf')); - // ensure there's no duplicates. This fixes an issue with duplicates - // in matrix selection. Since no other values are added to the list, - // there's no need to do this any time later. Ensure that the - // precedence is order is maintained though - if there is a duplicate, - // keep the one that's later in the list. - selection = selection.reduceRight( - (sel, fn) => (sel.some((selFn) => selFn.eq(fn)) ? sel : [fn, ...sel]), - new Array(), - ); - this.selection = selection; - this.tempSelect = tempSelect; - } - - private select(): Type { - let selected: Type; - if (this.tempSelect !== null) { - if (this.tempSelect.length === 0) { - throw new Error(`expected to select from tempSelect, but it's empty`); - } - selected = this.tempSelect[this.tempSelect.length - 1]; - } else if (this.selection.length > 0) { - selected = this.selection[this.selection.length - 1]; - } else { - throw new Error(`type selection impossible - no possible types left`); - } - return selected; - } - - compatibleWithConstraint(that: Type, scope: Scope): boolean { - if (this.eq(that)) { - return true; - } - return this.selection.some((ty) => - ty.compatibleWithConstraint(that, scope), - ); - } - - constrain(that: Type, scope: Scope, opts?: ConstrainOpts) { - // const isDbg = this.name === 'OneOf-n4310' || this.name === 'OneOf-n4311' || this.name === 'OneOf-n4303'; - const isDbg = false; - if (isDbg) { - stdout.write('~~> constraining ' + this.name + ' to: '); - console.dir(that, { depth: 6 }); - stdout.write('- this selection is '); - console.dir(this.selection, { depth: 4 }); - } - if (this.eq(that)) { - isDbg && console.log('we are the same'); - return; - } else if (opts && opts.stopAt === this) { - isDbg && console.log('stopping'); - return; - } - const thatOpts = that.fnselectOptions(); - // ok so it's easy enough to implement a Set intersection (which is what - // this method does). However, since there *is* an order of preference, - // we have to somehow unify that, which is harder to determine. We - // could do an averaging system, but that takes a *lot* of work that - // this module is not ready to implement. Right now, this algorithm - // just assumes that the first `OneOf` (`this` in this context) will - // assume the ordering of the RHS (if necessary) - in this context, - // it's `that`. - this.selection = thatOpts.reduce((sel, thatOpt) => { - const myApplies = this.selection.filter((ty) => - ty.compatibleWithConstraint(thatOpt, scope), - ); - if (isDbg) { - console.log('applicable to', thatOpt, '->', myApplies); - } - // the filter is to prevent duping elements. we must maintain - // preference order, so remove any elements from `sel` that apply - // at the current "preference level" (ie, the index of `thatOpts` - // that is `thatOpt`) - return [...sel.filter((ty) => !myApplies.includes(ty)), ...myApplies]; - }, new Array()); - this.selection.forEach((sel) => { - const optsThatApply = thatOpts.filter((opt) => - opt.compatibleWithConstraint(sel, scope), - ); - const flattened = Type.flatten(optsThatApply); - if (flattened !== null) { - if (sel.name === 'any-n18-n4296') { - console.log('FOUND IT', this.name); - } - // console.log('flattened is', flattened.name, 'for', this.name); - isDbg && stdout.write('squishy:'); - isDbg && console.dir(flattened, { depth: 4 }); - sel.constrain(flattened, scope, opts); - } else if (isDbg) console.log('not squishy:', optsThatApply); - }); - if (!opts || opts.stopAt === undefined) { - isDbg && console.log('stopping here'); - opts = { stopAt: this }; - } - that.constrain(this, scope, opts); - } - - contains(that: Type): boolean { - if (this === that) { - return true; - } - return this.selection.some((s) => s.contains(that)); - } - - eq(that: Equalable): boolean { - return ( - (that instanceof OneOf && - this.selection.length === that.selection.length && - this.selection.every((ty, ii) => ty.eq(that.selection[ii]))) || - this.select().eq(that) - ); - } - - instance(opts?: InstanceOpts): Type { - const selected = this.select(); - if (selected === undefined) { - throw new Error('uh whaaaaat'); - } - const res = selected.instance(opts); - return res; - } - - tempConstrain(to: Type, scope: Scope, opts?: TempConstrainOpts) { - const toOpts = to.fnselectOptions(); - this.tempSelect = toOpts.reduce((tempSel, toOpt) => { - const myApplies = this.selection.filter((ty) => - ty.compatibleWithConstraint(toOpt, scope), - ); - return [...tempSel.filter((ty) => myApplies.includes(ty)), ...myApplies]; - }, new Array()); - } - - resetTemp() { - this.selection.forEach((ty) => ty.resetTemp()); - this.tempSelect = null; - } - - size(): number { - return this.select().size(); - } - - fnselectOptions(): Type[] { - const selFrom = this.tempSelect === null ? this.selection : this.tempSelect; - // this still maintains preference order: say that this OneOf is somehow: - // [string, OneOf(int64, float64), bool] - // after the reduce, the result should be: - // [string, int64, float64, bool] - // which makes sense - highest preference no matter what should be `bool`, - // but the 2nd-level preference locally is either `int64` or `float64`. - // However, the nested `OneOf` prefers `float64`, so it wins the tie-breaker. - // - // An optimization step for `FunctionType.matrixSelect` *could* be to ensure - // that this list does not contain types that `.eq` each other (eliminating - // the element that is earlier in the list) but we'd need to perform - // profiling to see if that doesn't drastically reduce performance in the - // common case - return selFrom.reduce( - (options, ty) => [...options, ...ty.fnselectOptions()], - new Array(), - ); - } -} diff --git a/compiler/src/lnntoamm/index.ts b/compiler/src/lnntoamm/index.ts deleted file mode 100644 index 8800eb3b4..000000000 --- a/compiler/src/lnntoamm/index.ts +++ /dev/null @@ -1,63 +0,0 @@ -import * as fs from 'fs'; - -import * as Ast from './Ast'; -import * as Std from './Std'; -import { LPNode } from '../lp'; -import Module from './Module'; -import Event from './Event'; -import Output from './Amm'; - -type ModuleAsts = { [path: string]: LPNode }; - -const ammFromModuleAsts = (asts: ModuleAsts): string => { - const stdFiles: Set = new Set(); - for (const [modPath, mod] of Object.entries(asts)) { - for (const importt of Ast.resolveImports(modPath, mod)) { - if (importt.substring(0, 5) === '@std/') { - stdFiles.add(importt.substring(5, importt.length) + '.lnn'); - } - } - } - Std.loadStdModules(stdFiles); - const rootScope = Module.getAllModules()[''].exportScope; - Module.modulesFromAsts(asts, rootScope); - const amm = new Output(); - Event.allEvents.forEach((e) => e.compile(amm)); - return amm.toString(); -}; - -const moduleAstsFromFile = (filename: string): ModuleAsts => { - const moduleAsts: ModuleAsts = {}; - const paths = []; - const rootPath = fs.realpathSync(filename); - paths.push(rootPath); - - while (paths.length > 0) { - const modulePath = paths.shift(); - let module = null; - try { - module = Ast.fromFile(modulePath); - } catch (e) { - console.error(`Could not load ${modulePath}`); - throw e; - } - moduleAsts[modulePath] = module; - const imports = Ast.resolveImports(modulePath, module); - for (let i = 0; i < imports.length; i++) { - if ( - !moduleAsts[imports[i]] && - !(imports[i].substring(0, 5) === '@std/') - ) { - paths.push(imports[i]); - } - } - } - return moduleAsts; -}; - -export const fromFile = (filename: string): string | Buffer => - ammFromModuleAsts(moduleAstsFromFile(filename)); - -export const fromString = (str: string): string | Buffer => { - return null; -}; diff --git a/compiler/src/lnntoamm/opcodes.ts b/compiler/src/lnntoamm/opcodes.ts deleted file mode 100644 index 2a85ac3ae..000000000 --- a/compiler/src/lnntoamm/opcodes.ts +++ /dev/null @@ -1,348 +0,0 @@ -import Event from './Event'; -import { OpcodeFn } from './Fn'; -import Scope from './Scope'; -import Type from './Types'; - -let __opcodes: Scope = null; - -const opcodes = (): Scope => { - if (__opcodes === null) load(); - return __opcodes; -}; - -export default opcodes; - -const load = (): void => { - __opcodes = new Scope(); - - Object.entries({ - void: [], - int8: [], - int16: [], - int32: [], - int64: [], - float32: [], - float64: [], - bool: [], - string: [], - - Either: ['T', 'U'], - Error: [], - Maybe: ['T'], - Result: ['T'], - }).forEach(([name, generics]: [string, string[]]) => { - __opcodes.put(name, Type.opaque(name, generics)); - }); - - const anyIface = { - fields: [], - methods: [], - operators: [], - }; - Object.entries({ - any: anyIface, - anythingElse: anyIface, - }).forEach(([name, { fields, methods, operators }]) => { - __opcodes.put( - name, - Type.builtinInterface(name, fields, methods, operators), - ); - }); - - Object.entries({ - start: 'void', - }).forEach(([name, tyName]: [string, string]) => { - const eventTy: Type = __opcodes.get(tyName); - if (eventTy === null) { - throw new Error( - `builtin event ${name} has type ${tyName}, which isn't defined in the opcode scope`, - ); - } else if (!(eventTy instanceof Type)) { - throw new Error( - `builtin event ${name} is declared with type ${tyName}, but that's not a valid type`, - ); - } - let event: Event; - if (name === 'start') { - event = new Event('_start', eventTy, [], true); - } else { - event = new Event(name, eventTy, [], false); - } - __opcodes.put(name, event); - }); - - Object.entries({ - i8f64: [{ a: 'int8' }, 'float64'], - i16f64: [{ a: 'int16' }, 'float64'], - i32f64: [{ a: 'int32' }, 'float64'], - i64f64: [{ a: 'int64' }, 'float64'], - f32f64: [{ a: 'float32' }, 'float64'], - strf64: [{ a: 'string' }, 'float64'], - boolf64: [{ a: 'bool' }, 'float64'], - - i8f32: [{ a: 'int8' }, 'float32'], - i16f32: [{ a: 'int16' }, 'float32'], - i32f32: [{ a: 'int32' }, 'float32'], - i64f32: [{ a: 'int64' }, 'float32'], - f64f32: [{ a: 'float64' }, 'float32'], - strf32: [{ a: 'string' }, 'float32'], - boolf32: [{ a: 'bool' }, 'float32'], - - i8i64: [{ a: 'int8' }, 'int64'], - i16i64: [{ a: 'int16' }, 'int64'], - i32i64: [{ a: 'int32' }, 'int64'], - f32i64: [{ a: 'float32' }, 'int64'], - f64i64: [{ a: 'float64' }, 'int64'], - stri64: [{ a: 'string' }, 'int64'], - booli64: [{ a: 'bool' }, 'int64'], - - i8i32: [{ a: 'int8' }, 'int32'], - i16i32: [{ a: 'int16' }, 'int32'], - i64i32: [{ a: 'int64' }, 'int32'], - f32i32: [{ a: 'float32' }, 'int32'], - f64i32: [{ a: 'float64' }, 'int32'], - stri32: [{ a: 'string' }, 'int32'], - booli32: [{ a: 'bool' }, 'int32'], - - i8i16: [{ a: 'int8' }, 'int16'], - i32i16: [{ a: 'int32' }, 'int16'], - i64i16: [{ a: 'int64' }, 'int16'], - f32i16: [{ a: 'float32' }, 'int16'], - f64i16: [{ a: 'float64' }, 'int16'], - stri16: [{ a: 'string' }, 'int16'], - booli16: [{ a: 'bool' }, 'int16'], - - i16i8: [{ a: 'int16' }, 'int8'], - i32i8: [{ a: 'int32' }, 'int8'], - i64i8: [{ a: 'int64' }, 'int8'], - f32i8: [{ a: 'float32' }, 'int8'], - f64i8: [{ a: 'float64' }, 'int8'], - stri8: [{ a: 'string' }, 'int8'], - booli8: [{ a: 'bool' }, 'int8'], - - i8bool: [{ a: 'int8' }, 'bool'], - i16bool: [{ a: 'int16' }, 'bool'], - i32bool: [{ a: 'int32' }, 'bool'], - i64bool: [{ a: 'int64' }, 'bool'], - f32bool: [{ a: 'float32' }, 'bool'], - f64bool: [{ a: 'float64' }, 'bool'], - strbool: [{ a: 'string' }, 'bool'], - - i8str: [{ a: 'int8' }, 'string'], - i16str: [{ a: 'int16' }, 'string'], - i32str: [{ a: 'int32' }, 'string'], - i64str: [{ a: 'int64' }, 'string'], - f32str: [{ a: 'float32' }, 'string'], - f64str: [{ a: 'float64' }, 'string'], - boolstr: [{ a: 'bool' }, 'string'], - - eqi8: [{ a: 'int8', b: 'int8' }, 'bool'], - eqi16: [{ a: 'int16', b: 'int16' }, 'bool'], - eqi32: [{ a: 'int32', b: 'int32' }, 'bool'], - eqi64: [{ a: 'int64', b: 'int64' }, 'bool'], - eqf32: [{ a: 'float32', b: 'float32' }, 'bool'], - eqf64: [{ a: 'float64', b: 'float64' }, 'bool'], - eqstr: [{ a: 'string', b: 'string' }, 'bool'], - eqbool: [{ a: 'bool', b: 'bool' }, 'bool'], - - neqi8: [{ a: 'int8', b: 'int8' }, 'bool'], - neqi16: [{ a: 'int16', b: 'int16' }, 'bool'], - neqi32: [{ a: 'int32', b: 'int32' }, 'bool'], - neqi64: [{ a: 'int64', b: 'int64' }, 'bool'], - neqf32: [{ a: 'float32', b: 'float32' }, 'bool'], - neqf64: [{ a: 'float64', b: 'float64' }, 'bool'], - neqstr: [{ a: 'string', b: 'string' }, 'bool'], - neqbool: [{ a: 'bool', b: 'bool' }, 'bool'], - - lti8: [{ a: 'int8', b: 'int8' }, 'bool'], - lti16: [{ a: 'int16', b: 'int16' }, 'bool'], - lti32: [{ a: 'int32', b: 'int32' }, 'bool'], - lti64: [{ a: 'int64', b: 'int64' }, 'bool'], - ltf32: [{ a: 'float32', b: 'float32' }, 'bool'], - ltf64: [{ a: 'float64', b: 'float64' }, 'bool'], - ltstr: [{ a: 'string', b: 'string' }, 'bool'], - - ltei8: [{ a: 'int8', b: 'int8' }, 'bool'], - ltei16: [{ a: 'int16', b: 'int16' }, 'bool'], - ltei32: [{ a: 'int32', b: 'int32' }, 'bool'], - ltei64: [{ a: 'int64', b: 'int64' }, 'bool'], - ltef32: [{ a: 'float32', b: 'float32' }, 'bool'], - ltef64: [{ a: 'float64', b: 'float64' }, 'bool'], - ltestr: [{ a: 'string', b: 'string' }, 'bool'], - - gti8: [{ a: 'int8', b: 'int8' }, 'bool'], - gti16: [{ a: 'int16', b: 'int16' }, 'bool'], - gti32: [{ a: 'int32', b: 'int32' }, 'bool'], - gti64: [{ a: 'int64', b: 'int64' }, 'bool'], - gtf32: [{ a: 'float32', b: 'float32' }, 'bool'], - gtf64: [{ a: 'float64', b: 'float64' }, 'bool'], - gtstr: [{ a: 'string', b: 'string' }, 'bool'], - - gtei8: [{ a: 'int8', b: 'int8' }, 'bool'], - gtei16: [{ a: 'int16', b: 'int16' }, 'bool'], - gtei32: [{ a: 'int32', b: 'int32' }, 'bool'], - gtei64: [{ a: 'int64', b: 'int64' }, 'bool'], - gtef32: [{ a: 'float32', b: 'float32' }, 'bool'], - gtef64: [{ a: 'float64', b: 'float64' }, 'bool'], - gtestr: [{ a: 'string', b: 'string' }, 'bool'], - - notbool: [{ b: 'bool' }, 'bool'], - andbool: [{ a: 'bool', b: 'bool' }, 'bool'], - nandboo: [{ a: 'bool', b: 'bool' }, 'bool'], - orbool: [{ a: 'bool', b: 'bool' }, 'bool'], - xorbool: [{ a: 'bool', b: 'bool' }, 'bool'], - norbool: [{ a: 'bool', b: 'bool' }, 'bool'], - xnorboo: [{ a: 'bool', b: 'bool' }, 'bool'], - - modi8: [{ a: 'int8', b: 'int8' }, 'int8'], - modi16: [{ a: 'int16', b: 'int16' }, 'int16'], - modi32: [{ a: 'int32', b: 'int32' }, 'int32'], - modi64: [{ a: 'int64', c: 'int64' }, 'int64'], - - sqrtf32: [{ a: 'float32' }, 'float32'], - sqrtf64: [{ a: 'float64' }, 'float64'], - - absi8: [{ a: 'Result' }, 'Result'], - absi16: [{ a: 'Result' }, 'Result'], - absi32: [{ a: 'Result' }, 'Result'], - absi64: [{ a: 'Result' }, 'Result'], - absf32: [{ a: 'Result' }, 'Result'], - absf64: [{ a: 'Result' }, 'Result'], - sabsi8: [{ a: 'int8' }, 'int8'], - sabsi16: [{ a: 'int16' }, 'int16'], - sabsi32: [{ a: 'int32' }, 'int32'], - sabsi64: [{ a: 'int64' }, 'int64'], - sabsf32: [{ a: 'float32' }, 'float32'], - sabsf64: [{ a: 'float64' }, 'float64'], - - negi8: [{ a: 'Result' }, 'Result'], - negi16: [{ a: 'Result' }, 'Result'], - negi32: [{ a: 'Result' }, 'Result'], - negi64: [{ a: 'Result' }, 'Result'], - negf32: [{ a: 'Result' }, 'Result'], - negf64: [{ a: 'Result' }, 'Result'], - snegi8: [{ a: 'int8' }, 'int8'], - snegi16: [{ a: 'int16' }, 'int16'], - snegi32: [{ a: 'int32' }, 'int32'], - snegi64: [{ a: 'int64' }, 'int64'], - snegf32: [{ a: 'float32' }, 'float32'], - snegf64: [{ a: 'float64' }, 'float64'], - - addi8: [{ a: 'Result', b: 'Result' }, 'Result'], - addi16: [{ a: 'Result', b: 'Result' }, 'Result'], - addi32: [{ a: 'Result', b: 'Result' }, 'Result'], - addi64: [{ a: 'Result', b: 'Result' }, 'Result'], - addf32: [{ a: 'Result', b: 'Result' }, 'Result'], - addf64: [{ a: 'Result', b: 'Result' }, 'Result'], - saddi8: [{ a: 'int8', b: 'int8' }, 'int8'], - saddi16: [{ a: 'int16', b: 'int16' }, 'int16'], - saddi32: [{ a: 'int32', b: 'int32' }, 'int32'], - saddi64: [{ a: 'int64', b: 'int64' }, 'int64'], - saddf32: [{ a: 'float32', b: 'float32' }, 'float32'], - saddf64: [{ a: 'float64', b: 'float64' }, 'float64'], - - subi8: [{ a: 'Result', b: 'Result' }, 'Result'], - subi16: [{ a: 'Result', b: 'Result' }, 'Result'], - subi32: [{ a: 'Result', b: 'Result' }, 'Result'], - subi64: [{ a: 'Result', b: 'Result' }, 'Result'], - subf32: [{ a: 'Result', b: 'Result' }, 'Result'], - subf64: [{ a: 'Result', b: 'Result' }, 'Result'], - ssubi8: [{ a: 'int8', b: 'int8' }, 'int8'], - ssubi16: [{ a: 'int16', b: 'int16' }, 'int16'], - ssubi32: [{ a: 'int32', b: 'int32' }, 'int32'], - ssubi64: [{ a: 'int64', b: 'int64' }, 'int64'], - ssubf32: [{ a: 'float32', b: 'float32' }, 'float32'], - ssubf64: [{ a: 'float64', b: 'float64' }, 'float64'], - - muli8: [{ a: 'Result', b: 'Result' }, 'Result'], - muli16: [{ a: 'Result', b: 'Result' }, 'Result'], - muli32: [{ a: 'Result', b: 'Result' }, 'Result'], - muli64: [{ a: 'Result', b: 'Result' }, 'Result'], - mulf32: [{ a: 'Result', b: 'Result' }, 'Result'], - mulf64: [{ a: 'Result', b: 'Result' }, 'Result'], - smuli8: [{ a: 'int8', b: 'int8' }, 'int8'], - smuli16: [{ a: 'int16', b: 'int16' }, 'int16'], - smuli32: [{ a: 'int32', b: 'int32' }, 'int32'], - smuli64: [{ a: 'int64', b: 'int64' }, 'int64'], - smulf32: [{ a: 'float32', b: 'float32' }, 'float32'], - smulf64: [{ a: 'float64', b: 'float64' }, 'float64'], - - divi8: [{ a: 'Result', b: 'Result' }, 'Result'], - divi16: [{ a: 'Result', b: 'Result' }, 'Result'], - divi32: [{ a: 'Result', b: 'Result' }, 'Result'], - divi64: [{ a: 'Result', b: 'Result' }, 'Result'], - divf32: [{ a: 'Result', b: 'Result' }, 'Result'], - divf64: [{ a: 'Result', b: 'Result' }, 'Result'], - sdivi8: [{ a: 'int8', b: 'int8' }, 'int8'], - sdivi16: [{ a: 'int16', b: 'int16' }, 'int16'], - sdivi32: [{ a: 'int32', b: 'int32' }, 'int32'], - sdivi64: [{ a: 'int64', b: 'int64' }, 'int64'], - sdivf32: [{ a: 'float32', b: 'float32' }, 'float32'], - sdivf64: [{ a: 'float64', b: 'float64' }, 'float64'], - - powi8: [{ a: 'Result', b: 'Result' }, 'Result'], - powi16: [{ a: 'Result', b: 'Result' }, 'Result'], - powi32: [{ a: 'Result', b: 'Result' }, 'Result'], - powi64: [{ a: 'Result', b: 'Result' }, 'Result'], - powf32: [{ a: 'Result', b: 'Result' }, 'Result'], - powf64: [{ a: 'Result', b: 'Result' }, 'Result'], - spowi8: [{ a: 'int8', b: 'int8' }, 'int8'], - spowi16: [{ a: 'int16', b: 'int16' }, 'int16'], - spowi32: [{ a: 'int32', b: 'int32' }, 'int32'], - spowi64: [{ a: 'int64', b: 'int64' }, 'int64'], - spowf32: [{ a: 'float32', b: 'float32' }, 'float32'], - spowf64: [{ a: 'float64', b: 'float64' }, 'float64'], - - mainE: [{ v: 'any', s: 'int64' }, 'Either'], - altE: [{ v: 'any', s: 'int64' }, 'Either'], - isMain: [{ e: 'Either' }, 'bool'], - isAlt: [{ e: 'Either' }, 'bool'], - mainOr: [{ e: 'Either', d: 'any' }, 'any'], - altOr: [ - { e: 'Either', d: 'anythingElse' }, - 'anythingElse', - ], - - someM: [{ v: 'any', s: 'int64' }, 'Maybe'], - noneM: [{}, 'Maybe'], - isSome: [{ m: 'Maybe' }, 'bool'], - isNone: [{ m: 'Maybe' }, 'bool'], - getOrM: [{ m: 'Maybe', d: 'any' }, 'any'], - - okR: [{ a: 'any', s: 'int64' }, 'Result'], - err: [{ s: 'string' }, 'Result'], - error: [{ s: 'string' }, 'Error'], - noerr: [{}, 'Error'], - isOk: [{ r: 'Result' }, 'bool'], - isErr: [{ r: 'Result' }, 'bool'], - getOrR: [{ r: 'Result', d: 'any' }, 'any'], - getErr: [{ r: 'Result', e: 'Error' }, 'Error'], - - waitop: [{ t: 'int64' }, 'void'], - - catstr: [{ a: 'string', b: 'string' }, 'string'], - indstr: [{ s: 'string', t: 'string' }, 'Result'], - repstr: [{ s: 'string', n: 'int64' }, 'string'], - matches: [{ a: 'string', b: 'string' }, 'bool'], - lenstr: [{ s: 'string' }, 'int64'], - trim: [{ s: 'string' }, 'string'], - - copyi8: [{ a: 'int8' }, 'int8'], - copyi16: [{ a: 'int16' }, 'int16'], - copyi32: [{ a: 'int32' }, 'int32'], - copyi64: [{ a: 'int64' }, 'int64'], - copyf32: [{ a: 'float32' }, 'float32'], - copyf64: [{ a: 'float64' }, 'float64'], - copybool: [{ a: 'bool' }, 'bool'], - copystr: [{ a: 'string' }, 'string'], - - stdoutp: [{ out: 'string' }, 'void'], - stderrp: [{ err: 'string' }, 'void'], - exitop: [{ status: 'int8' }, 'void'], - } as { - [opcode: string]: [{ [arg: string]: string }, string]; - // Opcode constructor inserts into the opcode scope for us - }).forEach(([name, [args, ret]]) => { - new OpcodeFn(name, args, ret, __opcodes); - }); -}; diff --git a/compiler/src/lnntoamm/util.ts b/compiler/src/lnntoamm/util.ts deleted file mode 100644 index cf285b0a2..000000000 --- a/compiler/src/lnntoamm/util.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { v4 as uuid } from 'uuid'; -import Fn from './Fn'; -import Operator from './Operator'; - -let counter = 0; -export const genName = (name?: string) => - name ? `${name}-n${counter++}` : '_' + uuid().replace(/-/g, '_'); - -export const isFnArray = (val: any): val is Array => { - return val instanceof Array && (val.length === 0 || val[0] instanceof Fn); -}; - -export const isOpArray = (val: any): val is Array => { - return ( - val instanceof Array && (val.length === 0 || val[0] instanceof Operator) - ); -}; - -/** - * Assumes valOrMsg is a message if more than 1 argument passed. TS should prevent that though - * @param valOrMsg the value to debug print or the message to print alongside other values - * @param vals the values to print after a message - * @returns the first value passed - */ -export const DBG = function (valOrMsg: T | string, ...vals: T[]): T { - if (vals.length > 0) { - console.log('->', valOrMsg, ...vals); - return vals[0]; - } else { - console.log('~~', valOrMsg); - return valOrMsg as T; - } -}; - -export const TODO = (task?: string) => { - throw new Error(`TODO${task !== undefined ? ': ' + task : ''}`); -}; - -export interface Equalable { - eq(other: Equalable): boolean; -} - -export const matrixIndices = function* (matrix: Array>) { - const indices = matrix.map(() => 0); - yield indices; - while (!indices.every((index, ii) => index === matrix[ii].length - 1)) { - indices.reduceRight((carry, _matIdx, idx) => { - indices[idx] += carry; - if (indices[idx] === matrix[idx].length) { - indices[idx] = 0; - return 1; - } else { - return 0; - } - }, 1); - yield indices; - } -}; - -// TODO: is this necessary? -// export class MapButBetter { -// private __keys: K[] -// private __vals: V[] -// get size(): number { -// return this.__keys.length; -// } -// constructor() { -// this.__keys = []; -// this.__vals = []; -// } -// private idxFor(key: K): number { -// for (let ii = 0; ii < this.__keys.length; ii++) { -// if (Object.is(this.__keys[ii], key)) { -// return ii; -// } -// } -// return -1; -// } -// clear() { -// this.__keys = []; -// this.__vals = []; -// } -// /** -// * @param key The key to delete from the Map -// * @returns The value that was removed or null if the key is not in the Map -// */ -// delete(key: K): V { -// const idx = this.idxFor(key); -// if (idx === -1) { -// return null; -// } -// const res = this.__vals[idx]; -// this.__keys = this.__keys.splice(idx, 1); -// this.__vals = this.__vals.splice(idx, 1); -// return res; -// } -// /** -// * @param key The key of the value to return -// * @returns The value corresponding to the key or null if the key is not in the Map -// */ -// get(key: K): V { -// const idx = this.idxFor(key); -// if (idx === -1) { -// return null; -// } -// return this.__vals[idx]; -// } -// /** -// * @param key The key to look for -// * @returns True if the key is in the Map -// */ -// has(key: K): boolean { -// return this.idxFor(key) !== -1; -// } -// /** -// * @param key The key to set or reassign -// * @param val The value to assign to the key -// * @returns The value previously assigned to the key, or null if the key was not previously set -// */ -// set(key: K, val: V): V { -// const idx = this.idxFor(key); -// if (idx === -1) { -// this.__keys.push(key); -// this.__vals.push(val); -// return null; -// } -// const res = this.__vals[idx]; -// this.__vals[idx] = val; -// return res; -// } -// // TODO: these methods usually return iterators/generators, but -// // we don't need to worry about the difference right now... -// keys(): K[] { -// return [...this.__keys]; -// } -// values(): V[] { -// return [...this.__vals]; -// } -// entries(): [K, V][] { -// let res = []; -// for (let ii = 0; ii < this.size; ii++) { -// res.push([this.__keys[ii], this.__vals[ii]]); -// } -// return res; -// } -// forEach(callbackFn: (key: K, val: V) => void) { -// for (let ii = 0; ii < this.size; ii++) { -// callbackFn(this.__keys[ii], this.__vals[ii]); -// } -// } -// } diff --git a/compiler/src/lntoamm/Ast.ts b/compiler/src/lntoamm/Ast.ts deleted file mode 100644 index 75d8672c7..000000000 --- a/compiler/src/lntoamm/Ast.ts +++ /dev/null @@ -1,294 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; - -import { LP, LPNode, LPError } from '../lp'; -import * as ln from '../ln'; - -const resolve = (path: string) => { - try { - return fs.realpathSync(path); - } catch (e) { - return null; - } -}; - -export const fromString = (str: string) => { - const lp = LP.fromText(str); - const ast = ln.ln.apply(lp); - if (ast instanceof LPError) { - throw new Error(ast.msg); - } else if (ast.t.length !== str.length) { - const lp2 = lp.clone(); - lp2.advance(ast.t.length); - const body = ast.get('body').getAll(); - const last = body[body.length - 1]; - throw new Error( - `AST Parse error, cannot continue due to syntax error between line ${last.line}:${last.char} - ${lp2.line}:${lp2.char}`, - ); - } - - return ast; -}; - -export const fromFile = (filename: string) => { - const ast = fromString(fs.readFileSync(filename, { encoding: 'utf8' })); - ast.filename = filename; - return ast; -}; - -export const resolveDependency = (modulePath: string, dependency: LPNode) => { - // Special case path for the standard library importing itself - if (modulePath.substring(0, 4) === '@std') return dependency.t.trim(); - // For everything else... - let importPath = null; - // If the dependency is a local dependency, there's little logic in determining - // what is being imported. It's either the relative path to a file with the language - // extension, or the relative path to a directory containing an "index.ln" file - if (dependency.has('localdependency')) { - const dirPath = resolve( - path.join( - path.dirname(modulePath), - dependency.get('localdependency').t, - 'index.ln', - ), - ); - const filePath = resolve( - path.join( - path.dirname(modulePath), - dependency.get('localdependency').t + '.ln', - ), - ); - // It's possible for both to exist. Prefer the directory-based one, but warn the user - if (typeof dirPath === 'string' && typeof filePath === 'string') { - console.error( - dirPath + ' and ' + filePath + ' both exist. Using ' + dirPath, - ); - } - if (typeof filePath === 'string') { - importPath = filePath; - } - if (typeof dirPath === 'string') { - importPath = dirPath; - } - if (importPath === null) { - throw new Error( - `The dependency ${ - dependency.get('localdependency').t - } could not be found.`, - ); - } - } - // If the dependency is a global dependency, there's a more complicated resolution to find it. - // This is inspired by the Ruby and Node resolution mechanisms, but with some changes that - // should hopefully make some improvements so dependency-injection is effectively first-class - // and micro-libraries are discouraged (the latter will require a multi-pronged effort) - // - // Essentially, there are two recursively-found directories that global modules can be found, - // the `modules` directory and the `dependencies` directory (TBD: are these the final names?) - // The `modules` directory is recursively checked first (with a special check to make sure it - // ignores self-resolutions) and the first one found in that check, if any, is used. If not, - // there's a special check if the dependency is an `@std/...` dependency, and if so to return - // that string as-is so the built-in dependency is used. Next the same recursive check is - // performed on the `dependencies` directories until the dependency is found. If that also - // fails, then there will be a complaint and the process will exit. - // - // The idea is that the package manager will install dependencies into the `dependencies` - // directory at the root of the project (or maybe PWD, but that seems a bit too unwieldy). - // Meanwhile the `modules` directory will only exist if the developer wants it, but it can be - // useful for cross-cutting code in the same project that doesn't really need to be open- - // sourced but is annoying to always reference slightly differently in each file, eg - // `../../../util`. Instead the project can have a project-root-level `modules` directory and - // then `modules/util.ln` can be referenced simply with `import @util` anywhere in the project. - // - // Since this is also recursive, it's should make dependency injection a first-class citizen - // of the language. For instance you can put all of your models in `modules/models/`, and then - // your unit test suite can have its model mocks in `tests/modules/models/` and the dependency - // you intend to inject into can be symlinked in the `tests/` directory to cause that version - // to pull the injected code, instead. And of course, if different tests need different - // dependency injections, you can turn the test file into a directory of the same name and - // rename the file to `index.ln` within it, and then have the specific mocks that test needs - // stored in a `modules/` directory in parallel with it, which will not impact other mocks. - // - // Because these mocks also have a special exception to not import themselves, this can also - // be used for instrumentation purposes, where they override the actual module but then also - // import the real thing and add extra behavior to it. - // - // While there are certainly uses for splitting some logical piece of code into a tree of - // files and directories, it is my hope that the standard application organization path is a - // project with a root `index.ln` file and `modules` and `dependencies` directories, and little - // else. At least things like `modules/logger`, `modules/config`, etc should belong there. - if (dependency.has('globaldependency')) { - // Get the two potential dependency types, file and directory-style. - const fileModule = - dependency.get('globaldependency').t.substring(1) + '.ln'; - const dirModule = - dependency.get('globaldependency').t.substring(1) + '/index.ln'; - // Get the initial root to check - let pathRoot = path.dirname(modulePath); - // Search the recursively up the directory structure in the `modules` directories for the - // specified dependency, and if found, return it. - while (pathRoot != null) { - const dirPath = resolve(path.join(pathRoot, 'modules', dirModule)); - const filePath = resolve(path.join(pathRoot, 'modules', fileModule)); - // It's possible for a module to accidentally resolve to itself when the module wraps the - // actual dependency it is named for. - if (dirPath === modulePath || filePath === modulePath) { - pathRoot = path.dirname(pathRoot); - continue; - } - // It's possible for both to exist. Prefer the directory-based one, but warn the user - if (typeof dirPath === 'string' && typeof filePath === 'string') { - console.error( - dirPath + ' and ' + filePath + ' both exist. Using ' + dirPath, - ); - } - if (typeof dirPath === 'string') { - importPath = dirPath; - break; - } - if (typeof filePath === 'string') { - importPath = filePath; - break; - } - if (pathRoot === '/' || /[A-Z]:\\/.test(pathRoot)) { - pathRoot = null; - } else { - pathRoot = path.dirname(pathRoot); - } - } - if (importPath == null) { - // If we can't find it defined in a `modules` directory, check if it's an `@std/...` - // module and abort here so the built-in standard library is used. - if (dependency.get('globaldependency').t.substring(0, 5) === '@std/') { - // Not a valid path (starting with '@') to be used as signal to use built-in library) - importPath = dependency.get('globaldependency').t; - } else { - // Go back to the original point and search up the tree for `dependencies` directories - pathRoot = path.dirname(modulePath); - while (pathRoot != null) { - const dirPath = resolve( - path.join(pathRoot, 'dependencies', dirModule), - ); - const filePath = resolve( - path.join(pathRoot, 'dependencies', fileModule), - ); - // It's possible for both to exist. Prefer the directory-based one, but warn the user - if (typeof dirPath === 'string' && typeof filePath === 'string') { - console.error( - dirPath + ' and ' + filePath + ' both exist. Using ' + dirPath, - ); - } - if (typeof dirPath === 'string') { - importPath = dirPath; - break; - } - if (typeof filePath === 'string') { - importPath = filePath; - break; - } - if (pathRoot === '/' || /[A-Z]:\\/.test(pathRoot)) { - pathRoot = null; - } else { - pathRoot = path.dirname(pathRoot); - } - } - } - if (importPath == null) { - throw new Error( - `The dependency ${ - dependency.get('globaldependency').t - } could not be found.`, - ); - } - } - } - return importPath; -}; - -export const resolveImports = (modulePath: string, ast: LPNode) => { - const resolvedImports = []; - const imports = ast.get('imports').getAll(); - for (let i = 0; i < imports.length; i++) { - let dependency = null; - - if (imports[i].has('standardImport')) { - dependency = imports[i].get('standardImport').get('dependency'); - } - if (imports[i].has('fromImport')) { - dependency = imports[i].get('fromImport').get('dependency'); - } - if (!dependency) { - // Should I do anything else here? - throw new Error( - 'Malformed AST, import statement without an import definition?', - ); - } - const importPath = resolveDependency(modulePath, dependency); - resolvedImports.push(importPath); - } - return resolvedImports; -}; - -export const functionAstFromString = (fn: string) => { - const lp = LP.fromText(fn); - const ast = ln.functions.apply(lp); - if (ast instanceof LPError) { - throw new Error(ast.msg); - } else if (ast.t.length !== fn.length) { - const lp2 = lp.clone(); - lp2.advance(ast.t.length); - throw new Error( - `AST Parse error, cannot continue due to syntax error ending at line ${lp2.line}:${lp2.char}`, - ); - } - - return ast; -}; - -export const statementAstFromString = (s: string) => { - const lp = LP.fromText(s); - const ast = ln.statement.apply(lp); - if (ast instanceof LPError) { - throw new Error(ast.msg); - } else if (ast.t.length !== s.length) { - const lp2 = lp.clone(); - lp2.advance(ast.t.length); - throw new Error( - `AST Parse error, cannot continue due to syntax error ending at line ${lp2.line}:${lp2.char}`, - ); - } - - return ast; -}; - -export const fulltypenameAstFromString = (s: string) => { - const lp = LP.fromText(s); - const ast = ln.fulltypename.apply(lp); - if (ast instanceof LPError) { - throw new Error(ast.msg); - } else if (ast.t.length !== s.length) { - const lp2 = lp.clone(); - lp2.advance(ast.t.length); - throw new Error( - `AST Parse error, cannot continue due to syntax error ending at line ${lp2.line}:${lp2.char}`, - ); - } - - return ast; -}; - -export const assignablesAstFromString = (s: string) => { - const lp = LP.fromText(s); - const ast = ln.assignables.apply(lp); - if (ast instanceof LPError) { - throw new Error(ast.msg); - } else if (ast.t.length !== s.length) { - const lp2 = lp.clone(); - lp2.advance(ast.t.length); - throw new Error( - `AST Parse error, cannot continue due to syntax error ending at line ${lp2.line}:${lp2.char}`, - ); - } - - return ast; -}; diff --git a/compiler/src/lntoamm/Constant.ts b/compiler/src/lntoamm/Constant.ts deleted file mode 100644 index fb1e675a6..000000000 --- a/compiler/src/lntoamm/Constant.ts +++ /dev/null @@ -1,27 +0,0 @@ -import Scope from './Scope'; -import { LPNode } from '../lp'; - -class Constant { - name: string; - assignablesAst: LPNode; - scope: Scope; - - constructor(name: string, assignablesAst: LPNode, scope: Scope) { - this.name = name; - this.assignablesAst = assignablesAst; - this.scope = scope; - } - - static fromAst(constdeclaration: LPNode, scope: Scope) { - const name = constdeclaration.get('variable').t; - const outConst = new Constant( - name, - constdeclaration.get('assignables'), - scope, - ); - scope.put(name, outConst); - return outConst; - } -} - -export default Constant; diff --git a/compiler/src/lntoamm/Event.ts b/compiler/src/lntoamm/Event.ts deleted file mode 100644 index bc3dac486..000000000 --- a/compiler/src/lntoamm/Event.ts +++ /dev/null @@ -1,38 +0,0 @@ -import Scope from './Scope'; -import Type from './Type'; -import UserFunction from './UserFunction'; -import { LPNode } from '../lp'; - -class Event { - name: string; - type: Type; - builtIn: boolean; - handlers: Array; - static allEvents: Array = []; - - constructor(name: string, type: any, builtIn: boolean) { - (this.name = name), (this.type = type); - this.builtIn = builtIn; - this.handlers = []; - Event.allEvents.push(this); - } - - toString() { - return `event ${this.name}: ${this.type.typename}`; - } - - static fromAst(eventAst: LPNode, scope: Scope) { - const name = eventAst.get('variable').t; - const type = scope.deepGet(eventAst.get('fulltypename').t) as Type; - if (!type) { - throw new Error( - 'Could not find specified type: ' + eventAst.get('fulltypename').t, - ); - } else if (!(type instanceof Type)) { - throw new Error(eventAst.get('fulltypename').t + ' is not a type'); - } - return new Event(name, type, false); - } -} - -export default Event; diff --git a/compiler/src/lntoamm/Function.ts b/compiler/src/lntoamm/Function.ts deleted file mode 100644 index 3f8213527..000000000 --- a/compiler/src/lntoamm/Function.ts +++ /dev/null @@ -1,21 +0,0 @@ -import Microstatement from './Microstatement'; -import Scope from './Scope'; -import Type from './Type'; - -export type Args = { - [K: string]: Type; -}; - -export interface Fn { - getName(): string; - getArguments(): Args; - getReturnType(): Type; - isPure(): boolean; - microstatementInlining( - realArgNames: Array, - scope: Scope, - microstatements: Array, - ): void; -} - -export default Fn; diff --git a/compiler/src/lntoamm/Microstatement.ts b/compiler/src/lntoamm/Microstatement.ts deleted file mode 100644 index 877bfc35f..000000000 --- a/compiler/src/lntoamm/Microstatement.ts +++ /dev/null @@ -1,1983 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -import { v4 as uuid } from 'uuid'; - -import * as Ast from './Ast'; -import Event from './Event'; -import Operator from './Operator'; -import Constant from './Constant'; -import Scope from './Scope'; -import Statement from './Statement'; -import StatementType from './StatementType'; -import Type from './Type'; -import UserFunction from './UserFunction'; -import { Args, Fn } from './Function'; -import { LPNode } from '../lp'; - -const FIXED_TYPES = [ - 'int64', - 'int32', - 'int16', - 'int8', - 'float64', - 'float32', - 'bool', - 'void', -]; - -class Microstatement { - statementType: StatementType; - scope: Scope; - pure: boolean; - outputName: string; - alias: string; - outputType: Type; - inputNames: Array; - fns: Array; - closurePure: boolean; - closureStatements: Array; - closureArgs: Args; - closureOutputType: Type; - - constructor( - statementType: StatementType, - scope: Scope, - pure: boolean, - outputName: string, - outputType: Type = Type.builtinTypes.void, - inputNames: Array = [], - fns: Array = [], - alias = '', - closurePure = true, - closureStatements: Array = [], - closureArgs: Args = {}, - closureOutputType: Type = Type.builtinTypes.void, - ) { - this.statementType = statementType; - this.scope = scope; - this.pure = pure; - this.outputName = outputName; - this.outputType = outputType; - this.inputNames = inputNames; - this.fns = fns; - this.alias = alias; - this.closurePure = closurePure; - this.closureStatements = closureStatements; - this.closureArgs = closureArgs; - this.closureOutputType = closureOutputType; - } - - toString() { - let outString = ''; - switch (this.statementType) { - case StatementType.CONSTDEC: - outString = - 'const ' + this.outputName + ': ' + this.outputType.typename; - if (this.fns.length > 0) { - outString += - ' = ' + - this.fns[0].getName() + - '(' + - this.inputNames.join(', ') + - ')'; - } else if (this.inputNames.length > 0) { - outString += ' = ' + this.inputNames[0]; // Doesn't appear the list is ever used here - } - break; - case StatementType.LETDEC: - outString = 'let ' + this.outputName + ': ' + this.outputType.typename; - if (this.fns.length > 0) { - outString += - ' = ' + - this.fns[0].getName() + - '(' + - this.inputNames.join(', ') + - ')'; - } else if (this.inputNames.length > 0) { - outString += ' = ' + this.inputNames[0]; // Doesn't appear the list is ever used here - } - break; - case StatementType.ASSIGNMENT: - outString = this.outputName; - if (this.fns.length > 0) { - outString += - ' = ' + - this.fns[0].getName() + - '(' + - this.inputNames.join(', ') + - ')'; - } else if (this.inputNames.length > 0) { - outString += ' = ' + this.inputNames[0]; // Doesn't appear the list is ever used here - } else { - outString += 'NO!'; - } - break; - case StatementType.CALL: - if (this.fns.length > 0) { - outString += - this.fns[0].getName() + '(' + this.inputNames.join(', ') + ')'; - } - break; - case StatementType.EMIT: - outString = 'emit ' + this.outputName + ' '; - if (this.fns.length > 0) { - outString += - this.fns[0].getName() + '(' + this.inputNames.join(', ') + ')'; - } else if (this.inputNames.length > 0) { - outString += this.inputNames[0]; // Doesn't appear the list is ever used here - } - break; - case StatementType.EXIT: - outString = 'return ' + this.outputName; - break; - case StatementType.CLOSURE: - outString = 'const ' + this.outputName + ': function = fn ('; - const args = []; - for (const [name, type] of Object.entries(this.closureArgs)) { - if (name !== '' && type.typename != '') { - args.push(name + ': ' + type.typename); - } - } - outString += args.join(','); - outString += '): ' + this.closureOutputType.typename + ' {\n'; - for (const m of this.closureStatements) { - const s = m.toString(); - if (s !== '') { - outString += ' ' + m.toString() + '\n'; - } - } - outString += ' }'; - break; - case StatementType.REREF: - case StatementType.ARG: - case StatementType.CLOSUREDEF: - // Intentionally never output anything, this is metadata for the transpiler algo only - break; - } - return outString; - } - - static fromVarName( - varName: string, - scope: Scope, - microstatements: Array, - ) { - let original = null; - for (let i = microstatements.length - 1; i > -1; i--) { - const microstatement = microstatements[i]; - // TODO: var resolution is complex. Need to revisit this. - if (microstatement.outputName === varName) { - original = microstatement; - if (microstatement.statementType !== StatementType.REREF) { - break; - } - } - if (microstatement.alias === varName) { - original = microstatement; - for (let j = i - 1; j >= 0; j--) { - if ( - microstatements[j].outputName === original.outputName && - microstatements[j].statementType !== StatementType.REREF - ) { - original = microstatements[j]; - break; - } - } - break; - } - } - // Check if this is a module constant that should be un-hoisted - if ( - original === null && - !!scope.deepGet(varName) && - scope.deepGet(varName) instanceof Constant - ) { - const globalConst = scope.deepGet(varName) as Constant; - Microstatement.fromAssignablesAst( - globalConst.assignablesAst, - globalConst.scope, // Eval this in its original scope in case it was an exported const - microstatements, // that was dependent on unexported internal functions or constants - ); - const last = microstatements[microstatements.length - 1]; - microstatements.push( - new Microstatement( - StatementType.REREF, - scope, - true, - last.outputName, - last.outputType, - [], - [], - globalConst.name, - ), - ); - } - return original; - } - - static fromConstantsAst( - constantsAst: LPNode, - scope: Scope, - microstatements: Array, - ) { - const constName = '_' + uuid().replace(/-/g, '_'); - let constType = 'void'; - if (constantsAst.has('bool')) constType = 'bool'; - if (constantsAst.has('str')) constType = 'string'; - if (constantsAst.has('num')) { - // TODO: Add support for hex, octal, scientific, etc - const numberConst = constantsAst.t; - constType = numberConst.indexOf('.') > -1 ? 'float64' : 'int64'; - } - let constVal: string; - try { - JSON.parse(constantsAst.t); // Will fail on strings with escape chars - constVal = constantsAst.t; - } catch (e) { - // It may be a zero-padded number - if ( - ['int8', 'int16', 'int32', 'int64'].includes(constType) && - constantsAst.t[0] === '0' - ) { - constVal = parseInt(constantsAst.t, 10).toString(); - } else if ( - ['float32', 'float64'].includes(constType) && - constantsAst.t[0] === '0' - ) { - constVal = parseFloat(constantsAst.t).toString(); - } else { - // Hackery to get these strings to work - constVal = JSON.stringify( - constantsAst.t.replace(/^["']/, '').replace(/["']$/, ''), - ); - } - } - microstatements.push( - new Microstatement( - StatementType.CONSTDEC, - scope, - true, - constName, - scope.deepGet(constType) as Type, - [constVal], - [], - ), - ); - } - - static fromObjectLiteralsAst( - objectLiteralsAst: LPNode, - scope: Scope, - microstatements: Array, - ) { - if (objectLiteralsAst.has('arrayliteral')) { - // Array literals first need all of the microstatements of the array contents defined, then - // a `newarr` opcode call is inserted for the object literal itself, then `pusharr` opcode - // calls are emitted to insert the relevant data into the array, and finally the array itself - // is REREFed for the outer microstatement generation call. - let arrayLiteralContents = []; - const arraybase = objectLiteralsAst.get('arrayliteral').has('arraybase') - ? objectLiteralsAst.get('arrayliteral').get('arraybase') - : objectLiteralsAst - .get('arrayliteral') - .get('fullarrayliteral') - .get('arraybase'); - if (arraybase.has('assignablelist')) { - const assignablelist = arraybase.get('assignablelist'); - arrayLiteralContents.push(assignablelist.get('assignables')); - assignablelist - .get('cdr') - .getAll() - .forEach((r) => { - arrayLiteralContents.push(r.get('assignables')); - }); - arrayLiteralContents = arrayLiteralContents.map((r) => { - Microstatement.fromAssignablesAst(r, scope, microstatements); - return microstatements[microstatements.length - 1]; - }); - } - let type = null; - if (objectLiteralsAst.get('arrayliteral').has('fullarrayliteral')) { - const arrayTypeAst = objectLiteralsAst - .get('arrayliteral') - .get('fullarrayliteral') - .get('literaldec') - .get('fulltypename'); - type = scope.deepGet(arrayTypeAst.t.trim()) as Type; - if (!type) { - // Try to define it if it's a generic type - if (arrayTypeAst.has('opttypegenerics')) { - const outerType = scope.deepGet( - arrayTypeAst.get('typename').t.trim(), - ) as Type; - if (!outerType) { - throw new Error(`${arrayTypeAst.t} is not defined -${objectLiteralsAst.t} on line ${objectLiteralsAst.line}:${objectLiteralsAst.char}`); - } - const generics = []; - const genericsAst = arrayTypeAst - .get('opttypegenerics') - .get('generics'); - generics.push(genericsAst.get('fulltypename').t); - genericsAst - .get('cdr') - .getAll() - .forEach((r) => { - generics.push(r.get('fulltypename').t); - }); - outerType.solidify(generics, scope); - type = scope.deepGet(arrayTypeAst.t.trim()); - } - } - if (!(type instanceof Type)) { - throw new Error(`${arrayTypeAst.t.trim()} is not a type -${objectLiteralsAst.t} on line ${objectLiteralsAst.line}:${ - objectLiteralsAst.char - }`); - } - } else if (arrayLiteralContents.length > 0) { - const innerType = arrayLiteralContents[0].outputType.typename; - Type.builtinTypes['Array'].solidify([innerType], scope); - type = scope.deepGet(`Array<${innerType}>`) as Type; - } else { - throw new Error(`Ambiguous array type, please specify the type for an empty array with the syntax \`new Array []\` -${objectLiteralsAst.t} on line ${objectLiteralsAst.line}:${objectLiteralsAst.char}`); - } - // Create a new variable to hold the size of the array literal - const lenName = '_' + uuid().replace(/-/g, '_'); - microstatements.push( - new Microstatement( - StatementType.CONSTDEC, - scope, - true, - lenName, - Type.builtinTypes['int64'], - [`${arrayLiteralContents.length}`], - [], - ), - ); - // Add the opcode to create a new array with the specified size - const opcodes = require('./opcodes').default; - opcodes.exportScope - .get('newarr')[0] - .microstatementInlining([lenName], scope, microstatements); - // Get the array microstatement and extract the name and insert the correct type - const array = microstatements[microstatements.length - 1]; - array.outputType = type; - // Try to use the "real" type if knowable - if (arrayLiteralContents.length > 0) { - array.outputType = Type.builtinTypes['Array'].solidify( - [arrayLiteralContents[0].outputType.typename], - scope, - ); - } - const arrayName = array.outputName; - // Push the values into the array - for (let i = 0; i < arrayLiteralContents.length; i++) { - // Create a new variable to hold the size of the array value - const size = FIXED_TYPES.includes( - arrayLiteralContents[i].outputType.typename, - ) - ? '8' - : '0'; - const sizeName = '_' + uuid().replace(/-/g, '_'); - microstatements.push( - new Microstatement( - StatementType.CONSTDEC, - scope, - true, - sizeName, - Type.builtinTypes['int64'], - [size], - [], - ), - ); - // Push the value into the array - const opcodes = require('./opcodes').default; - opcodes.exportScope - .get('pusharr')[0] - .microstatementInlining( - [arrayName, arrayLiteralContents[i].outputName, sizeName], - scope, - microstatements, - ); - } - // REREF the array - microstatements.push( - new Microstatement( - StatementType.REREF, - scope, - true, - arrayName, - array.outputType, - [], - [], - ), - ); - } else if (objectLiteralsAst.has('typeliteral')) { - // User types are represented in AMM and lower as `Array`. This reduces the number of - // concepts that have to be maintained in the execution layer (and is really what C structs - // are, anyways). The order of the properties on the specified type directly map to the - // order that they are inserted into the Array, not the order they're defined in the object - // literal notation, so reads and updates later on can occur predictably by mapping the name - // of the property to its array index. - // - // If the type literal is missing any fields, that's a hard compile error to make sure - // accessing undefined data is impossible. If a value might not be needed, they should use - // the `Option` type and provide a `None` value there. - const typeAst = objectLiteralsAst - .get('typeliteral') - .get('literaldec') - .get('fulltypename'); - let type = scope.deepGet(typeAst.t.trim()) as Type; - if (type === null) { - // Try to define it if it's a generic type - if (typeAst.has('opttypegenerics')) { - const outerType = scope.deepGet( - typeAst.get('typename').t.trim(), - ) as Type; - if (outerType === null) { - throw new Error(`${typeAst.t} is not defined -${objectLiteralsAst.t} on line ${objectLiteralsAst.line}:${objectLiteralsAst.char}`); - } - const generics = []; - const genericsAst = typeAst.get('opttypegenerics').get('generics'); - generics.push(genericsAst.get('fulltypename').t); - genericsAst - .get('cdr') - .getAll() - .forEach((r) => { - generics.push(r.get('fulltypename').t); - }); - outerType.solidify(generics, scope); - type = scope.deepGet(typeAst.t.trim()) as Type; - } - } - if (!(type instanceof Type)) { - throw new Error(`${typeAst.t.trim()} is not a type -${objectLiteralsAst.t} on line ${objectLiteralsAst.line}:${ - objectLiteralsAst.char - }`); - } - const assignlist = objectLiteralsAst - .get('typeliteral') - .get('typebase') - .get('typeassignlist'); - const assignArr = []; - assignArr.push({ - field: assignlist.get('variable'), - val: assignlist.get('assignables'), - }); - assignlist - .get('cdr') - .getAll() - .forEach((r) => { - assignArr.push({ - field: r.get('variable'), - val: r.get('assignables'), - }); - }); - const assignfields = assignArr.map((r) => r.field.t); - const assignvals = assignArr.map((r) => r.val); - const fields = Object.keys(type.properties); - const missingFields = []; - const foundFields = []; - const extraFields = []; - const astLookup = {}; - for (let i = 0; i < assignfields.length; i++) { - const assignfield = assignfields[i]; - const assignval = assignvals[i]; - astLookup[assignfield] = assignval; - if (!fields.includes(assignfield)) { - extraFields.push(assignfield); - } - if (foundFields.includes(assignfield)) { - extraFields.push(assignfield); - } - foundFields.push(assignfield); - } - for (const field of fields) { - if (!foundFields.includes(field)) { - missingFields.push(field); - } - } - if (missingFields.length > 0 || extraFields.length > 0) { - let errMsg = `${typeAst.t.trim()} object literal improperly defined`; - if (missingFields.length > 0) { - errMsg += '\n' + `Missing fields: ${missingFields.join(', ')}`; - } - if (extraFields.length > 0) { - errMsg += '\n' + `Extra fields: ${extraFields.join(', ')}`; - } - errMsg += - '\n' + - objectLiteralsAst.t + - ' on line ' + - objectLiteralsAst.line + - ':' + - objectLiteralsAst.char; - throw new Error(errMsg); - } - // The assignment looks good, now we'll mimic the array literal logic mostly - const arrayLiteralContents = []; - for (let i = 0; i < fields.length; i++) { - Microstatement.fromAssignablesAst( - astLookup[fields[i]], - scope, - microstatements, - ); - arrayLiteralContents.push(microstatements[microstatements.length - 1]); - } - // Create a new variable to hold the size of the array literal - const lenName = '_' + uuid().replace(/-/g, '_'); - microstatements.push( - new Microstatement( - StatementType.CONSTDEC, - scope, - true, - lenName, - Type.builtinTypes['int64'], - [`${fields.length}`], - [], - ), - ); - // Add the opcode to create a new array with the specified size - const opcodes = require('./opcodes').default; - opcodes.exportScope - .get('newarr')[0] - .microstatementInlining([lenName], scope, microstatements); - // Get the array microstatement and extract the name and insert the correct type - const array = microstatements[microstatements.length - 1]; - array.outputType = type; - const arrayName = array.outputName; - // Push the values into the array - for (let i = 0; i < arrayLiteralContents.length; i++) { - // Create a new variable to hold the size of the array value - const size = FIXED_TYPES.includes( - arrayLiteralContents[i].outputType.typename, - ) - ? '8' - : '0'; - const sizeName = '_' + uuid().replace(/-/g, '_'); - microstatements.push( - new Microstatement( - StatementType.CONSTDEC, - scope, - true, - sizeName, - Type.builtinTypes['int64'], - [size], - [], - ), - ); - // Push the value into the array - const opcodes = require('./opcodes').default; - opcodes.exportScope - .get('pusharr')[0] - .microstatementInlining( - [arrayName, arrayLiteralContents[i].outputName, sizeName], - scope, - microstatements, - ); - } - // REREF the array - microstatements.push( - new Microstatement( - StatementType.REREF, - scope, - true, - arrayName, - array.outputType, - [], - [], - ), - ); - } - } - - static closureDef( - fns: Array, - scope: Scope, - microstatements: Array, - ) { - const closuredefName = '_' + uuid().replace(/-/g, '_'); - // Keep any rerefs around as closure references - const rerefs = microstatements.filter( - (m) => m.statementType === StatementType.REREF, - ); - microstatements.push( - new Microstatement( - StatementType.CLOSUREDEF, - scope, - true, // TODO: What should this be? - closuredefName, - Type.builtinTypes['function'], - [], - fns, - '', - true, - rerefs, - ), - ); - } - - static closureFromUserFunction( - userFunction: UserFunction, - scope: Scope, - microstatements: Array, - interfaceMap: Map, - ) { - const fn = userFunction.maybeTransform(interfaceMap); - const idx = microstatements.length; - const args = Object.entries(fn.args); - for (const [name, type] of args) { - if (name !== '' && type.typename != '') { - microstatements.push( - new Microstatement(StatementType.LETDEC, scope, true, name, type), - ); - } - } - const len = microstatements.length - args.length; - for (const s of fn.statements) { - Microstatement.fromStatementsAst(s.statementAst, scope, microstatements); - } - microstatements.splice(idx, args.length); - const newlen = microstatements.length; - // There might be off-by-one bugs in the conversion here - const innerMicrostatements = microstatements.slice(len, newlen); - microstatements.splice(len, newlen - len); - const constName = '_' + uuid().replace(/-/g, '_'); - // if closure is not void return the last inner statement - // TODO: Revisit this, if the closure doesn't have a type defined, sometimes it can only be - // determined in the calling context and shouldn't be assumed to be `void` - if ( - innerMicrostatements.length > 0 && - fn.getReturnType() !== Type.builtinTypes.void - ) { - const last = innerMicrostatements[innerMicrostatements.length - 1]; - innerMicrostatements.push( - new Microstatement( - StatementType.EXIT, - scope, - true, - last.outputName, - last.outputType, - ), - ); - } - microstatements.push( - new Microstatement( - StatementType.CLOSURE, - scope, - true, // TODO: Figure out if this is true or not - constName, - Type.builtinTypes['function'], - [], - [], - '', - fn.pure, - innerMicrostatements, - fn.args, - fn.getReturnType(), - ), - ); - } - - static fromEmitsAst( - emitsAst: LPNode, - scope: Scope, - microstatements: Array, - ) { - if (emitsAst.get('retval').has()) { - // If there's an assignable value here, add it to the list of microstatements first, then - // rewrite the final const assignment as the emit statement. - Microstatement.fromAssignablesAst( - emitsAst.get('retval').get('assignables'), - scope, - microstatements, - ); - const event = scope.deepGet(emitsAst.get('eventname').t); - if (!(event instanceof Event)) { - throw new Error(`${emitsAst.get('eventname').t} is not an event! -${emitsAst.t} on line ${emitsAst.line}:${emitsAst.char}`); - } - const last = microstatements[microstatements.length - 1]; - if ( - last.outputType != event.type && - !event.type.castable(last.outputType) - ) { - throw new Error(`Attempting to assign a value of type ${last.outputType.typename} to an event of type ${event.type.typename} -${emitsAst.t} on line ${emitsAst.line}:${emitsAst.char}`); - } - microstatements.push( - new Microstatement( - StatementType.EMIT, - scope, - true, - event.name, - event.type, - [last.outputName], - [], - ), - ); - } else { - // Otherwise, create an emit statement with no value - const event = scope.deepGet(emitsAst.get('eventname').t) as Event; - if (!(event instanceof Event)) { - throw new Error(`${emitsAst.get('eventname').t} is not an event! -${emitsAst.t} on line ${emitsAst.line}:${emitsAst.char}`); - } - if (event.type != Type.builtinTypes.void) { - throw new Error(`${emitsAst.get('eventname').t} must have a ${ - event.type - } value emitted to it! -${emitsAst.t} on line ${emitsAst.line}:${emitsAst.char}`); - } - microstatements.push( - new Microstatement( - StatementType.EMIT, - scope, - true, - event.name, - Type.builtinTypes.void, - [], - [], - ), - ); - } - } - - static fromExitsAst( - exitsAst: LPNode, - scope: Scope, - microstatements: Array, - ) { - // `alan--` handlers don't have the concept of a `return` statement, the functions are all inlined - // and the last assigned value for the function *is* the return statement - if (exitsAst.get('retval').has()) { - // If there's an assignable value here, add it to the list of microstatements - Microstatement.fromAssignablesAst( - exitsAst.get('retval').get('assignables'), - scope, - microstatements, - ); - } else { - // Otherwise, create a microstatement with no value - const constName = '_' + uuid().replace(/-/g, '_'); - microstatements.push( - new Microstatement( - StatementType.CONSTDEC, - scope, - true, - constName, - Type.builtinTypes.void, - ['void'], - null, - ), - ); - } - } - - static fromAssignmentsAst( - assignmentsAst: LPNode, - scope: Scope, - microstatements: Array, - ) { - // For reassigning to a variable, we need to determine that the root variable is a - // `let`-defined mutable variable and then tease out if any array or property accesses are done, - // and if so we need to `register` a mutable reference to the array memory space and then update - // the value with a `register` call from the assignables result address to the relevant inner - // address of the last access argument. The format of a `varn` can only be the following: - // `{moduleScope}.varName[arrayAccess].userProperty` where the array accesses and userProperties - // can come in any order after the preamble. *Fortunately,* for this scenario, any situation - // where `moduleScope` is included is invalid since only constants can be exported out of a - // module, not mutable values, so we only need to read the *first* segment to immediately - // determine if it is relevant or not -- if it comes back as a `Scope` object we abort with an - // error. If not, then we find the relevant `Microstatement` and determine if it is a `const` - // or a `let` declaration and abort if it is a `const`. After that, if there are no segments - // beyond the first one, we simply take the `assignable` microstatement output and turn it into - // an `ASSIGNMENT` StatementType, otherwise we need to go through a more complicated procedure - // to `register` the `n-1` remaining inner array segments to new variables as references and - // finally `register` the `assignable` into the location the last segment indicates. - const segments = assignmentsAst.get('varn').getAll(); - // Now, find the original variable and confirm that it actually is a let declaration - const letName = segments[0].t; - let actualLetName: string; - let original: Microstatement; - for (let i = microstatements.length - 1; i >= 0; i--) { - const microstatement = microstatements[i]; - if (microstatement.alias === letName) { - actualLetName = microstatement.outputName; - continue; - } - if (microstatement.outputName === actualLetName) { - if (microstatement.statementType === StatementType.LETDEC) { - original = microstatement; - break; - } else if (microstatement.statementType === StatementType.REREF) { - original = Microstatement.fromVarName( - microstatement.outputName, - scope, - microstatements, - ); - break; - } else if (microstatement.statementType === StatementType.ASSIGNMENT) { - // We could treat this as evidence that it's cool, but let's just skip it. - continue; - } else { - throw new Error(`Attempting to reassign a non-let variable. -${assignmentsAst.t} on line ${assignmentsAst.line}:${assignmentsAst.char}`); - } - } - if (microstatement.outputName === letName) { - original = microstatement; - } - } - if (!original) { - throw new Error(`Attempting to reassign to an undeclared variable -${assignmentsAst.t} on line ${assignmentsAst.line}:${assignmentsAst.char}`); - } - if (segments.length === 1) { - // Could be a simple let variable - const letName = segments[0].t; - let actualLetName: string; - for (let i = microstatements.length - 1; i >= 0; i--) { - const microstatement = microstatements[i]; - if (microstatement.alias === letName) { - actualLetName = microstatement.outputName; - continue; - } - if (microstatement.outputName === actualLetName) { - if (microstatement.statementType === StatementType.LETDEC) { - break; - } else if (microstatement.statementType === StatementType.REREF) { - original = Microstatement.fromVarName( - microstatement.outputName, - scope, - microstatements, - ); - break; - } else if ( - microstatement.statementType === StatementType.ASSIGNMENT - ) { - // Could treat this as evidence that it's okay, but let's be sure about that - continue; - } else { - throw new Error(`Attempting to reassign a non-let variable. -${letName} on line ${assignmentsAst.line}:${assignmentsAst.char}`); - } - } - if (microstatement.outputName === letName) { - actualLetName = letName; - } - } - Microstatement.fromAssignablesAst( - assignmentsAst.get('assignables'), - scope, - microstatements, - ); - // By definition the last microstatement is the const assignment we care about, so we can - // just mutate its object to rename the output variable name to the name we need instead. - let last = microstatements[microstatements.length - 1]; - if (last.statementType === StatementType.REREF) { - // Find what it's rereferencing and adjust that, instead - for (let i = microstatements.length - 2; i >= 0; i--) { - const m = microstatements[i]; - if ( - m.outputName === last.outputName && - m.statementType !== StatementType.REREF - ) { - last = m; - break; - } - } - } - if (last.statementType === StatementType.LETDEC) { - // Insert a ref call for this instead of mutating the original assignment - Microstatement.fromAssignablesAst( - Ast.assignablesAstFromString(`ref(${last.outputName})`), - scope, - microstatements, - ); - last = microstatements[microstatements.length - 1]; - if (last.statementType === StatementType.REREF) { - // Find what it's rereferencing and adjust that, instead - for (let i = microstatements.length - 2; i >= 0; i--) { - const m = microstatements[i]; - if ( - m.outputName === last.outputName && - m.statementType !== StatementType.REREF - ) { - last = m; - break; - } - } - } - } - last.outputName = actualLetName; - last.statementType = StatementType.ASSIGNMENT; - // Attempt to "merge" the output types, useful for multiple branches assigning into the same - // variable but only part of the type information is known in each branch (like in `Result` - // or `Either` with the result value only in one branch or one type in each of the branches - // for `Either`). - if (original.outputType.typename !== last.outputType.typename) { - if (original.outputType.iface) { - // Just overwrite if it's an interface type - original.outputType = last.outputType; - } else if ( - !!original.outputType.originalType && - !!last.outputType.originalType && - original.outputType.originalType.typename === - last.outputType.originalType.typename - ) { - // The tricky path, let's try to merge the two types together - const baseType = original.outputType.originalType; - const originalTypeAst = Ast.fulltypenameAstFromString( - original.outputType.typename, - ); - const lastTypeAst = Ast.fulltypenameAstFromString( - last.outputType.typename, - ); - const originalSubtypes = []; - if (originalTypeAst.has('opttypegenerics')) { - const originalTypeGenerics = originalTypeAst - .get('opttypegenerics') - .get('generics'); - originalSubtypes.push(originalTypeGenerics.get('fulltypename').t); - originalTypeGenerics - .get('cdr') - .getAll() - .forEach((r) => { - originalSubtypes.push(r.get('fulltypename').t); - }); - } - const lastSubtypes = []; - if (lastTypeAst.has('opttypegenerics')) { - const lastTypeGenerics = lastTypeAst - .get('opttypegenerics') - .get('generics'); - lastSubtypes.push(lastTypeGenerics.get('fulltypename').t); - lastTypeGenerics - .get('cdr') - .getAll() - .forEach((r) => { - lastSubtypes.push(r.get('fulltypename').t); - }); - } - const newSubtypes = []; - for (let i = 0; i < originalSubtypes.length; i++) { - if (originalSubtypes[i] === lastSubtypes[i]) { - newSubtypes.push(originalSubtypes[i]); - } else { - const originalSubtype = scope.deepGet( - originalSubtypes[i], - ) as Type; - if (originalSubtype.iface) { - newSubtypes.push(lastSubtypes[i]); - } else if (originalSubtype.originalType) { - // TODO: Support nesting - newSubtypes.push(originalSubtypes[i]); - } else { - newSubtypes.push(originalSubtypes[i]); - } - } - } - const newType = baseType.solidify(newSubtypes, scope); - original.outputType = newType; - } else { - // Hmm... what to do here? - original.outputType = last.outputType; - } - } - return; - } - // The more complicated path. First, rule out that the first segment is not a `scope`. - const test = scope.deepGet(segments[0].t); - if (!!test && test instanceof Scope) { - throw new Error(`Atempting to reassign to variable from another module -${assignmentsAst.get('varn').t} on line ${assignmentsAst.line}:${ - assignmentsAst.char - }`); - } - let nestedLetType = original.outputType; - for (let i = 1; i < segments.length - 1; i++) { - const segment = segments[i]; - // A separator, just do nothing else this loop - if (segment.has('methodsep')) continue; - // An array access. Until the grammar definition is reworked, this will parse correctly, but - // it is banned in alan (due to being unable to catch and report assignment errors to arrays) - if (segment.has('arrayaccess')) { - throw new Error( - `${segments.join( - '', - )} cannot be written to. Please use 'set' to mutate arrays and hash tables`, - ); - } - // If it's a varname here, then we're accessing an inner property type. We need to figure out - // which index it is in the underlying array structure and then `register` that piece (since - // this is an intermediate access and not the final access point) - if (segment.has('variable')) { - const fieldName = segment.get('variable').t; - const fields = Object.keys(nestedLetType.properties); - const fieldNum = fields.indexOf(fieldName); - if (fieldNum < 0) { - // Invalid object access - throw new Error(`${letName} does not have a field named ${fieldName} -${assignmentsAst.get('varn').t} on line ${assignmentsAst.get('varn').line}:${ - assignmentsAst.get('varn').char - }`); - } - // Create a new variable to hold the address within the array literal - const addrName = '_' + uuid().replace(/-/g, '_'); - microstatements.push( - new Microstatement( - StatementType.CONSTDEC, - scope, - true, - addrName, - Type.builtinTypes['int64'], - [`${fieldNum}`], - [], - ), - ); - // Insert a `register` opcode. - const opcodes = require('./opcodes').default; - opcodes.exportScope - .get('register')[0] - .microstatementInlining( - [original.outputName, addrName], - scope, - microstatements, - ); - // Now, we need to update the type we're working with. - nestedLetType = Object.values(nestedLetType.properties)[fieldNum]; - // Now update the `original` record to the new `register` result - original = microstatements[microstatements.length - 1]; - } - } - Microstatement.fromAssignablesAst( - assignmentsAst.get('assignables'), - scope, - microstatements, - ); - // Grab a reference to the final assignment variable. - const assign = microstatements[microstatements.length - 1]; - // Next, determine which kind of final segment this is and perform the appropriate action to - // insert into with a `copytof` or `copytov` opcode. - const copytoop = [ - 'int8', - 'int16', - 'int32', - 'int64', - 'float32', - 'float64', - 'bool', - ].includes(assign.outputType.typename) - ? 'copytof' - : 'copytov'; - const finalSegment = segments[segments.length - 1]; - if (finalSegment.has('variable')) { - const fieldName = finalSegment.t; - const fields = Object.keys(nestedLetType.properties); - const fieldNum = fields.indexOf(fieldName); - if (fieldNum < 0) { - // Invalid object access - throw new Error(`${letName} does not have a field named ${fieldName} -${letName} on line ${assignmentsAst.line}:${assignmentsAst.char}`); - } - // Check if the new variable is allowed to be assigned to this object - const originalType = nestedLetType.properties[fieldName]; - if (!originalType.typeApplies(assign.outputType, scope)) { - throw new Error( - `${letName}.${fieldName} is of type ${originalType.typename} but assigned a value of type ${assign.outputType.typename}`, - ); - } - // Create a new variable to hold the address within the array literal - const addrName = '_' + uuid().replace(/-/g, '_'); - microstatements.push( - new Microstatement( - StatementType.CONSTDEC, - scope, - true, - addrName, - Type.builtinTypes['int64'], - [`${fieldNum}`], - [], - ), - ); - // Insert a `copytof` or `copytov` opcode. - const opcodes = require('./opcodes').default; - opcodes.exportScope - .get(copytoop)[0] - .microstatementInlining( - [original.outputName, addrName, assign.outputName], - scope, - microstatements, - ); - } else { - throw new Error(`${finalSegment.t} cannot be the final piece in a reassignment statement -${letName} on line ${assignmentsAst.line}:${assignmentsAst.char}`); - } - } - - static fromLetdeclarationAst( - letdeclarationAst: LPNode, - scope: Scope, - microstatements: Array, - ) { - const letAlias = letdeclarationAst.get('variable').t; - const letTypeHint = letdeclarationAst.get('typedec').has() - ? letdeclarationAst.get('typedec').get('fulltypename').t - : ''; - const type = scope.deepGet(letTypeHint); - if (type === null && letTypeHint !== '') { - // Try to define it if it's a generic type - const letTypeAst = letdeclarationAst.get('typedec').get('fulltypename'); - if (letTypeAst.has('opttypegenerics')) { - const outerType = scope.deepGet(letTypeAst.get('typename').t) as Type; - if (outerType === null) { - throw new Error(`${letTypeAst.get('typename').t} is not defined -${letdeclarationAst.t} on line ${letdeclarationAst.line}:${ - letdeclarationAst.char - }`); - } - const generics = []; - const genericAst = letTypeAst.get('opttypegenerics').get('generics'); - generics.push(genericAst.get('fulltypename').t); - genericAst - .get('cdr') - .getAll() - .forEach((r) => { - generics.push(r.get('fulltypename').t); - }); - outerType.solidify(generics, scope); - } - } - Microstatement.fromAssignablesAst( - letdeclarationAst.get('assignables'), - scope, - microstatements, - ); - // By definition the last microstatement is the const assignment we care about, so we can just - // mutate its object to rename the output variable name to the name we need instead. - // EXCEPT with Arrays and User Types. The last is a REREF, so follow it back to the original - // and mutate that, instead - let val = microstatements[microstatements.length - 1]; - if (val.statementType === StatementType.REREF) { - val = Microstatement.fromVarName(val.alias, scope, microstatements); - } - if (val.outputType === Type.builtinTypes.string) { - // Special logic just for strings because hoisting them later is hard - const opcodes = require('./opcodes').default; - const copystr = opcodes.exportScope.get('copystr'); - microstatements.push( - new Microstatement( - StatementType.LETDEC, - scope, - true, - val.outputName + 'n', // TODO: Generate a new UUID here? - val.outputType, - [val.outputName], - copystr, - ), - ); - microstatements.push( - new Microstatement( - StatementType.REREF, - scope, - true, - val.outputName + 'n', - val.outputType, - [], - [], - letAlias, - ), - ); - } else { - val.statementType = StatementType.LETDEC; - microstatements.push( - new Microstatement( - StatementType.REREF, - scope, - true, - val.outputName, - val.outputType, - [], - [], - letAlias, - ), - ); - } - } - - static fromConstdeclarationAst( - constdeclarationAst: LPNode, - scope: Scope, - microstatements: Array, - ) { - const constName = '_' + uuid().replace(/-/g, '_'); - const constAlias = constdeclarationAst.get('variable').t; - const constTypeHint = constdeclarationAst.get('typedec').has() - ? constdeclarationAst.get('typedec').get('fulltypename').t - : ''; - const type = scope.deepGet(constTypeHint); - if (type === null && constTypeHint !== '') { - // Try to define it if it's a generic type - const constTypeAst = constdeclarationAst - .get('typedec') - .get('fulltypename'); - if (constTypeAst.has('opttypegenerics')) { - const outerType = scope.deepGet(constTypeAst.get('typename').t) as Type; - if (outerType === null) { - throw new Error(`${constTypeAst.get('typename').t} is not defined -${constdeclarationAst.t} on line ${constdeclarationAst.line}:${ - constdeclarationAst.char - }`); - } - const generics = []; - const genericAst = constTypeAst.get('opttypegenerics').get('generics'); - generics.push(genericAst.get('fulltypename').t); - genericAst - .get('cdr') - .getAll() - .forEach((r) => { - generics.push(r.get('fulltypename').t); - }); - outerType.solidify(generics, scope); - } - } - Microstatement.fromAssignablesAst( - constdeclarationAst.get('assignables'), - scope, - microstatements, - ); - // By definition the last microstatement is the const assignment we care about, so we can just - // mutate its object to rename the output variable name to the name we need instead. - microstatements.push( - new Microstatement( - StatementType.REREF, - scope, - true, - microstatements[microstatements.length - 1].outputName, - microstatements[microstatements.length - 1].outputType, - [], - [], - constAlias, - ), - ); - } - - // DFS recursive algo to get the microstatements in a valid ordering - static fromStatementsAst( - statementAst: LPNode, - scope: Scope, - microstatements: Array, - ) { - if (statementAst.has('declarations')) { - if (statementAst.get('declarations').has('constdeclaration')) { - Microstatement.fromConstdeclarationAst( - statementAst.get('declarations').get('constdeclaration'), - scope, - microstatements, - ); - } else { - Microstatement.fromLetdeclarationAst( - statementAst.get('declarations').get('letdeclaration'), - scope, - microstatements, - ); - } - } - if (statementAst.has('assignments')) { - Microstatement.fromAssignmentsAst( - statementAst.get('assignments'), - scope, - microstatements, - ); - } - if (statementAst.has('assignables')) { - Microstatement.fromAssignablesAst( - statementAst.get('assignables').get('assignables'), - scope, - microstatements, - ); - } - if (statementAst.has('exits')) { - Microstatement.fromExitsAst( - statementAst.get('exits'), - scope, - microstatements, - ); - } - if (statementAst.has('emits')) { - Microstatement.fromEmitsAst( - statementAst.get('emits'), - scope, - microstatements, - ); - } - - return microstatements; - } - - static fromBaseAssignableAst( - baseAssignableAsts: LPNode[], - scope: Scope, - microstatements: Array, - ) { - // The base assignables array are a lightly annotated set of primitives that can be combined - // together to produce an assignable value. Certain combinations of these primitives are invalid - // and TODO provide good error messaging when these are encountered. A state machine of valid - // transitions is defined below: - // - // null -> { var, obj, fn, const, group } - // var -> { dot, arraccess, call, eos } - // obj -> { dot, arraccess, eos } - // fn -> { call, eos } - // const -> { dot, eos } - // group -> { dot, arraccess, eos } - // call -> { call, arraccess, dot, eos } - // arraccess -> { arraccess, dot, call, eos } - // - // Where `null` is the initial state and `eos` is end-of-statement terminating state. `var` is - // some variable-name-like value (could be a scope, variable, property, or function name). `obj` - // is object literal syntax, `fn` is function literal syntax, `const` is a constant literal. - // `group)` is re-using the function call syntax to handle operator grouping (eg `2 * (3 + 4)`). - // Because of how operators are mixed in with the assignables, the only time this syntax is used - // as an operator grouping syntax is if it is the first element in the array. Otherwise it is - // being used as a function call for a given function (either defined by a variable, an - // inline-defined function, or a returned function from another call or array access) as `call`. - // Finally `arraccess` is when an array (and ideally later a HashMap) is accessed. This mode is - // also abusing the `obj` syntax, but only when it's an array literal with only one value and no - // `new Array` type definition *and* when there are prior elements in the list. This means - // `[0][0]` is unambiguous and would return a Result-wrapped zero value, for instance. - // - // The exact meaning of `var.var...` chains varies based on the elements of the array both - // before and after such a chain. If the start of such a list, and if a `call` is at the end, it - // could be something like `scope.variable.property.functionName(args)` where `.property` can - // repeat multiple times over. Basically, to properly parse any `.var` requires both the prior - // state *and* look-ahead to the next element in the list. - // - // All of this to re-iterate that for the sake of compile time, some of the complexities of the - // grammar have been moved from the LP definition into the compiler itself for performance - // reasons, explaining the complicated iterative logic that follows. - - let currVal: any = null; - for (let i = 0; i < baseAssignableAsts.length; i++) { - const baseassignable = baseAssignableAsts[i].get('baseassignable'); - if (baseassignable.has('methodsep')) { - if (i === 0) { - throw new Error(`Invalid start of assignable statement. Cannot begin with a dot (.) -${baseassignable.t} on line ${baseassignable.line}:${baseassignable.char}`); - } - const prevassignable = baseAssignableAsts[i - 1].get('baseassignable'); - if (prevassignable.has('methodsep')) { - throw new Error(`Invalid property access. You accidentally typed a dot twice in a row. -${baseassignable.t} on line ${baseassignable.line}:${baseassignable.char}`); - } else if (prevassignable.has('functions')) { - throw new Error(`Invalid property access. Functions do not have properties. -${baseassignable.t} on line ${baseassignable.line}:${baseassignable.char}`); - } - // TODO: Do we even do anything else in this branch? - } else if (baseassignable.has('variable')) { - const nextassignable = baseAssignableAsts[i + 1] - ? baseAssignableAsts[i + 1].get('baseassignable') - : undefined; - if (!!nextassignable && nextassignable.has('fncall')) { - // This is a function call path - const fncall = nextassignable.get('fncall'); - const argAsts = []; - if (fncall.get('assignablelist').has()) { - argAsts.push(fncall.get('assignablelist').get('assignables')); - fncall - .get('assignablelist') - .get('cdr') - .getAll() - .forEach((r) => { - argAsts.push(r.get('assignables')); - }); - } - const argMicrostatements = argAsts.map((arg) => { - Microstatement.fromAssignablesAst(arg, scope, microstatements); - return microstatements[microstatements.length - 1]; - }); - if (currVal === null) { - // This is a basic function call - const realArgNames = argMicrostatements.map( - (arg) => arg.outputName, - ); - const realArgTypes = argMicrostatements.map( - (arg) => arg.outputType, - ); - // Do a scan of the microstatements for an inner defined closure that might exist. - const fn = scope.deepGet( - baseassignable.get('variable').t, - ) as Array; - if ( - !fn || - !( - fn instanceof Array && - fn[0].microstatementInlining instanceof Function - ) - ) { - const fnName = baseassignable.get('variable').t; - let actualFnName: string; - let inlinedClosure = false; - for (let i = microstatements.length - 1; i >= 0; i--) { - if (microstatements[i].alias === fnName) { - actualFnName = microstatements[i].outputName; - continue; - } - if ( - microstatements[i].outputName === actualFnName && - microstatements[i].statementType === StatementType.CLOSUREDEF - ) { - const m = [ - ...microstatements, - ...microstatements[i].closureStatements, - ]; - const fn = UserFunction.dispatchFn( - microstatements[i].fns, - realArgTypes, - scope, - ); - const interfaceMap = new Map(); - Object.values(fn.getArguments()).forEach((t: Type, i) => - t.typeApplies(realArgTypes[i], scope, interfaceMap), - ); - Microstatement.closureFromUserFunction( - fn, - fn.scope || scope, - m, - interfaceMap, - ); - const closure = m.pop(); - microstatements.push( - ...closure.closureStatements.filter( - (s) => s.statementType !== StatementType.EXIT, - ), - ); - currVal = microstatements[microstatements.length - 1]; - inlinedClosure = true; - break; - } - } - if (!inlinedClosure) { - throw new Error(`${ - baseassignable.get('variable').t - } is not a function but used as one. -${baseassignable.t} on line ${baseassignable.line}:${baseassignable.char}`); - } - } else { - // Generate the relevant microstatements for this function. UserFunctions get inlined - // with the return statement turned into a const assignment as the last statement, - // while built-in functions are kept as function calls with the correct renaming. - UserFunction.dispatchFn( - fn, - realArgTypes, - scope, - ).microstatementInlining(realArgNames, scope, microstatements); - currVal = microstatements[microstatements.length - 1]; - } - } else if (currVal instanceof Scope) { - // This is calling a function by its parent scope - const realArgNames = argMicrostatements.map( - (arg) => arg.outputName, - ); - const realArgTypes = argMicrostatements.map( - (arg) => arg.outputType, - ); - const fn = currVal.deepGet( - baseassignable.get('variable').t, - ) as Array; - if ( - !fn || - !( - fn instanceof Array && - fn[0].microstatementInlining instanceof Function - ) - ) { - throw new Error(`${ - baseassignable.get('variable').t - } is not a function but used as one. -${baseassignable.t} on line ${baseassignable.line}:${baseassignable.char}`); - } - // Generate the relevant microstatements for this function. UserFunctions get inlined - // with the return statement turned into a const assignment as the last statement, - // while built-in functions are kept as function calls with the correct renaming. - UserFunction.dispatchFn( - fn, - realArgTypes, - scope, - ).microstatementInlining(realArgNames, scope, microstatements); - currVal = microstatements[microstatements.length - 1]; - } else { - // It's a method-style function call - const realArgNames = [ - currVal.outputName, - ...argMicrostatements.map((arg) => arg.outputName), - ]; - const realArgTypes = [ - currVal.outputType, - ...argMicrostatements.map((arg) => arg.outputType), - ]; - const fn = scope.deepGet( - baseassignable.get('variable').t, - ) as Array; - if ( - !fn || - !( - fn instanceof Array && - fn[0].microstatementInlining instanceof Function - ) - ) { - throw new Error(`${ - baseassignable.get('variable').t - } is not a function but used as one. -${baseassignable.t} on line ${baseassignable.line}:${baseassignable.char}`); - } - // Generate the relevant microstatements for this function. UserFunctions get inlined - // with the return statement turned into a const assignment as the last statement, - // while built-in functions are kept as function calls with the correct renaming. - UserFunction.dispatchFn( - fn, - realArgTypes, - scope, - ).microstatementInlining(realArgNames, scope, microstatements); - currVal = microstatements[microstatements.length - 1]; - } - // Intentionally skip over the `fncall` block on the next iteration - i++; - } else { - if (currVal === null) { - let thing = Microstatement.fromVarName( - baseassignable.get('variable').t, - scope, - microstatements, - ); - if (!thing) { - thing = scope.deepGet(baseassignable.get('variable').t); - } - if (!thing) { - throw new Error(`${baseassignable.get('variable').t} not found. - ${baseassignable.t} on line ${baseassignable.line}:${baseassignable.char}`); - } - currVal = thing; - } else if (currVal instanceof Scope) { - const thing = currVal.deepGet(baseassignable.get('variable').t); - if (!thing) { - throw new Error(`${ - baseassignable.get('variable').t - } not found in other scope. - ${baseassignable.t} on line ${baseassignable.line}:${baseassignable.char}`); - } - currVal = thing; - } else if (currVal instanceof Microstatement) { - const fieldName = baseassignable.get('variable').t; - const fields = Object.keys(currVal.outputType.properties); - const fieldNum = fields.indexOf(fieldName); - if (fieldNum < 0) { - // Invalid object access - throw new Error(`${fieldName} property not found. - ${baseassignable.t} on line ${baseassignable.line}:${baseassignable.char}`); - } - // Create a new variable to hold the address within the array literal - const addrName = '_' + uuid().replace(/-/g, '_'); - microstatements.push( - new Microstatement( - StatementType.CONSTDEC, - scope, - true, - addrName, - Type.builtinTypes['int64'], - [`${fieldNum}`], - [], - ), - ); - // Insert a `register` opcode. - const opcodes = require('./opcodes').default; - opcodes.exportScope - .get('register')[0] - .microstatementInlining( - [currVal.outputName, addrName], - scope, - microstatements, - ); - // We'll need a reference to this for later - const typeRecord = currVal; - // Set the original to this newly-generated microstatement - currVal = microstatements[microstatements.length - 1]; - // Now we do something odd, but correct here; we need to replace the `outputType` from - // `any` to the type that was actually copied so function resolution continues to work - currVal.outputType = typeRecord.outputType.properties[fieldName]; - } else { - // What is this? - throw new Error(`Impossible path found. Bug in compiler, please report! -Previous value type: ${typeof currVal} -${baseassignable.t} on line ${baseassignable.line}:${baseassignable.char}`); - } - } - } else if (baseassignable.has('constants')) { - if (currVal !== null) { - throw new Error(`Unexpected constant value detected. -Previous value type: ${typeof currVal} -${baseassignable.t} on line ${baseassignable.line}:${baseassignable.char}`); - } - Microstatement.fromConstantsAst( - baseassignable.get('constants'), - scope, - microstatements, - ); - currVal = microstatements[microstatements.length - 1]; - } else if (baseassignable.has('functions')) { - if (currVal !== null) { - throw new Error(`Unexpected function definition detected. -Previous value type: ${typeof currVal} -${baseassignable.t} on line ${baseassignable.line}:${baseassignable.char}`); - } - // So the closures eval correctly, we add the alias microstatements to the scope - // TODO: Is this the right approach? - microstatements - .filter((m) => !!m.alias) - .forEach((m) => scope.put(m.alias, m)); - const fn = UserFunction.fromFunctionsAst( - baseassignable.get('functions'), - scope, - ); - currVal = fn; - } else if (baseassignable.has('objectliterals')) { - if (currVal === null) { - // Has to be a "normal" object literal in this case - Microstatement.fromObjectLiteralsAst( - baseassignable.get('objectliterals'), - scope, - microstatements, - ); - currVal = microstatements[microstatements.length - 1]; - } else { - // Can only be an array accessor syntax - const objlit = baseassignable.get('objectliterals'); - if ( - objlit.has('typeliteral') || - objlit.get('arrayliteral').has('fullarrayliteral') - ) { - throw new Error(`Unexpected object literal definition detected. -Previous value type: ${typeof currVal} -${baseassignable.t} on line ${baseassignable.line}:${baseassignable.char}`); - } - const arrbase = objlit.get('arrayliteral').get('arraybase'); - if ( - !arrbase.get('assignablelist').has() || - arrbase.get('assignablelist').get('cdr').getAll().length > 0 - ) { - throw new Error(`Array access must provide only one index value to query the array with -${baseassignable.t} on line ${baseassignable.line}:${baseassignable.char}`); - } - const assignableAst = arrbase - .get('assignablelist') - .get('assignables'); - Microstatement.fromAssignablesAst( - assignableAst, - scope, - microstatements, - ); - const arrIndex = microstatements[microstatements.length - 1]; - if ( - !(currVal instanceof Microstatement) || - currVal.outputType.originalType.typename !== 'Array' - ) { - throw new Error(`Array access may only be performed on arrays. -Previous value type: ${currVal.outputType.typename} -${baseassignable.t} on line ${baseassignable.line}:${baseassignable.char}`); - } - if (arrIndex.outputType.typename === 'int64') { - const opcodes = require('./opcodes').default; - // Create a new variable to hold the `okR` size value - const sizeName = '_' + uuid().replace(/-/g, '_'); - microstatements.push( - new Microstatement( - StatementType.CONSTDEC, - scope, - true, - sizeName, - Type.builtinTypes['int64'], - ['8'], - [], - ), - ); - // Insert an `okR` opcode. - opcodes.exportScope - .get('okR')[0] - .microstatementInlining( - [arrIndex.outputName, sizeName], - scope, - microstatements, - ); - const wrapped = microstatements[microstatements.length - 1]; - // Insert a `resfrom` opcode. - opcodes.exportScope - .get('resfrom')[0] - .microstatementInlining( - [currVal.outputName, wrapped.outputName], - scope, - microstatements, - ); - } else if (arrIndex.outputType.typename === 'Result') { - const opcodes = require('./opcodes').default; - // Insert a `resfrom` opcode. - opcodes.exportScope - .get('resfrom')[0] - .microstatementInlining( - [currVal.outputName, arrIndex.outputName], - scope, - microstatements, - ); - } else { - throw new Error(`Array access must be done with an int64 or Result value -${baseassignable.t} on line ${baseassignable.line}:${baseassignable.char}`); - } - // We'll need a reference to this for later - const arrayRecord = currVal; - // Update to this newly-generated microstatement - currVal = microstatements[microstatements.length - 1]; - // Now we do something odd, but correct here; we need to replace the `outputType` from - // `any` to the type that was actually copied so function resolution continues to work - currVal.outputType = Type.builtinTypes.Result.solidify( - [Object.values(arrayRecord.outputType.properties)[0].typename], - scope, - ); - } - } else if (baseassignable.has('fncall')) { - // It's a `fncall` syntax block but it wasn't caught in a function call before, so it's - // either a function call on a returned function type, or it's an assignable group - if (!currVal) { - // It's probably an assignable group - if ( - !baseassignable.get('fncall').get('assignablelist').has() || - baseassignable - .get('fncall') - .get('assignablelist') - .get('cdr') - .getAll().length > 0 - ) { - throw new Error(`Expected a group of assignable values, but got a function call signature. -${baseassignable.t} on line ${baseassignable.line}:${baseassignable.char}`); - } - // It *is* an assignable group! - Microstatement.fromAssignablesAst( - baseassignable - .get('fncall') - .get('assignablelist') - .get('assignables'), - scope, - microstatements, - ); - currVal = microstatements[microstatements.length - 1]; - } else { - // TODO: handle functions/closures being called from access out of other function returns - // and the like - } - } else { - throw new Error(`Compiler error! Completely unhandled input. -${baseassignable.t} on line ${baseassignable.line}:${baseassignable.char}`); - } - } - if (!(currVal instanceof Microstatement)) { - if (currVal instanceof UserFunction) { - Microstatement.closureDef( - [currVal], - currVal.scope || scope, - microstatements, - ); - } else if ( - currVal instanceof Array && - currVal[0] instanceof UserFunction - ) { - Microstatement.closureDef( - currVal, - currVal[0].scope || scope, - microstatements, - ); - } - } else if (currVal.statementType !== StatementType.EMIT) { - microstatements.push( - new Microstatement( - StatementType.REREF, - scope, - true, - currVal.outputName, - currVal.outputType, - [], - [], - currVal.alias, - ), - ); - } - } - - static fromAssignablesAst( - assignablesAst: LPNode, - scope: Scope, - microstatements: Array, - ) { - const withoperators = assignablesAst.getAll(); - const withOperatorsList = []; - for (const operatorOrAssignable of withoperators) { - if (operatorOrAssignable.get('withoperators').has('operators')) { - const operator = operatorOrAssignable - .get('withoperators') - .get('operators') - .get(1); - const op = scope.get(operator.t); - if (op == null || !(op instanceof Array && op[0] instanceof Operator)) { - throw new Error('Operator ' + operator.t + ' is not defined'); - } - withOperatorsList.push(op); - } else if ( - operatorOrAssignable.get('withoperators').has('baseassignablelist') - ) { - Microstatement.fromBaseAssignableAst( - operatorOrAssignable - .get('withoperators') - .get('baseassignablelist') - .getAll(), - scope, - microstatements, - ); - const last = microstatements[microstatements.length - 1]; - withOperatorsList.push(last); - } - } - // Now to combine these operators and values in the correct order. A compiled language could - // never do something so inefficient, but I don't care about performance right now, so here's - // the algorithm: while the list length is greater than 1, perform the two steps: - // 1. Find the operator with the greatest precedence - // 2. Apply the underlying function to the values on either side of the operator (or just the - // right side if the operator is a prefix operator), then replace the operator with the - // returned value in the list and delete the impacted values. - while (withOperatorsList.length > 1) { - let maxPrecedence = -1; - let maxOperatorLoc = -1; - let maxOperatorListLoc = -1; - for (let i = 0; i < withOperatorsList.length; i++) { - if ( - withOperatorsList[i] instanceof Array && - withOperatorsList[i][0] instanceof Operator - ) { - const ops = withOperatorsList[i]; - let op = null; - let operatorListLoc = -1; - let operatorPrecedence = -127; - if (ops.length == 1) { - op = ops[0]; - operatorListLoc = 0; - } else { - // TODO: We need to identify which particular operator applies in this case. - // We're just going to short-circuit this process on the first operator that matches - // but we need to come up with a "best match" behavior (ie, if one argument is an int8 - // it may choose the int64-based operator because it was first and it can cast int8 to - // int64 and then miss the specialized int8 version of the function). - let left = null; - if (i != 0) left = withOperatorsList[i - 1]; - let right = null; - if (i != withOperatorsList.length - 1) - right = withOperatorsList[i + 1]; - // Skip over any operator that is followed by another operator as it must be a prefix - // operator (or a syntax error, but we'll catch that later) - if (right === null || right instanceof Microstatement) { - for (let j = 0; j < ops.length; j++) { - if ( - ops[j].precedence > operatorPrecedence && - ops[j].applicableFunction( - !left // Left is special, if two operators are in a row, this one - ? null // needs to be a prefix operator for this to work at all - : left instanceof Microstatement - ? left.outputType - : null, - right === null ? null : right.outputType, - scope, - ) != null - ) { - op = ops[j]; - operatorListLoc = j; - operatorPrecedence = op.precedence; - } - } - } - // During the process of determining the operator ordering, there may be tests that - // will not match because operator precedence will convert the neighboring types into - // types that will match. This is complicated and doing this statically will be more - // difficult, but for now, just skip over these. - if (op == null) continue; - } - - if (op.precedence > maxPrecedence) { - maxPrecedence = op.precedence; - maxOperatorLoc = i; - maxOperatorListLoc = operatorListLoc; - } - } - } - if (maxPrecedence == -1 || maxOperatorLoc == -1) { - let errMsg = `Cannot resolve operators with remaining statement -${assignablesAst.t}`; - const withOperatorsTranslation = []; - for (let i = 0; i < withOperatorsList.length; i++) { - const node = withOperatorsList[i]; - if (node instanceof Array && node[0] instanceof Operator) { - withOperatorsTranslation.push(node[0].name); - } else { - withOperatorsTranslation.push('<' + node.outputType.typename + '>'); - } - } - errMsg += '\n' + withOperatorsTranslation.join(' '); - throw new Error(errMsg); - } - const op = withOperatorsList[maxOperatorLoc][maxOperatorListLoc]; - const realArgNames = []; - const realArgTypes = []; - if (!op.isPrefix) { - const left = withOperatorsList[maxOperatorLoc - 1]; - realArgNames.push(left.outputName); - realArgTypes.push(left.outputType); - } - const right = withOperatorsList[maxOperatorLoc + 1]; - realArgNames.push(right.outputName); - realArgTypes.push(right.outputType); - UserFunction.dispatchFn( - op.potentialFunctions, - realArgTypes, - scope, - ).microstatementInlining(realArgNames, scope, microstatements); - const last = microstatements[microstatements.length - 1]; - withOperatorsList[maxOperatorLoc] = last; - withOperatorsList.splice(maxOperatorLoc + 1, 1); - if (!op.isPrefix) { - withOperatorsList.splice(maxOperatorLoc - 1, 1); - } - } - } - - static fromStatement( - statement: Statement, - microstatements: Array, - secondaryScope: Scope | null = null, - ) { - let actualStatement = statement; - if (secondaryScope !== null) { - const newScope = new Scope(statement.scope); - newScope.secondaryPar = secondaryScope; - actualStatement = new Statement( - statement.statementAst, - newScope, - statement.pure, - ); - } - Microstatement.fromStatementsAst( - actualStatement.statementAst, - actualStatement.scope, - microstatements, - ); - } -} - -export default Microstatement; diff --git a/compiler/src/lntoamm/Module.ts b/compiler/src/lntoamm/Module.ts deleted file mode 100644 index 022f40842..000000000 --- a/compiler/src/lntoamm/Module.ts +++ /dev/null @@ -1,495 +0,0 @@ -import * as Ast from './Ast'; -import Constant from './Constant'; -import Event from './Event'; -import Operator from './Operator'; -import Scope from './Scope'; -import UserFunction from './UserFunction'; -import { Fn } from './Function'; -import { FunctionType, Interface, OperatorType, Type } from './Type'; -import { LPNode } from '../lp'; - -const modules = {}; - -interface AstMap { - [key: string]: LPNode; -} - -class Module { - moduleScope: Scope; - exportScope: Scope; - - constructor(rootScope: Scope) { - // Thoughts on how to handle this right now: - // 1. The outermost module scope is read-only always. - // 2. Therefore anything in the export scope can simply be duplicated in both scopes - // 3. Therefore export scope needs access to the module scope so the functions function, but - // the module scope can just use its local copy - this.moduleScope = new Scope(rootScope); - this.exportScope = new Scope(this.moduleScope); - } - - static getAllModules() { - return modules; - } - - static populateModule( - path: string, - ast: LPNode, // ModuleContext - rootScope: Scope, - isStd = false, - ) { - // First, take the export scope of the root scope and put references to it in this module. If - // it is a built-in std module, it inherits from the root scope, otherwise it attaches all - // exported references. This way std modules get access to the opcode scope via inheritance and - // 'normal' modules do not. - const module = new Module(isStd ? rootScope : undefined); - if (!isStd) { - for (const rootModuleName of Object.keys(rootScope.vals)) { - module.moduleScope.put(rootModuleName, rootScope.vals[rootModuleName]); - } - } - // Now, populate all of the imports - const imports = ast.get('imports').getAll(); - for (const importAst of imports) { - // If it's a "standard" import, figure out what name to call it (if the user overrode it) - // and then attach the entire module with that name to the local scope. - if (importAst.has('standardImport')) { - const standardImport = importAst.get('standardImport'); - let importName: string; - if (standardImport.get('renamed').has()) { - importName = standardImport.get('renamed').get('varop').t; - } else { - const nameParts = standardImport.get('dependency').t.split('/'); - importName = nameParts[nameParts.length - 1]; - } - const importedModule = - modules[ - Ast.resolveDependency( - path, - importAst.get('standardImport').get('dependency'), - ) - ]; - module.moduleScope.put(importName, importedModule.exportScope); - } - // If it's a "from" import, we're picking off pieces of the exported scope and inserting them - // also potentially renaming them if requested by the user - if (importAst.has('fromImport')) { - const importedModule = - modules[ - Ast.resolveDependency( - path, - importAst.get('fromImport').get('dependency'), - ) - ]; - const vars = []; - vars.push( - importAst.get('fromImport').get('varlist').get('renameablevar'), - ); - importAst - .get('fromImport') - .get('varlist') - .get('cdr') - .getAll() - .forEach((r) => { - vars.push(r.get('renameablevar')); - }); - for (const moduleVar of vars) { - const exportName = moduleVar.get('varop').t; - let importName = exportName; - if (moduleVar.get('renamed').has()) { - importName = moduleVar.get('renamed').get('varop').t; - } - const thing = importedModule.exportScope.shallowGet(exportName); - if ( - thing instanceof Array && - thing[0].microstatementInlining instanceof Function - ) { - const otherthing = module.moduleScope.deepGet(importName); - if ( - !!otherthing && - otherthing instanceof Array && - (otherthing[0] as Fn).microstatementInlining instanceof Function - ) { - module.moduleScope.put(importName, [...thing, ...otherthing]); - } else { - module.moduleScope.put(importName, thing); - } - } else if (thing instanceof Array && thing[0] instanceof Operator) { - const otherthing = module.moduleScope.deepGet(importName); - if ( - !!otherthing && - otherthing instanceof Array && - otherthing instanceof Operator - ) { - module.moduleScope.put(importName, [...thing, ...otherthing]); - } else { - module.moduleScope.put(importName, thing); - } - } else { - module.moduleScope.put(importName, thing); - } - // Special behavior for interfaces. If there are any functions or operators that match - // the interface, pull them in. Similarly any types that match the entire interface. This - // allows concise importing of a related suite of tools without having to explicitly call - // out each one. - if (thing instanceof Type && thing.iface) { - const iface = thing.iface; - const typesToCheck = Object.keys(importedModule.exportScope.vals) - .map((n) => importedModule.exportScope.vals[n]) - .filter((v) => v instanceof Type); - const fnsToCheck = Object.keys(importedModule.exportScope.vals) - .map((n) => importedModule.exportScope.vals[n]) - .filter( - (v) => - v instanceof Array && - v[0].microstatementInlining instanceof Function, - ); - const opsToCheck = Object.keys(importedModule.exportScope.vals) - .map((n) => importedModule.exportScope.vals[n]) - .filter((v) => v instanceof Array && v[0] instanceof Operator); - - typesToCheck - .filter((t) => iface.typeApplies(t, importedModule.exportScope)) - .forEach((t) => { - module.moduleScope.put(t.typename, t); - }); - - fnsToCheck - .filter((fn) => { - // TODO: Make this better and move it to the Interface file in the future - return iface.functionTypes.some( - (ft: FunctionType) => ft.functionname === fn[0].getName(), - ); - }) - .forEach((fn) => { - module.moduleScope.put(fn[0].getName(), fn); - }); - - opsToCheck - .filter((op) => { - return iface.operatorTypes.some( - (ot: OperatorType) => ot.operatorname === op[0].name, - ); - }) - .forEach((op) => { - module.moduleScope.put(op[0].name, op); - }); - } - } - } - } - const body = ast.get('body').getAll(); - // Next, types - const types = body.filter((r) => r.has('types')).map((r) => r.get('types')); - for (const typeAst of types) { - const newType = Type.fromAst(typeAst, module.moduleScope); - module.moduleScope.put( - newType.typename, - newType.alias ? newType.alias : newType, - ); - } - // Next, interfaces - const interfaces = body - .filter((r) => r.has('interfaces')) - .map((r) => r.get('interfaces')); - for (const interfaceAst of interfaces) { - Interface.fromAst(interfaceAst, module.moduleScope); - // Automatically inserts the interface into the module scope, we're done. - } - // Next, constants - const constdeclarations = body - .filter((r) => r.has('constdeclaration')) - .map((r) => r.get('constdeclaration')); - for (const constdeclaration of constdeclarations) { - Constant.fromAst(constdeclaration, module.moduleScope); - } - // Next, events - const events = body - .filter((r) => r.has('events')) - .map((r) => r.get('events')); - for (const eventAst of events) { - const newEvent = Event.fromAst(eventAst, module.moduleScope); - module.moduleScope.put(newEvent.name, newEvent); - } - // Next, functions - const functions = body - .filter((r) => r.has('functions')) - .map((r) => r.get('functions')); - for (const functionAst of functions) { - const newFunc = UserFunction.fromAst(functionAst, module.moduleScope); - if (newFunc.getName() == null) { - throw new Error('Module-level functions must have a name'); - } - const fns = module.moduleScope.get(newFunc.getName()) as Array; - if (fns == null) { - module.moduleScope.put(newFunc.getName(), [newFunc]); - } else { - fns.push(newFunc); - } - } - // Next, operators - const operatorMapping = body - .filter((r) => r.has('operatormapping')) - .map((r) => r.get('operatormapping')); - for (const operatorAst of operatorMapping) { - const isPrefix = operatorAst.get('fix').has('prefix'); - const name = operatorAst - .get('opmap') - .get() - .get('fntoop') - .get('operators') - .t.trim(); - const precedence = parseInt( - operatorAst.get('opmap').get().get('opprecedence').get('num').t, - 10, - ); - const fns = module.moduleScope.deepGet( - operatorAst.get('opmap').get().get('fntoop').get('fnname').t, - ) as Array; - if (!fns) { - throw new Error( - 'Operator ' + - name + - ' declared for unknown function ' + - operatorAst.t, - ); - } - const op = new Operator(name, precedence, isPrefix, fns); - const opsBox = module.moduleScope.deepGet(name) as Array; - if (!opsBox) { - module.moduleScope.put(name, [op]); - } else { - // To make sure we don't accidentally mutate other scopes, we're cloning this operator list - const ops = [...opsBox]; - ops.push(op); - module.moduleScope.put(name, ops); - } - } - // Next, exports, which can be most of the above - const exports = body - .filter((r) => r.has('exportsn')) - .map((r) => r.get('exportsn').get('exportable')); - for (const exportAst of exports) { - if (exportAst.has('ref')) { - const exportVar = module.moduleScope.deepGet(exportAst.get('ref').t); - const splitName = exportAst.get('ref').t.split('.'); - module.moduleScope.put(splitName[splitName.length - 1], exportVar); - module.exportScope.put(splitName[splitName.length - 1], exportVar); - } else if (exportAst.has('types')) { - const newType = Type.fromAst( - exportAst.get('types'), - module.moduleScope, - ); - const typeBox = !newType.alias ? newType : newType.alias; - module.moduleScope.put(newType.typename, typeBox); - module.exportScope.put(newType.typename, typeBox); - } else if (exportAst.has('interfaces')) { - // Automatically inserts the interface into the module scope - const interfaceBox = Interface.fromAst( - exportAst.get('interfaces'), - module.moduleScope, - ); - module.exportScope.put(interfaceBox.typename, interfaceBox); - } else if (exportAst.has('constdeclaration')) { - const constVal = Constant.fromAst( - exportAst.get('constdeclaration'), - module.moduleScope, - ); - module.exportScope.put(constVal.name, constVal); - } else if (exportAst.has('functions')) { - const newFunc = UserFunction.fromAst( - exportAst.get('functions'), - module.moduleScope, - ); - if (!newFunc.getName()) { - throw new Error(`Module-level functions must have a name: -${exportAst.get('functions').t} -`); - } - // Exported scope must be checked first because it will fall through to the not-exported - // scope by default. - const expFns = module.exportScope.shallowGet( - newFunc.getName(), - ) as Array; - if (!expFns) { - module.exportScope.put(newFunc.getName(), [newFunc]); - } else { - expFns.push(newFunc); - } - const modFns = module.moduleScope.get(newFunc.getName()) as Array; - if (!modFns) { - module.moduleScope.put(newFunc.getName(), [newFunc]); - } else { - modFns.push(newFunc); - } - } else if (exportAst.has('operatormapping')) { - const operatorAst = exportAst.get('operatormapping'); - const isPrefix = operatorAst.get('fix').has('prefix'); - const name = operatorAst - .get('opmap') - .get() - .get('fntoop') - .get('operators') - .t.trim(); - const precedence = parseInt( - operatorAst.get('opmap').get().get('opprecedence').get('num').t, - 10, - ); - let fns = module.moduleScope.deepGet( - operatorAst.get('opmap').get().get('fntoop').get('fnname').t, - ) as Array; - if (!fns) { - fns = module.moduleScope.deepGet( - operatorAst.get('opmap').get().get('fntoop').get('fnname').t, - ) as Array; - if (fns) { - throw new Error( - 'Exported operator ' + - name + - ' wrapping unexported function ' + - operatorAst.get('opmap').get('fntoop').get('fnname').t + - ' which is not allowed, please export the function, as well.', - ); - } - throw new Error( - 'Operator ' + - name + - ' declared for unknown function ' + - operatorAst.get('opmap').get('fntoop').get('fnname').t, - ); - } - const op = new Operator(name, precedence, isPrefix, fns); - const modOpsBox = module.moduleScope.deepGet(name) as Array; - if (!modOpsBox) { - module.moduleScope.put(name, [op]); - } else { - const ops = [...modOpsBox]; - ops.push(op); - module.moduleScope.put(name, ops); - } - const expOpsBox = module.exportScope.deepGet(name) as Array; - if (!expOpsBox) { - module.exportScope.put(name, [op]); - } else { - const ops = [...expOpsBox]; - ops.push(op); - module.exportScope.put(name, ops); - } - } else if (exportAst.has('events')) { - const newEvent = Event.fromAst( - exportAst.get('events'), - module.moduleScope, - ); - module.moduleScope.put(newEvent.name, newEvent); - module.exportScope.put(newEvent.name, newEvent); - } else { - // What? - throw new Error( - 'What should be an impossible export state has been reached.', - ); - } - } - // Finally, event handlers, so they can depend on events that are exported from the same module - const handlers = body - .filter((r) => r.has('handlers')) - .map((r) => r.get('handlers')); - for (const handlerAst of handlers) { - const evt = module.moduleScope.deepGet(handlerAst.get('eventname').t); - if (!evt) - throw new Error( - 'Could not find specified event: ' + handlerAst.get('eventname').t, - ); - if (!(evt instanceof Event)) - throw new Error(handlerAst.get('eventname').t + ' is not an event'); - const handler = handlerAst.get('handler'); - let fn = null; - if (handler.has('fnname')) { - const fnName = handler.get('fnname').t; - const fns = module.moduleScope.deepGet(fnName) as Array; - if (!fns) - throw new Error('Could not find specified function: ' + fnName); - if ( - !( - fns instanceof Array && - fns[0].microstatementInlining instanceof Function - ) - ) { - throw new Error(fnName + ' is not a function'); - } - for (let i = 0; i < fns.length; i++) { - if ( - evt.type.typename === 'void' && - Object.values(fns[i].getArguments()).length === 0 - ) { - fn = fns[i]; - break; - } - const argTypes = Object.values(fns[i].getArguments()); - if (argTypes.length !== 1) continue; - if (argTypes[0] == evt.type) { - fn = fns[i]; - break; - } - } - if (fn == null) { - throw new Error( - 'Could not find function named ' + - fnName + - ' with matching function signature', - ); - } - } - if (handler.has('functions')) { - fn = UserFunction.fromAst(handler.get('functions'), module.moduleScope); - } - if (handler.has('functionbody')) { - fn = UserFunction.fromAst( - handler.get('functionbody'), - module.moduleScope, - ); - } - if (!fn) { - // Shouldn't be possible - throw new Error('Impossible state reached processing event handler'); - } - if ( - Object.keys(fn.getArguments()).length > 1 || - (evt.type === Type.builtinTypes['void'] && - Object.keys(fn.getArguments()).length !== 0) - ) { - throw new Error( - 'Function provided for ' + - handlerAst.get('eventname').t + - ' has invalid argument signature', - ); - } - evt.handlers.push(fn); - } - return module; - } - - static modulesFromAsts(astMap: AstMap, rootScope: Scope) { - const modulePaths = Object.keys(astMap); - while (modulePaths.length > 0) { - for (let i = 0; i < modulePaths.length; i++) { - const path = modulePaths[i]; - const moduleAst = astMap[path]; - const imports = Ast.resolveImports(path, moduleAst); - let loadable = true; - for (const importPath of imports) { - if (importPath[0] === '@') continue; - if (modules.hasOwnProperty(importPath)) continue; - loadable = false; - } - if (!loadable) continue; - modulePaths.splice(i, 1); - i--; - const module = Module.populateModule(path, moduleAst, rootScope); - modules[path] = module; - } - } - return modules; - } -} - -export default Module; diff --git a/compiler/src/lntoamm/Operator.ts b/compiler/src/lntoamm/Operator.ts deleted file mode 100644 index 499c469ec..000000000 --- a/compiler/src/lntoamm/Operator.ts +++ /dev/null @@ -1,132 +0,0 @@ -import * as Ast from './Ast'; -import Fn from './Function'; -import Scope from './Scope'; -import Type from './Type'; -import UserFunction from './UserFunction'; - -class Operator { - name: string; - precedence: number; - isPrefix: boolean; - potentialFunctions: Array; - - constructor( - name: string, - precedence: number, - isPrefix: boolean, - potentialFunctions: Array, - ) { - this.name = name; - this.precedence = precedence; - this.isPrefix = isPrefix; - this.potentialFunctions = potentialFunctions; - } - - applicableFunction(left: Type, right: Type, scope: Scope) { - const argumentTypeList = []; - if (!this.isPrefix) { - if (left == null) return null; - argumentTypeList.push(left); - } - argumentTypeList.push(right); - const fns = this.potentialFunctions; - for (let i = 0; i < fns.length; i++) { - const args = fns[i].getArguments(); - const argList: Array = Object.values(args); - if (argList.length != argumentTypeList.length) continue; - let skip = false; - for (let j = 0; j < argList.length; j++) { - if (argList[j].typename === argumentTypeList[j].typename) continue; - if ( - argList[j].iface && - argList[j].iface.typeApplies(argumentTypeList[j], scope) - ) - continue; - if ( - argList[j].generics.length > 0 && - argumentTypeList[j].originalType == argList[j] - ) { - continue; - } - if ( - argList[j].originalType != null && - argumentTypeList[j].originalType == argList[j].originalType - ) { - const argListAst = Ast.fulltypenameAstFromString(argList[j].typename); - const argumentTypeListAst = Ast.fulltypenameAstFromString( - argumentTypeList[j].typename, - ); - const argGenericTypes = []; - if (argListAst.has('opttypegenerics')) { - argGenericTypes.push( - argListAst - .get('opttypegenerics') - .get('generics') - .get('fulltypename').t, - ); - argListAst - .get('opttypegenerics') - .get('generics') - .get('cdr') - .getAll() - .map((r) => { - argGenericTypes.push(r.get('fulltypename').t); - }); - } - const argumentGenericTypes = []; - if (argumentTypeListAst.has('opttypegenerics')) { - argumentGenericTypes.push( - argumentTypeListAst - .get('opttypegenerics') - .get('generics') - .get('fulltypename').t, - ); - argumentTypeListAst - .get('opttypegenerics') - .get('generics') - .get('cdr') - .getAll() - .map((r) => { - argumentGenericTypes.push(r.get('fulltypename').t); - }); - } - let innerSkip = false; - for (let i = 0; i < argGenericTypes.length; i++) { - const argListTypeProp = argGenericTypes[i]; - const argumentTypeListTypeProp = argumentGenericTypes[i]; - if (argListTypeProp === argumentTypeListTypeProp) continue; - const argListProp = scope.deepGet(argListTypeProp) as Type; - const argumentTypeListProp = scope.deepGet( - argumentTypeListTypeProp, - ) as Type; - if (!argListProp || !(argListProp instanceof Type)) { - innerSkip = true; - break; - } - if ( - !argumentTypeListProp || - !(argumentTypeListProp instanceof Type) - ) { - innerSkip = true; - break; - } - if ( - argListProp.iface != null && - argListProp.iface.typeApplies(argumentTypeListProp, scope) - ) - continue; - innerSkip = true; - } - if (innerSkip) skip = true; - continue; - } - skip = true; - } - if (skip) continue; - return fns[i]; - } - return null; - } -} - -export default Operator; diff --git a/compiler/src/lntoamm/Scope.ts b/compiler/src/lntoamm/Scope.ts deleted file mode 100644 index 5283a8070..000000000 --- a/compiler/src/lntoamm/Scope.ts +++ /dev/null @@ -1,89 +0,0 @@ -import Constant from './Constant'; -import Event from './Event'; -import Fn from './Function'; -import Microstatement from './Microstatement'; -import Operator from './Operator'; -import Type from './Type'; - -type Boxish = - | Type - | Scope - | Microstatement - | Array - | Array - | Event - | Constant - | undefined; - -type BoxSet = { - [K: string]: Boxish; -}; - -class Scope { - vals: BoxSet; - par: Scope | null; - secondaryPar: Scope | null; - - constructor(par?: Scope) { - this.vals = {}; - this.par = par ? par : null; - this.secondaryPar = null; - } - - get(name: string) { - if (this.vals.hasOwnProperty(name)) { - return this.vals[name]; - } - if (this.par) { - const val = this.par.get(name); - if (!val && !!this.secondaryPar) { - return this.secondaryPar.get(name); - } else { - return val; - } - } - return null; - } - - shallowGet(name: string) { - if (this.vals.hasOwnProperty(name)) { - return this.vals[name]; - } - return null; - } - - deepGet(fullName: string) { - const fullVar = fullName.trim().split('.'); - let boxedVar: Boxish; - for (let i = 0; i < fullVar.length; i++) { - if (i === 0) { - boxedVar = this.get(fullVar[i]); - } else if (!boxedVar) { - return null; - } else { - if (boxedVar instanceof Scope) { - boxedVar = boxedVar.get(fullVar[i]); - } else { - return null; - } - } - } - return boxedVar; - } - - has(name: string) { - if (this.vals.hasOwnProperty(name)) { - return true; - } - if (this.par) { - return this.par.has(name); - } - return false; - } - - put(name: string, val: Boxish) { - this.vals[name.trim()] = val; - } -} - -export default Scope; diff --git a/compiler/src/lntoamm/Statement.ts b/compiler/src/lntoamm/Statement.ts deleted file mode 100644 index 75cd611b6..000000000 --- a/compiler/src/lntoamm/Statement.ts +++ /dev/null @@ -1,158 +0,0 @@ -import Operator from './Operator'; -import Scope from './Scope'; -import { Fn } from './Function'; -import { LPNode } from '../lp'; - -// Only implements the pieces necessary for the first stage compiler -class Statement { - statementAst: LPNode; - scope: Scope; - pure: boolean; - - constructor(statementAst: LPNode, scope: Scope, pure: boolean) { - (this.statementAst = statementAst), (this.scope = scope); - this.pure = pure; - } - - isConditionalStatement() { - return this.statementAst.has('conditionals'); - } - - isReturnStatement() { - return this.statementAst.has('exits'); - } - - static baseAssignableHasObjectLiteral(baseAssignableAst: LPNode) { - return baseAssignableAst.has('objectliterals'); - } - - static assignablesHasObjectLiteral(assignablesAst: LPNode) { - for (const w of assignablesAst.getAll()) { - const wo = w.get('withoperators'); - if (wo.has('operators')) continue; - for (const b of wo.get('baseassignablelist').getAll()) { - const ba = b.get('baseassignable'); - if (Statement.baseAssignableHasObjectLiteral(ba)) return true; - if (ba.has('fncall') && ba.get('fncall').has('assignablelist')) { - const innerAssignables = []; - innerAssignables.push( - ba.get('fncall').get('assignablelist').get('assignables'), - ); - ba.get('fncall') - .get('assignablelist') - .get('cdr') - .getAll() - .map((a) => { - innerAssignables.push(a.get('assignables')); - }); - for (const ia of innerAssignables) { - if (Statement.assignablesHasObjectLiteral(ia)) return true; - } - } - } - } - return false; - } - - static assignmentsHasObjectLiteral(assignmentsAst: LPNode) { - return Statement.assignablesHasObjectLiteral( - assignmentsAst.get('assignables'), - ); - } - - hasObjectLiteral() { - const s = this.statementAst; - if (s.has('declarations')) { - const d = s.get('declarations').has('constdeclaration') - ? s.get('declarations').get('constdeclaration') - : s.get('declarations').get('letdeclaration'); - return Statement.assignablesHasObjectLiteral(d.get('assignables')); - } - if (s.has('assignments')) - return Statement.assignmentsHasObjectLiteral(s.get('assignments')); - if (s.has('assignables')) - return Statement.assignablesHasObjectLiteral(s.get('assignables')); - if (s.has('exits') && s.get('exits').get('retval').has('assignables')) { - return Statement.assignablesHasObjectLiteral( - s.get('exits').get('retval').get('assignables'), - ); - } - if (s.has('emits') && s.get('emits').get('retval').has('assignables')) { - return Statement.assignablesHasObjectLiteral( - s.get('emits').get('retval').get('assignables'), - ); - } - // TODO: Cover conditionals - return false; - } - - static isAssignablePure(assignableAst: LPNode, scope: Scope) { - // TODO: Redo this - return true; - } - - static create(statementAst: LPNode | Error, scope: Scope) { - if (statementAst instanceof Error) throw statementAst; - let pure = true; - if (statementAst.has('declarations')) { - if (statementAst.get('declarations').has('constdeclaration')) { - pure = Statement.isAssignablePure( - statementAst - .get('declarations') - .get('constdeclaration') - .get('assignables'), - scope, - ); - } else if (statementAst.get('declarations').has('letdeclaration')) { - pure = Statement.isAssignablePure( - statementAst - .get('declarations') - .get('letdeclaration') - .get('assignables'), - scope, - ); - } else { - throw new Error( - 'Malformed AST. Invalid const/let declaration structure', - ); - } - } - if (statementAst.has('assignments')) { - if (statementAst.get('assignments').has('assignables')) { - pure = Statement.isAssignablePure( - statementAst.get('assignments').get('assignables'), - scope, - ); - } - } - if (statementAst.has('assignables')) { - pure = Statement.isAssignablePure( - statementAst.get('assignables').get('assignables'), - scope, - ); - } - if (statementAst.has('exits')) { - if (statementAst.get('exits').has('assignables')) { - pure = Statement.isAssignablePure( - statementAst.get('exits').get('assignables'), - scope, - ); - } - } - if (statementAst.has('emits')) { - if (statementAst.get('emits').has('assignables')) { - pure = Statement.isAssignablePure( - statementAst.get('emits').get('assignables'), - scope, - ); - } - } - return new Statement(statementAst, scope, pure); - } - - toString() { - return this.statementAst.t; - } -} - -export default Statement; diff --git a/compiler/src/lntoamm/StatementType.ts b/compiler/src/lntoamm/StatementType.ts deleted file mode 100644 index 80529913f..000000000 --- a/compiler/src/lntoamm/StatementType.ts +++ /dev/null @@ -1,15 +0,0 @@ -enum StatementType { - CONSTDEC = 'CONSTDEC', - LETDEC = 'LETDEC', - ASSIGNMENT = 'ASSIGNMENT', - CALL = 'CALL', - EMIT = 'EMIT', - REREF = 'REREF', - CLOSURE = 'CLOSURE', - ARG = 'ARG', - ENTERFN = 'ENTERFN', - EXIT = 'EXIT', - CLOSUREDEF = 'CLOSUREDEF', -} - -export default StatementType; diff --git a/compiler/src/lntoamm/Std.ts b/compiler/src/lntoamm/Std.ts deleted file mode 100644 index fc97bccc7..000000000 --- a/compiler/src/lntoamm/Std.ts +++ /dev/null @@ -1,100 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; - -import * as Ast from './Ast'; -import Module from './Module'; -import opcodes from './opcodes'; -import { LPNode } from '../lp'; - -interface AstRec { - name: string; - ast: LPNode; -} - -export const loadStdModules = (stdImports: Set) => { - const stdDir = path.join(__dirname, '../../std'); - const allStdAsts = fs - .readdirSync(stdDir) - .filter((n) => /.ln$/.test(n)) - .map((n) => ({ - name: n, - ast: Ast.fromFile(path.join(__dirname, '../../std', n)), - })); - const stdAsts = allStdAsts.filter( - (ast) => stdImports.has(ast.name) || ast.name === 'root.ln', - ); - // Load the rootScope first, all the others depend on it - let rootModule: Module; - stdAsts.forEach((moduleAst) => { - if (moduleAst.name === 'root.ln') { - rootModule = Module.populateModule( - '', - moduleAst.ast, - opcodes.exportScope, - true, - ); - Module.getAllModules()[''] = rootModule; - } - }); - // Put the remaining ASTs in a loadable order - const orderedAsts = []; - let i = 0; - while (stdAsts.length > 0) { - const stdAst: AstRec = stdAsts[i]; - // Just remove the root node, already processed - if (stdAst.name === 'root.ln') { - stdAsts.splice(i, 1); - i = i % stdAsts.length; - continue; - } - // Immediately add any node with no imports and remove from this list - if (stdAst.ast.get('imports').getAll().length === 0) { - orderedAsts.push(stdAst); - stdAsts.splice(i, 1); - i = i % stdAsts.length; - continue; - } - // For everything else, check if the dependencies are already queued up - const importAsts = stdAst.ast.get('imports').getAll(); - let safeToAdd = true; - for (const importAst of importAsts) { - const depName = ( - importAst.has('standardImport') - ? importAst.get('standardImport').get('dependency').t.trim() - : importAst.get('fromImport').get('dependency').t.trim() - ) - .replace('@std/', '') - .replace(/$/, '.ln'); - if (!orderedAsts.some((ast) => ast.name === depName)) { - // add std modules this std module imports if not present - if (!stdAsts.some((ast) => ast.name === depName)) { - stdAsts.splice(i, 0, allStdAsts.filter((a) => a.name === depName)[0]); - } - safeToAdd = false; - break; - } - } - // If it's safe, add it - if (safeToAdd) { - orderedAsts.push(stdAst); - stdAsts.splice(i, 1); - i = i % stdAsts.length; - continue; - } - // Otherwise, skip this one - i = (i + 1) % stdAsts.length; - } - // Now load the remainig modules based on the root scope - orderedAsts.forEach((moduleAst) => { - if (moduleAst.name !== 'root.ln') { - moduleAst.name = '@std/' + moduleAst.name.replace(/.ln$/, ''); - const stdModule = Module.populateModule( - moduleAst.name, - moduleAst.ast, - rootModule.exportScope, - true, - ); - Module.getAllModules()[moduleAst.name] = stdModule; - } - }); -}; diff --git a/compiler/src/lntoamm/Type.ts b/compiler/src/lntoamm/Type.ts deleted file mode 100644 index fdddbe151..000000000 --- a/compiler/src/lntoamm/Type.ts +++ /dev/null @@ -1,957 +0,0 @@ -import Operator from './Operator'; -import Scope from './Scope'; -import { Fn } from './Function'; -import { fulltypenameAstFromString } from './Ast'; -import { LPNode } from '../lp'; - -type Properties = { - [K: string]: Type; -}; - -type Generics = { - [K: string]: number; -}; - -export class FunctionType { - functionname: string | null; - args: Array; - returnType: Type; - - constructor( - functionname: string | null = null, - args: Array = [], - returnType: Type, - ) { - this.functionname = functionname; - this.args = args; - this.returnType = returnType; - } -} - -export class OperatorType { - operatorname: string | null; - isPrefix: boolean; - args: Array; - returnType: Type; - - constructor( - operatorname: string, - isPrefix = false, - args: Array, - returnType: Type, - ) { - this.operatorname = operatorname; - this.isPrefix = isPrefix; - this.args = args; - this.returnType = returnType; - } -} - -export class Interface { - interfacename: string; - functionTypes: Array; - operatorTypes: Array; - requiredProperties: Properties; - - constructor( - interfacename: string, - functionTypes: Array = [], - operatorTypes: Array = [], - requiredProperties: Properties = {}, - ) { - this.interfacename = interfacename; - this.functionTypes = functionTypes; - this.operatorTypes = operatorTypes; - this.requiredProperties = requiredProperties; - } - - typeApplies(typeToCheck: Type, scope: Scope) { - // Solve circular dependency issue - for (const requiredProperty of Object.keys(this.requiredProperties)) { - if (!typeToCheck.properties.hasOwnProperty(requiredProperty)) - return false; - } - - for (const functionType of this.functionTypes) { - if (!functionType.functionname) continue; // Anonymous functions checked at callsite - const potentialFunctions = scope.deepGet( - functionType.functionname, - ) as Array; - if ( - !potentialFunctions || - !( - potentialFunctions instanceof Array && - potentialFunctions[0].microstatementInlining instanceof Function - ) - ) { - throw new Error( - functionType.functionname + ' is not the name of a function', - ); - } - let functionFound = false; - for (const potentialFunction of potentialFunctions) { - const argTypes = potentialFunction.getArguments(); - let argsMatch = true; - const typeNames = Object.keys(argTypes); - for (let i = 0; i < typeNames.length; i++) { - const functionTypeArgType = functionType.args[i]; - if (argTypes[typeNames[i]] === functionTypeArgType) continue; - if (argTypes[typeNames[i]].originalType === functionTypeArgType) - continue; - if ( - argTypes[typeNames[i]].originalType === - functionTypeArgType.originalType && - Object.values(functionTypeArgType.properties).every((prop, j) => { - const comparable = Object.values( - argTypes[typeNames[i]].properties, - )[j]; - if (prop === comparable) return true; - if (prop.iface && prop.iface.typeApplies(comparable, scope)) - return true; - return false; - }) - ) - continue; - if (argTypes[typeNames[i]] === typeToCheck) continue; - if ( - !!argTypes[typeNames[i]].iface && - !!functionTypeArgType.iface && - argTypes[typeNames[i]].iface === functionTypeArgType.iface - ) - continue; - argsMatch = false; - break; - } - if (!argsMatch) continue; - functionFound = true; - break; - } - if (!functionFound) return false; - } - - for (const operatorType of this.operatorTypes) { - const potentialOperators = scope.deepGet( - operatorType.operatorname, - ) as Array; - if ( - !potentialOperators || - !( - potentialOperators instanceof Array && - potentialOperators[0] instanceof Operator - ) - ) { - throw new Error(`${operatorType.operatorname} is not an operator`); - } - let operatorFound = false; - for (const potentialOperator of potentialOperators) { - for (const potentialFunction of potentialOperator.potentialFunctions) { - const argTypes = potentialFunction.getArguments(); - let argsMatch = true; - const typeNames = Object.keys(argTypes); - for (let i = 0; i < typeNames.length; i++) { - const operatorTypeArgType = operatorType.args[i]; - if (argTypes[typeNames[i]] === operatorTypeArgType) continue; - if (argTypes[typeNames[i]].originalType === operatorTypeArgType) - continue; - if (argTypes[typeNames[i]] === typeToCheck) continue; - if ( - !!argTypes[typeNames[i]].iface && - !!operatorTypeArgType.iface && - argTypes[typeNames[i]].iface === operatorTypeArgType.iface - ) - continue; - argsMatch = false; - break; - } - if (!argsMatch) continue; - operatorFound = true; - break; - } - } - if (!operatorFound) return false; - } - - return true; - } - - static fromAst(interfaceAst: LPNode, scope: Scope) { - // Construct the basic interface, the wrapper type, and insert it into the scope - // This is all necessary so the interface can self-reference when constructing the function and - // operator types. - const interfacename = interfaceAst.get('variable').t; - const iface = new Interface(interfacename); - const ifaceType = new Type( - interfacename, - false, - false, - {}, - {}, - null, - iface, - ); - scope.put(interfacename, ifaceType); - - // Now, insert the actual declarations of the interface, if there are any (if there are none, - // it will provide only as much as a type generic -- you can set it to a variable and return it - // but nothing else, unlike Go's ridiculous interpretation of a bare interface). - if ( - interfaceAst.get('interfacedef').has('interfacebody') && - interfaceAst - .get('interfacedef') - .get('interfacebody') - .get('interfacelist') - .has() - ) { - const interfacelist = interfaceAst - .get('interfacedef') - .get('interfacebody') - .get('interfacelist'); - const interfacelines = []; - interfacelines.push(interfacelist.get('interfaceline')); - interfacelist - .get('cdr') - .getAll() - .forEach((l) => { - interfacelines.push(l.get('interfaceline')); - }); - for (const interfaceline of interfacelines) { - if (interfaceline.has('functiontypeline')) { - const functiontypeline = interfaceline.get('functiontypeline'); - const functionname = functiontypeline.get('variable').t; - const typenames = []; - typenames.push( - functiontypeline.get('functiontype').get('fulltypename').t, - ); - functiontypeline - .get('functiontype') - .get('cdr') - .getAll() - .forEach((r) => { - typenames.push(r.get('fulltypename').t); - }); - const returnType = scope.deepGet( - functiontypeline.get('functiontype').get('returntype').t, - ) as Type; - if (!returnType || !(returnType instanceof Type)) { - throw new Error( - functiontypeline.get('functiontype').get('returntype').t + - ' is not a type', - ); - } - const args = []; - for (let i = 0; i < typenames.length; i++) { - const argument = scope.deepGet(typenames[i]) as Type; - if (!argument || !(argument instanceof Type)) { - throw new Error(typenames[i] + ' is not a type'); - } - args.push(argument); - } - const functionType = new FunctionType(functionname, args, returnType); - iface.functionTypes.push(functionType); - } - if (interfaceline.has('operatortypeline')) { - const operatorname = interfaceline - .get('operatortypeline') - .get('operators').t; - const isPrefix = !interfaceline - .get('operatortypeline') - .has('optleftarg'); - const argTypenames = []; - if (!isPrefix) { - argTypenames.push( - interfaceline - .get('operatortypeline') - .get('optleftarg') - .get('leftarg').t, - ); - } - argTypenames.push( - interfaceline.get('operatortypeline').get('rightarg').t, - ); - const returnTypename = interfaceline - .get('operatortypeline') - .get('fulltypename').t; - const args = argTypenames.map((n) => { - const box = scope.deepGet(n); - if (!box || !(box instanceof Type)) { - throw new Error(`${n} is not a type`); - } - return box; - }); - const returnType = scope.deepGet(returnTypename) as Type; - if (!returnType || !(returnType instanceof Type)) { - throw new Error(`${returnTypename} is not a type`); - } - const operatorType = new OperatorType( - operatorname, - isPrefix, - args, - returnType, - ); - iface.operatorTypes.push(operatorType); - } - if (interfaceline.has('propertytypeline')) { - const propertyType = scope.deepGet( - interfaceline.get('propertytypeline').get('variable').t, - ) as Type; - if (!propertyType || !(propertyType instanceof Type)) { - throw new Error( - interfaceline.get('propertytypeline').get('variable').t + - ' is not a type', - ); - } - iface.requiredProperties[ - interfaceline.get('propertytypeline').get('variable').t - ] = propertyType; - } - } - } else if (interfaceAst.get('interfacedef').has('interfacealias')) { - const otherInterface = scope.deepGet( - interfaceAst.get('interfacedef').get('interfacealias').get('variable') - .t, - ) as Type; - if (!(otherInterface instanceof Type) || !otherInterface.iface) { - throw new Error( - `${ - interfaceAst - .get('interfacedef') - .get('interfacealias') - .get('variable').t - } is not an interface`, - ); - } - // Replace the interface with the other one - ifaceType.iface = otherInterface.iface; - } - return ifaceType; - } -} - -export class Type { - typename: string; - builtIn: boolean; - isGenericStandin: boolean; - properties: Properties; - generics: Generics; - originalType: Type | null; - iface: Interface | null; - alias: Type | null; - - constructor( - typename: string, - builtIn = false, - isGenericStandin = false, - properties: Properties = {}, - generics: Generics = {}, - originalType: Type | null = null, - iface: Interface | null = null, - alias: Type | null = null, - ) { - this.typename = typename; - this.builtIn = builtIn; - this.isGenericStandin = isGenericStandin; - this.properties = properties; - this.generics = generics; - this.originalType = originalType; - this.iface = iface; - this.alias = alias; - } - - toString() { - if (this.iface != null) return '// Interfaces TBD'; - let outString = 'type ' + this.typename; - if (this.alias != null) { - outString += ' = ' + this.alias.typename; - return outString; - } - if (this.generics.length > 0) { - outString += '<' + Object.keys(this.generics).join(', ') + '>'; - } - outString += '{\n'; - for (const propName of Object.keys(this.properties)) { - outString += - ' ' + propName + ': ' + this.properties[propName].typename + '\n'; - } - outString += '}\n'; - return outString; - } - - static fromAst(typeAst: LPNode, scope: Scope) { - const type = new Type(typeAst.get('fulltypename').get('typename').t); - const genScope = new Scope(); - const typeScope = new Scope(scope); - typeScope.secondaryPar = genScope; - if (typeAst.get('fulltypename').has('opttypegenerics')) { - const genericsAst = typeAst - .get('fulltypename') - .get('opttypegenerics') - .get('generics'); - const generics = []; - generics.push(genericsAst.get('fulltypename').t); - genericsAst - .get('cdr') - .getAll() - .forEach((r) => { - generics.push(r.get('fulltypename').t); - }); - for (let i = 0; i < generics.length; i++) { - type.generics[generics[i]] = i; - genScope.put(generics[i], new Type(generics[i], true, true)); - } - } - if (typeAst.get('typedef').has('typebody')) { - const typelist = typeAst.get('typedef').get('typebody').get('typelist'); - const lines = []; - lines.push(typelist.get('typeline')); - typelist - .get('cdr') - .getAll() - .forEach((r) => { - lines.push(r.get('typeline')); - }); - for (const lineAst of lines) { - const propertyName = lineAst.get('variable').t; - const typeName = lineAst.get('fulltypename').t.trim(); - const property = typeScope.deepGet(typeName) as Type; - if (!property || !(property instanceof Type)) { - // Potentially a type that depends on the type generics of this type - const baseTypeName = lineAst.get('fulltypename').get('typename').t; - const genericsList = []; - if (lineAst.get('fulltypename').has('opttypegenerics')) { - const innerGenerics = lineAst - .get('fulltypename') - .get('opttypegenerics') - .get('generics'); - genericsList.push(innerGenerics.get('fulltypename')); - innerGenerics - .get('cdr') - .getAll() - .forEach((r) => { - genericsList.push(r.get('fulltypename')); - }); - } - const innerGenerics = [...genericsList]; - const genericsQueue = []; - while (genericsList.length > 0) { - const generic = genericsList.shift(); - genericsQueue.push(generic); - if (generic.has('opttypegenerics')) { - const innerInnerGenerics = generic - .get('opttypegenerics') - .get('generics'); - genericsList.push(innerInnerGenerics.get('fulltypename')); - innerInnerGenerics - .get('cdr') - .getAll() - .forEach((r) => { - genericsList.push(r.get('fulltypename')); - }); - } - } - while (genericsQueue.length > 0) { - const generic = genericsQueue.pop(); - const innerType = typeScope.deepGet(generic.t) as Type; - if (!innerType) { - const innerBaseTypeName = generic.get('typename').t; - const innerBaseType = typeScope.deepGet( - innerBaseTypeName, - ) as Type; - if (!innerBaseType) { - throw new Error( - `Cannot find type ${innerBaseTypeName} while defining ${type}`, - ); - } - const innerBaseGenerics = []; - if (generic.has('opttypegenerics')) { - const innerInnerGenerics = generic - .get('opttypegenerics') - .get('generics'); - innerBaseGenerics.push( - innerInnerGenerics.get('fulltypename').t, - ); - innerInnerGenerics - .get('cdr') - .getAll() - .forEach((r) => { - innerBaseGenerics.push(r.get('fulltypename').t); - }); - } - innerBaseType.solidify(innerBaseGenerics, typeScope); - } - } - const baseType = scope.deepGet(baseTypeName) as Type; - if (!baseType || !(baseType instanceof Type)) { - throw new Error(lineAst.get('fulltypename').t + ' is not a type'); - } - type.properties[propertyName] = baseType.solidify( - innerGenerics.map((r) => r.t), - typeScope, - ); - } else { - type.properties[propertyName] = property; - } - } - } - if (typeAst.get('typedef').has('typealias')) { - const otherType = scope.deepGet( - typeAst - .get('typedef') - .get('typealias') - .get('fulltypename') - .get('typename').t, - ) as Type; - if (!otherType) { - throw new Error( - 'Type ' + - typeAst.get('typedef').get('typealias').get('fulltypename').t + - ' not defined', - ); - } - if (!(otherType instanceof Type)) { - throw new Error( - typeAst.get('typedef').get('typealias').get('fulltypename').t + - ' is not a valid type', - ); - } - - let fulltypename = otherType; - if ( - Object.keys(fulltypename.generics).length > 0 && - typeAst - .get('typedef') - .get('typealias') - .get('fulltypename') - .has('opttypegenerics') - ) { - const solidTypes = []; - const innerTypeGenerics = typeAst - .get('typedef') - .get('typealias') - .get('fulltypename') - .get('opttypegenerics') - .get('generics'); - solidTypes.push(innerTypeGenerics.get('fulltypename').t); - innerTypeGenerics - .get('cdr') - .getAll() - .forEach((r) => { - solidTypes.push(r.get('fulltypename').t); - }); - fulltypename = fulltypename.solidify(solidTypes, scope); - } - - // For simplification of the type aliasing functionality, the other type is attached as - // an alias. The module construction will, if present, perfer the alias over the actual - // type, to make sure built-in types that are aliased continue to work. This means that - // `type varA == type varB` will work if `varA` is assigned to an alias and `varB` to the - // orignal type. I can see the argument either way on this, but the simplicity of this - // approach is why I will go with this for now. - type.alias = fulltypename; - } - scope.put(type.typename, type); - return type; - } - - solidify(genericReplacements: Array, scope: Scope) { - const genericTypes = Object.keys(this.generics).map( - (t) => new Type(t, true, true), - ); - const replacementTypes = []; - for (const typename of genericReplacements) { - const typebox = scope.deepGet(typename) as Type; - if (!typebox || !(typebox instanceof Type)) { - const fulltypename = fulltypenameAstFromString(typename); - if (fulltypename.has('opttypegenerics')) { - const basename = fulltypename.get('typename').t; - const generics = []; - generics.push( - fulltypename - .get('opttypegenerics') - .get('generics') - .get('fulltypename').t, - ); - fulltypename - .get('opttypegenerics') - .get('generics') - .get('cdr') - .getAll() - .forEach((r) => { - generics.push(r.get('fulltypename').t); - }); - const baseType = scope.deepGet(basename) as Type; - if (!baseType || !(baseType instanceof Type)) { - throw new Error(basename + ' type not found'); - } else { - const newtype = baseType.solidify(generics, scope); - replacementTypes.push(newtype); - } - } else { - throw new Error(typename + ' type not found'); - } - } else { - replacementTypes.push(typebox); - } - } - const genericMap = new Map(); - genericTypes.forEach((g, i) => genericMap.set(g, replacementTypes[i])); - const solidifiedName = - this.typename + '<' + genericReplacements.join(', ') + '>'; - const solidified = new Type(solidifiedName, this.builtIn); - solidified.originalType = this; - for (const propKey of Object.keys(this.properties)) { - const propValue = this.properties[propKey]; - const newPropValue = propValue.realize(genericMap, scope); - solidified.properties[propKey] = newPropValue; - } - scope.put(solidifiedName, solidified); - return solidified; - } - - typeApplies( - otherType: Type, - scope: Scope, - interfaceMap: Map = new Map(), - ) { - if (this.typename === otherType.typename) return true; - if (this.iface) { - const applies = this.iface.typeApplies(otherType, scope); - if (applies) { - interfaceMap.set(this, otherType); - } - return applies; - } - if ( - !this.originalType || - !otherType.originalType || - this.originalType.typename !== otherType.originalType.typename - ) - return false; - const typeAst = fulltypenameAstFromString(this.typename); - const otherTypeAst = fulltypenameAstFromString(otherType.typename); - let generics = []; - if (typeAst.has('opttypegenerics')) { - generics.push( - typeAst.get('opttypegenerics').get('generics').get('fulltypename').t, - ); - typeAst - .get('opttypegenerics') - .get('generics') - .get('cdr') - .getAll() - .forEach((r) => { - generics.push(r.get('fulltypename').t); - }); - } - generics = generics.map( - (g) => - scope.deepGet(g) || - (Type.fromStringWithMap(g, interfaceMap, scope) as Type) || - new Type('-bogus-', false, true), - ); - let otherGenerics = []; - if (otherTypeAst.has('opttypegenerics')) { - otherGenerics.push( - otherTypeAst.get('opttypegenerics').get('generics').get('fulltypename') - .t, - ); - otherTypeAst - .get('opttypegenerics') - .get('generics') - .get('cdr') - .getAll() - .forEach((r) => { - otherGenerics.push(r.get('fulltypename').t); - }); - } - otherGenerics = otherGenerics.map( - (g) => - scope.deepGet(g) || - (Type.fromStringWithMap(g, interfaceMap, scope) as Type) || - new Type('-bogus-', false, true), - ); - return generics.every((t: Type, i) => - t.typeApplies(otherGenerics[i], scope, interfaceMap), - ); - } - - hasInterfaceType(): boolean { - if (this.iface) return true; - return Object.values(this.properties).some((t: Type): boolean => - t.hasInterfaceType(), - ); - } - - // There has to be a more elegant way to tackle this - static fromStringWithMap( - typestr: string, - interfaceMap: Map, - scope: Scope, - ) { - const typeAst = fulltypenameAstFromString(typestr); - const baseName = typeAst.get('typename').t; - const baseType = scope.deepGet(baseName) as Type; - if (typeAst.has('opttypegenerics')) { - const genericNames = []; - genericNames.push( - typeAst.get('opttypegenerics').get('generics').get('fulltypename').t, - ); - typeAst - .get('opttypegenerics') - .get('generics') - .get('cdr') - .getAll() - .forEach((r) => { - genericNames.push(r.get('fulltypename').t); - }); - const generics = genericNames.map((t: string) => { - const interfaceMapping = [...interfaceMap.entries()].find( - (e) => e[0].typename === t.trim(), - ); - if (interfaceMapping) return interfaceMapping[1]; - const innerType = Type.fromStringWithMap(t, interfaceMap, scope); - return innerType; - }); - return baseType.solidify( - generics - .map((g: Type) => interfaceMap.get(g) || g) - .map((t: Type) => t.typename), - scope, - ); - } else { - return interfaceMap.get(baseType) || baseType; - } - } - - realize(interfaceMap: Map, scope: Scope) { - if (this.isGenericStandin) - return [...interfaceMap.entries()].find( - (e) => e[0].typename === this.typename, - )[1]; - if (!this.iface && !this.originalType) return this; - if (this.iface) return interfaceMap.get(this) || this; - const self = new Type( - this.typename, - this.builtIn, - this.isGenericStandin, - { ...this.properties }, - { ...this.generics }, - this.originalType, - this.iface, - this.alias, - ); - const newProps = Object.values(self.properties).map((t) => - t.realize(interfaceMap, scope), - ); - Object.keys(self.properties).forEach((k, i) => { - self.properties[k] = newProps[i]; - }); - const newType = Type.fromStringWithMap(self.typename, interfaceMap, scope); - return newType; - } - - // This is only necessary for the numeric types. TODO: Can we eliminate it? - castable(otherType: Type) { - const intTypes = ['int8', 'int16', 'int32', 'int64']; - const floatTypes = ['float32', 'float64']; - if ( - intTypes.includes(this.typename) && - intTypes.includes(otherType.typename) - ) - return true; - if ( - floatTypes.includes(this.typename) && - floatTypes.includes(otherType.typename) - ) - return true; - if ( - floatTypes.includes(this.typename) && - intTypes.includes(otherType.typename) - ) - return true; - return false; - } - - static builtinTypes = { - void: new Type('void', true), - int8: new Type('int8', true), - int16: new Type('int16', true), - int32: new Type('int32', true), - int64: new Type('int64', true), - float32: new Type('float32', true), - float64: new Type('float64', true), - bool: new Type('bool', true), - string: new Type('string', true), - Error: new Type('Error', true, false, { - msg: new Type('string', true, true), - }), - Maybe: new Type( - 'Maybe', - true, - false, - { - value: new Type('T', true, true), - }, - { - T: 0, - }, - ), - Result: new Type( - 'Result', - true, - false, - { - value: new Type('T', true, true), - error: new Type('Error', true, false, { - msg: new Type('string', true, true), - }), - }, - { - T: 0, - }, - ), - Either: new Type( - 'Either', - true, - false, - { - main: new Type('T', true, true), - alt: new Type('U', true, true), - }, - { - T: 0, - U: 1, - }, - ), - Array: new Type( - 'Array', - true, - false, - { - records: new Type('V', true, true), - }, - { - V: 0, - }, - ), - ExecRes: new Type('ExecRes', false, false, { - exitCode: new Type('int64', true), - stdout: new Type('string', true), - stderr: new Type('string', true), - }), - InitialReduce: new Type( - 'InitialReduce', - false, - false, - { - arr: new Type( - 'Array', - true, - false, - { - records: new Type('T', true, true), - }, - { - T: 0, - }, - ), - initial: new Type('U', true, true), - }, - { - T: 0, - U: 1, - }, - ), - KeyVal: new Type( - 'KeyVal', - false, - false, - { - key: new Type('K', true, true), - val: new Type('V', true, true), - }, - { - K: 0, - V: 1, - }, - ), - // Placeholders to be replaced through weirdness with opcodes.ts as the self-referential piece - // does not play well with `static` - InternalRequest: new Type('InternalRequest', true, false, { - method: new Type('string', true), - url: new Type('string', true), - headers: new Type('headers', true), - body: new Type('string', true), - connId: new Type('int64', true), - }), - InternalResponse: new Type('InternalResponse', true, false, { - status: new Type('int64', true), - headers: new Type('headers', true), - body: new Type('string', true), - connId: new Type('int64', true), - }), - Seq: new Type('Seq', true, false, { - counter: new Type('int64', true, true), - limit: new Type('int64', true, true), - }), - Self: new Type('Self', true, false, { - seq: new Type('Seq', true, false, { - counter: new Type('int64', true, true), - limit: new Type('int64', true, true), - }), - recurseFn: new Type('function', true), - }), - TcpChannel: new Type('TcpChannel', true), - TcpContext: new Type( - 'TcpContext', - true, - false, - { - context: new Type('C', true, true), - channel: new Type('TcpChannel', true), - }, - { - C: 0, - }, - ), - Chunk: new Type('Chunk', true), - NsRef: new Type('NsRef', true, false, { - ns: new Type('string', true), - key: new Type('string', true), - }), - NsMut: new Type('NsMut', true, false, { - ns: new Type('string', true), - key: new Type('string', true), - }), - With: new Type( - 'With', - false, - false, - { - nskey: new Type('N', true, true), - with: new Type('T', true, true), - }, - { - N: 0, - T: 1, - }, - ), - function: new Type('function', true), - operator: new Type('operator', true), - Event: new Type( - 'Event', - true, - false, - { - type: new Type('E', true, true), - }, - { - E: 0, - }, - ), - type: new Type('type', true), - scope: new Type('scope', true), - microstatement: new Type('microstatement', true), - }; -} - -export default Type; diff --git a/compiler/src/lntoamm/UserFunction.ts b/compiler/src/lntoamm/UserFunction.ts deleted file mode 100644 index 42210aaa1..000000000 --- a/compiler/src/lntoamm/UserFunction.ts +++ /dev/null @@ -1,1073 +0,0 @@ -import { v4 as uuid } from 'uuid'; - -import * as Ast from './Ast'; -import Microstatement from './Microstatement'; -import Scope from './Scope'; -import Statement from './Statement'; -import StatementType from './StatementType'; -import Type from './Type'; -import { Args, Fn } from './Function'; -import { LPNode } from '../lp'; - -class UserFunction implements Fn { - name: string; - args: Args; - returnType: Type | LPNode; - scope: Scope; - statements: Array; - pure: boolean; - - constructor( - name: string, - args: Args, - returnType: Type | LPNode, - scope: Scope, - statements: Array, - pure: boolean, - ) { - this.name = name; - this.args = args; - this.returnType = returnType; - this.scope = scope; - for (let i = 0; i < statements.length - 1; i++) { - if (statements[i].isReturnStatement()) { - // There are unreachable statements after this line, abort - throw new Error(`Unreachable code in function '${name}' after: -${statements[i].statementAst.t.trim()} on line ${ - statements[i].statementAst.line - }:${statements[i].statementAst.char}`); - } - } - this.statements = statements; - this.pure = pure; - } - - static fromAst(functionishAst: LPNode, scope: Scope) { - if ( - functionishAst.has('fnname') || - functionishAst.has('functions') || - functionishAst.has('functionbody') - ) { - // It's a `blocklike` node - if (functionishAst.has('functions')) { - return UserFunction.fromFunctionsAst( - functionishAst.get('functions'), - scope, - ); - } - if (functionishAst.has('functionbody')) { - return UserFunction.fromFunctionbodyAst( - functionishAst.get('functionbody'), - scope, - ); - } - if (functionishAst.has('fnname')) { - // TODO: We didn't cover this path before? - } - } - if (functionishAst.has('fn')) { - // It's a `functions` node - return UserFunction.fromFunctionsAst(functionishAst, scope); - } - if (functionishAst.has('openCurly')) { - // It's a `functionbody` node - return UserFunction.fromFunctionbodyAst(functionishAst, scope); - } - return null; - } - - static fromFunctionbodyAst(functionbodyAst: LPNode, scope: Scope) { - const args = {}; - const returnType = Type.builtinTypes.void; - let pure = true; // Assume purity and then downgrade if needed - const statementsAst = functionbodyAst.get('statements'); - const statements = statementsAst.getAll().map((r) => { - const statement = Statement.create(r.get('statement'), scope); - if (!statement.pure) pure = false; - return statement; - }); - return new UserFunction(null, args, returnType, scope, statements, pure); - } - - static fromFunctionsAst(functionAst: LPNode, scope: Scope) { - const name = functionAst.has('optname') - ? functionAst.get('optname').t - : null; - const args = {}; - if (functionAst.get('optargs').has('arglist')) { - const argsAst = functionAst.get('optargs').get('arglist'); - const argsArr = []; - argsArr.push({ - variable: argsAst.get('variable').t, - fulltypename: argsAst.get('fulltypename'), - }); - argsAst - .get('cdr') - .getAll() - .forEach((r) => { - argsArr.push({ - variable: r.get('variable').t, - fulltypename: r.get('fulltypename'), - }); - }); - for (let i = 0; i < argsArr.length; i++) { - const argName = argsArr[i].variable; - let getArgType = scope.deepGet(argsArr[i].fulltypename.t); - if (!getArgType) { - if (argsArr[i].fulltypename.has('opttypegenerics')) { - getArgType = scope.deepGet( - argsArr[i].fulltypename.get('typename').t, - ) as Type; - if (!getArgType) { - throw new Error( - 'Could not find type ' + - argsArr[i].fulltypename.t + - ' for argument ' + - argName, - ); - } - if (!(getArgType instanceof Type)) { - throw new Error( - 'Function argument is not a valid type: ' + - argsArr[i].fulltypename.t, - ); - } - const genericTypes = []; - const genericAst = argsArr[i].fulltypename - .get('opttypegenerics') - .get('generics'); - genericTypes.push(genericAst.get('fulltypename').t); - genericAst - .get('cdr') - .getAll() - .forEach((r) => { - genericTypes.push(r.get('fulltypename').t); - }); - getArgType = getArgType.solidify(genericTypes, scope); - } else { - throw new Error( - 'Could not find type ' + - argsArr[i].fulltypename.t + - ' for argument ' + - argName, - ); - } - } - if (!(getArgType instanceof Type)) { - throw new Error( - 'Function argument is not a valid type: ' + - argsArr[i].fulltypename.t, - ); - } - args[argName] = getArgType; - } - } - let pure = true; - let statements = []; - if (functionAst.get('fullfunctionbody').has('functionbody')) { - const functionbody = functionAst - .get('fullfunctionbody') - .get('functionbody'); - statements = functionbody - .get('statements') - .getAll() - .map((r) => { - const statement = Statement.create(r.get('statement'), scope); - if (!statement.pure) pure = false; - return statement; - }); - } else { - const assignablesAst = functionAst - .get('fullfunctionbody') - .get('assignfunction') - .get('assignables'); - const statementAst = Ast.statementAstFromString( - `return ${assignablesAst.t};`, - ); - const statement = Statement.create(statementAst, scope); - if (!statement.pure) pure = false; - statements.push(statement); - } - return new UserFunction(name, args, functionAst, scope, statements, pure); - } - - getName() { - return this.name; - } - getArguments() { - return this.args; - } - generateReturnType() { - const functionAst = this.returnType as LPNode; // Abusing field to lazily load the return type - let returnType = null; - const scope = this.scope; - const args = this.args; - if (functionAst.has('optreturntype')) { - const fulltypename = functionAst.get('optreturntype').get('fulltypename'); - let getReturnType = scope.deepGet(fulltypename.t); - if (getReturnType == null || !(getReturnType instanceof Type)) { - if (fulltypename.has('opttypegenerics')) { - getReturnType = scope.deepGet(fulltypename.get('typename').t); - if (getReturnType == null) { - throw new Error( - 'Could not find type ' + - fulltypename.t + - ' for function ' + - functionAst.get('optname').t, - ); - } - if (!(getReturnType instanceof Type)) { - throw new Error( - 'Function return is not a valid type: ' + fulltypename.t, - ); - } - const genericTypes = []; - genericTypes.push( - fulltypename - .get('opttypegenerics') - .get('generics') - .get('fulltypename').t, - ); - fulltypename - .get('opttypegenerics') - .get('generics') - .get('cdr') - .getAll() - .forEach((r) => { - genericTypes.push(r.get('fulltypename').t); - }); - getReturnType = getReturnType.solidify(genericTypes, scope); - } else { - throw new Error( - 'Could not find type ' + - fulltypename.t + - ' for function ' + - functionAst.get('optname').t, - ); - } - } - returnType = getReturnType; - } - if (functionAst.get('fullfunctionbody').has('functionbody')) { - if (returnType === null) returnType = Type.builtinTypes['void']; - } else { - const assignablesAst = functionAst - .get('fullfunctionbody') - .get('assignfunction') - .get('assignables'); - if ( - !returnType && - Object.keys(args).every((arg) => args[arg].typename !== 'function') - ) { - // We're going to use the Microstatement logic here - const microstatements = []; - // First lets add all microstatements from the provided scope into the list - // TODO: If this pattern is ever used more than once, add a new method to the Scope type - Object.keys(scope.vals).forEach((val) => { - if (scope.vals[val] instanceof Microstatement) { - microstatements.push(scope.vals[val]); - } - }); - Object.keys(args).forEach((arg) => { - microstatements.push( - new Microstatement( - StatementType.REREF, - scope, - true, - arg, - args[arg], - [], - [], - arg, - ), - ); - }); - Microstatement.fromAssignablesAst( - assignablesAst, - scope, - microstatements, - ); - const last = microstatements[microstatements.length - 1]; - if (last.statementType !== StatementType.EMIT) { - // TODO: Come up with a better solution than this hackery for void function calls as the - // only value for a one-liner function - returnType = last.outputType; - } else { - returnType = Type.builtinTypes.void; - } - } else if (!returnType) { - // TODO: Generalize this hackery for opcodes that take closure functions - const opcodeName = assignablesAst.t.split('(')[0]; - const opcode = scope.deepGet(opcodeName) as Array; - returnType = opcode - ? opcode[0].getReturnType() - : Type.builtinTypes['void']; - } - } - return returnType; - } - - getReturnType() { - if (!(this.returnType instanceof Type)) { - this.returnType = this.generateReturnType(); - } - return this.returnType as Type; - } - - isPure() { - return this.pure; - } - - toFnStr() { - return ` - fn ${this.name || ''} (${Object.keys(this.args) - .map((argName) => `${argName}: ${this.args[argName].typename}`) - .join(', ')}): ${this.getReturnType().typename} { - ${this.statements.map((s) => s.statementAst.t).join('\n')} - } - `.trim(); - } - - static conditionalToCond(cond: LPNode, scope: Scope) { - const newStatements: Array = []; - let hasConditionalReturn = false; // Flag for potential second pass - const condName = '_' + uuid().replace(/-/g, '_'); - const condStatement = Ast.statementAstFromString( - ` - const ${condName}: bool = ${cond.get('assignables').t} - `.trim() + ';', - ); - const condBlockFn = ( - cond.get('blocklike').has('functionbody') - ? UserFunction.fromFunctionbodyAst( - cond.get('blocklike').get('functionbody'), - scope, - ) - : cond.get('blocklike').has('fnname') - ? // TODO: If more than one function matches, need to run multiple dispatch logic - scope.deepGet(cond.get('blocklike').get('fnname').t)[0] - : UserFunction.fromFunctionsAst( - cond.get('blocklike').get('functions'), - scope, - ) - ).maybeTransform(new Map(), scope); - if ( - condBlockFn.statements[ - condBlockFn.statements.length - 1 - ].isReturnStatement() - ) { - hasConditionalReturn = true; - } - const condBlock = condBlockFn.toFnStr(); - const condCall = Ast.statementAstFromString( - ` - cond(${condName}, ${condBlock}) - `.trim() + ';', - ); // TODO: If the blocklike is a reference, grab it and inline it - newStatements.push(condStatement, condCall); - if (cond.has('elsebranch')) { - const notcond = cond.get('elsebranch'); - if (notcond.get('condorblock').has('blocklike')) { - const notblock = notcond.get('condorblock').get('blocklike'); - const elseBlockFn = ( - notblock.has('functionbody') - ? UserFunction.fromFunctionbodyAst( - notblock.get('functionbody'), - scope, - ) - : notblock.has('fnname') - ? // TODO: If more than one function matches, need to run multiple dispatch logic - scope.deepGet(notblock.get('fnname').t)[0] - : UserFunction.fromFunctionsAst(notblock.get('functions'), scope) - ).maybeTransform(new Map(), scope); - if ( - elseBlockFn.statements[ - elseBlockFn.statements.length - 1 - ].isReturnStatement() - ) { - hasConditionalReturn = true; - } - const elseBlock = elseBlockFn.toFnStr(); - const elseStatement = Ast.statementAstFromString( - ` - cond(not(${condName}), ${elseBlock}) - `.trim() + ';', - ); - newStatements.push(elseStatement); - } else { - const res = UserFunction.conditionalToCond( - notcond.get('condorblock').get('conditionals'), - scope, - ); - const innerCondStatements = res[0] as Array; - if (res[1]) hasConditionalReturn = true; - const elseStatement = Ast.statementAstFromString( - ` - cond(!${condName}, fn { - ${innerCondStatements.map((s) => s.t).join('\n')} - }) - `.trim() + ';', - ); - newStatements.push(elseStatement); - } - } - return [newStatements, hasConditionalReturn]; - } - - static earlyReturnRewrite( - retVal: string, - retNotSet: string, - statements: Array, - scope: Scope, - ) { - const replacementStatements = []; - while (statements.length > 0) { - const s = statements.shift(); - // TODO: This doesn't work for actual direct-usage of `cond` in some sort of method chaining - // if that's even possible. Probably lots of other weirdness to deal with here. - if ( - s.has('assignables') && - s - .get('assignables') - .get('assignables') - .getAll()[0] - .get('withoperators') - .get('baseassignablelist') - .getAll().length >= 2 && - s - .get('assignables') - .get('assignables') - .getAll()[0] - .get('withoperators') - .get('baseassignablelist') - .getAll()[0] - .t.trim() === 'cond' && - s - .get('assignables') - .get('assignables') - .getAll()[0] - .get('withoperators') - .get('baseassignablelist') - .getAll()[1] - .get('baseassignable') - .has('fncall') - ) { - // TODO: Really need to rewrite - const argsAst = s - .get('assignables') - .get('assignables') - .getAll()[0] - .get('withoperators') - .get('baseassignablelist') - .getAll()[1] - .get('baseassignable') - .get('fncall') - .get('assignablelist'); - const args = []; - if (argsAst.has('assignables')) { - args.push(argsAst.get('assignables')); - argsAst - .get('cdr') - .getAll() - .forEach((r) => { - args.push(r.get('assignables')); - }); - } - if (args.length == 2) { - const block = args[1] - .getAll()[0] - .get('withoperators') - .has('baseassignablelist') - ? args[1] - .getAll()[0] - .get('withoperators') - .get('baseassignablelist') - .getAll()[0] - .get('baseassignable') - : null; - if (block) { - const blockFn = UserFunction.fromAst(block, scope); - if ( - blockFn.statements[ - blockFn.statements.length - 1 - ].isReturnStatement() - ) { - const innerStatements = blockFn.statements.map( - (s) => s.statementAst, - ); - const newBlockStatements = UserFunction.earlyReturnRewrite( - retVal, - retNotSet, - innerStatements, - scope, - ); - const cond = args[0].t.trim(); - const newBlock = Ast.statementAstFromString( - ` - cond(${cond}, fn { - ${newBlockStatements.map((s) => s.t).join('\n')} - }) - `.trim() + ';', - ); - replacementStatements.push(newBlock); - if (statements.length > 0) { - const remainingStatements = UserFunction.earlyReturnRewrite( - retVal, - retNotSet, - statements, - scope, - ); - const remainingBlock = Ast.statementAstFromString( - ` - cond(${retNotSet}, fn { - ${remainingStatements.map((s) => s.t).join('\n')} - }) - `.trim() + ';', - ); - replacementStatements.push(remainingBlock); - } - } else { - replacementStatements.push(s); - } - } else { - replacementStatements.push(s); - } - } else { - replacementStatements.push(s); - } - } else { - replacementStatements.push(s); - } - } - // If no inner conditional was found in this branch, check if there's a final return - if (replacementStatements[replacementStatements.length - 1].has('exits')) { - const retStatement = replacementStatements.pop(); - if (retStatement.get('exits').get('retval').has('assignables')) { - const newAssign = Ast.statementAstFromString( - ` - ${retVal} = ref(${ - retStatement.get('exits').get('retval').get('assignables').t - }) - `.trim() + ';', - ); - replacementStatements.push(newAssign); - } - replacementStatements.push( - Ast.statementAstFromString( - ` - ${retNotSet} = clone(false) - `.trim() + ';', - ), - ); - } - return replacementStatements; - } - - maybeTransform(interfaceMap: Map, scope?: Scope) { - if ( - this.statements.some((s) => s.isConditionalStatement()) || - this.statements.some((s) => s.hasObjectLiteral()) - ) { - // First pass, convert conditionals to `cond` fn calls and wrap assignment statements - let statementAsts = []; - let hasConditionalReturn = false; // Flag for potential second pass - for (let i = 0; i < this.statements.length; i++) { - const s = new Statement( - this.statements[i].statementAst, - this.statements[i].scope, - this.statements[i].pure, - ); - // Potentially rewrite the type for the object literal to match the interface type used by - // a specific call - const str = s.statementAst.t; - const corrected = str.replace( - /new ([^<]+)<([^{[]+)> *([{[])/g, - ( - _: any, - basetypestr: string, - genericstr: string, - openstr: string, - ) => { - let newScope = this.scope; - if (scope !== undefined) { - newScope = new Scope(scope); - newScope.secondaryPar = this.scope; - } - const originaltypestr = `${basetypestr.trim()}<${genericstr.trim()}>`; - let originalType = newScope.deepGet(originaltypestr) as Type; - if (!originalType || !(originalType instanceof Type)) { - // It may be the first time this particular type has shown up, let's build it - const typeAst = Ast.fulltypenameAstFromString(originaltypestr); - const baseTypeName = typeAst.get('typename').t; - const generics = []; - if (typeAst.has('opttypegenerics')) { - const genericsAst = typeAst - .get('opttypegenerics') - .get('generics'); - generics.push(genericsAst.get('fulltypename').t); - genericsAst - .get('cdr') - .getAll() - .forEach((r) => { - generics.push(r.get('fulltypename').t); - }); - } - const baseType = newScope.deepGet(baseTypeName) as Type; - if (!baseType || !(baseType instanceof Type)) { - // Now we panic - throw new Error('This should be impossible'); - } - originalType = baseType.solidify(generics, newScope); - } - const replacementType = originalType.realize( - interfaceMap, - newScope, - ); - return `new ${replacementType.typename} ${openstr}`; - }, - ); - // TODO: Get rid of these regex-based type corrections - const secondCorrection = corrected.replace( - /: (?!new )([^:<,]+)<([^{)]+)>( *[,{)])/g, - ( - _: any, - basetypestr: string, - genericstr: string, - openstr: string, - ) => { - let newScope = this.scope; - if (scope !== undefined) { - newScope = new Scope(scope); - newScope.secondaryPar = this.scope; - } - const originaltypestr = `${basetypestr.trim()}<${genericstr.trim()}>`; - let originalType = newScope.deepGet(originaltypestr) as Type; - if (!originalType || !(originalType instanceof Type)) { - // It may be the first time this particular type has shown up, let's build it - const typeAst = Ast.fulltypenameAstFromString(originaltypestr); - const baseTypeName = typeAst.get('typename').t; - const generics = []; - if (typeAst.has('opttypegenerics')) { - const genericsAst = typeAst - .get('opttypegenerics') - .get('generics'); - generics.push(genericsAst.get('fulltypename').t); - genericsAst - .get('cdr') - .getAll() - .forEach((r) => { - generics.push(r.get('fulltypename').t); - }); - } - const baseType = newScope.deepGet(baseTypeName) as Type; - if (!baseType || !(baseType instanceof Type)) { - // Now we panic - throw new Error('This should be impossible'); - } - originalType = baseType.solidify(generics, newScope); - } - const replacementType = originalType.realize( - interfaceMap, - newScope, - ); - return `: ${replacementType.typename}${openstr}`; - }, - ); - const correctedAst = Ast.statementAstFromString(secondCorrection); - s.statementAst = correctedAst; - // statementAsts.push(correctedAst) - let newScope = this.scope; - if (scope !== undefined) { - newScope = new Scope(scope); - newScope.secondaryPar = this.scope; - } - if (s.isConditionalStatement()) { - const cond = s.statementAst.get('conditionals'); - const res = UserFunction.conditionalToCond(cond, newScope); - const newStatements = res[0] as Array; - if (res[1]) hasConditionalReturn = true; - statementAsts.push(...newStatements); - } else if (s.statementAst.has('assignments')) { - const a = s.statementAst.get('assignments'); - const wrappedAst = Ast.statementAstFromString( - ` - ${a.get('varn').t} = ref(${a.get('assignables').t}) - `.trim() + ';', - ); - statementAsts.push(wrappedAst); - } else if ( - s.statementAst.has('declarations') && - s.statementAst.get('declarations').has('letdeclaration') - ) { - const l = s.statementAst.get('declarations').get('letdeclaration'); - const name = l.get('variable').t; - const type = l.has('typedec') - ? l.get('typedec').get('fulltypename').t - : undefined; - const v = l.get('assignables').t; - const wrappedAst = Ast.statementAstFromString( - ` - let ${name}${type ? `: ${type}` : ''} = ref(${v}) - `.trim() + ';', - ); - statementAsts.push(wrappedAst); - } else { - statementAsts.push(s.statementAst); - } - } - // Second pass, there was a conditional return, mutate everything *again* so the return is - // instead hoisted into writing a closure variable - if (hasConditionalReturn) { - // Need the UUID to make sure this is unique if there's multiple layers of nested returns - const retNamePostfix = '_' + uuid().replace(/-/g, '_'); - const retVal = 'retVal' + retNamePostfix; - const retNotSet = 'retNotSet' + retNamePostfix; - const retValStatement = Ast.statementAstFromString( - ` - let ${retVal}: ${this.getReturnType().typename} = clone() - `.trim() + ';', - ); - const retNotSetStatement = Ast.statementAstFromString( - ` - let ${retNotSet}: bool = clone(true) - `.trim() + ';', - ); - const replacementStatements = [retValStatement, retNotSetStatement]; - replacementStatements.push( - ...UserFunction.earlyReturnRewrite( - retVal, - retNotSet, - statementAsts, - this.scope, - ), - ); - replacementStatements.push( - Ast.statementAstFromString( - ` - return ${retVal} - `.trim() + ';', - ), - ); - statementAsts = replacementStatements; - } - - // TODO: Should these be attached to the scope or should callers provide a merged scope? - const newArgs = {}; - for (const argName in this.args) { - const a = this.args[argName]; - newArgs[argName] = interfaceMap.has(a) ? interfaceMap.get(a) : a; - this.scope.put(newArgs[argName].typename, newArgs[argName]); - } - const newRet = interfaceMap.has(this.getReturnType()) - ? interfaceMap.get(this.getReturnType()) - : this.getReturnType(); - this.scope.put(newRet.typename, newRet); - - const fnStr = ` - fn ${this.name || ''} (${Object.keys(newArgs) - .map((argName) => `${argName}: ${newArgs[argName].typename}`) - .join(', ')}): ${newRet.typename} { - ${statementAsts.map((s) => s.t).join('\n')} - } - `.trim(); - const fn = UserFunction.fromAst( - Ast.functionAstFromString(fnStr), - this.scope, - ); - return fn; - } else { - let hasNewType = false; - const newArgs = {}; - for (const argName in this.args) { - const a = this.args[argName]; - newArgs[argName] = interfaceMap.has(a) ? interfaceMap.get(a) : a; - if (newArgs[argName] !== this.args[argName]) { - this.scope.put(newArgs[argName].typename, newArgs[argName]); - hasNewType = true; - } - } - const newRet = interfaceMap.has(this.getReturnType()) - ? interfaceMap.get(this.getReturnType()) - : this.getReturnType(); - if (newRet !== this.getReturnType()) { - this.scope.put(newRet.typename, newRet); - hasNewType = true; - } - if (hasNewType) { - const statementAsts = this.statements.map((s) => s.statementAst); - const fnStr = ` - fn ${this.name || ''} (${Object.keys(newArgs) - .map((argName) => `${argName}: ${newArgs[argName].typename}`) - .join(', ')}): ${newRet.typename} { - ${statementAsts.map((s) => s.t).join('\n')} - } - `.trim(); - const fn = UserFunction.fromAst( - Ast.functionAstFromString(fnStr), - this.scope, - ); - return fn; - } else { - return this; - } - } - } - - microstatementInlining( - realArgNames: Array, - s: Scope, - microstatements: Array, - ) { - const scope = new Scope(s); - scope.secondaryPar = this.scope; - // Get the current statement length for usage in multiple cleanup routines - const originalStatementLength = microstatements.length; - // First, check if there are any ENTERFN microstatements indicating a nested inlining, then - // check that list for self-containment, which would cause an infinite loop in compilation and - // abort with a useful error message. - const enterfns = microstatements.filter( - (m) => m.statementType === StatementType.ENTERFN, - ); - const isRecursive = enterfns.some((m) => m.fns[0] === this); - if (isRecursive) { - const path = enterfns - .slice(enterfns.findIndex((m) => m.fns[0] === this)) - .map((m) => m.fns[0].getName()); - path.push(this.getName()); - const pathstr = path.join(' -> '); - throw new Error(`Recursive callstack detected: ${pathstr}. Aborting.`); - } else { - // Otherwise, add a marker for this - microstatements.push( - new Microstatement( - StatementType.ENTERFN, - scope, - true, - '', - Type.builtinTypes.void, - [], - [this], - ), - ); - } - // Perform a transform, if necessary, before generating the microstatements - // Resolve circular dependency issue - const internalNames = Object.keys(this.args); - const inputs = realArgNames.map((n) => - Microstatement.fromVarName(n, scope, microstatements), - ); - const inputTypes = inputs.map((i) => i.outputType); - const originalTypes = Object.values(this.getArguments()); - const interfaceMap: Map = new Map(); - originalTypes.forEach((t, i) => - t.typeApplies(inputTypes[i], scope, interfaceMap), - ); - for (let i = 0; i < internalNames.length; i++) { - const realArgName = realArgNames[i]; - // Instead of copying the relevant data, define a reference to where the data is located with - // an alias for the function's expected variable name so statements referencing the argument - // can be rewritten to use the new variable name. - microstatements.push( - new Microstatement( - StatementType.REREF, - scope, - true, - realArgName, - inputTypes[i], - [], - [], - internalNames[i], - ), - ); - } - const fn = this.maybeTransform(interfaceMap, scope); - for (const s of fn.statements) { - Microstatement.fromStatement(s, microstatements, scope); - } - // Delete `REREF`s except a `return` statement's `REREF` to make sure it doesn't interfere with - // the outer scope (if it has the same variable name defined, for instance) - for (let i = originalStatementLength; i < microstatements.length - 1; i++) { - if (microstatements[i].statementType == StatementType.REREF) { - microstatements.splice(i, 1); - i--; - } - } - // If the output return type is an interface or is a realized generic with an inner interface - // type, figure out what its actual type is. This is assuming that any input type of the same - // interface's real type is the same as the output type, which is a valid assumption as long as - // all inputs of that particular interface are the same type. TODO: If this is not true, it must - // be a compile-time error earlier on. - const last = microstatements[microstatements.length - 1]; - if (!this.getReturnType().typeApplies(last.outputType, scope, new Map())) { - const returnTypeAst = Ast.fulltypenameAstFromString( - this.getReturnType().typename, - ); - let returnSubtypes = []; - if (returnTypeAst.has('opttypegenerics')) { - const generics = returnTypeAst.get('opttypegenerics').get('generics'); - const returnSubtypeAsts = []; - returnSubtypeAsts.push(generics.get('fulltypename')); - generics - .get('cdr') - .getAll() - .forEach((r) => { - returnSubtypeAsts.push(r.get('fulltypename')); - }); - returnSubtypes = returnSubtypeAsts.map((r) => { - let t = scope.deepGet(r.t); - if (!t) { - const innerGenerics = []; - if (r.has('opttypegenerics')) { - innerGenerics.push( - r.get('opttypegenerics').get('generics').get('fulltypename').t, - ); - r.get('opttypegenerics') - .get('generics') - .get('cdr') - .getAll() - .forEach((r2) => { - innerGenerics.push(r2.t); - }); - } - t = (scope.deepGet(r.get('typename').t) as Type).solidify( - innerGenerics, - scope, - ); - } - return t; - }); - } - if (this.getReturnType().iface) { - const originalArgTypes = Object.values(this.args); - for (let i = 0; i < inputTypes.length; i++) { - if (this.getReturnType() === originalArgTypes[i]) { - microstatements[microstatements.length - 1].outputType = - inputTypes[i]; - } - } - } else if (returnSubtypes.some((t: Type) => t.hasInterfaceType())) { - const oldReturnType = this.getReturnType(); - const originalArgTypes = Object.values(this.args); - for (let i = 0; i < inputTypes.length; i++) { - for (let j = 0; j < returnSubtypes.length; j++) { - if (returnSubtypes[j] === originalArgTypes[i]) { - returnSubtypes[j] = inputTypes[i]; - } - } - } - // Try to tackle issue with `main` and `alt` when they are in a function without branching - if (returnSubtypes.some((t: Type) => !!t.iface)) { - last.outputType = this.getReturnType(); - } else { - // We were able to piece together the right type info, let's use it - const newReturnType = oldReturnType.originalType.solidify( - returnSubtypes.map((t: Type) => t.typename), - scope, - ); - last.outputType = newReturnType; - } - } else { - const lastTypeAst = Ast.fulltypenameAstFromString( - last.outputType.typename, - ); - const lastSubtypes = []; - if (lastTypeAst.has('opttypegenerics')) { - const generics = lastTypeAst.get('opttypegenerics').get('generics'); - lastSubtypes.push(scope.deepGet(generics.get('fulltypename').t)); - generics - .get('cdr') - .getAll() - .forEach((r) => { - lastSubtypes.push(scope.deepGet(r.get('fulltypename').t)); - }); - } - if (lastSubtypes.some((t: Type) => t.hasInterfaceType())) { - const oldLastType = last.outputType; - const originalArgTypes = Object.values(this.args); - for (let i = 0; i < inputTypes.length; i++) { - for (let j = 0; j < lastSubtypes.length; j++) { - if (lastSubtypes[j] === originalArgTypes[i]) { - lastSubtypes[j] = inputTypes[i]; - } - } - } - if (lastSubtypes.some((t: Type) => t.hasInterfaceType())) { - // Just fall back to the user-provided type for now - last.outputType = this.getReturnType(); - } else { - const newLastType = oldLastType.originalType.solidify( - lastSubtypes.map((t: Type) => t.typename), - scope, - ); - last.outputType = newLastType; - } - } - } - } - // If `last` is a REREF, we also need to potentially update the type on the original record - for (let i = 0; i < microstatements.length; i++) { - const m = microstatements[i]; - if ( - m.outputName === last.outputName && - m.statementType !== StatementType.REREF - ) { - m.outputType = last.outputType; - break; - } - } - // Now that we're done with this, we need to pop out all of the ENTERFN microstatements created - // after this one so we don't mark non-recursive calls to a function multiple times as recursive - // TODO: This is not the most efficient way to do things, come up with a better metadata - // mechanism to pass around. - for (let i = originalStatementLength; i < microstatements.length; i++) { - if (microstatements[i].statementType === StatementType.ENTERFN) { - microstatements.splice(i, 1); - i--; - } - } - } - - static dispatchFn(fns: Array, argumentTypeList: Array, s: Scope) { - let fn = null; - for (let i = 0; i < fns.length; i++) { - const scope = new Scope(s); - scope.secondaryPar = (fns[i] as UserFunction).scope; - const args = fns[i].getArguments(); - const argList = Object.values(args); - if (argList.length !== argumentTypeList.length) continue; - let skip = false; - for (let j = 0; j < argList.length; j++) { - if (argList[j].typeApplies(argumentTypeList[j], scope)) continue; - skip = true; - } - if (skip) continue; - fn = fns[i]; - } - if (fn == null) { - let errMsg = - 'Unable to find matching function for name and argument type set'; - const argTypes = []; - for (let i = 0; i < argumentTypeList.length; i++) { - argTypes.push('<' + argumentTypeList[i].typename + '>'); - } - errMsg += '\n' + fns[0].getName() + '(' + argTypes.join(', ') + ')\n'; - errMsg += 'Candidate functions considered:\n'; - for (let i = 0; i < fns.length; i++) { - const fn = fns[i]; - if (fn instanceof UserFunction) { - const fnStr = fn.toFnStr().split('{')[0]; - errMsg += `${fnStr}\n`; - } else { - // TODO: Add this to the opcode definition, too? - errMsg += `fn ${fn.getName()}(${Object.entries(fn.getArguments()).map( - (kv) => `${kv[0]}: ${kv[1].typename}`, - )}): ${fn.getReturnType().typename}\n`; - } - } - throw new Error(errMsg); - } - return fn; - } -} - -export default UserFunction; diff --git a/compiler/src/lntoamm/index.ts b/compiler/src/lntoamm/index.ts deleted file mode 100644 index c7b2e54bb..000000000 --- a/compiler/src/lntoamm/index.ts +++ /dev/null @@ -1,294 +0,0 @@ -import * as fs from 'fs'; - -import { v4 as uuid } from 'uuid'; - -import * as Ast from './Ast'; -import * as Std from './Std'; -import Event from './Event'; -import Microstatement from './Microstatement'; -import Module from './Module'; -import StatementType from './StatementType'; -import Type from './Type'; -import UserFunction from './UserFunction'; -import { LPNode } from '../lp'; - -const hoistConst = ( - microstatements: Array, - constantDedupeLookup: Record, - constantDuplicateLookup: Record, - constants: Set, - eventTypes: Set, -) => { - let i = 0; - while (i < microstatements.length) { - const m = microstatements[i]; - if (m.statementType === StatementType.CONSTDEC && m.fns.length === 0) { - const original = constantDedupeLookup[m.inputNames[0]]; - if (!original) { - constants.add(m); - if (!m.outputType.builtIn) { - eventTypes.add(m.outputType); - } - microstatements.splice(i, 1); - constantDedupeLookup[m.inputNames[0]] = m; - } else { - constantDuplicateLookup[m.outputName] = original.outputName; - // Rewrite with the replaced name - for (let j = i + 1; j < microstatements.length; j++) { - const n = microstatements[j]; - for (let k = 0; k < n.inputNames.length; k++) { - if (n.inputNames[k] === m.outputName) { - n.inputNames[k] = original.outputName; - } - } - } - microstatements.splice(i, 1); - } - } else if (m.statementType === StatementType.CLOSURE) { - hoistConst( - m.closureStatements, - constantDedupeLookup, - constantDuplicateLookup, - constants, - eventTypes, - ); - i++; - } else { - i++; - } - } -}; - -const finalDedupe = ( - microstatements: Array, - constantDuplicateLookup: Record, -) => { - for (let i = 0; i < microstatements.length; i++) { - const m = microstatements[i]; - if ( - m.statementType !== StatementType.LETDEC && - m.statementType !== StatementType.CLOSURE - ) { - for (let j = 0; j < m.inputNames.length; j++) { - if (constantDuplicateLookup[m.inputNames[j]]) { - m.inputNames[j] = constantDuplicateLookup[m.inputNames[j]]; - } - } - } else if (m.statementType === StatementType.CLOSURE) { - finalDedupe(m.closureStatements, constantDuplicateLookup); - } - } -}; - -const moduleAstsFromFile = (filename: string) => { - const moduleAsts = {}; - const paths = []; - const rootPath = fs.realpathSync(filename); - paths.push(rootPath); - while (paths.length > 0) { - const modulePath = paths.shift(); - let module = null; - try { - module = Ast.fromFile(modulePath); - } catch (e) { - console.error('Could not load ' + modulePath); - throw e; - } - moduleAsts[modulePath] = module; - const imports = Ast.resolveImports(modulePath, module); - for (let i = 0; i < imports.length; i++) { - if ( - !moduleAsts[imports[i]] && - !(imports[i].substring(0, 5) === '@std/') - ) { - paths.push(imports[i]); - } - } - } - return moduleAsts; -}; - -const moduleAstsFromString = (str: string) => { - // If loading from a string, it's in the browser and some internal state needs cleaning. Some of - // this doesn't appear to affect things, but better to compile from a known state - Event.allEvents = [Event.allEvents[0]]; // Keep the `start` event - Event.allEvents[0].handlers = []; // Reset the registered handlers on the `start` event - const moduleAsts = {}; - const fakeRoot = '/fake/root/test.ln'; - let module = null; - try { - module = Ast.fromString(str); - } catch (e) { - console.error('Could not load test.ln'); - throw e; - } - moduleAsts[fakeRoot] = module; - const imports = Ast.resolveImports(fakeRoot, module); - for (let i = 0; i < imports.length; i++) { - if ( - moduleAsts[imports[i]] === null && - !(imports[i].substring(0, 5) === '@std/') - ) { - console.error('Only @std imports allowed in the playground'); - throw new Error('Import declaration error'); - } - } - return moduleAsts; -}; - -interface ModuleAstLookup { - [key: string]: LPNode; -} -const ammFromModuleAsts = (moduleAsts: ModuleAstLookup) => { - // Load the standard library - const stdFiles: Set = new Set(); - for (const [modulePath, module] of Object.entries(moduleAsts)) { - for (const importt of Ast.resolveImports(modulePath, module)) { - if (importt.substring(0, 5) === '@std/') { - stdFiles.add(importt.substring(5, importt.length) + '.ln'); - } - } - } - Std.loadStdModules(stdFiles); - const rootScope = Module.getAllModules()[''].exportScope; - // Load all modules - Module.modulesFromAsts(moduleAsts, rootScope); - // This implicitly populates the `allEvents` static property on the `Event` type, which we can - // use to serialize out the definitions, skipping the built-in events. In the process we're need - // to check a hashset for duplicate event names and rename as necessary. We also need to get the - // list of user-defined types that we need to emit. - const eventNames = new Set(); - const eventTypeNames = new Set(); - const eventTypes: Set = new Set(); - const constantDedupeLookup = {}; // String to Microstatement object - const constantDuplicateLookup = {}; // String to String object - const constants: Set = new Set(); // Microstatment objects - for (const evt of Event.allEvents) { - // Skip built-in events - if (evt.builtIn) continue; - // Check if there's a collision - if (eventNames.has(evt.name)) { - // We modify the event name by attaching a UUIDv4 to it - evt.name = evt.name + '_' + uuid().replace(/-/g, '_'); - } - // Add the event to the list - eventNames.add(evt.name); - // Now on to event type processing - const type = evt.type; - // Skip built-in types, too - if (type.builtIn) continue; - // Check if there's a collision - if (eventTypeNames.has(type.typename)) { - // An event type may be seen multiple times, make sure this is an actual collision - if (eventTypes.has(type)) continue; // This event was already processed, so we're done - // Modify the type name by attaching a UUIDv4 to it - type.typename = type.typename + '_' + uuid().replace(/-/g, '_'); - } - // Add the type to the list - eventTypeNames.add(type.typename); - eventTypes.add(type); - // Determine if any of the properties of the type should be added to the list - for (const propType of Object.values(type.properties)) { - // Skip built-in types, too - if ((propType as Type).builtIn) continue; - // Check if there's a collision - if (eventTypeNames.has((propType as Type).typename)) { - // A type may be seen multiple times, make sure this is an actual collision - if (eventTypes.has(propType)) continue; // This event was already processed, so we're done - // Modify the type name by attaching a UUIDv4 to it - (propType as Type).typename = - (propType as Type).typename + '_' + uuid().replace(/-/g, '_'); - } - // Add the type to the list - eventTypeNames.add((propType as Type).typename); - eventTypes.add(propType); - } - } - // Extract the handler definitions and constant data - const handlers = {}; // String to array of Microstatement objects - for (const evt of Event.allEvents) { - for (const handler of evt.handlers) { - if (handler instanceof UserFunction) { - // Define the handler preamble - let handlerDec = 'on ' + evt.name + ' fn ('; - const argList = []; - const microstatements = []; - for (const arg of Object.keys(handler.getArguments())) { - argList.push(arg + ': ' + handler.getArguments()[arg].typename); - microstatements.push( - new Microstatement( - StatementType.ARG, - handler.scope, - true, - arg, - handler.getArguments()[arg], - [], - [], - ), - ); - } - handlerDec += argList.join(', '); - handlerDec += '): ' + handler.getReturnType().typename + ' {'; - // Extract the handler statements and compile into microstatements - const statements = handler.maybeTransform(new Map()).statements; - for (const s of statements) { - Microstatement.fromStatement(s, microstatements); - } - // Pull the constants out of the microstatements into the constants set. - hoistConst( - microstatements, - constantDedupeLookup, - constantDuplicateLookup, - constants, - eventTypes, - ); - // Register the handler and remaining statements - handlers.hasOwnProperty(handlerDec) - ? handlers[handlerDec].push(microstatements) - : (handlers[handlerDec] = [microstatements]); - } - } - } - // Second pass to fully-deduplicate constants - for (const handler of Object.keys(handlers)) { - const functions = handlers[handler]; - for (const microstatements of functions) { - finalDedupe(microstatements, constantDuplicateLookup); - } - } - - let outStr = ''; - // Print the event types - /* for (const eventType of eventTypes) { - outStr += eventType.toString() + "\n" - } */ // TODO: It doesn't appear to be required in the rest of the stack - // Print the constants - for (const constant of constants) { - outStr += constant.toString() + '\n'; - } - // Print the user-defined event declarations - for (const evt of Event.allEvents) { - if (evt.builtIn) continue; // Skip built-in events - outStr += evt.toString() + '\n'; - } - // Print the user-defined event handlers - for (const [handlerDec, handlersList] of Object.entries(handlers)) { - for (const microstatements of handlersList as Array< - Array - >) { - outStr += handlerDec + '\n'; - for (const m of microstatements) { - const mString = m.toString(); - if (mString === '') continue; - outStr += ' ' + mString + '\n'; - } - outStr += '}\n'; - } - } - return outStr; -}; - -export const fromFile = (filename: string) => - ammFromModuleAsts(moduleAstsFromFile(filename)); -export const fromString = (str: string) => - ammFromModuleAsts(moduleAstsFromString(str)); diff --git a/compiler/src/lntoamm/opcodes.ts b/compiler/src/lntoamm/opcodes.ts deleted file mode 100644 index c70c2daed..000000000 --- a/compiler/src/lntoamm/opcodes.ts +++ /dev/null @@ -1,1394 +0,0 @@ -import { v4 as uuid } from 'uuid'; - -import Event from './Event'; -import Microstatement from './Microstatement'; -import Module from './Module'; -import Scope from './Scope'; -import StatementType from './StatementType'; -import UserFunction from './UserFunction'; -import { Interface, Type } from './Type'; -import { fulltypenameAstFromString } from './Ast'; - -const opcodeScope = new Scope(); -const opcodeModule = new Module(opcodeScope); - -// Base types -const addBuiltIn = (name: string) => { - opcodeScope.put(name, Type.builtinTypes[name]); -}; -[ - 'void', - 'int8', - 'int16', - 'int32', - 'int64', - 'float32', - 'float64', - 'bool', - 'string', - 'function', - 'operator', - 'Error', - 'Maybe', - 'Result', - 'Either', - 'Array', - 'ExecRes', - 'InitialReduce', - 'KeyVal', - 'InternalRequest', - 'InternalResponse', - 'Seq', - 'Self', - 'TcpChannel', - 'TcpContext', - 'Chunk', - 'NsRef', - 'NsMut', - 'With', -].map(addBuiltIn); -Type.builtinTypes['Array'].solidify(['string'], opcodeScope); -opcodeScope.put( - 'any', - new Type('any', true, false, {}, {}, null, new Interface('any')), -); -opcodeScope.put( - 'anythingElse', - new Type( - 'anythingElse', - true, - false, - {}, - {}, - null, - new Interface('anythingElse'), - ), -); -Type.builtinTypes['Array'].solidify(['any'], opcodeScope); -Type.builtinTypes['Array'].solidify(['anythingElse'], opcodeScope); -Type.builtinTypes['KeyVal'].solidify(['string', 'string'], opcodeScope); -Type.builtinTypes['Array'].solidify(['KeyVal'], opcodeScope); -Type.builtinTypes['TcpContext'].solidify(['any'], opcodeScope); -// HTTP server opcode-related builtin Types hackery, also defined in std/http.ln -Type.builtinTypes.InternalRequest.properties = { - method: opcodeScope.get('string') as Type, - url: opcodeScope.get('string') as Type, - headers: opcodeScope.get('Array>') as Type, - body: opcodeScope.get('string') as Type, - connId: opcodeScope.get('int64') as Type, -}; -Type.builtinTypes.InternalResponse.properties = { - status: opcodeScope.get('int64') as Type, - headers: opcodeScope.get('Array>') as Type, - body: opcodeScope.get('string') as Type, - connId: opcodeScope.get('int64') as Type, -}; -Type.builtinTypes.Maybe.solidify(['any'], opcodeScope); -Type.builtinTypes.Maybe.solidify(['string'], opcodeScope); -Type.builtinTypes.Result.solidify(['any'], opcodeScope); -Type.builtinTypes.Result.solidify(['anythingElse'], opcodeScope); -Type.builtinTypes.Result.solidify(['int8'], opcodeScope); -Type.builtinTypes.Result.solidify(['int16'], opcodeScope); -Type.builtinTypes.Result.solidify(['int32'], opcodeScope); -Type.builtinTypes.Result.solidify(['int64'], opcodeScope); -Type.builtinTypes.Result.solidify(['float32'], opcodeScope); -Type.builtinTypes.Result.solidify(['float64'], opcodeScope); -Type.builtinTypes.Result.solidify(['string'], opcodeScope); -Type.builtinTypes.Result.solidify(['InternalResponse'], opcodeScope); -Type.builtinTypes.Either.solidify(['any', 'anythingElse'], opcodeScope); -Type.builtinTypes.InitialReduce.solidify(['any', 'anythingElse'], opcodeScope); -Type.builtinTypes.With.solidify(['NsRef', 'any'], opcodeScope); -Type.builtinTypes.With.solidify(['NsMut', 'any'], opcodeScope); -opcodeScope.put('start', new Event('_start', Type.builtinTypes.void, true)); -opcodeScope.put( - '__conn', - new Event('__conn', Type.builtinTypes.InternalRequest, true), -); -opcodeScope.put( - '__ctrl', - new Event('__ctrl', Type.builtinTypes.InternalRequest, true), -); -opcodeScope.put( - 'tcpConn', - new Event('tcpConn', Type.builtinTypes.TcpChannel, true), -); -opcodeScope.put( - 'chunk', - new Event('chunk', opcodeScope.get('TcpContext') as Type, true), -); -opcodeScope.put( - 'tcpClose', - new Event('tcpClose', opcodeScope.get('TcpContext') as Type, true), -); -const t = (str: string) => opcodeScope.get(str); - -// opcode declarations -const addopcodes = ( - opcodes: Record< - string, - [Record, Type] | [Record] - >, -) => { - const opcodeNames = Object.keys(opcodes); - opcodeNames.forEach((opcodeName) => { - const opcodeDef = opcodes[opcodeName]; - const [args, returnType] = opcodeDef; - if (!returnType) { - // This is a three-arg, 0-return opcode - const opcodeObj = { - getName: () => opcodeName, - getArguments: () => args, - getReturnType: () => Type.builtinTypes.void, - isPure: () => true, - microstatementInlining: ( - realArgNames: Array, - scope: Scope, - microstatements: Array, - ) => { - if (['seqwhile'].includes(opcodeName)) { - const inputs = realArgNames.map((n) => - Microstatement.fromVarName(n, scope, microstatements), - ); - const condfn = UserFunction.dispatchFn(inputs[1].fns, [], scope); - const condidx = microstatements.indexOf(inputs[1]); - const condm = microstatements.slice(0, condidx); - Microstatement.closureFromUserFunction( - condfn, - condfn.scope || scope, - condm, - new Map(), - ); - const condclosure = condm[condm.length - 1]; - microstatements.splice(condidx, 0, condclosure); - realArgNames[1] = condclosure.outputName; - const bodyfn = UserFunction.dispatchFn(inputs[2].fns, [], scope); - const bodyidx = microstatements.indexOf(inputs[2]); - const bodym = microstatements.slice(0, bodyidx); - Microstatement.closureFromUserFunction( - bodyfn, - bodyfn.scope || scope, - bodym, - new Map(), - ); - const bodyclosure = bodym[bodym.length - 1]; - microstatements.splice(bodyidx, 0, bodyclosure); - realArgNames[2] = bodyclosure.outputName; - } - microstatements.push( - new Microstatement( - StatementType.CALL, - scope, - true, - null, - opcodeObj.getReturnType(), - realArgNames, - [opcodeObj], - ), - ); - }, - }; - // Add each opcode - opcodeScope.put(opcodeName, [opcodeObj]); - } else { - const opcodeObj = { - getName: () => opcodeName, - getArguments: () => args, - getReturnType: () => returnType, - isPure: () => true, - microstatementInlining: ( - realArgNames: Array, - scope: Scope, - microstatements: Array, - ) => { - const inputs = realArgNames.map((n) => - Microstatement.fromVarName(n, scope, microstatements), - ); - const inputTypes = inputs.map((i) => i.outputType); - const interfaceMap: Map = new Map(); - Object.values(args).forEach((t: Type, i) => - t.typeApplies(inputTypes[i], scope, interfaceMap), - ); - microstatements.push( - new Microstatement( - StatementType.CONSTDEC, - scope, - true, - '_' + uuid().replace(/-/g, '_'), - ((inputTypes, scope) => { - // The `syncop` opcode's return type is always the return type of the closure it was - // given, so we can short circuit this really quickly. - if (opcodeName === 'syncop') { - const fn = inputs[0].fns[0]; - const idx = microstatements.indexOf(inputs[0]); - const m = microstatements.slice(0, idx); - Microstatement.closureFromUserFunction( - fn, - fn.scope || scope, - m, - interfaceMap, - ); - const closure = m[m.length - 1]; - microstatements.splice(idx, 0, closure); - realArgNames[0] = closure.outputName; - return fn.getReturnType(); - } - if (returnType.iface) { - // Path 1: the opcode returns an interface based on the interface type of an input - let replacementType: Type; - Object.values(args).forEach((a: Type, i: number) => { - if (inputs[i].statementType === StatementType.CLOSUREDEF) { - const idx = microstatements.indexOf(inputs[i]); - const m = microstatements.slice(0, idx); - let fn: any; - // TODO: Remove this hackery after function types are more than just 'function' - if ( - [ - 'map', - 'mapl', - 'each', - 'eachl', - 'every', - 'everyl', - 'some', - 'somel', - 'filter', - 'filterl', - 'seqeach', - ].includes(opcodeName) - ) { - // TODO: Try to re-unify these blocks from above - const arrayInnerType = scope.deepGet( - inputTypes[0].typename.replace(/^Array<(.*)>$/, '$1'), - ) as Type; - const innerType = inputTypes[0].originalType - ? arrayInnerType - : Type.builtinTypes.int64; // Hackery for seqeach - try { - fn = UserFunction.dispatchFn( - inputs[i].fns, - [innerType], - scope, - )( - Object.values(fn.getArguments())[0] as Type, - ).typeApplies(innerType, scope, interfaceMap); - } catch { - try { - fn = UserFunction.dispatchFn( - inputs[i].fns, - [], - scope, - ); - } catch { - fn = UserFunction.dispatchFn( - inputs[i].fns, - [arrayInnerType, Type.builtinTypes.int64], - scope, - ); - const closureArgs = Object.values( - fn.getArguments(), - ) as Type[]; - closureArgs[0].typeApplies( - arrayInnerType, - scope, - interfaceMap, - ); - closureArgs[1].typeApplies( - Type.builtinTypes.int64, - scope, - interfaceMap, - ); - } - } - } else if (['reducel', 'reducep'].includes(opcodeName)) { - const arrayInnerType = scope.deepGet( - inputTypes[0].typename.replace(/^Array<(.*)>$/, '$1'), - ) as Type; - fn = UserFunction.dispatchFn( - inputs[i].fns, - [arrayInnerType, arrayInnerType], - scope, - ); - const closureArgs = Object.values( - fn.getArguments(), - ) as Type[]; - closureArgs[0].typeApplies( - arrayInnerType, - scope, - interfaceMap, - ); - closureArgs[1].typeApplies( - arrayInnerType, - scope, - interfaceMap, - ); - } else if (['foldl'].includes(opcodeName)) { - const reducerTypes = Object.values( - inputTypes[0].properties, - ) as Type[]; - const inType = scope.deepGet( - reducerTypes[0].typename.replace( - /^Array<(.*)>$/, - '$1', - ), - ) as Type; - const fnArgTypes = [reducerTypes[1], inType]; - fn = UserFunction.dispatchFn( - inputs[i].fns, - fnArgTypes, - scope, - ); - const closureArgs = Object.values( - fn.getArguments(), - ) as Type[]; - closureArgs[0].typeApplies( - reducerTypes[1], - scope, - interfaceMap, - ); - closureArgs[1].typeApplies(inType, scope, interfaceMap); - } else if (['foldp'].includes(opcodeName)) { - const reducerTypes = Object.values( - inputTypes[0].properties, - ) as Type[]; - const inType = scope.deepGet( - reducerTypes[0].typename.replace( - /^Array<(.*)>$/, - '$1', - ), - ) as Type; - const fnArgTypes = [reducerTypes[1], inType]; - fn = UserFunction.dispatchFn( - inputs[i].fns, - fnArgTypes, - scope, - ); - const closureArgs = Object.values( - fn.getArguments(), - ) as Type[]; - closureArgs[0].typeApplies( - reducerTypes[1], - scope, - interfaceMap, - ); - closureArgs[1].typeApplies(inType, scope, interfaceMap); - } else if (['seqrec'].includes(opcodeName)) { - // TODO: Is this even reachable? - // TODO: How would multiple dispatch even work here? - fn = inputs[1].fns[0]; - } else if (['selfrec'].includes(opcodeName)) { - // TODO: Is this even reachable? - fn = inputs[0].fns[0]; - } else { - fn = UserFunction.dispatchFn(inputs[i].fns, [], scope); - } - Microstatement.closureFromUserFunction( - fn, - fn.scope || scope, - m, - interfaceMap, - ); - const closure = m[m.length - 1]; - microstatements.splice(idx, 0, closure); - realArgNames[i] = closure.outputName; - } - if ( - !!a.iface && - a.iface.interfacename === returnType.iface.interfacename - ) { - replacementType = inputTypes[i]; - } - if ( - Object.values(a.properties).some( - (p) => - !!p.iface && - p.iface.interfacename === - returnType.iface.interfacename, - ) - ) { - Object.values(a.properties).forEach((p, j) => { - if ( - !!p.iface && - p.iface.interfacename === - returnType.iface.interfacename - ) { - replacementType = Object.values( - inputTypes[i].properties, - )[j] as Type; - } - }); - } - }); - if (!replacementType) return returnType; - return replacementType; - } else if ( - returnType.originalType && - Object.values(returnType.properties).some( - (p: Type) => !!p.iface, - ) - ) { - // TODO: Remove this hackery after function types are more than just 'function' - if ( - [ - 'map', - 'mapl', - 'each', - 'eachl', - 'every', - 'everyl', - 'some', - 'somel', - 'filter', - 'filterl', - 'seqeach', - ].includes(opcodeName) - ) { - // The ideal `map` opcode type declaration is something like: - // `map(Array, fn (any): anythingElse): Array` and then the - // interface matching logic figures out what the return type of the opcode is - // based on the return type of the function given to it. - // For now, we just do that "by hand." - const arrayInnerTypeStr = inputTypes[0].typename.replace( - /^Array<(.*)>$/, - '$1', - ); - const arrayInnerType = arrayInnerTypeStr.includes('<') - ? (() => { - const fulltypenameAst = - fulltypenameAstFromString(arrayInnerTypeStr); - const baseTypeStr = fulltypenameAst.get('typename').t; - const baseType = scope.deepGet(baseTypeStr) as Type; - const generics = [ - fulltypenameAst - .get('opttypegenerics') - .get('generics') - .get('fulltypename').t, - ]; - fulltypenameAst - .get('opttypegenerics') - .get('generics') - .get('cdr') - .getAll() - .forEach((r) => { - generics.push(r.get('fulltypename').t); - }); - return baseType.solidify(generics, scope); - })() - : (scope.deepGet(arrayInnerTypeStr) as Type); - const innerType = inputTypes[0].originalType - ? arrayInnerType - : Type.builtinTypes.int64; // Hackery for seqeach - let fn: any; - try { - fn = UserFunction.dispatchFn( - inputs[1].fns, - [innerType], - scope, - ); - } catch { - try { - fn = UserFunction.dispatchFn(inputs[1].fns, [], scope); - } catch { - fn = UserFunction.dispatchFn( - inputs[1].fns, - [arrayInnerType, Type.builtinTypes.int64], - scope, - ); - } - } - const closureArgs = Object.values( - fn.getArguments(), - ) as Type[]; - if (closureArgs[0]) { - closureArgs[0].typeApplies( - innerType, - scope, - interfaceMap, - ); - } - if (closureArgs[1]) { - closureArgs[1].typeApplies( - Type.builtinTypes.int64, - scope, - interfaceMap, - ); - } - const idx = microstatements.indexOf(inputs[1]); - const m = microstatements.slice(0, idx); - Microstatement.closureFromUserFunction( - fn, - fn.scope || scope, - m, - interfaceMap, - ); - const closure = m[m.length - 1]; - microstatements.splice(idx, 0, closure); - realArgNames[1] = closure.outputName; - if (['filter', 'filterl'].includes(opcodeName)) { - return inputs[0].outputType; - } else { - const innerType = closure.closureOutputType; - const newInnerType = innerType.realize( - interfaceMap, - scope, - ); // Necessary? - const baseType = returnType.originalType; - const newReturnType = baseType - ? baseType.solidify([newInnerType.typename], scope) - : returnType; - return newReturnType; - } - } else if ( - [ - 'dsrrun', - 'dsmrun', - 'dsrwith', - 'dsmwith', - 'dsrclos', - 'dsmclos', - ].includes(opcodeName) - ) { - // TODO: Make datastore typesafe. We can't currently do proper dispatch - // on these opcodes, so we have to bail out if the list of potential - // functions for the second argument is more than 1. - if (inputs[1].fns.length !== 1) { - throw new Error( - `${opcodeName} does not currently support multiple dispatch`, - ); - } - const fn = inputs[1].fns[0]; - const idx = microstatements.indexOf(inputs[1]); - const m = microstatements.slice(0, idx); - Microstatement.closureFromUserFunction( - fn, - fn.scope || scope, - m, - interfaceMap, - ); - const closure = m[m.length - 1]; - microstatements.splice(idx, 0, closure); - realArgNames[1] = closure.outputName; - const retType = fn.getReturnType(); - return (scope.deepGet('Result') as Type).solidify( - [retType.typename], - scope, - ); - } else if (['find', 'findl'].includes(opcodeName)) { - const arrayInnerType = scope.deepGet( - inputTypes[0].typename.replace(/^Array<(.*)>$/, '$1'), - ) as Type; - const innerType = inputTypes[0].originalType - ? arrayInnerType - : Type.builtinTypes.int64; // Hackery for seqeach - const fn = UserFunction.dispatchFn( - inputs[1].fns, - [arrayInnerType], - scope, - ); - const closureArgs = Object.values( - fn.getArguments(), - ) as Type[]; - if (closureArgs[0]) { - closureArgs[0].typeApplies( - innerType, - scope, - interfaceMap, - ); - } - const idx = microstatements.indexOf(inputs[1]); - const m = microstatements.slice(0, idx); - Microstatement.closureFromUserFunction( - fn, - fn.scope || scope, - m, - interfaceMap, - ); - const closure = m[m.length - 1]; - microstatements.splice(idx, 0, closure); - realArgNames[1] = closure.outputName; - return Type.builtinTypes.Result.solidify( - [innerType.typename], - scope, - ); - } else if (['reducel', 'reducep'].includes(opcodeName)) { - const arrayInnerType = scope.deepGet( - inputTypes[0].typename.replace(/^Array<(.*)>$/, '$1'), - ) as Type; - const fn = UserFunction.dispatchFn( - inputs[1].fns, - [arrayInnerType, arrayInnerType], - scope, - ); - const closureArgs = Object.values( - fn.getArguments(), - ) as Type[]; - closureArgs[0].typeApplies( - arrayInnerType, - scope, - interfaceMap, - ); - closureArgs[1].typeApplies( - arrayInnerType, - scope, - interfaceMap, - ); - const idx = microstatements.indexOf(inputs[1]); - const m = microstatements.slice(0, idx); - Microstatement.closureFromUserFunction( - fn, - fn.scope || scope, - m, - interfaceMap, - ); - const closure = m[m.length - 1]; - microstatements.splice(idx, 0, closure); - realArgNames[1] = closure.outputName; - return arrayInnerType; - } else if (['foldl'].includes(opcodeName)) { - const reducerTypes = Object.values( - inputTypes[0].properties, - ) as Type[]; - const inType = scope.deepGet( - reducerTypes[0].typename.replace(/^Array<(.*)>$/, '$1'), - ) as Type; - const fnArgTypes = [reducerTypes[1], inType]; - const fn = UserFunction.dispatchFn( - inputs[1].fns, - fnArgTypes, - scope, - ); - const closureArgs = Object.values( - fn.getArguments(), - ) as Type[]; - closureArgs[0].typeApplies( - reducerTypes[1], - scope, - interfaceMap, - ); - closureArgs[1].typeApplies(inType, scope, interfaceMap); - const idx = microstatements.indexOf(inputs[1]); - const m = microstatements.slice(0, idx); - Microstatement.closureFromUserFunction( - fn, - fn.scope || scope, - m, - interfaceMap, - ); - const closure = m[m.length - 1]; - microstatements.splice(idx, 0, closure); - realArgNames[1] = closure.outputName; - return closure.closureOutputType; - } else if (['foldp'].includes(opcodeName)) { - const reducerTypes = Object.values( - inputTypes[0].properties, - ) as Type[]; - const inType = scope.deepGet( - reducerTypes[0].typename.replace(/^Array<(.*)>$/, '$1'), - ) as Type; - const fnArgTypes = [reducerTypes[1], inType]; - const fn = UserFunction.dispatchFn( - inputs[1].fns, - fnArgTypes, - scope, - ); - const closureArgs = Object.values( - fn.getArguments(), - ) as Type[]; - closureArgs[0].typeApplies( - reducerTypes[1], - scope, - interfaceMap, - ); - closureArgs[1].typeApplies(inType, scope, interfaceMap); - const idx = microstatements.indexOf(inputs[1]); - const m = microstatements.slice(0, idx); - Microstatement.closureFromUserFunction( - fn, - fn.scope || scope, - m, - interfaceMap, - ); - const closure = m[m.length - 1]; - microstatements.splice(idx, 0, closure); - realArgNames[1] = closure.outputName; - return Type.builtinTypes['Array'].solidify( - [closure.closureOutputType.typename], - scope, - ); - } else if (['seqrec'].includes(opcodeName)) { - // TODO: How would multiple dispatch even work here? - const fn = inputs[1].inputNames[1].fns[0]; - const idx = microstatements.indexOf(inputs[1]); - const m = microstatements.slice(0, idx); - Microstatement.closureFromUserFunction( - fn, - fn.scope || scope, - m, - interfaceMap, - ); - const closure = m[m.length - 1]; - microstatements.splice(idx, 0, closure); - realArgNames[1] = closure.outputName; - // TODO: How do interface types work here? - return closure.closureOutputType.typename; - } else if (['selfrec'].includes(opcodeName)) { - // TODO: This is absolute crap. How to fix? - return inputs[0].inputNames[1] - ? Microstatement.fromVarName( - inputs[0].inputNames[1], - scope, - microstatements, - ).closureOutputType - : returnType; - } else { - // Path 2: the opcode returns solidified generic type with an interface generic - // that mathces the interface type of an input - const returnIfaces = Object.values(returnType.properties) - .filter((p: Type) => !!p.iface) - .map((p: Type) => p.iface); - if (returnIfaces.length > 0) { - const newReturnType = returnType.realize( - interfaceMap, - scope, - ); - return newReturnType; - } else { - return returnType; - } - } - } else { - // No need to adjust the return type, but may still need to lazy eval a closure - Object.values(args).forEach((_a: Type, i: number) => { - if (inputs[i].statementType === StatementType.CLOSUREDEF) { - const idx = microstatements.indexOf(inputs[i]); - const m = microstatements.slice(0, idx); - let fn: any; - // TODO: Remove this hackery after function types are more than just 'function' - if ( - [ - 'map', - 'mapl', - 'each', - 'eachl', - 'every', - 'everyl', - 'some', - 'somel', - 'filter', - 'filterl', - 'seqeach', - ].includes(opcodeName) - ) { - // TODO: Try to re-unify these blocks from above - const arrayInnerTypeStr = - inputTypes[0].typename.replace(/^Array<(.*)>$/, '$1'); - const arrayInnerType = arrayInnerTypeStr.includes('<') - ? (() => { - const fulltypenameAst = - fulltypenameAstFromString(arrayInnerTypeStr); - const baseTypeStr = - fulltypenameAst.get('typename').t; - const baseType = scope.deepGet( - baseTypeStr, - ) as Type; - const generics = [ - fulltypenameAst - .get('opttypegenerics') - .get('generics') - .get('fulltypename').t, - ]; - fulltypenameAst - .get('opttypegenerics') - .get('generics') - .get('cdr') - .getAll() - .forEach((r) => { - generics.push(r.get('fulltypename').t); - }); - return baseType.solidify(generics, scope); - })() - : (scope.deepGet(arrayInnerTypeStr) as Type); - const innerType = inputTypes[0].originalType - ? arrayInnerType - : Type.builtinTypes.int64; // Hackery for seqeach - try { - fn = UserFunction.dispatchFn( - inputs[i].fns, - [innerType], - scope, - ); - } catch { - try { - fn = UserFunction.dispatchFn( - inputs[i].fns, - [], - scope, - ); - } catch { - fn = UserFunction.dispatchFn( - inputs[i].fns, - [arrayInnerType, Type.builtinTypes.int64], - scope, - ); - } - } - const closureArgs = Object.values( - fn.getArguments(), - ) as Type[]; - if (closureArgs[0]) { - closureArgs[0].typeApplies( - innerType, - scope, - interfaceMap, - ); - } - if (closureArgs[1]) { - closureArgs[1].typeApplies( - Type.builtinTypes.int64, - scope, - interfaceMap, - ); - } - } else if (['reducel', 'reducep'].includes(opcodeName)) { - const arrayInnerType = scope.deepGet( - inputTypes[0].typename.replace(/^Array<(.*)>$/, '$1'), - ) as Type; - fn = UserFunction.dispatchFn( - inputs[1].fns, - [arrayInnerType, arrayInnerType], - scope, - ); - const closureArgs = Object.values( - fn.getArguments(), - ) as Type[]; - closureArgs[0].typeApplies( - arrayInnerType, - scope, - interfaceMap, - ); - closureArgs[1].typeApplies( - arrayInnerType, - scope, - interfaceMap, - ); - } else if (['foldl'].includes(opcodeName)) { - const reducerTypes = Object.values( - inputTypes[0].properties, - ) as Type[]; - const inType = scope.deepGet( - reducerTypes[0].typename.replace( - /^Array<(.*)>$/, - '$1', - ), - ) as Type; - const fnArgTypes = [reducerTypes[1], inType]; - const fn = UserFunction.dispatchFn( - inputs[1].fns, - fnArgTypes, - scope, - ); - const closureArgs = Object.values( - fn.getArguments(), - ) as Type[]; - closureArgs[0].typeApplies( - reducerTypes[1], - scope, - interfaceMap, - ); - closureArgs[1].typeApplies(inType, scope, interfaceMap); - } else if (['seqrec'].includes(opcodeName)) { - // TODO: How would multiple dispatch even work here? - fn = inputs[1].fns[0]; - } else if (['selfrec'].includes(opcodeName)) { - // TODO: Is this even reachable? - fn = inputs[0].inputNames[1].fns[0]; - } else if (['dsmonly', 'dswonly'].includes(opcodeName)) { - // TODO: Make datastore typesafe. We can't currently do proper dispatch - // on these opcodes, so we have to bail out if the list of potential - // functions for the second argument is more than 1. - if (inputs[1].fns.length !== 1) { - throw new Error( - `${opcodeName} does not currently support multiple dispatch`, - ); - } - fn = inputs[1].fns[0]; - } else { - fn = UserFunction.dispatchFn(inputs[i].fns, [], scope); - } - Microstatement.closureFromUserFunction( - fn, - fn.scope || scope, - m, - interfaceMap, - ); - const closure = m[m.length - 1]; - microstatements.splice(idx, 0, closure); - realArgNames[i] = closure.outputName; - } - }); - } - return returnType; - })(inputTypes, scope), - realArgNames, - [opcodeObj], - ), - ); - }, - }; - // Add each opcode - opcodeScope.put(opcodeName, [opcodeObj]); - } - }); -}; - -addopcodes({ - i8f64: [{ number: t('int8') }, t('float64')], - i16f64: [{ number: t('int16') }, t('float64')], - i32f64: [{ number: t('int32') }, t('float64')], - i64f64: [{ number: t('int64') }, t('float64')], - f32f64: [{ number: t('float32') }, t('float64')], - strf64: [{ str: t('string') }, t('float64')], - boolf64: [{ boo: t('bool') }, t('float64')], - i8f32: [{ number: t('int8') }, t('float32')], - i16f32: [{ number: t('int16') }, t('float32')], - i32f32: [{ number: t('int32') }, t('float32')], - i64f32: [{ number: t('int64') }, t('float32')], - f64f32: [{ number: t('float64') }, t('float32')], - strf32: [{ str: t('string') }, t('float32')], - boolf32: [{ boo: t('bool') }, t('float32')], - i8i64: [{ number: t('int8') }, t('int64')], - i16i64: [{ number: t('int16') }, t('int64')], - i32i64: [{ number: t('int32') }, t('int64')], - f32i64: [{ number: t('float32') }, t('int64')], - f64i64: [{ number: t('float64') }, t('int64')], - stri64: [{ str: t('string') }, t('int64')], - booli64: [{ boo: t('bool') }, t('int64')], - i8i32: [{ number: t('int8') }, t('int32')], - i16i32: [{ number: t('int16') }, t('int32')], - i64i32: [{ number: t('int64') }, t('int32')], - f32i32: [{ number: t('float32') }, t('int32')], - f64i32: [{ number: t('float64') }, t('int32')], - stri32: [{ str: t('string') }, t('int32')], - booli32: [{ boo: t('bool') }, t('int32')], - i8i16: [{ number: t('int8') }, t('int16')], - i32i16: [{ number: t('int32') }, t('int16')], - i64i16: [{ number: t('int64') }, t('int16')], - f32i16: [{ number: t('float32') }, t('int16')], - f64i16: [{ number: t('float64') }, t('int16')], - stri16: [{ str: t('string') }, t('int16')], - booli16: [{ boo: t('bool') }, t('int16')], - i16i8: [{ number: t('int16') }, t('int8')], - i32i8: [{ number: t('int32') }, t('int8')], - i64i8: [{ number: t('int64') }, t('int8')], - f32i8: [{ number: t('float32') }, t('int8')], - f64i8: [{ number: t('float64') }, t('int8')], - stri8: [{ str: t('string') }, t('int8')], - booli8: [{ boo: t('bool') }, t('int8')], - i8bool: [{ number: t('int8') }, t('bool')], - i16bool: [{ number: t('int16') }, t('bool')], - i32bool: [{ number: t('int32') }, t('bool')], - i64bool: [{ number: t('int64') }, t('bool')], - f32bool: [{ number: t('float32') }, t('bool')], - f64bool: [{ number: t('float64') }, t('bool')], - strbool: [{ str: t('string') }, t('bool')], - i8str: [{ number: t('int8') }, t('string')], - i16str: [{ number: t('int16') }, t('string')], - i32str: [{ number: t('int32') }, t('string')], - i64str: [{ number: t('int64') }, t('string')], - f32str: [{ number: t('float32') }, t('string')], - f64str: [{ number: t('float64') }, t('string')], - boolstr: [{ boo: t('bool') }, t('string')], - addi8: [{ a: t('Result'), b: t('Result') }, t('Result')], - addi16: [ - { a: t('Result'), b: t('Result') }, - t('Result'), - ], - addi32: [ - { a: t('Result'), b: t('Result') }, - t('Result'), - ], - addi64: [ - { a: t('Result'), b: t('Result') }, - t('Result'), - ], - addf32: [ - { a: t('Result'), b: t('Result') }, - t('Result'), - ], - addf64: [ - { a: t('Result'), b: t('Result') }, - t('Result'), - ], - subi8: [{ a: t('Result'), b: t('Result') }, t('Result')], - subi16: [ - { a: t('Result'), b: t('Result') }, - t('Result'), - ], - subi32: [ - { a: t('Result'), b: t('Result') }, - t('Result'), - ], - subi64: [ - { a: t('Result'), b: t('Result') }, - t('Result'), - ], - subf32: [ - { a: t('Result'), b: t('Result') }, - t('Result'), - ], - subf64: [ - { a: t('Result'), b: t('Result') }, - t('Result'), - ], - negi8: [{ a: t('Result') }, t('Result')], - negi16: [{ a: t('Result') }, t('Result')], - negi32: [{ a: t('Result') }, t('Result')], - negi64: [{ a: t('Result') }, t('Result')], - negf32: [{ a: t('Result') }, t('Result')], - negf64: [{ a: t('Result') }, t('Result')], - absi8: [{ a: t('Result') }, t('Result')], - absi16: [{ a: t('Result') }, t('Result')], - absi32: [{ a: t('Result') }, t('Result')], - absi64: [{ a: t('Result') }, t('Result')], - absf32: [{ a: t('Result') }, t('Result')], - absf64: [{ a: t('Result') }, t('Result')], - muli8: [{ a: t('Result'), b: t('Result') }, t('Result')], - muli16: [ - { a: t('Result'), b: t('Result') }, - t('Result'), - ], - muli32: [ - { a: t('Result'), b: t('Result') }, - t('Result'), - ], - muli64: [ - { a: t('Result'), b: t('Result') }, - t('Result'), - ], - mulf32: [ - { a: t('Result'), b: t('Result') }, - t('Result'), - ], - mulf64: [ - { a: t('Result'), b: t('Result') }, - t('Result'), - ], - divi8: [{ a: t('Result'), b: t('Result') }, t('Result')], - divi16: [ - { a: t('Result'), b: t('Result') }, - t('Result'), - ], - divi32: [ - { a: t('Result'), b: t('Result') }, - t('Result'), - ], - divi64: [ - { a: t('Result'), b: t('Result') }, - t('Result'), - ], - divf32: [ - { a: t('Result'), b: t('Result') }, - t('Result'), - ], - divf64: [ - { a: t('Result'), b: t('Result') }, - t('Result'), - ], - modi8: [{ a: t('int8'), b: t('int8') }, t('int8')], - modi16: [{ a: t('int16'), b: t('int16') }, t('int16')], - modi32: [{ a: t('int32'), b: t('int32') }, t('int32')], - modi64: [{ a: t('int64'), b: t('int64') }, t('int64')], - powi8: [{ a: t('Result'), b: t('Result') }, t('Result')], - powi16: [ - { a: t('Result'), b: t('Result') }, - t('Result'), - ], - powi32: [ - { a: t('Result'), b: t('Result') }, - t('Result'), - ], - powi64: [ - { a: t('Result'), b: t('Result') }, - t('Result'), - ], - powf32: [ - { a: t('Result'), b: t('Result') }, - t('Result'), - ], - powf64: [ - { a: t('Result'), b: t('Result') }, - t('Result'), - ], - sqrtf32: [{ a: t('float32') }, t('float32')], - sqrtf64: [{ a: t('float64') }, t('float64')], - saddi8: [{ a: t('int8'), b: t('int8') }, t('int8')], - saddi16: [{ a: t('int16'), b: t('int16') }, t('int16')], - saddi32: [{ a: t('int32'), b: t('int32') }, t('int32')], - saddi64: [{ a: t('int64'), b: t('int64') }, t('int64')], - saddf32: [{ a: t('float32'), b: t('float32') }, t('float32')], - saddf64: [{ a: t('float64'), b: t('float64') }, t('float64')], - ssubi8: [{ a: t('int8'), b: t('int8') }, t('int8')], - ssubi16: [{ a: t('int16'), b: t('int16') }, t('int16')], - ssubi32: [{ a: t('int32'), b: t('int32') }, t('int32')], - ssubi64: [{ a: t('int64'), b: t('int64') }, t('int64')], - ssubf32: [{ a: t('float32'), b: t('float32') }, t('float32')], - ssubf64: [{ a: t('float64'), b: t('float64') }, t('float64')], - snegi8: [{ a: t('int8') }, t('int8')], - snegi16: [{ a: t('int16') }, t('int16')], - snegi32: [{ a: t('int32') }, t('int32')], - snegi64: [{ a: t('int64') }, t('int64')], - snegf32: [{ a: t('float32') }, t('float32')], - snegf64: [{ a: t('float64') }, t('float64')], - sabsi8: [{ a: t('int8') }, t('int8')], - sabsi16: [{ a: t('int16') }, t('int16')], - sabsi32: [{ a: t('int32') }, t('int32')], - sabsi64: [{ a: t('int64') }, t('int64')], - sabsf32: [{ a: t('float32') }, t('float32')], - sabsf64: [{ a: t('float64') }, t('float64')], - smuli8: [{ a: t('int8'), b: t('int8') }, t('int8')], - smuli16: [{ a: t('int16'), b: t('int16') }, t('int16')], - smuli32: [{ a: t('int32'), b: t('int32') }, t('int32')], - smuli64: [{ a: t('int64'), b: t('int64') }, t('int64')], - smulf32: [{ a: t('float32'), b: t('float32') }, t('float32')], - smulf64: [{ a: t('float64'), b: t('float64') }, t('float64')], - sdivi8: [{ a: t('int8'), b: t('int8') }, t('int8')], - sdivi16: [{ a: t('int16'), b: t('int16') }, t('int16')], - sdivi32: [{ a: t('int32'), b: t('int32') }, t('int32')], - sdivi64: [{ a: t('int64'), b: t('int64') }, t('int64')], - sdivf32: [{ a: t('float32'), b: t('float32') }, t('float32')], - sdivf64: [{ a: t('float64'), b: t('float64') }, t('float64')], - spowi8: [{ a: t('int8'), b: t('int8') }, t('int8')], - spowi16: [{ a: t('int16'), b: t('int16') }, t('int16')], - spowi32: [{ a: t('int32'), b: t('int32') }, t('int32')], - spowi64: [{ a: t('int64'), b: t('int64') }, t('int64')], - spowf32: [{ a: t('float32'), b: t('float32') }, t('float32')], - spowf64: [{ a: t('float64'), b: t('float64') }, t('float64')], - andi8: [{ a: t('int8'), b: t('int8') }, t('int8')], - andi16: [{ a: t('int16'), b: t('int16') }, t('int16')], - andi32: [{ a: t('int32'), b: t('int32') }, t('int32')], - andi64: [{ a: t('int64'), b: t('int64') }, t('int64')], - andbool: [{ a: t('bool'), b: t('bool') }, t('bool')], - ori8: [{ a: t('int8'), b: t('int8') }, t('int8')], - ori16: [{ a: t('int16'), b: t('int16') }, t('int16')], - ori32: [{ a: t('int32'), b: t('int32') }, t('int32')], - ori64: [{ a: t('int64'), b: t('int64') }, t('int64')], - orbool: [{ a: t('bool'), b: t('bool') }, t('bool')], - xori8: [{ a: t('int8'), b: t('int8') }, t('int8')], - xori16: [{ a: t('int16'), b: t('int16') }, t('int16')], - xori32: [{ a: t('int32'), b: t('int32') }, t('int32')], - xori64: [{ a: t('int64'), b: t('int64') }, t('int64')], - xorbool: [{ a: t('bool'), b: t('bool') }, t('bool')], - noti8: [{ a: t('int8') }, t('int8')], - noti16: [{ a: t('int16') }, t('int16')], - noti32: [{ a: t('int32') }, t('int32')], - noti64: [{ a: t('int64') }, t('int64')], - notbool: [{ a: t('bool') }, t('bool')], - nandi8: [{ a: t('int8'), b: t('int8') }, t('int8')], - nandi16: [{ a: t('int16'), b: t('int16') }, t('int16')], - nandi32: [{ a: t('int32'), b: t('int32') }, t('int32')], - nandi64: [{ a: t('int64'), b: t('int64') }, t('int64')], - nandboo: [{ a: t('bool'), b: t('bool') }, t('bool')], - nori8: [{ a: t('int8'), b: t('int8') }, t('int8')], - nori16: [{ a: t('int16'), b: t('int16') }, t('int16')], - nori32: [{ a: t('int32'), b: t('int32') }, t('int32')], - nori64: [{ a: t('int64'), b: t('int64') }, t('int64')], - norbool: [{ a: t('bool'), b: t('bool') }, t('bool')], - xnori8: [{ a: t('int8'), b: t('int8') }, t('int8')], - xnori16: [{ a: t('int16'), b: t('int16') }, t('int16')], - xnori32: [{ a: t('int32'), b: t('int32') }, t('int32')], - xnori64: [{ a: t('int64'), b: t('int64') }, t('int64')], - xnorboo: [{ a: t('bool'), b: t('bool') }, t('bool')], - eqi8: [{ a: t('int8'), b: t('int8') }, t('bool')], - eqi16: [{ a: t('int16'), b: t('int16') }, t('bool')], - eqi32: [{ a: t('int32'), b: t('int32') }, t('bool')], - eqi64: [{ a: t('int64'), b: t('int64') }, t('bool')], - eqf32: [{ a: t('float32'), b: t('float32') }, t('bool')], - eqf64: [{ a: t('float64'), b: t('float64') }, t('bool')], - eqbool: [{ a: t('bool'), b: t('bool') }, t('bool')], - eqstr: [{ a: t('string'), b: t('string') }, t('bool')], - neqi8: [{ a: t('int8'), b: t('int8') }, t('bool')], - neqi16: [{ a: t('int16'), b: t('int16') }, t('bool')], - neqi32: [{ a: t('int32'), b: t('int32') }, t('bool')], - neqi64: [{ a: t('int64'), b: t('int64') }, t('bool')], - neqf32: [{ a: t('float32'), b: t('float32') }, t('bool')], - neqf64: [{ a: t('float64'), b: t('float64') }, t('bool')], - neqbool: [{ a: t('bool'), b: t('bool') }, t('bool')], - neqstr: [{ a: t('string'), b: t('string') }, t('bool')], - lti8: [{ a: t('int8'), b: t('int8') }, t('bool')], - lti16: [{ a: t('int16'), b: t('int16') }, t('bool')], - lti32: [{ a: t('int32'), b: t('int32') }, t('bool')], - lti64: [{ a: t('int64'), b: t('int64') }, t('bool')], - ltf32: [{ a: t('float32'), b: t('float32') }, t('bool')], - ltf64: [{ a: t('float64'), b: t('float64') }, t('bool')], - ltstr: [{ a: t('string'), b: t('string') }, t('bool')], - ltei8: [{ a: t('int8'), b: t('int8') }, t('bool')], - ltei16: [{ a: t('int16'), b: t('int16') }, t('bool')], - ltei32: [{ a: t('int32'), b: t('int32') }, t('bool')], - ltei64: [{ a: t('int64'), b: t('int64') }, t('bool')], - ltef32: [{ a: t('float32'), b: t('float32') }, t('bool')], - ltef64: [{ a: t('float64'), b: t('float64') }, t('bool')], - ltestr: [{ a: t('string'), b: t('string') }, t('bool')], - gti8: [{ a: t('int8'), b: t('int8') }, t('bool')], - gti16: [{ a: t('int16'), b: t('int16') }, t('bool')], - gti32: [{ a: t('int32'), b: t('int32') }, t('bool')], - gti64: [{ a: t('int64'), b: t('int64') }, t('bool')], - gtf32: [{ a: t('float32'), b: t('float32') }, t('bool')], - gtf64: [{ a: t('float64'), b: t('float64') }, t('bool')], - gtstr: [{ a: t('string'), b: t('string') }, t('bool')], - gtei8: [{ a: t('int8'), b: t('int8') }, t('bool')], - gtei16: [{ a: t('int16'), b: t('int16') }, t('bool')], - gtei32: [{ a: t('int32'), b: t('int32') }, t('bool')], - gtei64: [{ a: t('int64'), b: t('int64') }, t('bool')], - gtef32: [{ a: t('float32'), b: t('float32') }, t('bool')], - gtef64: [{ a: t('float64'), b: t('float64') }, t('bool')], - gtestr: [{ a: t('string'), b: t('string') }, t('bool')], - httpreq: [{ a: t('InternalRequest') }, t('Result')], - httpsend: [{ a: t('InternalResponse') }, t('Result')], - execop: [{ a: t('string') }, t('ExecRes')], - waitop: [{ a: t('int64') }, t('void')], - syncop: [{ f: t('function'), a: t('any') }, t('anythingElse')], - catstr: [{ a: t('string'), b: t('string') }, t('string')], - catarr: [{ a: t('Array'), b: t('Array') }, t('Array')], - split: [{ str: t('string'), spl: t('string') }, t('Array')], - repstr: [{ s: t('string'), n: t('int64') }, t('string')], - reparr: [{ arr: t('Array'), n: t('int64') }, t('Array')], - matches: [{ s: t('string'), t: t('string') }, t('bool')], - indstr: [{ s: t('string'), t: t('string') }, t('Result')], - indarrf: [{ arr: t('Array'), val: t('any') }, t('Result')], - indarrv: [{ arr: t('Array'), val: t('any') }, t('Result')], - lenstr: [{ s: t('string') }, t('int64')], - lenarr: [{ arr: t('Array') }, t('int64')], - trim: [{ s: t('string') }, t('string')], - condfn: [{ cond: t('bool'), optional: t('function') }, t('any')], - pusharr: [{ arr: t('Array'), val: t('any'), size: t('int64') }], - poparr: [{ arr: t('Array') }, t('Result')], - delindx: [{ arr: t('Array'), idx: t('int64') }, t('Result')], - each: [{ arr: t('Array'), cb: t('function') }, t('void')], - eachl: [{ arr: t('Array'), cb: t('function') }, t('void')], - map: [{ arr: t('Array'), cb: t('function') }, t('Array')], - mapl: [{ arr: t('Array'), cb: t('function') }, t('Array')], - reducel: [{ arr: t('Array'), cb: t('function') }, t('any')], - reducep: [{ arr: t('Array'), cb: t('function') }, t('any')], - foldl: [ - { arr: t('InitialReduce'), cb: t('function') }, - t('anythingElse'), - ], - foldp: [ - { arr: t('InitialReduce'), cb: t('function') }, - t('Array'), - ], - filter: [{ arr: t('Array'), cb: t('function') }, t('Array')], - filterl: [{ arr: t('Array'), cb: t('function') }, t('Array')], - find: [{ arr: t('Array'), cb: t('function') }, t('Result')], - findl: [{ arr: t('Array'), cb: t('function') }, t('Result')], - every: [{ arr: t('Array'), cb: t('function') }, t('bool')], - everyl: [{ arr: t('Array'), cb: t('function') }, t('bool')], - some: [{ arr: t('Array'), cb: t('function') }, t('bool')], - somel: [{ arr: t('Array'), cb: t('function') }, t('bool')], - join: [{ arr: t('Array'), sep: t('string') }, t('string')], - newarr: [{ size: t('int64') }, t('Array')], - stdoutp: [{ out: t('string') }, t('void')], - stderrp: [{ err: t('string') }, t('void')], - exitop: [{ code: t('int8') }, t('void')], - copyfrom: [{ arr: t('Array'), addr: t('int64') }, t('any')], - copytof: [{ arr: t('Array'), addr: t('int64'), val: t('any') }], - copytov: [{ arr: t('Array'), addr: t('int64'), val: t('any') }], - register: [{ arr: t('Array'), addr: t('int64') }, t('Array')], - copyi8: [{ a: t('int8') }, t('int8')], - copyi16: [{ a: t('int16') }, t('int16')], - copyi32: [{ a: t('int32') }, t('int32')], - copyi64: [{ a: t('int64') }, t('int64')], - copyvoid: [{ a: t('void') }, t('void')], - copyf32: [{ a: t('float32') }, t('float32')], - copyf64: [{ a: t('float64') }, t('float64')], - copybool: [{ a: t('bool') }, t('bool')], - copystr: [{ a: t('string') }, t('string')], - copyarr: [{ a: t('any') }, t('any')], - zeroed: [{}, t('any')], - lnf64: [{ a: t('float64') }, t('float64')], - logf64: [{ a: t('float64') }, t('float64')], - sinf64: [{ a: t('float64') }, t('float64')], - cosf64: [{ a: t('float64') }, t('float64')], - tanf64: [{ a: t('float64') }, t('float64')], - asinf64: [{ a: t('float64') }, t('float64')], - acosf64: [{ a: t('float64') }, t('float64')], - atanf64: [{ a: t('float64') }, t('float64')], - sinhf64: [{ a: t('float64') }, t('float64')], - coshf64: [{ a: t('float64') }, t('float64')], - tanhf64: [{ a: t('float64') }, t('float64')], - error: [{ a: t('string') }, t('Error')], - reff: [{ a: t('any') }, t('any')], - refv: [{ a: t('any') }, t('any')], - noerr: [{}, t('Error')], - errorstr: [{ a: t('Error') }, t('string')], - someM: [{ a: t('any'), size: t('int64') }, t('Maybe')], - noneM: [{}, t('Maybe')], - isSome: [{ a: t('Maybe') }, t('bool')], - isNone: [{ a: t('Maybe') }, t('bool')], - getOrM: [{ a: t('Maybe'), b: t('any') }, t('any')], - getMaybe: [{ a: t('Maybe') }, t('any')], - okR: [{ a: t('any'), size: t('int64') }, t('Result')], - err: [{ a: t('string') }, t('Result')], - isOk: [{ a: t('Result') }, t('bool')], - isErr: [{ a: t('Result') }, t('bool')], - getOrR: [{ a: t('Result'), b: t('any') }, t('any')], - getOrRS: [{ a: t('Result'), b: t('string') }, t('string')], - getR: [{ a: t('Result') }, t('any')], - getErr: [{ a: t('Result'), b: t('Error') }, t('Error')], - resfrom: [ - { arr: t('Array'), addr: t('Result') }, - t('Result'), - ], - mainE: [{ a: t('any'), size: t('int64') }, t('Either')], - altE: [ - { a: t('anythingElse'), size: t('int64') }, - t('Either'), - ], - isMain: [{ a: t('Either') }, t('bool')], - isAlt: [{ a: t('Either') }, t('bool')], - mainOr: [{ a: t('Either'), b: t('any') }, t('any')], - altOr: [ - { a: t('Either'), b: t('anythingElse') }, - t('anythingElse'), - ], - getMain: [{ a: t('Either') }, t('any')], - getAlt: [{ a: t('Either') }, t('anythingElse')], - hashf: [{ a: t('any') }, t('int64')], - hashv: [{ a: t('any') }, t('int64')], - dssetf: [{ ns: t('string'), key: t('string'), val: t('any') }], - dssetv: [{ ns: t('string'), key: t('string'), val: t('any') }], - dshas: [{ ns: t('string'), key: t('string') }, t('bool')], - dsdel: [{ ns: t('string'), key: t('string') }, t('bool')], - dsgetf: [{ ns: t('string'), key: t('string') }, t('Result')], - dsgetv: [{ ns: t('string'), key: t('string') }, t('Result')], - dsrrun: [{ nskey: t('NsRef'), func: t('function') }, t('Result')], - dsmrun: [{ nskey: t('NsMut'), func: t('function') }, t('Result')], - dsrwith: [ - { with: t('With'), func: t('function') }, - t('Result'), - ], - dsmwith: [ - { with: t('With'), func: t('function') }, - t('Result'), - ], - dsmonly: [{ nskey: t('NsMut'), func: t('function') }, t('void')], - dswonly: [{ with: t('With'), func: t('function') }, t('void')], - dsrclos: [{ nskey: t('NsRef'), func: t('function') }, t('Result')], - dsmclos: [{ nskey: t('NsMut'), func: t('function') }, t('Result')], - getcs: [{}, t('Maybe')], - newseq: [{ limit: t('int64') }, t('Seq')], - seqnext: [{ seq: t('Seq') }, t('Result')], - seqeach: [{ seq: t('Seq'), func: t('function') }, t('void')], - seqwhile: [{ seq: t('Seq'), condFn: t('function'), bodyFn: t('function') }], - seqdo: [{ seq: t('Seq'), bodyFn: t('function') }, t('void')], - selfrec: [{ self: t('Self'), arg: t('any') }, t('Result')], - seqrec: [{ seq: t('Seq'), recurseFn: t('function') }, t('Self')], - tcpconn: [{ host: t('string'), port: t('int16') }, t('TcpChannel')], - tcpAddC: [{ channel: t('TcpChannel'), context: t('any') }, t('TcpChannel')], - tcpReady: [{ channel: t('TcpChannel') }, t('TcpChannel')], - tcpRead: [{ channel: t('TcpChannel') }, t('Chunk')], - tcpWrite: [{ channel: t('TcpChannel'), chunk: t('Chunk') }, t('TcpChannel')], - tcpTerm: [{ channel: t('TcpChannel') }, t('void')], - tcptun: [{ port: t('int16') }, t('bool')], -}); - -export default opcodeModule; diff --git a/compiler/src/lp.ts b/compiler/src/lp.ts deleted file mode 100644 index b88fbebdf..000000000 --- a/compiler/src/lp.ts +++ /dev/null @@ -1,1041 +0,0 @@ -import * as fs from 'fs'; // This syntax is so dumb - -// A snapshot of the metadata surrounding an LP record -export interface LPSnap { - line: number; - char: number; - i: number; -} - -// An LP record and methods, used for keeping track of advancements through the text to parse -export class LP { - filename: string; - data: string; - line: number; - char: number; - i: number; - - constructor(filename: string, loadData = true) { - this.filename = filename; - this.data = loadData ? fs.readFileSync(filename, 'utf8') : ''; - this.line = 1; - this.char = 1; - this.i = 0; - } - - advance(n: number) { - for (let i = 0; i < n; i++) { - this.i += 1; - if (this.data[this.i] === '\n') { - this.line += 1; - this.char = 1; - } else { - this.char += 1; - } - } - } - - clone(): LP { - const clone = new LP(this.filename, false); - clone.data = this.data; - clone.line = this.line; - clone.char = this.char; - clone.i = this.i; - return clone; - } - - static fromText(data: string): LP { - const lp = new LP('fakeFile', false); - lp.data = data; - return lp; - } - - snapshot(): LPSnap { - return { - line: this.line, - char: this.char, - i: this.i, - }; - } - - restore(snap: LPSnap) { - this.line = snap.line; - this.char = snap.char; - this.i = snap.i; - } -} - -// Any kind of type that provides enough data to attach metadata to error messages -export interface LPmeta { - filename: string; - line: number; - char: number; -} - -// Any kind of type that can operate on LP records to build the AST. -export interface LPNode { - t: string; - line: number; - char: number; - get(id?: string | number): LPNode; - getAll(): LPNode[]; - has(id?: string | number): boolean; - apply(lp: LP): LPNode | LPError; -} - -export class LPError { - msg: string; - parent: LPError | LPError[] | undefined; - constructor(msg, parent = undefined) { - this.msg = msg; - this.parent = parent; - } -} - -export const lpError = (message: string, obj: LPmeta) => - new LPError( - `${message} in file ${obj.filename} line ${obj.line}:${obj.char}`, - ); - -// A special AST node that indicates that you successfully matched nothing, useful for optional ASTs -export class NulLP implements LPNode { - t: string; - line: number; - char: number; - - constructor() { - this.t = ''; - this.line = -1; - this.char = -1; - } - - get(): NulLP { - return this; - } - - getAll(): NulLP[] { - return [this]; - } - - has(): boolean { - return false; - } - - apply(): LPNode | LPError { - return new LPError('nullish'); - } - - toString(): string { - return this.t; - } -} - -// One of the 'leaf' AST nodes. It declares a fixed set of characters in a row to match -export class Token implements LPNode { - t: string; - filename: string; - line: number; - char: number; - - constructor(t: string, filename: string, line: number, char: number) { - this.t = t; - this.filename = filename; - this.line = line; - this.char = char; - } - - static build(t: string): Token { - return new Token(t, '', -1, -1); - } - - toString(): string { - return this.t; - } - - get(): LPNode { - return this; - } - - getAll(): LPNode[] { - return [this]; - } - - has(): boolean { - return this.line > -1; - } - - check(lp: LP): boolean { - let matches = true; - const t = this.t; - const len = t.length; - const data = lp.data; - const j = lp.i; - for (let i = 0; i < len; i++) { - if (t[i] !== data[i + j]) { - matches = false; - break; - } - } - return matches; - } - - apply(lp: LP): Token | LPError { - if (this.check(lp)) { - lp.advance(this.t.length); - return new Token(this.t, lp.filename, lp.line, lp.char); - } - return lpError( - `Token mismatch, ${this.t} not found, instead ${lp.data[lp.i]}`, - lp, - ); - } -} - -// Another 'leaf' AST node. It matches any characters that DO NOT match the string provided -export class Not implements LPNode { - t: string; - filename: string; - line: number; - char: number; - - constructor(t: string, filename: string, line: number, char: number) { - this.t = t; - this.filename = filename; - this.line = line; - this.char = char; - } - - static build(t: string): Not { - return new Not(t, '', -1, -1); - } - - toString(): string { - return this.t; - } - - check(lp: LP): boolean { - let matches = true; - const t = this.t; - const len = t.length; - const data = lp.data; - const j = lp.i; - for (let i = 0; i < len; i++) { - if (t[i] !== data[i + j]) { - matches = false; - break; - } - } - return !matches; - } - - get(): Not { - return this; - } - - getAll(): Not[] { - return [this]; - } - - has(): boolean { - return this.line > -1; - } - - apply(lp: LP): Not | LPError { - if (this.check(lp)) { - const newT = lp.data[lp.i]; - lp.advance(this.t.length); - return new Not(newT, lp.filename, lp.line, lp.char); - } - return lpError(`Not mismatch, ${this.t} found`, lp); - } -} - -// An AST node that optionally matches the AST node below it -export class ZeroOrOne implements LPNode { - t: string; - zeroOrOne: LPNode; - filename: string; - line: number; - char: number; - - constructor( - t: string, - zeroOrOne: LPNode, - filename: string, - line: number, - char: number, - ) { - this.t = t; - this.zeroOrOne = zeroOrOne; - this.filename = filename; - this.line = line; - this.char = char; - } - - static build(zeroOrOne: LPNode): ZeroOrOne { - return new ZeroOrOne(zeroOrOne.t, zeroOrOne, '', -1, -1); - } - - toString(): string { - return this.t; - } - - get(): LPNode { - return this.zeroOrOne; - } - - getAll(): LPNode[] { - return [this.zeroOrOne]; - } - - has(): boolean { - return this.line > -1; - } - - apply(lp: LP): LPNode { - const s = lp.snapshot(); - const zeroOrOne = this.zeroOrOne.apply(lp); - if (zeroOrOne instanceof LPError) { - lp.restore(s); - return new NulLP(); - } - return zeroOrOne; - } -} - -// An AST node that optionally matches the AST node below it as many times as possible -export class ZeroOrMore implements LPNode { - t: string; - zeroOrMore: LPNode[]; - filename: string; - line: number; - char: number; - - constructor( - t: string, - zeroOrMore: LPNode[], - filename: string, - line: number, - char: number, - ) { - this.t = t; - this.zeroOrMore = zeroOrMore; - this.filename = filename; - this.line = line; - this.char = char; - } - - static build(zeroOrMore: LPNode): ZeroOrMore { - return new ZeroOrMore(zeroOrMore.t, [zeroOrMore], '', -1, -1); - } - - toString(): string { - return this.t; - } - - get(i: number): LPNode { - if (this.zeroOrMore[i]) return this.zeroOrMore[i]; - return new NulLP(); - } - - getAll(): LPNode[] { - return this.zeroOrMore; - } - - has(id?: number): boolean { - if (typeof id === 'number') { - if (this.zeroOrMore[id]) { - return this.zeroOrMore[id].has(); - } - return false; - } - return this.line > -1; - } - - apply(lp: LP): LPNode | LPError { - const filename = lp.filename; - const line = lp.line; - const char = lp.char; - let t = ''; - const zeroOrMore = []; - do { - const s = lp.snapshot(); - const z = this.zeroOrMore[0].apply(lp); - if (z instanceof LPError) { - lp.restore(s); - return new ZeroOrMore(t, zeroOrMore, filename, line, char); - } - const t2 = z.toString(); - if (!t2 || t2.length === 0) { - return lpError( - 'ZeroOrMore made no forward progress, will infinite loop', - lp, - ); - } - t += t2; - zeroOrMore.push(z); - } while (true); // eslint-disable-line no-constant-condition - } -} - -// An AST node that matches the node below it multiple times and fails if it finds no match -export class OneOrMore implements LPNode { - t: string; - oneOrMore: LPNode[]; - filename: string; - line: number; - char: number; - - constructor( - t: string, - oneOrMore: LPNode[], - filename: string, - line: number, - char: number, - ) { - this.t = t; - this.oneOrMore = oneOrMore; - this.filename = filename; - this.line = line; - this.char = char; - } - - static build(oneOrMore: LPNode): OneOrMore { - return new OneOrMore(oneOrMore.t, [oneOrMore], '', -1, -1); - } - - toString(): string { - return this.t; - } - - get(i: number): LPNode { - if (this.oneOrMore[i]) return this.oneOrMore[i]; - return new NulLP(); - } - - getAll(): LPNode[] { - return this.oneOrMore; - } - - has(id?: number): boolean { - if (typeof id === 'number') { - if (this.oneOrMore[id]) { - return this.oneOrMore[id].has(); - } - return false; - } - return this.line > -1; - } - - apply(lp: LP): LPNode | LPError { - const filename = lp.filename; - const line = lp.line; - const char = lp.char; - let t = ''; - const oneOrMore = []; - do { - const s = lp.snapshot(); - const o = this.oneOrMore[0].apply(lp); - if (o instanceof LPError) { - lp.restore(s); - if (oneOrMore.length === 0) { - const err = lpError( - `No match for OneOrMore ${this.oneOrMore.toString()}`, - lp, - ); - err.parent = o; - return err; - } - return new OneOrMore(t, oneOrMore, filename, line, char); - } - const t2 = o.toString(); - if (t2.length === 0) { - return lpError( - 'OneOrMore made no forward progress, will infinite loop', - lp, - ); - } - t += t2; - oneOrMore.push(o); - } while (true); // eslint-disable-line no-constant-condition - } -} - -// An AST node that matches a sequence of child nodes in a row or fails -export class And implements LPNode { - t: string; - and: LPNode[]; - filename: string; - line: number; - char: number; - - constructor( - t: string, - and: LPNode[], - filename: string, - line: number, - char: number, - ) { - this.t = t; - this.and = and; - this.filename = filename; - this.line = line; - this.char = char; - } - - static build(and: LPNode[]): And { - return new And(`(${and.map((a) => a.t).join(' & ')})`, and, '', -1, -1); - } - - toString(): string { - return this.t; - } - - get(i: number): LPNode { - if (this.and[i]) return this.and[i]; - return new NulLP(); - } - - getAll(): LPNode[] { - return this.and; - } - - has(id?: number): boolean { - if (typeof id === 'number') { - if (this.and[id]) { - return this.and[id].has(); - } - return false; - } - return this.line > -1; - } - - apply(lp: LP): And | LPError { - const filename = lp.filename; - const line = lp.line; - const char = lp.char; - let t = ''; - const and = []; - const s = lp.snapshot(); - // This can fail, allow the underlying error to bubble up - for (let i = 0; i < this.and.length; i++) { - const a = this.and[i].apply(lp); - if (a instanceof LPError) { - lp.restore(s); - return a; - } - t += a.toString(); - and.push(a); - } - return new And(t, and, filename, line, char); - } -} - -// An AST node that matches any of its child nodes or fails. Only returns the first match. -export class Or implements LPNode { - t: string; - or: LPNode[]; - filename: string; - line: number; - char: number; - - constructor( - t: string, - or: LPNode[], - filename: string, - line: number, - char: number, - ) { - this.t = t; - this.or = or; - this.filename = filename; - this.line = line; - this.char = char; - } - - static build(or: LPNode[]): Or { - return new Or(`(${or.map((o) => o.t).join(' | ')})`, or, '', -1, -1); - } - - toString(): string { - return this.t; - } - - get(): LPNode { - if (this.or[0]) return this.or[0]; - return new NulLP(); - } - - getAll(): LPNode[] { - return this.or; - } - - has(id?: number): boolean { - if (typeof id === 'number') { - if (this.or[id]) { - return this.or[id].has(); - } - return false; - } - return this.line > -1; - } - - apply(lp: LP): Or | LPError { - const filename = lp.filename; - const line = lp.line; - const char = lp.char; - let t = ''; - const or = []; - const errs = []; - // Return the first match (if there are multiple matches, it is the first one) - for (let i = 0; i < this.or.length; i++) { - const s = lp.snapshot(); - const o = this.or[i].apply(lp); - if (o instanceof LPError) { - errs.push(o); - lp.restore(s); - continue; - } - // We have a match! - t = o.toString(); - or.push(o); - break; - } - if (or.length === 0) { - const err = lpError( - `No matching tokens ${this.or.map((o) => o.t).join(' | ')} found`, - lp, - ); - err.parent = errs; - return err; - } - return new Or(t, or, filename, line, char); - } -} - -export class ExclusiveOr implements LPNode { - t: string; - xor: LPNode[]; - filename: string; - line: number; - char: number; - - constructor( - t: string, - xor: LPNode[], - filename: string, - line: number, - char: number, - ) { - this.t = t; - this.xor = xor; - this.filename = filename; - this.line = line; - this.char = char; - } - - static build(xor: LPNode[]): ExclusiveOr { - return new ExclusiveOr( - `(${xor.map((x) => x.t).join(' ^ ')})`, - xor, - '', - -1, - -1, - ); - } - - toString(): string { - return this.t; - } - - get(): LPNode { - if (this.xor[0]) return this.xor[0]; - return new NulLP(); - } - - getAll(): LPNode[] { - return this.xor; - } - - has(id?: number): boolean { - if (typeof id === 'number') { - if (this.xor[id]) { - return this.xor[id].has(); - } - return false; - } - return this.line > -1; - } - - apply(lp: LP): ExclusiveOr | LPError { - const filename = lp.filename; - const line = lp.line; - const char = lp.char; - let t = ''; - const xor = []; - const errs = []; - // Checks the matches, it only succeeds if there's only one match - for (let i = 0; i < this.xor.length; i++) { - const s = lp.snapshot(); - const x = this.xor[i].apply(lp); - if (x instanceof LPError) { - errs.push(x); - lp.restore(s); - continue; - } - // We have a match! - t = x.toString(); - xor.push(i); - // We still restore the snapshot for further iterations - lp.restore(s); - } - if (xor.length === 0) { - const err = lpError('No matching tokens found', lp); - err.parent = errs; - return err; - } - if (xor.length > 1) { - const err = lpError('Multiple matching tokens found', lp); - err.parent = errs; - return err; - } - // Since we restored the state every time, we need to take the one that matched and re-run it - // to make sure the offset is correct - return new ExclusiveOr( - t, - [this.xor[xor[0]].apply(lp) as LPNode], - filename, - line, - char, - ); - } -} - -export class LeftSubset implements LPNode { - t: string; - left: LPNode; - right: LPNode; - filename: string; - line: number; - char: number; - - constructor( - t: string, - left: LPNode, - right: LPNode, - filename: string, - line: number, - char: number, - ) { - this.t = t; - this.left = left; - this.right = right; - this.filename = filename; - this.line = line; - this.char = char; - } - - static build(left: LPNode, right: LPNode): LeftSubset { - return new LeftSubset(`(${left.t} - ${right.t})`, left, right, '', -1, -1); - } - - toString(): string { - return this.t; - } - - get(): LPNode { - return this.left; - } - - getAll(): LPNode[] { - return [this.left]; - } - - has(): boolean { - return this.line > -1; - } - - apply(lp: LP): LeftSubset | LPError { - const filename = lp.filename; - const line = lp.line; - const char = lp.char; - // Check the left set first, immediately return an error if it failed - const s = lp.snapshot(); - const l = this.left.apply(lp); - if (l instanceof LPError) { - lp.restore(s); - return l; - } - // Check the right set *against* the value returned by the left set. If they exactly match, also - // fail - const lp2 = LP.fromText(l.toString()); - const r = this.right.apply(lp2); - if (r instanceof LPError || r.toString().length !== l.toString().length) { - // The right subset did not match the left, we're good! - return new LeftSubset(l.toString(), l, new NulLP(), filename, line, char); - } - // In this path, we force a failure because the match also exists in the right subset - lp.restore(s); - return lpError(`Right subset ${this.right.t} matches unexpectedly`, lp); - } -} - -interface Named { - [key: string]: LPNode; -} - -// An AST node that matches all of the child nodes or fails. Also provides easier access to the -// matched child nodes. -export class NamedAnd implements LPNode { - t: string; - and: Named; - filename: string; - line: number; - char: number; - - constructor( - t: string, - and: Named, - filename: string, - line: number, - char: number, - ) { - this.t = t; - this.and = and; - this.filename = filename; - this.line = line; - this.char = char; - } - - static build(and: Named): NamedAnd { - return new NamedAnd(`(${Object.keys(and).join(' & ')})`, and, '', -1, -1); - } - - toString(): string { - return this.t; - } - - get(name: string): LPNode { - if (this.and[name]) return this.and[name]; - return new NulLP(); - } - - getAll(): LPNode[] { - return Object.values(this.and); - } - - has(id?: string): boolean { - if (typeof id === 'string') { - if (this.and[id]) { - return this.and[id].has(); - } - return false; - } - return this.line > -1; - } - - apply(lp: LP): NamedAnd | LPError { - const filename = lp.filename; - const line = lp.line; - const char = lp.char; - let t = ''; - const and = {}; - const andNames = Object.keys(this.and); - const s = lp.snapshot(); - // This can fail, allow the underlying error to bubble up - for (let i = 0; i < andNames.length; i++) { - const a = this.and[andNames[i]].apply(lp); - if (a instanceof LPError) { - lp.restore(s); - return a; - } - t += a.toString(); - and[andNames[i]] = a; - } - return new NamedAnd(t, and, filename, line, char); - } -} - -// An AST node that matches one of the child nodes or fails. The first match is returned. Also -// provides easier access to the child node by name. -export class NamedOr implements LPNode { - t: string; - or: Named; - filename: string; - line: number; - char: number; - - constructor( - t: string, - or: Named, - filename: string, - line: number, - char: number, - ) { - this.t = t; - this.or = or; - this.filename = filename; - this.line = line; - this.char = char; - } - - static build(or: Named): NamedOr { - return new NamedOr(`(${Object.keys(or).join(' | ')})`, or, '', -1, -1); - } - - toString(): string { - return this.t; - } - - get(name: string): LPNode { - if (this.or[name]) return this.or[name]; - return new NulLP(); - } - - getAll(): LPNode[] { - return Object.values(this.or); - } - - has(id?: string): boolean { - if (typeof id === 'string') { - if (this.or[id]) { - return this.or[id].has(); - } - return false; - } - return this.line > -1; - } - - apply(lp: LP): NamedOr | LPError { - const filename = lp.filename; - const line = lp.line; - const char = lp.char; - let t = ''; - const or = {}; - const errs = []; - const orNames = Object.keys(this.or); - // Return the first match (if there are multiple matches, it is the first one) - for (let i = 0; i < orNames.length; i++) { - const s = lp.snapshot(); - const o = this.or[orNames[i]].apply(lp); - if (o instanceof LPError) { - errs.push(o); - lp.restore(s); - continue; - } - // We have a match! - t = o.toString(); - or[orNames[i]] = o; - break; - } - if (Object.keys(or).length === 0) { - const err = lpError('No matching or tokens found', lp); - err.parent = errs; - return err; - } - return new NamedOr(t, or, filename, line, char); - } -} - -// A 'leaf' AST node that matches a character within the specified range of characters. Useful for -// building regex-like matchers. -export class CharSet implements LPNode { - t: string; - lowerCharCode: number; - upperCharCode: number; - filename: string; - line: number; - char: number; - - constructor( - t: string, - lowerChar: string, - upperChar: string, - filename: string, - line: number, - char: number, - ) { - this.t = t; - this.lowerCharCode = lowerChar.charCodeAt(0); - this.upperCharCode = upperChar.charCodeAt(0); - this.filename = filename; - this.line = line; - this.char = char; - } - - static build(lowerChar: string, upperChar: string): CharSet { - return new CharSet( - `[${lowerChar}-${upperChar}]`, - lowerChar, - upperChar, - '', - -1, - -1, - ); - } - - toString(): string { - return this.t; - } - - check(lp: LP): boolean { - const lpCharCode = lp.data.charCodeAt(lp.i); - return this.lowerCharCode <= lpCharCode && this.upperCharCode >= lpCharCode; - } - - get(): CharSet { - return this; - } - - getAll(): CharSet[] { - return [this]; - } - - has(): boolean { - return this.line > -1; - } - - apply(lp: LP): CharSet | LPError { - if (this.check(lp)) { - const outCharSet = new CharSet( - lp.data[lp.i], - String.fromCharCode(this.lowerCharCode), - String.fromCharCode(this.upperCharCode), - lp.filename, - lp.line, - lp.char, - ); - lp.advance(1); - return outCharSet; - } - return lpError( - `Token mismatch, expected character in range of ${String.fromCharCode( - this.lowerCharCode, - )}-${String.fromCharCode(this.upperCharCode)}`, - lp, - ); - } -} - -// A composite AST 'node' that matches the child node between the minimum and maximum repetitions or -// fails. -export const RangeSet = ( - toRepeat: LPNode, - min: number, - max: number, -): LPNode | LPError => { - const sets = []; - for (let i = min; i <= max; i++) { - if (i === 0) { - sets.push(Token.build('')); - continue; - } else { - const set = []; - for (let j = 0; j < i; j++) { - set.push(toRepeat); - } - sets.push(And.build(set)); - } - } - return Or.build(sets); -}; diff --git a/compiler/src/pipeline.ts b/compiler/src/pipeline.ts deleted file mode 100644 index 34d9ec018..000000000 --- a/compiler/src/pipeline.ts +++ /dev/null @@ -1,148 +0,0 @@ -interface Converter { - fromFile(filename: string): string | Buffer; - fromString(str: string): string | Buffer; -} -interface ConverterIntermediate { - prev: string; - fromFile(filename: string): string | Buffer; - fromString(str: string): string | Buffer; -} - -type convert = [string, string, Converter]; - -interface OutMap { - [out: string]: Converter; -} - -interface ConverterMap { - [input: string]: OutMap; -} - -const buildPipeline = (converters: convert[]): ConverterMap => { - // Get a unique set of inputs and outputs, and index the converters by their input and output - const inputs = new Set(); - const outputs = new Set(); - const both = new Set(); - const byInput = new Map(); - const byOutput = new Map(); - const byBoth = new Map(); - converters.forEach((converter) => { - inputs.add(converter[0]); - outputs.add(converter[1]); - both.add(converter[0]); - both.add(converter[1]); - if (!byInput.has(converter[0])) { - byInput.set(converter[0], []); - } - byInput.get(converter[0]).push(converter); - if (!byOutput.has(converter[1])) { - byOutput.set(converter[1], []); - } - byOutput.get(converter[1]).push(converter); - byBoth.set(converter[0] + converter[1], converter[2]); - }); - // Compute the shortest path from every input to every output, or drop it if not possible - const paths = {}; - inputs.forEach((input: string) => { - outputs.forEach((output: string) => { - // Skip identical inputs and outputs - if (input === output) return; - // Short-circuit if a direct conversion is possible - if (byBoth.has(input + output)) { - if (!paths[input]) paths[input] = {}; - paths[input][output] = [input, output]; - return; - } - // Otherwise, scan through the graph using Djikstra's Algorithm - const nodes = new Set(); - const dist = new Map(); - const prev = new Map(); - both.forEach((n) => { - nodes.add(n); - dist.set(n, Infinity); - prev.set(n, undefined); - }); - dist.set(input, 0); - let minDist = 0; - let minNode = input; - while (nodes.size > 0) { - const n = minNode; - if (n === output) break; - nodes.delete(n); - minNode = undefined; - minDist = Infinity; - // Find the smallest remaining distance node to continue the search - nodes.forEach((node: string) => { - if (dist.get(node) < minDist || minDist === Infinity) { - minDist = dist.get(node); - minNode = node; - } - }); - if (byInput.has(n)) { - byInput - .get(n) - .map((r: convert) => r[1]) - .forEach((neighbor: string) => { - const newDist = dist.get(n) + 1; - if (newDist < dist.get(neighbor)) { - dist.set(neighbor, newDist); - prev.set(neighbor, n); - } - if (newDist < minDist) { - minDist = newDist; - minNode = neighbor; - } - }); - } - } - const path = []; - let node = output; - while (node) { - path.unshift(node); - node = prev.get(node); - } - if (path.length < 2) return; // Invalid/impossible path, skip it - if (!paths[input]) paths[input] = {}; - paths[input][output] = path; - }); - }); - const lookup: ConverterMap = {}; - Object.keys(paths).forEach((i) => { - Object.keys(paths[i]).forEach((o) => { - if (!lookup[i]) lookup[i] = {}; - const c = paths[i][o].reduce( - (cumu: ConverterIntermediate, curr: string) => { - if (!cumu.prev) - return { - prev: curr, - fromFile: undefined, - fromString: undefined, - }; - const converter = byBoth.get(cumu.prev + curr); - if (!cumu.fromFile) { - return { - prev: curr, - fromFile: converter.fromFile, - fromString: converter.fromString, - }; - } - return { - prev: curr, - fromFile: (filename: string) => - converter.fromString(cumu.fromFile(filename)), - fromString: (str: string) => - converter.fromString(cumu.fromString(str)), - }; - }, - { prev: undefined, fromFile: undefined, fromString: undefined }, - ); - lookup[i][o] = { - fromFile: c.fromFile, - fromString: c.fromString, - }; - }); - }); - return lookup; -}; - -export default buildPipeline; diff --git a/compiler/test.html b/compiler/test.html deleted file mode 100644 index 86e569971..000000000 --- a/compiler/test.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - Test - - - - -
- - -
-
- - diff --git a/compiler/tsconfig.json b/compiler/tsconfig.json deleted file mode 100644 index 3fc4c1aa8..000000000 --- a/compiler/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "sourceMap": true, - "allowJs": true, - "outDir": "dist", - "target": "ES2020", - "resolveJsonModule": true - }, - "include": ["src/*", "src/**/*"], - "exclude": ["node_modules"] -} diff --git a/compiler/yarn.lock b/compiler/yarn.lock deleted file mode 100644 index 516d76ad4..000000000 --- a/compiler/yarn.lock +++ /dev/null @@ -1,3461 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@aashutoshrathi/word-wrap@^1.2.3": - version "1.2.6" - resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" - integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== - -"@colors/colors@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" - integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== - -"@cypress/request@^3.0.0": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@cypress/request/-/request-3.0.1.tgz#72d7d5425236a2413bd3d8bb66d02d9dc3168960" - integrity sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - http-signature "~1.3.6" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - performance-now "^2.1.0" - qs "6.10.4" - safe-buffer "^5.1.2" - tough-cookie "^4.1.3" - tunnel-agent "^0.6.0" - uuid "^8.3.2" - -"@cypress/xvfb@^1.2.4": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@cypress/xvfb/-/xvfb-1.2.4.tgz#2daf42e8275b39f4aa53c14214e557bd14e7748a" - integrity sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q== - dependencies: - debug "^3.1.0" - lodash.once "^4.1.1" - -"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": - version "4.4.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" - integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== - dependencies: - eslint-visitor-keys "^3.3.0" - -"@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": - version "4.10.0" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" - integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== - -"@eslint/eslintrc@^2.1.3": - version "2.1.3" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.3.tgz#797470a75fe0fbd5a53350ee715e85e87baff22d" - integrity sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA== - dependencies: - ajv "^6.12.4" - debug "^4.3.2" - espree "^9.6.0" - globals "^13.19.0" - ignore "^5.2.0" - import-fresh "^3.2.1" - js-yaml "^4.1.0" - minimatch "^3.1.2" - strip-json-comments "^3.1.1" - -"@eslint/js@8.53.0": - version "8.53.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.53.0.tgz#bea56f2ed2b5baea164348ff4d5a879f6f81f20d" - integrity sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w== - -"@humanwhocodes/config-array@^0.11.13": - version "0.11.13" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.13.tgz#075dc9684f40a531d9b26b0822153c1e832ee297" - integrity sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ== - dependencies: - "@humanwhocodes/object-schema" "^2.0.1" - debug "^4.1.1" - minimatch "^3.0.5" - -"@humanwhocodes/module-importer@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" - integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== - -"@humanwhocodes/object-schema@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz#e5211452df060fa8522b55c7b3c0c4d1981cb044" - integrity sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw== - -"@jest/schemas@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" - integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== - dependencies: - "@sinclair/typebox" "^0.27.8" - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@pkgr/utils@^2.3.1": - version "2.4.2" - resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.4.2.tgz#9e638bbe9a6a6f165580dc943f138fd3309a2cbc" - integrity sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw== - dependencies: - cross-spawn "^7.0.3" - fast-glob "^3.3.0" - is-glob "^4.0.3" - open "^9.1.0" - picocolors "^1.0.0" - tslib "^2.6.0" - -"@sinclair/typebox@^0.27.8": - version "0.27.8" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" - integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== - -"@types/json-schema@^7.0.12": - version "7.0.15" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" - integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== - -"@types/node@*": - version "20.9.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.9.0.tgz#bfcdc230583aeb891cf51e73cfdaacdd8deae298" - integrity sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw== - dependencies: - undici-types "~5.26.4" - -"@types/node@^16": - version "16.18.61" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.61.tgz#5ea47e3018348bf3bbbe646b396ba5e720310be1" - integrity sha512-k0N7BqGhJoJzdh6MuQg1V1ragJiXTh8VUBAZTWjJ9cUq23SG0F0xavOwZbhiP4J3y20xd6jxKx+xNUhkMAi76Q== - -"@types/node@^18.17.5": - version "18.18.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.18.9.tgz#5527ea1832db3bba8eb8023ce8497b7d3f299592" - integrity sha512-0f5klcuImLnG4Qreu9hPj/rEfFq6YRc5n2mAjSsH+ec/mJL+3voBH0+8T7o8RpFjH7ovc+TRsL/c7OYIQsPTfQ== - dependencies: - undici-types "~5.26.4" - -"@types/semver@^7.5.0": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.5.tgz#deed5ab7019756c9c90ea86139106b0346223f35" - integrity sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg== - -"@types/sinonjs__fake-timers@8.1.1": - version "8.1.1" - resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" - integrity sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g== - -"@types/sizzle@^2.3.2": - version "2.3.3" - resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef" - integrity sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ== - -"@types/uuid@^8.0.0": - version "8.3.4" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" - integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== - -"@types/yauzl@^2.9.1": - version "2.10.3" - resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.3.tgz#e9b2808b4f109504a03cda958259876f61017999" - integrity sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q== - dependencies: - "@types/node" "*" - -"@typescript-eslint/eslint-plugin@^6": - version "6.10.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.10.0.tgz#cfe2bd34e26d2289212946b96ab19dcad64b661a" - integrity sha512-uoLj4g2OTL8rfUQVx2AFO1hp/zja1wABJq77P6IclQs6I/m9GLrm7jCdgzZkvWdDCQf1uEvoa8s8CupsgWQgVg== - dependencies: - "@eslint-community/regexpp" "^4.5.1" - "@typescript-eslint/scope-manager" "6.10.0" - "@typescript-eslint/type-utils" "6.10.0" - "@typescript-eslint/utils" "6.10.0" - "@typescript-eslint/visitor-keys" "6.10.0" - debug "^4.3.4" - graphemer "^1.4.0" - ignore "^5.2.4" - natural-compare "^1.4.0" - semver "^7.5.4" - ts-api-utils "^1.0.1" - -"@typescript-eslint/parser@^6", "@typescript-eslint/parser@^6.7.5": - version "6.10.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.10.0.tgz#578af79ae7273193b0b6b61a742a2bc8e02f875a" - integrity sha512-+sZwIj+s+io9ozSxIWbNB5873OSdfeBEH/FR0re14WLI6BaKuSOnnwCJ2foUiu8uXf4dRp1UqHP0vrZ1zXGrog== - dependencies: - "@typescript-eslint/scope-manager" "6.10.0" - "@typescript-eslint/types" "6.10.0" - "@typescript-eslint/typescript-estree" "6.10.0" - "@typescript-eslint/visitor-keys" "6.10.0" - debug "^4.3.4" - -"@typescript-eslint/scope-manager@6.10.0": - version "6.10.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.10.0.tgz#b0276118b13d16f72809e3cecc86a72c93708540" - integrity sha512-TN/plV7dzqqC2iPNf1KrxozDgZs53Gfgg5ZHyw8erd6jd5Ta/JIEcdCheXFt9b1NYb93a1wmIIVW/2gLkombDg== - dependencies: - "@typescript-eslint/types" "6.10.0" - "@typescript-eslint/visitor-keys" "6.10.0" - -"@typescript-eslint/type-utils@6.10.0": - version "6.10.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.10.0.tgz#1007faede067c78bdbcef2e8abb31437e163e2e1" - integrity sha512-wYpPs3hgTFblMYwbYWPT3eZtaDOjbLyIYuqpwuLBBqhLiuvJ+9sEp2gNRJEtR5N/c9G1uTtQQL5AhV0fEPJYcg== - dependencies: - "@typescript-eslint/typescript-estree" "6.10.0" - "@typescript-eslint/utils" "6.10.0" - debug "^4.3.4" - ts-api-utils "^1.0.1" - -"@typescript-eslint/types@6.10.0": - version "6.10.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.10.0.tgz#f4f0a84aeb2ac546f21a66c6e0da92420e921367" - integrity sha512-36Fq1PWh9dusgo3vH7qmQAj5/AZqARky1Wi6WpINxB6SkQdY5vQoT2/7rW7uBIsPDcvvGCLi4r10p0OJ7ITAeg== - -"@typescript-eslint/typescript-estree@6.10.0": - version "6.10.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.10.0.tgz#667381eed6f723a1a8ad7590a31f312e31e07697" - integrity sha512-ek0Eyuy6P15LJVeghbWhSrBCj/vJpPXXR+EpaRZqou7achUWL8IdYnMSC5WHAeTWswYQuP2hAZgij/bC9fanBg== - dependencies: - "@typescript-eslint/types" "6.10.0" - "@typescript-eslint/visitor-keys" "6.10.0" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - semver "^7.5.4" - ts-api-utils "^1.0.1" - -"@typescript-eslint/utils@6.10.0": - version "6.10.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.10.0.tgz#4d76062d94413c30e402c9b0df8c14aef8d77336" - integrity sha512-v+pJ1/RcVyRc0o4wAGux9x42RHmAjIGzPRo538Z8M1tVx6HOnoQBCX/NoadHQlZeC+QO2yr4nNSFWOoraZCAyg== - dependencies: - "@eslint-community/eslint-utils" "^4.4.0" - "@types/json-schema" "^7.0.12" - "@types/semver" "^7.5.0" - "@typescript-eslint/scope-manager" "6.10.0" - "@typescript-eslint/types" "6.10.0" - "@typescript-eslint/typescript-estree" "6.10.0" - semver "^7.5.4" - -"@typescript-eslint/visitor-keys@6.10.0": - version "6.10.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.10.0.tgz#b9eaf855a1ac7e95633ae1073af43d451e8f84e3" - integrity sha512-xMGluxQIEtOM7bqFCo+rCMh5fqI+ZxV5RUUOa29iVPz1OgCZrtc7rFnz5cLUazlkPKYqX+75iuDq7m0HQ48nCg== - dependencies: - "@typescript-eslint/types" "6.10.0" - eslint-visitor-keys "^3.4.1" - -"@ungap/structured-clone@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" - integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== - -JSONStream@^1.0.3: - version "1.3.5" - resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" - integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== - dependencies: - jsonparse "^1.2.0" - through ">=2.2.7 <3" - -acorn-jsx@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - -acorn-node@^1.2.0, acorn-node@^1.3.0, acorn-node@^1.5.2, acorn-node@^1.6.1: - version "1.8.2" - resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" - integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== - dependencies: - acorn "^7.0.0" - acorn-walk "^7.0.0" - xtend "^4.0.2" - -acorn-walk@^7.0.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" - integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== - -acorn@^7.0.0: - version "7.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" - integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== - -acorn@^8.9.0: - version "8.11.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" - integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== - -aggregate-error@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" - integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== - dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" - -ajv@^6.12.4: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -alan-js-runtime@../js-runtime: - version "0.1.45-beta3" - dependencies: - cross-fetch "^3.0.6" - xxhashjs "^0.2.2" - -ansi-colors@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== - -ansi-escapes@^4.3.0: - version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" - integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== - dependencies: - type-fest "^0.21.3" - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" - integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== - -arch@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11" - integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - -asn1.js@^5.2.0: - version "5.4.1" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" - integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== - dependencies: - bn.js "^4.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - safer-buffer "^2.1.0" - -asn1@~0.2.3: - version "0.2.6" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" - integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== - dependencies: - safer-buffer "~2.1.0" - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= - -assert@^1.4.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" - integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== - dependencies: - object-assign "^4.1.1" - util "0.10.3" - -astral-regex@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" - integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== - -async@^2.6.4: - version "2.6.4" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" - integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== - dependencies: - lodash "^4.17.14" - -async@^3.2.0: - version "3.2.3" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9" - integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g== - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= - -at-least-node@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" - integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== - -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= - -aws4@^1.8.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" - integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base64-js@^1.0.2, base64-js@^1.3.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -basic-auth@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" - integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg== - dependencies: - safe-buffer "5.1.2" - -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= - dependencies: - tweetnacl "^0.14.3" - -big-integer@^1.6.44: - version "1.6.51" - resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" - integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== - -blob-util@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb" - integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ== - -bluebird@^3.7.2: - version "3.7.2" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" - integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== - -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: - version "4.12.0" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" - integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== - -bn.js@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" - integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== - -bn.js@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" - integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== - -bplist-parser@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.2.0.tgz#43a9d183e5bf9d545200ceac3e712f79ebbe8d0e" - integrity sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw== - dependencies: - big-integer "^1.6.44" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -brorand@^1.0.1, brorand@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" - integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= - -browser-pack@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/browser-pack/-/browser-pack-6.1.0.tgz#c34ba10d0b9ce162b5af227c7131c92c2ecd5774" - integrity sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA== - dependencies: - JSONStream "^1.0.3" - combine-source-map "~0.8.0" - defined "^1.0.0" - safe-buffer "^5.1.1" - through2 "^2.0.0" - umd "^3.0.0" - -browser-resolve@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-2.0.0.tgz#99b7304cb392f8d73dba741bb2d7da28c6d7842b" - integrity sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ== - dependencies: - resolve "^1.17.0" - -browserify-aes@^1.0.0, browserify-aes@^1.0.4: - version "1.2.0" - resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" - integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== - dependencies: - buffer-xor "^1.0.3" - cipher-base "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.3" - inherits "^2.0.1" - safe-buffer "^5.0.1" - -browserify-cipher@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" - integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== - dependencies: - browserify-aes "^1.0.4" - browserify-des "^1.0.0" - evp_bytestokey "^1.0.0" - -browserify-des@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" - integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== - dependencies: - cipher-base "^1.0.1" - des.js "^1.0.0" - inherits "^2.0.1" - safe-buffer "^5.1.2" - -browserify-rsa@^4.0.0, browserify-rsa@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" - integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== - dependencies: - bn.js "^5.0.0" - randombytes "^2.0.1" - -browserify-sign@^4.0.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.2.tgz#e78d4b69816d6e3dd1c747e64e9947f9ad79bc7e" - integrity sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg== - dependencies: - bn.js "^5.2.1" - browserify-rsa "^4.1.0" - create-hash "^1.2.0" - create-hmac "^1.1.7" - elliptic "^6.5.4" - inherits "^2.0.4" - parse-asn1 "^5.1.6" - readable-stream "^3.6.2" - safe-buffer "^5.2.1" - -browserify-zlib@~0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" - integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== - dependencies: - pako "~1.0.5" - -browserify@^16.5.1: - version "16.5.2" - resolved "https://registry.yarnpkg.com/browserify/-/browserify-16.5.2.tgz#d926835e9280fa5fd57f5bc301f2ef24a972ddfe" - integrity sha512-TkOR1cQGdmXU9zW4YukWzWVSJwrxmNdADFbqbE3HFgQWe5wqZmOawqZ7J/8MPCwk/W8yY7Y0h+7mOtcZxLP23g== - dependencies: - JSONStream "^1.0.3" - assert "^1.4.0" - browser-pack "^6.0.1" - browser-resolve "^2.0.0" - browserify-zlib "~0.2.0" - buffer "~5.2.1" - cached-path-relative "^1.0.0" - concat-stream "^1.6.0" - console-browserify "^1.1.0" - constants-browserify "~1.0.0" - crypto-browserify "^3.0.0" - defined "^1.0.0" - deps-sort "^2.0.0" - domain-browser "^1.2.0" - duplexer2 "~0.1.2" - events "^2.0.0" - glob "^7.1.0" - has "^1.0.0" - htmlescape "^1.1.0" - https-browserify "^1.0.0" - inherits "~2.0.1" - insert-module-globals "^7.0.0" - labeled-stream-splicer "^2.0.0" - mkdirp-classic "^0.5.2" - module-deps "^6.2.3" - os-browserify "~0.3.0" - parents "^1.0.1" - path-browserify "~0.0.0" - process "~0.11.0" - punycode "^1.3.2" - querystring-es3 "~0.2.0" - read-only-stream "^2.0.0" - readable-stream "^2.0.2" - resolve "^1.1.4" - shasum "^1.0.0" - shell-quote "^1.6.1" - stream-browserify "^2.0.0" - stream-http "^3.0.0" - string_decoder "^1.1.1" - subarg "^1.0.0" - syntax-error "^1.1.1" - through2 "^2.0.0" - timers-browserify "^1.0.1" - tty-browserify "0.0.1" - url "~0.11.0" - util "~0.10.1" - vm-browserify "^1.0.0" - xtend "^4.0.0" - -buffer-crc32@~0.2.3: - version "0.2.13" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -buffer-xor@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" - integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= - -buffer@^5.6.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.1.13" - -buffer@~5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.2.1.tgz#dd57fa0f109ac59c602479044dca7b8b3d0b71d6" - integrity sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - -builtin-status-codes@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" - integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= - -bundle-name@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-3.0.0.tgz#ba59bcc9ac785fb67ccdbf104a2bf60c099f0e1a" - integrity sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw== - dependencies: - run-applescript "^5.0.0" - -cached-path-relative@^1.0.0, cached-path-relative@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/cached-path-relative/-/cached-path-relative-1.1.0.tgz#865576dfef39c0d6a7defde794d078f5308e3ef3" - integrity sha512-WF0LihfemtesFcJgO7xfOoOcnWzY/QHR4qeDqV44jPU3HTI54+LnfXK3SA27AVVGCdZFgjjFFaqUA9Jx7dMJZA== - -cachedir@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8" - integrity sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw== - -call-bind@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.5.tgz#6fa2b7845ce0ea49bf4d8b9ef64727a2c2e2e513" - integrity sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ== - dependencies: - function-bind "^1.1.2" - get-intrinsic "^1.2.1" - set-function-length "^1.1.1" - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= - -chalk@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" - -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -check-more-types@^2.24.0: - version "2.24.0" - resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600" - integrity sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA= - -ci-info@^3.2.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" - integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== - -cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" - integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -clean-stack@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" - integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== - -cli-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" - integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== - dependencies: - restore-cursor "^3.1.0" - -cli-table3@~0.6.1: - version "0.6.3" - resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.3.tgz#61ab765aac156b52f222954ffc607a6f01dbeeb2" - integrity sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg== - dependencies: - string-width "^4.2.0" - optionalDependencies: - "@colors/colors" "1.5.0" - -cli-truncate@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" - integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== - dependencies: - slice-ansi "^3.0.0" - string-width "^4.2.0" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -colorette@^2.0.16: - version "2.0.20" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" - integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== - -combine-source-map@^0.8.0, combine-source-map@~0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/combine-source-map/-/combine-source-map-0.8.0.tgz#a58d0df042c186fcf822a8e8015f5450d2d79a8b" - integrity sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos= - dependencies: - convert-source-map "~1.1.0" - inline-source-map "~0.6.0" - lodash.memoize "~3.0.3" - source-map "~0.5.3" - -combined-stream@^1.0.6, combined-stream@~1.0.6: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -commander@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" - integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== - -commander@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" - integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== - -common-tags@^1.4.0, common-tags@^1.8.0: - version "1.8.2" - resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.2.tgz#94ebb3c076d26032745fd54face7f688ef5ac9c6" - integrity sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -concat-stream@^1.6.0, concat-stream@^1.6.1, concat-stream@~1.6.0: - version "1.6.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - -console-browserify@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" - integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== - -constants-browserify@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" - integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= - -convert-source-map@~1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.1.3.tgz#4829c877e9fe49b3161f3bf3673888e204699860" - integrity sha1-SCnId+n+SbMWHzvzZziI4gRpmGA= - -core-util-is@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - -corser@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/corser/-/corser-2.0.1.tgz#8eda252ecaab5840dcd975ceb90d9370c819ff87" - integrity sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ== - -create-ecdh@^4.0.0: - version "4.0.4" - resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" - integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== - dependencies: - bn.js "^4.1.0" - elliptic "^6.5.3" - -create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" - integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== - dependencies: - cipher-base "^1.0.1" - inherits "^2.0.1" - md5.js "^1.3.4" - ripemd160 "^2.0.1" - sha.js "^2.4.0" - -create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" - integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== - dependencies: - cipher-base "^1.0.3" - create-hash "^1.1.0" - inherits "^2.0.1" - ripemd160 "^2.0.0" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -cross-fetch@^3.0.6: - version "3.1.5" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" - integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== - dependencies: - node-fetch "2.6.7" - -cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -crypto-browserify@^3.0.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" - integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== - dependencies: - browserify-cipher "^1.0.0" - browserify-sign "^4.0.0" - create-ecdh "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.0" - diffie-hellman "^5.0.0" - inherits "^2.0.1" - pbkdf2 "^3.0.3" - public-encrypt "^4.0.0" - randombytes "^2.0.0" - randomfill "^1.0.3" - -cuint@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b" - integrity sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs= - -cypress@^13: - version "13.5.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.5.0.tgz#8c149074186130972f08b2cdce6ded41f014bacd" - integrity sha512-oh6U7h9w8wwHfzNDJQ6wVcAeXu31DlIYlNOBvfd6U4CcB8oe4akawQmH+QJVOMZlM42eBoCne015+svVqdwdRQ== - dependencies: - "@cypress/request" "^3.0.0" - "@cypress/xvfb" "^1.2.4" - "@types/node" "^18.17.5" - "@types/sinonjs__fake-timers" "8.1.1" - "@types/sizzle" "^2.3.2" - arch "^2.2.0" - blob-util "^2.0.2" - bluebird "^3.7.2" - buffer "^5.6.0" - cachedir "^2.3.0" - chalk "^4.1.0" - check-more-types "^2.24.0" - cli-cursor "^3.1.0" - cli-table3 "~0.6.1" - commander "^6.2.1" - common-tags "^1.8.0" - dayjs "^1.10.4" - debug "^4.3.4" - enquirer "^2.3.6" - eventemitter2 "6.4.7" - execa "4.1.0" - executable "^4.1.1" - extract-zip "2.0.1" - figures "^3.2.0" - fs-extra "^9.1.0" - getos "^3.2.1" - is-ci "^3.0.0" - is-installed-globally "~0.4.0" - lazy-ass "^1.6.0" - listr2 "^3.8.3" - lodash "^4.17.21" - log-symbols "^4.0.0" - minimist "^1.2.8" - ospath "^1.2.2" - pretty-bytes "^5.6.0" - process "^0.11.10" - proxy-from-env "1.0.0" - request-progress "^3.0.0" - semver "^7.5.3" - supports-color "^8.1.1" - tmp "~0.2.1" - untildify "^4.0.0" - yauzl "^2.10.0" - -dash-ast@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/dash-ast/-/dash-ast-1.0.0.tgz#12029ba5fb2f8aa6f0a861795b23c1b4b6c27d37" - integrity sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA== - -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= - dependencies: - assert-plus "^1.0.0" - -dayjs@^1.10.4: - version "1.11.10" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0" - integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ== - -debug@^3.1.0, debug@^3.2.7: - version "3.2.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== - dependencies: - ms "^2.1.1" - -debug@^4.1.1: - version "4.3.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" - integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== - dependencies: - ms "2.1.2" - -debug@^4.3.2, debug@^4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -deep-is@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" - integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== - -default-browser-id@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-3.0.0.tgz#bee7bbbef1f4e75d31f98f4d3f1556a14cea790c" - integrity sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA== - dependencies: - bplist-parser "^0.2.0" - untildify "^4.0.0" - -default-browser@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-4.0.0.tgz#53c9894f8810bf86696de117a6ce9085a3cbc7da" - integrity sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA== - dependencies: - bundle-name "^3.0.0" - default-browser-id "^3.0.0" - execa "^7.1.1" - titleize "^3.0.0" - -define-data-property@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.1.tgz#c35f7cd0ab09883480d12ac5cb213715587800b3" - integrity sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ== - dependencies: - get-intrinsic "^1.2.1" - gopd "^1.0.1" - has-property-descriptors "^1.0.0" - -define-lazy-prop@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" - integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== - -defined@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" - integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= - -deps-sort@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/deps-sort/-/deps-sort-2.0.1.tgz#9dfdc876d2bcec3386b6829ac52162cda9fa208d" - integrity sha512-1orqXQr5po+3KI6kQb9A4jnXT1PBwggGl2d7Sq2xsnOeI9GPcE/tGcF9UiSZtZBM7MukY4cAh7MemS6tZYipfw== - dependencies: - JSONStream "^1.0.3" - shasum-object "^1.0.0" - subarg "^1.0.0" - through2 "^2.0.0" - -des.js@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" - integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== - dependencies: - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -detective@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b" - integrity sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg== - dependencies: - acorn-node "^1.6.1" - defined "^1.0.0" - minimist "^1.1.1" - -diffie-hellman@^5.0.0: - version "5.0.3" - resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" - integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== - dependencies: - bn.js "^4.1.0" - miller-rabin "^4.0.0" - randombytes "^2.0.0" - -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - -dlv@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" - integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== - -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - -domain-browser@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" - integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== - -duplexer2@^0.1.2, duplexer2@~0.1.0, duplexer2@~0.1.2: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" - integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= - dependencies: - readable-stream "^2.0.2" - -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= - dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" - -elliptic@^6.5.3, elliptic@^6.5.4: - version "6.5.4" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" - integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== - dependencies: - bn.js "^4.11.9" - brorand "^1.1.0" - hash.js "^1.0.0" - hmac-drbg "^1.0.1" - inherits "^2.0.4" - minimalistic-assert "^1.0.1" - minimalistic-crypto-utils "^1.0.1" - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -end-of-stream@^1.1.0: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -enquirer@^2.3.6: - version "2.4.1" - resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.4.1.tgz#93334b3fbd74fc7097b224ab4a8fb7e40bf4ae56" - integrity sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ== - dependencies: - ansi-colors "^4.1.1" - strip-ansi "^6.0.1" - -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -eslint-config-prettier@^9: - version "9.0.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz#eb25485946dd0c66cd216a46232dc05451518d1f" - integrity sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw== - -eslint-plugin-prettier@^5: - version "5.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.1.tgz#a3b399f04378f79f066379f544e42d6b73f11515" - integrity sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg== - dependencies: - prettier-linter-helpers "^1.0.0" - synckit "^0.8.5" - -eslint-scope@^7.1.1, eslint-scope@^7.2.2: - version "7.2.2" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" - integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== - dependencies: - esrecurse "^4.3.0" - estraverse "^5.2.0" - -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: - version "3.4.3" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" - integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== - -eslint@^8, eslint@^8.7.0: - version "8.53.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.53.0.tgz#14f2c8244298fcae1f46945459577413ba2697ce" - integrity sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@eslint-community/regexpp" "^4.6.1" - "@eslint/eslintrc" "^2.1.3" - "@eslint/js" "8.53.0" - "@humanwhocodes/config-array" "^0.11.13" - "@humanwhocodes/module-importer" "^1.0.1" - "@nodelib/fs.walk" "^1.2.8" - "@ungap/structured-clone" "^1.2.0" - ajv "^6.12.4" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.3.2" - doctrine "^3.0.0" - escape-string-regexp "^4.0.0" - eslint-scope "^7.2.2" - eslint-visitor-keys "^3.4.3" - espree "^9.6.1" - esquery "^1.4.2" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" - find-up "^5.0.0" - glob-parent "^6.0.2" - globals "^13.19.0" - graphemer "^1.4.0" - ignore "^5.2.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - is-path-inside "^3.0.3" - js-yaml "^4.1.0" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash.merge "^4.6.2" - minimatch "^3.1.2" - natural-compare "^1.4.0" - optionator "^0.9.3" - strip-ansi "^6.0.1" - text-table "^0.2.0" - -espree@^9.3.1, espree@^9.6.0, espree@^9.6.1: - version "9.6.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" - integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== - dependencies: - acorn "^8.9.0" - acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.4.1" - -esquery@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" - integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== - dependencies: - estraverse "^5.1.0" - -esquery@^1.4.2: - version "1.5.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" - integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^5.1.0, estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -eventemitter2@6.4.7: - version "6.4.7" - resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.7.tgz#a7f6c4d7abf28a14c1ef3442f21cb306a054271d" - integrity sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg== - -eventemitter3@^4.0.0: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - -events@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/events/-/events-2.1.0.tgz#2a9a1e18e6106e0e812aa9ebd4a819b3c29c0ba5" - integrity sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg== - -evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" - integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== - dependencies: - md5.js "^1.3.4" - safe-buffer "^5.1.1" - -execa@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" - integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== - dependencies: - cross-spawn "^7.0.0" - get-stream "^5.0.0" - human-signals "^1.1.1" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.0" - onetime "^5.1.0" - signal-exit "^3.0.2" - strip-final-newline "^2.0.0" - -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - -execa@^7.1.1: - version "7.2.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-7.2.0.tgz#657e75ba984f42a70f38928cedc87d6f2d4fe4e9" - integrity sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.1" - human-signals "^4.3.0" - is-stream "^3.0.0" - merge-stream "^2.0.0" - npm-run-path "^5.1.0" - onetime "^6.0.0" - signal-exit "^3.0.7" - strip-final-newline "^3.0.0" - -executable@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c" - integrity sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg== - dependencies: - pify "^2.2.0" - -extend@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -extract-zip@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" - integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== - dependencies: - debug "^4.1.1" - get-stream "^5.1.0" - yauzl "^2.10.0" - optionalDependencies: - "@types/yauzl" "^2.9.1" - -extsprintf@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= - -extsprintf@^1.2.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" - integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== - -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-diff@^1.1.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" - integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== - -fast-glob@^3.2.9: - version "3.2.11" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" - integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fast-glob@^3.3.0: - version "3.3.2" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" - integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-levenshtein@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= - -fast-safe-stringify@^2.0.7: - version "2.1.1" - resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" - integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== - -fastq@^1.6.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" - integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== - dependencies: - reusify "^1.0.4" - -fd-slicer@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" - integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= - dependencies: - pend "~1.2.0" - -figures@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" - integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== - dependencies: - escape-string-regexp "^1.0.5" - -file-entry-cache@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" - integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== - dependencies: - flat-cache "^3.0.4" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -flat-cache@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" - integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== - dependencies: - flatted "^3.1.0" - rimraf "^3.0.2" - -flatted@^3.1.0: - version "3.2.5" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3" - integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg== - -follow-redirects@^1.0.0: - version "1.15.4" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf" - integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw== - -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= - -form-data@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" - integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" - -fs-extra@^9.1.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" - integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -get-assigned-identifiers@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz#6dbf411de648cbaf8d9169ebb0d2d576191e2ff1" - integrity sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ== - -get-intrinsic@^1.0.2, get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.2.tgz#281b7622971123e1ef4b3c90fd7539306da93f3b" - integrity sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA== - dependencies: - function-bind "^1.1.2" - has-proto "^1.0.1" - has-symbols "^1.0.3" - hasown "^2.0.0" - -get-stream@^5.0.0, get-stream@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" - integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== - dependencies: - pump "^3.0.0" - -get-stream@^6.0.0, get-stream@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - -getos@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/getos/-/getos-3.2.1.tgz#0134d1f4e00eb46144c5a9c0ac4dc087cbb27dc5" - integrity sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q== - dependencies: - async "^3.2.0" - -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= - dependencies: - assert-plus "^1.0.0" - -glob-parent@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-parent@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" - -glob@^7.1.0, glob@^7.1.3: - version "7.2.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" - integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -global-dirs@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.1.tgz#0c488971f066baceda21447aecb1a8b911d22485" - integrity sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA== - dependencies: - ini "2.0.0" - -globals@^13.19.0: - version "13.23.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.23.0.tgz#ef31673c926a0976e1f61dab4dca57e0c0a8af02" - integrity sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA== - dependencies: - type-fest "^0.20.2" - -globby@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" - integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.2.9" - ignore "^5.2.0" - merge2 "^1.4.1" - slash "^3.0.0" - -gopd@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" - integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== - dependencies: - get-intrinsic "^1.1.3" - -graceful-fs@^4.1.6, graceful-fs@^4.2.0: - version "4.2.9" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" - integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== - -graphemer@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" - integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== - -has-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= - dependencies: - ansi-regex "^2.0.0" - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-property-descriptors@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz#52ba30b6c5ec87fd89fa574bc1c39125c6f65340" - integrity sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg== - dependencies: - get-intrinsic "^1.2.2" - -has-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" - integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== - -has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - -has@^1.0.0, has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -hash-base@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" - integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== - dependencies: - inherits "^2.0.4" - readable-stream "^3.6.0" - safe-buffer "^5.2.0" - -hash.js@^1.0.0, hash.js@^1.0.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" - integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== - dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.1" - -hasown@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" - integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== - dependencies: - function-bind "^1.1.2" - -he@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -hmac-drbg@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" - integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= - dependencies: - hash.js "^1.0.3" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.1" - -html-encoding-sniffer@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9" - integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA== - dependencies: - whatwg-encoding "^2.0.0" - -htmlescape@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/htmlescape/-/htmlescape-1.1.1.tgz#3a03edc2214bca3b66424a3e7959349509cb0351" - integrity sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E= - -http-proxy@^1.18.1: - version "1.18.1" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" - integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== - dependencies: - eventemitter3 "^4.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" - -http-server@^14.1.1: - version "14.1.1" - resolved "https://registry.yarnpkg.com/http-server/-/http-server-14.1.1.tgz#d60fbb37d7c2fdff0f0fbff0d0ee6670bd285e2e" - integrity sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A== - dependencies: - basic-auth "^2.0.1" - chalk "^4.1.2" - corser "^2.0.1" - he "^1.2.0" - html-encoding-sniffer "^3.0.0" - http-proxy "^1.18.1" - mime "^1.6.0" - minimist "^1.2.6" - opener "^1.5.1" - portfinder "^1.0.28" - secure-compare "3.0.1" - union "~0.5.0" - url-join "^4.0.1" - -http-signature@~1.3.6: - version "1.3.6" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9" - integrity sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw== - dependencies: - assert-plus "^1.0.0" - jsprim "^2.0.2" - sshpk "^1.14.1" - -https-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" - integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= - -human-signals@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" - integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - -human-signals@^4.3.0: - version "4.3.1" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2" - integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ== - -iconv-lite@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - -ieee754@^1.1.13, ieee754@^1.1.4: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - -ignore@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" - integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== - -ignore@^5.2.4: - version "5.2.4" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" - integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== - -import-fresh@^3.2.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= - -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -inherits@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" - integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= - -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - -ini@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" - integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== - -inline-source-map@~0.6.0: - version "0.6.2" - resolved "https://registry.yarnpkg.com/inline-source-map/-/inline-source-map-0.6.2.tgz#f9393471c18a79d1724f863fa38b586370ade2a5" - integrity sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU= - dependencies: - source-map "~0.5.3" - -insert-module-globals@^7.0.0: - version "7.2.1" - resolved "https://registry.yarnpkg.com/insert-module-globals/-/insert-module-globals-7.2.1.tgz#d5e33185181a4e1f33b15f7bf100ee91890d5cb3" - integrity sha512-ufS5Qq9RZN+Bu899eA9QCAYThY+gGW7oRkmb0vC93Vlyu/CFGcH0OYPEjVkDXA5FEbTt1+VWzdoOD3Ny9N+8tg== - dependencies: - JSONStream "^1.0.3" - acorn-node "^1.5.2" - combine-source-map "^0.8.0" - concat-stream "^1.6.1" - is-buffer "^1.1.0" - path-is-absolute "^1.0.1" - process "~0.11.0" - through2 "^2.0.0" - undeclared-identifiers "^1.1.2" - xtend "^4.0.0" - -is-buffer@^1.1.0: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - -is-ci@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.1.tgz#db6ecbed1bd659c43dac0f45661e7674103d1867" - integrity sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ== - dependencies: - ci-info "^3.2.0" - -is-core-module@^2.8.1: - version "2.8.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211" - integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA== - dependencies: - has "^1.0.3" - -is-docker@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - -is-docker@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" - integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-inside-container@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" - integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== - dependencies: - is-docker "^3.0.0" - -is-installed-globally@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" - integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== - dependencies: - global-dirs "^3.0.0" - is-path-inside "^3.0.2" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-path-inside@^3.0.2, is-path-inside@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -is-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" - integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== - -is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= - -is-unicode-supported@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" - integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== - -is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= - -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" - integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= - -json-stable-stringify@~0.0.0: - version "0.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz#611c23e814db375527df851193db59dd2af27f45" - integrity sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U= - dependencies: - jsonify "~0.0.0" - -json-stringify-safe@~5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= - -jsonfile@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" - integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== - dependencies: - universalify "^2.0.0" - optionalDependencies: - graceful-fs "^4.1.6" - -jsonify@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" - integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= - -jsonparse@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" - integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA= - -jsprim@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d" - integrity sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ== - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.4.0" - verror "1.10.0" - -labeled-stream-splicer@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz#42a41a16abcd46fd046306cf4f2c3576fffb1c21" - integrity sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw== - dependencies: - inherits "^2.0.1" - stream-splicer "^2.0.0" - -lazy-ass@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" - integrity sha1-eZllXoZGwX8In90YfRUNMyTVRRM= - -levn@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" - integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - dependencies: - prelude-ls "^1.2.1" - type-check "~0.4.0" - -listr2@^3.8.3: - version "3.14.0" - resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.14.0.tgz#23101cc62e1375fd5836b248276d1d2b51fdbe9e" - integrity sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g== - dependencies: - cli-truncate "^2.1.0" - colorette "^2.0.16" - log-update "^4.0.0" - p-map "^4.0.0" - rfdc "^1.3.0" - rxjs "^7.5.1" - through "^2.3.8" - wrap-ansi "^7.0.0" - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -lodash.memoize@~3.0.3: - version "3.0.4" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-3.0.4.tgz#2dcbd2c287cbc0a55cc42328bd0c736150d53e3f" - integrity sha1-LcvSwofLwKVcxCMovQxzYVDVPj8= - -lodash.merge@^4.6.0, lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== - -lodash.once@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" - integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= - -lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -log-symbols@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" - integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== - dependencies: - chalk "^4.1.0" - is-unicode-supported "^0.1.0" - -log-update@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" - integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== - dependencies: - ansi-escapes "^4.3.0" - cli-cursor "^3.1.0" - slice-ansi "^4.0.0" - wrap-ansi "^6.2.0" - -loglevel-colored-level-prefix@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz#6a40218fdc7ae15fc76c3d0f3e676c465388603e" - integrity sha1-akAhj9x64V/HbD0PPmdsRlOIYD4= - dependencies: - chalk "^1.1.3" - loglevel "^1.4.1" - -loglevel@^1.4.1: - version "1.8.0" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.0.tgz#e7ec73a57e1e7b419cb6c6ac06bf050b67356114" - integrity sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA== - -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - -md5.js@^1.3.4: - version "1.3.5" - resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" - integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - safe-buffer "^5.1.2" - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -merge2@^1.3.0, merge2@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -micromatch@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" - integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== - dependencies: - braces "^3.0.1" - picomatch "^2.2.3" - -miller-rabin@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" - integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== - dependencies: - bn.js "^4.0.0" - brorand "^1.0.1" - -mime-db@1.51.0: - version "1.51.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" - integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== - -mime-types@^2.1.12, mime-types@~2.1.19: - version "2.1.34" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24" - integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A== - dependencies: - mime-db "1.51.0" - -mime@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -mimic-fn@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" - integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== - -minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - -minimalistic-crypto-utils@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" - integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= - -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^3.0.5, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimist@^1.1.0, minimist@^1.1.1: - version "1.2.6" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" - integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== - -minimist@^1.2.6, minimist@^1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - -mkdirp-classic@^0.5.2: - version "0.5.3" - resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" - integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== - -mkdirp@^0.5.6: - version "0.5.6" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" - integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== - dependencies: - minimist "^1.2.6" - -module-deps@^6.2.3: - version "6.2.3" - resolved "https://registry.yarnpkg.com/module-deps/-/module-deps-6.2.3.tgz#15490bc02af4b56cf62299c7c17cba32d71a96ee" - integrity sha512-fg7OZaQBcL4/L+AK5f4iVqf9OMbCclXfy/znXRxTVhJSeW5AIlS9AwheYwDaXM3lVW7OBeaeUEY3gbaC6cLlSA== - dependencies: - JSONStream "^1.0.3" - browser-resolve "^2.0.0" - cached-path-relative "^1.0.2" - concat-stream "~1.6.0" - defined "^1.0.0" - detective "^5.2.0" - duplexer2 "^0.1.2" - inherits "^2.0.1" - parents "^1.0.0" - readable-stream "^2.0.2" - resolve "^1.4.0" - stream-combiner2 "^1.1.1" - subarg "^1.0.0" - through2 "^2.0.0" - xtend "^4.0.0" - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@^2.1.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= - -node-fetch@2.6.7: - version "2.6.7" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" - integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== - dependencies: - whatwg-url "^5.0.0" - -npm-run-path@^4.0.0, npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -npm-run-path@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.1.0.tgz#bc62f7f3f6952d9894bd08944ba011a6ee7b7e00" - integrity sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q== - dependencies: - path-key "^4.0.0" - -object-assign@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - -object-inspect@^1.9.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" - integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== - -once@^1.3.0, once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -onetime@^5.1.0, onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -onetime@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" - integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== - dependencies: - mimic-fn "^4.0.0" - -open@^9.1.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/open/-/open-9.1.0.tgz#684934359c90ad25742f5a26151970ff8c6c80b6" - integrity sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg== - dependencies: - default-browser "^4.0.0" - define-lazy-prop "^3.0.0" - is-inside-container "^1.0.0" - is-wsl "^2.2.0" - -opener@^1.5.1: - version "1.5.2" - resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" - integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== - -optionator@^0.9.3: - version "0.9.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" - integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== - dependencies: - "@aashutoshrathi/word-wrap" "^1.2.3" - deep-is "^0.1.3" - fast-levenshtein "^2.0.6" - levn "^0.4.1" - prelude-ls "^1.2.1" - type-check "^0.4.0" - -os-browserify@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" - integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= - -ospath@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b" - integrity sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs= - -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -p-map@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" - integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== - dependencies: - aggregate-error "^3.0.0" - -pako@~1.0.5: - version "1.0.11" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" - integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parents@^1.0.0, parents@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parents/-/parents-1.0.1.tgz#fedd4d2bf193a77745fe71e371d73c3307d9c751" - integrity sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E= - dependencies: - path-platform "~0.11.15" - -parse-asn1@^5.0.0, parse-asn1@^5.1.6: - version "5.1.6" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4" - integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw== - dependencies: - asn1.js "^5.2.0" - browserify-aes "^1.0.0" - evp_bytestokey "^1.0.0" - pbkdf2 "^3.0.3" - safe-buffer "^5.1.1" - -path-browserify@~0.0.0: - version "0.0.1" - resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" - integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-key@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" - integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-platform@~0.11.15: - version "0.11.15" - resolved "https://registry.yarnpkg.com/path-platform/-/path-platform-0.11.15.tgz#e864217f74c36850f0852b78dc7bf7d4a5721bf2" - integrity sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I= - -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - -pbkdf2@^3.0.3: - version "3.1.2" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" - integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== - dependencies: - create-hash "^1.1.2" - create-hmac "^1.1.4" - ripemd160 "^2.0.1" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -pend@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" - integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= - -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= - -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - -picomatch@^2.2.3: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -pify@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" - integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= - -portfinder@^1.0.28: - version "1.0.32" - resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.32.tgz#2fe1b9e58389712429dc2bea5beb2146146c7f81" - integrity sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg== - dependencies: - async "^2.6.4" - debug "^3.2.7" - mkdirp "^0.5.6" - -prelude-ls@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" - integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== - -prettier-eslint@^16: - version "16.1.2" - resolved "https://registry.yarnpkg.com/prettier-eslint/-/prettier-eslint-16.1.2.tgz#86364fea13dd063f3df715b922678dd8a0fd4be1" - integrity sha512-mGFGZQbAh11FSnwW3H1zngzQYR2QMmHO8vdfgnAuzOFhnDeUZHYtwpqQvOMOMT0k818Dr1X+J4a/sVE0r34RKQ== - dependencies: - "@typescript-eslint/parser" "^6.7.5" - common-tags "^1.4.0" - dlv "^1.1.0" - eslint "^8.7.0" - indent-string "^4.0.0" - lodash.merge "^4.6.0" - loglevel-colored-level-prefix "^1.0.0" - prettier "^3.0.1" - pretty-format "^29.7.0" - require-relative "^0.8.7" - typescript "^5.2.2" - vue-eslint-parser "^9.1.0" - -prettier-linter-helpers@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" - integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== - dependencies: - fast-diff "^1.1.2" - -prettier@^3, prettier@^3.0.1: - version "3.0.3" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.3.tgz#432a51f7ba422d1469096c0fdc28e235db8f9643" - integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg== - -pretty-bytes@^5.6.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" - integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== - -pretty-format@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" - integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== - dependencies: - "@jest/schemas" "^29.6.3" - ansi-styles "^5.0.0" - react-is "^18.0.0" - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -process@^0.11.10, process@~0.11.0: - version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= - -proxy-from-env@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" - integrity sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A== - -psl@^1.1.33: - version "1.9.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" - integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== - -public-encrypt@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" - integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== - dependencies: - bn.js "^4.1.0" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - parse-asn1 "^5.0.0" - randombytes "^2.0.1" - safe-buffer "^5.1.2" - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= - -punycode@^1.3.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= - -punycode@^2.1.0, punycode@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -qs@6.10.4: - version "6.10.4" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.4.tgz#6a3003755add91c0ec9eacdc5f878b034e73f9e7" - integrity sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g== - dependencies: - side-channel "^1.0.4" - -qs@^6.4.0: - version "6.11.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" - integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA== - dependencies: - side-channel "^1.0.4" - -querystring-es3@~0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" - integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= - -querystring@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= - -querystringify@^2.1.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" - integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -randomfill@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" - integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== - dependencies: - randombytes "^2.0.5" - safe-buffer "^5.1.0" - -react-is@^18.0.0: - version "18.2.0" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" - integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== - -read-only-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/read-only-stream/-/read-only-stream-2.0.0.tgz#2724fd6a8113d73764ac288d4386270c1dbf17f0" - integrity sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A= - dependencies: - readable-stream "^2.0.2" - -readable-stream@^2.0.2, readable-stream@^2.2.2, readable-stream@~2.3.6: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readable-stream@^3.6.2: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -request-progress@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-3.0.0.tgz#4ca754081c7fec63f505e4faa825aa06cd669dbe" - integrity sha1-TKdUCBx/7GP1BeT6qCWqBs1mnb4= - dependencies: - throttleit "^1.0.0" - -require-relative@^0.8.7: - version "0.8.7" - resolved "https://registry.yarnpkg.com/require-relative/-/require-relative-0.8.7.tgz#7999539fc9e047a37928fa196f8e1563dabd36de" - integrity sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4= - -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -resolve@^1.1.4, resolve@^1.17.0, resolve@^1.4.0: - version "1.22.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" - integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== - dependencies: - is-core-module "^2.8.1" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -restore-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" - integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== - dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -rfdc@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" - integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== - -rimraf@^3.0.0, rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -ripemd160@^2.0.0, ripemd160@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" - integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - -run-applescript@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-5.0.0.tgz#e11e1c932e055d5c6b40d98374e0268d9b11899c" - integrity sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg== - dependencies: - execa "^5.0.0" - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -rxjs@^7.5.1: - version "7.8.1" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" - integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== - dependencies: - tslib "^2.1.0" - -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -"safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -secure-compare@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/secure-compare/-/secure-compare-3.0.1.tgz#f1a0329b308b221fae37b9974f3d578d0ca999e3" - integrity sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw== - -semver@^7.3.6, semver@^7.5.3, semver@^7.5.4: - version "7.5.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" - integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== - dependencies: - lru-cache "^6.0.0" - -set-function-length@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.1.1.tgz#4bc39fafb0307224a33e106a7d35ca1218d659ed" - integrity sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ== - dependencies: - define-data-property "^1.1.1" - get-intrinsic "^1.2.1" - gopd "^1.0.1" - has-property-descriptors "^1.0.0" - -sha.js@^2.4.0, sha.js@^2.4.8, sha.js@~2.4.4: - version "2.4.11" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" - integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -shasum-object@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shasum-object/-/shasum-object-1.0.0.tgz#0b7b74ff5b66ecf9035475522fa05090ac47e29e" - integrity sha512-Iqo5rp/3xVi6M4YheapzZhhGPVs0yZwHj7wvwQ1B9z8H6zk+FEnI7y3Teq7qwnekfEhu8WmG2z0z4iWZaxLWVg== - dependencies: - fast-safe-stringify "^2.0.7" - -shasum@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/shasum/-/shasum-1.0.2.tgz#e7012310d8f417f4deb5712150e5678b87ae565f" - integrity sha1-5wEjENj0F/TetXEhUOVni4euVl8= - dependencies: - json-stable-stringify "~0.0.0" - sha.js "~2.4.4" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -shell-quote@^1.6.1: - version "1.7.3" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123" - integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw== - -side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== - dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" - -signal-exit@^3.0.2: - version "3.0.6" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.6.tgz#24e630c4b0f03fea446a2bd299e62b4a6ca8d0af" - integrity sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ== - -signal-exit@^3.0.3, signal-exit@^3.0.7: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -simple-concat@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" - integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -slice-ansi@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" - integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== - dependencies: - ansi-styles "^4.0.0" - astral-regex "^2.0.0" - is-fullwidth-code-point "^3.0.0" - -slice-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" - integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== - dependencies: - ansi-styles "^4.0.0" - astral-regex "^2.0.0" - is-fullwidth-code-point "^3.0.0" - -source-map@~0.5.3: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - -sshpk@^1.14.1: - version "1.17.0" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5" - integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ== - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" - -stream-browserify@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" - integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== - dependencies: - inherits "~2.0.1" - readable-stream "^2.0.2" - -stream-combiner2@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/stream-combiner2/-/stream-combiner2-1.1.1.tgz#fb4d8a1420ea362764e21ad4780397bebcb41cbe" - integrity sha1-+02KFCDqNidk4hrUeAOXvry0HL4= - dependencies: - duplexer2 "~0.1.0" - readable-stream "^2.0.2" - -stream-http@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-3.2.0.tgz#1872dfcf24cb15752677e40e5c3f9cc1926028b5" - integrity sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A== - dependencies: - builtin-status-codes "^3.0.0" - inherits "^2.0.4" - readable-stream "^3.6.0" - xtend "^4.0.2" - -stream-splicer@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/stream-splicer/-/stream-splicer-2.0.1.tgz#0b13b7ee2b5ac7e0609a7463d83899589a363fcd" - integrity sha512-Xizh4/NPuYSyAXyT7g8IvdJ9HJpxIGL9PjyhtywCZvvP0OPIdqyrr4dMikeuvY8xahpdKEBlBTySe583totajg== - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.2" - -string-width@^4.1.0, string-width@^4.2.0: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -strip-final-newline@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" - integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== - -strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -subarg@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2" - integrity sha1-9izxdYHplrSPyWVpn1TAauJouNI= - dependencies: - minimist "^1.1.0" - -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-color@^8.1.1: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -synckit@^0.8.5: - version "0.8.5" - resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.5.tgz#b7f4358f9bb559437f9f167eb6bc46b3c9818fa3" - integrity sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q== - dependencies: - "@pkgr/utils" "^2.3.1" - tslib "^2.5.0" - -syntax-error@^1.1.1: - version "1.4.0" - resolved "https://registry.yarnpkg.com/syntax-error/-/syntax-error-1.4.0.tgz#2d9d4ff5c064acb711594a3e3b95054ad51d907c" - integrity sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w== - dependencies: - acorn-node "^1.2.0" - -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= - -throttleit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c" - integrity sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw= - -through2@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" - integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== - dependencies: - readable-stream "~2.3.6" - xtend "~4.0.1" - -"through@>=2.2.7 <3", through@^2.3.8: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= - -timers-browserify@^1.0.1: - version "1.4.2" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-1.4.2.tgz#c9c58b575be8407375cb5e2462dacee74359f41d" - integrity sha1-ycWLV1voQHN1y14kYtrO50NZ9B0= - dependencies: - process "~0.11.0" - -titleize@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/titleize/-/titleize-3.0.0.tgz#71c12eb7fdd2558aa8a44b0be83b8a76694acd53" - integrity sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ== - -tmp@~0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" - integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== - dependencies: - rimraf "^3.0.0" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -tough-cookie@^4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf" - integrity sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw== - dependencies: - psl "^1.1.33" - punycode "^2.1.1" - universalify "^0.2.0" - url-parse "^1.5.3" - -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= - -ts-api-utils@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331" - integrity sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg== - -tslib@^2.1.0, tslib@^2.5.0, tslib@^2.6.0: - version "2.6.2" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" - integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== - -tty-browserify@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.1.tgz#3f05251ee17904dfd0677546670db9651682b811" - integrity sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw== - -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= - dependencies: - safe-buffer "^5.0.1" - -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= - -type-check@^0.4.0, type-check@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" - integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== - dependencies: - prelude-ls "^1.2.1" - -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - -type-fest@^0.21.3: - version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" - integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== - -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= - -typescript@^5, typescript@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" - integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== - -umd@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/umd/-/umd-3.0.3.tgz#aa9fe653c42b9097678489c01000acb69f0b26cf" - integrity sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow== - -undeclared-identifiers@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/undeclared-identifiers/-/undeclared-identifiers-1.1.3.tgz#9254c1d37bdac0ac2b52de4b6722792d2a91e30f" - integrity sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw== - dependencies: - acorn-node "^1.3.0" - dash-ast "^1.0.0" - get-assigned-identifiers "^1.2.0" - simple-concat "^1.0.0" - xtend "^4.0.1" - -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== - -union@~0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/union/-/union-0.5.0.tgz#b2c11be84f60538537b846edb9ba266ba0090075" - integrity sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA== - dependencies: - qs "^6.4.0" - -universalify@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" - integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== - -universalify@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" - integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== - -untildify@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" - integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -url-join@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" - integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== - -url-parse@^1.5.3: - version "1.5.10" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" - integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== - dependencies: - querystringify "^2.1.1" - requires-port "^1.0.0" - -url@~0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" - integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= - dependencies: - punycode "1.3.2" - querystring "0.2.0" - -util-deprecate@^1.0.1, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -util@0.10.3: - version "0.10.3" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" - integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk= - dependencies: - inherits "2.0.1" - -util@~0.10.1: - version "0.10.4" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" - integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== - dependencies: - inherits "2.0.3" - -uuid@^8.0.0, uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - -verror@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - -vm-browserify@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" - integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== - -vue-eslint-parser@^9.1.0: - version "9.3.2" - resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-9.3.2.tgz#6f9638e55703f1c77875a19026347548d93fd499" - integrity sha512-q7tWyCVaV9f8iQyIA5Mkj/S6AoJ9KBN8IeUSf3XEmBrOtxOZnfTg5s4KClbZBCK3GtnT/+RyCLZyDHuZwTuBjg== - dependencies: - debug "^4.3.4" - eslint-scope "^7.1.1" - eslint-visitor-keys "^3.3.0" - espree "^9.3.1" - esquery "^1.4.0" - lodash "^4.17.21" - semver "^7.3.6" - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= - -whatwg-encoding@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" - integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg== - dependencies: - iconv-lite "0.6.3" - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -wrap-ansi@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" - integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - -xxhashjs@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/xxhashjs/-/xxhashjs-0.2.2.tgz#8a6251567621a1c46a5ae204da0249c7f8caa9d8" - integrity sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw== - dependencies: - cuint "^0.2.2" - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - -yauzl@^2.10.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" - integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= - dependencies: - buffer-crc32 "~0.2.3" - fd-slicer "~1.1.0" - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/js-runtime/.gitattributes b/js-runtime/.gitattributes deleted file mode 100644 index d77456e4a..000000000 --- a/js-runtime/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -yarn.lock binary \ No newline at end of file diff --git a/js-runtime/.gitignore b/js-runtime/.gitignore deleted file mode 100644 index a5199f687..000000000 --- a/js-runtime/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules/ -.idea \ No newline at end of file diff --git a/js-runtime/README.md b/js-runtime/README.md deleted file mode 100644 index ec065be59..000000000 --- a/js-runtime/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# js-runtime - -Runtime support for transpiled alan code in Javascript - -## Usage - -Make sure you `npm install alan-js-runtime` in any Node project that uses an `alan-compiler` Javascript output. - -For the web, [browserify](http://browserify.org/) is an easy to use tool for bundling this runtime with your project, though you can use any such tool. - -## License - -MIT diff --git a/js-runtime/index.js b/js-runtime/index.js deleted file mode 100644 index ca3fcea32..000000000 --- a/js-runtime/index.js +++ /dev/null @@ -1,1347 +0,0 @@ -require('cross-fetch/polyfill') -const EventEmitter = require('events') -const http = require('http') -const net = require('net') -const util = require('util') - -const xxh = require('xxhashjs') - -const exec = util.promisify ? util.promisify(require('child_process').exec) : () => {} // browsers - -const e = new EventEmitter() - -const INT8MAX = 2 ** 7 - 1 -const INT8MIN = -(2 ** 7) -const INT16MAX = 2 ** 15 - 1 -const INT16MIN = -(2 ** 15) -const INT32MAX = 2 ** 31 - 1 -const INT32MIN = -(2 ** 31) -const INT64MAX = 2n ** 63n - 1n -const INT64MIN = -(2n ** 63n) - -// JS really doesn't have BigInt equivalents of these? -const BigMin = (a, b) => a < b ? a : b -const BigMax = (a, b) => a > b ? a : b -const BigAbs = a => a > 0n ? a : -a // A six pack? - -// Hashing opcodes (hashv is recursive, needs to be defined outside of the export object) -const hashcore = (hasher, a) => { - // TODO: We have to turn these values into ArrayBuffers of the right type. There's currently an - // issue if a floating point number that is also an integer is provided -- the duck typing here - // will treat it as an i64 instead of an f64 so the hash will be different between the JS and - // Rust implementations. There are a few ways to solve this, but they all have tradeoffs. Will - // revisit this in the future. - let buffer = new ArrayBuffer(8) - if (typeof a === 'number') { - if (a === parseInt(a)) { - const view = new BigInt64Array(buffer) - view.set([BigInt(a)], 0) - } else { - const view = new Float64Array(buffer) - view.set([a], 0) - } - } else if (typeof a === 'bigint') { - const view = new BigInt64Array(buffer) - view.set([a], 0) - } else if (typeof a === 'string') { - // If it's a string, we treat it like an array of 64-bit integers with a prefixed 64-bit length - // to match the behavior of the Rust runtime - const len = a.length - const len8 = Math.ceil(len / 8) * 8 - buffer = new ArrayBuffer(8 + len8) - const lenview = new BigInt64Array(buffer) - lenview.set([BigInt(len)], 0) - const strview = new Int8Array(buffer) - // The following only works in the ASCII subset for now, since JS chose to use utf16 instead of - // utf8. TODO: Find a pure Javascript library that converts utf16 codepoints to utf8, or write - // one. :/ - strview.set(a.split('').map(s => s.charCodeAt(0)), 8) - } else { - // Booleans are treated as if they are 64-bit integers - const val = a ? BigInt(1) : BigInt(0) - const view = new BigInt64Array(buffer) - view.set([val], 0) - } - for (let i = 0; i < buffer.byteLength; i += 8) { - const piece = buffer.slice(i, i + 8) - hasher.update(piece) - } - return hasher -} -const hashf = a => BigInt.asIntN(64, hashcore(xxh.h64().init(0xfa57), a).digest()) -const hashv = arr => { - // The Rust runtime considers strings a variable type, but they are more like a fixed type for JS - if (typeof arr === 'string') return hashf(arr) - const hasher = xxh.h64().init(0xfa57) - let stack = [arr] - while (stack.length > 0) { - let arr = stack.pop() - for (const elem of arr) { - if (elem instanceof Array) { - stack.push(elem) - } else { - hashcore(hasher, elem) - } - } - } - return BigInt.asIntN(64, hasher.digest()) -} - -const copyarr = a => { - try { - return JSON.parse(JSON.stringify(a)) - } catch (e) { - if (typeof a[0] === 'bigint') { - return a.map(v => BigInt(v)) - } else { - return a.map(v => copyarr(v)) - } - } -} - -// Not very OOP, but since the HTTP server is a singleton right now, store open connections here -const httpConns = {} - -// Same justification for the TCP server -const tcpConns = {} - -// The shared mutable state for the datastore library -const ds = {} - -module.exports = { - // Type conversion opcodes (mostly no-ops in JS, unless we implement a strict mode) - i8f64: a => a, - i16f64: a => a, - i32f64: a => a, - i64f64: a => parseFloat(a.toString()), - f32f64: a => a, - strf64: a => parseFloat(a), - boolf64: a => a ? 1.0 : 0.0, - - i8f32: a => a, - i16f32: a => a, - i32f32: a => a, - i64f32: a => parseFloat(a.toString()), - f64f32: a => a, - strf32: a => parseFloat(a), - boolf32: a => a ? 1.0 : 0.0, - - i8i64: a => BigInt(a), - i16i64: a => BigInt(a), - i32i64: a => BigInt(a), - f32i64: a => BigInt(Math.floor(a)), - f64i64: a => BigInt(Math.floor(a)), - stri64: a => BigInt(parseInt(a)), // intentionally allowing other bases here - booli64: a => a ? 1n : 0n, - - i8i32: a => a, - i16i32: a => a, - i64i32: a => Number(BigInt.asIntN(32, a)), - f32i32: a => Math.floor(a), - f64i32: a => Math.floor(a), - stri32: a => parseInt(a), - booli32: a => a ? 1 : 0, - - i8i16: a => a, - i32i16: a => a, - i64i16: a => Number(BigInt.asIntN(16, a)), - f32i16: a => Math.floor(a), - f64i16: a => Math.floor(a), - stri16: a => parseInt(a), - booli16: a => a ? 1 : 0, - - i16i8: a => a, - i32i8: a => a, - i64i8: a => Number(BigInt.asIntN(8, a)), - f32i8: a => Math.floor(a), - f64i8: a => Math.floor(a), - stri8: a => parseInt(a), - booli8: a => a ? 1 : 0, - - i8bool: a => a !== 0, - i16bool: a => a !== 0, - i32bool: a => a !== 0, - i64bool: a => a !== 0n, - f32bool: a => a !== 0.0, - f64bool: a => a !== 0.0, - strbool: a => a === "true", - - i8str: a => a.toString(), - i16str: a => a.toString(), - i32str: a => a.toString(), - i64str: a => a.toString(), - f32str: a => a.toString(), - f64str: a => a.toString(), - boolstr: a => a.toString(), - - // Arithmetic opcodes - addi8: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - if (a > 0 && b > 0 && a > INT8MAX - b) return [false, 'overflow'] - if (a < 0 && b < 0 && a < INT8MIN - b) return [false, 'underflow'] - return [true, a + b] - }, - addi16: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - if (a > 0 && b > 0 && a > INT16MAX - b) return [false, 'overflow'] - if (a < 0 && b < 0 && a < INT16MIN - b) return [false, 'underflow'] - return [true, a + b] - }, - addi32: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - if (a > 0 && b > 0 && a > INT32MAX - b) return [false, 'overflow'] - if (a < 0 && b < 0 && a < INT32MIN - b) return [false, 'underflow'] - return [true, a + b] - }, - addi64: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - if (a > 0n && b > 0n && a > INT64MAX - b) return [false, 'overflow'] - if (a < 0n && b < 0n && a < INT64MIN - b) return [false, 'underflow'] - return [true, a + b] - }, - addf32: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - const out = a + b - if (out === Number.POSITIVE_INFINITY) return [false, 'overflow'] - if (out === Number.NEGATIVE_INFINITY) return [false, 'underflow'] - return [true, out] - }, - addf64: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - const out = a + b - if (out === Number.POSITIVE_INFINITY) return [false, 'overflow'] - if (out === Number.NEGATIVE_INFINITY) return [false, 'underflow'] - return [true, out] - }, - - subi8: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - if (a > 0 && b < 0 && a > INT8MAX + b) return [false, 'overflow'] - if (a < 0 && b > 0 && a < INT8MIN + b) return [false, 'underflow'] - return [true, a - b] - }, - subi16: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - if (a > 0 && b < 0 && a > INT16MAX + b) return [false, 'overflow'] - if (a < 0 && b > 0 && a < INT16MIN + b) return [false, 'underflow'] - return [true, a - b] - }, - subi32: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - if (a > 0 && b < 0 && a > INT32MAX + b) return [false, 'overflow'] - if (a < 0 && b > 0 && a < INT32MIN + b) return [false, 'underflow'] - return [true, a - b] - }, - subi64: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - if (a > 0n && b < 0n && a > INT64MAX + b) return [false, 'overflow'] - if (a < 0n && b > 0n && a < INT64MIN + b) return [false, 'underflow'] - return [true, a - b] - }, - subf32: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - const out = a - b - if (out === Number.POSITIVE_INFINITY) return [false, 'overflow'] - if (out === Number.NEGATIVE_INFINITY) return [false, 'underflow'] - return [true, out] - }, - subf64: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - const out = a - b - if (out === Number.POSITIVE_INFINITY) return [false, 'overflow'] - if (out === Number.NEGATIVE_INFINITY) return [false, 'underflow'] - return [true, out] - }, - - negi8: ra => !ra[0] ? ra : (0 - ra[1] <= INT8MAX ? [true, 0 - ra[1]] : [false, 'overflow']), - negi16: ra => !ra[0] ? ra : (0 - ra[1] <= INT16MAX ? [true, 0 - ra[1]] : [false, 'overflow']), - negi32: ra => !ra[0] ? ra : (0 - ra[1] <= INT32MAX ? [true, 0 - ra[1]] : [false, 'overflow']), - negi64: ra => !ra[0] ? ra : (0n - ra[1] <= INT64MAX ? [true, 0n - ra[1]] : [false, 'overflow']), - negf32: ra => !ra[0] ? ra : [true, 0.0 - a], - negf64: ra => !ra[0] ? ra : [true, 0.0 - a], - - absi8: ra => !ra[0] ? ra : (Math.abs(ra[1]) <= INT8MAX ? [true, Math.abs(ra[1])] : [false, 'overflow']), - absi16: ra => !ra[0] ? ra : (Math.abs(ra[1]) <= INT16MAX ? [true, Math.abs(ra[1])] : [false, 'overflow']), - absi32: ra => !ra[0] ? ra : (Math.abs(ra[1]) <= INT32MAX ? [true, Math.abs(ra[1])] : [false, 'overflow']), - absi64: ra => !ra[0] ? ra : (BigAbs(ra[1]) <= INT64MAX ? [true, BigAbs(ra[1])] : [false, 'overflow']), - absf32: ra => !ra[0] ? ra : [true, Math.abs(ra[1])], - absf64: ra => !ra[0] ? ra : [true, Math.abs(ra[1])], - - muli8: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - if (a > 0 && b > 0 && a > INT8MAX / b) return [false, 'overflow'] - if (a < 0 && b < 0 && a < INT8MIN / b) return [false, 'underflow'] - return [true, a * b] - }, - muli16: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - if (a > 0 && b > 0 && a > INT16MAX / b) return [false, 'overflow'] - if (a < 0 && b < 0 && a < INT16MIN / b) return [false, 'underflow'] - return [true, a * b] - }, - muli32: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - if (a > 0 && b > 0 && a > INT32MAX / b) return [false, 'overflow'] - if (a < 0 && b < 0 && a < INT32MIN / b) return [false, 'underflow'] - return [true, a * b] - }, - muli64: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - if (a > 0n && b > 0n && a > INT64MAX / b) return [false, 'overflow'] - if (a < 0n && b < 0n && a < INT64MIN / b) return [false, 'underflow'] - return [true, a * b] - }, - mulf32: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - const out = a * b - if (out === Number.POSITIVE_INFINITY) return [false, 'overflow'] - if (out === Number.NEGATIVE_INFINITY) return [false, 'underflow'] - return [true, out] - }, - mulf64: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - const out = a * b - if (out === Number.POSITIVE_INFINITY) return [false, 'overflow'] - if (out === Number.NEGATIVE_INFINITY) return [false, 'underflow'] - return [true, out] - }, - - divi8: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - if (b === 0) return [false, 'divide-by-zero'] - return [true, Math.floor(a / b)] - }, - divi16: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - if (b === 0) return [false, 'divide-by-zero'] - return [true, Math.floor(a / b)] - }, - divi32: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - if (b === 0) return [false, 'divide-by-zero'] - return [true, Math.floor(a / b)] - }, - divi64: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - if (b === 0n) return [false, 'divide-by-zero'] - return [true, a / b] - }, - divf32: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - if (b === 0.0) return [false, 'divide-by-zero'] - const out = a / b - if (out === Number.POSITIVE_INFINITY) return [false, 'overflow'] - if (out === Number.NEGATIVE_INFINITY) return [false, 'underflow'] - return [true, out] - }, - divf64: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - if (b === 0.0) return [false, 'divide-by-zero'] - const out = a / b - if (out === Number.POSITIVE_INFINITY) return [false, 'overflow'] - if (out === Number.NEGATIVE_INFINITY) return [false, 'underflow'] - return [true, out] - }, - - modi8: (a, b) => a % b, - modi16: (a, b) => a % b, - modi32: (a, b) => a % b, - modi64: (a, b) => a % b, - - powi8: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - if (a > 0 && b > 1 && a > INT8MAX ** (1 / b)) return [false, 'overflow'] - if (a < 0 && b > 1 && a < INT8MIN ** (1 / b)) return [false, 'underflow'] - return [true, Math.floor(a ** b)] - }, - powi16: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - if (a > 0 && b > 1 && a > INT16MAX ** (1 / b)) return [false, 'overflow'] - if (a < 0 && b > 1 && a < INT16MIN ** (1 / b)) return [false, 'underflow'] - return [true, Math.floor(a ** b)] - }, - powi32: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - if (a > 0 && b > 1 && a > INT32MAX ** (1 / b)) return [false, 'overflow'] - if (a < 0 && b > 1 && a < INT32MIN ** (1 / b)) return [false, 'underflow'] - return [true, Math.floor(a ** b)] - }, - powi64: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - if (a > 0 && b > 1n) { - const af = parseFloat(a.toString()) - const bf = parseFloat(b.toString()) - const maxf = parseFloat(INT64MAX.toString()) - if (af > maxf ** (1 / bf)) return [false, 'overflow'] - } - if (a < 0n && b > 1n) { - const af = parseFloat(a.toString()) - const bf = parseFloat(b.toString()) - const minf = parseFloat(INT64MIN.toString()) - if (af < minf ** (1 / bf)) return [false, 'underflow'] - } - return [true, a ** b] - }, - powf32: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - const out = a ** b - if (out === Number.POSITIVE_INFINITY) return [false, 'overflow'] - if (out === Number.NEGATIVE_INFINITY) return [false, 'underflow'] - return [true, out] - }, - powf64: (ra, rb) => { - if (!ra[0]) return ra - if (!rb[0]) return rb - const a = ra[1] - const b = rb[1] - const out = a ** b - if (out === Number.POSITIVE_INFINITY) return [false, 'overflow'] - if (out === Number.NEGATIVE_INFINITY) return [false, 'underflow'] - return [true, out] - }, - - sqrtf32: a => Math.sqrt(a), - sqrtf64: a => Math.sqrt(a), - - // Saturating arithmetic - saddi8: (a, b) => { - if (a > 0 && b > 0 && a > INT8MAX - b) return INT8MAX - if (a < 0 && b < 0 && a < INT8MIN - b) return INT8MIN - return a + b - }, - saddi16: (a, b) => { - if (a > 0 && b > 0 && a > INT16MAX - b) return INT16MAX - if (a < 0 && b < 0 && a < INT16MIN - b) return INT16MIN - return a + b - }, - saddi32: (a, b) => { - if (a > 0 && b > 0 && a > INT32MAX - b) return INT32MAX - if (a < 0 && b < 0 && a < INT32MIN - b) return INT32MIN - return a + b - }, - saddi64: (a, b) => { - if (a > 0n && b > 0n && a > INT64MAX - b) return INT64MAX - if (a < 0n && b < 0n && a < INT64MIN - b) return INT64MIN - return a + b - }, - saddf32: (a, b) => a + b, - saddf64: (a, b) => a + b, - - ssubi8: (a, b) => { - if (a > 0 && b < 0 && a > INT8MAX + b) return INT8MAX - if (a < 0 && b > 0 && a < INT8MIN + b) return INT8MIN - return a - b - }, - ssubi16: (a, b) => { - if (a > 0 && b < 0 && a > INT16MAX + b) return INT16MAX - if (a < 0 && b > 0 && a < INT16MIN + b) return INT16MIN - return a - b - }, - ssubi32: (a, b) => { - if (a > 0 && b < 0 && a > INT32MAX + b) return INT32MAX - if (a < 0 && b > 0 && a < INT32MIN + b) return INT32MIN - return a - b - }, - ssubi64: (a, b) => { - if (a > 0n && b < 0n && a > INT64MAX + b) return INT64MAX - if (a < 0n && b > 0n && a < INT64MIN + b) return INT64MIN - return a - b - }, - ssubf32: (a, b) => a - b, - ssubf64: (a, b) => a - b, - - snegi8: a => Math.min(0 - a, INT8MAX), - snegi16: a => Math.min(0 - a, INT16MAX), - snegi32: a => Math.min(0 - a, INT32MAX), - snegi64: a => BigMin(0n - a, INT64MAX), - snegf32: a => 0.0 - a, - snegf64: a => 0.0 - a, - - sabsi8: a => Math.min(Math.abs(a), INT8MAX), - sabsi16: a => Math.min(Math.abs(a), INT16MAX), - sabsi32: a => Math.min(Math.abs(a), INT32MAX), - sabsi64: a => BigMin(a > 0n ? a : -a, INT64MAX), - sabsf32: a => Math.abs(a), - sabsf64: a => Math.abs(a), - - smuli8: (a, b) => { - if (a > 0 && b > 0 && a > INT8MAX / b) return INT8MAX - if (a < 0 && b < 0 && a < INT8MIN / b) return INT8MIN - return a * b - }, - smuli16: (a, b) => { - if (a > 0 && b > 0 && a > INT16MAX / b) return INT16MAX - if (a < 0 && b < 0 && a < INT16MIN / b) return INT16MIN - return a * b - }, - smuli32: (a, b) => { - if (a > 0 && b > 0 && a > INT32MAX / b) return INT32MAX - if (a < 0 && b < 0 && a < INT32MIN / b) return INT32MIN - return a * b - }, - smuli64: (a, b) => { - if (a > 0n && b > 0n && a > INT64MAX / b) return INT64MAX - if (a < 0n && b < 0n && a < INT64MIN / b) return INT64MIN - return a * b - }, - smulf32: (a, b) => a * b, - smulf64: (a, b) => a * b, - - sdivi8: (a, b) => { - if (b === 0) return a > 0 ? INT8MAX : INT8MIN - return Math.floor(a / b) - }, - sdivi16: (a, b) => { - if (b === 0) return a > 0 ? INT16MAX : INT16MIN - return Math.floor(a / b) - }, - sdivi32: (a, b) => { - if (b === 0) return a > 0 ? INT32MAX : INT32MIN - return Math.floor(a / b) - }, - sdivi64: (a, b) => { - if (b === 0n) return a > 0n ? INT64MAX : INT64MIN - return a / b - }, - sdivf32: (a, b) => a / b, - sdivf64: (a, b) => a / b, - - spowi8: (a, b) => { - if (a > 0 && b > 1 && a > INT8MAX ** (1 / b)) return INT8MAX - if (a < 0 && b > 1 && a < INT8MIN ** (1 / b)) return INT8MIN - return Math.floor(a ** b) - }, - spowi16: (a, b) => { - if (a > 0 && b > 1 && a > INT16MAX ** (1 / b)) return INT16MAX - if (a < 0 && b > 1 && a < INT16MIN ** (1 / b)) return INT16MIN - return Math.floor(a ** b) - }, - spowi32: (a, b) => { - if (a > 0 && b > 1 && a > INT32MAX ** (1 / b)) return INT32MAX - if (a < 0 && b > 1 && a < INT32MIN ** (1 / b)) return INT32MIN - return Math.floor(a ** b) - }, - spowi64: (a, b) => { - if (a > 0 && b > 1n) { - const af = parseFloat(a.toString()) - const bf = parseFloat(b.toString()) - const maxf = parseFloat(INT64MAX.toString()) - if (af > maxf ** (1 / bf)) return INT64MAX - } - if (a < 0n && b > 1n) { - const af = parseFloat(a.toString()) - const bf = parseFloat(b.toString()) - const minf = parseFloat(INT64MIN.toString()) - if (af < minf ** (1 / bf)) return INT64MIN - } - return a ** b - }, - spowf32: (a, b) => a ** b, - spowf64: (a, b) => a ** b, - - // Boolean and bitwise opcodes - andi8: (a, b) => a & b, - andi16: (a, b) => a & b, - andi32: (a, b) => a & b, - andi64: (a, b) => a & b, - andbool: (a, b) => a && b, - - ori8: (a, b) => a | b, - ori16: (a, b) => a | b, - ori32: (a, b) => a | b, - ori64: (a, b) => a | b, - orbool: (a, b) => a || b, - - xori8: (a, b) => a ^ b, - xori16: (a, b) => a ^ b, - xori32: (a, b) => a ^ b, - xori64: (a, b) => a ^ b, - xorbool: (a, b) => !!(a ^ b), - - noti8: a => ~a, - noti16: a => ~a, - noti32: a => ~a, - noti64: a => ~a, - notbool: a => !a, - - nandi8: (a, b) => ~(a & b), - nandi16: (a, b) => ~(a & b), - nandi32: (a, b) => ~(a & b), - nandi64: (a, b) => ~(a & b), - nandboo: (a, b) => !(a && b), - - nori8: (a, b) => ~(a | b), - nori16: (a, b) => ~(a | b), - nori32: (a, b) => ~(a | b), - nori64: (a, b) => ~(a | b), - norbool: (a, b) => !(a || b), - - xnori8: (a, b) => ~(a ^ b), - xnori16: (a, b) => ~(a ^ b), - xnori32: (a, b) => ~(a ^ b), - xnori64: (a, b) => ~(a ^ b), - xnorboo: (a, b) => !(a ^ b), - - // Equality and order opcodes - eqi8: (a, b) => a === b, - eqi16: (a, b) => a === b, - eqi32: (a, b) => a === b, - eqi64: (a, b) => a === b, - eqf32: (a, b) => a === b, - eqf64: (a, b) => a === b, - eqstr: (a, b) => a === b, - eqbool: (a, b) => a === b, - - neqi8: (a, b) => a !== b, - neqi16: (a, b) => a !== b, - neqi32: (a, b) => a !== b, - neqi64: (a, b) => a !== b, - neqf32: (a, b) => a !== b, - neqf64: (a, b) => a !== b, - neqstr: (a, b) => a !== b, - neqbool: (a, b) => a !== b, - - lti8: (a, b) => a < b, - lti16: (a, b) => a < b, - lti32: (a, b) => a < b, - lti64: (a, b) => a < b, - ltf32: (a, b) => a < b, - ltf64: (a, b) => a < b, - ltstr: (a, b) => a < b, - - ltei8: (a, b) => a <= b, - ltei16: (a, b) => a <= b, - ltei32: (a, b) => a <= b, - ltei64: (a, b) => a <= b, - ltef32: (a, b) => a <= b, - ltef64: (a, b) => a <= b, - ltestr: (a, b) => a <= b, - - gti8: (a, b) => a > b, - gti16: (a, b) => a > b, - gti32: (a, b) => a > b, - gti64: (a, b) => a > b, - gtf32: (a, b) => a > b, - gtf64: (a, b) => a > b, - gtstr: (a, b) => a > b, - - gtei8: (a, b) => a >= b, - gtei16: (a, b) => a >= b, - gtei32: (a, b) => a >= b, - gtei64: (a, b) => a >= b, - gtef32: (a, b) => a >= b, - gtef64: (a, b) => a >= b, - gtestr: (a, b) => a >= b, - - // String opcodes - catstr: (a, b) => a.concat(b), - split: (a, b) => a.split(b), - repstr: (a, b) => new Array(parseInt(b.toString())).fill(a).join(''), - // TODO: templ, after maps are figured out - matches: (a, b) => RegExp(b).test(a), - indstr: (a, b) => { - const ind = BigInt(a.indexOf(b)) - return ind > -1 ? [ true, ind, ] : [ false, 'substring not found', ] - }, - lenstr: a => BigInt(a.length), - trim: a => a.trim(), - copyfrom:(arr, ind) => JSON.parse(JSON.stringify(arr[ind])), - copytof: (arr, ind, val) => { arr[ind] = val }, // These do the same thing in JS - copytov: (arr, ind, val) => { arr[ind] = val }, - register:(arr, ind) => arr[ind], // Only on references to inner arrays - - // Array opcodes TODO more to come - newarr: size => new Array(), // Ignored because JS push doesn't behave as desired - pusharr: (arr, val, size) => arr.push(val), - pushf: (arr, val) => arr.push(val), - pushv: (arr, val) => arr.push(val), - poparr: arr => arr.length > 0 ? [ true, arr.pop(), ] : [ false, 'cannot pop empty array', ], - lenarr: arr => BigInt(arr.length), - indarrf: (arr, val) => { - const ind = BigInt(arr.indexOf(val)) - return ind > -1 ? [ true, ind, ] : [ false, 'element not found', ] - }, - indarrv: (arr, val) => { - const ind = BigInt(arr.indexOf(val)) - return ind > -1 ? [ true, ind, ] : [ false, 'element not found', ] - }, - delindx: (arr, idx) => { - const spliced = arr.splice(parseInt(idx.toString()), 1) - if (spliced.length === 1 && parseInt(idx.toString()) >= 0) { - return [ true, spliced[0] ] - } else { - return [ false, `cannot remove idx ${idx} from array with length ${arr.length}` ] - } - }, - join: (arr, sep) => arr.join(sep), - map: async (arr, fn) => await Promise.all(arr.map((v, i) => fn(v, BigInt(i)))), - mapl: async (arr, fn) => await Promise.all(arr.map((v, i) => fn(v, BigInt(i)))), - reparr: (arr, n) => Array.from(new Array(parseInt(n.toString()) * arr.length)) - .map((_, i) => typeof arr[i % arr.length] === 'bigint' ? - BigInt(arr[i % arr.length]) : - JSON.parse(JSON.stringify(arr[i % arr.length])) - ), - each: async (arr, fn) => { // Thrown away but awaited to maintain consistent execution - await Promise.all(arr.map((v, i) => fn(v, BigInt(i)))) - }, - eachl: async (arr, fn) => { // Thrown away but awaited to maintain consistent execution - await Promise.all(arr.map((v, i) => fn(v, BigInt(i)))) - }, - find: async (arr, fn) => { - let val = undefined - const len = arr.length - for (let i = 0; i < len && val === undefined; i++) { - if (await fn(arr[i])) { - val = arr[i] - } - } - if (val === undefined) { - return [ - false, - 'no element matches', - ] - } else { - return [ - true, - val, - ] - } - }, - findl: async (arr, fn) => { - let val = undefined - const len = arr.length - for (let i = 0; i < len && val === undefined; i++) { - if (await fn(arr[i])) { - val = arr[i] - } - } - if (val === undefined) { - return [ - false, - 'no element matches', - ] - } else { - return [ - true, - val, - ] - } - }, - every: async (arr, fn) => { - const len = arr.length - for (let i = 0; i < len; i++) { - if (!await fn(arr[i])) return false - } - return true - }, - everyl: async (arr, fn) => { - const len = arr.length - for (let i = 0; i < len; i++) { - if (!await fn(arr[i])) return false - } - return true - }, - some: async (arr, fn) => { - const len = arr.length - for (let i = 0; i < len; i++) { - if (await fn(arr[i])) return true - } - return false - }, - somel: async (arr, fn) => { - const len = arr.length - for (let i = 0; i < len; i++) { - if (await fn(arr[i])) return true - } - return false - }, - filter: async (arr, fn) => { - let out = [] - let len = arr.length - for (let i = 0; i < len; i++) { - if (await fn(arr[i])) out.push(arr[i]) - } - return out - }, - filterl: async (arr, fn) => { - let out = [] - let len = arr.length - for (let i = 0; i < len; i++) { - if (await fn(arr[i])) out.push(arr[i]) - } - return out - }, - reducel: async (arr, fn) => { - let cumu = arr[0] - let len = arr.length - for (let i = 1; i < len; i++) { - cumu = await fn(cumu, arr[i]) - } - return cumu - }, - reducep: async (arr, fn) => { - let cumu = arr[0] - let len = arr.length - for (let i = 1; i < len; i++) { - cumu = await fn(cumu, arr[i]) - } - return cumu - }, - foldl: async (obj, fn) => { - const [arr, init] = obj - let cumu = init - let len = arr.length - for (let i = 0; i < len; i++) { - cumu = await fn(cumu, arr[i]) - } - return cumu - }, - foldp: async (obj, fn) => { - const [arr, init] = obj - let cumu = init - let len = arr.length - for (let i = 0; i < len; i++) { - cumu = await fn(cumu, arr[i]) - } - return [cumu] // This path is expected to return an array of folded values per thread - }, - catarr: (a, b) => [...a, ...b], - - // Map opcodes TODO after maps are figured out - - // Ternary functions - // TODO: pair and condarr after arrays are figured out - condfn: async (cond, fn) => cond ? await fn() : undefined, - - // Copy opcodes (for let reassignment) - copyi8: a => JSON.parse(JSON.stringify(a)), - copyi16: a => JSON.parse(JSON.stringify(a)), - copyi32: a => JSON.parse(JSON.stringify(a)), - copyi64: a => BigInt(a), - copyvoid: a => JSON.parse(JSON.stringify(a)), - copyf32: a => JSON.parse(JSON.stringify(a)), - copyf64: a => JSON.parse(JSON.stringify(a)), - copybool: a => JSON.parse(JSON.stringify(a)), - copystr: a => JSON.parse(JSON.stringify(a)), - // Actually the recommended deep clone mechanism: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Deep_Clone - // Doesn't work with BigInt :( - // copyarr: a => JSON.parse(JSON.stringify(a)), - // Implementation is now recursive with a try-catch wrapper, so not great for perf - copyarr, - zeroed: () => null, - - // Trig opcodes - lnf64: a => Math.log(a), - logf64: a => Math.log(a) / Math.log(10), - sinf64: a => Math.sin(a), - cosf64: a => Math.cos(a), - tanf64: a => Math.tan(a), - asinf64: a => Math.asin(a), - acosf64: a => Math.acos(a), - atanf64: a => Math.atan(a), - sinhf64: a => Math.sinh(a), - coshf64: a => Math.cosh(a), - tanhf64: a => Math.tanh(a), - - // Error, Maybe, Result, Either opcodes - error: a => a, - reff: a => a, // Just an alias for error but without the type mangling in the compiler - refv: a => a, // Just an alias for error but without the type mangling in the compiler - noerr: () => '', - errorstr: a => a.toString(), - someM: a => [ - true, - a, - ], - noneM: () => [ - false, - ], - isSome: a => a[0], - isNone: a => !a[0], - getOrM: (a, b) => a[0] ? a[1] : b, - getMaybe: a => { - if (a[0]) { - return a[1] - } else { - throw new Error('runtime error: illegal access') - } - }, - okR: a => [ - true, - a, - ], - err: a => [ - false, - a, - ], - isOk: a => a[0], - isErr: a => !a[0], - getOrR: (a, b) => a[0] ? a[1] : b, - getOrRS: (a, b) => a[0] ? a[1] : b, - getR: (a) => { - if (a[0]) { - return a[1] - } else { - throw new Error('runtime error: illegal access') - } - }, - getErr: (a, b) => a[0] ? b : a[1], - resfrom: (arr, rind) => { - if (!rind[0]) return rind - const ind = rind[1] - if (ind >= 0 && ind < arr.length) { - return [ - true, - arr[ind], - ] - } else { - return [ - false, - 'out-of-bounds access', - ] - } - }, - mainE: a => [ - true, - a, - ], - altE: a => [ - false, - a, - ], - isMain: a => a[0], - isAlt: a => !a[0], - mainOr: (a, b) => a[0] ? a[1] : b, - altOr: (a, b) => a[0] ? b : a[1], - getMain: a => { - if (a[0]) { - return a[1] - } else { - throw new Error('runtime error: illegal access') - } - }, - getAlt: a => { - if (!a[0]) { - return a[1] - } else { - throw new Error('runtime error: illegal access') - } - }, - - // Hashing opcodes (hashv is recursive, needs to be defined elsewhere) - hashf, - hashv, - - // In Node.js the datastore opcodes don't have to be IO opcodes, but in the Rust runtime they do, - // because of the multithreaded nature of the Rust runtime. Not sure if they should be "fake" - // async here or not. - dssetf: (ns, key, val) => { - ds[`${ns}:${key}`] = val - }, - dssetv: (ns, key, val) => { - ds[`${ns}:${key}`] = val - }, - dshas: (ns, key) => ds.hasOwnProperty(`${ns}:${key}`), - dsdel: (ns, key) => { - const fullKey = `${ns}:${key}` - const toDelete = ds.hasOwnProperty(fullKey) - if (toDelete) delete ds[fullKey] - return toDelete - }, - dsgetf: (ns, key) => { - const fullKey = `${ns}:${key}` - if (ds.hasOwnProperty(fullKey)) { - return [ true, ds[`${ns}:${key}`], ] - } else { - return [ false, 'namespace-key pair not found', ] - } - }, - dsgetv: (ns, key) => { - const fullKey = `${ns}:${key}` - if (ds.hasOwnProperty(fullKey)) { - return [ true, ds[`${ns}:${key}`], ] - } else { - return [ false, 'namespace-key pair not found', ] - } - }, - dsrrun: async (nskey, func) => { - const val = ds[`${nskey[0]}:${nskey[1]}`]; - return [true, await func(val)]; - }, - dsmrun: async (nskey, func) => { - let val = ds[`${nskey[0]}:${nskey[1]}`]; - const [out, newval] = await func(val); - ds[`${nskey[0]}:${nskey[1]}`] = newval; - return [true, out]; - }, - dsrwith: async (wth, func) => { - const nskey = wth[0]; - const a = wth[1]; - const b = ds[`${nskey[0]}:${nskey[1]}`]; - return [true, await func(b, a)]; - }, - dsmwith: async (wth, func) => { - const nskey = wth[0]; - const a = wth[1]; - let b = ds[`${nskey[0]}:${nskey[1]}`]; - // Get out, newb! - const [out, newb] = await func(b, a); - ds[`${nskey[0]}:${nskey[1]}`] = newb; - return [true, out]; - }, - dsmonly: async (nskey, func) => { - let val = ds[`${nskey[0]}:${nskey[1]}`]; - const newval = await func(val); - ds[`${nskey[0]}:${nskey[1]}`] = newval; - }, - dswonly: async (wth, func) => { - const nskey = wth[0]; - const a = wth[1]; - const b = ds[`${nskey[0]}:${nskey[1]}`]; - const newb = await func(b, a); - ds[`${nskey[0]}:${nskey[1]}`] = newb; - }, - dsrclos: async (nskey, func) => { - const val = ds[`${nskey[0]}:${nskey[1]}`]; - return [true, await func(val)]; - }, - dsmclos: async (nskey, func) => { - let val = ds[`${nskey[0]}:${nskey[1]}`]; - const [out, newval] = await func(val); - ds[`${nskey[0]}:${nskey[1]}`] = newval; - return [true, out]; - }, - getcs: () => [false], - newseq: (limit) => [0n, limit], - seqnext: (seq) => { - if (seq[0] < seq[1]) { - const out = [true, seq[0]] - seq[0]++ - return out - } else { - return [false, 'error: sequence out-of-bounds'] - } - }, - seqeach: async (seq, func) => { - while (seq[0] < seq[1]) { - await func(seq[0]) - seq[0]++ - } - }, - seqwhile:async (seq, condFn, bodyFn) => { - while (seq[0] < seq[1] && await condFn()) { - await bodyFn() - seq[0]++ - } - }, - seqdo: async (seq, bodyFn) => { - let ok = true - do { - ok = await bodyFn() - seq[0]++ - } while (seq[0] < seq[1] && ok) - }, - selfrec: async (self, arg) => { - const [seq, recurseFn] = self - if (seq[0] < seq[1]) { - seq[0]++ - return recurseFn(self, arg) - } else { - return [false, 'error: sequence out-of-bounds'] - } - }, - seqrec: (seq, recurseFn) => [seq, recurseFn], - - // IO opcodes - httpreq: async req => { - const [ method, url, headers, body, ] = req - try { - const response = await fetch(url, { - method, - headers, - body: body.length > 0 ? body : undefined, - }); - const rstatus = response.status - const rheaders = [...response.headers.entries()].map(kv => [kv[0] + '', kv[1] + '']) - const rbody = await response.text() - return [ true, [ rstatus, rheaders, rbody, 0n ] ] - } catch (e) { - return [ false, e.toString() ] - } - }, - httplsn: async () => { - const server = http.createServer((req, res) => { - const connId = Number(hashf(Math.random().toString())) - httpConns[connId] = { - req, - res, - } - let body = '' - req.on('data', d => { - body += d - }) - req.on('end', () => { - e.emit('__conn', [ - req.method, - req.url, - Object.entries(req.headers), - body, - connId, - ]) - }) - }) - const listenResult = await new Promise(resolve => { - server.on('error', e => resolve(e)) - server.listen({ - port: 8000, - }, () => resolve(true)) - }) - if (listenResult === true) { - console.log("HTTP server listening on port 8000") - } else { - console.error(`HTTP server failed to listen to port 8000: ${listenResult}`) - } - }, - httpsend: async (ires) => { - const [ status, headers, body, connId, ] = ires - const conn = httpConns[connId] - if (!conn) return [ false, 'connection not found', ] - delete httpConns[connId] - return new Promise(resolve => { - conn.res.on('close', () => resolve([ false, 'client hangup', ])) - conn.res - .writeHead(Number(status), headers.reduce((acc, kv) => { - acc[kv[0]] = kv[1] - return acc - }, {})) - .end(body, () => resolve([ true, 'ok', ])) - }) - }, - waitop: async (a) => await new Promise(resolve => setTimeout(resolve, Number(a))), - syncop: async (f, a) => await f(a), - execop: async (cmd) => { - try { - const res = await exec(cmd) - const { stdout, stderr } = res - return [ 0n, stdout, stderr ] - } catch (e) { - return [ BigInt(e.code), e.stdout, e.stderr ] - } - }, - tcplsn: async () => { - const server = net.createServer((c) => { - // To allow the `tcpConn` event to set up anything it needs - c.pause() - const connId = Number(hashf(Math.random().toString())) - const context = [connId, undefined, { - c, - dataArr: [], - state: 'paused', - }] - tcpConns[connId] = context - c.on('data', (buf) => { - context[2].dataArr.push(buf) - e.emit('chunk', context) - }) - c.on('error', () => context[2].state = 'error') - c.on('timeout', () => { - context[2].state = 'timeout' - c.end() // Node.js doesn't automatically do this on timeout for some reason? - }) - c.on('close', () => { - if (context[2].state === 'open') context[2].state = 'closed' - e.emit('tcpClose', context) - delete tcpConns[connId] - }) - e.emit('tcpConn', connId) - }) - const listenResult = await new Promise(resolve => { - server.on('error', e => resolve(e)) - server.listen({ - port: 8000, - }, () => resolve(true)) - }) - if (listenResult === true) { - console.log("TCP server listening on port 8000") - } else { - console.error(`TCP server failed to listen on port 8000: ${listenResult}`) - } - }, - tcptun: async (port) => { - const server = net.createServer((c) => { - // To set up the connection to the server to forward this data to. - c.pause(); - const t = net.createConnection({ port, }, () => { - c.resume(); - }); - c.on('data', (data) => t.write(data)); - t.on('data', (data) => c.write(data)); - c.on('end', () => t.end()); - t.on('end', () => c.end()); - }); - return await new Promise((resolve) => { - server.on('error', e => resolve(e)); - server.listen({ - port: 8000, - }, () => resolve(true)); - }); - }, - tcpconn: async (host, port) => { - return new Promise(resolve => { - const c = net.createConnection(port, host, () => { - const connId = Number(hashf(Math.random().toString())) - const context = [connId, undefined, { - connId, - c, - dataArr: [], - state: 'paused', - }] - tcpConns[connId] = context - c.on('data', (buf) => { - context[2].dataArr.push(buf) - e.emit('chunk', context) - }) - c.on('error', () => context[2].state = 'error') - c.on('timeout', () => { - context[2].state = 'timeout' - c.end() // Node.js doesn't automatically do this on timeout for some reason - }) - c.on('close', () => { - if (context[2].state === 'open') context[2].state = 'closed' - e.emit('tcpClose', context) - delete tcpConns[connId] - }) - resolve(connId) - }) - // To allow the setup of everything needed - c.pause() - }) - }, - tcpAddC: (connId, context) => { - tcpConns[connId][1] = context - return connId - }, - tcpReady: (connId) => { - const channel = tcpConns[connId] - if (!channel) return connId - channel[2].state = 'open' - channel[2].c.resume() - return connId - }, - tcpRead: (connId) => { - const channel = tcpConns[connId] - if (!channel) return new Buffer() - const chunk = channel[2].dataArr.shift() - return chunk - }, - tcpWrite: (connId, chunk) => { - const channel = tcpConns[connId] - if (!channel) return connId - channel[2].c.write(chunk) - return connId - }, - tcpTerm: (connId) => { - const channel = tcpConns[connId] - if (!channel) return undefined - channel[2].c.end() - }, - - // "Special" opcodes - stdoutp: out => process.stdout.write(out), - stderrp: err => process.stderr.write(err), - exitop: code => process.exit(parseInt(code.toString())), - - // Event bookkeeping - emit: (name, payload) => e.emit(name, payload), - on: (name, cb) => e.on(name, cb), - emitter: e, -} - -module.exports.asyncopcodes = Object.keys(module.exports).filter(k => module.exports[k].constructor.name === 'AsyncFunction') diff --git a/js-runtime/package.json b/js-runtime/package.json deleted file mode 100644 index c3c0f0d58..000000000 --- a/js-runtime/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "alan-js-runtime", - "version": "0.1.45-beta3", - "description": "The runtime component for alan-js.", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [ - "alan", - "alan-js", - "runtime", - "transpiler" - ], - "author": "David Ellis ", - "license": "MIT", - "dependencies": { - "cross-fetch": "^3.0.6", - "xxhashjs": "^0.2.2" - } -} diff --git a/js-runtime/yarn.lock b/js-runtime/yarn.lock deleted file mode 100644 index 9caac28e9..000000000 --- a/js-runtime/yarn.lock +++ /dev/null @@ -1,47 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -cross-fetch@^3.0.6: - version "3.1.5" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" - integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== - dependencies: - node-fetch "2.6.7" - -cuint@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b" - integrity sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs= - -node-fetch@2.6.7: - version "2.6.7" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" - integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== - dependencies: - whatwg-url "^5.0.0" - -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - -xxhashjs@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/xxhashjs/-/xxhashjs-0.2.2.tgz#8a6251567621a1c46a5ae204da0249c7f8caa9d8" - integrity sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw== - dependencies: - cuint "^0.2.2" diff --git a/src/compile.rs b/src/compile.rs new file mode 100644 index 000000000..33bb22ca9 --- /dev/null +++ b/src/compile.rs @@ -0,0 +1,4111 @@ +// TODO: Figure out how to integrate `rustc` into the `alan` binary. +use std::fs::{remove_file, write}; +use std::path::PathBuf; +use std::process::{Command, Stdio}; + +use crate::lntors::lntors; + +/// The `compile` function is a very thin wrapper on top of `lntors`, just handling the file +/// loading and temporary file storage and removal on the path to generating the binary. +pub fn compile(source_file: String) -> Result<(), Box> { + // Fail if rustc is not present + Command::new("which").arg("rustc").output()?; + // Generate the rust code to compile + let rs_str = lntors(source_file.clone())?; + // Shove it into a temp file for rustc + let tmp_file = match PathBuf::from(source_file).file_stem() { + Some(pb) => format!("{}.rs", pb.to_string_lossy().to_string()), + None => { + return Err("Invalid path".into()); + } + }; + write(&tmp_file, rs_str)?; + // Build the executable + Command::new("rustc") + .arg(&tmp_file) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output()?; + // Drop the temp file + remove_file(tmp_file)?; + Ok(()) +} + +/// The majority of this file is dedicated to a comprehensive test suite, converted from the prior +/// test suite using macros to make it a bit more dense than it would have been otherwise. +/// The macro here is composed of three parts: The test program source, the expected exit code +/// (usually 0 so it is optional), and the expected stdout or stderr text (also optional). +/// The syntax to use it is something like: +/// test!(hello_world => r#" +/// on start { +/// print("Hello, World!"); +/// } +/// "#; +/// stdout "Hello, World!\n"; +/// status 0; +/// ); +macro_rules! test { + ( $rule: ident => $code:expr; $( $type:ident $test_val:expr);+ $(;)? ) => { + #[cfg(test)] + mod $rule { + #[test] + fn $rule() -> Result<(), Box> { + let filename = format!("{}.ln", stringify!($rule)); + super::write(&filename, $code)?; + assert_eq!((), super::compile(filename.to_string())?); + let run = super::Command::new(format!("./{}", stringify!($rule))).output()?; + $( $type!($test_val, &run); )+ + // Cleanup the temp files. TODO: Make this happen regardless of test failure? + super::remove_file(&filename)?; + super::remove_file(stringify!($rule))?; + Ok(()) + } + } + } +} +#[cfg(test)] +macro_rules! stdout { + ( $test_val:expr, $real_val:expr ) => { + let std_out = String::from_utf8($real_val.stdout.clone())?; + assert_eq!($test_val, &std_out); + } +} +#[cfg(test)] +macro_rules! stderr { + ( $test_val:expr, $real_val:expr ) => { + let std_err = String::from_utf8($real_val.stderr.clone())?; + assert_eq!($test_val, &std_err); + } +} +#[cfg(test)] +macro_rules! status { + ( $test_val:expr, $real_val:expr ) => { + let status = $real_val.status.code().unwrap(); + assert_eq!($test_val, status); + } +} + +// The only test that works for now +test!(hello_world => r#" + on start { + print("Hello, World!"); + }"#; + stdout "Hello, World!\n"; + status 0; +); + +// Event Tests + +test!(normal_exit_code => r#" + from @std/app import start, exit + + on start { emit exit 0; }"#; + status 0; +); +test!(error_exit_code => r#" + from @std/app import start, exit + + on start { emit exit 1; }"#; + status 1; +); +test!(non_global_memory_exit_code => r#" + import @std/app + + on app.start { + let x: int64 = 0; + emit app.exit x; + }"#; + status 0; +); +test!(passing_ints_from_global_memory => r#" + from @std/app import start, print, exit + + event aNumber: int64; + + on aNumber fn(num: int64) { + print('I got a number! ' + num.toString()); + emit exit 0; + } + + on start { + emit aNumber 5; + }"#; + stdout "I got a number! 5\n"; + status 0; +); + +// Printing Tests + +// This one will replace the hello_world test above once the syntax is updated +test!(print_function => r#" + from @std/app import start, print, exit + on start { + print('Hello, World'); + emit exit 0; + }"#; + stdout "Hello, World\n"; +); +test!(stdout_event => r#" + from @std/app import start, stdout, exit + on start { + emit stdout 'Hello, World'; + wait(10); + emit exit 0; + }"#; + stdout "Hello, World"; +); + +// Basic Math Tests + +test!(int8_add => r#" + from @std/app import start, exit + on start { emit exit add(toInt8(1), toInt8(2)).getOrExit(); }"#; + status 3; +); +test!(int8_sub => r#" + from @std/app import start, exit + on start { emit exit sub(toInt8(2), toInt8(1)).getOrExit(); }"#; + status 1; +); +test!(int8_mul => r#" + from @std/app import start, exit + on start { emit exit mul(toInt8(2), toInt8(1)).getOrExit(); }"#; + status 2; +); +test!(int8_div => r#" + from @std/app import start, exit + on start { emit exit div(toInt8(6), toInt8(2)).getOrExit(); }"#; + status 3; +); +test!(int8_mod => r#" + from @std/app import start, exit + on start { emit exit mod(toInt8(6), toInt8(4)); }"#; + status 2; +); +test!(int8_pow => r#" + from @std/app import start, exit + on start { emit exit pow(toInt8(6), toInt8(2)).getOrExit(); }"#; + status 36; +); +test!(int8_min => r#" + from @std/app import start, print, exit + on start { + min(3.toInt8(), 5.toInt8()).print(); + emit exit 0; + }"#; + status 3; +); +test!(int8_max => r#" + from @std/app import start, print, exit + on start { + max(3.toInt8(), 5.toInt8()).print(); + emit exit 0; + }"#; + status 5; +); + +test!(int16_add => r#" + from @std/app import start, print, exit + on start { + print(add(toInt16(1), toInt16(2))); + emit exit 0; + }"#; + stdout "3\n"; +); +test!(int16_sub => r#" + from @std/app import start, print, exit + on start { + print(sub(toInt16(2), toInt16(1))); + emit exit 0; + }"#; + stdout "1\n"; +); +test!(int16_mul => r#" + from @std/app import start, print, exit + on start { + print(mul(toInt16(2), toInt16(1))); + emit exit 0; + }"#; + stdout "2\n"; +); +test!(int16_div => r#" + from @std/app import start, print, exit + on start { + print(div(toInt16(6), toInt16(2))); + emit exit 0; + }"#; + stdout "3\n"; +); +test!(int16_mod => r#" + from @std/app import start, print, exit + on start { + print(mod(toInt16(6), toInt16(4))); + emit exit 0; + }"#; + stdout "2\n"; +); +test!(int16_pow => r#" + from @std/app import start, print, exit + on start { + print(pow(toInt16(6), toInt16(2))); + emit exit 0; + }"#; + stdout "36\n"; +); +test!(int16_min => r#" + from @std/app import start, print, exit + on start { + min(3.toInt16(), 5.toInt16()).print(); + emit exit 0; + }"#; + stdout "3\n"; +); +test!(int16_max => r#" + from @std/app import start, print, exit + on start { + max(3.toInt16(), 5.toInt16()).print(); + emit exit 0; + }"#; + stdout "5\n"; +); + +test!(int32_add => r#" + from @std/app import start, print, exit + on start { + add(1.toInt32(), 2.toInt32()).print(); + emit exit 0; + }"#; + stdout "3\n"; +); +test!(int32_sub => r#" + from @std/app import start, print, exit + on start { + sub(2.toInt32(), 1.toInt32()).print(); + emit exit 0; + }"#; + stdout "1\n"; +); +test!(int32_mul => r#" + from @std/app import start, print, exit + on start { + mul(2.toInt32(), 1.toInt32()).print(); + emit exit 0; + }"#; + stdout "2\n"; +); +test!(int32_div => r#" + from @std/app import start, print, exit + on start { + div(6.toInt32(), 2.toInt32()).print(); + emit exit 0; + }"#; + stdout "3\n"; +); +test!(int32_mod => r#" + from @std/app import start, print, exit + on start { + mod(6.toInt32(), 4.toInt32()).print(); + emit exit 0; + }"#; + stdout "2\n"; +); +test!(int32_pow => r#" + from @std/app import start, print, exit + on start { + pow(6.toInt32(), 2.toInt32()).print(); + emit exit 0; + }"#; + stdout "36\n"; +); +test!(int32_min => r#" + from @std/app import start, print, exit + on start { + min(3.toInt32(), 5.toInt32()).print(); + emit exit 0; + }"#; + stdout "3\n"; +); +test!(int32_max => r#" + from @std/app import start, print, exit + on start { + max(3.toInt32(), 5.toInt32()).print(); + emit exit 0; + }"#; + stdout "5\n"; +); + +test!(int64_add => r#" + from @std/app import start, print, exit + on start { + print(1 + 2); + emit exit 0; + }"#; + stdout "3\n"; +); +test!(int64_sub => r#" + from @std/app import start, print, exit + on start { + print(2 - 1); + emit exit 0; + }"#; + stdout "1\n"; +); +test!(int64_mul => r#" + from @std/app import start, print, exit + on start { + print(2 * 1); + emit exit 0; + }"#; + stdout "2\n"; +); +test!(int64_div => r#" + from @std/app import start, print, exit + on start { + print(6 / 2); + emit exit 0; + }"#; + stdout "3\n"; +); +test!(int64_mod => r#" + from @std/app import start, print, exit + on start { + print(6 % 4); + emit exit 0; + }"#; + stdout "2\n"; +); +test!(int64_pow => r#" + from @std/app import start, print, exit + on start { + print(6 ** 2); + emit exit 0; + }"#; + stdout "36\n"; +); +test!(int64_min => r#" + from @std/app import start, print, exit + on start { + min(3, 5).print(); + emit exit 0; + }"#; + stdout "3\n"; +); +test!(int64_max => r#" + from @std/app import start, print, exit + on start { + max(3.toInt64(), 5.toInt64()).print(); + emit exit 0; + }"#; + stdout "5\n"; +); + +test!(float32_add => r#" + from @std/app import start, print, exit + on start { + print(toFloat32(1) + toFloat32(2)); + emit exit 0; + }"#; + stdout "3\n"; +); +test!(float32_sub => r#" + from @std/app import start, print, exit + on start { + print(toFloat32(2) - toFloat32(1)); + emit exit 0; + }"#; + stdout "1\n"; +); +test!(float32_mul => r#" + from @std/app import start, print, exit + on start { + print(toFloat32(2) * toFloat32(1)); + emit exit 0; + }"#; + stdout "2\n"; +); +test!(float32_div => r#" + from @std/app import start, print, exit + on start { + print(toFloat32(6) / toFloat32(2)); + emit exit 0; + }"#; + stdout "3\n"; +); +test!(float32_sqrt => r#" + from @std/app import start, print, exit + on start { + print(sqrt(toFloat32(36))); + emit exit 0; + }"#; + stdout "6\n"; +); +test!(float32_pow => r#" + from @std/app import start, print, exit + on start { + print(toFloat32(6) ** toFloat32(2)); + emit exit 0; + }"#; + stdout "36\n"; +); +test!(float32_min => r#" + from @std/app import start, print, exit + on start { + min(3.toFloat32(), 5.toFloat32()).print(); + emit exit 0; + }"#; + stdout "3\n"; +); +test!(float32_max => r#" + from @std/app import start, print, exit + on start { + max(3.toFloat32(), 5.toFloat32()).print(); + emit exit 0; + }"#; + stdout "5\n"; +); + +test!(float64_add => r#" + from @std/app import start, print, exit + on start { + (1.0 + 2.0).print(); + emit exit 0; + }"#; + stdout "3\n"; +); +test!(float64_sub => r#" + from @std/app import start, print, exit + on start { + (2.0 - 1.0).print(); + emit exit 0; + }"#; + stdout "1\n"; +); +test!(float64_mul => r#" + from @std/app import start, print, exit + on start { + (2.0 * 1.0).print(); + emit exit 0; + }"#; + stdout "2\n"; +); +test!(float64_div => r#" + from @std/app import start, print, exit + on start { + (6.0 / 2.0).print(); + emit exit 0; + }"#; + stdout "3\n"; +); +test!(float64_sqrt => r#" + from @std/app import start, print, exit + on start { + sqrt(36.0).print(); + emit exit 0; + }"#; + stdout "6\n"; +); +test!(float64_pow => r#" + from @std/app import start, print, exit + on start { + (6.0 ** 2.0).print(); + emit exit 0; + }"#; + stdout "36\n"; +); +test!(float64_min => r#" + from @std/app import start, print, exit + on start { + min(3.toFloat64(), 5.toFloat64()).print(); + emit exit 0; + }"#; + stdout "3\n"; +); +test!(float64_max => r#" + from @std/app import start, print, exit + on start { + max(3.toFloat64(), 5.toFloat64()).print(); + emit exit 0; + }"#; + stdout "5\n"; +); + +test!(grouping => r#" + from @std/app import start, print, exit + on start { + print(2 / (3)); + print(3 / (1 + 2)); + emit exit 0; + }"#; + stdout "0\n1\n"; +); + +test!(string_min => r#" + from @std/app import start, print, exit + on start { + min(3.toString(), 5.toString()).print(); + emit exit 0; + }"#; + stdout "3\n"; +); +test!(string_max => r#" + from @std/app import start, print, exit + on start { + max(3.toString(), 5.toString()).print(); + emit exit 0; + }"#; + stdout "5\n"; +); + +// Bitwise Math + +test!(int8_bitwise => r#" + from @std/app import start, print, exit + + prefix toInt8 as ~ precedence 10 + + on start { + print(~1 & ~2); + print(~1 | ~3); + print(~5 ^ ~3); + print(! ~0); + print(~1 !& ~2); + print(~1 !| ~2); + print(~5 !^ ~3); + emit exit 0; + }"#; + stdout "0\n3\n6\n-1\n-1\n-4\n-7\n"; +); +test!(int16_bitwise => r#" + from @std/app import start, print, exit + + prefix toInt16 as ~ precedence 10 + + on start { + print(~1 & ~2); + print(~1 | ~3); + print(~5 ^ ~3); + print(! ~0); + print(~1 !& ~2); + print(~1 !| ~2); + print(~5 !^ ~3); + emit exit 0; + }"#; + stdout "0\n3\n6\n-1\n-1\n-4\n-7\n"; +); +test!(int32_bitwise => r#" + from @std/app import start, print, exit + + prefix toInt32 as ~ precedence 10 + + on start { + print(~1 & ~2); + print(~1 | ~3); + print(~5 ^ ~3); + print(! ~0); + print(~1 !& ~2); + print(~1 !| ~2); + print(~5 !^ ~3); + emit exit 0; + }"#; + stdout "0\n3\n6\n-1\n-1\n-4\n-7\n"; +); +test!(int64_bitwise => r#" + from @std/app import start, print, exit + + on start { + print(1 & 2); + print(1 | 3); + print(5 ^ 3); + print(!0); + print(1 !& 2); + print(1 !| 2); + print(5 !^ 3); + emit exit 0; + }"#; + stdout "0\n3\n6\n-1\n-1\n-4\n-7\n"; +); + +// Boolean Logic + +test!(boolean_logic => r#" + from @std/app import start, print, exit + + on start { + print(true); + print(false); + print(toBool(1)); + print(toBool(0)); + print(toBool(15)); + print(toBool(-1)); + print(toBool(0.0)); + print(toBool(1.2)); + print(toBool('')); + print(toBool('hi')); + + print(true && true); + print(and(true, false)); + print(false & true); + print(false.and(false)); + + print(true || true); + print(or(true, false)); + print(false | true); + print(false.or(false)); + + print(true ^ true); + print(xor(true, false)); + print(false ^ true); + print(false.xor(false)); + + print(!true); + print(not(false)); + + print(true !& true); + print(nand(true, false)); + print(false !& true); + false.nand(false).print(); + + print(true !| true); + print(nor(true, false)); + print(false !| true); + false.nor(false).print(); + + print(true !^ true); + print(xnor(true, false)); + print(false !^ true); + false.xnor(false).print(); + + emit exit 0; + }"#; + stdout r#"true +false +true +false +true +true +false +true +false +false +true +false +false +false +true +true +true +false +false +true +true +false +false +true +false +true +true +true +false +false +false +true +true +false +false +true +"#; +); + +// String Manipulation + +test!(string_ops => r#" + from @std/app import start, print, exit + + on start { + concat('Hello, ', 'World!').print(); + print('Hello, ' + 'World!'); + + repeat('hi ', 5).print(); + print('hi ' * 5); + + matches('foobar', 'fo.*').print(); + print('foobar' ~ 'fo.*'); + + index('foobar', 'ba').print(); + print('foobar' @ 'ba'); + + length('foobar').print(); + print(#'foobar'); + + trim(' hi ').print(); + print(\`' hi '); + + split('Hello, World!', ', ')[0].print(); + print(('Hello, World!' / ', ')[1]); + + const res = split('Hello, World!', ', '); + res[0].print(); + + const res2 = 'Hello, World!' / ', '; + print(res2[1]); + + emit exit 0; + }"#; + stdout r#"Hello, World! +Hello, World! +hi hi hi hi hi +hi hi hi hi hi +true +true +3 +3 +6 +6 +hi +hi +Hello +World! +Hello +World! +"#; +); +test!(string_global_local_equality => r#" + from @std/app import start, print, exit + + on start { + const foo = 'foo'; + print(foo.trim() == foo); + emit exit 0; + }"#; + stdout "true\n"; +); +test!(string_char_array => r#" + from @std/app import start, print, exit + + on start { + const fooCharArray = 'foo'.toCharArray(); + print(#fooCharArray); + print(fooCharArray[0]); + print(fooCharArray[1]); + print(fooCharArray[2]); + + emit exit 0; + }"#; + stdout r#"3 +f +o +o +"#; +); +/* Pending +test!(string_templating => r#" + from @std/app import start, print, exit + + on start { + template('\${greet}, \${name}!', new Map { + 'greet': 'Hello' + 'name': 'World' + }).print() + print('\${greet}, \${name}!' % new Map { + 'greet': 'Good-bye' + 'name': 'World' + }) + + emit exit 0 + }"#; + stdout "Hello, World!\nGood-bye, World!\n"; +); +*/ + +// Comparators + +test!(equality => r#" + from @std/app import start, print, exit + + on start { + print(toInt8(0) == toInt8(0)); + print(toInt8(1).eq(toInt8(0))); + + print(toInt16(0) == toInt16(0)); + print(toInt16(1).eq(toInt16(0))); + + print(toInt32(0) == toInt32(0)); + print(toInt32(1).eq(toInt32(0))); + + print(0 == 0); + print(1.eq(0)); + + print(toFloat32(0.0) == toFloat32(0.0)); + print(toFloat32(1.2).eq(toFloat32(0.0))); + + print(0.0 == 0.0); + print(1.2.eq(0.0)); + + print(true == true); + print(true.eq(false)); + + print('hello' == 'hello'); + print('hello'.eq('world')); + + emit exit 0; + }"#; + stdout r#"true +false +true +false +true +false +true +false +true +false +true +false +true +false +true +false +"#; +); +test!(not_equals => r#" + from @std/app import start, print, exit + + on start { + print(toInt8(0) != toInt8(0)); + print(toInt8(1).neq(toInt8(0))); + + print(toInt16(0) != toInt16(0)); + print(toInt16(1).neq(toInt16(0))); + + print(toInt32(0) != toInt32(0)); + print(toInt32(1).neq(toInt32(0))); + + print(0 != 0); + print(1.neq(0)); + + print(toFloat32(0.0) != toFloat32(0.0)); + print(toFloat32(1.2).neq(toFloat32(0.0))); + + print(0.0 != 0.0); + print(1.2.neq(0.0)); + + print(true != true); + print(true.neq(false)); + + print('hello' != 'hello'); + print('hello'.neq('world')); + + emit exit 0; + }"#; + stdout r#"false +true +false +true +false +true +false +true +false +true +false +true +false +true +false +true +"#; +); +test!(less_than => r#" + from @std/app import start, print, exit + + on start { + print(toInt8(0) < toInt8(1)); + print(toInt8(1).lt(toInt8(0))); + + print(toInt16(0) < toInt16(1)); + print(toInt16(1).lt(toInt16(0))); + + print(toInt32(0) < toInt32(1)); + print(toInt32(1).lt(toInt32(0))); + + print(0 < 1); + print(1.lt(0)); + + print(toFloat32(0.0) < toFloat32(1.0)); + print(toFloat32(1.2).lt(toFloat32(0.0))); + + print(0.0 < 1.0); + print(1.2.lt(0.0)); + + print('hello' < 'hello'); + print('hello'.lt('world')); + + emit exit 0; + }"#; + stdout r#"true +false +true +false +true +false +true +false +true +false +true +false +false +true +"#; +); +test!(less_than_or_equal => r#" + from @std/app import start, print, exit + + on start { + print(toInt8(0) <= toInt8(1)); + print(toInt8(1).lte(toInt8(0))); + + print(toInt16(0) <= toInt16(1)); + print(toInt16(1).lte(toInt16(0))); + + print(toInt32(0) <= toInt32(1)); + print(toInt32(1).lte(toInt32(0))); + + print(0 <= 1); + print(1.lte(0)); + + print(toFloat32(0.0) <= toFloat32(1.0)); + print(toFloat32(1.2).lte(toFloat32(0.0))); + + print(0.0 <= 1.0); + print(1.2.lte(0.0)); + + print('hello' <= 'hello'); + print('hello'.lte('world')); + + emit exit 0; + }"#; + stdout r#"true +false +true +false +true +false +true +false +true +false +true +false +true +true +"#; +); +test!(greater_than => r#" + from @std/app import start, print, exit + + on start { + print(toInt8(0) > toInt8(1)); + print(toInt8(1).gt(toInt8(0))); + + print(toInt16(0) > toInt16(1)); + print(toInt16(1).gt(toInt16(0))); + + print(toInt32(0) > toInt32(1)); + print(toInt32(1).gt(toInt32(0))); + + print(0 > 1); + print(1.gt(0)); + + print(toFloat32(0.0) > toFloat32(1.0)); + print(toFloat32(1.2).gt(toFloat32(0.0))); + + print(0.0 > 1.0); + print(1.2.gt(0.0)); + + print('hello' > 'hello'); + print('hello'.gt('world')); + + emit exit 0; + }"#; + stdout r#"false +true +false +true +false +true +false +true +false +true +false +true +false +false +"#; +); +test!(greater_than_or_equal => r#" + from @std/app import start, print, exit + + on start { + print(toInt8(0) >= toInt8(1)); + print(toInt8(1).gte(toInt8(0))); + + print(toInt16(0) >= toInt16(1)); + print(toInt16(1).gte(toInt16(0))); + + print(toInt32(0) >= toInt32(1)); + print(toInt32(1).gte(toInt32(0))); + + print(0 >= 1); + print(1.gte(0)); + + print(toFloat32(0.0) >= toFloat32(1.0)); + print(toFloat32(1.2).gte(toFloat32(0.0))); + + print(0.0 >= 1.0); + print(1.2.gte(0.0)); + + print('hello' >= 'hello'); + print('hello'.gte('world')); + + emit exit 0; + }"#; + stdout r#"false +true +false +true +false +true +false +true +false +true +false +true +true +false +"#; +); +test!(type_coercion_aliases => r#" + from @std/app import start, print, exit + + on start { + print(toInt(0) == toInt64(0)); + print(toFloat(0.0) == toFloat(0.0)); + + emit exit 0; + }"#; + stdout "true\ntrue\n"; +); + +// Functions and Custom Operators + +test!(functions_and_custom_operators => r#" + from @std/app import start, print, exit + + fn foo() { + print('foo'); + } + + fn bar(str: string, a: int64, b: int64): string { + return str * a + b.toString(); + } + + fn baz(pre: string, body: string): void { + print(pre + bar(body, 1, 2)); + } + + // 'int' is an alias for 'int64' + fn double(a: int) = a * 2; + + prefix double as ## precedence 10 + + /** + * It should be possible to write 'doublesum' as: + * + * fn doublesum(a: int64, b: int64) = ##a + ##b + * + * but the function definitions are all parsed before the first operator mapping is done. + */ + fn doublesum(a: int64, b: int64) = a.double() + b.double(); + + infix doublesum as #+# precedence 11 + + on start fn (): void { + foo(); + 'to bar'.bar(2, 3).print(); + '>> '.baz('text here'); + 4.double().print(); + print(##3); + 4.doublesum(1).print(); + print(2 #+# 3); + emit exit 0; + }"#; + stdout r#"foo +to barto bar3 +>> text here2 +8 +6 +10 +10 +"#; +); + +// Conditionals + +test!(basic_conditionals => r#" + from @std/app import start, print, exit + + fn bar() { + print('bar!'); + } + + fn baz() { + print('baz!'); + } + + on start { + if 1 == 0 { + print('What!?'); + } else { + print('Math is sane...'); + } + + if 1 == 0 { + print('Not this again...'); + } else if 1 == 2 { + print('Still wrong...'); + } else { + print('Math is still sane, for now...'); + } + + const foo: bool = true == true; + if foo bar else baz + + const isTrue = true == true; + cond(isTrue, fn { + print(\"It's true!\"); + }); + cond(!isTrue, fn { + print('This should not have run'); + }); + + emit exit 0; + }"#; + stdout r#"Math is sane... +Math is still sane, for now... +bar! +It's true! +"#; +); +test!(nested_conditionals => r#" + from @std/app import start, print, exit + + on start { + if true { + print(1); + if 1 == 2 { + print('What?'); + } else { + print(2); + if 2 == 1 { + print('Uhh...'); + } else if 2 == 2 { + print(3); + } else { + print('Nope'); + } + } + } else { + print('Hmm'); + } + emit exit 0; + }"#; + stdout "1\n2\n3\n"; +); +test!(early_return => r#" + from @std/app import start, print, exit + + fn nearOrFar(distance: float64): string { + if distance < 5.0 { + return 'Near!'; + } else { + return 'Far!'; + } + } + + on start { + print(nearOrFar(3.14)); + print(nearOrFar(6.28)); + + emit exit 0; + }"#; + stdout "Near!\nFar!\n"; +); +/* Dropping the ternary operators since either they behave consistently with other operators and + * are therefore unexpected for end users, or they are inconsistent and a whole lot of pain is + * needed to support them. */ +test!(conditional_let_assignment => r#" + from @std/app import start, print, exit + + on start { + let a = 0; + let b = 1; + let c = 2; + + if true { + a = b; + } else { + a = c; + } + print(a); + emit exit 0; + }"#; + stdout "1\n"; +); + +// Object Literals + +test!(object_literal_compiler_checks => r#" + from @std/app import start, print, exit + + type Foo { + bar: string, + baz: bool, + } + + on start { + const foo = new Foo { + bay: 1.23, + }; + emit exit 0; + }"#; + stderr r#"Foo object literal improperly defined +Missing fields: bar, baz +Extra fields: bay +new Foo { + bay: 1.23, + } on line 2:24 +"#; +); +test!(array_literals => r#" + from @std/app import start, print, exit + + on start { + const test3 = new Array [ 1, 2, 4, 8, 16, 32, 64 ]; + print(test3[0]); + print(test3[1]); + print(test3[2]); + + emit exit 0; + }"#; + stdout "1\n2\n4\n"; +); +test!(object_literals => r#" + from @std/app import start, print, exit + + type MyType { + foo: string, + bar: bool, + } + + on start { + const test = new MyType { + foo: 'foo!', + bar: true, + }; + print(test.foo); + print(test.bar); + + emit exit 0; + }"#; + stdout "foo!\ntrue\n"; +); +test!(object_and_array_reassignment => r#" + from @std/app import start, print, exit + + type Foo { + bar: bool + } + + on start { + let test = new Array [ 1, 2, 3 ]; + print(test[0]); + test.set(0, 0); + print(test[0]); + + let test2 = new Array [ + new Foo { + bar: true + }, + new Foo { + bar: false + } + ]; + let test3 = test2[0] || new Foo { + bar: false + }; + print(test3.bar); + test3.bar = false; + test2.set(0, test3); // TODO: is the a better way to do nested updates? + const test4 = test2[0] || new Foo { + bar: true + }; + print(test4.bar); + + emit exit 0; + }"#; + stdout "1\n0\ntrue\nfalse\n"; +); +/* Pending +test!(map_support => r#" + from @std/app import start, print, exit + + on start { + const test5 = new Map { + true: 1 + false: 0 + } + + print(test5[true]) + print(test5[false]) + + let test6 = new Map { + 'foo': 'bar' + } + test6['foo'] = 'baz' + print(test6['foo']) + + emit exit 0 + }"#; + stdout "1\n0\nbaz\n"; +); +*/ + +// Arrays + +test!(array_accessor_and_length => r#" + from @std/app import start, print, exit + + on start { + print('Testing...'); + const test = '1,2,3'.split(','); + print(test.length()); + print(test[0]); + print(test[1]); + print(test[2]); + emit exit 0; + }"#; + stdout r#"Testing... +3 +1 +2 +3 +"#; +); + +test!(array_literal_syntax => r#" + from @std/app import start, print, exit + + on start { + print('Testing...'); + const test = new Array [ 1, 2, 3 ]; + print(test[0]); + print(test[1]); + print(test[2]); + const test2 = [ 4, 5, 6 ]; + print(test2[0]); + print(test2[1]); + print(test2[2]); + emit exit 0; + }"#; + stdout r#"Testing... +1 +2 +3 +4 +5 +6 +"#; +); +test!(array_mutable_push_pop => r#" + from @std/app import start, print, exit + + on start { + print('Testing...'); + let test = new Array []; + test.push(1); + test.push(2); + test.push(3); + print(test[0]); + print(test[1]); + print(test[2]); + print(test.pop()); + print(test.pop()); + print(test.pop()); + print(test.pop()); // Should print error message + emit exit 0; + }"#; + stdout r#"Testing... +1 +2 +3 +3 +2 +1 +cannot pop empty array +"#; +); +test!(array_length_index_has_join => r#" + from @std/app import start, print, exit + + on start { + const test = new Array [ 1, 1, 2, 3, 5, 8 ]; + const test2 = new Array [ 'Hello', 'World!' ]; + print('has test'); + print(test.has(3)); + print(test.has(4)); + + print('length test'); + test.length().print(); + print(#test); + + print('index test'); + test.index(5).print(); + print(test2 @ 'Hello'); + + print('join test'); + test2.join(', ').print(); + + emit exit 0; + }"#; + stdout r#"has test +true +false +length test +6 +6 +index test +4 +0 +join test +Hello, World! +"#; +); +/* Without the ternary syntax, there is no ternary abuse possible. But a syntax kinda like this + * doesn't seem so bad? (Eg `1:2:3` produces an array of [1, 2, 3]. It almost feels like a + * replacement for the array literal syntax. */ +test!(array_map => r#" + from @std/app import start, print, exit + + on start { + const count = [1, 2, 3, 4, 5]; // Ah, ah, ahh! + const byTwos = count.map(fn (n: int64): Result = n * 2); + count.map(fn (n: int64) = toString(n)).join(', ').print(); + byTwos.map(fn (n: Result) = toString(n)).join(', ').print(); + emit exit 0; + }"#; + stdout "1, 2, 3, 4, 5\n2, 4, 6, 8, 10\n"; +); +test!(array_repeat_and_map_lin => r#" + from @std/app import start, print, exit + + on start { + const arr = [1, 2, 3] * 3; + const out = arr.mapLin(fn (x: int64): string = x.toString()).join(', '); + print(out); + emit exit 0; + }"#; + stdout "1, 2, 3, 1, 2, 3, 1, 2, 3\n"; +); +test!(array_each_and_find => r#" + from @std/app import start, print, exit + + on start { + const test = [ 1, 1, 2, 3, 5, 8 ]; + test.find(fn (val: int64): bool = val % 2 == 1).getOr(0).print(); + test.each(fn (val: int64) = print('=' * val)); + emit exit 0; + }"#; + stdout r#"1 += += +== +=== +===== +======== +"#; +); +test!(array_every_some_del => r#" + from @std/app import start, print, exit + + fn isOdd (val: int64): bool = val % 2 == 1; + + on start { + const test = [ 1, 1, 2, 3, 5, 8 ]; + test.every(isOdd).print(); + test.some(isOdd).print(); + print(test.length()); + print(test.delete(1)); + print(test.delete(4)); + print(test.delete(10)); + emit exit 0; + }"#; + stdout r#"false +true +6 +1 +8 +cannot remove idx 10 from array with length 4 +"#; +); +test!(array_reduce_filter_concat => r#" + from @std/app import start, print, exit + + on start { + const test = [ 1, 1, 2, 3, 5, 8 ]; + const test2 = [ 4, 5, 6 ]; + print('reduce test'); + test.reduce(fn (a: int, b: int): int = a + b || 0).print(); + test.reduce(min).print(); + test.reduce(max).print(); + + print('filter test'); + test.filter(fn (val: int64): bool { + return val % 2 == 1; + }).map(fn (val: int64): string { + return toString(val); + }).join(', ').print(); + + print('concat test'); + test.concat(test2).map(fn (val: int64): string { + return toString(val); + }).join(', ').print(); + (test + test2).map(fn (val: int64): string { + return toString(val); + }).join(', ').print(); + + print('reduce as filter and concat test'); + // TODO: Lots of improvements needed for closures passed directly to opcodes. This one-liner is ridiculous + test.reduce(fn (acc: string, i: int): string = ((acc == '') && (i % 2 == 1)) ? i.toString() : (i % 2 == 1 ? (acc + ', ' + i.toString()) : acc), '').print(); + // TODO: Even more ridiculous when you want to allow parallelism + test.reducePar(fn (acc: string, i: int): string = ((acc == '') && (i % 2 == 1)) ? i.toString() : (i % 2 == 1 ? (acc + ', ' + i.toString()) : acc), fn (acc: string, cur: string): string = ((acc != '') && (cur != '')) ? (acc + ', ' + cur) : (acc != '' ? acc : cur), '').print(); + + emit exit 0; + }"#; + stdout r#"reduce test +20 +1 +8 +filter test +1, 1, 3, 5 +concat test +1, 1, 2, 3, 5, 8, 4, 5, 6 +1, 1, 2, 3, 5, 8, 4, 5, 6 +reduce as filter and concat test +1, 1, 3, 5 +1, 1, 3, 5 +"#; +); +test!(array_custom_types => r#" + from @std/app import start, print, exit + + type Foo { + foo: string, + bar: bool + } + + on start { + const five = [1, 2, 3, 4, 5]; + five.map(fn (n: int64): Foo { + return new Foo { + foo: n.toString(), + bar: n % 2 == 0, + }; + }).filter(fn (f: Foo): bool = f.bar).map(fn (f: Foo): string = f.foo).join(', ').print(); + emit exit 0; + }"#; + stdout "2, 4\n"; +); + +// Hashing +// TODO: I have no idea how I'm going to make this work in pure Rust, but damnit I'm gonna try. +// This was super useful for a whole host of things. + +test!(to_hash => r#" + from @std/app import start, print, exit + + on start { + print(toHash(1)); + print(toHash(3.14159)); + print(toHash(true)); + print(toHash('false')); + print(toHash([1, 2, 5, 3])); + emit exit 0; + }"#; + stdout r#"-1058942856030168491 +-5016367128657347516 +-1058942856030168491 +6288867289231076425 +-1521185239552941064 +"#; +); +test!(basic_hashmap => r#" + from @std/app import start, print, exit + + on start { + const test = newHashMap('foo', 1); + test.set('bar', 2); + test.set('baz', 99); + print(test.keyVal().map(fn (n: KeyVal): string { + return 'key: ' + n.key + \"\\nval: \" + toString(n.val); + }).join(\"\\n\")); + print(test.keys().join(', ')); + print(test.vals().map(fn (n: int64): string = n.toString()).join(', ')); + print(test.length()); + print(test.get('foo')); + emit exit 0; + }"#; + stdout r#"key: foo +val: 1 +key: bar +val: 2 +key: baz +val: 99 +foo, bar, baz +1, 2, 99 +3 +1 +"#; +); +test!(keyval_to_hashmap => r#" + from @std/app import start, print, exit + + fn kv(k: any, v: anythingElse) = new KeyVal { + key: k, + val: v + } + + on start { + const kva = [ kv(1, 'foo'), kv(2, 'bar'), kv(3, 'baz') ]; + const hm = kva.toHashMap(); + print(hm.keyVal().map(fn (n: KeyVal): string { + return 'key: ' + toString(n.key) + \"\\nval: \" + n.val; + }).join(\"\\n\")); + print(hm.get(1)); + emit exit 0; + }"#; + stdout r#"key: 1 +val: foo +key: 2 +val: bar +key: 3 +val: baz +foo +"#; +); +test!(hashmap_double_set => r#" + from @std/app import start, print, exit + + on start { + let test = newHashMap('foo', 'bar'); + test.get('foo').print(); + test.set('foo', 'baz'); + print(test.get('foo')); + emit exit 0; + }"#; + stdout "bar\nbaz\n"; +); +/* Pending +test!(hashmap_ops => r#" + from @std/app import start, print, exit + + on start { + const test = new Map { + 'foo': 1 + 'bar': 2 + 'baz': 99 + } + + print('keyVal test') + test.keyVal().each(fn (n: KeyVal) { + print('key: ' + n.key) + print('val: ' + n.value.toString()) + }) + + print('keys test') + test.keys().each(print) + + print('values test') + test.values().each(print) + + print('length test') + test.length().print() + print(#test) + + emit exit 0 + }"#; + stdout r#"keyVal test +key: bar +val: 2 +key: foo +val: 1 +key: baz +val: 99 +keys test +bar +foo +baz +values test +2 +1 +99 +length test +3 +3 +"#; +); +*/ + +// Generics + +test!(generics => r#" + from @std/app import start, print, exit + + type box { + set: bool, + val: V + } + + on start fn { + let int8Box = new box { + val: 8.toInt8(), + set: true + }; + print(int8Box.val); + print(int8Box.set); + + let stringBox = new box { + val: 'hello, generics!', + set: true + }; + print(stringBox.val); + print(stringBox.set); + + const stringBoxBox = new box> { + val: new box { + val: 'hello, nested generics!', + set: true + }, + set: true + }; + stringBoxBox.set.print(); + stringBoxBox.val.set.print(); + print(stringBoxBox.val.val); + + emit exit 0; + }"#; + stdout r#"8 +true +hello, generics! +true +true +true +hello, nested generics! +"#; +); +test!(invalid_generics => r#" + from @std/app import start, print, exit + + type box { + set: bool, + val: V + } + + on start fn { + let stringBox = new box { + set: true, + val: 'str' + }; + stringBox.val = 8; + + emit exit 0; + }"#; + stderr "stringBox.val is of type string but assigned a value of type int64\n" +); + +// Interfaces + +test!(basic_interfaces => r#" + from @std/app import start, print, exit + + interface Stringifiable { + toString(Stringifiable): string + } + + fn quoteAndPrint(toQuote: Stringifiable) { + print(\"'\" + toString(toQuote) + \"'\"); + } + + on start { + quoteAndPrint('Hello, World'); + quoteAndPrint(5); + emit exit 0; + }"#; + stdout "'Hello, World!'\n'5'\n"; +); + +/* TODO: Add support for generating multiple source files for a test. Just copying over the whole + * original test for now because the exact structure isn't yet clear + * + Describe "import behavior" + before() { + sourceToFile datetime.ln " + from @std/app import print + + export type Year { + year: int32 + } + + export type YearMonth { + year: int32, + month: int8 + } + + export type Date { + year: int32, + month: int8, + day: int8 + } + + export type Hour { + hour: int8 + } + + export type HourMinute { + hour: int8, + minute: int8 + } + + export type Time { + hour: int8, + minute: int8, + second: float64 + } + + export type DateTime { + date: Date, + time: Time, + timezone: HourMinute + } + + export fn makeYear(year: int32): Year { + return new Year { + year: year + }; + } + + export fn makeYear(year: int64): Year { + return new Year { + year: toInt32(year) + }; + } + + export fn makeYearMonth(year: int32, month: int8): YearMonth { + return new YearMonth { + year: year, + month: month + }; + } + + export fn makeYearMonth(y: Year, month: int64): YearMonth { + return new YearMonth { + year: y.year, + month: toInt8(month), + }; + } + + export fn makeDate(year: int32, month: int8, day: int8): Date { + return new Date { + year: year, + month: month, + day: day, + }; + } + + export fn makeDate(ym: YearMonth, day: int64): Date { + return new Date { + year: ym.year, + month: ym.month, + day: toInt8(day) + }; + } + + export fn makeHour(hour: int8): Hour { + return new Hour { + hour: hour + }; + } + + export fn makeHourMinute(hour: int8, minute: int8): HourMinute { + return new HourMinute { + hour: hour, + minute: minute + }; + } + + export fn makeHourMinute(hour: int64, minute: int64): HourMinute { + return new HourMinute { + hour: toInt8(hour), + minute: toInt8(minute) + }; + } + + export fn makeHourMinute(h: Hour, minute: int8): HourMinute { + return new HourMinute { + hour: h.hour, + minute: minute + }; + } + + export fn makeTime(hour: int8, minute: int8, second: float64): Time { + return new Time { + hour: hour, + minute: minute, + second: second + }; + } + + export fn makeTime(hm: HourMinute, second: float64): Time { + return new Time { + hour: hm.hour, + minute: hm.minute, + second: second + }; + } + + export fn makeTime(hm: HourMinute, second: int64): Time { + return new Time { + hour: hm.hour, + minute: hm.minute, + second: toFloat64(second) + }; + } + + export fn makeTime(hm: Array, second: int64): Time { + return new Time { + hour: hm[0].toInt8(), + minute: hm[1].toInt8(), + second: second.toFloat64() + }; + } + + export fn makeDateTime(date: Date, time: Time, timezone: HourMinute): DateTime { + return new DateTime { + date: date, + time: time, + timezone: timezone + }; + } + + export fn makeDateTime(date: Date, time: Time): DateTime { + return new DateTime { + date: date, + time: time, + timezone: 00:00, + }; + } + + export fn makeDateTimeTimezone(dt: DateTime, timezone: HourMinute): DateTime { + return new DateTime { + date: dt.date, + time: dt.time, + timezone: timezone + }; + } + + export fn makeDateTimeTimezone(dt: DateTime, timezone: Array): DateTime { + return new DateTime { + date: dt.date, + time: dt.time, + timezone: new HourMinute { + hour: timezone[0].toInt8(), + minute: timezone[1].toInt8(), + } + }; + } + + export fn makeDateTimeTimezoneRev(dt: DateTime, timezone: HourMinute): DateTime { + return new DateTime { + date: dt.date, + time: dt.time, + timezone: new HourMinute { + hour: timezone.hour.snegate(), + minute: timezone.minute + } + }; + } + + export fn makeDateTimeTimezoneRev(dt: DateTime, timezone: Array): DateTime { + return new Datetime { + date: dt.date, + time: dt.time, + timezone: new HourMinute { + hour: toInt8(timezone[0]).snegate(), + minute: toInt8(timezone[1]) + } + }; + } + + export fn print(dt: DateTime) { + // TODO: Work on formatting stuff + const timezoneOffsetSymbol = dt.timezone.hour < toInt8(0) ? \"-\" : \"+\"; + let str = (new Array [ + toString(dt.date.year), \"-\", toString(dt.date.month), \"-\", toString(dt.date.day), \"@\", + toString(dt.time.hour), \":\", toString(dt.time.minute), \":\", toString(dt.time.second), + timezoneOffsetSymbol, sabs(dt.timezone.hour).toString(), \":\", toString(dt.timezone.minute) + ]).join(''); + print(str); + } + + export prefix makeYear as # precedence 2 + export infix makeYearMonth as - precedence 2 + export infix makeDate as - precedence 2 + export infix makeHourMinute as : precedence 7 + export infix makeTime as : precedence 7 + export infix makeDateTime as @ precedence 2 + export infix makeDateTimeTimezone as + precedence 2 + export infix makeDateTimeTimezoneRev as - precedence 2 + + export interface datetime { + # int64: Year, + Year - int64: YearMonth, + YearMonth - int64: Date, + int64 : int64: HourMinute, + HourMinute : int64: Time, + Date @ Time: DateTime, + DateTime + HourMinute: DateTime, + DateTime - HourMinute: DateTime, + print(DateTime): void, + } + " + + sourceToAll " + from @std/app import start, print, exit + from ./datetime import datetime + + on start { + const dt = #2020 - 07 - 02@12:07:30 - 08:00; + dt.print(); + emit exit 0; + } + " + } + BeforeAll before + + after() { + cleanFile datetime.ln + cleanTemp + } + AfterAll after + + It "runs js" + When run test_js + The output should eq "2020-7-2@12:7:30-8:0" + End + + It "runs agc" + When run test_agc + The output should eq "2020-7-2@12:7:30-8:0" + End + End +*/ + +// Maybe, Result, and Either + +test!(maybe => r#" + from @std/app import start, print, exit + + fn fiver(val: float64) { + if val.toInt64() == 5 { + return some(5); + } else { + return none(); + } + } + + on start { + const maybe5 = fiver(5.5); + if maybe5.isSome() { + print(maybe5.getOr(0)); + } else { + print('what?'); + } + + const maybeNot5 = fiver(4.4); + if maybeNot5.isNone() { + print('Correctly received nothing!'); + } else { + print('uhhh'); + } + + if maybe5.isSome() { + print(maybe5 || 0); + } else { + print('what?'); + } + + if maybeNot5.isNone() { + print('Correctly received nothing!'); + } else { + print('uhhh'); + } + + maybe5.toString().print(); + maybeNot5.toString().print(); + + emit exit 0; + }"#; + stdout r#"5 +Correctly received nothing! +5 +Correctly received nothing! +5 +none +"#; +); +test!(result => r#" + from @std/app import start, print, exit + + fn reciprocal(val: float64) { + if val == 0.0 { + return err('Divide by zero error!'); + } else { + return 1.0 / val; + } + } + + on start { + const oneFifth = reciprocal(5.0); + if oneFifth.isOk() { + print(oneFifth.getOr(0.0)); + } else { + print('what?'); + } + + const oneZeroth = reciprocal(0.0); + if oneZeroth.isErr() { + const error = oneZeroth.getErr(noerr()); + print(error); + } else { + print('uhhh'); + } + + if oneFifth.isOk() { + print(oneFifth || 0.0); + } else { + print('what?'); + } + + if oneZeroth.isErr() { + print(oneZeroth || 1.2345); + } else { + print('uhhh'); + } + + oneFifth.toString().print(); + oneZeroth.toString().print(); + + const res = ok('foo'); + print(res.getErr('there is no error')); + + emit exit 0; + }"#; + stdout r#"0.2 +Divide by zero error! +0.2 +1.2345 +0.2 +Divide by zero error! +there is no error +"#; +); +test!(either => r#" + from @std/app import start, print, exit + + on start { + const strOrNum = getMainOrAlt(true); + if strOrNum.isMain() { + print(strOrNum.getMainOr('')); + } else { + print('what?'); + } + + const strOrNum2 = getMainOrAlt(false); + if strOrNum2.isAlt() { + print(strOrNum2.getAltOr(0)); + } else { + print('uhhh'); + } + + strOrNum.toString().print(); + strOrNum2.toString().print(); + + emit exit 0; + } + + fn getMainOrAlt(isMain: bool) { + if isMain { + return main('string'); + } else { + return alt(2); + } + }"#; + stdout r#"string +2 +string +2 +"#; +); + +// Types + +test!(user_types_and_generics => r#" + from @std/app import start, print, exit + + type foo { + bar: A, + baz: B + } + + type foo2 = foo + + on start fn { + let a = new foo { + bar: 'bar', + baz: 0 + }; + let b = new foo { + bar: 0, + baz: true + }; + let c = new foo2 { + bar: 0, + baz: 1.23 + }; + let d = new foo { + bar: 1, + baz: 3.14 + }; + print(a.bar); + print(b.bar); + print(c.bar); + print(d.bar); + + emit exit 0; + }"#; + stdout "bar\n0\n0\n1\n"; +); +/* Pending multi-file support + * + Describe "using non-imported type returned by imported function" + before() { + sourceToTemp " + from @std/app import start, exit + from @std/http import fetch, Request + + on start { + arghFn('{\"test\":\"test\"}'); + emit exit 0; + } + + fn arghFn(arghStr: string) { + fetch(new Request { + method: 'POST', + url: 'https://reqbin.com/echo/post/json', + headers: newHashMap('Content-Length', arghStr.length().toString()), + body: arghStr, + }); + } + " + sourceToFile test_server.js " + const http = require('http'); + + http.createServer((req, res) => { + console.log('received'); + res.end('Hello, world!'); + }).listen(8088); + " + } + BeforeAll before + + after() { + cleanTemp + } + AfterAll after + + afterEach() { + kill $PID1 + wait $PID1 2>/dev/null + # kill $PID2 + # wait $PID2 2>/dev/null + return 0 + } + After afterEach + + It "runs js" + Pending unimported-types-returned-by-imported-functions + node test_$$/test_server.js 1>test_$$/test_server.js.out 2>/dev/null & + PID1=$! + # node test_$$/temp.js 1>/dev/null & + # PID2=$! + sleep 1 + When run cat test_$$/test_server.js.out + The output should eq "received" + End + + It "runs agc" + Pending unimported-types-returned-by-imported-functions + node test_$$/test_server.js 1>test_$$/test_server.agc.out 2>/dev/null & + PID1=$! + # alan run test_$$/temp.agc 1>/dev/null 2>/dev/null & + # PID2=$! + sleep 1 + When run cat test_$$/test_server.agc.out + The output should eq "received" + End + End +*/ + +// Custom Events + +test!(custom_event_loop => r#" + from @std/app import start, print, exit + + event loop: int64 + + on loop fn looper(val: int64) { + print(val); + if val >= 10 { + emit exit 0; + } else { + emit loop val + 1 || 0; + } + } + + on start { + emit loop 0; + }"#; + stdout r#"0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +"#; +); +test!(user_defined_type_event => r#" + from @std/app import start, print, exit + + type Thing { + foo: int64, + bar: string + } + + event thing: Thing + + on thing fn (t: Thing) { + print(t.foo); + print(t.bar); + emit exit 0; + } + + on start { + emit thing new Thing { + foo: 1, + bar: 'baz' + }; + }"#; + stdout "1\nbaz\n"; +); +test!(multiple_event_handlers => r#" + from @std/app import start, print, exit + + event aString: string + + on aString fn(str: string) { + print('hey I got a string! ' + str); + } + + on aString fn(str: string) { + print('I also got a string! ' + str); + } + + on aString fn(ignore: string) { + wait(100); + emit exit 0; + } + + on start { + emit aString 'hi'; + }"#; + stdout "hey I got a string! hi\nI also got a string! hi\n"; // TODO: The order is not guaranteed, support that +); + +// Closures + +test!(closure_creation_and_usage => r#" + from @std/app import start, print, exit + + fn closure(): function { + let num = 0; + return fn (): int64 { + num = num + 1 || 0; + return num; + }; + } + + on start fn (): void { + const counter1 = closure(); + const counter2 = closure(); + print(counter1()); + print(counter1()); + print(counter2()); + emit exit 0; + }"#; + stdout "1\n2\n1\n"; +); +test!(closure_by_name => r#" + from @std/app import start, print, exit + + fn double(x: int64): int64 = x * 2 || 0; + + on start { + const numbers = [1, 2, 3, 4, 5]; + numbers.map(double).map(toString).join(', ').print(); + emit exit 0; + }"#; + stdout "2, 4, 6, 8, 10\n"; +); +test!(inlined_closure_with_arg => r#" + from @std/app import start, print, exit + + on start { + const arghFn = fn(argh: string) { + print(argh); + }; + arghFn('argh'); + emit exit 0; + }"#; + stdout "argh\n"; +); + +// Compiler Errors + +test!(cross_type_comparisons => r#" + from @std/app import start, print, exit + + on start { + print(true == 1); + emit exit 0; + }"#; + stderr r#"Cannot resolve operators with remaining statement +true == 1 + == +"#; +); +test!(unreachable_code => r#" + from @std/app import start, print, exit + + fn unreachable() { + return 'blah'; + print('unreachable!'); + } + + on start { + unreachable(); + emit exit 0; + }"#; + stderr r#"Unreachable code in function 'unreachable' after: +return 'blah'; on line 4:12 +"#; +); +test!(recursive_functions => r#" + from @std/app import start, print, exit + + fn fibonacci(n: int64) { + if n < 2 { + return 1; + } else { + return fibonacci(n - 1 || 0) + fibonacci(n - 2 || 0); + } + } + + on start { + print(fibonacci(0)); + print(fibonacci(1)); + print(fibonacci(2)); + print(fibonacci(3)); + print(fibonacci(4)); + emit exit 0; + }"#; + stderr "Recursive callstack detected: fibonacci -> fibonacci. Aborting.\n"; +); +test!(undefined_function_call => r#" + from @std/app import start, print, exit + + on start { + print(i64str(5)); // Illegal direct opcode usage + emit exit 0; + }"#; + stderr "i64str is not a function but used as one.\ni64str on line 4:18\n"; +); +test!(totally_broken_statement => r#" + import @std/app + + on app.start { + app.oops + }"#; + stderr "TODO"; +); +/* Pending + Describe "Importing unexported values" + before() { + sourceToFile piece.ln " + type Piece { + owner: bool + } + " + sourceToTemp " + from @std/app import start, print, exit + from ./piece import Piece + + on start { + const piece = new Piece { + owner: false + }; + print('Hello World'); + if piece.owner == true { + print('OK'); + } else { + print('False'); + } + emit exit 0; + } + " + } + BeforeAll before + + after() { + cleanFile piece.ln + cleanTemp + } + AfterAll after + + It "doesn't work" + When run alan compile test_$$/temp.ln test_$$/temp.amm + The status should not eq "0" + The error should eq "Piece is not a type +new Piece { + owner: false + } on line 2:26" + End + End +*/ + +// Module-level constants + +test!(module_level_constant => r#" + import @std/app + + const helloWorld = 'Hello, World!'; + + on app.start { + app.print(helloWorld); + emit app.exit 0; + }"#; + stdout "Hello, World!\n"; +); +test!(module_level_constant_from_function_call => r#" + from @std/app import start, print, exit + + const three = add(1, 2); + + fn fiver() = 5; + + const five = fiver(); + + on start { + print(three); + print(five); + emit exit 0; + }"#; + stdout "3\n5\n"; +); + +// @std/trig + +test!(std_trig => r#" + from @std/app import start, print, exit + import @std/trig + from @std/trig import e, pi, tau + // shouldn't be necessary, but compiler issue makes it so + + on start { + 'Logarithms and e^x'.print(); + print(trig.exp(e)); + print(trig.ln(e)); + print(trig.log(e)); + + 'Basic Trig functions'.print(); + print(trig.sin(tau / 6.0)); + print(trig.cos(tau / 6.0)); + print(trig.tan(tau / 6.0)); + print(trig.sec(tau / 6.0)); + print(trig.csc(tau / 6.0)); + print(trig.cot(tau / 6.0)); + + 'Inverse Trig functions'.print(); + print(trig.arcsine(0.0)); + print(trig.arccosine(1.0)); + print(trig.arctangent(0.0)); + print(trig.arcsecant(tau / 6.0)); + print(trig.arccosecant(tau / 6.0)); + print(trig.arccotangent(tau / 6.0)); + + 'Historic Trig functions (useful for navigation and as a teaching aid: https://en.wikipedia.org/wiki/File:Circle-trig6.svg )'.print(); + print(trig.versine(pi / 3.0)); + print(trig.vercosine(pi / 3.0)); + print(trig.coversine(pi / 3.0)); + print(trig.covercosine(pi / 3.0)); + print(trig.haversine(pi / 3.0)); + print(trig.havercosine(pi / 3.0)); + print(trig.hacoversine(pi / 3.0)); + print(trig.hacovercosine(pi / 3.0)); + print(trig.exsecant(pi / 3.0)); + print(trig.excosecant(pi / 3.0)); + print(trig.chord(pi / 3.0)); + + 'Historic Inverse Trig functions'.print(); + print(trig.aver(0.0)); + print(trig.avcs(0.5)); + print(trig.acvs(1.0)); + print(trig.acvc(1.0)); + print(trig.ahav(0.5)); + print(trig.ahvc(0.5)); + print(trig.ahcv(0.5)); + print(trig.ahcc(0.5)); + print(trig.aexs(0.5)); + print(trig.aexc(0.5)); + print(trig.acrd(0.5)); + + 'Hyperbolic Trig functions'.print(); + print(trig.sinh(tau / 6.0)); + print(trig.cosh(tau / 6.0)); + print(trig.tanh(tau / 6.0)); + print(trig.sech(tau / 6.0)); + print(trig.csch(tau / 6.0)); + print(trig.coth(tau / 6.0)); + + 'Inverse Hyperbolic Trig functions'.print(); + print(trig.hyperbolicArcsine(tau / 6.0)); + print(trig.hyperbolicArccosine(tau / 6.0)); + print(trig.hyperbolicArctangent(tau / 6.0)); + print(trig.hyperbolicArcsecant(0.5)); + print(trig.hyperbolicArccosecant(tau / 6.0)); + print(trig.hyperbolicArccotangent(tau / 6.0)); + + emit exit 0; + }"#; + stdout r#"Logarithms and e^x +15.154262241479259 +1 +0.4342944819032518 +Basic Trig functions +0.8660254037844386 +0.5000000000000001 +1.7320508075688767 +1.9999999999999996 +1.1547005383792517 +0.577350269189626 +Inverse Trig functions +0 +0 +0 +0.3013736097452911 +1.2694227170496055 +0.7623475341648746 +Historic Trig functions (useful for navigation and as a teaching aid: https://en.wikipedia.org/wiki/File:Circle-trig6.svg ) +0.4999999999999999 +1.5 +0.1339745962155614 +1.8660254037844386 +0.24999999999999994 +0.75 +0.0669872981077807 +0.9330127018922193 +0.9999999999999996 +0.15470053837925168 +0.9999999999999999 +Historic Inverse Trig functions +0 +2.0943951023931957 +0 +0 +1.5707963267948966 +1.5707963267948966 +0 +0 +0.8410686705679303 +0.7297276562269663 +0.5053605102841573 +Hyperbolic Trig functions +1.2493670505239751 +1.600286857702386 +0.7807144353592677 +0.6248879662960872 +0.8004052928885931 +1.2808780710450447 +Inverse Hyperbolic Trig functions +0.9143566553928857 +0.3060421086132653 +1.8849425394276085 +1.3169578969248166 +0.8491423010640059 +1.8849425394276085 +"#; +); + +/* TODO: Convert the @std/dep (maybe, dep management will likely change quite a bit with the new + * import syntax) and @std/http tests some time in the future. For now they are included below + * as-is: + * +Describe "@std/deps" + Describe "package dependency add" + before() { + sourceToAll " + from @std/deps import Package, install, add, commit, dependency, using, block, fullBlock + + on install fn (package: Package) = package + .using(['@std/app', '@std/cmd']) + .dependency('https://github.com/alantech/hellodep.git') + .add() + .block('@std/tcp') + .fullBlock('@std/httpcommon') + .commit() + " + } + BeforeAll before + + after() { + cleanTemp + } + AfterAll after + + after_each() { + rm -r ./dependencies + } + After after_each + + has_dependencies() { + test -d "./dependencies" + } + + has_alantech() { + test -d "./dependencies/alantech" + } + + has_hellodep() { + test -d "./dependencies/alantech/hellodep" + } + + has_index() { + test -f "./dependencies/alantech/hellodep/index.ln" + } + + has_nested_dependencies() { + test -d "./dependencies/alantech/hellodep/dependencies" + } + + has_nested_alantech() { + test -d "./dependencies/alantech/hellodep/dependencies/alantech" + } + + has_nested_hellodep() { + test -d "./dependencies/alantech/hellodep/dependencies/alantech/nestedhellodep" + } + + has_nested_index() { + test -f "./dependencies/alantech/hellodep/dependencies/alantech/nestedhellodep/index.ln" + } + + has_modules() { + test -d "./dependencies/modules" + } + + has_std() { + test -d "./dependencies/modules/std" + } + + has_blacklisted_module() { + test -d "./dependencies/modules/std/tcpserver" + } + + not_has_cmd() { + if [ -d ./dependencies/modules/std/cmd ]; then + return 1 + fi + return 0 + } + + has_pkg_block() { + test -d "./dependencies/modules/std/tcp" + } + + has_pkg_full_block_applied() { + test -d "./dependencies/alantech/hellodep/modules/std/httpcommon" && grep -R -q "export const mock = true" "./dependencies/alantech/hellodep/modules/std/httpcommon/index.ln" + } + + run_js() { + node test_$$/temp.js | head -1 + } + + run_agc() { + alan run test_$$/temp.agc | head -1 + } + + It "runs js" + When run run_js + The output should eq "Cloning into './dependencies/alantech/hellodep'..." + Assert has_dependencies + Assert has_alantech + Assert has_hellodep + Assert has_index + Assert has_nested_dependencies + Assert has_nested_alantech + Assert has_nested_hellodep + Assert has_nested_index + Assert has_modules + Assert has_std + Assert has_blacklisted_module + Assert not_has_cmd + Assert has_pkg_block + Assert has_pkg_full_block_applied + End + + It "runs agc" + When run run_agc + The output should eq "Cloning into './dependencies/alantech/hellodep'..." + Assert has_dependencies + Assert has_alantech + Assert has_hellodep + Assert has_index + Assert has_nested_dependencies + Assert has_nested_alantech + Assert has_nested_hellodep + Assert has_nested_index + Assert has_modules + Assert has_std + Assert has_blacklisted_module + Assert not_has_cmd + Assert has_pkg_block + Assert has_pkg_full_block_applied + End + End +End + +Describe "@std/http" + Describe "basic get" + before() { + sourceToAll " + from @std/app import start, print, exit + from @std/http import get + + on start { + print(get('https://raw.githubusercontent.com/alantech/hellodep/aea1ce817a423d00107577a430a046993e4e6cad/index.ln')); + emit exit 0; + } + " + } + BeforeAll before + + after() { + cleanTemp + } + AfterAll after + + It "runs js" + When run test_js + The output should eq "export const comeGetMe = \"You got me!\"" + End + + It "runs agc" + When run test_agc + The output should eq "export const comeGetMe = \"You got me!\"" + End + End + +Describe "basic post" + before() { + # All my homies hate CORS... + node -e "const http = require('http'); http.createServer((req, res) => { const headers = { 'Access-Control-Allow-Origin': '*','Access-Control-Allow-Methods': 'OPTIONS, POST, GET, PUT','Access-Control-Max-Age': 2592000, 'Access-Control-Allow-Headers': '*', }; if (req.method === 'OPTIONS') { res.writeHead(204, headers); res.end(); return; } res.writeHead(200, headers); req.pipe(res); req.on('end', () => res.end()); }).listen(8765)" 1>/dev/null 2>/dev/null & + ECHO_PID=$! + disown $ECHO_PID + sourceToAll " + from @std/app import start, print, exit + from @std/http import post + + on start { + print(post('http://localhost:8765', '{\"test\":\"test\"}')); + emit exit 0; + } + " + } + BeforeAll before + + after() { + kill -9 $ECHO_PID 1>/dev/null 2>/dev/null || true + cleanTemp + } + AfterAll after + + It "runs js" + When run test_js + The output should eq "{\"test\":\"test\"}" + End + + It "runs agc" + When run test_agc + The output should eq "{\"test\":\"test\"}" + End +End + + Describe "fetch directly" + before() { + sourceToAll " + from @std/app import start, print, exit + from @std/http import fetch, Request, Response + + on start { + const res = fetch(new Request { + method: 'GET', + url: 'https://raw.githubusercontent.com/alantech/hellodep/aea1ce817a423d00107577a430a046993e4e6cad/index.ln', + headers: newHashMap('User-Agent', 'Alanlang'), + body: '', + }); + print(res.isOk()); + const r = res.getOrExit(); + print(r.status); + print(r.headers.length()); + print(r.body); + emit exit 0; + } + " + } + BeforeAll before + + after() { + cleanTemp + } + AfterAll after + + # The number of headers returned in the two runtimes is slightly different. Node includes the + # "connection: close" header and Hyper.rs does not + FETCHJSOUTPUT="true +200 +25 +export const comeGetMe = \"You got me!\"" + + FETCHAGCOUTPUT="true +200 +23 +export const comeGetMe = \"You got me!\"" + + It "runs js" + When run test_js + The output should eq "$FETCHJSOUTPUT" + End + + It "runs agc" + When run test_agc + The output should eq "$FETCHAGCOUTPUT" + End + End + + Describe "Hello World webserver" + before() { + sourceToAll " + from @std/app import start, exit + from @std/httpserver import connection, body, send, Connection + + on connection fn (conn: Connection) { + const req = conn.req; + const res = conn.res; + set(res.headers, 'Content-Type', 'text/plain'); + if req.method == 'GET' { + res.body('Hello, World!').send(); + } else { + res.body('Hello, Failure!').send(); + } + } + " + } + BeforeAll before + + after() { + cleanTemp + } + AfterAll after + + afterEach() { + kill $PID + wait $PID 2>/dev/null + return 0 + } + After afterEach + + It "runs js" + node test_$$/temp.js 1>/dev/null 2>/dev/null & + PID=$! + sleep 1 + When run curl -s localhost:8000 + The output should eq "Hello, World!" + End + + It "runs agc" + alan run test_$$/temp.agc 1>/dev/null 2>/dev/null & + PID=$! + sleep 1 + When run curl -s localhost:8000 + The output should eq "Hello, World!" + End + End + + Describe "importing http get doesn't break hashmap get" + before() { + sourceToAll " + from @std/app import start, print, exit + from @std/http import get + + on start { + const str = get('https://raw.githubusercontent.com/alantech/hellodep/aea1ce817a423d00107577a430a046993e4e6cad/index.ln').getOr(''); + const kv = str.split(' = '); + const key = kv[0] || 'bad'; + const val = kv[1] || 'bad'; + const hm = newHashMap(key, val); + hm.get(key).getOr('failed').print(); + hm.get('something else').getOr('correct').print(); + emit exit 0; + } + " + } + BeforeAll before + + after() { + cleanTemp + } + AfterAll after + + GETGETOUTPUT="\"You got me!\" + +correct" + + It "runs js" + When run test_js + The output should eq "${GETGETOUTPUT}" + End + + It "runs agc" + When run test_agc + The output should eq "${GETGETOUTPUT}" + End + End + + Describe "Double-send in a single connection doesn't crash" + before() { + sourceToAll " + from @std/app import print, exit + from @std/httpserver import connection, Connection, body, send + + on connection fn (conn: Connection) { + const res = conn.res; + const firstMessage = res.body('First Message').send(); + print(firstMessage); + const secondMessage = res.body('Second Message').send(); + print(secondMessage); + wait(1000); + emit exit 0; + } + " + } + BeforeAll before + + after() { + cleanTemp + } + AfterAll after + + It "runs js" + node test_$$/temp.js 1>./out.txt 2>/dev/null & + sleep 1 + When run curl -s localhost:8000 + The output should eq "First Message" + End + + It "response from js" + When run cat ./out.txt + The output should eq "HTTP server listening on port 8000 +ok +connection not found" + rm out.txt + End + + It "runs agc" + sleep 2 + alan run test_$$/temp.agc 1>./out.txt 2>/dev/null & + sleep 1 + When run curl -s localhost:8000 + The output should eq "First Message" + End + + It "response from agc" + When run cat ./out.txt + The output should eq "HTTP server listening on port 8000 +ok +cannot call send twice for the same connection" + rm out.txt + End + End + +End +*/ + +// Clone + +test!(clone => r#" + from @std/app import start, print, exit + + on start { + let a = 3; + let b = a.clone(); + a = 4; + print(a); + print(b); + let c = [1, 2, 3]; + let d = c.clone(); + d.set(0, 2); + c.map(fn (val: int): string = val.toString()).join(', ').print(); + d.map(fn (val: int): string = val.toString()).join(', ').print(); + emit exit 0; + }"#; + stdout "4\n3\n1, 2, 3\n2, 2, 3\n"; +); + +// Runtime Error + +test!(get_or_exit => r#" + from @std/app import start, print, exit + + on start { + const xs = [0, 1, 2, 5]; + const x1 = xs[1].getOrExit(); + print(x1); + const x2 = xs[2].getOrExit(); + print(x2); + const x5 = xs[5].getOrExit(); + print(x5); + + emit exit 0; + }"#; + status 1; +); + +/* It's not known *if* @std/datastore will be restored or what changes there will be needed with + * the new focus, so just copying the tests for it directly to keep or drop eventually + * + +Describe "@std/datastore" + Describe "distributed kv" + before() { + sourceToAll " + from @std/app import start, print, exit + from @std/datastore import namespace, has, set, del, getOr + + on start { + const ns = namespace('foo'); + print(ns.has('bar')); + ns.set('bar', 'baz'); + print(ns.has('bar')); + print(ns.getOr('bar', '')); + ns.del('bar'); + print(ns.has('bar')); + print(ns.getOr('bar', '')); + + ns.set('inc', 0); + emit waitAndInc 100; + emit waitAndInc 200; + emit waitAndInc 300; + } + + event waitAndInc: int64 + + on waitAndInc fn (ms: int64) { + wait(ms); + let i = namespace('foo').getOr('inc', 0); + i = i + 1 || 0; + print(i); + namespace('foo').set('inc', i); + if i == 3 { + emit exit 0; + } + } + " + } + BeforeAll before + + after() { + cleanTemp + } + AfterAll after + + DSOUTPUT="false +true +baz +false + +1 +2 +3" + + It "runs js" + When run test_js + The output should eq "$DSOUTPUT" + End + + It "runs agc" + When run test_agc + The output should eq "$DSOUTPUT" + End + End + + Describe "distributed compute" + before() { + sourceToAll " + from @std/app import start, print, exit + from @std/datastore import namespace, set, ref, mut, with, run, mutOnly, closure, getOr + + on start { + // Initial setup + const ns = namespace('foo'); + ns.set('foo', 'bar'); + + // Basic remote execution + const baz = ns.ref('foo').run(fn (foo: string) = foo.length()); + print(baz); + + // Closure-based remote execution + let bar = 'bar'; + const bay = ns.ref('foo').closure(fn (foo: string): int64 { + bar = 'foobar: ' + foo + bar; + return foo.length(); + }); + print(bay); + print(bar); + + // Constrained-closure that only gets the 'with' variable + const bax = ns.ref('foo').with(bar).run(fn (foo: string, bar: string): int64 = #foo +. #bar); + print(bax); + + // Mutable closure + const baw = ns.mut('foo').run(fn (foo: string): int64 { + foo = foo + 'bar'; + return foo.length(); + }); + print(baw); + + // Mutable closure that affects the foo variable + const bav = ns.mut('foo').closure(fn (foo: string): int64 { + foo = foo + 'bar'; + bar = bar * foo.length(); + return bar.length(); + }); + print(bav); + print(bar); + + // Constrained mutable closure that affects the foo variable + const bau = ns.mut('foo').with(bar).run(fn (foo: string, bar: string): int64 { + foo = foo * #bar; + return foo.length(); + }); + print(bau); + + // 'Pure' function that only does mutation + ns.mut('foo').mutOnly(fn (foo: string) { + foo = foo + foo; + }); + print(ns.getOr('foo', 'not found')); + + // Constrained 'pure' function that only does mutation + ns.mut('foo').with(bar).mutOnly(fn (foo: string, bar: string) { + foo = foo + bar; + }); + print(ns.getOr('foo', 'not found')); + + emit exit 0; + } + " + } + BeforeAll before + + after() { + cleanTemp + } + AfterAll after + + DCOUTPUT="3 +3 +foobar: barbar +17 +6 +126 +foobar: barbarfoobar: barbarfoobar: barbarfoobar: barbarfoobar: barbarfoobar: barbarfoobar: barbarfoobar: barbarfoobar: barbar +1134 +barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar +barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarfoobar: barbarfoobar: barbarfoobar: barbarfoobar: barbarfoobar: barbarfoobar: barbarfoobar: barbarfoobar: barbarfoobar: barbar" + + It "runs js" + When run test_js + The output should eq "$DCOUTPUT" + End + + It "runs agc" + When run test_agc + The output should eq "$DCOUTPUT" + End + End + +End +*/ + +// @std/seq + +test!(seq_and_next => r#" + from @std/app import start, print, exit + from @std/seq import seq, next + + on start { + let s = seq(2); + print(s.next()); + print(s.next()); + print(s.next()); + emit exit 0; + }"#; + stdout "0\n1\nerror: sequence out-of-bounds\n"; +); +test!(seq_each => r#" + from @std/app import start, print, exit + from @std/seq import seq, each + + on start { + let s = seq(3); + s.each(fn (i: int64) = print(i)); + emit exit 0; + }"#; + stdout "0\n1\n2\n"; +); +test!(seq_while => r#" + from @std/app import start, print, exit + from @std/seq import seq, while + + on start { + let s = seq(100); + let sum = 0; + s.while(fn = sum < 10, fn { + sum = sum + 1 || 0; + }); + print(sum); + emit exit 0; + }"#; + stdout "10\n"; +); +test!(seq_do_while => r#" + from @std/app import start, print, exit + from @std/seq import seq, doWhile + + on start { + let s = seq(100); + let sum = 0; + // TODO: Get automatic type inference working on anonymous multi-line functions + s.doWhile(fn (): bool { + sum = sum + 1 || 0; + return sum < 10; + }); + print(sum); + emit exit 0; + }"#; + stdout "10\n"; +); +test!(seq_recurse => r#" + from @std/app import start, print, exit + from @std/seq import seq, Self, recurse + + on start { + print(seq(100).recurse(fn fibonacci(self: Self, i: int64): Result { + if i < 2 { + return ok(1); + } else { + const prev = self.recurse(i - 1 || 0); + const prevPrev = self.recurse(i - 2 || 0); + if prev.isErr() { + return prev; + } + if prevPrev.isErr() { + return prevPrev; + } + // TODO: Get type inference inside of recurse working so we don't need to unwrap these + return (prev || 0) + (prevPrev || 0); + } + }, 8)); + emit exit 0; + }"#; + stdout "34\n"; +); +test!(seq_no_op_one_liner_regression_test => r#" + import @std/app + from @std/seq import seq, Self, recurse + + fn doNothing(x: int) : int = x; + + fn doNothingRec(x: int) : int = seq(x).recurse(fn (self: Self, x: int) : Result { + return ok(x); + }, x) || 0; + + on app.start { + const x = 5; + app.print(doNothing(x)); // 5 + app.print(doNothingRec(x)); // 5 + + const xs = [1, 2, 3]; + app.print(xs.map(doNothing).map(toString).join(' ')); // 1 2 3 + app.print(xs.map(doNothingRec).map(toString).join(' ')); // 1 2 3 + + emit app.exit 0; + }"#; + stdout "5\n5\n1 2 3\n1 2 3\n"; // TODO: Do we keep a regression test for a prior iteration? +); +test!(seq_recurse_decrement_regression_test => r#" + import @std/app + from @std/seq import seq, Self, recurse + + fn triangularRec(x: int) : int = seq(x + 1 || 0).recurse(fn (self: Self, x: int) : Result { + if x == 0 { + return ok(x); + } else { + // TODO: Get type inference inside of recurse working so we don't need to unwrap these + return x + (self.recurse(x - 1 || 0) || 0); + } + }, x) || 0 + + on app.start { + const xs = [1, 2, 3]; + app.print(xs.map(triangularRec).map(toString).join(' ')); // 1 3 6 + + emit app.exit 0; + }"#; + stdout "1 3 6\n"; // TODO: Same concern, do regression tests matter for a different codebase? +); + +// Tree + +test!(tree_construction_and_access => r#" + from @std/app import start, print, exit + + on start { + const myTree = newTree('foo'); + const barNode = myTree.addChild('bar'); + const bazNode = myTree.addChild('baz'); + const bayNode = barNode.addChild('bay'); + + print(myTree.getRootNode() || 'wrong'); + print(bayNode.getParent() || 'wrong'); + print(myTree.getChildren().map(fn (c: Node): string = c || 'wrong').join(', ')); + + emit exit 0; + }"#; + stdout "foo\nbar\nbar, baz\n"; +); +test!(tree_user_defined_types => r#" + from @std/app import start, print, exit + + type Foo { + foo: string, + bar: bool, + } + + on start { + const myTree = newTree(new Foo { + foo: 'myFoo', + bar: false, + }); + const wrongFoo = new Foo { + foo: 'wrongFoo', + bar: false, + }; + const myFoo = myTree.getRootNode() || wrongFoo; + print(myFoo.foo); + emit exit 0; + }"#; + stdout "myFoo\n"; +); +test!(tree_every_find_some_reduce_prune => r#" + from @std/app import start, print, exit + + on start { + const myTree = newTree('foo'); + const barNode = myTree.addChild('bar'); + const bazNode = myTree.addChild('baz'); + const bayNode = barNode.addChild('bay'); + + print(myTree.every(fn (c: Node): bool = (c || 'wrong').length() == 3)); + print(myTree.some(fn (c: Node): bool = (c || 'wrong').length() == 1)); + print(myTree.find(fn (c: Node): bool = (c || 'wrong') == 'bay').getOr('wrong')); + print(myTree.find(fn (c: Node): bool = (c || 'wrong') == 'asf').getOr('wrong')); + + print(myTree.length()); + myTree.getChildren().eachLin(fn (c: Node) { + const n = c || 'wrong'; + if n == 'bar' { + c.prune(); + } + }); + print(myTree.getChildren().map(fn (c: Node): string = c || 'wrong').join(', ')); + print(myTree.length()); + + myTree.reduce(fn (acc: int, i: Node): int = (i || 'wrong').length() + acc || 0, 0).print(); + emit exit 0; + }"#; + stdout r#"true +false +bay +wrong +4 +baz +2 +6 +"#; +); +test!(subtree_and_nested_tree_construction => r#" + from @std/app import start, print, exit + + on start { + const bigNestedTree = newTree('foo') + .addChild('bar') + .getTree() + .addChild(newTree('baz') + .addChild('quux') + .getTree() + ).getTree(); + + const mySubtree = bigNestedTree + .getRootNode() + .getChildren()[1] + .getOr(newTree('what').getRootNode()) + .toSubtree(); + + print(bigNestedTree.getRootNode() || 'wrong'); + print(mySubtree.getRootNode() || 'wrong'); + + emit exit 0; + }"#; + stdout "foo\nbaz\n"; +); + +// Error printing + +test!(eprint => r#" + from @std/app import start, eprint, exit + on start { + eprint('This is an error'); + emit exit 0; + }"#; + stderr "This is an error\n"; +); +test!(stderr_event => r#" + from @std/app import start, stderr, exit + on start { + emit stderr 'This is an error'; + wait(10); + emit exit 0; + }"#; + stderr "This is an error"; +); + +// @std/cmd + +test!(cmd_exec => r#" + import @std/app + import @std/cmd + + on app.start { + const executionResult: cmd.ExecRes = cmd.exec('echo 1'); + app.print(executionResult.stdout); + emit app.exit 0; + }"#; + stdout "1\n"; +); +test!(cmd_sequential => r#" + from @std/app import start, print, exit + from @std/cmd import exec + + on start { + exec('touch test.txt'); + exec('echo foo >> test.txt'); + exec('echo bar >> test.txt'); + exec('cat test.txt').stdout.print(); + exec('rm test.txt'); + + emit exit 0; + }"#; + stdout "foobar\n"; +); + +/* TODO: Module import testing once the test macros are improved + +Describe "Module imports" + Describe "can import with trailing whitespace" + before() { + sourceToFile piece.ln " + export type Piece { + owner: bool, + } + " + sourceToAll " + from @std/app import start, print, exit + // Intentionally put an extra space after the import + from ./piece import Piece + + on start { + const piece = new Piece { + owner: false, + }; + print('Hello, World!'); + if piece.owner == true { + print('OK'); + } else { + print('False'); + } + emit exit 0; + } + " + } + BeforeAll before + + after() { + cleanTemp + } + AfterAll after + + It "runs js" + When run test_js + The output should eq "Hello, World! +False" + End + + It "runs agc" + When run test_agc + The output should eq "Hello, World! +False" + End + End +End +*/ + +// JSON + +test!(json_construction_printing => r#" + from @std/app import start, print, exit + from @std/json import JSON, toJSON, toString, JSONBase, JSONNode, IsObject, Null + + on start { + 1.0.toJSON().print(); + true.toJSON().print(); + 'Hello, JSON!'.toJSON().print(); + [1.0, 2.0, 5.0].toJSON().print(); + toJSON().print(); + + emit exit 0; + }"#; + stdout r#"1 +true +"Hello, JSON!" +[1, 2, 5] +null +"#; +); +test!(json_complex_construction => r#" + from @std/app import start, print, exit + from @std/json import JSON, toString, JSONBase, JSONNode, IsObject, Null, newJSONObject, newJSONArray, addKeyVal, push + + on start { + newJSONObject() + .addKeyVal('mixed', 'values') + .addKeyVal('work', true) + .addKeyVal('even', newJSONArray() + .push(4.0) + .push('arrays')) + .print(); + + emit exit 0; + }"#; + stdout r#"{"mixed": "values", "work": true, "even": [4, "arrays"]}""#; +); + +/* TODO: Support the tcp server tests + +Describe "@std/tcp" + Describe "webserver tunnel test" + before() { + sourceToTemp " + from @std/tcpserver import tcpConn + from @std/tcp import TcpChannel, connect, addContext, ready, chunk, TcpContext, read, write, tcpClose, close + + on tcpConn fn (channel: TcpChannel) { + const tunnel = connect('localhost', 8088); + channel.addContext(tunnel); + tunnel.addContext(channel); + channel.ready(); + tunnel.ready(); + } + + on chunk fn (ctx: TcpContext) { + ctx.context.write(ctx.channel.read()); + } + + on tcpClose fn (ctx: TcpContext) { + ctx.context.close(); + } + " + tempToAmm + tempToJs + sourceToFile test_server.js " + const http = require('http') + + http.createServer((req, res) => res.end('Hello, World!')).listen(8088) + " + } + BeforeAll before + + after() { + cleanTemp + } + AfterAll after + + afterEach() { + kill $PID1 + wait $PID1 2>/dev/null + kill $PID2 + wait $PID2 2>/dev/null + return 0 + } + After afterEach + + It "runs js" + node test_$$/test_server.js 1>/dev/null 2>/dev/null & + PID1=$! + node test_$$/temp.js 1>/dev/null 2>/dev/null & + PID2=$! + sleep 1 + When run curl -s localhost:8000 + The output should eq "Hello, World!" + End + End + + Describe "webserver tunnel function test" + before() { + sourceToAll " + from @std/tcpserver import tunnel + from @std/app import start, print + + on start { + let connected = tunnel(8088); + print(connected ? 'Tunneling to 8088' : 'Failed to establish a tunnel'); + } + " + sourceToFile test_server.js " + const http = require('http') + + http.createServer((req, res) => res.end('Hello, World!')).listen(8088) + " + } + BeforeAll before + + after() { + cleanTemp + } + AfterAll after + + afterEach() { + kill $PID1 + wait $PID1 2>/dev/null + kill $PID2 + wait $PID2 2>/dev/null + return 0 + } + After afterEach + + It "runs js" + node test_$$/test_server.js 1>/dev/null 2>/dev/null & + PID1=$! + node test_$$/temp.js 1>/dev/null 2>/dev/null & + PID2=$! + sleep 1 + When run curl -s localhost:8000 + The output should eq "Hello, World!" + End + + It "runs agc" + node test_$$/test_server.js 1>/dev/null 2>/dev/null & + PID1=$! + alan run test_$$/temp.agc 1>/dev/null 2>/dev/null & + PID2=$! + sleep 1 + When run curl -s localhost:8000 + The output should eq "Hello, World!" + End + End +End +*/ + +// Saturating Math + +test!(int8_sadd => r#" + from @std/app import start, exit + on start { emit exit sadd(toInt8(1), toInt8(2)); }"#; + status 3; +); +test!(int8_ssub => r#" + from @std/app import start, exit + on start { emit exit ssub(toInt8(2), toInt8(1)); }"#; + status 1; +); +test!(int8_smul => r#" + from @std/app import start, exit + on start { emit exit smul(toInt8(2), toInt8(1)); }"#; + status 2; +); +test!(int8_sdiv => r#" + from @std/app import start, exit + on start { emit exit sdiv(toInt8(6), toInt8(0)); }"#; + status 127; +); +test!(int8_spow => r#" + from @std/app import start, exit + on start { emit exit spow(toInt8(6), toInt8(2)); }"#; + status 36; +); +test!(int16_sadd => r#" + from @std/app import start, print, exit + on start { + print(sadd(toInt16(1), toInt16(2))); + emit exit 0; + }"#; + stdout "3\n"; +); +test!(int16_ssub => r#" + from @std/app import start, print, exit + on start { + print(ssub(toInt16(2), toInt16(1))); + emit exit 0; + }"#; + stdout "1\n"; +); +test!(int16_smul => r#" + from @std/app import start, print, exit + on start { + print(smul(toInt16(2), toInt16(1))); + emit exit 0; + }"#; + stdout "2\n"; +); +test!(int16_sdiv => r#" + from @std/app import start, print, exit + on start { + print(sdiv(toInt16(6), toInt16(2))); + emit exit 0; + }"#; + stdout "3\n"; +); +test!(int16_spow => r#" + from @std/app import start, print, exit + on start { + print(spow(toInt16(6), toInt16(2))); + emit exit 0; + }"#; + stdout "36\n"; +); +test!(int32_sadd => r#" + from @std/app import start, print, exit + on start { + sadd(1.toInt32(), 2.toInt32()).print(); + emit exit 0; + }"#; + stdout "3\n"; +); +test!(int32_ssub => r#" + from @std/app import start, print, exit + on start { + ssub(2.toInt32(), 1.toInt32()).print(); + emit exit 0; + }"#; + stdout "1\n"; +); +test!(int32_smul => r#" + from @std/app import start, print, exit + on start { + smul(2.toInt32(), 1.toInt32()).print(); + emit exit 0; + }"#; + stdout "2\n"; +); +test!(int32_sdiv => r#" + from @std/app import start, print, exit + on start { + sdiv(6.toInt32(), 2.toInt32()).print(); + emit exit 0; + }"#; + stdout "3\n"; +); +test!(int32_spow => r#" + from @std/app import start, print, exit + on start { + spow(6.toInt32(), 2.toInt32()).print(); + emit exit 0; + }"#; + stdout "36\n"; +); +test!(int64_sadd => r#" + from @std/app import start, print, exit + on start { + print(1 +. 2); + emit exit 0; + }"#; + stdout "3\n"; +); +test!(int64_ssub => r#" + from @std/app import start, print, exit + on start { + print(2 -. 1); + emit exit 0; + }"#; + stdout "1\n"; +); +test!(int64_smul => r#" + from @std/app import start, print, exit + on start { + print(2 *. 1); + emit exit 0; + }"#; + stdout "2\n"; +); +test!(int64_sdiv => r#" + from @std/app import start, print, exit + on start { + print(6 /. 2); + emit exit 0; + }"#; + stdout "3\n"; +); +test!(int64_spow => r#" + from @std/app import start, print, exit + on start { + print(6 **. 2); + emit exit 0; + }"#; + stdout "36\n"; +); +test!(float32_sadd => r#" + from @std/app import start, print, exit + on start { + print(toFloat32(1) +. toFloat32(2)); + emit exit 0; + }"#; + stdout "3\n"; +); +test!(float32_ssub => r#" + from @std/app import start, print, exit + on start { + print(toFloat32(2) -. toFloat32(1)); + emit exit 0; + }"#; + stdout "1\n"; +); +test!(float32_smul => r#" + from @std/app import start, print, exit + on start { + print(toFloat32(2) *. toFloat32(1)); + emit exit 0; + }"#; + stdout "2\n"; +); +test!(float32_sdiv => r#" + from @std/app import start, print, exit + on start { + print(toFloat32(6) /. toFloat32(2)); + emit exit 0; + }"#; + stdout "3\n"; +); +test!(float32_spow => r#" + from @std/app import start, print, exit + on start { + print(toFloat32(6) **. toFloat32(2)); + emit exit 0; + }"#; + stdout "36\n"; +); +test!(float64_sadd => r#" + from @std/app import start, print, exit + on start { + (1.0 +. 2.0).print(); + emit exit 0; + }"#; + stdout "3\n"; +); +test!(float64_ssub => r#" + from @std/app import start, print, exit + on start { + (2.0 -. 1.0).print(); + emit exit 0; + }"#; + stdout "1\n"; +); +test!(float64_smul => r#" + from @std/app import start, print, exit + on start { + (2.0 *. 1.0).print(); + emit exit 0; + }"#; + stdout "2\n"; +); +test!(float64_sdiv => r#" + from @std/app import start, print, exit + on start { + (6.0 /. 2.0).print(); + emit exit 0; + }"#; + stdout "3\n"; +); +test!(float64_spow => r#" + from @std/app import start, print, exit + on start { + (6.0 **. 2.0).print(); + emit exit 0; + }"#; + stdout "36\n"; +); diff --git a/src/lntors.rs b/src/lntors.rs new file mode 100644 index 000000000..df4b12c38 --- /dev/null +++ b/src/lntors.rs @@ -0,0 +1,128 @@ +// TODO: Use the Program type to load up the code and all of the relevant data structures, then +// start manipulating them to produce Rust code. Because of the borrow checker, making +// idiomatic-looking Rust from Alan may be tough, so let's start off with something like the old +// lntoamm and just generate a crap-ton of simple statements with auto-generated variable names and +// let LLVM optimize it all away. + +use crate::program::Program; +use crate::parse::{Statement, WithOperators, BaseAssignable, Constants}; + +pub fn lntors(entry_file: String) -> Result> { + // TODO: Support things beyond the "Hello, World" example + let mut out = "".to_string(); + let program = Program::new(entry_file)?; + // Assuming a single scope for now + let scope = match program.scopes_by_file.get(&program.entry_file.clone()) { + Some((_, _, s)) => s, + None => { + return Err("Somehow didn't find a scope for the entry file!?".into()); + } + }; + // Assuming the 'start' handler has been defined + let start = match scope.handlers.get("start") { + Some(h) => h, + None => { + return Err("Entry file has no handlers. This is not yet supported.".into()); + } + }; + // A handler without a function should be impossible, so this part, at least, shouldn't change + let func = match scope.functions.get(&start.functionname.clone()) { + Some(f) => f, + None => { + return Err("A handler has been found without a function definition. This should be impossible.".into()); + } + }; + // The `start` handler takes no arguments and returns no value + assert_eq!(func.args.len(), 0); + assert_eq!(func.rettype, None); + // Assertion proven, start emitting the `start` handler as a `main` function + out = "fn main() {\n".to_string(); + for statement in &func.statements { + // TODO: Need a proper root scope to define these mappings better, and a statement to + // "microstatement" function to encapsulate all of the logic (and dynamic precedence logic + // to construct a tree to depth-first traverse) For now, we're gonna wing it to have + // something here. + let mut stmt = "".to_string(); + match statement { + Statement::A(_) => { continue; }, + Statement::Assignables(assignable) => { + for assignable_or_operator in assignable.assignables.iter() { + match assignable_or_operator { + WithOperators::BaseAssignableList(baseassignablelist) => { + for (i, baseassignable) in baseassignablelist.iter().enumerate() { + match baseassignable { + BaseAssignable::Variable(var) => { + // The behavior of a variable depends on if there's + // anything following after it. Many things following are + // invalid syntax, but `FnCall` and `MethodSep` are valid + let next = baseassignablelist.get(i + 1); + if let Some(otherbase) = next { + if match otherbase { + BaseAssignable::FnCall(_) => true, + BaseAssignable::MethodSep(_) => false, // TODO + _ => false + } { + // TODO: Real function name lookup goes here + match var.as_str() { + "print" => { + stmt = format!("{}println!", stmt).to_string(); + }, + _ => { + return Err(format!("Function {} not found", var).into()); + } + } + } else { + return Err(format!("Invalid syntax after {}", var).into()); + } + } else { + // It's just a variable, return it as-is + stmt = format!("{}{}", stmt, var); + } + }, + BaseAssignable::FnCall(call) => { + // TODO: This should be properly recursive, just going to + // hardwire grabbing the constant from within it for now + let arg = &call.assignablelist[0][0]; + let txt = match arg { + WithOperators::BaseAssignableList(l) => { + match &l[0] { + BaseAssignable::Constants(c) => { + match c { + Constants::Strn(s) => s, + _ => { + return Err("Unsupported constant type".into()); + } + } + }, + _ => { + return Err("Unsupported argument type".into()); + } + } + }, + _ => { + return Err("Unsupported argument type".into()); + } + }; + stmt = format!("{}({})", stmt, txt); + }, + _ => { + return Err("Unsupported assignable type".into()); + } + } + } + }, + _ => { + return Err("Operators currently unsupported".into()); + } + } + } + }, + _ => { + return Err("Unsupported statement".into()); + } + } + out = format!("{} {};\n", out, stmt); + } + out = format!("{}\n}}", out); + Ok(out) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 000000000..38464bbb7 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,54 @@ +use clap::{Parser, Subcommand}; +use program::Program; +use compile::compile; + +mod compile; +mod lntors; +mod parse; +mod program; + +#[derive(Parser, Debug)] +#[command(author, version, about, propagate_version = true)] +struct Cli { + #[command(subcommand)] + commands: Option, + + #[arg(value_name = "LN_FILE", help = ".ln source file to interpret")] + file: Option, +} + +#[derive(Subcommand, Debug)] +enum Commands { + #[command(about = "Compile .ln file(s) to an executable")] + Compile { + #[arg( + value_name = "LN_FILE", + help = ".ln source file to compile.", + default_value = "./index.ln" + )] + file: String, + }, + #[command(about = "Install dependencies for your Alan project")] + Install { + #[arg( + value_name = "DEP_FILE", + help = "The .ln install script to run and install the necessary dependencies into /dependences", + default_value = "./.dependencies.ln" + )] + file: String, + }, +} + +fn main() -> Result<(), Box> { + let args = Cli::parse(); + if let Some(file) = args.file { + let program = Program::new(file)?; + println!("{:?}", program); + Ok(()) + } else { + match &args.commands { + Some(Commands::Compile { file }) => Ok(compile(file.to_string())?), + _ => Err("Command not yet supported".into()), + } + } +} diff --git a/src/parse.rs b/src/parse.rs new file mode 100644 index 000000000..d7056a73d --- /dev/null +++ b/src/parse.rs @@ -0,0 +1,1483 @@ +use std::path::PathBuf; + +use nom::{ + branch::alt, + bytes::complete::{tag, take}, + character::complete::satisfy, + combinator::{all_consuming, opt, peek, recognize}, + error::{Error, ErrorKind}, + multi::{many0, many1, separated_list0, separated_list1}, + sequence::{delimited, tuple}, + IResult, +}; + +/// Macros to make building nom functions nicer (for me). For now they always make everything +/// public for easier usage of this file. Also `#[derive(Debug)]` is added to all structs and enums + +/// The `build` macro provides the function wrapper and naming for the function in question +macro_rules! build { + ( $name:ident, $body:expr $(,)? ) => { + pub fn $name(input: &str) -> IResult<&str, &str> { + $body(input) + } + }; + ( $name:ident: $ret:ty, $body:expr $(,)? ) => { + pub fn $name(input: &str) -> IResult<&str, $ret> { + $body(input) + } + }; +} + +/// The `token` macro matches an exact string +macro_rules! token { + ( $str:expr ) => { + tag($str) + }; +} + +/// The `not` macro matches anything except the string in question for the same length as the +/// string. It behaves *very differently* to nom's `is_not` function, which is more like an +/// inverse `charset` +macro_rules! not { + ( $str:expr ) => { + (|input| match tag::<&str, &str, Error<&str>>($str)(input) { + Ok(_) => Err(nom::Err::Error(Error::new(input, ErrorKind::Fail))), + Err(_) => take($str.len())(input), + }) + }; +} + +/// The `one_or_more` macro matches one or more instances of a given rule, returning the whole +/// string matched +macro_rules! one_or_more { + ( $rule:expr ) => { + recognize(many1($rule)) + }; +} + +/// The `zero_or_more` macro matches zero or more instances of a given rule, returning a string, +/// even if empty +macro_rules! zero_or_more { + ( $rule:expr ) => { + recognize(many0($rule)) + }; +} + +/// The `zero_or_one` macro matches the given rule zero or one time, returning a string, +/// potentially empty +macro_rules! zero_or_one { + ( $rule:expr ) => { + recognize(opt($rule)) + }; +} + +/// The `or` macro matches one of the given rules in a tuple, returning the first match +macro_rules! or { + ( $($rule:expr),+ $(,)? ) => {alt(($($rule,)+))} +} + +/// The `and` macro matches all of the given rules in a tuple, returning them all as a single +/// string +macro_rules! and { + ( $($rule:expr),+ $(,)? ) => {recognize(tuple(($($rule,)+)))} +} + +/// The `charset` macro matches a single character in the given character set. Multiple such sets +/// can be concatenated in the same charset with normal `|` syntax. Eg `'_' | 'a'..='z'` +macro_rules! charset { + ( $ranges:pat ) => { + recognize(satisfy(|c| match c { + $ranges => true, + _ => false, + })) + }; +} + +/// The `named_and` macro matches all of the given rules just like `and`, but it also defines a +/// struct and field names for these matches. For simplicity, the `str`s are copied into `String`s +/// Because defining a struct needs to happen in the top-level of the source file, this macro +/// implicitly `build`s itself and cannot be wrapped like the other macros can. +macro_rules! named_and { + ( $fn_name:ident: $struct_name:ident => $( $field_name:ident: $field_type:ty as $rule:expr ),+ $(,)? ) => { + #[derive(Debug, PartialEq, Clone)] + pub struct $struct_name { + $(pub $field_name: $field_type,)+ + } + + pub fn $fn_name(input: &str) -> IResult<&str, $struct_name> { + let mut i = input; + let out = $struct_name { + $( $field_name: { + let res = $rule(i)?; + i = res.0; + res.1.into() + },)+ + }; + return Ok((i, out)); + } + } +} + +/// The `named_or` macro is similar to the above, but constructs an enum instead of a struct. It +/// also cannot be used with `build`. This variant is much more useful when you want to know *what* +/// kind of match you're dealing with, and considering Rust has a built-in `match` syntax, I am +/// surprised that I can't find something like this in `nom` already? +macro_rules! named_or { + ( $fn_name:ident: $enum_name:ident => $( $option_name:ident: $option_type:ty as $rule:expr ),+ $(,)? ) => { + #[derive(Debug, PartialEq, Clone)] + pub enum $enum_name { + $($option_name($option_type),)+ + } + + pub fn $fn_name(input: &str) -> IResult<&str, $enum_name> { + $(if let Ok((i, val)) = $rule(input) { + return Ok((i, $enum_name::$option_name(val.into()))); + })+ + // Reaching this point is an error. For now, just return a generic one + Err(nom::Err::Error(Error::new(input, ErrorKind::Fail))) + } + } +} + +/// `left_subset` matches if the first argument (the left side) matches, but the second argument +/// (the right side) does not, when viewing these operations like a venn diagram. `and` would be +/// the intersection of the two sets, `or` is all three sections of the two sets, `xor` would be +/// the first and third sections, and `left_subset` is just the first section. (A `right_subset` +/// would be redundant since you can just swap the order of the arguments, and the first and second +/// sections just reduces down to checking the left side only so there's no need for such an +/// operation) +macro_rules! left_subset { + ( $left:expr, $right:expr $(,)? ) => { + (|input| { + let left_match = $left(input)?; + let right_match = $right(left_match.1); + if let Ok((i, _)) = right_match { + if i.len() == 0 { + return Err(nom::Err::Error(Error::new(input, ErrorKind::Fail))); + } + } + return Ok(left_match); + }) + }; +} + +/// `list` didn't exist in the prior code, but there's a nice primitive in `nom` that makes this +/// easier (and I can avoid complicating the typing of the other primitives for now) +macro_rules! list { + // Special path for Strings because str/String split is annoying + ( $fn_name:ident: String => $rule:expr, $sep:expr $(,)? ) => { + pub fn $fn_name(input: &str) -> IResult<&str, Vec> { + let res = separated_list1($sep, $rule)(input)?; + Ok((res.0, res.1.iter().map(|s| s.to_string()).collect())) + } + }; + // Normal path + ( $fn_name:ident: $type:ty => $rule:expr, $sep:expr $(,)? ) => { + pub fn $fn_name(input: &str) -> IResult<&str, Vec<$type>> { + separated_list1($sep, $rule)(input) + } + }; + // Path for no separators + ( $fn_name: ident: $type:ty => $rule:expr $(,)? ) => { + pub fn $fn_name(input: &str) -> IResult<&str, Vec<$type>> { + many1($rule)(input) + } + }; + // Normal path where an empty vector is fine + ( opt $fn_name:ident: $type:ty => $rule:expr, $sep:expr $(,)? ) => { + pub fn $fn_name(input: &str) -> IResult<&str, Vec<$type>> { + separated_list0($sep, $rule)(input) + } + }; + // Path for no separators where an empty vector is fine + ( opt $fn_name: ident: $type:ty => $rule:expr $(,)? ) => { + pub fn $fn_name(input: &str) -> IResult<&str, Vec<$type>> { + many0($rule)(input) + } + }; +} + +/// Because `opt` returns an `Option<&str>` for &str sources, but you can't easily put that inside +/// of structs and enums, this macro handles that situation +macro_rules! opt_string { + ( $rule:expr ) => { + opt(|input| match $rule(input) { + Ok((i, o)) => Ok((i, o.to_string())), + Err(e) => Err(e), + }) + }; +} + +/// If I'm gonna have in-file unit tests, let's have them as absolutely close as possible to the +/// relevant functions, yeah? This macro makes a mod and a unit test a single statement. The +/// following macros help make super brief unit tests. Usage: +/// test!(parser_function => +/// fail "input that causes failure", +/// pass "input that causes success", +/// pass "another successful input" => "the new input it generates", "the output it generates", +/// ); +macro_rules! test { + ( $rule:ident => $( $type:ident $test_val:expr $(=> $i:expr $(, $o:expr)?)? );+ $(;)? ) => { + #[cfg(test)] + mod $rule { + #[test] + fn $rule() { + let cmd = |input| super::$rule(input); + $( $type!(cmd, $test_val $(, $i $(, $o)?)?); )+ + } + } + } +} +#[cfg(test)] +macro_rules! pass { + ( $cmd:ident, $input:expr ) => { + match $cmd($input) { + Err(_) => panic!("Did not parse {} correctly!", $input), + Ok(_) => {} + } + }; + ( $cmd:ident, $input:expr, $i:expr, $o:expr) => { + match $cmd($input) { + Err(_) => panic!("Did not parse {} correctly!", $input), + Ok((i, o)) => { + assert_eq!(i, $i); + assert_eq!(o, $o); + } + } + }; + ( $cmd:ident, $input:expr, $i:expr) => { + match $cmd($input) { + Err(_) => panic!("Did not parse {} correctly!", $input), + Ok((i, _)) => { + assert_eq!(i, $i); + } + } + }; +} +#[cfg(test)] +macro_rules! fail { + ( $cmd:ident, $input:expr ) => { + match $cmd($input) { + Ok(_) => panic!("Unexpectedly parsed the input! {}", $input), + Err(_) => {} + } + }; +} + +// Begin defining the nom functions to parse Alan code. This is pretty dense code. If you read it +// top-to-bottom, it mostly follows a leaf-to-root ordering of nodes (but some cycles exist in this +// grammar, so it's not just a simple DAG). You may want to scroll to the bottom of the file and +// start from the `get_ast` function to see how it all comes together conceptually. + +build!(space, token!(" ")); +// There won't be a test case for *every* token function, just validating they work as expected +test!(space => + fail ""; + fail "a"; + pass " " => "", " "; + pass " " => " ", " "; + pass " "; +); +// Similarly validating one_or_more behaves as expected here +build!(blank, one_or_more!(space)); +test!(blank => + fail ""; + fail "a"; + pass " " => "", " "; + pass " " => "", " "; + pass " a" => "a", " "; +); +// And validating zero_or_one here +build!(optblank, zero_or_one!(blank)); +test!(optblank => + pass "" => "", ""; + pass " " => "", " "; + pass " a" => "a", " "; + pass "a" => "a", ""; +); +// Validating or +build!(newline, or!(token!("\n"), token!("\r"))); +test!(newline => + fail ""; + fail " "; + pass "\n" => "", "\n"; + pass "\r" => "", "\r"; + pass "\r\n" => "\n", "\r"; +); +// Validating not +build!(notnewline, not!("\n")); // TODO: Properly support windows newlines here +test!(notnewline => + fail "\n"; + pass " " => "", " "; + pass " " => " ", " "; +); +// Validating and +build!( + singlelinecomment, + and!(token!("//"), zero_or_more!(notnewline), newline) +); +test!(singlelinecomment => + fail ""; + fail "/"; + fail "//"; + pass "//\n"; + pass "// This is a comment\n" => "", "// This is a comment\n"; +); +build!(star, token!("*")); +build!(notstar, not!("*")); +build!(notslash, not!("/")); +// Adding a test here just because of the complexity +build!( + multilinecomment, + and!( + token!("/*"), + zero_or_more!(or!(notstar, and!(star, peek(notslash)))), + token!("*/"), + ) +); +test!(multilinecomment => + fail ""; + pass "/**/" => "", "/**/"; + pass "/*\n This is a basic multi-line comment.\n*/"; + pass "/***\n * This is a multi-line comment.\n */"; + pass "/***\n * This is a multi-line comment with a standard style we now support.\n **/"; +); +build!( + whitespace, + one_or_more!(or!(space, newline, singlelinecomment, multilinecomment)) +); +build!(optwhitespace, zero_or_one!(whitespace)); +build!(colon, token!(":")); +build!(under, token!("_")); +build!(negate, token!("-")); +build!(dot, token!(".")); +build!(par, token!("..")); +build!(eq, token!("=")); +build!(openparen, token!("(")); +build!(closeparen, token!(")")); +build!(opencurly, token!("{")); +build!(closecurly, token!("}")); +build!(opencaret, token!("<")); +build!(closecaret, token!(">")); +build!(openarr, token!("[")); +build!(closearr, token!("]")); +build!(comma, token!(",")); +build!(semicolon, token!(";")); +build!(optsemicolon, zero_or_one!(semicolon)); +build!(at, token!("@")); +build!(slash, token!("/")); +// Validating charset +build!(base10, charset!('0'..='9')); +test!(base10 => + fail ""; + fail "a"; + pass "0" => "", "0"; + pass "00" => "0", "0"; +); +build!(natural, one_or_more!(base10)); +build!(integer, and!(zero_or_one!(negate), natural)); +build!(real, and!(integer, dot, natural)); +// Validating named_or +named_or!(num: Number => RealNum: String as real, IntNum: String as integer); +test!(num => + fail ""; + fail "a"; + pass "0" => "", super::Number::IntNum("0".to_string()); + pass "0.5" => "", super::Number::RealNum("0.5".to_string()); + pass "-5" => "", super::Number::IntNum("-5".to_string()); + pass "-5.5" => "", super::Number::RealNum("-5.5".to_string()); +); +build!(t, token!("true")); +build!(f, token!("false")); +build!(booln, or!(t, f)); +build!(lower, charset!('a'..='z')); +build!(upper, charset!('A'..='Z')); +// Validating left_subset +build!( + variable, + left_subset!( + and!( + one_or_more!(or!(under, lower, upper)), + zero_or_more!(or!(under, lower, upper, base10)) + ), + booln, + ) +); +test!(variable => + fail ""; + fail "123abc"; + fail "true"; + fail "false"; + pass "falsetto"; + pass "_123abc"; + pass "variable after_variable" => " after_variable", "variable"; +); +build!( + operators, + one_or_more!(or!( + token!("+"), + token!("-"), + token!("/"), + token!("\\"), + token!("*"), + token!("^"), + token!("."), + token!("~"), + token!("`"), + token!("!"), + token!("@"), + token!("#"), + token!("$"), + token!("%"), + token!("&"), + token!("|"), + token!(":"), + token!("<"), + token!(">"), + token!("?"), + token!("="), + )) +); +build!(interface, token!("interface")); +build!(new, token!("new")); +build!(ifn, token!("if")); +build!(elsen, token!("else")); +build!(precedence, token!("precedence")); +build!(infix, token!("infix")); +build!(prefix, token!("prefix")); +build!(asn, token!("as")); +build!(exit, token!("return")); // Why did I do this before? +build!(emit, token!("emit")); +build!(letn, token!("let")); +build!(constn, token!("const")); +build!(on, token!("on")); +build!(event, token!("event")); +build!(export, token!("export")); +build!(typen, token!("type")); +build!(import, token!("import")); +build!(from, token!("from")); +build!(fnn, token!("fn")); +build!(binds, token!("binds")); +build!(quote, token!("'")); +build!(doublequote, token!("\"")); +build!(escapequote, token!("\\'")); +build!(escapedoublequote, token!("\\\"")); +build!(notquote, not!("'")); +build!(notdoublequote, not!("\"")); +// This is used a lot, so let's test it +build!(sep, and!(optwhitespace, comma, optwhitespace)); +test!(sep => + fail ""; + pass ","; + pass " ,"; + pass " ,"; + pass " , "; + pass "\n,\n"; + pass ",something" => "something", ","; +); +build!(optsep, zero_or_one!(sep)); +// Also complex, let's check it +build!( + strn, + or!( + and!(quote, zero_or_more!(or!(escapequote, notquote)), quote), + and!( + doublequote, + zero_or_more!(or!(escapedoublequote, notdoublequote)), + doublequote + ), + ) +); +test!(strn => + fail "bare text"; + pass "'str'"; + pass "\"str2\""; + pass "'str\\'3'"; + pass "\"str\\\"4\""; +); +build!(arrayaccess: Vec, delimited(and!(openarr, optwhitespace), assignables, and!(optwhitespace, closearr))); +named_or!(varsegment: VarSegment => + Variable: String as variable, + MethodSep: String as and!(optwhitespace, dot, optwhitespace), + ArrayAccess: Vec as arrayaccess, +); +build!(var, one_or_more!(varsegment)); +test!(var => + pass "hm.lookup"; +); +named_or!(varop: VarOp => Variable: String as variable, Operator: String as operators); +// Validating named_and +named_and!(renamed: Renamed => + a: String as blank, + asn: String as asn, + b: String as blank, + varop: VarOp as varop, +); +test!(renamed => + fail ""; + fail "as"; + fail " as "; + pass " as foo" => "", super::Renamed{ + a: " ".to_string(), + asn: "as".to_string(), + b: " ".to_string(), + varop: super::VarOp::Variable("foo".to_string()) + }; + pass " as +"; + pass " as foo bar" => " bar", super::Renamed{ + a: " ".to_string(), + asn: "as".to_string(), + b: " ".to_string(), + varop: super::VarOp::Variable("foo".to_string()) + }; +); +// Validate optional fields +named_and!(renameablevar: RenameableVar => varop: VarOp as varop, optrenamed: Option as opt(renamed)); +test!(renameablevar => + fail ""; + pass "foo" => "", super::RenameableVar{ + varop: super::VarOp::Variable("foo".to_string()), + optrenamed: None, + }; + pass "foo as bar" => "", super::RenameableVar{ + varop: super::VarOp::Variable("foo".to_string()), + optrenamed: Some(super::Renamed{ + a: " ".to_string(), + asn: "as".to_string(), + b: " ".to_string(), + varop: super::VarOp::Variable("bar".to_string()) + }) + }; +); +// Validating list +list!(varlist: RenameableVar => renameablevar, sep); +test!(varlist => + fail ""; + pass "foo" => "", vec![super::RenameableVar{ + varop: super::VarOp::Variable("foo".to_string()), + optrenamed: None, + }]; + pass "foo, bar" => "", vec![ + super::RenameableVar{ + varop: super::VarOp::Variable("foo".to_string()), + optrenamed: None, + }, + super::RenameableVar{ + varop: super::VarOp::Variable("bar".to_string()), + optrenamed: None, + } + ]; +); +list!(depsegments: String => variable, slash); +named_and!(curdir: CurDir => + dot: String as dot, + slash: String as slash, + depsegments: Vec as depsegments, +); +impl CurDir { + fn to_string(&self) -> String { + format!("./{}", self.depsegments.join("/")) + } +} +named_and!(pardir: ParDir => + par: String as par, + slash: String as slash, + depsegments: Vec as depsegments, +); +impl ParDir { + fn to_string(&self) -> String { + format!("../{}", self.depsegments.join("/")) + } +} +named_or!(localdependency: LocalDependency => CurDir: CurDir as curdir, ParDir: ParDir as pardir); +impl LocalDependency { + fn to_string(&self) -> String { + match &self { + LocalDependency::CurDir(c) => c.to_string(), + LocalDependency::ParDir(p) => p.to_string(), + } + } +} +named_and!(globaldependency: GlobalDependency => + at: String as at, + depsegments: Vec as depsegments, +); +impl GlobalDependency { + fn to_string(&self) -> String { + format!("@{}", self.depsegments.join("/")) + } +} +// This one is kinda complex, so let's take a look at it +named_or!(dependency: Dependency => + Local: LocalDependency as localdependency, + Global: GlobalDependency as globaldependency, +); +test!(dependency => + fail ""; + fail "foo"; + pass "./foo" => "", super::Dependency::Local(super::LocalDependency::CurDir(super::CurDir{ + dot: ".".to_string(), + slash: "/".to_string(), + depsegments: vec!["foo".to_string()], + })); + pass "./foo/bar"; + pass "../foo"; + pass "../foo/bar"; + pass "@foo"; + pass "@foo/bar"; +); +impl Dependency { + pub fn resolve(&self, curr_file: String) -> Result> { + match &self { + Dependency::Local(l) => { + let path = PathBuf::from(curr_file) + .parent() + .unwrap() + .join(l.to_string()) + .canonicalize()?; + Ok(path.to_string_lossy().to_string()) + } + Dependency::Global(g) => { + let path = PathBuf::from("./dependencies") + .join(g.depsegments.join("/")) + .canonicalize()?; + Ok(path.to_string_lossy().to_string()) + } + } + } + + pub fn varname(&self) -> Result> { + match &self { + Dependency::Local(l) => match PathBuf::from(l.to_string()).file_stem() { + Some(pb) => Ok(pb.to_string_lossy().to_string()), + None => Err("Invalid path".into()), + }, + Dependency::Global(g) => { + match PathBuf::from("./dependencies") + .join(g.depsegments.join("/")) + .canonicalize()? + .file_stem() + { + Some(pb) => Ok(pb.to_string_lossy().to_string()), + None => Err("Invalid path".into()), + } + } + } + } +} +named_and!(standardimport: StandardImport => + import: String as import, + a: String as blank, + dependency: Dependency as dependency, + renamed: Option as opt(renamed), + b: String as optblank, + c: String as newline, + d: String as optwhitespace, +); +named_and!(fromimport: FromImport => + from: String as from, + a: String as blank, + dependency: Dependency as dependency, + b: String as blank, + import: String as import, + c: String as blank, + varlist: Vec as varlist, + d: String as optblank, + e: String as newline, + f: String as optwhitespace, +); +named_or!(importstatement: ImportStatement => + Standard: StandardImport as standardimport, + From: FromImport as fromimport, +); +list!(opt imports: ImportStatement => importstatement); +// Function aliases don't seem to exist in Rust, so just redefining this, it's the same as 'var' +build!(typename, one_or_more!(varsegment)); +named_and!(typegenerics: TypeGenerics => + a: String as opencaret, + b: String as optwhitespace, + generics: Vec as generics, + c: String as optwhitespace, + d: String as closecaret, +); +named_and!(fulltypename: FullTypename => + typename: String as typename, // TODO: Maybe we want to keep this in a tree form in the future? + opttypegenerics: Option as opt(typegenerics) +); +test!(fulltypename => + pass "Array>" => ""; +); +impl FullTypename { + pub fn to_string(&self) -> String { + format!( + "{}{}", + self.typename, + match &self.opttypegenerics { + None => "".to_string(), + Some(g) => format!( + "<{}>", + g.generics + .iter() + .map(|gen| gen.to_string()) + .collect::>() + .join(", ") + ), + } + ) + .to_string() + } +} +list!(generics: FullTypename => fulltypename, sep); +named_and!(typeline: Typeline => + variable: String as variable, + a: String as optwhitespace, + colon: String as colon, + b: String as optwhitespace, + fulltypename: FullTypename as fulltypename, +); +list!(typelist: Typeline => typeline, sep); +named_and!(typebody: TypeBody => + a: String as opencurly, + b: String as optwhitespace, + typelist: Vec as typelist, + c: String as optsep, + d: String as optwhitespace, + e: String as closecurly, +); +test!(typebody => + pass "{\n arr: Array,\n initial: U,\n}"; +); +named_and!(typealias: TypeAlias => + a: String as eq, + b: String as blank, + fulltypename: FullTypename as fulltypename, +); +named_and!(typebind: TypeBind => + binds: String as binds, + a: String as blank, + rusttypename: FullTypename as fulltypename, // TODO: 100% correct Rust type declaration +); +named_or!(typedef: TypeDef => + TypeBody: TypeBody as typebody, + TypeAlias: TypeAlias as typealias, + TypeBind: TypeBind as typebind, +); +named_and!(types: Types => + a: String as typen, + b: String as blank, + fulltypename: FullTypename as fulltypename, + c: String as optwhitespace, + typedef: TypeDef as typedef, +); +test!(types => + pass "type Foo {\n bar: String,\n}"; + pass "type Foo = Bar"; + pass "type Result binds Result"; +); +named_or!(constants: Constants => + Bool: String as booln, + Num: Number as num, + Strn: String as strn, +); +named_or!(baseassignable: BaseAssignable => + ObjectLiterals: ObjectLiterals as objectliterals, + Functions: Functions as functions, + FnCall: FnCall as fncall, + Variable: String as variable, + Constants: Constants as constants, + MethodSep: String as and!(optwhitespace, dot, optwhitespace), +); +test!(baseassignable => + pass "new Foo{}" => "", super::BaseAssignable::ObjectLiterals(super::ObjectLiterals::TypeLiteral(super::TypeLiteral{ + literaldec: super::LiteralDec{ + new: "new".to_string(), + a: " ".to_string(), + fulltypename: super::FullTypename{ + typename: "Foo".to_string(), + opttypegenerics: None, + }, + b: "".to_string(), + }, + typebase: super::TypeBase{ + opencurly: "{".to_string(), + a: "".to_string(), + typeassignlist: Vec::new(), + b: "".to_string(), + c: "".to_string(), + closecurly: "}".to_string(), + } + })); +); +list!(baseassignablelist: BaseAssignable => baseassignable); +test!(baseassignablelist => + pass "new Foo{}" => "", vec![super::BaseAssignable::ObjectLiterals(super::ObjectLiterals::TypeLiteral(super::TypeLiteral{ + literaldec: super::LiteralDec{ + new: "new".to_string(), + a: " ".to_string(), + fulltypename: super::FullTypename{ + typename: "Foo".to_string(), + opttypegenerics: None, + }, + b: "".to_string(), + }, + typebase: super::TypeBase{ + opencurly: "{".to_string(), + a: "".to_string(), + typeassignlist: Vec::new(), + b: "".to_string(), + c: "".to_string(), + closecurly: "}".to_string(), + } + }))]; +); +named_or!(withoperators: WithOperators => + BaseAssignableList: Vec as baseassignablelist, + Operators: String as and!(optwhitespace, operators, optwhitespace), +); +test!(withoperators => + pass "new Foo{}" => "", super::WithOperators::BaseAssignableList(vec![super::BaseAssignable::ObjectLiterals(super::ObjectLiterals::TypeLiteral(super::TypeLiteral{ + literaldec: super::LiteralDec{ + new: "new".to_string(), + a: " ".to_string(), + fulltypename: super::FullTypename{ + typename: "Foo".to_string(), + opttypegenerics: None, + }, + b: "".to_string(), + }, + typebase: super::TypeBase{ + opencurly: "{".to_string(), + a: "".to_string(), + typeassignlist: Vec::new(), + b: "".to_string(), + c: "".to_string(), + closecurly: "}".to_string(), + } + }))]); + pass "new Array> [ new Array [], ] * lookupLen;" => " * lookupLen;"; +); +list!(assignables: WithOperators => withoperators); +test!(assignables => + pass "maybe.isSome()"; + pass "new Foo{}" => "", vec![super::WithOperators::BaseAssignableList(vec![super::BaseAssignable::ObjectLiterals(super::ObjectLiterals::TypeLiteral(super::TypeLiteral{ + literaldec: super::LiteralDec{ + new: "new".to_string(), + a: " ".to_string(), + fulltypename: super::FullTypename{ + typename: "Foo".to_string(), + opttypegenerics: None, + }, + b: "".to_string(), + }, + typebase: super::TypeBase{ + opencurly: "{".to_string(), + a: "".to_string(), + typeassignlist: Vec::new(), + b: "".to_string(), + c: "".to_string(), + closecurly: "}".to_string(), + } + }))])]; + pass "new InitialReduce {\n arr: arr,\n initial: initial,\n }"; + pass "new Array> [ new Array [], ] * lookupLen;" => ";"; +); +named_and!(typedec: TypeDec => + colon: String as colon, + a: String as optwhitespace, + fulltypename: FullTypename as fulltypename, + b: String as optwhitespace, +); +named_and!(constdeclaration: ConstDeclaration => + constn: String as constn, + whitespace: String as whitespace, + variable: String as variable, + a: String as optwhitespace, + typedec: Option as opt(typedec), + b: String as optwhitespace, + eq: String as eq, + c: String as optwhitespace, + assignables: Vec as assignables, + semicolon: String as semicolon, +); +test!(constdeclaration => + pass "const args = new Foo{};"; + pass "const args = new InitialReduce {\n arr: arr,\n initial: initial,\n };"; +); +named_and!(letdeclaration: LetDeclaration => + letn: String as letn, + whitespace: String as whitespace, + variable: String as variable, + a: String as optwhitespace, + typedec: Option as opt(typedec), + eq: String as eq, + b: String as optwhitespace, + assignables: Vec as assignables, + semicolon: String as semicolon, +); +named_or!(declarations: Declarations => + Const: ConstDeclaration as constdeclaration, + Let: LetDeclaration as letdeclaration, +); +named_and!(assignments: Assignments => + var: String as var, + a: String as optwhitespace, + eq: String as eq, + b: String as optwhitespace, + assignables: Vec as assignables, + semicolon: String as semicolon, +); +test!(assignments => + pass "hm.lookup = new Array> [ new Array [], ] * lookupLen;" => ""; +); +named_and!(retval: RetVal => + assignables: Vec as assignables, + a: String as optwhitespace, +); +build!(optretval: Option, opt(retval)); +named_and!(exits: Exits => + exit: String as exit, + a: String as optwhitespace, + retval: Option as optretval, + semicolon: String as semicolon, +); +test!(exits => + pass "return maybe.getMaybe().toString();"; +); +named_and!(emits: Emits => + emit: String as emit, + a: String as optwhitespace, + eventname: String as var, + b: String as optwhitespace, + retval: Option as optretval, + semicolon: String as semicolon, +); +named_and!(arg: Arg => + variable: String as variable, + a: String as optblank, + colon: String as colon, + b: String as optblank, + fulltypename: FullTypename as fulltypename, +); +list!(opt arglist: Arg => arg, sep); +named_and!(functionbody: FunctionBody => + opencurly: String as opencurly, + a: String as optwhitespace, + statements: Vec as statements, + b: String as optwhitespace, + closecurly:String as closecurly, +); +test!(functionbody => + pass "{ if maybe.isSome() {\n return maybe.getMaybe().toString();\n } else {\n return 'none';\n } }"; + pass "{ const args = new InitialReduce {\n arr: arr,\n initial: initial,\n };\n return foldl(args, cb);\n}"; + pass "{ // TODO: Rust-like fn:: syntax?\n let hm = new HashMap {\n keyVal: new Array> [],\n lookup: new Array> [ new Array [] ] * 128, // 1KB of space\n };\n return hm.set(firstKey, firstVal);\n}" => ""; +); +named_and!(assignfunction: AssignFunction => + eq: String as eq, + a: String as optwhitespace, + assignables: Vec as assignables, + b: String as optsemicolon, +); +named_and!(bindfunction: BindFunction => + binds: String as binds, + a: String as blank, + rustfunc: String as and!(variable, opt(token!("!"))), // TODO: Support methods for a particular type somehow? + b: String as optsemicolon, +); +named_or!(fullfunctionbody: FullFunctionBody => + FunctionBody: FunctionBody as functionbody, + AssignFunction: AssignFunction as assignfunction, + BindFunction: BindFunction as bindfunction, +); +named_and!(args: Args => + openparen: String as openparen, + a: String as optwhitespace, + arglist: Vec as arglist, + b: String as optwhitespace, + closeparen: String as closeparen, + c: String as optwhitespace, +); +named_and!(returntype: ReturnType => + colon: String as colon, + a: String as optwhitespace, + fulltypename: FullTypename as fulltypename, + b: String as optwhitespace, +); +named_and!(functions: Functions => + fnn: String as fnn, + a: String as optwhitespace, + optname: Option as opt_string!(variable), + b: String as optwhitespace, + optargs: Option as opt(args), + optreturntype: Option as opt(returntype), + fullfunctionbody: FullFunctionBody as fullfunctionbody, +); +test!(functions => + pass "fn newHashMap(firstKey: Hashable, firstVal: any): HashMap { // TODO: Rust-like fn:: syntax?\n let hm = new HashMap {\n keyVal: new Array> [],\n lookup: new Array> [ new Array [] ] * 128, // 1KB of space\n };\n return hm.set(firstKey, firstVal);\n}" => ""; + pass "fn foo binds foo;" => ""; + pass "fn print(val: String) binds println!;" => ""; +); +named_or!(blocklike: Blocklike => + Functions: Functions as functions, + FunctionBody: FunctionBody as functionbody, + FnName: String as var, +); +named_or!(condorblock: CondOrBlock => + Conditional: Conditional as conditional, + Blocklike: Blocklike as blocklike, +); +named_and!(elsebranch: ElseBranch => + a: String as whitespace, + elsen: String as elsen, + b: String as optwhitespace, + condorblock: Box as condorblock, +); +named_and!(conditional: Conditional => + ifn: String as ifn, + a: String as whitespace, + assignables: Vec as assignables, + b: String as optwhitespace, + blocklike: Blocklike as blocklike, + optelsebranch: Option as opt(elsebranch), +); +test!(conditional => + pass "if maybe.isSome() {\n return maybe.getMaybe().toString();\n } else {\n return 'none';\n }"; +); +named_and!(assignablestatement: AssignableStatement => + assignables: Vec as assignables, + semicolon: String as semicolon, +); +named_or!(statement: Statement => + Declarations: Declarations as declarations, + Exits: Exits as exits, + Emits: Emits as emits, + Assignments: Assignments as assignments, + Conditional: Conditional as conditional, + Assignables: AssignableStatement as assignablestatement, + A: String as whitespace, +); +test!(statement => + pass "return maybe.getMaybe().toString();"; + pass "if maybe.isSome() {\n return maybe.getMaybe().toString();\n } else {\n return 'none';\n }"; +); +list!(statements: Statement => statement); +test!(statements => + pass "return maybe.getMaybe().toString();"; + pass "if maybe.isSome() {\n return maybe.getMaybe().toString();\n } else {\n return 'none';\n }"; + pass "let hm = new HashMap {\n keyVal: new Array> [],\n lookup: new Array> [ new Array [] ] * 128, // 1KB of space\n };\n return hm.set(firstKey, firstVal);" => ""; +); +named_and!(literaldec: LiteralDec => + new: String as new, + a: String as blank, + fulltypename: FullTypename as fulltypename, + b: String as optblank, +); +test!(literaldec => + pass "new Foo" => "", super::LiteralDec{ + new: "new".to_string(), + a: " ".to_string(), + fulltypename: super::FullTypename{ + typename: "Foo".to_string(), + opttypegenerics: None, + }, + b: "".to_string(), + }; +); +list!(opt assignablelist: Vec => assignables, sep); +named_and!(arraybase: ArrayBase => + openarr: String as openarr, + a: String as optwhitespace, + assignablelist: Vec> as assignablelist, + b: String as optsep, + c: String as optwhitespace, + closearr: String as closearr, +); +named_and!(fullarrayliteral: FullArrayLiteral => + literaldec: LiteralDec as literaldec, + arraybase: ArrayBase as arraybase, +); +named_or!(arrayliteral: ArrayLiteral => + ArrayBase: ArrayBase as arraybase, + FullArrayLiteral: FullArrayLiteral as fullarrayliteral, +); +named_and!(typeassign: TypeAssign => + variable: String as variable, + a: String as optwhitespace, + colon: String as colon, + b: String as optwhitespace, + assignables: Vec as assignables, + c: String as optwhitespace +); +list!(opt typeassignlist: TypeAssign => typeassign, sep); +named_and!(typebase: TypeBase => + opencurly: String as opencurly, + a: String as optwhitespace, + typeassignlist: Vec as typeassignlist, + b: String as optsep, + c: String as optwhitespace, + closecurly: String as closecurly, +); +named_and!(typeliteral: TypeLiteral => + literaldec: LiteralDec as literaldec, + typebase: TypeBase as typebase, +); +test!(typeliteral => + pass "new Foo{}" => "", super::TypeLiteral{ + literaldec: super::LiteralDec{ + new: "new".to_string(), + a: " ".to_string(), + fulltypename: super::FullTypename{ + typename: "Foo".to_string(), + opttypegenerics: None, + }, + b: "".to_string(), + }, + typebase: super::TypeBase{ + opencurly: "{".to_string(), + a: "".to_string(), + typeassignlist: Vec::new(), + b: "".to_string(), + c: "".to_string(), + closecurly: "}".to_string(), + } + }; +); +named_or!(objectliterals: ObjectLiterals => + ArrayLiteral: ArrayLiteral as arrayliteral, + TypeLiteral: TypeLiteral as typeliteral, +); +test!(objectliterals => + pass "new Foo{}" => "", super::ObjectLiterals::TypeLiteral(super::TypeLiteral{ + literaldec: super::LiteralDec{ + new: "new".to_string(), + a: " ".to_string(), + fulltypename: super::FullTypename{ + typename: "Foo".to_string(), + opttypegenerics: None, + }, + b: "".to_string(), + }, + typebase: super::TypeBase{ + opencurly: "{".to_string(), + a: "".to_string(), + typeassignlist: Vec::new(), + b: "".to_string(), + c: "".to_string(), + closecurly: "}".to_string(), + } + }); + pass "new Array> [ new Array [], ]" => ""; +); +named_and!(fncall: FnCall => + openparen: String as openparen, + a: String as optwhitespace, + assignablelist: Vec> as assignablelist, + b: String as optwhitespace, + closeparen: String as closeparen, +); +named_and!(fntoop: FnToOp => + fnname: String as variable, + a: String as blank, + asn: String as asn, + b: String as blank, + operators: String as operators, +); +named_and!(opprecedence: OpPrecedence => + precedence: String as precedence, + blank: String as blank, + num: String as integer, +); +named_or!(fix: Fix => + Prefix: String as prefix, + Infix: String as infix, +); +named_and!(fnopprecedence: FnOpPrecedence => + fntoop: FnToOp as fntoop, + blank: String as blank, + opprecedence: OpPrecedence as opprecedence, +); +named_and!(precedencefnop: PrecedenceFnOp => + opprecedence: OpPrecedence as opprecedence, + blank: String as blank, + fntoop: FnToOp as fntoop, +); +// TODO: Maybe I can drop the enum here and make a unified type +named_or!(opmap: OpMap => + FnOpPrecedence: FnOpPrecedence as fnopprecedence, + PrecedenceFnOp: PrecedenceFnOp as precedencefnop, +); +named_and!(operatormapping: OperatorMapping => + fix: Fix as fix, + blank: String as blank, + opmap: OpMap as opmap, +); +named_and!(events: Events => + event: String as event, + a: String as whitespace, + variable: String as variable, + b: String as optwhitespace, + colon: String as colon, + c: String as optwhitespace, + fulltypename: FullTypename as fulltypename, +); +named_and!(propertytypeline: PropertyTypeline => + variable: String as variable, + a: String as blank, + colon: String as colon, + b: String as blank, + fulltypename: FullTypename as fulltypename, +); +named_and!(leftarg: LeftArg => + leftarg: FullTypename as fulltypename, + whitespace: String as whitespace, +); +named_and!(operatortypeline: OperatorTypeline => + optleftarg: Option as opt(leftarg), + operators: String as operators, + whitespace: String as whitespace, + rightarg: FullTypename as fulltypename, + a: String as optwhitespace, + colon: String as colon, + b: String as optwhitespace, + fulltypename: FullTypename as fulltypename, +); +list!(opt argtypelist: FullTypename => fulltypename, sep); +test!(argtypelist => + pass "" => "", Vec::new(); + pass "foo" => "", vec![super::FullTypename{ + typename: "foo".to_string(), + opttypegenerics: None, + }]; + pass "foo, bar" => "", vec![super::FullTypename{ + typename: "foo".to_string(), + opttypegenerics: None, + }, super::FullTypename{ + typename: "bar".to_string(), + opttypegenerics: None, + }]; +); +named_and!(functiontype: FunctionType => + openparen: String as openparen, + a: String as optwhitespace, + argtypelist: Vec as argtypelist, + optsep: String as optsep, + b: String as optwhitespace, + closeparen: String as closeparen, + c: String as optwhitespace, + colon: String as colon, + d: String as optwhitespace, + returntype: FullTypename as fulltypename, +); +test!(functiontype => + fail "()"; + pass "():void"; + pass "(Foo): Bar"; + pass "(Foo, Bar): Baz"; +); +named_and!(functiontypeline: FunctionTypeline => + variable: String as variable, + a: String as optblank, + functiontype: FunctionType as functiontype, +); +test!(functiontypeline => + pass "toString(Stringifiable): string," => ",", super::FunctionTypeline{ + variable: "toString".to_string(), + a: "".to_string(), + functiontype: super::FunctionType{ + openparen: "(".to_string(), + a: "".to_string(), + argtypelist: vec![super::FullTypename{ + typename: "Stringifiable".to_string(), + opttypegenerics: None, + }], + optsep: "".to_string(), + b: "".to_string(), + closeparen: ")".to_string(), + c: "".to_string(), + colon: ":".to_string(), + d: " ".to_string(), + returntype: super::FullTypename{ + typename: "string".to_string(), + opttypegenerics: None, + } + } + }; +); +named_or!(interfaceline: InterfaceLine => + FunctionTypeline: FunctionTypeline as functiontypeline, + OperatorTypeline: OperatorTypeline as operatortypeline, + PropertyTypeline: PropertyTypeline as propertytypeline, +); +test!(interfaceline => + pass "toString(Stringifiable): string," => ",", super::InterfaceLine::FunctionTypeline(super::FunctionTypeline{ + variable: "toString".to_string(), + a: "".to_string(), + functiontype: super::FunctionType{ + openparen: "(".to_string(), + a: "".to_string(), + argtypelist: vec![super::FullTypename{ + typename: "Stringifiable".to_string(), + opttypegenerics: None, + }], + optsep: "".to_string(), + b: "".to_string(), + closeparen: ")".to_string(), + c: "".to_string(), + colon: ":".to_string(), + d: " ".to_string(), + returntype: super::FullTypename{ + typename: "string".to_string(), + opttypegenerics: None, + } + } + }); +); +list!(opt interfacelist: InterfaceLine => interfaceline, sep); +test!(interfacelist => + pass "toString(Stringifiable): string," => ",", vec![super::InterfaceLine::FunctionTypeline(super::FunctionTypeline{ + variable: "toString".to_string(), + a: "".to_string(), + functiontype: super::FunctionType{ + openparen: "(".to_string(), + a: "".to_string(), + argtypelist: vec![super::FullTypename{ + typename: "Stringifiable".to_string(), + opttypegenerics: None, + }], + optsep: "".to_string(), + b: "".to_string(), + closeparen: ")".to_string(), + c: "".to_string(), + colon: ":".to_string(), + d: " ".to_string(), + returntype: super::FullTypename{ + typename: "string".to_string(), + opttypegenerics: None, + } + } + })]; +); +named_and!(interfacebody: InterfaceBody => + opencurly: String as opencurly, + a: String as optwhitespace, + interfacelist: Vec as interfacelist, + b: String as optsep, + c: String as optwhitespace, + closecurly: String as closecurly, +); +named_and!(interfacealias: InterfaceAlias => + eq: String as eq, + blank: String as blank, + variable: String as variable, +); +named_or!(interfacedef: InterfaceDef => + InterfaceBody: InterfaceBody as interfacebody, + InterfaceAlias: InterfaceAlias as interfacealias, +); +named_and!(interfaces: Interfaces => + interface: String as interface, + a: String as optblank, + variable: String as variable, + b: String as optblank, + interfacedef: InterfaceDef as interfacedef, +); +test!(interfaces => + pass "interface any {}"; + pass "interface anythingElse = any"; + pass "interface Stringifiable {\ntoString(Stringifiable): string,\n}"; +); +named_or!(exportable: Exportable => + Functions: Functions as functions, + ConstDeclaration: ConstDeclaration as constdeclaration, + Types: Types as types, + Intefaces: Interfaces as interfaces, + OperatorMapping: OperatorMapping as operatormapping, + Events: Events as events, + Ref: String as variable, +); +named_and!(exports: Exports => + export: String as export, + a: String as blank, + exportable: Exportable as exportable, +); +test!(exports => + pass "export fn newHashMap(firstKey: Hashable, firstVal: any): HashMap { // TODO: Rust-like fn:: syntax?\n let hm = new HashMap {\n keyVal: new Array> [],\n lookup: new Array> [ new Array [] ] * 128, // 1KB of space\n };\n return hm.set(firstKey, firstVal);\n}" => ""; +); +named_or!(handler: Handler => + Functions: Functions as functions, + FunctionBody: FunctionBody as functionbody, + FnName: String as variable, +); +named_and!(handlers: Handlers => + on: String as on, + a: String as whitespace, + eventname: String as var, + b: String as whitespace, + handler: Handler as handler, +); +named_or!(rootelements: RootElements => + Whitespace: String as whitespace, + Exports: Exports as exports, + Handlers: Handlers as handlers, + Functions: Functions as functions, + Types: Types as types, + ConstDeclaration: ConstDeclaration as constdeclaration, + OperatorMapping: OperatorMapping as operatormapping, + Events: Events as events, + Interfaces: Interfaces as interfaces, +); +list!(opt body: RootElements => rootelements); +named_and!(ln: Ln => + a: String as optwhitespace, + imports: Vec as imports, + body: Vec as body, +); +test!(ln => + pass ""; + pass " " => "", super::Ln{ + a: " ".to_string(), + imports: Vec::new(), + body: Vec::new(), + }; + pass "import ./foo" => "import ./foo", super::Ln{ + a: "".to_string(), + imports: Vec::new(), + body: Vec::new(), + }; + pass "import ./foo\n" => "", super::Ln{ + a: "".to_string(), + imports: vec![super::ImportStatement::Standard(super::StandardImport{ + import: "import".to_string(), + a: " ".to_string(), + dependency: super::Dependency::Local(super::LocalDependency::CurDir(super::CurDir{ + dot: ".".to_string(), + slash: "/".to_string(), + depsegments: vec!["foo".to_string()], + })), + renamed: None, + b: "".to_string(), + c: "\n".to_string(), + d: "".to_string() + })], + body: Vec::new(), + }; +); + +pub fn get_ast(input: &str) -> Result>> { + // We wrap the `ln` root parser in `all_consuming` to cause an error if there's unexpected + // cruft at the end of the input, which we consider a syntax error at compile time. An LSP + // would probably use `ln` directly, instead, so new lines/functions/etc the user is currently + // writing don't trip things up. + match all_consuming(ln)(input) { + Ok((_, out)) => Ok(out), + Err(e) => Err(e), + } +} +// TODO: Modify the test macro to allow for functions without nom-like signatures to have +// assertions +test!(get_ast => + pass ""; + pass " "; + fail "import ./foo"; + pass "import ./foo\n"; + pass "export fn main {\n print('Hello, World!');\n}"; +); diff --git a/src/program.rs b/src/program.rs new file mode 100644 index 000000000..8d62608a2 --- /dev/null +++ b/src/program.rs @@ -0,0 +1,321 @@ +use std::fs::read_to_string; +use std::pin::Pin; + +use crate::parse; + +use ordered_hash_map::OrderedHashMap; + +// This data structure should allow file-level reloading, which we can probably use as a rough +// approximation for iterative recompliation and language server support, and since Rust is fast, +// this might just be "good enough" assuming non-insane source file sizes. +#[derive(Debug)] +pub struct Program { + pub entry_file: String, + pub scopes_by_file: OrderedHashMap>, parse::Ln, Scope)>, +} + +impl Program { + pub fn new(entry_file: String) -> Result> { + let mut p = Program { + entry_file: entry_file.clone(), + scopes_by_file: OrderedHashMap::new(), + }; + p = match p.load(entry_file) { + Ok(p) => p, + Err(_) => { + return Err("Failed to load entry file".into()); + } + }; + Ok(p) + } + + pub fn load(self, path: String) -> Result> { + let ln_src = read_to_string(&path)?; + let (mut p, tuple) = Scope::from_src(self, &path, ln_src)?; + p.scopes_by_file.insert(path, tuple); + Ok(p) + } +} + +#[derive(Debug)] +pub struct Scope { + pub imports: OrderedHashMap, + pub types: OrderedHashMap, + pub consts: OrderedHashMap, + pub functions: OrderedHashMap, + pub handlers: OrderedHashMap, + // TODO: Implement these other concepts + // operatormappings: OrderedHashMap, + // events: OrderedHashMap, + // interfaces: OrderedHashMap, + // exports: Scope, + // Should we include something for documentation? Maybe testing? +} + +impl Scope { + fn from_src( + program: Program, + path: &String, + src: String, + ) -> Result<(Program, (Pin>, parse::Ln, Scope)), Box> { + let txt = Box::pin(src); + let txt_ptr: *const str = &**txt; + // *How* would this move, anyways? But TODO: See if there's a way to handle this safely + let ast = unsafe { parse::get_ast(&*txt_ptr)? }; + let mut s = Scope { + imports: OrderedHashMap::new(), + types: OrderedHashMap::new(), + consts: OrderedHashMap::new(), + functions: OrderedHashMap::new(), + handlers: OrderedHashMap::new(), + }; + let mut p = program; + for i in ast.imports.iter() { + p = Import::from_ast(p, path.clone(), &mut s, i)?; + } + for element in ast.body.iter() { + match element { + parse::RootElements::Types(t) => Type::from_ast(&mut s, t)?, + parse::RootElements::Handlers(h) => Handler::from_ast(&mut s, h)?, + parse::RootElements::Functions(f) => Function::from_ast(&mut s, f)?, + parse::RootElements::ConstDeclaration(c) => Const::from_ast(&mut s, c)?, + parse::RootElements::Whitespace(_) => { /* Do nothing */ }, + _ => println!("TODO"), + } + } + //program.scopes_by_file.insert(path, (txt, ast, s)); + Ok((p, (txt, ast, s))) + //Ok(()) + } +} + +// import ./foo +// import ./foo as bar +// from ./foo import bar +// from ./foo import bar as baz + +#[derive(Debug)] +pub enum ImportType { + Standard(String), + Fields(Vec<(String, String)>), +} + +#[derive(Debug)] +pub struct Import { + pub source_scope_name: String, + pub import_type: ImportType, +} + +impl Import { + fn from_ast( + program: Program, + path: String, + scope: &mut Scope, + import_ast: &parse::ImportStatement, + ) -> Result> { + match &import_ast { + parse::ImportStatement::Standard(s) => { + // First, get the path for the code + let ln_file = s.dependency.resolve(path)?; + let exists = match &program.scopes_by_file.get(&ln_file) { + Some(_) => true, + None => false, + }; + let mut p = program; + if !exists { + // Need to load this file into the program first + p = p.load(ln_file.clone())?; + } + let i = Import { + source_scope_name: ln_file.clone(), + import_type: ImportType::Standard(ln_file.clone()), + }; + scope.imports.insert(ln_file, i); + Ok(p) + } + parse::ImportStatement::From(_f) => { + // TODO + Ok(program) + } + } + } +} + +#[derive(Debug)] +pub enum TypeType { + Structlike(parse::TypeBody), + Alias(parse::FullTypename), + Bind(parse::FullTypename), +} + +#[derive(Debug)] +pub struct Type { + pub typename: parse::FullTypename, + pub typetype: TypeType, +} + +impl Type { + fn from_ast( + scope: &mut Scope, + type_ast: &parse::Types, + ) -> Result<(), Box> { + let t = Type { + typename: type_ast.fulltypename.clone(), + typetype: match &type_ast.typedef { + parse::TypeDef::TypeBody(body) => TypeType::Structlike(body.clone()), + parse::TypeDef::TypeAlias(alias) => TypeType::Alias(alias.fulltypename.clone()), + parse::TypeDef::TypeBind(bind) => TypeType::Bind(bind.rusttypename.clone()), + }, + }; + scope.types.insert(t.typename.to_string(), t); + Ok(()) + } +} + +#[derive(Debug)] +pub struct Const { + pub name: String, + pub typename: Option, + pub assignables: Vec, +} + +impl Const { + fn from_ast( + scope: &mut Scope, + const_ast: &parse::ConstDeclaration, + ) -> Result<(), Box> { + let name = const_ast.variable.clone(); + let typename = match &const_ast.typedec { + Some(typedec) => Some(typedec.fulltypename.to_string()), + None => None, + }; + let assignables = const_ast.assignables.clone(); + let c = Const { + name, + typename, + assignables, + }; + scope.consts.insert(c.name.clone(), c); + Ok(()) + } +} + +#[derive(Debug)] +pub struct Function { + pub name: String, + pub args: Vec<(String, String)>, // Making everything Stringly-typed kinda sucks, but no good way to give an error message in the parser for unknown types otherwise + pub rettype: Option, + pub statements: Vec, // TODO: Do we need to wrap this, or is the AST fine here? + pub bind: Option, +} + +impl Function { + fn from_ast( + scope: &mut Scope, + function_ast: &parse::Functions, + ) -> Result<(), Box> { + // In the top-level of a file, all functions *must* be named + let name = match &function_ast.optname { + Some(name) => name.clone(), + None => { + return Err("Top-level function without a name!".into()); + } + }; + Function::from_ast_with_name(scope, function_ast, name) + } + + fn from_ast_with_name( + scope: &mut Scope, + function_ast: &parse::Functions, + name: String, + ) -> Result<(), Box> { + let args = match &function_ast.optargs { + None => Vec::new(), + Some(arglist) => { + // TODO: Make the arg types optional + arglist + .arglist + .iter() + .map(|arg| (arg.variable.clone(), arg.fulltypename.to_string())) + .collect() + } + }; + let rettype = match &function_ast.optreturntype { + None => None, + Some(returntype) => Some(returntype.fulltypename.to_string()), + }; + let statements = match &function_ast.fullfunctionbody { + parse::FullFunctionBody::FunctionBody(body) => body.statements.clone(), + parse::FullFunctionBody::AssignFunction(assign) => { + vec![parse::Statement::Assignables(parse::AssignableStatement { + assignables: assign.assignables.clone(), + semicolon: ";".to_string(), + })] + }, + parse::FullFunctionBody::BindFunction(_) => Vec::new(), + }; + let bind = match &function_ast.fullfunctionbody { + parse::FullFunctionBody::BindFunction(b) => Some(b.rustfunc.clone()), + _ => None, + }; + let function = Function { + name, + args, + rettype, + statements, + bind, + }; + scope.functions.insert(function.name.clone(), function); + Ok(()) + } +} + +#[derive(Debug)] +pub struct Handler { + pub eventname: String, + pub functionname: String, +} + +impl Handler { + fn from_ast( + scope: &mut Scope, + handler_ast: &parse::Handlers, + ) -> Result<(), Box> { + let functionname = match &handler_ast.handler { + parse::Handler::Functions(function) => { + // Inline defined function possibly with a name, grab the name and shove it into the + // function list for this scope, otherwise + let name = match &function.optname { + Some(name) => name.clone(), + None => format!(":::on:::{}", &handler_ast.eventname).to_string(), // Impossible for users to write, so no collisions ever + }; + let _ = Function::from_ast_with_name(scope, function, name.clone()); + name + } + parse::Handler::FnName(name) => name.clone(), + // This is the *only* place where a function body can just be passed in without the + // "proper" `fn` declaration prior (at least right now), so just keeping the weird + // Function object initialization in here instead of as a new method on the Function + // type. + parse::Handler::FunctionBody(body) => { + let name = format!(":::on:::{}", &handler_ast.eventname).to_string(); + let function = Function { + name: name.clone(), + args: Vec::new(), + rettype: None, + statements: body.statements.clone(), + bind: None, + }; + scope.functions.insert(name.clone(), function); + name + }, + // TODO: Should you be allowed to bind a Rust function as a handler directly? + }; + let h = Handler { + eventname: handler_ast.eventname.clone(), + functionname, + }; + scope.handlers.insert(h.eventname.clone(), h); + Ok(()) + } +} diff --git a/std/README.md b/src/std/README.md similarity index 77% rename from std/README.md rename to src/std/README.md index 9c916df24..2da28211a 100644 --- a/std/README.md +++ b/src/std/README.md @@ -4,7 +4,7 @@ The alan standard library defined in alan. ## Usage -This project is not structured like a normal alan library because it is not meant to be installed like external libraries. It is meant to be consumed by the alan [compiler](https://github.com/alantech/alan/tree/master/compiler) to target the appropriate opcodes in the alan runtimes. +This project is not structured like a normal alan library because it is not meant to be installed like external libraries. It is meant to be consumed by the alan [compiler](https://github.com/alantech/alan/tree/main/src) to target the appropriate opcodes in the alan runtimes. ## How it works @@ -12,7 +12,7 @@ These `.ln` files are not quite the same as others. The compiler loads these fil ## Development -In order to have any change from this repo available locally remember to execute `make clean` before building. +In order to have any change from this directory available locally you must rebuild the `alan` executable as these files are hardwired into the binary. ## License diff --git a/std/app.ln b/src/std/app.ln similarity index 100% rename from std/app.ln rename to src/std/app.ln diff --git a/std/avmdaemon.ln b/src/std/avmdaemon.ln similarity index 100% rename from std/avmdaemon.ln rename to src/std/avmdaemon.ln diff --git a/std/cmd.ln b/src/std/cmd.ln similarity index 100% rename from std/cmd.ln rename to src/std/cmd.ln diff --git a/std/datastore.ln b/src/std/datastore.ln similarity index 100% rename from std/datastore.ln rename to src/std/datastore.ln diff --git a/std/deps.ln b/src/std/deps.ln similarity index 100% rename from std/deps.ln rename to src/std/deps.ln diff --git a/std/http.ln b/src/std/http.ln similarity index 100% rename from std/http.ln rename to src/std/http.ln diff --git a/std/httpcommon.ln b/src/std/httpcommon.ln similarity index 100% rename from std/httpcommon.ln rename to src/std/httpcommon.ln diff --git a/std/httpserver.ln b/src/std/httpserver.ln similarity index 100% rename from std/httpserver.ln rename to src/std/httpserver.ln diff --git a/std/json.ln b/src/std/json.ln similarity index 100% rename from std/json.ln rename to src/std/json.ln diff --git a/std/root.ln b/src/std/root.ln similarity index 100% rename from std/root.ln rename to src/std/root.ln diff --git a/std/seq.ln b/src/std/seq.ln similarity index 100% rename from std/seq.ln rename to src/std/seq.ln diff --git a/std/tcp.ln b/src/std/tcp.ln similarity index 100% rename from std/tcp.ln rename to src/std/tcp.ln diff --git a/std/tcpserver.ln b/src/std/tcpserver.ln similarity index 100% rename from std/tcpserver.ln rename to src/std/tcpserver.ln diff --git a/std/trig.ln b/src/std/trig.ln similarity index 100% rename from std/trig.ln rename to src/std/trig.ln diff --git a/std/app.lnn b/std/app.lnn deleted file mode 100644 index f8d66b107..000000000 --- a/std/app.lnn +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @std/app - The entrypoint for CLI apps - */ - -// The `start` event with a signature like `event start` but has special meaning in the runtime -export start - -// The `stdout` event -export event stdout: string - -// `@std/app` has access to a special `stdoutp` opcode to trigger stdout writing -on stdout fn (out: string) = stdoutp(out); - -// The `print` function converts its input to a string, appends a newline, and does a blocking write to stdout -export fn print(out: Stringifiable) { - stdoutp(out.toString() + "\n"); -} - -// The `exit` event -export event exit: int8 - -// `@std/app` has access to a special `exitop` opcode to trigger the exit behavior -on exit fn (status: int8) = exitop(status); - -// The `stderr` event -export event stderr: string - -// `@std/app` has access to a special `stderrp` opcode to trigger stderr writing -on stderr fn (err: string) = stderrp(err); - -// The `eprint` function converts its input to a string, appends a newline, and does a blocking write to stderr -// export fn eprint(err: Stringifiable) { -// stderrp(err.toString() + "\n"); -// } diff --git a/std/root.lnn b/std/root.lnn deleted file mode 100644 index db43e3810..000000000 --- a/std/root.lnn +++ /dev/null @@ -1,430 +0,0 @@ -// note: most-recently-defined wins as tie-breaker - -// opaque types -export void -export int8 -export int16 -export int32 -export int64 -export float32 -export float64 -export bool -export string -export Either -export Error -export Maybe -export Result -// interfaces -export any -export anythingElse - -export interface Stringifiable { - toString(Stringifiable): string, -} - -// TODO: alias int = int64, float = float64 - -// Either functions -export fn main(val: any): Either = mainE(val, 0); -export fn main(val: int8): Either = mainE(val, 8); -export fn main(val: int16): Either = mainE(val, 8); -export fn main(val: int32): Either = mainE(val, 8); -export fn main(val: int64): Either = mainE(val, 8); -export fn main(val: float32): Either = mainE(val, 8); -export fn main(val: float64): Either = mainE(val, 8); -export fn main(val: bool): Either = mainE(val, 8); -export fn alt(val: any): Either = altE(val, 0); -export fn alt(val: int8): Either = altE(val, 8); -export fn alt(val: int16): Either = altE(val, 8); -export fn alt(val: int32): Either = altE(val, 8); -export fn alt(val: int64): Either = altE(val, 8); -export fn alt(val: float32): Either = altE(val, 8); -export fn alt(val: float64): Either = altE(val, 8); -export fn alt(val: bool): Either = altE(val, 8); -export isMain // opcode with signature `fn isMain(Either): bool` -export isAlt // opcode with signature `fn isAlt(Either): bool` -export fn getMainOr(either: Either, default: any): any = mainOr(either, default); -export fn getAltOr(either: Either, default: anythingElse): anythingElse = altOr(either, default); - -// Maybe functions -export fn some(val: any): Maybe = someM(val, 0); -export fn some(val: int8): Maybe = someM(val, 8); -export fn some(val: int16): Maybe = someM(val, 8); -export fn some(val: int32): Maybe = someM(val, 8); -export fn some(val: int64): Maybe = someM(val, 8); -export fn some(val: float32): Maybe = someM(val, 8); -export fn some(val: float64): Maybe = someM(val, 8); -export fn some(val: bool): Maybe = someM(val, 8); -export fn none(): Maybe = noneM(); -export isSome // opcode with signature `fn isSome(Maybe): bool` -export isNone // opcode with signature `fn isNone(Maybe): bool` -export fn getOr(maybe: Maybe, default: any): any = getOrM(maybe, default); - -// Result functions -export fn ok(val: any): Result = okR(val, 0); -export fn ok(val: int8): Result = okR(val, 8); -export fn ok(val: int16): Result = okR(val, 8); -export fn ok(val: int32): Result = okR(val, 8); -export fn ok(val: int64): Result = okR(val, 8); -export fn ok(val: float32): Result = okR(val, 8); -export fn ok(val: float64): Result = okR(val, 8); -export fn ok(val: bool): Result = okR(val, 8); -export err // opcode with signature `fn err(string): Result` -export error // opcode with signature `fn error(string): Error` -export noerr // opcode with signature `fn noerr(): Error` -export isOk // opcode with signature `fn isOk(Result): bool` -export isErr // opcode with signature `fn isErr(Result): bool` -export fn getOr(result: Result, default: any): any = getOrR(result, default); -export getErr // opcode with signature `fn getErr(Result, Error): Error` -export fn getErr(result: Result, default: string): Error = getErr(result, error(default)); - -// TODO: other defs -export fn toFloat64(n: int8): float64 = i8f64(n); -export fn toFloat64(n: int16): float64 = i16f64(n); -export fn toFloat64(n: int32): float64 = i32f64(n); -export fn toFloat64(n: int64): float64 = i64f64(n); -export fn toFloat64(n: float32): float64 = f32f64(n); -export fn toFloat64(n: float64): float64 = n; -export fn toFloat64(n: string): float64 = strf64(n); -export fn toFloat64(n: bool): float64 = boolf64(n); - -export fn toFloat32(n: int8): float32 = i8f32(n); -export fn toFloat32(n: int16): float32 = i16f32(n); -export fn toFloat32(n: int32): float32 = i32f32(n); -export fn toFloat32(n: int64): float32 = i64f32(n); -export fn toFloat32(n: float32): float32 = n; -export fn toFloat32(n: float64): float32 = f64f32(n); -export fn toFloat32(n: string): float32 = strf32(n); -export fn toFloat32(n: bool): float32 = boolf32(n); - -export fn toInt64(n: int8): int64 = i8i64(n); -export fn toInt64(n: int16): int64 = i16i64(n); -export fn toInt64(n: int32): int64 = i32i64(n); -export fn toInt64(n: int64): int64 = n; -export fn toInt64(n: float32): int64 = f32i64(n); -export fn toInt64(n: float64): int64 = f64i64(n); -export fn toInt64(n: string): int64 = stri64(n); -export fn toInt64(n: bool): int64 = booli64(n); - -export fn toInt32(n: int8): int32 = i8i32(n); -export fn toInt32(n: int16): int32 = i16i32(n); -export fn toInt32(n: int32): int32 = n; -export fn toInt32(n: int64): int32 = i64i32(n); -export fn toInt32(n: float32): int32 = f32i32(n); -export fn toInt32(n: float64): int32 = f64i32(n); -export fn toInt32(n: string): int32 = stri32(n); -export fn toInt32(n: bool): int32 = booli32(n); - -export fn toInt16(n: int8): int16 = i8i16(n); -export fn toInt16(n: int16): int16 = n; -export fn toInt16(n: int32): int16 = i32i16(n); -export fn toInt16(n: int64): int16 = i64i16(n); -export fn toInt16(n: float32): int16 = f32i16(n); -export fn toInt16(n: float64): int16 = f64i16(n); -export fn toInt16(n: string): int16 = stri16(n); -export fn toInt16(n: bool): int16 = booli16(n); - -export fn toInt8(n: int8): int8 = n; -export fn toInt8(n: int16): int8 = i16i8(n); -export fn toInt8(n: int32): int8 = i32i8(n); -export fn toInt8(n: int64): int8 = i64i8(n); -export fn toInt8(n: float32): int8 = f32i8(n); -export fn toInt8(n: float64): int8 = f64i8(n); -export fn toInt8(n: string): int8 = stri8(n); -export fn toInt8(n: bool): int8 = booli8(n); - -export fn toBool(n: int8): bool = i8bool(n); -export fn toBool(n: int16): bool = i16bool(n); -export fn toBool(n: int32): bool = i32bool(n); -export fn toBool(n: int64): bool = i64bool(n); -export fn toBool(n: float32): bool = f32bool(n); -export fn toBool(n: float64): bool = f64bool(n); -export fn toBool(n: string): bool = strbool(n); -export fn toBool(n: bool): bool = n; - -export fn toString(n: int8): string = i8str(n); -export fn toString(n: int16): string = i16str(n); -export fn toString(n: int32): string = i32str(n); -export fn toString(n: int64): string = i64str(n); -export fn toString(n: float32): string = f32str(n); -export fn toString(n: float64): string = f64str(n); -export fn toString(n: string): string = n; -export fn toString(n: bool): string = boolstr(n); - -export fn eq(a: int8, b: int8): bool = eqi8(a, b); -export fn eq(a: int16, b: int16): bool = eqi16(a, b); -export fn eq(a: int32, b: int32): bool = eqi32(a, b); -export fn eq(a: int64, b: int64): bool = eqi64(a, b); -export fn eq(a: float32, b: float32): bool = eqf32(a, b); -export fn eq(a: float64, b: float64): bool = eqf64(a, b); -export fn eq(a: string, b: string): bool = eqstr(a, b); -export fn eq(a: bool, b: bool): bool = eqbool(a, b); - -export fn neq(a: int8, b: int8): bool = neqi8(a, b); -export fn neq(a: int16, b: int16): bool = neqi16(a, b); -export fn neq(a: int32, b: int32): bool = neqi32(a, b); -export fn neq(a: int64, b: int64): bool = neqi64(a, b); -export fn neq(a: float32, b: float32): bool = neqf32(a, b); -export fn neq(a: float64, b: float64): bool = neqf64(a, b); -export fn neq(a: string, b: string): bool = neqstr(a, b); -export fn neq(a: bool, b: bool): bool = neqbool(a, b); - -export fn lt(a: int8, b: int8): bool = lti8(a, b); -export fn lt(a: int16, b: int16): bool = lti16(a, b); -export fn lt(a: int32, b: int32): bool = lti32(a, b); -export fn lt(a: int64, b: int64): bool = lti64(a, b); -export fn lt(a: float32, b: float32): bool = ltf32(a, b); -export fn lt(a: float64, b: float64): bool = ltf64(a, b); -export fn lt(a: string, b: string): bool = ltstr(a, b); - -export fn lte(a: int8, b: int8): bool = ltei8(a, b); -export fn lte(a: int16, b: int16): bool = ltei16(a, b); -export fn lte(a: int32, b: int32): bool = ltei32(a, b); -export fn lte(a: int64, b: int64): bool = ltei64(a, b); -export fn lte(a: float32, b: float32): bool = ltef32(a, b); -export fn lte(a: float64, b: float64): bool = ltef64(a, b); -export fn lte(a: string, b: string): bool = ltestr(a, b); - -export fn gt(a: int8, b: int8): bool = gti8(a, b); -export fn gt(a: int16, b: int16): bool = gti16(a, b); -export fn gt(a: int32, b: int32): bool = gti32(a, b); -export fn gt(a: int64, b: int64): bool = gti64(a, b); -export fn gt(a: float32, b: float32): bool = gtf32(a, b); -export fn gt(a: float64, b: float64): bool = gtf64(a, b); -export fn gt(a: string, b: string): bool = gtstr(a, b); - -export fn gte(a: int8, b: int8): bool = gtei8(a, b); -export fn gte(a: int16, b: int16): bool = gtei16(a, b); -export fn gte(a: int32, b: int32): bool = gtei32(a, b); -export fn gte(a: int64, b: int64): bool = gtei64(a, b); -export fn gte(a: float32, b: float32): bool = gtef32(a, b); -export fn gte(a: float64, b: float64): bool = gtef64(a, b); -export fn gte(a: string, b: string): bool = gtestr(a, b); - -export fn not(b: bool): bool = notbool(b); -export fn and(a: bool, b: bool): bool = andbool(a, b); -export fn nand(a: bool, b: bool): bool = nandboo(a, b); -export fn or(a: bool, b: bool): bool = orbool(a, b); -export fn xor(a: bool, b: bool): bool = xorbool(a, b); -export fn nor(a: bool, b: bool): bool = norbool(a, b); -export fn xnor(a: bool, b: bool): bool = xnorboo(a, b); -// This aliasing is for operator definition purposes only -export fn booland(a: bool, b: bool): bool = and(a, b); -export fn boolor(a: bool, b: bool): bool = or(a, b); - -export fn abs(a: Result): Result = absi8(a); -export fn abs(a: int8): Result = absi8(ok(a)); -export fn abs(a: Result): Result = absi16(a); -export fn abs(a: int16): Result = absi16(ok(a)); -export fn abs(a: Result): Result = absi32(a); -export fn abs(a: int32): Result = absi32(ok(a)); -export fn abs(a: Result): Result = absi64(a); -export fn abs(a: int64): Result = absi64(ok(a)); -export fn abs(a: Result): Result = absf32(a); -export fn abs(a: float32): Result = absf32(ok(a)); -export fn abs(a: Result): Result = absf64(a); -export fn abs(a: float64): Result = absf64(ok(a)); - -export fn negate(a: Result): Result = negi8(a); -export fn negate(a: int8): Result = negi8(ok(a)); -export fn negate(a: Result): Result = negi16(a); -export fn negate(a: int16): Result = negi16(ok(a)); -export fn negate(a: Result): Result = negi32(a); -export fn negate(a: int32): Result = negi32(ok(a)); -export fn negate(a: Result): Result = negi64(a); -export fn negate(a: int64): Result = negi64(ok(a)); -export fn negate(a: Result): Result = negf32(a); -export fn negate(a: float32): Result = negf32(ok(a)); -export fn negate(a: Result): Result = negf64(a); -export fn negate(a: float64): Result = negf64(ok(a)); - -export fn add(a: int8, b: int8): Result = addi8(ok(a), ok(b)); -export fn add(a: Result, b: int8): Result = addi8(a, ok(b)); -export fn add(a: int8, b: Result): Result = addi8(ok(a), b); -export fn add(a: Result, b: Result): Result = addi8(a, b); -export fn add(a: int16, b: int16): Result = addi16(ok(a), ok(b)); -export fn add(a: Result, b: int16): Result = addi16(a, ok(b)); -export fn add(a: int16, b: Result): Result = addi16(ok(a), b); -export fn add(a: Result, b: Result): Result = addi16(a, b); -export fn add(a: int32, b: int32): Result = addi32(ok(a), ok(b)); -export fn add(a: Result, b: int32): Result = addi32(a, ok(b)); -export fn add(a: int32, b: Result): Result = addi32(ok(a), b); -export fn add(a: Result, b: Result): Result = addi32(a, b); -export fn add(a: int64, b: int64): Result = addi64(ok(a), ok(b)); -export fn add(a: Result, b: int64): Result = addi64(a, ok(b)); -export fn add(a: int64, b: Result): Result = addi64(ok(a), b); -export fn add(a: Result, b: Result): Result = addi64(a, b); -export fn add(a: float32, b: float32): Result = addf32(ok(a), ok(b)); -export fn add(a: Result, b: float32): Result = addf32(a, ok(b)); -export fn add(a: float32, b: Result): Result = addf32(ok(a), b); -export fn add(a: Result, b: Result): Result = addf32(a, b); -export fn add(a: float64, b: float64): Result = addf64(ok(a), ok(b)); -export fn add(a: Result, b: float64): Result = addf64(a, ok(b)); -export fn add(a: float64, b: Result): Result = addf64(ok(a), b); -export fn add(a: Result, b: Result): Result = addf64(a, b); - -export fn sub(a: int8, b: int8): Result = subi8(ok(a), ok(b)); -export fn sub(a: Result, b: int8): Result = subi8(a, ok(b)); -export fn sub(a: int8, b: Result): Result = subi8(ok(a), b); -export fn sub(a: Result, b: Result): Result = subi8(a, b); -export fn sub(a: int16, b: int16): Result = subi16(ok(a), ok(b)); -export fn sub(a: Result, b: int16): Result = subi16(a, ok(b)); -export fn sub(a: int16, b: Result): Result = subi16(ok(a), b); -export fn sub(a: Result, b: Result): Result = subi16(a, b); -export fn sub(a: int32, b: int32): Result = subi32(ok(a), ok(b)); -export fn sub(a: Result, b: int32): Result = subi32(a, ok(b)); -export fn sub(a: int32, b: Result): Result = subi32(ok(a), b); -export fn sub(a: Result, b: Result): Result = subi32(a, b); -export fn sub(a: int64, b: int64): Result = subi64(ok(a), ok(b)); -export fn sub(a: Result, b: int64): Result = subi64(a, ok(b)); -export fn sub(a: int64, b: Result): Result = subi64(ok(a), b); -export fn sub(a: Result, b: Result): Result = subi64(a, b); -export fn sub(a: float32, b: float32): Result = subf32(ok(a), ok(b)); -export fn sub(a: Result, b: float32): Result = subf32(a, ok(b)); -export fn sub(a: float32, b: Result): Result = subf32(ok(a), b); -export fn sub(a: Result, b: Result): Result = subf32(a, b); -export fn sub(a: float64, b: float64): Result = subf64(ok(a), ok(b)); -export fn sub(a: Result, b: float64): Result = subf64(a, ok(b)); -export fn sub(a: float64, b: Result): Result = subf64(ok(a), b); -export fn sub(a: Result, b: Result): Result = subf64(a, b); - -export fn mul(a: int8, b: int8): Result = muli8(ok(a), ok(b)); -export fn mul(a: Result, b: int8): Result = muli8(a, ok(b)); -export fn mul(a: int8, b: Result): Result = muli8(ok(a), b); -export fn mul(a: Result, b: Result): Result = muli8(a, b); -export fn mul(a: int16, b: int16): Result = muli16(ok(a), ok(b)); -export fn mul(a: Result, b: int16): Result = muli16(a, ok(b)); -export fn mul(a: int16, b: Result): Result = muli16(ok(a), b); -export fn mul(a: Result, b: Result): Result = muli16(a, b); -export fn mul(a: int32, b: int32): Result = muli32(ok(a), ok(b)); -export fn mul(a: Result, b: int32): Result = muli32(a, ok(b)); -export fn mul(a: int32, b: Result): Result = muli32(ok(a), b); -export fn mul(a: Result, b: Result): Result = muli32(a, b); -export fn mul(a: int64, b: int64): Result = muli64(ok(a), ok(b)); -export fn mul(a: Result, b: int64): Result = muli64(a, ok(b)); -export fn mul(a: int64, b: Result): Result = muli64(ok(a), b); -export fn mul(a: Result, b: Result): Result = muli64(a, b); -export fn mul(a: float32, b: float32): Result = mulf32(ok(a), ok(b)); -export fn mul(a: Result, b: float32): Result = mulf32(a, ok(b)); -export fn mul(a: float32, b: Result): Result = mulf32(ok(a), b); -export fn mul(a: Result, b: Result): Result = mulf32(a, b); -export fn mul(a: float64, b: float64): Result = mulf64(ok(a), ok(b)); -export fn mul(a: Result, b: float64): Result = mulf64(a, ok(b)); -export fn mul(a: float64, b: Result): Result = mulf64(ok(a), b); -export fn mul(a: Result, b: Result): Result = mulf64(a, b); - -export fn div(a: int8, b: int8): Result = divi8(ok(a), ok(b)); -export fn div(a: Result, b: int8): Result = divi8(a, ok(b)); -export fn div(a: int8, b: Result): Result = divi8(ok(a), b); -export fn div(a: Result, b: Result): Result = divi8(a, b); -export fn div(a: int16, b: int16): Result = divi16(ok(a), ok(b)); -export fn div(a: Result, b: int16): Result = divi16(a, ok(b)); -export fn div(a: int16, b: Result): Result = divi16(ok(a), b); -export fn div(a: Result, b: Result): Result = divi16(a, b); -export fn div(a: int32, b: int32): Result = divi32(ok(a), ok(b)); -export fn div(a: Result, b: int32): Result = divi32(a, ok(b)); -export fn div(a: int32, b: Result): Result = divi32(ok(a), b); -export fn div(a: Result, b: Result): Result = divi32(a, b); -export fn div(a: int64, b: int64): Result = divi64(ok(a), ok(b)); -export fn div(a: Result, b: int64): Result = divi64(a, ok(b)); -export fn div(a: int64, b: Result): Result = divi64(ok(a), b); -export fn div(a: Result, b: Result): Result = divi64(a, b); -export fn div(a: float32, b: float32): Result = divf32(ok(a), ok(b)); -export fn div(a: Result, b: float32): Result = divf32(a, ok(b)); -export fn div(a: float32, b: Result): Result = divf32(ok(a), b); -export fn div(a: Result, b: Result): Result = divf32(a, b); -export fn div(a: float64, b: float64): Result = divf64(ok(a), ok(b)); -export fn div(a: Result, b: float64): Result = divf64(a, ok(b)); -export fn div(a: float64, b: Result): Result = divf64(ok(a), b); -export fn div(a: Result, b: Result): Result = divf64(a, b); - -// TODO: enable Result-wrapped params when conditionals are enabled -export fn mod(a: int8, b: int8): int8 = modi8(a, b); -export fn mod(a: int16, b: int16): int16 = modi16(a, b); -export fn mod(a: int32, b: int32): int32 = modi32(a, b); -export fn mod(a: int64, b: int64): int64 = modi64(a, b); - -export fn pow(a: Result, b: Result): Result = powi8(a, b); -export fn pow(a: Result, b: int8): Result = powi8(a, ok(b)); -export fn pow(a: int8, b: Result): Result = powi8(ok(a), b); -export fn pow(a: int8, b: int8): Result = powi8(ok(a), ok(b)); -export fn pow(a: Result, b: Result): Result = powi16(a, b); -export fn pow(a: Result, b: int16): Result = powi16(a, ok(b)); -export fn pow(a: int16, b: Result): Result = powi16(ok(a), b); -export fn pow(a: int16, b: int16): Result = powi16(ok(a), ok(b)); -export fn pow(a: Result, b: Result): Result = powi32(a, b); -export fn pow(a: Result, b: int32): Result = powi32(a, ok(b)); -export fn pow(a: int32, b: Result): Result = powi32(ok(a), b); -export fn pow(a: int32, b: int32): Result = powi32(ok(a), ok(b)); -export fn pow(a: Result, b: Result): Result = powi64(a, b); -export fn pow(a: Result, b: int64): Result = powi64(a, ok(b)); -export fn pow(a: int64, b: Result): Result = powi64(ok(a), b); -export fn pow(a: int64, b: int64): Result = powi64(ok(a), ok(b)); -export fn pow(a: Result, b: Result): Result = powf32(a, b); -export fn pow(a: Result, b: float32): Result = powf32(a, ok(b)); -export fn pow(a: float32, b: Result): Result = powf32(ok(a), b); -export fn pow(a: float32, b: float32): Result = powf32(ok(a), ok(b)); -export fn pow(a: Result, b: Result): Result = powf64(a, b); -export fn pow(a: Result, b: float64): Result = powf64(a, ok(b)); -export fn pow(a: float64, b: Result): Result = powf64(ok(a), b); -export fn pow(a: float64, b: float64): Result = powf64(ok(a), ok(b)); - -export fn sqrt(a: float32): float32 = sqrtf32(a); -export fn sqrt(a: float64): float64 = sqrtf64(a); - -// Wait functions -export fn wait(n: int8): void = waitop(i8i64(n)); -export fn wait(n: int16): void = waitop(i16i64(n)); -export fn wait(n: int32): void = waitop(i32i64(n)); -export fn wait(n: int64): void = waitop(n); - -// String functions -export fn concat(a: string, b: string): string = catstr(a, b); -export fn repeat(s: string, n: int64): string = repstr(s, n); -export matches -export fn length(s: string): int64 = lenstr(s); -export trim - -// "clone" function useful for hoisting assignments and making duplicates -export fn clone(a: int8): int8 = copyi8(a); -export fn clone(a: int16): int16 = copyi16(a); -export fn clone(a: int32): int32 = copyi32(a); -export fn clone(a: int64): int64 = copyi64(a); -export fn clone(a: float32): float32 = copyf32(a); -export fn clone(a: float64): float64 = copyf64(a); -export fn clone(a: bool): bool = copybool(a); -export fn clone(a: string): string = copystr(a); - -export prefix length as # precedence 10 -export prefix not as ! precedence 10 -export prefix negate as - precedence 10 -export prefix trim as ` precedence 10 -export infix pow as ** precedence 2 -export infix mul as * precedence 3 -export infix repeat as * precedence 3 -export infix div as / precedence 3 -// export infix split as / precedence 3 -export infix mod as % precedence 3 -export infix add as + precedence 4 -export infix concat as + precedence 4 -export infix sub as - precedence 4 -// export infix pair as : precedence 4 -// export infix push as : precedence 5 -export infix lt as < precedence 5 -export infix lte as <= precedence 5 -export infix gt as > precedence 5 -export infix gte as >= precedence 5 -export infix eq as == precedence 6 -export infix neq as != precedence 6 -export infix matches as ~ precedence 6 -export infix and as & precedence 7 -export infix booland as && precedence 7 -export infix nand as !& precedence 7 -export infix xnor as !^ precedence 8 -export infix xor as ^ precedence 8 -// export infix index as @ precedence 8 -export infix nor as !| precedence 9 -export infix or as | precedence 9 -export infix boolor as || precedence 9 -export infix getOr as || precedence 9 -// export infix cond as ? precedence 10