diff --git a/src/builtin_parser.rs b/src/builtin_parser.rs index 1c3cf7d..9726416 100644 --- a/src/builtin_parser.rs +++ b/src/builtin_parser.rs @@ -15,7 +15,7 @@ pub(crate) mod lexer; pub(crate) mod parser; pub(crate) mod runner; -pub use runner::{environment::Environment, unique_rc::*, RunError, Value}; +pub use runner::{environment::Environment, error::RunError, unique_rc::*, Value}; /// Wrapper around `T` that stores a [Span] (A location in the source code) #[derive(Debug, Clone)] diff --git a/src/builtin_parser/runner.rs b/src/builtin_parser/runner.rs index a113973..c2ae2a6 100644 --- a/src/builtin_parser/runner.rs +++ b/src/builtin_parser/runner.rs @@ -4,9 +4,10 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc}; use environment::Environment; -use crate::command::{CommandHint, CommandHintColor, CommandHints}; +use crate::{command::CommandHints, ui::COMMAND_RESULT_NAME}; use self::{ + error::RunError, reflection::{object_to_dynamic_struct, CreateRegistration, IntoResource}, unique_rc::{UniqueRc, WeakRef}, }; @@ -19,8 +20,9 @@ use bevy::{ prelude::*, reflect::{DynamicEnum, Enum, ReflectMut, TypeInfo, TypeRegistration}, }; -use logos::Span; + pub mod environment; +pub mod error; pub mod reflection; pub mod stdlib; pub mod unique_rc; @@ -35,34 +37,6 @@ pub struct EvalParams<'world, 'env, 'reg> { registrations: &'reg [&'reg TypeRegistration], } -/// An error occuring during the while executing the [`AST`](Ast) of the command. -#[derive(Debug)] -pub enum RunError { - /// A custom text message. Contains very little contextual information, try to find an existing error instead. - Custom { - /// The text of the message - text: String, - span: Span, - }, - VariableNotFound(Span), - ExpectedNumberAfterUnaryOperator(Value), - InvalidVariantForResource(String, String), - CannotIndexValue(Span), - FieldNotFoundInStruct(Span), - CouldntDereferenceValue(Span), - ReferenceToMovedData(Span), - VariableMoved(Span), - CannotBorrowValue(Span), - IncompatibleReflectTypes { - expected: String, - actual: String, - }, - EnumVariantNotFound { - name: String, - }, - CannotMoveOutOfResource(Spanned), -} - pub fn run(ast: Ast, world: &mut World) { // Temporarily remove the [`Environment`] resource to gain // mutability without needing a mutable reference. @@ -118,17 +92,16 @@ pub fn run(ast: Ast, world: &mut World) { match value { Ok(Value::None) => {} Ok(value) => match value.try_format(span, world, ®istrations) { - Ok(value) => info!(name: "console_result", "> {value}"), + Ok(value) => info!(name: COMMAND_RESULT_NAME, "> {value}"), Err(err) => error!("{err:?}"), }, - Err(err) => error!("{err:?}"), + Err(err) => { + hints.push(err.hints()); + + error!("{}", err.message()); + } } } - hints.push([CommandHint::new( - 3..4, - CommandHintColor::Error, - "woah description", - )]); } // Add back the resources @@ -192,13 +165,15 @@ fn eval_expression( let TypeInfo::Enum(enum_info) = registeration.type_info() else { unreachable!(); }; - match value_expr.value { + let Spanned { span, value } = *value_expr; + match value { Expression::Variable(variable) => { if enum_info.contains_variant(&variable) { let new_enum = DynamicEnum::new(variable, ()); dyn_enum.set(Box::new(new_enum)).map_err(|new_enum| { RunError::IncompatibleReflectTypes { + span, expected: dyn_enum.variant_name().to_string(), actual: new_enum .downcast::() @@ -208,7 +183,10 @@ fn eval_expression( } }) } else { - Err(RunError::EnumVariantNotFound { name: variable }) + Err(RunError::EnumVariantNotFound { + name: variable, + span, + }) }? } Expression::StructObject { name, map } => { @@ -244,6 +222,7 @@ fn eval_expression( } } _ => { + let span = value_expr.span.clone(); let value = eval_expression( *value_expr, EvalParams { @@ -262,6 +241,7 @@ fn eval_expression( reflect.set(value_reflect).map_err(|value_reflect| { RunError::IncompatibleReflectTypes { + span, expected: reflect.reflect_type_path().to_string(), actual: value_reflect.reflect_type_path().to_string(), } @@ -334,9 +314,10 @@ fn eval_expression( registrations, }, ), - Expression::UnaryOp(expr) => { - let expr = eval_expression( - *expr, + Expression::UnaryOp(sub_expr) => { + let span = sub_expr.span.clone(); + let value = eval_expression( + *sub_expr, EvalParams { world, environment, @@ -344,9 +325,12 @@ fn eval_expression( }, )?; - match expr { + match value { Value::Number(number) => Ok(Value::Number(-number)), - _ => Err(RunError::ExpectedNumberAfterUnaryOperator(expr)), + _ => Err(RunError::ExpectedNumberAfterUnaryOperator(Spanned { + span, + value, + })), } } Expression::StructObject { name, map } => { diff --git a/src/builtin_parser/runner/environment.rs b/src/builtin_parser/runner/environment.rs index 2f3e697..28a161c 100644 --- a/src/builtin_parser/runner/environment.rs +++ b/src/builtin_parser/runner/environment.rs @@ -7,9 +7,10 @@ use logos::Span; use super::{ super::{parser::Expression, Spanned}, + error::RunError, eval_expression, stdlib, unique_rc::UniqueRc, - EvalParams, RunError, Value, + EvalParams, Value, }; /// Macro for mass registering functions. diff --git a/src/builtin_parser/runner/error.rs b/src/builtin_parser/runner/error.rs new file mode 100644 index 0000000..1472de0 --- /dev/null +++ b/src/builtin_parser/runner/error.rs @@ -0,0 +1,95 @@ +use std::borrow::Cow; + +use logos::Span; + +use crate::{ + builtin_parser::Spanned, + command::{CommandHint, CommandHintColor}, +}; + +use super::Value; + +/// An error occuring during the while executing the [`AST`](Ast) of the command. +#[derive(Debug)] +pub enum RunError { + /// A custom text message. Contains very little contextual information, try to find an existing error instead. + Custom { + /// The text of the message + text: String, + span: Span, + }, + VariableNotFound(Span), + ExpectedNumberAfterUnaryOperator(Spanned), + InvalidVariantForResource(String, String), + CannotIndexValue(Span), + FieldNotFoundInStruct(Span), + CouldntDereferenceValue(Span), + ReferenceToMovedData(Span), + VariableMoved(Span), + CannotBorrowValue(Span), + IncompatibleReflectTypes { + expected: String, + actual: String, + span: Span, + }, + EnumVariantNotFound { + name: String, + span: Span, + }, + CannotMoveOutOfResource(Spanned), +} + +impl RunError { + pub fn spans(&self) -> Vec { + use RunError::*; + + match self { + Custom { span, .. } => vec![span.clone()], + VariableNotFound(span) => vec![span.clone()], + ExpectedNumberAfterUnaryOperator(Spanned { span, .. }) => vec![span.clone()], + InvalidVariantForResource(_, _) => todo!(), + CannotIndexValue(span) => vec![span.clone()], + FieldNotFoundInStruct(span) => vec![span.clone()], + CouldntDereferenceValue(span) => vec![span.clone()], + ReferenceToMovedData(span) => vec![span.clone()], + VariableMoved(span) => vec![span.clone()], + CannotBorrowValue(span) => vec![span.clone()], + IncompatibleReflectTypes { span, .. } => vec![span.clone()], + EnumVariantNotFound { span, .. } => vec![span.clone()], + CannotMoveOutOfResource(Spanned { span, .. }) => vec![span.clone()], + } + } + pub fn hints(&self) -> Vec { + self.spans() + .into_iter() + .map(|span| CommandHint::new(span, CommandHintColor::Error, "todo")) + .collect() + } + pub fn message(&self) -> Cow<'static, str> { + use RunError::*; + + match self { + Custom { text, .. } => text.clone().into(), + VariableNotFound(_) => "Variable not found.".into(), + ExpectedNumberAfterUnaryOperator(Spanned { value, .. }) => format!( + "Expected a number after unary operator (-) but got {} instead.", + value.kind() + ) + .into(), + InvalidVariantForResource(_, _) => todo!(), + CannotIndexValue(_) => todo!(), + FieldNotFoundInStruct(_) => todo!(), + CouldntDereferenceValue(_) => todo!(), + ReferenceToMovedData(_) => todo!(), + VariableMoved(_) => todo!(), + CannotBorrowValue(_) => todo!(), + IncompatibleReflectTypes { + expected, + actual, + span, + } => todo!(), + EnumVariantNotFound { name, span } => todo!(), + CannotMoveOutOfResource(Spanned { value, .. }) => format!("Cannot move out of resource `{value}`, try borrowing it instead.").into(), + } + } +} diff --git a/src/builtin_parser/runner/stdlib.rs b/src/builtin_parser/runner/stdlib.rs index 746d20c..c5b4666 100644 --- a/src/builtin_parser/runner/stdlib.rs +++ b/src/builtin_parser/runner/stdlib.rs @@ -2,7 +2,7 @@ use crate::register; use bevy::{ecs::world::World, log::info, reflect::TypeRegistration}; use std::{cell::Ref, ops::Range}; -use super::{Environment, RunError, Spanned, Value}; +use super::{error::RunError, Environment, Spanned, Value}; fn print( value: Spanned, diff --git a/src/builtin_parser/runner/value.rs b/src/builtin_parser/runner/value.rs index becb7fb..558f89e 100644 --- a/src/builtin_parser/runner/value.rs +++ b/src/builtin_parser/runner/value.rs @@ -7,7 +7,7 @@ use crate::builtin_parser::{Environment, StrongRef}; use super::environment::FunctionParam; use super::reflection::{CreateRegistration, IntoResource}; use super::unique_rc::WeakRef; -use super::{super::Spanned, RunError}; +use super::{super::Spanned, error::RunError}; use bevy::ecs::world::World; use bevy::reflect::{ @@ -19,7 +19,6 @@ use logos::Span; /// A runtime value #[derive(Debug)] -#[non_exhaustive] pub enum Value { /// Nothing at all None, @@ -131,6 +130,19 @@ impl Value { Value::Resource(resource) => Ok(fancy_debug_print(resource, world, registrations)), } } + + pub fn kind(&self) -> &'static str { + match self { + Value::None => "nothing", + Value::Number(_) => "a number", + Value::Boolean(_) => "a boolean", + Value::String(_) => "a string", + Value::Reference(_) => "a reference", + Value::Object(_) => "a object", + Value::StructObject { .. } => "a struct object", + Value::Resource(_) => "a resource", + } + } } /// A massive function that takes in a type registration and the world and then /// does all the hard work of printing out the type nicely. diff --git a/src/command.rs b/src/command.rs index dee79e8..b6d9148 100644 --- a/src/command.rs +++ b/src/command.rs @@ -29,6 +29,7 @@ impl From> for DefaultCommandParser { } /// A hint displayed to the user when they make a mistake. +#[derive(Debug, Clone)] pub struct CommandHint { /// The color of the hint. pub color: CommandHintColor, @@ -53,6 +54,7 @@ impl CommandHint { } /// The color of a [`CommandHint`], may either be a standard color or a [`Custom`](CommandHintColor::Custom) [`Color`]. +#[derive(Debug, Clone)] pub enum CommandHintColor { /// An error marks bad code that cannot be recovered from. /// @@ -78,11 +80,30 @@ pub enum CommandHintColor { /// A resource where hints (errors/warnings/etc) are stored /// to be displayed in the developer console. #[derive(Resource, Default, Deref)] -pub struct CommandHints(Vec>); +pub struct CommandHints { + #[deref] + hints: Vec>, + hint_added: bool, +} impl CommandHints { - /// Push a list of hints. This should be done once per command call (even if theres no hints). + /// Push a list of hints. This should be done once per command call. pub fn push(&mut self, hints: impl Into>) { - self.0.push(hints.into()); + if self.hint_added { + warn!( + "Hints were added twice! Hint 1: {:?}, Hint 2: {:?}", + self.hints.last(), + hints.into() + ) + } else { + self.hints.push(hints.into()); + } + } + pub(crate) fn reset_hint_added(&mut self) { + if self.hint_added { + self.hint_added = false; + } else { + self.push([]); + } } } @@ -97,10 +118,10 @@ impl CommandHints { /// pub struct MyCustomParser; /// impl CommandParser for MyCustomParser { /// fn parse(&self, command: &str, world: &mut World) { -/// // The `name: "console_result"` tells the console this is a result from +/// // The `name: COMMAND_RESULT_NAME` tells the console this is a result from /// // the parser and then formats it accordingly. /// // TODO: figure out better solution for this -/// info!(name: "console_result", "You just entered the command {command}") +/// info!(name: COMMAND_RESULT_NAME, "You just entered the command {command}") /// } /// } /// ``` diff --git a/src/config.rs b/src/config.rs index 4f8aa95..d602e08 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,7 +1,7 @@ //! Configuration structs for the developer console. use bevy::{log::Level, prelude::*}; -use bevy_egui::egui::{self, Color32, FontId, TextFormat}; +use bevy_egui::egui::{Color32, FontId, TextFormat}; /// The configuration of the developer console. #[derive(Resource)] @@ -25,8 +25,10 @@ impl Default for ConsoleConfig { pub struct ConsoleTheme { /// The font used in the developer console. pub font: FontId, - pub dark: Color, + /// The default color of text. pub text_color: Color, + /// The color of dark text. + pub dark: Color, /// The color of the "error" level. /// /// Designates very serious errors. diff --git a/src/logging/log_plugin.rs b/src/logging/log_plugin.rs index 0a9d365..ea684dc 100644 --- a/src/logging/log_plugin.rs +++ b/src/logging/log_plugin.rs @@ -259,7 +259,7 @@ impl Layer for LogEventLayer { fn on_event( &self, event: &bevy::utils::tracing::Event<'_>, - ctx: tracing_subscriber::layer::Context<'_, S>, + _ctx: tracing_subscriber::layer::Context<'_, S>, ) { let mut message = None; event.record(&mut LogEventVisitor(&mut message)); diff --git a/src/ui.rs b/src/ui.rs index d45efa4..e79af65 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -17,6 +17,12 @@ use crate::{ prelude::ConsoleConfig, }; +const COMMAND_MESSAGE_PREFIX: &str = "$ "; +/// Identifier for log messages that show a previous command. +pub const COMMAND_MESSAGE_NAME: &str = "console_command"; +/// Identifier for log messages that show the result of a history +pub const COMMAND_RESULT_NAME: &str = "console_result"; + #[derive(Default, Resource)] pub(crate) struct ConsoleUiState { /// Whever the console is open or not. @@ -61,7 +67,7 @@ pub(crate) fn render_ui( ) { let mut submit_command = |command: &mut String| { if !command.trim().is_empty() { - info!(name: "console_command", "$ {}", command.trim()); + info!(name: COMMAND_MESSAGE_NAME, "{COMMAND_MESSAGE_PREFIX}{}", command.trim()); // Get the owned command string by replacing it with an empty string let command = std::mem::take(command); commands.add(ExecuteCommand(command)); @@ -193,23 +199,35 @@ fn format_line( 0.0, config.theme.format_dark(), ); - if *name == "console_command" || *name == "console_result" { - if *name == "console_command" { + if *name == COMMAND_MESSAGE_NAME || *name == COMMAND_RESULT_NAME { + if *name == COMMAND_MESSAGE_NAME { if new { hints.reset_hint_added(); } - if let Some(hints) = hints.get(*command_index) { - let hint = &hints[0]; - text.append(&message[..hint.span.start], 0., config.theme.format_text()); + let hints = &hints[*command_index]; + + // TODO: Handle more than just he first element + if let Some(hint) = hints.first() { + const PREFIX_LEN: usize = COMMAND_MESSAGE_PREFIX.len(); + text.append( - &message[hint.span.clone()], + &message[..hint.span.start + PREFIX_LEN], + 0., + config.theme.format_text(), + ); + text.append( + &message[hint.span.start + PREFIX_LEN..hint.span.end + PREFIX_LEN], 0., TextFormat { - underline: Stroke::new(2.0, config.theme.error.to_color32()), + underline: Stroke::new(1.0, config.theme.error.to_color32()), ..config.theme.format_text() }, ); - text.append(&message[hint.span.end..], 0., config.theme.format_text()); + text.append( + &message[hint.span.end + PREFIX_LEN..], + 0., + config.theme.format_text(), + ); return text; } *command_index += 1;