Add code comments.

This commit is contained in:
Stephen Chung 2020-03-18 10:36:50 +08:00
parent 777f66ff3c
commit ca20faf170
4 changed files with 385 additions and 152 deletions

View File

@ -1,7 +1,7 @@
//! Helper module which defines the `Any` trait to to allow dynamic value handling. //! Helper module which defines the `Any` trait to to allow dynamic value handling.
use crate::stdlib::{ use crate::stdlib::{
any::{self, type_name, TypeId}, any::{type_name, Any as StdAny, TypeId},
boxed::Box, boxed::Box,
fmt, fmt,
}; };
@ -13,7 +13,7 @@ pub type Variant = dyn Any;
pub type Dynamic = Box<Variant>; pub type Dynamic = Box<Variant>;
/// A trait covering any type. /// A trait covering any type.
pub trait Any: any::Any { pub trait Any: StdAny {
/// Get the `TypeId` of this type. /// Get the `TypeId` of this type.
fn type_id(&self) -> TypeId; fn type_id(&self) -> TypeId;
@ -23,12 +23,12 @@ pub trait Any: any::Any {
/// Convert into `Dynamic`. /// Convert into `Dynamic`.
fn into_dynamic(&self) -> Dynamic; fn into_dynamic(&self) -> Dynamic;
/// This type may only be implemented by `rhai`. /// This trait may only be implemented by `rhai`.
#[doc(hidden)] #[doc(hidden)]
fn _closed(&self) -> _Private; fn _closed(&self) -> _Private;
} }
impl<T: Clone + any::Any + ?Sized> Any for T { impl<T: Clone + StdAny + ?Sized> Any for T {
fn type_id(&self) -> TypeId { fn type_id(&self) -> TypeId {
TypeId::of::<T>() TypeId::of::<T>()
} }
@ -91,7 +91,7 @@ pub trait AnyExt: Sized {
/// Get a copy of a `Dynamic` value as a specific type. /// Get a copy of a `Dynamic` value as a specific type.
fn downcast<T: Any + Clone>(self) -> Result<Box<T>, Self>; fn downcast<T: Any + Clone>(self) -> Result<Box<T>, Self>;
/// This type may only be implemented by `rhai`. /// This trait may only be implemented by `rhai`.
#[doc(hidden)] #[doc(hidden)]
fn _closed(&self) -> _Private; fn _closed(&self) -> _Private;
} }

View File

@ -5,7 +5,7 @@ use crate::any::Any;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use crate::engine::Array; use crate::engine::Array;
use crate::engine::Engine; use crate::engine::Engine;
use crate::fn_register::{RegisterFn, RegisterResultFn}; use crate::fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
use crate::parser::{Position, INT}; use crate::parser::{Position, INT};
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
@ -55,6 +55,7 @@ macro_rules! reg_op_result1 {
impl Engine<'_> { impl Engine<'_> {
/// Register the core built-in library. /// Register the core built-in library.
pub(crate) fn register_core_lib(&mut self) { pub(crate) fn register_core_lib(&mut self) {
/// Checked add
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn add<T: Display + CheckedAdd>(x: T, y: T) -> Result<T, EvalAltResult> { fn add<T: Display + CheckedAdd>(x: T, y: T) -> Result<T, EvalAltResult> {
x.checked_add(&y).ok_or_else(|| { x.checked_add(&y).ok_or_else(|| {
@ -64,6 +65,7 @@ impl Engine<'_> {
) )
}) })
} }
/// Checked subtract
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn sub<T: Display + CheckedSub>(x: T, y: T) -> Result<T, EvalAltResult> { fn sub<T: Display + CheckedSub>(x: T, y: T) -> Result<T, EvalAltResult> {
x.checked_sub(&y).ok_or_else(|| { x.checked_sub(&y).ok_or_else(|| {
@ -73,6 +75,7 @@ impl Engine<'_> {
) )
}) })
} }
/// Checked multiply
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn mul<T: Display + CheckedMul>(x: T, y: T) -> Result<T, EvalAltResult> { fn mul<T: Display + CheckedMul>(x: T, y: T) -> Result<T, EvalAltResult> {
x.checked_mul(&y).ok_or_else(|| { x.checked_mul(&y).ok_or_else(|| {
@ -82,11 +85,13 @@ impl Engine<'_> {
) )
}) })
} }
/// Checked divide
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn div<T>(x: T, y: T) -> Result<T, EvalAltResult> fn div<T>(x: T, y: T) -> Result<T, EvalAltResult>
where where
T: Display + CheckedDiv + PartialEq + Zero, T: Display + CheckedDiv + PartialEq + Zero,
{ {
// Detect division by zero
if y == T::zero() { if y == T::zero() {
return Err(EvalAltResult::ErrorArithmetic( return Err(EvalAltResult::ErrorArithmetic(
format!("Division by zero: {} / {}", x, y), format!("Division by zero: {} / {}", x, y),
@ -101,6 +106,7 @@ impl Engine<'_> {
) )
}) })
} }
/// Checked negative - e.g. -(i32::MIN) will overflow i32::MAX
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn neg<T: Display + CheckedNeg>(x: T) -> Result<T, EvalAltResult> { fn neg<T: Display + CheckedNeg>(x: T) -> Result<T, EvalAltResult> {
x.checked_neg().ok_or_else(|| { x.checked_neg().ok_or_else(|| {
@ -110,6 +116,7 @@ impl Engine<'_> {
) )
}) })
} }
/// Checked absolute
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn abs<T: Display + CheckedNeg + PartialOrd + Zero>(x: T) -> Result<T, EvalAltResult> { fn abs<T: Display + CheckedNeg + PartialOrd + Zero>(x: T) -> Result<T, EvalAltResult> {
// FIX - We don't use Signed::abs() here because, contrary to documentation, it panics // FIX - We don't use Signed::abs() here because, contrary to documentation, it panics
@ -125,26 +132,32 @@ impl Engine<'_> {
}) })
} }
} }
/// Unchecked add - may panic on overflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))] #[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn add_u<T: Add>(x: T, y: T) -> <T as Add>::Output { fn add_u<T: Add>(x: T, y: T) -> <T as Add>::Output {
x + y x + y
} }
/// Unchecked subtract - may panic on underflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))] #[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn sub_u<T: Sub>(x: T, y: T) -> <T as Sub>::Output { fn sub_u<T: Sub>(x: T, y: T) -> <T as Sub>::Output {
x - y x - y
} }
/// Unchecked multiply - may panic on overflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))] #[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn mul_u<T: Mul>(x: T, y: T) -> <T as Mul>::Output { fn mul_u<T: Mul>(x: T, y: T) -> <T as Mul>::Output {
x * y x * y
} }
/// Unchecked divide - may panic when dividing by zero
#[cfg(any(feature = "unchecked", not(feature = "no_float")))] #[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn div_u<T: Div>(x: T, y: T) -> <T as Div>::Output { fn div_u<T: Div>(x: T, y: T) -> <T as Div>::Output {
x / y x / y
} }
/// Unchecked negative - may panic on overflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))] #[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn neg_u<T: Neg>(x: T) -> <T as Neg>::Output { fn neg_u<T: Neg>(x: T) -> <T as Neg>::Output {
-x -x
} }
/// Unchecked absolute - may panic on overflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))] #[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn abs_u<T>(x: T) -> <T as Neg>::Output fn abs_u<T>(x: T) -> <T as Neg>::Output
where where
@ -157,6 +170,9 @@ impl Engine<'_> {
x.into() x.into()
} }
} }
// Comparison operators
fn lt<T: PartialOrd>(x: T, y: T) -> bool { fn lt<T: PartialOrd>(x: T, y: T) -> bool {
x < y x < y
} }
@ -175,6 +191,9 @@ impl Engine<'_> {
fn ne<T: PartialEq>(x: T, y: T) -> bool { fn ne<T: PartialEq>(x: T, y: T) -> bool {
x != y x != y
} }
// Logic operators
fn and(x: bool, y: bool) -> bool { fn and(x: bool, y: bool) -> bool {
x && y x && y
} }
@ -184,6 +203,9 @@ impl Engine<'_> {
fn not(x: bool) -> bool { fn not(x: bool) -> bool {
!x !x
} }
// Bit operators
fn binary_and<T: BitAnd>(x: T, y: T) -> <T as BitAnd>::Output { fn binary_and<T: BitAnd>(x: T, y: T) -> <T as BitAnd>::Output {
x & y x & y
} }
@ -193,8 +215,11 @@ impl Engine<'_> {
fn binary_xor<T: BitXor>(x: T, y: T) -> <T as BitXor>::Output { fn binary_xor<T: BitXor>(x: T, y: T) -> <T as BitXor>::Output {
x ^ y x ^ y
} }
/// Checked left-shift
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn shl<T: Display + CheckedShl>(x: T, y: INT) -> Result<T, EvalAltResult> { fn shl<T: Display + CheckedShl>(x: T, y: INT) -> Result<T, EvalAltResult> {
// Cannot shift by a negative number of bits
if y < 0 { if y < 0 {
return Err(EvalAltResult::ErrorArithmetic( return Err(EvalAltResult::ErrorArithmetic(
format!("Left-shift by a negative number: {} << {}", x, y), format!("Left-shift by a negative number: {} << {}", x, y),
@ -204,13 +229,15 @@ impl Engine<'_> {
CheckedShl::checked_shl(&x, y as u32).ok_or_else(|| { CheckedShl::checked_shl(&x, y as u32).ok_or_else(|| {
EvalAltResult::ErrorArithmetic( EvalAltResult::ErrorArithmetic(
format!("Left-shift overflow: {} << {}", x, y), format!("Left-shift by too many bits: {} << {}", x, y),
Position::none(), Position::none(),
) )
}) })
} }
/// Checked right-shift
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn shr<T: Display + CheckedShr>(x: T, y: INT) -> Result<T, EvalAltResult> { fn shr<T: Display + CheckedShr>(x: T, y: INT) -> Result<T, EvalAltResult> {
// Cannot shift by a negative number of bits
if y < 0 { if y < 0 {
return Err(EvalAltResult::ErrorArithmetic( return Err(EvalAltResult::ErrorArithmetic(
format!("Right-shift by a negative number: {} >> {}", x, y), format!("Right-shift by a negative number: {} >> {}", x, y),
@ -220,44 +247,49 @@ impl Engine<'_> {
CheckedShr::checked_shr(&x, y as u32).ok_or_else(|| { CheckedShr::checked_shr(&x, y as u32).ok_or_else(|| {
EvalAltResult::ErrorArithmetic( EvalAltResult::ErrorArithmetic(
format!("Right-shift overflow: {} % {}", x, y), format!("Right-shift by too many bits: {} % {}", x, y),
Position::none(), Position::none(),
) )
}) })
} }
/// Unchecked left-shift - may panic if shifting by a negative number of bits
#[cfg(feature = "unchecked")] #[cfg(feature = "unchecked")]
fn shl_u<T: Shl<T>>(x: T, y: T) -> <T as Shl<T>>::Output { fn shl_u<T: Shl<T>>(x: T, y: T) -> <T as Shl<T>>::Output {
x.shl(y) x.shl(y)
} }
/// Unchecked right-shift - may panic if shifting by a negative number of bits
#[cfg(feature = "unchecked")] #[cfg(feature = "unchecked")]
fn shr_u<T: Shr<T>>(x: T, y: T) -> <T as Shr<T>>::Output { fn shr_u<T: Shr<T>>(x: T, y: T) -> <T as Shr<T>>::Output {
x.shr(y) x.shr(y)
} }
/// Checked modulo
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn modulo<T: Display + CheckedRem>(x: T, y: T) -> Result<T, EvalAltResult> { fn modulo<T: Display + CheckedRem>(x: T, y: T) -> Result<T, EvalAltResult> {
x.checked_rem(&y).ok_or_else(|| { x.checked_rem(&y).ok_or_else(|| {
EvalAltResult::ErrorArithmetic( EvalAltResult::ErrorArithmetic(
format!("Modulo division overflow: {} % {}", x, y), format!("Modulo division by zero or overflow: {} % {}", x, y),
Position::none(), Position::none(),
) )
}) })
} }
/// Unchecked modulo - may panic if dividing by zero
#[cfg(any(feature = "unchecked", not(feature = "no_float")))] #[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn modulo_u<T: Rem>(x: T, y: T) -> <T as Rem>::Output { fn modulo_u<T: Rem>(x: T, y: T) -> <T as Rem>::Output {
x % y x % y
} }
/// Checked power
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn pow_i_i_u(x: INT, y: INT) -> Result<INT, EvalAltResult> { fn pow_i_i(x: INT, y: INT) -> Result<INT, EvalAltResult> {
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
{ {
if y > (u32::MAX as INT) { if y > (u32::MAX as INT) {
Err(EvalAltResult::ErrorArithmetic( Err(EvalAltResult::ErrorArithmetic(
format!("Power overflow: {} ~ {}", x, y), format!("Integer raised to too large an index: {} ~ {}", x, y),
Position::none(), Position::none(),
)) ))
} else if y < 0 { } else if y < 0 {
Err(EvalAltResult::ErrorArithmetic( Err(EvalAltResult::ErrorArithmetic(
format!("Power underflow: {} ~ {}", x, y), format!("Integer raised to a negative index: {} ~ {}", x, y),
Position::none(), Position::none(),
)) ))
} else { } else {
@ -274,7 +306,7 @@ impl Engine<'_> {
{ {
if y < 0 { if y < 0 {
Err(EvalAltResult::ErrorArithmetic( Err(EvalAltResult::ErrorArithmetic(
format!("Power underflow: {} ~ {}", x, y), format!("Integer raised to a negative index: {} ~ {}", x, y),
Position::none(), Position::none(),
)) ))
} else { } else {
@ -287,29 +319,34 @@ impl Engine<'_> {
} }
} }
} }
/// Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX)
#[cfg(feature = "unchecked")] #[cfg(feature = "unchecked")]
fn pow_i_i(x: INT, y: INT) -> INT { fn pow_i_i_u(x: INT, y: INT) -> INT {
x.pow(y as u32) x.pow(y as u32)
} }
/// Floating-point power - always well-defined
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
fn pow_f_f(x: FLOAT, y: FLOAT) -> FLOAT { fn pow_f_f(x: FLOAT, y: FLOAT) -> FLOAT {
x.powf(y) x.powf(y)
} }
/// Checked power
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
fn pow_f_i_u(x: FLOAT, y: INT) -> Result<FLOAT, EvalAltResult> { fn pow_f_i(x: FLOAT, y: INT) -> Result<FLOAT, EvalAltResult> {
// Raise to power that is larger than an i32
if y > (i32::MAX as INT) { if y > (i32::MAX as INT) {
return Err(EvalAltResult::ErrorArithmetic( return Err(EvalAltResult::ErrorArithmetic(
format!("Power overflow: {} ~ {}", x, y), format!("Number raised to too large an index: {} ~ {}", x, y),
Position::none(), Position::none(),
)); ));
} }
Ok(x.powi(y as i32)) Ok(x.powi(y as i32))
} }
/// Unchecked power - may be incorrect if the power index is too high (> i32::MAX)
#[cfg(feature = "unchecked")] #[cfg(feature = "unchecked")]
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
fn pow_f_i(x: FLOAT, y: INT) -> FLOAT { fn pow_f_i_u(x: FLOAT, y: INT) -> FLOAT {
x.powi(y as i32) x.powi(y as i32)
} }
@ -393,8 +430,10 @@ impl Engine<'_> {
} }
} }
// `&&` and `||` are treated specially as they short-circuit
//reg_op!(self, "||", or, bool); //reg_op!(self, "||", or, bool);
//reg_op!(self, "&&", and, bool); //reg_op!(self, "&&", and, bool);
reg_op!(self, "|", or, bool); reg_op!(self, "|", or, bool);
reg_op!(self, "&", and, bool); reg_op!(self, "&", and, bool);
@ -448,18 +487,18 @@ impl Engine<'_> {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
{ {
self.register_result_fn("~", pow_i_i_u); self.register_result_fn("~", pow_i_i);
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
self.register_result_fn("~", pow_f_i_u); self.register_result_fn("~", pow_f_i);
} }
#[cfg(feature = "unchecked")] #[cfg(feature = "unchecked")]
{ {
self.register_fn("~", pow_i_i); self.register_fn("~", pow_i_i_u);
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
self.register_fn("~", pow_f_i); self.register_fn("~", pow_f_i_u);
} }
{ {
@ -628,9 +667,6 @@ macro_rules! reg_fn2y {
impl Engine<'_> { impl Engine<'_> {
#[cfg(not(feature = "no_stdlib"))] #[cfg(not(feature = "no_stdlib"))]
pub(crate) fn register_stdlib(&mut self) { pub(crate) fn register_stdlib(&mut self) {
#[cfg(not(feature = "no_index"))]
use crate::fn_register::RegisterDynamicFn;
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
{ {
// Advanced math functions // Advanced math functions

View File

@ -13,50 +13,54 @@ use crate::stdlib::{
boxed::Box, vec, boxed::Box, vec,
}; };
/// Level of optimization performed /// Level of optimization performed.
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)] #[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
pub enum OptimizationLevel { pub enum OptimizationLevel {
/// No optimization performed /// No optimization performed.
None, None,
/// Only perform simple optimizations without evaluating functions /// Only perform simple optimizations without evaluating functions.
Simple, Simple,
/// Full optimizations performed, including evaluating functions. /// Full optimizations performed, including evaluating functions.
/// Take care that this may cause side effects. /// Take care that this may cause side effects as it essentially assumes that all functions are pure.
Full, Full,
} }
/// Mutable state throughout an optimization pass.
struct State<'a> { struct State<'a> {
/// Has the AST been changed during this pass?
changed: bool, changed: bool,
/// Collection of constants to use for eager function evaluations.
constants: Vec<(String, Expr)>, constants: Vec<(String, Expr)>,
engine: Option<&'a Engine<'a>>, /// An `Engine` instance for eager function evaluation.
engine: &'a Engine<'a>,
} }
impl State<'_> { impl State<'_> {
pub fn new() -> Self { /// Reset the state from dirty to clean.
State {
changed: false,
constants: vec![],
engine: None,
}
}
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.changed = false; self.changed = false;
} }
/// Set the AST state to be dirty (i.e. changed).
pub fn set_dirty(&mut self) { pub fn set_dirty(&mut self) {
self.changed = true; self.changed = true;
} }
/// Is the AST dirty (i.e. changed)?
pub fn is_dirty(&self) -> bool { pub fn is_dirty(&self) -> bool {
self.changed self.changed
} }
/// Does a constant exist?
pub fn contains_constant(&self, name: &str) -> bool { pub fn contains_constant(&self, name: &str) -> bool {
self.constants.iter().any(|(n, _)| n == name) self.constants.iter().any(|(n, _)| n == name)
} }
/// Prune the list of constants back to a specified size.
pub fn restore_constants(&mut self, len: usize) { pub fn restore_constants(&mut self, len: usize) {
self.constants.truncate(len) self.constants.truncate(len)
} }
/// Add a new constant to the list.
pub fn push_constant(&mut self, name: &str, value: Expr) { pub fn push_constant(&mut self, name: &str, value: Expr) {
self.constants.push((name.to_string(), value)) self.constants.push((name.to_string(), value))
} }
/// Look up a constant from the list.
pub fn find_constant(&self, name: &str) -> Option<&Expr> { pub fn find_constant(&self, name: &str) -> Option<&Expr> {
for (n, expr) in self.constants.iter().rev() { for (n, expr) in self.constants.iter().rev() {
if n == name { if n == name {
@ -68,60 +72,68 @@ impl State<'_> {
} }
} }
/// Optimize a statement.
fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -> Stmt { fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -> Stmt {
match stmt { match stmt {
Stmt::IfElse(expr, stmt1, None) if stmt1.is_noop() => { // if expr { Noop }
Stmt::IfElse(expr, if_block, None) if matches!(*if_block, Stmt::Noop(_)) => {
state.set_dirty(); state.set_dirty();
let pos = expr.position(); let pos = expr.position();
let expr = optimize_expr(*expr, state); let expr = optimize_expr(*expr, state);
if matches!(expr, Expr::False(_) | Expr::True(_)) { if preserve_result {
Stmt::Noop(stmt1.position()) // -> { expr, Noop }
Stmt::Block(vec![Stmt::Expr(Box::new(expr)), *if_block], pos)
} else { } else {
let stmt = Stmt::Expr(Box::new(expr)); // -> expr
Stmt::Expr(Box::new(expr))
if preserve_result {
Stmt::Block(vec![stmt, *stmt1], pos)
} else {
stmt
}
} }
} }
// if expr { if_block }
Stmt::IfElse(expr, stmt1, None) => match *expr { Stmt::IfElse(expr, if_block, None) => match *expr {
// if false { if_block } -> Noop
Expr::False(pos) => { Expr::False(pos) => {
state.set_dirty(); state.set_dirty();
Stmt::Noop(pos) Stmt::Noop(pos)
} }
Expr::True(_) => optimize_stmt(*stmt1, state, true), // if true { if_block } -> if_block
Expr::True(_) => optimize_stmt(*if_block, state, true),
// if expr { if_block }
expr => Stmt::IfElse( expr => Stmt::IfElse(
Box::new(optimize_expr(expr, state)), Box::new(optimize_expr(expr, state)),
Box::new(optimize_stmt(*stmt1, state, true)), Box::new(optimize_stmt(*if_block, state, true)),
None, None,
), ),
}, },
// if expr { if_block } else { else_block }
Stmt::IfElse(expr, stmt1, Some(stmt2)) => match *expr { Stmt::IfElse(expr, if_block, Some(else_block)) => match *expr {
Expr::False(_) => optimize_stmt(*stmt2, state, true), // if false { if_block } else { else_block } -> else_block
Expr::True(_) => optimize_stmt(*stmt1, state, true), Expr::False(_) => optimize_stmt(*else_block, state, true),
// if true { if_block } else { else_block } -> if_block
Expr::True(_) => optimize_stmt(*if_block, state, true),
// if expr { if_block } else { else_block }
expr => Stmt::IfElse( expr => Stmt::IfElse(
Box::new(optimize_expr(expr, state)), Box::new(optimize_expr(expr, state)),
Box::new(optimize_stmt(*stmt1, state, true)), Box::new(optimize_stmt(*if_block, state, true)),
match optimize_stmt(*stmt2, state, true) { match optimize_stmt(*else_block, state, true) {
stmt if stmt.is_noop() => None, stmt if matches!(stmt, Stmt::Noop(_)) => None, // Noop -> no else block
stmt => Some(Box::new(stmt)), stmt => Some(Box::new(stmt)),
}, },
), ),
}, },
// while expr { block }
Stmt::While(expr, stmt) => match *expr { Stmt::While(expr, block) => match *expr {
// while false { block } -> Noop
Expr::False(pos) => { Expr::False(pos) => {
state.set_dirty(); state.set_dirty();
Stmt::Noop(pos) Stmt::Noop(pos)
} }
Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*stmt, state, false))), // while true { block } -> loop { block }
expr => match optimize_stmt(*stmt, state, false) { Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*block, state, false))),
// while expr { block }
expr => match optimize_stmt(*block, state, false) {
// while expr { break; } -> { expr; }
Stmt::Break(pos) => { Stmt::Break(pos) => {
// Only a single break statement - turn into running the guard expression once // Only a single break statement - turn into running the guard expression once
state.set_dirty(); state.set_dirty();
@ -131,48 +143,50 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
} }
Stmt::Block(statements, pos) Stmt::Block(statements, pos)
} }
// while expr { block }
stmt => Stmt::While(Box::new(optimize_expr(expr, state)), Box::new(stmt)), stmt => Stmt::While(Box::new(optimize_expr(expr, state)), Box::new(stmt)),
}, },
}, },
Stmt::Loop(stmt) => match optimize_stmt(*stmt, state, false) { // loop { block }
Stmt::Loop(block) => match optimize_stmt(*block, state, false) {
// loop { break; } -> Noop
Stmt::Break(pos) => { Stmt::Break(pos) => {
// Only a single break statement // Only a single break statement
state.set_dirty(); state.set_dirty();
Stmt::Noop(pos) Stmt::Noop(pos)
} }
// loop { block }
stmt => Stmt::Loop(Box::new(stmt)), stmt => Stmt::Loop(Box::new(stmt)),
}, },
Stmt::For(id, expr, stmt) => Stmt::For( // for id in expr { block }
Stmt::For(id, expr, block) => Stmt::For(
id, id,
Box::new(optimize_expr(*expr, state)), Box::new(optimize_expr(*expr, state)),
Box::new(match optimize_stmt(*stmt, state, false) { Box::new(optimize_stmt(*block, state, false)),
Stmt::Break(pos) => {
// Only a single break statement
state.set_dirty();
Stmt::Noop(pos)
}
stmt => stmt,
}),
), ),
// let id = expr;
Stmt::Let(id, Some(expr), pos) => { Stmt::Let(id, Some(expr), pos) => {
Stmt::Let(id, Some(Box::new(optimize_expr(*expr, state))), pos) Stmt::Let(id, Some(Box::new(optimize_expr(*expr, state))), pos)
} }
// let id;
Stmt::Let(_, None, _) => stmt, Stmt::Let(_, None, _) => stmt,
// { block }
Stmt::Block(block, pos) => {
let orig_len = block.len(); // Original number of statements in the block, for change detection
let orig_constants_len = state.constants.len(); // Original number of constants in the state, for restore later
Stmt::Block(statements, pos) => { // Optimize each statement in the block
let orig_len = statements.len(); let mut result: Vec<_> = block
let orig_constants_len = state.constants.len(); .into_iter()
let mut result: Vec<_> = statements
.into_iter() // For each statement
.map(|stmt| { .map(|stmt| {
if let Stmt::Const(name, value, pos) = stmt { if let Stmt::Const(name, value, pos) = stmt {
// Add constant into the state
state.push_constant(&name, *value); state.push_constant(&name, *value);
state.set_dirty(); state.set_dirty();
Stmt::Noop(pos) // No need to keep constants Stmt::Noop(pos) // No need to keep constants
} else { } else {
optimize_stmt(stmt, state, preserve_result) // Optimize the statement // Optimize the statement
optimize_stmt(stmt, state, preserve_result)
} }
}) })
.collect(); .collect();
@ -207,11 +221,12 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
result.push(Stmt::Noop(pos)) result.push(Stmt::Noop(pos))
} }
// Optimize all the statements again
result = result result = result
.into_iter() .into_iter()
.rev() .rev()
.enumerate() .enumerate()
.map(|(i, s)| optimize_stmt(s, state, i == 0)) // Optimize all other statements again .map(|(i, s)| optimize_stmt(s, state, i == 0))
.rev() .rev()
.collect(); .collect();
} }
@ -234,10 +249,12 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
true true
}); });
// Change detection
if orig_len != result.len() { if orig_len != result.len() {
state.set_dirty(); state.set_dirty();
} }
// Pop the stack and remove all the local constants
state.restore_constants(orig_constants_len); state.restore_constants(orig_constants_len);
match result[..] { match result[..] {
@ -254,44 +271,54 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
_ => Stmt::Block(result, pos), _ => Stmt::Block(result, pos),
} }
} }
// expr;
Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, state))), Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, state))),
// return expr;
Stmt::ReturnWithVal(Some(expr), is_return, pos) => { Stmt::ReturnWithVal(Some(expr), is_return, pos) => {
Stmt::ReturnWithVal(Some(Box::new(optimize_expr(*expr, state))), is_return, pos) Stmt::ReturnWithVal(Some(Box::new(optimize_expr(*expr, state))), is_return, pos)
} }
// All other statements - skip
stmt => stmt, stmt => stmt,
} }
} }
/// Optimize an expression.
fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
// These keywords are handled specially
const SKIP_FUNC_KEYWORDS: [&str; 3] = [KEYWORD_PRINT, KEYWORD_DEBUG, KEYWORD_DUMP_AST]; const SKIP_FUNC_KEYWORDS: [&str; 3] = [KEYWORD_PRINT, KEYWORD_DEBUG, KEYWORD_DUMP_AST];
match expr { match expr {
// ( stmt )
Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, state, true) { Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, state, true) {
// ( Noop ) -> ()
Stmt::Noop(_) => { Stmt::Noop(_) => {
state.set_dirty(); state.set_dirty();
Expr::Unit(pos) Expr::Unit(pos)
} }
// ( expr ) -> expr
Stmt::Expr(expr) => { Stmt::Expr(expr) => {
state.set_dirty(); state.set_dirty();
*expr *expr
} }
// ( stmt )
stmt => Expr::Stmt(Box::new(stmt), pos), stmt => Expr::Stmt(Box::new(stmt), pos),
}, },
Expr::Assignment(id1, expr1, pos1) => match *expr1 { // id = expr
Expr::Assignment(id2, expr2, pos2) => match (*id1, *id2) { Expr::Assignment(id, expr, pos) => match *expr {
(Expr::Variable(var1, _), Expr::Variable(var2, _)) if var1 == var2 => { //id = id2 = expr2
Expr::Assignment(id2, expr2, pos2) => match (*id, *id2) {
// var = var = expr2 -> var = expr2
(Expr::Variable(var, _), Expr::Variable(var2, _)) if var == var2 => {
// Assignment to the same variable - fold // Assignment to the same variable - fold
state.set_dirty(); state.set_dirty();
Expr::Assignment( Expr::Assignment(
Box::new(Expr::Variable(var1, pos1)), Box::new(Expr::Variable(var, pos)),
Box::new(optimize_expr(*expr2, state)), Box::new(optimize_expr(*expr2, state)),
pos1, pos,
) )
} }
// id1 = id2 = expr2
(id1, id2) => Expr::Assignment( (id1, id2) => Expr::Assignment(
Box::new(id1), Box::new(id1),
Box::new(Expr::Assignment( Box::new(Expr::Assignment(
@ -299,19 +326,23 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
Box::new(optimize_expr(*expr2, state)), Box::new(optimize_expr(*expr2, state)),
pos2, pos2,
)), )),
pos1, pos,
), ),
}, },
expr => Expr::Assignment(id1, Box::new(optimize_expr(expr, state)), pos1), // id = expr
expr => Expr::Assignment(id, Box::new(optimize_expr(expr, state)), pos),
}, },
// lhs.rhs
Expr::Dot(lhs, rhs, pos) => Expr::Dot( Expr::Dot(lhs, rhs, pos) => Expr::Dot(
Box::new(optimize_expr(*lhs, state)), Box::new(optimize_expr(*lhs, state)),
Box::new(optimize_expr(*rhs, state)), Box::new(optimize_expr(*rhs, state)),
pos, pos,
), ),
// lhs[rhs]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) { Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) {
// array[int]
(Expr::Array(mut items, _), Expr::IntegerConstant(i, _)) (Expr::Array(mut items, _), Expr::IntegerConstant(i, _))
if i >= 0 && (i as usize) < items.len() && items.iter().all(|x| x.is_pure()) => if i >= 0 && (i as usize) < items.len() && items.iter().all(|x| x.is_pure()) =>
{ {
@ -320,6 +351,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
state.set_dirty(); state.set_dirty();
items.remove(i as usize) items.remove(i as usize)
} }
// string[int]
(Expr::StringConstant(s, pos), Expr::IntegerConstant(i, _)) (Expr::StringConstant(s, pos), Expr::IntegerConstant(i, _))
if i >= 0 && (i as usize) < s.chars().count() => if i >= 0 && (i as usize) < s.chars().count() =>
{ {
@ -327,14 +359,14 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
state.set_dirty(); state.set_dirty();
Expr::CharConstant(s.chars().nth(i as usize).expect("should get char"), pos) Expr::CharConstant(s.chars().nth(i as usize).expect("should get char"), pos)
} }
// lhs[rhs]
(lhs, rhs) => Expr::Index( (lhs, rhs) => Expr::Index(
Box::new(optimize_expr(lhs, state)), Box::new(optimize_expr(lhs, state)),
Box::new(optimize_expr(rhs, state)), Box::new(optimize_expr(rhs, state)),
pos, pos,
), ),
}, },
// [ items .. ]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Array(items, pos) => { Expr::Array(items, pos) => {
let orig_len = items.len(); let orig_len = items.len();
@ -350,38 +382,47 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
Expr::Array(items, pos) Expr::Array(items, pos)
} }
// lhs && rhs
Expr::And(lhs, rhs) => match (*lhs, *rhs) { Expr::And(lhs, rhs) => match (*lhs, *rhs) {
// true && rhs -> rhs
(Expr::True(_), rhs) => { (Expr::True(_), rhs) => {
state.set_dirty(); state.set_dirty();
rhs rhs
} }
// false && rhs -> false
(Expr::False(pos), _) => { (Expr::False(pos), _) => {
state.set_dirty(); state.set_dirty();
Expr::False(pos) Expr::False(pos)
} }
// lhs && true -> lhs
(lhs, Expr::True(_)) => { (lhs, Expr::True(_)) => {
state.set_dirty(); state.set_dirty();
lhs optimize_expr(lhs, state)
} }
// lhs && rhs
(lhs, rhs) => Expr::And( (lhs, rhs) => Expr::And(
Box::new(optimize_expr(lhs, state)), Box::new(optimize_expr(lhs, state)),
Box::new(optimize_expr(rhs, state)), Box::new(optimize_expr(rhs, state)),
), ),
}, },
// lhs || rhs
Expr::Or(lhs, rhs) => match (*lhs, *rhs) { Expr::Or(lhs, rhs) => match (*lhs, *rhs) {
// false || rhs -> rhs
(Expr::False(_), rhs) => { (Expr::False(_), rhs) => {
state.set_dirty(); state.set_dirty();
rhs rhs
} }
// true || rhs -> true
(Expr::True(pos), _) => { (Expr::True(pos), _) => {
state.set_dirty(); state.set_dirty();
Expr::True(pos) Expr::True(pos)
} }
// lhs || false
(lhs, Expr::False(_)) => { (lhs, Expr::False(_)) => {
state.set_dirty(); state.set_dirty();
lhs optimize_expr(lhs, state)
} }
// lhs || rhs
(lhs, rhs) => Expr::Or( (lhs, rhs) => Expr::Or(
Box::new(optimize_expr(lhs, state)), Box::new(optimize_expr(lhs, state)),
Box::new(optimize_expr(rhs, state)), Box::new(optimize_expr(rhs, state)),
@ -392,24 +433,23 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
Expr::FunctionCall(id, args, def_value, pos) if SKIP_FUNC_KEYWORDS.contains(&id.as_str())=> Expr::FunctionCall(id, args, def_value, pos) if SKIP_FUNC_KEYWORDS.contains(&id.as_str())=>
Expr::FunctionCall(id, args, def_value, pos), Expr::FunctionCall(id, args, def_value, pos),
// Actually call function to optimize it // Eagerly call functions
Expr::FunctionCall(id, args, def_value, pos) Expr::FunctionCall(id, args, def_value, pos)
if state.engine.map(|eng| eng.optimization_level == OptimizationLevel::Full).unwrap_or(false) // full optimizations if state.engine.optimization_level == OptimizationLevel::Full // full optimizations
&& args.iter().all(|expr| expr.is_constant()) // all arguments are constants && args.iter().all(|expr| expr.is_constant()) // all arguments are constants
=> { => {
let engine = state.engine.expect("engine should be Some");
let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect(); let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect();
let call_args: FnCallArgs = arg_values.iter_mut().map(Dynamic::as_mut).collect(); let call_args: FnCallArgs = arg_values.iter_mut().map(Dynamic::as_mut).collect();
// Save the typename of the first argument if it is `type_of()` // Save the typename of the first argument if it is `type_of()`
// This is to avoid `call_args` being passed into the closure // This is to avoid `call_args` being passed into the closure
let arg_for_type_of = if id == KEYWORD_TYPE_OF && call_args.len() == 1 { let arg_for_type_of = if id == KEYWORD_TYPE_OF && call_args.len() == 1 {
engine.map_type_name(call_args[0].type_name()) state.engine.map_type_name(call_args[0].type_name())
} else { } else {
"" ""
}; };
engine.call_ext_fn_raw(&id, call_args, pos).ok().map(|r| state.engine.call_ext_fn_raw(&id, call_args, pos).ok().map(|r|
r.or_else(|| { r.or_else(|| {
if !arg_for_type_of.is_empty() { if !arg_for_type_of.is_empty() {
// Handle `type_of()` // Handle `type_of()`
@ -425,10 +465,10 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
}) })
).flatten().unwrap_or_else(|| Expr::FunctionCall(id, args, def_value, pos)) ).flatten().unwrap_or_else(|| Expr::FunctionCall(id, args, def_value, pos))
} }
// Optimize the function call arguments // id(args ..) -> optimize function call arguments
Expr::FunctionCall(id, args, def_value, pos) => Expr::FunctionCall(id, args, def_value, pos) =>
Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos), Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos),
// constant-name
Expr::Variable(ref name, _) if state.contains_constant(name) => { Expr::Variable(ref name, _) if state.contains_constant(name) => {
state.set_dirty(); state.set_dirty();
@ -438,28 +478,25 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
.expect("should find constant in scope!") .expect("should find constant in scope!")
.clone() .clone()
} }
// All other expressions - skip
expr => expr, expr => expr,
} }
} }
pub(crate) fn optimize<'a>( pub(crate) fn optimize<'a>(statements: Vec<Stmt>, engine: &Engine<'a>, scope: &Scope) -> Vec<Stmt> {
statements: Vec<Stmt>,
engine: Option<&Engine<'a>>,
scope: &Scope,
) -> Vec<Stmt> {
// If optimization level is None then skip optimizing // If optimization level is None then skip optimizing
if engine if engine.optimization_level == OptimizationLevel::None {
.map(|eng| eng.optimization_level == OptimizationLevel::None)
.unwrap_or(false)
{
return statements; return statements;
} }
// Set up the state // Set up the state
let mut state = State::new(); let mut state = State {
state.engine = engine; changed: false,
constants: vec![],
engine,
};
// Add constants from the scope into the state
scope scope
.iter() .iter()
.filter(|ScopeEntry { var_type, expr, .. }| { .filter(|ScopeEntry { var_type, expr, .. }| {
@ -476,9 +513,9 @@ pub(crate) fn optimize<'a>(
let orig_constants_len = state.constants.len(); let orig_constants_len = state.constants.len();
// Optimization loop
let mut result = statements; let mut result = statements;
// Optimization loop
loop { loop {
state.reset(); state.reset();
state.restore_constants(orig_constants_len); state.restore_constants(orig_constants_len);
@ -496,7 +533,7 @@ pub(crate) fn optimize<'a>(
} else { } else {
// Keep all variable declarations at this level // Keep all variable declarations at this level
// and always keep the last return value // and always keep the last return value
let keep = stmt.is_var() || i == num_statements - 1; let keep = matches!(stmt, Stmt::Let(_, _, _)) || i == num_statements - 1;
optimize_stmt(stmt, &mut state, keep) optimize_stmt(stmt, &mut state, keep)
} }
@ -521,6 +558,7 @@ pub(crate) fn optimize<'a>(
result result
} }
/// Optimize an AST.
pub fn optimize_ast( pub fn optimize_ast(
engine: &Engine, engine: &Engine,
scope: &Scope, scope: &Scope,
@ -530,8 +568,8 @@ pub fn optimize_ast(
AST( AST(
match engine.optimization_level { match engine.optimization_level {
OptimizationLevel::None => statements, OptimizationLevel::None => statements,
OptimizationLevel::Simple => optimize(statements, None, &scope), OptimizationLevel::Simple => optimize(statements, engine, &scope),
OptimizationLevel::Full => optimize(statements, Some(engine), &scope), OptimizationLevel::Full => optimize(statements, engine, &scope),
}, },
functions functions
.into_iter() .into_iter()
@ -540,14 +578,21 @@ pub fn optimize_ast(
OptimizationLevel::None => (), OptimizationLevel::None => (),
OptimizationLevel::Simple | OptimizationLevel::Full => { OptimizationLevel::Simple | OptimizationLevel::Full => {
let pos = fn_def.body.position(); let pos = fn_def.body.position();
let mut body = optimize(vec![fn_def.body], None, &Scope::new());
// Optimize the function body
let mut body = optimize(vec![fn_def.body], engine, &Scope::new());
// {} -> Noop
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) { fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
// { return val; } -> val
Stmt::ReturnWithVal(Some(val), ReturnType::Return, _) => { Stmt::ReturnWithVal(Some(val), ReturnType::Return, _) => {
Stmt::Expr(val) Stmt::Expr(val)
} }
// { return; } -> ()
Stmt::ReturnWithVal(None, ReturnType::Return, pos) => { Stmt::ReturnWithVal(None, ReturnType::Return, pos) => {
Stmt::Expr(Box::new(Expr::Unit(pos))) Stmt::Expr(Box::new(Expr::Unit(pos)))
} }
// All others
stmt => stmt, stmt => stmt,
}; };
} }

View File

@ -29,13 +29,13 @@ use crate::stdlib::{
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
pub type INT = i64; pub type INT = i64;
/// The system integer type /// The system integer type.
/// ///
/// If the `only_i32` feature is not enabled, this will be `i64` instead. /// If the `only_i32` feature is not enabled, this will be `i64` instead.
#[cfg(feature = "only_i32")] #[cfg(feature = "only_i32")]
pub type INT = i32; pub type INT = i32;
/// The system floating-point type /// The system floating-point type.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
pub type FLOAT = f64; pub type FLOAT = f64;
@ -45,7 +45,9 @@ type PERR = ParseErrorType;
/// A location (line number + character position) in the input script. /// A location (line number + character position) in the input script.
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
pub struct Position { pub struct Position {
/// Line number - 0 = none, MAX = EOF
line: usize, line: usize,
/// Character position - 0 = BOL, MAX = EOF
pos: usize, pos: usize,
} }
@ -162,45 +164,69 @@ impl fmt::Debug for Position {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct AST(pub(crate) Vec<Stmt>, pub(crate) Vec<Arc<FnDef>>); pub struct AST(pub(crate) Vec<Stmt>, pub(crate) Vec<Arc<FnDef>>);
/// A script-function definition.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct FnDef { pub struct FnDef {
/// Function name.
pub name: String, pub name: String,
/// Names of function parameters.
pub params: Vec<String>, pub params: Vec<String>,
/// Function body.
pub body: Stmt, pub body: Stmt,
/// Position of the function definition.
pub pos: Position, pub pos: Position,
} }
impl FnDef { impl FnDef {
/// Function to order two FnDef records, for binary search.
pub fn compare(&self, name: &str, params_len: usize) -> Ordering { pub fn compare(&self, name: &str, params_len: usize) -> Ordering {
// First order by name
match self.name.as_str().cmp(name) { match self.name.as_str().cmp(name) {
// Then by number of parameters
Ordering::Equal => self.params.len().cmp(&params_len), Ordering::Equal => self.params.len().cmp(&params_len),
order => order, order => order,
} }
} }
} }
/// `return`/`throw` statement.
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum ReturnType { pub enum ReturnType {
/// `return` statement.
Return, Return,
/// `throw` statement.
Exception, Exception,
} }
/// A statement.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Stmt { pub enum Stmt {
/// No-op.
Noop(Position), Noop(Position),
/// if expr { stmt } else { stmt }
IfElse(Box<Expr>, Box<Stmt>, Option<Box<Stmt>>), IfElse(Box<Expr>, Box<Stmt>, Option<Box<Stmt>>),
/// while expr { stmt }
While(Box<Expr>, Box<Stmt>), While(Box<Expr>, Box<Stmt>),
/// loop { stmt }
Loop(Box<Stmt>), Loop(Box<Stmt>),
/// for id in expr { stmt }
For(String, Box<Expr>, Box<Stmt>), For(String, Box<Expr>, Box<Stmt>),
/// let id = expr
Let(String, Option<Box<Expr>>, Position), Let(String, Option<Box<Expr>>, Position),
/// const id = expr
Const(String, Box<Expr>, Position), Const(String, Box<Expr>, Position),
/// { stmt; ... }
Block(Vec<Stmt>, Position), Block(Vec<Stmt>, Position),
/// { stmt }
Expr(Box<Expr>), Expr(Box<Expr>),
/// break
Break(Position), Break(Position),
/// `return`/`throw`
ReturnWithVal(Option<Box<Expr>>, ReturnType, Position), ReturnWithVal(Option<Box<Expr>>, ReturnType, Position),
} }
impl Stmt { impl Stmt {
/// Get the `Position` of this statement.
pub fn position(&self) -> Position { pub fn position(&self) -> Position {
match self { match self {
Stmt::Noop(pos) Stmt::Noop(pos)
@ -214,18 +240,7 @@ impl Stmt {
} }
} }
pub fn is_noop(&self) -> bool { /// Is this statement self-terminated (i.e. no need for a semicolon terminator)?
matches!(self, Stmt::Noop(_))
}
pub fn is_op(&self) -> bool {
!matches!(self, Stmt::Noop(_))
}
pub fn is_var(&self) -> bool {
matches!(self, Stmt::Let(_, _, _))
}
pub fn is_self_terminated(&self) -> bool { pub fn is_self_terminated(&self) -> bool {
match self { match self {
Stmt::Noop(_) Stmt::Noop(_)
@ -243,6 +258,7 @@ impl Stmt {
} }
} }
/// Is this statement _pure_?
pub fn is_pure(&self) -> bool { pub fn is_pure(&self) -> bool {
match self { match self {
Stmt::Noop(_) => true, Stmt::Noop(_) => true,
@ -262,31 +278,54 @@ impl Stmt {
} }
} }
/// An expression.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Expr { pub enum Expr {
/// Integer constant.
IntegerConstant(INT, Position), IntegerConstant(INT, Position),
/// Floating-point constant.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
FloatConstant(FLOAT, Position), FloatConstant(FLOAT, Position),
Variable(String, Position), /// Character constant.
Property(String, Position),
CharConstant(char, Position), CharConstant(char, Position),
/// String constant.
StringConstant(String, Position), StringConstant(String, Position),
/// Variable access.
Variable(String, Position),
/// Property access.
Property(String, Position),
/// { stmt }
Stmt(Box<Stmt>, Position), Stmt(Box<Stmt>, Position),
/// func(expr, ... )
FunctionCall(String, Vec<Expr>, Option<Dynamic>, Position), FunctionCall(String, Vec<Expr>, Option<Dynamic>, Position),
/// expr = expr
Assignment(Box<Expr>, Box<Expr>, Position), Assignment(Box<Expr>, Box<Expr>, Position),
/// lhs.rhs
Dot(Box<Expr>, Box<Expr>, Position), Dot(Box<Expr>, Box<Expr>, Position),
/// expr[expr]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Index(Box<Expr>, Box<Expr>, Position), Index(Box<Expr>, Box<Expr>, Position),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
/// [ expr, ... ]
Array(Vec<Expr>, Position), Array(Vec<Expr>, Position),
/// lhs && rhs
And(Box<Expr>, Box<Expr>), And(Box<Expr>, Box<Expr>),
/// lhs || rhs
Or(Box<Expr>, Box<Expr>), Or(Box<Expr>, Box<Expr>),
/// true
True(Position), True(Position),
/// false
False(Position), False(Position),
/// ()
Unit(Position), Unit(Position),
} }
impl Expr { impl Expr {
/// Get the `Dynamic` value of a constant expression.
///
/// # Panics
///
/// Panics when the expression is not constant.
pub fn get_constant_value(&self) -> Dynamic { pub fn get_constant_value(&self) -> Dynamic {
match self { match self {
Expr::IntegerConstant(i, _) => i.into_dynamic(), Expr::IntegerConstant(i, _) => i.into_dynamic(),
@ -310,6 +349,11 @@ impl Expr {
} }
} }
/// Get the display value of a constant expression.
///
/// # Panics
///
/// Panics when the expression is not constant.
pub fn get_constant_str(&self) -> String { pub fn get_constant_str(&self) -> String {
match self { match self {
Expr::IntegerConstant(i, _) => i.to_string(), Expr::IntegerConstant(i, _) => i.to_string(),
@ -329,6 +373,7 @@ impl Expr {
} }
} }
/// Get the `Position` of the expression.
pub fn position(&self) -> Position { pub fn position(&self) -> Position {
match self { match self {
Expr::IntegerConstant(_, pos) Expr::IntegerConstant(_, pos)
@ -358,7 +403,7 @@ impl Expr {
} }
} }
/// Is this expression pure? /// Is the expression pure?
/// ///
/// A pure expression has no side effects. /// A pure expression has no side effects.
pub fn is_pure(&self) -> bool { pub fn is_pure(&self) -> bool {
@ -377,6 +422,7 @@ impl Expr {
} }
} }
/// Is the expression a constant?
pub fn is_constant(&self) -> bool { pub fn is_constant(&self) -> bool {
match self { match self {
Expr::IntegerConstant(_, _) Expr::IntegerConstant(_, _)
@ -389,6 +435,7 @@ impl Expr {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Expr::FloatConstant(_, _) => true, Expr::FloatConstant(_, _) => true,
// An array literal is constant if all items are constant
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Array(expressions, _) => expressions.iter().all(Expr::is_constant), Expr::Array(expressions, _) => expressions.iter().all(Expr::is_constant),
@ -397,6 +444,7 @@ impl Expr {
} }
} }
/// Tokens.
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum Token { pub enum Token {
IntegerConstant(INT), IntegerConstant(INT),
@ -470,6 +518,7 @@ pub enum Token {
} }
impl Token { impl Token {
/// Get the syntax of the token.
pub fn syntax<'a>(&'a self) -> Cow<'a, str> { pub fn syntax<'a>(&'a self) -> Cow<'a, str> {
use self::Token::*; use self::Token::*;
@ -550,8 +599,8 @@ impl Token {
} }
} }
// if another operator is after these, it's probably an unary operator // If another operator is after these, it's probably an unary operator
// not sure about fn's name // (not sure about fn name).
pub fn is_next_unary(&self) -> bool { pub fn is_next_unary(&self) -> bool {
use self::Token::*; use self::Token::*;
@ -612,6 +661,7 @@ impl Token {
} }
} }
/// Get the precedence number of the token.
pub fn precedence(&self) -> u8 { pub fn precedence(&self) -> u8 {
match self { match self {
Self::Equals Self::Equals
@ -652,8 +702,10 @@ impl Token {
} }
} }
/// Does an expression bind to the right (instead of left)?
pub fn is_bind_right(&self) -> bool { pub fn is_bind_right(&self) -> bool {
match self { match self {
// Assignments bind to the right
Self::Equals Self::Equals
| Self::PlusAssign | Self::PlusAssign
| Self::MinusAssign | Self::MinusAssign
@ -667,6 +719,7 @@ impl Token {
| Self::ModuloAssign | Self::ModuloAssign
| Self::PowerOfAssign => true, | Self::PowerOfAssign => true,
// Property access binds to the right
Self::Period => true, Self::Period => true,
_ => false, _ => false,
@ -674,24 +727,36 @@ impl Token {
} }
} }
/// An iterator on a `Token` stream.
pub struct TokenIterator<'a> { pub struct TokenIterator<'a> {
/// The last token seen.
last: Token, last: Token,
/// Current position.
pos: Position, pos: Position,
/// The input characters stream.
char_stream: Peekable<Chars<'a>>, char_stream: Peekable<Chars<'a>>,
} }
impl<'a> TokenIterator<'a> { impl<'a> TokenIterator<'a> {
/// Move the current position one character ahead.
fn advance(&mut self) { fn advance(&mut self) {
self.pos.advance(); self.pos.advance();
} }
/// Move the current position back one character.
///
/// # Panics
///
/// Panics if already at the beginning of a line - cannot rewind to the previous line.
fn rewind(&mut self) { fn rewind(&mut self) {
self.pos.rewind(); self.pos.rewind();
} }
/// Move the current position to the next line.
fn new_line(&mut self) { fn new_line(&mut self) {
self.pos.new_line() self.pos.new_line()
} }
pub fn parse_string_const( /// Parse a string literal wrapped by `enclosing_char`.
pub fn parse_string_literal(
&mut self, &mut self,
enclosing_char: char, enclosing_char: char,
) -> Result<String, (LexError, Position)> { ) -> Result<String, (LexError, Position)> {
@ -703,25 +768,31 @@ impl<'a> TokenIterator<'a> {
self.advance(); self.advance();
match next_char.ok_or((LERR::UnterminatedString, Position::eof()))? { match next_char.ok_or((LERR::UnterminatedString, Position::eof()))? {
// \...
'\\' if escape.is_empty() => { '\\' if escape.is_empty() => {
escape.push('\\'); escape.push('\\');
} }
// \\
'\\' if !escape.is_empty() => { '\\' if !escape.is_empty() => {
escape.clear(); escape.clear();
result.push('\\'); result.push('\\');
} }
// \t
't' if !escape.is_empty() => { 't' if !escape.is_empty() => {
escape.clear(); escape.clear();
result.push('\t'); result.push('\t');
} }
// \n
'n' if !escape.is_empty() => { 'n' if !escape.is_empty() => {
escape.clear(); escape.clear();
result.push('\n'); result.push('\n');
} }
// \r
'r' if !escape.is_empty() => { 'r' if !escape.is_empty() => {
escape.clear(); escape.clear();
result.push('\r'); result.push('\r');
} }
// \x??, \u????, \U????????
ch @ 'x' | ch @ 'u' | ch @ 'U' if !escape.is_empty() => { ch @ 'x' | ch @ 'u' | ch @ 'U' if !escape.is_empty() => {
let mut seq = escape.clone(); let mut seq = escape.clone();
seq.push(ch); seq.push(ch);
@ -754,15 +825,25 @@ impl<'a> TokenIterator<'a> {
.ok_or_else(|| (LERR::MalformedEscapeSequence(seq), self.pos))?, .ok_or_else(|| (LERR::MalformedEscapeSequence(seq), self.pos))?,
); );
} }
// \{enclosing_char} - escaped
ch if enclosing_char == ch && !escape.is_empty() => result.push(ch), ch if enclosing_char == ch && !escape.is_empty() => result.push(ch),
// Close wrapper
ch if enclosing_char == ch && escape.is_empty() => break, ch if enclosing_char == ch && escape.is_empty() => break,
// Unknown escape sequence
_ if !escape.is_empty() => { _ if !escape.is_empty() => {
return Err((LERR::MalformedEscapeSequence(escape), self.pos)) return Err((LERR::MalformedEscapeSequence(escape), self.pos))
} }
// Cannot have new-lines inside string literals
'\n' => { '\n' => {
self.rewind(); self.rewind();
return Err((LERR::UnterminatedString, self.pos)); return Err((LERR::UnterminatedString, self.pos));
} }
// All other characters
ch => { ch => {
escape.clear(); escape.clear();
result.push(ch); result.push(ch);
@ -773,6 +854,7 @@ impl<'a> TokenIterator<'a> {
Ok(result.iter().collect()) Ok(result.iter().collect())
} }
/// Get the next token.
fn inner_next(&mut self) -> Option<(Token, Position)> { fn inner_next(&mut self) -> Option<(Token, Position)> {
let mut negated = false; let mut negated = false;
@ -782,7 +864,9 @@ impl<'a> TokenIterator<'a> {
let pos = self.pos; let pos = self.pos;
match c { match c {
// \n
'\n' => self.new_line(), '\n' => self.new_line(),
// digit ...
'0'..='9' => { '0'..='9' => {
let mut result = Vec::new(); let mut result = Vec::new();
let mut radix_base: Option<u32> = None; let mut radix_base: Option<u32> = None;
@ -811,6 +895,7 @@ impl<'a> TokenIterator<'a> {
} }
} }
} }
// 0x????, 0o????, 0b????
ch @ 'x' | ch @ 'X' | ch @ 'o' | ch @ 'O' | ch @ 'b' | ch @ 'B' ch @ 'x' | ch @ 'X' | ch @ 'o' | ch @ 'O' | ch @ 'b' | ch @ 'B'
if c == '0' => if c == '0' =>
{ {
@ -860,6 +945,7 @@ impl<'a> TokenIterator<'a> {
result.insert(0, '-'); result.insert(0, '-');
} }
// Parse number
if let Some(radix) = radix_base { if let Some(radix) = radix_base {
let out: String = result.iter().skip(2).filter(|&&c| c != '_').collect(); let out: String = result.iter().skip(2).filter(|&&c| c != '_').collect();
@ -875,6 +961,7 @@ impl<'a> TokenIterator<'a> {
let out: String = result.iter().filter(|&&c| c != '_').collect(); let out: String = result.iter().filter(|&&c| c != '_').collect();
let num = INT::from_str(&out).map(Token::IntegerConstant); let num = INT::from_str(&out).map(Token::IntegerConstant);
// If integer parsing is unnecessary, try float instead
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
let num = num.or_else(|_| FLOAT::from_str(&out).map(Token::FloatConstant)); let num = num.or_else(|_| FLOAT::from_str(&out).map(Token::FloatConstant));
@ -886,6 +973,7 @@ impl<'a> TokenIterator<'a> {
)); ));
} }
} }
// letter ...
'A'..='Z' | 'a'..='z' | '_' => { 'A'..='Z' | 'a'..='z' | '_' => {
let mut result = Vec::new(); let mut result = Vec::new();
result.push(c); result.push(c);
@ -930,13 +1018,15 @@ impl<'a> TokenIterator<'a> {
pos, pos,
)); ));
} }
// " - string literal
'"' => { '"' => {
return match self.parse_string_const('"') { return match self.parse_string_literal('"') {
Ok(out) => Some((Token::StringConst(out), pos)), Ok(out) => Some((Token::StringConst(out), pos)),
Err(e) => Some((Token::LexError(e.0), e.1)), Err(e) => Some((Token::LexError(e.0), e.1)),
} }
} }
'\'' => match self.parse_string_const('\'') { // ' - character literal
'\'' => match self.parse_string_literal('\'') {
Ok(result) => { Ok(result) => {
let mut chars = result.chars(); let mut chars = result.chars();
@ -955,16 +1045,22 @@ impl<'a> TokenIterator<'a> {
} }
Err(e) => return Some((Token::LexError(e.0), e.1)), Err(e) => return Some((Token::LexError(e.0), e.1)),
}, },
// Braces
'{' => return Some((Token::LeftBrace, pos)), '{' => return Some((Token::LeftBrace, pos)),
'}' => return Some((Token::RightBrace, pos)), '}' => return Some((Token::RightBrace, pos)),
// Parentheses
'(' => return Some((Token::LeftParen, pos)), '(' => return Some((Token::LeftParen, pos)),
')' => return Some((Token::RightParen, pos)), ')' => return Some((Token::RightParen, pos)),
// Indexing
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
'[' => return Some((Token::LeftBracket, pos)), '[' => return Some((Token::LeftBracket, pos)),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
']' => return Some((Token::RightBracket, pos)), ']' => return Some((Token::RightBracket, pos)),
// Operators
'+' => { '+' => {
return Some(( return Some((
match self.char_stream.peek() { match self.char_stream.peek() {
@ -1228,6 +1324,7 @@ impl<'a> Iterator for TokenIterator<'a> {
} }
} }
/// Tokenize an input text stream.
pub fn lex(input: &str) -> TokenIterator<'_> { pub fn lex(input: &str) -> TokenIterator<'_> {
TokenIterator { TokenIterator {
last: Token::LexError(LERR::InputError("".into())), last: Token::LexError(LERR::InputError("".into())),
@ -1236,6 +1333,7 @@ pub fn lex(input: &str) -> TokenIterator<'_> {
} }
} }
/// Parse ( expr )
fn parse_paren_expr<'a>( fn parse_paren_expr<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
begin: Position, begin: Position,
@ -1252,13 +1350,16 @@ fn parse_paren_expr<'a>(
let expr = parse_expr(input)?; let expr = parse_expr(input)?;
match input.next() { match input.next() {
// ( xxx )
Some((Token::RightParen, _)) => Ok(expr), Some((Token::RightParen, _)) => Ok(expr),
// ( xxx ???
Some((_, pos)) => { Some((_, pos)) => {
return Err(ParseError::new( return Err(ParseError::new(
PERR::MissingRightParen("a matching ( in the expression".into()), PERR::MissingRightParen("a matching ( in the expression".into()),
pos, pos,
)) ))
} }
// ( xxx
None => Err(ParseError::new( None => Err(ParseError::new(
PERR::MissingRightParen("a matching ( in the expression".into()), PERR::MissingRightParen("a matching ( in the expression".into()),
Position::eof(), Position::eof(),
@ -1266,6 +1367,7 @@ fn parse_paren_expr<'a>(
} }
} }
/// Parse a function call.
fn parse_call_expr<'a>( fn parse_call_expr<'a>(
id: String, id: String,
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
@ -1273,6 +1375,7 @@ fn parse_call_expr<'a>(
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
let mut args_expr_list = Vec::new(); let mut args_expr_list = Vec::new();
// id()
if let (Token::RightParen, _) = input.peek().ok_or_else(|| { if let (Token::RightParen, _) = input.peek().ok_or_else(|| {
ParseError::new( ParseError::new(
PERR::MissingRightParen(format!( PERR::MissingRightParen(format!(
@ -1318,6 +1421,7 @@ fn parse_call_expr<'a>(
} }
} }
/// Parse an indexing expression.s
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
fn parse_index_expr<'a>( fn parse_index_expr<'a>(
lhs: Box<Expr>, lhs: Box<Expr>,
@ -1328,6 +1432,7 @@ fn parse_index_expr<'a>(
// Check type of indexing - must be integer // Check type of indexing - must be integer
match &idx_expr { match &idx_expr {
// lhs[int]
Expr::IntegerConstant(i, pos) if *i < 0 => { Expr::IntegerConstant(i, pos) if *i < 0 => {
return Err(ParseError::new( return Err(ParseError::new(
PERR::MalformedIndexExpr(format!( PERR::MalformedIndexExpr(format!(
@ -1337,6 +1442,7 @@ fn parse_index_expr<'a>(
*pos, *pos,
)) ))
} }
// lhs[float]
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Expr::FloatConstant(_, pos) => { Expr::FloatConstant(_, pos) => {
return Err(ParseError::new( return Err(ParseError::new(
@ -1344,6 +1450,7 @@ fn parse_index_expr<'a>(
*pos, *pos,
)) ))
} }
// lhs[char]
Expr::CharConstant(_, pos) => { Expr::CharConstant(_, pos) => {
return Err(ParseError::new( return Err(ParseError::new(
PERR::MalformedIndexExpr( PERR::MalformedIndexExpr(
@ -1352,18 +1459,21 @@ fn parse_index_expr<'a>(
*pos, *pos,
)) ))
} }
// lhs[string]
Expr::StringConstant(_, pos) => { Expr::StringConstant(_, pos) => {
return Err(ParseError::new( return Err(ParseError::new(
PERR::MalformedIndexExpr("Array access expects integer index, not a string".into()), PERR::MalformedIndexExpr("Array access expects integer index, not a string".into()),
*pos, *pos,
)) ))
} }
// lhs[??? = ??? ], lhs[()]
Expr::Assignment(_, _, pos) | Expr::Unit(pos) => { Expr::Assignment(_, _, pos) | Expr::Unit(pos) => {
return Err(ParseError::new( return Err(ParseError::new(
PERR::MalformedIndexExpr("Array access expects integer index, not ()".into()), PERR::MalformedIndexExpr("Array access expects integer index, not ()".into()),
*pos, *pos,
)) ))
} }
// lhs[??? && ???], lhs[??? || ???]
Expr::And(lhs, _) | Expr::Or(lhs, _) => { Expr::And(lhs, _) | Expr::Or(lhs, _) => {
return Err(ParseError::new( return Err(ParseError::new(
PERR::MalformedIndexExpr( PERR::MalformedIndexExpr(
@ -1372,6 +1482,7 @@ fn parse_index_expr<'a>(
lhs.position(), lhs.position(),
)) ))
} }
// lhs[true], lhs[false]
Expr::True(pos) | Expr::False(pos) => { Expr::True(pos) | Expr::False(pos) => {
return Err(ParseError::new( return Err(ParseError::new(
PERR::MalformedIndexExpr( PERR::MalformedIndexExpr(
@ -1380,6 +1491,7 @@ fn parse_index_expr<'a>(
*pos, *pos,
)) ))
} }
// All other expressions
_ => (), _ => (),
} }
@ -1403,29 +1515,35 @@ fn parse_index_expr<'a>(
} }
} }
/// Parse an expression that begins with an identifier.
fn parse_ident_expr<'a>( fn parse_ident_expr<'a>(
id: String, id: String,
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
begin: Position, begin: Position,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
match input.peek() { match input.peek() {
// id(...) - function call
Some((Token::LeftParen, _)) => { Some((Token::LeftParen, _)) => {
input.next(); input.next();
parse_call_expr(id, input, begin) parse_call_expr(id, input, begin)
} }
// id[...] - indexing
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Some((Token::LeftBracket, pos)) => { Some((Token::LeftBracket, pos)) => {
let pos = *pos; let pos = *pos;
input.next(); input.next();
parse_index_expr(Box::new(Expr::Variable(id, begin)), input, pos) parse_index_expr(Box::new(Expr::Variable(id, begin)), input, pos)
} }
// id - variable
Some(_) => Ok(Expr::Variable(id, begin)), Some(_) => Ok(Expr::Variable(id, begin)),
// EOF
None => Ok(Expr::Variable(id, Position::eof())), None => Ok(Expr::Variable(id, Position::eof())),
} }
} }
/// Parse an array literal.
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
fn parse_array_expr<'a>( fn parse_array_literal<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
begin: Position, begin: Position,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
@ -1472,8 +1590,9 @@ fn parse_array_expr<'a>(
} }
} }
/// Parse a primary expression.
fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> { fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> {
// Block statement as expression // { - block statement as expression
match input.peek() { match input.peek() {
Some((Token::LeftBrace, pos)) => { Some((Token::LeftBrace, pos)) => {
let pos = *pos; let pos = *pos;
@ -1510,7 +1629,7 @@ fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pa
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
(Token::LeftBracket, pos) => { (Token::LeftBracket, pos) => {
can_be_indexed = true; can_be_indexed = true;
parse_array_expr(input, pos) parse_array_literal(input, pos)
} }
(Token::True, pos) => Ok(Expr::True(pos)), (Token::True, pos) => Ok(Expr::True(pos)),
(Token::False, pos) => Ok(Expr::False(pos)), (Token::False, pos) => Ok(Expr::False(pos)),
@ -1534,11 +1653,13 @@ fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pa
Ok(root_expr) Ok(root_expr)
} }
/// Parse a potential unary operator.
fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> { fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> {
match input match input
.peek() .peek()
.ok_or_else(|| ParseError::new(PERR::InputPastEndOfFile, Position::eof()))? .ok_or_else(|| ParseError::new(PERR::InputPastEndOfFile, Position::eof()))?
{ {
// -expr
(Token::UnaryMinus, pos) => { (Token::UnaryMinus, pos) => {
let pos = *pos; let pos = *pos;
@ -1571,10 +1692,12 @@ fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pars
expr => Ok(Expr::FunctionCall("-".into(), vec![expr], None, pos)), expr => Ok(Expr::FunctionCall("-".into(), vec![expr], None, pos)),
} }
} }
// +expr
(Token::UnaryPlus, _) => { (Token::UnaryPlus, _) => {
input.next(); input.next();
parse_unary(input) parse_unary(input)
} }
// !expr
(Token::Bang, pos) => { (Token::Bang, pos) => {
let pos = *pos; let pos = *pos;
@ -1587,34 +1710,41 @@ fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pars
pos, pos,
)) ))
} }
// All other tokens
_ => parse_primary(input), _ => parse_primary(input),
} }
} }
/// Parse an assignment.
fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseError> { fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseError> {
// Is the LHS in a valid format?
fn valid_assignment_chain(expr: &Expr, is_top: bool) -> Option<ParseError> { fn valid_assignment_chain(expr: &Expr, is_top: bool) -> Option<ParseError> {
match expr { match expr {
// var
Expr::Variable(_, _) => { Expr::Variable(_, _) => {
assert!(is_top, "property expected but gets variable"); assert!(is_top, "property expected but gets variable");
None None
} }
// property
Expr::Property(_, _) => { Expr::Property(_, _) => {
assert!(!is_top, "variable expected but gets property"); assert!(!is_top, "variable expected but gets property");
None None
} }
// var[...]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(idx_lhs, _, _) if matches!(idx_lhs.as_ref(), &Expr::Variable(_, _)) => { Expr::Index(idx_lhs, _, _) if matches!(idx_lhs.as_ref(), &Expr::Variable(_, _)) => {
assert!(is_top, "property expected but gets variable"); assert!(is_top, "property expected but gets variable");
None None
} }
// property[...]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(idx_lhs, _, _) if matches!(idx_lhs.as_ref(), &Expr::Property(_, _)) => { Expr::Index(idx_lhs, _, _) if matches!(idx_lhs.as_ref(), &Expr::Property(_, _)) => {
assert!(!is_top, "variable expected but gets property"); assert!(!is_top, "variable expected but gets property");
None None
} }
// idx_lhs[...]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(idx_lhs, _, pos) => Some(ParseError::new( Expr::Index(idx_lhs, _, pos) => Some(ParseError::new(
match idx_lhs.as_ref() { match idx_lhs.as_ref() {
@ -1624,22 +1754,27 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseEr
*pos, *pos,
)), )),
// dot_lhs.dot_rhs
Expr::Dot(dot_lhs, dot_rhs, _) => match dot_lhs.as_ref() { Expr::Dot(dot_lhs, dot_rhs, _) => match dot_lhs.as_ref() {
// var.dot_rhs
Expr::Variable(_, _) if is_top => valid_assignment_chain(dot_rhs, false), 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), Expr::Property(_, _) if !is_top => valid_assignment_chain(dot_rhs, false),
// var[...]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(idx_lhs, _, _) Expr::Index(idx_lhs, _, _)
if matches!(idx_lhs.as_ref(), &Expr::Variable(_, _)) && is_top => if matches!(idx_lhs.as_ref(), &Expr::Variable(_, _)) && is_top =>
{ {
valid_assignment_chain(dot_rhs, false) valid_assignment_chain(dot_rhs, false)
} }
// property[...]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(idx_lhs, _, _) Expr::Index(idx_lhs, _, _)
if matches!(idx_lhs.as_ref(), &Expr::Property(_, _)) && !is_top => if matches!(idx_lhs.as_ref(), &Expr::Property(_, _)) && !is_top =>
{ {
valid_assignment_chain(dot_rhs, false) valid_assignment_chain(dot_rhs, false)
} }
// idx_lhs[...]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(idx_lhs, _, _) => Some(ParseError::new( Expr::Index(idx_lhs, _, _) => Some(ParseError::new(
ParseErrorType::AssignmentToCopy, ParseErrorType::AssignmentToCopy,
@ -1662,6 +1797,7 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseEr
} }
} }
/// Parse an operator-assignment expression.
fn parse_op_assignment( fn parse_op_assignment(
function: &str, function: &str,
lhs: Expr, lhs: Expr,
@ -1677,6 +1813,7 @@ fn parse_op_assignment(
) )
} }
/// Parse a binary expression.
fn parse_binary_op<'a>( fn parse_binary_op<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
parent_precedence: u8, parent_precedence: u8,
@ -1830,11 +1967,13 @@ fn parse_binary_op<'a>(
} }
} }
/// Parse an expression.
fn parse_expr<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> { fn parse_expr<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> {
let lhs = parse_unary(input)?; let lhs = parse_unary(input)?;
parse_binary_op(input, 1, lhs) parse_binary_op(input, 1, lhs)
} }
/// Parse an if statement.
fn parse_if<'a>( fn parse_if<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
breakable: bool, breakable: bool,
@ -1859,6 +1998,7 @@ fn parse_if<'a>(
Ok(Stmt::IfElse(Box::new(guard), Box::new(if_body), else_body)) Ok(Stmt::IfElse(Box::new(guard), Box::new(if_body), else_body))
} }
/// Parse a while loop.
fn parse_while<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> { fn parse_while<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
input.next(); input.next();
@ -1868,6 +2008,7 @@ fn parse_while<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Pars
Ok(Stmt::While(Box::new(guard), Box::new(body))) Ok(Stmt::While(Box::new(guard), Box::new(body)))
} }
/// Parse a loop statement.
fn parse_loop<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> { fn parse_loop<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
input.next(); input.next();
@ -1876,6 +2017,7 @@ fn parse_loop<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Parse
Ok(Stmt::Loop(Box::new(body))) Ok(Stmt::Loop(Box::new(body)))
} }
/// Parse a for loop.
fn parse_for<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> { fn parse_for<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
input.next(); input.next();
@ -1904,7 +2046,8 @@ fn parse_for<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseE
Ok(Stmt::For(name, Box::new(expr), Box::new(body))) Ok(Stmt::For(name, Box::new(expr), Box::new(body)))
} }
fn parse_var<'a>( /// Parse a variable definition statement.
fn parse_let<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
var_type: VariableType, var_type: VariableType,
) -> Result<Stmt, ParseError> { ) -> Result<Stmt, ParseError> {
@ -1945,6 +2088,7 @@ fn parse_var<'a>(
} }
} }
/// Parse a statement block.
fn parse_block<'a>( fn parse_block<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
breakable: bool, breakable: bool,
@ -2005,10 +2149,12 @@ fn parse_block<'a>(
} }
} }
/// Parse an expression as a statement.
fn parse_expr_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> { fn parse_expr_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
Ok(Stmt::Expr(Box::new(parse_expr(input)?))) Ok(Stmt::Expr(Box::new(parse_expr(input)?)))
} }
/// Parse a single statement.
fn parse_stmt<'a>( fn parse_stmt<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
breakable: bool, breakable: bool,
@ -2040,14 +2186,14 @@ fn parse_stmt<'a>(
input.next(); input.next();
match input.peek() { match input.peek() {
// return/throw at EOF // `return`/`throw` at EOF
None => Ok(Stmt::ReturnWithVal(None, return_type, Position::eof())), None => Ok(Stmt::ReturnWithVal(None, return_type, Position::eof())),
// return; or throw; // `return;` or `throw;`
Some((Token::SemiColon, pos)) => { Some((Token::SemiColon, pos)) => {
let pos = *pos; let pos = *pos;
Ok(Stmt::ReturnWithVal(None, return_type, pos)) Ok(Stmt::ReturnWithVal(None, return_type, pos))
} }
// return or throw with expression // `return` or `throw` with expression
Some((_, pos)) => { Some((_, pos)) => {
let pos = *pos; let pos = *pos;
Ok(Stmt::ReturnWithVal( Ok(Stmt::ReturnWithVal(
@ -2059,12 +2205,13 @@ fn parse_stmt<'a>(
} }
} }
(Token::LeftBrace, _) => parse_block(input, breakable), (Token::LeftBrace, _) => parse_block(input, breakable),
(Token::Let, _) => parse_var(input, VariableType::Normal), (Token::Let, _) => parse_let(input, VariableType::Normal),
(Token::Const, _) => parse_var(input, VariableType::Constant), (Token::Const, _) => parse_let(input, VariableType::Constant),
_ => parse_expr_stmt(input), _ => parse_expr_stmt(input),
} }
} }
/// Parse a function definition.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseError> { fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseError> {
let pos = input let pos = input
@ -2166,6 +2313,7 @@ fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseE
}) })
} }
/// Parse the global level statements.
fn parse_global_level<'a, 'e>( fn parse_global_level<'a, 'e>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
) -> Result<(Vec<Stmt>, Vec<FnDef>), ParseError> { ) -> Result<(Vec<Stmt>, Vec<FnDef>), ParseError> {
@ -2214,6 +2362,7 @@ fn parse_global_level<'a, 'e>(
Ok((statements, functions)) Ok((statements, functions))
} }
/// Run the parser on an input stream, returning an AST.
pub fn parse<'a, 'e>( pub fn parse<'a, 'e>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
engine: &Engine<'e>, engine: &Engine<'e>,
@ -2229,6 +2378,9 @@ pub fn parse<'a, 'e>(
) )
} }
/// Map a `Dynamic` value to an expression.
///
/// Returns Some(expression) if conversion is successful. Otherwise None.
pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> (Option<Expr>, Dynamic) { pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> (Option<Expr>, Dynamic) {
if value.is::<INT>() { if value.is::<INT>() {
let value2 = value.clone(); let value2 = value.clone();