Skip to content

Commit

Permalink
Add Access type for MemberExpressions
Browse files Browse the repository at this point in the history
  • Loading branch information
doonv committed Jan 23, 2024
1 parent 41de567 commit a084a0c
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 76 deletions.
11 changes: 10 additions & 1 deletion src/builtin_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,16 @@ pub struct Spanned<T> {
/// The value of `T`.
pub value: T,
}

impl<T> Spanned<T> {
/// Maps a `Spanned<T>` to `Spanned<U>` by applying a function to a
/// contained `T` value, leaving the [`Span`] value untouched.
pub fn map<U>(self, f: impl Fn(T) -> U) -> Spanned<U> {
Spanned {
span: self.span,
value: f(self.value),
}
}
}
impl Default for DefaultCommandParser {
fn default() -> Self {
Self(Box::new(BuiltinCommandParser))
Expand Down
127 changes: 79 additions & 48 deletions src/builtin_parser/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ pub enum Expression {
UnaryOp(Box<Spanned<Expression>>),
Member {
left: Box<Spanned<Expression>>,
right: String,
right: Spanned<Access>,
},

// Statement-like
Expand All @@ -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 {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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<Spanned<Number>, 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<Expression>,
tokens: &mut TokenStream<'_>,
Expand Down
70 changes: 44 additions & 26 deletions src/builtin_parser/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -614,35 +606,61 @@ 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")
}
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))),
}
Expand Down
21 changes: 20 additions & 1 deletion src/builtin_parser/runner/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -59,9 +60,15 @@ pub enum RunError {
CannotReflectReference(Span),
CannotReflectResource(Span),
EnumVariantTupleFieldNotFound {
span: Span,
field_index: usize,
variant_name: String,
span: std::ops::Range<usize>,
},
IncorrectAccessOperation {
span: Span,
expected_access: &'static [&'static str],
expected_type: &'static str,
got: Access,
},
}

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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(),
}
}
}

0 comments on commit a084a0c

Please sign in to comment.