diff --git a/Cargo.lock b/Cargo.lock index ddbf39b..f2ebba6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -536,6 +536,7 @@ dependencies = [ "bevy_egui", "chrono", "console_error_panic_hook", + "const_format", "logos", "tracing-log 0.2.0", "tracing-subscriber", @@ -1395,6 +1396,26 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "const_format" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "const_panic" version = "0.2.8" diff --git a/Cargo.toml b/Cargo.toml index 909f08b..ceb4bac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ web-time = "0.2.4" # builtin-parser features logos = { version = "0.13.0", optional = true } +const_format = "0.2.32" [dev-dependencies] bevy = "0.12.1" diff --git a/src/builtin_parser.rs b/src/builtin_parser.rs index 554f094..282c6af 100644 --- a/src/builtin_parser.rs +++ b/src/builtin_parser.rs @@ -47,7 +47,16 @@ pub struct Spanned { /// The value of `T`. pub value: T, } - +impl Spanned { + /// Maps a `Spanned` to `Spanned` by applying a function to a + /// contained `T` value, leaving the [`Span`] value untouched. + pub fn map(self, f: impl Fn(T) -> U) -> Spanned { + Spanned { + span: self.span, + value: f(self.value), + } + } +} impl Default for DefaultCommandParser { fn default() -> Self { Self(Box::new(BuiltinCommandParser)) diff --git a/src/builtin_parser/lexer.rs b/src/builtin_parser/lexer.rs index 1c7f638..7456d7a 100644 --- a/src/builtin_parser/lexer.rs +++ b/src/builtin_parser/lexer.rs @@ -38,7 +38,7 @@ pub enum Token { #[token("%")] Modulo, - #[token(".")] + #[token(".", priority = 10)] Dot, #[token("&")] Ampersand, @@ -69,7 +69,7 @@ pub enum Token { #[regex(r#"[0-9]+"#)] IntegerNumber, - #[regex(r#"[0-9]*\.?[0-9]*"#)] + #[regex(r#"[0-9]+\.[0-9]*"#)] FloatNumber, #[token("i8")] diff --git a/src/builtin_parser/parser.rs b/src/builtin_parser/parser.rs index 228a260..4a328a4 100644 --- a/src/builtin_parser/parser.rs +++ b/src/builtin_parser/parser.rs @@ -64,7 +64,7 @@ pub enum Expression { UnaryOp(Box>), Member { left: Box>, - right: String, + right: Spanned, }, // Statement-like @@ -83,6 +83,71 @@ pub enum Expression { }, } +/// A singular element access within a [`Expression::Member`]. +/// +/// Based on `bevy_reflect`'s `Access`. +#[derive(Debug, Clone)] +pub enum Access { + /// A name-based field access on a struct. + Field(String), + /// An index-based access on a tuple. + TupleIndex(usize), + // /// An index-based access on a list. + // ListIndex(usize), +} +pub enum AccessKind { + Field, + TupleIndex, +} +impl Access { + pub const fn kind(&self) -> AccessKind { + match self { + Access::Field(_) => AccessKind::Field, + Access::TupleIndex(_) => AccessKind::TupleIndex, + } + } + /// Returns the kind of [`Access`] as a [string slice](str) with an `a` or `an` prepended to it. + /// Used for more natural sounding error messages. + pub const fn natural_kind(&self) -> &'static str { + self.kind().natural() + } +} + +impl AccessKind { + /// Returns the kind of [`Access`] as a [string slice](str) with an `a` or `an` prepended to it. + /// Used for more natural sounding error messages. + pub const fn natural(&self) -> &'static str { + match self { + AccessKind::Field => "a field", + AccessKind::TupleIndex => "a tuple", + } + } +} + +/// Get the access if its of a certain type, if not, return a [`RunError`](super::runner::error::RunError). +/// +/// For examples, take a look at existing uses in the code. +macro_rules! access_unwrap { + ($expected:literal, $($variant:ident($variant_inner:ident))|+ = $val:expr => $block:block) => {{ + let val = $val; + if let $(Access::$variant($variant_inner))|+ = val.value $block else { + use $crate::builtin_parser::parser::AccessKind; + use $crate::builtin_parser::runner::error::RunError; + + // We have to put this in a `const` first to avoid a + // `temporary value dropped while borrowed` error. + const EXPECTED_ACCESS: &[&str] = &[$(AccessKind::$variant.natural()),+]; + Err(RunError::IncorrectAccessOperation { + span: val.span, + expected_access: EXPECTED_ACCESS, + expected_type: $expected, + got: val.value, + })? + } + }}; +} +pub(crate) use access_unwrap; + impl Expression { pub fn kind(&self) -> &'static str { match self { @@ -154,7 +219,7 @@ fn parse_expression( tokens: &mut TokenStream, environment: &Environment, ) -> Result, ParseError> { - Ok(match tokens.peek() { + match tokens.peek() { Some(Ok(Token::For)) => { let start = tokens.span().start; tokens.next(); @@ -183,35 +248,37 @@ fn parse_expression( let block = parse_block(tokens, environment)?; let end = tokens.span().end; - Spanned { + Ok(Spanned { value: Expression::ForLoop { index_name, loop_count, block, }, span: start..end, - } + }) } Some(Ok(_)) => { let expr = parse_additive(tokens, environment)?; match tokens.peek() { - Some(Ok(Token::Equals)) => parse_var_assign(expr, tokens, environment)?, - _ => expr, + Some(Ok(Token::Equals)) => Ok(parse_var_assign(expr, tokens, environment)?), + _ => Ok(expr), } } Some(Err(FailedToLexCharacter)) => { - return Err(ParseError::FailedToLexCharacter(tokens.peek_span())) + Err(ParseError::FailedToLexCharacter(tokens.peek_span())) } - None => return Err(ParseError::ExpectedMoreTokens(tokens.peek_span())), - }) + None => Err(ParseError::ExpectedMoreTokens(tokens.peek_span())), + } } + fn parse_block(tokens: &mut TokenStream, environment: &Environment) -> Result { expect!(tokens, Token::LeftBracket); let ast = parse(tokens, environment)?; expect!(tokens, Token::RightBracket); Ok(ast) } + fn parse_additive( tokens: &mut TokenStream, environment: &Environment, @@ -271,211 +338,293 @@ fn parse_primary( tokens: &mut TokenStream, environment: &Environment, ) -> Result, ParseError> { - let mut expr = match tokens.next() { - Some(Ok(Token::LeftParen)) => { - let start = tokens.span().start; - if let Some(Ok(Token::RightParen)) = tokens.peek() { - tokens.next(); - Ok(Spanned { - span: start..tokens.span().end, - value: Expression::None, - }) - } else { - let expr = parse_expression(tokens, environment)?; - if let Some(Ok(Token::Comma)) = tokens.peek() { - let mut tuple = vec![expr]; - - while let Some(Ok(Token::Comma)) = tokens.peek() { - tokens.next(); - let expr = parse_expression(tokens, environment)?; - - tuple.push(expr); - } - - expect!(tokens, Token::RightParen); - + /// Parses a primary without member expressions + fn parse_subprimary( + tokens: &mut TokenStream, + environment: &Environment, + ) -> Result, ParseError> { + match tokens.next() { + Some(Ok(Token::LeftParen)) => { + let start = tokens.span().start; + if let Some(Ok(Token::RightParen)) = tokens.peek() { + tokens.next(); Ok(Spanned { span: start..tokens.span().end, - value: Expression::Tuple(tuple), + value: Expression::None, }) } else { - expect!(tokens, Token::RightParen); - - Ok(expr) - } - } - } - Some(Ok(Token::Identifer)) => { - let start = tokens.span().start; - let name = tokens.slice().to_string(); - - match tokens.peek() { - Some(Ok(Token::LeftParen)) => { - tokens.next(); - let expr = parse_expression(tokens, environment)?; + if let Some(Ok(Token::Comma)) = tokens.peek() { + let mut tuple = vec![expr]; - let mut tuple = vec![expr]; + while let Some(Ok(Token::Comma)) = tokens.peek() { + tokens.next(); + let expr = parse_expression(tokens, environment)?; - while let Some(Ok(Token::Comma)) = tokens.peek() { - tokens.next(); - let expr = parse_expression(tokens, environment)?; + tuple.push(expr); + } - tuple.push(expr); - } + expect!(tokens, Token::RightParen); - expect!(tokens, Token::RightParen); + Ok(Spanned { + span: start..tokens.span().end, + value: Expression::Tuple(tuple), + }) + } else { + expect!(tokens, Token::RightParen); - Ok(Spanned { - span: start..tokens.span().end, - value: Expression::StructTuple { name, tuple }, - }) + Ok(expr) + } } - Some(Ok(Token::LeftBracket)) => { - tokens.next(); + } + Some(Ok(Token::Identifer)) => { + let start = tokens.span().start; + let name = tokens.slice().to_string(); - let map = parse_object(tokens, environment)?; + match tokens.peek() { + Some(Ok(Token::LeftParen)) => { + tokens.next(); - Ok(Spanned { - span: tokens.span(), - value: Expression::StructObject { name, map }, - }) - } - _ => { - if let Some(Function { argument_count, .. }) = environment.get_function(&name) { - dbg!(argument_count); + let expr = parse_expression(tokens, environment)?; - let mut arguments = Vec::new(); - for _ in 0..(*argument_count) { + let mut tuple = vec![expr]; + + while let Some(Ok(Token::Comma)) = tokens.peek() { + tokens.next(); let expr = parse_expression(tokens, environment)?; - arguments.push(expr); + + tuple.push(expr); } + + expect!(tokens, Token::RightParen); + Ok(Spanned { span: start..tokens.span().end, - value: Expression::Function { name, arguments }, + value: Expression::StructTuple { name, tuple }, }) - } else { - Ok(tokens.wrap_span(Expression::Variable(name))) } - } - } - } - Some(Ok(Token::LeftBracket)) => { - let map = parse_object(tokens, environment)?; + Some(Ok(Token::LeftBracket)) => { + tokens.next(); - Ok(Spanned { - span: tokens.span(), - value: Expression::Object(map), - }) - } - Some(Ok(Token::String)) => { - let slice = tokens.slice(); - let string = slice[1..slice.len() - 1].to_string(); - Ok(tokens.wrap_span(Expression::String(string))) - } - Some(Ok(Token::Minus)) => { - let expr = parse_primary(tokens, environment)?; - Ok(tokens.wrap_span(Expression::UnaryOp(Box::new(expr)))) - } - Some(Ok(Token::Ampersand)) => { - let expr = parse_primary(tokens, environment)?; - Ok(tokens.wrap_span(Expression::Borrow(Box::new(expr)))) - } - Some(Ok(Token::Asterisk)) => { - let expr = parse_primary(tokens, environment)?; - Ok(tokens.wrap_span(Expression::Dereference(Box::new(expr)))) - } - 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)?), - "usize" => Number::usize(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)?), - "isize" => Number::isize(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(); + let map = parse_object(tokens, environment)?; - 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::StructObject { name, map }, + }) + } + _ => { + if let Some(Function { argument_count, .. }) = + environment.get_function(&name) + { + dbg!(argument_count); + + let mut arguments = Vec::new(); + for _ in 0..(*argument_count) { + let expr = parse_expression(tokens, environment)?; + arguments.push(expr); + } + Ok(Spanned { + span: start..tokens.span().end, + value: Expression::Function { name, arguments }, + }) + } else { + Ok(tokens.wrap_span(Expression::Variable(name))) + } + } + } + } + Some(Ok(Token::LeftBracket)) => { + let map = parse_object(tokens, environment)?; Ok(Spanned { span: tokens.span(), - value: Expression::Number(number), + value: Expression::Object(map), }) } - } - 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; + Some(Ok(Token::String)) => { + let slice = tokens.slice(); + let string = slice[1..slice.len() - 1].to_string(); + Ok(tokens.wrap_span(Expression::String(string))) + } + Some(Ok(Token::Minus)) => { + let expr = parse_primary(tokens, environment)?; + Ok(tokens.wrap_span(Expression::UnaryOp(Box::new(expr)))) + } + Some(Ok(Token::Ampersand)) => { + let expr = parse_subprimary(tokens, environment)?; + dbg!(&expr); + Ok(tokens.wrap_span(Expression::Borrow(Box::new(expr)))) + } + Some(Ok(Token::Asterisk)) => { + let expr = parse_primary(tokens, environment)?; + Ok(tokens.wrap_span(Expression::Dereference(Box::new(expr)))) + } + Some(Ok(Token::IntegerNumber)) => { + parse_number(tokens).map(|s| s.map(Expression::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(); + 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: 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), - }) + 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 => Err(ParseError::ExpectedMoreTokens(tokens.span())), } - 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 => Err(ParseError::ExpectedMoreTokens(tokens.span())), - }?; + } + + let mut expr = parse_subprimary(tokens, environment)?; // If theres a dot after the expression, do a member expression: while let Some(Ok(Token::Dot)) = tokens.peek() { - tokens.next(); // skip the dot - expect!(tokens, Token::Identifer); - let right = tokens.slice().to_string(); - expr = Spanned { - span: expr.span.start..tokens.span().end, - value: Expression::Member { - left: Box::new(expr), - right, - }, - }; + tokens.next(); // Skip the dot + match tokens.next() { + Some(Ok(Token::Identifer)) => { + let right = tokens.slice().to_string(); + expr = Spanned { + span: expr.span.start..tokens.span().end, + value: Expression::Member { + left: Box::new(expr), + right: tokens.wrap_span(Access::Field(right)), + }, + }; + } + Some(Ok(Token::IntegerNumber)) => { + let right = tokens + .slice() + .parse() + .map_err(map_parseint_error(tokens.span()))?; + expr = Spanned { + span: expr.span.start..tokens.span().end, + value: Expression::Member { + left: Box::new(expr), + right: tokens.wrap_span(Access::TupleIndex(right)), + }, + }; + } + _ => todo!(), + } } Ok(expr) } +fn map_parseint_error(span: Span) -> impl Fn(std::num::ParseIntError) -> ParseError { + move |error| match error.kind() { + IntErrorKind::PosOverflow => ParseError::PositiveIntOverflow(span.clone()), + IntErrorKind::NegOverflow => ParseError::NegativeIntOverflow(span.clone()), + _ => unreachable!( + "Lexer makes sure other errors aren't possible. Create an bevy_dev_console issue!" + ), + } +} + +fn parse_number(tokens: &mut TokenStream) -> Result, ParseError> { + if let Some(Ok(Token::NumberType)) = tokens.peek() { + let number: Number = match tokens.peek_slice() { + "u8" => Number::u8( + tokens + .slice() + .parse() + .map_err(map_parseint_error(tokens.span()))?, + ), + "u16" => Number::u16( + tokens + .slice() + .parse() + .map_err(map_parseint_error(tokens.span()))?, + ), + "u32" => Number::u32( + tokens + .slice() + .parse() + .map_err(map_parseint_error(tokens.span()))?, + ), + "u64" => Number::u64( + tokens + .slice() + .parse() + .map_err(map_parseint_error(tokens.span()))?, + ), + "usize" => Number::usize( + tokens + .slice() + .parse() + .map_err(map_parseint_error(tokens.span()))?, + ), + "i8" => Number::i8( + tokens + .slice() + .parse() + .map_err(map_parseint_error(tokens.span()))?, + ), + "i16" => Number::i16( + tokens + .slice() + .parse() + .map_err(map_parseint_error(tokens.span()))?, + ), + "i32" => Number::i32( + tokens + .slice() + .parse() + .map_err(map_parseint_error(tokens.span()))?, + ), + "isize" => Number::isize( + tokens + .slice() + .parse() + .map_err(map_parseint_error(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: number, + }) + } else { + let number = Number::Integer(tokens.slice().parse().unwrap()); + + Ok(Spanned { + span: tokens.span(), + value: number, + }) + } +} + fn parse_var_assign( name: Spanned, tokens: &mut TokenStream<'_>, diff --git a/src/builtin_parser/runner.rs b/src/builtin_parser/runner.rs index 02905dd..9676c03 100644 --- a/src/builtin_parser/runner.rs +++ b/src/builtin_parser/runner.rs @@ -12,18 +12,20 @@ use crate::command::CommandHints; use crate::ui::COMMAND_RESULT_NAME; use self::error::RunError; +use self::member::{eval_member_expression, eval_path, Path}; use self::reflection::{object_to_dynamic_struct, CreateRegistration, IntoResource}; -use self::unique_rc::{UniqueRc, WeakRef}; +use self::unique_rc::UniqueRc; use super::parser::{Ast, Expression, Operator}; use super::{Number, SpanExtension, Spanned}; -pub mod environment; -pub mod error; -pub mod reflection; -pub mod stdlib; -pub mod unique_rc; -pub mod value; +pub(super) mod environment; +pub(super) mod error; +pub(super) mod member; +pub(super) mod reflection; +pub(super) mod stdlib; +pub(super) mod unique_rc; +pub(super) mod value; pub use value::Value; @@ -42,6 +44,8 @@ macro_rules! todo_error { })? }; } +// This makes `todo_error` accessible to the runners submodules +use todo_error; /// Container for every value needed by evaluation functions. pub struct EvalParams<'world, 'env, 'reg> { @@ -530,155 +534,6 @@ fn eval_expression( } } -fn eval_member_expression( - left: Spanned, - right: String, - params: EvalParams, -) -> Result { - let left_span = left.span.clone(); - let EvalParams { - world, - environment, - registrations, - } = params; - // TODO: Add ability to borrow from a struct. - let left = eval_expression( - left, - EvalParams { - world, - environment, - registrations, - }, - )?; - - match left { - Value::Object(mut map) | Value::StructObject { mut map, .. } => { - let value = map - .remove(&right) - .ok_or(RunError::FieldNotFoundInStruct(left_span))?; - - Ok(value.into_inner()) - } - Value::Resource(mut resource) => { - resource.path.push('.'); - resource.path += &right; - - Ok(Value::Resource(resource)) - } - _ => Err(RunError::CannotIndexValue(left_span.wrap(left))), - } -} - -enum Path { - Variable(WeakRef), - NewVariable(String), - Resource(IntoResource), -} - -fn eval_path( - expr: Spanned, - EvalParams { - world, - environment, - registrations, - }: EvalParams, -) -> Result, RunError> { - match expr.value { - Expression::Variable(variable) => { - if let Some(registration) = registrations - .iter() - .find(|v| v.type_info().type_path_table().short_path() == variable) - { - Ok(Spanned { - span: expr.span, - value: Path::Resource(IntoResource::new(registration.type_id())), - }) - } else if let Ok(variable) = environment.get(&variable, expr.span.clone()) { - Ok(Spanned { - span: expr.span, - value: Path::Variable(variable.borrow()), - }) - } else { - Ok(Spanned { - span: expr.span, - value: Path::NewVariable(variable), - }) - } - } - Expression::Member { left, right } => { - let left = eval_path( - *left, - EvalParams { - world, - environment, - registrations, - }, - )?; - - match left.value { - Path::Variable(variable) => match &*variable.upgrade().unwrap().borrow() { - Value::Resource(resource) => { - let mut resource = resource.clone(); - - resource.path.push('.'); - resource.path += &right; - - Ok(left.span.wrap(Path::Resource(resource))) - } - Value::Object(object) | Value::StructObject { map: object, .. } => { - let weak = match object.get(&right) { - Some(rc) => Ok(rc.borrow()), - None => todo_error!(), - }?; - - Ok(left.span.wrap(Path::Variable(weak))) - } - Value::Tuple(tuple) | Value::StructTuple { tuple, .. } => { - todo_error!("eval_path only takes in a ident so this doesn't really work") - } - value => todo_error!("{value:?}"), - }, - Path::Resource(mut resource) => { - resource.path.push('.'); - resource.path += &right; - - Ok(left.span.wrap(Path::Resource(resource))) - } - Path::NewVariable(name) => Err(RunError::VariableNotFound(left.span.wrap(name))), - } - } - Expression::Dereference(inner) => { - let path = eval_path( - *inner, - EvalParams { - world, - environment, - registrations, - }, - )?; - match path.value { - Path::Variable(value) => { - let strong = value - .upgrade() - .ok_or(RunError::ReferenceToMovedData(path.span))?; - let borrow = strong.borrow(); - - if let Value::Reference(ref reference) = &*borrow { - Ok(expr.span.wrap(Path::Variable(reference.clone()))) - } else { - Err(RunError::CannotDereferenceValue( - expr.span.wrap(borrow.natural_kind()), - )) - } - } - Path::NewVariable(_) => todo_error!(), - Path::Resource(_) => todo_error!(), - } - } - expr => todo_error!("{expr:#?}"), - } -} - fn eval_object( map: HashMap>, EvalParams { diff --git a/src/builtin_parser/runner/error.rs b/src/builtin_parser/runner/error.rs index ba51978..f317d3a 100644 --- a/src/builtin_parser/runner/error.rs +++ b/src/builtin_parser/runner/error.rs @@ -3,6 +3,7 @@ use std::borrow::Cow; use logos::Span; use crate::builtin_parser::number::Number; +use crate::builtin_parser::parser::Access; use crate::builtin_parser::Spanned; use crate::command::{CommandHint, CommandHintColor}; @@ -27,7 +28,6 @@ pub enum RunError { VariableNotFound(Spanned), ExpectedNumberAfterUnaryOperator(Spanned), CannotIndexValue(Spanned), - FieldNotFoundInStruct(Span), ReferenceToMovedData(Span), VariableMoved(Spanned), CannotDereferenceValue(Spanned<&'static str>), @@ -59,9 +59,21 @@ pub enum RunError { CannotReflectReference(Span), CannotReflectResource(Span), EnumVariantTupleFieldNotFound { + span: Span, field_index: usize, variant_name: String, - span: std::ops::Range, + }, + IncorrectAccessOperation { + span: Span, + expected_access: &'static [&'static str], + expected_type: &'static str, + got: Access, + }, + FieldNotFoundInStruct(Spanned), + FieldNotFoundInTuple { + span: Span, + field_index: usize, + tuple_size: usize, }, } @@ -75,7 +87,7 @@ impl RunError { VariableNotFound(Spanned { span, .. }) => vec![span.clone()], ExpectedNumberAfterUnaryOperator(Spanned { span, .. }) => vec![span.clone()], CannotIndexValue(Spanned { span, .. }) => vec![span.clone()], - FieldNotFoundInStruct(span) => vec![span.clone()], + FieldNotFoundInStruct(Spanned { span, value: _ }) => vec![span.clone()], CannotDereferenceValue(Spanned { span, .. }) => vec![span.clone()], ReferenceToMovedData(span) => vec![span.clone()], VariableMoved(Spanned { span, .. }) => vec![span.clone()], @@ -92,6 +104,8 @@ impl RunError { CannotReflectReference(span) => vec![span.clone()], CannotReflectResource(span) => vec![span.clone()], InvalidOperation { span, .. } => vec![span.clone()], + IncorrectAccessOperation { span, .. } => vec![span.clone()], + FieldNotFoundInTuple { span, .. } => vec![span.clone()], } } /// Returns all the hints for this error. @@ -118,14 +132,13 @@ impl RunError { CannotIndexValue(Spanned { span: _, value }) => { format!("Cannot index {} with a member expression.", value.kind()).into() } - FieldNotFoundInStruct(_) => todo!(), ReferenceToMovedData(_) => todo!(), VariableMoved(Spanned { value, .. }) => format!("Variable `{value}` was moved.").into(), CannotDereferenceValue(Spanned { value: kind, .. }) => { format!("Cannot dereference {kind}.").into() } CannotBorrowValue(Spanned { value: kind, .. }) => { - format!("Cannot borrow {kind}.").into() + format!("Cannot borrow {kind}. Only variables can be borrowed.").into() } IncompatibleReflectTypes { expected, actual, .. @@ -181,6 +194,26 @@ impl RunError { operation, span: _, } => format!("Invalid operation: Cannot {operation} {left} by {right}").into(), + IncorrectAccessOperation { + expected_access, + expected_type, + got, + span: _, + } => format!( + "{} to access {expected_type} but got {}", + expected_access.join(" and "), + got.natural_kind() + ) + .into(), + FieldNotFoundInStruct(Spanned { span: _, value }) => { + format!("Field {value} not found in struct").into() + } + FieldNotFoundInTuple { + field_index, + tuple_size, + span: _, + } => format!("Field {field_index} is out of bounds for tuple of size {tuple_size}") + .into(), } } } diff --git a/src/builtin_parser/runner/member.rs b/src/builtin_parser/runner/member.rs new file mode 100644 index 0000000..5dae8a3 --- /dev/null +++ b/src/builtin_parser/runner/member.rs @@ -0,0 +1,267 @@ +//! Evaluation for member expressions and paths + +use crate::builtin_parser::parser::{access_unwrap, Access, Expression}; +use crate::builtin_parser::{RunError, SpanExtension, Spanned, WeakRef}; + +use super::reflection::IntoResource; +use super::{eval_expression, todo_error, EvalParams, Value}; + +/// Evaluate a member expression. +/// +/// A member expression allows indexing of values to access their inner fields. +/// +/// # Examples +/// +/// ```text +/// $ {a: 5}.a +/// > 5 +/// $ x = {key: "hi"} +/// $ &x.key +/// > "hi" +/// $ x +/// > {key: "hi"} +/// $ x.key +/// > "hi" +/// $ x +/// Error: Variable moved +/// ``` +pub fn eval_member_expression( + left: Spanned, + right: Spanned, + EvalParams { + world, + environment, + registrations, + }: EvalParams, +) -> Result { + let left_span = left.span.clone(); + let span = left.span.start..right.span.end; + let left = eval_expression( + left, + EvalParams { + world, + environment, + registrations, + }, + )?; + + match left { + Value::Reference(reference) => { + let Some(strong) = reference.upgrade() else { + return Err(RunError::ReferenceToMovedData(left_span)); + }; + let reference = strong.borrow(); + match &&*reference { + Value::Object(map) | Value::StructObject { map, .. } => { + access_unwrap!("an object reference", Field(field) = right => { + let value = map.get(&field).ok_or(RunError::FieldNotFoundInStruct(span.wrap(field)))?; + + Ok(Value::Reference(value.borrow())) + }) + } + Value::Tuple(tuple) | Value::StructTuple { tuple, .. } => { + access_unwrap!("a tuple reference", TupleIndex(index) = right => { + let Spanned { span: _, value } = + tuple.get(index).ok_or(RunError::FieldNotFoundInTuple { + span, + field_index: index, + tuple_size: tuple.len(), + })?; + + Ok(Value::Reference(value.borrow())) + }) + } + Value::Resource(resource) => { + access_unwrap!("a resource", Field(field) = right => { + let mut resource = resource.clone(); + + resource.path.push('.'); + resource.path += &field; + + Ok(Value::Resource(resource)) + }) + } + var => Err(RunError::CannotIndexValue(left_span.wrap((*var).clone()))), + } + } + Value::Object(mut map) | Value::StructObject { mut map, .. } => { + access_unwrap!("an object", Field(field) = right => { + let value = map + .remove(&field) + .ok_or(RunError::FieldNotFoundInStruct(span.wrap(field)))?; + + Ok(value.into_inner()) + }) + } + Value::Tuple(tuple) | Value::StructTuple { tuple, .. } => { + access_unwrap!("a tuple reference", TupleIndex(field_index) = right => { + let tuple_size = tuple.len(); + let Spanned { span: _, value } = + tuple + .into_vec() + .into_iter() + .nth(field_index) + .ok_or(RunError::FieldNotFoundInTuple { + span, + field_index, + tuple_size, + })?; + + Ok(value.into_inner()) + }) + } + Value::Resource(mut resource) => { + access_unwrap!("a resource", Field(field) = right => { + resource.path.push('.'); + resource.path += &field; + + Ok(Value::Resource(resource)) + }) + } + _ => Err(RunError::CannotIndexValue(left_span.wrap(left))), + } +} + +pub enum Path { + Variable(WeakRef), + NewVariable(String), + Resource(IntoResource), +} + +/// Evaluate a path expression. +/// +/// A path expression, in contrast to a member expression, is for creating new variables or assigning to existing ones. +/// +/// # Examples +/// +/// ```text +/// a -> Path::NewVariable("a") +/// a.b -> if a.b exists, returns Path::Variable(a.b) +/// MyResource.field -> appends "field" to the IntoResource path and returns Resource +/// a.b.c -> if a.b.c, returns Path::Variable(a.b.c) (wow look its recursive) +/// ``` +pub fn eval_path( + expr: Spanned, + EvalParams { + world, + environment, + registrations, + }: EvalParams, +) -> Result, RunError> { + match expr.value { + Expression::Variable(variable) => { + if let Some(registration) = registrations + .iter() + .find(|v| v.type_info().type_path_table().short_path() == variable) + { + Ok(Spanned { + span: expr.span, + value: Path::Resource(IntoResource::new(registration.type_id())), + }) + } else if let Ok(variable) = environment.get(&variable, expr.span.clone()) { + Ok(Spanned { + span: expr.span, + value: Path::Variable(variable.borrow()), + }) + } else { + Ok(Spanned { + span: expr.span, + value: Path::NewVariable(variable), + }) + } + } + Expression::Member { left, right } => { + let left = eval_path( + *left, + EvalParams { + world, + environment, + registrations, + }, + )?; + match left.value { + Path::Variable(variable) => match &*variable.upgrade().unwrap().borrow() { + Value::Resource(resource) => { + access_unwrap!("a resource", Field(field) = right => { + let mut resource = resource.clone(); + + resource.path.push('.'); + resource.path += &field; + + Ok(left.span.wrap(Path::Resource(resource))) + }) + } + Value::Object(object) | Value::StructObject { map: object, .. } => { + let span = left.span.start..right.span.end; + access_unwrap!("an object", Field(field) = right => { + let weak = match object.get(&field) { + Some(rc) => rc.borrow(), + None => { + return Err(RunError::FieldNotFoundInStruct(span.wrap(field))) + } + }; + + Ok(span.wrap(Path::Variable(weak))) + }) + } + Value::Tuple(tuple) | Value::StructTuple { tuple, .. } => { + let span = left.span.start..right.span.end; + access_unwrap!("a tuple", TupleIndex(index) = right => { + let weak = match tuple.get(index) { + Some(Spanned { value: rc, span: _ }) => rc.borrow(), + None => { + return Err(RunError::FieldNotFoundInTuple { + span, + field_index: index, + tuple_size: tuple.len(), + }) + } + }; + + Ok(span.wrap(Path::Variable(weak))) + }) + } + value => todo_error!("{value:?}"), + }, + Path::Resource(mut resource) => { + access_unwrap!("a resource", Field(field) = right => { + resource.path.push('.'); + resource.path += &field; + + Ok(left.span.wrap(Path::Resource(resource))) + }) + } + Path::NewVariable(name) => Err(RunError::VariableNotFound(left.span.wrap(name))), + } + } + Expression::Dereference(inner) => { + let path = eval_path( + *inner, + EvalParams { + world, + environment, + registrations, + }, + )?; + match path.value { + Path::Variable(value) => { + let strong = value + .upgrade() + .ok_or(RunError::ReferenceToMovedData(path.span))?; + let borrow = strong.borrow(); + + if let Value::Reference(ref reference) = &*borrow { + Ok(expr.span.wrap(Path::Variable(reference.clone()))) + } else { + Err(RunError::CannotDereferenceValue( + expr.span.wrap(borrow.natural_kind()), + )) + } + } + Path::NewVariable(_) => todo_error!(), + Path::Resource(_) => todo_error!(), + } + } + expr => todo_error!("can't eval path of this expr: {expr:#?}"), + } +} diff --git a/src/builtin_parser/runner/unique_rc.rs b/src/builtin_parser/runner/unique_rc.rs index 5e26a5c..ab3adc9 100644 --- a/src/builtin_parser/runner/unique_rc.rs +++ b/src/builtin_parser/runner/unique_rc.rs @@ -66,7 +66,7 @@ impl DerefMut for UniqueRc { } } -/// A weak reference to a [`UniqueRc`] may or may not exist. +/// A weak reference to a [`UniqueRc`] that may or may not exist. #[derive(Debug)] pub struct WeakRef { reference: Weak>,