diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index 6c980e1d..93e50190 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -2,7 +2,7 @@ use super::{ASTNode, BinaryExpr, Expr, FnCallExpr, Ident, OptionFlags, AST_OPTION_FLAGS::*}; use crate::engine::KEYWORD_EVAL; -use crate::tokenizer::Token; +use crate::tokenizer::{Span, Token}; use crate::{calc_fn_hash, Position, StaticVec, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -134,7 +134,7 @@ pub struct TryCatchBlock { /// _(internals)_ A scoped block of statements. /// Exported under the `internals` feature only. #[derive(Clone, Hash, Default)] -pub struct StmtBlock(StaticVec, (Position, Position)); +pub struct StmtBlock(StaticVec, Span); impl StmtBlock { /// A [`StmtBlock`] that does not exist. @@ -149,13 +149,13 @@ impl StmtBlock { ) -> Self { let mut statements: StaticVec<_> = statements.into_iter().collect(); statements.shrink_to_fit(); - Self(statements, (start_pos, end_pos)) + Self(statements, Span::new(start_pos, end_pos)) } /// Create an empty [`StmtBlock`]. #[inline(always)] #[must_use] pub const fn empty(pos: Position) -> Self { - Self(StaticVec::new_const(), (pos, pos)) + Self(StaticVec::new_const(), Span::new(pos, pos)) } /// Is this statements block empty? #[inline(always)] @@ -191,38 +191,34 @@ impl StmtBlock { #[inline(always)] #[must_use] pub const fn position(&self) -> Position { - (self.1).0 + (self.1).start() } /// Get the end position (location of the ending `}`) of this statements block. #[inline(always)] #[must_use] pub const fn end_position(&self) -> Position { - (self.1).1 + (self.1).end() } /// Get the positions (locations of the beginning `{` and ending `}`) of this statements block. #[inline(always)] #[must_use] - pub const fn positions(&self) -> (Position, Position) { + pub const fn span(&self) -> Span { self.1 } /// Get the positions (locations of the beginning `{` and ending `}`) of this statements block /// or a default. #[inline(always)] #[must_use] - pub const fn positions_or_else( - &self, - def_start_pos: Position, - def_end_pos: Position, - ) -> (Position, Position) { - ( - (self.1).0.or_else(def_start_pos), - (self.1).1.or_else(def_end_pos), + pub const fn span_or_else(&self, def_start_pos: Position, def_end_pos: Position) -> Span { + Span::new( + (self.1).start().or_else(def_start_pos), + (self.1).end().or_else(def_end_pos), ) } /// Set the positions of this statements block. #[inline(always)] pub fn set_position(&mut self, start_pos: Position, end_pos: Position) { - self.1 = (start_pos, end_pos); + self.1 = Span::new(start_pos, end_pos); } } @@ -260,10 +256,8 @@ impl fmt::Debug for StmtBlock { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("Block")?; fmt::Debug::fmt(&self.0, f)?; - (self.1).0.debug_print(f)?; - #[cfg(not(feature = "no_position"))] - if !(self.1).1.is_none() { - write!(f, "-{:?}", (self.1).1)?; + if !self.1.is_none() { + write!(f, " @ {:?}", self.1)?; } Ok(()) } @@ -273,11 +267,11 @@ impl From for StmtBlock { #[inline] fn from(stmt: Stmt) -> Self { match stmt { - Stmt::Block(mut block, pos) => Self(block.iter_mut().map(mem::take).collect(), pos), - Stmt::Noop(pos) => Self(StaticVec::new_const(), (pos, pos)), + Stmt::Block(mut block, span) => Self(block.iter_mut().map(mem::take).collect(), span), + Stmt::Noop(pos) => Self(StaticVec::new_const(), Span::new(pos, pos)), _ => { let pos = stmt.position(); - Self(vec![stmt].into(), (pos, Position::NONE)) + Self(vec![stmt].into(), Span::new(pos, Position::NONE)) } } } @@ -344,7 +338,7 @@ pub enum Stmt { /// function call forming one statement. FnCall(Box, Position), /// `{` stmt`;` ... `}` - Block(Box<[Stmt]>, (Position, Position)), + Block(Box<[Stmt]>, Span), /// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}` TryCatch(Box, Position), /// [expression][Expr] @@ -412,7 +406,6 @@ impl Stmt { match self { Self::Noop(pos) | Self::BreakLoop(.., pos) - | Self::Block(.., (pos, ..)) | Self::Assignment(.., pos) | Self::FnCall(.., pos) | Self::If(.., pos) @@ -424,6 +417,8 @@ impl Stmt { | Self::Var(.., pos) | Self::TryCatch(.., pos) => *pos, + Self::Block(.., span) => span.start(), + Self::Expr(x) => x.start_position(), #[cfg(not(feature = "no_module"))] @@ -440,7 +435,6 @@ impl Stmt { match self { Self::Noop(pos) | Self::BreakLoop(.., pos) - | Self::Block(.., (pos, ..)) | Self::Assignment(.., pos) | Self::FnCall(.., pos) | Self::If(.., pos) @@ -452,6 +446,8 @@ impl Stmt { | Self::Var(.., pos) | Self::TryCatch(.., pos) => *pos = new_pos, + Self::Block(.., span) => *span = Span::new(new_pos, span.end()), + Self::Expr(x) => { x.set_position(new_pos); } diff --git a/src/lib.rs b/src/lib.rs index 18061326..72417e22 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -247,7 +247,7 @@ pub use tokenizer::{get_next_token, parse_string_literal}; #[cfg(feature = "internals")] pub use tokenizer::{ - InputStream, MultiInputsStream, Token, TokenIterator, TokenizeState, TokenizerControl, + InputStream, MultiInputsStream, Span, Token, TokenIterator, TokenizeState, TokenizerControl, TokenizerControlBlock, }; diff --git a/src/optimizer.rs b/src/optimizer.rs index e8ddf4f8..496937a5 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -6,7 +6,7 @@ use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, use crate::eval::{EvalState, GlobalRuntimeState}; use crate::func::builtin::get_builtin_binary_op_fn; use crate::func::hashing::get_hasher; -use crate::tokenizer::Token; +use crate::tokenizer::{Span, Token}; use crate::types::dynamic::AccessMode; use crate::{ calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnPtr, Position, Scope, @@ -471,7 +471,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // -> { expr, Noop } Stmt::Block( [Stmt::Expr(expr), Stmt::Noop(pos)].into(), - (pos, Position::NONE), + Span::new(pos, Position::NONE), ) } else { // -> expr @@ -490,7 +490,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b match optimize_stmt_block(mem::take(&mut *x.1), state, preserve_result, true, false) { statements if statements.is_empty() => Stmt::Noop(x.1.position()), - statements => Stmt::Block(statements.into_boxed_slice(), x.1.positions()), + statements => Stmt::Block(statements.into_boxed_slice(), x.1.span()), } } // if true { if_block } else { else_block } -> if_block @@ -500,7 +500,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b match optimize_stmt_block(mem::take(&mut *x.0), state, preserve_result, true, false) { statements if statements.is_empty() => Stmt::Noop(x.0.position()), - statements => Stmt::Block(statements.into_boxed_slice(), x.0.positions()), + statements => Stmt::Block(statements.into_boxed_slice(), x.0.span()), } } // if expr { if_block } else { else_block } @@ -534,7 +534,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b mem::take(&mut block.statements), Stmt::Block( def_stmt.into_boxed_slice(), - x.def_case.positions_or_else(*pos, Position::NONE), + x.def_case.span_or_else(*pos, Position::NONE), ) .into(), )), @@ -549,8 +549,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b true, false, ); - *stmt = - Stmt::Block(statements.into_boxed_slice(), block.statements.positions()); + *stmt = Stmt::Block(statements.into_boxed_slice(), block.statements.span()); } state.set_dirty(); @@ -590,7 +589,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b mem::take(&mut block.statements), Stmt::Block( def_stmt.into_boxed_slice(), - x.def_case.positions_or_else(*pos, Position::NONE), + x.def_case.span_or_else(*pos, Position::NONE), ) .into(), )), @@ -601,10 +600,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b let statements = mem::take(&mut *block.statements); let statements = optimize_stmt_block(statements, state, true, true, false); - *stmt = Stmt::Block( - statements.into_boxed_slice(), - block.statements.positions(), - ); + *stmt = + Stmt::Block(statements.into_boxed_slice(), block.statements.span()); } state.set_dirty(); @@ -651,7 +648,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b optimize_stmt_block(mem::take(&mut x.def_case), state, true, true, false); *stmt = Stmt::Block( def_stmt.into_boxed_slice(), - x.def_case.positions_or_else(*pos, Position::NONE), + x.def_case.span_or_else(*pos, Position::NONE), ); } // switch @@ -708,8 +705,10 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b if preserve_result { statements.push(Stmt::Noop(pos)) } - *stmt = - Stmt::Block(statements.into_boxed_slice(), (pos, Position::NONE)); + *stmt = Stmt::Block( + statements.into_boxed_slice(), + Span::new(pos, Position::NONE), + ); } else { *stmt = Stmt::Noop(pos); }; @@ -726,7 +725,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b *stmt = Stmt::Block( optimize_stmt_block(mem::take(&mut **body), state, false, true, false) .into_boxed_slice(), - body.positions(), + body.span(), ); } // do { block } while|until expr @@ -747,21 +746,21 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b #[cfg(not(feature = "no_module"))] Stmt::Import(expr, ..) => optimize_expr(expr, state, false), // { block } - Stmt::Block(statements, pos) => { + Stmt::Block(statements, span) => { let statements = mem::take(statements).into_vec().into(); let mut block = optimize_stmt_block(statements, state, preserve_result, true, false); match block.as_mut_slice() { [] => { state.set_dirty(); - *stmt = Stmt::Noop(pos.0); + *stmt = Stmt::Noop(span.start()); } // Only one statement which is not block-dependent - promote [s] if !s.is_block_dependent() => { state.set_dirty(); *stmt = mem::take(s); } - _ => *stmt = Stmt::Block(block.into_boxed_slice(), *pos), + _ => *stmt = Stmt::Block(block.into_boxed_slice(), *span), } } // try { pure try_block } catch ( var ) { catch_block } -> try_block @@ -771,7 +770,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b *stmt = Stmt::Block( optimize_stmt_block(mem::take(&mut *x.try_block), state, false, true, false) .into_boxed_slice(), - x.try_block.positions(), + x.try_block.span(), ); } // try { try_block } catch ( var ) { catch_block } diff --git a/src/parser.rs b/src/parser.rs index 8f5a28ca..e9b114d7 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -9,7 +9,7 @@ use crate::ast::{ use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS}; use crate::func::hashing::get_hasher; use crate::tokenizer::{ - is_keyword_function, is_valid_function_name, is_valid_identifier, Token, TokenStream, + is_keyword_function, is_valid_function_name, is_valid_identifier, Span, Token, TokenStream, TokenizerControl, }; use crate::types::dynamic::AccessMode; @@ -2818,7 +2818,7 @@ fn parse_block( Ok(Stmt::Block( statements.into_boxed_slice(), - (settings.pos, end_pos), + Span::new(settings.pos, end_pos), )) } diff --git a/src/tokenizer.rs b/src/tokenizer.rs index ecc887aa..cc05cdb2 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -59,10 +59,10 @@ pub type TokenStream<'a> = Peekable>; /// Advancing beyond the maximum line length or maximum number of lines is not an error but has no effect. #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] pub struct Position { - /// Line number - 0 = none + /// Line number: 0 = none #[cfg(not(feature = "no_position"))] line: u16, - /// Character position - 0 = BOL + /// Character position: 0 = BOL #[cfg(not(feature = "no_position"))] pos: u16, } @@ -300,6 +300,71 @@ impl AddAssign for Position { } } +/// _(internals)_ A span consisting of a starting and an ending [positions][Position]. +/// Exported under the `internals` feature only. +#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, Default)] +pub struct Span { + /// Starting [position][Position]. + start: Position, + /// Ending [position][Position]. + end: Position, +} + +impl Span { + pub const NONE: Self = Self::new(Position::NONE, Position::NONE); + + /// Create a new [`Span`]. + #[inline(always)] + #[must_use] + pub const fn new(start: Position, end: Position) -> Self { + Self { start, end } + } + /// Is this [`Span`] non-existent? + #[inline(always)] + #[must_use] + pub const fn is_none(&self) -> bool { + self.start.is_none() && self.end.is_none() + } + /// Get the [`Span`]'s starting [position][Position]. + #[inline(always)] + #[must_use] + pub const fn start(&self) -> Position { + self.start + } + /// Get the [`Span`]'s ending [position][Position]. + #[inline(always)] + #[must_use] + pub const fn end(&self) -> Position { + self.end + } +} + +impl fmt::Display for Span { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match (self.start().is_none(), self.end().is_none()) { + (false, false) if self.start().line() != self.end().line() => { + write!(f, "{:?}-{:?}", self.start(), self.end()) + } + (false, false) => write!( + f, + "{}:{}-{}", + self.start().line().unwrap(), + self.start().position().unwrap_or(0), + self.end().position().unwrap_or(0) + ), + (true, false) => write!(f, "..{:?}", self.end()), + (false, true) => write!(f, "{:?}", self.start()), + (true, true) => write!(f, "{:?}", Position::NONE), + } + } +} + +impl fmt::Debug for Span { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + /// _(internals)_ A Rhai language token. /// Exported under the `internals` feature only. #[derive(Debug, PartialEq, Clone, Hash)]