diff --git a/Cargo.toml b/Cargo.toml index beb3694..c252ff8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ tracing-log = "0.2.0" tracing-subscriber = "0.3.18" web-time = "0.2.4" -# built-in parser +# builtin-parser feature logos = { version = "0.13.0", optional = true } [target.'cfg(target_os = "android")'.dependencies] diff --git a/examples/custom_functions.rs b/examples/custom_functions.rs index 3e8d83d..3254309 100644 --- a/examples/custom_functions.rs +++ b/examples/custom_functions.rs @@ -5,7 +5,7 @@ use bevy::{ prelude::*, }; use bevy_dev_console::{ - builtin_parser::{Environment, RunError, Spanned, StrongRef, Value}, + builtin_parser::{Environment, Number, RunError, Spanned, StrongRef, Value}, prelude::*, register, }; @@ -20,7 +20,9 @@ fn time_since_epoch() { info!("The unix epoch was {} seconds ago", time.as_secs()); } -/// Function with parameters and return value +/// Function with parameters and return value. +/// +/// Note that this will cause an error if an integer to passed to this function. fn add(num1: f64, num2: f64) -> f64 { num1 + num2 } @@ -37,24 +39,25 @@ fn print_debug_info(value: Spanned) { struct MyCounter(u32); /// Function with [`World`] -fn increment_global_counter(world: &mut World) -> f64 { +fn increment_global_counter(world: &mut World) -> u32 { world.resource_scope(|_, mut counter: Mut| { counter.0 += 1; - counter.0 as f64 + counter.0 }) } // Function with reference (Syntax subject to change soon) fn increment_number(number: Spanned>) -> Result<(), RunError> { + let span = number.span; let mut reference = number.value.borrow_mut(); if let Value::Number(number) = &mut *reference { - *number += 1.0; + *number = Number::add(*number, Number::Integer(1), span).unwrap(); Ok(()) } else { Err(RunError::Custom { text: "Oh nooo".to_string(), - span: number.span, + span, }) } } diff --git a/src/builtin_parser.rs b/src/builtin_parser.rs index 9726416..c7bc222 100644 --- a/src/builtin_parser.rs +++ b/src/builtin_parser.rs @@ -12,11 +12,29 @@ use crate::command::{CommandParser, DefaultCommandParser}; use self::{lexer::TokenStream, parser::parse}; pub(crate) mod lexer; +pub(crate) mod number; pub(crate) mod parser; pub(crate) mod runner; +pub use number::*; pub use runner::{environment::Environment, error::RunError, unique_rc::*, Value}; +/// Additonal traits for span. +pub trait SpanExtension { + /// Wrap this value with a [`Spanned`]. + fn wrap(self, value: T) -> Spanned; + /// Combine two [`Span`]s into one. + fn join(self, span: Self) -> Self; +} +impl SpanExtension for Span { + fn wrap(self, value: T) -> Spanned { + Spanned { span: self, value } + } + fn join(self, span: Self) -> Self { + self.start..span.end + } +} + /// Wrapper around `T` that stores a [Span] (A location in the source code) #[derive(Debug, Clone)] pub struct Spanned { diff --git a/src/builtin_parser/lexer.rs b/src/builtin_parser/lexer.rs index ac6f808..90b8a6f 100644 --- a/src/builtin_parser/lexer.rs +++ b/src/builtin_parser/lexer.rs @@ -67,8 +67,22 @@ pub enum Token { #[regex("[a-zA-Z_][a-zA-Z0-9_]*")] Identifer, - #[regex("[0-9]+")] - Number, + #[regex(r#"[0-9]+"#)] + IntegerNumber, + #[regex(r#"[0-9]*\.?[0-9]*"#)] + FloatNumber, + + #[token("i8")] + #[token("i16")] + #[token("i32")] + #[token("i64")] + #[token("u8")] + #[token("u16")] + #[token("u32")] + #[token("u64")] + #[token("f32")] + #[token("f64")] + NumberType, } /// A wrapper for the lexer which provides token peeking and other helper functions @@ -138,7 +152,7 @@ impl<'a> TokenStream<'a> { } /// Get a [`str`] slice of the next [`Token`]. - pub fn _peek_slice(&self) -> &str { + pub fn peek_slice(&self) -> &str { self.lexer.slice() } @@ -171,7 +185,7 @@ mod tests { #[test] fn var_assign() { - let mut lexer = TokenStream::new("x = 1 + 2 - 30"); + let mut lexer = TokenStream::new("x = 1 + 2 - 30.6"); assert_eq!(lexer.next(), Some(Ok(Token::Identifer))); assert_eq!(lexer.slice(), "x"); @@ -179,19 +193,19 @@ mod tests { assert_eq!(lexer.next(), Some(Ok(Token::Equals))); assert_eq!(lexer.slice(), "="); - assert_eq!(lexer.next(), Some(Ok(Token::Number))); + assert_eq!(lexer.next(), Some(Ok(Token::IntegerNumber))); assert_eq!(lexer.slice(), "1"); assert_eq!(lexer.next(), Some(Ok(Token::Plus))); assert_eq!(lexer.slice(), "+"); - assert_eq!(lexer.next(), Some(Ok(Token::Number))); + assert_eq!(lexer.next(), Some(Ok(Token::IntegerNumber))); assert_eq!(lexer.slice(), "2"); assert_eq!(lexer.next(), Some(Ok(Token::Minus))); assert_eq!(lexer.slice(), "-"); - assert_eq!(lexer.next(), Some(Ok(Token::Number))); - assert_eq!(lexer.slice(), "30"); + assert_eq!(lexer.next(), Some(Ok(Token::FloatNumber))); + assert_eq!(lexer.slice(), "30.6"); } } diff --git a/src/builtin_parser/number.rs b/src/builtin_parser/number.rs new file mode 100644 index 0000000..cea88ef --- /dev/null +++ b/src/builtin_parser/number.rs @@ -0,0 +1,217 @@ +use std::fmt::Display; +use std::ops::*; + +use bevy::reflect::Reflect; +use logos::Span; + +use super::{RunError, SpanExtension, Spanned}; + +/// An enum that contains any type of number. +/// +/// The [`Integer`](Number::Integer) and [`Float`](Number::Float) types +/// are generic types that then get downcasted when they first interact +/// with a concrete type. (i.e. calling a function, etc) +#[derive(Debug, Clone, Copy)] +#[allow(missing_docs, non_camel_case_types)] +pub enum Number { + /// Generic integer that can get downcasted. + Integer(i128), + /// Generic float that can get downcasted to a [`f64`] and [`f32`] + Float(f64), + + u8(u8), + u16(u16), + u32(u32), + u64(u64), + i8(i8), + i16(i16), + i32(i32), + i64(i64), + f32(f32), + f64(f64), +} + +impl Number { + /// Converts this into a [`Box`](Reflect). + pub fn reflect(self) -> Box { + match self { + Number::u8(number) => Box::new(number), + Number::u16(number) => Box::new(number), + Number::u32(number) => Box::new(number), + Number::u64(number) => Box::new(number), + Number::i8(number) => Box::new(number), + Number::i16(number) => Box::new(number), + Number::i32(number) => Box::new(number), + Number::i64(number) => Box::new(number), + Number::f32(number) => Box::new(number), + Number::f64(number) => Box::new(number), + Number::Integer(_) => todo!(), + Number::Float(_) => todo!(), + } + } + + /// Returns a [`&'static str`](str) represents the kind of the number. + pub fn kind(&self) -> &'static str { + match self { + Number::Float(_) => "a float", + Number::Integer(_) => "an integer", + Number::u8(_) => "a u8", + Number::u16(_) => "a u16", + Number::u32(_) => "a u32", + Number::u64(_) => "a u64", + Number::i8(_) => "a i8", + Number::i16(_) => "a i16", + Number::i32(_) => "a i32", + Number::i64(_) => "a i64", + Number::f32(_) => "a f32", + Number::f64(_) => "a f64", + } + } +} + +impl Display for Number { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Number::Float(number) => write!(f, "{number} (float)"), + Number::Integer(number) => write!(f, "{number} (integer)"), + Number::u8(number) => write!(f, "{number} (u8)"), + Number::u16(number) => write!(f, "{number} (u16)"), + Number::u32(number) => write!(f, "{number} (u32)"), + Number::u64(number) => write!(f, "{number} (u64)"), + Number::i8(number) => write!(f, "{number} (i8)"), + Number::i16(number) => write!(f, "{number} (i16)"), + Number::i32(number) => write!(f, "{number} (i32)"), + Number::i64(number) => write!(f, "{number} (i64)"), + Number::f32(number) => write!(f, "{number} (f32)"), + Number::f64(number) => write!(f, "{number} (f64)"), + } + } +} + +macro_rules! impl_op { + ($fn:ident, $op:tt) => { + impl Number { + #[doc = concat!("Performs the `", stringify!($op), "` calculation.")] + pub fn $fn(left: Number, right: Number, span: Span) -> Result { + match (left, right) { + (Number::u8(left), Number::u8(right)) => Ok(Number::u8(left $op right)), + (Number::u16(left), Number::u16(right)) => Ok(Number::u16(left $op right)), + (Number::u32(left), Number::u32(right)) => Ok(Number::u32(left $op right)), + (Number::u64(left), Number::u64(right)) => Ok(Number::u64(left $op right)), + (Number::i8(left), Number::i8(right)) => Ok(Number::i8(left $op right)), + (Number::i16(left), Number::i16(right)) => Ok(Number::i16(left $op right)), + (Number::i32(left), Number::i32(right)) => Ok(Number::i32(left $op right)), + (Number::i64(left), Number::i64(right)) => Ok(Number::i64(left $op right)), + (Number::f32(left), Number::f32(right)) => Ok(Number::f32(left $op right)), + (Number::f64(left), Number::f64(right)) => Ok(Number::f64(left $op right)), + + (Number::Integer(left), Number::u8(right)) => Ok(Number::u8(left as u8 $op right)), + (Number::Integer(left), Number::u16(right)) => Ok(Number::u16(left as u16 $op right)), + (Number::Integer(left), Number::u32(right)) => Ok(Number::u32(left as u32 $op right)), + (Number::Integer(left), Number::u64(right)) => Ok(Number::u64(left as u64 $op right)), + (Number::Integer(left), Number::i8(right)) => Ok(Number::i8(left as i8 $op right)), + (Number::Integer(left), Number::i16(right)) => Ok(Number::i16(left as i16 $op right)), + (Number::Integer(left), Number::i32(right)) => Ok(Number::i32(left as i32 $op right)), + (Number::Integer(left), Number::i64(right)) => Ok(Number::i64(left as i64 $op right)), + (Number::Integer(left), Number::Integer(right)) => Ok(Number::Integer(left $op right)), + (Number::u8(left), Number::Integer(right)) => Ok(Number::u8(left $op right as u8)), + (Number::u16(left), Number::Integer(right)) => Ok(Number::u16(left $op right as u16)), + (Number::u32(left), Number::Integer(right)) => Ok(Number::u32(left $op right as u32)), + (Number::u64(left), Number::Integer(right)) => Ok(Number::u64(left $op right as u64)), + (Number::i8(left), Number::Integer(right)) => Ok(Number::i8(left $op right as i8)), + (Number::i16(left), Number::Integer(right)) => Ok(Number::i16(left $op right as i16)), + (Number::i32(left), Number::Integer(right)) => Ok(Number::i32(left $op right as i32)), + (Number::i64(left), Number::Integer(right)) => Ok(Number::i64(left $op right as i64)), + + (Number::Float(left), Number::f32(right)) => Ok(Number::f32(left as f32 $op right)), + (Number::Float(left), Number::f64(right)) => Ok(Number::f64(left as f64 $op right)), + (Number::Float(left), Number::Float(right)) => Ok(Number::Float(left $op right)), + (Number::f32(left), Number::Float(right)) => Ok(Number::f32(left $op right as f32)), + (Number::f64(left), Number::Float(right)) => Ok(Number::f64(left $op right as f64)), + _ => Err(RunError::IncompatibleNumberTypes { + left: left.kind(), + right: right.kind(), + span + }) + } + } + } + }; +} + +impl_op!(add, +); +impl_op!(sub, -); +impl_op!(mul, *); +impl_op!(div, /); +impl_op!(rem, %); + +macro_rules! impl_op_spanned { + ($trait:ident, $method:ident) => { + impl $trait for Spanned { + type Output = Result; + fn $method(self, rhs: Self) -> Self::Output { + let span = self.span.join(rhs.span); + + Number::$method(self.value, rhs.value, span) + } + } + }; +} + +impl_op_spanned!(Add, add); +impl_op_spanned!(Sub, sub); +impl_op_spanned!(Mul, mul); +impl_op_spanned!(Rem, rem); + +impl Number { + /// Performs the unary `-` operation. + pub fn neg(self, span: Span) -> Result { + match self { + Number::u8(_) => Err(RunError::CannotNegateUnsignedInteger(Spanned { + span, + value: self, + })), + Number::u16(_) => Err(RunError::CannotNegateUnsignedInteger(Spanned { + span, + value: self, + })), + Number::u32(_) => Err(RunError::CannotNegateUnsignedInteger(Spanned { + span, + value: self, + })), + Number::u64(_) => Err(RunError::CannotNegateUnsignedInteger(Spanned { + span, + value: self, + })), + Number::i8(number) => Ok(Number::i8(-number)), + Number::i16(number) => Ok(Number::i16(-number)), + Number::i32(number) => Ok(Number::i32(-number)), + Number::i64(number) => Ok(Number::i64(-number)), + Number::f32(number) => Ok(Number::f32(-number)), + Number::f64(number) => Ok(Number::f64(-number)), + Number::Float(number) => Ok(Number::Float(-number)), + Number::Integer(number) => Ok(Number::Integer(-number)), + } + } +} + +macro_rules! from_primitive { + ($primitive:ident) => { + impl From<$primitive> for Number { + fn from(value: $primitive) -> Self { + Number::$primitive(value) + } + } + }; +} + +from_primitive!(u8); +from_primitive!(u16); +from_primitive!(u32); +from_primitive!(u64); +from_primitive!(i8); +from_primitive!(i16); +from_primitive!(i32); +from_primitive!(i64); +from_primitive!(f32); +from_primitive!(f64); diff --git a/src/builtin_parser/parser.rs b/src/builtin_parser/parser.rs index 01ecdd8..f22c8d3 100644 --- a/src/builtin_parser/parser.rs +++ b/src/builtin_parser/parser.rs @@ -1,10 +1,11 @@ //! Generates an abstract syntax tree from a list of tokens. use logos::Span; -use std::collections::HashMap; +use std::{collections::HashMap, num::IntErrorKind}; use super::{ lexer::{FailedToLexCharacter, Token, TokenStream}, + number::Number, runner::environment::Function, Environment, Spanned, }; @@ -39,7 +40,7 @@ pub enum Expression { name: Box>, value: Box>, }, - Number(f64), + Number(Number), Variable(String), BinaryOp { left: Box>, @@ -92,6 +93,9 @@ pub enum ParseError { }, ExpectedEndline(Spanned), UnexpectedToken(Spanned), + InvalidSuffixForDecimal(std::ops::Range), + NegativeIntOverflow(Span), + PositiveIntOverflow(Span), } pub fn parse(tokens: &mut TokenStream, environment: &Environment) -> Result { @@ -129,7 +133,14 @@ fn parse_expression( } let loop_count = match parse_additive(tokens, environment)?.value { - Expression::Number(number) => number as u64, + Expression::Number(Number::u8(number)) => number as u64, + Expression::Number(Number::u16(number)) => number as u64, + Expression::Number(Number::u32(number)) => number as u64, + Expression::Number(Number::u64(number)) => number, + Expression::Number(Number::i8(number)) => number as u64, + Expression::Number(Number::i16(number)) => number as u64, + Expression::Number(Number::i32(number)) => number as u64, + Expression::Number(Number::i64(number)) => number as u64, t => todo!("{t:?}"), }; @@ -255,6 +266,7 @@ fn parse_primary( if let Some(Function { argument_count, .. }) = environment.get_function(tokens.slice()) { + dbg!(argument_count); let name = tokens.slice().to_string(); let start = tokens.span().start; let mut arguments = Vec::new(); @@ -296,14 +308,79 @@ fn parse_primary( let expr = parse_primary(tokens, environment)?; Ok(tokens.wrap_span(Expression::Dereference(Box::new(expr)))) } - Some(Ok(Token::Number)) => { - Ok(tokens.wrap_span(Expression::Number(tokens.slice().parse().unwrap()))) + Some(Ok(Token::IntegerNumber)) => { + if let Some(Ok(Token::NumberType)) = tokens.peek() { + let err_map = |error: std::num::ParseIntError| match error.kind() { + IntErrorKind::PosOverflow => ParseError::PositiveIntOverflow(tokens.span()), + IntErrorKind::NegOverflow => ParseError::NegativeIntOverflow(tokens.span()), + _ => unreachable!("lexer makes sure other errors arent possible"), + }; + let number: Number = match tokens.peek_slice() { + "u8" => Number::u8(tokens.slice().parse().map_err(err_map)?), + "u16" => Number::u16(tokens.slice().parse().map_err(err_map)?), + "u32" => Number::u32(tokens.slice().parse().map_err(err_map)?), + "u64" => Number::u64(tokens.slice().parse().map_err(err_map)?), + "i8" => Number::i8(tokens.slice().parse().map_err(err_map)?), + "i16" => Number::i16(tokens.slice().parse().map_err(err_map)?), + "i32" => Number::i32(tokens.slice().parse().map_err(err_map)?), + "i64" => Number::i64(tokens.slice().parse().map_err(err_map)?), + "f32" => Number::f32(tokens.slice().parse().unwrap()), + "f64" => Number::f64(tokens.slice().parse().unwrap()), + _ => unreachable!(), + }; + let start_span = tokens.span().end; + tokens.next(); + + Ok(Spanned { + span: start_span..tokens.span().end, + value: Expression::Number(number), + }) + } else { + let number = Number::Integer(tokens.slice().parse().unwrap()); + + Ok(Spanned { + span: tokens.span(), + value: Expression::Number(number), + }) + } + } + Some(Ok(Token::FloatNumber)) => { + if let Some(Ok(Token::NumberType)) = tokens.peek() { + let number: Number = match tokens.peek_slice() { + "u8" => Err(ParseError::InvalidSuffixForDecimal(tokens.span()))?, + "u16" => Err(ParseError::InvalidSuffixForDecimal(tokens.span()))?, + "u32" => Err(ParseError::InvalidSuffixForDecimal(tokens.span()))?, + "u64" => Err(ParseError::InvalidSuffixForDecimal(tokens.span()))?, + "i8" => Err(ParseError::InvalidSuffixForDecimal(tokens.span()))?, + "i16" => Err(ParseError::InvalidSuffixForDecimal(tokens.span()))?, + "i32" => Err(ParseError::InvalidSuffixForDecimal(tokens.span()))?, + "i64" => Err(ParseError::InvalidSuffixForDecimal(tokens.span()))?, + "f32" => Number::f32(tokens.slice().parse().unwrap()), + "f64" => Number::f64(tokens.slice().parse().unwrap()), + _ => unreachable!(), + }; + let start_span = tokens.span().end; + + tokens.next(); + + Ok(Spanned { + span: start_span..tokens.span().end, + value: Expression::Number(number), + }) + } else { + let number = Number::Float(tokens.slice().parse().unwrap()); + + Ok(Spanned { + span: tokens.span(), + value: Expression::Number(number), + }) + } } Some(Ok(Token::True)) => Ok(tokens.wrap_span(Expression::Boolean(true))), Some(Ok(Token::False)) => Ok(tokens.wrap_span(Expression::Boolean(false))), Some(Ok(token)) => Err(ParseError::UnexpectedToken(tokens.wrap_span(token))), Some(Err(FailedToLexCharacter)) => Err(ParseError::FailedToLexCharacter(tokens.span())), - None => unreachable!("oh fuck what have i done to cause this to happen"), + None => todo!(), }?; // If theres a dot after the expression, do a member expression: while let Some(Ok(Token::Dot)) = tokens.peek() { diff --git a/src/builtin_parser/runner.rs b/src/builtin_parser/runner.rs index cdbaf7a..e6e5ac3 100644 --- a/src/builtin_parser/runner.rs +++ b/src/builtin_parser/runner.rs @@ -14,7 +14,7 @@ use self::{ use super::{ parser::{Ast, Expression, Operator}, - Spanned, + Number, SpanExtension, Spanned, }; use bevy::{ prelude::*, @@ -291,11 +291,11 @@ fn eval_expression( match (left, right) { (Value::Number(left), Value::Number(right)) => Ok(Value::Number(match operator { - Operator::Add => left + right, - Operator::Sub => left - right, - Operator::Mul => left * right, - Operator::Div => left / right, - Operator::Mod => left % right, + Operator::Add => Number::add(left, right, expr.span)?, + Operator::Sub => Number::sub(left, right, expr.span)?, + Operator::Mul => Number::mul(left, right, expr.span)?, + Operator::Div => Number::div(left, right, expr.span)?, + Operator::Mod => Number::rem(left, right, expr.span)?, })), (left, right) => todo!("{left:#?}, {right:#?}"), } @@ -325,12 +325,13 @@ fn eval_expression( }, )?; - match value { - Value::Number(number) => Ok(Value::Number(-number)), - _ => Err(RunError::ExpectedNumberAfterUnaryOperator(Spanned { + if let Value::Number(number) = value { + Ok(Value::Number(number.neg(span)?)) + } else { + 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 28a161c..1a396b8 100644 --- a/src/builtin_parser/runner/environment.rs +++ b/src/builtin_parser/runner/environment.rs @@ -154,8 +154,7 @@ macro_rules! impl_into_function { world, environment, registrations - ) - .unwrap_or_else(|_| todo!()); + )?; res }),+)? @@ -320,7 +319,7 @@ impl Environment { /// Registers a function for use inside the language. /// - /// All parameters must implement [`TryFrom`]. + /// All parameters must implement [`FunctionParam`]. /// There is a limit of 8 parameters. /// /// The return value of the function must implement [`Into`] diff --git a/src/builtin_parser/runner/error.rs b/src/builtin_parser/runner/error.rs index 166730a..57c9ec5 100644 --- a/src/builtin_parser/runner/error.rs +++ b/src/builtin_parser/runner/error.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use logos::Span; use crate::{ - builtin_parser::Spanned, + builtin_parser::{number::Number, Spanned}, command::{CommandHint, CommandHintColor}, }; @@ -11,6 +11,7 @@ use super::Value; /// An error occuring during the while executing the [`AST`](Ast) of the command. #[derive(Debug)] +#[allow(missing_docs)] pub enum RunError { /// A custom text message. Contains very little contextual information, try to find an existing error instead. Custom { @@ -37,6 +38,17 @@ pub enum RunError { span: Span, }, CannotMoveOutOfResource(Spanned), + CannotNegateUnsignedInteger(Spanned), + IncompatibleNumberTypes { + left: &'static str, + right: &'static str, + span: Span, + }, + IncompatibleFunctionParameter { + expected: &'static str, + actual: &'static str, + span: Span, + }, } impl RunError { @@ -57,12 +69,15 @@ impl RunError { IncompatibleReflectTypes { span, .. } => vec![span.clone()], EnumVariantNotFound { span, .. } => vec![span.clone()], CannotMoveOutOfResource(Spanned { span, .. }) => vec![span.clone()], + CannotNegateUnsignedInteger(Spanned { span, .. }) => vec![span.clone()], + IncompatibleNumberTypes { span, .. } => vec![span.clone()], + IncompatibleFunctionParameter { span, .. } => vec![span.clone()], } } pub fn hints(&self) -> Vec { self.spans() .into_iter() - .map(|span| CommandHint::new(span, CommandHintColor::Error, "todo")) + .map(|span| CommandHint::new(span, CommandHintColor::Error, self.message())) .collect() } pub fn message(&self) -> Cow<'static, str> { @@ -92,6 +107,21 @@ impl RunError { CannotMoveOutOfResource(Spanned { value, .. }) => { format!("Cannot move out of resource `{value}`, try borrowing it instead.").into() } + CannotNegateUnsignedInteger(Spanned { value, .. }) => format!( + "Unsigned integers cannot be negated. (Type: {})", + value.kind() + ) + .into(), + IncompatibleNumberTypes { left, right, .. } => { + format!("Incompatible number types; `{left}` and `{right}` are incompatible.") + .into() + } + IncompatibleFunctionParameter { + expected, actual, .. + } => { + format!("Mismatched function paramater type. Expected {expected} but got {actual}") + .into() + } } } } diff --git a/src/builtin_parser/runner/unique_rc.rs b/src/builtin_parser/runner/unique_rc.rs index 681b039..d50568e 100644 --- a/src/builtin_parser/runner/unique_rc.rs +++ b/src/builtin_parser/runner/unique_rc.rs @@ -93,10 +93,10 @@ impl WeakRef { /// ``` /// use bevy_dev_console::builtin_parser::{Value, StrongRef}; /// -/// fn add_to_reference(my_reference: StrongRef, add: f64) { +/// fn add_to_reference(my_reference: StrongRef, add: String) { /// // currently you can only do it with `Value` -/// if let Value::Number(number) = &mut *my_reference.borrow_mut() { -/// *number += add; +/// if let Value::String(string) = &mut *my_reference.borrow_mut() { +/// *string += &add; /// } else { /// todo!(); /// } diff --git a/src/builtin_parser/runner/value.rs b/src/builtin_parser/runner/value.rs index 558f89e..f5c137b 100644 --- a/src/builtin_parser/runner/value.rs +++ b/src/builtin_parser/runner/value.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::fmt::Debug; use std::{cell::RefCell, rc::Rc}; +use crate::builtin_parser::number::Number; use crate::builtin_parser::{Environment, StrongRef}; use super::environment::FunctionParam; @@ -22,8 +23,8 @@ use logos::Span; pub enum Value { /// Nothing at all None, - /// A number, for simplicity only f64s are used. (However this will probably change in the future) - Number(f64), + /// A number. + Number(Number), /// `true` or `false`. Thats it... Boolean(bool), /// A string... there isn't much to say about this one. @@ -54,7 +55,7 @@ impl Value { pub fn reflect(self) -> Box { match self { Value::None => Box::new(()), - Value::Number(number) => Box::new(number), + Value::Number(number) => number.reflect(), Value::Boolean(boolean) => Box::new(boolean), Value::String(string) => Box::new(string), Value::Reference(reference) => todo!(), @@ -134,7 +135,7 @@ impl Value { pub fn kind(&self) -> &'static str { match self { Value::None => "nothing", - Value::Number(_) => "a number", + Value::Number(number) => number.kind(), Value::Boolean(_) => "a boolean", Value::String(_) => "a string", Value::Reference(_) => "a reference", @@ -241,38 +242,41 @@ impl From<()> for Value { Value::None } } -impl From for Value { - fn from(number: f64) -> Self { - Value::Number(number) - } -} -impl From for Value { - fn from(string: String) -> Self { - Value::String(string) - } -} -impl From for Value { - fn from(boolean: bool) -> Self { - Value::Boolean(boolean) - } -} -impl From>>> for Value { - fn from(hashmap: HashMap>>) -> Self { - Value::Object(hashmap) - } +macro_rules! from_t { + (impl $type:ty: $var:ident => $expr:expr) => { + impl From<$type> for Value { + fn from($var: $type) -> Self { + $expr + } + } + }; } -impl From> for Value { - fn from(hashmap: HashMap) -> Self { - Value::Object( - hashmap - .into_iter() - .map(|(k, v)| (k, Rc::new(RefCell::new(v)))) - .collect(), - ) - } +macro_rules! from_number { + ($($number:ident),*$(,)?) => { + $( + impl From<$number> for Value { + fn from(number: $number) -> Self { + Value::Number(Number::$number(number)) + } + } + )* + }; } +from_number!(u8, u16, u32, u64, i8, i16, i32, i64, f32, f64); + +from_t!(impl String: string => Value::String(string)); +from_t!(impl bool: bool => Value::Boolean(bool)); +from_t!(impl Number: number => Value::Number(number)); +from_t!(impl HashMap>>: hashmap => Value::Object(hashmap)); +from_t!(impl HashMap: hashmap => Value::Object( + hashmap + .into_iter() + .map(|(k, v)| (k, Rc::new(RefCell::new(v)))) + .collect(), +)); + impl FunctionParam for Spanned { type Item<'world, 'env, 'reg> = Self; const USES_VALUE: bool = true; @@ -286,7 +290,7 @@ impl FunctionParam for Spanned { Ok(value.unwrap()) } } -impl> FunctionParam for Spanned { +impl, Error = RunError>> FunctionParam for Spanned { type Item<'world, 'env, 'reg> = Self; const USES_VALUE: bool = true; @@ -298,8 +302,8 @@ impl> FunctionParam for Spanned { ) -> Result, RunError> { let value = value.unwrap(); Ok(Spanned { - span: value.span, - value: T::try_from(value.value)?, + span: value.span.clone(), + value: T::try_from(value)?, }) } } @@ -329,18 +333,23 @@ macro_rules! impl_function_param_for_value { _: &mut Option<&'env mut Environment>, _: &'reg [&'reg TypeRegistration], ) -> Result, RunError> { - if let $value_pattern = value.unwrap().value { + let value = value.unwrap(); + if let $value_pattern = value.value { Ok($return) } else { - todo!() + Err(RunError::IncompatibleFunctionParameter { + expected: stringify!($type), + actual: value.value.kind(), + span: value.span, + }) } } } - impl TryFrom for $type { + impl TryFrom> for $type { type Error = RunError; - fn try_from(value: Value) -> Result { - if let $value_pattern = value { + fn try_from(value: Spanned) -> Result { + if let $value_pattern = value.value { Ok($return) } else { todo!() @@ -349,8 +358,55 @@ macro_rules! impl_function_param_for_value { } }; } -impl_function_param_for_value!(impl f64: Value::Number(number) => number); +macro_rules! impl_function_param_for_numbers { + ($generic:ident ($($number:ident),*$(,)?)) => { + $( + impl FunctionParam for $number { + type Item<'world, 'env, 'reg> = Self; + const USES_VALUE: bool = true; + + fn get<'world, 'env, 'reg>( + value: Option>, + _: &mut Option<&'world mut World>, + _: &mut Option<&'env mut Environment>, + _: &'reg [&'reg TypeRegistration], + ) -> Result, RunError> { + let value = value.unwrap(); + match value.value { + Value::Number(Number::$number(value)) => Ok(value), + Value::Number(Number::$generic(value)) => Ok(value as $number), + _ => Err(RunError::IncompatibleFunctionParameter { + expected: concat!("a ", stringify!($number)), + actual: value.value.kind(), + span: value.span, + }) + } + } + } + impl TryFrom> for $number { + type Error = RunError; + + fn try_from(value: Spanned) -> Result { + match value.value { + Value::Number(Number::$number(value)) => Ok(value), + Value::Number(Number::$generic(value)) => Ok(value as $number), + _ => Err(RunError::IncompatibleFunctionParameter { + expected: concat!("a ", stringify!($number)), + actual: value.value.kind(), + span: value.span + }) + } + } + } + )* + }; +} + +impl_function_param_for_numbers!(Float(f32, f64)); +impl_function_param_for_numbers!(Integer(u8, u16, u32, u64, i8, i16, i32, i64)); + impl_function_param_for_value!(impl bool: Value::Boolean(boolean) => boolean); +impl_function_param_for_value!(impl Number: Value::Number(number) => number); impl_function_param_for_value!(impl String: Value::String(string) => string); impl_function_param_for_value!(impl HashMap>>: Value::Object(object) => object); impl_function_param_for_value!(impl HashMap: Value::Object(object) => { diff --git a/src/command.rs b/src/command.rs index 92ad1a1..5ab07ed 100644 --- a/src/command.rs +++ b/src/command.rs @@ -79,7 +79,7 @@ pub enum CommandHintColor { /// A resource where hints (errors/warnings/etc) are stored /// to be displayed in the developer console. -#[derive(Resource, Default, Deref)] +#[derive(Resource, Debug, Default, Deref)] pub struct CommandHints { #[deref] hints: Vec>, @@ -95,13 +95,16 @@ impl CommandHints { hints.into() ) } else { + self.hint_added = true; self.hints.push(hints.into()); } } pub(crate) fn reset_hint_added(&mut self) { if self.hint_added { + dbg!("yeah"); self.hint_added = false; } else { + dbg!("nah"); self.push([]); } } @@ -121,7 +124,7 @@ impl CommandHints { /// fn parse(&self, command: &str, world: &mut World) { /// // 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 +/// # // TODO: figure out better solution for this /// info!(name: COMMAND_RESULT_NAME, "You just entered the command {command}") /// } /// } diff --git a/src/ui.rs b/src/ui.rs index 2532e86..e3dac1c 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -215,7 +215,7 @@ fn format_line( } let hints = &hints[*command_index]; - dbg!(&command_index); + *command_index += 1; // TODO: Handle more than just he first element if let Some(hint) = hints.first() { @@ -241,7 +241,6 @@ fn format_line( ); return text; } - *command_index += 1; } text.append(message.as_str(), 0.0, config.theme.format_text());