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.
|
//! Helper module which defines the `Any` trait to to allow dynamic value handling.
|
||||||
|
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
any::{self, type_name, TypeId},
|
any::{type_name, Any as StdAny, TypeId},
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
fmt,
|
fmt,
|
||||||
};
|
};
|
||||||
@ -13,7 +13,7 @@ pub type Variant = dyn Any;
|
|||||||
pub type Dynamic = Box<Variant>;
|
pub type Dynamic = Box<Variant>;
|
||||||
|
|
||||||
/// A trait covering any type.
|
/// A trait covering any type.
|
||||||
pub trait Any: any::Any {
|
pub trait Any: StdAny {
|
||||||
/// Get the `TypeId` of this type.
|
/// Get the `TypeId` of this type.
|
||||||
fn type_id(&self) -> TypeId;
|
fn type_id(&self) -> TypeId;
|
||||||
|
|
||||||
@ -23,12 +23,12 @@ pub trait Any: any::Any {
|
|||||||
/// Convert into `Dynamic`.
|
/// Convert into `Dynamic`.
|
||||||
fn into_dynamic(&self) -> Dynamic;
|
fn into_dynamic(&self) -> Dynamic;
|
||||||
|
|
||||||
/// This type may only be implemented by `rhai`.
|
/// This trait may only be implemented by `rhai`.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
fn _closed(&self) -> _Private;
|
fn _closed(&self) -> _Private;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Clone + any::Any + ?Sized> Any for T {
|
impl<T: Clone + StdAny + ?Sized> Any for T {
|
||||||
fn type_id(&self) -> TypeId {
|
fn type_id(&self) -> TypeId {
|
||||||
TypeId::of::<T>()
|
TypeId::of::<T>()
|
||||||
}
|
}
|
||||||
@ -91,7 +91,7 @@ pub trait AnyExt: Sized {
|
|||||||
/// Get a copy of a `Dynamic` value as a specific type.
|
/// Get a copy of a `Dynamic` value as a specific type.
|
||||||
fn downcast<T: Any + Clone>(self) -> Result<Box<T>, Self>;
|
fn downcast<T: Any + Clone>(self) -> Result<Box<T>, Self>;
|
||||||
|
|
||||||
/// This type may only be implemented by `rhai`.
|
/// This trait may only be implemented by `rhai`.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
fn _closed(&self) -> _Private;
|
fn _closed(&self) -> _Private;
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ use crate::any::Any;
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
use crate::engine::Array;
|
use crate::engine::Array;
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::fn_register::{RegisterFn, RegisterResultFn};
|
use crate::fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
|
||||||
use crate::parser::{Position, INT};
|
use crate::parser::{Position, INT};
|
||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
|
|
||||||
@ -55,6 +55,7 @@ macro_rules! reg_op_result1 {
|
|||||||
impl Engine<'_> {
|
impl Engine<'_> {
|
||||||
/// Register the core built-in library.
|
/// Register the core built-in library.
|
||||||
pub(crate) fn register_core_lib(&mut self) {
|
pub(crate) fn register_core_lib(&mut self) {
|
||||||
|
/// Checked add
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
fn add<T: Display + CheckedAdd>(x: T, y: T) -> Result<T, EvalAltResult> {
|
fn add<T: Display + CheckedAdd>(x: T, y: T) -> Result<T, EvalAltResult> {
|
||||||
x.checked_add(&y).ok_or_else(|| {
|
x.checked_add(&y).ok_or_else(|| {
|
||||||
@ -64,6 +65,7 @@ impl Engine<'_> {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/// Checked subtract
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
fn sub<T: Display + CheckedSub>(x: T, y: T) -> Result<T, EvalAltResult> {
|
fn sub<T: Display + CheckedSub>(x: T, y: T) -> Result<T, EvalAltResult> {
|
||||||
x.checked_sub(&y).ok_or_else(|| {
|
x.checked_sub(&y).ok_or_else(|| {
|
||||||
@ -73,6 +75,7 @@ impl Engine<'_> {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/// Checked multiply
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
fn mul<T: Display + CheckedMul>(x: T, y: T) -> Result<T, EvalAltResult> {
|
fn mul<T: Display + CheckedMul>(x: T, y: T) -> Result<T, EvalAltResult> {
|
||||||
x.checked_mul(&y).ok_or_else(|| {
|
x.checked_mul(&y).ok_or_else(|| {
|
||||||
@ -82,11 +85,13 @@ impl Engine<'_> {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/// Checked divide
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
fn div<T>(x: T, y: T) -> Result<T, EvalAltResult>
|
fn div<T>(x: T, y: T) -> Result<T, EvalAltResult>
|
||||||
where
|
where
|
||||||
T: Display + CheckedDiv + PartialEq + Zero,
|
T: Display + CheckedDiv + PartialEq + Zero,
|
||||||
{
|
{
|
||||||
|
// Detect division by zero
|
||||||
if y == T::zero() {
|
if y == T::zero() {
|
||||||
return Err(EvalAltResult::ErrorArithmetic(
|
return Err(EvalAltResult::ErrorArithmetic(
|
||||||
format!("Division by zero: {} / {}", x, y),
|
format!("Division by zero: {} / {}", x, y),
|
||||||
@ -101,6 +106,7 @@ impl Engine<'_> {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/// Checked negative - e.g. -(i32::MIN) will overflow i32::MAX
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
fn neg<T: Display + CheckedNeg>(x: T) -> Result<T, EvalAltResult> {
|
fn neg<T: Display + CheckedNeg>(x: T) -> Result<T, EvalAltResult> {
|
||||||
x.checked_neg().ok_or_else(|| {
|
x.checked_neg().ok_or_else(|| {
|
||||||
@ -110,6 +116,7 @@ impl Engine<'_> {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/// Checked absolute
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
fn abs<T: Display + CheckedNeg + PartialOrd + Zero>(x: T) -> Result<T, EvalAltResult> {
|
fn abs<T: Display + CheckedNeg + PartialOrd + Zero>(x: T) -> Result<T, EvalAltResult> {
|
||||||
// FIX - We don't use Signed::abs() here because, contrary to documentation, it panics
|
// FIX - We don't use Signed::abs() here because, contrary to documentation, it panics
|
||||||
@ -125,26 +132,32 @@ impl Engine<'_> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Unchecked add - may panic on overflow
|
||||||
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
|
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
|
||||||
fn add_u<T: Add>(x: T, y: T) -> <T as Add>::Output {
|
fn add_u<T: Add>(x: T, y: T) -> <T as Add>::Output {
|
||||||
x + y
|
x + y
|
||||||
}
|
}
|
||||||
|
/// Unchecked subtract - may panic on underflow
|
||||||
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
|
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
|
||||||
fn sub_u<T: Sub>(x: T, y: T) -> <T as Sub>::Output {
|
fn sub_u<T: Sub>(x: T, y: T) -> <T as Sub>::Output {
|
||||||
x - y
|
x - y
|
||||||
}
|
}
|
||||||
|
/// Unchecked multiply - may panic on overflow
|
||||||
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
|
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
|
||||||
fn mul_u<T: Mul>(x: T, y: T) -> <T as Mul>::Output {
|
fn mul_u<T: Mul>(x: T, y: T) -> <T as Mul>::Output {
|
||||||
x * y
|
x * y
|
||||||
}
|
}
|
||||||
|
/// Unchecked divide - may panic when dividing by zero
|
||||||
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
|
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
|
||||||
fn div_u<T: Div>(x: T, y: T) -> <T as Div>::Output {
|
fn div_u<T: Div>(x: T, y: T) -> <T as Div>::Output {
|
||||||
x / y
|
x / y
|
||||||
}
|
}
|
||||||
|
/// Unchecked negative - may panic on overflow
|
||||||
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
|
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
|
||||||
fn neg_u<T: Neg>(x: T) -> <T as Neg>::Output {
|
fn neg_u<T: Neg>(x: T) -> <T as Neg>::Output {
|
||||||
-x
|
-x
|
||||||
}
|
}
|
||||||
|
/// Unchecked absolute - may panic on overflow
|
||||||
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
|
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
|
||||||
fn abs_u<T>(x: T) -> <T as Neg>::Output
|
fn abs_u<T>(x: T) -> <T as Neg>::Output
|
||||||
where
|
where
|
||||||
@ -157,6 +170,9 @@ impl Engine<'_> {
|
|||||||
x.into()
|
x.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Comparison operators
|
||||||
|
|
||||||
fn lt<T: PartialOrd>(x: T, y: T) -> bool {
|
fn lt<T: PartialOrd>(x: T, y: T) -> bool {
|
||||||
x < y
|
x < y
|
||||||
}
|
}
|
||||||
@ -175,6 +191,9 @@ impl Engine<'_> {
|
|||||||
fn ne<T: PartialEq>(x: T, y: T) -> bool {
|
fn ne<T: PartialEq>(x: T, y: T) -> bool {
|
||||||
x != y
|
x != y
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Logic operators
|
||||||
|
|
||||||
fn and(x: bool, y: bool) -> bool {
|
fn and(x: bool, y: bool) -> bool {
|
||||||
x && y
|
x && y
|
||||||
}
|
}
|
||||||
@ -184,6 +203,9 @@ impl Engine<'_> {
|
|||||||
fn not(x: bool) -> bool {
|
fn not(x: bool) -> bool {
|
||||||
!x
|
!x
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bit operators
|
||||||
|
|
||||||
fn binary_and<T: BitAnd>(x: T, y: T) -> <T as BitAnd>::Output {
|
fn binary_and<T: BitAnd>(x: T, y: T) -> <T as BitAnd>::Output {
|
||||||
x & y
|
x & y
|
||||||
}
|
}
|
||||||
@ -193,8 +215,11 @@ impl Engine<'_> {
|
|||||||
fn binary_xor<T: BitXor>(x: T, y: T) -> <T as BitXor>::Output {
|
fn binary_xor<T: BitXor>(x: T, y: T) -> <T as BitXor>::Output {
|
||||||
x ^ y
|
x ^ y
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checked left-shift
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
fn shl<T: Display + CheckedShl>(x: T, y: INT) -> Result<T, EvalAltResult> {
|
fn shl<T: Display + CheckedShl>(x: T, y: INT) -> Result<T, EvalAltResult> {
|
||||||
|
// Cannot shift by a negative number of bits
|
||||||
if y < 0 {
|
if y < 0 {
|
||||||
return Err(EvalAltResult::ErrorArithmetic(
|
return Err(EvalAltResult::ErrorArithmetic(
|
||||||
format!("Left-shift by a negative number: {} << {}", x, y),
|
format!("Left-shift by a negative number: {} << {}", x, y),
|
||||||
@ -204,13 +229,15 @@ impl Engine<'_> {
|
|||||||
|
|
||||||
CheckedShl::checked_shl(&x, y as u32).ok_or_else(|| {
|
CheckedShl::checked_shl(&x, y as u32).ok_or_else(|| {
|
||||||
EvalAltResult::ErrorArithmetic(
|
EvalAltResult::ErrorArithmetic(
|
||||||
format!("Left-shift overflow: {} << {}", x, y),
|
format!("Left-shift by too many bits: {} << {}", x, y),
|
||||||
Position::none(),
|
Position::none(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/// Checked right-shift
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
fn shr<T: Display + CheckedShr>(x: T, y: INT) -> Result<T, EvalAltResult> {
|
fn shr<T: Display + CheckedShr>(x: T, y: INT) -> Result<T, EvalAltResult> {
|
||||||
|
// Cannot shift by a negative number of bits
|
||||||
if y < 0 {
|
if y < 0 {
|
||||||
return Err(EvalAltResult::ErrorArithmetic(
|
return Err(EvalAltResult::ErrorArithmetic(
|
||||||
format!("Right-shift by a negative number: {} >> {}", x, y),
|
format!("Right-shift by a negative number: {} >> {}", x, y),
|
||||||
@ -220,44 +247,49 @@ impl Engine<'_> {
|
|||||||
|
|
||||||
CheckedShr::checked_shr(&x, y as u32).ok_or_else(|| {
|
CheckedShr::checked_shr(&x, y as u32).ok_or_else(|| {
|
||||||
EvalAltResult::ErrorArithmetic(
|
EvalAltResult::ErrorArithmetic(
|
||||||
format!("Right-shift overflow: {} % {}", x, y),
|
format!("Right-shift by too many bits: {} % {}", x, y),
|
||||||
Position::none(),
|
Position::none(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/// Unchecked left-shift - may panic if shifting by a negative number of bits
|
||||||
#[cfg(feature = "unchecked")]
|
#[cfg(feature = "unchecked")]
|
||||||
fn shl_u<T: Shl<T>>(x: T, y: T) -> <T as Shl<T>>::Output {
|
fn shl_u<T: Shl<T>>(x: T, y: T) -> <T as Shl<T>>::Output {
|
||||||
x.shl(y)
|
x.shl(y)
|
||||||
}
|
}
|
||||||
|
/// Unchecked right-shift - may panic if shifting by a negative number of bits
|
||||||
#[cfg(feature = "unchecked")]
|
#[cfg(feature = "unchecked")]
|
||||||
fn shr_u<T: Shr<T>>(x: T, y: T) -> <T as Shr<T>>::Output {
|
fn shr_u<T: Shr<T>>(x: T, y: T) -> <T as Shr<T>>::Output {
|
||||||
x.shr(y)
|
x.shr(y)
|
||||||
}
|
}
|
||||||
|
/// Checked modulo
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
fn modulo<T: Display + CheckedRem>(x: T, y: T) -> Result<T, EvalAltResult> {
|
fn modulo<T: Display + CheckedRem>(x: T, y: T) -> Result<T, EvalAltResult> {
|
||||||
x.checked_rem(&y).ok_or_else(|| {
|
x.checked_rem(&y).ok_or_else(|| {
|
||||||
EvalAltResult::ErrorArithmetic(
|
EvalAltResult::ErrorArithmetic(
|
||||||
format!("Modulo division overflow: {} % {}", x, y),
|
format!("Modulo division by zero or overflow: {} % {}", x, y),
|
||||||
Position::none(),
|
Position::none(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/// Unchecked modulo - may panic if dividing by zero
|
||||||
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
|
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
|
||||||
fn modulo_u<T: Rem>(x: T, y: T) -> <T as Rem>::Output {
|
fn modulo_u<T: Rem>(x: T, y: T) -> <T as Rem>::Output {
|
||||||
x % y
|
x % y
|
||||||
}
|
}
|
||||||
|
/// Checked power
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
fn pow_i_i_u(x: INT, y: INT) -> Result<INT, EvalAltResult> {
|
fn pow_i_i(x: INT, y: INT) -> Result<INT, EvalAltResult> {
|
||||||
#[cfg(not(feature = "only_i32"))]
|
#[cfg(not(feature = "only_i32"))]
|
||||||
{
|
{
|
||||||
if y > (u32::MAX as INT) {
|
if y > (u32::MAX as INT) {
|
||||||
Err(EvalAltResult::ErrorArithmetic(
|
Err(EvalAltResult::ErrorArithmetic(
|
||||||
format!("Power overflow: {} ~ {}", x, y),
|
format!("Integer raised to too large an index: {} ~ {}", x, y),
|
||||||
Position::none(),
|
Position::none(),
|
||||||
))
|
))
|
||||||
} else if y < 0 {
|
} else if y < 0 {
|
||||||
Err(EvalAltResult::ErrorArithmetic(
|
Err(EvalAltResult::ErrorArithmetic(
|
||||||
format!("Power underflow: {} ~ {}", x, y),
|
format!("Integer raised to a negative index: {} ~ {}", x, y),
|
||||||
Position::none(),
|
Position::none(),
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
@ -274,7 +306,7 @@ impl Engine<'_> {
|
|||||||
{
|
{
|
||||||
if y < 0 {
|
if y < 0 {
|
||||||
Err(EvalAltResult::ErrorArithmetic(
|
Err(EvalAltResult::ErrorArithmetic(
|
||||||
format!("Power underflow: {} ~ {}", x, y),
|
format!("Integer raised to a negative index: {} ~ {}", x, y),
|
||||||
Position::none(),
|
Position::none(),
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
@ -287,29 +319,34 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX)
|
||||||
#[cfg(feature = "unchecked")]
|
#[cfg(feature = "unchecked")]
|
||||||
fn pow_i_i(x: INT, y: INT) -> INT {
|
fn pow_i_i_u(x: INT, y: INT) -> INT {
|
||||||
x.pow(y as u32)
|
x.pow(y as u32)
|
||||||
}
|
}
|
||||||
|
/// Floating-point power - always well-defined
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
fn pow_f_f(x: FLOAT, y: FLOAT) -> FLOAT {
|
fn pow_f_f(x: FLOAT, y: FLOAT) -> FLOAT {
|
||||||
x.powf(y)
|
x.powf(y)
|
||||||
}
|
}
|
||||||
|
/// Checked power
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
fn pow_f_i_u(x: FLOAT, y: INT) -> Result<FLOAT, EvalAltResult> {
|
fn pow_f_i(x: FLOAT, y: INT) -> Result<FLOAT, EvalAltResult> {
|
||||||
|
// Raise to power that is larger than an i32
|
||||||
if y > (i32::MAX as INT) {
|
if y > (i32::MAX as INT) {
|
||||||
return Err(EvalAltResult::ErrorArithmetic(
|
return Err(EvalAltResult::ErrorArithmetic(
|
||||||
format!("Power overflow: {} ~ {}", x, y),
|
format!("Number raised to too large an index: {} ~ {}", x, y),
|
||||||
Position::none(),
|
Position::none(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(x.powi(y as i32))
|
Ok(x.powi(y as i32))
|
||||||
}
|
}
|
||||||
|
/// Unchecked power - may be incorrect if the power index is too high (> i32::MAX)
|
||||||
#[cfg(feature = "unchecked")]
|
#[cfg(feature = "unchecked")]
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
fn pow_f_i(x: FLOAT, y: INT) -> FLOAT {
|
fn pow_f_i_u(x: FLOAT, y: INT) -> FLOAT {
|
||||||
x.powi(y as i32)
|
x.powi(y as i32)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,8 +430,10 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `&&` and `||` are treated specially as they short-circuit
|
||||||
//reg_op!(self, "||", or, bool);
|
//reg_op!(self, "||", or, bool);
|
||||||
//reg_op!(self, "&&", and, bool);
|
//reg_op!(self, "&&", and, bool);
|
||||||
|
|
||||||
reg_op!(self, "|", or, bool);
|
reg_op!(self, "|", or, bool);
|
||||||
reg_op!(self, "&", and, bool);
|
reg_op!(self, "&", and, bool);
|
||||||
|
|
||||||
@ -448,18 +487,18 @@ impl Engine<'_> {
|
|||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
{
|
{
|
||||||
self.register_result_fn("~", pow_i_i_u);
|
self.register_result_fn("~", pow_i_i);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
self.register_result_fn("~", pow_f_i_u);
|
self.register_result_fn("~", pow_f_i);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "unchecked")]
|
#[cfg(feature = "unchecked")]
|
||||||
{
|
{
|
||||||
self.register_fn("~", pow_i_i);
|
self.register_fn("~", pow_i_i_u);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
self.register_fn("~", pow_f_i);
|
self.register_fn("~", pow_f_i_u);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -628,9 +667,6 @@ macro_rules! reg_fn2y {
|
|||||||
impl Engine<'_> {
|
impl Engine<'_> {
|
||||||
#[cfg(not(feature = "no_stdlib"))]
|
#[cfg(not(feature = "no_stdlib"))]
|
||||||
pub(crate) fn register_stdlib(&mut self) {
|
pub(crate) fn register_stdlib(&mut self) {
|
||||||
#[cfg(not(feature = "no_index"))]
|
|
||||||
use crate::fn_register::RegisterDynamicFn;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
{
|
{
|
||||||
// Advanced math functions
|
// Advanced math functions
|
||||||
|
235
src/optimize.rs
235
src/optimize.rs
@ -13,50 +13,54 @@ use crate::stdlib::{
|
|||||||
boxed::Box, vec,
|
boxed::Box, vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Level of optimization performed
|
/// Level of optimization performed.
|
||||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
|
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
|
||||||
pub enum OptimizationLevel {
|
pub enum OptimizationLevel {
|
||||||
/// No optimization performed
|
/// No optimization performed.
|
||||||
None,
|
None,
|
||||||
/// Only perform simple optimizations without evaluating functions
|
/// Only perform simple optimizations without evaluating functions.
|
||||||
Simple,
|
Simple,
|
||||||
/// Full optimizations performed, including evaluating functions.
|
/// Full optimizations performed, including evaluating functions.
|
||||||
/// Take care that this may cause side effects.
|
/// Take care that this may cause side effects as it essentially assumes that all functions are pure.
|
||||||
Full,
|
Full,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mutable state throughout an optimization pass.
|
||||||
struct State<'a> {
|
struct State<'a> {
|
||||||
|
/// Has the AST been changed during this pass?
|
||||||
changed: bool,
|
changed: bool,
|
||||||
|
/// Collection of constants to use for eager function evaluations.
|
||||||
constants: Vec<(String, Expr)>,
|
constants: Vec<(String, Expr)>,
|
||||||
engine: Option<&'a Engine<'a>>,
|
/// An `Engine` instance for eager function evaluation.
|
||||||
|
engine: &'a Engine<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State<'_> {
|
impl State<'_> {
|
||||||
pub fn new() -> Self {
|
/// Reset the state from dirty to clean.
|
||||||
State {
|
|
||||||
changed: false,
|
|
||||||
constants: vec![],
|
|
||||||
engine: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
self.changed = false;
|
self.changed = false;
|
||||||
}
|
}
|
||||||
|
/// Set the AST state to be dirty (i.e. changed).
|
||||||
pub fn set_dirty(&mut self) {
|
pub fn set_dirty(&mut self) {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
}
|
}
|
||||||
|
/// Is the AST dirty (i.e. changed)?
|
||||||
pub fn is_dirty(&self) -> bool {
|
pub fn is_dirty(&self) -> bool {
|
||||||
self.changed
|
self.changed
|
||||||
}
|
}
|
||||||
|
/// Does a constant exist?
|
||||||
pub fn contains_constant(&self, name: &str) -> bool {
|
pub fn contains_constant(&self, name: &str) -> bool {
|
||||||
self.constants.iter().any(|(n, _)| n == name)
|
self.constants.iter().any(|(n, _)| n == name)
|
||||||
}
|
}
|
||||||
|
/// Prune the list of constants back to a specified size.
|
||||||
pub fn restore_constants(&mut self, len: usize) {
|
pub fn restore_constants(&mut self, len: usize) {
|
||||||
self.constants.truncate(len)
|
self.constants.truncate(len)
|
||||||
}
|
}
|
||||||
|
/// Add a new constant to the list.
|
||||||
pub fn push_constant(&mut self, name: &str, value: Expr) {
|
pub fn push_constant(&mut self, name: &str, value: Expr) {
|
||||||
self.constants.push((name.to_string(), value))
|
self.constants.push((name.to_string(), value))
|
||||||
}
|
}
|
||||||
|
/// Look up a constant from the list.
|
||||||
pub fn find_constant(&self, name: &str) -> Option<&Expr> {
|
pub fn find_constant(&self, name: &str) -> Option<&Expr> {
|
||||||
for (n, expr) in self.constants.iter().rev() {
|
for (n, expr) in self.constants.iter().rev() {
|
||||||
if n == name {
|
if n == name {
|
||||||
@ -68,60 +72,68 @@ impl State<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Optimize a statement.
|
||||||
fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -> Stmt {
|
fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -> Stmt {
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::IfElse(expr, stmt1, None) if stmt1.is_noop() => {
|
// if expr { Noop }
|
||||||
|
Stmt::IfElse(expr, if_block, None) if matches!(*if_block, Stmt::Noop(_)) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
|
|
||||||
let pos = expr.position();
|
let pos = expr.position();
|
||||||
let expr = optimize_expr(*expr, state);
|
let expr = optimize_expr(*expr, state);
|
||||||
|
|
||||||
if matches!(expr, Expr::False(_) | Expr::True(_)) {
|
if preserve_result {
|
||||||
Stmt::Noop(stmt1.position())
|
// -> { expr, Noop }
|
||||||
|
Stmt::Block(vec![Stmt::Expr(Box::new(expr)), *if_block], pos)
|
||||||
} else {
|
} else {
|
||||||
let stmt = Stmt::Expr(Box::new(expr));
|
// -> expr
|
||||||
|
Stmt::Expr(Box::new(expr))
|
||||||
if preserve_result {
|
|
||||||
Stmt::Block(vec![stmt, *stmt1], pos)
|
|
||||||
} else {
|
|
||||||
stmt
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// if expr { if_block }
|
||||||
Stmt::IfElse(expr, stmt1, None) => match *expr {
|
Stmt::IfElse(expr, if_block, None) => match *expr {
|
||||||
|
// if false { if_block } -> Noop
|
||||||
Expr::False(pos) => {
|
Expr::False(pos) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
Stmt::Noop(pos)
|
Stmt::Noop(pos)
|
||||||
}
|
}
|
||||||
Expr::True(_) => optimize_stmt(*stmt1, state, true),
|
// if true { if_block } -> if_block
|
||||||
|
Expr::True(_) => optimize_stmt(*if_block, state, true),
|
||||||
|
// if expr { if_block }
|
||||||
expr => Stmt::IfElse(
|
expr => Stmt::IfElse(
|
||||||
Box::new(optimize_expr(expr, state)),
|
Box::new(optimize_expr(expr, state)),
|
||||||
Box::new(optimize_stmt(*stmt1, state, true)),
|
Box::new(optimize_stmt(*if_block, state, true)),
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
// if expr { if_block } else { else_block }
|
||||||
Stmt::IfElse(expr, stmt1, Some(stmt2)) => match *expr {
|
Stmt::IfElse(expr, if_block, Some(else_block)) => match *expr {
|
||||||
Expr::False(_) => optimize_stmt(*stmt2, state, true),
|
// if false { if_block } else { else_block } -> else_block
|
||||||
Expr::True(_) => optimize_stmt(*stmt1, state, true),
|
Expr::False(_) => optimize_stmt(*else_block, state, true),
|
||||||
|
// if true { if_block } else { else_block } -> if_block
|
||||||
|
Expr::True(_) => optimize_stmt(*if_block, state, true),
|
||||||
|
// if expr { if_block } else { else_block }
|
||||||
expr => Stmt::IfElse(
|
expr => Stmt::IfElse(
|
||||||
Box::new(optimize_expr(expr, state)),
|
Box::new(optimize_expr(expr, state)),
|
||||||
Box::new(optimize_stmt(*stmt1, state, true)),
|
Box::new(optimize_stmt(*if_block, state, true)),
|
||||||
match optimize_stmt(*stmt2, state, true) {
|
match optimize_stmt(*else_block, state, true) {
|
||||||
stmt if stmt.is_noop() => None,
|
stmt if matches!(stmt, Stmt::Noop(_)) => None, // Noop -> no else block
|
||||||
stmt => Some(Box::new(stmt)),
|
stmt => Some(Box::new(stmt)),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
// while expr { block }
|
||||||
Stmt::While(expr, stmt) => match *expr {
|
Stmt::While(expr, block) => match *expr {
|
||||||
|
// while false { block } -> Noop
|
||||||
Expr::False(pos) => {
|
Expr::False(pos) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
Stmt::Noop(pos)
|
Stmt::Noop(pos)
|
||||||
}
|
}
|
||||||
Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*stmt, state, false))),
|
// while true { block } -> loop { block }
|
||||||
expr => match optimize_stmt(*stmt, state, false) {
|
Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*block, state, false))),
|
||||||
|
// while expr { block }
|
||||||
|
expr => match optimize_stmt(*block, state, false) {
|
||||||
|
// while expr { break; } -> { expr; }
|
||||||
Stmt::Break(pos) => {
|
Stmt::Break(pos) => {
|
||||||
// Only a single break statement - turn into running the guard expression once
|
// Only a single break statement - turn into running the guard expression once
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
@ -131,48 +143,50 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
|
|||||||
}
|
}
|
||||||
Stmt::Block(statements, pos)
|
Stmt::Block(statements, pos)
|
||||||
}
|
}
|
||||||
|
// while expr { block }
|
||||||
stmt => Stmt::While(Box::new(optimize_expr(expr, state)), Box::new(stmt)),
|
stmt => Stmt::While(Box::new(optimize_expr(expr, state)), Box::new(stmt)),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Stmt::Loop(stmt) => match optimize_stmt(*stmt, state, false) {
|
// loop { block }
|
||||||
|
Stmt::Loop(block) => match optimize_stmt(*block, state, false) {
|
||||||
|
// loop { break; } -> Noop
|
||||||
Stmt::Break(pos) => {
|
Stmt::Break(pos) => {
|
||||||
// Only a single break statement
|
// Only a single break statement
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
Stmt::Noop(pos)
|
Stmt::Noop(pos)
|
||||||
}
|
}
|
||||||
|
// loop { block }
|
||||||
stmt => Stmt::Loop(Box::new(stmt)),
|
stmt => Stmt::Loop(Box::new(stmt)),
|
||||||
},
|
},
|
||||||
Stmt::For(id, expr, stmt) => Stmt::For(
|
// for id in expr { block }
|
||||||
|
Stmt::For(id, expr, block) => Stmt::For(
|
||||||
id,
|
id,
|
||||||
Box::new(optimize_expr(*expr, state)),
|
Box::new(optimize_expr(*expr, state)),
|
||||||
Box::new(match optimize_stmt(*stmt, state, false) {
|
Box::new(optimize_stmt(*block, state, false)),
|
||||||
Stmt::Break(pos) => {
|
|
||||||
// Only a single break statement
|
|
||||||
state.set_dirty();
|
|
||||||
Stmt::Noop(pos)
|
|
||||||
}
|
|
||||||
stmt => stmt,
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
|
// let id = expr;
|
||||||
Stmt::Let(id, Some(expr), pos) => {
|
Stmt::Let(id, Some(expr), pos) => {
|
||||||
Stmt::Let(id, Some(Box::new(optimize_expr(*expr, state))), pos)
|
Stmt::Let(id, Some(Box::new(optimize_expr(*expr, state))), pos)
|
||||||
}
|
}
|
||||||
|
// let id;
|
||||||
Stmt::Let(_, None, _) => stmt,
|
Stmt::Let(_, None, _) => stmt,
|
||||||
|
// { block }
|
||||||
|
Stmt::Block(block, pos) => {
|
||||||
|
let orig_len = block.len(); // Original number of statements in the block, for change detection
|
||||||
|
let orig_constants_len = state.constants.len(); // Original number of constants in the state, for restore later
|
||||||
|
|
||||||
Stmt::Block(statements, pos) => {
|
// Optimize each statement in the block
|
||||||
let orig_len = statements.len();
|
let mut result: Vec<_> = block
|
||||||
let orig_constants_len = state.constants.len();
|
.into_iter()
|
||||||
|
|
||||||
let mut result: Vec<_> = statements
|
|
||||||
.into_iter() // For each statement
|
|
||||||
.map(|stmt| {
|
.map(|stmt| {
|
||||||
if let Stmt::Const(name, value, pos) = stmt {
|
if let Stmt::Const(name, value, pos) = stmt {
|
||||||
|
// Add constant into the state
|
||||||
state.push_constant(&name, *value);
|
state.push_constant(&name, *value);
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
Stmt::Noop(pos) // No need to keep constants
|
Stmt::Noop(pos) // No need to keep constants
|
||||||
} else {
|
} else {
|
||||||
optimize_stmt(stmt, state, preserve_result) // Optimize the statement
|
// Optimize the statement
|
||||||
|
optimize_stmt(stmt, state, preserve_result)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@ -207,11 +221,12 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
|
|||||||
result.push(Stmt::Noop(pos))
|
result.push(Stmt::Noop(pos))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optimize all the statements again
|
||||||
result = result
|
result = result
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.rev()
|
.rev()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, s)| optimize_stmt(s, state, i == 0)) // Optimize all other statements again
|
.map(|(i, s)| optimize_stmt(s, state, i == 0))
|
||||||
.rev()
|
.rev()
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
@ -234,10 +249,12 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
|
|||||||
true
|
true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Change detection
|
||||||
if orig_len != result.len() {
|
if orig_len != result.len() {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pop the stack and remove all the local constants
|
||||||
state.restore_constants(orig_constants_len);
|
state.restore_constants(orig_constants_len);
|
||||||
|
|
||||||
match result[..] {
|
match result[..] {
|
||||||
@ -254,44 +271,54 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
|
|||||||
_ => Stmt::Block(result, pos),
|
_ => Stmt::Block(result, pos),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// expr;
|
||||||
Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, state))),
|
Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, state))),
|
||||||
|
// return expr;
|
||||||
Stmt::ReturnWithVal(Some(expr), is_return, pos) => {
|
Stmt::ReturnWithVal(Some(expr), is_return, pos) => {
|
||||||
Stmt::ReturnWithVal(Some(Box::new(optimize_expr(*expr, state))), is_return, pos)
|
Stmt::ReturnWithVal(Some(Box::new(optimize_expr(*expr, state))), is_return, pos)
|
||||||
}
|
}
|
||||||
|
// All other statements - skip
|
||||||
stmt => stmt,
|
stmt => stmt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Optimize an expression.
|
||||||
fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||||
|
// These keywords are handled specially
|
||||||
const SKIP_FUNC_KEYWORDS: [&str; 3] = [KEYWORD_PRINT, KEYWORD_DEBUG, KEYWORD_DUMP_AST];
|
const SKIP_FUNC_KEYWORDS: [&str; 3] = [KEYWORD_PRINT, KEYWORD_DEBUG, KEYWORD_DUMP_AST];
|
||||||
|
|
||||||
match expr {
|
match expr {
|
||||||
|
// ( stmt )
|
||||||
Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, state, true) {
|
Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, state, true) {
|
||||||
|
// ( Noop ) -> ()
|
||||||
Stmt::Noop(_) => {
|
Stmt::Noop(_) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
Expr::Unit(pos)
|
Expr::Unit(pos)
|
||||||
}
|
}
|
||||||
|
// ( expr ) -> expr
|
||||||
Stmt::Expr(expr) => {
|
Stmt::Expr(expr) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
*expr
|
*expr
|
||||||
}
|
}
|
||||||
|
// ( stmt )
|
||||||
stmt => Expr::Stmt(Box::new(stmt), pos),
|
stmt => Expr::Stmt(Box::new(stmt), pos),
|
||||||
},
|
},
|
||||||
Expr::Assignment(id1, expr1, pos1) => match *expr1 {
|
// id = expr
|
||||||
Expr::Assignment(id2, expr2, pos2) => match (*id1, *id2) {
|
Expr::Assignment(id, expr, pos) => match *expr {
|
||||||
(Expr::Variable(var1, _), Expr::Variable(var2, _)) if var1 == var2 => {
|
//id = id2 = expr2
|
||||||
|
Expr::Assignment(id2, expr2, pos2) => match (*id, *id2) {
|
||||||
|
// var = var = expr2 -> var = expr2
|
||||||
|
(Expr::Variable(var, _), Expr::Variable(var2, _)) if var == var2 => {
|
||||||
// Assignment to the same variable - fold
|
// Assignment to the same variable - fold
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
|
|
||||||
Expr::Assignment(
|
Expr::Assignment(
|
||||||
Box::new(Expr::Variable(var1, pos1)),
|
Box::new(Expr::Variable(var, pos)),
|
||||||
Box::new(optimize_expr(*expr2, state)),
|
Box::new(optimize_expr(*expr2, state)),
|
||||||
pos1,
|
pos,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
// id1 = id2 = expr2
|
||||||
(id1, id2) => Expr::Assignment(
|
(id1, id2) => Expr::Assignment(
|
||||||
Box::new(id1),
|
Box::new(id1),
|
||||||
Box::new(Expr::Assignment(
|
Box::new(Expr::Assignment(
|
||||||
@ -299,19 +326,23 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
|||||||
Box::new(optimize_expr(*expr2, state)),
|
Box::new(optimize_expr(*expr2, state)),
|
||||||
pos2,
|
pos2,
|
||||||
)),
|
)),
|
||||||
pos1,
|
pos,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
expr => Expr::Assignment(id1, Box::new(optimize_expr(expr, state)), pos1),
|
// id = expr
|
||||||
|
expr => Expr::Assignment(id, Box::new(optimize_expr(expr, state)), pos),
|
||||||
},
|
},
|
||||||
|
// lhs.rhs
|
||||||
Expr::Dot(lhs, rhs, pos) => Expr::Dot(
|
Expr::Dot(lhs, rhs, pos) => Expr::Dot(
|
||||||
Box::new(optimize_expr(*lhs, state)),
|
Box::new(optimize_expr(*lhs, state)),
|
||||||
Box::new(optimize_expr(*rhs, state)),
|
Box::new(optimize_expr(*rhs, state)),
|
||||||
pos,
|
pos,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// lhs[rhs]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) {
|
Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) {
|
||||||
|
// array[int]
|
||||||
(Expr::Array(mut items, _), Expr::IntegerConstant(i, _))
|
(Expr::Array(mut items, _), Expr::IntegerConstant(i, _))
|
||||||
if i >= 0 && (i as usize) < items.len() && items.iter().all(|x| x.is_pure()) =>
|
if i >= 0 && (i as usize) < items.len() && items.iter().all(|x| x.is_pure()) =>
|
||||||
{
|
{
|
||||||
@ -320,6 +351,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
|||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
items.remove(i as usize)
|
items.remove(i as usize)
|
||||||
}
|
}
|
||||||
|
// string[int]
|
||||||
(Expr::StringConstant(s, pos), Expr::IntegerConstant(i, _))
|
(Expr::StringConstant(s, pos), Expr::IntegerConstant(i, _))
|
||||||
if i >= 0 && (i as usize) < s.chars().count() =>
|
if i >= 0 && (i as usize) < s.chars().count() =>
|
||||||
{
|
{
|
||||||
@ -327,14 +359,14 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
|||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
Expr::CharConstant(s.chars().nth(i as usize).expect("should get char"), pos)
|
Expr::CharConstant(s.chars().nth(i as usize).expect("should get char"), pos)
|
||||||
}
|
}
|
||||||
|
// lhs[rhs]
|
||||||
(lhs, rhs) => Expr::Index(
|
(lhs, rhs) => Expr::Index(
|
||||||
Box::new(optimize_expr(lhs, state)),
|
Box::new(optimize_expr(lhs, state)),
|
||||||
Box::new(optimize_expr(rhs, state)),
|
Box::new(optimize_expr(rhs, state)),
|
||||||
pos,
|
pos,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
// [ items .. ]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Array(items, pos) => {
|
Expr::Array(items, pos) => {
|
||||||
let orig_len = items.len();
|
let orig_len = items.len();
|
||||||
@ -350,38 +382,47 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
|||||||
|
|
||||||
Expr::Array(items, pos)
|
Expr::Array(items, pos)
|
||||||
}
|
}
|
||||||
|
// lhs && rhs
|
||||||
Expr::And(lhs, rhs) => match (*lhs, *rhs) {
|
Expr::And(lhs, rhs) => match (*lhs, *rhs) {
|
||||||
|
// true && rhs -> rhs
|
||||||
(Expr::True(_), rhs) => {
|
(Expr::True(_), rhs) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
rhs
|
rhs
|
||||||
}
|
}
|
||||||
|
// false && rhs -> false
|
||||||
(Expr::False(pos), _) => {
|
(Expr::False(pos), _) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
Expr::False(pos)
|
Expr::False(pos)
|
||||||
}
|
}
|
||||||
|
// lhs && true -> lhs
|
||||||
(lhs, Expr::True(_)) => {
|
(lhs, Expr::True(_)) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
lhs
|
optimize_expr(lhs, state)
|
||||||
}
|
}
|
||||||
|
// lhs && rhs
|
||||||
(lhs, rhs) => Expr::And(
|
(lhs, rhs) => Expr::And(
|
||||||
Box::new(optimize_expr(lhs, state)),
|
Box::new(optimize_expr(lhs, state)),
|
||||||
Box::new(optimize_expr(rhs, state)),
|
Box::new(optimize_expr(rhs, state)),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
// lhs || rhs
|
||||||
Expr::Or(lhs, rhs) => match (*lhs, *rhs) {
|
Expr::Or(lhs, rhs) => match (*lhs, *rhs) {
|
||||||
|
// false || rhs -> rhs
|
||||||
(Expr::False(_), rhs) => {
|
(Expr::False(_), rhs) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
rhs
|
rhs
|
||||||
}
|
}
|
||||||
|
// true || rhs -> true
|
||||||
(Expr::True(pos), _) => {
|
(Expr::True(pos), _) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
Expr::True(pos)
|
Expr::True(pos)
|
||||||
}
|
}
|
||||||
|
// lhs || false
|
||||||
(lhs, Expr::False(_)) => {
|
(lhs, Expr::False(_)) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
lhs
|
optimize_expr(lhs, state)
|
||||||
}
|
}
|
||||||
|
// lhs || rhs
|
||||||
(lhs, rhs) => Expr::Or(
|
(lhs, rhs) => Expr::Or(
|
||||||
Box::new(optimize_expr(lhs, state)),
|
Box::new(optimize_expr(lhs, state)),
|
||||||
Box::new(optimize_expr(rhs, state)),
|
Box::new(optimize_expr(rhs, state)),
|
||||||
@ -392,24 +433,23 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
|||||||
Expr::FunctionCall(id, args, def_value, pos) if SKIP_FUNC_KEYWORDS.contains(&id.as_str())=>
|
Expr::FunctionCall(id, args, def_value, pos) if SKIP_FUNC_KEYWORDS.contains(&id.as_str())=>
|
||||||
Expr::FunctionCall(id, args, def_value, pos),
|
Expr::FunctionCall(id, args, def_value, pos),
|
||||||
|
|
||||||
// Actually call function to optimize it
|
// Eagerly call functions
|
||||||
Expr::FunctionCall(id, args, def_value, pos)
|
Expr::FunctionCall(id, args, def_value, pos)
|
||||||
if state.engine.map(|eng| eng.optimization_level == OptimizationLevel::Full).unwrap_or(false) // full optimizations
|
if state.engine.optimization_level == OptimizationLevel::Full // full optimizations
|
||||||
&& args.iter().all(|expr| expr.is_constant()) // all arguments are constants
|
&& args.iter().all(|expr| expr.is_constant()) // all arguments are constants
|
||||||
=> {
|
=> {
|
||||||
let engine = state.engine.expect("engine should be Some");
|
|
||||||
let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect();
|
let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect();
|
||||||
let call_args: FnCallArgs = arg_values.iter_mut().map(Dynamic::as_mut).collect();
|
let call_args: FnCallArgs = arg_values.iter_mut().map(Dynamic::as_mut).collect();
|
||||||
|
|
||||||
// Save the typename of the first argument if it is `type_of()`
|
// Save the typename of the first argument if it is `type_of()`
|
||||||
// This is to avoid `call_args` being passed into the closure
|
// This is to avoid `call_args` being passed into the closure
|
||||||
let arg_for_type_of = if id == KEYWORD_TYPE_OF && call_args.len() == 1 {
|
let arg_for_type_of = if id == KEYWORD_TYPE_OF && call_args.len() == 1 {
|
||||||
engine.map_type_name(call_args[0].type_name())
|
state.engine.map_type_name(call_args[0].type_name())
|
||||||
} else {
|
} else {
|
||||||
""
|
""
|
||||||
};
|
};
|
||||||
|
|
||||||
engine.call_ext_fn_raw(&id, call_args, pos).ok().map(|r|
|
state.engine.call_ext_fn_raw(&id, call_args, pos).ok().map(|r|
|
||||||
r.or_else(|| {
|
r.or_else(|| {
|
||||||
if !arg_for_type_of.is_empty() {
|
if !arg_for_type_of.is_empty() {
|
||||||
// Handle `type_of()`
|
// Handle `type_of()`
|
||||||
@ -425,10 +465,10 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
|||||||
})
|
})
|
||||||
).flatten().unwrap_or_else(|| Expr::FunctionCall(id, args, def_value, pos))
|
).flatten().unwrap_or_else(|| Expr::FunctionCall(id, args, def_value, pos))
|
||||||
}
|
}
|
||||||
// Optimize the function call arguments
|
// id(args ..) -> optimize function call arguments
|
||||||
Expr::FunctionCall(id, args, def_value, pos) =>
|
Expr::FunctionCall(id, args, def_value, pos) =>
|
||||||
Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos),
|
Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos),
|
||||||
|
// constant-name
|
||||||
Expr::Variable(ref name, _) if state.contains_constant(name) => {
|
Expr::Variable(ref name, _) if state.contains_constant(name) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
|
|
||||||
@ -438,28 +478,25 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
|||||||
.expect("should find constant in scope!")
|
.expect("should find constant in scope!")
|
||||||
.clone()
|
.clone()
|
||||||
}
|
}
|
||||||
|
// All other expressions - skip
|
||||||
expr => expr,
|
expr => expr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn optimize<'a>(
|
pub(crate) fn optimize<'a>(statements: Vec<Stmt>, engine: &Engine<'a>, scope: &Scope) -> Vec<Stmt> {
|
||||||
statements: Vec<Stmt>,
|
|
||||||
engine: Option<&Engine<'a>>,
|
|
||||||
scope: &Scope,
|
|
||||||
) -> Vec<Stmt> {
|
|
||||||
// If optimization level is None then skip optimizing
|
// If optimization level is None then skip optimizing
|
||||||
if engine
|
if engine.optimization_level == OptimizationLevel::None {
|
||||||
.map(|eng| eng.optimization_level == OptimizationLevel::None)
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
return statements;
|
return statements;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up the state
|
// Set up the state
|
||||||
let mut state = State::new();
|
let mut state = State {
|
||||||
state.engine = engine;
|
changed: false,
|
||||||
|
constants: vec![],
|
||||||
|
engine,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add constants from the scope into the state
|
||||||
scope
|
scope
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|ScopeEntry { var_type, expr, .. }| {
|
.filter(|ScopeEntry { var_type, expr, .. }| {
|
||||||
@ -476,9 +513,9 @@ pub(crate) fn optimize<'a>(
|
|||||||
|
|
||||||
let orig_constants_len = state.constants.len();
|
let orig_constants_len = state.constants.len();
|
||||||
|
|
||||||
// Optimization loop
|
|
||||||
let mut result = statements;
|
let mut result = statements;
|
||||||
|
|
||||||
|
// Optimization loop
|
||||||
loop {
|
loop {
|
||||||
state.reset();
|
state.reset();
|
||||||
state.restore_constants(orig_constants_len);
|
state.restore_constants(orig_constants_len);
|
||||||
@ -496,7 +533,7 @@ pub(crate) fn optimize<'a>(
|
|||||||
} else {
|
} else {
|
||||||
// Keep all variable declarations at this level
|
// Keep all variable declarations at this level
|
||||||
// and always keep the last return value
|
// and always keep the last return value
|
||||||
let keep = stmt.is_var() || i == num_statements - 1;
|
let keep = matches!(stmt, Stmt::Let(_, _, _)) || i == num_statements - 1;
|
||||||
|
|
||||||
optimize_stmt(stmt, &mut state, keep)
|
optimize_stmt(stmt, &mut state, keep)
|
||||||
}
|
}
|
||||||
@ -521,6 +558,7 @@ pub(crate) fn optimize<'a>(
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Optimize an AST.
|
||||||
pub fn optimize_ast(
|
pub fn optimize_ast(
|
||||||
engine: &Engine,
|
engine: &Engine,
|
||||||
scope: &Scope,
|
scope: &Scope,
|
||||||
@ -530,8 +568,8 @@ pub fn optimize_ast(
|
|||||||
AST(
|
AST(
|
||||||
match engine.optimization_level {
|
match engine.optimization_level {
|
||||||
OptimizationLevel::None => statements,
|
OptimizationLevel::None => statements,
|
||||||
OptimizationLevel::Simple => optimize(statements, None, &scope),
|
OptimizationLevel::Simple => optimize(statements, engine, &scope),
|
||||||
OptimizationLevel::Full => optimize(statements, Some(engine), &scope),
|
OptimizationLevel::Full => optimize(statements, engine, &scope),
|
||||||
},
|
},
|
||||||
functions
|
functions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -540,14 +578,21 @@ pub fn optimize_ast(
|
|||||||
OptimizationLevel::None => (),
|
OptimizationLevel::None => (),
|
||||||
OptimizationLevel::Simple | OptimizationLevel::Full => {
|
OptimizationLevel::Simple | OptimizationLevel::Full => {
|
||||||
let pos = fn_def.body.position();
|
let pos = fn_def.body.position();
|
||||||
let mut body = optimize(vec![fn_def.body], None, &Scope::new());
|
|
||||||
|
// Optimize the function body
|
||||||
|
let mut body = optimize(vec![fn_def.body], engine, &Scope::new());
|
||||||
|
|
||||||
|
// {} -> Noop
|
||||||
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
|
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
|
||||||
|
// { return val; } -> val
|
||||||
Stmt::ReturnWithVal(Some(val), ReturnType::Return, _) => {
|
Stmt::ReturnWithVal(Some(val), ReturnType::Return, _) => {
|
||||||
Stmt::Expr(val)
|
Stmt::Expr(val)
|
||||||
}
|
}
|
||||||
|
// { return; } -> ()
|
||||||
Stmt::ReturnWithVal(None, ReturnType::Return, pos) => {
|
Stmt::ReturnWithVal(None, ReturnType::Return, pos) => {
|
||||||
Stmt::Expr(Box::new(Expr::Unit(pos)))
|
Stmt::Expr(Box::new(Expr::Unit(pos)))
|
||||||
}
|
}
|
||||||
|
// All others
|
||||||
stmt => stmt,
|
stmt => stmt,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
218
src/parser.rs
218
src/parser.rs
@ -29,13 +29,13 @@ use crate::stdlib::{
|
|||||||
#[cfg(not(feature = "only_i32"))]
|
#[cfg(not(feature = "only_i32"))]
|
||||||
pub type INT = i64;
|
pub type INT = i64;
|
||||||
|
|
||||||
/// The system integer type
|
/// The system integer type.
|
||||||
///
|
///
|
||||||
/// If the `only_i32` feature is not enabled, this will be `i64` instead.
|
/// If the `only_i32` feature is not enabled, this will be `i64` instead.
|
||||||
#[cfg(feature = "only_i32")]
|
#[cfg(feature = "only_i32")]
|
||||||
pub type INT = i32;
|
pub type INT = i32;
|
||||||
|
|
||||||
/// The system floating-point type
|
/// The system floating-point type.
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
pub type FLOAT = f64;
|
pub type FLOAT = f64;
|
||||||
|
|
||||||
@ -45,7 +45,9 @@ type PERR = ParseErrorType;
|
|||||||
/// A location (line number + character position) in the input script.
|
/// A location (line number + character position) in the input script.
|
||||||
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
|
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
|
||||||
pub struct Position {
|
pub struct Position {
|
||||||
|
/// Line number - 0 = none, MAX = EOF
|
||||||
line: usize,
|
line: usize,
|
||||||
|
/// Character position - 0 = BOL, MAX = EOF
|
||||||
pos: usize,
|
pos: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,45 +164,69 @@ impl fmt::Debug for Position {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct AST(pub(crate) Vec<Stmt>, pub(crate) Vec<Arc<FnDef>>);
|
pub struct AST(pub(crate) Vec<Stmt>, pub(crate) Vec<Arc<FnDef>>);
|
||||||
|
|
||||||
|
/// A script-function definition.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FnDef {
|
pub struct FnDef {
|
||||||
|
/// Function name.
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
/// Names of function parameters.
|
||||||
pub params: Vec<String>,
|
pub params: Vec<String>,
|
||||||
|
/// Function body.
|
||||||
pub body: Stmt,
|
pub body: Stmt,
|
||||||
|
/// Position of the function definition.
|
||||||
pub pos: Position,
|
pub pos: Position,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FnDef {
|
impl FnDef {
|
||||||
|
/// Function to order two FnDef records, for binary search.
|
||||||
pub fn compare(&self, name: &str, params_len: usize) -> Ordering {
|
pub fn compare(&self, name: &str, params_len: usize) -> Ordering {
|
||||||
|
// First order by name
|
||||||
match self.name.as_str().cmp(name) {
|
match self.name.as_str().cmp(name) {
|
||||||
|
// Then by number of parameters
|
||||||
Ordering::Equal => self.params.len().cmp(¶ms_len),
|
Ordering::Equal => self.params.len().cmp(¶ms_len),
|
||||||
order => order,
|
order => order,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `return`/`throw` statement.
|
||||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||||
pub enum ReturnType {
|
pub enum ReturnType {
|
||||||
|
/// `return` statement.
|
||||||
Return,
|
Return,
|
||||||
|
/// `throw` statement.
|
||||||
Exception,
|
Exception,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A statement.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Stmt {
|
pub enum Stmt {
|
||||||
|
/// No-op.
|
||||||
Noop(Position),
|
Noop(Position),
|
||||||
|
/// if expr { stmt } else { stmt }
|
||||||
IfElse(Box<Expr>, Box<Stmt>, Option<Box<Stmt>>),
|
IfElse(Box<Expr>, Box<Stmt>, Option<Box<Stmt>>),
|
||||||
|
/// while expr { stmt }
|
||||||
While(Box<Expr>, Box<Stmt>),
|
While(Box<Expr>, Box<Stmt>),
|
||||||
|
/// loop { stmt }
|
||||||
Loop(Box<Stmt>),
|
Loop(Box<Stmt>),
|
||||||
|
/// for id in expr { stmt }
|
||||||
For(String, Box<Expr>, Box<Stmt>),
|
For(String, Box<Expr>, Box<Stmt>),
|
||||||
|
/// let id = expr
|
||||||
Let(String, Option<Box<Expr>>, Position),
|
Let(String, Option<Box<Expr>>, Position),
|
||||||
|
/// const id = expr
|
||||||
Const(String, Box<Expr>, Position),
|
Const(String, Box<Expr>, Position),
|
||||||
|
/// { stmt; ... }
|
||||||
Block(Vec<Stmt>, Position),
|
Block(Vec<Stmt>, Position),
|
||||||
|
/// { stmt }
|
||||||
Expr(Box<Expr>),
|
Expr(Box<Expr>),
|
||||||
|
/// break
|
||||||
Break(Position),
|
Break(Position),
|
||||||
|
/// `return`/`throw`
|
||||||
ReturnWithVal(Option<Box<Expr>>, ReturnType, Position),
|
ReturnWithVal(Option<Box<Expr>>, ReturnType, Position),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stmt {
|
impl Stmt {
|
||||||
|
/// Get the `Position` of this statement.
|
||||||
pub fn position(&self) -> Position {
|
pub fn position(&self) -> Position {
|
||||||
match self {
|
match self {
|
||||||
Stmt::Noop(pos)
|
Stmt::Noop(pos)
|
||||||
@ -214,18 +240,7 @@ impl Stmt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_noop(&self) -> bool {
|
/// Is this statement self-terminated (i.e. no need for a semicolon terminator)?
|
||||||
matches!(self, Stmt::Noop(_))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_op(&self) -> bool {
|
|
||||||
!matches!(self, Stmt::Noop(_))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_var(&self) -> bool {
|
|
||||||
matches!(self, Stmt::Let(_, _, _))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_self_terminated(&self) -> bool {
|
pub fn is_self_terminated(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Stmt::Noop(_)
|
Stmt::Noop(_)
|
||||||
@ -243,6 +258,7 @@ impl Stmt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Is this statement _pure_?
|
||||||
pub fn is_pure(&self) -> bool {
|
pub fn is_pure(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Stmt::Noop(_) => true,
|
Stmt::Noop(_) => true,
|
||||||
@ -262,31 +278,54 @@ impl Stmt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An expression.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Expr {
|
pub enum Expr {
|
||||||
|
/// Integer constant.
|
||||||
IntegerConstant(INT, Position),
|
IntegerConstant(INT, Position),
|
||||||
|
/// Floating-point constant.
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
FloatConstant(FLOAT, Position),
|
FloatConstant(FLOAT, Position),
|
||||||
Variable(String, Position),
|
/// Character constant.
|
||||||
Property(String, Position),
|
|
||||||
CharConstant(char, Position),
|
CharConstant(char, Position),
|
||||||
|
/// String constant.
|
||||||
StringConstant(String, Position),
|
StringConstant(String, Position),
|
||||||
|
/// Variable access.
|
||||||
|
Variable(String, Position),
|
||||||
|
/// Property access.
|
||||||
|
Property(String, Position),
|
||||||
|
/// { stmt }
|
||||||
Stmt(Box<Stmt>, Position),
|
Stmt(Box<Stmt>, Position),
|
||||||
|
/// func(expr, ... )
|
||||||
FunctionCall(String, Vec<Expr>, Option<Dynamic>, Position),
|
FunctionCall(String, Vec<Expr>, Option<Dynamic>, Position),
|
||||||
|
/// expr = expr
|
||||||
Assignment(Box<Expr>, Box<Expr>, Position),
|
Assignment(Box<Expr>, Box<Expr>, Position),
|
||||||
|
/// lhs.rhs
|
||||||
Dot(Box<Expr>, Box<Expr>, Position),
|
Dot(Box<Expr>, Box<Expr>, Position),
|
||||||
|
/// expr[expr]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Index(Box<Expr>, Box<Expr>, Position),
|
Index(Box<Expr>, Box<Expr>, Position),
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
/// [ expr, ... ]
|
||||||
Array(Vec<Expr>, Position),
|
Array(Vec<Expr>, Position),
|
||||||
|
/// lhs && rhs
|
||||||
And(Box<Expr>, Box<Expr>),
|
And(Box<Expr>, Box<Expr>),
|
||||||
|
/// lhs || rhs
|
||||||
Or(Box<Expr>, Box<Expr>),
|
Or(Box<Expr>, Box<Expr>),
|
||||||
|
/// true
|
||||||
True(Position),
|
True(Position),
|
||||||
|
/// false
|
||||||
False(Position),
|
False(Position),
|
||||||
|
/// ()
|
||||||
Unit(Position),
|
Unit(Position),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Expr {
|
impl Expr {
|
||||||
|
/// Get the `Dynamic` value of a constant expression.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics when the expression is not constant.
|
||||||
pub fn get_constant_value(&self) -> Dynamic {
|
pub fn get_constant_value(&self) -> Dynamic {
|
||||||
match self {
|
match self {
|
||||||
Expr::IntegerConstant(i, _) => i.into_dynamic(),
|
Expr::IntegerConstant(i, _) => i.into_dynamic(),
|
||||||
@ -310,6 +349,11 @@ impl Expr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the display value of a constant expression.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics when the expression is not constant.
|
||||||
pub fn get_constant_str(&self) -> String {
|
pub fn get_constant_str(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Expr::IntegerConstant(i, _) => i.to_string(),
|
Expr::IntegerConstant(i, _) => i.to_string(),
|
||||||
@ -329,6 +373,7 @@ impl Expr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the `Position` of the expression.
|
||||||
pub fn position(&self) -> Position {
|
pub fn position(&self) -> Position {
|
||||||
match self {
|
match self {
|
||||||
Expr::IntegerConstant(_, pos)
|
Expr::IntegerConstant(_, pos)
|
||||||
@ -358,7 +403,7 @@ impl Expr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is this expression pure?
|
/// Is the expression pure?
|
||||||
///
|
///
|
||||||
/// A pure expression has no side effects.
|
/// A pure expression has no side effects.
|
||||||
pub fn is_pure(&self) -> bool {
|
pub fn is_pure(&self) -> bool {
|
||||||
@ -377,6 +422,7 @@ impl Expr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Is the expression a constant?
|
||||||
pub fn is_constant(&self) -> bool {
|
pub fn is_constant(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Expr::IntegerConstant(_, _)
|
Expr::IntegerConstant(_, _)
|
||||||
@ -389,6 +435,7 @@ impl Expr {
|
|||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Expr::FloatConstant(_, _) => true,
|
Expr::FloatConstant(_, _) => true,
|
||||||
|
|
||||||
|
// An array literal is constant if all items are constant
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Array(expressions, _) => expressions.iter().all(Expr::is_constant),
|
Expr::Array(expressions, _) => expressions.iter().all(Expr::is_constant),
|
||||||
|
|
||||||
@ -397,6 +444,7 @@ impl Expr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tokens.
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub enum Token {
|
pub enum Token {
|
||||||
IntegerConstant(INT),
|
IntegerConstant(INT),
|
||||||
@ -470,6 +518,7 @@ pub enum Token {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Token {
|
impl Token {
|
||||||
|
/// Get the syntax of the token.
|
||||||
pub fn syntax<'a>(&'a self) -> Cow<'a, str> {
|
pub fn syntax<'a>(&'a self) -> Cow<'a, str> {
|
||||||
use self::Token::*;
|
use self::Token::*;
|
||||||
|
|
||||||
@ -550,8 +599,8 @@ impl Token {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if another operator is after these, it's probably an unary operator
|
// If another operator is after these, it's probably an unary operator
|
||||||
// not sure about fn's name
|
// (not sure about fn name).
|
||||||
pub fn is_next_unary(&self) -> bool {
|
pub fn is_next_unary(&self) -> bool {
|
||||||
use self::Token::*;
|
use self::Token::*;
|
||||||
|
|
||||||
@ -612,6 +661,7 @@ impl Token {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the precedence number of the token.
|
||||||
pub fn precedence(&self) -> u8 {
|
pub fn precedence(&self) -> u8 {
|
||||||
match self {
|
match self {
|
||||||
Self::Equals
|
Self::Equals
|
||||||
@ -652,8 +702,10 @@ impl Token {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Does an expression bind to the right (instead of left)?
|
||||||
pub fn is_bind_right(&self) -> bool {
|
pub fn is_bind_right(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
|
// Assignments bind to the right
|
||||||
Self::Equals
|
Self::Equals
|
||||||
| Self::PlusAssign
|
| Self::PlusAssign
|
||||||
| Self::MinusAssign
|
| Self::MinusAssign
|
||||||
@ -667,6 +719,7 @@ impl Token {
|
|||||||
| Self::ModuloAssign
|
| Self::ModuloAssign
|
||||||
| Self::PowerOfAssign => true,
|
| Self::PowerOfAssign => true,
|
||||||
|
|
||||||
|
// Property access binds to the right
|
||||||
Self::Period => true,
|
Self::Period => true,
|
||||||
|
|
||||||
_ => false,
|
_ => false,
|
||||||
@ -674,24 +727,36 @@ impl Token {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An iterator on a `Token` stream.
|
||||||
pub struct TokenIterator<'a> {
|
pub struct TokenIterator<'a> {
|
||||||
|
/// The last token seen.
|
||||||
last: Token,
|
last: Token,
|
||||||
|
/// Current position.
|
||||||
pos: Position,
|
pos: Position,
|
||||||
|
/// The input characters stream.
|
||||||
char_stream: Peekable<Chars<'a>>,
|
char_stream: Peekable<Chars<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TokenIterator<'a> {
|
impl<'a> TokenIterator<'a> {
|
||||||
|
/// Move the current position one character ahead.
|
||||||
fn advance(&mut self) {
|
fn advance(&mut self) {
|
||||||
self.pos.advance();
|
self.pos.advance();
|
||||||
}
|
}
|
||||||
|
/// Move the current position back one character.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if already at the beginning of a line - cannot rewind to the previous line.
|
||||||
fn rewind(&mut self) {
|
fn rewind(&mut self) {
|
||||||
self.pos.rewind();
|
self.pos.rewind();
|
||||||
}
|
}
|
||||||
|
/// Move the current position to the next line.
|
||||||
fn new_line(&mut self) {
|
fn new_line(&mut self) {
|
||||||
self.pos.new_line()
|
self.pos.new_line()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_string_const(
|
/// Parse a string literal wrapped by `enclosing_char`.
|
||||||
|
pub fn parse_string_literal(
|
||||||
&mut self,
|
&mut self,
|
||||||
enclosing_char: char,
|
enclosing_char: char,
|
||||||
) -> Result<String, (LexError, Position)> {
|
) -> Result<String, (LexError, Position)> {
|
||||||
@ -703,25 +768,31 @@ impl<'a> TokenIterator<'a> {
|
|||||||
self.advance();
|
self.advance();
|
||||||
|
|
||||||
match next_char.ok_or((LERR::UnterminatedString, Position::eof()))? {
|
match next_char.ok_or((LERR::UnterminatedString, Position::eof()))? {
|
||||||
|
// \...
|
||||||
'\\' if escape.is_empty() => {
|
'\\' if escape.is_empty() => {
|
||||||
escape.push('\\');
|
escape.push('\\');
|
||||||
}
|
}
|
||||||
|
// \\
|
||||||
'\\' if !escape.is_empty() => {
|
'\\' if !escape.is_empty() => {
|
||||||
escape.clear();
|
escape.clear();
|
||||||
result.push('\\');
|
result.push('\\');
|
||||||
}
|
}
|
||||||
|
// \t
|
||||||
't' if !escape.is_empty() => {
|
't' if !escape.is_empty() => {
|
||||||
escape.clear();
|
escape.clear();
|
||||||
result.push('\t');
|
result.push('\t');
|
||||||
}
|
}
|
||||||
|
// \n
|
||||||
'n' if !escape.is_empty() => {
|
'n' if !escape.is_empty() => {
|
||||||
escape.clear();
|
escape.clear();
|
||||||
result.push('\n');
|
result.push('\n');
|
||||||
}
|
}
|
||||||
|
// \r
|
||||||
'r' if !escape.is_empty() => {
|
'r' if !escape.is_empty() => {
|
||||||
escape.clear();
|
escape.clear();
|
||||||
result.push('\r');
|
result.push('\r');
|
||||||
}
|
}
|
||||||
|
// \x??, \u????, \U????????
|
||||||
ch @ 'x' | ch @ 'u' | ch @ 'U' if !escape.is_empty() => {
|
ch @ 'x' | ch @ 'u' | ch @ 'U' if !escape.is_empty() => {
|
||||||
let mut seq = escape.clone();
|
let mut seq = escape.clone();
|
||||||
seq.push(ch);
|
seq.push(ch);
|
||||||
@ -754,15 +825,25 @@ impl<'a> TokenIterator<'a> {
|
|||||||
.ok_or_else(|| (LERR::MalformedEscapeSequence(seq), self.pos))?,
|
.ok_or_else(|| (LERR::MalformedEscapeSequence(seq), self.pos))?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// \{enclosing_char} - escaped
|
||||||
ch if enclosing_char == ch && !escape.is_empty() => result.push(ch),
|
ch if enclosing_char == ch && !escape.is_empty() => result.push(ch),
|
||||||
|
|
||||||
|
// Close wrapper
|
||||||
ch if enclosing_char == ch && escape.is_empty() => break,
|
ch if enclosing_char == ch && escape.is_empty() => break,
|
||||||
|
|
||||||
|
// Unknown escape sequence
|
||||||
_ if !escape.is_empty() => {
|
_ if !escape.is_empty() => {
|
||||||
return Err((LERR::MalformedEscapeSequence(escape), self.pos))
|
return Err((LERR::MalformedEscapeSequence(escape), self.pos))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cannot have new-lines inside string literals
|
||||||
'\n' => {
|
'\n' => {
|
||||||
self.rewind();
|
self.rewind();
|
||||||
return Err((LERR::UnterminatedString, self.pos));
|
return Err((LERR::UnterminatedString, self.pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// All other characters
|
||||||
ch => {
|
ch => {
|
||||||
escape.clear();
|
escape.clear();
|
||||||
result.push(ch);
|
result.push(ch);
|
||||||
@ -773,6 +854,7 @@ impl<'a> TokenIterator<'a> {
|
|||||||
Ok(result.iter().collect())
|
Ok(result.iter().collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the next token.
|
||||||
fn inner_next(&mut self) -> Option<(Token, Position)> {
|
fn inner_next(&mut self) -> Option<(Token, Position)> {
|
||||||
let mut negated = false;
|
let mut negated = false;
|
||||||
|
|
||||||
@ -782,7 +864,9 @@ impl<'a> TokenIterator<'a> {
|
|||||||
let pos = self.pos;
|
let pos = self.pos;
|
||||||
|
|
||||||
match c {
|
match c {
|
||||||
|
// \n
|
||||||
'\n' => self.new_line(),
|
'\n' => self.new_line(),
|
||||||
|
// digit ...
|
||||||
'0'..='9' => {
|
'0'..='9' => {
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
let mut radix_base: Option<u32> = None;
|
let mut radix_base: Option<u32> = None;
|
||||||
@ -811,6 +895,7 @@ impl<'a> TokenIterator<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 0x????, 0o????, 0b????
|
||||||
ch @ 'x' | ch @ 'X' | ch @ 'o' | ch @ 'O' | ch @ 'b' | ch @ 'B'
|
ch @ 'x' | ch @ 'X' | ch @ 'o' | ch @ 'O' | ch @ 'b' | ch @ 'B'
|
||||||
if c == '0' =>
|
if c == '0' =>
|
||||||
{
|
{
|
||||||
@ -860,6 +945,7 @@ impl<'a> TokenIterator<'a> {
|
|||||||
result.insert(0, '-');
|
result.insert(0, '-');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse number
|
||||||
if let Some(radix) = radix_base {
|
if let Some(radix) = radix_base {
|
||||||
let out: String = result.iter().skip(2).filter(|&&c| c != '_').collect();
|
let out: String = result.iter().skip(2).filter(|&&c| c != '_').collect();
|
||||||
|
|
||||||
@ -875,6 +961,7 @@ impl<'a> TokenIterator<'a> {
|
|||||||
let out: String = result.iter().filter(|&&c| c != '_').collect();
|
let out: String = result.iter().filter(|&&c| c != '_').collect();
|
||||||
let num = INT::from_str(&out).map(Token::IntegerConstant);
|
let num = INT::from_str(&out).map(Token::IntegerConstant);
|
||||||
|
|
||||||
|
// If integer parsing is unnecessary, try float instead
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
let num = num.or_else(|_| FLOAT::from_str(&out).map(Token::FloatConstant));
|
let num = num.or_else(|_| FLOAT::from_str(&out).map(Token::FloatConstant));
|
||||||
|
|
||||||
@ -886,6 +973,7 @@ impl<'a> TokenIterator<'a> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// letter ...
|
||||||
'A'..='Z' | 'a'..='z' | '_' => {
|
'A'..='Z' | 'a'..='z' | '_' => {
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
result.push(c);
|
result.push(c);
|
||||||
@ -930,13 +1018,15 @@ impl<'a> TokenIterator<'a> {
|
|||||||
pos,
|
pos,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
// " - string literal
|
||||||
'"' => {
|
'"' => {
|
||||||
return match self.parse_string_const('"') {
|
return match self.parse_string_literal('"') {
|
||||||
Ok(out) => Some((Token::StringConst(out), pos)),
|
Ok(out) => Some((Token::StringConst(out), pos)),
|
||||||
Err(e) => Some((Token::LexError(e.0), e.1)),
|
Err(e) => Some((Token::LexError(e.0), e.1)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'\'' => match self.parse_string_const('\'') {
|
// ' - character literal
|
||||||
|
'\'' => match self.parse_string_literal('\'') {
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
let mut chars = result.chars();
|
let mut chars = result.chars();
|
||||||
|
|
||||||
@ -955,16 +1045,22 @@ impl<'a> TokenIterator<'a> {
|
|||||||
}
|
}
|
||||||
Err(e) => return Some((Token::LexError(e.0), e.1)),
|
Err(e) => return Some((Token::LexError(e.0), e.1)),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Braces
|
||||||
'{' => return Some((Token::LeftBrace, pos)),
|
'{' => return Some((Token::LeftBrace, pos)),
|
||||||
'}' => return Some((Token::RightBrace, pos)),
|
'}' => return Some((Token::RightBrace, pos)),
|
||||||
|
|
||||||
|
// Parentheses
|
||||||
'(' => return Some((Token::LeftParen, pos)),
|
'(' => return Some((Token::LeftParen, pos)),
|
||||||
')' => return Some((Token::RightParen, pos)),
|
')' => return Some((Token::RightParen, pos)),
|
||||||
|
|
||||||
|
// Indexing
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
'[' => return Some((Token::LeftBracket, pos)),
|
'[' => return Some((Token::LeftBracket, pos)),
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
']' => return Some((Token::RightBracket, pos)),
|
']' => return Some((Token::RightBracket, pos)),
|
||||||
|
|
||||||
|
// Operators
|
||||||
'+' => {
|
'+' => {
|
||||||
return Some((
|
return Some((
|
||||||
match self.char_stream.peek() {
|
match self.char_stream.peek() {
|
||||||
@ -1228,6 +1324,7 @@ impl<'a> Iterator for TokenIterator<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tokenize an input text stream.
|
||||||
pub fn lex(input: &str) -> TokenIterator<'_> {
|
pub fn lex(input: &str) -> TokenIterator<'_> {
|
||||||
TokenIterator {
|
TokenIterator {
|
||||||
last: Token::LexError(LERR::InputError("".into())),
|
last: Token::LexError(LERR::InputError("".into())),
|
||||||
@ -1236,6 +1333,7 @@ pub fn lex(input: &str) -> TokenIterator<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse ( expr )
|
||||||
fn parse_paren_expr<'a>(
|
fn parse_paren_expr<'a>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
begin: Position,
|
begin: Position,
|
||||||
@ -1252,13 +1350,16 @@ fn parse_paren_expr<'a>(
|
|||||||
let expr = parse_expr(input)?;
|
let expr = parse_expr(input)?;
|
||||||
|
|
||||||
match input.next() {
|
match input.next() {
|
||||||
|
// ( xxx )
|
||||||
Some((Token::RightParen, _)) => Ok(expr),
|
Some((Token::RightParen, _)) => Ok(expr),
|
||||||
|
// ( xxx ???
|
||||||
Some((_, pos)) => {
|
Some((_, pos)) => {
|
||||||
return Err(ParseError::new(
|
return Err(ParseError::new(
|
||||||
PERR::MissingRightParen("a matching ( in the expression".into()),
|
PERR::MissingRightParen("a matching ( in the expression".into()),
|
||||||
pos,
|
pos,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
// ( xxx
|
||||||
None => Err(ParseError::new(
|
None => Err(ParseError::new(
|
||||||
PERR::MissingRightParen("a matching ( in the expression".into()),
|
PERR::MissingRightParen("a matching ( in the expression".into()),
|
||||||
Position::eof(),
|
Position::eof(),
|
||||||
@ -1266,6 +1367,7 @@ fn parse_paren_expr<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a function call.
|
||||||
fn parse_call_expr<'a>(
|
fn parse_call_expr<'a>(
|
||||||
id: String,
|
id: String,
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
@ -1273,6 +1375,7 @@ fn parse_call_expr<'a>(
|
|||||||
) -> Result<Expr, ParseError> {
|
) -> Result<Expr, ParseError> {
|
||||||
let mut args_expr_list = Vec::new();
|
let mut args_expr_list = Vec::new();
|
||||||
|
|
||||||
|
// id()
|
||||||
if let (Token::RightParen, _) = input.peek().ok_or_else(|| {
|
if let (Token::RightParen, _) = input.peek().ok_or_else(|| {
|
||||||
ParseError::new(
|
ParseError::new(
|
||||||
PERR::MissingRightParen(format!(
|
PERR::MissingRightParen(format!(
|
||||||
@ -1318,6 +1421,7 @@ fn parse_call_expr<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse an indexing expression.s
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
fn parse_index_expr<'a>(
|
fn parse_index_expr<'a>(
|
||||||
lhs: Box<Expr>,
|
lhs: Box<Expr>,
|
||||||
@ -1328,6 +1432,7 @@ fn parse_index_expr<'a>(
|
|||||||
|
|
||||||
// Check type of indexing - must be integer
|
// Check type of indexing - must be integer
|
||||||
match &idx_expr {
|
match &idx_expr {
|
||||||
|
// lhs[int]
|
||||||
Expr::IntegerConstant(i, pos) if *i < 0 => {
|
Expr::IntegerConstant(i, pos) if *i < 0 => {
|
||||||
return Err(ParseError::new(
|
return Err(ParseError::new(
|
||||||
PERR::MalformedIndexExpr(format!(
|
PERR::MalformedIndexExpr(format!(
|
||||||
@ -1337,6 +1442,7 @@ fn parse_index_expr<'a>(
|
|||||||
*pos,
|
*pos,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
// lhs[float]
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Expr::FloatConstant(_, pos) => {
|
Expr::FloatConstant(_, pos) => {
|
||||||
return Err(ParseError::new(
|
return Err(ParseError::new(
|
||||||
@ -1344,6 +1450,7 @@ fn parse_index_expr<'a>(
|
|||||||
*pos,
|
*pos,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
// lhs[char]
|
||||||
Expr::CharConstant(_, pos) => {
|
Expr::CharConstant(_, pos) => {
|
||||||
return Err(ParseError::new(
|
return Err(ParseError::new(
|
||||||
PERR::MalformedIndexExpr(
|
PERR::MalformedIndexExpr(
|
||||||
@ -1352,18 +1459,21 @@ fn parse_index_expr<'a>(
|
|||||||
*pos,
|
*pos,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
// lhs[string]
|
||||||
Expr::StringConstant(_, pos) => {
|
Expr::StringConstant(_, pos) => {
|
||||||
return Err(ParseError::new(
|
return Err(ParseError::new(
|
||||||
PERR::MalformedIndexExpr("Array access expects integer index, not a string".into()),
|
PERR::MalformedIndexExpr("Array access expects integer index, not a string".into()),
|
||||||
*pos,
|
*pos,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
// lhs[??? = ??? ], lhs[()]
|
||||||
Expr::Assignment(_, _, pos) | Expr::Unit(pos) => {
|
Expr::Assignment(_, _, pos) | Expr::Unit(pos) => {
|
||||||
return Err(ParseError::new(
|
return Err(ParseError::new(
|
||||||
PERR::MalformedIndexExpr("Array access expects integer index, not ()".into()),
|
PERR::MalformedIndexExpr("Array access expects integer index, not ()".into()),
|
||||||
*pos,
|
*pos,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
// lhs[??? && ???], lhs[??? || ???]
|
||||||
Expr::And(lhs, _) | Expr::Or(lhs, _) => {
|
Expr::And(lhs, _) | Expr::Or(lhs, _) => {
|
||||||
return Err(ParseError::new(
|
return Err(ParseError::new(
|
||||||
PERR::MalformedIndexExpr(
|
PERR::MalformedIndexExpr(
|
||||||
@ -1372,6 +1482,7 @@ fn parse_index_expr<'a>(
|
|||||||
lhs.position(),
|
lhs.position(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
// lhs[true], lhs[false]
|
||||||
Expr::True(pos) | Expr::False(pos) => {
|
Expr::True(pos) | Expr::False(pos) => {
|
||||||
return Err(ParseError::new(
|
return Err(ParseError::new(
|
||||||
PERR::MalformedIndexExpr(
|
PERR::MalformedIndexExpr(
|
||||||
@ -1380,6 +1491,7 @@ fn parse_index_expr<'a>(
|
|||||||
*pos,
|
*pos,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
// All other expressions
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1403,29 +1515,35 @@ fn parse_index_expr<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse an expression that begins with an identifier.
|
||||||
fn parse_ident_expr<'a>(
|
fn parse_ident_expr<'a>(
|
||||||
id: String,
|
id: String,
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
begin: Position,
|
begin: Position,
|
||||||
) -> Result<Expr, ParseError> {
|
) -> Result<Expr, ParseError> {
|
||||||
match input.peek() {
|
match input.peek() {
|
||||||
|
// id(...) - function call
|
||||||
Some((Token::LeftParen, _)) => {
|
Some((Token::LeftParen, _)) => {
|
||||||
input.next();
|
input.next();
|
||||||
parse_call_expr(id, input, begin)
|
parse_call_expr(id, input, begin)
|
||||||
}
|
}
|
||||||
|
// id[...] - indexing
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Some((Token::LeftBracket, pos)) => {
|
Some((Token::LeftBracket, pos)) => {
|
||||||
let pos = *pos;
|
let pos = *pos;
|
||||||
input.next();
|
input.next();
|
||||||
parse_index_expr(Box::new(Expr::Variable(id, begin)), input, pos)
|
parse_index_expr(Box::new(Expr::Variable(id, begin)), input, pos)
|
||||||
}
|
}
|
||||||
|
// id - variable
|
||||||
Some(_) => Ok(Expr::Variable(id, begin)),
|
Some(_) => Ok(Expr::Variable(id, begin)),
|
||||||
|
// EOF
|
||||||
None => Ok(Expr::Variable(id, Position::eof())),
|
None => Ok(Expr::Variable(id, Position::eof())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse an array literal.
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
fn parse_array_expr<'a>(
|
fn parse_array_literal<'a>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
begin: Position,
|
begin: Position,
|
||||||
) -> Result<Expr, ParseError> {
|
) -> Result<Expr, ParseError> {
|
||||||
@ -1472,8 +1590,9 @@ fn parse_array_expr<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a primary expression.
|
||||||
fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> {
|
fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> {
|
||||||
// Block statement as expression
|
// { - block statement as expression
|
||||||
match input.peek() {
|
match input.peek() {
|
||||||
Some((Token::LeftBrace, pos)) => {
|
Some((Token::LeftBrace, pos)) => {
|
||||||
let pos = *pos;
|
let pos = *pos;
|
||||||
@ -1510,7 +1629,7 @@ fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pa
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
(Token::LeftBracket, pos) => {
|
(Token::LeftBracket, pos) => {
|
||||||
can_be_indexed = true;
|
can_be_indexed = true;
|
||||||
parse_array_expr(input, pos)
|
parse_array_literal(input, pos)
|
||||||
}
|
}
|
||||||
(Token::True, pos) => Ok(Expr::True(pos)),
|
(Token::True, pos) => Ok(Expr::True(pos)),
|
||||||
(Token::False, pos) => Ok(Expr::False(pos)),
|
(Token::False, pos) => Ok(Expr::False(pos)),
|
||||||
@ -1534,11 +1653,13 @@ fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pa
|
|||||||
Ok(root_expr)
|
Ok(root_expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a potential unary operator.
|
||||||
fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> {
|
fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> {
|
||||||
match input
|
match input
|
||||||
.peek()
|
.peek()
|
||||||
.ok_or_else(|| ParseError::new(PERR::InputPastEndOfFile, Position::eof()))?
|
.ok_or_else(|| ParseError::new(PERR::InputPastEndOfFile, Position::eof()))?
|
||||||
{
|
{
|
||||||
|
// -expr
|
||||||
(Token::UnaryMinus, pos) => {
|
(Token::UnaryMinus, pos) => {
|
||||||
let pos = *pos;
|
let pos = *pos;
|
||||||
|
|
||||||
@ -1571,10 +1692,12 @@ fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pars
|
|||||||
expr => Ok(Expr::FunctionCall("-".into(), vec![expr], None, pos)),
|
expr => Ok(Expr::FunctionCall("-".into(), vec![expr], None, pos)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// +expr
|
||||||
(Token::UnaryPlus, _) => {
|
(Token::UnaryPlus, _) => {
|
||||||
input.next();
|
input.next();
|
||||||
parse_unary(input)
|
parse_unary(input)
|
||||||
}
|
}
|
||||||
|
// !expr
|
||||||
(Token::Bang, pos) => {
|
(Token::Bang, pos) => {
|
||||||
let pos = *pos;
|
let pos = *pos;
|
||||||
|
|
||||||
@ -1587,34 +1710,41 @@ fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pars
|
|||||||
pos,
|
pos,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
// All other tokens
|
||||||
_ => parse_primary(input),
|
_ => parse_primary(input),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse an assignment.
|
||||||
fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseError> {
|
fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseError> {
|
||||||
|
// Is the LHS in a valid format?
|
||||||
fn valid_assignment_chain(expr: &Expr, is_top: bool) -> Option<ParseError> {
|
fn valid_assignment_chain(expr: &Expr, is_top: bool) -> Option<ParseError> {
|
||||||
match expr {
|
match expr {
|
||||||
|
// var
|
||||||
Expr::Variable(_, _) => {
|
Expr::Variable(_, _) => {
|
||||||
assert!(is_top, "property expected but gets variable");
|
assert!(is_top, "property expected but gets variable");
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
// property
|
||||||
Expr::Property(_, _) => {
|
Expr::Property(_, _) => {
|
||||||
assert!(!is_top, "variable expected but gets property");
|
assert!(!is_top, "variable expected but gets property");
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// var[...]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(idx_lhs, _, _) if matches!(idx_lhs.as_ref(), &Expr::Variable(_, _)) => {
|
Expr::Index(idx_lhs, _, _) if matches!(idx_lhs.as_ref(), &Expr::Variable(_, _)) => {
|
||||||
assert!(is_top, "property expected but gets variable");
|
assert!(is_top, "property expected but gets variable");
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
// property[...]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(idx_lhs, _, _) if matches!(idx_lhs.as_ref(), &Expr::Property(_, _)) => {
|
Expr::Index(idx_lhs, _, _) if matches!(idx_lhs.as_ref(), &Expr::Property(_, _)) => {
|
||||||
assert!(!is_top, "variable expected but gets property");
|
assert!(!is_top, "variable expected but gets property");
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// idx_lhs[...]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(idx_lhs, _, pos) => Some(ParseError::new(
|
Expr::Index(idx_lhs, _, pos) => Some(ParseError::new(
|
||||||
match idx_lhs.as_ref() {
|
match idx_lhs.as_ref() {
|
||||||
@ -1624,22 +1754,27 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseEr
|
|||||||
*pos,
|
*pos,
|
||||||
)),
|
)),
|
||||||
|
|
||||||
|
// dot_lhs.dot_rhs
|
||||||
Expr::Dot(dot_lhs, dot_rhs, _) => match dot_lhs.as_ref() {
|
Expr::Dot(dot_lhs, dot_rhs, _) => match dot_lhs.as_ref() {
|
||||||
|
// var.dot_rhs
|
||||||
Expr::Variable(_, _) if is_top => valid_assignment_chain(dot_rhs, false),
|
Expr::Variable(_, _) if is_top => valid_assignment_chain(dot_rhs, false),
|
||||||
|
// property.dot_rhs
|
||||||
Expr::Property(_, _) if !is_top => valid_assignment_chain(dot_rhs, false),
|
Expr::Property(_, _) if !is_top => valid_assignment_chain(dot_rhs, false),
|
||||||
|
// var[...]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(idx_lhs, _, _)
|
Expr::Index(idx_lhs, _, _)
|
||||||
if matches!(idx_lhs.as_ref(), &Expr::Variable(_, _)) && is_top =>
|
if matches!(idx_lhs.as_ref(), &Expr::Variable(_, _)) && is_top =>
|
||||||
{
|
{
|
||||||
valid_assignment_chain(dot_rhs, false)
|
valid_assignment_chain(dot_rhs, false)
|
||||||
}
|
}
|
||||||
|
// property[...]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(idx_lhs, _, _)
|
Expr::Index(idx_lhs, _, _)
|
||||||
if matches!(idx_lhs.as_ref(), &Expr::Property(_, _)) && !is_top =>
|
if matches!(idx_lhs.as_ref(), &Expr::Property(_, _)) && !is_top =>
|
||||||
{
|
{
|
||||||
valid_assignment_chain(dot_rhs, false)
|
valid_assignment_chain(dot_rhs, false)
|
||||||
}
|
}
|
||||||
|
// idx_lhs[...]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(idx_lhs, _, _) => Some(ParseError::new(
|
Expr::Index(idx_lhs, _, _) => Some(ParseError::new(
|
||||||
ParseErrorType::AssignmentToCopy,
|
ParseErrorType::AssignmentToCopy,
|
||||||
@ -1662,6 +1797,7 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseEr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse an operator-assignment expression.
|
||||||
fn parse_op_assignment(
|
fn parse_op_assignment(
|
||||||
function: &str,
|
function: &str,
|
||||||
lhs: Expr,
|
lhs: Expr,
|
||||||
@ -1677,6 +1813,7 @@ fn parse_op_assignment(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a binary expression.
|
||||||
fn parse_binary_op<'a>(
|
fn parse_binary_op<'a>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
parent_precedence: u8,
|
parent_precedence: u8,
|
||||||
@ -1830,11 +1967,13 @@ fn parse_binary_op<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse an expression.
|
||||||
fn parse_expr<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> {
|
fn parse_expr<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> {
|
||||||
let lhs = parse_unary(input)?;
|
let lhs = parse_unary(input)?;
|
||||||
parse_binary_op(input, 1, lhs)
|
parse_binary_op(input, 1, lhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse an if statement.
|
||||||
fn parse_if<'a>(
|
fn parse_if<'a>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
breakable: bool,
|
breakable: bool,
|
||||||
@ -1859,6 +1998,7 @@ fn parse_if<'a>(
|
|||||||
Ok(Stmt::IfElse(Box::new(guard), Box::new(if_body), else_body))
|
Ok(Stmt::IfElse(Box::new(guard), Box::new(if_body), else_body))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a while loop.
|
||||||
fn parse_while<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
fn parse_while<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
||||||
input.next();
|
input.next();
|
||||||
|
|
||||||
@ -1868,6 +2008,7 @@ fn parse_while<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Pars
|
|||||||
Ok(Stmt::While(Box::new(guard), Box::new(body)))
|
Ok(Stmt::While(Box::new(guard), Box::new(body)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a loop statement.
|
||||||
fn parse_loop<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
fn parse_loop<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
||||||
input.next();
|
input.next();
|
||||||
|
|
||||||
@ -1876,6 +2017,7 @@ fn parse_loop<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Parse
|
|||||||
Ok(Stmt::Loop(Box::new(body)))
|
Ok(Stmt::Loop(Box::new(body)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a for loop.
|
||||||
fn parse_for<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
fn parse_for<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
||||||
input.next();
|
input.next();
|
||||||
|
|
||||||
@ -1904,7 +2046,8 @@ fn parse_for<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseE
|
|||||||
Ok(Stmt::For(name, Box::new(expr), Box::new(body)))
|
Ok(Stmt::For(name, Box::new(expr), Box::new(body)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_var<'a>(
|
/// Parse a variable definition statement.
|
||||||
|
fn parse_let<'a>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
var_type: VariableType,
|
var_type: VariableType,
|
||||||
) -> Result<Stmt, ParseError> {
|
) -> Result<Stmt, ParseError> {
|
||||||
@ -1945,6 +2088,7 @@ fn parse_var<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a statement block.
|
||||||
fn parse_block<'a>(
|
fn parse_block<'a>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
breakable: bool,
|
breakable: bool,
|
||||||
@ -2005,10 +2149,12 @@ fn parse_block<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse an expression as a statement.
|
||||||
fn parse_expr_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
fn parse_expr_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
||||||
Ok(Stmt::Expr(Box::new(parse_expr(input)?)))
|
Ok(Stmt::Expr(Box::new(parse_expr(input)?)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a single statement.
|
||||||
fn parse_stmt<'a>(
|
fn parse_stmt<'a>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
breakable: bool,
|
breakable: bool,
|
||||||
@ -2040,14 +2186,14 @@ fn parse_stmt<'a>(
|
|||||||
input.next();
|
input.next();
|
||||||
|
|
||||||
match input.peek() {
|
match input.peek() {
|
||||||
// return/throw at EOF
|
// `return`/`throw` at EOF
|
||||||
None => Ok(Stmt::ReturnWithVal(None, return_type, Position::eof())),
|
None => Ok(Stmt::ReturnWithVal(None, return_type, Position::eof())),
|
||||||
// return; or throw;
|
// `return;` or `throw;`
|
||||||
Some((Token::SemiColon, pos)) => {
|
Some((Token::SemiColon, pos)) => {
|
||||||
let pos = *pos;
|
let pos = *pos;
|
||||||
Ok(Stmt::ReturnWithVal(None, return_type, pos))
|
Ok(Stmt::ReturnWithVal(None, return_type, pos))
|
||||||
}
|
}
|
||||||
// return or throw with expression
|
// `return` or `throw` with expression
|
||||||
Some((_, pos)) => {
|
Some((_, pos)) => {
|
||||||
let pos = *pos;
|
let pos = *pos;
|
||||||
Ok(Stmt::ReturnWithVal(
|
Ok(Stmt::ReturnWithVal(
|
||||||
@ -2059,12 +2205,13 @@ fn parse_stmt<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
(Token::LeftBrace, _) => parse_block(input, breakable),
|
(Token::LeftBrace, _) => parse_block(input, breakable),
|
||||||
(Token::Let, _) => parse_var(input, VariableType::Normal),
|
(Token::Let, _) => parse_let(input, VariableType::Normal),
|
||||||
(Token::Const, _) => parse_var(input, VariableType::Constant),
|
(Token::Const, _) => parse_let(input, VariableType::Constant),
|
||||||
_ => parse_expr_stmt(input),
|
_ => parse_expr_stmt(input),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a function definition.
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseError> {
|
fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseError> {
|
||||||
let pos = input
|
let pos = input
|
||||||
@ -2166,6 +2313,7 @@ fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseE
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse the global level statements.
|
||||||
fn parse_global_level<'a, 'e>(
|
fn parse_global_level<'a, 'e>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
) -> Result<(Vec<Stmt>, Vec<FnDef>), ParseError> {
|
) -> Result<(Vec<Stmt>, Vec<FnDef>), ParseError> {
|
||||||
@ -2214,6 +2362,7 @@ fn parse_global_level<'a, 'e>(
|
|||||||
Ok((statements, functions))
|
Ok((statements, functions))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Run the parser on an input stream, returning an AST.
|
||||||
pub fn parse<'a, 'e>(
|
pub fn parse<'a, 'e>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
engine: &Engine<'e>,
|
engine: &Engine<'e>,
|
||||||
@ -2229,6 +2378,9 @@ pub fn parse<'a, 'e>(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Map a `Dynamic` value to an expression.
|
||||||
|
///
|
||||||
|
/// Returns Some(expression) if conversion is successful. Otherwise None.
|
||||||
pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> (Option<Expr>, Dynamic) {
|
pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> (Option<Expr>, Dynamic) {
|
||||||
if value.is::<INT>() {
|
if value.is::<INT>() {
|
||||||
let value2 = value.clone();
|
let value2 = value.clone();
|
||||||
|
Loading…
Reference in New Issue
Block a user