diff --git a/src/ast.rs b/src/ast.rs index 5b0262a1..24aa32e1 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -640,25 +640,6 @@ impl AsRef for AST { } } -/// _(INTERNALS)_ An identifier containing a [string][String] name and a [position][Position]. -/// Exported under the `internals` feature only. -/// -/// ## WARNING -/// -/// This type is volatile and may change. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct Ident { - pub name: String, - pub pos: Position, -} - -impl Ident { - /// Create a new `Identifier`. - pub fn new(name: String, pos: Position) -> Self { - Self { name, pos } - } -} - /// _(INTERNALS)_ An identifier containing an [immutable string][ImmutableString] name and a [position][Position]. /// Exported under the `internals` feature only. /// @@ -666,30 +647,11 @@ impl Ident { /// /// This type is volatile and may change. #[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct IdentX { +pub struct Ident { pub name: ImmutableString, pub pos: Position, } -impl From for IdentX { - fn from(value: Ident) -> Self { - Self { - name: value.name.into(), - pos: value.pos, - } - } -} - -impl IdentX { - /// Create a new `Identifier`. - pub fn new(name: impl Into, pos: Position) -> Self { - Self { - name: name.into(), - pos, - } - } -} - /// _(INTERNALS)_ A type encapsulating the mode of a `return`/`throw` statement. /// Exported under the `internals` feature only. /// @@ -729,9 +691,9 @@ pub enum Stmt { /// `for` id `in` expr `{` stmt `}` For(Expr, Box<(String, Stmt)>, Position), /// \[`export`\] `let` id `=` expr - Let(Box, Option, bool, Position), + Let(Box, Option, bool, Position), /// \[`export`\] `const` id `=` expr - Const(Box, Option, bool, Position), + Const(Box, Option, bool, Position), /// expr op`=` expr Assignment(Box<(Expr, Cow<'static, str>, Expr)>, Position), /// `{` stmt`;` ... `}` @@ -748,13 +710,13 @@ pub enum Stmt { Return((ReturnType, Position), Option, Position), /// `import` expr `as` var #[cfg(not(feature = "no_module"))] - Import(Expr, Option>, Position), + Import(Expr, Option>, Position), /// `export` var `as` var `,` ... #[cfg(not(feature = "no_module"))] - Export(Vec<(IdentX, Option)>, Position), + Export(Vec<(Ident, Option)>, Position), /// Convert a variable to shared. #[cfg(not(feature = "no_closure"))] - Share(IdentX), + Share(Ident), } impl Default for Stmt { @@ -996,13 +958,13 @@ pub enum Expr { /// [ expr, ... ] Array(Box>, Position), /// #{ name:expr, ... } - Map(Box>, Position), + Map(Box>, Position), /// () Unit(Position), /// Variable access - (optional index, optional modules, hash, variable name) - Variable(Box<(Option, Option>, u64, IdentX)>), + Variable(Box<(Option, Option>, u64, Ident)>), /// Property access - (getter, setter), prop - Property(Box<((ImmutableString, ImmutableString), IdentX)>), + Property(Box<((ImmutableString, ImmutableString), Ident)>), /// { [statement][Stmt] } Stmt(Box>, Position), /// Wrapped [expression][`Expr`] - should not be optimized away. diff --git a/src/engine.rs b/src/engine.rs index 9e578753..af54b2da 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,6 +1,6 @@ //! Main module defining the script evaluation [`Engine`]. -use crate::ast::{Expr, FnCallExpr, Ident, IdentX, ReturnType, Stmt}; +use crate::ast::{Expr, FnCallExpr, Ident, ReturnType, Stmt}; use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant}; use crate::fn_call::run_builtin_op_assignment; use crate::fn_native::{ @@ -734,7 +734,7 @@ pub fn search_imports( state: &mut State, namespace: &NamespaceRef, ) -> Result, Box> { - let IdentX { name: root, pos } = &namespace[0]; + let Ident { name: root, pos } = &namespace[0]; // Qualified - check if the root module is directly indexed let index = if state.always_search { @@ -884,7 +884,7 @@ impl Engine { match expr { Expr::Variable(v) => match v.as_ref() { // Qualified variable - (_, Some(modules), hash_var, IdentX { name, pos }) => { + (_, Some(modules), hash_var, Ident { name, pos }) => { let module = search_imports(mods, state, modules)?; let target = module.get_qualified_var(*hash_var).map_err(|mut err| { match *err { @@ -918,7 +918,7 @@ impl Engine { this_ptr: &'s mut Option<&mut Dynamic>, expr: &'a Expr, ) -> Result<(Target<'s>, &'a str, Position), Box> { - let (index, _, _, IdentX { name, pos }) = match expr { + let (index, _, _, Ident { name, pos }) = match expr { Expr::Variable(v) => v.as_ref(), _ => unreachable!(), }; @@ -1115,7 +1115,7 @@ impl Engine { Expr::FnCall(_, _) => unreachable!(), // {xxx:map}.id = ??? Expr::Property(x) if target_val.is::() && new_val.is_some() => { - let IdentX { name, pos } = &x.1; + let Ident { name, pos } = &x.1; let index = name.clone().into(); let mut val = self.get_indexed_mut( mods, state, lib, target_val, index, *pos, true, is_ref, false, level, @@ -1128,7 +1128,7 @@ impl Engine { } // {xxx:map}.id Expr::Property(x) if target_val.is::() => { - let IdentX { name, pos } = &x.1; + let Ident { name, pos } = &x.1; let index = name.clone().into(); let val = self.get_indexed_mut( mods, state, lib, target_val, index, *pos, false, is_ref, false, level, @@ -1138,7 +1138,7 @@ impl Engine { } // xxx.id = ??? Expr::Property(x) if new_val.is_some() => { - let ((_, setter), IdentX { pos, .. }) = x.as_ref(); + let ((_, setter), Ident { pos, .. }) = x.as_ref(); let mut new_val = new_val; let mut args = [target_val, &mut new_val.as_mut().unwrap().0]; self.exec_fn_call( @@ -1150,7 +1150,7 @@ impl Engine { } // xxx.id Expr::Property(x) => { - let ((getter, _), IdentX { pos, .. }) = x.as_ref(); + let ((getter, _), Ident { pos, .. }) = x.as_ref(); let mut args = [target_val]; self.exec_fn_call( mods, state, lib, getter, 0, &mut args, is_ref, true, false, *pos, @@ -1163,7 +1163,7 @@ impl Engine { Expr::Index(x, x_pos) | Expr::Dot(x, x_pos) if target_val.is::() => { let mut val = match &x.lhs { Expr::Property(p) => { - let IdentX { name, pos } = &p.1; + let Ident { name, pos } = &p.1; let index = name.clone().into(); self.get_indexed_mut( mods, state, lib, target_val, index, *pos, false, is_ref, true, @@ -1204,7 +1204,7 @@ impl Engine { match &x.lhs { // xxx.prop[expr] | xxx.prop.expr Expr::Property(p) => { - let ((getter, setter), IdentX { pos, .. }) = p.as_ref(); + let ((getter, setter), Ident { pos, .. }) = p.as_ref(); let arg_values = &mut [target_val, &mut Default::default()]; let args = &mut arg_values[..1]; let (mut val, updated) = self @@ -1319,7 +1319,7 @@ impl Engine { match lhs { // id.??? or id[???] Expr::Variable(x) => { - let IdentX { + let Ident { name: var_name, pos: var_pos, } = &x.3; @@ -1675,7 +1675,7 @@ impl Engine { if target.is::() { // map.prop - point directly to the item - let (_, IdentX { name, pos }) = p.as_ref(); + let (_, Ident { name, pos }) = p.as_ref(); let idx = name.clone().into(); if target.is_shared() || target.is_value() { @@ -1693,7 +1693,7 @@ impl Engine { .map(|v| (v, *pos)) } else { // var.prop - call property getter - let ((getter, _), IdentX { pos, .. }) = p.as_ref(); + let ((getter, _), Ident { pos, .. }) = p.as_ref(); let mut args = [target.as_mut()]; self.exec_fn_call( mods, state, lib, getter, 0, &mut args, is_ref, true, false, *pos, @@ -1781,7 +1781,7 @@ impl Engine { Expr::Map(x, _) => { let mut map = Map::with_capacity(crate::stdlib::cmp::max(TYPICAL_MAP_SIZE, x.len())); - for (IdentX { name: key, .. }, expr) in x.as_ref() { + for (Ident { name: key, .. }, expr) in x.as_ref() { map.insert( key.clone(), self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?, @@ -2272,9 +2272,9 @@ impl Engine { if let Some(Ident { name, .. }) = var_def { let var_name: Cow<'_, str> = if state.is_global() { - name.clone().into() + name.to_string().into() } else { - unsafe_cast_var_name_to_lifetime(name).into() + unsafe_cast_var_name_to_lifetime(&name).into() }; scope.push(var_name, value); } @@ -2410,7 +2410,7 @@ impl Engine { // Export statement #[cfg(not(feature = "no_module"))] Stmt::Export(list, _) => { - for (IdentX { name, pos: id_pos }, rename) in list.iter() { + for (Ident { name, pos: id_pos }, rename) in list.iter() { // Mark scope variables as public if let Some(index) = scope.get_index(name).map(|(i, _)| i) { let alias = rename.as_ref().map(|x| &x.name).unwrap_or_else(|| name); diff --git a/src/lib.rs b/src/lib.rs index f19553d9..ab0b5c1c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -180,7 +180,7 @@ pub use token::{get_next_token, parse_string_literal, InputStream, Token, Tokeni #[cfg(feature = "internals")] #[deprecated(note = "this type is volatile and may change")] pub use ast::{ - BinaryExpr, CustomExpr, Expr, FnCallExpr, Ident, IdentX, ReturnType, ScriptFnDef, Stmt, + BinaryExpr, CustomExpr, Expr, FnCallExpr, Ident, Ident, ReturnType, ScriptFnDef, Stmt, }; #[cfg(feature = "internals")] diff --git a/src/module/mod.rs b/src/module/mod.rs index e5d34f9a..84862afe 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -1,6 +1,6 @@ //! Module defining external-loaded modules for Rhai. -use crate::ast::{FnAccess, IdentX}; +use crate::ast::{FnAccess, Ident}; use crate::dynamic::Variant; use crate::fn_native::{shared_take_or_clone, CallableFunction, FnCallArgs, IteratorFn, SendSync}; use crate::fn_register::by_value as cast_arg; @@ -1906,7 +1906,7 @@ impl Module { /// /// This type is volatile and may change. #[derive(Clone, Eq, PartialEq, Default, Hash)] -pub struct NamespaceRef(StaticVec, Option); +pub struct NamespaceRef(StaticVec, Option); impl fmt::Debug for NamespaceRef { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -1921,7 +1921,7 @@ impl fmt::Debug for NamespaceRef { } impl Deref for NamespaceRef { - type Target = StaticVec; + type Target = StaticVec; fn deref(&self) -> &Self::Target { &self.0 @@ -1936,15 +1936,15 @@ impl DerefMut for NamespaceRef { impl fmt::Display for NamespaceRef { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for IdentX { name, .. } in self.0.iter() { + for Ident { name, .. } in self.0.iter() { write!(f, "{}{}", name, Token::DoubleColon.syntax())?; } Ok(()) } } -impl From> for NamespaceRef { - fn from(modules: StaticVec) -> Self { +impl From> for NamespaceRef { + fn from(modules: StaticVec) -> Self { Self(modules, None) } } diff --git a/src/parser.rs b/src/parser.rs index a8f72932..9a879083 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,8 +1,6 @@ //! Main module defining the lexer and parser. -use crate::ast::{ - BinaryExpr, CustomExpr, Expr, FnCallExpr, Ident, IdentX, ReturnType, ScriptFnDef, Stmt, -}; +use crate::ast::{BinaryExpr, CustomExpr, Expr, FnCallExpr, Ident, ReturnType, ScriptFnDef, Stmt}; use crate::dynamic::{AccessMode, Union}; use crate::engine::KEYWORD_THIS; use crate::module::NamespaceRef; @@ -703,7 +701,7 @@ fn parse_map_literal( #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let mut map: StaticVec<(IdentX, Expr)> = Default::default(); + let mut map: StaticVec<(Ident, Expr)> = Default::default(); loop { const MISSING_RBRACE: &str = "to end this object map literal"; @@ -773,7 +771,8 @@ fn parse_map_literal( } let expr = parse_expr(input, state, lib, settings.level_up())?; - map.push((IdentX::new(name, pos), expr)); + let name = state.get_interned_string(name); + map.push((Ident { name, pos }, expr)); match input.peek().unwrap() { (Token::Comma, _) => { @@ -970,7 +969,10 @@ fn parse_primary( { state.allow_capture = true; } - let var_name_def = IdentX::new(state.get_interned_string(s), settings.pos); + let var_name_def = Ident { + name: state.get_interned_string(s), + pos: settings.pos, + }; Expr::Variable(Box::new((None, None, 0, var_name_def))) } // Namespace qualification @@ -981,20 +983,29 @@ fn parse_primary( { state.allow_capture = true; } - let var_name_def = IdentX::new(state.get_interned_string(s), settings.pos); + let var_name_def = Ident { + name: state.get_interned_string(s), + pos: settings.pos, + }; Expr::Variable(Box::new((None, None, 0, var_name_def))) } // Normal variable access Token::Identifier(s) => { let index = state.access_var(&s, settings.pos); - let var_name_def = IdentX::new(state.get_interned_string(s), settings.pos); + let var_name_def = Ident { + name: state.get_interned_string(s), + pos: settings.pos, + }; Expr::Variable(Box::new((index, None, 0, var_name_def))) } // Function call is allowed to have reserved keyword Token::Reserved(s) if *next_token == Token::LeftParen || *next_token == Token::Bang => { if is_keyword_function(&s) { - let var_name_def = IdentX::new(state.get_interned_string(s), settings.pos); + let var_name_def = Ident { + name: state.get_interned_string(s), + pos: settings.pos, + }; Expr::Variable(Box::new((None, None, 0, var_name_def))) } else { return Err(PERR::Reserved(s).into_err(settings.pos)); @@ -1007,7 +1018,10 @@ fn parse_primary( let msg = format!("'{}' can only be used in functions", s); return Err(LexError::ImproperSymbol(s, msg).into_err(settings.pos)); } else { - let var_name_def = IdentX::new(state.get_interned_string(s), settings.pos); + let var_name_def = Ident { + name: state.get_interned_string(s), + pos: settings.pos, + }; Expr::Variable(Box::new((None, None, 0, var_name_def))) } } @@ -1067,13 +1081,13 @@ fn parse_primary( .into_err(pos)); } - let (_, modules, _, IdentX { name, pos }) = *x; + let (_, modules, _, Ident { name, pos }) = *x; settings.pos = pos; parse_fn_call(input, state, lib, name, true, modules, settings.level_up())? } // Function call (Expr::Variable(x), Token::LeftParen) => { - let (_, modules, _, IdentX { name, pos }) = *x; + let (_, modules, _, Ident { name, pos }) = *x; settings.pos = pos; parse_fn_call(input, state, lib, name, false, modules, settings.level_up())? } @@ -1091,7 +1105,10 @@ fn parse_primary( modules = Some(Box::new(m)); } - let var_name_def = IdentX::new(state.get_interned_string(id2), pos2); + let var_name_def = Ident { + name: state.get_interned_string(id2), + pos: pos2, + }; Expr::Variable(Box::new((index, modules, 0, var_name_def))) } (Token::Reserved(id2), pos2) if is_valid_identifier(id2.chars()) => { @@ -1129,7 +1146,7 @@ fn parse_primary( _ => None, } .map(|x| { - let (_, modules, hash, IdentX { name, .. }) = x.as_mut(); + let (_, modules, hash, Ident { name, .. }) = x.as_mut(); let namespace = modules.as_mut().unwrap(); // Qualifiers + variable name @@ -1311,58 +1328,46 @@ fn make_assignment_stmt<'a>( state: &mut ParseState, lhs: Expr, rhs: Expr, - pos: Position, + op_pos: Position, ) -> Result { match &lhs { // var (non-indexed) = rhs - Expr::Variable(x) if x.0.is_none() => { - Ok(Stmt::Assignment(Box::new((lhs, fn_name.into(), rhs)), pos)) - } + Expr::Variable(x) if x.0.is_none() => Ok(Stmt::Assignment( + Box::new((lhs, fn_name.into(), rhs)), + op_pos, + )), // var (indexed) = rhs Expr::Variable(x) => { - let ( - index, - _, - _, - IdentX { - name, - pos: name_pos, - }, - ) = x.as_ref(); + let (index, _, _, Ident { name, pos }) = x.as_ref(); match state.stack[(state.stack.len() - index.unwrap().get())].1 { - AccessMode::ReadWrite => { - Ok(Stmt::Assignment(Box::new((lhs, fn_name.into(), rhs)), pos)) - } + AccessMode::ReadWrite => Ok(Stmt::Assignment( + Box::new((lhs, fn_name.into(), rhs)), + op_pos, + )), // Constant values cannot be assigned to AccessMode::ReadOnly => { - Err(PERR::AssignmentToConstant(name.to_string()).into_err(*name_pos)) + Err(PERR::AssignmentToConstant(name.to_string()).into_err(*pos)) } } } // xxx[???] = rhs, xxx.??? = rhs Expr::Index(x, _) | Expr::Dot(x, _) => match &x.lhs { // var[???] (non-indexed) = rhs, var.??? (non-indexed) = rhs - Expr::Variable(x) if x.0.is_none() => { - Ok(Stmt::Assignment(Box::new((lhs, fn_name.into(), rhs)), pos)) - } + Expr::Variable(x) if x.0.is_none() => Ok(Stmt::Assignment( + Box::new((lhs, fn_name.into(), rhs)), + op_pos, + )), // var[???] (indexed) = rhs, var.??? (indexed) = rhs Expr::Variable(x) => { - let ( - index, - _, - _, - IdentX { - name, - pos: name_pos, - }, - ) = x.as_ref(); + let (index, _, _, Ident { name, pos }) = x.as_ref(); match state.stack[(state.stack.len() - index.unwrap().get())].1 { - AccessMode::ReadWrite => { - Ok(Stmt::Assignment(Box::new((lhs, fn_name.into(), rhs)), pos)) - } + AccessMode::ReadWrite => Ok(Stmt::Assignment( + Box::new((lhs, fn_name.into(), rhs)), + op_pos, + )), // Constant values cannot be assigned to AccessMode::ReadOnly => { - Err(PERR::AssignmentToConstant(name.to_string()).into_err(*name_pos)) + Err(PERR::AssignmentToConstant(name.to_string()).into_err(*pos)) } } } @@ -1378,7 +1383,7 @@ fn make_assignment_stmt<'a>( "=".to_string(), "Possibly a typo of '=='?".to_string(), ) - .into_err(pos)), + .into_err(op_pos)), // expr = rhs _ => Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(lhs.position())), } @@ -1883,11 +1888,11 @@ fn parse_custom_syntax( match required_token.as_str() { MARKER_IDENT => match input.next().unwrap() { (Token::Identifier(s), pos) => { - let ident = state.get_interned_string(s); - let var_name_def = IdentX::new(ident.clone(), pos); - keywords.push(Expr::Variable(Box::new((None, None, 0, var_name_def)))); - segments.push(ident); + let name = state.get_interned_string(s); + segments.push(name.clone()); tokens.push(state.get_interned_string(MARKER_IDENT)); + let var_name_def = Ident { name, pos }; + keywords.push(Expr::Variable(Box::new((None, None, 0, var_name_def)))); } (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { return Err(PERR::Reserved(s).into_err(pos)); @@ -2208,16 +2213,16 @@ fn parse_let( match var_type { // let name = expr AccessMode::ReadWrite => { - let var_name = state.get_interned_string(name.clone()); - state.stack.push((var_name, AccessMode::ReadWrite)); - let var_def = IdentX::new(name, pos); + let name = state.get_interned_string(name); + state.stack.push((name.clone(), AccessMode::ReadWrite)); + let var_def = Ident { name, pos }; Ok(Stmt::Let(Box::new(var_def), init_expr, export, token_pos)) } // const name = { expr:constant } AccessMode::ReadOnly => { - let var_name = state.get_interned_string(name.clone()); - state.stack.push((var_name, AccessMode::ReadOnly)); - let var_def = IdentX::new(name, pos); + let name = state.get_interned_string(name); + state.stack.push((name.clone(), AccessMode::ReadOnly)); + let var_def = Ident { name, pos }; Ok(Stmt::Const(Box::new(var_def), init_expr, export, token_pos)) } } @@ -2261,7 +2266,10 @@ fn parse_import( Ok(Stmt::Import( expr, - Some(Box::new(IdentX::new(name, settings.pos))), + Some(Box::new(Ident { + name, + pos: settings.pos, + })), token_pos, )) } @@ -2310,7 +2318,10 @@ fn parse_export( let rename = if match_token(input, Token::As).0 { match input.next().unwrap() { - (Token::Identifier(s), pos) => Some(IdentX::new(state.get_interned_string(s), pos)), + (Token::Identifier(s), pos) => Some(Ident { + name: state.get_interned_string(s), + pos, + }), (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { return Err(PERR::Reserved(s).into_err(pos)); } @@ -2321,7 +2332,13 @@ fn parse_export( None }; - exports.push((IdentX::new(state.get_interned_string(id), id_pos), rename)); + exports.push(( + Ident { + name: state.get_interned_string(id), + pos: id_pos, + }, + rename, + )); match input.peek().unwrap() { (Token::Comma, _) => { @@ -2651,7 +2668,10 @@ fn parse_try_catch( // try { body } catch ( let var_def = if match_token(input, Token::LeftParen).0 { let id = match input.next().unwrap() { - (Token::Identifier(s), pos) => Ident::new(s, pos), + (Token::Identifier(s), pos) => Ident { + name: state.get_interned_string(s), + pos, + }, (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), }; @@ -2780,7 +2800,7 @@ fn parse_fn( /// Creates a curried expression from a list of external variables #[cfg(not(feature = "no_function"))] -fn make_curry_from_externals(fn_expr: Expr, externals: StaticVec, pos: Position) -> Expr { +fn make_curry_from_externals(fn_expr: Expr, externals: StaticVec, pos: Position) -> Expr { if externals.is_empty() { return fn_expr; } @@ -2889,13 +2909,16 @@ fn parse_anon_fn( // External variables may need to be processed in a consistent order, // so extract them into a list. - let externals: StaticVec = { + let externals: StaticVec = { #[cfg(not(feature = "no_closure"))] { state .externals .iter() - .map(|(k, &v)| IdentX::new(k.clone(), v)) + .map(|(name, &pos)| Ident { + name: name.clone(), + pos, + }) .collect() } #[cfg(feature = "no_closure")] @@ -3114,7 +3137,7 @@ pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option { Union::Map(map, _) => { let items: Vec<_> = map .into_iter() - .map(|(k, v)| (IdentX::new(k, pos), map_dynamic_to_expr(v, pos))) + .map(|(name, value)| (Ident { name, pos }, map_dynamic_to_expr(value, pos))) .collect(); if items.iter().all(|(_, expr)| expr.is_some()) { diff --git a/tests/optimizer.rs b/tests/optimizer.rs index b6468d03..bfb9c4e0 100644 --- a/tests/optimizer.rs +++ b/tests/optimizer.rs @@ -55,7 +55,7 @@ fn test_optimizer_parse() -> Result<(), Box> { let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?; - assert!(format!("{:?}", ast).starts_with(r#"AST { source: None, statements: [Block([Const(IdentX { name: "DECISION", pos: 1:9 }, Some(Unit(0:0)), false, 1:3), Expr(IntegerConstant(123, 1:53))], 1:1)]"#)); + assert!(format!("{:?}", ast).starts_with(r#"AST { source: None, statements: [Block([Const(Ident { name: "DECISION", pos: 1:9 }, Some(Unit(0:0)), false, 1:3), Expr(IntegerConstant(123, 1:53))], 1:1)]"#)); let ast = engine.compile("if 1 == 2 { 42 }")?;