From a084a0c24742a35e7e6c06dd1d6703f1daddc50d Mon Sep 17 00:00:00 2001 From: Doonv <58695417+doonv@users.noreply.github.com> Date: Tue, 23 Jan 2024 18:17:50 +0200 Subject: [PATCH] Add Access type for MemberExpressions --- src/builtin_parser.rs | 11 ++- src/builtin_parser/parser.rs | 127 ++++++++++++++++++----------- src/builtin_parser/runner.rs | 70 ++++++++++------ src/builtin_parser/runner/error.rs | 21 ++++- 4 files changed, 153 insertions(+), 76 deletions(-) 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/parser.rs b/src/builtin_parser/parser.rs index 228a260..e02e386 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,28 @@ 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), +} +impl Access { + /// 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 fn natural_kind(&self) -> &'static str { + match self { + Access::Field(_) => "a field access", + } + } +} + impl Expression { pub fn kind(&self) -> &'static str { match self { @@ -385,43 +407,7 @@ fn parse_primary( 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(); - - 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::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() { @@ -462,20 +448,65 @@ fn parse_primary( }?; // 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::LeftBrace)) => {} + _ => todo!(), + } } Ok(expr) } +fn parse_number(tokens: &mut TokenStream) -> Result, ParseError> { + 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 aren't possible. Create an bevy_dev_console issue!" + ), + }; + 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(); + + 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..78a15ad 100644 --- a/src/builtin_parser/runner.rs +++ b/src/builtin_parser/runner.rs @@ -15,7 +15,7 @@ use self::error::RunError; use self::reflection::{object_to_dynamic_struct, CreateRegistration, IntoResource}; use self::unique_rc::{UniqueRc, WeakRef}; -use super::parser::{Ast, Expression, Operator}; +use super::parser::{Access, Ast, Expression, Operator}; use super::{Number, SpanExtension, Spanned}; pub mod environment; @@ -446,15 +446,7 @@ fn eval_expression( loop_count, block, } => todo_error!("for loop {index_name}, {loop_count}, {block:#?}"), - Expression::Member { left, right } => eval_member_expression( - *left, - right, - EvalParams { - world, - environment, - registrations, - }, - ), + Expression::Member { left, right } => todo!(), Expression::UnaryOp(sub_expr) => { let span = sub_expr.span.clone(); let value = eval_expression( @@ -614,24 +606,41 @@ fn eval_path( 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))) + if let Access::Field(right) = right.value { + let mut resource = resource.clone(); + + resource.path.push('.'); + resource.path += &right; + + Ok(left.span.wrap(Path::Resource(resource))) + } else { + Err(RunError::IncorrectAccessOperation { + span: right.span, + expected_access: &["a field access"], + expected_type: "a resource", + got: right.value, + }) + } } 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))) + if let Access::Field(right) = right.value { + let weak = match object.get(&right) { + Some(rc) => Ok(rc.borrow()), + None => todo_error!(), + }?; + + Ok(left.span.wrap(Path::Variable(weak))) + } else { + Err(RunError::IncorrectAccessOperation { + span: right.span, + expected_access: &["a field access"], + expected_type: "an object", + got: right.value, + }) + } } Value::Tuple(tuple) | Value::StructTuple { tuple, .. } => { todo_error!("eval_path only takes in a ident so this doesn't really work") @@ -639,10 +648,19 @@ fn eval_path( value => todo_error!("{value:?}"), }, Path::Resource(mut resource) => { - resource.path.push('.'); - resource.path += &right; + if let Access::Field(right) = right.value { + resource.path.push('.'); + resource.path += &right; - Ok(left.span.wrap(Path::Resource(resource))) + Ok(left.span.wrap(Path::Resource(resource))) + } else { + Err(RunError::IncorrectAccessOperation { + span: right.span, + expected_access: &["a field access"], + expected_type: "a resource", + got: right.value, + }) + } } Path::NewVariable(name) => Err(RunError::VariableNotFound(left.span.wrap(name))), } diff --git a/src/builtin_parser/runner/error.rs b/src/builtin_parser/runner/error.rs index ba51978..3dc815c 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}; @@ -59,9 +60,15 @@ 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, }, } @@ -92,6 +99,7 @@ impl RunError { CannotReflectReference(span) => vec![span.clone()], CannotReflectResource(span) => vec![span.clone()], InvalidOperation { span, .. } => vec![span.clone()], + IncorrectAccessOperation { span, .. } => vec![span.clone()], } } /// Returns all the hints for this error. @@ -181,6 +189,17 @@ 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(), } } }