├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .travis.yml ├── .vscode └── settings.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── THIRD_PARTY_LICENSES.html ├── about.hbs ├── about.toml ├── deny.toml ├── examples ├── cartesian-product.rant ├── closure-test.rant ├── cucumberbatch.rant ├── deck-of-cards.rant ├── factorial.rant ├── fibonacci.rant ├── fizzbuzz.rant ├── float_literals.rant ├── harvard-sentences.rant ├── hello_rant.rs ├── helloworld.rant ├── item-sword.rant ├── mandelbrot.rant ├── modules │ ├── test-module.rant │ └── test-submodule.rant ├── personality.rant ├── phone-numbers.rant ├── require.rant ├── selector-rainbows.rant ├── substitution-cipher.rant ├── text-filter.rant └── weighting.rant ├── src ├── collections │ ├── list.rs │ ├── map.rs │ ├── mod.rs │ ├── range.rs │ └── tuple.rs ├── compiler │ ├── lexer.rs │ ├── message.rs │ ├── mod.rs │ ├── parser.rs │ └── reader.rs ├── convert.rs ├── data.rs ├── format │ ├── case.rs │ ├── mod.rs │ ├── num.rs │ └── ws.rs ├── func.rs ├── lang.rs ├── lib.rs ├── modres.rs ├── rng.rs ├── runtime │ ├── error.rs │ ├── intent.rs │ ├── mod.rs │ ├── output.rs │ ├── resolver.rs │ └── stack.rs ├── selector.rs ├── stdlib │ ├── assertion.rs │ ├── block.rs │ ├── boolean.rs │ ├── collections.rs │ ├── compare.rs │ ├── convert.rs │ ├── format.rs │ ├── general.rs │ ├── generate.rs │ ├── math.rs │ ├── mod.rs │ ├── proto.rs │ ├── strings.rs │ └── verify.rs ├── string.rs ├── tools │ └── cli │ │ ├── _copyright.txt │ │ ├── _credits.txt │ │ └── main.rs ├── util.rs ├── value.rs └── var.rs └── tests ├── runtime_tests.rs └── sources ├── access ├── add_assign.rant ├── and_assign.rant ├── div_assign.rant ├── dynamic_index_setter.rant ├── dynamic_multi_index_setter.rant ├── empty-def.rant ├── getter_fallback_from_index.rant ├── getter_fallback_from_key.rant ├── getter_fallback_from_var.rant ├── inv_index_get.rant ├── inv_index_set.rant ├── mod_assign.rant ├── mul_assign.rant ├── or_assign.rant ├── override_shadowed_locals_with_multi_descope.rant ├── pow_assign.rant ├── sub_assign.rant └── xor_assign.rant ├── anonymous ├── anon_getter.rant ├── anon_setter.rant ├── dynamic_anon_getter.rant └── dynamic_anon_setter.rant ├── assert ├── assert_fail.rant └── assert_pass.rant ├── branch ├── if-else.rant ├── if-elseif-else.rant ├── if-elseif.rant └── if.rant ├── charms ├── func_return_output.rant ├── func_return_value.rant ├── rep_break_output.rant ├── rep_break_value.rant ├── rep_continue_output.rant ├── rep_continue_value.rant ├── top_level_return.rant └── weight_all_zero.rant ├── closure ├── closure_capture_arg.rant ├── closure_capture_var.rant └── closure_mutate_captured_value.rant ├── collections ├── filter_with_native_predicate.rant ├── filter_with_user_predicate.rant ├── list_autoconcat.rant ├── list_autoconcat_repeater.rant ├── list_tuple_autoconcat.rant ├── map_autoconcat.rant ├── map_with_native_callback.rant ├── map_with_user_callback.rant ├── tuple_autoconcat.rant ├── tuple_autoconcat_repeater.rant ├── zip_with_native_callback.rant └── zip_with_user_callback.rant ├── const ├── const_define.rant ├── const_function.rant ├── const_shadow.rant ├── redef_const.rant └── redef_var_with_const.rant ├── func ├── assignment_pipe_def_const.rant ├── assignment_pipe_def_var.rant ├── assignment_pipe_set.rant ├── func_percolation.rant ├── func_with_optional_param.rant ├── func_with_variadic_plus.rant ├── func_with_variadic_star.rant ├── function_piping.rant ├── function_piping_callback.rant └── pipecall_pipeval.rant ├── math ├── max.rant └── min.rant ├── modules ├── require.rant └── test-module.rant ├── ops ├── and.rant ├── and_short_circuit.rant ├── cmp.rant ├── math.rant ├── not.rant ├── or.rant ├── or_short_circuit.rant └── xor.rant ├── range ├── range_forward.rant ├── range_forward_step_divisible.rant ├── range_forward_step_indivisible.rant ├── range_reverse.rant ├── range_reverse_step_divisible.rant └── range_reverse_step_indivisible.rant ├── slice ├── list │ ├── between_dynamic.rant │ ├── between_static.rant │ ├── from_dynamic.rant │ ├── from_static.rant │ ├── full.rant │ ├── to_dynamic.rant │ └── to_static.rant ├── range │ ├── between_dynamic.rant │ ├── between_static.rant │ ├── from_dynamic.rant │ ├── from_static.rant │ ├── full.rant │ ├── to_dynamic.rant │ └── to_static.rant ├── string │ ├── between_dynamic.rant │ ├── between_static.rant │ ├── from_dynamic.rant │ ├── from_static.rant │ ├── full.rant │ ├── to_dynamic.rant │ └── to_static.rant └── tuple │ ├── between_dynamic.rant │ ├── between_static.rant │ ├── from_dynamic.rant │ ├── from_static.rant │ ├── full.rant │ ├── to_dynamic.rant │ └── to_static.rant ├── splice ├── dynamic_from_list.rant ├── dynamic_from_tuple.rant ├── static_from_list.rant └── static_from_tuple.rant ├── spread ├── spread_all.rant ├── spread_inner.rant ├── spread_left.rant ├── spread_multi.rant ├── spread_right.rant ├── spread_variadic_plus.rant └── spread_variadic_star.rant └── temporal ├── temporal_one.rant ├── temporal_one_mixed.rant ├── temporal_pipe_temporal.rant ├── temporal_two_diffsize.rant ├── temporal_two_diffsize_mixed.rant ├── temporal_two_samesize.rant ├── temporal_two_samesize_mixed.rant └── temporal_two_samesize_sync.rant /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [TheBerkin] 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Something isn't working right 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Platform** 27 | - OS: [e.g. Windows] 28 | - Browser, if applicable [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | debug.log 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - 1.47.0 4 | - stable 5 | - beta 6 | - nightly 7 | script: 8 | - cargo +$TRAVIS_RUST_VERSION test --verbose 9 | jobs: 10 | allow_failures: 11 | - rust: nightly 12 | fast_finish: true 13 | cache: cargo 14 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "rust-analyzer.cargo.allFeatures": true, 4 | "files.autoSave": "off", 5 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute to Rant 2 | 3 | So you want to help develop Rant. Great! 4 | 5 | Here's a brief overview of how you can contribute: 6 | 7 | ## Developer resources 8 | 9 | * [Rant Discord]https://discord.gg/U8Bj6gSshJ) 10 | * [Rant Reference](https://docs.rant-lang.org/) 11 | 12 | ## Ways to contribute 13 | 14 | ### Bug reports 15 | 16 | If you find something in Rant that isn't behaving correctly, please tell us! 17 | 18 | Bugs should be reported through the issue tracker. 19 | Please search the issues beforehand to see if your bug has already been reported. 20 | Even if your bug is already reported, please feel free to add to the discussion and provide any additional insight. 21 | 22 | A bug report should provide a detailed description of the following: 23 | 24 | 1. Your execution environment (OS, architecture, any wrapper library in use, etc.) 25 | 2. Compiler info as applicable (Rust version, build profiles, etc.) 26 | 3. Steps to reproduce the bug 27 | 4. The expected and actual behaviors 28 | 29 | ### Feature requests 30 | 31 | Your ideas for how to improve Rant are always welcome. As with bugs, feature requests are submitted via the issue tracker. 32 | 33 | ### Pull requests 34 | 35 | If you'd like to tackle a bug yourself you can submit a pull request for review. 36 | 37 | (TODO: Pull request template) -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rant" 3 | version = "4.0.0-alpha.33" 4 | edition = "2021" 5 | description = "The Rant procedural templating language" 6 | license = "MIT OR Apache-2.0" 7 | repository = "https://github.com/rant-lang/rant" 8 | homepage = "https://rant-lang.org" 9 | keywords = ["rant", "templating", "procedural", "generation", "procgen"] 10 | categories = ["text-processing", "game-development", "template-engine"] 11 | readme = "README.md" 12 | rust-version = "1.61.0" 13 | include = [ 14 | "**/*.rs", 15 | "Cargo.toml", 16 | "README.md", 17 | "LICENSE*", 18 | "THIRD_PARTY_LICENSES*", 19 | "CHANGELOG.md", 20 | "!tests/unincluded_*.rs", 21 | "src/tools/cli/_*.txt" 22 | ] 23 | default-run = "rant" 24 | # publish = false 25 | 26 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 27 | 28 | [profile.release] 29 | opt-level = 3 30 | codegen-units = 1 31 | 32 | [[bin]] 33 | name = "rant" 34 | path = "src/tools/cli/main.rs" 35 | required-features = ["cli"] 36 | 37 | [features] 38 | cli = ["atty", "clap", "codemap", "codemap-diagnostic", "colored", "ctrlc", "embedded-triple", "exitcode"] 39 | vm-trace = [] 40 | 41 | 42 | [dependencies] 43 | # Library 44 | cast = "0.3.0" # Automates a lot of native <-> Rant value conversions 45 | cervine = "0.0.6" # Simplifies Rant's by-ref variable passing code. 46 | fnv = "1.0.7" # Hashing algorithm used by Rant's map type and other internals. 47 | line-col = "0.2.1" # Calculates locations for compiler messages. 48 | logos = "0.12.0" # Powers the lexer. 49 | once_cell = "1.5.2" # Lazy initialization for various internals. 50 | quickscope = "0.2.0" # Powers the variable scoping system. 51 | rand = "0.8.3" # RNG utilities used throughout Rant. 52 | rand_xoshiro = "0.6.0" # RNG implementation used by the Rant runtime. 53 | smallvec = "1.6.1" # List optimization used throughout Rant. 54 | smartstring = "1.0.1" # String optimization used throughout Rant. 55 | unicode-segmentation = "1.7.1" # Used by Rant to index strings by grapheme cluster. 56 | 57 | # CLI 58 | atty = { version = "0.2.14", optional = true } 59 | clap = { version = "2.33.3", optional = true } 60 | codemap = { version = "0.1.3", optional = true } 61 | codemap-diagnostic = { version = "0.1.1", optional = true } 62 | colored = { version = "2.0.0", optional = true } 63 | ctrlc = { version = "3.1.9", optional = true } 64 | embedded-triple = { version = "0.1.0", optional = true } 65 | exitcode = { version = "1.1.2", optional = true } 66 | 67 | [dev-dependencies] 68 | assert_matches = "1.5.0" -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Robin Pederson 2 | 3 | The MIT License (MIT) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Rant Logo 3 |

4 | 5 | [![Crates.io](https://img.shields.io/crates/v/rant)](https://crates.io/crates/rant) 6 | [![Docs.rs](https://docs.rs/rant/badge.svg)](https://docs.rs/rant) 7 | [![Discord](https://img.shields.io/discord/332251452334669834?color=6C8BD5&label=discord&logo=discord&logoColor=%23fff)](https://discord.gg/U8Bj6gSshJ) 8 | 9 | **Rant** is a dynamically-typed, multi-paradigm templating language designed primarily for procedural generation. It is designed with scalability in mind: it can handle tasks ranging from simple randomized string generation to more complex workloads such as procedural dialogue, character generation, and worldbuilding. 10 | 11 | *** 12 | 13 | > ## **Prerelease notice** 14 | > 15 | > **This project is in alpha.** 16 | > **This means that the API is unstable, functionality may be broken/missing, and everything is subject to change.** 17 | > 18 | > **Please dont hesitate to try it out and give feedback; however, _do not_ use in production environments until a stable version is released.** 19 | > 20 | > **Features may appear/disappear at any time for any reason. Assume that every alpha release will have breaking changes.** 21 | 22 | 23 | ## Introducing Rant 4 24 | 25 | Rant is the result of a long-standing desire for an all-in-one data templating tool made especially for creative applications like games and interactive art. 26 | 27 | Rant 4 is the next leap forward in achieving this goal: the syntax, standard library, and interpreter have all been completely reimagined from the ground up. 28 | 29 | ## Features 30 | 31 | 🧰 **Painless API**
32 | Rant has a no-nonsense API designed for ease of use. 33 | No getting lost in configuration hell. Integrating Rant into your project only takes a few lines of code. 34 | 35 | 💻 **Cross-platform**
36 | Write once, run anywhere! The runtime works the same across Windows, Mac, Linux, and WebAssembly. 37 | 38 | ✍ **Templating that does more**
39 | Rant is all about "printing": each lexical scope has an output to print (append values) to, which then prints itself to the parent output, and so on. 40 | This enables you to intuitively build strings, collections, and more in a familiar templating setting. 41 | 42 | 🎨 **Now *intentionally* Turing-complete!**
43 | In addition to being a templating language, Rant adopts declarative and imperative programming concepts with design influences from many other popular languages. 44 | 45 | ✨ **Generate anything — not just text**
46 | Unlike older Rant versions that could only generate strings, Rant 4 can output arbitrary data structures using any of the built-in data types. Enjoy first-class support for common primitives like strings, numbers, collections, closures, and more. 47 | 48 | 🎲 **Built with ♥ for RNG**
49 | Rant is made with random generation in mind as a major use-case. 50 | 51 | Make use of a wide array of built-in utilities for generating random numbers, strings, booleans, lists, list subsets, and much more for all your randomization needs. 52 | The internal RNG can be manually seeded to produce repeatable outputs. 53 | 54 | 🔱 **Branching and beyond**
55 | Augment regular control flow behavior with a multitude of configuration options for iterative, randomized, and weighted branch selection. 56 | 57 | 🧬 **Delightful combinatorics**
58 | Perform nested mappings, filters, zips, combinations, and more with minimal effort. 59 | Rant's powerful piping syntax lets you perform complex operations with shorter, more readable code. 60 | 61 | 📝 **Automatic text formatting**
62 | Passively format text output with automatic capitalization, whitespace normalization, and number formatting — including built-in support for numerous writing systems. 63 | 64 | 📦 **Data sources**
65 | Attach custom data sources to your Rant execution context to give your scripts controlled access to external resources. 66 | 67 | 🧩 **Simple module system**
68 | Sharing code between Rant programs is trivial. Just write your module script and `@require` it elsewhere. 69 | 70 | Need custom module resolution logic? No problem. You can write your own resolver and just plug it in. 71 | 72 | 📚 **Rant Standard Library**
73 | A comprehensive standard library provides the tools needed to quickly iterate on your ideas. 74 | 75 | 🧪 **Use integrated or standalone**
76 | Whether you want to integrate Rant directly into a product or use it as a standalone tool to assist with writing, Rant has a place in any part of your workflow. 77 | 78 | ## Getting started 79 | 80 | Rant is written in [Rust](https://rust-lang.org), so you'll need to install [the toolchain](https://www.rust-lang.org/tools/install) in order to build it. 81 | 82 | ### CLI 83 | 84 | Rant's CLI can run Rant code from files or the command line. 85 | Install it from Cargo with: 86 | 87 | ```sh 88 | $ cargo install rant --version 4.0.0-alpha.33 --features cli 89 | ``` 90 | 91 | Then run it: 92 | 93 | ```sh 94 | # Launch the REPL 95 | $ rant 96 | 97 | # Get a full list of options 98 | $ rant -h 99 | 100 | # Run an inline script and display output 101 | $ rant -e '[rep:3] [sep:\s] {b{ee|i|o|u}bbity}' 102 | 103 | # Run hello_world.rant and send output to result.txt 104 | $ rant hello_world.rant > result.txt 105 | ``` 106 | 107 | ### Library 108 | 109 | To use Rant in a Rust project, add the `rant` crate to `Cargo.toml`: 110 | 111 | ```toml 112 | [dependencies] 113 | rant = "*" 114 | ``` 115 | 116 | You can run a Rant program with just a few lines of code: 117 | 118 | ```rust 119 | use rant::Rant; 120 | use std::error::Error; 121 | 122 | fn main() -> Result<(), Box> { 123 | // Create a default Rant context 124 | let mut rant = Rant::new(); 125 | 126 | // Compile a simple program 127 | let program = rant.compile_quiet(r#" 128 | [$greet:name] { 129 | {Hello|Hi|Hey} ! 130 | } 131 | [greet:world] 132 | "#)?; 133 | 134 | // Run the program and print the output 135 | let output = rant.run(&program)?; 136 | println!("{}", output); 137 | 138 | Ok(()) 139 | } 140 | ``` 141 | 142 | ## [Examples](./examples/) 143 | 144 | This repository includes a collection of example Rant scripts for you to learn from. Check them out! 145 | 146 | ## Documentation 147 | 148 | The latest reference documentation can be found at **[docs.rant-lang.org](https://docs.rant-lang.org)**. 149 | 150 | Since Rant 4 is early in development, some documentation may be outdated/incomplete, but it is actively updated to ensure that it reflects current features with reasonable accuracy. 151 | 152 | 153 | ## [Changelog](./CHANGELOG.md) 154 | 155 | The changelog is updated constantly throughout the development process, providing a complete summary of upcoming changes at a glance even before the next release. 156 | 157 | ## License 158 | 159 | Licensed under either of 160 | 161 | * Apache License, Version 2.0 162 | ([LICENSE-APACHE](LICENSE-APACHE) or ) 163 | * MIT license 164 | ([LICENSE-MIT](LICENSE-MIT) or ) 165 | 166 | at your option. -------------------------------------------------------------------------------- /THIRD_PARTY_LICENSES.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rant-lang/rant/caf374fda38801f0469d5e9ffccfd90b8a5bef83/THIRD_PARTY_LICENSES.html -------------------------------------------------------------------------------- /about.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 36 | 37 | 38 | 39 |
40 |
41 |

Third Party Licenses

42 |

This page lists the licenses of the projects used in Rant.

43 |
44 | 45 |

Overview of licenses:

46 |
    47 | {{#each overview}} 48 |
  • {{name}} ({{count}})
  • 49 | {{/each}} 50 |
51 | 52 |

All license text:

53 |
    54 | {{#each licenses}} 55 |
  • 56 |

    {{name}}

    57 |

    Used by:

    58 | 63 |
    {{text}}
    64 |
  • 65 | {{/each}} 66 |
67 |
68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /about.toml: -------------------------------------------------------------------------------- 1 | accepted = [ 2 | "AGPL-3.0", 3 | "Apache-2.0", 4 | "MIT", 5 | "MPL-2.0" 6 | ] 7 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | # This template contains all of the possible sections and their default values 2 | 3 | # Note that all fields that take a lint level have these possible values: 4 | # * deny - An error will be produced and the check will fail 5 | # * warn - A warning will be produced, but the check will not fail 6 | # * allow - No warning or error will be produced, though in some cases a note 7 | # will be 8 | 9 | # The values provided in this template are the default values that will be used 10 | # when any section or field is not specified in your own configuration 11 | 12 | # If 1 or more target triples (and optionally, target_features) are specified, 13 | # only the specified targets will be checked when running `cargo deny check`. 14 | # This means, if a particular package is only ever used as a target specific 15 | # dependency, such as, for example, the `nix` crate only being used via the 16 | # `target_family = "unix"` configuration, that only having windows targets in 17 | # this list would mean the nix crate, as well as any of its exclusive 18 | # dependencies not shared by any other crates, would be ignored, as the target 19 | # list here is effectively saying which targets you are building for. 20 | targets = [ 21 | # The triple can be any string, but only the target triples built in to 22 | # rustc (as of 1.40) can be checked against actual config expressions 23 | #{ triple = "x86_64-unknown-linux-musl" }, 24 | # You can also specify which target_features you promise are enabled for a 25 | # particular target. target_features are currently not validated against 26 | # the actual valid features supported by the target architecture. 27 | #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, 28 | ] 29 | 30 | # This section is considered when running `cargo deny check advisories` 31 | # More documentation for the advisories section can be found here: 32 | # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html 33 | [advisories] 34 | # The path where the advisory database is cloned/fetched into 35 | db-path = "~/.cargo/advisory-db" 36 | # The url of the advisory database to use 37 | db-url = "https://github.com/rustsec/advisory-db" 38 | # The lint level for security vulnerabilities 39 | vulnerability = "deny" 40 | # The lint level for unmaintained crates 41 | unmaintained = "warn" 42 | # The lint level for crates that have been yanked from their source registry 43 | yanked = "warn" 44 | # The lint level for crates with security notices. Note that as of 45 | # 2019-12-17 there are no security notice advisories in 46 | # https://github.com/rustsec/advisory-db 47 | notice = "warn" 48 | # A list of advisory IDs to ignore. Note that ignored advisories will still 49 | # output a note when they are encountered. 50 | ignore = [ 51 | #"RUSTSEC-0000-0000", 52 | ] 53 | # Threshold for security vulnerabilities, any vulnerability with a CVSS score 54 | # lower than the range specified will be ignored. Note that ignored advisories 55 | # will still output a note when they are encountered. 56 | # * None - CVSS Score 0.0 57 | # * Low - CVSS Score 0.1 - 3.9 58 | # * Medium - CVSS Score 4.0 - 6.9 59 | # * High - CVSS Score 7.0 - 8.9 60 | # * Critical - CVSS Score 9.0 - 10.0 61 | #severity-threshold = 62 | 63 | # This section is considered when running `cargo deny check licenses` 64 | # More documentation for the licenses section can be found here: 65 | # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html 66 | [licenses] 67 | # The lint level for crates which do not have a detectable license 68 | unlicensed = "deny" 69 | # List of explictly allowed licenses 70 | # See https://spdx.org/licenses/ for list of possible licenses 71 | # [possible values: any SPDX 3.7 short identifier (+ optional exception)]. 72 | allow = [ 73 | "MIT", 74 | "Apache-2.0", 75 | "Apache-2.0 WITH LLVM-exception", 76 | ] 77 | # List of explictly disallowed licenses 78 | # See https://spdx.org/licenses/ for list of possible licenses 79 | # [possible values: any SPDX 3.7 short identifier (+ optional exception)]. 80 | deny = [ 81 | #"Nokia", 82 | ] 83 | # Lint level for licenses considered copyleft 84 | copyleft = "warn" 85 | # Blanket approval or denial for OSI-approved or FSF Free/Libre licenses 86 | # * both - The license will be approved if it is both OSI-approved *AND* FSF 87 | # * either - The license will be approved if it is either OSI-approved *OR* FSF 88 | # * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF 89 | # * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved 90 | # * neither - This predicate is ignored and the default lint level is used 91 | allow-osi-fsf-free = "neither" 92 | # Lint level used when no other predicates are matched 93 | # 1. License isn't in the allow or deny lists 94 | # 2. License isn't copyleft 95 | # 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" 96 | default = "deny" 97 | # The confidence threshold for detecting a license from license text. 98 | # The higher the value, the more closely the license text must be to the 99 | # canonical license text of a valid SPDX license file. 100 | # [possible values: any between 0.0 and 1.0]. 101 | confidence-threshold = 0.8 102 | # Allow 1 or more licenses on a per-crate basis, so that particular licenses 103 | # aren't accepted for every possible crate as with the normal allow list 104 | exceptions = [ 105 | # Each entry is the crate and version constraint, and its specific allow 106 | # list 107 | #{ allow = ["Zlib"], name = "adler32", version = "*" }, 108 | ] 109 | 110 | # Some crates don't have (easily) machine readable licensing information, 111 | # adding a clarification entry for it allows you to manually specify the 112 | # licensing information 113 | #[[licenses.clarify]] 114 | # The name of the crate the clarification applies to 115 | #name = "ring" 116 | # THe optional version constraint for the crate 117 | #version = "*" 118 | # The SPDX expression for the license requirements of the crate 119 | #expression = "MIT AND ISC AND OpenSSL" 120 | # One or more files in the crate's source used as the "source of truth" for 121 | # the license expression. If the contents match, the clarification will be used 122 | # when running the license check, otherwise the clarification will be ignored 123 | # and the crate will be checked normally, which may produce warnings or errors 124 | # depending on the rest of your configuration 125 | #license-files = [ 126 | # Each entry is a crate relative path, and the (opaque) hash of its contents 127 | #{ path = "LICENSE", hash = 0xbd0eed23 } 128 | #] 129 | 130 | [licenses.private] 131 | # If true, ignores workspace crates that aren't published, or are only 132 | # published to private registries 133 | ignore = false 134 | # One or more private registries that you might publish crates to, if a crate 135 | # is only published to private registries, and ignore is true, the crate will 136 | # not have its license(s) checked 137 | registries = [ 138 | #"https://sekretz.com/registry 139 | ] 140 | 141 | # This section is considered when running `cargo deny check bans`. 142 | # More documentation about the 'bans' section can be found here: 143 | # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html 144 | [bans] 145 | # Lint level for when multiple versions of the same crate are detected 146 | multiple-versions = "warn" 147 | # Lint level for when a crate version requirement is `*` 148 | wildcards = "warn" 149 | # The graph highlighting used when creating dotgraphs for crates 150 | # with multiple versions 151 | # * lowest-version - The path to the lowest versioned duplicate is highlighted 152 | # * simplest-path - The path to the version with the fewest edges is highlighted 153 | # * all - Both lowest-version and simplest-path are used 154 | highlight = "all" 155 | # List of crates that are allowed. Use with care! 156 | allow = [ 157 | #{ name = "ansi_term", version = "=0.11.0" }, 158 | ] 159 | # List of crates to deny 160 | deny = [ 161 | # Each entry the name of a crate and a version range. If version is 162 | # not specified, all versions will be matched. 163 | #{ name = "ansi_term", version = "=0.11.0" }, 164 | ] 165 | # Certain crates/versions that will be skipped when doing duplicate detection. 166 | skip = [ 167 | #{ name = "ansi_term", version = "=0.11.0" }, 168 | ] 169 | # Similarly to `skip` allows you to skip certain crates during duplicate 170 | # detection. Unlike skip, it also includes the entire tree of transitive 171 | # dependencies starting at the specified crate, up to a certain depth, which is 172 | # by default infinite 173 | skip-tree = [ 174 | #{ name = "ansi_term", version = "=0.11.0", depth = 20 }, 175 | ] 176 | 177 | # This section is considered when running `cargo deny check sources`. 178 | # More documentation about the 'sources' section can be found here: 179 | # https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html 180 | [sources] 181 | # Lint level for what to happen when a crate from a crate registry that is not 182 | # in the allow list is encountered 183 | unknown-registry = "warn" 184 | # Lint level for what to happen when a crate from a git repository that is not 185 | # in the allow list is encountered 186 | unknown-git = "warn" 187 | # List of URLs for allowed crate registries. Defaults to the crates.io index 188 | # if not specified. If it is specified but empty, no registries are allowed. 189 | allow-registry = ["https://github.com/rust-lang/crates.io-index"] 190 | # List of URLs for allowed Git repositories 191 | allow-git = [] 192 | -------------------------------------------------------------------------------- /examples/cartesian-product.rant: -------------------------------------------------------------------------------- 1 | # Define A, B, and A x B 2 | <%a = (1; 2; 3)> 3 | <%b = (A; B; C)> 4 | <%axb = [tuple: ** ; ** |> tuple]> 5 | 6 | # Display the results 7 | [ws-fmt: verbatim] 8 | A = \n 9 | B = \n 10 | A x B = \n -------------------------------------------------------------------------------- /examples/closure-test.rant: -------------------------------------------------------------------------------- 1 | [cat: **[range: 3] |> cat: ([?] { My value:\s[] }) > %funcs] 2 | 3 | [cat: ** |> [] |> cat: \n] -------------------------------------------------------------------------------- /examples/cucumberbatch.rant: -------------------------------------------------------------------------------- 1 | [cat: 🍓🍌🍆🥒 |> cat: **[]; **[]; **[]; **[] |> list] -------------------------------------------------------------------------------- /examples/deck-of-cards.rant: -------------------------------------------------------------------------------- 1 | [shuffle-thru: 2 | [cat: 3 | **(A; 2; 3; 4; 5; 6; 7; 8; 9; 10; J; Q; K); 4 | **(♠; ♥; ♣; ♦) 5 | |> list 6 | ] 7 | (:🤡;🤡) 8 | ] -------------------------------------------------------------------------------- /examples/factorial.rant: -------------------------------------------------------------------------------- 1 | [%fac: n] { 2 | [rep: ] 1 { @edit x: * [step] } 3 | } 4 | 5 | [rep: 10] [sep: \n] { [fac: [step]] } -------------------------------------------------------------------------------- /examples/fibonacci.rant: -------------------------------------------------------------------------------- 1 | [%fib: n] { 2 | [rep: ] (:) { @edit f: (: + ) } 3 | } 4 | 5 | [fib: 16] -------------------------------------------------------------------------------- /examples/fizzbuzz.rant: -------------------------------------------------------------------------------- 1 | [rep: 100][sep: \n] 2 | { 3 | [alt: 4 | [if: [step |> is-factor: 3]] { fizz } 5 | [if: [step |> is-factor: 5]] { buzz }; 6 | [step] 7 | ] 8 | } -------------------------------------------------------------------------------- /examples/float_literals.rant: -------------------------------------------------------------------------------- 1 | # Exhaustively generates the various forms of C89 float literals. 2 | 3 | # No fractional part 4 | [cat: 5 | # Integral part 6 | 8; 7 | # Exponent + suffix 8 | ** (.;) ~[cat: **(<>;.); **FfLl |> list] [cat: **(<>;.); **Ee; **(;\+;-); 8; **(;F;f;L;l) |> list]; 9 | # Newline 10 | \n 11 | ] 12 | 13 | # With fractional part 14 | [cat: 15 | # Integral part 16 | ** (<>;8); 17 | # Fractional part 18 | .8; 19 | # Exponent + suffix 20 | ** (<>;F;f;L;l) [cat: **Ee; **(<>;\+;-); 8; **(<>;F;f;L;l) |> list]; 21 | # Newline 22 | \n 23 | ] -------------------------------------------------------------------------------- /examples/hello_rant.rs: -------------------------------------------------------------------------------- 1 | use rant::Rant; 2 | use std::error::Error; 3 | 4 | fn main() -> Result<(), Box> { 5 | // Create a Rant context and load the standard library 6 | let mut rant = Rant::with_random_seed(); 7 | 8 | // Compile a simple program 9 | let program = rant.compile_quiet(r#" 10 | [$greet:name] { 11 | {Hello|Hi|Hey} ! 12 | } 13 | [greet:world] 14 | "#)?; 15 | 16 | // Run the program and fetch the result string 17 | let output = rant.run(&program)?; 18 | println!("{}", output); 19 | Ok(()) 20 | } -------------------------------------------------------------------------------- /examples/helloworld.rant: -------------------------------------------------------------------------------- 1 | {Hi|Hey|Hello} world! -------------------------------------------------------------------------------- /examples/item-sword.rant: -------------------------------------------------------------------------------- 1 | [%enchant-item: item] { 2 | @{ 3 | @if @not [has: ; enchantments]: { 4 | 5 | } 6 | <$damage = 0; $weight = 0> 7 | 8 | # Apply enchantment 9 | [augment-self: ; (:: {{ 10 | fire @weight 10 | 11 | ice @weight 12 | 12 | lightning @weight 5 | 13 | wind @weight 8 | 14 | time @weight 3 | 15 | demon @weight 1 16 | }} = 1)] 17 | 18 | # Apply bonus damage from enchantment 19 | [augment-self: ; (:: ; )] 20 | } 21 | } 22 | 23 | [%refine-item: item] { 24 | <$weight = ; $damage = > 25 | [rand: 1; 3 > %refine-amount] 26 | @if @gt; 0: { / > } 27 | * * [randf: 1; 1.5]> 28 | > 29 | > 30 | > 31 | } 32 | 33 | [%name-sword: item] { 34 | @{ 35 | { 36 | { 37 | {Big|Great|{Iron|Bronze|Steel}|Gleaming|Shiny|Long|Battle|Standard|War{|time}|{Knight's|Warrior's}|Vengeful} 38 | | 39 | {Masterful|Godly|Last|Amazing|Holy|Fearful|Abundant} @weight {[len: ] - 2} * 10 40 | | 41 | {Boring|Ordinary|Average|Common|Standard} @weight @not @and @not 42 | } @weight [either: ; 1; 3] 43 | | 44 | {Horrible|Terrible|Hell|Damned|Banished|Fate|Cryptid|Headless|Risen|Punished|Devil's|Horned} @weight * 3 45 | | 46 | {Blazing|Hot|Flaming|Fiery|Ember|Lava|Magma|Sun|Smoldering|Smoking|{Red|White}-hot} @weight 47 | | 48 | {Roaring|Freezing|Bitter|Sub-zero|Frosty|Frigid|Winter} @weight 49 | | 50 | {Whistling|Howling|Flighted|Airborne|Gusting|Tornadic|Soaring} @weight 51 | | 52 | {Century|Early|Late|Speedy|Slow-motion|Midnight} @weight 53 | }\s 54 | { 55 | {{Sword|Cutter|Blade}|{Smasher|Slicer|Dicer|Cutter-Upper}|Cutlass|Broadsword|Scimitar|Machete|Katana|Greatsword @weight * 0.7} 56 | | 57 | {Sunbeam|Heatstroke|Sun|Burninator|Crucible|Flame|Nuclear Fuel Rod @weight 0.01} @weight 58 | | 59 | {Glacier|Blizzard|Snowstorm|Icicle|Freezer|Coldsnap|Flash Freeze|Avalanche} @weight 60 | | 61 | {Hurricane|Leafblower|Windstorm|Tornado|Twister|Gale|{Wing|Feather}} @weight 62 | | 63 | { 64 | {Nightmare|Apocalypse|Temptress|Devil|Summoner|Ritual|Tragedy|Horseman} 65 | | 66 | {Lake of Fire|{Flames|Fires} of Hell|{Soul|Sin|Sinner}-{Burner|Melter|Smelter}} @weight * 10 67 | } @weight 68 | | 69 | {Clockstopper|Sundial} @weight 70 | } 71 | } 72 | @if : { 73 | \s\({Hardened|Fine|Refined|Special Edition}\) 74 | } 75 | { @edit name: > } 76 | } 77 | 78 | [%gen-base-sword-stats] { 79 | < 80 | %weight = [randf: 0.1; 10]; 81 | %damage = / 16.0 + [randf: 1; 10]; 82 | > 83 | (:: ; ) 84 | } 85 | 86 | [%gen-sword] { 87 | # Generate the base stats for the sword 88 | [gen-base-sword-stats > %sword] 89 | 90 | # Enchant it 91 | @if [maybe: 0.9]: { 92 | [rep: {1 @weight 10 | 2 @weight 3 | 3 @weight 1}] { 93 | [enchant-item: ] 94 | } 95 | } 96 | 97 | # Refine it 98 | @if [maybe: 0.05]: { 99 | [refine-item: ] 100 | } 101 | 102 | # Name it 103 | [name-sword: ] 104 | 105 | # Return the sword to the caller 106 | 107 | } 108 | 109 | [%stat-num-str: n] { 110 | [num-fmt-precision: 2] 111 | "" 112 | } 113 | 114 | [rep: 10][sep: \n] 115 | { 116 | [gen-sword |> cat: 117 | <[]/name>\n; 118 | ~" - Enchantments: " <[]/enchantments ? none>\n; 119 | ~" - Weight: " [stat-num-str: <[]/weight>]\n; 120 | ~" - Damage: " [stat-num-str: <[]/damage>] 121 | ] 122 | } -------------------------------------------------------------------------------- /examples/mandelbrot.rant: -------------------------------------------------------------------------------- 1 | # I wrote this mostly as a joke. It's very slow. 2 | 3 | [%vlen: v] { [sum: [pow: **; 2.0 |> tuple] |> sqrt] } 4 | 5 | <%iter-colors = (🟫;🟥;🟧;🟨;🟩;🟦;🟪)> 6 | 7 | [%mandelbrot: w; h; n] { 8 | [rep: ][sep: \n] 9 | { 10 | <%y = [step-index |> to-float]> 11 | [rep: ] 12 | { 13 | < 14 | %x = [step-index] * 0.35; 15 | %c = ( * 7.75 / - 2.0; * 2.0 / - 1.0); 16 | $z = (0.0; 0.0); 17 | $out = ⬛; 18 | > 19 | 20 | [rep: ] 21 | { 22 | ** 2 - ** 2 + ; * * 2.0 + )> 23 | <%i = [step-index]> 24 | @if [vlen: ] @gt 2.0: { 25 | % [len: ])>> @break 26 | } 27 | } 28 | 29 | } 30 | } 31 | } 32 | 33 | [mandelbrot: 75; 50; 20] -------------------------------------------------------------------------------- /examples/modules/test-module.rant: -------------------------------------------------------------------------------- 1 | [require: test-submodule] 2 | 3 | <$module = (::)> 4 | 5 | [$module/test] { 6 | Hello from test-module! 7 | } 8 | 9 | [$module/test-sub] { 10 | [test-submodule/test-sub] 11 | } 12 | 13 | -------------------------------------------------------------------------------- /examples/modules/test-submodule.rant: -------------------------------------------------------------------------------- 1 | # This file is used by test_module to demonstrate sub-dependency discovery. 2 | <$module=(::)> 3 | [$module/test-sub] { 4 | Hello from test-submodule! 5 | } 6 | -------------------------------------------------------------------------------- /examples/personality.rant: -------------------------------------------------------------------------------- 1 | # Generate a map of the "Big Five" personality traits 2 | # with random rating values that add up to 50 3 | <$personality = 4 | [assoc: 5 | (ope; con; ext; agr; neu); 6 | [rand-list-sum: 50; 5; [rand: 1; 10]] # Use random variance to reduce trait deviation 7 | ] 8 | > 9 | 10 | # Print the values 11 | [ws-fmt:verbatim] 12 | "Openness to experience:" \n 13 | "Conscientiousness:" \n 14 | "Extraversion:" \n 15 | "Agreeableness:" \n 16 | "Neuroticism:" \n -------------------------------------------------------------------------------- /examples/phone-numbers.rant: -------------------------------------------------------------------------------- 1 | <%us-area-code-ranges = [irange: ***( 2 | (201;219); (224;225); (228;229); (231;231); (234;234); (239;240); (248;248); (251;254); (256;256); (260;260); (262;262); (267;267); (269;269); (270;270); (276;276); (281;281); 3 | (301;305); (307;310); (312;321); (323;323); (325;325); (330;330); (334;334); (336;337); (339;339); (347;347); (351;352); (360;361); (386;386); 4 | (401;402); (404;410); (412;415); (417;417); (419;419); (423;423); (425;425); (430;430); (432;432); (434;435); (440;440); (443;443); (469;469); (478;479); (480;480); (484;484); 5 | (501;505); (507;510); (512;513); (515;518); (520;520); (530;530); (540;541); (551;551); (559;559); (561;563); (567;567); (570;571); (573;575); (580;580); (585;586); 6 | (601;603); (605;610); (612;612); (614;620); (623;623); (626;626); (630;631); (636;636); (641;641); (646;646); (650;651); (660;662); (678;678); (682;682); 7 | (701;704); (706;708); (712;720); (724;724); (727;727); (731;732); (734;734); (740;740); (754;754); (757;757); (760;760); (763;763); (765;765); (770;770); (772;775); (781;781); (785;786); 8 | (801;806); (808;808); (810;810); (812;818); (828;828); (830;832); (843;843); (845;845); (847;848); (850;850); (856;860); (862;866); (870;870); 9 | (901;901); (903;904); (906;910); (912;920); (925;925); (928;928); (931;931); (936;937); (940;941); (947;947); (949;949); (951;952); (954;954); (956;956); (970;973); (978;980); (985;985); (989;989) 10 | ) |> tuple]> 11 | 12 | [%phone-number-us] { 13 | \(([num-fmt-padding: 3][pick-sparse: *])\)\s 14 | [dig:3]\-[dig:4] 15 | } 16 | 17 | [rep:10][sep:\n]{[phone-number-us]} -------------------------------------------------------------------------------- /examples/require.rant: -------------------------------------------------------------------------------- 1 | @require "modules/test-module" 2 | 3 | [test-module/test] -------------------------------------------------------------------------------- /examples/selector-rainbows.rant: -------------------------------------------------------------------------------- 1 | <%width = 16; %height = 6> 2 | <%sels = [ 3 | cat: **(: random; one; forward; forward-mirror; forward-clamp; reverse; reverse-mirror; reverse-clamp; deck; deck-loop; deck-clamp; deck-mirror; ping; pong; no-double) 4 | |> cat: (:: name = []; state = [mksel: []]) 5 | |> list 6 | ]> 7 | 8 | [len: |> rep] 9 | [mut: [?: el] { \n [el] \n }] 10 | { 11 | <%current-sel = > 12 | \:\n 13 | [rep: ] 14 | [sep: \n] 15 | { 16 | [rep: ] 17 | [sel: ] 18 | {⚫|🟤|🔴|🟠|🟡|🟢|🔵|🟣} 19 | } 20 | } -------------------------------------------------------------------------------- /examples/substitution-cipher.rant: -------------------------------------------------------------------------------- 1 | # Constructs a substitution cipher function 2 | [%make-cipher: alphabet] { 3 | < 4 | $letters = [split: ]; 5 | $sub-letters = [shuffled: ]; 6 | $cipher = [assoc: ; ]; 7 | $cipher-rev = [assoc: ; ]; 8 | > 9 | # Return cipher functions 10 | (:: 11 | encode = [?: message] { 12 | [split: |> translate: |> sum] 13 | }; 14 | decode = [?: message] { 15 | [split: |> translate: |> sum] 16 | }; 17 | ) 18 | } 19 | 20 | # Create a cipher and encode/decode a message 21 | <$cipher = [make-cipher: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"]> 22 | <$secret-message = "The quick brown fox jumps over the lazy dog."> 23 | <$encoded-message = [cipher/encode: ]> 24 | <$decoded-message = [cipher/decode: ]> 25 | 26 | # Finally, print the result 27 | Original: \"\"\n 28 | Encoded: \"\"\n 29 | Decoded: \"\"\n 30 | -------------------------------------------------------------------------------- /examples/text-filter.rant: -------------------------------------------------------------------------------- 1 | [%wavy: s] { 2 | <%sync = [mksel: forward]> 3 | [cat: ** 4 | |> !{ [sel: ] { | } } 5 | ] 6 | } 7 | 8 | [wavy: The quick brown fox jumps over the lazy dog.] 9 | 10 | -------------------------------------------------------------------------------- /examples/weighting.rant: -------------------------------------------------------------------------------- 1 | [rep:20][sep:\n] 2 | { 3 | { 4 | # Larger weights are more likely to be selected 5 | "boring" @weight 2 6 | | 7 | # Unweighted elements have a default weight of 1 8 | "common" 9 | | 10 | # Smaller weights are less likely to be selected 11 | "uncommon" @weight 0.5 12 | | 13 | "rare" @weight 0.1 14 | | 15 | # Elements with a weight of 0 are never selected. 16 | # If all elements have weights of 0, the last element is selected. 17 | "unused" @weight 0 18 | } {herb|fungus|potion|poison|mineral|book} 19 | } -------------------------------------------------------------------------------- /src/collections/list.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, iter::FromIterator, ops::{DerefMut, Deref, Add}, rc::Rc}; 2 | use crate::{RantValue, RantTuple, RantTupleHandle}; 3 | 4 | 5 | /// Reference handle for a Rant list 6 | #[derive(Debug, Clone, PartialEq)] 7 | pub struct RantListHandle(Rc>); 8 | 9 | impl RantListHandle { 10 | /// Makes a copy of the underlying list and returns a handle containing it. 11 | pub fn cloned(&self) -> Self { 12 | Self(Rc::new(RefCell::new((*self.0.borrow()).clone()))) 13 | } 14 | } 15 | 16 | impl From for RantListHandle { 17 | #[inline] 18 | fn from(list: RantList) -> Self { 19 | Self(Rc::new(RefCell::new(list))) 20 | } 21 | } 22 | 23 | impl Deref for RantListHandle { 24 | type Target = RefCell; 25 | #[inline] 26 | fn deref(&self) -> &Self::Target { 27 | self.0.as_ref() 28 | } 29 | } 30 | 31 | /// Represents Rant's `list` type, which stores an ordered, mutable collection of values. 32 | #[derive(Debug, Clone, PartialEq)] 33 | pub struct RantList(Vec); 34 | 35 | impl RantList { 36 | /// Creates an empty RantList. 37 | pub fn new() -> Self { 38 | Self(vec![]) 39 | } 40 | 41 | /// Creates an empty RantList with the specified initial capacity. 42 | pub fn with_capacity(capacity: usize) -> Self { 43 | Self(Vec::with_capacity(capacity)) 44 | } 45 | 46 | #[inline(always)] 47 | pub fn len(&self) -> usize { 48 | self.0.len() 49 | } 50 | 51 | #[inline(always)] 52 | pub fn is_empty(&self) -> bool { 53 | self.0.is_empty() 54 | } 55 | 56 | #[inline] 57 | pub fn into_handle(self) -> RantListHandle { 58 | RantListHandle::from(self) 59 | } 60 | 61 | #[inline] 62 | pub fn into_rant_tuple(self) -> RantTuple { 63 | RantTuple::from(self.0) 64 | } 65 | 66 | #[inline] 67 | pub fn to_rant_tuple(&self) -> RantTuple { 68 | RantTuple::from(self.0.clone()) 69 | } 70 | } 71 | 72 | impl From> for RantList { 73 | fn from(list: Vec) -> Self { 74 | Self(list) 75 | } 76 | } 77 | 78 | impl Default for RantList { 79 | fn default() -> Self { 80 | Self::new() 81 | } 82 | } 83 | 84 | impl Deref for RantList { 85 | type Target = Vec; 86 | fn deref(&self) -> &Self::Target { 87 | &self.0 88 | } 89 | } 90 | 91 | impl DerefMut for RantList { 92 | fn deref_mut(&mut self) -> &mut Self::Target { 93 | &mut self.0 94 | } 95 | } 96 | 97 | impl FromIterator for RantList { 98 | fn from_iter>(iter: T) -> Self { 99 | let mut list = Self::new(); 100 | for item in iter { 101 | list.push(item); 102 | } 103 | list 104 | } 105 | } 106 | 107 | impl IntoIterator for RantList { 108 | type Item = RantValue; 109 | type IntoIter = std::vec::IntoIter; 110 | 111 | fn into_iter(self) -> Self::IntoIter { 112 | self.0.into_iter() 113 | } 114 | } 115 | 116 | impl Add for RantList { 117 | type Output = RantList; 118 | 119 | fn add(self, rhs: RantList) -> Self::Output { 120 | self.into_iter().chain(rhs.into_iter()).collect::() 121 | } 122 | } 123 | 124 | impl Add<&RantList> for RantList { 125 | type Output = RantList; 126 | 127 | fn add(self, rhs: &RantList) -> Self::Output { 128 | self.into_iter().chain(rhs.iter().cloned()).collect::() 129 | } 130 | } 131 | 132 | impl Add for RantList { 133 | type Output = RantList; 134 | 135 | fn add(self, rhs: RantTuple) -> Self::Output { 136 | self.into_iter().chain(rhs.into_iter()).collect::() 137 | } 138 | } 139 | 140 | impl Add<&RantTuple> for RantList { 141 | type Output = RantList; 142 | 143 | fn add(self, rhs: &RantTuple) -> Self::Output { 144 | self.into_iter().chain(rhs.iter().cloned()).collect::() 145 | } 146 | } 147 | 148 | impl Add for &RantList { 149 | type Output = RantList; 150 | 151 | fn add(self, rhs: RantList) -> Self::Output { 152 | self.iter().cloned().chain(rhs.into_iter()).collect::() 153 | } 154 | } 155 | 156 | impl Add<&RantList> for &RantList { 157 | type Output = RantList; 158 | 159 | fn add(self, rhs: &RantList) -> Self::Output { 160 | self.iter().cloned().chain(rhs.iter().cloned()).collect::() 161 | } 162 | } 163 | 164 | impl Add for &RantList { 165 | type Output = RantList; 166 | 167 | fn add(self, rhs: RantTuple) -> Self::Output { 168 | self.iter().cloned().chain(rhs.into_iter()).collect::() 169 | } 170 | } 171 | 172 | impl Add<&RantTuple> for &RantList { 173 | type Output = RantList; 174 | 175 | fn add(self, rhs: &RantTuple) -> Self::Output { 176 | self.iter().cloned().chain(rhs.iter().cloned()).collect::() 177 | } 178 | } 179 | 180 | impl Add for RantListHandle { 181 | type Output = RantListHandle; 182 | 183 | fn add(self, rhs: Self) -> Self::Output { 184 | (&*self.borrow() + &*rhs.borrow()).into_handle() 185 | } 186 | } 187 | 188 | impl Add for RantListHandle { 189 | type Output = RantListHandle; 190 | 191 | fn add(self, rhs: RantTupleHandle) -> Self::Output { 192 | (&*self.borrow() + &*rhs).into_handle() 193 | } 194 | } -------------------------------------------------------------------------------- /src/collections/map.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Cow, cell::RefCell, ops::Deref, rc::Rc}; 2 | use crate::{InternalString, RantValue, RantList}; 3 | use fnv::FnvHashMap; 4 | 5 | /// Reference handle for a Rant map 6 | #[derive(Debug, Clone)] 7 | pub struct RantMapHandle(Rc>); 8 | 9 | impl RantMapHandle { 10 | pub fn cloned(&self) -> Self { 11 | Self(Rc::new(RefCell::new((*self.0.borrow()).clone()))) 12 | } 13 | } 14 | 15 | impl PartialEq for RantMapHandle { 16 | fn eq(&self, other: &Self) -> bool { 17 | self.0.as_ptr() == other.0.as_ptr() 18 | } 19 | } 20 | 21 | impl From for RantMapHandle { 22 | #[inline] 23 | fn from(map: RantMap) -> Self { 24 | Self(Rc::new(RefCell::new(map))) 25 | } 26 | } 27 | 28 | impl Deref for RantMapHandle { 29 | type Target = RefCell; 30 | #[inline] 31 | fn deref(&self) -> &Self::Target { 32 | self.0.as_ref() 33 | } 34 | } 35 | 36 | /// Represents Rant's `map` type, which stores a mutable collection of key-value pairs. 37 | /// Map keys are always strings. 38 | #[derive(Debug, Clone)] 39 | pub struct RantMap { 40 | /// The physical contents of the map 41 | map: FnvHashMap, 42 | /// The prototype of the map 43 | proto: Option 44 | } 45 | 46 | impl RantMap { 47 | pub fn new() -> Self { 48 | Self { 49 | map: Default::default(), 50 | proto: None 51 | } 52 | } 53 | 54 | #[inline] 55 | pub fn into_handle(self) -> RantMapHandle { 56 | RantMapHandle::from(self) 57 | } 58 | 59 | #[inline] 60 | pub fn clear(&mut self) { 61 | self.map.clear(); 62 | } 63 | 64 | #[inline] 65 | pub fn raw_len(&self) -> usize { 66 | self.map.len() 67 | } 68 | 69 | #[inline] 70 | pub fn is_empty(&self) -> bool { 71 | self.map.is_empty() 72 | } 73 | 74 | #[inline] 75 | pub fn proto(&self) -> Option { 76 | self.proto.clone() 77 | } 78 | 79 | #[inline] 80 | pub fn extend>(&mut self, other: M) 81 | { 82 | for (k, v) in other.map.iter() { 83 | self.map.insert(k.clone(), v.clone()); 84 | } 85 | } 86 | 87 | #[inline] 88 | pub fn set_proto(&mut self, proto: Option) { 89 | self.proto = proto; 90 | } 91 | 92 | #[inline] 93 | pub fn raw_set(&mut self, key: &str, val: RantValue) { 94 | self.map.insert(InternalString::from(key), val); 95 | } 96 | 97 | #[inline] 98 | pub fn raw_remove(&mut self, key: &str) { 99 | self.map.remove(key); 100 | } 101 | 102 | #[inline] 103 | pub fn raw_take(&mut self, key: &str) -> Option { 104 | self.map.remove(key) 105 | } 106 | 107 | #[inline] 108 | pub fn raw_get(&self, key: &str) -> Option<&RantValue> { 109 | self.map.get(key) 110 | } 111 | 112 | #[inline] 113 | pub fn get(&self, key: &str) -> Option> { 114 | // Check if the member is in the map itself 115 | if let Some(member) = self.raw_get(key) { 116 | return Some(Cow::Borrowed(member)) 117 | } 118 | 119 | // Climb the prototype chain to see if the member is in one of them 120 | let mut next_proto = self.proto.as_ref().map(RantMapHandle::clone); 121 | while let Some(cur_proto) = next_proto { 122 | let cur_proto_ref = cur_proto.borrow(); 123 | if let Some(proto_member) = cur_proto_ref.raw_get(key) { 124 | return Some(Cow::Owned(proto_member.clone())); 125 | } 126 | next_proto = cur_proto_ref.proto.as_ref().map(RantMapHandle::clone); 127 | } 128 | None 129 | } 130 | 131 | #[inline] 132 | pub fn raw_has_key(&self, key: &str) -> bool { 133 | self.map.contains_key(key) 134 | } 135 | 136 | #[inline] 137 | pub fn raw_keys(&self) -> RantList { 138 | self.map.keys().map(|k| RantValue::String(k.as_str().into())).collect() 139 | } 140 | 141 | #[inline] 142 | pub fn raw_values(&self) -> RantList { 143 | self.map.values().cloned().collect() 144 | } 145 | 146 | #[inline] 147 | pub(crate) fn raw_pairs_internal(&self) -> impl Iterator { 148 | self.map.iter().map(|(k, v)| (k.as_str(), v)) 149 | } 150 | } 151 | 152 | impl Default for RantMap { 153 | fn default() -> Self { 154 | RantMap::new() 155 | } 156 | } -------------------------------------------------------------------------------- /src/collections/mod.rs: -------------------------------------------------------------------------------- 1 | mod list; 2 | mod map; 3 | mod range; 4 | mod tuple; 5 | 6 | // Re-export 7 | pub use self::list::*; 8 | pub use self::map::*; 9 | pub use self::range::*; 10 | pub use self::tuple::*; -------------------------------------------------------------------------------- /src/collections/range.rs: -------------------------------------------------------------------------------- 1 | use std::{cmp::Ordering, fmt::Display}; 2 | use crate::{RantValue, util, RantList, RantTuple}; 3 | 4 | /// Represents Rant's `range` type, which characterizes a closed range of integers with an exclusive end bound. 5 | /// 6 | /// Includes a `step` value which specifies how far apart adjacent values in the range should be. 7 | /// If the size of the range isn't evenly divisible by `step`, the ending step will be smaller. 8 | #[derive(Debug, Clone, PartialEq)] 9 | pub struct RantRange { 10 | start: i64, 11 | end: i64, 12 | step: i64, 13 | } 14 | 15 | impl RantRange { 16 | #[inline] 17 | pub fn new(start: i64, end: i64, abs_step: u64) -> Self { 18 | let abs_step = if abs_step == 0 { 19 | 1 20 | } else { 21 | abs_step 22 | }; 23 | 24 | Self { 25 | start, 26 | end, 27 | step: if end < start { 28 | -(abs_step as i64) 29 | } else { 30 | abs_step as i64 31 | }, 32 | } 33 | } 34 | } 35 | 36 | impl Default for RantRange { 37 | fn default() -> Self { 38 | Self { 39 | start: 0, 40 | end: 0, 41 | step: 1, 42 | } 43 | } 44 | } 45 | 46 | impl Display for RantRange { 47 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 48 | let comparison = if self.start < self.end { 49 | "<" 50 | } else { 51 | ">" 52 | }; 53 | let op = if self.start < self.end { '+' } else { '-' }; 54 | write!(f, "[range({} {} ", self.start, op)?; 55 | if self.step > 1 { 56 | write!(f, "{}", self.step)?; 57 | } 58 | write!(f, "x {} {})]", comparison, self.end)?; 59 | Ok(()) 60 | } 61 | } 62 | 63 | impl RantRange { 64 | /// Gets the start bound of the range. 65 | #[inline] 66 | pub fn start(&self) -> i64 { 67 | self.start 68 | } 69 | 70 | /// Gets the end bound of the range. 71 | #[inline] 72 | pub fn end(&self) -> i64 { 73 | self.end 74 | } 75 | 76 | /// Gets the absolute step value of the range. 77 | #[inline] 78 | pub fn abs_step(&self) -> u64 { 79 | self.step.saturating_abs() as u64 80 | } 81 | 82 | /// Gets the signed step value of the range. 83 | #[inline] 84 | pub fn step(&self) -> i64 { 85 | self.step 86 | } 87 | 88 | /// Gets the absolute difference between the start and end bounds, ignoring the step size. 89 | #[inline(always)] 90 | pub fn abs_size(&self) -> usize { 91 | self.end.saturating_sub(self.start).saturating_abs() as usize 92 | } 93 | 94 | /// Gets the total number of steps in the range, taking into account the step size. 95 | #[inline] 96 | pub fn len(&self) -> usize { 97 | ((self.end - self.start) as f64 / self.step as f64).ceil() as usize 98 | } 99 | 100 | /// Gets a reversed copy of the range. 101 | #[inline] 102 | pub fn reversed(&self) -> Self { 103 | if self.start == self.end { 104 | return self.clone() 105 | } 106 | 107 | let shift: i64 = if self.start < self.end { -1 } else { 1 }; 108 | 109 | Self { 110 | start: self.end + shift, 111 | end: self.start + shift, 112 | step: -self.step, 113 | } 114 | } 115 | 116 | /// Indicates whether there are no steps in the range. 117 | #[inline] 118 | pub fn is_empty(&self) -> bool { 119 | self.start == self.end || self.abs_step() as usize > self.abs_size() 120 | } 121 | 122 | /// Gets the nth value in the range. 123 | #[inline] 124 | pub fn get(&self, index: usize) -> Option { 125 | let offset = self.step * index as i64; 126 | (index < self.len()).then(|| self.start + offset) 127 | } 128 | 129 | #[inline] 130 | fn get_bound(&self, index: usize) -> Option { 131 | match index.cmp(&self.len()) { 132 | Ordering::Less => Some(self.start + self.step * index as i64), 133 | Ordering::Equal => Some(self.end), 134 | Ordering::Greater => None 135 | } 136 | } 137 | 138 | #[inline] 139 | pub fn sliced(&self, from: Option, to: Option) -> Option { 140 | let abs_step = self.abs_step(); 141 | Some(match (from, to) { 142 | (None, None) => self.clone(), 143 | (None, Some(to)) => Self::new(self.get_bound(0)?, self.get_bound(to)?, abs_step), 144 | (Some(from), None) => Self::new(self.get_bound(from)?, self.get_bound(self.len())?, abs_step), 145 | (Some(from), Some(to)) => { 146 | let (from, to) = util::minmax(from, to); 147 | Self::new(self.get_bound(from)?, self.get_bound(to)?, abs_step) 148 | }, 149 | }) 150 | } 151 | 152 | /// Enumerates the values of the range and returns the results as a Rant `list` object. 153 | #[inline] 154 | pub fn to_rant_list(&self) -> RantList { 155 | let n = self.len(); 156 | let mut list = RantList::new(); 157 | 158 | for i in 0..n { 159 | if let Some(item) = self.get(i) { 160 | list.push(RantValue::Int(item)); 161 | } 162 | } 163 | 164 | list 165 | } 166 | 167 | #[inline] 168 | pub fn to_rant_tuple(&self) -> RantTuple { 169 | let n = self.len(); 170 | let mut items = Vec::with_capacity(n); 171 | 172 | for i in 0..n { 173 | if let Some(item) = self.get(i) { 174 | items.push(RantValue::Int(item)); 175 | } 176 | } 177 | 178 | items.into() 179 | } 180 | } -------------------------------------------------------------------------------- /src/collections/tuple.rs: -------------------------------------------------------------------------------- 1 | use std::{iter::FromIterator, ops::{Deref, Add}, rc::Rc}; 2 | use crate::{RantValue, RantList, RantListHandle}; 3 | 4 | /// Reference handle for a Rant tuple 5 | #[derive(Debug, Clone, PartialEq)] 6 | pub struct RantTupleHandle(Rc); 7 | 8 | impl RantTupleHandle { 9 | /// Makes a copy of the underlying tuple and returns a handle containing it. 10 | pub fn cloned(&self) -> Self { 11 | Self(Rc::new((*self.0).clone())) 12 | } 13 | } 14 | 15 | impl From for RantTupleHandle { 16 | #[inline] 17 | fn from(tuple: RantTuple) -> Self { 18 | Self(Rc::new(tuple)) 19 | } 20 | } 21 | 22 | impl Deref for RantTupleHandle { 23 | type Target = RantTuple; 24 | #[inline] 25 | fn deref(&self) -> &Self::Target { 26 | self.0.as_ref() 27 | } 28 | } 29 | 30 | /// Represents Rant's `tuple` type, which stores an ordered, immutable collection of values. 31 | #[derive(Debug, Clone, PartialEq, Default)] 32 | pub struct RantTuple(Vec); 33 | 34 | impl RantTuple { 35 | #[inline] 36 | pub fn new() -> Self { 37 | Self(vec![]) 38 | } 39 | 40 | #[inline] 41 | pub fn is_empty(&self) -> bool { 42 | self.0.is_empty() 43 | } 44 | 45 | #[inline] 46 | pub fn len(&self) -> usize { 47 | self.0.len() 48 | } 49 | 50 | #[inline] 51 | pub fn into_handle(self) -> RantTupleHandle { 52 | RantTupleHandle::from(self) 53 | } 54 | 55 | #[inline] 56 | pub fn to_rant_list(&self) -> RantList { 57 | RantList::from(self.0.clone()) 58 | } 59 | 60 | #[inline] 61 | pub fn into_rant_list(self) -> RantList { 62 | RantList::from(self.0) 63 | } 64 | } 65 | 66 | impl From> for RantTuple { 67 | fn from(values: Vec) -> Self { 68 | Self(values) 69 | } 70 | } 71 | 72 | impl Deref for RantTuple { 73 | type Target = Vec; 74 | fn deref(&self) -> &Self::Target { 75 | &self.0 76 | } 77 | } 78 | 79 | impl<'a> FromIterator<&'a RantValue> for RantTuple { 80 | fn from_iter>(iter: T) -> Self { 81 | let vec: Vec = iter.into_iter().cloned().collect(); 82 | Self(vec) 83 | } 84 | } 85 | 86 | impl FromIterator for RantTuple { 87 | fn from_iter>(iter: T) -> Self { 88 | let vec: Vec = iter.into_iter().collect(); 89 | Self(vec) 90 | } 91 | } 92 | 93 | impl IntoIterator for RantTuple { 94 | type Item = RantValue; 95 | type IntoIter = std::vec::IntoIter; 96 | 97 | fn into_iter(self) -> Self::IntoIter { 98 | self.0.into_iter() 99 | } 100 | } 101 | 102 | impl Add for RantTuple { 103 | type Output = RantTuple; 104 | 105 | fn add(self, rhs: Self) -> Self::Output { 106 | self.into_iter().chain(rhs.into_iter()).collect::() 107 | } 108 | } 109 | 110 | impl Add<&RantTuple> for RantTuple { 111 | type Output = RantTuple; 112 | 113 | fn add(self, rhs: &RantTuple) -> Self::Output { 114 | self.into_iter().chain(rhs.iter().cloned()).collect::() 115 | } 116 | } 117 | 118 | impl Add for RantTuple { 119 | type Output = RantList; 120 | 121 | fn add(self, rhs: RantList) -> Self::Output { 122 | self.into_iter().chain(rhs.into_iter()).collect::() 123 | } 124 | } 125 | 126 | impl Add<&RantList> for RantTuple { 127 | type Output = RantList; 128 | 129 | fn add(self, rhs: &RantList) -> Self::Output { 130 | self.into_iter().chain(rhs.iter().cloned()).collect::() 131 | } 132 | } 133 | 134 | impl Add for &RantTuple { 135 | type Output = RantTuple; 136 | 137 | fn add(self, rhs: RantTuple) -> Self::Output { 138 | self.iter().cloned().chain(rhs.into_iter()).collect::() 139 | } 140 | } 141 | 142 | impl Add<&RantTuple> for &RantTuple { 143 | type Output = RantTuple; 144 | 145 | fn add(self, rhs: &RantTuple) -> Self::Output { 146 | self.iter().cloned().chain(rhs.iter().cloned()).collect::() 147 | } 148 | } 149 | 150 | impl Add for &RantTuple { 151 | type Output = RantList; 152 | 153 | fn add(self, rhs: RantList) -> Self::Output { 154 | self.iter().cloned().chain(rhs.into_iter()).collect::() 155 | } 156 | } 157 | 158 | impl Add<&RantList> for &RantTuple { 159 | type Output = RantList; 160 | 161 | fn add(self, rhs: &RantList) -> Self::Output { 162 | self.iter().cloned().chain(rhs.iter().cloned()).collect::() 163 | } 164 | } 165 | 166 | impl Add for RantTupleHandle { 167 | type Output = RantTupleHandle; 168 | 169 | fn add(self, rhs: Self) -> Self::Output { 170 | (&*self + &*rhs).into_handle() 171 | } 172 | } 173 | 174 | impl Add for RantTupleHandle { 175 | type Output = RantListHandle; 176 | 177 | fn add(self, rhs: RantListHandle) -> Self::Output { 178 | (&*self + &*rhs.borrow()).into_handle() 179 | } 180 | } -------------------------------------------------------------------------------- /src/compiler/lexer.rs: -------------------------------------------------------------------------------- 1 | use logos::*; 2 | use crate::InternalString; 3 | 4 | // Module keywords 5 | pub const KW_REQUIRE: &str = "require"; 6 | 7 | // Control flow keywords 8 | pub const KW_RETURN: &str = "return"; 9 | pub const KW_BREAK: &str = "break"; 10 | pub const KW_CONTINUE: &str = "continue"; 11 | pub const KW_WEIGHT: &str = "weight"; 12 | pub const KW_IF: &str = "if"; 13 | pub const KW_ELSEIF: &str = "elseif"; 14 | pub const KW_ELSE: &str = "else"; 15 | 16 | // Value constant keywords 17 | pub const KW_TRUE: &str = "true"; 18 | pub const KW_FALSE: &str = "false"; 19 | 20 | // Hinting keywords 21 | pub const KW_TEXT: &str = "text"; 22 | 23 | // Output modifier keywords 24 | pub const KW_EDIT: &str = "edit"; 25 | 26 | // Infix operator keywords 27 | pub const KW_NEG: &str = "neg"; 28 | pub const KW_NOT: &str = "not"; 29 | pub const KW_EQ: &str = "eq"; 30 | pub const KW_NEQ: &str = "neq"; 31 | pub const KW_GT: &str = "gt"; 32 | pub const KW_GE: &str = "ge"; 33 | pub const KW_LT: &str = "lt"; 34 | pub const KW_LE: &str = "le"; 35 | 36 | pub fn is_valid_keyword_name(kw_name: &str) -> bool { 37 | matches!(kw_name, 38 | KW_REQUIRE | 39 | KW_RETURN | KW_BREAK | KW_CONTINUE | KW_WEIGHT | KW_IF | KW_ELSEIF | KW_ELSE | 40 | KW_TRUE | KW_FALSE | KW_TEXT | KW_EDIT | 41 | KW_NEG | KW_NOT | 42 | KW_EQ | KW_NEQ | KW_GT | KW_GE | KW_LT | KW_LE 43 | ) 44 | } 45 | 46 | #[derive(Debug, PartialEq)] 47 | pub struct KeywordInfo { 48 | pub name: InternalString, 49 | pub is_valid: bool, 50 | } 51 | 52 | /// Represents the contents of a positive float literal token. 53 | #[derive(Debug, PartialEq)] 54 | pub enum PositiveFloatToken { 55 | Value(f64), 56 | OutOfRange, 57 | } 58 | 59 | /// Represents the contents of a positive integer literal token. 60 | #[derive(Debug, PartialEq)] 61 | pub enum PositiveIntegerToken { 62 | Value(u64), 63 | OutOfRange, 64 | } 65 | 66 | /// Represents an escape sequence output. 67 | #[derive(Debug, PartialEq)] 68 | pub enum ParsedEscape { 69 | Char(char), 70 | InvalidChar(char), 71 | InvalidUnicode(String), 72 | } 73 | 74 | #[derive(Logos, Debug, PartialEq)] 75 | pub enum RantToken { 76 | /// Sequence of printable non-whitespace characters that isn't a number 77 | /// This regex is so crazy because simply doing [\w\-_]+ would accidentally capture negative numbers 78 | #[error] 79 | #[regex(r"([0-9]+(\.[0-9]+([Ee][+\-]?\d+)?|[Ee][+\-]?\d+)?[\p{L}\-_]|[\w_][\p{L}\-_]|\-[\p{L}\-_])[\w\-_]*", priority = 1)] 80 | Fragment, 81 | 82 | /// Sequence of printable whitespace characters 83 | #[regex(r"\s+", filter_bs, priority = 2)] 84 | Whitespace, 85 | 86 | /// Sequence of non-printable whitespace characters 87 | #[regex(r"[\r\n]+\s*|\s*[\r\n]+", logos::skip, priority = 3)] 88 | IgnoredWhitespace, 89 | 90 | /// `-` 91 | #[token("-", priority = 10)] 92 | Minus, 93 | 94 | /// `-=` 95 | #[token("-=", priority = 11)] 96 | MinusEquals, 97 | 98 | /// `{` 99 | #[token("{")] 100 | LeftBrace, 101 | 102 | /// `|` 103 | #[token("|")] 104 | VertBar, 105 | 106 | /// `|=` 107 | #[token("|=")] 108 | VertBarEquals, 109 | 110 | /// `}` 111 | #[token("}")] 112 | RightBrace, 113 | 114 | /// `|>` 115 | #[token("|>")] 116 | PipeOp, 117 | 118 | /// `[]` 119 | #[token("[]")] 120 | PipeValue, 121 | 122 | /// `[` 123 | #[token("[")] 124 | LeftBracket, 125 | 126 | /// `]` 127 | #[token("]")] 128 | RightBracket, 129 | 130 | /// `(` 131 | #[token("(")] 132 | LeftParen, 133 | 134 | /// `)` 135 | #[token(")")] 136 | RightParen, 137 | 138 | /// `<>` 139 | #[token("<>")] 140 | NothingLiteral, 141 | 142 | /// `<` 143 | #[token("<")] 144 | LeftAngle, 145 | 146 | /// `>` 147 | #[token(">")] 148 | RightAngle, 149 | 150 | /// `:` 151 | #[token(":")] 152 | Colon, 153 | 154 | /// `::` 155 | #[token("::")] 156 | DoubleColon, 157 | 158 | /// `..` 159 | #[token("..")] 160 | DoubleDot, 161 | 162 | /// `**` 163 | #[token("**")] 164 | DoubleStar, 165 | 166 | /// `**=` 167 | #[token("**=")] 168 | DoubleStarEquals, 169 | 170 | /// Labeled temporal operator, e.g. `*a*` 171 | #[regex(r"\*[\w\-_][\w\d\-_]*\*", parse_temporal_spread_label)] 172 | TemporalLabeled(InternalString), 173 | 174 | /// `*` 175 | #[token("*")] 176 | Star, 177 | 178 | /// `*=` 179 | #[token("*=")] 180 | StarEquals, 181 | 182 | /// `+` 183 | #[token("+")] 184 | Plus, 185 | 186 | /// `+=` 187 | #[token("+=")] 188 | PlusEquals, 189 | 190 | /// `=` 191 | #[token("=")] 192 | Equals, 193 | 194 | /// `?` 195 | #[token("?")] 196 | Question, 197 | 198 | /// `;` 199 | #[token(";")] 200 | Semicolon, 201 | 202 | /// `@` 203 | #[token("@", priority = 1)] 204 | At, 205 | 206 | /// Keyword, e.g. `@return` 207 | #[regex(r"@[a-z0-9_-]+", parse_keyword, priority = 2, ignore(case))] 208 | Keyword(KeywordInfo), 209 | 210 | /// `/` 211 | #[token("/")] 212 | Slash, 213 | 214 | /// `/=` 215 | #[token("/=")] 216 | SlashEquals, 217 | 218 | /// `^` 219 | #[token("^")] 220 | Caret, 221 | 222 | /// `^=` 223 | #[token("^=")] 224 | CaretEquals, 225 | 226 | /// `$` 227 | #[token("$")] 228 | Dollar, 229 | 230 | /// `%` 231 | #[token("%")] 232 | Percent, 233 | 234 | /// `%=` 235 | #[token("%=")] 236 | PercentEquals, 237 | 238 | /// ` 239 | #[token("`")] 240 | Hint, 241 | 242 | /// `~` 243 | #[token("~")] 244 | Sink, 245 | 246 | /// `&` 247 | #[token("&")] 248 | And, 249 | 250 | /// `&=` 251 | #[token("&=")] 252 | AndEquals, 253 | 254 | /// Unsigned integer literal 255 | #[regex(r"[0-9]+", parse_integer, priority = 2)] 256 | IntegerPositive(PositiveIntegerToken), 257 | 258 | /// Unsigned floating-point literal 259 | #[regex(r"[0-9]+(\.[0-9]+([Ee][+\-]?\d+)?|[Ee][+\-]?\d+)", parse_float, priority = 3)] 260 | FloatPositive(PositiveFloatToken), 261 | 262 | /// Represents inline and multi-line comments 263 | #[regex(r"\s*##([^#]|#[^#])*(##\s*)?", logos::skip, priority = 6)] 264 | #[regex(r"\s*#([^#][^\r\n]*)?\n?", logos::skip, priority = 5)] 265 | Comment, 266 | 267 | /// Represents any escape sequence 268 | #[regex(r"\\\S", parse_escape, priority = 10)] 269 | #[regex(r"\\x\S\S", parse_byte_escape, priority = 11)] 270 | #[regex(r"\\u\S\S\S\S", parse_unicode_escape, priority = 11)] 271 | #[regex(r"\\U\S\S\S\S\S\S\S\S", parse_unicode_escape, priority = 11)] 272 | #[regex(r"\\U\(\S+\)", parse_unicode_unsized_escape, priority = 12)] 273 | Escape(ParsedEscape), 274 | 275 | /// Represents a verbatim string literal, e.g. `"hello world"` 276 | #[regex(r#""(""|[^"])*""#, parse_string_literal)] 277 | StringLiteral(InternalString), 278 | 279 | /// Error token indicating an unterminated string literal, e.g. `"foo` 280 | #[regex(r#""(""|[^"])*"#)] 281 | UnterminatedStringLiteral, 282 | } 283 | 284 | fn parse_temporal_spread_label(lex: &mut Lexer) -> InternalString { 285 | let slice = lex.slice(); 286 | InternalString::from(&slice[1 .. slice.len() - 1]) 287 | } 288 | 289 | fn parse_string_literal(lex: &mut Lexer) -> InternalString { 290 | let literal = lex.slice(); 291 | let literal_content = &literal[1..literal.len() - 1]; 292 | let mut string_content = InternalString::new(); 293 | let mut prev_quote = false; 294 | for c in literal_content.chars() { 295 | match c { 296 | '"' => { 297 | if prev_quote { 298 | prev_quote = false; 299 | string_content.push('"'); 300 | } else { 301 | prev_quote = true; 302 | } 303 | }, 304 | c => { 305 | string_content.push(c) 306 | } 307 | } 308 | } 309 | string_content 310 | } 311 | 312 | fn parse_keyword(lex: &mut Lexer) -> KeywordInfo { 313 | let kwd_literal = lex.slice(); 314 | let kwd_content = &kwd_literal[1..]; 315 | KeywordInfo { 316 | is_valid: is_valid_keyword_name(kwd_content), 317 | name: InternalString::from(kwd_content), 318 | } 319 | } 320 | 321 | /// Filter function for whitespace lexer rule to exclude whitespace at start of source 322 | fn filter_bs(lex: &mut Lexer) -> Filter<()> { 323 | if lex.span().start > 0 { 324 | return Filter::Emit(()) 325 | } 326 | Filter::Skip 327 | } 328 | 329 | fn parse_escape(lex: &mut Lexer) -> ParsedEscape { 330 | let slice = lex.slice(); 331 | ParsedEscape::Char(match slice.chars().nth(1).unwrap() { 332 | 'r' => '\r', 333 | 'n' => '\n', 334 | 't' => '\t', 335 | '0' => '\0', 336 | 's' => ' ', 337 | c @ ( 338 | '(' | ')' | '[' | ']' | '{' | '}' | '<' | '>' | 339 | '\\' | '@' | ':' | ';' | '|' | '"' | 340 | '+' | '-' | '*' | '/' | '$' | '%' | '`' | '~' | '^' 341 | ) => c, 342 | c => return ParsedEscape::InvalidChar(c) 343 | }) 344 | } 345 | 346 | fn parse_byte_escape(lex: &mut Lexer) -> ParsedEscape { 347 | let slice = &lex.slice()[2..]; 348 | let c = u8::from_str_radix(slice, 16).ok().map(char::from); 349 | match c { 350 | Some(c) => ParsedEscape::Char(c), 351 | None => ParsedEscape::InvalidUnicode(slice.to_owned()), 352 | } 353 | } 354 | 355 | fn parse_unicode_escape(lex: &mut Lexer) -> ParsedEscape { 356 | let slice = &lex.slice()[2..]; 357 | let c = u32::from_str_radix(slice, 16).ok().and_then(char::from_u32); 358 | match c { 359 | Some(c) => ParsedEscape::Char(c), 360 | None => ParsedEscape::InvalidUnicode(slice.to_owned()), 361 | } 362 | } 363 | 364 | fn parse_unicode_unsized_escape(lex: &mut Lexer) -> ParsedEscape { 365 | let len = lex.slice().len(); 366 | let codepoint_len = len - 4; 367 | let slice = &lex.slice()[3..(len - 1)]; 368 | if codepoint_len > 8 { return ParsedEscape::InvalidUnicode(slice.to_owned()) } 369 | let c = u32::from_str_radix(slice, 16).ok().and_then(char::from_u32); 370 | match c { 371 | Some(c) => ParsedEscape::Char(c), 372 | None => ParsedEscape::InvalidUnicode(slice.to_owned()), 373 | } 374 | } 375 | 376 | fn parse_float(lex: &mut Lexer) -> PositiveFloatToken { 377 | let slice = lex.slice(); 378 | match slice.parse() { 379 | Ok(f) => PositiveFloatToken::Value(f), 380 | Err(_) => PositiveFloatToken::OutOfRange, 381 | } 382 | } 383 | 384 | fn parse_integer(lex: &mut Lexer) -> PositiveIntegerToken { 385 | let slice = lex.slice(); 386 | match slice.parse() { 387 | Ok(i) => PositiveIntegerToken::Value(i), 388 | Err(_) => PositiveIntegerToken::OutOfRange, 389 | } 390 | } -------------------------------------------------------------------------------- /src/compiler/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{RantProgram, RantProgramInfo}; 2 | use self::parser::RantParser; 3 | use std::{error::Error, fs}; 4 | use std::{fmt::Display, path::Path, rc::Rc}; 5 | use std::io::ErrorKind as IOErrorKind; 6 | 7 | pub(crate) mod lexer; 8 | pub(crate) mod reader; 9 | pub(crate) mod parser; 10 | pub(crate) mod message; 11 | 12 | pub use message::*; 13 | 14 | /// Type alias for `Result` 15 | pub type CompileResult = Result; 16 | 17 | /// Describes why a compilation failed. 18 | #[derive(Debug)] 19 | pub enum CompilerError { 20 | /// Compilation failed due to one or more syntax errors. 21 | SyntaxError, 22 | /// Compilation failed due to a file I/O error. 23 | IOError(IOErrorKind), 24 | } 25 | 26 | impl Display for CompilerError { 27 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 28 | match self { 29 | CompilerError::SyntaxError => write!(f, "syntax error"), 30 | CompilerError::IOError(_) => write!(f, "I/O error"), 31 | } 32 | } 33 | } 34 | 35 | impl Error for CompilerError { 36 | fn source(&self) -> Option<&(dyn Error + 'static)> { 37 | None 38 | } 39 | fn cause(&self) -> Option<&dyn Error> { 40 | self.source() 41 | } 42 | } 43 | 44 | /// Provides an interface through which the compiler can report errors and warnings. 45 | pub trait Reporter { 46 | /// Passes a compiler message to the implementor for processing. 47 | fn report(&mut self, msg: CompilerMessage); 48 | } 49 | 50 | impl Reporter for () { 51 | fn report(&mut self, _msg: CompilerMessage) {} 52 | } 53 | 54 | impl Reporter for Vec { 55 | fn report(&mut self, msg: CompilerMessage) { 56 | self.push(msg); 57 | } 58 | } 59 | 60 | pub(crate) fn compile_string(source: &str, reporter: &mut R, debug_enabled: bool, info: RantProgramInfo) -> CompileResult { 61 | let info = Rc::new(info); 62 | 63 | let mut parser = RantParser::new(source, reporter, debug_enabled, &info); 64 | 65 | // Return compilation result 66 | match parser.parse() { 67 | Ok(seq) => Ok(RantProgram::new(seq, info)), 68 | Err(()) => Err(CompilerError::SyntaxError), 69 | } 70 | } 71 | 72 | pub(crate) fn compile_file, R: Reporter>(path: P, reporter: &mut R, debug_enabled: bool) -> CompileResult { 73 | let source_name = path.as_ref().canonicalize().unwrap_or_else(|_| path.as_ref().to_path_buf()).to_string_lossy().to_string(); 74 | let file_read_result = fs::read_to_string(path); 75 | match file_read_result { 76 | Ok(source) => { 77 | compile_string(&source, reporter, debug_enabled, RantProgramInfo { 78 | name: None, 79 | path: Some(source_name) 80 | }) 81 | }, 82 | // Something went wrong with reading the file 83 | Err(err) => { 84 | // Report file IO issue 85 | let problem = match err.kind() { 86 | IOErrorKind::NotFound => Problem::FileNotFound(source_name), 87 | _ => Problem::FileSystemError(err.to_string()) 88 | }; 89 | reporter.report(CompilerMessage::new(problem, Severity::Error, None)); 90 | Err(CompilerError::IOError(err.kind())) 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /src/compiler/reader.rs: -------------------------------------------------------------------------------- 1 | use logos::*; 2 | use super::lexer::RantToken; 3 | use std::ops::Range; 4 | use crate::InternalString; 5 | 6 | pub struct RantTokenReader<'source> { 7 | lexer: Lexer<'source, RantToken>, 8 | peeked: Option<(RantToken, Range)>, 9 | } 10 | 11 | impl<'source> RantTokenReader<'source> { 12 | pub fn new(src: &'source str) -> Self { 13 | Self { 14 | lexer: RantToken::lexer(src), 15 | peeked: None, 16 | } 17 | } 18 | 19 | pub fn next(&mut self) -> Option<(RantToken, Range)> { 20 | // Consume any peeked token before iterating lexer again 21 | self.peeked.take().or_else(|| self.lexer.next().map(|token| (token, self.lexer.span()))) 22 | } 23 | 24 | pub fn skip_one(&mut self) { 25 | self.next(); 26 | } 27 | 28 | // Consumes the next token if it satisfies the predicate and returns a bool indicating whether any token was eaten. 29 | pub fn eat_where)>) -> bool>(&mut self, predicate: F) -> bool { 30 | if predicate(self.peek()) { 31 | self.skip_one(); 32 | return true 33 | } 34 | false 35 | } 36 | 37 | /// Consumes the next token if it's equal to the specified token and returns a bool indicating whether any token was eaten. 38 | pub fn eat(&mut self, token: RantToken) -> bool { 39 | if let Some((peeked, _span)) = self.peek() { 40 | if peeked.eq(&token) { 41 | self.skip_one(); 42 | return true 43 | } 44 | } 45 | false 46 | } 47 | 48 | pub fn eat_kw(&mut self, kwname: &str) -> bool { 49 | self.eat_where(|t| { 50 | if let Some((RantToken::Keyword(kw), _)) = t.as_ref() { 51 | return kw.name.as_str() == kwname 52 | } 53 | false 54 | }) 55 | } 56 | 57 | // Consumes the next token if it satisfies the predicate and returns it if the predicate was satisfied; otherwise, returns `None`. 58 | pub fn take_where)>) -> bool>(&mut self, predicate: F) -> Option<(RantToken, Range)> { 59 | if predicate(self.peek()) { 60 | self.next() 61 | } else { 62 | None 63 | } 64 | } 65 | 66 | /// Gets the last token string that was read. 67 | pub fn last_token_string(&self) -> InternalString { 68 | InternalString::from(self.lexer.slice()) 69 | } 70 | 71 | /// Gets the next non-whitespace token. 72 | pub fn next_solid(&mut self) -> Option<(RantToken, Range)> { 73 | loop { 74 | match self.next() { 75 | Some((RantToken::Whitespace, _)) => continue, 76 | Some((token, span)) => return Some((token, span)), 77 | None => return None 78 | } 79 | } 80 | } 81 | 82 | /// Skips past whitespace tokens. 83 | pub fn skip_ws(&mut self) { 84 | while let Some((RantToken::Whitespace, _)) = self.peek() { 85 | self.next(); 86 | } 87 | } 88 | 89 | /// Gets the starting position of the most recently read token. 90 | pub fn last_token_pos(&self) -> usize { 91 | self.lexer.span().start 92 | } 93 | 94 | /// Gets the span of the most recently read token. 95 | pub fn last_token_span(&self) -> Range { 96 | self.lexer.span() 97 | } 98 | 99 | /// Returns a reference to the next token without consuming it. 100 | pub fn peek(&mut self) -> Option<&(RantToken, Range)> { 101 | // If a peek was already performed, return a reference to it 102 | if self.peeked.is_some() { 103 | return self.peeked.as_ref(); 104 | } 105 | 106 | // If no previous peek was performed for the current iteration, iterate, store, and return reference to token 107 | let token = self.next(); 108 | self.peeked = token; 109 | self.peeked.as_ref() 110 | } 111 | } -------------------------------------------------------------------------------- /src/data.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, fmt::{Debug, Display}}; 2 | 3 | use crate::{RantValue, runtime::{IntoRuntimeResult, RuntimeError, RuntimeErrorType, RuntimeResult}}; 4 | 5 | /// Result type used for data source operations. 6 | pub type DataSourceResult = Result; 7 | 8 | /// Trait for defining a Rant data source type. 9 | /// 10 | /// ## Security 11 | /// This trait can potentially be used to grant Rant access to network, filesystem, or other sensitive resources. 12 | /// Please take care to ensure that such access is adequately sandboxed, has granular permissions, 13 | /// and that failed access to such resources is handled gracefully. 14 | pub trait DataSource: Debug { 15 | /// Returns a string identifying the type of data source this is; 16 | /// ideally, this should be something short and human-readable. 17 | /// Refer to implementor documentation for the specific string returned. 18 | /// 19 | /// ## Security 20 | /// Because implementors can make this method return any string at all, 21 | /// usage should be limited to diagnostic and filtering purposes only. 22 | fn type_id(&self) -> &str; 23 | 24 | /// Requests some data from the data source. 25 | /// 26 | /// This method is called by Rant's `[data]` funtion to interact with data sources. 27 | fn request_data(&self, args: Vec) -> DataSourceResult; 28 | } 29 | 30 | /// Error type for data source operations. 31 | #[derive(Debug, Clone)] 32 | pub enum DataSourceError { 33 | /// User messed up; check string for explanation. 34 | User(String), 35 | /// Data source messed up; check string for explanation. 36 | Internal(String), 37 | } 38 | 39 | impl Display for DataSourceError { 40 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 41 | match self { 42 | Self::User(msg) => write!(f, "user error: {}", msg), 43 | Self::Internal(msg) => write!(f, "internal: {}", msg), 44 | } 45 | } 46 | } 47 | 48 | impl Error for DataSourceError { 49 | fn source(&self) -> Option<&(dyn Error + 'static)> { 50 | None 51 | } 52 | 53 | fn cause(&self) -> Option<&dyn Error> { 54 | self.source() 55 | } 56 | } 57 | 58 | impl IntoRuntimeResult for DataSourceResult { 59 | fn into_runtime_result(self) -> RuntimeResult { 60 | self.map_err(|err| RuntimeError { 61 | error_type: RuntimeErrorType::DataSourceError(err), 62 | description: None, 63 | stack_trace: None, 64 | }) 65 | } 66 | } -------------------------------------------------------------------------------- /src/format/case.rs: -------------------------------------------------------------------------------- 1 | use crate::{TryFromRant, RantValue, ValueError, RantString, InternalString}; 2 | 3 | #[derive(Debug, Clone, Default)] 4 | pub struct CasingFormat { 5 | locale: CasingLocale, 6 | style: CasingStyle, 7 | } 8 | 9 | impl CasingFormat { 10 | 11 | } 12 | 13 | /// Provides locale disambiguation for casing operations. 14 | #[derive(Debug, Copy, Clone)] 15 | pub enum CasingLocale { 16 | /// Follow the default casing rules defined by Unicode. 17 | Invariant, 18 | /// Follow casing rules specific to the [Common Turkic Alphabet](https://en.wikipedia.org/wiki/Common_Turkic_Alphabet). 19 | Turkic, 20 | } 21 | 22 | impl CasingLocale { 23 | #[inline] 24 | pub fn convert_upper(&self, input: &RantString) -> RantString { 25 | transform_str(input.as_str(), match self { 26 | CasingLocale::Invariant => convert_char_upper_invariant, 27 | CasingLocale::Turkic => convert_char_upper_turkic, 28 | }).into() 29 | } 30 | 31 | #[inline] 32 | pub fn convert_lower(&self, input: &RantString) -> RantString { 33 | transform_str(input.as_str(), match self { 34 | CasingLocale::Invariant => convert_char_lower_invariant, 35 | CasingLocale::Turkic => convert_char_lower_turkic, 36 | }).into() 37 | } 38 | } 39 | 40 | impl TryFromRant for CasingLocale { 41 | fn try_from_rant(val: RantValue) -> Result { 42 | Ok(match val { 43 | RantValue::String(s) => match s.as_str() { 44 | "invariant" => Self::Invariant, 45 | "turkic" => Self::Turkic, 46 | other => return Err(ValueError::InvalidConversion { 47 | from: "string", 48 | to: "casing locale", 49 | message: Some(format!("unrecognized casing locale: '{other}'")), 50 | }) 51 | }, 52 | other => return Err(ValueError::InvalidConversion { 53 | from: other.type_name(), 54 | to: "casing locale", 55 | message: None, 56 | }) 57 | }) 58 | } 59 | } 60 | 61 | impl Default for CasingLocale { 62 | fn default() -> Self { 63 | Self::Invariant 64 | } 65 | } 66 | 67 | #[derive(Debug, Copy, Clone)] 68 | pub enum CasingStyle { 69 | /// Don't change the casing at all. 70 | Normal, 71 | /// Convert to uppercase. 72 | Upper, 73 | /// Convert to lowercase. 74 | Lower, 75 | /// Capitalize the next letter. 76 | FirstLetter, 77 | /// Capitalize the first letter of every sentence. 78 | Sentence, 79 | } 80 | 81 | impl Default for CasingStyle { 82 | fn default() -> Self { 83 | Self::Normal 84 | } 85 | } 86 | 87 | impl TryFromRant for CasingStyle { 88 | fn try_from_rant(val: RantValue) -> Result { 89 | Ok(match val { 90 | RantValue::String(s) => match s.as_str() { 91 | "normal" => Self::Normal, 92 | "upper" => Self::Upper, 93 | "lower" => Self::Lower, 94 | "first-letter" => Self::FirstLetter, 95 | "sentence" => Self::Sentence, 96 | other => return Err(ValueError::InvalidConversion { 97 | from: "string", 98 | to: "casing style", 99 | message: Some(format!("unrecognized casing style: '{other}'")), 100 | }) 101 | }, 102 | other => return Err(ValueError::InvalidConversion { 103 | from: other.type_name(), 104 | to: "casing style", 105 | message: None, 106 | }) 107 | }) 108 | } 109 | } 110 | 111 | fn transform_str(input: &str, transformer: F) -> InternalString 112 | where F: Fn(char, &mut InternalString) 113 | { 114 | let mut output = InternalString::new(); 115 | for c in input.chars() { 116 | transformer(c, &mut output); 117 | } 118 | output 119 | } 120 | 121 | #[inline] 122 | fn convert_char_upper_invariant(input: char, buffer: &mut InternalString) { 123 | match input { 124 | 'ß' => buffer.push('ẞ'), 125 | other => buffer.push_str(&other.to_uppercase().to_string()) 126 | } 127 | } 128 | 129 | #[inline] 130 | fn convert_char_lower_invariant(input: char, buffer: &mut InternalString) { 131 | match input { 132 | other => buffer.push_str(&other.to_lowercase().to_string()) 133 | } 134 | } 135 | 136 | #[inline] 137 | fn convert_char_upper_turkic(input: char, buffer: &mut InternalString) { 138 | match input { 139 | 'i' => buffer.push('İ'), 140 | other => convert_char_upper_invariant(other, buffer) 141 | } 142 | } 143 | 144 | #[inline] 145 | fn convert_char_lower_turkic(input: char, buffer: &mut InternalString) { 146 | match input { 147 | 'I' => buffer.push('ı'), 148 | other => convert_char_lower_invariant(other, buffer) 149 | } 150 | } -------------------------------------------------------------------------------- /src/format/mod.rs: -------------------------------------------------------------------------------- 1 | mod num; 2 | mod case; 3 | mod ws; 4 | 5 | pub use self::num::*; 6 | pub use self::case::*; 7 | pub use self::ws::*; 8 | 9 | #[derive(Debug, Clone, Default)] 10 | pub struct OutputFormat { 11 | pub whitespace_format: WhitespaceNormalizationMode, 12 | pub number_format: NumberFormat, 13 | pub casing_format: CasingFormat, 14 | } -------------------------------------------------------------------------------- /src/format/ws.rs: -------------------------------------------------------------------------------- 1 | use crate::RantValue; 2 | 3 | #[derive(Debug, Clone)] 4 | pub enum WhitespaceNormalizationMode { 5 | /// Normalizes all whitespace tokens to a single ASCII space character (0x20). 6 | Default, 7 | /// Strips all (non-literal) whitespace. 8 | IgnoreAll, 9 | /// Prints all non-breaking whitespace verbatim. 10 | Verbatim, 11 | /// Normalizes all whitespace to a custom value. 12 | Custom(RantValue) 13 | } 14 | 15 | impl Default for WhitespaceNormalizationMode { 16 | fn default() -> Self { 17 | Self::Default 18 | } 19 | } -------------------------------------------------------------------------------- /src/func.rs: -------------------------------------------------------------------------------- 1 | use std::{mem::{transmute, size_of}, rc::Rc, fmt::Debug}; 2 | use crate::*; 3 | use crate::lang::*; 4 | use crate::runtime::*; 5 | use crate::stdlib::*; 6 | 7 | /// A function callable from Rant. 8 | #[derive(Debug)] 9 | pub struct RantFunction { 10 | /// Parameter information for the function. 11 | pub(crate) params: Rc>, 12 | /// The number of required parameters. 13 | pub(crate) min_arg_count: usize, 14 | /// The parameter index at which variadic parameters start. 15 | /// If this is greater than or equal to the number of params, there are no variadic parameters. 16 | pub(crate) vararg_start_index: usize, 17 | /// The external variables captured by the function when it was defined. 18 | pub(crate) captured_vars: Vec<(Identifier, RantVar)>, 19 | /// The body of the function. 20 | pub(crate) body: RantFunctionInterface, 21 | /// Assigns a custom flavor to the stack frame created by the function call. 22 | /// If not set, the default function call flavor will be used. 23 | pub(crate) flavor: Option, 24 | } 25 | 26 | impl RantFunction { 27 | /// Returns true if the function should be treated as variadic. 28 | #[inline] 29 | pub fn is_variadic(&self) -> bool { 30 | self.vararg_start_index < self.params.len() || self.vararg_start_index < self.min_arg_count 31 | } 32 | 33 | /// Returns true if the function is native. 34 | #[inline] 35 | pub fn is_native(&self) -> bool { 36 | matches!(self.body, RantFunctionInterface::Foreign(_)) 37 | } 38 | } 39 | 40 | /// Defines endpoint variants for Rant functions. 41 | #[derive(Clone)] 42 | pub enum RantFunctionInterface { 43 | /// Represents a foreign function as a wrapper function accepting a variable number of arguments. 44 | Foreign(Rc) -> RantStdResult>), 45 | /// Represents a user function as an RST. 46 | User(Rc) 47 | } 48 | 49 | impl Debug for RantFunctionInterface { 50 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 51 | match self { 52 | RantFunctionInterface::Foreign(func) => unsafe { 53 | let (a, b) = transmute::<_, (usize, usize)>(Rc::as_ptr(func)); 54 | write!(f, "{:#02$x}{:02$x}", a, b, &(size_of::() * 2)) 55 | }, 56 | RantFunctionInterface::User(func) => write!(f, "{:#p}", Rc::as_ptr(func)) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/modres.rs: -------------------------------------------------------------------------------- 1 | use std::{env, path::PathBuf, io::ErrorKind, fmt::Debug}; 2 | use super::*; 3 | 4 | /// Result type used by the module loader. 5 | pub type ModuleResolveResult = Result; 6 | 7 | /// Represents the features required for a module resolver. 8 | /// 9 | /// A module resolver only resolves the `RantProgram` that the final module object is loaded from. 10 | /// This is designed as such to ensure that module loading is limited to the maximum call stack size of the requesting program. 11 | pub trait ModuleResolver: Debug { 12 | fn try_resolve(&self, context: &mut Rant, module_path: &str, dependant: Option<&RantProgramInfo>) -> ModuleResolveResult; 13 | } 14 | 15 | /// The default filesystem-based module resolver. 16 | /// 17 | /// ### Resolution strategy 18 | /// This resolver uses the following strategy to locate module files: 19 | /// 1. If triggered by a program, the program's containing directory is searched first. 20 | /// 1. The directory specified in `local_modules_path` is searched next. If not specified, uses the host application's current working directory. 21 | /// 1. If `enable_global_modules` is set to `true`, the global modules path is searched. 22 | #[derive(Debug)] 23 | pub struct DefaultModuleResolver { 24 | /// Enables loading modules from RANT_MODULES_PATH. 25 | pub enable_global_modules: bool, 26 | /// Specifies a preferred module loading path with higher precedence than the global module path. 27 | /// If not specified, looks in the current working directory. 28 | pub local_modules_path: Option, 29 | } 30 | 31 | impl DefaultModuleResolver { 32 | /// The name of the environment variable that used to provide the global modules path. 33 | pub const ENV_MODULES_PATH_KEY: &'static str = "RANT_MODULES_PATH"; 34 | } 35 | 36 | impl Default for DefaultModuleResolver { 37 | fn default() -> Self { 38 | Self { 39 | enable_global_modules: true, 40 | local_modules_path: None, 41 | } 42 | } 43 | } 44 | 45 | impl ModuleResolver for DefaultModuleResolver { 46 | fn try_resolve(&self, context: &mut Rant, module_path: &str, dependant: Option<&RantProgramInfo>) -> ModuleResolveResult { 47 | 48 | // Try to find module path that exists 49 | if let Some(full_module_path) = self.find_module_path(module_path, dependant) { 50 | let mut errors = vec![]; 51 | let compile_result = context.compile_file(full_module_path, &mut errors); 52 | match compile_result { 53 | Ok(module) => Ok(module), 54 | Err(err) => { 55 | Err(ModuleResolveError { 56 | name: module_path.to_owned(), 57 | reason: match err{ 58 | CompilerError::SyntaxError => { 59 | ModuleResolveErrorReason::CompileFailed(errors) 60 | }, 61 | CompilerError::IOError(ioerr) => { 62 | match ioerr { 63 | IOErrorKind::NotFound => { 64 | ModuleResolveErrorReason::NotFound 65 | }, 66 | _ => ModuleResolveErrorReason::FileIOError(ioerr) 67 | } 68 | } 69 | } 70 | }) 71 | } 72 | } 73 | } else { 74 | Err(ModuleResolveError { 75 | name: module_path.to_owned(), 76 | reason: ModuleResolveErrorReason::NotFound, 77 | }) 78 | } 79 | } 80 | } 81 | 82 | impl DefaultModuleResolver { 83 | #[inline] 84 | fn find_module_path(&self, module_path: &str, dependant: Option<&RantProgramInfo>) -> Option { 85 | 86 | let module_path = PathBuf::from( 87 | module_path.replace("/", &String::from(std::path::MAIN_SEPARATOR)) 88 | ) 89 | .with_extension(RANT_FILE_EXTENSION); 90 | 91 | macro_rules! search_for_module { 92 | ($path:expr) => { 93 | let path = $path; 94 | // Construct full path to module 95 | if let Ok(full_module_path) = path 96 | .join(&module_path) 97 | .canonicalize() 98 | { 99 | // Verify file is still in modules directory and it exists 100 | if full_module_path.starts_with(path) 101 | && full_module_path.exists() 102 | { 103 | return Some(full_module_path) 104 | } 105 | } 106 | } 107 | } 108 | 109 | // Search path of dependant running program 110 | if let Some(dependant_path) = dependant.map(|d| d.path.as_deref()) { 111 | if let Some(program_path) = 112 | dependant_path 113 | .map(PathBuf::from) 114 | .as_deref() 115 | .and_then(|p| p.parent()) 116 | { 117 | search_for_module!(program_path); 118 | } 119 | } 120 | 121 | // Search local modules path 122 | if let Some(local_modules_path) = 123 | self.local_modules_path 124 | .as_ref() 125 | .map(PathBuf::from) 126 | .or_else(|| 127 | env::current_dir() 128 | .ok() 129 | ) 130 | .and_then(|p| p.canonicalize().ok()) 131 | { 132 | search_for_module!(local_modules_path); 133 | } 134 | 135 | // Check global modules, if enabled 136 | if self.enable_global_modules { 137 | if let Some(global_modules_path) = 138 | env::var_os(Self::ENV_MODULES_PATH_KEY) 139 | .map(PathBuf::from) 140 | .and_then(|p| p.canonicalize().ok()) 141 | { 142 | search_for_module!(global_modules_path); 143 | } 144 | } 145 | 146 | None 147 | } 148 | } 149 | 150 | /// Stub module resolver that completely disables modules. 151 | /// 152 | /// All calls to `try_resolve` on this resolver will return a "not found" error. 153 | #[derive(Debug)] 154 | pub struct NoModuleResolver; 155 | 156 | impl ModuleResolver for NoModuleResolver { 157 | fn try_resolve(&self, _context: &mut Rant, module_path: &str, _dependant: Option<&RantProgramInfo>) -> ModuleResolveResult { 158 | Err(ModuleResolveError { 159 | name: module_path.to_owned(), 160 | reason: ModuleResolveErrorReason::NotFound, 161 | }) 162 | } 163 | } 164 | 165 | /// Represents an error that occurred when attempting to load a Rant module. 166 | #[derive(Debug)] 167 | pub struct ModuleResolveError { 168 | pub name: String, 169 | pub reason: ModuleResolveErrorReason, 170 | } 171 | 172 | impl Error for ModuleResolveError {} 173 | 174 | impl ModuleResolveError { 175 | /// Gets the name of the module that failed to load. 176 | #[inline] 177 | pub fn name(&self) -> &str { 178 | &self.name 179 | } 180 | 181 | /// Gets the reason for the module load failure. 182 | #[inline] 183 | pub fn reason(&self) -> &ModuleResolveErrorReason { 184 | &self.reason 185 | } 186 | } 187 | 188 | /// Represents the reason for which a Rant module failed to load. 189 | #[derive(Debug)] 190 | pub enum ModuleResolveErrorReason { 191 | /// The module was not found. 192 | NotFound, 193 | /// The module could not be compiled. 194 | CompileFailed(Vec), 195 | /// The module could not load due to a file I/O error. 196 | FileIOError(ErrorKind), 197 | } 198 | 199 | impl Display for ModuleResolveError { 200 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 201 | match self.reason() { 202 | ModuleResolveErrorReason::NotFound => write!(f, "module '{}' not found", self.name()), 203 | ModuleResolveErrorReason::CompileFailed(msgs) => write!(f, "module '{}' failed to compile: {}", 204 | self.name(), 205 | msgs.iter().fold(String::new(), |mut acc, msg| { 206 | acc.push_str(&format!("[{}] {}\n", msg.severity(), msg.message())); 207 | acc 208 | })), 209 | ModuleResolveErrorReason::FileIOError(ioerr) => write!(f, "file I/O error ({:?})", ioerr), 210 | } 211 | } 212 | } 213 | 214 | impl IntoRuntimeResult for ModuleResolveResult { 215 | fn into_runtime_result(self) -> RuntimeResult { 216 | self.map_err(|err| RuntimeError { 217 | error_type: RuntimeErrorType::ModuleError(err), 218 | description: None, 219 | stack_trace: None, 220 | }) 221 | } 222 | } -------------------------------------------------------------------------------- /src/rng.rs: -------------------------------------------------------------------------------- 1 | use rand::Rng; 2 | use rand_xoshiro::rand_core::SeedableRng; 3 | use rand_xoshiro::Xoshiro256PlusPlus; 4 | use std::{cell::RefCell, hash::Hasher}; 5 | use fnv::FnvHasher; 6 | use crate::util::*; 7 | 8 | /// Rant's random number generator, which is a thin wrapper around a xoshiro256++ PRNG. 9 | #[derive(Debug)] 10 | pub struct RantRng { 11 | seed: u64, 12 | rng: RefCell, 13 | } 14 | 15 | impl RantRng { 16 | /// Creates a new RNG with the supplied seed. 17 | pub fn new(seed: u64) -> Self { 18 | Self { 19 | seed, 20 | rng: RefCell::new(Xoshiro256PlusPlus::seed_from_u64(seed)) 21 | } 22 | } 23 | 24 | /// Creates a new RNG by hashing the parent seed with the supplied `u64` to produce a new seed. 25 | /// Uses the Fowler-Noll-Vo hash function. 26 | pub fn fork_u64(&self, seed: u64) -> Self { 27 | let mut hasher = FnvHasher::default(); 28 | hasher.write_u64(self.seed); 29 | hasher.write_u64(seed); 30 | RantRng::new(hasher.finish()) 31 | } 32 | 33 | /// Creates a new RNG by hashing the parent seed with the supplied `i64` to produce a new seed. 34 | /// Uses the Fowler-Noll-Vo hash function. 35 | pub fn fork_i64(&self, seed: i64) -> Self { 36 | let mut hasher = FnvHasher::default(); 37 | hasher.write_u64(self.seed); 38 | hasher.write_i64(seed); 39 | RantRng::new(hasher.finish()) 40 | } 41 | 42 | /// Creates a new RNG by hashing the parent seed with the supplied string to produce a new seed. 43 | /// Uses the Fowler-Noll-Vo hash function. 44 | pub fn fork_str(&self, seed: &str) -> Self { 45 | let mut hasher = FnvHasher::default(); 46 | hasher.write_u64(self.seed); 47 | hasher.write(seed.as_bytes()); 48 | RantRng::new(hasher.finish()) 49 | } 50 | 51 | /// Creates a new RNG by hashing the parent seed and with the current generation to produce a new seed. 52 | /// Uses the Fowler-Noll-Vo hash function. 53 | pub fn fork_random(&self) -> Self { 54 | let mut hasher = FnvHasher::default(); 55 | hasher.write_u64(self.seed); 56 | hasher.write_u64(self.rng.borrow_mut().gen()); 57 | RantRng::new(hasher.finish()) 58 | } 59 | } 60 | 61 | impl RantRng { 62 | /// Gets the current seed of the RNG. 63 | pub fn seed(&self) -> u64 { 64 | self.seed 65 | } 66 | 67 | /// Generates a pseudorandom `i64` between two inclusive values. The range may be specified in either order. 68 | #[inline] 69 | pub fn next_i64(&self, a: i64, b: i64) -> i64 { 70 | if a == b { return a } 71 | let (min, max) = minmax(a, b); 72 | self.rng.borrow_mut().gen_range(min ..= max) 73 | } 74 | 75 | /// Generates a pseudorandom `f64` between two inclusive values. The range may be specified in either order. 76 | #[inline] 77 | pub fn next_f64(&self, a: f64, b: f64) -> f64 { 78 | if a.eq(&b) { return a } 79 | let (min, max) = minmax(a, b); 80 | self.rng.borrow_mut().gen_range(min .. max) 81 | } 82 | 83 | /// Generates a pseudorandom `usize` between 0 and `max` (exclusive). 84 | #[inline] 85 | pub fn next_usize(&self, max: usize) -> usize { 86 | self.rng.borrow_mut().gen_range(0 .. max) 87 | } 88 | 89 | #[inline] 90 | pub(crate) fn next_usize_weighted(&self, max: usize, weights: &[f64], weight_sum: f64) -> usize { 91 | if weight_sum > 0.0 { 92 | let mut rem = self.rng.borrow_mut().gen_range(0.0 .. weight_sum); 93 | for (i, w) in weights.iter().enumerate() { 94 | if *w == 0.0 { 95 | continue 96 | } 97 | if &rem < w { 98 | return i 99 | } 100 | rem -= w; 101 | } 102 | } 103 | max - 1 104 | } 105 | 106 | /// Generates a pseudorandom `f64` between 0 and 1. 107 | #[inline] 108 | pub fn next_normal_f64(&self) -> f64 { 109 | self.rng.borrow_mut().gen() 110 | } 111 | 112 | /// Generates a `bool` with `p` probability of being `true`. 113 | #[inline] 114 | pub fn next_bool(&self, p: f64) -> bool { 115 | self.rng.borrow_mut().gen_bool(saturate(p)) 116 | } 117 | } -------------------------------------------------------------------------------- /src/runtime/error.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, fmt::Display}; 2 | 3 | use crate::{IndexError, KeyError, ModuleResolveError, SliceError, ValueError, data::DataSourceError, SelectorError}; 4 | 5 | /// Type alias for `Result` 6 | pub type RuntimeResult = Result; 7 | /// A runtime error raised by a Rant program. 8 | #[derive(Debug)] 9 | pub struct RuntimeError { 10 | /// The type of runtime error. 11 | pub error_type: RuntimeErrorType, 12 | /// A description of what went wrong. 13 | pub description: Option, 14 | /// A stack trace describing the location of the error. 15 | pub stack_trace: Option, 16 | } 17 | 18 | impl Error for RuntimeError { 19 | fn source(&self) -> Option<&(dyn Error + 'static)> { 20 | match &self.error_type { 21 | RuntimeErrorType::IndexError(err) => Some(err), 22 | RuntimeErrorType::KeyError(err) => Some(err), 23 | RuntimeErrorType::ValueError(err) => Some(err), 24 | _ => None, 25 | } 26 | } 27 | 28 | fn cause(&self) -> Option<&dyn Error> { 29 | self.source() 30 | } 31 | } 32 | 33 | impl Display for RuntimeError { 34 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 35 | write!(f, "[{}] ", self.error_type.id())?; 36 | if let Some(desc) =& self.description { 37 | write!(f, "{}", desc)?; 38 | } else { 39 | write!(f, "{}", self.error_type)?; 40 | } 41 | Ok(()) 42 | } 43 | } 44 | 45 | /// Provides general categories of runtime errors encountered in Rant. 46 | #[derive(Debug)] 47 | pub enum RuntimeErrorType { 48 | /// Stack has overflowed. 49 | /// 50 | /// Rant error ID: `STACK_OVERFLOW_ERROR` 51 | StackOverflow, 52 | /// Stack has underflowed. 53 | /// 54 | /// Rant error ID: `STACK_UNDERFLOW_ERROR` 55 | StackUnderflow, 56 | /// Variable access error, such as attempting to access a nonexistent variable or write to a constant 57 | /// 58 | /// Rant error ID: `INVALID_ACCESS_ERROR` 59 | InvalidAccess, 60 | /// Operation is not valid for the current program state 61 | /// 62 | /// Rant error ID: `INVALID_OP_ERROR` 63 | InvalidOperation, 64 | /// Internal VM error, usually indicating a bug or corrupted data 65 | /// 66 | /// Rant error ID: `INTERNAL_ERROR` 67 | InternalError, 68 | /// Too few/many arguments were passed to a function 69 | /// 70 | /// Rant error ID: `ARG_MISMATCH_ERROR` 71 | ArgumentMismatch, 72 | /// Invalid argument passed to function 73 | /// 74 | /// Rant error ID: `ARG_ERROR` 75 | ArgumentError, 76 | /// Tried to invoke a non-function 77 | /// 78 | /// Rant error ID: `INVOKE_ERROR` 79 | CannotInvokeValue, 80 | /// Assertion failed 81 | /// 82 | /// Rant error ID: `ASSERT_ERROR` 83 | AssertError, 84 | /// Error occurred due to unexpected value type 85 | /// 86 | /// Rant error ID: `TYPE_ERROR` 87 | TypeError, 88 | /// Error occurred when creating value 89 | /// 90 | /// Rant error ID: `VALUE_ERROR` 91 | ValueError(ValueError), 92 | /// Error occurred while indexing value 93 | /// 94 | /// Rant error ID: `INDEX_ERROR` 95 | IndexError(IndexError), 96 | /// Error occurred while keying value 97 | /// 98 | /// Rant error ID: `KEY_ERROR` 99 | KeyError(KeyError), 100 | /// Error occurred while slicing value 101 | /// 102 | /// Rant error ID: `SLICE_ERROR` 103 | SliceError(SliceError), 104 | /// Error occurred while iterating selector 105 | /// 106 | /// Rant error ID: `SELECTOR_ERROR` 107 | SelectorError(SelectorError), 108 | /// Error occurred while trying to load a module 109 | /// 110 | /// Rant error ID: `MODULE_ERROR` 111 | ModuleError(ModuleResolveError), 112 | /// Error manually triggered by program 113 | /// 114 | /// Rant error ID: `USER_ERROR` 115 | UserError, 116 | /// Error during control flow operation (e.g. return or break) 117 | /// 118 | /// Rant error ID: `CONTROL_FLOW_ERROR` 119 | ControlFlowError, 120 | /// Error occurred during data source operation. 121 | /// 122 | /// Rant error ID: `DATA_SOURCE_ERROR` 123 | DataSourceError(DataSourceError), 124 | } 125 | 126 | impl RuntimeErrorType { 127 | pub fn id(&self) -> &'static str { 128 | match self { 129 | Self::StackOverflow => "STACK_OVERFLOW_ERROR", 130 | Self::StackUnderflow => "STACK_UNDERFLOW_ERROR", 131 | Self::InvalidAccess => "INVALID_ACCESS_ERROR", 132 | Self::InvalidOperation => "INVALID_OP_ERROR", 133 | Self::InternalError => "INTERNAL_ERROR", 134 | Self::ArgumentMismatch => "ARG_MISMATCH_ERROR", 135 | Self::ArgumentError => "ARG_ERROR", 136 | Self::CannotInvokeValue => "INVOKE_ERROR", 137 | Self::UserError => "USER_ERROR", 138 | Self::AssertError => "ASSERT_ERROR", 139 | Self::TypeError => "TYPE_ERROR", 140 | Self::ValueError(_) => "VALUE_ERROR", 141 | Self::IndexError(_) => "INDEX_ERROR", 142 | Self::KeyError(_) => "KEY_ERROR", 143 | Self::SliceError(_) => "SLICE_ERROR", 144 | Self::SelectorError(_) => "SELECTOR_ERROR", 145 | Self::ModuleError(_) => "MODULE_ERROR", 146 | Self::ControlFlowError => "CONTROL_FLOW_ERROR", 147 | Self::DataSourceError(_) => "DATA_SOURCE_ERROR", 148 | } 149 | } 150 | } 151 | 152 | impl Display for RuntimeErrorType { 153 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 154 | match self { 155 | Self::ValueError(e) => write!(f, "{}", e), 156 | Self::IndexError(e) => write!(f, "{}", e), 157 | Self::KeyError(e) => write!(f, "{}", e), 158 | Self::SliceError(e) => write!(f, "{}", e), 159 | Self::SelectorError(e) => write!(f, "{}", e), 160 | Self::ModuleError(e) => write!(f, "{}", e), 161 | Self::DataSourceError(e) => write!(f, "{}", e), 162 | _ => write!(f, "{}", self.id()), 163 | } 164 | } 165 | } 166 | 167 | pub(crate) trait IntoRuntimeResult { 168 | fn into_runtime_result(self) -> RuntimeResult; 169 | } -------------------------------------------------------------------------------- /src/runtime/output.rs: -------------------------------------------------------------------------------- 1 | use crate::{InternalString, RantList, RantMap, RantValue, format::{NumberFormat, OutputFormat}, RantTuple}; 2 | use super::format::{WhitespaceNormalizationMode}; 3 | use std::rc::Rc; 4 | 5 | const INITIAL_CHAIN_CAPACITY: usize = 64; 6 | const DEFAULT_SPACE: &str = " "; 7 | 8 | /// Writes a stream of buffers that can be passed to a parent buffer or rendered to a string. 9 | pub struct OutputWriter { 10 | buffers: Vec, 11 | format: Rc, 12 | mode: OutputPrintMode, 13 | } 14 | 15 | impl OutputWriter { 16 | #[inline] 17 | pub fn new(prev_output: Option<&Self>) -> Self { 18 | Self { 19 | buffers: Vec::with_capacity(INITIAL_CHAIN_CAPACITY), 20 | format: prev_output.map(|o| Rc::clone(&o.format)).unwrap_or_default(), 21 | mode: OutputPrintMode::Single, 22 | } 23 | } 24 | 25 | /// Gets a reference to the current output format. 26 | #[inline] 27 | pub fn format(&self) -> &OutputFormat { 28 | &self.format 29 | } 30 | 31 | /// Gets a mutable reference to the current output format. 32 | #[inline] 33 | pub fn format_mut(&mut self) -> &mut OutputFormat { 34 | Rc::make_mut(&mut self.format) 35 | } 36 | 37 | #[inline] 38 | fn last_buffer(&self) -> Option<&OutputBuffer> { 39 | self.buffers.last() 40 | } 41 | 42 | #[inline] 43 | fn last_buffer_mut(&mut self) -> Option<&mut OutputBuffer> { 44 | self.buffers.last_mut() 45 | } 46 | 47 | #[inline] 48 | pub fn update_number_format(&mut self) { 49 | let fmt = self.format.number_format.clone(); 50 | if let Some(OutputBuffer::NumberFormatUpdate(upd)) = self.last_buffer_mut() { 51 | *upd = fmt; 52 | } else { 53 | self.write_buffer(OutputBuffer::NumberFormatUpdate(fmt)); 54 | } 55 | } 56 | 57 | /// Writes a value to the output. 58 | #[inline] 59 | pub fn write_value(&mut self, value: RantValue) { 60 | if !matches!(value, RantValue::Nothing) { 61 | self.write_buffer(OutputBuffer::Value(value)); 62 | } 63 | } 64 | 65 | #[inline] 66 | fn write_buffer(&mut self, value: OutputBuffer) { 67 | // Set the correct mode for the output content 68 | match (self.buffers.len() + 1, self.mode) { 69 | // Decide mode for first buffer in chain 70 | (1, _) => { 71 | match &value { 72 | OutputBuffer::Fragment(_) => { 73 | self.mode = OutputPrintMode::Text; 74 | }, 75 | OutputBuffer::Value(RantValue::List(_)) => { 76 | self.mode = OutputPrintMode::List; 77 | }, 78 | OutputBuffer::Value(RantValue::Tuple(_)) => { 79 | self.mode = OutputPrintMode::Tuple; 80 | } 81 | OutputBuffer::Value(RantValue::Map(_)) => { 82 | self.mode = OutputPrintMode::Map; 83 | }, 84 | _ => {}, 85 | } 86 | }, 87 | // Single and concat modes transition to either text or concat 88 | (_, OutputPrintMode::Single | OutputPrintMode::Concat) => { 89 | match &value { 90 | OutputBuffer::Fragment(_) | OutputBuffer::Whitespace(_) | OutputBuffer::Value(RantValue::String(_)) => { 91 | self.mode = OutputPrintMode::Text; 92 | }, 93 | _ => { 94 | self.mode = OutputPrintMode::Concat; 95 | } 96 | } 97 | }, 98 | (_, OutputPrintMode::List) => { 99 | if !matches!(value, OutputBuffer::Whitespace(_) | OutputBuffer::Value(RantValue::List(_) | RantValue::Tuple(_))) { 100 | self.mode = OutputPrintMode::Text; 101 | } 102 | }, 103 | (_, OutputPrintMode::Tuple) => { 104 | match value { 105 | OutputBuffer::Whitespace(_) | OutputBuffer::Value(RantValue::Tuple(_)) => {}, 106 | // List beats tuple 107 | OutputBuffer::Value(RantValue::List(_)) => self.mode = OutputPrintMode::List, 108 | _ => self.mode = OutputPrintMode::Text 109 | } 110 | }, 111 | (_, OutputPrintMode::Map) => { 112 | if !matches!(value, OutputBuffer::Whitespace(_) | OutputBuffer::Value(RantValue::Map(_))) { 113 | self.mode = OutputPrintMode::Text; 114 | } 115 | }, 116 | _ => {} 117 | } 118 | 119 | self.buffers.push(value); 120 | } 121 | 122 | /// Writes a text fragment to the output. 123 | #[inline] 124 | pub fn write_frag(&mut self, value: &str) { 125 | self.write_buffer(OutputBuffer::Fragment(InternalString::from(value))); 126 | } 127 | 128 | /// Writes a whitespace string to the output. 129 | #[inline] 130 | pub fn write_ws(&mut self, value: &str) { 131 | let ws_str = match &self.format.whitespace_format { 132 | WhitespaceNormalizationMode::Default => DEFAULT_SPACE, 133 | WhitespaceNormalizationMode::IgnoreAll => return, 134 | WhitespaceNormalizationMode::Verbatim => value, 135 | WhitespaceNormalizationMode::Custom(val) => { 136 | let val = val.to_string(); 137 | self.write_buffer(OutputBuffer::Whitespace(InternalString::from(&val))); 138 | return 139 | }, 140 | }; 141 | self.write_buffer(OutputBuffer::Whitespace(InternalString::from(ws_str))); 142 | } 143 | } 144 | 145 | impl OutputWriter { 146 | /// Consumes the output and returns the final value. 147 | #[inline] 148 | pub fn render_value(mut self) -> RantValue { 149 | match self.buffers.len() { 150 | // An empty output always returns an empty value 151 | 0 => RantValue::Nothing, 152 | // Single buffer is always returned unchanged 153 | 1 => { 154 | let buffer = self.buffers.pop().unwrap(); 155 | match buffer { 156 | OutputBuffer::Fragment(s) | OutputBuffer::Whitespace(s) => RantValue::String(s.as_str().into()), 157 | OutputBuffer::Value(v) => v, 158 | _ => RantValue::Nothing, 159 | } 160 | }, 161 | _ => { 162 | match self.mode { 163 | OutputPrintMode::Single | OutputPrintMode::Text => { 164 | // Multiple buffers are concatenated into a single string, unless they are all empty 165 | let mut has_any_nonempty = false; 166 | let mut output = InternalString::new(); 167 | let mut format: OutputFormat = Default::default(); 168 | for buf in self.buffers { 169 | if let Some(s) = buf.render_string(&mut format) { 170 | has_any_nonempty = true; 171 | output.push_str(s.as_str()); 172 | } 173 | } 174 | // If there is at least one non-empty, return the string; otherwise, return empty value 175 | if has_any_nonempty { 176 | RantValue::String(output.as_str().into()) 177 | } else { 178 | RantValue::Nothing 179 | } 180 | }, 181 | OutputPrintMode::List => { 182 | let mut output = RantList::new(); 183 | for buf in self.buffers { 184 | match buf { 185 | OutputBuffer::Value(RantValue::List(list)) => { 186 | output.extend(list.borrow().iter().cloned()); 187 | }, 188 | OutputBuffer::Value(RantValue::Tuple(tuple)) => { 189 | output.extend(tuple.iter().cloned()); 190 | }, 191 | _ => {} 192 | } 193 | } 194 | RantValue::List(output.into_handle()) 195 | }, 196 | OutputPrintMode::Tuple => { 197 | let mut output: Vec = vec![]; 198 | for buf in self.buffers { 199 | if let OutputBuffer::Value(RantValue::Tuple(tuple)) = buf { 200 | output.extend(tuple.iter().cloned()); 201 | } 202 | } 203 | RantValue::Tuple(RantTuple::from(output).into_handle()) 204 | }, 205 | OutputPrintMode::Map => { 206 | let mut output = RantMap::new(); 207 | for buf in self.buffers { 208 | if let OutputBuffer::Value(RantValue::Map(map)) = buf { 209 | output.extend(map.borrow()) 210 | } 211 | } 212 | RantValue::Map(output.into_handle()) 213 | }, 214 | OutputPrintMode::Concat => { 215 | let mut val = RantValue::Nothing; 216 | for buf in self.buffers { 217 | if let OutputBuffer::Value(bufval) = buf { 218 | val = val + bufval; 219 | } 220 | } 221 | val 222 | } 223 | } 224 | } 225 | } 226 | } 227 | } 228 | 229 | impl Default for OutputWriter { 230 | fn default() -> Self { 231 | OutputWriter::new(Default::default()) 232 | } 233 | } 234 | 235 | #[derive(Debug, Copy, Clone)] 236 | enum OutputPrintMode { 237 | /// Only the first value buffer is printed. 238 | Single, 239 | /// The entire buffer chain is printed as text. 240 | Text, 241 | /// All buffers containing a list (or tuple) are concatenated into a single list. 242 | List, 243 | /// All buffers containing a tuple are concatenated into a single tuple. 244 | Tuple, 245 | /// All buffers containing a map are merged into a single map. 246 | Map, 247 | /// All buffers are concatenated according to the `RantValue::concat` rules. 248 | Concat, 249 | } 250 | 251 | /// A unit of output. 252 | #[derive(Debug)] 253 | enum OutputBuffer { 254 | Fragment(InternalString), 255 | Whitespace(InternalString), 256 | Value(RantValue), 257 | NumberFormatUpdate(NumberFormat), 258 | } 259 | 260 | impl OutputBuffer { 261 | /// Consumes the buffer and returns its contents rendered as a single `String`. 262 | #[inline] 263 | pub(crate) fn render_string(self, format: &mut OutputFormat) -> Option { 264 | Some(match self { 265 | Self::Fragment(s) => s, 266 | Self::Whitespace(s) => s, 267 | Self::Value(RantValue::Nothing) => return None, 268 | Self::Value(RantValue::Int(n)) => format.number_format.format_integer(n), 269 | Self::Value(RantValue::Float(n)) => format.number_format.format_float(n), 270 | Self::Value(v) => InternalString::from(v.to_string()), 271 | Self::NumberFormatUpdate(fmt) => { 272 | format.number_format = fmt; 273 | return None 274 | }, 275 | }) 276 | } 277 | } -------------------------------------------------------------------------------- /src/stdlib/assertion.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub fn assert(vm: &mut VM, (condition, message): (bool, Option)) -> RantStdResult { 4 | if !condition { 5 | runtime_error!(RuntimeErrorType::AssertError, "{}", message.as_deref().unwrap_or("assertion failed: condition was false")); 6 | } 7 | Ok(()) 8 | } 9 | 10 | pub fn assert_not(vm: &mut VM, (condition, message): (bool, Option)) -> RantStdResult { 11 | if condition { 12 | runtime_error!(RuntimeErrorType::AssertError, "{}", message.as_deref().unwrap_or("assertion failed: condition was true")); 13 | } 14 | Ok(()) 15 | } 16 | 17 | pub fn assert_eq(vm: &mut VM, (actual, expected, message): (RantValue, RantValue, Option)) -> RantStdResult { 18 | if expected != actual { 19 | runtime_error!(RuntimeErrorType::AssertError, "{}", message.unwrap_or_else(|| format!("expected: {}; actual: {}", expected, actual))); 20 | } 21 | Ok(()) 22 | } 23 | 24 | pub fn assert_neq(vm: &mut VM, (actual, unexpected, message): (RantValue, RantValue, Option)) -> RantStdResult { 25 | if unexpected == actual { 26 | runtime_error!(RuntimeErrorType::AssertError, "{}", message.unwrap_or_else(|| format!("unexpected value: {}", unexpected))); 27 | } 28 | Ok(()) 29 | } -------------------------------------------------------------------------------- /src/stdlib/block.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::runtime::resolver::Reps; 3 | 4 | pub fn if_(vm: &mut VM, condition: bool) -> RantStdResult { 5 | vm.resolver_mut().attrs_mut().make_if(condition); 6 | Ok(()) 7 | } 8 | 9 | pub fn elseif(vm: &mut VM, condition: bool) -> RantStdResult { 10 | vm.resolver_mut().attrs_mut().make_else_if(condition); 11 | Ok(()) 12 | } 13 | 14 | pub fn else_(vm: &mut VM, _: ()) -> RantStdResult { 15 | vm.resolver_mut().attrs_mut().make_else(); 16 | Ok(()) 17 | } 18 | 19 | pub fn rep(vm: &mut VM, reps: RantValue) -> RantStdResult { 20 | vm.resolver_mut().attrs_mut().reps = match reps { 21 | RantValue::Int(n) => Reps::Repeat(n.max(0) as usize), 22 | RantValue::String(s) => match s.as_str() { 23 | "once" => Reps::Once, 24 | "all" => Reps::All, 25 | "forever" => Reps::Forever, 26 | _ => return Err(RuntimeError { 27 | error_type: RuntimeErrorType::ArgumentError, 28 | description: Some(format!("unknown repetition mode: '{}'", s)), 29 | stack_trace: None, 30 | }) 31 | }, 32 | _ => return Err(RuntimeError { 33 | error_type: RuntimeErrorType::ArgumentError, 34 | description: Some(format!("value of type '{}' cannot be used as repetition value", reps.type_name())), 35 | stack_trace: None, 36 | }) 37 | }; 38 | Ok(()) 39 | } 40 | 41 | pub fn sep(vm: &mut VM, separator: RantValue) -> RantStdResult { 42 | vm.resolver_mut().attrs_mut().separator = separator; 43 | Ok(()) 44 | } 45 | 46 | pub fn mut_(vm: &mut VM, mutator_func: Option) -> RantStdResult { 47 | vm.resolver_mut().attrs_mut().mutator = mutator_func; 48 | Ok(()) 49 | } 50 | 51 | pub fn step_index(vm: &mut VM, _: ()) -> RantStdResult { 52 | let n = vm.resolver().active_block().map_or(0, |block| block.step_index()); 53 | vm.cur_frame_mut().write(n as i64); 54 | Ok(()) 55 | } 56 | 57 | pub fn step(vm: &mut VM, _: ()) -> RantStdResult { 58 | let n = vm.resolver().active_block().map_or(0, |block| block.step()); 59 | vm.cur_frame_mut().write(n as i64); 60 | Ok(()) 61 | } 62 | 63 | pub fn step_count(vm: &mut VM, _: ()) -> RantStdResult { 64 | let n = vm.resolver().active_block().map_or(0, |block| block.step_count()); 65 | vm.cur_frame_mut().write(n as i64); 66 | Ok(()) 67 | } 68 | 69 | pub fn mksel(vm: &mut VM, mode: SelectorMode) -> RantStdResult { 70 | let sel = RantSelector::new(mode); 71 | vm.cur_frame_mut().write(sel); 72 | Ok(()) 73 | } 74 | 75 | pub fn sel(vm: &mut VM, selector: Option) -> RantStdResult { 76 | match selector { 77 | Some(RantValue::Selector(handle)) => { 78 | vm.resolver_mut().attrs_mut().selector = Some(handle); 79 | }, 80 | Some(val @ RantValue::String(_)) => { 81 | let mode = SelectorMode::try_from_rant(val).into_runtime_result()?; 82 | let selector = RantSelector::new(mode).into_handle(); 83 | vm.resolver_mut().attrs_mut().selector = Some(selector); 84 | }, 85 | Some(val) => { 86 | return Err(RuntimeError { 87 | error_type: RuntimeErrorType::ValueError(ValueError::InvalidConversion { 88 | from: val.type_name(), 89 | to: "selector", 90 | message: None, 91 | }), 92 | description: Some("value is not a selector".to_owned()), 93 | stack_trace: None, 94 | }) 95 | }, 96 | None => { 97 | let selector = vm.resolver().attrs().selector 98 | .clone() 99 | .map(|s| s.into_rant()) 100 | .unwrap_or(RantValue::Nothing); 101 | vm.cur_frame_mut().write(selector); 102 | }, 103 | } 104 | Ok(()) 105 | } 106 | 107 | pub fn sel_skip(vm: &mut VM, (selector, n): (RantSelectorHandle, Option)) -> RantStdResult { 108 | let mut sel = selector.borrow_mut(); 109 | let count = sel.count(); 110 | let n = n.unwrap_or(1); 111 | for _ in 0..n { 112 | sel.select(count, vm.rng()).into_runtime_result()?; 113 | } 114 | Ok(()) 115 | } 116 | 117 | pub fn sel_freeze(vm: &mut VM, (selector, frozen): (RantSelectorHandle, Option)) -> RantStdResult { 118 | let mut sel = selector.borrow_mut(); 119 | sel.set_frozen(frozen.unwrap_or(true)); 120 | Ok(()) 121 | } 122 | 123 | pub fn sel_frozen(vm: &mut VM, (selector, frozen): (RantSelectorHandle, bool)) -> RantStdResult { 124 | let sel = selector.borrow(); 125 | vm.cur_frame_mut().write(sel.is_frozen()); 126 | Ok(()) 127 | } 128 | 129 | pub fn reset_attrs(vm: &mut VM, _: ()) -> RantStdResult { 130 | vm.resolver_mut().reset_attrs(); 131 | Ok(()) 132 | } -------------------------------------------------------------------------------- /src/stdlib/boolean.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// `[$and: lhs (bool); rhs (bool); extra* (bool)]` 4 | /// 5 | /// Returns the logical AND of the operands. 6 | pub fn and(vm: &mut VM, (lhs, rhs, extra): (bool, bool, VarArgs)) -> RantStdResult { 7 | let result = (lhs && rhs) && extra.iter().all(|b| *b); 8 | vm.cur_frame_mut().write(result); 9 | Ok(()) 10 | } 11 | 12 | /// `[$or: lhs (bool); rhs (bool); extra* (bool)]` 13 | /// 14 | /// Returns the logical OR of the operands. 15 | pub fn or(vm: &mut VM, (lhs, rhs, extra): (bool, bool, VarArgs)) -> RantStdResult { 16 | let result = (lhs || rhs) || extra.iter().any(|b| *b); 17 | vm.cur_frame_mut().write(result); 18 | Ok(()) 19 | } 20 | 21 | /// `[$not: val (bool)]` 22 | /// 23 | /// Gets the inverse of the operand. 24 | pub fn not(vm: &mut VM, val: bool) -> RantStdResult { 25 | vm.cur_frame_mut().write(!val); 26 | Ok(()) 27 | } 28 | 29 | /// `$xor: lhs (bool); rhs (bool)]` 30 | /// 31 | /// Retirms the logical XOR of the operands. 32 | pub fn xor(vm: &mut VM, (lhs, rhs): (bool, bool)) -> RantStdResult { 33 | vm.cur_frame_mut().write(lhs ^ rhs); 34 | Ok(()) 35 | } -------------------------------------------------------------------------------- /src/stdlib/compare.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub fn eq(vm: &mut VM, (a, b): (RantValue, RantValue)) -> RantStdResult { 4 | vm.cur_frame_mut().write(a == b); 5 | Ok(()) 6 | } 7 | 8 | pub fn neq(vm: &mut VM, (a, b): (RantValue, RantValue)) -> RantStdResult { 9 | vm.cur_frame_mut().write(a != b); 10 | Ok(()) 11 | } 12 | 13 | pub fn lt(vm: &mut VM, (a, b): (RantValue, RantValue)) -> RantStdResult { 14 | vm.cur_frame_mut().write(a < b); 15 | Ok(()) 16 | } 17 | 18 | pub fn gt(vm: &mut VM, (a, b): (RantValue, RantValue)) -> RantStdResult { 19 | vm.cur_frame_mut().write(a > b); 20 | Ok(()) 21 | } 22 | 23 | pub fn le(vm: &mut VM, (a, b): (RantValue, RantValue)) -> RantStdResult { 24 | vm.cur_frame_mut().write(a <= b); 25 | Ok(()) 26 | } 27 | 28 | pub fn ge(vm: &mut VM, (a, b): (RantValue, RantValue)) -> RantStdResult { 29 | vm.cur_frame_mut().write(a >= b); 30 | Ok(()) 31 | } -------------------------------------------------------------------------------- /src/stdlib/convert.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub fn to_int(vm: &mut VM, value: RantValue) -> RantStdResult { 4 | vm.cur_frame_mut().write(value.into_int_value()); 5 | Ok(()) 6 | } 7 | 8 | pub fn to_float(vm: &mut VM, value: RantValue) -> RantStdResult { 9 | vm.cur_frame_mut().write(value.into_float_value()); 10 | Ok(()) 11 | } 12 | 13 | pub fn to_string(vm: &mut VM, value: RantValue) -> RantStdResult { 14 | vm.cur_frame_mut().write(value.into_string_value()); 15 | Ok(()) 16 | } 17 | 18 | pub fn to_bool(vm: &mut VM, value: RantValue) -> RantStdResult { 19 | vm.cur_frame_mut().write(value.into_bool_value()); 20 | Ok(()) 21 | } 22 | 23 | pub fn to_list(vm: &mut VM, collection: RantValue) -> RantStdResult { 24 | vm.cur_frame_mut().write(collection.into_list_value()); 25 | Ok(()) 26 | } 27 | 28 | pub fn to_tuple(vm: &mut VM, collection: RantValue) -> RantStdResult { 29 | vm.cur_frame_mut().write(collection.into_tuple_value()); 30 | Ok(()) 31 | } -------------------------------------------------------------------------------- /src/stdlib/general.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | 3 | use data::DataSourceError; 4 | 5 | use super::*; 6 | use crate::lang::{VarAccessMode}; 7 | 8 | /// Prints the first argument that isn't of type `nothing`. 9 | pub fn alt(vm: &mut VM, (a, mut b): (RantValue, RequiredVarArgs)) -> RantStdResult { 10 | if !a.is_nothing() { 11 | vm.cur_frame_mut().write(a); 12 | Ok(()) 13 | } else { 14 | for val in b.drain(..) { 15 | if !val.is_nothing() { 16 | vm.cur_frame_mut().write(val); 17 | break 18 | } 19 | } 20 | Ok(()) 21 | } 22 | } 23 | 24 | /// Calls a function with the specified arguments. 25 | pub fn call(vm: &mut VM, (func, args): (RantFunctionHandle, Option>)) -> RantStdResult { 26 | vm.push_val(RantValue::Function(Rc::clone(&func)))?; 27 | let argc = args.as_ref().map(|args| args.len()).unwrap_or(0); 28 | if let Some(mut args) = args { 29 | for arg in args.drain(..).rev() { 30 | vm.push_val(arg)?; 31 | } 32 | } 33 | vm.cur_frame_mut().push_intent(Intent::Call { argc, override_print: false }); 34 | Ok(()) 35 | } 36 | 37 | /// Combines and prints the specified values. 38 | pub fn cat(vm: &mut VM, mut args: VarArgs) -> RantStdResult { 39 | let frame = vm.cur_frame_mut(); 40 | for val in args.drain(..) { 41 | frame.write(val); 42 | } 43 | 44 | Ok(()) 45 | } 46 | 47 | /// Prints the specified values to the calling scope. 48 | pub fn print(vm: &mut VM, mut args: VarArgs) -> RantStdResult { 49 | if args.len() < 2 { 50 | let frame = vm.cur_frame_mut(); 51 | for val in args.drain(..) { 52 | frame.write(val); 53 | } 54 | } else if let Some(frame) = vm.parent_frame_mut(1) { 55 | for val in args.drain(..) { 56 | frame.write(val); 57 | } 58 | } 59 | 60 | Ok(()) 61 | } 62 | 63 | /// Returns a copy of a value. 64 | pub fn copy(vm: &mut VM, val: RantValue) -> RantStdResult { 65 | vm.cur_frame_mut().write(val.shallow_copy()); 66 | Ok(()) 67 | } 68 | 69 | /// Prints `a` if `cond` is true, or `b` otherwise. 70 | pub fn either(vm: &mut VM, (cond, a, b): (bool, RantValue, RantValue)) -> RantStdResult { 71 | let val = if cond { a } else { b }; 72 | vm.cur_frame_mut().write(val); 73 | Ok(()) 74 | } 75 | 76 | /// Forks the RNG with the specified seed. 77 | pub fn fork(vm: &mut VM, seed: Option) -> RantStdResult { 78 | let rng = match seed { 79 | Some(RantValue::Int(i)) => vm.rng().fork_i64(i), 80 | Some(RantValue::String(s)) => vm.rng().fork_str(s.as_str()), 81 | Some(other) => runtime_error!(RuntimeErrorType::ArgumentError, "seeding fork with '{}' value is not supported", other.type_name()), 82 | None => vm.rng().fork_random(), 83 | }; 84 | vm.push_rng(Rc::new(rng)); 85 | Ok(()) 86 | } 87 | 88 | /// Prints the type name of `val`. 89 | pub fn type_(vm: &mut VM, val: RantValue) -> RantStdResult { 90 | vm.cur_frame_mut().write_frag(val.type_name()); 91 | Ok(()) 92 | } 93 | 94 | /// Unforks the RNG down one level. 95 | pub fn unfork(vm: &mut VM, _: ()) -> RantStdResult { 96 | if vm.pop_rng().is_none() { 97 | runtime_error!(RuntimeErrorType::InvalidOperation, "cannot unfork root seed"); 98 | } 99 | Ok(()) 100 | } 101 | 102 | /// Does nothing and takes any number of arguments. Use this as a no-op or non-printing temporal pipe. 103 | pub fn tap(vm: &mut VM, _: VarArgs) -> RantStdResult { 104 | Ok(()) 105 | } 106 | 107 | /// Prints the RNG seed currently in use. 108 | pub fn seed(vm: &mut VM, _: ()) -> RantStdResult { 109 | let signed_seed = unsafe { 110 | mem::transmute::(vm.rng().seed()) 111 | }; 112 | let frame = vm.cur_frame_mut(); 113 | frame.write(RantValue::Int(signed_seed)); 114 | Ok(()) 115 | } 116 | 117 | /// Prints the length of `val`. 118 | pub fn len(vm: &mut VM, val: RantValue) -> RantStdResult { 119 | vm.cur_frame_mut().write(val.len().try_into_rant().into_runtime_result()?); 120 | Ok(()) 121 | } 122 | 123 | /// Raises a runtime error. 124 | pub fn error(vm: &mut VM, msg: Option) -> RantStdResult { 125 | const DEFAULT_ERROR_MESSAGE: &str = "user error"; 126 | Err(RuntimeError { 127 | error_type: RuntimeErrorType::UserError, 128 | description: msg, 129 | stack_trace: None, 130 | }) 131 | } 132 | 133 | /// Generates a `range` value with an inclusive start bound and exclusive end bound. 134 | pub fn range(vm: &mut VM, (a, b, step): (i64, Option, Option)) -> RantStdResult { 135 | let step = step.unwrap_or(1); 136 | 137 | let range = if let Some(b) = b { 138 | RantRange::new(a, b, step) 139 | } else { 140 | RantRange::new(0, a, step) 141 | }; 142 | 143 | vm.cur_frame_mut().write(range); 144 | Ok(()) 145 | } 146 | 147 | /// Generates a `range` value with inclusive bounds. 148 | pub fn irange(vm: &mut VM, (a, b, step): (i64, Option, Option)) -> RantStdResult { 149 | let step = step.unwrap_or(1); 150 | 151 | let range = if let Some(b) = b { 152 | RantRange::new(a, b + if a <= b { 1 } else { -1 }, step) 153 | } else { 154 | RantRange::new(0, a + if a >= 0 { 1 } else { -1 }, step) 155 | }; 156 | 157 | vm.cur_frame_mut().write(range); 158 | Ok(()) 159 | } 160 | 161 | /// Imports a module. 162 | pub fn require(vm: &mut VM, module_path: String) -> RantStdResult { 163 | // Get name of module from path 164 | if let Some(module_name) = 165 | PathBuf::from(&module_path) 166 | .with_extension("") 167 | .file_name() 168 | .map(|name| name.to_str()) 169 | .flatten() 170 | .map(|name| name.to_owned()) 171 | { 172 | // Check if module is cached; if so, don't do anything 173 | if let Some(cached_module) = vm.context().get_cached_module(&module_name) { 174 | vm.def_var_value(module_name.as_str(), VarAccessMode::Descope(1), cached_module, true)?; 175 | return Ok(()) 176 | } 177 | 178 | // If not cached, attempt to resolve it and load the module 179 | let dependant = Rc::clone(vm.cur_frame().origin()); 180 | let module_resolver = Rc::clone(&vm.context().module_resolver); 181 | match module_resolver.try_resolve(vm.context_mut(), module_path.as_str(), Some(dependant.as_ref())) { 182 | Ok(module_program) => { 183 | vm.cur_frame_mut().push_intent(Intent::ImportLastAsModule { module_name, descope: 1 }); 184 | vm.push_frame_flavored(Rc::clone(&module_program.root), StackFrameFlavor::FunctionBody)?; 185 | Ok(()) 186 | }, 187 | Err(err) => runtime_error!(RuntimeErrorType::ModuleError(err)), 188 | } 189 | } else { 190 | runtime_error!(RuntimeErrorType::ArgumentError, "missing module name from path: {}", module_path); 191 | } 192 | } 193 | 194 | /// Attempts to call the `context` function. If it raises a runtime error, calls the `handler` function and passes in the error information. 195 | pub fn try_(vm: &mut VM, (context, handler): (RantFunctionHandle, Option)) -> RantStdResult { 196 | vm.push_unwind_state(handler); 197 | vm.cur_frame_mut().push_intent(Intent::DropStaleUnwinds); 198 | vm.call_func(context, vec![], false)?; 199 | Ok(()) 200 | } 201 | 202 | /// Requests data from a data source whose ID matches `dsid`. 203 | pub fn ds_request(vm: &mut VM, (dsid, args): (InternalString, VarArgs)) -> RantStdResult { 204 | match vm.context().data_source(dsid.as_str()) { 205 | Some(ds) => { 206 | let result = ds.request_data(args.into_vec()).into_runtime_result()?; 207 | vm.cur_frame_mut().write(result); 208 | }, 209 | None => { 210 | runtime_error!(RuntimeErrorType::DataSourceError(DataSourceError::User(format!("data source '{}' not found", &dsid)))) 211 | } 212 | } 213 | Ok(()) 214 | } 215 | 216 | /// Prints a list of available data source IDs. 217 | pub fn ds_query_sources(vm: &mut VM, _: ()) -> RantStdResult { 218 | let sources = vm.context().iter_data_sources().map(|(id, _)| id.into_rant()).collect::(); 219 | vm.cur_frame_mut().write(sources); 220 | Ok(()) 221 | } -------------------------------------------------------------------------------- /src/stdlib/generate.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub fn rand(vm: &mut VM, (a, b): (i64, i64)) -> RantStdResult { 4 | let n = vm.rng().next_i64(a, b); 5 | vm.cur_frame_mut().write(n); 6 | Ok(()) 7 | } 8 | 9 | pub fn randf(vm: &mut VM, (a, b): (f64, f64)) -> RantStdResult { 10 | let n = vm.rng().next_f64(a, b); 11 | vm.cur_frame_mut().write(n); 12 | Ok(()) 13 | } 14 | 15 | pub fn rand_list(vm: &mut VM, (a, b, n): (i64, i64, usize)) -> RantStdResult { 16 | let mut list = RantList::new(); 17 | let rng = vm.rng(); 18 | for _ in 0..n { 19 | list.push(RantValue::Int(rng.next_i64(a, b))); 20 | } 21 | vm.cur_frame_mut().write(list); 22 | Ok(()) 23 | } 24 | 25 | pub fn randf_list(vm: &mut VM, (a, b, n): (f64, f64, usize)) -> RantStdResult { 26 | let mut list = RantList::new(); 27 | let rng = vm.rng(); 28 | for _ in 0..n { 29 | list.push(RantValue::Float(rng.next_f64(a, b))); 30 | } 31 | vm.cur_frame_mut().write(list); 32 | Ok(()) 33 | } 34 | 35 | pub fn alpha(vm: &mut VM, count: Option) -> RantStdResult { 36 | const CHARS: &[u8] = b"abcdefghijklmnopqrstuvwxyz"; 37 | let count = count.unwrap_or(1); 38 | let mut s = String::with_capacity(count); 39 | let rng = vm.rng(); 40 | for _ in 0..count { 41 | let ch = CHARS[rng.next_usize(CHARS.len())] as char; 42 | s.push(ch); 43 | } 44 | vm.cur_frame_mut().write_frag(s.as_str()); 45 | Ok(()) 46 | } 47 | 48 | pub fn digh(vm: &mut VM, count: Option) -> RantStdResult { 49 | const CHARS: &[u8] = b"0123456789abcdef"; 50 | let count = count.unwrap_or(1); 51 | let mut s = String::with_capacity(count); 52 | let rng = vm.rng(); 53 | for _ in 0..count { 54 | let ch = CHARS[rng.next_usize(CHARS.len())] as char; 55 | s.push(ch); 56 | } 57 | vm.cur_frame_mut().write_frag(s.as_str()); 58 | Ok(()) 59 | } 60 | 61 | pub fn dig(vm: &mut VM, count: Option) -> RantStdResult { 62 | const CHARS: &[u8] = b"0123456789"; 63 | let count = count.unwrap_or(1); 64 | let mut s = String::with_capacity(count); 65 | let rng = vm.rng(); 66 | for _ in 0..count { 67 | let ch = CHARS[rng.next_usize(CHARS.len())] as char; 68 | s.push(ch); 69 | } 70 | vm.cur_frame_mut().write_frag(s.as_str()); 71 | Ok(()) 72 | } 73 | 74 | pub fn dignz(vm: &mut VM, count: Option) -> RantStdResult { 75 | const CHARS: &[u8] = b"123456789"; 76 | let count = count.unwrap_or(1); 77 | let mut s = String::with_capacity(count); 78 | let rng = vm.rng(); 79 | for _ in 0..count { 80 | let ch = CHARS[rng.next_usize(CHARS.len())] as char; 81 | s.push(ch); 82 | } 83 | vm.cur_frame_mut().write_frag(s.as_str()); 84 | Ok(()) 85 | } 86 | 87 | pub fn rand_list_sum(vm: &mut VM, (value, n, variance): (RantValue, i64, Option)) -> RantStdResult { 88 | if n <= 0 { 89 | return Err(RuntimeError { 90 | error_type: RuntimeErrorType::ArgumentError, 91 | description: Some("shred count must be greater than zero".to_owned()), 92 | stack_trace: None, 93 | }) 94 | } 95 | 96 | let rng = vm.rng(); 97 | 98 | match value { 99 | RantValue::Int(m) => { 100 | let mut shreds = vec![]; 101 | let variance = variance.unwrap_or_default().abs() as i64; 102 | let quotient = m / n; 103 | let remainder = m % n; 104 | 105 | let (head_chunk, tail_chunk) = (quotient + remainder, quotient); 106 | 107 | // Populate chunks 108 | for i in 0..n { 109 | shreds.push(if i == 0 { head_chunk } else { tail_chunk }); 110 | } 111 | 112 | // Redistribute chunk size randomly 113 | for i in 0..n { 114 | let shift = rng.next_i64(0, variance + 1); 115 | let cell = shreds.get_mut(i as usize).unwrap(); 116 | *cell -= shift; 117 | let cell = shreds.get_mut(((i + 1) % n) as usize).unwrap(); 118 | *cell += shift; 119 | } 120 | 121 | vm.cur_frame_mut().write(shreds); 122 | }, 123 | RantValue::Float(m) => { 124 | let mut shreds = vec![]; 125 | let variance = variance.unwrap_or_default().abs() as f64; 126 | let nf = n as f64; 127 | let quotient = m / nf; 128 | let remainder = m % nf; 129 | 130 | let (head_chunk, tail_chunk) = (quotient + remainder, quotient); 131 | 132 | // Populate chunks 133 | for i in 0..n { 134 | shreds.push(if i == 0 { head_chunk } else { tail_chunk }); 135 | } 136 | 137 | // Redistribute chunk size randomly 138 | for i in 0..n { 139 | let shift = rng.next_f64(0.0, variance); 140 | let cell = shreds.get_mut(i as usize).unwrap(); 141 | *cell -= shift; 142 | let cell = shreds.get_mut(((i + 1) % n) as usize).unwrap(); 143 | *cell += shift; 144 | } 145 | 146 | vm.cur_frame_mut().write(shreds); 147 | }, 148 | other => { 149 | return Err(RuntimeError { 150 | error_type: RuntimeErrorType::ArgumentError, 151 | description: Some(format!("cannot shred '{}' value", other.type_name())), 152 | stack_trace: None, 153 | }) 154 | } 155 | } 156 | 157 | Ok(()) 158 | } 159 | 160 | pub fn maybe(vm: &mut VM, p: Option) -> RantStdResult { 161 | let b = vm.rng().next_bool(p.unwrap_or(0.5)); 162 | vm.cur_frame_mut().write(b); 163 | Ok(()) 164 | } 165 | 166 | pub fn pick(vm: &mut VM, list: RantValue) -> RantStdResult { 167 | let n = list.len(); 168 | if n > 0 { 169 | let index = vm.rng().next_usize(n); 170 | let item = list.index_get(index as i64).into_runtime_result()?; 171 | vm.cur_frame_mut().write(item); 172 | } 173 | Ok(()) 174 | } 175 | 176 | pub fn pickn(vm: &mut VM, (input, count): (RantOrderedCollection, usize)) -> RantStdResult { 177 | let rng = vm.rng(); 178 | let len = input.len(); 179 | let list = (0..count) 180 | .into_iter() 181 | .map(|_| input.index_get(rng.next_usize(len).try_into().unwrap_or(i64::MAX)).into_runtime_result()) 182 | .collect::>()?; 183 | vm.cur_frame_mut().write(list); 184 | Ok(()) 185 | } 186 | 187 | pub fn pick_sparse(vm: &mut VM, mut items: RequiredVarArgs) -> RantStdResult { 188 | let len_sum = items 189 | .iter() 190 | .map(|v| v.len()) 191 | .fold(0usize, |acc, x| acc.saturating_add(x)); 192 | 193 | let mut rem_sum = vm.rng().next_usize(len_sum); 194 | 195 | for item in items.drain(..) { 196 | let item_len = item.len(); 197 | if rem_sum < item_len { 198 | vm.cur_frame_mut().write(if item.is_indexable() { 199 | item.index_get(rem_sum.try_into().unwrap_or(i64::MAX)).into_runtime_result()? 200 | } else { 201 | item 202 | }); 203 | break 204 | } 205 | rem_sum = rem_sum.saturating_sub(item_len); 206 | } 207 | 208 | Ok(()) 209 | } -------------------------------------------------------------------------------- /src/stdlib/math.rs: -------------------------------------------------------------------------------- 1 | use cervine::Cow; 2 | 3 | use super::*; 4 | 5 | /// `[$add: lhs (any); rhs (any)]` 6 | /// 7 | /// Adds two values. 8 | pub fn add(vm: &mut VM, (lhs, rhs): (RantValue, RantValue)) -> RantStdResult { 9 | vm.cur_frame_mut().write(lhs + rhs); 10 | Ok(()) 11 | } 12 | 13 | pub fn clamp(vm: &mut VM, (value, a, b): (RantValue, RantValue, RantValue)) -> RantStdResult { 14 | vm.cur_frame_mut().write(util::clamp(value, a, b)); 15 | Ok(()) 16 | } 17 | 18 | /// `[$mul: lhs (any); rhs (any)]` 19 | /// 20 | /// Multiplies two values. 21 | pub fn mul(vm: &mut VM, (lhs, rhs): (RantValue, RantValue)) -> RantStdResult { 22 | vm.cur_frame_mut().write(lhs * rhs); 23 | Ok(()) 24 | } 25 | 26 | /// `[$mul-add: lhs (any); mhs (any); rhs (any)]` 27 | /// 28 | /// Multiplies two values, then adds a third value to the result. 29 | pub fn mul_add(vm: &mut VM, (lhs, mhs, rhs): (RantValue, RantValue, RantValue)) -> RantStdResult { 30 | vm.cur_frame_mut().write(lhs * mhs + rhs); 31 | Ok(()) 32 | } 33 | 34 | /// `[$sub: lhs (any); rhs (any)]` 35 | /// 36 | /// Subtracts one value from another. 37 | pub fn sub(vm: &mut VM, (lhs, rhs): (RantValue, RantValue)) -> RantStdResult { 38 | vm.cur_frame_mut().write(lhs - rhs); 39 | Ok(()) 40 | } 41 | 42 | /// `[$div: lhs (any); rhs (any)]` 43 | /// 44 | /// Divides one number by another. 45 | pub fn div(vm: &mut VM, (lhs, rhs): (RantValue, RantValue)) -> RantStdResult { 46 | vm.cur_frame_mut().write((lhs / rhs).into_runtime_result()?); 47 | Ok(()) 48 | } 49 | 50 | /// `[$mod: lhs (any); rhs (any)]` 51 | /// 52 | /// Gets the modulus of two values. 53 | pub fn mod_(vm: &mut VM, (lhs, rhs): (RantValue, RantValue)) -> RantStdResult { 54 | vm.cur_frame_mut().write((lhs % rhs).into_runtime_result()?); 55 | Ok(()) 56 | } 57 | 58 | /// `[$neg: val (any)]` 59 | /// 60 | /// Negates a value. 61 | pub fn neg(vm: &mut VM, val: RantValue) -> RantStdResult { 62 | vm.cur_frame_mut().write(-val); 63 | Ok(()) 64 | } 65 | 66 | /// `[$recip: val (any)]` 67 | /// 68 | /// Gets the reciproval of a value. 69 | pub fn recip(vm: &mut VM, val: RantValue) -> RantStdResult { 70 | vm.cur_frame_mut().write((RantValue::Float(1.0) / val).into_runtime_result()?); 71 | Ok(()) 72 | } 73 | 74 | /// `[$floor: val (int|float)]` 75 | /// 76 | /// Gets the largest integer that is less than or equal to the specified value. 77 | pub fn floor(vm: &mut VM, val: RantValue) -> RantStdResult { 78 | let val_result = match val { 79 | RantValue::Float(f) => RantValue::Float(f.floor()), 80 | RantValue::Int(i) => RantValue::Int(i), 81 | other => runtime_error!(RuntimeErrorType::ArgumentError, "cannot use floor function on '{}' value") 82 | }; 83 | vm.cur_frame_mut().write(val_result); 84 | Ok(()) 85 | } 86 | 87 | /// `[$ceil: val (int|float)]` 88 | /// 89 | /// Gets the smallest integer that is greater than or equal to the specified value. 90 | pub fn ceil(vm: &mut VM, val: RantValue) -> RantStdResult { 91 | let val_result = match val { 92 | RantValue::Float(f) => RantValue::Float(f.ceil()), 93 | RantValue::Int(i) => RantValue::Int(i), 94 | other => runtime_error!(RuntimeErrorType::ArgumentError, "cannot use ceil function on '{}' value") 95 | }; 96 | vm.cur_frame_mut().write(val_result); 97 | Ok(()) 98 | } 99 | 100 | /// `[$frac: val (float)]` 101 | /// 102 | /// Gets the fractional part of the specified float value. 103 | pub fn frac(vm: &mut VM, val: RantValue) -> RantStdResult { 104 | let val_result = match val { 105 | RantValue::Float(f) => RantValue::Float(f.fract()), 106 | other => runtime_error!(RuntimeErrorType::ArgumentError, "cannot use frac function on '{}' value") 107 | }; 108 | vm.cur_frame_mut().write(val_result); 109 | Ok(()) 110 | } 111 | 112 | /// `[$abs: num]` 113 | /// 114 | /// Calculates the absolute value of `num`. 115 | pub fn abs(vm: &mut VM, num: RantValue) -> RantStdResult { 116 | vm.cur_frame_mut().write(num.abs().into_runtime_result()?); 117 | Ok(()) 118 | } 119 | 120 | /// `[$pow: lhs (int|float); rhs (int|float)]` 121 | /// 122 | /// Raises `lhs` to the `rhs` power. 123 | pub fn pow(vm: &mut VM, (lhs, rhs): (RantValue, RantValue)) -> RantStdResult { 124 | vm.cur_frame_mut().write(lhs.pow(rhs).into_runtime_result()?); 125 | Ok(()) 126 | } 127 | 128 | /// `[$sin: x (float)]` 129 | /// 130 | /// Calculates the sine of `x` (in radians). 131 | pub fn sin(vm: &mut VM, x: f64) -> RantStdResult { 132 | vm.cur_frame_mut().write(x.sin()); 133 | Ok(()) 134 | } 135 | 136 | /// `[$asin: x (float)]` 137 | /// 138 | /// Calculates the arcsine (in radians) of `x`. 139 | pub fn asin(vm: &mut VM, x: f64) -> RantStdResult { 140 | vm.cur_frame_mut().write(x.asin()); 141 | Ok(()) 142 | } 143 | 144 | /// `[$cos: x (float)]` 145 | /// 146 | /// Calculates the cosine of `x` (in radians). 147 | pub fn cos(vm: &mut VM, x: f64) -> RantStdResult { 148 | vm.cur_frame_mut().write(x.cos()); 149 | Ok(()) 150 | } 151 | 152 | /// `[$acos: x (float)]` 153 | /// 154 | /// Calculates the arccosine (in radians) of `x`. 155 | pub fn acos(vm: &mut VM, x: f64) -> RantStdResult { 156 | vm.cur_frame_mut().write(x.acos()); 157 | Ok(()) 158 | } 159 | 160 | /// `[$tan: x (float)]` 161 | /// 162 | /// Calculates the tangent of `x` (in radians). 163 | pub fn tan(vm: &mut VM, x: f64) -> RantStdResult { 164 | vm.cur_frame_mut().write(x.tan()); 165 | Ok(()) 166 | } 167 | 168 | /// `[$atan: x (float)]` 169 | /// 170 | /// Calculates the arctangent (in radians) of `x`. 171 | pub fn atan(vm: &mut VM, x: f64) -> RantStdResult { 172 | vm.cur_frame_mut().write(x.atan()); 173 | Ok(()) 174 | } 175 | 176 | /// `[$atan: y (float); x (float)]` 177 | /// 178 | /// Calculates the four-quadrant arctangent (in radians) of `y / x`. 179 | pub fn atan2(vm: &mut VM, (y, x): (f64, f64)) -> RantStdResult { 180 | vm.cur_frame_mut().write(y.atan2(x)); 181 | Ok(()) 182 | } 183 | 184 | /// `[sqrt: num (int|float)]` 185 | /// 186 | /// Calculates the square root of `num`. 187 | pub fn sqrt(vm: &mut VM, num: RantNumber) -> RantStdResult { 188 | let result = match num { 189 | RantNumber::Int(i) => (i as f64).sqrt(), 190 | RantNumber::Float(f) => f.sqrt(), 191 | }; 192 | vm.cur_frame_mut().write(result); 193 | Ok(()) 194 | } 195 | 196 | pub fn min(vm: &mut VM, values: RequiredVarArgs) -> RantStdResult { 197 | if values.is_empty() { 198 | return Ok(()) 199 | } 200 | 201 | let iter = values.iter(); 202 | let mut min = None; 203 | 204 | for val in iter { 205 | let flat_val = match val { 206 | RantValue::List(list_ref) => { 207 | Some(Cow::Owned(util::min_rant_value(list_ref.borrow().iter()).cloned().unwrap_or_default())) 208 | }, 209 | RantValue::Tuple(tuple_ref) => { 210 | Some(Cow::Owned(util::min_rant_value(tuple_ref.iter()).cloned().unwrap_or_default())) 211 | } 212 | other => Some(Cow::Borrowed(other)), 213 | }; 214 | 215 | if min.is_none() || flat_val < min { 216 | min = flat_val; 217 | } 218 | } 219 | 220 | vm.cur_frame_mut().write(match min.unwrap() { 221 | Cow::Owned(val) => val, 222 | Cow::Borrowed(val) => val.clone(), 223 | }); 224 | 225 | Ok(()) 226 | } 227 | 228 | pub fn max(vm: &mut VM, values: RequiredVarArgs) -> RantStdResult { 229 | if values.is_empty() { 230 | return Ok(()) 231 | } 232 | 233 | let iter = values.iter(); 234 | let mut max = None; 235 | 236 | for val in iter { 237 | let flat_val = match val { 238 | RantValue::List(list_ref) => { 239 | Some(Cow::Owned(util::max_rant_value(list_ref.borrow().iter()).cloned().unwrap_or_default())) 240 | }, 241 | RantValue::Tuple(tuple_ref) => { 242 | Some(Cow::Owned(util::max_rant_value(tuple_ref.iter()).cloned().unwrap_or_default())) 243 | } 244 | other => Some(Cow::Borrowed(other)), 245 | }; 246 | 247 | if max.is_none() || flat_val >= max { 248 | max = flat_val; 249 | } 250 | } 251 | 252 | vm.cur_frame_mut().write(match max.unwrap() { 253 | Cow::Owned(val) => val, 254 | Cow::Borrowed(val) => val.clone(), 255 | }); 256 | 257 | Ok(()) 258 | } -------------------------------------------------------------------------------- /src/stdlib/mod.rs: -------------------------------------------------------------------------------- 1 | //! The Rant standard library. 2 | 3 | #![allow(unused_variables)] 4 | #![allow(clippy::unnecessary_wraps)] 5 | 6 | use std::rc::Rc; 7 | use crate::*; 8 | use crate::runtime::*; 9 | use crate::convert::*; 10 | use crate::convert::TryIntoRant; 11 | 12 | mod assertion; 13 | mod block; 14 | mod boolean; 15 | mod collections; 16 | mod compare; 17 | mod convert; 18 | mod format; 19 | mod general; 20 | mod generate; 21 | mod math; 22 | mod proto; 23 | mod strings; 24 | mod verify; 25 | 26 | use self::{ 27 | assertion::*, block::*, boolean::*, collections::*, 28 | compare::*, convert::*, format::*, 29 | general::*, generate::*, math::*, proto::*, 30 | strings::*, verify::* 31 | }; 32 | 33 | pub(crate) type RantStdResult = Result<(), RuntimeError>; 34 | 35 | #[macro_export] 36 | macro_rules! runtime_error { 37 | ($err_type:expr) => {{ 38 | return Err(RuntimeError { 39 | error_type: $err_type, 40 | description: None, 41 | stack_trace: None, 42 | }) 43 | }}; 44 | ($err_type:expr, $msg:literal) => { 45 | return Err(RuntimeError { 46 | error_type: $err_type, 47 | description: Some($msg.to_owned()), 48 | stack_trace: None, 49 | }) 50 | }; 51 | ($err_type:expr, $msg_fmt:literal, $($msg_fmt_args:expr),+) => { 52 | return Err(RuntimeError { 53 | error_type: $err_type, 54 | description: Some(format!($msg_fmt, $($msg_fmt_args),+)), 55 | stack_trace: None, 56 | }) 57 | }; 58 | } 59 | 60 | pub fn load_stdlib(context: &mut Rant) 61 | { 62 | macro_rules! load_func { 63 | ($fname:ident) => {{ 64 | let func = $fname.into_rant_func(); 65 | let name = stringify!($fname).trim_end_matches('_').replace("_", "-"); 66 | context.set_global_force(name.as_str(), RantValue::Function(Rc::new(func)), true); 67 | }}; 68 | ($fname:ident, $id:literal) => {{ 69 | let func = $fname.into_rant_func(); 70 | context.set_global_force($id, RantValue::Function(Rc::new(func)), true); 71 | }}; 72 | } 73 | 74 | macro_rules! load_funcs { 75 | ($($fname:ident $(as $id:expr)?),+) => { 76 | $(load_func!($fname$(, $id)?);)+ 77 | }; 78 | } 79 | 80 | load_funcs!( 81 | // General functions 82 | alt, call, cat, either, len, type_, seed, tap, print, range, require, irange, fork, unfork, try_, 83 | 84 | // Data source functions 85 | ds_request, ds_query_sources, 86 | 87 | // Assertion functions 88 | assert, assert_not, assert_eq, assert_neq, 89 | 90 | // Formatting functions 91 | ws_fmt, 92 | num_fmt, num_fmt_system, num_fmt_alt, num_fmt_padding, num_fmt_precision, num_fmt_upper, 93 | num_fmt_endian, num_fmt_sign, num_fmt_infinity, num_fmt_group_sep, num_fmt_decimal_sep, 94 | 95 | // Attribute functions 96 | if_, elseif, else_, mksel, rep, sel, sep, mut_, sel_skip, sel_freeze, sel_frozen, 97 | 98 | // Attribute frame stack functions 99 | reset_attrs, 100 | 101 | // Block state functions 102 | step, step_index, step_count, 103 | 104 | // Boolean functions 105 | and, not, or, xor, 106 | 107 | // Comparison functions 108 | eq, neq, gt, lt, ge, le, 109 | 110 | // Verification functions 111 | is_string, is_int, is_float, is_number, is_bool, is_nothing, is_nan, is_odd, is_even, is_factor, is_between, is_some, is, 112 | 113 | // Math functions 114 | abs, add, sub, mul, div, mul_add, mod_, neg, pow, recip, 115 | clamp, min, max, floor, ceil, frac, 116 | asin, sin, acos, cos, atan, atan2, tan, sqrt, 117 | 118 | // Conversion functions 119 | to_int, to_float, to_string, to_bool, to_list, to_tuple, 120 | 121 | // Generator functions 122 | alpha, dig, digh, dignz, maybe, pick, pickn, pick_sparse, 123 | rand, randf, rand_list, randf_list, rand_list_sum, 124 | 125 | // Prototype functions 126 | proto, set_proto, 127 | 128 | // Collection functions 129 | assoc, augment, augment_self, augment_thru, chunks, clear, fill_self, fill_thru, has, index_of, insert, keys, last_index_of, list, 130 | nlist, remove, rev, sift_self, sift_thru, sift, squish_self, squish_thru, squish, take, translate, values, 131 | filter, join, map, sort_self, sort_thru, sort, shuffle_self, shuffle_thru, shuffle, sum, tuple, 132 | push, pop, oxford_join, zip, 133 | 134 | // String functions 135 | char_, lower, upper, seg, split, lines, indent, string_replace, trim, ord, ord_all, 136 | 137 | // Error functions 138 | error 139 | ); 140 | 141 | // Constants 142 | context.set_global_force("RANT_VERSION", RantValue::String(RANT_LANG_VERSION.into()), true); 143 | context.set_global_force("BUILD_VERSION", RantValue::String(BUILD_VERSION.into()), true); 144 | context.set_global_force("EPSILON", RantValue::EPSILON, true); 145 | context.set_global_force("MIN_FLOAT", RantValue::MIN_FLOAT, true); 146 | context.set_global_force("MAX_FLOAT", RantValue::MAX_FLOAT, true); 147 | context.set_global_force("MIN_INT", RantValue::MIN_INT, true); 148 | context.set_global_force("MAX_INT", RantValue::MAX_INT, true); 149 | context.set_global_force("INFINITY", RantValue::INFINITY, true); 150 | context.set_global_force("NEG_INFINITY", RantValue::NEG_INFINITY, true); 151 | context.set_global_force("NAN", RantValue::NAN, true); 152 | } -------------------------------------------------------------------------------- /src/stdlib/proto.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub fn proto(vm: &mut VM, map: RantMapHandle) -> RantStdResult { 4 | vm.cur_frame_mut().write(map.borrow().proto().map_or(RantValue::Nothing, RantValue::Map)); 5 | Ok(()) 6 | } 7 | 8 | pub fn set_proto(vm: &mut VM, (map, proto): (RantMapHandle, Option)) -> RantStdResult { 9 | map.borrow_mut().set_proto(proto); 10 | Ok(()) 11 | } -------------------------------------------------------------------------------- /src/stdlib/strings.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub fn split(vm: &mut VM, (s, at): (String, Option)) -> RantStdResult { 4 | let list = if at.as_ref().map(|s| s.is_empty()).unwrap_or(true) { 5 | s.chars() 6 | .map(|c| c.to_string()) 7 | .collect::>() 8 | } else { 9 | s 10 | .split(at.unwrap().as_str()) 11 | .map(|part| part.to_owned()) 12 | .collect::>() 13 | }.try_into_rant().into_runtime_result()?; 14 | 15 | vm.cur_frame_mut().write(list); 16 | Ok(()) 17 | } 18 | 19 | pub fn lines(vm: &mut VM, s: String) -> RantStdResult { 20 | let lines: Vec = s.lines().map(|line| RantValue::String(line.into())).collect(); 21 | vm.cur_frame_mut().write(lines); 22 | Ok(()) 23 | } 24 | 25 | pub fn indent(vm: &mut VM, (text, indent): (String, String)) -> RantStdResult { 26 | let frame = vm.cur_frame_mut(); 27 | let mut first = true; 28 | for line in text.lines() { 29 | if first { 30 | first = false; 31 | } else { 32 | frame.write_frag("\n"); 33 | } 34 | frame.write_frag(indent.as_str()); 35 | frame.write_frag(line); 36 | } 37 | Ok(()) 38 | } 39 | 40 | pub fn upper(vm: &mut VM, s: String) -> RantStdResult { 41 | vm.cur_frame_mut().write_frag(s.to_uppercase().as_str()); 42 | Ok(()) 43 | } 44 | 45 | pub fn lower(vm: &mut VM, s: String) -> RantStdResult { 46 | vm.cur_frame_mut().write_frag(s.to_lowercase().as_str()); 47 | Ok(()) 48 | } 49 | 50 | pub fn string_replace(vm: &mut VM, (input, query, replacement): (RantString, RantString, RantString)) -> RantStdResult { 51 | vm.cur_frame_mut().write_frag(input.as_str().replace(query.as_str(), replacement.as_str()).as_str()); 52 | Ok(()) 53 | } 54 | 55 | pub fn trim(vm: &mut VM, s: RantString) -> RantStdResult { 56 | vm.cur_frame_mut().write(s.as_str().trim()); 57 | Ok(()) 58 | } 59 | 60 | pub fn ord(vm: &mut VM, s: InternalString) -> RantStdResult { 61 | if s.is_empty() { 62 | return Ok(()) 63 | } 64 | 65 | vm.cur_frame_mut().write(s.chars().next().unwrap() as u32); 66 | 67 | Ok(()) 68 | } 69 | 70 | pub fn char_(vm: &mut VM, code_point: u32) -> RantStdResult { 71 | if let Some(c) = char::from_u32(code_point) { 72 | vm.cur_frame_mut().write(c); 73 | } 74 | Ok(()) 75 | } 76 | 77 | pub fn ord_all(vm: &mut VM, s: InternalString) -> RantStdResult { 78 | vm.cur_frame_mut().write(s.chars().map(|c| (c as u32).into_rant()).collect::()); 79 | Ok(()) 80 | } -------------------------------------------------------------------------------- /src/stdlib/verify.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::util; 3 | 4 | 5 | /// `[$is-odd: val (int)]` 6 | /// 7 | /// Returns true if `val` is odd. 8 | pub fn is_odd(vm: &mut VM, val: i64) -> RantStdResult { 9 | vm.cur_frame_mut().write(val % 2 != 0); 10 | Ok(()) 11 | } 12 | 13 | /// `[$is-even: val (int)]` 14 | /// 15 | /// Returns true if `val` is even. 16 | pub fn is_even(vm: &mut VM, val: i64) -> RantStdResult { 17 | vm.cur_frame_mut().write(val % 2 == 0); 18 | Ok(()) 19 | } 20 | 21 | /// `[$is-factor: value (int); factor (int)]` 22 | /// 23 | /// Returns true if `value` is divisible by `factor`. 24 | pub fn is_factor(vm: &mut VM, (value, factor): (i64, i64)) -> RantStdResult { 25 | vm.cur_frame_mut().write(factor != 0 && value % factor == 0); 26 | Ok(()) 27 | } 28 | 29 | pub fn is_string(vm: &mut VM, value: RantValue) -> RantStdResult { 30 | vm.cur_frame_mut().write(value.get_type() == RantValueType::String); 31 | Ok(()) 32 | } 33 | 34 | pub fn is_int(vm: &mut VM, value: RantValue) -> RantStdResult { 35 | vm.cur_frame_mut().write(value.get_type() == RantValueType::Int); 36 | Ok(()) 37 | } 38 | 39 | pub fn is_float(vm: &mut VM, value: RantValue) -> RantStdResult { 40 | vm.cur_frame_mut().write(value.get_type() == RantValueType::Float); 41 | Ok(()) 42 | } 43 | 44 | pub fn is_number(vm: &mut VM, value: RantValue) -> RantStdResult { 45 | vm.cur_frame_mut().write(matches!(value.get_type(), RantValueType::Int | RantValueType::Float)); 46 | Ok(()) 47 | } 48 | 49 | pub fn is_between(vm: &mut VM, (value, a, b): (RantValue, RantValue, RantValue)) -> RantStdResult { 50 | let (a, b) = util::minmax(a, b); 51 | let result = value >= a && value <= b; 52 | vm.cur_frame_mut().write(result); 53 | Ok(()) 54 | } 55 | 56 | pub fn is(vm: &mut VM, (value, type_name): (RantValue, String)) -> RantStdResult { 57 | vm.cur_frame_mut().write(value.type_name() == type_name); 58 | Ok(()) 59 | } 60 | 61 | pub fn is_nothing(vm: &mut VM, value: RantValue) -> RantStdResult { 62 | vm.cur_frame_mut().write(value.is_nothing()); 63 | Ok(()) 64 | } 65 | 66 | pub fn is_some(vm: &mut VM, value: RantValue) -> RantStdResult { 67 | vm.cur_frame_mut().write(!value.is_nothing()); 68 | Ok(()) 69 | } 70 | 71 | pub fn is_bool(vm: &mut VM, value: RantValue) -> RantStdResult { 72 | vm.cur_frame_mut().write(value.get_type() == RantValueType::Boolean); 73 | Ok(()) 74 | } 75 | 76 | pub fn is_nan(vm: &mut VM, value: RantValue) -> RantStdResult { 77 | vm.cur_frame_mut().write(value.is_nan()); 78 | Ok(()) 79 | } -------------------------------------------------------------------------------- /src/string.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::{Debug}, ops::Add, fmt::Display}; 2 | 3 | use once_cell::sync::OnceCell; 4 | use smallvec::{SmallVec}; 5 | use unicode_segmentation::UnicodeSegmentation; 6 | 7 | use crate::{InternalString, RantList, RantValue, util, RantTuple}; 8 | 9 | type Graphemes = SmallVec<[(usize, usize); 1]>; 10 | 11 | /// Represents Rant's `string` type. 12 | #[derive(Debug, Default)] 13 | pub struct RantString { 14 | raw: InternalString, 15 | graphemes: OnceCell>, 16 | } 17 | 18 | impl RantString { 19 | /// Creates a new, empty string. 20 | #[inline] 21 | pub fn new() -> Self { 22 | Default::default() 23 | } 24 | 25 | #[inline] 26 | fn from_str(s: &str) -> Self { 27 | Self { 28 | raw: InternalString::from(s), 29 | .. Default::default() 30 | } 31 | } 32 | 33 | #[inline] 34 | fn from_char(c: char) -> Self { 35 | let mut s = InternalString::new(); 36 | s.push(c); 37 | Self { 38 | raw: s, 39 | .. Default::default() 40 | } 41 | } 42 | } 43 | 44 | impl RantString { 45 | #[inline] 46 | pub(crate) fn graphemes(&self) -> &Graphemes { 47 | self.graphemes.get_or_init(|| 48 | Some(UnicodeSegmentation::grapheme_indices(self.raw.as_str(), true) 49 | .map(|(i, slice)| (i, i + slice.len())) 50 | .collect::()) 51 | ).as_ref().unwrap() 52 | } 53 | 54 | /// Creates a copy of the string with the graphemes in reverse order. 55 | #[inline] 56 | pub fn reversed(&self) -> Self { 57 | let mut buf = InternalString::new(); 58 | for i in (0..self.len()).rev() { 59 | if let Some(g) = self.grapheme_at(i) { 60 | buf.push_str(g.as_str()); 61 | } 62 | } 63 | 64 | Self { 65 | raw: buf, 66 | .. Default::default() 67 | } 68 | } 69 | 70 | /// Gets a reference to the string as a string slice. 71 | #[inline] 72 | pub fn as_str(&self) -> &str { 73 | self.raw.as_str() 74 | } 75 | 76 | /// Gets the grapheme string at the specified index. 77 | #[inline] 78 | pub fn grapheme_at(&self, index: usize) -> Option { 79 | if index >= self.len() { 80 | return None 81 | } 82 | 83 | let (start, end) = self.graphemes()[index]; 84 | Some(RantString::from(&self.raw[start..end])) 85 | } 86 | 87 | /// Splits the string into individual graphemes and returns them as a Rant list. 88 | #[inline] 89 | pub fn to_rant_list(&self) -> RantList { 90 | let n = self.len(); 91 | let mut list = RantList::with_capacity(n); 92 | for i in 0..n { 93 | let c = self.grapheme_at(i).unwrap(); 94 | list.push(RantValue::String(c)); 95 | } 96 | list 97 | } 98 | 99 | /// Splits the string into individual graphemes and returns them as a Rant tuple. 100 | #[inline] 101 | pub fn to_rant_tuple(&self) -> RantTuple { 102 | let n = self.len(); 103 | let mut items = Vec::with_capacity(n); 104 | for i in 0..n { 105 | let c = self.grapheme_at(i).unwrap(); 106 | items.push(RantValue::String(c)); 107 | } 108 | RantTuple::from(items) 109 | } 110 | 111 | /// Gets the string at the specified slice. 112 | pub fn to_slice(&self, start: Option, end: Option) -> Option { 113 | let graphemes = self.graphemes(); 114 | let len = graphemes.len(); 115 | 116 | // Bounds checks 117 | if let Some(start) = start { 118 | if start > len { 119 | return None 120 | } 121 | } 122 | 123 | if let Some(end) = end { 124 | if end > len { 125 | return None 126 | } 127 | } 128 | 129 | Some(match (start, end) { 130 | (None, None) => self.clone(), 131 | (None, Some(end)) => { 132 | let raw_end = if end < len { 133 | graphemes[end].0 134 | } else { 135 | self.raw.len() 136 | }; 137 | Self::from(&self.raw[..raw_end]) 138 | }, 139 | (Some(start), None) => { 140 | let raw_start = graphemes[start].0; 141 | Self::from(&self.raw[raw_start..]) 142 | }, 143 | (Some(start), Some(end)) => { 144 | let (start, end) = util::minmax(start, end); 145 | if start == end { 146 | return Some(Self::default()) 147 | } 148 | let raw_start = graphemes[start].0; 149 | let raw_end = if end < len { 150 | graphemes[end].0 151 | } else { 152 | self.raw.len() 153 | }; 154 | Self::from(&self.raw[raw_start..raw_end]) 155 | } 156 | }) 157 | } 158 | } 159 | 160 | impl Clone for RantString { 161 | #[inline] 162 | fn clone(&self) -> Self { 163 | Self { 164 | raw: self.raw.clone(), 165 | .. Default::default() 166 | } 167 | } 168 | } 169 | 170 | impl RantString { 171 | #[inline] 172 | pub fn len(&self) -> usize { 173 | self.graphemes().len() 174 | } 175 | 176 | #[inline] 177 | pub fn is_empty(&self) -> bool { 178 | self.graphemes().is_empty() 179 | } 180 | } 181 | 182 | impl From for RantString { 183 | fn from(s: InternalString) -> Self { 184 | Self::from_str(s.as_str()) 185 | } 186 | } 187 | 188 | impl From<&str> for RantString { 189 | fn from(s: &str) -> Self { 190 | Self::from_str(s) 191 | } 192 | } 193 | 194 | impl From for RantString { 195 | fn from(s: String) -> Self { 196 | Self::from_str(&s) 197 | } 198 | } 199 | 200 | impl From<&String> for RantString { 201 | fn from(s: &String) -> Self { 202 | Self::from_str(s) 203 | } 204 | } 205 | 206 | impl From for RantString { 207 | fn from(c: char) -> Self { 208 | Self::from_char(c) 209 | } 210 | } 211 | 212 | impl Add for RantString { 213 | type Output = RantString; 214 | 215 | fn add(self, rhs: Self) -> Self::Output { 216 | Self { 217 | raw: self.raw + rhs.raw, 218 | .. Default::default() 219 | } 220 | } 221 | } 222 | 223 | impl Display for RantString { 224 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 225 | write!(f, "{}", &self.raw) 226 | } 227 | } 228 | 229 | impl PartialEq for RantString { 230 | fn eq(&self, other: &Self) -> bool { 231 | self.raw == other.raw 232 | } 233 | } 234 | 235 | impl PartialOrd for RantString { 236 | fn partial_cmp(&self, other: &Self) -> Option { 237 | self.raw.partial_cmp(&other.raw) 238 | } 239 | } -------------------------------------------------------------------------------- /src/tools/cli/_copyright.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Robin Pederson -------------------------------------------------------------------------------- /src/tools/cli/_credits.txt: -------------------------------------------------------------------------------- 1 | RRRRRRRRR 2 | RR RRRRRRRRR 3 | RR RR 4 | RR RRRRRR RR 5 | RR RRRRRRRR RR 6 | RR RRRRRRRR RR 7 | RR RRRRRRRRR RR 8 | RR RRRRRR RR 9 | RR RRRRR RR 10 | RR RRRRR RR 11 | RR RRRRRRRRR 12 | RR RRRRRRRRR 13 | RRRRRRRRRRRRRRRRRRRRRR 14 | 15 | Designed and developed by Robin Pederson 16 | 17 | Over the years, there have been many developers and community members 18 | whose efforts and feedback helped to shape Rant into what it is today. 19 | 20 | In particular, I want to thank: 21 | 22 | Ashley Rogers (@dialupnoises) 23 | Dr. Sajid Farooq 24 | Tamme Schichler (@Tamschi) 25 | 26 | ... and the many others who tested, submitted issues/PRs, and donated. -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use crate::RantValue; 2 | 3 | /// Sorts `a` and `b` in ascending order and returns them as a `(min, max)` tuple. 4 | #[inline] 5 | pub fn minmax(a: T, b: T) -> (T, T) { 6 | if a <= b { (a, b) } else { (b, a) } 7 | } 8 | 9 | /// Clamps `val` between `a` and `b`. 10 | #[inline] 11 | pub fn clamp(val: T, a: T, b: T) -> T { 12 | let (min, max) = minmax(a, b); 13 | if val >= min && val <= max { 14 | val 15 | } else if val < min { 16 | min 17 | } else { 18 | max 19 | } 20 | } 21 | 22 | /// Clamps `val` between 0 and 1. 23 | #[inline] 24 | pub fn saturate(val: f64) -> f64 { 25 | if val < 0. { 26 | 0. 27 | } else if val > 1. { 28 | 1. 29 | } else { 30 | val 31 | } 32 | } 33 | 34 | /// Converts `true` to `1` and `false` to `0`. 35 | #[inline(always)] 36 | pub fn bi64(val: bool) -> i64 { 37 | if val { 1 } else { 0 } 38 | } 39 | 40 | /// Converts `true` to `1.0` and `false` to `0.0`. 41 | #[inline(always)] 42 | pub fn bf64(val: bool) -> f64 { 43 | if val { 1.0 } else { 0.0 } 44 | } 45 | 46 | #[inline] 47 | pub fn max_rant_value<'a>(mut iter: impl Iterator) -> Option<&'a RantValue> { 48 | if let Some(mut max) = iter.next() { 49 | for val in iter { 50 | if val >= max { 51 | max = val; 52 | } 53 | } 54 | Some(max) 55 | } else { 56 | None 57 | } 58 | } 59 | 60 | #[inline] 61 | pub fn min_rant_value<'a>(mut iter: impl Iterator) -> Option<&'a RantValue> { 62 | if let Some(mut min) = iter.next() { 63 | for val in iter { 64 | if val < min { 65 | min = val; 66 | } 67 | } 68 | Some(min) 69 | } else { 70 | None 71 | } 72 | } -------------------------------------------------------------------------------- /src/var.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, mem, ops::Deref, rc::Rc}; 2 | 3 | use crate::RantValue; 4 | 5 | /// Represents a Rant variable of one of two flavors: **by-value** or **by-reference**. 6 | /// 7 | /// ## Cloning 8 | /// The `Clone` implementation for this type does not copy any data in `ByRef*` variants; it only copies the reference. 9 | #[derive(Debug)] 10 | pub enum RantVar { 11 | /// By-value variable 12 | ByVal(RantValue), 13 | /// By-value constant 14 | ByValConst(RantValue), 15 | /// By-ref variable 16 | ByRef(Rc>), 17 | /// By-ref constant 18 | ByRefConst(Rc) 19 | } 20 | 21 | impl Default for RantVar { 22 | fn default() -> Self { 23 | Self::ByVal(RantValue::Nothing) 24 | } 25 | } 26 | 27 | impl Clone for RantVar { 28 | /// Creates a copy of the variable, preserving references. 29 | fn clone(&self) -> Self { 30 | match self { 31 | Self::ByVal(val) => Self::ByVal(val.clone()), 32 | Self::ByRef(val_ref) => Self::ByRef(Rc::clone(val_ref)), 33 | Self::ByValConst(val) => Self::ByValConst(val.clone()), 34 | Self::ByRefConst(val_ref) => Self::ByRefConst(Rc::clone(val_ref)), 35 | } 36 | } 37 | } 38 | 39 | impl RantVar { 40 | /// Returns `true` if the variable is a constant. 41 | #[inline] 42 | pub fn is_const(&self) -> bool { 43 | matches!(self, Self::ByValConst(_) | Self::ByRefConst(_)) 44 | } 45 | 46 | /// Returns `true` if the variable is by-value. 47 | #[inline] 48 | pub fn is_by_val(&self) -> bool { 49 | matches!(self, Self::ByVal(_) | Self::ByValConst(_)) 50 | } 51 | 52 | /// Returns `true` if the variable is by-reference. 53 | #[inline] 54 | pub fn is_by_ref(&self) -> bool { 55 | matches!(self, Self::ByRef(_) | Self::ByRefConst(_)) 56 | } 57 | 58 | /// Converts the variable to its by-reference counterpart. 59 | #[inline] 60 | pub fn make_by_ref(&mut self) { 61 | if self.is_by_ref() { return } 62 | match mem::take(self) { 63 | Self::ByVal(val) => *self = Self::ByRef(Rc::new(RefCell::new(val))), 64 | Self::ByValConst(val) => *self = Self::ByRefConst(Rc::new(val)), 65 | _ => unreachable!() 66 | } 67 | } 68 | 69 | /// Attempts to write the specified value to the variable. 70 | /// If the variable is a constant, returns `false`; otherwise, returns `true`. 71 | #[inline] 72 | pub fn write(&mut self, value: RantValue) -> bool { 73 | match self { 74 | Self::ByVal(val) => *val = value, 75 | Self::ByRef(val_ref) => { 76 | val_ref.replace(value); 77 | }, 78 | Self::ByRefConst(_) | Self::ByValConst(_) => return false, 79 | } 80 | true 81 | } 82 | 83 | /// Returns an immutable reference to the contained value. 84 | #[inline] 85 | pub fn value_ref(&self) -> impl Deref + '_ { 86 | match self { 87 | Self::ByVal(val) | Self::ByValConst(val) => cervine::Cow::Borrowed(val), 88 | Self::ByRef(val_ref) => cervine::Cow::Owned(val_ref.borrow()), 89 | Self::ByRefConst(val_ref) => cervine::Cow::Borrowed(val_ref.as_ref()), 90 | } 91 | } 92 | 93 | /// Returns a copy of the variable value. 94 | #[inline] 95 | pub fn value_cloned(&self) -> RantValue { 96 | match self { 97 | RantVar::ByVal(val) | RantVar::ByValConst(val) => val.clone(), 98 | RantVar::ByRef(val_ref) => val_ref.borrow().clone(), 99 | RantVar::ByRefConst(val_ref) => val_ref.as_ref().clone(), 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /tests/sources/access/add_assign.rant: -------------------------------------------------------------------------------- 1 | <$x = 1> 2 | 3 | [assert-eq: ; 2] -------------------------------------------------------------------------------- /tests/sources/access/and_assign.rant: -------------------------------------------------------------------------------- 1 | <$x = @true> 2 | 3 | [assert-eq: ; @false] -------------------------------------------------------------------------------- /tests/sources/access/div_assign.rant: -------------------------------------------------------------------------------- 1 | <$x = 20> 2 | 3 | [assert-eq: ; 10] -------------------------------------------------------------------------------- /tests/sources/access/dynamic_index_setter.rant: -------------------------------------------------------------------------------- 1 | <$list = (:1;2;3)> 2 | 3 | [join:;,\s] -------------------------------------------------------------------------------- /tests/sources/access/dynamic_multi_index_setter.rant: -------------------------------------------------------------------------------- 1 | <$lists = (:(:1;2;3);(:4;5;6))> 2 | 3 | [join:[sum:];,\s] -------------------------------------------------------------------------------- /tests/sources/access/empty-def.rant: -------------------------------------------------------------------------------- 1 | <$foo> 2 | [assert-eq: ; <>] -------------------------------------------------------------------------------- /tests/sources/access/getter_fallback_from_index.rant: -------------------------------------------------------------------------------- 1 | <$list = (foo; bar; baz)> 2 | ,\s 3 | ,\s 4 | ,\s 5 | 6 | -------------------------------------------------------------------------------- /tests/sources/access/getter_fallback_from_key.rant: -------------------------------------------------------------------------------- 1 | <$obj = (:: a=foo; b=bar; c=baz)> 2 | ,\s 3 | ,\s 4 | ,\s 5 | -------------------------------------------------------------------------------- /tests/sources/access/getter_fallback_from_var.rant: -------------------------------------------------------------------------------- 1 | <$foo = 123> 2 | , -------------------------------------------------------------------------------- /tests/sources/access/inv_index_get.rant: -------------------------------------------------------------------------------- 1 | <$list = (1;2;3)> 2 | , , -------------------------------------------------------------------------------- /tests/sources/access/inv_index_set.rant: -------------------------------------------------------------------------------- 1 | <$list = (:1;2;3)> 2 | 3 | [join:;,\s] -------------------------------------------------------------------------------- /tests/sources/access/mod_assign.rant: -------------------------------------------------------------------------------- 1 | <$x = 10> 2 | 3 | [assert-eq: ; 1] -------------------------------------------------------------------------------- /tests/sources/access/mul_assign.rant: -------------------------------------------------------------------------------- 1 | <$x = 2> 2 | 3 | [assert-eq: ; 6] -------------------------------------------------------------------------------- /tests/sources/access/or_assign.rant: -------------------------------------------------------------------------------- 1 | <$x = @true> 2 | 3 | [assert-eq: ; @true] -------------------------------------------------------------------------------- /tests/sources/access/override_shadowed_locals_with_multi_descope.rant: -------------------------------------------------------------------------------- 1 | <@text $test=foo> 2 | { 3 | <@text $test=bar> 4 | { 5 | <@text $test=baz> 6 | <^^test> <^test> 7 | } 8 | } -------------------------------------------------------------------------------- /tests/sources/access/pow_assign.rant: -------------------------------------------------------------------------------- 1 | <$x = 2> 2 | 3 | [assert-eq: ; 8] -------------------------------------------------------------------------------- /tests/sources/access/sub_assign.rant: -------------------------------------------------------------------------------- 1 | <$x = 1> 2 | 3 | [assert-eq: ; 0] -------------------------------------------------------------------------------- /tests/sources/access/xor_assign.rant: -------------------------------------------------------------------------------- 1 | <$x = @true> 2 | 3 | [assert-eq: ; @false] -------------------------------------------------------------------------------- /tests/sources/anonymous/anon_getter.rant: -------------------------------------------------------------------------------- 1 | <$list = (foo;bar)> 2 | 3 | [$get-list] {} 4 | 5 | <([get-list])/0> <([get-list])/1> -------------------------------------------------------------------------------- /tests/sources/anonymous/anon_setter.rant: -------------------------------------------------------------------------------- 1 | <$list = (: foo;bar)> 2 | 3 | [$get-list] {} 4 | 5 | <([get-list])/0 = baz> 6 | <([get-list])/1 = qux> 7 | 8 | -------------------------------------------------------------------------------- /tests/sources/anonymous/dynamic_anon_getter.rant: -------------------------------------------------------------------------------- 1 | <$lists = ((1;2;3);(4;5;6))> 2 | [$get-lists]{} 3 | <([get-lists])/(1)/(2)> -------------------------------------------------------------------------------- /tests/sources/anonymous/dynamic_anon_setter.rant: -------------------------------------------------------------------------------- 1 | <$lists = (: (: 1; 2; 3); (: 4; 5; 6))> 2 | [$get-lists]{} 3 | <([get-lists])/(1)/(2) = 7> 4 | -------------------------------------------------------------------------------- /tests/sources/assert/assert_fail.rant: -------------------------------------------------------------------------------- 1 | [add: 2; 2 |> eq: 5 |> assert] -------------------------------------------------------------------------------- /tests/sources/assert/assert_pass.rant: -------------------------------------------------------------------------------- 1 | [add: 1; 1 |> eq: 2 |> assert] -------------------------------------------------------------------------------- /tests/sources/branch/if-else.rant: -------------------------------------------------------------------------------- 1 | [assert-eq: @if 0: { 2 | <> 3 | } @else: { 4 | 123 5 | }; 123] -------------------------------------------------------------------------------- /tests/sources/branch/if-elseif-else.rant: -------------------------------------------------------------------------------- 1 | <%a = 3> 2 | 3 | [assert-eq: @if @eq 1: { 4 | <> 5 | } @elseif @eq 2: { 6 | <> 7 | } @else: { 8 | 123 9 | }; 123] -------------------------------------------------------------------------------- /tests/sources/branch/if-elseif.rant: -------------------------------------------------------------------------------- 1 | <%a = 2> 2 | 3 | [assert-eq: @if @eq 1: { 4 | <> 5 | } @elseif @eq 2: { 6 | 123 7 | }; 123] -------------------------------------------------------------------------------- /tests/sources/branch/if.rant: -------------------------------------------------------------------------------- 1 | [assert-eq: @if @true: { 2 | 123 3 | }; 123] -------------------------------------------------------------------------------- /tests/sources/charms/func_return_output.rant: -------------------------------------------------------------------------------- 1 | [$foo] { 2 | bar @return 3 | } 4 | 5 | [foo |> assert-eq: bar] -------------------------------------------------------------------------------- /tests/sources/charms/func_return_value.rant: -------------------------------------------------------------------------------- 1 | [$foo] { 2 | bar @return baz 3 | } 4 | 5 | [foo |> assert-eq: baz] -------------------------------------------------------------------------------- /tests/sources/charms/rep_break_output.rant: -------------------------------------------------------------------------------- 1 | <%test-val = [rep:3]{A @break}> 2 | 3 | [assert-eq: ; A] -------------------------------------------------------------------------------- /tests/sources/charms/rep_break_value.rant: -------------------------------------------------------------------------------- 1 | <%test-val = [rep:3]{A @break B}> 2 | 3 | [assert-eq: ; B] -------------------------------------------------------------------------------- /tests/sources/charms/rep_continue_output.rant: -------------------------------------------------------------------------------- 1 | <%test-val = [rep:3]{A{@continue}B}> 2 | 3 | [assert-eq: ; AAA] -------------------------------------------------------------------------------- /tests/sources/charms/rep_continue_value.rant: -------------------------------------------------------------------------------- 1 | <%test-val = [rep:3]{A @continue B}> 2 | 3 | [assert-eq: ; BBB] -------------------------------------------------------------------------------- /tests/sources/charms/top_level_return.rant: -------------------------------------------------------------------------------- 1 | @return foo -------------------------------------------------------------------------------- /tests/sources/charms/weight_all_zero.rant: -------------------------------------------------------------------------------- 1 | [assert-eq: { foo @weight 0 }; <>] -------------------------------------------------------------------------------- /tests/sources/closure/closure_capture_arg.rant: -------------------------------------------------------------------------------- 1 | [$gen-closure:msg] { 2 | [?] { 3 | 4 | } 5 | } 6 | 7 | [([gen-closure:foo]) |> assert-eq: foo] -------------------------------------------------------------------------------- /tests/sources/closure/closure_capture_var.rant: -------------------------------------------------------------------------------- 1 | [$gen-closure] { 2 | <$a = foo> 3 | [?]{} 4 | } 5 | 6 | [([gen-closure]) |> assert-eq: foo] -------------------------------------------------------------------------------- /tests/sources/closure/closure_mutate_captured_value.rant: -------------------------------------------------------------------------------- 1 | { 2 | <$a=0> 3 | [$^next-number] { 4 | <$val = > 5 | + 1> 6 | 7 | } 8 | } 9 | 10 | [rep:4][sep:\s] 11 | { 12 | [next-number] 13 | } -------------------------------------------------------------------------------- /tests/sources/collections/filter_with_native_predicate.rant: -------------------------------------------------------------------------------- 1 | [join: 2 | [filter: (:1;2;3;4;5;6;7;8;9;10); ]; 3 | ,\s 4 | ] -------------------------------------------------------------------------------- /tests/sources/collections/filter_with_user_predicate.rant: -------------------------------------------------------------------------------- 1 | [join: 2 | [filter: (:1;2;3;4;5;6;7;8;9;10); [?:x] {[is-odd: ]}]; 3 | ,\s 4 | ] -------------------------------------------------------------------------------- /tests/sources/collections/list_autoconcat.rant: -------------------------------------------------------------------------------- 1 | [assert-eq: (:1; 2) (:3; 4); (:1; 2; 3; 4)] -------------------------------------------------------------------------------- /tests/sources/collections/list_autoconcat_repeater.rant: -------------------------------------------------------------------------------- 1 | [assert-eq: [rep:10]{(:[step])}; (:1; 2; 3; 4; 5; 6; 7; 8; 9; 10)] -------------------------------------------------------------------------------- /tests/sources/collections/list_tuple_autoconcat.rant: -------------------------------------------------------------------------------- 1 | [assert-eq: (1; 2) (:3; 4); (: 1; 2; 3; 4)] -------------------------------------------------------------------------------- /tests/sources/collections/map_autoconcat.rant: -------------------------------------------------------------------------------- 1 | <%foo = 2 | (:: a = 1) 3 | (:: b = 2) 4 | > 5 | 6 | [assert-eq: ; 1] 7 | [assert-eq: ; 2] -------------------------------------------------------------------------------- /tests/sources/collections/map_with_native_callback.rant: -------------------------------------------------------------------------------- 1 | [join: 2 | [map: (:1;2;3;4;5;6;7;8;9;10); ]; 3 | ,\s 4 | ] -------------------------------------------------------------------------------- /tests/sources/collections/map_with_user_callback.rant: -------------------------------------------------------------------------------- 1 | [join: 2 | [map: (:1;2;3;4;5;6;7;8;9;10); [?:x] { [neg:] } ]; 3 | ,\s 4 | ] -------------------------------------------------------------------------------- /tests/sources/collections/tuple_autoconcat.rant: -------------------------------------------------------------------------------- 1 | [assert-eq: (1; 2) (3; 4); (1; 2; 3; 4)] -------------------------------------------------------------------------------- /tests/sources/collections/tuple_autoconcat_repeater.rant: -------------------------------------------------------------------------------- 1 | [assert-eq: [rep:10]{([step];)}; (1; 2; 3; 4; 5; 6; 7; 8; 9; 10)] -------------------------------------------------------------------------------- /tests/sources/collections/zip_with_native_callback.rant: -------------------------------------------------------------------------------- 1 | <$vec1 = (:1;2;3)> 2 | <$vec2 = (:4;5;6)> 3 | [join: [zip: ; ; ]; ,\s] -------------------------------------------------------------------------------- /tests/sources/collections/zip_with_user_callback.rant: -------------------------------------------------------------------------------- 1 | <$vec1 = (:1;2;3)> 2 | <$vec2 = (:4;5;6)> 3 | [join: [zip: ; ; [?:x;y]{[add:;]}]; ,\s] -------------------------------------------------------------------------------- /tests/sources/const/const_define.rant: -------------------------------------------------------------------------------- 1 | <%foo = 123> 2 | [assert-eq: ; 123] -------------------------------------------------------------------------------- /tests/sources/const/const_function.rant: -------------------------------------------------------------------------------- 1 | [%foo] { 2 | bar 3 | } 4 | 5 | [assert-eq: [foo]; bar] -------------------------------------------------------------------------------- /tests/sources/const/const_shadow.rant: -------------------------------------------------------------------------------- 1 | <%foo = a> 2 | { 3 | <$foo = b; foo = c> 4 | } 5 | [assert-eq: ; a] -------------------------------------------------------------------------------- /tests/sources/const/redef_const.rant: -------------------------------------------------------------------------------- 1 | <%foo = a; %foo = b> -------------------------------------------------------------------------------- /tests/sources/const/redef_var_with_const.rant: -------------------------------------------------------------------------------- 1 | <$foo = a> 2 | <%foo = b> 3 | [assert-eq: ; b] -------------------------------------------------------------------------------- /tests/sources/func/assignment_pipe_def_const.rant: -------------------------------------------------------------------------------- 1 | [cat: "hello" > %test] 2 | [assert-eq: ; "hello"] -------------------------------------------------------------------------------- /tests/sources/func/assignment_pipe_def_var.rant: -------------------------------------------------------------------------------- 1 | [cat: "hello" > $test] 2 | [assert-eq: ; "hello"] -------------------------------------------------------------------------------- /tests/sources/func/assignment_pipe_set.rant: -------------------------------------------------------------------------------- 1 | <$test> 2 | [cat: "hello" > test] 3 | [assert-eq: ; "hello"] -------------------------------------------------------------------------------- /tests/sources/func/func_percolation.rant: -------------------------------------------------------------------------------- 1 | [$/test-global] { 2 | global 3 | } 4 | 5 | [$test-local] { 6 | local 7 | } 8 | 9 | { 10 | # Override parent functions with an empty 11 | <$test-global; $test-local> 12 | 13 | # Attempt to call the functions anyway 14 | [test-global]\n[test-local]\n 15 | 16 | # Now bring the test-local function into this scope 17 | [$test-local] { very local } 18 | 19 | # Call test-local again 20 | [test-local] 21 | } -------------------------------------------------------------------------------- /tests/sources/func/func_with_optional_param.rant: -------------------------------------------------------------------------------- 1 | [$arg-or-foo:arg?] { 2 | [alt:;foo] 3 | } 4 | 5 | [arg-or-foo]\n 6 | [arg-or-foo:bar] -------------------------------------------------------------------------------- /tests/sources/func/func_with_variadic_plus.rant: -------------------------------------------------------------------------------- 1 | [$list-args: args+] { 2 | [rep:[len:]] 3 | [sep:\s] 4 | { 5 | 6 | } 7 | } 8 | 9 | [list-args:a]\n 10 | [list-args:a;b]\n 11 | [list-args:a;b;c]\n 12 | [list-args:a;b;c;d] -------------------------------------------------------------------------------- /tests/sources/func/func_with_variadic_star.rant: -------------------------------------------------------------------------------- 1 | [$list-args: args*] { 2 | [rep:[len:]] 3 | [sep:\s] 4 | { 5 | 6 | } 7 | } 8 | 9 | [list-args]\n 10 | [list-args:a]\n 11 | [list-args:a;b]\n 12 | [list-args:a;b;c]\n 13 | [list-args:a;b;c;d] -------------------------------------------------------------------------------- /tests/sources/func/function_piping.rant: -------------------------------------------------------------------------------- 1 | [split: "the quick brown fox jumps over the lazy dog"; \s |> filter: []; [?:word] { [len: |> le: 3] } |> join: \s] -------------------------------------------------------------------------------- /tests/sources/func/function_piping_callback.rant: -------------------------------------------------------------------------------- 1 | [$get-func: a] { 2 | [?: b]{ } 3 | } 4 | 5 | [get-func: foo |> ([]): bar] -------------------------------------------------------------------------------- /tests/sources/func/pipecall_pipeval.rant: -------------------------------------------------------------------------------- 1 | [cat: [?] { meow } |> [] |> assert-eq: meow] -------------------------------------------------------------------------------- /tests/sources/math/max.rant: -------------------------------------------------------------------------------- 1 | [max: 1 |> assert-eq: 1] 2 | [max: 3; 2 |> assert-eq: 3] 3 | [max: (3; 2) |> assert-eq: 3] 4 | [max: 3; (4; -2; 0; 10); 6 |> assert-eq: 10] -------------------------------------------------------------------------------- /tests/sources/math/min.rant: -------------------------------------------------------------------------------- 1 | [min: 1 |> assert-eq: 1] 2 | [min: 3; 2 |> assert-eq: 2] 3 | [min: (3; 2) |> assert-eq: 2] 4 | [min: 3; (4; -2; 0; 10); 6 |> assert-eq: -2] -------------------------------------------------------------------------------- /tests/sources/modules/require.rant: -------------------------------------------------------------------------------- 1 | @require "tests/sources/modules/test-module" 2 | [test-module/test |> assert-eq: 42] -------------------------------------------------------------------------------- /tests/sources/modules/test-module.rant: -------------------------------------------------------------------------------- 1 | <$module = (::)> 2 | 3 | [$module/test] { 4 | 42 5 | } 6 | 7 | -------------------------------------------------------------------------------- /tests/sources/ops/and.rant: -------------------------------------------------------------------------------- 1 | [assert-eq: @true & @true; @true] 2 | [assert-eq: @false & @true; @false] 3 | [assert-eq: @true & @false; @false] 4 | [assert-eq: @false & @false; @false] 5 | 6 | [assert-eq: 1 & 2; 2] 7 | [assert-eq: 0 & 2; 0] 8 | [assert-eq: 1 & 0; 0] 9 | [assert-eq: "" & 0; ""] -------------------------------------------------------------------------------- /tests/sources/ops/and_short_circuit.rant: -------------------------------------------------------------------------------- 1 | # Setup 2 | <$lhs-reads = 0; $rhs-reads = 0; $lhs; $rhs> 3 | 4 | [%get-lhs] { 5 | + 1> 6 | 7 | } 8 | 9 | [%get-rhs] { 10 | + 1> 11 | 12 | } 13 | 14 | # Test @false & @true 15 | 16 | [tap: [get-lhs] & [get-rhs]] 17 | [assert-eq: ; 1] 18 | [assert-eq: ; 0] 19 | 20 | # Reset stats 21 | 22 | 23 | # Test @true & @true 24 | 25 | [tap: [get-lhs] & [get-rhs]] 26 | [assert-eq: ; 1] 27 | [assert-eq: ; 1] -------------------------------------------------------------------------------- /tests/sources/ops/cmp.rant: -------------------------------------------------------------------------------- 1 | [assert: 1 @eq 1] 2 | [assert-not: 1 @eq 2] 3 | [assert: 1 @neq 2] 4 | [assert-not: 1 @neq 1] 5 | 6 | [assert: 1 @lt 2] 7 | [assert-not: 2 @lt 1] 8 | 9 | [assert: 1 @le 1] 10 | [assert: 1 @le 2] 11 | [assert-not: 2 @le 1] 12 | 13 | [assert: 2 @gt 1] 14 | [assert-not: 1 @gt 2] 15 | 16 | [assert: 1 @ge 1] 17 | [assert: 2 @ge 1] 18 | [assert-not: 1 @ge 2] -------------------------------------------------------------------------------- /tests/sources/ops/math.rant: -------------------------------------------------------------------------------- 1 | [assert-eq: 1 + 1; 2] 2 | [assert-eq: 2 + 2 - 1; 3] 3 | [assert-eq: 1 + 2 * 3 - 2; 5] 4 | [assert-eq: 1 + 2 * 3 ** 2 - 2; 17] 5 | [assert-eq: 10 % 4; 2] 6 | [assert-eq: @neg 3 * 4 * -4; 48] 7 | [assert-eq: @neg 3 * -4 * @neg 4; -48] -------------------------------------------------------------------------------- /tests/sources/ops/not.rant: -------------------------------------------------------------------------------- 1 | [assert-eq: @not @true; @false] 2 | [assert-eq: @not @false & @not @false; @true] 3 | [assert-eq: @not @false; @true] 4 | [assert-eq: @not 0; @true] 5 | [assert-eq: @not 1; @false] -------------------------------------------------------------------------------- /tests/sources/ops/or.rant: -------------------------------------------------------------------------------- 1 | [assert-eq: @true | @true; @true] 2 | [assert-eq: @false | @true; @true] 3 | [assert-eq: @true | @false; @true] 4 | [assert-eq: @false | @false; @false] 5 | 6 | [assert-eq: 1 | 2; 1] 7 | [assert-eq: 0 | 2; 2] 8 | [assert-eq: 1 | 0; 1] 9 | [assert-eq: "" | 0; 0] -------------------------------------------------------------------------------- /tests/sources/ops/or_short_circuit.rant: -------------------------------------------------------------------------------- 1 | # Setup 2 | <$lhs-reads = 0; $rhs-reads = 0; $lhs; $rhs> 3 | 4 | [%get-lhs] { 5 | + 1> 6 | 7 | } 8 | 9 | [%get-rhs] { 10 | + 1> 11 | 12 | } 13 | 14 | # Test @true | @false 15 | 16 | [tap: [get-lhs] | [get-rhs]] 17 | [assert-eq: ; 1] 18 | [assert-eq: ; 0] 19 | 20 | # Reset stats 21 | 22 | 23 | # Test @false | @true 24 | 25 | [tap: [get-lhs] | [get-rhs]] 26 | [assert-eq: ; 1] 27 | [assert-eq: ; 1] -------------------------------------------------------------------------------- /tests/sources/ops/xor.rant: -------------------------------------------------------------------------------- 1 | [assert: @true ^ @false] 2 | [assert: @false ^ @true] 3 | [assert-not: @true ^ @true] 4 | [assert-not: @false ^ @false] 5 | 6 | [assert: 1 ^ 0] 7 | [assert: 0 ^ 1] 8 | [assert-not: 1 ^ 1] 9 | [assert-not: 0 ^ 0] -------------------------------------------------------------------------------- /tests/sources/range/range_forward.rant: -------------------------------------------------------------------------------- 1 | <%r = [range: 0; 5]> 2 | [assert-eq: [len: ]; 5; "range length is wrong"] 3 | [assert-eq: , , , , ; "0, 1, 2, 3, 4"; "range values are wrong"] -------------------------------------------------------------------------------- /tests/sources/range/range_forward_step_divisible.rant: -------------------------------------------------------------------------------- 1 | <%r = [range: 0; 6; 2]> 2 | [assert-eq: [len: ]; 3; "range length is wrong"] 3 | [assert-eq: , , ; "0, 2, 4"; "range values are wrong"] -------------------------------------------------------------------------------- /tests/sources/range/range_forward_step_indivisible.rant: -------------------------------------------------------------------------------- 1 | <%r = [range: 0; 10; 3]> 2 | [assert-eq: [len: ]; 4; "range length is wrong"] 3 | [assert-eq: , , , ; "0, 3, 6, 9"; "range values are wrong"] -------------------------------------------------------------------------------- /tests/sources/range/range_reverse.rant: -------------------------------------------------------------------------------- 1 | <%r = [range: 5; 0]> 2 | [assert-eq: [len: ]; 5; "range length is wrong"] 3 | [assert-eq: , , , , ; "5, 4, 3, 2, 1"; "range values are wrong"] -------------------------------------------------------------------------------- /tests/sources/range/range_reverse_step_divisible.rant: -------------------------------------------------------------------------------- 1 | <%r = [range: 6; 0; 2]> 2 | [assert-eq: [len: ]; 3; "range length is wrong"] 3 | [assert-eq: , , ; "6, 4, 2"; "range values are wrong"] -------------------------------------------------------------------------------- /tests/sources/range/range_reverse_step_indivisible.rant: -------------------------------------------------------------------------------- 1 | <%r = [range: 10; 0; 3]> 2 | [assert-eq: [len: ]; 4; "range length is wrong"] 3 | [assert-eq: , , , ; "10, 7, 4, 1"; "range values are wrong"] -------------------------------------------------------------------------------- /tests/sources/slice/list/between_dynamic.rant: -------------------------------------------------------------------------------- 1 | <$a = (: a; b; c; d; e; f; g; h)> 2 | 3 | [assert-eq: <$i = 0; a/()..4>; (: a; b; c; d)] 4 | 5 | [assert-eq: <$i = 4; a/0..()>; (: a; b; c; d)] 6 | 7 | [assert-eq: <$i = 4; $j = 8; a/()..()>; (: e; f; g; h)] -------------------------------------------------------------------------------- /tests/sources/slice/list/between_static.rant: -------------------------------------------------------------------------------- 1 | <$a = (: a; b; c; d; e; f; g; h)> 2 | 3 | [assert-eq: ; (: a; b; c; d; e; f; g; h)] 4 | [assert-eq: ; (: a; b; c; d; e; f; g; h)] 5 | 6 | [assert-eq: ; (: a; b; c; d)] 7 | [assert-eq: ; (: a; b; c; d)] 8 | 9 | [assert-eq: ; (: e; f; g; h)] 10 | [assert-eq: ; (: e; f; g; h)] 11 | 12 | [assert-eq: ; (: b; c; d; e; f; g)] 13 | [assert-eq: ; (: b; c; d; e; f; g)] 14 | 15 | [assert-eq: ; (: e; f; g)] 16 | [assert-eq: ; (: e; f; g)] 17 | 18 | [assert-eq: ; (:)] -------------------------------------------------------------------------------- /tests/sources/slice/list/from_dynamic.rant: -------------------------------------------------------------------------------- 1 | <$a = (: a; b; c; d; e; f; g; h)> 2 | 3 | [assert-eq: <$i = 0; a/()..>; (: a; b; c; d; e; f; g; h)] 4 | [assert-eq: <$i = 0; a/ () .. >; (: a; b; c; d; e; f; g; h)] 5 | [assert-eq: <$i = -8; a/()..>; (: a; b; c; d; e; f; g; h)] 6 | [assert-eq: <$i = -8; a/ () .. >; (: a; b; c; d; e; f; g; h)] 7 | 8 | [assert-eq: <$i = 1; a/()..>; (: b; c; d; e; f; g; h)] 9 | [assert-eq: <$i = 1; a/ () .. >; (: b; c; d; e; f; g; h)] 10 | 11 | [assert-eq: <$i = -7; a/()..>; (: b; c; d; e; f; g; h)] 12 | [assert-eq: <$i = -7; a/ () .. >; (: b; c; d; e; f; g; h)] 13 | 14 | [assert-eq: <$i = -1; a/()..>; (: h)] 15 | [assert-eq: <$i = -1; a/ () .. >; (: h)] -------------------------------------------------------------------------------- /tests/sources/slice/list/from_static.rant: -------------------------------------------------------------------------------- 1 | <$a = (: a; b; c; d; e; f; g; h)> 2 | 3 | [assert-eq: ; (: a; b; c; d; e; f; g; h)] 4 | [assert-eq: ; (: a; b; c; d; e; f; g; h)] 5 | [assert-eq: ; (: a; b; c; d; e; f; g; h)] 6 | [assert-eq: ; (: a; b; c; d; e; f; g; h)] 7 | 8 | [assert-eq: ; (: b; c; d; e; f; g; h)] 9 | [assert-eq: ; (: b; c; d; e; f; g; h)] 10 | 11 | [assert-eq: ; (: b; c; d; e; f; g; h)] 12 | [assert-eq: ; (: b; c; d; e; f; g; h)] 13 | 14 | [assert-eq: ; (: h)] 15 | [assert-eq: ; (: h)] -------------------------------------------------------------------------------- /tests/sources/slice/list/full.rant: -------------------------------------------------------------------------------- 1 | <$a = (: a; b; c; d; e; f; g; h)> 2 | [assert-eq: ; (: a; b; c; d; e; f; g; h)] 3 | [assert-eq: ; (: a; b; c; d; e; f; g; h)] -------------------------------------------------------------------------------- /tests/sources/slice/list/to_dynamic.rant: -------------------------------------------------------------------------------- 1 | <$a = (: a; b; c; d; e; f; g; h)> 2 | 3 | [assert-eq: <$i = 8; a/..()>; (: a; b; c; d; e; f; g; h)] 4 | [assert-eq: <$i = 8; a/ .. () >; (: a; b; c; d; e; f; g; h)] 5 | 6 | [assert-eq: <$i = 1; a/..()>; (: a)] 7 | [assert-eq: <$i = 1; a/ .. () >; (: a)] 8 | 9 | [assert-eq: <$i = 4; a/..()>; (: a; b; c; d)] 10 | [assert-eq: <$i = 4; a/ .. () >; (: a; b; c; d)] 11 | 12 | [assert-eq: <$i = -1; a/..()>; (: a; b; c; d; e; f; g)] 13 | [assert-eq: <$i = -1; a/ .. () >; (: a; b; c; d; e; f; g)] 14 | -------------------------------------------------------------------------------- /tests/sources/slice/list/to_static.rant: -------------------------------------------------------------------------------- 1 | <$a = (: a; b; c; d; e; f; g; h)> 2 | 3 | [assert-eq: ; (: a; b; c; d; e; f; g; h)] 4 | [assert-eq: ; (: a; b; c; d; e; f; g; h)] 5 | 6 | [assert-eq: ; (: a)] 7 | [assert-eq: ; (: a)] 8 | 9 | [assert-eq: ; (: a; b; c; d)] 10 | [assert-eq: ; (: a; b; c; d)] 11 | 12 | [assert-eq: ; (: a; b; c; d; e; f; g)] 13 | [assert-eq: ; (: a; b; c; d; e; f; g)] 14 | -------------------------------------------------------------------------------- /tests/sources/slice/range/between_dynamic.rant: -------------------------------------------------------------------------------- 1 | <$a = [irange: 1; 8]> 2 | 3 | [assert-eq: [to-list: ]; (: 1; 2; 3; 4)] 4 | [assert-eq: [to-list:]; (: 1; 2; 3; 4)] 5 | [assert-eq: [to-list:]; (: 5; 6; 7; 8)] -------------------------------------------------------------------------------- /tests/sources/slice/range/between_static.rant: -------------------------------------------------------------------------------- 1 | <$a = [irange: 1; 8]> 2 | 3 | [assert-eq: [to-list:]; (: 1; 2; 3; 4; 5; 6; 7; 8)] 4 | [assert-eq: [to-list:]; (: 1; 2; 3; 4; 5; 6; 7; 8)] 5 | 6 | [assert-eq: [to-list:]; (: 1; 2; 3; 4)] 7 | [assert-eq: [to-list:]; (: 1; 2; 3; 4)] 8 | 9 | [assert-eq: [to-list:]; (: 5; 6; 7; 8)] 10 | [assert-eq: [to-list:]; (: 5; 6; 7; 8)] 11 | 12 | [assert-eq: [to-list:]; (: 2; 3; 4; 5; 6; 7)] 13 | [assert-eq: [to-list:]; (: 2; 3; 4; 5; 6; 7)] 14 | 15 | [assert-eq: [to-list:]; (: 5; 6; 7)] 16 | [assert-eq: [to-list:]; (: 5; 6; 7)] 17 | 18 | [assert-eq: [to-list:]; (:)] -------------------------------------------------------------------------------- /tests/sources/slice/range/from_dynamic.rant: -------------------------------------------------------------------------------- 1 | <$a = [irange: 1; 8]> 2 | 3 | [assert-eq: [to-list:]; (: 1; 2; 3; 4; 5; 6; 7; 8)] 4 | [assert-eq: [to-list:]; (: 1; 2; 3; 4; 5; 6; 7; 8)] 5 | [assert-eq: [to-list:]; (: 2; 3; 4; 5; 6; 7; 8)] 6 | [assert-eq: [to-list:]; (: 2; 3; 4; 5; 6; 7; 8)] 7 | [assert-eq: [to-list:]; (: 8)] -------------------------------------------------------------------------------- /tests/sources/slice/range/from_static.rant: -------------------------------------------------------------------------------- 1 | <$a = [irange: 1; 8]> 2 | 3 | [assert-eq: [to-list:]; (: 1; 2; 3; 4; 5; 6; 7; 8)] 4 | [assert-eq: [to-list:]; (: 1; 2; 3; 4; 5; 6; 7; 8)] 5 | [assert-eq: [to-list:]; (: 2; 3; 4; 5; 6; 7; 8)] 6 | [assert-eq: [to-list:]; (: 2; 3; 4; 5; 6; 7; 8)] 7 | [assert-eq: [to-list:]; (: 8)] -------------------------------------------------------------------------------- /tests/sources/slice/range/full.rant: -------------------------------------------------------------------------------- 1 | <$a = [irange: 1; 8]> 2 | 3 | [assert-eq: [to-list:]; (: 1; 2; 3; 4; 5; 6; 7; 8)] -------------------------------------------------------------------------------- /tests/sources/slice/range/to_dynamic.rant: -------------------------------------------------------------------------------- 1 | <$a = [irange: 1; 8]> 2 | 3 | [assert-eq: [to-list:]; (: 1; 2; 3; 4; 5; 6; 7; 8)] 4 | [assert-eq: [to-list:]; (: 1)] 5 | [assert-eq: [to-list:]; (: 1; 2; 3; 4)] 6 | [assert-eq: [to-list:]; (: 1; 2; 3; 4; 5; 6; 7)] 7 | -------------------------------------------------------------------------------- /tests/sources/slice/range/to_static.rant: -------------------------------------------------------------------------------- 1 | <$a = [irange: 1; 8]> 2 | 3 | [assert-eq: [to-list:]; (: 1; 2; 3; 4; 5; 6; 7; 8)] 4 | [assert-eq: [to-list:]; (: 1)] 5 | [assert-eq: [to-list:]; (: 1; 2; 3; 4)] 6 | [assert-eq: [to-list:]; (: 1; 2; 3; 4; 5; 6; 7)] -------------------------------------------------------------------------------- /tests/sources/slice/string/between_dynamic.rant: -------------------------------------------------------------------------------- 1 | <$a = ABCDEFGH> 2 | 3 | [assert-eq: ; ABCD] 4 | [assert-eq: ; ABCD] 5 | [assert-eq: ; EFGH] -------------------------------------------------------------------------------- /tests/sources/slice/string/between_static.rant: -------------------------------------------------------------------------------- 1 | <$a = ABCDEFGH> 2 | 3 | [assert-eq: ; ABCDEFGH] 4 | [assert-eq: ; ABCDEFGH] 5 | 6 | [assert-eq: ; ABCD] 7 | [assert-eq: ; ABCD] 8 | 9 | [assert-eq: ; EFGH] 10 | [assert-eq: ; EFGH] 11 | 12 | [assert-eq: ; BCDEFG] 13 | [assert-eq: ; BCDEFG] 14 | 15 | [assert-eq: ; EFG] 16 | [assert-eq: ; EFG] 17 | 18 | [assert-eq: ; ""] -------------------------------------------------------------------------------- /tests/sources/slice/string/from_dynamic.rant: -------------------------------------------------------------------------------- 1 | <$a = ABCDEFGH> 2 | 3 | [assert-eq: ; ABCDEFGH] 4 | [assert-eq: ; ABCDEFGH] 5 | [assert-eq: ; BCDEFGH] 6 | [assert-eq: ; BCDEFGH] 7 | [assert-eq: ; H] -------------------------------------------------------------------------------- /tests/sources/slice/string/from_static.rant: -------------------------------------------------------------------------------- 1 | <$a = ABCDEFGH> 2 | 3 | [assert-eq: ; ABCDEFGH] 4 | [assert-eq: ; ABCDEFGH] 5 | [assert-eq: ; BCDEFGH] 6 | [assert-eq: ; BCDEFGH] 7 | [assert-eq: ; H] -------------------------------------------------------------------------------- /tests/sources/slice/string/full.rant: -------------------------------------------------------------------------------- 1 | <$a = ABCDEFGH> 2 | 3 | [assert-eq: ; ABCDEFGH] -------------------------------------------------------------------------------- /tests/sources/slice/string/to_dynamic.rant: -------------------------------------------------------------------------------- 1 | <$a = ABCDEFGH> 2 | 3 | [assert-eq: ; ABCDEFGH] 4 | [assert-eq: ; A] 5 | [assert-eq: ; ABCD] 6 | [assert-eq: ; ABCDEFG] 7 | -------------------------------------------------------------------------------- /tests/sources/slice/string/to_static.rant: -------------------------------------------------------------------------------- 1 | <$a = ABCDEFGH> 2 | 3 | [assert-eq: ; ABCDEFGH] 4 | [assert-eq: ; A] 5 | [assert-eq: ; ABCD] 6 | [assert-eq: ; ABCDEFG] -------------------------------------------------------------------------------- /tests/sources/slice/tuple/between_dynamic.rant: -------------------------------------------------------------------------------- 1 | <$a = (: a; b; c; d; e; f; g; h)> 2 | 3 | [assert-eq: <$i = 0; a/()..4>; (: a; b; c; d)] 4 | 5 | [assert-eq: <$i = 4; a/0..()>; (: a; b; c; d)] 6 | 7 | [assert-eq: <$i = 4; $j = 8; a/()..()>; (: e; f; g; h)] -------------------------------------------------------------------------------- /tests/sources/slice/tuple/between_static.rant: -------------------------------------------------------------------------------- 1 | <$a = (a; b; c; d; e; f; g; h)> 2 | 3 | [assert-eq: ; (a; b; c; d; e; f; g; h)] 4 | [assert-eq: ; (a; b; c; d; e; f; g; h)] 5 | 6 | [assert-eq: ; (a; b; c; d)] 7 | [assert-eq: ; (a; b; c; d)] 8 | 9 | [assert-eq: ; (e; f; g; h)] 10 | [assert-eq: ; (e; f; g; h)] 11 | 12 | [assert-eq: ; (b; c; d; e; f; g)] 13 | [assert-eq: ; (b; c; d; e; f; g)] 14 | 15 | [assert-eq: ; (e; f; g)] 16 | [assert-eq: ; (e; f; g)] 17 | 18 | [assert-eq: ; ()] -------------------------------------------------------------------------------- /tests/sources/slice/tuple/from_dynamic.rant: -------------------------------------------------------------------------------- 1 | <$a = (a; b; c; d; e; f; g; h)> 2 | 3 | [assert-eq: <$i = 0; a/()..>; (a; b; c; d; e; f; g; h)] 4 | [assert-eq: <$i = 0; a/ () .. >; (a; b; c; d; e; f; g; h)] 5 | [assert-eq: <$i = -8; a/()..>; (a; b; c; d; e; f; g; h)] 6 | [assert-eq: <$i = -8; a/ () .. >; (a; b; c; d; e; f; g; h)] 7 | 8 | [assert-eq: <$i = 1; a/()..>; (b; c; d; e; f; g; h)] 9 | [assert-eq: <$i = 1; a/ () .. >; (b; c; d; e; f; g; h)] 10 | 11 | [assert-eq: <$i = -7; a/()..>; (b; c; d; e; f; g; h)] 12 | [assert-eq: <$i = -7; a/ () .. >; (b; c; d; e; f; g; h)] 13 | 14 | [assert-eq: <$i = -1; a/()..>; (h;)] 15 | [assert-eq: <$i = -1; a/ () .. >; (h;)] -------------------------------------------------------------------------------- /tests/sources/slice/tuple/from_static.rant: -------------------------------------------------------------------------------- 1 | <$a = (a; b; c; d; e; f; g; h)> 2 | 3 | [assert-eq: ; (a; b; c; d; e; f; g; h)] 4 | [assert-eq: ; (a; b; c; d; e; f; g; h)] 5 | [assert-eq: ; (a; b; c; d; e; f; g; h)] 6 | [assert-eq: ; (a; b; c; d; e; f; g; h)] 7 | 8 | [assert-eq: ; (b; c; d; e; f; g; h)] 9 | [assert-eq: ; (b; c; d; e; f; g; h)] 10 | 11 | [assert-eq: ; (b; c; d; e; f; g; h)] 12 | [assert-eq: ; (b; c; d; e; f; g; h)] 13 | 14 | [assert-eq: ; (h;)] 15 | [assert-eq: ; (h;)] -------------------------------------------------------------------------------- /tests/sources/slice/tuple/full.rant: -------------------------------------------------------------------------------- 1 | <$a = (a; b; c; d; e; f; g; h)> 2 | [assert-eq: ; (a; b; c; d; e; f; g; h)] 3 | [assert-eq: ; (a; b; c; d; e; f; g; h)] -------------------------------------------------------------------------------- /tests/sources/slice/tuple/to_dynamic.rant: -------------------------------------------------------------------------------- 1 | <$a = (a; b; c; d; e; f; g; h)> 2 | 3 | [assert-eq: <$i = 8; a/..()>; (a; b; c; d; e; f; g; h)] 4 | [assert-eq: <$i = 8; a/ .. () >; (a; b; c; d; e; f; g; h)] 5 | 6 | [assert-eq: <$i = 1; a/..()>; (a;)] 7 | [assert-eq: <$i = 1; a/ .. () >; (a;)] 8 | 9 | [assert-eq: <$i = 4; a/..()>; (a; b; c; d)] 10 | [assert-eq: <$i = 4; a/ .. () >; (a; b; c; d)] 11 | 12 | [assert-eq: <$i = -1; a/..()>; (a; b; c; d; e; f; g)] 13 | [assert-eq: <$i = -1; a/ .. () >; (a; b; c; d; e; f; g)] 14 | -------------------------------------------------------------------------------- /tests/sources/slice/tuple/to_static.rant: -------------------------------------------------------------------------------- 1 | <$a = (a; b; c; d; e; f; g; h)> 2 | 3 | [assert-eq: ; (a; b; c; d; e; f; g; h)] 4 | [assert-eq: ; (a; b; c; d; e; f; g; h)] 5 | 6 | [assert-eq: ; (a;)] 7 | [assert-eq: ; (a;)] 8 | 9 | [assert-eq: ; (a; b; c; d)] 10 | [assert-eq: ; (a; b; c; d)] 11 | 12 | [assert-eq: ; (a; b; c; d; e; f; g)] 13 | [assert-eq: ; (a; b; c; d; e; f; g)] 14 | -------------------------------------------------------------------------------- /tests/sources/splice/dynamic_from_list.rant: -------------------------------------------------------------------------------- 1 | <$a = (:a; b; c; d; e; f; g; h)> 2 | 3 | <$i = 2; a/..() = (:1; 2; 3)> 4 | [assert-eq: ; (:1; 2; 3; c; d; e; f; g; h)] 5 | 6 | <$i = 7; a/().. = (:4; 5; 6)> 7 | [assert-eq: ; (:1; 2; 3; c; d; e; f; 4; 5; 6)] 8 | 9 | <$i = 3; $j = 7; a/()..() = (:foo;)> 10 | [assert-eq: ; (:1; 2; 3; foo; 4; 5; 6)] 11 | 12 | <$i = 3; a/()..4 = (:bar)> 13 | [assert-eq: ; (:1; 2; 3; bar; 4; 5; 6)] 14 | 15 | <$i = 4; a/3..() = (:baz)> 16 | [assert-eq: ; (:1; 2; 3; baz; 4; 5; 6)] -------------------------------------------------------------------------------- /tests/sources/splice/dynamic_from_tuple.rant: -------------------------------------------------------------------------------- 1 | <$a = (:a; b; c; d; e; f; g; h)> 2 | 3 | <$i = 2; a/..() = (1; 2; 3)> 4 | [assert-eq: ; (:1; 2; 3; c; d; e; f; g; h)] 5 | 6 | <$i = 7; a/().. = (4; 5; 6)> 7 | [assert-eq: ; (:1; 2; 3; c; d; e; f; 4; 5; 6)] 8 | 9 | <$i = 3; $j = 7; a/()..() = (foo;)> 10 | [assert-eq: ; (:1; 2; 3; foo; 4; 5; 6)] 11 | 12 | <$i = 3; a/()..4 = (bar;)> 13 | [assert-eq: ; (:1; 2; 3; bar; 4; 5; 6)] 14 | 15 | <$i = 4; a/3..() = (baz;)> 16 | [assert-eq: ; (:1; 2; 3; baz; 4; 5; 6)] -------------------------------------------------------------------------------- /tests/sources/splice/static_from_list.rant: -------------------------------------------------------------------------------- 1 | <$a = (:a; b; c; d; e; f; g; h)> 2 | 3 | 4 | [assert-eq: ; (:1; 2; 3; c; d; e; f; g; h)] 5 | 6 | 7 | [assert-eq: ; (:1; 2; 3; c; d; e; f; 4; 5; 6)] 8 | 9 | 10 | [assert-eq: ; (:1; 2; 3; foo; 4; 5; 6)] 11 | 12 | 13 | [assert-eq: ; (:foo; bar)] -------------------------------------------------------------------------------- /tests/sources/splice/static_from_tuple.rant: -------------------------------------------------------------------------------- 1 | <$a = (:a; b; c; d; e; f; g; h)> 2 | 3 | 4 | [assert-eq: ; (:1; 2; 3; c; d; e; f; g; h)] 5 | 6 | 7 | [assert-eq: ; (:1; 2; 3; c; d; e; f; 4; 5; 6)] 8 | 9 | 10 | [assert-eq: ; (:1; 2; 3; foo; 4; 5; 6)] 11 | 12 | 13 | [assert-eq: ; (:foo; bar)] -------------------------------------------------------------------------------- /tests/sources/spread/spread_all.rant: -------------------------------------------------------------------------------- 1 | <$args = (foo; bar; baz; qux)> 2 | 3 | [$abcd: a; b; c; d] { 4 | 5 | } 6 | 7 | [abcd: * |> assert-eq: "foobarbazqux"] -------------------------------------------------------------------------------- /tests/sources/spread/spread_inner.rant: -------------------------------------------------------------------------------- 1 | <$args = (bar; baz)> 2 | 3 | [$abcd: a; b; c; d] { 4 | 5 | } 6 | 7 | [abcd: foo; *; qux |> assert-eq: "foobarbazqux"] -------------------------------------------------------------------------------- /tests/sources/spread/spread_left.rant: -------------------------------------------------------------------------------- 1 | <$args = (foo; bar; baz)> 2 | 3 | [$abcd: a; b; c; d] { 4 | 5 | } 6 | 7 | [abcd: *; qux |> assert-eq: "foobarbazqux"] -------------------------------------------------------------------------------- /tests/sources/spread/spread_multi.rant: -------------------------------------------------------------------------------- 1 | <$args-a = (foo; bar); $args-b = (baz; qux)> 2 | 3 | [$abcd: a; b; c; d] { 4 | 5 | } 6 | 7 | [abcd: *; * |> assert-eq: "foobarbazqux"] -------------------------------------------------------------------------------- /tests/sources/spread/spread_right.rant: -------------------------------------------------------------------------------- 1 | <$args = (bar; baz; qux)> 2 | 3 | [$abcd: a; b; c; d] { 4 | 5 | } 6 | 7 | [abcd: foo; * |> assert-eq: "foobarbazqux"] -------------------------------------------------------------------------------- /tests/sources/spread/spread_variadic_plus.rant: -------------------------------------------------------------------------------- 1 | <$args = (foo; bar; baz; qux)> 2 | 3 | [$concat: a+] { 4 | [join:;\s] 5 | } 6 | 7 | [concat: * |> assert-eq: "foo bar baz qux"] -------------------------------------------------------------------------------- /tests/sources/spread/spread_variadic_star.rant: -------------------------------------------------------------------------------- 1 | <$args = (foo; bar; baz; qux)> 2 | 3 | [$concat: a*] { 4 | [join:;\s] 5 | } 6 | 7 | [concat: * |> assert-eq: "foo bar baz qux"] -------------------------------------------------------------------------------- /tests/sources/temporal/temporal_one.rant: -------------------------------------------------------------------------------- 1 | [%println: cols*] { 2 | [join: ; \t]\n 3 | } 4 | 5 | [assert-eq: foo\nbar\n; [println: **(foo; bar)]] -------------------------------------------------------------------------------- /tests/sources/temporal/temporal_one_mixed.rant: -------------------------------------------------------------------------------- 1 | [assert-eq: "foo...bar..."; [cat: **(foo; bar); "..."]] -------------------------------------------------------------------------------- /tests/sources/temporal/temporal_pipe_temporal.rant: -------------------------------------------------------------------------------- 1 | <%test = [cat: **(a; b) |> cat: **(c; d)]> 2 | 3 | [assert-eq: ; "acadbcbd"] -------------------------------------------------------------------------------- /tests/sources/temporal/temporal_two_diffsize.rant: -------------------------------------------------------------------------------- 1 | [assert-eq: "foobazbarbazboobazfooquxbarquxbooqux"; [cat: **(foo; bar; boo); **(baz; qux)]] -------------------------------------------------------------------------------- /tests/sources/temporal/temporal_two_diffsize_mixed.rant: -------------------------------------------------------------------------------- 1 | [assert-eq: "foobaz...barbaz...boobaz...fooqux...barqux...booqux..."; [cat: **(foo; bar; boo); **(baz; qux); "..."]] -------------------------------------------------------------------------------- /tests/sources/temporal/temporal_two_samesize.rant: -------------------------------------------------------------------------------- 1 | [assert-eq: "foobazbarbazfooquxbarqux"; [cat: **(foo; bar); **(baz; qux)]] -------------------------------------------------------------------------------- /tests/sources/temporal/temporal_two_samesize_mixed.rant: -------------------------------------------------------------------------------- 1 | [assert-eq: "foobaz...barbaz...fooqux...barqux..."; [cat: **(foo; bar); **(baz; qux); "..."]] -------------------------------------------------------------------------------- /tests/sources/temporal/temporal_two_samesize_sync.rant: -------------------------------------------------------------------------------- 1 | [assert-eq: foobarbazqux; [cat: *a*(foo; baz); *a*(bar; qux)]] --------------------------------------------------------------------------------