Deep linking for dot/index chains.

This commit is contained in:
Stephen Chung 2020-04-26 18:04:07 +08:00
parent 9998cf8890
commit 33d3e34908
6 changed files with 482 additions and 716 deletions

View File

@ -1947,7 +1947,7 @@ Properties and methods in a Rust custom type registered with the [`Engine`] can
```rust ```rust
let a = new_ts(); // constructor function let a = new_ts(); // constructor function
a.field = 500; // property access a.field = 500; // property access
a.update(); // method call a.update(); // method call, 'a' can be changed
update(a); // this works, but 'a' is unchanged because only update(a); // this works, but 'a' is unchanged because only
// a COPY of 'a' is passed to 'update' by VALUE // a COPY of 'a' is passed to 'update' by VALUE

View File

@ -212,7 +212,7 @@ impl fmt::Display for Dynamic {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(value) => write!(f, "{}", value), Union::Float(value) => write!(f, "{}", value),
Union::Array(value) => write!(f, "{:?}", value), Union::Array(value) => write!(f, "{:?}", value),
Union::Map(value) => write!(f, "{:?}", value), Union::Map(value) => write!(f, "#{:?}", value),
Union::Variant(_) => write!(f, "?"), Union::Variant(_) => write!(f, "?"),
} }
} }
@ -229,7 +229,7 @@ impl fmt::Debug for Dynamic {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(value) => write!(f, "{:?}", value), Union::Float(value) => write!(f, "{:?}", value),
Union::Array(value) => write!(f, "{:?}", value), Union::Array(value) => write!(f, "{:?}", value),
Union::Map(value) => write!(f, "{:?}", value), Union::Map(value) => write!(f, "#{:?}", value),
Union::Variant(_) => write!(f, "<dynamic>"), Union::Variant(_) => write!(f, "<dynamic>"),
} }
} }
@ -268,16 +268,6 @@ fn cast_box<X: Variant, T: Variant>(item: Box<X>) -> Result<T, Box<X>> {
} }
impl Dynamic { impl Dynamic {
/// Get a reference to the inner `Union`.
pub(crate) fn get_ref(&self) -> &Union {
&self.0
}
/// Get a mutable reference to the inner `Union`.
pub(crate) fn get_mut(&mut self) -> &mut Union {
&mut self.0
}
/// Create a `Dynamic` from any type. A `Dynamic` value is simply returned as is. /// Create a `Dynamic` from any type. A `Dynamic` value is simply returned as is.
/// ///
/// Beware that you need to pass in an `Array` type for it to be recognized as an `Array`. /// Beware that you need to pass in an `Array` type for it to be recognized as an `Array`.

File diff suppressed because it is too large Load Diff

View File

@ -100,8 +100,6 @@ pub enum ParseErrorType {
FnMissingBody(String), FnMissingBody(String),
/// Assignment to an inappropriate LHS (left-hand-side) expression. /// Assignment to an inappropriate LHS (left-hand-side) expression.
AssignmentToInvalidLHS, AssignmentToInvalidLHS,
/// Assignment to a copy of a value.
AssignmentToCopy,
/// Assignment to an a constant variable. /// Assignment to an a constant variable.
AssignmentToConstant(String), AssignmentToConstant(String),
/// Break statement not inside a loop. /// Break statement not inside a loop.
@ -150,7 +148,6 @@ impl ParseError {
ParseErrorType::FnMissingBody(_) => "Expecting body statement block for function declaration", ParseErrorType::FnMissingBody(_) => "Expecting body statement block for function declaration",
ParseErrorType::WrongFnDefinition => "Function definitions must be at global level and cannot be inside a block or another function", ParseErrorType::WrongFnDefinition => "Function definitions must be at global level and cannot be inside a block or another function",
ParseErrorType::AssignmentToInvalidLHS => "Cannot assign to this expression", ParseErrorType::AssignmentToInvalidLHS => "Cannot assign to this expression",
ParseErrorType::AssignmentToCopy => "Cannot assign to this expression because it will only be changing a copy of the value",
ParseErrorType::AssignmentToConstant(_) => "Cannot assign to a constant variable.", ParseErrorType::AssignmentToConstant(_) => "Cannot assign to a constant variable.",
ParseErrorType::LoopBreak => "Break statement should only be used inside a loop" ParseErrorType::LoopBreak => "Break statement should only be used inside a loop"
} }

View File

@ -476,34 +476,41 @@ impl Expr {
/// Is a particular token allowed as a postfix operator to this expression? /// Is a particular token allowed as a postfix operator to this expression?
pub fn is_valid_postfix(&self, token: &Token) -> bool { pub fn is_valid_postfix(&self, token: &Token) -> bool {
match self { match self {
Expr::IntegerConstant(_, _) Self::IntegerConstant(_, _)
| Expr::FloatConstant(_, _) | Self::FloatConstant(_, _)
| Expr::CharConstant(_, _) | Self::CharConstant(_, _)
| Expr::In(_, _, _) | Self::In(_, _, _)
| Expr::And(_, _, _) | Self::And(_, _, _)
| Expr::Or(_, _, _) | Self::Or(_, _, _)
| Expr::True(_) | Self::True(_)
| Expr::False(_) | Self::False(_)
| Expr::Unit(_) => false, | Self::Unit(_) => false,
Expr::StringConstant(_, _) Self::StringConstant(_, _)
| Expr::Stmt(_, _) | Self::Stmt(_, _)
| Expr::FunctionCall(_, _, _, _) | Self::FunctionCall(_, _, _, _)
| Expr::Assignment(_, _, _) | Self::Assignment(_, _, _)
| Expr::Dot(_, _, _) | Self::Dot(_, _, _)
| Expr::Index(_, _, _) | Self::Index(_, _, _)
| Expr::Array(_, _) | Self::Array(_, _)
| Expr::Map(_, _) => match token { | Self::Map(_, _) => match token {
Token::LeftBracket => true, Token::LeftBracket => true,
_ => false, _ => false,
}, },
Expr::Variable(_, _) | Expr::Property(_, _) => match token { Self::Variable(_, _) | Self::Property(_, _) => match token {
Token::LeftBracket | Token::LeftParen => true, Token::LeftBracket | Token::LeftParen => true,
_ => false, _ => false,
}, },
} }
} }
pub(crate) fn into_property(self) -> Self {
match self {
Self::Variable(id, pos) => Self::Property(id, pos),
_ => self,
}
}
} }
/// Consume a particular token, checking that it is the expected one. /// Consume a particular token, checking that it is the expected one.
@ -734,7 +741,17 @@ fn parse_index_expr<'a>(
match input.peek().unwrap() { match input.peek().unwrap() {
(Token::RightBracket, _) => { (Token::RightBracket, _) => {
eat_token(input, Token::RightBracket); eat_token(input, Token::RightBracket);
Ok(Expr::Index(lhs, Box::new(idx_expr), pos))
let idx_expr = Box::new(idx_expr);
match input.peek().unwrap() {
(Token::LeftBracket, _) => {
let follow_pos = eat_token(input, Token::LeftBracket);
let follow = parse_index_expr(idx_expr, input, follow_pos, allow_stmt_expr)?;
Ok(Expr::Index(lhs, Box::new(follow), pos))
}
_ => Ok(Expr::Index(lhs, idx_expr, pos)),
}
} }
(Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(*pos)), (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(*pos)),
(_, pos) => Err(PERR::MissingToken( (_, pos) => Err(PERR::MissingToken(
@ -1005,71 +1022,6 @@ fn parse_unary<'a>(
} }
} }
/// Parse an assignment.
fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, Box<ParseError>> {
// Is the LHS in a valid format for an assignment target?
fn valid_assignment_chain(expr: &Expr, is_top: bool) -> Option<Box<ParseError>> {
match expr {
// var
Expr::Variable(_, _) => {
assert!(is_top, "property expected but gets variable");
None
}
// property
Expr::Property(_, _) => {
assert!(!is_top, "variable expected but gets property");
None
}
// idx_lhs[...]
Expr::Index(idx_lhs, _, pos) => match idx_lhs.as_ref() {
// var[...]
Expr::Variable(_, _) => {
assert!(is_top, "property expected but gets variable");
None
}
// property[...]
Expr::Property(_, _) => {
assert!(!is_top, "variable expected but gets property");
None
}
// ???[...][...]
Expr::Index(_, _, _) => Some(ParseErrorType::AssignmentToCopy.into_err(*pos)),
// idx_lhs[...]
_ => Some(ParseErrorType::AssignmentToInvalidLHS.into_err(*pos)),
},
// dot_lhs.dot_rhs
Expr::Dot(dot_lhs, dot_rhs, pos) => match dot_lhs.as_ref() {
// var.dot_rhs
Expr::Variable(_, _) if is_top => valid_assignment_chain(dot_rhs, false),
// property.dot_rhs
Expr::Property(_, _) if !is_top => valid_assignment_chain(dot_rhs, false),
// idx_lhs[...].dot_rhs
Expr::Index(idx_lhs, _, _) => match idx_lhs.as_ref() {
// var[...].dot_rhs
Expr::Variable(_, _) if is_top => valid_assignment_chain(dot_rhs, false),
// property[...].dot_rhs
Expr::Property(_, _) if !is_top => valid_assignment_chain(dot_rhs, false),
// ???[...][...].dot_rhs
Expr::Index(_, _, _) => Some(ParseErrorType::AssignmentToCopy.into_err(*pos)),
// idx_lhs[...].dot_rhs
_ => Some(ParseErrorType::AssignmentToCopy.into_err(idx_lhs.position())),
},
expr => panic!("unexpected dot expression {:#?}", expr),
},
_ => Some(ParseErrorType::AssignmentToInvalidLHS.into_err(expr.position())),
}
}
match valid_assignment_chain(&lhs, true) {
None => Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs), pos)),
Some(err) => Err(err),
}
}
fn parse_assignment_stmt<'a>( fn parse_assignment_stmt<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
lhs: Expr, lhs: Expr,
@ -1077,7 +1029,7 @@ fn parse_assignment_stmt<'a>(
) -> Result<Expr, Box<ParseError>> { ) -> Result<Expr, Box<ParseError>> {
let pos = eat_token(input, Token::Equals); let pos = eat_token(input, Token::Equals);
let rhs = parse_expr(input, allow_stmt_expr)?; let rhs = parse_expr(input, allow_stmt_expr)?;
parse_assignment(lhs, rhs, pos) Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs), pos))
} }
/// Parse an operator-assignment expression. /// Parse an operator-assignment expression.
@ -1108,15 +1060,51 @@ fn parse_op_assignment_stmt<'a>(
let rhs = parse_expr(input, allow_stmt_expr)?; let rhs = parse_expr(input, allow_stmt_expr)?;
// lhs op= rhs -> lhs = op(lhs, rhs) // lhs op= rhs -> lhs = op(lhs, rhs)
parse_assignment( let rhs_expr = Expr::FunctionCall(op.into(), vec![lhs_copy, rhs], None, pos);
lhs, Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs_expr), pos))
Expr::FunctionCall(op.into(), vec![lhs_copy, rhs], None, pos),
pos,
)
} }
/// Parse an 'in' expression. /// Make a dot expression.
fn parse_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, Box<ParseError>> { fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position, is_index: bool) -> Expr {
match (lhs, rhs) {
// idx_lhs[idx_rhs].rhs
(Expr::Index(idx_lhs, idx_rhs, idx_pos), rhs) => Expr::Index(
idx_lhs,
Box::new(make_dot_expr(*idx_rhs, rhs, op_pos, true)),
idx_pos,
),
// lhs.id
(lhs, rhs @ Expr::Variable(_, _)) | (lhs, rhs @ Expr::Property(_, _)) => {
let lhs = if is_index { lhs.into_property() } else { lhs };
Expr::Dot(Box::new(lhs), Box::new(rhs.into_property()), op_pos)
}
// lhs.dot_lhs.dot_rhs
(lhs, Expr::Dot(dot_lhs, dot_rhs, dot_pos)) => Expr::Dot(
Box::new(lhs),
Box::new(Expr::Dot(
Box::new(dot_lhs.into_property()),
Box::new(dot_rhs.into_property()),
dot_pos,
)),
op_pos,
),
// lhs.idx_lhs[idx_rhs]
(lhs, Expr::Index(idx_lhs, idx_rhs, idx_pos)) => Expr::Dot(
Box::new(lhs),
Box::new(Expr::Index(
Box::new(idx_lhs.into_property()),
Box::new(idx_rhs.into_property()),
idx_pos,
)),
op_pos,
),
// lhs.rhs
(lhs, rhs) => Expr::Dot(Box::new(lhs), Box::new(rhs.into_property()), op_pos),
}
}
/// Make an 'in' expression.
fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, Box<ParseError>> {
match (&lhs, &rhs) { match (&lhs, &rhs) {
(_, Expr::IntegerConstant(_, pos)) (_, Expr::IntegerConstant(_, pos))
| (_, Expr::FloatConstant(_, pos)) | (_, Expr::FloatConstant(_, pos))
@ -1321,34 +1309,10 @@ fn parse_binary_op<'a>(
Token::Pipe => Expr::FunctionCall("|".into(), vec![current_lhs, rhs], None, pos), Token::Pipe => Expr::FunctionCall("|".into(), vec![current_lhs, rhs], None, pos),
Token::XOr => Expr::FunctionCall("^".into(), vec![current_lhs, rhs], None, pos), Token::XOr => Expr::FunctionCall("^".into(), vec![current_lhs, rhs], None, pos),
Token::In => parse_in_expr(current_lhs, rhs, pos)?, Token::In => make_in_expr(current_lhs, rhs, pos)?,
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Token::Period => { Token::Period => make_dot_expr(current_lhs, rhs, pos, false),
fn check_property(expr: Expr) -> Result<Expr, Box<ParseError>> {
match expr {
// xxx.lhs.rhs
Expr::Dot(lhs, rhs, pos) => Ok(Expr::Dot(
Box::new(check_property(*lhs)?),
Box::new(check_property(*rhs)?),
pos,
)),
// xxx.lhs[idx]
Expr::Index(lhs, idx, pos) => {
Ok(Expr::Index(Box::new(check_property(*lhs)?), idx, pos))
}
// xxx.id
Expr::Variable(id, pos) => Ok(Expr::Property(id, pos)),
// xxx.prop
expr @ Expr::Property(_, _) => Ok(expr),
// xxx.fn()
expr @ Expr::FunctionCall(_, _, _, _) => Ok(expr),
expr => Err(PERR::PropertyExpected.into_err(expr.position())),
}
}
Expr::Dot(Box::new(current_lhs), Box::new(check_property(rhs)?), pos)
}
token => return Err(PERR::UnknownOperator(token.syntax().into()).into_err(pos)), token => return Err(PERR::UnknownOperator(token.syntax().into()).into_err(pos)),
}; };

View File

@ -4,7 +4,7 @@ use crate::any::{Dynamic, Variant};
use crate::parser::{map_dynamic_to_expr, Expr}; use crate::parser::{map_dynamic_to_expr, Expr};
use crate::token::Position; use crate::token::Position;
use crate::stdlib::{borrow::Cow, iter, vec::Vec}; use crate::stdlib::{borrow::Cow, cell::RefCell, iter, vec::Vec};
/// Type of an entry in the Scope. /// Type of an entry in the Scope.
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] #[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)]
@ -23,7 +23,7 @@ pub struct Entry<'a> {
/// Type of the entry. /// Type of the entry.
pub typ: EntryType, pub typ: EntryType,
/// Current value of the entry. /// Current value of the entry.
pub value: Dynamic, pub value: RefCell<Dynamic>,
/// A constant expression if the initial value matches one of the recognized types. /// A constant expression if the initial value matches one of the recognized types.
pub expr: Option<Expr>, pub expr: Option<Expr>,
} }
@ -226,15 +226,17 @@ impl<'a> Scope<'a> {
value: Dynamic, value: Dynamic,
map_expr: bool, map_expr: bool,
) { ) {
let expr = if map_expr {
map_dynamic_to_expr(value.clone(), Position::none())
} else {
None
};
self.0.push(Entry { self.0.push(Entry {
name: name.into(), name: name.into(),
typ: entry_type, typ: entry_type,
value: value.clone(), value: value.into(),
expr: if map_expr { expr,
map_dynamic_to_expr(value, Position::none())
} else {
None
},
}); });
} }
@ -311,7 +313,7 @@ impl<'a> Scope<'a> {
index, index,
typ: *typ, typ: *typ,
}, },
value.clone(), value.borrow().clone(),
)) ))
} else { } else {
None None
@ -337,7 +339,7 @@ impl<'a> Scope<'a> {
.iter() .iter()
.rev() .rev()
.find(|Entry { name: key, .. }| name == key) .find(|Entry { name: key, .. }| name == key)
.and_then(|Entry { value, .. }| value.downcast_ref::<T>().cloned()) .and_then(|Entry { value, .. }| value.borrow().downcast_ref::<T>().cloned())
} }
/// Update the value of the named entry. /// Update the value of the named entry.
@ -377,14 +379,14 @@ impl<'a> Scope<'a> {
.. ..
}, },
_, _,
)) => self.0.get_mut(index).unwrap().value = Dynamic::from(value), )) => *self.0.get_mut(index).unwrap().value.borrow_mut() = Dynamic::from(value),
None => self.push(name, value), None => self.push(name, value),
} }
} }
/// Get a mutable reference to an entry in the Scope. /// Get a mutable reference to an entry in the Scope.
pub(crate) fn get_mut(&mut self, key: EntryRef) -> &mut Dynamic { pub(crate) fn get_ref(&self, key: EntryRef) -> &RefCell<Dynamic> {
let entry = self.0.get_mut(key.index).expect("invalid index in Scope"); let entry = self.0.get(key.index).expect("invalid index in Scope");
assert_eq!(entry.typ, key.typ, "entry type not matched"); assert_eq!(entry.typ, key.typ, "entry type not matched");
// assert_ne!( // assert_ne!(
@ -394,7 +396,7 @@ impl<'a> Scope<'a> {
// ); // );
assert_eq!(entry.name, key.name, "incorrect key at Scope entry"); assert_eq!(entry.name, key.name, "incorrect key at Scope entry");
&mut entry.value &entry.value
} }
/// Get an iterator to entries in the Scope. /// Get an iterator to entries in the Scope.
@ -418,7 +420,7 @@ where
.extend(iter.into_iter().map(|(name, typ, value)| Entry { .extend(iter.into_iter().map(|(name, typ, value)| Entry {
name: name.into(), name: name.into(),
typ, typ,
value, value: value.into(),
expr: None, expr: None,
})); }));
} }