Add code comments.
This commit is contained in:
parent
777f66ff3c
commit
ca20faf170
10
src/any.rs
10
src/any.rs
@ -1,7 +1,7 @@
|
||||
//! Helper module which defines the `Any` trait to to allow dynamic value handling.
|
||||
|
||||
use crate::stdlib::{
|
||||
any::{self, type_name, TypeId},
|
||||
any::{type_name, Any as StdAny, TypeId},
|
||||
boxed::Box,
|
||||
fmt,
|
||||
};
|
||||
@ -13,7 +13,7 @@ pub type Variant = dyn Any;
|
||||
pub type Dynamic = Box<Variant>;
|
||||
|
||||
/// A trait covering any type.
|
||||
pub trait Any: any::Any {
|
||||
pub trait Any: StdAny {
|
||||
/// Get the `TypeId` of this type.
|
||||
fn type_id(&self) -> TypeId;
|
||||
|
||||
@ -23,12 +23,12 @@ pub trait Any: 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 + any::Any + ?Sized> Any for T {
|
||||
impl<T: Clone + StdAny + ?Sized> Any for T {
|
||||
fn type_id(&self) -> TypeId {
|
||||
TypeId::of::<T>()
|
||||
}
|
||||
@ -91,7 +91,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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
@ -55,6 +55,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(|| {
|
||||
@ -64,6 +65,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(|| {
|
||||
@ -73,6 +75,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(|| {
|
||||
@ -82,11 +85,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),
|
||||
@ -101,6 +106,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(|| {
|
||||
@ -110,6 +116,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
|
||||
@ -125,26 +132,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
|
||||
@ -157,6 +170,9 @@ impl Engine<'_> {
|
||||
x.into()
|
||||
}
|
||||
}
|
||||
|
||||
// Comparison operators
|
||||
|
||||
fn lt<T: PartialOrd>(x: T, y: T) -> bool {
|
||||
x < y
|
||||
}
|
||||
@ -175,6 +191,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
|
||||
}
|
||||
@ -184,6 +203,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
|
||||
}
|
||||
@ -193,8 +215,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),
|
||||
@ -204,13 +229,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),
|
||||
@ -220,44 +247,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 {
|
||||
@ -274,7 +306,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 {
|
||||
@ -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")]
|
||||
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)
|
||||
}
|
||||
|
||||
@ -393,8 +430,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);
|
||||
|
||||
@ -448,18 +487,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);
|
||||
}
|
||||
|
||||
{
|
||||
@ -628,9 +667,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
|
||||
|
235
src/optimize.rs
235
src/optimize.rs
@ -13,50 +13,54 @@ use crate::stdlib::{
|
||||
boxed::Box, vec,
|
||||
};
|
||||
|
||||
/// 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 {
|
||||
@ -68,60 +72,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();
|
||||
@ -131,48 +143,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();
|
||||
@ -207,11 +221,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();
|
||||
}
|
||||
@ -234,10 +249,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[..] {
|
||||
@ -254,44 +271,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(
|
||||
@ -299,19 +326,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()) =>
|
||||
{
|
||||
@ -320,6 +351,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() =>
|
||||
{
|
||||
@ -327,14 +359,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();
|
||||
@ -350,38 +382,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)),
|
||||
@ -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),
|
||||
|
||||
// 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()`
|
||||
@ -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))
|
||||
}
|
||||
// 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();
|
||||
|
||||
@ -438,28 +478,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, .. }| {
|
||||
@ -476,9 +513,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);
|
||||
@ -496,7 +533,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)
|
||||
}
|
||||
@ -521,6 +558,7 @@ pub(crate) fn optimize<'a>(
|
||||
result
|
||||
}
|
||||
|
||||
/// Optimize an AST.
|
||||
pub fn optimize_ast(
|
||||
engine: &Engine,
|
||||
scope: &Scope,
|
||||
@ -530,8 +568,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()
|
||||
@ -540,14 +578,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,
|
||||
};
|
||||
}
|
||||
|
218
src/parser.rs
218
src/parser.rs
@ -29,13 +29,13 @@ use crate::stdlib::{
|
||||
#[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;
|
||||
|
||||
@ -45,7 +45,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,
|
||||
}
|
||||
|
||||
@ -162,45 +164,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(¶ms_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)
|
||||
@ -214,18 +240,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(_)
|
||||
@ -243,6 +258,7 @@ impl Stmt {
|
||||
}
|
||||
}
|
||||
|
||||
/// Is this statement _pure_?
|
||||
pub fn is_pure(&self) -> bool {
|
||||
match self {
|
||||
Stmt::Noop(_) => true,
|
||||
@ -262,31 +278,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(),
|
||||
@ -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 {
|
||||
match self {
|
||||
Expr::IntegerConstant(i, _) => i.to_string(),
|
||||
@ -329,6 +373,7 @@ impl Expr {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the `Position` of the expression.
|
||||
pub fn position(&self) -> Position {
|
||||
match self {
|
||||
Expr::IntegerConstant(_, pos)
|
||||
@ -358,7 +403,7 @@ impl Expr {
|
||||
}
|
||||
}
|
||||
|
||||
/// Is this expression pure?
|
||||
/// Is the expression pure?
|
||||
///
|
||||
/// A pure expression has no side effects.
|
||||
pub fn is_pure(&self) -> bool {
|
||||
@ -377,6 +422,7 @@ impl Expr {
|
||||
}
|
||||
}
|
||||
|
||||
/// Is the expression a constant?
|
||||
pub fn is_constant(&self) -> bool {
|
||||
match self {
|
||||
Expr::IntegerConstant(_, _)
|
||||
@ -389,6 +435,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),
|
||||
|
||||
@ -397,6 +444,7 @@ impl Expr {
|
||||
}
|
||||
}
|
||||
|
||||
/// Tokens.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Token {
|
||||
IntegerConstant(INT),
|
||||
@ -470,6 +518,7 @@ pub enum Token {
|
||||
}
|
||||
|
||||
impl Token {
|
||||
/// Get the syntax of the token.
|
||||
pub fn syntax<'a>(&'a self) -> Cow<'a, str> {
|
||||
use self::Token::*;
|
||||
|
||||
@ -550,8 +599,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::*;
|
||||
|
||||
@ -612,6 +661,7 @@ impl Token {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the precedence number of the token.
|
||||
pub fn precedence(&self) -> u8 {
|
||||
match self {
|
||||
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 {
|
||||
match self {
|
||||
// Assignments bind to the right
|
||||
Self::Equals
|
||||
| Self::PlusAssign
|
||||
| Self::MinusAssign
|
||||
@ -667,6 +719,7 @@ impl Token {
|
||||
| Self::ModuloAssign
|
||||
| Self::PowerOfAssign => true,
|
||||
|
||||
// Property access binds to the right
|
||||
Self::Period => true,
|
||||
|
||||
_ => false,
|
||||
@ -674,24 +727,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)> {
|
||||
@ -703,25 +768,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);
|
||||
@ -754,15 +825,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);
|
||||
@ -773,6 +854,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;
|
||||
|
||||
@ -782,7 +864,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;
|
||||
@ -811,6 +895,7 @@ impl<'a> TokenIterator<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 0x????, 0o????, 0b????
|
||||
ch @ 'x' | ch @ 'X' | ch @ 'o' | ch @ 'O' | ch @ 'b' | ch @ 'B'
|
||||
if c == '0' =>
|
||||
{
|
||||
@ -860,6 +945,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();
|
||||
|
||||
@ -875,6 +961,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));
|
||||
|
||||
@ -886,6 +973,7 @@ impl<'a> TokenIterator<'a> {
|
||||
));
|
||||
}
|
||||
}
|
||||
// letter ...
|
||||
'A'..='Z' | 'a'..='z' | '_' => {
|
||||
let mut result = Vec::new();
|
||||
result.push(c);
|
||||
@ -930,13 +1018,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();
|
||||
|
||||
@ -955,16 +1045,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() {
|
||||
@ -1228,6 +1324,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())),
|
||||
@ -1236,6 +1333,7 @@ pub fn lex(input: &str) -> TokenIterator<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse ( expr )
|
||||
fn parse_paren_expr<'a>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
begin: Position,
|
||||
@ -1252,13 +1350,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(),
|
||||
@ -1266,6 +1367,7 @@ fn parse_paren_expr<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a function call.
|
||||
fn parse_call_expr<'a>(
|
||||
id: String,
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
@ -1273,6 +1375,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!(
|
||||
@ -1318,6 +1421,7 @@ fn parse_call_expr<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse an indexing expression.s
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
fn parse_index_expr<'a>(
|
||||
lhs: Box<Expr>,
|
||||
@ -1328,6 +1432,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!(
|
||||
@ -1337,6 +1442,7 @@ fn parse_index_expr<'a>(
|
||||
*pos,
|
||||
))
|
||||
}
|
||||
// lhs[float]
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Expr::FloatConstant(_, pos) => {
|
||||
return Err(ParseError::new(
|
||||
@ -1344,6 +1450,7 @@ fn parse_index_expr<'a>(
|
||||
*pos,
|
||||
))
|
||||
}
|
||||
// lhs[char]
|
||||
Expr::CharConstant(_, pos) => {
|
||||
return Err(ParseError::new(
|
||||
PERR::MalformedIndexExpr(
|
||||
@ -1352,18 +1459,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(
|
||||
@ -1372,6 +1482,7 @@ fn parse_index_expr<'a>(
|
||||
lhs.position(),
|
||||
))
|
||||
}
|
||||
// lhs[true], lhs[false]
|
||||
Expr::True(pos) | Expr::False(pos) => {
|
||||
return Err(ParseError::new(
|
||||
PERR::MalformedIndexExpr(
|
||||
@ -1380,6 +1491,7 @@ fn parse_index_expr<'a>(
|
||||
*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>(
|
||||
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> {
|
||||
@ -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> {
|
||||
// Block statement as expression
|
||||
// { - block statement as expression
|
||||
match input.peek() {
|
||||
Some((Token::LeftBrace, 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"))]
|
||||
(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)),
|
||||
@ -1534,11 +1653,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;
|
||||
|
||||
@ -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
|
||||
(Token::UnaryPlus, _) => {
|
||||
input.next();
|
||||
parse_unary(input)
|
||||
}
|
||||
// !expr
|
||||
(Token::Bang, pos) => {
|
||||
let pos = *pos;
|
||||
|
||||
@ -1587,34 +1710,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() {
|
||||
@ -1624,22 +1754,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,
|
||||
@ -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(
|
||||
function: &str,
|
||||
lhs: Expr,
|
||||
@ -1677,6 +1813,7 @@ fn parse_op_assignment(
|
||||
)
|
||||
}
|
||||
|
||||
/// Parse a binary expression.
|
||||
fn parse_binary_op<'a>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
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> {
|
||||
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,
|
||||
@ -1859,6 +1998,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();
|
||||
|
||||
@ -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)))
|
||||
}
|
||||
|
||||
/// Parse a loop statement.
|
||||
fn parse_loop<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
||||
input.next();
|
||||
|
||||
@ -1876,6 +2017,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();
|
||||
|
||||
@ -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)))
|
||||
}
|
||||
|
||||
fn parse_var<'a>(
|
||||
/// Parse a variable definition statement.
|
||||
fn parse_let<'a>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
var_type: VariableType,
|
||||
) -> Result<Stmt, ParseError> {
|
||||
@ -1945,6 +2088,7 @@ fn parse_var<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a statement block.
|
||||
fn parse_block<'a>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
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> {
|
||||
Ok(Stmt::Expr(Box::new(parse_expr(input)?)))
|
||||
}
|
||||
|
||||
/// Parse a single statement.
|
||||
fn parse_stmt<'a>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
breakable: bool,
|
||||
@ -2040,14 +2186,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(
|
||||
@ -2059,12 +2205,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
|
||||
@ -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>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
) -> Result<(Vec<Stmt>, Vec<FnDef>), ParseError> {
|
||||
@ -2214,6 +2362,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>,
|
||||
@ -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) {
|
||||
if value.is::<INT>() {
|
||||
let value2 = value.clone();
|
||||
|
Loading…
Reference in New Issue
Block a user