├── .gitignore ├── rustfmt.toml ├── doc └── console.png ├── src ├── prelude.rs ├── builtin_parser │ ├── runner │ │ ├── stdlib │ │ │ └── math.rs │ │ ├── stdlib.rs │ │ ├── reflection.rs │ │ ├── unique_rc.rs │ │ ├── error.rs │ │ ├── member.rs │ │ ├── environment.rs │ │ └── value.rs │ ├── completions.rs │ ├── lexer.rs │ ├── number.rs │ ├── runner.rs │ └── parser.rs ├── lib.rs ├── logging.rs ├── builtin_parser.rs ├── config.rs ├── ui │ └── completions.rs ├── command.rs └── ui.rs ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── pull_request_template.md └── workflows │ └── ci.yml ├── examples ├── basic.rs ├── resource.rs └── custom_functions.rs ├── LICENSE-MIT ├── Cargo.toml ├── README.md └── LICENSE-APACHE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.cargo 3 | Cargo.lock -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | imports_granularity = "Module" 2 | -------------------------------------------------------------------------------- /doc/console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doonv/bevy_dev_console/HEAD/doc/console.png -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | //! `use bevy_dev_console::prelude::*` to quickly import the required plugins for [`bevy_dev_console`](crate). 2 | 3 | pub use crate::config::ConsoleConfig; 4 | pub use crate::logging::custom_log_layer; 5 | pub use crate::DevConsolePlugin; 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Propose a new feature! 4 | title: '' 5 | labels: Enchancement, Needs Labels 6 | assignees: '' 7 | --- 8 | 9 | ## What problem does this solve or what need does it fill? 10 | 11 | A description of why this particular feature should be added. 12 | 13 | ## What solution would you like? 14 | 15 | The solution you propose for the problem presented. 16 | 17 | ## What alternative(s) have you considered? 18 | 19 | Other solutions to solve and/or work around the problem presented. 20 | 21 | ## Additional context 22 | 23 | Any other information you would like to add such as related previous work, 24 | screenshots, benchmarks, etc. 25 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Objective 2 | 3 | - Describe the objective or issue this PR addresses. 4 | - If you're fixing a specific issue, say "Fixes #X". 5 | 6 | ## Solution 7 | 8 | - Describe the solution used to achieve the objective above. 9 | 10 | --- 11 | 12 | ## Changelog 13 | 14 | > This section is optional. If this was a trivial fix, or has no externally-visible impact, you can delete this section. 15 | 16 | - What changed as a result of this PR? 17 | - If applicable, organize changes under "Added", "Changed", or "Fixed" sub-headings 18 | - Stick to one or two sentences. If more detail is needed for a particular change, consider adding it to the "Solution" section 19 | - If you can't summarize the work, your change may be unreasonably large / unrelated. Consider splitting your PR to make it easier to review and merge! 20 | -------------------------------------------------------------------------------- /examples/basic.rs: -------------------------------------------------------------------------------- 1 | //! A simple example showing how to setup the developer console plugin. 2 | 3 | use bevy::log::LogPlugin; 4 | use bevy::prelude::*; 5 | use bevy_dev_console::prelude::*; 6 | 7 | fn main() { 8 | App::new() 9 | .add_plugins(( 10 | // Add the log plugin with the custom log layer 11 | DefaultPlugins.set(LogPlugin { 12 | custom_layer: custom_log_layer, 13 | // Add a filter to the log plugin that shows all log levels from this example 14 | filter: format!("wgpu=error,naga=warn,{}=trace", module_path!()), 15 | ..default() 16 | }), 17 | // Add the dev console plugin itself. 18 | DevConsolePlugin, 19 | )) 20 | .add_systems(Startup, test) 21 | .run(); 22 | } 23 | 24 | fn test() { 25 | trace!("tracing"); 26 | debug!("solving issues..."); 27 | info!("hello :)"); 28 | warn!("spooky warning"); 29 | error!("scary error"); 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | name = "bevy_dev_console" 4 | version = "0.1.0" 5 | edition = "2021" 6 | exclude = ["assets/", "docs/", ".github/"] 7 | keywords = ["bevy", "console", "development", "source"] 8 | license = "MIT OR Apache-2.0" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | [features] 12 | default = ["builtin-parser", "completions", "builtin-parser-completions"] 13 | 14 | completions = [] 15 | 16 | builtin-parser-completions = [ 17 | "completions", 18 | "builtin-parser", 19 | "dep:fuzzy-matcher", 20 | ] 21 | 22 | builtin-parser = ["dep:logos"] 23 | 24 | [dependencies] 25 | # This crate by itself doesn't use any bevy features, but `bevy_egui` (dep) uses "bevy_asset". 26 | bevy = { version = "0.14.0", default-features = false, features = [] } 27 | bevy_egui = "0.28.0" 28 | chrono = "0.4.31" 29 | tracing-log = "0.2.0" 30 | tracing-subscriber = "0.3.18" 31 | web-time = "1.0.0" 32 | 33 | # builtin-parser features 34 | logos = { version = "0.14.0", optional = true } 35 | fuzzy-matcher = { version = "0.3.7", optional = true } 36 | 37 | [dev-dependencies] 38 | bevy = "0.14.0" 39 | 40 | [lints] 41 | clippy.useless_format = "allow" 42 | rust.missing_docs = "warn" 43 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report a bug to help us improve! 4 | title: '' 5 | labels: Bug, Needs Labels 6 | assignees: '' 7 | --- 8 | 9 | ## bevy_dev_console version 10 | 11 | The release number or commit hash of the version you're using. 12 | 13 | ## \[Optional\] Relevant system information 14 | 15 | If you cannot get `bevy_dev_console` to build or run on your machine, please include: 16 | 17 | - the Rust version you're using (you can get this by running `cargo --version`) 18 | - Bevy (and by extension `bevy_dev_console`) relies on the "latest stable release" of Rust 19 | - nightly should generally work, but there are sometimes regressions: please let us know! 20 | - the operating system or browser used, including its version 21 | - e.g. Windows 10, Ubuntu 18.04, iOS 14 22 | 23 | ## What you did 24 | 25 | Describe how you arrived at the problem. If you can, consider providing a code snippet or link. 26 | 27 | ## What went wrong 28 | 29 | If it's not clear, break this out into: 30 | 31 | - what were you expecting? 32 | - what actually happened? 33 | 34 | ## Additional information 35 | 36 | Other information that can be used to further reproduce or isolate the problem. 37 | This commonly includes: 38 | 39 | - screenshots 40 | - logs 41 | - theories about what might be going wrong 42 | - workarounds that you used 43 | - links to related bugs, PRs or discussions 44 | -------------------------------------------------------------------------------- /src/builtin_parser/runner/stdlib/math.rs: -------------------------------------------------------------------------------- 1 | use crate::builtin_parser::{Environment, EvalError, Number, Spanned}; 2 | use crate::register; 3 | 4 | macro_rules! float_calc_op { 5 | ($fn:ident, $name:expr) => { 6 | fn $fn(number: Spanned) -> Result { 7 | match number.value { 8 | Number::Float(number) => Ok(Number::Float(number.$fn())), 9 | Number::f32(number) => Ok(Number::f32(number.$fn())), 10 | Number::f64(number) => Ok(Number::f64(number.$fn())), 11 | _ => Err(EvalError::Custom { 12 | text: concat!("Cannot calculate the ", $name, " of a non-float value").into(), 13 | span: number.span, 14 | }), 15 | } 16 | } 17 | }; 18 | } 19 | 20 | float_calc_op!(sqrt, "square root"); 21 | float_calc_op!(sin, "sine"); 22 | float_calc_op!(cos, "cosine"); 23 | float_calc_op!(tan, "tangent"); 24 | 25 | float_calc_op!(abs, "absolute value"); 26 | 27 | float_calc_op!(ceil, "rounded-up value"); 28 | float_calc_op!(floor, "rounded-down value"); 29 | float_calc_op!(round, "rounded value"); 30 | float_calc_op!(trunc, "truncuated value"); 31 | 32 | pub fn register(env: &mut Environment) { 33 | register!(env => { 34 | fn sqrt; 35 | fn sin; 36 | fn cos; 37 | fn tan; 38 | 39 | fn abs; 40 | 41 | fn ceil; 42 | fn floor; 43 | fn round; 44 | fn trunc; 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /examples/resource.rs: -------------------------------------------------------------------------------- 1 | //! Example of modifying resources via the console via reflection. 2 | //! 3 | //! **Warning:** This is very experimental, might not work. 4 | 5 | use bevy::log::LogPlugin; 6 | use bevy::prelude::*; 7 | use bevy_dev_console::prelude::*; 8 | 9 | #[derive(Resource, Reflect, Default, Debug)] 10 | enum MyEnum { 11 | #[default] 12 | None, 13 | Numero1, 14 | Structio { 15 | a: f64, 16 | b: String, 17 | }, 18 | Tupleo(String, f64), 19 | } 20 | 21 | #[derive(Resource, Reflect, Default, Debug)] 22 | struct MyStruct { 23 | number: f64, 24 | string: String, 25 | struct_in_struct: SubStruct, 26 | tuple: (i32, u8), 27 | } 28 | 29 | #[derive(Reflect, Default, Debug)] 30 | struct SubStruct { 31 | boolean: bool, 32 | enume: MyEnum, 33 | } 34 | 35 | fn main() { 36 | App::new() 37 | .register_type::() 38 | .init_resource::() 39 | .insert_resource(MyStruct { 40 | number: 5.6, 41 | string: "hi there :)".to_string(), 42 | struct_in_struct: SubStruct { 43 | boolean: false, 44 | enume: MyEnum::Tupleo("nooo".to_string(), 5.), 45 | }, 46 | tuple: (-5, 255), 47 | }) 48 | .register_type::() 49 | .add_plugins(( 50 | DefaultPlugins.set(LogPlugin { 51 | custom_layer: custom_log_layer, 52 | ..default() 53 | }), 54 | DevConsolePlugin, 55 | )) 56 | .run(); 57 | } 58 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | use bevy::prelude::*; 4 | use bevy_egui::EguiPlugin; 5 | use command::CommandHints; 6 | use config::ConsoleConfig; 7 | use ui::ConsoleUiState; 8 | 9 | #[cfg(feature = "builtin-parser")] 10 | pub mod builtin_parser; 11 | pub mod command; 12 | pub mod config; 13 | pub mod logging; 14 | pub mod prelude; 15 | pub mod ui; 16 | 17 | /// Adds a Developer Console to your Bevy application. 18 | /// 19 | /// Requires [custom_log_layer](logging::custom_log_layer). 20 | pub struct DevConsolePlugin; 21 | impl Plugin for DevConsolePlugin { 22 | fn build(&self, app: &mut App) { 23 | if !app.is_plugin_added::() { 24 | app.add_plugins(EguiPlugin); 25 | } 26 | 27 | #[cfg(feature = "builtin-parser")] 28 | { 29 | app.init_non_send_resource::(); 30 | app.init_resource::(); 31 | #[cfg(feature = "builtin-parser-completions")] 32 | app.init_resource::(); 33 | } 34 | #[cfg(feature = "completions")] 35 | app.init_resource::(); 36 | 37 | app.init_resource::() 38 | .init_resource::() 39 | .init_resource::() 40 | .register_type::() 41 | .add_systems( 42 | Update, 43 | ( 44 | ui::read_logs, 45 | ( 46 | ui::open_close_ui, 47 | ui::render_ui_system.run_if(|s: Res| s.open), 48 | ) 49 | .chain(), 50 | ), 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/builtin_parser/completions.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | use super::runner::environment::Variable; 4 | use super::Environment; 5 | 6 | /// Stores the names of variables and functions for fast async access. 7 | #[derive(Resource)] 8 | pub struct EnvironmentCache { 9 | pub function_names: Vec, 10 | pub variable_names: Vec, 11 | } 12 | impl FromWorld for EnvironmentCache { 13 | fn from_world(world: &mut World) -> Self { 14 | if let Some(environment) = world.get_non_send_resource::() { 15 | store_in_cache(environment) 16 | } else { 17 | Self::empty() 18 | } 19 | } 20 | } 21 | impl EnvironmentCache { 22 | pub const fn empty() -> Self { 23 | EnvironmentCache { 24 | function_names: Vec::new(), 25 | variable_names: Vec::new(), 26 | } 27 | } 28 | } 29 | 30 | pub fn store_in_cache(environment: &Environment) -> EnvironmentCache { 31 | let mut function_names = Vec::new(); 32 | let mut variable_names = Vec::new(); 33 | store_in_cache_vec(environment, &mut function_names, &mut variable_names); 34 | 35 | EnvironmentCache { 36 | function_names, 37 | variable_names, 38 | } 39 | } 40 | fn store_in_cache_vec( 41 | environment: &Environment, 42 | function_names: &mut Vec, 43 | variable_names: &mut Vec, 44 | ) { 45 | for (name, variable) in &environment.variables { 46 | match variable { 47 | Variable::Function(_) => function_names.push(name.clone()), 48 | Variable::Unmoved(_) => variable_names.push(name.clone()), 49 | Variable::Moved => {} 50 | } 51 | } 52 | if let Some(environment) = &environment.parent { 53 | store_in_cache_vec(environment, function_names, variable_names); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | env: 11 | CARGO_TERM_COLOR: always 12 | 13 | jobs: 14 | check: 15 | name: Check 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Install Rust 20 | uses: dtolnay/rust-toolchain@stable 21 | - name: Install alsa and udev 22 | run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev 23 | if: runner.os == 'linux' 24 | - name: Run cargo check 25 | run: cargo check 26 | 27 | test: 28 | name: Test 29 | strategy: 30 | matrix: 31 | os: [windows-latest, ubuntu-latest, macos-latest] 32 | runs-on: ${{ matrix.os }} 33 | timeout-minutes: 60 34 | steps: 35 | - uses: actions/checkout@v4 36 | - name: Install Rust 37 | uses: dtolnay/rust-toolchain@stable 38 | - name: Install alsa and udev 39 | run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev 40 | if: runner.os == 'linux' 41 | - name: Run tests 42 | run: cargo test 43 | 44 | lints: 45 | name: Lints 46 | runs-on: ubuntu-latest 47 | steps: 48 | - uses: actions/checkout@v4 49 | - name: Install Rust 50 | uses: dtolnay/rust-toolchain@stable 51 | - name: Install alsa and udev 52 | run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev 53 | - name: Run cargo fmt 54 | run: cargo fmt --all -- --check 55 | - name: Run cargo clippy 56 | # TODO: Mark warnings as errors once all warnings are gone 57 | # run: cargo clippy -- -D warnings 58 | run: cargo clippy 59 | 60 | typos: 61 | runs-on: ubuntu-latest 62 | timeout-minutes: 30 63 | steps: 64 | - uses: actions/checkout@v4 65 | - name: Check for typos 66 | uses: crate-ci/typos@v1.18.2 67 | - name: Typos info 68 | if: failure() 69 | run: | 70 | echo 'To fix typos, please run `typos -w`' 71 | echo 'To check for a diff, run `typos`' 72 | echo 'You can find typos here: https://crates.io/crates/typos' 73 | echo 'if you use VSCode, you can also install `Typos Spell Checker' 74 | echo 'You can find the extension here: https://marketplace.visualstudio.com/items?itemName=tekumara.typos-vscode' 75 | -------------------------------------------------------------------------------- /src/builtin_parser/runner/stdlib.rs: -------------------------------------------------------------------------------- 1 | use crate::builtin_parser::runner::environment::Variable; 2 | use crate::register; 3 | use bevy::ecs::world::World; 4 | use bevy::log::info; 5 | use bevy::reflect::TypeRegistration; 6 | use std::cell::Ref; 7 | use std::ops::Range; 8 | 9 | mod math; 10 | 11 | use super::error::EvalError; 12 | use super::{Environment, Spanned, Value}; 13 | 14 | fn print( 15 | value: Spanned, 16 | world: &mut World, 17 | registrations: &[&TypeRegistration], 18 | ) -> Result<(), EvalError> { 19 | match value.value { 20 | Value::String(string) => info!("{string}"), 21 | _ => { 22 | let string = value.value.try_format(value.span, world, registrations)?; 23 | info!("{string}"); 24 | } 25 | } 26 | Ok(()) 27 | } 28 | 29 | fn dbg(any: Value) { 30 | info!("Value::{any:?}"); 31 | } 32 | 33 | fn ref_depth(Spanned { span, value }: Spanned) -> Result { 34 | fn ref_depth_reference(value: Ref, span: Range) -> Result { 35 | Ok(match &*value { 36 | Value::Reference(reference) => { 37 | ref_depth_reference( 38 | reference 39 | .upgrade() 40 | .ok_or(EvalError::ReferenceToMovedData(span.clone()))? 41 | .borrow(), 42 | span, 43 | )? + 1 44 | } 45 | _ => 0, 46 | }) 47 | } 48 | 49 | Ok(match value { 50 | Value::Reference(reference) => { 51 | ref_depth_reference( 52 | reference 53 | .upgrade() 54 | .ok_or(EvalError::ReferenceToMovedData(span.clone()))? 55 | .borrow(), 56 | span, 57 | )? + 1 58 | } 59 | _ => 0, 60 | }) 61 | } 62 | 63 | fn print_env(env: &mut Environment) { 64 | for (name, variable) in env.iter() { 65 | match variable { 66 | Variable::Moved => info!("{name}: Moved"), 67 | Variable::Unmoved(rc) => info!("{name}: {:?}", rc.borrow_inner().borrow()), 68 | Variable::Function(_) => {} 69 | } 70 | } 71 | } 72 | 73 | fn typeof_value(value: Value) -> String { 74 | value.kind().to_string() 75 | } 76 | 77 | /// Disposes of a [`Value`]. 78 | fn drop(_: Value) {} 79 | 80 | pub fn register(environment: &mut Environment) { 81 | math::register(environment); 82 | 83 | register!(environment => { 84 | fn print; 85 | fn dbg; 86 | fn ref_depth; 87 | fn drop; 88 | fn print_env; 89 | fn typeof_value as "typeof"; 90 | }); 91 | } 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bevy_dev_console 2 | 3 | `bevy_dev_console` is a Source-inspired developer console plugin for the [Bevy Game Engine](https://github.com/bevyengine/bevy). 4 | 5 | ![Image of the developer console](doc/console.png) 6 | 7 | > [!WARNING] 8 | > 9 | > `bevy_dev_console` is currently in its early development stages. Expect breaking changes in the near future (especially when using the built-in command parser). For this reason its only available as a git package at the moment. 10 | 11 | ## Features 12 | 13 | - Log viewing 14 | - View all the hidden data from any log message by hovering over it. 15 | - Powerful Built-in parser language built specifically for `bevy_dev_console`. ([Documentation](https://github.com/doonv/bevy_dev_console/wiki/Built%E2%80%90in-Parser)) 16 | - Calculations 17 | - Variables 18 | - Uses a simplified version of ownership and borrowing 19 | - Standard library (Doesn't have much at the moment) 20 | - [Custom native functions](https://github.com/doonv/bevy_dev_console/blob/master/examples/custom_functions.rs) (`World` access included!) 21 | - [Many types](https://github.com/doonv/bevy_dev_console/wiki/Built%E2%80%90in-Parser#types) 22 | - Resource viewing and modification 23 | - Enums 24 | - Structs 25 | - ~~Entity queries~~ [*Coming Soon...*](https://github.com/doonv/bevy_dev_console/issues/3) (Syntax suggestions would be appreciated!) 26 | - ...and more! 27 | 28 | ## Usage 29 | 30 | 1. Add the `bevy_dev_console` git package. 31 | 32 | ```bash 33 | cargo add --git https://github.com/doonv/bevy_dev_console.git 34 | ``` 35 | 36 | 2. Import the `prelude`. 37 | 38 | ```rust 39 | use bevy_dev_console::prelude::*; 40 | ``` 41 | 42 | 3. Add the plugins. 43 | 44 | ```rust,no_run 45 | use bevy::{prelude::*, log::LogPlugin}; 46 | use bevy_dev_console::prelude::*; 47 | 48 | App::new() 49 | .add_plugins(( 50 | // Add the log plugin with the custom log layer 51 | DefaultPlugins.set(LogPlugin { 52 | custom_layer: custom_log_layer, 53 | ..default() 54 | }), 55 | // Add the dev console plugin itself. 56 | DevConsolePlugin, 57 | )) 58 | .run(); 59 | ``` 60 | 61 | 4. That should be it! You can now press the `` ` `` / `~` key on your keyboard and it should open the console! 62 | 63 | ## Togglable Features 64 | 65 | **(default)** `builtin-parser` includes the default parser. Disabling this allows you to remove the built-in parser and replace it with your own (or you could do nothing and make the console into a log reader). 66 | 67 | ## Bevy Compatibility 68 | 69 | | bevy | bevy_dev_console | 70 | | ------ | ---------------- | 71 | | 0.14.* | git (master) | 72 | -------------------------------------------------------------------------------- /examples/custom_functions.rs: -------------------------------------------------------------------------------- 1 | //! An example showing how to create custom functions 2 | 3 | use bevy::log::LogPlugin; 4 | use bevy::prelude::*; 5 | use bevy_dev_console::builtin_parser::{Environment, EvalError, Number, Spanned, StrongRef, Value}; 6 | use bevy_dev_console::prelude::*; 7 | use bevy_dev_console::register; 8 | use web_time as time; 9 | 10 | // Declare the functions we want to create: 11 | 12 | /// Basic function 13 | fn time_since_epoch() { 14 | let time = time::SystemTime::now() 15 | .duration_since(time::UNIX_EPOCH) 16 | .unwrap(); 17 | info!("The unix epoch was {} seconds ago", time.as_secs()); 18 | } 19 | 20 | /// Function with parameters and return value. 21 | /// 22 | /// Note that this will cause an error if an integer is passed onto this function. 23 | fn add(num1: f64, num2: f64) -> f64 { 24 | num1 + num2 25 | } 26 | 27 | /// Function with any value + span 28 | fn print_debug_info(value: Spanned) { 29 | info!( 30 | "Location in command: {:?}, Value: {:?}", 31 | value.span, value.value 32 | ) 33 | } 34 | 35 | #[derive(Resource)] 36 | struct MyCounter(u32); 37 | 38 | /// Function with [`World`] 39 | fn increment_global_counter(world: &mut World) -> u32 { 40 | world.resource_scope(|_, mut counter: Mut| { 41 | counter.0 += 1; 42 | 43 | counter.0 44 | }) 45 | } 46 | 47 | // Function with reference (Syntax subject to change soon) 48 | fn increment_number(number: Spanned>) -> Result<(), EvalError> { 49 | let span = number.span; 50 | let mut reference = number.value.borrow_mut(); 51 | if let Value::Number(number) = &mut *reference { 52 | *number = Number::add(*number, Number::Integer(1), span).unwrap(); 53 | Ok(()) 54 | } else { 55 | Err(EvalError::Custom { 56 | text: "Oh nooo".into(), 57 | span, 58 | }) 59 | } 60 | } 61 | 62 | // For more examples take a look at the standard library. 63 | 64 | // Register our functions by creating and inserting our own environment 65 | fn custom_environment() -> Environment { 66 | let mut environment = Environment::default(); 67 | 68 | // The register macro allows us to easily add functions to the environment. 69 | register!(&mut environment => { 70 | fn time_since_epoch; 71 | fn add; 72 | fn print_debug_info; 73 | fn increment_global_counter; 74 | fn increment_number; 75 | }); 76 | 77 | environment 78 | } 79 | 80 | fn main() { 81 | App::new() 82 | .insert_resource(MyCounter(0)) 83 | // Insert our new environment 84 | .insert_non_send_resource(custom_environment()) 85 | .add_plugins(( 86 | DefaultPlugins.set(LogPlugin { 87 | custom_layer: custom_log_layer, 88 | ..default() 89 | }), 90 | DevConsolePlugin, 91 | )) 92 | .run(); 93 | } 94 | -------------------------------------------------------------------------------- /src/builtin_parser/runner/reflection.rs: -------------------------------------------------------------------------------- 1 | use std::any::TypeId; 2 | use std::collections::HashMap; 3 | 4 | use bevy::prelude::*; 5 | use bevy::reflect::{DynamicStruct, ReflectFromPtr, TypeRegistration}; 6 | use logos::Span; 7 | 8 | use crate::builtin_parser::EvalError; 9 | 10 | use super::Value; 11 | 12 | /// [`IntoResource`] allows `bevy_dev_console` to create a long-lasting resource "reference" 13 | /// that can be "unwrapped" into the appropriate resource. 14 | #[derive(Debug, Clone)] 15 | pub struct IntoResource { 16 | pub id: TypeId, 17 | pub path: String, 18 | } 19 | impl IntoResource { 20 | pub const fn new(id: TypeId) -> Self { 21 | Self { 22 | id, 23 | path: String::new(), 24 | } 25 | } 26 | pub fn ref_dyn_reflect<'a>( 27 | &self, 28 | world: &'a World, 29 | registration: impl CreateRegistration, 30 | ) -> &'a dyn Reflect { 31 | let registration = registration.create_registration(self.id); 32 | let ref_dyn_reflect = ref_dyn_reflect(world, registration).unwrap(); 33 | 34 | ref_dyn_reflect 35 | } 36 | pub fn mut_dyn_reflect<'a>( 37 | &self, 38 | world: &'a mut World, 39 | registration: impl CreateRegistration, 40 | ) -> Mut<'a, dyn Reflect> { 41 | let registration = registration.create_registration(self.id); 42 | let ref_dyn_reflect = mut_dyn_reflect(world, registration).unwrap(); 43 | 44 | ref_dyn_reflect 45 | } 46 | } 47 | 48 | pub fn object_to_dynamic_struct( 49 | hashmap: HashMap, 50 | ) -> Result { 51 | let mut dynamic_struct = DynamicStruct::default(); 52 | 53 | for (key, (value, span, reflect)) in hashmap { 54 | dynamic_struct.insert_boxed(&key, value.reflect(span, reflect)?); 55 | } 56 | 57 | Ok(dynamic_struct) 58 | } 59 | 60 | pub fn mut_dyn_reflect<'a>( 61 | world: &'a mut World, 62 | registration: &TypeRegistration, 63 | ) -> Option> { 64 | let Some(component_id) = world.components().get_resource_id(registration.type_id()) else { 65 | error!( 66 | "Couldn't get the component id of the {} resource.", 67 | registration.type_info().type_path() 68 | ); 69 | return None; 70 | }; 71 | let resource = world.get_resource_mut_by_id(component_id).unwrap(); 72 | let reflect_from_ptr = registration.data::().unwrap(); 73 | // SAFETY: from the context it is known that `ReflectFromPtr` was made for the type of the `MutUntyped` 74 | let val: Mut = 75 | resource.map_unchanged(|ptr| unsafe { reflect_from_ptr.as_reflect_mut(ptr) }); 76 | Some(val) 77 | } 78 | 79 | pub fn ref_dyn_reflect<'a>( 80 | world: &'a World, 81 | registration: &TypeRegistration, 82 | ) -> Option<&'a dyn Reflect> { 83 | let Some(component_id) = world.components().get_resource_id(registration.type_id()) else { 84 | error!( 85 | "Couldn't get the component id of the {} resource.", 86 | registration.type_info().type_path() 87 | ); 88 | return None; 89 | }; 90 | let resource = world.get_resource_by_id(component_id).unwrap(); 91 | let reflect_from_ptr = registration.data::().unwrap(); 92 | // SAFETY: from the context it is known that `ReflectFromPtr` was made for the type of the `MutUntyped` 93 | let val: &dyn Reflect = unsafe { reflect_from_ptr.as_reflect(resource) }; 94 | Some(val) 95 | } 96 | 97 | pub trait CreateRegistration { 98 | fn create_registration(&self, type_id: TypeId) -> &TypeRegistration; 99 | } 100 | impl CreateRegistration for &TypeRegistration { 101 | fn create_registration(&self, type_id: TypeId) -> &TypeRegistration { 102 | assert!(self.type_id() == type_id); 103 | 104 | self 105 | } 106 | } 107 | impl CreateRegistration for &[&TypeRegistration] { 108 | fn create_registration(&self, type_id: TypeId) -> &TypeRegistration { 109 | self.iter() 110 | .find(|reg| reg.type_id() == type_id) 111 | .expect("registration no longer exists") 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/logging.rs: -------------------------------------------------------------------------------- 1 | //! Custom [LogPlugin](bevy::log::LogPlugin) functionality. 2 | 3 | use bevy::log::{BoxedLayer, Level}; 4 | use bevy::prelude::*; 5 | use bevy::utils::tracing::Subscriber; 6 | use std::sync::mpsc; 7 | use tracing_subscriber::field::Visit; 8 | use tracing_subscriber::Layer; 9 | use web_time::SystemTime; 10 | 11 | /// A function that implements the log reading functionality for the 12 | /// developer console via [`LogPlugin::custom_layer`](bevy::log::LogPlugin::custom_layer). 13 | pub fn custom_log_layer(app: &mut App) -> Option { 14 | Some(Box::new(create_custom_log_layer(app))) 15 | } 16 | 17 | fn create_custom_log_layer(app: &mut App) -> LogCaptureLayer { 18 | let (sender, receiver) = mpsc::channel(); 19 | app.add_event::(); 20 | app.insert_non_send_resource(CapturedLogEvents(receiver)); 21 | app.add_systems(PostUpdate, transfer_log_events); 22 | 23 | LogCaptureLayer { sender } 24 | } 25 | 26 | /// A [`tracing`](bevy::utils::tracing) log message event. 27 | /// 28 | /// This event is helpful for creating custom log viewing systems such as consoles and terminals. 29 | #[derive(Event, Debug, Clone)] 30 | pub(crate) struct LogMessage { 31 | /// The message contents. 32 | pub message: String, 33 | 34 | /// The name of the span described by this metadata. 35 | pub name: &'static str, 36 | 37 | /// The part of the system that the span that this 38 | /// metadata describes occurred in. 39 | pub target: &'static str, 40 | 41 | /// The level of verbosity of the described span. 42 | pub level: Level, 43 | 44 | /// The name of the Rust module where the span occurred, 45 | /// or `None` if this could not be determined. 46 | pub module_path: Option<&'static str>, 47 | 48 | /// The name of the source code file where the span occurred, 49 | /// or `None` if this could not be determined. 50 | pub file: Option<&'static str>, 51 | 52 | /// The line number in the source code file where the span occurred, 53 | /// or `None` if this could not be determined. 54 | pub line: Option, 55 | 56 | /// The time the log occurred. 57 | pub time: SystemTime, 58 | } 59 | 60 | /// Transfers information from the [`CapturedLogEvents`] resource to [`Events`](LogMessage). 61 | fn transfer_log_events( 62 | receiver: NonSend, 63 | mut log_events: EventWriter, 64 | ) { 65 | log_events.send_batch(receiver.0.try_iter()); 66 | } 67 | 68 | /// This struct temporarily stores [`LogMessage`]s before they are 69 | /// written to [`EventWriter`] by [`transfer_log_events`]. 70 | struct CapturedLogEvents(mpsc::Receiver); 71 | 72 | /// A [`Layer`] that captures log events and saves them to [`CapturedLogEvents`]. 73 | struct LogCaptureLayer { 74 | sender: mpsc::Sender, 75 | } 76 | impl Layer for LogCaptureLayer { 77 | fn on_event( 78 | &self, 79 | event: &bevy::utils::tracing::Event<'_>, 80 | _ctx: tracing_subscriber::layer::Context<'_, S>, 81 | ) { 82 | let mut message = None; 83 | event.record(&mut LogEventVisitor(&mut message)); 84 | if let Some(message) = message { 85 | let metadata = event.metadata(); 86 | self.sender 87 | .send(LogMessage { 88 | message, 89 | name: metadata.name(), 90 | target: metadata.target(), 91 | level: *metadata.level(), 92 | module_path: metadata.module_path(), 93 | file: metadata.file(), 94 | line: metadata.line(), 95 | time: SystemTime::now(), 96 | }) 97 | .expect("CapturedLogEvents resource no longer exists!"); 98 | } 99 | } 100 | } 101 | 102 | /// A [`Visit`]or that records log messages that are transferred to [`LogCaptureLayer`]. 103 | struct LogEventVisitor<'a>(&'a mut Option); 104 | impl Visit for LogEventVisitor<'_> { 105 | fn record_debug( 106 | &mut self, 107 | field: &bevy::utils::tracing::field::Field, 108 | value: &dyn std::fmt::Debug, 109 | ) { 110 | // Only log out messages 111 | if field.name() == "message" { 112 | *self.0 = Some(format!("{value:?}")); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/builtin_parser/runner/unique_rc.rs: -------------------------------------------------------------------------------- 1 | use std::cell::{Ref, RefCell, RefMut}; 2 | use std::fmt::Debug; 3 | use std::ops::{Deref, DerefMut}; 4 | use std::rc::{Rc, Weak}; 5 | 6 | /// A uniquely owned [`Rc`] with interior mutability. Interior mutability is abstracted away with [`WeakRef`]. 7 | /// 8 | /// This represents an [`Rc`] that is known to be uniquely owned -- that is, have exactly one strong 9 | /// reference. 10 | /// 11 | /// **TODO:** This is actually going to be a standard library feature. Use [`alloc::rc::UniqueRc`] when it is stabilized. 12 | #[derive(Debug)] 13 | pub struct UniqueRc(Rc>); 14 | impl UniqueRc { 15 | /// Get a reference to the inner [`Rc`] of [`UniqueRc`]. 16 | /// 17 | /// # Safety 18 | /// 19 | /// This function is unsafe because it allows direct access to the [`Rc`]. 20 | /// If cloned then the guarantee that there is only ever one strong reference is no longer satisfied. 21 | const unsafe fn get_rc(&self) -> &Rc> { 22 | &self.0 23 | } 24 | pub(crate) fn borrow_inner(&self) -> &RefCell { 25 | &self.0 26 | } 27 | /// Create a new weak pointer to this [`UniqueRc`]. 28 | pub fn borrow(&self) -> WeakRef { 29 | WeakRef::new(self) 30 | } 31 | } 32 | impl UniqueRc { 33 | /// Create a new [`UniqueRc`]. 34 | pub fn new(value: T) -> UniqueRc { 35 | UniqueRc(Rc::new(RefCell::new(value))) 36 | } 37 | /// Get the inner value (`T`) of this [`UniqueRc`]. 38 | pub fn into_inner(self) -> T { 39 | Rc::try_unwrap(self.0) 40 | .unwrap_or_else(|rc| { 41 | panic!( 42 | "There are {} strong pointers to a UniqueRc!", 43 | Rc::strong_count(&rc) 44 | ) 45 | }) 46 | .into_inner() 47 | } 48 | } 49 | impl Clone for UniqueRc { 50 | fn clone(&self) -> Self { 51 | let t = self.borrow_inner().clone().into_inner(); 52 | 53 | Self::new(t) 54 | } 55 | } 56 | 57 | impl Deref for UniqueRc { 58 | type Target = RefCell; 59 | fn deref(&self) -> &Self::Target { 60 | self.0.as_ref() 61 | } 62 | } 63 | impl DerefMut for UniqueRc { 64 | fn deref_mut(&mut self) -> &mut Self::Target { 65 | Rc::get_mut(&mut self.0).unwrap() 66 | } 67 | } 68 | 69 | /// A weak reference to a [`UniqueRc`] that may or may not exist. 70 | #[derive(Debug)] 71 | pub struct WeakRef { 72 | reference: Weak>, 73 | } 74 | impl Clone for WeakRef { 75 | fn clone(&self) -> Self { 76 | Self { 77 | reference: self.reference.clone(), 78 | } 79 | } 80 | } 81 | impl WeakRef { 82 | fn new(unique_rc: &UniqueRc) -> Self { 83 | // SAFETY: We are not cloning the `Rc`, so this is fine. 84 | let rc = unsafe { unique_rc.get_rc() }; 85 | Self { 86 | reference: Rc::downgrade(rc), 87 | } 88 | } 89 | /// Converts this [`WeakRef`] into a [`StrongRef`] (may be unsafe, see [`StrongRef`]'s documentation). 90 | pub fn upgrade(&self) -> Option> { 91 | Some(StrongRef(self.reference.upgrade()?)) 92 | } 93 | } 94 | 95 | /// A strong reference to value `T`. 96 | /// 97 | /// This value is *technically* unsafe due to [`UniqueRc`] expecting only one strong reference to its inner value. 98 | /// However in practice the only way you could obtain it is by having it passed into a custom function. 99 | /// In which case it is safe (probably). 100 | /// 101 | /// ``` 102 | /// use bevy_dev_console::builtin_parser::{Value, StrongRef}; 103 | /// 104 | /// fn add_to_reference(my_reference: StrongRef, add: String) { 105 | /// // currently you can only do it with `Value` (TODO) 106 | /// if let Value::String(string) = &mut *my_reference.borrow_mut() { 107 | /// *string += &add; 108 | /// } else { 109 | /// todo!(); 110 | /// } 111 | /// } 112 | /// ``` 113 | #[derive(Debug)] 114 | pub struct StrongRef(Rc>); 115 | impl StrongRef { 116 | /// Immutably borrows the wrapped value. 117 | pub fn borrow(&self) -> Ref { 118 | self.0.borrow() 119 | } 120 | /// Mutably borrows the wrapped value. 121 | pub fn borrow_mut(&self) -> RefMut { 122 | self.0.borrow_mut() 123 | } 124 | } 125 | 126 | #[cfg(test)] 127 | mod tests { 128 | use super::*; 129 | 130 | #[test] 131 | #[should_panic] 132 | fn strong_ref_panic() { 133 | let rc = UniqueRc::new(0); 134 | 135 | let weak = rc.borrow(); 136 | 137 | let strong = weak.upgrade().unwrap(); 138 | 139 | println!("{}", rc.into_inner()); // Panic! 140 | 141 | println!("{}", strong.borrow()); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/builtin_parser.rs: -------------------------------------------------------------------------------- 1 | //! [`bevy_dev_console`](crate)'s built-in command parser. 2 | //! 3 | //! Currently the built-in command parser is in very early development. 4 | //! It's purpose is to provide a simple, yet powerful method of modifying 5 | //! the game world via commands. 6 | 7 | use bevy::prelude::*; 8 | use logos::Span; 9 | 10 | use crate::builtin_parser::runner::ExecutionError; 11 | use crate::command::{CommandHints, CommandParser, DefaultCommandParser}; 12 | 13 | #[cfg(feature = "builtin-parser-completions")] 14 | use crate::command::CompletionSuggestion; 15 | 16 | #[cfg(feature = "builtin-parser-completions")] 17 | pub(crate) mod completions; 18 | pub(crate) mod lexer; 19 | pub(crate) mod number; 20 | pub(crate) mod parser; 21 | pub(crate) mod runner; 22 | 23 | pub use number::*; 24 | pub use runner::environment::Environment; 25 | pub use runner::error::EvalError; 26 | pub use runner::unique_rc::*; 27 | pub use runner::Value; 28 | 29 | /// Additional traits for span. 30 | pub trait SpanExtension { 31 | /// Wrap this value with a [`Spanned`]. 32 | #[must_use] 33 | fn wrap(self, value: T) -> Spanned; 34 | /// Combine two [`Span`]s into one. 35 | #[must_use] 36 | fn join(self, span: Self) -> Self; 37 | } 38 | impl SpanExtension for Span { 39 | #[inline] 40 | fn wrap(self, value: T) -> Spanned { 41 | Spanned { span: self, value } 42 | } 43 | #[inline] 44 | fn join(self, span: Self) -> Self { 45 | self.start..span.end 46 | } 47 | } 48 | 49 | /// Wrapper around `T` that stores a [Span] (A location in the source code) 50 | #[derive(Debug, Clone)] 51 | pub struct Spanned { 52 | /// The location of `T` in the source/command. 53 | pub span: Span, 54 | /// The value of `T`. 55 | pub value: T, 56 | } 57 | impl Spanned { 58 | /// Maps a [`Spanned`] to [`Spanned`] by applying a function to the 59 | /// contained `T` value, leaving the [`Span`] value untouched. 60 | #[must_use] 61 | pub fn map(self, f: impl FnOnce(T) -> U) -> Spanned { 62 | Spanned { 63 | span: self.span, 64 | value: f(self.value), 65 | } 66 | } 67 | } 68 | 69 | impl Default for DefaultCommandParser { 70 | fn default() -> Self { 71 | Self(Box::new(BuiltinCommandParser)) 72 | } 73 | } 74 | 75 | /// [`bevy_dev_console`](crate)'s built-in command parser. 76 | /// 77 | /// See the [module level documentation for more](self). 78 | #[derive(Default)] 79 | pub struct BuiltinCommandParser; 80 | impl CommandParser for BuiltinCommandParser { 81 | fn parse(&self, command: &str, world: &mut World) { 82 | let mut tokens = lexer::TokenStream::new(command); 83 | 84 | let environment = world.non_send_resource::(); 85 | let ast = parser::parse(&mut tokens, environment); 86 | 87 | dbg!(&ast); 88 | 89 | match ast { 90 | Ok(ast) => match runner::run(ast, world) { 91 | Ok(()) => (), 92 | Err(error) => { 93 | if let ExecutionError::Eval(eval_error) = &error { 94 | world 95 | .resource_mut::() 96 | .push(eval_error.hints()); 97 | } 98 | error!("{error}") 99 | } 100 | }, 101 | Err(err) => { 102 | world.resource_mut::().push([err.hint()]); 103 | 104 | error!("{err}") 105 | } 106 | } 107 | #[cfg(feature = "builtin-parser-completions")] 108 | { 109 | *world.resource_mut() = 110 | completions::store_in_cache(world.non_send_resource::()); 111 | } 112 | } 113 | 114 | #[cfg(feature = "builtin-parser-completions")] 115 | fn completion(&self, command: &str, world: &World) -> Vec { 116 | use fuzzy_matcher::FuzzyMatcher; 117 | 118 | use crate::builtin_parser::completions::EnvironmentCache; 119 | 120 | let matcher = fuzzy_matcher::skim::SkimMatcherV2::default(); 121 | let environment_cache = world.resource::(); 122 | 123 | let mut names: Vec<_> = environment_cache 124 | .function_names 125 | .iter() 126 | .chain(environment_cache.variable_names.iter()) 127 | .map(|name| (matcher.fuzzy_indices(name, command), name.clone())) 128 | .filter_map(|(fuzzy, name)| fuzzy.map(|v| (v, name))) 129 | .collect(); 130 | names.sort_by_key(|((score, _), _)| std::cmp::Reverse(*score)); 131 | names.truncate(crate::ui::MAX_COMPLETION_SUGGESTIONS); 132 | 133 | names 134 | .into_iter() 135 | .map(|((_, indices), name)| CompletionSuggestion { 136 | suggestion: name, 137 | highlighted_indices: indices, 138 | }) 139 | .collect() 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | //! Configuration structs for the developer console. 2 | 3 | use bevy::log::Level; 4 | use bevy::prelude::*; 5 | use bevy_egui::egui::{Color32, FontId, TextFormat}; 6 | 7 | /// The configuration of the developer console. 8 | #[derive(Resource, Reflect, Debug)] 9 | pub struct ConsoleConfig { 10 | /// The colors used in the developer console. 11 | pub theme: ConsoleTheme, 12 | /// The key used to open the developer console. 13 | pub open_key: KeyCode, 14 | /// The key used to submit a command to the command parser. 15 | pub submit_key: KeyCode, 16 | } 17 | 18 | impl Default for ConsoleConfig { 19 | fn default() -> Self { 20 | Self { 21 | theme: ConsoleTheme::ONE_DARK, 22 | open_key: KeyCode::Backquote, 23 | submit_key: KeyCode::Enter, 24 | } 25 | } 26 | } 27 | 28 | /// The colors used by the text in the developer console. 29 | #[derive(Reflect, Debug)] 30 | pub struct ConsoleTheme { 31 | /// The font used in the developer console. 32 | #[reflect(ignore)] 33 | pub font: FontId, 34 | /// The default color of text. 35 | pub text_color: Color, 36 | /// The color of dark text. 37 | pub dark: Color, 38 | /// The color of the "error" level. 39 | /// 40 | /// Designates very serious errors. 41 | pub error: Color, 42 | /// The color of the "warn" level. 43 | /// 44 | /// Designates hazardous situations. 45 | pub warning: Color, 46 | /// The color of the "info" level. 47 | /// 48 | /// Designates useful information. 49 | pub info: Color, 50 | /// The color of the "debug" level. 51 | /// 52 | /// Designates lower priority information. 53 | pub debug: Color, 54 | /// The color of the "trace" level. 55 | /// 56 | /// Designates very low priority, often extremely verbose, information. 57 | pub trace: Color, 58 | } 59 | 60 | /// Helper trait that allows conversion between [`bevy::Color`](Color) and [`egui::Color32`]. 61 | pub trait ToColor32 { 62 | /// Convert this [`bevy::Color`](Color) to a [`egui::Color32`]. 63 | fn to_color32(&self) -> Color32; 64 | } 65 | impl ToColor32 for Color { 66 | fn to_color32(&self) -> Color32 { 67 | let Srgba { 68 | red, 69 | green, 70 | blue, 71 | alpha, 72 | } = self.to_srgba(); 73 | 74 | Color32::from_rgba_unmultiplied( 75 | (red * 255.0) as u8, 76 | (green * 255.0) as u8, 77 | (blue * 255.0) as u8, 78 | (alpha * 255.0) as u8, 79 | ) 80 | } 81 | } 82 | 83 | macro_rules! define_text_format_method { 84 | ($name:ident, $color:ident) => { 85 | #[doc = concat!("Returns a [`TextFormat`] colored with [`Self::", stringify!($color), "`]")] 86 | pub fn $name(&self) -> TextFormat { 87 | TextFormat { 88 | color: self.$color.to_color32(), 89 | ..self.format_text() 90 | } 91 | } 92 | }; 93 | } 94 | 95 | impl ConsoleTheme { 96 | /// Atom's iconic One Dark theme. 97 | pub const ONE_DARK: Self = Self { 98 | font: FontId::monospace(14.0), 99 | dark: Color::srgb(0.42, 0.44, 0.48), 100 | text_color: Color::srgb(0.67, 0.7, 0.75), 101 | error: Color::srgb(0.91, 0.46, 0.5), 102 | warning: Color::srgb(0.82, 0.56, 0.32), 103 | info: Color::srgb(0.55, 0.76, 0.4), 104 | debug: Color::srgb(0.29, 0.65, 0.94), 105 | trace: Color::srgb(0.78, 0.45, 0.89), 106 | }; 107 | /// High contrast theme, might help some people. 108 | pub const HIGH_CONTRAST: Self = Self { 109 | font: FontId::monospace(14.0), 110 | dark: Color::srgb(0.5, 0.5, 0.5), 111 | text_color: Color::srgb(1.0, 1.0, 1.0), 112 | error: Color::srgb(1.0, 0.0, 0.0), 113 | warning: Color::srgb(1.0, 1.0, 0.0), 114 | info: Color::srgb(0.0, 1.0, 0.0), 115 | debug: Color::srgb(0.25, 0.25, 1.0), 116 | trace: Color::srgb(1.0, 0.0, 1.0), 117 | }; 118 | 119 | /// Returns a [`Color32`] based on the `level` 120 | pub fn color_level(&self, level: Level) -> Color32 { 121 | match level { 122 | Level::ERROR => self.error.to_color32(), 123 | Level::WARN => self.warning.to_color32(), 124 | Level::INFO => self.info.to_color32(), 125 | Level::DEBUG => self.debug.to_color32(), 126 | Level::TRACE => self.trace.to_color32(), 127 | } 128 | } 129 | 130 | /// Returns a [`TextFormat`] with a color based on the [`Level`] and the [`ConsoleTheme`]. 131 | pub fn format_level(&self, level: Level) -> TextFormat { 132 | TextFormat { 133 | color: self.color_level(level), 134 | ..self.format_text() 135 | } 136 | } 137 | 138 | /// Returns a [`TextFormat`] with the default font and color. 139 | pub fn format_text(&self) -> TextFormat { 140 | TextFormat { 141 | font_id: self.font.clone(), 142 | color: self.text_color.to_color32(), 143 | 144 | ..default() 145 | } 146 | } 147 | 148 | /// Returns a [`TextFormat`] with the default font and white color. 149 | pub fn format_bold(&self) -> TextFormat { 150 | TextFormat { 151 | font_id: self.font.clone(), 152 | color: Color32::WHITE, 153 | 154 | ..default() 155 | } 156 | } 157 | 158 | define_text_format_method!(format_dark, dark); 159 | define_text_format_method!(format_error, error); 160 | define_text_format_method!(format_warning, warning); 161 | define_text_format_method!(format_info, info); 162 | define_text_format_method!(format_debug, debug); 163 | define_text_format_method!(format_trace, trace); 164 | } 165 | -------------------------------------------------------------------------------- /src/builtin_parser/lexer.rs: -------------------------------------------------------------------------------- 1 | //! Generates a stream of tokens from a string. 2 | 3 | use logos::{Lexer, Logos, Span}; 4 | 5 | #[derive(Debug, Clone, Default, PartialEq)] 6 | pub struct FailedToLexCharacter; 7 | 8 | #[derive(Logos, Debug, Clone, PartialEq)] 9 | #[logos(skip r"[ \t\n\f]+", error = FailedToLexCharacter)] 10 | pub enum Token { 11 | #[token("(")] 12 | LeftParen, 13 | #[token(")")] 14 | RightParen, 15 | 16 | #[token("{")] 17 | LeftBracket, 18 | #[token("}")] 19 | RightBracket, 20 | 21 | #[token("[")] 22 | LeftBrace, 23 | #[token("]")] 24 | RightBrace, 25 | 26 | #[token("=")] 27 | Equals, 28 | #[token("+")] 29 | Plus, 30 | #[token("-")] 31 | Minus, 32 | #[token("/")] 33 | Slash, 34 | #[token("*")] 35 | Asterisk, 36 | #[token("%")] 37 | Modulo, 38 | 39 | #[token(".", priority = 10)] 40 | Dot, 41 | #[token("&")] 42 | Ampersand, 43 | 44 | #[token("loop")] 45 | Loop, 46 | #[token("for")] 47 | For, 48 | #[token("while")] 49 | While, 50 | 51 | #[token("in")] 52 | In, 53 | 54 | #[token(":")] 55 | Colon, 56 | #[token(";")] 57 | SemiColon, 58 | #[token(",")] 59 | Comma, 60 | 61 | #[token("true")] 62 | True, 63 | #[token("false")] 64 | False, 65 | 66 | #[regex(r#""(\\[\\"]|[^"])*""#)] 67 | String, 68 | 69 | #[regex("[a-zA-Z_][a-zA-Z0-9_]*")] 70 | Identifier, 71 | 72 | #[regex(r#"[0-9]+"#)] 73 | IntegerNumber, 74 | #[regex(r#"[0-9]+\.[0-9]*"#)] 75 | FloatNumber, 76 | 77 | #[token("i8")] 78 | #[token("i16")] 79 | #[token("i32")] 80 | #[token("i64")] 81 | #[token("isize")] 82 | #[token("u8")] 83 | #[token("u16")] 84 | #[token("u32")] 85 | #[token("u64")] 86 | #[token("usize")] 87 | #[token("f32")] 88 | #[token("f64")] 89 | NumberType, 90 | } 91 | 92 | /// A wrapper for the lexer which provides token peeking and other helper functions 93 | #[derive(Debug)] 94 | pub struct TokenStream<'a> { 95 | lexer: Lexer<'a, Token>, 96 | next: Option>, 97 | current_slice: &'a str, 98 | current_span: Span, 99 | } 100 | 101 | impl<'a> TokenStream<'a> { 102 | /// Creates a new [`TokenStream`] from `src`. 103 | pub fn new(src: &'a str) -> Self { 104 | let mut lexer = Token::lexer(src); 105 | 106 | let current_slice = lexer.slice(); 107 | let current_span = lexer.span(); 108 | let next = lexer.next(); 109 | 110 | Self { 111 | lexer, 112 | next, 113 | current_slice, 114 | current_span, 115 | } 116 | } 117 | 118 | /// Returns the next [`Token`] and advances the iterator 119 | pub fn next(&mut self) -> Option> { 120 | let val = self.next.take(); 121 | self.current_slice = self.lexer.slice(); 122 | self.current_span = self.lexer.span(); 123 | self.next = self.lexer.next(); 124 | 125 | val 126 | } 127 | 128 | // pub fn next_pe(&mut self) -> Result { 129 | // let token = self.next(); 130 | 131 | // self.to_parse_error(token) 132 | // } 133 | 134 | // pub fn to_parse_error( 135 | // &mut self, 136 | // token: Option>, 137 | // ) -> Result { 138 | // Ok(token 139 | // .ok_or(ParseError::ExpectedMoreTokens(self.span()))? 140 | // .map_err(|FailedToLexCharacter| ParseError::FailedToLexCharacter(self.span()))?) 141 | // } 142 | 143 | /// Returns a reference to next [`Token`] without advancing the iterator 144 | #[inline] 145 | #[must_use] 146 | pub fn peek(&self) -> &Option> { 147 | &self.next 148 | } 149 | 150 | /// Get the range for the current [`Token`] in `Source`. 151 | #[inline] 152 | #[must_use] 153 | pub fn span(&self) -> Span { 154 | self.current_span.clone() 155 | } 156 | 157 | /// Get a [`str`] slice of the current [`Token`]. 158 | #[inline] 159 | #[must_use] 160 | pub fn slice(&self) -> &str { 161 | self.current_slice 162 | } 163 | 164 | /// Get a [`str`] slice of the next [`Token`]. 165 | #[inline] 166 | #[must_use] 167 | pub fn peek_slice(&self) -> &str { 168 | self.lexer.slice() 169 | } 170 | 171 | /// Get a [`Span`] of the next [`Token`]. 172 | #[inline] 173 | #[must_use] 174 | pub fn peek_span(&self) -> Span { 175 | self.lexer.span() 176 | } 177 | } 178 | 179 | impl Iterator for TokenStream<'_> { 180 | type Item = Result; 181 | 182 | #[inline] 183 | fn next(&mut self) -> Option> { 184 | self.next() 185 | } 186 | } 187 | 188 | #[cfg(test)] 189 | mod tests { 190 | use super::{Token, TokenStream}; 191 | 192 | #[test] 193 | fn var_assign() { 194 | let mut lexer = TokenStream::new("x = 1 + 2 - 30.6"); 195 | 196 | assert_eq!(lexer.next(), Some(Ok(Token::Identifier))); 197 | assert_eq!(lexer.slice(), "x"); 198 | 199 | assert_eq!(lexer.next(), Some(Ok(Token::Equals))); 200 | assert_eq!(lexer.slice(), "="); 201 | 202 | assert_eq!(lexer.next(), Some(Ok(Token::IntegerNumber))); 203 | assert_eq!(lexer.slice(), "1"); 204 | 205 | assert_eq!(lexer.next(), Some(Ok(Token::Plus))); 206 | assert_eq!(lexer.slice(), "+"); 207 | 208 | assert_eq!(lexer.next(), Some(Ok(Token::IntegerNumber))); 209 | assert_eq!(lexer.slice(), "2"); 210 | 211 | assert_eq!(lexer.next(), Some(Ok(Token::Minus))); 212 | assert_eq!(lexer.slice(), "-"); 213 | 214 | assert_eq!(lexer.next(), Some(Ok(Token::FloatNumber))); 215 | assert_eq!(lexer.slice(), "30.6"); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/ui/completions.rs: -------------------------------------------------------------------------------- 1 | use bevy::ecs::system::Commands; 2 | use bevy_egui::egui; 3 | 4 | use crate::command::{AutoCompletions, CompletionSuggestion, UpdateAutoComplete}; 5 | use crate::prelude::ConsoleConfig; 6 | 7 | use super::ConsoleUiState; 8 | 9 | /// The max amount of completion suggestions shown at once. 10 | pub const MAX_COMPLETION_SUGGESTIONS: usize = 6; 11 | 12 | pub fn completions( 13 | text_edit: egui::text_edit::TextEditOutput, 14 | text_edit_id: egui::Id, 15 | state: &mut ConsoleUiState, 16 | ui: &mut egui::Ui, 17 | commands: &mut Commands, 18 | completions: &AutoCompletions, 19 | config: &ConsoleConfig, 20 | ) { 21 | let text_edit_complete_id = ui.make_persistent_id("text_edit_complete"); 22 | 23 | if let Some(cursor_range) = text_edit.state.cursor.char_range() { 24 | let [primary, secondary] = cursor_range.sorted(); 25 | 26 | fn non_keyword(character: char) -> bool { 27 | !(character.is_alphanumeric() || character == '_') 28 | } 29 | 30 | let cursor_index = (|| { 31 | // Convert the cursor's char index into a byte index 32 | // aswell as returning the character at the cursor's position position 33 | let (primary_index, char) = state 34 | .command 35 | .char_indices() 36 | .nth(primary.index.saturating_sub(1))?; 37 | 38 | if non_keyword(char) { 39 | return None; 40 | } 41 | 42 | Some(primary_index) 43 | })(); 44 | if text_edit.response.changed() { 45 | state.selected_completion = 0; 46 | } 47 | // todo check cursor position changed https://github.com/emilk/egui/discussions/4540 48 | // if text_edit.response.changed() { 49 | if true { 50 | if let Some(cursor_index) = cursor_index { 51 | ui.memory_mut(|mem| { 52 | if !completions.is_empty() { 53 | mem.open_popup(text_edit_complete_id) 54 | } 55 | }); 56 | let before_cursor = &state.command[..=cursor_index]; 57 | let keyword_before = match before_cursor.rfind(non_keyword) { 58 | // If found, return the slice from the end of the non-alphanumeric character to the cursor position 59 | Some(index) => &before_cursor[(index + 1)..], 60 | // If not found, the whole substring is a word 61 | None => before_cursor, 62 | }; 63 | commands.add(UpdateAutoComplete(keyword_before.to_owned())); 64 | } else { 65 | ui.memory_mut(|mem| { 66 | if mem.is_popup_open(text_edit_complete_id) { 67 | mem.close_popup(); 68 | } 69 | }); 70 | } 71 | } 72 | if let Some(cursor_index) = cursor_index { 73 | if ui.input(|i| i.key_pressed(egui::Key::Tab)) { 74 | // Remove the old text 75 | let before_cursor = &state.command[..=cursor_index]; 76 | let index_before = match before_cursor.rfind(non_keyword) { 77 | Some(index) => index + 1, 78 | None => 0, 79 | }; 80 | let after_cursor = &state.command[cursor_index..]; 81 | match after_cursor.find(non_keyword) { 82 | Some(characters_after) => state 83 | .command 84 | .drain(index_before..cursor_index + characters_after), 85 | None => state.command.drain(index_before..), 86 | }; 87 | // Add the completed text 88 | let completed_text = &completions.0[state.selected_completion].suggestion; 89 | 90 | state.command.insert_str(index_before, completed_text); 91 | 92 | // Set the cursor position 93 | let mut text_edit_state = text_edit.state; 94 | 95 | let mut cursor_range = egui::text::CCursorRange::two(primary, secondary); 96 | 97 | cursor_range.primary.index += 98 | completed_text.len() - (cursor_index - index_before) - 1; 99 | cursor_range.secondary.index += 100 | completed_text.len() - (cursor_index - index_before) - 1; 101 | 102 | text_edit_state.cursor.set_char_range(Some(cursor_range)); 103 | egui::TextEdit::store_state(ui.ctx(), text_edit_id, text_edit_state); 104 | } 105 | } 106 | } 107 | egui::popup_below_widget( 108 | ui, 109 | text_edit_complete_id, 110 | &text_edit.response, 111 | egui::PopupCloseBehavior::CloseOnClickOutside, 112 | |ui| { 113 | ui.vertical(|ui| { 114 | for ( 115 | i, 116 | CompletionSuggestion { 117 | suggestion, 118 | highlighted_indices, 119 | }, 120 | ) in completions.iter().take(6).enumerate() 121 | { 122 | let mut layout = egui::text::LayoutJob::default(); 123 | for (i, _) in suggestion.char_indices() { 124 | layout.append( 125 | &suggestion[i..=i], 126 | 0.0, 127 | if highlighted_indices.contains(&i) { 128 | config.theme.format_bold() 129 | } else { 130 | config.theme.format_text() 131 | }, 132 | ); 133 | } 134 | let res = ui.label(layout); 135 | if i == state.selected_completion { 136 | res.highlight(); 137 | } 138 | } 139 | }) 140 | }, 141 | ); 142 | } 143 | 144 | /// Also consumes the up and down arrow keys. 145 | pub fn change_selected_completion( 146 | ui: &mut egui::Ui, 147 | state: &mut ConsoleUiState, 148 | completions: &[CompletionSuggestion], 149 | ) { 150 | if ui.input_mut(|i| i.consume_key(egui::Modifiers::NONE, egui::Key::ArrowUp)) { 151 | state.selected_completion = state.selected_completion.saturating_sub(1); 152 | } 153 | if ui.input_mut(|i| i.consume_key(egui::Modifiers::NONE, egui::Key::ArrowDown)) { 154 | state.selected_completion = state 155 | .selected_completion 156 | .saturating_add(1) 157 | .min(completions.len() - 1); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/command.rs: -------------------------------------------------------------------------------- 1 | //! Command execution functionality. 2 | 3 | use std::borrow::Cow; 4 | use std::ops::Range; 5 | 6 | use bevy::ecs::world::Command; 7 | use bevy::prelude::*; 8 | 9 | /// The command parser currently being used by the dev console. 10 | #[derive(Resource)] 11 | pub struct DefaultCommandParser(pub Box); 12 | 13 | impl DefaultCommandParser { 14 | /// Shortcut method for calling `parser.0.parse(command, world)`. 15 | #[inline] 16 | pub fn parse(&self, command: &str, world: &mut World) { 17 | self.0.parse(command, world) 18 | } 19 | /// Shortcut method for calling `parser.0.completion(command, world)`. 20 | #[inline] 21 | #[must_use] 22 | #[cfg(feature = "completions")] 23 | pub fn completion(&self, keyword: &str, world: &World) -> Vec { 24 | self.0.completion(keyword, world) 25 | } 26 | } 27 | impl From for DefaultCommandParser { 28 | fn from(value: Parser) -> Self { 29 | Self(Box::new(value)) 30 | } 31 | } 32 | impl From> for DefaultCommandParser { 33 | fn from(value: Box) -> Self { 34 | Self(value) 35 | } 36 | } 37 | 38 | /// A hint displayed to the user when they make a mistake. 39 | #[derive(Debug, Clone)] 40 | pub struct CommandHint { 41 | /// The color of the hint. 42 | pub color: CommandHintColor, 43 | /// The location of the hint in the command. 44 | pub span: Range, 45 | /// Additional information about the hint when hovered over. 46 | /// (Doesn't do anything atm) 47 | pub description: Cow<'static, str>, 48 | } 49 | impl CommandHint { 50 | /// Creates a new [`CommandHint`]. 51 | pub fn new( 52 | span: Range, 53 | color: CommandHintColor, 54 | description: impl Into>, 55 | ) -> Self { 56 | Self { 57 | color, 58 | span, 59 | description: description.into(), 60 | } 61 | } 62 | } 63 | 64 | /// The color of a [`CommandHint`], may either be a standard color or a [`Custom`](CommandHintColor::Custom) [`Color`]. 65 | #[derive(Debug, Clone)] 66 | pub enum CommandHintColor { 67 | /// An error marks bad code that cannot be recovered from. 68 | /// 69 | /// Usually colored red. 70 | Error, 71 | /// A warning marks code that could cause problems in the future. 72 | /// 73 | /// Usually colored yellow. 74 | Warning, 75 | /// A hint marks code that is questionable, but is otherwise fine. 76 | /// 77 | /// Usually colored blue. 78 | Hint, 79 | /// This marks code that could be improved. 80 | /// 81 | /// Usually colored green. 82 | Help, 83 | /// A custom color of your choice! This is usually not recommended as 84 | /// you're much better off using the standard colors. 85 | Custom(Color), 86 | } 87 | 88 | /// A resource where hints (errors/warnings/etc) are stored 89 | /// to be displayed in the developer console. 90 | #[derive(Resource, Debug, Default, Deref)] 91 | pub struct CommandHints { 92 | #[deref] 93 | hints: Vec>, 94 | hint_added: bool, 95 | } 96 | impl CommandHints { 97 | /// Push a list of hints. This should be done once per command call. 98 | pub fn push(&mut self, hints: impl Into>) { 99 | if self.hint_added { 100 | warn!( 101 | "Hints were added twice! Hint 1: {:?}, Hint 2: {:?}", 102 | self.hints.last(), 103 | hints.into() 104 | ) 105 | } else { 106 | self.hint_added = true; 107 | self.hints.push(hints.into()); 108 | } 109 | } 110 | pub(crate) fn reset_hint_added(&mut self) { 111 | if !self.hint_added { 112 | self.push([]); 113 | } 114 | self.hint_added = false; 115 | } 116 | } 117 | 118 | /// The trait that all [`CommandParser`]s implement. 119 | /// You can take a look at the [builtin parser](crate::builtin_parser) for an advanced example. 120 | /// 121 | /// ``` 122 | /// # use bevy::ecs::world::World; 123 | /// # use bevy_dev_console::command::CommandParser; 124 | /// # use bevy::log::info; 125 | /// # use bevy_dev_console::ui::COMMAND_RESULT_NAME; 126 | /// 127 | /// pub struct MyCustomParser; 128 | /// impl CommandParser for MyCustomParser { 129 | /// fn parse(&self, command: &str, world: &mut World) { 130 | /// // The `name: COMMAND_RESULT_NAME` tells the console this is a result from 131 | /// // the parser and then formats it accordingly. 132 | /// # // TODO: figure out better solution for this 133 | /// info!(name: COMMAND_RESULT_NAME, "You just entered the command {command}") 134 | /// } 135 | /// } 136 | /// ``` 137 | pub trait CommandParser: Send + Sync + 'static { 138 | /// This method is called by the console when a command is ran. 139 | fn parse(&self, command: &str, world: &mut World); 140 | /// This method is called by the console when the command is changed. 141 | #[inline] 142 | #[must_use] 143 | #[cfg(feature = "completions")] 144 | fn completion(&self, keyword: &str, world: &World) -> Vec { 145 | let _ = (keyword, world); 146 | Vec::new() 147 | } 148 | } 149 | 150 | /// A suggestion for autocomplete. 151 | #[cfg(feature = "completions")] 152 | pub struct CompletionSuggestion { 153 | /// The suggestion string 154 | pub suggestion: String, 155 | /// The character indices of the [`suggestion`](Self::suggestion) to highlight. 156 | pub highlighted_indices: Vec, 157 | } 158 | 159 | pub(crate) struct ExecuteCommand(pub String); 160 | impl Command for ExecuteCommand { 161 | fn apply(self, world: &mut World) { 162 | if let Some(parser) = world.remove_resource::() { 163 | parser.parse(&self.0, world); 164 | world.insert_resource(parser); 165 | } else { 166 | error!("Default command parser doesn't exist, cannot execute command."); 167 | } 168 | } 169 | } 170 | 171 | #[derive(Resource, Default, Deref, DerefMut)] 172 | #[cfg(feature = "completions")] 173 | pub struct AutoCompletions(pub(crate) Vec); 174 | #[cfg(feature = "completions")] 175 | pub(crate) struct UpdateAutoComplete(pub String); 176 | #[cfg(feature = "completions")] 177 | impl Command for UpdateAutoComplete { 178 | fn apply(self, world: &mut World) { 179 | if let Some(parser) = world.remove_resource::() { 180 | let completions = parser.completion(&self.0, world); 181 | world.resource_mut::().0 = completions; 182 | world.insert_resource(parser); 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/builtin_parser/runner/error.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use logos::Span; 4 | 5 | use crate::builtin_parser::number::Number; 6 | use crate::builtin_parser::parser::Access; 7 | use crate::builtin_parser::Spanned; 8 | use crate::command::{CommandHint, CommandHintColor}; 9 | 10 | use super::Value; 11 | 12 | /// An error occurring during the while executing the [`AST`](Ast) of the command. 13 | #[derive(Debug)] 14 | #[allow(missing_docs)] 15 | pub enum EvalError { 16 | /// A custom text message. Contains very little contextual information, try to find an existing error instead. 17 | Custom { 18 | /// The text of the message 19 | text: Cow<'static, str>, 20 | span: Span, 21 | }, 22 | InvalidOperation { 23 | left: Number, 24 | right: Number, 25 | operation: &'static str, 26 | span: Span, 27 | }, 28 | VariableNotFound(Spanned), 29 | ExpectedNumberAfterUnaryOperator(Spanned), 30 | CannotIndexValue(Spanned), 31 | ReferenceToMovedData(Span), 32 | VariableMoved(Spanned), 33 | CannotDereferenceValue(Spanned<&'static str>), 34 | CannotBorrowValue(Spanned<&'static str>), 35 | IncompatibleReflectTypes { 36 | expected: String, 37 | actual: String, 38 | span: Span, 39 | }, 40 | EnumVariantNotFound(Spanned), 41 | CannotMoveOutOfResource(Spanned), 42 | CannotNegateUnsignedInteger(Spanned), 43 | IncompatibleNumberTypes { 44 | left: &'static str, 45 | right: &'static str, 46 | span: Span, 47 | }, 48 | IncompatibleFunctionParameter { 49 | expected: &'static str, 50 | actual: &'static str, 51 | span: Span, 52 | }, 53 | EnumVariantStructFieldNotFound { 54 | field_name: String, 55 | variant_name: String, 56 | span: Span, 57 | }, 58 | ExpectedVariableGotFunction(Spanned), 59 | CannotReflectReference(Span), 60 | CannotReflectResource(Span), 61 | EnumVariantTupleFieldNotFound { 62 | span: Span, 63 | field_index: usize, 64 | variant_name: String, 65 | }, 66 | IncorrectAccessOperation { 67 | span: Span, 68 | expected_access: &'static [&'static str], 69 | expected_type: &'static str, 70 | got: Access, 71 | }, 72 | FieldNotFoundInStruct(Spanned), 73 | FieldNotFoundInTuple { 74 | span: Span, 75 | field_index: usize, 76 | tuple_size: usize, 77 | }, 78 | } 79 | 80 | impl EvalError { 81 | /// Get all the locations of the error in the source. 82 | pub fn spans(&self) -> Vec { 83 | use EvalError as E; 84 | 85 | match self { 86 | E::Custom { span, .. } => vec![span.clone()], 87 | E::VariableNotFound(Spanned { span, .. }) => vec![span.clone()], 88 | E::ExpectedNumberAfterUnaryOperator(Spanned { span, .. }) => vec![span.clone()], 89 | E::CannotIndexValue(Spanned { span, .. }) => vec![span.clone()], 90 | E::FieldNotFoundInStruct(Spanned { span, value: _ }) => vec![span.clone()], 91 | E::CannotDereferenceValue(Spanned { span, .. }) => vec![span.clone()], 92 | E::ReferenceToMovedData(span) => vec![span.clone()], 93 | E::VariableMoved(Spanned { span, .. }) => vec![span.clone()], 94 | E::CannotBorrowValue(Spanned { span, .. }) => vec![span.clone()], 95 | E::IncompatibleReflectTypes { span, .. } => vec![span.clone()], 96 | E::EnumVariantNotFound(Spanned { span, .. }) => vec![span.clone()], 97 | E::EnumVariantStructFieldNotFound { span, .. } => vec![span.clone()], 98 | E::EnumVariantTupleFieldNotFound { span, .. } => vec![span.clone()], 99 | E::CannotMoveOutOfResource(Spanned { span, .. }) => vec![span.clone()], 100 | E::CannotNegateUnsignedInteger(Spanned { span, .. }) => vec![span.clone()], 101 | E::IncompatibleNumberTypes { span, .. } => vec![span.clone()], 102 | E::IncompatibleFunctionParameter { span, .. } => vec![span.clone()], 103 | E::ExpectedVariableGotFunction(Spanned { span, .. }) => vec![span.clone()], 104 | E::CannotReflectReference(span) => vec![span.clone()], 105 | E::CannotReflectResource(span) => vec![span.clone()], 106 | E::InvalidOperation { span, .. } => vec![span.clone()], 107 | E::IncorrectAccessOperation { span, .. } => vec![span.clone()], 108 | E::FieldNotFoundInTuple { span, .. } => vec![span.clone()], 109 | } 110 | } 111 | /// Returns all the hints for this error. 112 | pub fn hints(&self) -> Vec { 113 | self.spans() 114 | .into_iter() 115 | .map(|span| CommandHint::new(span, CommandHintColor::Error, self.to_string())) 116 | .collect() 117 | } 118 | } 119 | 120 | impl std::fmt::Display for EvalError { 121 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 122 | use EvalError as E; 123 | 124 | match self { 125 | E::Custom { text, .. } => f.write_str(text), 126 | E::VariableNotFound(Spanned { value, .. }) => { 127 | write!(f, "Variable `{value}` not found.") 128 | } 129 | E::ExpectedNumberAfterUnaryOperator(Spanned { value, .. }) => write!( 130 | f, 131 | "Expected a number after unary operator (-) but got {} instead.", 132 | value.natural_kind() 133 | ), 134 | E::CannotIndexValue(Spanned { span: _, value }) => { 135 | write!(f, "Cannot index {} with a member expression.", value.kind()) 136 | } 137 | E::ReferenceToMovedData(_) => write!(f, "Cannot access reference to moved data."), 138 | E::VariableMoved(Spanned { value, .. }) => { 139 | write!(f, "Variable `{value}` was moved.") 140 | } 141 | E::CannotDereferenceValue(Spanned { value: kind, .. }) => { 142 | write!(f, "Cannot dereference {kind}.") 143 | } 144 | E::CannotBorrowValue(Spanned { value: kind, .. }) => { 145 | write!(f, "Cannot borrow {kind}. Only variables can be borrowed.") 146 | } 147 | E::IncompatibleReflectTypes { 148 | expected, actual, .. 149 | } => write!( 150 | f, 151 | "Cannot set incompatible reflect types. Expected `{expected}`, got `{actual}`" 152 | ), 153 | E::EnumVariantNotFound(Spanned { value: name, .. }) => { 154 | write!(f, "Enum variant `{name}` was not found.") 155 | } 156 | E::EnumVariantStructFieldNotFound { 157 | field_name, 158 | variant_name, 159 | .. 160 | } => write!( 161 | f, 162 | "Field `{field_name}` doesn't exist on struct variant `{variant_name}`." 163 | ), 164 | E::EnumVariantTupleFieldNotFound { 165 | field_index, 166 | variant_name, 167 | .. 168 | } => write!( 169 | f, 170 | "Field `{field_index}` doesn't exist on tuple variant `{variant_name}`." 171 | ), 172 | E::CannotMoveOutOfResource(Spanned { value, .. }) => write!( 173 | f, 174 | "Cannot move out of resource `{value}`, try borrowing it instead." 175 | ), 176 | E::CannotNegateUnsignedInteger(Spanned { value, .. }) => write!( 177 | f, 178 | "Unsigned integers cannot be negated. (Type: {})", 179 | value.natural_kind() 180 | ), 181 | E::IncompatibleNumberTypes { left, right, .. } => write!( 182 | f, 183 | "Incompatible number types; `{left}` and `{right}` are incompatible." 184 | ), 185 | E::IncompatibleFunctionParameter { 186 | expected, actual, .. 187 | } => write!( 188 | f, 189 | "Mismatched function parameter type. Expected {expected} but got {actual}" 190 | ), 191 | E::ExpectedVariableGotFunction(Spanned { value, .. }) => write!( 192 | f, 193 | "Expected `{value}` to be a variable, but got a function instead." 194 | ), 195 | E::CannotReflectReference(_) => { 196 | write!( 197 | f, 198 | "Cannot reflect a reference. Try dereferencing it instead." 199 | ) 200 | } 201 | E::CannotReflectResource(_) => { 202 | write!( 203 | f, 204 | "Cannot reflecting resources is not possible at the moment." 205 | ) 206 | } 207 | E::InvalidOperation { 208 | left, 209 | right, 210 | operation, 211 | span: _, 212 | } => write!(f, "Invalid operation: Cannot {operation} {left} by {right}"), 213 | E::IncorrectAccessOperation { 214 | expected_access, 215 | expected_type, 216 | got, 217 | span: _, 218 | } => write!( 219 | f, 220 | "Expected {} access to access {expected_type} but got {}", 221 | expected_access.join(" and "), 222 | got.natural_kind() 223 | ), 224 | E::FieldNotFoundInStruct(Spanned { span: _, value }) => { 225 | write!(f, "Field {value} not found in struct") 226 | } 227 | E::FieldNotFoundInTuple { 228 | field_index, 229 | tuple_size, 230 | span: _, 231 | } => write!( 232 | f, 233 | "Field {field_index} is out of bounds for tuple of size {tuple_size}" 234 | ), 235 | } 236 | } 237 | } 238 | 239 | impl std::error::Error for EvalError {} 240 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /src/builtin_parser/runner/member.rs: -------------------------------------------------------------------------------- 1 | //! Evaluation for member expressions and paths 2 | 3 | use crate::builtin_parser::parser::{access_unwrap, Access, Expression}; 4 | use crate::builtin_parser::{EvalError, SpanExtension, Spanned, WeakRef}; 5 | 6 | use super::reflection::IntoResource; 7 | use super::{eval_expression, todo_error, EvalParams, Value}; 8 | 9 | /// Evaluate a member expression. 10 | /// 11 | /// A member expression allows indexing of values to access their inner fields. 12 | /// 13 | /// # Examples 14 | /// 15 | /// ```text 16 | /// $ {a: 5}.a 17 | /// > 5 18 | /// $ x = {key: "hi"} 19 | /// $ &x.key 20 | /// > "hi" 21 | /// $ x 22 | /// > {key: "hi"} 23 | /// $ x.key 24 | /// > "hi" 25 | /// $ x 26 | /// Error: Variable moved 27 | /// ``` 28 | pub fn eval_member_expression( 29 | left: Spanned, 30 | right: Spanned, 31 | EvalParams { 32 | world, 33 | environment, 34 | registrations, 35 | }: EvalParams, 36 | ) -> Result { 37 | let left_span = left.span.clone(); 38 | let span = left.span.start..right.span.end; 39 | let left = eval_expression( 40 | left, 41 | EvalParams { 42 | world, 43 | environment, 44 | registrations, 45 | }, 46 | )?; 47 | 48 | match left { 49 | Value::Reference(reference) => { 50 | let Some(strong) = reference.upgrade() else { 51 | return Err(EvalError::ReferenceToMovedData(left_span)); 52 | }; 53 | let reference = strong.borrow(); 54 | match &&*reference { 55 | Value::Object(map) | Value::StructObject { map, .. } => { 56 | access_unwrap!("an object reference", Field(field) = right => { 57 | let value = map.get(&field).ok_or(EvalError::FieldNotFoundInStruct(span.wrap(field)))?; 58 | 59 | Ok(Value::Reference(value.borrow())) 60 | }) 61 | } 62 | Value::Tuple(tuple) | Value::StructTuple { tuple, .. } => { 63 | access_unwrap!("a tuple reference", TupleIndex(index) = right => { 64 | let Spanned { span: _, value } = 65 | tuple.get(index).ok_or(EvalError::FieldNotFoundInTuple { 66 | span, 67 | field_index: index, 68 | tuple_size: tuple.len(), 69 | })?; 70 | 71 | Ok(Value::Reference(value.borrow())) 72 | }) 73 | } 74 | Value::Resource(resource) => { 75 | access_unwrap!("a resource", Field(field) = right => { 76 | let mut resource = resource.clone(); 77 | 78 | resource.path.push('.'); 79 | resource.path += &field; 80 | 81 | Ok(Value::Resource(resource)) 82 | }) 83 | } 84 | var => Err(EvalError::CannotIndexValue(left_span.wrap((*var).clone()))), 85 | } 86 | } 87 | Value::Object(mut map) | Value::StructObject { mut map, .. } => { 88 | access_unwrap!("an object", Field(field) = right => { 89 | let value = map 90 | .remove(&field) 91 | .ok_or(EvalError::FieldNotFoundInStruct(span.wrap(field)))?; 92 | 93 | Ok(value.into_inner()) 94 | }) 95 | } 96 | Value::Tuple(tuple) | Value::StructTuple { tuple, .. } => { 97 | access_unwrap!("a tuple reference", TupleIndex(field_index) = right => { 98 | let tuple_size = tuple.len(); 99 | let Spanned { span: _, value } = 100 | tuple 101 | .into_vec() 102 | .into_iter() 103 | .nth(field_index) 104 | .ok_or(EvalError::FieldNotFoundInTuple { 105 | span, 106 | field_index, 107 | tuple_size, 108 | })?; 109 | 110 | Ok(value.into_inner()) 111 | }) 112 | } 113 | Value::Resource(mut resource) => { 114 | access_unwrap!("a resource", Field(field) = right => { 115 | resource.path.push('.'); 116 | resource.path += &field; 117 | 118 | Ok(Value::Resource(resource)) 119 | }) 120 | } 121 | _ => Err(EvalError::CannotIndexValue(left_span.wrap(left))), 122 | } 123 | } 124 | 125 | pub enum Path { 126 | Variable(WeakRef), 127 | NewVariable(String), 128 | Resource(IntoResource), 129 | } 130 | 131 | /// Evaluate a path expression. 132 | /// 133 | /// A path expression, in contrast to a member expression, is for creating new variables or assigning to existing ones. 134 | /// 135 | /// # Examples 136 | /// 137 | /// ```text 138 | /// a -> Path::NewVariable("a") 139 | /// a.b -> if a.b exists, returns Path::Variable(a.b) 140 | /// MyResource.field -> appends "field" to the IntoResource path and returns Resource 141 | /// a.b.c -> if a.b.c, returns Path::Variable(a.b.c) (wow look its recursive) 142 | /// ``` 143 | pub fn eval_path( 144 | expr: Spanned, 145 | EvalParams { 146 | world, 147 | environment, 148 | registrations, 149 | }: EvalParams, 150 | ) -> Result, EvalError> { 151 | match expr.value { 152 | Expression::Variable(variable) => { 153 | if let Some(registration) = registrations 154 | .iter() 155 | .find(|v| v.type_info().type_path_table().short_path() == variable) 156 | { 157 | Ok(Spanned { 158 | span: expr.span, 159 | value: Path::Resource(IntoResource::new(registration.type_id())), 160 | }) 161 | } else if let Ok(variable) = environment.get(&variable, expr.span.clone()) { 162 | Ok(Spanned { 163 | span: expr.span, 164 | value: Path::Variable(variable.borrow()), 165 | }) 166 | } else { 167 | Ok(Spanned { 168 | span: expr.span, 169 | value: Path::NewVariable(variable), 170 | }) 171 | } 172 | } 173 | Expression::Member { left, right } => { 174 | let left = eval_path( 175 | *left, 176 | EvalParams { 177 | world, 178 | environment, 179 | registrations, 180 | }, 181 | )?; 182 | match left.value { 183 | Path::Variable(variable) => match &*variable.upgrade().unwrap().borrow() { 184 | Value::Resource(resource) => { 185 | access_unwrap!("a resource", Field(field) = right => { 186 | let mut resource = resource.clone(); 187 | 188 | resource.path.push('.'); 189 | resource.path += &field; 190 | 191 | Ok(left.span.wrap(Path::Resource(resource))) 192 | }) 193 | } 194 | Value::Object(object) | Value::StructObject { map: object, .. } => { 195 | let span = left.span.start..right.span.end; 196 | access_unwrap!("an object", Field(field) = right => { 197 | let weak = match object.get(&field) { 198 | Some(rc) => rc.borrow(), 199 | None => { 200 | return Err(EvalError::FieldNotFoundInStruct(span.wrap(field))) 201 | } 202 | }; 203 | 204 | Ok(span.wrap(Path::Variable(weak))) 205 | }) 206 | } 207 | Value::Tuple(tuple) | Value::StructTuple { tuple, .. } => { 208 | let span = left.span.start..right.span.end; 209 | access_unwrap!("a tuple", TupleIndex(index) = right => { 210 | let weak = match tuple.get(index) { 211 | Some(Spanned { value: rc, span: _ }) => rc.borrow(), 212 | None => { 213 | return Err(EvalError::FieldNotFoundInTuple { 214 | span, 215 | field_index: index, 216 | tuple_size: tuple.len(), 217 | }) 218 | } 219 | }; 220 | 221 | Ok(span.wrap(Path::Variable(weak))) 222 | }) 223 | } 224 | value => todo_error!("{value:?}"), 225 | }, 226 | Path::Resource(mut resource) => { 227 | access_unwrap!("a resource", Field(field) = right => { 228 | resource.path.push('.'); 229 | resource.path += &field; 230 | 231 | Ok(left.span.wrap(Path::Resource(resource))) 232 | }) 233 | } 234 | Path::NewVariable(name) => Err(EvalError::VariableNotFound(left.span.wrap(name))), 235 | } 236 | } 237 | Expression::Dereference(inner) => { 238 | let path = eval_path( 239 | *inner, 240 | EvalParams { 241 | world, 242 | environment, 243 | registrations, 244 | }, 245 | )?; 246 | match path.value { 247 | Path::Variable(value) => { 248 | let strong = value 249 | .upgrade() 250 | .ok_or(EvalError::ReferenceToMovedData(path.span))?; 251 | let borrow = strong.borrow(); 252 | 253 | if let Value::Reference(ref reference) = &*borrow { 254 | Ok(expr.span.wrap(Path::Variable(reference.clone()))) 255 | } else { 256 | Err(EvalError::CannotDereferenceValue( 257 | expr.span.wrap(borrow.natural_kind()), 258 | )) 259 | } 260 | } 261 | Path::NewVariable(_) => todo_error!(), 262 | Path::Resource(_) => todo_error!(), 263 | } 264 | } 265 | expr => todo_error!("can't eval path of this expr: {expr:#?}"), 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /src/ui.rs: -------------------------------------------------------------------------------- 1 | //! The module that handles the user interface of the console. 2 | //! 3 | //! Made with [`bevy_egui`]. 4 | 5 | use bevy::prelude::*; 6 | use bevy_egui::egui::text::LayoutJob; 7 | use bevy_egui::egui::{Stroke, TextFormat}; 8 | use bevy_egui::*; 9 | use chrono::prelude::*; 10 | use web_time::SystemTime; 11 | 12 | use crate::command::{CommandHints, ExecuteCommand}; 13 | use crate::config::ToColor32; 14 | use crate::logging::LogMessage; 15 | use crate::prelude::ConsoleConfig; 16 | 17 | #[cfg(feature = "completions")] 18 | use crate::command::AutoCompletions; 19 | 20 | #[cfg(feature = "completions")] 21 | mod completions; 22 | #[cfg(feature = "completions")] 23 | pub use completions::MAX_COMPLETION_SUGGESTIONS; 24 | 25 | /// Prefix for log messages that show a previous command. 26 | pub const COMMAND_MESSAGE_PREFIX: &str = "$ "; 27 | /// Prefix for log messages that show the result of a command. 28 | pub const COMMAND_RESULT_PREFIX: &str = "> "; 29 | /// Identifier for log messages that show a previous command. 30 | pub const COMMAND_MESSAGE_NAME: &str = "console_command"; 31 | /// Identifier for log messages that show the result of a command. 32 | pub const COMMAND_RESULT_NAME: &str = "console_result"; 33 | 34 | #[derive(Default, Resource)] 35 | pub struct ConsoleUiState { 36 | /// Whether the console is open or not. 37 | pub(crate) open: bool, 38 | /// Whether we have set focus this open or not. 39 | pub(crate) text_focus: bool, 40 | /// A list of all log messages received plus an 41 | /// indicator indicating if the message is new. 42 | pub(crate) log: Vec<(LogMessage, bool)>, 43 | /// The command in the text bar. 44 | pub(crate) command: String, 45 | #[cfg(feature = "completions")] 46 | pub(crate) selected_completion: usize, 47 | } 48 | 49 | impl ConsoleUiState { 50 | /// Whether the console is currently open or not 51 | pub fn open(&self) -> bool { 52 | self.open 53 | } 54 | } 55 | 56 | fn system_time_to_chrono_utc(t: SystemTime) -> chrono::DateTime { 57 | let dur = t.duration_since(web_time::SystemTime::UNIX_EPOCH).unwrap(); 58 | let (sec, nsec) = (dur.as_secs() as i64, dur.subsec_nanos()); 59 | 60 | chrono::Utc.timestamp_opt(sec, nsec).unwrap() 61 | } 62 | 63 | pub(crate) fn read_logs(mut logs: EventReader, mut state: ResMut) { 64 | for log_message in logs.read() { 65 | state.log.push((log_message.clone(), true)); 66 | } 67 | } 68 | 69 | pub(crate) fn open_close_ui( 70 | mut state: ResMut, 71 | key: Res>, 72 | config: Res, 73 | ) { 74 | if key.just_pressed(config.open_key) { 75 | state.open = !state.open; 76 | state.text_focus = false; 77 | } 78 | } 79 | 80 | pub(crate) fn render_ui_system( 81 | mut contexts: EguiContexts, 82 | mut commands: Commands, 83 | mut state: ResMut, 84 | key: Res>, 85 | mut hints: ResMut, 86 | config: Res, 87 | #[cfg(feature = "completions")] completions: Res, 88 | ) { 89 | egui::Window::new("Developer Console") 90 | .collapsible(false) 91 | .default_width(900.) 92 | .show(contexts.ctx_mut(), |ui| { 93 | render_ui( 94 | ui, 95 | &mut commands, 96 | &mut state, 97 | &key, 98 | &mut hints, 99 | &config, 100 | &completions, 101 | ) 102 | }); 103 | } 104 | 105 | /// The function that renders the UI of the developer console. 106 | pub fn render_ui( 107 | ui: &mut egui::Ui, 108 | commands: &mut Commands, 109 | state: &mut ConsoleUiState, 110 | key: &ButtonInput, 111 | hints: &mut CommandHints, 112 | config: &ConsoleConfig, 113 | #[cfg(feature = "completions")] completions: &AutoCompletions, 114 | ) { 115 | fn submit_command(command: &mut String, commands: &mut Commands) { 116 | if !command.trim().is_empty() { 117 | info!(name: COMMAND_MESSAGE_NAME, "{COMMAND_MESSAGE_PREFIX}{}", command.trim()); 118 | // Get the owned command string by replacing it with an empty string 119 | let command = std::mem::take(command); 120 | commands.add(ExecuteCommand(command)); 121 | } 122 | } 123 | 124 | if key.just_pressed(config.submit_key) { 125 | submit_command(&mut state.command, commands); 126 | } 127 | 128 | completions::change_selected_completion(ui, state, &completions); 129 | 130 | // A General rule when creating layouts in egui is to place elements which fill remaining space last. 131 | // Since immediate mode ui can't predict the final sizes of widgets until they've already been drawn 132 | 133 | // Thus we create a bottom panel first, where our text edit and submit button resides. 134 | egui::TopBottomPanel::bottom("bottom panel") 135 | .frame(egui::Frame::none().outer_margin(egui::Margin { 136 | left: 5.0, 137 | right: 5.0, 138 | top: 5. + 6., 139 | bottom: 5.0, 140 | })) 141 | .show_inside(ui, |ui| { 142 | let text_edit_id = egui::Id::new("text_edit"); 143 | 144 | //We can use a right to left layout, so we can place the text input last and tell it to fill all remaining space 145 | ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { 146 | // ui.button is a shorthand command, a similar command exists for text edits, but this is how to manually construct a widget. 147 | // doing this also allows access to more options of the widget, rather than being stuck with the default the shorthand picks. 148 | if ui.button("Submit").clicked() { 149 | submit_command(&mut state.command, commands); 150 | 151 | // Return keyboard focus to the text edit control. 152 | ui.ctx().memory_mut(|mem| mem.request_focus(text_edit_id)); 153 | } 154 | 155 | #[cfg_attr(not(feature = "completions"), allow(unused_variables))] 156 | let text_edit = egui::TextEdit::singleline(&mut state.command) 157 | .id(text_edit_id) 158 | .desired_width(ui.available_width()) 159 | .margin(egui::Vec2::splat(4.0)) 160 | .font(config.theme.font.clone()) 161 | .lock_focus(true) 162 | .show(ui); 163 | 164 | // Display completions if the "completions" feature is enabled 165 | #[cfg(feature = "completions")] 166 | completions::completions( 167 | text_edit, 168 | text_edit_id, 169 | state, 170 | ui, 171 | commands, 172 | &completions, 173 | &config, 174 | ); 175 | 176 | // Each time we open the console, we want to set focus to the text edit control. 177 | if !state.text_focus { 178 | state.text_focus = true; 179 | ui.ctx().memory_mut(|mem| mem.request_focus(text_edit_id)); 180 | } 181 | }); 182 | }); 183 | // Now we can fill the remaining minutespace with a scrollarea, which has only the vertical scrollbar enabled and expands to be as big as possible. 184 | egui::ScrollArea::new([false, true]) 185 | .auto_shrink([false, true]) 186 | .show(ui, |ui| { 187 | ui.vertical(|ui| { 188 | let mut command_index = 0; 189 | 190 | for (id, (message, is_new)) in state.log.iter_mut().enumerate() { 191 | add_log(ui, id, message, is_new, hints, &config, &mut command_index); 192 | } 193 | }); 194 | }); 195 | } 196 | 197 | fn add_log( 198 | ui: &mut egui::Ui, 199 | id: usize, 200 | event: &LogMessage, 201 | is_new: &mut bool, 202 | hints: &mut CommandHints, 203 | config: &ConsoleConfig, 204 | command_index: &mut usize, 205 | ) { 206 | ui.push_id(id, |ui| { 207 | let time_utc = system_time_to_chrono_utc(event.time); 208 | let time: DateTime = time_utc.into(); 209 | 210 | let text = format_line(time, config, event, *is_new, command_index, hints); 211 | let label = ui.label(text); 212 | 213 | if *is_new { 214 | label.scroll_to_me(Some(egui::Align::Max)); 215 | *is_new = false; 216 | } 217 | label.on_hover_ui(|ui| { 218 | let mut text = LayoutJob::default(); 219 | text.append("Time: ", 0.0, config.theme.format_text()); 220 | text.append( 221 | &time.format("%x %X %:z").to_string(), 222 | 0.0, 223 | config.theme.format_dark(), 224 | ); 225 | text.append("\nTime (UTC): ", 0.0, config.theme.format_text()); 226 | text.append( 227 | &time_utc.to_rfc3339_opts(chrono::SecondsFormat::Micros, true), 228 | 0.0, 229 | config.theme.format_dark(), 230 | ); 231 | text.append("\nName: ", 0.0, config.theme.format_text()); 232 | text.append(event.name, 0.0, config.theme.format_dark()); 233 | text.append("\nTarget : ", 0.0, config.theme.format_text()); 234 | text.append(event.target, 0.0, config.theme.format_dark()); 235 | text.append("\nModule Path: ", 0.0, config.theme.format_text()); 236 | if let Some(module_path) = event.module_path { 237 | text.append(module_path, 0.0, config.theme.format_dark()); 238 | } else { 239 | text.append("(Unknown)", 0.0, config.theme.format_dark()); 240 | } 241 | text.append("\nFile: ", 0.0, config.theme.format_text()); 242 | if let (Some(file), Some(line)) = (event.file, event.line) { 243 | text.append(&format!("{file}:{line}"), 0.0, config.theme.format_dark()); 244 | } else { 245 | text.append("(Unknown)", 0.0, config.theme.format_dark()); 246 | } 247 | 248 | ui.label(text); 249 | }); 250 | }); 251 | } 252 | 253 | fn format_line( 254 | time: DateTime, 255 | config: &ConsoleConfig, 256 | LogMessage { 257 | message, 258 | name, 259 | level, 260 | .. 261 | }: &LogMessage, 262 | new: bool, 263 | command_index: &mut usize, 264 | hints: &mut CommandHints, 265 | ) -> LayoutJob { 266 | let mut text = LayoutJob::default(); 267 | text.append( 268 | &time.format("%H:%M ").to_string(), 269 | 0.0, 270 | config.theme.format_dark(), 271 | ); 272 | match *name { 273 | COMMAND_MESSAGE_NAME => { 274 | if new { 275 | hints.reset_hint_added(); 276 | } 277 | let hints = &hints[*command_index]; 278 | 279 | *command_index += 1; 280 | 281 | let message_stripped = message 282 | .strip_prefix(COMMAND_MESSAGE_PREFIX) 283 | .unwrap_or(message); 284 | text.append(COMMAND_MESSAGE_PREFIX, 0.0, config.theme.format_dark()); 285 | // TODO: Handle more than just the first element 286 | if let Some(hint) = hints.first() { 287 | text.append( 288 | &message_stripped[..hint.span.start], 289 | 0., 290 | config.theme.format_text(), 291 | ); 292 | text.append( 293 | &message_stripped[hint.span.start..hint.span.end], 294 | 0., 295 | TextFormat { 296 | underline: Stroke::new(1.0, config.theme.error.to_color32()), 297 | ..config.theme.format_text() 298 | }, 299 | ); 300 | text.append( 301 | &message_stripped[hint.span.end..], 302 | 0., 303 | config.theme.format_text(), 304 | ); 305 | return text; 306 | } 307 | text.append(message_stripped, 0.0, config.theme.format_text()); 308 | text 309 | } 310 | COMMAND_RESULT_NAME => { 311 | text.append(COMMAND_RESULT_PREFIX, 0.0, config.theme.format_dark()); 312 | text.append( 313 | message 314 | .strip_prefix(COMMAND_RESULT_PREFIX) 315 | .unwrap_or(message), 316 | 0.0, 317 | config.theme.format_text(), 318 | ); 319 | text 320 | } 321 | _ => { 322 | text.append(level.as_str(), 0.0, config.theme.format_level(*level)); 323 | text.append(&format!(" {message}"), 0.0, config.theme.format_text()); 324 | 325 | text 326 | } 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /src/builtin_parser/runner/environment.rs: -------------------------------------------------------------------------------- 1 | //! Environment and function registration 2 | 3 | use std::collections::HashMap; 4 | use std::fmt::Debug; 5 | 6 | use crate::builtin_parser::SpanExtension; 7 | use bevy::ecs::world::World; 8 | use bevy::log::warn; 9 | use bevy::reflect::TypeRegistration; 10 | use logos::Span; 11 | 12 | use super::super::parser::Expression; 13 | use super::super::Spanned; 14 | use super::error::EvalError; 15 | use super::unique_rc::UniqueRc; 16 | use super::{eval_expression, stdlib, EvalParams, Value}; 17 | 18 | /// Macro for mass registering functions. 19 | /// 20 | /// ``` 21 | /// fn a() {} 22 | /// fn b() {} 23 | /// fn c() {} 24 | /// 25 | /// # use bevy_dev_console::register; 26 | /// # let mut environment = bevy_dev_console::builtin_parser::Environment::default(); 27 | /// register!(environment => { 28 | /// fn a; 29 | /// fn b; 30 | /// fn c; 31 | /// }); 32 | /// ``` 33 | #[macro_export] 34 | macro_rules! register { 35 | { 36 | $environment:expr => fn $fn_name:ident; 37 | } => { 38 | $environment 39 | .register_fn(stringify!($fn_name), $fn_name) 40 | }; 41 | { 42 | $environment:expr => { 43 | $( 44 | fn $fn_name:ident $(as $renamed:expr)?; 45 | )* 46 | } 47 | } => { 48 | $( 49 | #[allow(unused_mut, unused_assignments)] 50 | let mut name = stringify!($fn_name); 51 | $(name = $renamed;)? 52 | 53 | $environment.register_fn(name, $fn_name); 54 | )* 55 | }; 56 | } 57 | 58 | /// Get around implementation of Result causing stupid errors 59 | pub(super) struct ResultContainer(pub Result); 60 | 61 | impl> From for ResultContainer { 62 | fn from(value: T) -> Self { 63 | ResultContainer(Ok(value.into())) 64 | } 65 | } 66 | impl From> for Result { 67 | fn from(ResultContainer(result): ResultContainer) -> Self { 68 | result 69 | } 70 | } 71 | impl, E> From> for ResultContainer { 72 | fn from(result: Result) -> Self { 73 | ResultContainer(result.map(|v| v.into())) 74 | } 75 | } 76 | /// A parameter in a [`Function`]. 77 | pub trait FunctionParam: Sized { 78 | /// TODO: Add `Self` as default when gets merged 79 | type Item<'world, 'env, 'reg>; 80 | /// Whether this parameter requires a [`Spanned`]. 81 | /// If `false` then `FunctionParam::get`'s `value` will be [`None`], and vice versa. 82 | const USES_VALUE: bool; 83 | 84 | fn get<'world, 'env, 'reg>( 85 | value: Option>, 86 | world: &mut Option<&'world mut World>, 87 | environment: &mut Option<&'env mut Environment>, 88 | registrations: &'reg [&'reg TypeRegistration], 89 | ) -> Result, EvalError>; 90 | } 91 | 92 | pub type FunctionType = dyn FnMut(Vec>, EvalParams) -> Result; 93 | pub struct Function { 94 | pub argument_count: usize, 95 | pub body: Box, 96 | } 97 | impl Debug for Function { 98 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 99 | f.debug_struct("Function") 100 | .field("argument_count", &self.argument_count) 101 | .finish_non_exhaustive() 102 | } 103 | } 104 | 105 | /// Trait that represents a [`Fn`] that can be turned into a [`Function`]. 106 | pub trait IntoFunction { 107 | fn into_function(self) -> Function; 108 | } 109 | 110 | macro_rules! impl_into_function { 111 | ( 112 | $($( 113 | $params:ident 114 | ),+)? 115 | ) => { 116 | #[allow(non_snake_case)] 117 | impl IntoFunction<( $($($params,)+)? )> for F 118 | where 119 | for<'a, 'world, 'env, 'reg> &'a mut F: 120 | FnMut( $($($params),*)? ) -> R + 121 | FnMut( $($(<$params as FunctionParam>::Item<'world, 'env, 'reg>),*)? ) -> R, 122 | R: Into>, 123 | { 124 | fn into_function(mut self) -> Function { 125 | #[allow(unused_variables, unused_mut)] 126 | let body = Box::new(move |args: Vec>, params: EvalParams| { 127 | let EvalParams { 128 | world, 129 | environment, 130 | registrations, 131 | } = params; 132 | let mut args = args.into_iter().map(|expr| { 133 | Ok(Spanned { 134 | span: expr.span.clone(), 135 | value: eval_expression( 136 | expr, 137 | EvalParams { 138 | world, 139 | environment, 140 | registrations, 141 | } 142 | )? 143 | }) 144 | }).collect::, EvalError>>()?.into_iter(); 145 | let world = &mut Some(world); 146 | let environment = &mut Some(environment); 147 | 148 | #[allow(clippy::too_many_arguments)] 149 | fn call_inner>, $($($params),*)?>( 150 | mut f: impl FnMut($($($params),*)?) -> R, 151 | $($($params: $params),*)? 152 | ) -> R { 153 | f($($($params),*)?) 154 | } 155 | call_inner( 156 | &mut self, 157 | $($({ 158 | let arg = if $params::USES_VALUE { 159 | Some(args.next().unwrap()) 160 | } else { 161 | None 162 | }; 163 | 164 | let res = $params::get( 165 | arg, 166 | world, 167 | environment, 168 | registrations 169 | )?; 170 | 171 | res 172 | }),+)? 173 | ) 174 | .into().into() 175 | }); 176 | 177 | let argument_count = $($( 178 | $params::USES_VALUE as usize + 179 | )+)? 0; 180 | 181 | Function { body, argument_count } 182 | } 183 | } 184 | } 185 | } 186 | impl_into_function!(); 187 | impl_into_function!(T1); 188 | impl_into_function!(T1, T2); 189 | impl_into_function!(T1, T2, T3); 190 | impl_into_function!(T1, T2, T3, T4); 191 | impl_into_function!(T1, T2, T3, T4, T5); 192 | impl_into_function!(T1, T2, T3, T4, T5, T6); 193 | impl_into_function!(T1, T2, T3, T4, T5, T6, T7); 194 | impl_into_function!(T1, T2, T3, T4, T5, T6, T7, T8); 195 | 196 | /// A variable inside the [`Environment`]. 197 | #[derive(Debug)] 198 | pub enum Variable { 199 | Unmoved(UniqueRc), 200 | Moved, 201 | Function(Function), 202 | } 203 | 204 | /// The environment stores all variables and functions. 205 | pub struct Environment { 206 | pub(crate) parent: Option>, 207 | pub(crate) variables: HashMap, 208 | } 209 | impl Default for Environment { 210 | fn default() -> Self { 211 | let mut env = Self { 212 | parent: None, 213 | variables: HashMap::new(), 214 | }; 215 | 216 | stdlib::register(&mut env); 217 | 218 | env 219 | } 220 | } 221 | 222 | impl Environment { 223 | /// Set a variable. 224 | pub fn set(&mut self, name: impl Into, value: UniqueRc) { 225 | self.variables.insert(name.into(), Variable::Unmoved(value)); 226 | } 227 | 228 | /// Returns a reference to a function if it exists. 229 | pub fn get_function(&self, name: &str) -> Option<&Function> { 230 | let (env, _) = self.resolve(name, 0..0).ok()?; 231 | 232 | match env.variables.get(name) { 233 | Some(Variable::Function(function)) => Some(function), 234 | _ => None, 235 | } 236 | } 237 | 238 | pub(crate) fn function_scope( 239 | &mut self, 240 | name: &str, 241 | function: impl FnOnce(&mut Self, &mut Function) -> T, 242 | ) -> T { 243 | let (env, _) = self.resolve_mut(name, 0..0).unwrap(); 244 | 245 | let return_result; 246 | let var = env.variables.get_mut(name); 247 | let fn_obj = match var { 248 | Some(Variable::Function(_)) => { 249 | let Variable::Function(mut fn_obj) = 250 | std::mem::replace(var.unwrap(), Variable::Moved) 251 | else { 252 | unreachable!() 253 | }; 254 | 255 | return_result = function(env, &mut fn_obj); 256 | 257 | fn_obj 258 | } 259 | _ => unreachable!(), 260 | }; 261 | 262 | let var = env.variables.get_mut(name); 263 | let _ = std::mem::replace(var.unwrap(), Variable::Function(fn_obj)); 264 | 265 | return_result 266 | } 267 | /// Returns a reference to a variable. 268 | pub fn get(&self, name: &str, span: Span) -> Result<&UniqueRc, EvalError> { 269 | let (env, span) = self.resolve(name, span)?; 270 | 271 | match env.variables.get(name) { 272 | Some(Variable::Unmoved(value)) => Ok(value), 273 | Some(Variable::Moved) => Err(EvalError::VariableMoved(span.wrap(name.to_string()))), 274 | Some(Variable::Function(_)) => Err(EvalError::ExpectedVariableGotFunction( 275 | span.wrap(name.to_owned()), 276 | )), 277 | None => Err(EvalError::VariableNotFound(span.wrap(name.to_string()))), 278 | } 279 | } 280 | 281 | /// "Moves" a variable, giving you ownership over it. 282 | /// 283 | /// However it will no longer be able to be used unless it's a [`Value::None`], 284 | /// [`Value::Boolean`], or [`Value::Number`] in which case it will be copied. 285 | pub fn move_var(&mut self, name: &str, span: Span) -> Result { 286 | let (env, span) = self.resolve_mut(name, span)?; 287 | 288 | match env.variables.get_mut(name) { 289 | Some(Variable::Moved) => Err(EvalError::VariableMoved(span.wrap(name.to_string()))), 290 | Some(Variable::Function(_)) => Err(EvalError::ExpectedVariableGotFunction( 291 | span.wrap(name.to_owned()), 292 | )), 293 | Some(variable_reference) => { 294 | let Variable::Unmoved(reference) = variable_reference else { 295 | unreachable!() 296 | }; 297 | // This is a pretty bad way of handling something similar to rust's [`Copy`] trait but whatever. 298 | match &*reference.borrow_inner().borrow() { 299 | Value::None => return Ok(Value::None), 300 | Value::Boolean(bool) => return Ok(Value::Boolean(*bool)), 301 | Value::Number(number) => return Ok(Value::Number(*number)), 302 | _ => {} 303 | }; 304 | let Variable::Unmoved(value) = 305 | std::mem::replace(variable_reference, Variable::Moved) 306 | else { 307 | unreachable!() 308 | }; 309 | Ok(value.into_inner()) 310 | } 311 | None => Err(EvalError::VariableNotFound(span.wrap(name.to_string()))), 312 | } 313 | } 314 | 315 | fn resolve(&self, name: &str, span: Span) -> Result<(&Self, Span), EvalError> { 316 | if self.variables.contains_key(name) { 317 | return Ok((self, span)); 318 | } 319 | 320 | match &self.parent { 321 | Some(parent) => parent.resolve(name, span), 322 | None => Err(EvalError::VariableNotFound(span.wrap(name.to_string()))), 323 | } 324 | } 325 | fn resolve_mut(&mut self, name: &str, span: Span) -> Result<(&mut Self, Span), EvalError> { 326 | if self.variables.contains_key(name) { 327 | return Ok((self, span)); 328 | } 329 | 330 | match &mut self.parent { 331 | Some(parent) => parent.resolve_mut(name, span), 332 | None => Err(EvalError::VariableNotFound(span.wrap(name.to_string()))), 333 | } 334 | } 335 | 336 | /// Registers a function for use inside the language. 337 | /// 338 | /// All parameters must implement [`FunctionParam`]. 339 | /// There is a limit of 8 parameters. 340 | /// 341 | /// The return value of the function must implement [`Into`] 342 | /// 343 | /// You should take a look at the [Standard Library](super::stdlib) for examples. 344 | pub fn register_fn( 345 | &mut self, 346 | name: impl Into, 347 | function: impl IntoFunction, 348 | ) -> &mut Self { 349 | let name = name.into(); 350 | if self.variables.contains_key(&name) { 351 | warn!("Function {name} declared twice."); 352 | } 353 | self.variables 354 | .insert(name, Variable::Function(function.into_function())); 355 | 356 | self 357 | } 358 | /// Iterate over all the variables and functions in the current scope of the environment. 359 | /// 360 | /// Does not include variables and functions from higher scopes. 361 | pub fn iter(&self) -> std::collections::hash_map::Iter { 362 | self.variables.iter() 363 | } 364 | } 365 | -------------------------------------------------------------------------------- /src/builtin_parser/number.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | use std::ops::*; 3 | 4 | use bevy::reflect::Reflect; 5 | use logos::Span; 6 | 7 | use super::{EvalError, SpanExtension, Spanned}; 8 | 9 | /// An enum that contains any type of number. 10 | /// 11 | /// The [`Integer`](Number::Integer) and [`Float`](Number::Float) types 12 | /// are generic types that then get downcasted when they first interact 13 | /// with a concrete type. (i.e. calling a function, etc) 14 | #[derive(Debug, Clone, Copy)] 15 | #[allow(missing_docs, non_camel_case_types)] 16 | pub enum Number { 17 | /// Generic integer that can get downcasted. 18 | Integer(i128), 19 | /// Generic float that can get downcasted to a [`f64`] and [`f32`] 20 | Float(f64), 21 | 22 | u8(u8), 23 | u16(u16), 24 | u32(u32), 25 | u64(u64), 26 | usize(usize), 27 | i8(i8), 28 | i16(i16), 29 | i32(i32), 30 | i64(i64), 31 | isize(isize), 32 | f32(f32), 33 | f64(f64), 34 | } 35 | 36 | impl Number { 37 | /// Converts this into a [`Box`](Reflect). 38 | pub fn reflect(self, span: Span, ty: &str) -> Result, EvalError> { 39 | match self { 40 | Number::u8(number) => Ok(Box::new(number)), 41 | Number::u16(number) => Ok(Box::new(number)), 42 | Number::u32(number) => Ok(Box::new(number)), 43 | Number::u64(number) => Ok(Box::new(number)), 44 | Number::usize(number) => Ok(Box::new(number)), 45 | Number::i8(number) => Ok(Box::new(number)), 46 | Number::i16(number) => Ok(Box::new(number)), 47 | Number::i32(number) => Ok(Box::new(number)), 48 | Number::i64(number) => Ok(Box::new(number)), 49 | Number::isize(number) => Ok(Box::new(number)), 50 | Number::f32(number) => Ok(Box::new(number)), 51 | Number::f64(number) => Ok(Box::new(number)), 52 | Number::Integer(number) => match ty { 53 | "u8" => Ok(Box::new(number as u8)), 54 | "u16" => Ok(Box::new(number as u16)), 55 | "u32" => Ok(Box::new(number as u32)), 56 | "u64" => Ok(Box::new(number as u64)), 57 | "usize" => Ok(Box::new(number as usize)), 58 | "i8" => Ok(Box::new(number as i8)), 59 | "i16" => Ok(Box::new(number as i16)), 60 | "i32" => Ok(Box::new(number as i32)), 61 | "i64" => Ok(Box::new(number as i64)), 62 | "isize" => Ok(Box::new(number as isize)), 63 | ty => Err(EvalError::IncompatibleReflectTypes { 64 | expected: "integer".to_string(), 65 | actual: ty.to_string(), 66 | span, 67 | }), 68 | }, 69 | Number::Float(number) => match ty { 70 | "f32" => Ok(Box::new(number as f32)), 71 | "f64" => Ok(Box::new(number)), 72 | ty => Err(EvalError::IncompatibleReflectTypes { 73 | expected: "float".to_string(), 74 | actual: ty.to_string(), 75 | span, 76 | }), 77 | }, 78 | } 79 | } 80 | 81 | /// Returns the kind of [`Number`] as a [string slice](str). 82 | /// You may want to use [`natural_kind`](Self::natural_kind) 83 | /// instead for more natural sounding error messages 84 | pub const fn kind(&self) -> &'static str { 85 | match self { 86 | Number::Float(_) => "float", 87 | Number::Integer(_) => "integer", 88 | Number::u8(_) => "u8", 89 | Number::u16(_) => "u16", 90 | Number::u32(_) => "u32", 91 | Number::u64(_) => "u64", 92 | Number::usize(_) => "usize", 93 | Number::i8(_) => "i8", 94 | Number::i16(_) => "i16", 95 | Number::i32(_) => "i32", 96 | Number::i64(_) => "i64", 97 | Number::isize(_) => "usize", 98 | Number::f32(_) => "f32", 99 | Number::f64(_) => "f64", 100 | } 101 | } 102 | 103 | /// Returns the kind of [`Number`] as a [string slice](str) with an `a` or `an` prepended to it. 104 | /// Used for more natural sounding error messages. 105 | pub const fn natural_kind(&self) -> &'static str { 106 | match self { 107 | Number::Float(_) => "a float", 108 | Number::Integer(_) => "an integer", 109 | Number::u8(_) => "a u8", 110 | Number::u16(_) => "a u16", 111 | Number::u32(_) => "a u32", 112 | Number::u64(_) => "a u64", 113 | Number::usize(_) => "a usize", 114 | Number::i8(_) => "a i8", 115 | Number::i16(_) => "a i16", 116 | Number::i32(_) => "a i32", 117 | Number::i64(_) => "a i64", 118 | Number::isize(_) => "a usize", 119 | Number::f32(_) => "a f32", 120 | Number::f64(_) => "a f64", 121 | } 122 | } 123 | } 124 | 125 | impl Display for Number { 126 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 127 | match self { 128 | Number::Float(number) => write!(f, "{number} (float)"), 129 | Number::Integer(number) => write!(f, "{number} (integer)"), 130 | Number::u8(number) => write!(f, "{number} (u8)"), 131 | Number::u16(number) => write!(f, "{number} (u16)"), 132 | Number::u32(number) => write!(f, "{number} (u32)"), 133 | Number::u64(number) => write!(f, "{number} (u64)"), 134 | Number::usize(number) => write!(f, "{number} (usize)"), 135 | Number::i8(number) => write!(f, "{number} (i8)"), 136 | Number::i16(number) => write!(f, "{number} (i16)"), 137 | Number::i32(number) => write!(f, "{number} (i32)"), 138 | Number::i64(number) => write!(f, "{number} (i64)"), 139 | Number::isize(number) => write!(f, "{number} (isize)"), 140 | Number::f32(number) => write!(f, "{number} (f32)"), 141 | Number::f64(number) => write!(f, "{number} (f64)"), 142 | } 143 | } 144 | } 145 | 146 | macro_rules! impl_op { 147 | ($fn:ident, $op:tt, $checked:ident)=> { 148 | impl Number { 149 | #[doc = concat!("Performs the `", stringify!($op), "` calculation.")] 150 | pub fn $fn(left: Number, right: Number, span: Span) -> Result { 151 | let op_err = || EvalError::InvalidOperation { 152 | left, 153 | right, 154 | operation: stringify!($fn), 155 | span: span.clone(), 156 | }; 157 | match (left, right) { 158 | (Number::u8(left), Number::u8(right)) => Ok(Number::u8(left.$checked(right).ok_or_else(op_err)?)), 159 | (Number::u16(left), Number::u16(right)) => Ok(Number::u16(left.$checked(right).ok_or_else(op_err)?)), 160 | (Number::u32(left), Number::u32(right)) => Ok(Number::u32(left.$checked(right).ok_or_else(op_err)?)), 161 | (Number::u64(left), Number::u64(right)) => Ok(Number::u64(left.$checked(right).ok_or_else(op_err)?)), 162 | (Number::usize(left), Number::usize(right)) => Ok(Number::usize(left.$checked(right).ok_or_else(op_err)?)), 163 | (Number::i8(left), Number::i8(right)) => Ok(Number::i8(left.$checked(right).ok_or_else(op_err)?)), 164 | (Number::i16(left), Number::i16(right)) => Ok(Number::i16(left.$checked(right).ok_or_else(op_err)?)), 165 | (Number::i32(left), Number::i32(right)) => Ok(Number::i32(left.$checked(right).ok_or_else(op_err)?)), 166 | (Number::i64(left), Number::i64(right)) => Ok(Number::i64(left.$checked(right).ok_or_else(op_err)?)), 167 | (Number::isize(left), Number::isize(right)) => Ok(Number::isize(left.$checked(right).ok_or_else(op_err)?)), 168 | (Number::f32(left), Number::f32(right)) => Ok(Number::f32(left $op right)), 169 | (Number::f64(left), Number::f64(right)) => Ok(Number::f64(left $op right)), 170 | 171 | (Number::Integer(left), Number::u8(right)) => Ok(Number::u8((left as u8).$checked(right).ok_or_else(op_err)?)), 172 | (Number::Integer(left), Number::u16(right)) => Ok(Number::u16((left as u16).$checked(right).ok_or_else(op_err)?)), 173 | (Number::Integer(left), Number::u32(right)) => Ok(Number::u32((left as u32).$checked(right).ok_or_else(op_err)?)), 174 | (Number::Integer(left), Number::u64(right)) => Ok(Number::u64((left as u64).$checked(right).ok_or_else(op_err)?)), 175 | (Number::Integer(left), Number::usize(right)) => Ok(Number::usize((left as usize).$checked(right).ok_or_else(op_err)?)), 176 | (Number::Integer(left), Number::i8(right)) => Ok(Number::i8((left as i8).$checked(right).ok_or_else(op_err)?)), 177 | (Number::Integer(left), Number::i16(right)) => Ok(Number::i16((left as i16).$checked(right).ok_or_else(op_err)?)), 178 | (Number::Integer(left), Number::i32(right)) => Ok(Number::i32((left as i32).$checked(right).ok_or_else(op_err)?)), 179 | (Number::Integer(left), Number::i64(right)) => Ok(Number::i64((left as i64).$checked(right).ok_or_else(op_err)?)), 180 | (Number::Integer(left), Number::isize(right)) => Ok(Number::isize((left as isize).$checked(right).ok_or_else(op_err)?)), 181 | (Number::Integer(left), Number::Integer(right)) => Ok(Number::Integer(left.$checked(right).ok_or_else(op_err)?)), 182 | (Number::u8(left), Number::Integer(right)) => Ok(Number::u8(left.$checked(right as u8).ok_or_else(op_err)?)), 183 | (Number::u16(left), Number::Integer(right)) => Ok(Number::u16(left.$checked(right as u16).ok_or_else(op_err)?)), 184 | (Number::u32(left), Number::Integer(right)) => Ok(Number::u32(left.$checked(right as u32).ok_or_else(op_err)?)), 185 | (Number::u64(left), Number::Integer(right)) => Ok(Number::u64(left.$checked(right as u64).ok_or_else(op_err)?)), 186 | (Number::usize(left), Number::Integer(right)) => Ok(Number::usize(left.$checked(right as usize).ok_or_else(op_err)?)), 187 | (Number::i8(left), Number::Integer(right)) => Ok(Number::i8(left.$checked(right as i8).ok_or_else(op_err)?)), 188 | (Number::i16(left), Number::Integer(right)) => Ok(Number::i16(left.$checked(right as i16).ok_or_else(op_err)?)), 189 | (Number::i32(left), Number::Integer(right)) => Ok(Number::i32(left.$checked(right as i32).ok_or_else(op_err)?)), 190 | (Number::i64(left), Number::Integer(right)) => Ok(Number::i64(left.$checked(right as i64).ok_or_else(op_err)?)), 191 | (Number::isize(left), Number::Integer(right)) => Ok(Number::isize(left.$checked(right as isize).ok_or_else(op_err)?)), 192 | 193 | (Number::Float(left), Number::f32(right)) => Ok(Number::f32(left as f32 $op right)), 194 | (Number::Float(left), Number::f64(right)) => Ok(Number::f64(left as f64 $op right)), 195 | (Number::Float(left), Number::Float(right)) => Ok(Number::Float(left $op right)), 196 | (Number::f32(left), Number::Float(right)) => Ok(Number::f32(left $op right as f32)), 197 | (Number::f64(left), Number::Float(right)) => Ok(Number::f64(left $op right as f64)), 198 | _ => Err(EvalError::IncompatibleNumberTypes { 199 | left: left.natural_kind(), 200 | right: right.natural_kind(), 201 | span 202 | }) 203 | } 204 | } 205 | } 206 | }; 207 | } 208 | 209 | impl_op!(add, +, checked_add); 210 | impl_op!(sub, -, checked_sub); 211 | impl_op!(mul, *, checked_mul); 212 | impl_op!(div, /, checked_div); 213 | impl_op!(rem, %, checked_rem); 214 | 215 | macro_rules! impl_op_spanned { 216 | ($trait:ident, $method:ident) => { 217 | impl $trait for Spanned { 218 | type Output = Result; 219 | fn $method(self, rhs: Self) -> Self::Output { 220 | let span = self.span.join(rhs.span); 221 | 222 | Number::$method(self.value, rhs.value, span) 223 | } 224 | } 225 | }; 226 | } 227 | 228 | impl_op_spanned!(Add, add); 229 | impl_op_spanned!(Sub, sub); 230 | impl_op_spanned!(Mul, mul); 231 | impl_op_spanned!(Rem, rem); 232 | 233 | impl Number { 234 | /// Performs the unary `-` operation. 235 | pub fn neg(self, span: Span) -> Result { 236 | match self { 237 | Number::u8(_) => Err(EvalError::CannotNegateUnsignedInteger(Spanned { 238 | span, 239 | value: self, 240 | })), 241 | Number::u16(_) => Err(EvalError::CannotNegateUnsignedInteger(Spanned { 242 | span, 243 | value: self, 244 | })), 245 | Number::u32(_) => Err(EvalError::CannotNegateUnsignedInteger(Spanned { 246 | span, 247 | value: self, 248 | })), 249 | Number::u64(_) => Err(EvalError::CannotNegateUnsignedInteger(Spanned { 250 | span, 251 | value: self, 252 | })), 253 | Number::usize(_) => Err(EvalError::CannotNegateUnsignedInteger(Spanned { 254 | span, 255 | value: self, 256 | })), 257 | Number::i8(number) => Ok(Number::i8(-number)), 258 | Number::i16(number) => Ok(Number::i16(-number)), 259 | Number::i32(number) => Ok(Number::i32(-number)), 260 | Number::i64(number) => Ok(Number::i64(-number)), 261 | Number::isize(number) => Ok(Number::isize(-number)), 262 | Number::f32(number) => Ok(Number::f32(-number)), 263 | Number::f64(number) => Ok(Number::f64(-number)), 264 | Number::Float(number) => Ok(Number::Float(-number)), 265 | Number::Integer(number) => Ok(Number::Integer(-number)), 266 | } 267 | } 268 | } 269 | 270 | macro_rules! from_primitive { 271 | ($primitive:ident) => { 272 | impl From<$primitive> for Number { 273 | fn from(value: $primitive) -> Self { 274 | Number::$primitive(value) 275 | } 276 | } 277 | }; 278 | } 279 | 280 | from_primitive!(u8); 281 | from_primitive!(u16); 282 | from_primitive!(u32); 283 | from_primitive!(u64); 284 | from_primitive!(i8); 285 | from_primitive!(i16); 286 | from_primitive!(i32); 287 | from_primitive!(i64); 288 | from_primitive!(f32); 289 | from_primitive!(f64); 290 | -------------------------------------------------------------------------------- /src/builtin_parser/runner.rs: -------------------------------------------------------------------------------- 1 | //! Executes the abstract syntax tree. 2 | 3 | use environment::Environment; 4 | use std::collections::HashMap; 5 | 6 | use bevy::prelude::*; 7 | use bevy::reflect::{ 8 | DynamicEnum, DynamicTuple, ReflectMut, TypeInfo, TypeRegistration, VariantInfo, 9 | }; 10 | 11 | use crate::ui::COMMAND_RESULT_NAME; 12 | 13 | use self::error::EvalError; 14 | use self::member::{eval_member_expression, eval_path, Path}; 15 | use self::reflection::{object_to_dynamic_struct, CreateRegistration, IntoResource}; 16 | use self::unique_rc::UniqueRc; 17 | 18 | use super::parser::{Ast, Expression, Operator}; 19 | use super::{Number, SpanExtension, Spanned}; 20 | 21 | pub(super) mod environment; 22 | pub(super) mod error; 23 | pub(super) mod member; 24 | pub(super) mod reflection; 25 | pub(super) mod stdlib; 26 | pub(super) mod unique_rc; 27 | pub(super) mod value; 28 | 29 | pub use value::Value; 30 | 31 | /// Temporary macro that prevents panicking by replacing the [`todo!`] panic with an error message. 32 | macro_rules! todo_error { 33 | () => { 34 | Err(EvalError::Custom { 35 | text: concat!("todo error invoked at ", file!(), ":", line!(), ":", column!()).into(), 36 | span: 0..0 37 | })? 38 | }; 39 | ($($arg:tt)+) => { 40 | Err(EvalError::Custom { 41 | text: format!(concat!("todo error invoked at ", file!(), ":", line!(), ":", column!(), " : {}"), format_args!($($arg)+)).into(), 42 | span: 0..0 43 | })? 44 | }; 45 | } 46 | // This makes `todo_error` accessible to the runners submodules 47 | use todo_error; 48 | 49 | /// Container for every value needed by evaluation functions. 50 | pub struct EvalParams<'world, 'env, 'reg> { 51 | world: &'world mut World, 52 | environment: &'env mut Environment, 53 | registrations: &'reg [&'reg TypeRegistration], 54 | } 55 | 56 | #[derive(Debug)] 57 | pub enum ExecutionError { 58 | NoEnvironment, 59 | NoTypeRegistry, 60 | Eval(EvalError), 61 | } 62 | 63 | impl std::fmt::Display for ExecutionError { 64 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 65 | match self { 66 | Self::NoEnvironment => write!( 67 | f, 68 | "Environment resource doesn't exist, not executing command." 69 | ), 70 | Self::NoTypeRegistry => write!( 71 | f, 72 | "The AppTypeRegistry doesn't exist, not executing command. " 73 | ), 74 | Self::Eval(run_error) => ::fmt(run_error, f), 75 | } 76 | } 77 | } 78 | impl std::error::Error for ExecutionError { 79 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 80 | match self { 81 | ExecutionError::Eval(eval) => Some(eval), 82 | _ => None, 83 | } 84 | } 85 | } 86 | impl From for ExecutionError { 87 | fn from(value: EvalError) -> Self { 88 | Self::Eval(value) 89 | } 90 | } 91 | 92 | pub fn run(ast: Ast, world: &mut World) -> Result<(), ExecutionError> { 93 | // Temporarily remove the [`Environment`] resource to gain 94 | // mutability without needing a mutable reference. 95 | let mut environment = world 96 | .remove_non_send_resource::() 97 | .ok_or(ExecutionError::NoEnvironment)?; 98 | 99 | // Same thing here (this time we are doing it because we are passing a `&mut World` to `eval_expression`) 100 | let Some(registry) = world.remove_resource::() else { 101 | // Make sure to re-insert the resource on failure 102 | world.insert_non_send_resource(environment); 103 | 104 | return Err(ExecutionError::NoTypeRegistry); 105 | }; 106 | 107 | let result = (|| { 108 | let registry_read = registry.read(); 109 | 110 | let registrations: Vec<_> = registry_read 111 | .iter() 112 | .filter(|registration| { 113 | world 114 | .components() 115 | .get_resource_id(registration.type_id()) 116 | .is_some() 117 | }) 118 | .collect(); 119 | 120 | for mut statement in ast { 121 | // Automatically borrow variables 122 | statement.value = match statement.value { 123 | Expression::Variable(variable) => Expression::Borrow(Box::new(Spanned { 124 | span: statement.span.clone(), 125 | value: Expression::Variable(variable), 126 | })), 127 | expr => expr, 128 | }; 129 | 130 | let span = statement.span.clone(); 131 | let value = eval_expression( 132 | statement, 133 | EvalParams { 134 | world, 135 | environment: &mut environment, 136 | registrations: ®istrations, 137 | }, 138 | )?; 139 | 140 | match value { 141 | Value::None => {} 142 | value => { 143 | let value = value.try_format(span, world, ®istrations)?; 144 | 145 | info!(name: COMMAND_RESULT_NAME, "{}{value}", crate::ui::COMMAND_RESULT_PREFIX); 146 | } 147 | } 148 | } 149 | 150 | Ok(()) 151 | })(); 152 | 153 | // Add back the resources 154 | world.insert_resource(registry); 155 | world.insert_non_send_resource(environment); 156 | 157 | result 158 | } 159 | 160 | fn eval_expression( 161 | expr: Spanned, 162 | EvalParams { 163 | world, 164 | environment, 165 | registrations, 166 | }: EvalParams, 167 | ) -> Result { 168 | match expr.value { 169 | Expression::VarAssign { 170 | name, 171 | value: value_expr, 172 | } => match eval_path( 173 | *name, 174 | EvalParams { 175 | world, 176 | environment, 177 | registrations, 178 | }, 179 | )? 180 | .value 181 | { 182 | Path::Variable(variable) => { 183 | let value = eval_expression( 184 | *value_expr, 185 | EvalParams { 186 | world, 187 | environment, 188 | registrations, 189 | }, 190 | )?; 191 | 192 | match variable.upgrade() { 193 | Some(strong) => *strong.borrow_mut() = value, 194 | None => todo_error!("cannot "), 195 | } 196 | 197 | Ok(Value::Reference(variable)) 198 | } 199 | Path::NewVariable(variable) => { 200 | let value = eval_expression( 201 | *value_expr, 202 | EvalParams { 203 | world, 204 | environment, 205 | registrations, 206 | }, 207 | )?; 208 | let rc = UniqueRc::new(value); 209 | let weak = rc.borrow(); 210 | 211 | environment.set(variable, rc); 212 | 213 | Ok(Value::Reference(weak)) 214 | } 215 | Path::Resource(resource) => { 216 | let registration = registrations.create_registration(resource.id); 217 | let mut dyn_reflect = resource.mut_dyn_reflect(world, registration); 218 | 219 | let reflect = dyn_reflect 220 | .reflect_path_mut(resource.path.as_str()) 221 | .unwrap(); 222 | 223 | match reflect.reflect_mut() { 224 | ReflectMut::Enum(dyn_enum) => { 225 | let TypeInfo::Enum(enum_info) = registration.type_info() else { 226 | unreachable!() 227 | }; 228 | let Spanned { span, value } = *value_expr; 229 | match value { 230 | Expression::Variable(name) => { 231 | let variant_info = match enum_info.variant(&name) { 232 | Some(variant_info) => variant_info, 233 | None => { 234 | return Err(EvalError::EnumVariantNotFound(span.wrap(name))) 235 | } 236 | }; 237 | let VariantInfo::Unit(_) = variant_info else { 238 | return todo_error!("{variant_info:?}"); 239 | }; 240 | 241 | let new_enum = DynamicEnum::new(name, ()); 242 | 243 | dyn_enum.apply(&new_enum); 244 | } 245 | Expression::StructObject { name, map } => { 246 | let variant_info = match enum_info.variant(&name) { 247 | Some(variant_info) => variant_info, 248 | None => { 249 | return Err(EvalError::EnumVariantNotFound(span.wrap(name))) 250 | } 251 | }; 252 | let VariantInfo::Struct(variant_info) = variant_info else { 253 | return todo_error!("{variant_info:?}"); 254 | }; 255 | 256 | let map: HashMap<_, _> = map 257 | .into_iter() 258 | .map(|(k, v)| { 259 | let ty = match variant_info.field(&k) { 260 | Some(field) => Ok(field.type_path_table().short_path()), 261 | None => { 262 | Err(EvalError::EnumVariantStructFieldNotFound { 263 | field_name: k.clone(), 264 | variant_name: name.clone(), 265 | span: span.clone(), 266 | }) 267 | } 268 | }?; 269 | 270 | let span = v.span.clone(); 271 | 272 | Ok(( 273 | k, 274 | ( 275 | eval_expression( 276 | v, 277 | EvalParams { 278 | world, 279 | environment, 280 | registrations, 281 | }, 282 | )?, 283 | span, 284 | ty, 285 | ), 286 | )) 287 | }) 288 | .collect::>()?; 289 | 290 | let new_enum = 291 | DynamicEnum::new(name, object_to_dynamic_struct(map)?); 292 | 293 | let mut dyn_reflect = 294 | resource.mut_dyn_reflect(world, registrations); 295 | 296 | let dyn_enum = dyn_reflect 297 | .reflect_path_mut(resource.path.as_str()) 298 | .unwrap(); 299 | 300 | dyn_enum.apply(&new_enum); 301 | } 302 | Expression::StructTuple { name, tuple } => { 303 | let variant_info = match enum_info.variant(&name) { 304 | Some(variant_info) => variant_info, 305 | None => { 306 | return Err(EvalError::EnumVariantNotFound(span.wrap(name))) 307 | } 308 | }; 309 | let VariantInfo::Tuple(variant_info) = variant_info else { 310 | return todo_error!("{variant_info:?}"); 311 | }; 312 | 313 | let tuple = eval_tuple( 314 | tuple, 315 | EvalParams { 316 | world, 317 | environment, 318 | registrations, 319 | }, 320 | )?; 321 | 322 | let mut dynamic_tuple = DynamicTuple::default(); 323 | 324 | for (index, element) in tuple.into_vec().into_iter().enumerate() { 325 | let ty = match variant_info.field_at(index) { 326 | Some(field) => Ok(field.type_path_table().short_path()), 327 | None => Err(EvalError::EnumVariantTupleFieldNotFound { 328 | field_index: index, 329 | variant_name: name.clone(), 330 | span: span.clone(), 331 | }), 332 | }?; 333 | 334 | dynamic_tuple.insert_boxed( 335 | element.value.into_inner().reflect(element.span, ty)?, 336 | ); 337 | } 338 | 339 | let new_enum = DynamicEnum::new(name, dynamic_tuple); 340 | 341 | let mut dyn_reflect = 342 | resource.mut_dyn_reflect(world, registrations); 343 | 344 | let dyn_enum = dyn_reflect 345 | .reflect_path_mut(resource.path.as_str()) 346 | .unwrap(); 347 | 348 | dyn_enum.apply(&new_enum); 349 | } 350 | _ => todo_error!(), 351 | } 352 | } 353 | _ => { 354 | let span = value_expr.span.clone(); 355 | let ty = reflect.reflect_short_type_path().to_owned(); 356 | let value = eval_expression( 357 | *value_expr, 358 | EvalParams { 359 | world, 360 | environment, 361 | registrations, 362 | }, 363 | )?; 364 | let value_reflect = value.reflect(span.clone(), &ty)?; 365 | 366 | let mut dyn_reflect = resource.mut_dyn_reflect(world, registrations); 367 | 368 | let reflect = dyn_reflect 369 | .reflect_path_mut(resource.path.as_str()) 370 | .unwrap(); 371 | 372 | reflect.set(value_reflect).map_err(|value_reflect| { 373 | EvalError::IncompatibleReflectTypes { 374 | span, 375 | expected: reflect.reflect_type_path().to_string(), 376 | actual: value_reflect.reflect_type_path().to_string(), 377 | } 378 | })?; 379 | } 380 | } 381 | 382 | Ok(Value::Resource(resource)) 383 | } 384 | }, 385 | Expression::String(string) => Ok(Value::String(string)), 386 | Expression::Number(number) => Ok(Value::Number(number)), 387 | Expression::Variable(variable) => { 388 | if registrations 389 | .iter() 390 | .any(|v| v.type_info().type_path_table().short_path() == variable) 391 | { 392 | Err(EvalError::CannotMoveOutOfResource(Spanned { 393 | span: expr.span, 394 | value: variable, 395 | })) 396 | } else { 397 | environment.move_var(&variable, expr.span) 398 | } 399 | } 400 | Expression::StructObject { name, map } => { 401 | let hashmap = eval_object( 402 | map, 403 | EvalParams { 404 | world, 405 | environment, 406 | registrations, 407 | }, 408 | )?; 409 | Ok(Value::StructObject { name, map: hashmap }) 410 | } 411 | Expression::Object(map) => { 412 | let hashmap = eval_object( 413 | map, 414 | EvalParams { 415 | world, 416 | environment, 417 | registrations, 418 | }, 419 | )?; 420 | Ok(Value::Object(hashmap)) 421 | } 422 | Expression::Tuple(tuple) => { 423 | let tuple = eval_tuple( 424 | tuple, 425 | EvalParams { 426 | world, 427 | environment, 428 | registrations, 429 | }, 430 | )?; 431 | Ok(Value::Tuple(tuple)) 432 | } 433 | Expression::StructTuple { name, tuple } => { 434 | let tuple = eval_tuple( 435 | tuple, 436 | EvalParams { 437 | world, 438 | environment, 439 | registrations, 440 | }, 441 | )?; 442 | Ok(Value::StructTuple { name, tuple }) 443 | } 444 | 445 | Expression::BinaryOp { 446 | left, 447 | operator, 448 | right, 449 | } => { 450 | let left = eval_expression( 451 | *left, 452 | EvalParams { 453 | world, 454 | environment, 455 | registrations, 456 | }, 457 | )?; 458 | let right = eval_expression( 459 | *right, 460 | EvalParams { 461 | world, 462 | environment, 463 | registrations, 464 | }, 465 | )?; 466 | 467 | match (left, right) { 468 | (Value::Number(left), Value::Number(right)) => Ok(Value::Number(match operator { 469 | Operator::Add => Number::add(left, right, expr.span)?, 470 | Operator::Sub => Number::sub(left, right, expr.span)?, 471 | Operator::Mul => Number::mul(left, right, expr.span)?, 472 | Operator::Div => Number::div(left, right, expr.span)?, 473 | Operator::Mod => Number::rem(left, right, expr.span)?, 474 | })), 475 | (left, right) => todo_error!("{left:#?}, {right:#?}"), 476 | } 477 | } 478 | Expression::ForLoop { 479 | index_name, 480 | loop_count, 481 | block, 482 | } => todo_error!("for loop {index_name}, {loop_count}, {block:#?}"), 483 | Expression::Member { left, right } => eval_member_expression( 484 | *left, 485 | right, 486 | EvalParams { 487 | world, 488 | environment, 489 | registrations, 490 | }, 491 | ), 492 | Expression::UnaryOp(sub_expr) => { 493 | let span = sub_expr.span.clone(); 494 | let value = eval_expression( 495 | *sub_expr, 496 | EvalParams { 497 | world, 498 | environment, 499 | registrations, 500 | }, 501 | )?; 502 | 503 | if let Value::Number(number) = value { 504 | Ok(Value::Number(number.neg(span)?)) 505 | } else { 506 | Err(EvalError::ExpectedNumberAfterUnaryOperator(Spanned { 507 | span, 508 | value, 509 | })) 510 | } 511 | } 512 | Expression::Dereference(inner) => { 513 | if let Expression::Variable(variable) = inner.value { 514 | let var = environment.get(&variable, inner.span)?; 515 | match &*var.borrow_inner().borrow() { 516 | Value::Reference(reference) => { 517 | let reference = reference 518 | .upgrade() 519 | .ok_or(EvalError::ReferenceToMovedData(expr.span))?; 520 | let owned = reference.borrow().clone(); 521 | Ok(owned) 522 | } 523 | value => Ok(value.clone()), 524 | } 525 | } else { 526 | Err(EvalError::CannotDereferenceValue( 527 | expr.span.wrap(inner.value.kind()), 528 | )) 529 | } 530 | } 531 | Expression::Borrow(inner) => { 532 | if let Expression::Variable(variable) = inner.value { 533 | if let Some(registration) = registrations 534 | .iter() 535 | .find(|v| v.type_info().type_path_table().short_path() == variable) 536 | { 537 | Ok(Value::Resource(IntoResource::new(registration.type_id()))) 538 | } else { 539 | let rc = environment.get(&variable, inner.span)?; 540 | let weak = rc.borrow(); 541 | 542 | Ok(Value::Reference(weak)) 543 | } 544 | } else { 545 | Err(EvalError::CannotBorrowValue( 546 | expr.span.wrap(inner.value.kind()), 547 | )) 548 | } 549 | } 550 | Expression::None => Ok(Value::None), 551 | Expression::Boolean(bool) => Ok(Value::Boolean(bool)), 552 | Expression::Function { name, arguments } => { 553 | environment.function_scope(&name, move |environment, function| { 554 | (function.body)( 555 | arguments, 556 | EvalParams { 557 | world, 558 | environment, 559 | registrations, 560 | }, 561 | ) 562 | }) 563 | } 564 | } 565 | } 566 | 567 | fn eval_object( 568 | map: HashMap>, 569 | EvalParams { 570 | world, 571 | environment, 572 | registrations, 573 | }: EvalParams, 574 | ) -> Result>, EvalError> { 575 | let map = map 576 | .into_iter() 577 | .map( 578 | |(key, expr)| -> Result<(String, UniqueRc), EvalError> { 579 | Ok(( 580 | key, 581 | UniqueRc::new(eval_expression( 582 | expr, 583 | EvalParams { 584 | world, 585 | environment, 586 | registrations, 587 | }, 588 | )?), 589 | )) 590 | }, 591 | ) 592 | .collect::>()?; 593 | 594 | Ok(map) 595 | } 596 | fn eval_tuple( 597 | tuple: Vec>, 598 | EvalParams { 599 | world, 600 | environment, 601 | registrations, 602 | }: EvalParams, 603 | ) -> Result>]>, EvalError> { 604 | tuple 605 | .into_iter() 606 | .map(|expr| { 607 | let span = expr.span.clone(); 608 | let value = UniqueRc::new(eval_expression( 609 | expr, 610 | EvalParams { 611 | world, 612 | environment, 613 | registrations, 614 | }, 615 | )?); 616 | Ok(span.wrap(value)) 617 | }) 618 | .collect::>() 619 | } 620 | -------------------------------------------------------------------------------- /src/builtin_parser/runner/value.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::fmt::Debug; 3 | 4 | use crate::builtin_parser::number::Number; 5 | use crate::builtin_parser::{Environment, StrongRef, UniqueRc}; 6 | 7 | use super::super::Spanned; 8 | use super::environment::FunctionParam; 9 | use super::error::EvalError; 10 | use super::reflection::{CreateRegistration, IntoResource}; 11 | use super::unique_rc::WeakRef; 12 | 13 | use bevy::ecs::world::World; 14 | use bevy::reflect::{ 15 | DynamicStruct, DynamicTuple, GetPath, Reflect, ReflectRef, TypeInfo, TypeRegistration, 16 | VariantInfo, VariantType, 17 | }; 18 | 19 | use logos::Span; 20 | 21 | /// A runtime value 22 | #[derive(Debug, Clone)] 23 | pub enum Value { 24 | /// Nothing at all 25 | None, 26 | /// A number. 27 | Number(Number), 28 | /// `true` or `false`. Thats it... 29 | Boolean(bool), 30 | /// A string... there isn't much to say about this one. 31 | String(String), 32 | /// A reference. 33 | /// 34 | /// References are very similar to rust's ownership and borrowing. 35 | /// We achieve this by storing every variable as a [`UniqueRc`] 36 | /// (which is essentially just [`Rc>`] but having only 37 | /// the owner of the value have a strong reference, while every 38 | /// other value has a weak reference. This causes 39 | /// [`Rc::try_unwrap`] to succeed every time) 40 | Reference(WeakRef), 41 | /// A dynamic [`HashMap`]. 42 | Object(HashMap>), 43 | /// An [`Object`](Value::Object) with a name attached to it. 44 | StructObject { 45 | /// The name of the struct 46 | name: String, 47 | /// The [`Object`](Value::Object) [`HashMap`]. 48 | map: HashMap>, 49 | }, 50 | /// A fixed size list of values that can have different types. 51 | Tuple(Box<[Spanned>]>), 52 | /// A [`Tuple`](Value::Tuple) with a name attached to it. 53 | StructTuple { 54 | /// The name of the tuple 55 | name: String, 56 | /// The [`Object`](Value::Object) slice. 57 | tuple: Box<[Spanned>]>, 58 | }, 59 | /// A reference to a dynamic value. (aka a reference) 60 | Resource(IntoResource), 61 | } 62 | 63 | impl Value { 64 | /// Converts this value into a [`Box`]. 65 | /// 66 | /// `ty` is used for type inference. 67 | pub fn reflect(self, span: Span, ty: &str) -> Result, EvalError> { 68 | match self { 69 | Value::None => Ok(Box::new(())), 70 | Value::Number(number) => number.reflect(span, ty), 71 | Value::Boolean(boolean) => Ok(Box::new(boolean)), 72 | Value::String(string) => Ok(Box::new(string)), 73 | Value::Reference(_reference) => Err(EvalError::CannotReflectReference(span)), 74 | Value::Object(object) | Value::StructObject { map: object, .. } => { 75 | let mut dyn_struct = DynamicStruct::default(); 76 | 77 | for (name, value) in object { 78 | dyn_struct.insert_boxed(&name, value.into_inner().reflect(span.clone(), ty)?); 79 | } 80 | 81 | Ok(Box::new(dyn_struct)) 82 | } 83 | Value::Tuple(tuple) | Value::StructTuple { tuple, .. } => { 84 | let mut dyn_tuple = DynamicTuple::default(); 85 | 86 | for element in Vec::from(tuple).into_iter() { 87 | dyn_tuple.insert_boxed(element.value.into_inner().reflect(element.span, ty)?); 88 | } 89 | 90 | Ok(Box::new(dyn_tuple)) 91 | } 92 | Value::Resource(_) => Err(EvalError::CannotReflectResource(span)), 93 | } 94 | } 95 | 96 | /// Attempts to format this [`Value`]. 97 | /// 98 | /// Returns an error if the [`Value`] is a reference to moved data. 99 | pub fn try_format( 100 | &self, 101 | span: Span, 102 | world: &World, 103 | registrations: &[&TypeRegistration], 104 | ) -> Result { 105 | const TAB: &str = " "; 106 | match self { 107 | Value::None => Ok(format!("()")), 108 | Value::Number(number) => Ok(format!("{number}")), 109 | Value::Boolean(bool) => Ok(format!("{bool}")), 110 | Value::String(string) => Ok(format!("\"{string}\"")), 111 | Value::Reference(reference) => { 112 | if let Some(rc) = reference.upgrade() { 113 | Ok(rc.borrow().try_format(span, world, registrations)?) 114 | } else { 115 | Err(EvalError::ReferenceToMovedData(span)) 116 | } 117 | } 118 | Value::Object(map) => { 119 | let mut string = String::new(); 120 | string.push('{'); 121 | for (key, value) in map { 122 | string += &format!( 123 | "\n{TAB}{key}: {},", 124 | value.borrow_inner().borrow().try_format( 125 | span.clone(), 126 | world, 127 | registrations 128 | )? 129 | ); 130 | } 131 | if !map.is_empty() { 132 | string.push('\n'); 133 | } 134 | string.push('}'); 135 | Ok(string) 136 | } 137 | Value::StructObject { name, map } => { 138 | let mut string = String::new(); 139 | string += &format!("{name} {{"); 140 | for (key, value) in map { 141 | string += &format!( 142 | "\n{TAB}{key}: {},", 143 | value.borrow_inner().borrow().try_format( 144 | span.clone(), 145 | world, 146 | registrations 147 | )? 148 | ); 149 | } 150 | if !map.is_empty() { 151 | string.push('\n'); 152 | } 153 | string.push('}'); 154 | Ok(string) 155 | } 156 | Value::Tuple(tuple) => { 157 | let mut string = String::new(); 158 | string.push('('); 159 | for element in tuple.iter() { 160 | string += &format!( 161 | "\n{TAB}{},", 162 | element.value.borrow_inner().borrow().try_format( 163 | span.clone(), 164 | world, 165 | registrations 166 | )? 167 | ); 168 | } 169 | if !tuple.is_empty() { 170 | string.push('\n'); 171 | } 172 | string.push(')'); 173 | Ok(string) 174 | } 175 | Value::StructTuple { name, tuple } => { 176 | let mut string = String::new(); 177 | string.push_str(name); 178 | string.push('('); 179 | for element in tuple.iter() { 180 | string += &format!( 181 | "\n{TAB}{},", 182 | element.value.borrow_inner().borrow().try_format( 183 | span.clone(), 184 | world, 185 | registrations 186 | )? 187 | ); 188 | } 189 | if !tuple.is_empty() { 190 | string.push('\n'); 191 | } 192 | string.push(')'); 193 | Ok(string) 194 | } 195 | Value::Resource(resource) => Ok(fancy_debug_print(resource, world, registrations)), 196 | } 197 | } 198 | 199 | /// Returns the kind of [`Value`] as a [string slice](str). 200 | /// You may want to use [`natural_kind`](Self::natural_kind) 201 | /// instead for more natural sounding error messages 202 | pub const fn kind(&self) -> &'static str { 203 | match self { 204 | Value::None => "none", 205 | Value::Number(number) => number.kind(), 206 | Value::Boolean(..) => "boolean", 207 | Value::String(..) => "string", 208 | Value::Reference(..) => "reference", 209 | Value::Object(..) => "object", 210 | Value::StructObject { .. } => "struct object", 211 | Value::Tuple(..) => "tuple", 212 | Value::StructTuple { .. } => "struct tuple", 213 | Value::Resource(..) => "resource", 214 | } 215 | } 216 | 217 | /// Returns the kind of [`Value`] as a [string slice](str) with an `a` or `an` prepended to it. 218 | /// Used for more natural sounding error messages. 219 | pub const fn natural_kind(&self) -> &'static str { 220 | match self { 221 | Value::None => "nothing", 222 | Value::Number(number) => number.natural_kind(), 223 | Value::Boolean(..) => "a boolean", 224 | Value::String(..) => "a string", 225 | Value::Reference(..) => "a reference", 226 | Value::Object(..) => "a object", 227 | Value::StructObject { .. } => "a struct object", 228 | Value::Tuple(..) => "a tuple", 229 | Value::StructTuple { .. } => "a struct tuple", 230 | Value::Resource(..) => "a resource", 231 | } 232 | } 233 | } 234 | 235 | /// A massive function that takes in a type registration and the world and then 236 | /// does all the hard work of printing out the type nicely. 237 | fn fancy_debug_print( 238 | resource: &IntoResource, 239 | world: &World, 240 | registrations: &[&TypeRegistration], 241 | ) -> String { 242 | const TAB: &str = " "; 243 | let registration = registrations.create_registration(resource.id); 244 | let dyn_reflect = resource.ref_dyn_reflect(world, registration); 245 | 246 | let reflect = dyn_reflect.reflect_path(resource.path.as_str()).unwrap(); 247 | 248 | fn debug_subprint(reflect: &dyn Reflect, indentation: usize) -> String { 249 | let mut f = String::new(); 250 | let reflect_ref = reflect.reflect_ref(); 251 | let indentation_string = TAB.repeat(indentation); 252 | match reflect_ref { 253 | ReflectRef::Struct(struct_info) => { 254 | f += "{\n"; 255 | for i in 0..struct_info.field_len() { 256 | let field = struct_info.field_at(i).unwrap(); 257 | let field_name = struct_info.name_at(i).unwrap(); 258 | 259 | let field_value = debug_subprint(field, indentation + 1); 260 | f += &format!( 261 | "{indentation_string}{TAB}{field_name}: {} = {field_value},\n", 262 | field.reflect_short_type_path(), 263 | ); 264 | } 265 | f += &indentation_string; 266 | f += "}"; 267 | } 268 | ReflectRef::TupleStruct(_) => todo!(), 269 | ReflectRef::Tuple(tuple_info) => { 270 | f += "(\n"; 271 | for field in tuple_info.iter_fields() { 272 | let field_value = debug_subprint(field, indentation + 1); 273 | f += &format!("{indentation_string}{TAB}{field_value},\n",); 274 | } 275 | f += &indentation_string; 276 | f += ")"; 277 | } 278 | ReflectRef::List(_) => todo!(), 279 | ReflectRef::Array(_) => todo!(), 280 | ReflectRef::Map(_) => todo!(), 281 | ReflectRef::Enum(variant) => { 282 | // Print out the enum types 283 | f += variant.variant_name(); 284 | 285 | match variant.variant_type() { 286 | VariantType::Struct => { 287 | f += " {\n"; 288 | for field in variant.iter_fields() { 289 | f += &format!( 290 | "{indentation_string}{TAB}{}: {} = {},\n", 291 | field.name().unwrap(), 292 | field.value().reflect_short_type_path(), 293 | debug_subprint(field.value(), indentation + 1) 294 | ); 295 | } 296 | f += &indentation_string; 297 | f += "}"; 298 | } 299 | VariantType::Tuple => { 300 | f += "(\n"; 301 | for field in variant.iter_fields() { 302 | f += &format!( 303 | "{indentation_string}{TAB}{} = {},\n", 304 | field.value().reflect_short_type_path(), 305 | debug_subprint(field.value(), indentation + 1) 306 | ); 307 | } 308 | f += &indentation_string; 309 | f += ")"; 310 | } 311 | VariantType::Unit => {} 312 | } 313 | } 314 | ReflectRef::Value(_) => { 315 | f += &format!("{reflect:?}"); 316 | } 317 | } 318 | 319 | f 320 | } 321 | 322 | let mut f = String::new(); 323 | let reflect_ref = reflect.reflect_ref(); 324 | match reflect_ref { 325 | ReflectRef::Struct(struct_info) => { 326 | f += &format!("struct {} {{\n", struct_info.reflect_short_type_path()); 327 | for i in 0..struct_info.field_len() { 328 | let field = struct_info.field_at(i).unwrap(); 329 | let field_name = struct_info.name_at(i).unwrap(); 330 | 331 | let field_value = debug_subprint(field, 1); 332 | f += &format!( 333 | "{TAB}{}: {} = {},\n", 334 | field_name, 335 | field.reflect_short_type_path(), 336 | field_value 337 | ); 338 | } 339 | f += "}"; 340 | } 341 | ReflectRef::TupleStruct(_) => todo!(), 342 | ReflectRef::Tuple(_) => todo!(), 343 | ReflectRef::List(_) => todo!(), 344 | ReflectRef::Array(_) => todo!(), 345 | ReflectRef::Map(_) => todo!(), 346 | ReflectRef::Enum(set_variant_info) => { 347 | // Print out the enum types 348 | f += &format!("enum {} {{\n", set_variant_info.reflect_short_type_path()); 349 | let TypeInfo::Enum(enum_info) = registration.type_info() else { 350 | unreachable!() 351 | }; 352 | for variant in enum_info.iter() { 353 | f += "\t"; 354 | f += variant.name(); 355 | match variant { 356 | VariantInfo::Struct(variant) => { 357 | f += " {\n"; 358 | for field in variant.iter() { 359 | f += &format!( 360 | "{TAB}{TAB}{}: {},\n", 361 | field.name(), 362 | field.type_path_table().short_path() 363 | ); 364 | } 365 | f += TAB; 366 | f += "}"; 367 | } 368 | VariantInfo::Tuple(variant) => { 369 | f += "("; 370 | let mut iter = variant.iter(); 371 | if let Some(first) = iter.next() { 372 | f += &format!("{}", first.type_path_table().short_path()); 373 | for field in iter { 374 | f += &format!(", {}", field.type_path_table().short_path()); 375 | } 376 | } 377 | f += ")"; 378 | } 379 | VariantInfo::Unit(_) => {} 380 | } 381 | f += ",\n"; 382 | } 383 | // Print out the current value 384 | f += "} = "; 385 | f += set_variant_info.variant_name(); 386 | match set_variant_info.variant_type() { 387 | VariantType::Struct => { 388 | f += " {\n"; 389 | for field in set_variant_info.iter_fields() { 390 | f += &format!("{TAB}{}: {:?},\n", field.name().unwrap(), field.value()); 391 | } 392 | f += "}"; 393 | } 394 | VariantType::Tuple => { 395 | f += "(\n"; 396 | for field in set_variant_info.iter_fields() { 397 | f += &format!("{TAB}{:?},\n", field.value()); 398 | } 399 | f += ")"; 400 | } 401 | VariantType::Unit => {} 402 | } 403 | } 404 | ReflectRef::Value(value) => { 405 | f += &format!("{value:?}"); 406 | } 407 | } 408 | f 409 | } 410 | 411 | impl From<()> for Value { 412 | fn from((): ()) -> Self { 413 | Value::None 414 | } 415 | } 416 | 417 | macro_rules! from_t { 418 | (impl $type:ty: $var:ident => $expr:expr) => { 419 | impl From<$type> for Value { 420 | fn from($var: $type) -> Self { 421 | $expr 422 | } 423 | } 424 | }; 425 | } 426 | macro_rules! from_number { 427 | ($($number:ident),*$(,)?) => { 428 | $( 429 | from_t!(impl $number: number => Value::Number(Number::$number(number))); 430 | )* 431 | }; 432 | } 433 | 434 | from_number!(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize, f32, f64); 435 | 436 | from_t!(impl String: string => Value::String(string)); 437 | from_t!(impl bool: bool => Value::Boolean(bool)); 438 | from_t!(impl Number: number => Value::Number(number)); 439 | from_t!(impl HashMap>: hashmap => Value::Object(hashmap)); 440 | from_t!(impl HashMap: hashmap => Value::Object( 441 | hashmap 442 | .into_iter() 443 | .map(|(k, v)| (k, UniqueRc::new(v))) 444 | .collect(), 445 | )); 446 | 447 | impl FunctionParam for Spanned { 448 | type Item<'world, 'env, 'reg> = Self; 449 | const USES_VALUE: bool = true; 450 | 451 | fn get<'world, 'env, 'reg>( 452 | value: Option>, 453 | _: &mut Option<&'world mut World>, 454 | _: &mut Option<&'env mut Environment>, 455 | _: &'reg [&'reg TypeRegistration], 456 | ) -> Result, EvalError> { 457 | Ok(value.unwrap()) 458 | } 459 | } 460 | impl, Error = EvalError>> FunctionParam for Spanned { 461 | type Item<'world, 'env, 'reg> = Self; 462 | const USES_VALUE: bool = true; 463 | 464 | fn get<'world, 'env, 'reg>( 465 | value: Option>, 466 | _: &mut Option<&'world mut World>, 467 | _: &mut Option<&'env mut Environment>, 468 | _: &'reg [&'reg TypeRegistration], 469 | ) -> Result, EvalError> { 470 | let value = value.unwrap(); 471 | Ok(Spanned { 472 | span: value.span.clone(), 473 | value: T::try_from(value)?, 474 | }) 475 | } 476 | } 477 | impl FunctionParam for Value { 478 | type Item<'world, 'env, 'reg> = Self; 479 | const USES_VALUE: bool = true; 480 | 481 | fn get<'world, 'env, 'reg>( 482 | value: Option>, 483 | _: &mut Option<&'world mut World>, 484 | _: &mut Option<&'env mut Environment>, 485 | _: &'reg [&'reg TypeRegistration], 486 | ) -> Result, EvalError> { 487 | Ok(value.unwrap().value) 488 | } 489 | } 490 | 491 | macro_rules! impl_function_param_for_value { 492 | (impl $type:ty: $value_pattern:pat => $return:expr) => { 493 | impl FunctionParam for $type { 494 | type Item<'world, 'env, 'reg> = Self; 495 | const USES_VALUE: bool = true; 496 | 497 | fn get<'world, 'env, 'reg>( 498 | value: Option>, 499 | _: &mut Option<&'world mut World>, 500 | _: &mut Option<&'env mut Environment>, 501 | _: &'reg [&'reg TypeRegistration], 502 | ) -> Result, EvalError> { 503 | let value = value.unwrap(); 504 | if let $value_pattern = value.value { 505 | Ok($return) 506 | } else { 507 | Err(EvalError::IncompatibleFunctionParameter { 508 | expected: stringify!($type), 509 | actual: value.value.natural_kind(), 510 | span: value.span, 511 | }) 512 | } 513 | } 514 | } 515 | impl TryFrom> for $type { 516 | type Error = EvalError; 517 | 518 | fn try_from(value: Spanned) -> Result { 519 | if let $value_pattern = value.value { 520 | Ok($return) 521 | } else { 522 | todo!() 523 | } 524 | } 525 | } 526 | }; 527 | } 528 | macro_rules! impl_function_param_for_numbers { 529 | ($generic:ident ($($number:ident),*$(,)?)) => { 530 | $( 531 | impl FunctionParam for $number { 532 | type Item<'world, 'env, 'reg> = Self; 533 | const USES_VALUE: bool = true; 534 | 535 | fn get<'world, 'env, 'reg>( 536 | value: Option>, 537 | _: &mut Option<&'world mut World>, 538 | _: &mut Option<&'env mut Environment>, 539 | _: &'reg [&'reg TypeRegistration], 540 | ) -> Result, EvalError> { 541 | let value = value.unwrap(); 542 | match value.value { 543 | Value::Number(Number::$number(value)) => Ok(value), 544 | Value::Number(Number::$generic(value)) => Ok(value as $number), 545 | _ => Err(EvalError::IncompatibleFunctionParameter { 546 | expected: concat!("a ", stringify!($number)), 547 | actual: value.value.natural_kind(), 548 | span: value.span, 549 | }) 550 | } 551 | } 552 | } 553 | impl TryFrom> for $number { 554 | type Error = EvalError; 555 | 556 | fn try_from(value: Spanned) -> Result { 557 | match value.value { 558 | Value::Number(Number::$number(value)) => Ok(value), 559 | Value::Number(Number::$generic(value)) => Ok(value as $number), 560 | _ => Err(EvalError::IncompatibleFunctionParameter { 561 | expected: concat!("a ", stringify!($number)), 562 | actual: value.value.natural_kind(), 563 | span: value.span 564 | }) 565 | } 566 | } 567 | } 568 | )* 569 | }; 570 | } 571 | 572 | impl_function_param_for_numbers!(Float(f32, f64)); 573 | impl_function_param_for_numbers!(Integer(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize)); 574 | 575 | impl_function_param_for_value!(impl bool: Value::Boolean(boolean) => boolean); 576 | impl_function_param_for_value!(impl Number: Value::Number(number) => number); 577 | impl_function_param_for_value!(impl String: Value::String(string) => string); 578 | // impl_function_param_for_value!(impl HashMap>: Value::Object(object) => object); 579 | impl_function_param_for_value!(impl HashMap: Value::Object(object) => { 580 | object.into_iter().map(|(k, v)| (k, v.into_inner())).collect() 581 | }); 582 | impl_function_param_for_value!(impl StrongRef: Value::Reference(reference) => reference.upgrade().unwrap()); 583 | 584 | impl FunctionParam for &mut World { 585 | type Item<'world, 'env, 'reg> = &'world mut World; 586 | const USES_VALUE: bool = false; 587 | 588 | fn get<'world, 'env, 'reg>( 589 | _: Option>, 590 | world: &mut Option<&'world mut World>, 591 | _: &mut Option<&'env mut Environment>, 592 | _: &'reg [&'reg TypeRegistration], 593 | ) -> Result, EvalError> { 594 | let Some(world) = world.take() else { 595 | // make this unreachable by checking the function when it gets registered 596 | todo!("world borrowed twice"); 597 | }; 598 | 599 | Ok(world) 600 | } 601 | } 602 | 603 | // This probably isn't a good idea. But eh who cares, more power to the user. 604 | impl FunctionParam for &mut Environment { 605 | type Item<'world, 'env, 'reg> = &'env mut Environment; 606 | const USES_VALUE: bool = false; 607 | 608 | fn get<'world, 'env, 'reg>( 609 | _: Option>, 610 | _: &mut Option<&'world mut World>, 611 | environment: &mut Option<&'env mut Environment>, 612 | _: &'reg [&'reg TypeRegistration], 613 | ) -> Result, EvalError> { 614 | Ok(environment.take().unwrap()) 615 | } 616 | } 617 | 618 | impl FunctionParam for &[&TypeRegistration] { 619 | type Item<'world, 'env, 'reg> = &'reg [&'reg TypeRegistration]; 620 | const USES_VALUE: bool = false; 621 | 622 | fn get<'world, 'env, 'reg>( 623 | _: Option>, 624 | _: &mut Option<&'world mut World>, 625 | _: &mut Option<&'env mut Environment>, 626 | registrations: &'reg [&'reg TypeRegistration], 627 | ) -> Result, EvalError> { 628 | Ok(registrations) 629 | } 630 | } 631 | -------------------------------------------------------------------------------- /src/builtin_parser/parser.rs: -------------------------------------------------------------------------------- 1 | //! Generates an abstract syntax tree from a list of tokens. 2 | 3 | use logos::Span; 4 | use std::collections::HashMap; 5 | use std::num::IntErrorKind; 6 | 7 | use crate::command::{CommandHint, CommandHintColor}; 8 | 9 | use super::lexer::{FailedToLexCharacter, Token, TokenStream}; 10 | use super::number::Number; 11 | use super::runner::environment::Function; 12 | use super::{Environment, SpanExtension, Spanned}; 13 | 14 | /// An [Abstract Syntax Tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree). 15 | /// 16 | /// This type represents a list of expressions, which is what makes up a command. 17 | pub type Ast = Vec>; 18 | 19 | macro_rules! expect { 20 | ($tokens:ident, $($token:tt)+) => { 21 | match $tokens.next() { 22 | Some(Ok($($token)+)) => ($($token)+) , 23 | Some(Ok(token)) => { 24 | return Err(ParseError::ExpectedTokenButGot { 25 | expected: $($token)+, 26 | got: token, 27 | span: $tokens.span(), 28 | }) 29 | } 30 | Some(Err(FailedToLexCharacter)) => { 31 | return Err(ParseError::FailedToLexCharacters($tokens.span().wrap($tokens.slice().to_string()))) 32 | } 33 | None => return Err(ParseError::ExpectedMoreTokens($tokens.span())), 34 | } 35 | }; 36 | } 37 | 38 | /// A type that represents an expression. 39 | #[derive(Debug, Clone)] 40 | pub enum Expression { 41 | // Primitives 42 | None, 43 | Boolean(bool), 44 | Number(Number), 45 | Variable(String), 46 | String(String), 47 | Borrow(Box>), 48 | Dereference(Box>), 49 | Object(HashMap>), 50 | StructObject { 51 | name: String, 52 | map: HashMap>, 53 | }, 54 | Tuple(Vec>), 55 | StructTuple { 56 | name: String, 57 | tuple: Vec>, 58 | }, 59 | 60 | // Expressions 61 | BinaryOp { 62 | left: Box>, 63 | operator: Operator, 64 | right: Box>, 65 | }, 66 | UnaryOp(Box>), 67 | Member { 68 | left: Box>, 69 | right: Spanned, 70 | }, 71 | 72 | // Statement-like 73 | VarAssign { 74 | name: Box>, 75 | value: Box>, 76 | }, 77 | Function { 78 | name: String, 79 | arguments: Vec>, 80 | }, 81 | ForLoop { 82 | index_name: String, 83 | loop_count: u64, 84 | block: Ast, 85 | }, 86 | } 87 | 88 | /// A singular element access within a [`Expression::Member`]. 89 | /// 90 | /// Based on `bevy_reflect`'s `Access`. 91 | #[derive(Debug, Clone)] 92 | pub enum Access { 93 | /// A name-based field access on a struct. 94 | Field(String), 95 | /// An index-based access on a tuple. 96 | TupleIndex(usize), 97 | // /// An index-based access on a list. 98 | // ListIndex(usize), 99 | } 100 | pub enum AccessKind { 101 | Field, 102 | TupleIndex, 103 | } 104 | impl Access { 105 | pub const fn kind(&self) -> AccessKind { 106 | match self { 107 | Access::Field(_) => AccessKind::Field, 108 | Access::TupleIndex(_) => AccessKind::TupleIndex, 109 | } 110 | } 111 | /// Returns the kind of [`Access`] as a [string slice](str) with an `a` or `an` prepended to it. 112 | /// Used for more natural sounding error messages. 113 | pub const fn natural_kind(&self) -> &'static str { 114 | self.kind().natural() 115 | } 116 | } 117 | 118 | impl AccessKind { 119 | /// Returns the kind of [`Access`] as a [string slice](str) with an `a` or `an` prepended to it. 120 | /// Used for more natural sounding error messages. 121 | pub const fn natural(&self) -> &'static str { 122 | match self { 123 | AccessKind::Field => "a field", 124 | AccessKind::TupleIndex => "a tuple", 125 | } 126 | } 127 | } 128 | 129 | /// Get the access if its of a certain type, if not, return a [`EvalError`](super::runner::error::EvalError). 130 | /// 131 | /// For examples, take a look at existing uses in the code. 132 | macro_rules! access_unwrap { 133 | ($expected:literal, $($variant:ident($variant_inner:ident))|+ = $val:expr => $block:block) => {{ 134 | let val = $val; 135 | if let $(Access::$variant($variant_inner))|+ = val.value $block else { 136 | use $crate::builtin_parser::parser::AccessKind; 137 | use $crate::builtin_parser::runner::error::EvalError; 138 | 139 | // We have to put this in a `const` first to avoid a 140 | // `temporary value dropped while borrowed` error. 141 | const EXPECTED_ACCESS: &[&str] = &[$(AccessKind::$variant.natural()),+]; 142 | Err(EvalError::IncorrectAccessOperation { 143 | span: val.span, 144 | expected_access: EXPECTED_ACCESS, 145 | expected_type: $expected, 146 | got: val.value, 147 | })? 148 | } 149 | }}; 150 | } 151 | pub(crate) use access_unwrap; 152 | 153 | impl Expression { 154 | pub const fn kind(&self) -> &'static str { 155 | match self { 156 | Expression::None => "nothing", 157 | Expression::Boolean(..) => "a boolean", 158 | Expression::Number(..) => "a number", 159 | Expression::Variable(..) => "a variable name", 160 | Expression::String(..) => "a string", 161 | Expression::Borrow(..) => "a borrow", 162 | Expression::Dereference(..) => "a dereference", 163 | Expression::Object(..) => "an object", 164 | Expression::StructObject { .. } => "a struct object", 165 | Expression::Tuple(..) => "a tuple", 166 | Expression::StructTuple { .. } => "a struct tuple", 167 | 168 | Expression::BinaryOp { .. } => "a binary operation", 169 | Expression::UnaryOp(..) => "a unary operation", 170 | Expression::Member { .. } => "a member expression", 171 | Expression::VarAssign { .. } => "a variable assignment", 172 | Expression::Function { .. } => "a function call", 173 | Expression::ForLoop { .. } => "a for loop", 174 | } 175 | } 176 | } 177 | 178 | #[derive(Debug, Clone)] 179 | pub enum Operator { 180 | Add, 181 | Sub, 182 | Mul, 183 | Div, 184 | Mod, 185 | } 186 | 187 | #[derive(Debug)] 188 | pub enum ParseError { 189 | FailedToLexCharacters(Spanned), 190 | ExpectedMoreTokens(Span), 191 | ExpectedTokenButGot { 192 | expected: Token, 193 | got: Token, 194 | span: Span, 195 | }, 196 | ExpectedEndline(Spanned), 197 | ExpectedLiteral(Spanned), 198 | InvalidSuffixForFloat(Spanned), 199 | NegativeIntOverflow { 200 | span: Span, 201 | number: String, 202 | number_kind: &'static str, 203 | }, 204 | PositiveIntOverflow { 205 | span: Span, 206 | number: String, 207 | number_kind: &'static str, 208 | }, 209 | ExpectedObjectContinuation(Spanned>>), 210 | ExpectedIndexer { 211 | got: Token, 212 | span: Span, 213 | }, 214 | UnsupportedLoop { 215 | ty: &'static str, 216 | span: Span, 217 | }, 218 | } 219 | 220 | impl ParseError { 221 | pub fn span(&self) -> Span { 222 | use ParseError as E; 223 | 224 | match self { 225 | E::FailedToLexCharacters(Spanned { span, value: _ }) => span, 226 | E::ExpectedMoreTokens(span) => span, 227 | E::ExpectedTokenButGot { span, .. } => span, 228 | E::ExpectedEndline(Spanned { span, value: _ }) => span, 229 | E::ExpectedLiteral(Spanned { span, value: _ }) => span, 230 | E::InvalidSuffixForFloat(Spanned { span, value: _ }) => span, 231 | E::PositiveIntOverflow { span, .. } => span, 232 | E::NegativeIntOverflow { span, .. } => span, 233 | E::ExpectedObjectContinuation(Spanned { span, value: _ }) => span, 234 | E::ExpectedIndexer { got: _, span } => span, 235 | E::UnsupportedLoop { ty: _, span } => span, 236 | } 237 | .clone() 238 | } 239 | 240 | pub fn hint(&self) -> CommandHint { 241 | CommandHint { 242 | color: CommandHintColor::Error, 243 | span: self.span(), 244 | description: self.to_string().into(), 245 | } 246 | } 247 | } 248 | 249 | impl std::fmt::Display for ParseError { 250 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 251 | use ParseError as E; 252 | 253 | match self { 254 | E::FailedToLexCharacters(Spanned { span: _, value }) => write!(f, "Invalid character(s) \"{value}\" (Did you mean to use a string?)"), 255 | E::ExpectedMoreTokens(_) => write!(f, "Expected more tokens, got nothing."), 256 | E::ExpectedTokenButGot { 257 | expected, 258 | got, 259 | span: _, 260 | } => write!(f, "Expected token {expected:?}, got token {got:?} instead."), 261 | E::ExpectedEndline(_) => write!(f, "Expected a semicolon or endline after a complete statement, but got more tokens than expected."), 262 | E::ExpectedLiteral(Spanned { span: _, value }) => write!(f, "Expected a literal token, got {value:?} which is not a valid literal."), 263 | E::InvalidSuffixForFloat(Spanned { span: _, value: suffix }) => write!(f, r#""{suffix}" is an invalid suffix for a float. The only valid suffixes are "f32" and "f64"."#), 264 | E::NegativeIntOverflow { span: _, number, number_kind } => write!(f, "{number} cannot be represented as a {number_kind} as it is too small."), 265 | E::PositiveIntOverflow { span: _, number, number_kind } => write!(f, "{number} cannot be represented as a {number_kind} as it is too large."), 266 | E::ExpectedObjectContinuation(Spanned { span: _, value: got }) => write!(f, "Expected a continuation to the object declaration (such as a comma or a closing bracket), but got {got:?} instead."), 267 | E::ExpectedIndexer { got, span: _ } => write!(f, "Expected an identifier or integer when accessing member of variable, got {got:?} instead."), 268 | E::UnsupportedLoop { ty, span : _} => write!(f, "{ty} loops are not yet supported. See issue #8.") 269 | } 270 | } 271 | } 272 | impl std::error::Error for ParseError {} 273 | 274 | const FLOAT_PARSE_EXPECT_REASON: &str = 275 | "Float parsing errors are handled by the lexer, and floats cannot overflow."; 276 | const NUMBER_TYPE_WILDCARD_UNREACHABLE_REASON: &str = 277 | "Lexer guarantees `NumberType`'s slice to be included one of the match arms."; 278 | 279 | pub fn parse(tokens: &mut TokenStream, environment: &Environment) -> Result { 280 | let mut ast = Vec::new(); 281 | 282 | while tokens.peek().is_some() { 283 | ast.push(parse_expression(tokens, environment)?); 284 | 285 | match tokens.next() { 286 | Some(Ok(Token::SemiColon)) => continue, 287 | Some(Ok(token)) => return Err(ParseError::ExpectedEndline(tokens.span().wrap(token))), 288 | Some(Err(FailedToLexCharacter)) => { 289 | return Err(ParseError::FailedToLexCharacters( 290 | tokens.span().wrap(tokens.slice().to_string()), 291 | )) 292 | } 293 | None => break, 294 | } 295 | } 296 | 297 | Ok(ast) 298 | } 299 | 300 | fn parse_expression( 301 | tokens: &mut TokenStream, 302 | environment: &Environment, 303 | ) -> Result, ParseError> { 304 | match tokens.peek() { 305 | Some(Ok(Token::Loop)) => Err(ParseError::UnsupportedLoop { 306 | ty: "infinite", 307 | span: tokens.peek_span(), 308 | }), 309 | Some(Ok(Token::While)) => Err(ParseError::UnsupportedLoop { 310 | ty: "while", 311 | span: tokens.peek_span(), 312 | }), 313 | Some(Ok(Token::For)) => Err(ParseError::UnsupportedLoop { 314 | ty: "for", 315 | span: tokens.peek_span(), 316 | }), 317 | Some(Ok(_)) => { 318 | let expr = parse_additive(tokens, environment)?; 319 | 320 | match tokens.peek() { 321 | Some(Ok(Token::Equals)) => Ok(parse_var_assign(expr, tokens, environment)?), 322 | _ => Ok(expr), 323 | } 324 | } 325 | Some(Err(FailedToLexCharacter)) => Err(ParseError::FailedToLexCharacters( 326 | tokens.peek_span().wrap(tokens.slice().to_string()), 327 | )), 328 | None => Err(ParseError::ExpectedMoreTokens(tokens.peek_span())), 329 | } 330 | } 331 | 332 | fn _parse_block(tokens: &mut TokenStream, environment: &Environment) -> Result { 333 | expect!(tokens, Token::LeftBracket); 334 | let ast = parse(tokens, environment)?; 335 | expect!(tokens, Token::RightBracket); 336 | Ok(ast) 337 | } 338 | 339 | fn parse_additive( 340 | tokens: &mut TokenStream, 341 | environment: &Environment, 342 | ) -> Result, ParseError> { 343 | let mut node = parse_multiplicitive(tokens, environment)?; 344 | 345 | while let Some(Ok(Token::Plus | Token::Minus)) = tokens.peek() { 346 | let operator = match tokens.next() { 347 | Some(Ok(Token::Plus)) => Operator::Add, 348 | Some(Ok(Token::Minus)) => Operator::Sub, 349 | _ => unreachable!(), 350 | }; 351 | 352 | let right = parse_multiplicitive(tokens, environment)?; 353 | 354 | node = Spanned { 355 | span: node.span.start..right.span.end, 356 | value: Expression::BinaryOp { 357 | left: Box::new(node), 358 | operator, 359 | right: Box::new(right), 360 | }, 361 | }; 362 | } 363 | 364 | Ok(node) 365 | } 366 | fn parse_multiplicitive( 367 | tokens: &mut TokenStream, 368 | environment: &Environment, 369 | ) -> Result, ParseError> { 370 | let mut node = parse_value(tokens, environment)?; 371 | 372 | while let Some(Ok(Token::Asterisk | Token::Slash | Token::Modulo)) = tokens.peek() { 373 | let operator = match tokens.next() { 374 | Some(Ok(Token::Asterisk)) => Operator::Mul, 375 | Some(Ok(Token::Slash)) => Operator::Div, 376 | Some(Ok(Token::Modulo)) => Operator::Mod, 377 | _ => unreachable!(), 378 | }; 379 | 380 | let right = parse_value(tokens, environment)?; 381 | 382 | node = Spanned { 383 | span: node.span.start..right.span.end, 384 | value: Expression::BinaryOp { 385 | left: Box::new(node), 386 | operator, 387 | right: Box::new(right), 388 | }, 389 | }; 390 | } 391 | 392 | Ok(node) 393 | } 394 | 395 | fn parse_value( 396 | tokens: &mut TokenStream, 397 | environment: &Environment, 398 | ) -> Result, ParseError> { 399 | /// Parses a literal (value without member expressions) 400 | fn parse_literal( 401 | tokens: &mut TokenStream, 402 | environment: &Environment, 403 | ) -> Result, ParseError> { 404 | match tokens.next() { 405 | Some(Ok(Token::LeftParen)) => { 406 | let start = tokens.span().start; 407 | if let Some(Ok(Token::RightParen)) = tokens.peek() { 408 | tokens.next(); 409 | Ok(Spanned { 410 | span: start..tokens.span().end, 411 | value: Expression::None, 412 | }) 413 | } else { 414 | let expr = parse_expression(tokens, environment)?; 415 | if let Some(Ok(Token::Comma)) = tokens.peek() { 416 | let mut tuple = vec![expr]; 417 | 418 | while let Some(Ok(Token::Comma)) = tokens.peek() { 419 | tokens.next(); 420 | let expr = parse_expression(tokens, environment)?; 421 | 422 | tuple.push(expr); 423 | } 424 | 425 | expect!(tokens, Token::RightParen); 426 | 427 | Ok(Spanned { 428 | span: start..tokens.span().end, 429 | value: Expression::Tuple(tuple), 430 | }) 431 | } else { 432 | expect!(tokens, Token::RightParen); 433 | 434 | Ok(expr) 435 | } 436 | } 437 | } 438 | Some(Ok(Token::Identifier)) => { 439 | let start = tokens.span().start; 440 | let name = tokens.slice().to_string(); 441 | 442 | match tokens.peek() { 443 | Some(Ok(Token::LeftParen)) => { 444 | tokens.next(); 445 | 446 | let expr = parse_expression(tokens, environment)?; 447 | 448 | let mut tuple = vec![expr]; 449 | 450 | while let Some(Ok(Token::Comma)) = tokens.peek() { 451 | tokens.next(); 452 | let expr = parse_expression(tokens, environment)?; 453 | 454 | tuple.push(expr); 455 | } 456 | 457 | expect!(tokens, Token::RightParen); 458 | 459 | Ok(Spanned { 460 | span: start..tokens.span().end, 461 | value: Expression::StructTuple { name, tuple }, 462 | }) 463 | } 464 | Some(Ok(Token::LeftBracket)) => { 465 | tokens.next(); 466 | 467 | let map = parse_object(tokens, environment)?; 468 | 469 | Ok(Spanned { 470 | span: tokens.span(), 471 | value: Expression::StructObject { name, map }, 472 | }) 473 | } 474 | _ => { 475 | if let Some(Function { argument_count, .. }) = 476 | environment.get_function(&name) 477 | { 478 | dbg!(argument_count); 479 | 480 | let mut arguments = Vec::new(); 481 | for _ in 0..(*argument_count) { 482 | let expr = parse_expression(tokens, environment)?; 483 | arguments.push(expr); 484 | } 485 | Ok(Spanned { 486 | span: start..tokens.span().end, 487 | value: Expression::Function { name, arguments }, 488 | }) 489 | } else { 490 | Ok(tokens.span().wrap(Expression::Variable(name))) 491 | } 492 | } 493 | } 494 | } 495 | Some(Ok(Token::LeftBracket)) => { 496 | let map = parse_object(tokens, environment)?; 497 | 498 | Ok(Spanned { 499 | span: tokens.span(), 500 | value: Expression::Object(map), 501 | }) 502 | } 503 | Some(Ok(Token::String)) => { 504 | let slice = tokens.slice(); 505 | let string = slice[1..slice.len() - 1].to_string(); 506 | Ok(tokens.span().wrap(Expression::String(string))) 507 | } 508 | Some(Ok(Token::Minus)) => { 509 | let expr = parse_literal(tokens, environment)?; 510 | Ok(tokens.span().wrap(Expression::UnaryOp(Box::new(expr)))) 511 | } 512 | Some(Ok(Token::Ampersand)) => { 513 | let expr = parse_literal(tokens, environment)?; 514 | 515 | Ok(tokens.span().wrap(Expression::Borrow(Box::new(expr)))) 516 | } 517 | Some(Ok(Token::Asterisk)) => { 518 | let expr = parse_literal(tokens, environment)?; 519 | Ok(tokens.span().wrap(Expression::Dereference(Box::new(expr)))) 520 | } 521 | Some(Ok(Token::IntegerNumber)) => { 522 | parse_number(tokens).map(|s| s.map(Expression::Number)) 523 | } 524 | Some(Ok(Token::FloatNumber)) => { 525 | if let Some(Ok(Token::NumberType)) = tokens.peek() { 526 | let number: Number = match tokens.peek_slice() { 527 | "u8" | "u16" | "u32" | "u64" | "usize" | "i8" | "i16" | "i32" | "i64" 528 | | "isize" => Err(ParseError::InvalidSuffixForFloat( 529 | tokens.span().wrap(tokens.slice().to_string()), 530 | ))?, 531 | "f32" => { 532 | Number::f32(tokens.slice().parse().expect(FLOAT_PARSE_EXPECT_REASON)) 533 | } 534 | "f64" => { 535 | Number::f64(tokens.slice().parse().expect(FLOAT_PARSE_EXPECT_REASON)) 536 | } 537 | _ => unreachable!("{NUMBER_TYPE_WILDCARD_UNREACHABLE_REASON}"), 538 | }; 539 | let start_span = tokens.span().end; 540 | 541 | tokens.next(); 542 | 543 | Ok(Spanned { 544 | span: start_span..tokens.span().end, 545 | value: Expression::Number(number), 546 | }) 547 | } else { 548 | let number = Number::Float(tokens.slice().parse().unwrap()); 549 | 550 | Ok(Spanned { 551 | span: tokens.span(), 552 | value: Expression::Number(number), 553 | }) 554 | } 555 | } 556 | Some(Ok(Token::True)) => Ok(tokens.span().wrap(Expression::Boolean(true))), 557 | Some(Ok(Token::False)) => Ok(tokens.span().wrap(Expression::Boolean(false))), 558 | Some(Ok(token)) => Err(ParseError::ExpectedLiteral(tokens.span().wrap(token))), 559 | Some(Err(FailedToLexCharacter)) => Err(ParseError::FailedToLexCharacters( 560 | tokens.span().wrap(tokens.slice().to_string()), 561 | )), 562 | None => Err(ParseError::ExpectedMoreTokens(tokens.span())), 563 | } 564 | } 565 | 566 | let mut expr = parse_literal(tokens, environment)?; 567 | // If theres a dot after the expression, do a member expression: 568 | while let Some(Ok(Token::Dot)) = tokens.peek() { 569 | tokens.next(); // Skip the dot 570 | match tokens.next() { 571 | Some(Ok(Token::Identifier)) => { 572 | let right = tokens.slice().to_string(); 573 | expr = Spanned { 574 | span: expr.span.start..tokens.span().end, 575 | value: Expression::Member { 576 | left: Box::new(expr), 577 | right: tokens.span().wrap(Access::Field(right)), 578 | }, 579 | }; 580 | } 581 | Some(Ok(Token::IntegerNumber)) => { 582 | let right = tokens.slice().parse().map_err(map_parseint_error( 583 | tokens.span(), 584 | tokens.slice(), 585 | "usize", 586 | ))?; 587 | 588 | expr = Spanned { 589 | span: expr.span.start..tokens.span().end, 590 | value: Expression::Member { 591 | left: Box::new(expr), 592 | right: tokens.span().wrap(Access::TupleIndex(right)), 593 | }, 594 | }; 595 | } 596 | Some(Ok(token)) => { 597 | return Err(ParseError::ExpectedIndexer { 598 | got: token, 599 | span: tokens.span(), 600 | }) 601 | } 602 | Some(Err(FailedToLexCharacter)) => { 603 | return Err(ParseError::FailedToLexCharacters( 604 | tokens.span().wrap(tokens.slice().to_string()), 605 | )) 606 | } 607 | None => return Err(ParseError::ExpectedMoreTokens(tokens.span())), 608 | } 609 | } 610 | Ok(expr) 611 | } 612 | 613 | fn map_parseint_error<'s>( 614 | span: Span, 615 | slice: &'s str, 616 | number_kind: &'static str, 617 | ) -> impl FnOnce(std::num::ParseIntError) -> ParseError + 's { 618 | move |error| match error.kind() { 619 | IntErrorKind::PosOverflow => ParseError::PositiveIntOverflow { 620 | span, 621 | number: slice.to_string(), 622 | number_kind, 623 | }, 624 | IntErrorKind::NegOverflow => ParseError::NegativeIntOverflow { 625 | span, 626 | number: slice.to_string(), 627 | number_kind, 628 | }, 629 | IntErrorKind::Empty | IntErrorKind::InvalidDigit | IntErrorKind::Zero => unreachable!( 630 | "Lexer makes sure other errors aren't possible. Create an bevy_dev_console issue!" 631 | ), 632 | _ => unimplemented!(), // Required due to IntErrorKind being #[non_exhaustive] 633 | } 634 | } 635 | 636 | fn parse_number(tokens: &mut TokenStream) -> Result, ParseError> { 637 | if let Some(Ok(Token::NumberType)) = tokens.peek() { 638 | let number: Number = match tokens.peek_slice() { 639 | "u8" => Number::u8(tokens.slice().parse().map_err(map_parseint_error( 640 | tokens.span(), 641 | tokens.slice(), 642 | "u8", 643 | ))?), 644 | "u16" => Number::u16(tokens.slice().parse().map_err(map_parseint_error( 645 | tokens.span(), 646 | tokens.slice(), 647 | "u16", 648 | ))?), 649 | "u32" => Number::u32(tokens.slice().parse().map_err(map_parseint_error( 650 | tokens.span(), 651 | tokens.slice(), 652 | "u32", 653 | ))?), 654 | "u64" => Number::u64(tokens.slice().parse().map_err(map_parseint_error( 655 | tokens.span(), 656 | tokens.slice(), 657 | "u64", 658 | ))?), 659 | "usize" => Number::usize(tokens.slice().parse().map_err(map_parseint_error( 660 | tokens.span(), 661 | tokens.slice(), 662 | "usize", 663 | ))?), 664 | "i8" => Number::i8(tokens.slice().parse().map_err(map_parseint_error( 665 | tokens.span(), 666 | tokens.slice(), 667 | "i8", 668 | ))?), 669 | "i16" => Number::i16(tokens.slice().parse().map_err(map_parseint_error( 670 | tokens.span(), 671 | tokens.slice(), 672 | "i16", 673 | ))?), 674 | "i32" => Number::i32(tokens.slice().parse().map_err(map_parseint_error( 675 | tokens.span(), 676 | tokens.slice(), 677 | "i32", 678 | ))?), 679 | "isize" => Number::isize(tokens.slice().parse().map_err(map_parseint_error( 680 | tokens.span(), 681 | tokens.slice(), 682 | "isize", 683 | ))?), 684 | "f32" => Number::f32(tokens.slice().parse().expect(FLOAT_PARSE_EXPECT_REASON)), 685 | "f64" => Number::f64(tokens.slice().parse().expect(FLOAT_PARSE_EXPECT_REASON)), 686 | _ => unreachable!("{}", NUMBER_TYPE_WILDCARD_UNREACHABLE_REASON), 687 | }; 688 | let start_span = tokens.span().end; 689 | tokens.next(); 690 | 691 | Ok(Spanned { 692 | span: start_span..tokens.span().end, 693 | value: number, 694 | }) 695 | } else { 696 | let number = Number::Integer(tokens.slice().parse().unwrap()); 697 | 698 | Ok(Spanned { 699 | span: tokens.span(), 700 | value: number, 701 | }) 702 | } 703 | } 704 | 705 | fn parse_var_assign( 706 | name: Spanned, 707 | tokens: &mut TokenStream<'_>, 708 | environment: &Environment, 709 | ) -> Result, ParseError> { 710 | tokens.next(); // We already know that the next token is an equals 711 | 712 | let value = parse_additive(tokens, environment)?; 713 | 714 | Ok(Spanned { 715 | span: name.span.start..value.span.end, 716 | value: Expression::VarAssign { 717 | name: Box::new(name), 718 | value: Box::new(value), 719 | }, 720 | }) 721 | } 722 | 723 | /// Parses an object. 724 | /// 725 | /// - `{}` 726 | /// - `{test: 4}` 727 | /// - `{str: "sup!", num: -6.2}` 728 | fn parse_object( 729 | tokens: &mut TokenStream, 730 | environment: &Environment, 731 | ) -> Result>, ParseError> { 732 | let mut map = HashMap::new(); 733 | while let Some(Ok(Token::Identifier)) = tokens.peek() { 734 | tokens.next(); 735 | let ident = tokens.slice().to_string(); 736 | expect!(tokens, Token::Colon); 737 | let expr = parse_expression(tokens, environment)?; 738 | map.insert(ident, expr); 739 | match tokens.peek() { 740 | Some(Ok(Token::RightBracket)) => break, 741 | Some(Ok(Token::Comma)) => { 742 | tokens.next(); 743 | } 744 | token => Err(ParseError::ExpectedObjectContinuation( 745 | tokens.span().wrap(token.clone()), 746 | ))?, 747 | } 748 | } 749 | expect!(tokens, Token::RightBracket); 750 | Ok(map) 751 | } 752 | 753 | #[cfg(test)] 754 | mod tests { 755 | use super::super::lexer::TokenStream; 756 | use super::super::Environment; 757 | use super::parse; 758 | 759 | #[test] 760 | fn var_assign() { 761 | let mut lexer = TokenStream::new("x = 1 + 2 - 30 + y"); 762 | let environment = Environment::default(); 763 | 764 | let ast = parse(&mut lexer, &environment); 765 | 766 | assert!(ast.is_ok()); 767 | 768 | // TODO: figure out how to assert ast 769 | } 770 | } 771 | --------------------------------------------------------------------------------