Add code comments.

This commit is contained in:
Stephen Chung 2020-03-18 10:36:50 +08:00
parent dca2a927b5
commit ef8d428f42
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.
use std::{
any::{type_name, TypeId},
any::{type_name, Any as StdAny, TypeId},
fmt,
};
@ -12,7 +12,7 @@ pub type Variant = dyn Any;
pub type Dynamic = Box<Variant>;
/// A trait covering any type.
pub trait Any: std::any::Any {
pub trait Any: StdAny {
/// Get the `TypeId` of this type.
fn type_id(&self) -> TypeId;
@ -22,12 +22,12 @@ pub trait Any: std::any::Any {
/// Convert into `Dynamic`.
fn into_dynamic(&self) -> Dynamic;
/// This type may only be implemented by `rhai`.
/// This trait may only be implemented by `rhai`.
#[doc(hidden)]
fn _closed(&self) -> _Private;
}
impl<T: Clone + std::any::Any + ?Sized> Any for T {
impl<T: Clone + StdAny + ?Sized> Any for T {
fn type_id(&self) -> TypeId {
TypeId::of::<T>()
}
@ -90,7 +90,7 @@ pub trait AnyExt: Sized {
/// Get a copy of a `Dynamic` value as a specific type.
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)]
fn _closed(&self) -> _Private;
}

View File

@ -5,7 +5,7 @@ use crate::any::Any;
#[cfg(not(feature = "no_index"))]
use crate::engine::Array;
use crate::engine::Engine;
use crate::fn_register::{RegisterFn, RegisterResultFn};
use crate::fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
use crate::parser::{Position, INT};
use crate::result::EvalAltResult;
@ -52,6 +52,7 @@ macro_rules! reg_op_result1 {
impl Engine<'_> {
/// Register the core built-in library.
pub(crate) fn register_core_lib(&mut self) {
/// Checked add
#[cfg(not(feature = "unchecked"))]
fn add<T: Display + CheckedAdd>(x: T, y: T) -> Result<T, EvalAltResult> {
x.checked_add(&y).ok_or_else(|| {
@ -61,6 +62,7 @@ impl Engine<'_> {
)
})
}
/// Checked subtract
#[cfg(not(feature = "unchecked"))]
fn sub<T: Display + CheckedSub>(x: T, y: T) -> Result<T, EvalAltResult> {
x.checked_sub(&y).ok_or_else(|| {
@ -70,6 +72,7 @@ impl Engine<'_> {
)
})
}
/// Checked multiply
#[cfg(not(feature = "unchecked"))]
fn mul<T: Display + CheckedMul>(x: T, y: T) -> Result<T, EvalAltResult> {
x.checked_mul(&y).ok_or_else(|| {
@ -79,11 +82,13 @@ impl Engine<'_> {
)
})
}
/// Checked divide
#[cfg(not(feature = "unchecked"))]
fn div<T>(x: T, y: T) -> Result<T, EvalAltResult>
where
T: Display + CheckedDiv + PartialEq + Zero,
{
// Detect division by zero
if y == T::zero() {
return Err(EvalAltResult::ErrorArithmetic(
format!("Division by zero: {} / {}", x, y),
@ -98,6 +103,7 @@ impl Engine<'_> {
)
})
}
/// Checked negative - e.g. -(i32::MIN) will overflow i32::MAX
#[cfg(not(feature = "unchecked"))]
fn neg<T: Display + CheckedNeg>(x: T) -> Result<T, EvalAltResult> {
x.checked_neg().ok_or_else(|| {
@ -107,6 +113,7 @@ impl Engine<'_> {
)
})
}
/// Checked absolute
#[cfg(not(feature = "unchecked"))]
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
@ -122,26 +129,32 @@ impl Engine<'_> {
})
}
}
/// Unchecked add - may panic on overflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn add_u<T: Add>(x: T, y: T) -> <T as Add>::Output {
x + y
}
/// Unchecked subtract - may panic on underflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn sub_u<T: Sub>(x: T, y: T) -> <T as Sub>::Output {
x - y
}
/// Unchecked multiply - may panic on overflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn mul_u<T: Mul>(x: T, y: T) -> <T as Mul>::Output {
x * y
}
/// Unchecked divide - may panic when dividing by zero
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn div_u<T: Div>(x: T, y: T) -> <T as Div>::Output {
x / y
}
/// Unchecked negative - may panic on overflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn neg_u<T: Neg>(x: T) -> <T as Neg>::Output {
-x
}
/// Unchecked absolute - may panic on overflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn abs_u<T>(x: T) -> <T as Neg>::Output
where
@ -154,6 +167,9 @@ impl Engine<'_> {
x.into()
}
}
// Comparison operators
fn lt<T: PartialOrd>(x: T, y: T) -> bool {
x < y
}
@ -172,6 +188,9 @@ impl Engine<'_> {
fn ne<T: PartialEq>(x: T, y: T) -> bool {
x != y
}
// Logic operators
fn and(x: bool, y: bool) -> bool {
x && y
}
@ -181,6 +200,9 @@ impl Engine<'_> {
fn not(x: bool) -> bool {
!x
}
// Bit operators
fn binary_and<T: BitAnd>(x: T, y: T) -> <T as BitAnd>::Output {
x & y
}
@ -190,8 +212,11 @@ impl Engine<'_> {
fn binary_xor<T: BitXor>(x: T, y: T) -> <T as BitXor>::Output {
x ^ y
}
/// Checked left-shift
#[cfg(not(feature = "unchecked"))]
fn shl<T: Display + CheckedShl>(x: T, y: INT) -> Result<T, EvalAltResult> {
// Cannot shift by a negative number of bits
if y < 0 {
return Err(EvalAltResult::ErrorArithmetic(
format!("Left-shift by a negative number: {} << {}", x, y),
@ -201,13 +226,15 @@ impl Engine<'_> {
CheckedShl::checked_shl(&x, y as u32).ok_or_else(|| {
EvalAltResult::ErrorArithmetic(
format!("Left-shift overflow: {} << {}", x, y),
format!("Left-shift by too many bits: {} << {}", x, y),
Position::none(),
)
})
}
/// Checked right-shift
#[cfg(not(feature = "unchecked"))]
fn shr<T: Display + CheckedShr>(x: T, y: INT) -> Result<T, EvalAltResult> {
// Cannot shift by a negative number of bits
if y < 0 {
return Err(EvalAltResult::ErrorArithmetic(
format!("Right-shift by a negative number: {} >> {}", x, y),
@ -217,44 +244,49 @@ impl Engine<'_> {
CheckedShr::checked_shr(&x, y as u32).ok_or_else(|| {
EvalAltResult::ErrorArithmetic(
format!("Right-shift overflow: {} % {}", x, y),
format!("Right-shift by too many bits: {} % {}", x, y),
Position::none(),
)
})
}
/// Unchecked left-shift - may panic if shifting by a negative number of bits
#[cfg(feature = "unchecked")]
fn shl_u<T: Shl<T>>(x: T, y: T) -> <T as Shl<T>>::Output {
x.shl(y)
}
/// Unchecked right-shift - may panic if shifting by a negative number of bits
#[cfg(feature = "unchecked")]
fn shr_u<T: Shr<T>>(x: T, y: T) -> <T as Shr<T>>::Output {
x.shr(y)
}
/// Checked modulo
#[cfg(not(feature = "unchecked"))]
fn modulo<T: Display + CheckedRem>(x: T, y: T) -> Result<T, EvalAltResult> {
x.checked_rem(&y).ok_or_else(|| {
EvalAltResult::ErrorArithmetic(
format!("Modulo division overflow: {} % {}", x, y),
format!("Modulo division by zero or overflow: {} % {}", x, y),
Position::none(),
)
})
}
/// Unchecked modulo - may panic if dividing by zero
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn modulo_u<T: Rem>(x: T, y: T) -> <T as Rem>::Output {
x % y
}
/// Checked power
#[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"))]
{
if y > (u32::MAX as INT) {
Err(EvalAltResult::ErrorArithmetic(
format!("Power overflow: {} ~ {}", x, y),
format!("Integer raised to too large an index: {} ~ {}", x, y),
Position::none(),
))
} else if y < 0 {
Err(EvalAltResult::ErrorArithmetic(
format!("Power underflow: {} ~ {}", x, y),
format!("Integer raised to a negative index: {} ~ {}", x, y),
Position::none(),
))
} else {
@ -271,7 +303,7 @@ impl Engine<'_> {
{
if y < 0 {
Err(EvalAltResult::ErrorArithmetic(
format!("Power underflow: {} ~ {}", x, y),
format!("Integer raised to a negative index: {} ~ {}", x, y),
Position::none(),
))
} else {
@ -284,29 +316,34 @@ impl Engine<'_> {
}
}
}
/// Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX)
#[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)
}
/// Floating-point power - always well-defined
#[cfg(not(feature = "no_float"))]
fn pow_f_f(x: FLOAT, y: FLOAT) -> FLOAT {
x.powf(y)
}
/// Checked power
#[cfg(not(feature = "unchecked"))]
#[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) {
return Err(EvalAltResult::ErrorArithmetic(
format!("Power overflow: {} ~ {}", x, y),
format!("Number raised to too large an index: {} ~ {}", x, y),
Position::none(),
));
}
Ok(x.powi(y as i32))
}
/// Unchecked power - may be incorrect if the power index is too high (> i32::MAX)
#[cfg(feature = "unchecked")]
#[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)
}
@ -390,8 +427,10 @@ impl Engine<'_> {
}
}
// `&&` and `||` are treated specially as they short-circuit
//reg_op!(self, "||", or, bool);
//reg_op!(self, "&&", and, bool);
reg_op!(self, "|", or, bool);
reg_op!(self, "&", and, bool);
@ -445,18 +484,18 @@ impl Engine<'_> {
#[cfg(not(feature = "unchecked"))]
{
self.register_result_fn("~", pow_i_i_u);
self.register_result_fn("~", pow_i_i);
#[cfg(not(feature = "no_float"))]
self.register_result_fn("~", pow_f_i_u);
self.register_result_fn("~", pow_f_i);
}
#[cfg(feature = "unchecked")]
{
self.register_fn("~", pow_i_i);
self.register_fn("~", pow_i_i_u);
#[cfg(not(feature = "no_float"))]
self.register_fn("~", pow_f_i);
self.register_fn("~", pow_f_i_u);
}
{
@ -625,9 +664,6 @@ macro_rules! reg_fn2y {
impl Engine<'_> {
#[cfg(not(feature = "no_stdlib"))]
pub(crate) fn register_stdlib(&mut self) {
#[cfg(not(feature = "no_index"))]
use crate::fn_register::RegisterDynamicFn;
#[cfg(not(feature = "no_float"))]
{
// Advanced math functions

View File

@ -9,50 +9,54 @@ use crate::scope::{Scope, ScopeEntry, VariableType};
use std::sync::Arc;
/// Level of optimization performed
/// Level of optimization performed.
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
pub enum OptimizationLevel {
/// No optimization performed
/// No optimization performed.
None,
/// Only perform simple optimizations without evaluating functions
/// Only perform simple optimizations without evaluating functions.
Simple,
/// 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,
}
/// Mutable state throughout an optimization pass.
struct State<'a> {
/// Has the AST been changed during this pass?
changed: bool,
/// Collection of constants to use for eager function evaluations.
constants: Vec<(String, Expr)>,
engine: Option<&'a Engine<'a>>,
/// An `Engine` instance for eager function evaluation.
engine: &'a Engine<'a>,
}
impl State<'_> {
pub fn new() -> Self {
State {
changed: false,
constants: vec![],
engine: None,
}
}
/// Reset the state from dirty to clean.
pub fn reset(&mut self) {
self.changed = false;
}
/// Set the AST state to be dirty (i.e. changed).
pub fn set_dirty(&mut self) {
self.changed = true;
}
/// Is the AST dirty (i.e. changed)?
pub fn is_dirty(&self) -> bool {
self.changed
}
/// Does a constant exist?
pub fn contains_constant(&self, name: &str) -> bool {
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) {
self.constants.truncate(len)
}
/// Add a new constant to the list.
pub fn push_constant(&mut self, name: &str, value: Expr) {
self.constants.push((name.to_string(), value))
}
/// Look up a constant from the list.
pub fn find_constant(&self, name: &str) -> Option<&Expr> {
for (n, expr) in self.constants.iter().rev() {
if n == name {
@ -64,60 +68,68 @@ impl State<'_> {
}
}
/// Optimize a statement.
fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -> 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();
let pos = expr.position();
let expr = optimize_expr(*expr, state);
if matches!(expr, Expr::False(_) | Expr::True(_)) {
Stmt::Noop(stmt1.position())
if preserve_result {
// -> { expr, Noop }
Stmt::Block(vec![Stmt::Expr(Box::new(expr)), *if_block], pos)
} else {
let stmt = Stmt::Expr(Box::new(expr));
if preserve_result {
Stmt::Block(vec![stmt, *stmt1], pos)
} else {
stmt
}
// -> expr
Stmt::Expr(Box::new(expr))
}
}
Stmt::IfElse(expr, stmt1, None) => match *expr {
// if expr { if_block }
Stmt::IfElse(expr, if_block, None) => match *expr {
// if false { if_block } -> Noop
Expr::False(pos) => {
state.set_dirty();
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(
Box::new(optimize_expr(expr, state)),
Box::new(optimize_stmt(*stmt1, state, true)),
Box::new(optimize_stmt(*if_block, state, true)),
None,
),
},
Stmt::IfElse(expr, stmt1, Some(stmt2)) => match *expr {
Expr::False(_) => optimize_stmt(*stmt2, state, true),
Expr::True(_) => optimize_stmt(*stmt1, state, true),
// if expr { if_block } else { else_block }
Stmt::IfElse(expr, if_block, Some(else_block)) => match *expr {
// if false { if_block } else { else_block } -> else_block
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(
Box::new(optimize_expr(expr, state)),
Box::new(optimize_stmt(*stmt1, state, true)),
match optimize_stmt(*stmt2, state, true) {
stmt if stmt.is_noop() => None,
Box::new(optimize_stmt(*if_block, state, true)),
match optimize_stmt(*else_block, state, true) {
stmt if matches!(stmt, Stmt::Noop(_)) => None, // Noop -> no else block
stmt => Some(Box::new(stmt)),
},
),
},
Stmt::While(expr, stmt) => match *expr {
// while expr { block }
Stmt::While(expr, block) => match *expr {
// while false { block } -> Noop
Expr::False(pos) => {
state.set_dirty();
Stmt::Noop(pos)
}
Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*stmt, state, false))),
expr => match optimize_stmt(*stmt, state, false) {
// while true { block } -> loop { block }
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) => {
// Only a single break statement - turn into running the guard expression once
state.set_dirty();
@ -127,48 +139,50 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
}
Stmt::Block(statements, pos)
}
// while expr { block }
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) => {
// Only a single break statement
state.set_dirty();
Stmt::Noop(pos)
}
// loop { block }
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,
Box::new(optimize_expr(*expr, state)),
Box::new(match optimize_stmt(*stmt, state, false) {
Stmt::Break(pos) => {
// Only a single break statement
state.set_dirty();
Stmt::Noop(pos)
}
stmt => stmt,
}),
Box::new(optimize_stmt(*block, state, false)),
),
// let id = expr;
Stmt::Let(id, Some(expr), pos) => {
Stmt::Let(id, Some(Box::new(optimize_expr(*expr, state))), pos)
}
// let id;
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) => {
let orig_len = statements.len();
let orig_constants_len = state.constants.len();
let mut result: Vec<_> = statements
.into_iter() // For each statement
// Optimize each statement in the block
let mut result: Vec<_> = block
.into_iter()
.map(|stmt| {
if let Stmt::Const(name, value, pos) = stmt {
// Add constant into the state
state.push_constant(&name, *value);
state.set_dirty();
Stmt::Noop(pos) // No need to keep constants
} else {
optimize_stmt(stmt, state, preserve_result) // Optimize the statement
// Optimize the statement
optimize_stmt(stmt, state, preserve_result)
}
})
.collect();
@ -203,11 +217,12 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
result.push(Stmt::Noop(pos))
}
// Optimize all the statements again
result = result
.into_iter()
.rev()
.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()
.collect();
}
@ -230,10 +245,12 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
true
});
// Change detection
if orig_len != result.len() {
state.set_dirty();
}
// Pop the stack and remove all the local constants
state.restore_constants(orig_constants_len);
match result[..] {
@ -250,44 +267,54 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
_ => Stmt::Block(result, pos),
}
}
// expr;
Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, state))),
// return expr;
Stmt::ReturnWithVal(Some(expr), is_return, pos) => {
Stmt::ReturnWithVal(Some(Box::new(optimize_expr(*expr, state))), is_return, pos)
}
// All other statements - skip
stmt => stmt,
}
}
/// Optimize an expression.
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];
match expr {
// ( stmt )
Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, state, true) {
// ( Noop ) -> ()
Stmt::Noop(_) => {
state.set_dirty();
Expr::Unit(pos)
}
// ( expr ) -> expr
Stmt::Expr(expr) => {
state.set_dirty();
*expr
}
// ( stmt )
stmt => Expr::Stmt(Box::new(stmt), pos),
},
Expr::Assignment(id1, expr1, pos1) => match *expr1 {
Expr::Assignment(id2, expr2, pos2) => match (*id1, *id2) {
(Expr::Variable(var1, _), Expr::Variable(var2, _)) if var1 == var2 => {
// id = expr
Expr::Assignment(id, expr, pos) => match *expr {
//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
state.set_dirty();
Expr::Assignment(
Box::new(Expr::Variable(var1, pos1)),
Box::new(Expr::Variable(var, pos)),
Box::new(optimize_expr(*expr2, state)),
pos1,
pos,
)
}
// id1 = id2 = expr2
(id1, id2) => Expr::Assignment(
Box::new(id1),
Box::new(Expr::Assignment(
@ -295,19 +322,23 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
Box::new(optimize_expr(*expr2, state)),
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(
Box::new(optimize_expr(*lhs, state)),
Box::new(optimize_expr(*rhs, state)),
pos,
),
// lhs[rhs]
#[cfg(not(feature = "no_index"))]
Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) {
// array[int]
(Expr::Array(mut items, _), Expr::IntegerConstant(i, _))
if i >= 0 && (i as usize) < items.len() && items.iter().all(|x| x.is_pure()) =>
{
@ -316,6 +347,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
state.set_dirty();
items.remove(i as usize)
}
// string[int]
(Expr::StringConstant(s, pos), Expr::IntegerConstant(i, _))
if i >= 0 && (i as usize) < s.chars().count() =>
{
@ -323,14 +355,14 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
state.set_dirty();
Expr::CharConstant(s.chars().nth(i as usize).expect("should get char"), pos)
}
// lhs[rhs]
(lhs, rhs) => Expr::Index(
Box::new(optimize_expr(lhs, state)),
Box::new(optimize_expr(rhs, state)),
pos,
),
},
// [ items .. ]
#[cfg(not(feature = "no_index"))]
Expr::Array(items, pos) => {
let orig_len = items.len();
@ -346,38 +378,47 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
Expr::Array(items, pos)
}
// lhs && rhs
Expr::And(lhs, rhs) => match (*lhs, *rhs) {
// true && rhs -> rhs
(Expr::True(_), rhs) => {
state.set_dirty();
rhs
}
// false && rhs -> false
(Expr::False(pos), _) => {
state.set_dirty();
Expr::False(pos)
}
// lhs && true -> lhs
(lhs, Expr::True(_)) => {
state.set_dirty();
lhs
optimize_expr(lhs, state)
}
// lhs && rhs
(lhs, rhs) => Expr::And(
Box::new(optimize_expr(lhs, state)),
Box::new(optimize_expr(rhs, state)),
),
},
// lhs || rhs
Expr::Or(lhs, rhs) => match (*lhs, *rhs) {
// false || rhs -> rhs
(Expr::False(_), rhs) => {
state.set_dirty();
rhs
}
// true || rhs -> true
(Expr::True(pos), _) => {
state.set_dirty();
Expr::True(pos)
}
// lhs || false
(lhs, Expr::False(_)) => {
state.set_dirty();
lhs
optimize_expr(lhs, state)
}
// lhs || rhs
(lhs, rhs) => Expr::Or(
Box::new(optimize_expr(lhs, state)),
Box::new(optimize_expr(rhs, state)),
@ -388,24 +429,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),
// Actually call function to optimize it
// Eagerly call functions
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
=> {
let engine = state.engine.expect("engine should be Some");
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();
// Save the typename of the first argument if it is `type_of()`
// 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 {
engine.map_type_name(call_args[0].type_name())
state.engine.map_type_name(call_args[0].type_name())
} 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(|| {
if !arg_for_type_of.is_empty() {
// Handle `type_of()`
@ -421,10 +461,10 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
})
).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.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos),
// constant-name
Expr::Variable(ref name, _) if state.contains_constant(name) => {
state.set_dirty();
@ -434,28 +474,25 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
.expect("should find constant in scope!")
.clone()
}
// All other expressions - skip
expr => expr,
}
}
pub(crate) fn optimize<'a>(
statements: Vec<Stmt>,
engine: Option<&Engine<'a>>,
scope: &Scope,
) -> Vec<Stmt> {
pub(crate) fn optimize<'a>(statements: Vec<Stmt>, engine: &Engine<'a>, scope: &Scope) -> Vec<Stmt> {
// If optimization level is None then skip optimizing
if engine
.map(|eng| eng.optimization_level == OptimizationLevel::None)
.unwrap_or(false)
{
if engine.optimization_level == OptimizationLevel::None {
return statements;
}
// Set up the state
let mut state = State::new();
state.engine = engine;
let mut state = State {
changed: false,
constants: vec![],
engine,
};
// Add constants from the scope into the state
scope
.iter()
.filter(|ScopeEntry { var_type, expr, .. }| {
@ -472,9 +509,9 @@ pub(crate) fn optimize<'a>(
let orig_constants_len = state.constants.len();
// Optimization loop
let mut result = statements;
// Optimization loop
loop {
state.reset();
state.restore_constants(orig_constants_len);
@ -492,7 +529,7 @@ pub(crate) fn optimize<'a>(
} else {
// Keep all variable declarations at this level
// 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)
}
@ -517,6 +554,7 @@ pub(crate) fn optimize<'a>(
result
}
/// Optimize an AST.
pub fn optimize_ast(
engine: &Engine,
scope: &Scope,
@ -526,8 +564,8 @@ pub fn optimize_ast(
AST(
match engine.optimization_level {
OptimizationLevel::None => statements,
OptimizationLevel::Simple => optimize(statements, None, &scope),
OptimizationLevel::Full => optimize(statements, Some(engine), &scope),
OptimizationLevel::Simple => optimize(statements, engine, &scope),
OptimizationLevel::Full => optimize(statements, engine, &scope),
},
functions
.into_iter()
@ -536,14 +574,21 @@ pub fn optimize_ast(
OptimizationLevel::None => (),
OptimizationLevel::Simple | OptimizationLevel::Full => {
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)) {
// { return val; } -> val
Stmt::ReturnWithVal(Some(val), ReturnType::Return, _) => {
Stmt::Expr(val)
}
// { return; } -> ()
Stmt::ReturnWithVal(None, ReturnType::Return, pos) => {
Stmt::Expr(Box::new(Expr::Unit(pos)))
}
// All others
stmt => stmt,
};
}

View File

@ -19,13 +19,13 @@ use std::{
#[cfg(not(feature = "only_i32"))]
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.
#[cfg(feature = "only_i32")]
pub type INT = i32;
/// The system floating-point type
/// The system floating-point type.
#[cfg(not(feature = "no_float"))]
pub type FLOAT = f64;
@ -35,7 +35,9 @@ type PERR = ParseErrorType;
/// A location (line number + character position) in the input script.
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
pub struct Position {
/// Line number - 0 = none, MAX = EOF
line: usize,
/// Character position - 0 = BOL, MAX = EOF
pos: usize,
}
@ -152,45 +154,69 @@ impl fmt::Debug for Position {
#[derive(Debug, Clone)]
pub struct AST(pub(crate) Vec<Stmt>, pub(crate) Vec<Arc<FnDef>>);
/// A script-function definition.
#[derive(Debug, Clone)]
pub struct FnDef {
/// Function name.
pub name: String,
/// Names of function parameters.
pub params: Vec<String>,
/// Function body.
pub body: Stmt,
/// Position of the function definition.
pub pos: Position,
}
impl FnDef {
/// Function to order two FnDef records, for binary search.
pub fn compare(&self, name: &str, params_len: usize) -> Ordering {
// First order by name
match self.name.as_str().cmp(name) {
// Then by number of parameters
Ordering::Equal => self.params.len().cmp(&params_len),
order => order,
}
}
}
/// `return`/`throw` statement.
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum ReturnType {
/// `return` statement.
Return,
/// `throw` statement.
Exception,
}
/// A statement.
#[derive(Debug, Clone)]
pub enum Stmt {
/// No-op.
Noop(Position),
/// if expr { stmt } else { stmt }
IfElse(Box<Expr>, Box<Stmt>, Option<Box<Stmt>>),
/// while expr { stmt }
While(Box<Expr>, Box<Stmt>),
/// loop { stmt }
Loop(Box<Stmt>),
/// for id in expr { stmt }
For(String, Box<Expr>, Box<Stmt>),
/// let id = expr
Let(String, Option<Box<Expr>>, Position),
/// const id = expr
Const(String, Box<Expr>, Position),
/// { stmt; ... }
Block(Vec<Stmt>, Position),
/// { stmt }
Expr(Box<Expr>),
/// break
Break(Position),
/// `return`/`throw`
ReturnWithVal(Option<Box<Expr>>, ReturnType, Position),
}
impl Stmt {
/// Get the `Position` of this statement.
pub fn position(&self) -> Position {
match self {
Stmt::Noop(pos)
@ -204,18 +230,7 @@ impl Stmt {
}
}
pub fn is_noop(&self) -> bool {
matches!(self, Stmt::Noop(_))
}
pub fn is_op(&self) -> bool {
!matches!(self, Stmt::Noop(_))
}
pub fn is_var(&self) -> bool {
matches!(self, Stmt::Let(_, _, _))
}
/// Is this statement self-terminated (i.e. no need for a semicolon terminator)?
pub fn is_self_terminated(&self) -> bool {
match self {
Stmt::Noop(_)
@ -233,6 +248,7 @@ impl Stmt {
}
}
/// Is this statement _pure_?
pub fn is_pure(&self) -> bool {
match self {
Stmt::Noop(_) => true,
@ -252,31 +268,54 @@ impl Stmt {
}
}
/// An expression.
#[derive(Debug, Clone)]
pub enum Expr {
/// Integer constant.
IntegerConstant(INT, Position),
/// Floating-point constant.
#[cfg(not(feature = "no_float"))]
FloatConstant(FLOAT, Position),
Variable(String, Position),
Property(String, Position),
/// Character constant.
CharConstant(char, Position),
/// String constant.
StringConstant(String, Position),
/// Variable access.
Variable(String, Position),
/// Property access.
Property(String, Position),
/// { stmt }
Stmt(Box<Stmt>, Position),
/// func(expr, ... )
FunctionCall(String, Vec<Expr>, Option<Dynamic>, Position),
/// expr = expr
Assignment(Box<Expr>, Box<Expr>, Position),
/// lhs.rhs
Dot(Box<Expr>, Box<Expr>, Position),
/// expr[expr]
#[cfg(not(feature = "no_index"))]
Index(Box<Expr>, Box<Expr>, Position),
#[cfg(not(feature = "no_index"))]
/// [ expr, ... ]
Array(Vec<Expr>, Position),
/// lhs && rhs
And(Box<Expr>, Box<Expr>),
/// lhs || rhs
Or(Box<Expr>, Box<Expr>),
/// true
True(Position),
/// false
False(Position),
/// ()
Unit(Position),
}
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 {
match self {
Expr::IntegerConstant(i, _) => i.into_dynamic(),
@ -300,6 +339,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 {
match self {
Expr::IntegerConstant(i, _) => i.to_string(),
@ -319,6 +363,7 @@ impl Expr {
}
}
/// Get the `Position` of the expression.
pub fn position(&self) -> Position {
match self {
Expr::IntegerConstant(_, pos)
@ -348,7 +393,7 @@ impl Expr {
}
}
/// Is this expression pure?
/// Is the expression pure?
///
/// A pure expression has no side effects.
pub fn is_pure(&self) -> bool {
@ -367,6 +412,7 @@ impl Expr {
}
}
/// Is the expression a constant?
pub fn is_constant(&self) -> bool {
match self {
Expr::IntegerConstant(_, _)
@ -379,6 +425,7 @@ impl Expr {
#[cfg(not(feature = "no_float"))]
Expr::FloatConstant(_, _) => true,
// An array literal is constant if all items are constant
#[cfg(not(feature = "no_index"))]
Expr::Array(expressions, _) => expressions.iter().all(Expr::is_constant),
@ -387,6 +434,7 @@ impl Expr {
}
}
/// Tokens.
#[derive(Debug, PartialEq, Clone)]
pub enum Token {
IntegerConstant(INT),
@ -460,6 +508,7 @@ pub enum Token {
}
impl Token {
/// Get the syntax of the token.
pub fn syntax<'a>(&'a self) -> Cow<'a, str> {
use self::Token::*;
@ -540,8 +589,8 @@ impl Token {
}
}
// if another operator is after these, it's probably an unary operator
// not sure about fn's name
// If another operator is after these, it's probably an unary operator
// (not sure about fn name).
pub fn is_next_unary(&self) -> bool {
use self::Token::*;
@ -602,6 +651,7 @@ impl Token {
}
}
/// Get the precedence number of the token.
pub fn precedence(&self) -> u8 {
match self {
Self::Equals
@ -642,8 +692,10 @@ impl Token {
}
}
/// Does an expression bind to the right (instead of left)?
pub fn is_bind_right(&self) -> bool {
match self {
// Assignments bind to the right
Self::Equals
| Self::PlusAssign
| Self::MinusAssign
@ -657,6 +709,7 @@ impl Token {
| Self::ModuloAssign
| Self::PowerOfAssign => true,
// Property access binds to the right
Self::Period => true,
_ => false,
@ -664,24 +717,36 @@ impl Token {
}
}
/// An iterator on a `Token` stream.
pub struct TokenIterator<'a> {
/// The last token seen.
last: Token,
/// Current position.
pos: Position,
/// The input characters stream.
char_stream: Peekable<Chars<'a>>,
}
impl<'a> TokenIterator<'a> {
/// Move the current position one character ahead.
fn advance(&mut self) {
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) {
self.pos.rewind();
}
/// Move the current position to the next line.
fn new_line(&mut self) {
self.pos.new_line()
}
pub fn parse_string_const(
/// Parse a string literal wrapped by `enclosing_char`.
pub fn parse_string_literal(
&mut self,
enclosing_char: char,
) -> Result<String, (LexError, Position)> {
@ -693,25 +758,31 @@ impl<'a> TokenIterator<'a> {
self.advance();
match next_char.ok_or((LERR::UnterminatedString, Position::eof()))? {
// \...
'\\' if escape.is_empty() => {
escape.push('\\');
}
// \\
'\\' if !escape.is_empty() => {
escape.clear();
result.push('\\');
}
// \t
't' if !escape.is_empty() => {
escape.clear();
result.push('\t');
}
// \n
'n' if !escape.is_empty() => {
escape.clear();
result.push('\n');
}
// \r
'r' if !escape.is_empty() => {
escape.clear();
result.push('\r');
}
// \x??, \u????, \U????????
ch @ 'x' | ch @ 'u' | ch @ 'U' if !escape.is_empty() => {
let mut seq = escape.clone();
seq.push(ch);
@ -744,15 +815,25 @@ impl<'a> TokenIterator<'a> {
.ok_or_else(|| (LERR::MalformedEscapeSequence(seq), self.pos))?,
);
}
// \{enclosing_char} - escaped
ch if enclosing_char == ch && !escape.is_empty() => result.push(ch),
// Close wrapper
ch if enclosing_char == ch && escape.is_empty() => break,
// Unknown escape sequence
_ if !escape.is_empty() => {
return Err((LERR::MalformedEscapeSequence(escape), self.pos))
}
// Cannot have new-lines inside string literals
'\n' => {
self.rewind();
return Err((LERR::UnterminatedString, self.pos));
}
// All other characters
ch => {
escape.clear();
result.push(ch);
@ -763,6 +844,7 @@ impl<'a> TokenIterator<'a> {
Ok(result.iter().collect())
}
/// Get the next token.
fn inner_next(&mut self) -> Option<(Token, Position)> {
let mut negated = false;
@ -772,7 +854,9 @@ impl<'a> TokenIterator<'a> {
let pos = self.pos;
match c {
// \n
'\n' => self.new_line(),
// digit ...
'0'..='9' => {
let mut result = Vec::new();
let mut radix_base: Option<u32> = None;
@ -801,6 +885,7 @@ impl<'a> TokenIterator<'a> {
}
}
}
// 0x????, 0o????, 0b????
ch @ 'x' | ch @ 'X' | ch @ 'o' | ch @ 'O' | ch @ 'b' | ch @ 'B'
if c == '0' =>
{
@ -850,6 +935,7 @@ impl<'a> TokenIterator<'a> {
result.insert(0, '-');
}
// Parse number
if let Some(radix) = radix_base {
let out: String = result.iter().skip(2).filter(|&&c| c != '_').collect();
@ -865,6 +951,7 @@ impl<'a> TokenIterator<'a> {
let out: String = result.iter().filter(|&&c| c != '_').collect();
let num = INT::from_str(&out).map(Token::IntegerConstant);
// If integer parsing is unnecessary, try float instead
#[cfg(not(feature = "no_float"))]
let num = num.or_else(|_| FLOAT::from_str(&out).map(Token::FloatConstant));
@ -876,6 +963,7 @@ impl<'a> TokenIterator<'a> {
));
}
}
// letter ...
'A'..='Z' | 'a'..='z' | '_' => {
let mut result = Vec::new();
result.push(c);
@ -920,13 +1008,15 @@ impl<'a> TokenIterator<'a> {
pos,
));
}
// " - string literal
'"' => {
return match self.parse_string_const('"') {
return match self.parse_string_literal('"') {
Ok(out) => Some((Token::StringConst(out), pos)),
Err(e) => Some((Token::LexError(e.0), e.1)),
}
}
'\'' => match self.parse_string_const('\'') {
// ' - character literal
'\'' => match self.parse_string_literal('\'') {
Ok(result) => {
let mut chars = result.chars();
@ -945,16 +1035,22 @@ impl<'a> TokenIterator<'a> {
}
Err(e) => return Some((Token::LexError(e.0), e.1)),
},
// Braces
'{' => return Some((Token::LeftBrace, pos)),
'}' => return Some((Token::RightBrace, pos)),
// Parentheses
'(' => return Some((Token::LeftParen, pos)),
')' => return Some((Token::RightParen, pos)),
// Indexing
#[cfg(not(feature = "no_index"))]
'[' => return Some((Token::LeftBracket, pos)),
#[cfg(not(feature = "no_index"))]
']' => return Some((Token::RightBracket, pos)),
// Operators
'+' => {
return Some((
match self.char_stream.peek() {
@ -1218,6 +1314,7 @@ impl<'a> Iterator for TokenIterator<'a> {
}
}
/// Tokenize an input text stream.
pub fn lex(input: &str) -> TokenIterator<'_> {
TokenIterator {
last: Token::LexError(LERR::InputError("".into())),
@ -1226,6 +1323,7 @@ pub fn lex(input: &str) -> TokenIterator<'_> {
}
}
/// Parse ( expr )
fn parse_paren_expr<'a>(
input: &mut Peekable<TokenIterator<'a>>,
begin: Position,
@ -1242,13 +1340,16 @@ fn parse_paren_expr<'a>(
let expr = parse_expr(input)?;
match input.next() {
// ( xxx )
Some((Token::RightParen, _)) => Ok(expr),
// ( xxx ???
Some((_, pos)) => {
return Err(ParseError::new(
PERR::MissingRightParen("a matching ( in the expression".into()),
pos,
))
}
// ( xxx
None => Err(ParseError::new(
PERR::MissingRightParen("a matching ( in the expression".into()),
Position::eof(),
@ -1256,6 +1357,7 @@ fn parse_paren_expr<'a>(
}
}
/// Parse a function call.
fn parse_call_expr<'a>(
id: String,
input: &mut Peekable<TokenIterator<'a>>,
@ -1263,6 +1365,7 @@ fn parse_call_expr<'a>(
) -> Result<Expr, ParseError> {
let mut args_expr_list = Vec::new();
// id()
if let (Token::RightParen, _) = input.peek().ok_or_else(|| {
ParseError::new(
PERR::MissingRightParen(format!(
@ -1308,6 +1411,7 @@ fn parse_call_expr<'a>(
}
}
/// Parse an indexing expression.s
#[cfg(not(feature = "no_index"))]
fn parse_index_expr<'a>(
lhs: Box<Expr>,
@ -1318,6 +1422,7 @@ fn parse_index_expr<'a>(
// Check type of indexing - must be integer
match &idx_expr {
// lhs[int]
Expr::IntegerConstant(i, pos) if *i < 0 => {
return Err(ParseError::new(
PERR::MalformedIndexExpr(format!(
@ -1327,6 +1432,7 @@ fn parse_index_expr<'a>(
*pos,
))
}
// lhs[float]
#[cfg(not(feature = "no_float"))]
Expr::FloatConstant(_, pos) => {
return Err(ParseError::new(
@ -1334,6 +1440,7 @@ fn parse_index_expr<'a>(
*pos,
))
}
// lhs[char]
Expr::CharConstant(_, pos) => {
return Err(ParseError::new(
PERR::MalformedIndexExpr(
@ -1342,18 +1449,21 @@ fn parse_index_expr<'a>(
*pos,
))
}
// lhs[string]
Expr::StringConstant(_, pos) => {
return Err(ParseError::new(
PERR::MalformedIndexExpr("Array access expects integer index, not a string".into()),
*pos,
))
}
// lhs[??? = ??? ], lhs[()]
Expr::Assignment(_, _, pos) | Expr::Unit(pos) => {
return Err(ParseError::new(
PERR::MalformedIndexExpr("Array access expects integer index, not ()".into()),
*pos,
))
}
// lhs[??? && ???], lhs[??? || ???]
Expr::And(lhs, _) | Expr::Or(lhs, _) => {
return Err(ParseError::new(
PERR::MalformedIndexExpr(
@ -1362,6 +1472,7 @@ fn parse_index_expr<'a>(
lhs.position(),
))
}
// lhs[true], lhs[false]
Expr::True(pos) | Expr::False(pos) => {
return Err(ParseError::new(
PERR::MalformedIndexExpr(
@ -1370,6 +1481,7 @@ fn parse_index_expr<'a>(
*pos,
))
}
// All other expressions
_ => (),
}
@ -1393,29 +1505,35 @@ fn parse_index_expr<'a>(
}
}
/// Parse an expression that begins with an identifier.
fn parse_ident_expr<'a>(
id: String,
input: &mut Peekable<TokenIterator<'a>>,
begin: Position,
) -> Result<Expr, ParseError> {
match input.peek() {
// id(...) - function call
Some((Token::LeftParen, _)) => {
input.next();
parse_call_expr(id, input, begin)
}
// id[...] - indexing
#[cfg(not(feature = "no_index"))]
Some((Token::LeftBracket, pos)) => {
let pos = *pos;
input.next();
parse_index_expr(Box::new(Expr::Variable(id, begin)), input, pos)
}
// id - variable
Some(_) => Ok(Expr::Variable(id, begin)),
// EOF
None => Ok(Expr::Variable(id, Position::eof())),
}
}
/// Parse an array literal.
#[cfg(not(feature = "no_index"))]
fn parse_array_expr<'a>(
fn parse_array_literal<'a>(
input: &mut Peekable<TokenIterator<'a>>,
begin: Position,
) -> Result<Expr, ParseError> {
@ -1462,8 +1580,9 @@ fn parse_array_expr<'a>(
}
}
/// Parse a primary expression.
fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> {
// Block statement as expression
// { - block statement as expression
match input.peek() {
Some((Token::LeftBrace, pos)) => {
let pos = *pos;
@ -1500,7 +1619,7 @@ fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pa
#[cfg(not(feature = "no_index"))]
(Token::LeftBracket, pos) => {
can_be_indexed = true;
parse_array_expr(input, pos)
parse_array_literal(input, pos)
}
(Token::True, pos) => Ok(Expr::True(pos)),
(Token::False, pos) => Ok(Expr::False(pos)),
@ -1524,11 +1643,13 @@ fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pa
Ok(root_expr)
}
/// Parse a potential unary operator.
fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> {
match input
.peek()
.ok_or_else(|| ParseError::new(PERR::InputPastEndOfFile, Position::eof()))?
{
// -expr
(Token::UnaryMinus, pos) => {
let pos = *pos;
@ -1561,10 +1682,12 @@ fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pars
expr => Ok(Expr::FunctionCall("-".into(), vec![expr], None, pos)),
}
}
// +expr
(Token::UnaryPlus, _) => {
input.next();
parse_unary(input)
}
// !expr
(Token::Bang, pos) => {
let pos = *pos;
@ -1577,34 +1700,41 @@ fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pars
pos,
))
}
// All other tokens
_ => parse_primary(input),
}
}
/// Parse an assignment.
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> {
match expr {
// var
Expr::Variable(_, _) => {
assert!(is_top, "property expected but gets variable");
None
}
// property
Expr::Property(_, _) => {
assert!(!is_top, "variable expected but gets property");
None
}
// var[...]
#[cfg(not(feature = "no_index"))]
Expr::Index(idx_lhs, _, _) if matches!(idx_lhs.as_ref(), &Expr::Variable(_, _)) => {
assert!(is_top, "property expected but gets variable");
None
}
// property[...]
#[cfg(not(feature = "no_index"))]
Expr::Index(idx_lhs, _, _) if matches!(idx_lhs.as_ref(), &Expr::Property(_, _)) => {
assert!(!is_top, "variable expected but gets property");
None
}
// idx_lhs[...]
#[cfg(not(feature = "no_index"))]
Expr::Index(idx_lhs, _, pos) => Some(ParseError::new(
match idx_lhs.as_ref() {
@ -1614,22 +1744,27 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseEr
*pos,
)),
// dot_lhs.dot_rhs
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),
// property.dot_rhs
Expr::Property(_, _) if !is_top => valid_assignment_chain(dot_rhs, false),
// var[...]
#[cfg(not(feature = "no_index"))]
Expr::Index(idx_lhs, _, _)
if matches!(idx_lhs.as_ref(), &Expr::Variable(_, _)) && is_top =>
{
valid_assignment_chain(dot_rhs, false)
}
// property[...]
#[cfg(not(feature = "no_index"))]
Expr::Index(idx_lhs, _, _)
if matches!(idx_lhs.as_ref(), &Expr::Property(_, _)) && !is_top =>
{
valid_assignment_chain(dot_rhs, false)
}
// idx_lhs[...]
#[cfg(not(feature = "no_index"))]
Expr::Index(idx_lhs, _, _) => Some(ParseError::new(
ParseErrorType::AssignmentToCopy,
@ -1652,6 +1787,7 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseEr
}
}
/// Parse an operator-assignment expression.
fn parse_op_assignment(
function: &str,
lhs: Expr,
@ -1667,6 +1803,7 @@ fn parse_op_assignment(
)
}
/// Parse a binary expression.
fn parse_binary_op<'a>(
input: &mut Peekable<TokenIterator<'a>>,
parent_precedence: u8,
@ -1820,11 +1957,13 @@ fn parse_binary_op<'a>(
}
}
/// Parse an expression.
fn parse_expr<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> {
let lhs = parse_unary(input)?;
parse_binary_op(input, 1, lhs)
}
/// Parse an if statement.
fn parse_if<'a>(
input: &mut Peekable<TokenIterator<'a>>,
breakable: bool,
@ -1849,6 +1988,7 @@ fn parse_if<'a>(
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> {
input.next();
@ -1858,6 +1998,7 @@ fn parse_while<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Pars
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> {
input.next();
@ -1866,6 +2007,7 @@ fn parse_loop<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Parse
Ok(Stmt::Loop(Box::new(body)))
}
/// Parse a for loop.
fn parse_for<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
input.next();
@ -1894,7 +2036,8 @@ fn parse_for<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseE
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>>,
var_type: VariableType,
) -> Result<Stmt, ParseError> {
@ -1935,6 +2078,7 @@ fn parse_var<'a>(
}
}
/// Parse a statement block.
fn parse_block<'a>(
input: &mut Peekable<TokenIterator<'a>>,
breakable: bool,
@ -1995,10 +2139,12 @@ fn parse_block<'a>(
}
}
/// Parse an expression as a statement.
fn parse_expr_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
Ok(Stmt::Expr(Box::new(parse_expr(input)?)))
}
/// Parse a single statement.
fn parse_stmt<'a>(
input: &mut Peekable<TokenIterator<'a>>,
breakable: bool,
@ -2030,14 +2176,14 @@ fn parse_stmt<'a>(
input.next();
match input.peek() {
// return/throw at EOF
// `return`/`throw` at EOF
None => Ok(Stmt::ReturnWithVal(None, return_type, Position::eof())),
// return; or throw;
// `return;` or `throw;`
Some((Token::SemiColon, pos)) => {
let pos = *pos;
Ok(Stmt::ReturnWithVal(None, return_type, pos))
}
// return or throw with expression
// `return` or `throw` with expression
Some((_, pos)) => {
let pos = *pos;
Ok(Stmt::ReturnWithVal(
@ -2049,12 +2195,13 @@ fn parse_stmt<'a>(
}
}
(Token::LeftBrace, _) => parse_block(input, breakable),
(Token::Let, _) => parse_var(input, VariableType::Normal),
(Token::Const, _) => parse_var(input, VariableType::Constant),
(Token::Let, _) => parse_let(input, VariableType::Normal),
(Token::Const, _) => parse_let(input, VariableType::Constant),
_ => parse_expr_stmt(input),
}
}
/// Parse a function definition.
#[cfg(not(feature = "no_function"))]
fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseError> {
let pos = input
@ -2156,6 +2303,7 @@ fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseE
})
}
/// Parse the global level statements.
fn parse_global_level<'a, 'e>(
input: &mut Peekable<TokenIterator<'a>>,
) -> Result<(Vec<Stmt>, Vec<FnDef>), ParseError> {
@ -2204,6 +2352,7 @@ fn parse_global_level<'a, 'e>(
Ok((statements, functions))
}
/// Run the parser on an input stream, returning an AST.
pub fn parse<'a, 'e>(
input: &mut Peekable<TokenIterator<'a>>,
engine: &Engine<'e>,
@ -2219,6 +2368,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) {
if value.is::<INT>() {
let value2 = value.clone();