Add code comments.
This commit is contained in:
parent
dca2a927b5
commit
ef8d428f42
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 std::{
|
||||
any::{type_name, TypeId},
|
||||
any::{type_name, Any as StdAny, TypeId},
|
||||
fmt,
|
||||
};
|
||||
|
||||
@ -12,7 +12,7 @@ pub type Variant = dyn Any;
|
||||
pub type Dynamic = Box<Variant>;
|
||||
|
||||
/// A trait covering any type.
|
||||
pub trait Any: std::any::Any {
|
||||
pub trait Any: StdAny {
|
||||
/// Get the `TypeId` of this type.
|
||||
fn type_id(&self) -> TypeId;
|
||||
|
||||
@ -22,12 +22,12 @@ pub trait Any: std::any::Any {
|
||||
/// Convert into `Dynamic`.
|
||||
fn into_dynamic(&self) -> Dynamic;
|
||||
|
||||
/// This type may only be implemented by `rhai`.
|
||||
/// This trait may only be implemented by `rhai`.
|
||||
#[doc(hidden)]
|
||||
fn _closed(&self) -> _Private;
|
||||
}
|
||||
|
||||
impl<T: Clone + std::any::Any + ?Sized> Any for T {
|
||||
impl<T: Clone + StdAny + ?Sized> Any for T {
|
||||
fn type_id(&self) -> TypeId {
|
||||
TypeId::of::<T>()
|
||||
}
|
||||
@ -90,7 +90,7 @@ pub trait AnyExt: Sized {
|
||||
/// Get a copy of a `Dynamic` value as a specific type.
|
||||
fn downcast<T: Any + Clone>(self) -> Result<Box<T>, Self>;
|
||||
|
||||
/// This type may only be implemented by `rhai`.
|
||||
/// This trait may only be implemented by `rhai`.
|
||||
#[doc(hidden)]
|
||||
fn _closed(&self) -> _Private;
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use crate::any::Any;
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
use crate::engine::Array;
|
||||
use crate::engine::Engine;
|
||||
use crate::fn_register::{RegisterFn, RegisterResultFn};
|
||||
use crate::fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
|
||||
use crate::parser::{Position, INT};
|
||||
use crate::result::EvalAltResult;
|
||||
|
||||
@ -52,6 +52,7 @@ macro_rules! reg_op_result1 {
|
||||
impl Engine<'_> {
|
||||
/// Register the core built-in library.
|
||||
pub(crate) fn register_core_lib(&mut self) {
|
||||
/// Checked add
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
fn add<T: Display + CheckedAdd>(x: T, y: T) -> Result<T, EvalAltResult> {
|
||||
x.checked_add(&y).ok_or_else(|| {
|
||||
@ -61,6 +62,7 @@ impl Engine<'_> {
|
||||
)
|
||||
})
|
||||
}
|
||||
/// Checked subtract
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
fn sub<T: Display + CheckedSub>(x: T, y: T) -> Result<T, EvalAltResult> {
|
||||
x.checked_sub(&y).ok_or_else(|| {
|
||||
@ -70,6 +72,7 @@ impl Engine<'_> {
|
||||
)
|
||||
})
|
||||
}
|
||||
/// Checked multiply
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
fn mul<T: Display + CheckedMul>(x: T, y: T) -> Result<T, EvalAltResult> {
|
||||
x.checked_mul(&y).ok_or_else(|| {
|
||||
@ -79,11 +82,13 @@ impl Engine<'_> {
|
||||
)
|
||||
})
|
||||
}
|
||||
/// Checked divide
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
fn div<T>(x: T, y: T) -> Result<T, EvalAltResult>
|
||||
where
|
||||
T: Display + CheckedDiv + PartialEq + Zero,
|
||||
{
|
||||
// Detect division by zero
|
||||
if y == T::zero() {
|
||||
return Err(EvalAltResult::ErrorArithmetic(
|
||||
format!("Division by zero: {} / {}", x, y),
|
||||
@ -98,6 +103,7 @@ impl Engine<'_> {
|
||||
)
|
||||
})
|
||||
}
|
||||
/// Checked negative - e.g. -(i32::MIN) will overflow i32::MAX
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
fn neg<T: Display + CheckedNeg>(x: T) -> Result<T, EvalAltResult> {
|
||||
x.checked_neg().ok_or_else(|| {
|
||||
@ -107,6 +113,7 @@ impl Engine<'_> {
|
||||
)
|
||||
})
|
||||
}
|
||||
/// Checked absolute
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
fn abs<T: Display + CheckedNeg + PartialOrd + Zero>(x: T) -> Result<T, EvalAltResult> {
|
||||
// FIX - We don't use Signed::abs() here because, contrary to documentation, it panics
|
||||
@ -122,26 +129,32 @@ impl Engine<'_> {
|
||||
})
|
||||
}
|
||||
}
|
||||
/// Unchecked add - may panic on overflow
|
||||
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
|
||||
fn add_u<T: Add>(x: T, y: T) -> <T as Add>::Output {
|
||||
x + y
|
||||
}
|
||||
/// Unchecked subtract - may panic on underflow
|
||||
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
|
||||
fn sub_u<T: Sub>(x: T, y: T) -> <T as Sub>::Output {
|
||||
x - y
|
||||
}
|
||||
/// Unchecked multiply - may panic on overflow
|
||||
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
|
||||
fn mul_u<T: Mul>(x: T, y: T) -> <T as Mul>::Output {
|
||||
x * y
|
||||
}
|
||||
/// Unchecked divide - may panic when dividing by zero
|
||||
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
|
||||
fn div_u<T: Div>(x: T, y: T) -> <T as Div>::Output {
|
||||
x / y
|
||||
}
|
||||
/// Unchecked negative - may panic on overflow
|
||||
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
|
||||
fn neg_u<T: Neg>(x: T) -> <T as Neg>::Output {
|
||||
-x
|
||||
}
|
||||
/// Unchecked absolute - may panic on overflow
|
||||
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
|
||||
fn abs_u<T>(x: T) -> <T as Neg>::Output
|
||||
where
|
||||
@ -154,6 +167,9 @@ impl Engine<'_> {
|
||||
x.into()
|
||||
}
|
||||
}
|
||||
|
||||
// Comparison operators
|
||||
|
||||
fn lt<T: PartialOrd>(x: T, y: T) -> bool {
|
||||
x < y
|
||||
}
|
||||
@ -172,6 +188,9 @@ impl Engine<'_> {
|
||||
fn ne<T: PartialEq>(x: T, y: T) -> bool {
|
||||
x != y
|
||||
}
|
||||
|
||||
// Logic operators
|
||||
|
||||
fn and(x: bool, y: bool) -> bool {
|
||||
x && y
|
||||
}
|
||||
@ -181,6 +200,9 @@ impl Engine<'_> {
|
||||
fn not(x: bool) -> bool {
|
||||
!x
|
||||
}
|
||||
|
||||
// Bit operators
|
||||
|
||||
fn binary_and<T: BitAnd>(x: T, y: T) -> <T as BitAnd>::Output {
|
||||
x & y
|
||||
}
|
||||
@ -190,8 +212,11 @@ impl Engine<'_> {
|
||||
fn binary_xor<T: BitXor>(x: T, y: T) -> <T as BitXor>::Output {
|
||||
x ^ y
|
||||
}
|
||||
|
||||
/// Checked left-shift
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
fn shl<T: Display + CheckedShl>(x: T, y: INT) -> Result<T, EvalAltResult> {
|
||||
// Cannot shift by a negative number of bits
|
||||
if y < 0 {
|
||||
return Err(EvalAltResult::ErrorArithmetic(
|
||||
format!("Left-shift by a negative number: {} << {}", x, y),
|
||||
@ -201,13 +226,15 @@ impl Engine<'_> {
|
||||
|
||||
CheckedShl::checked_shl(&x, y as u32).ok_or_else(|| {
|
||||
EvalAltResult::ErrorArithmetic(
|
||||
format!("Left-shift overflow: {} << {}", x, y),
|
||||
format!("Left-shift by too many bits: {} << {}", x, y),
|
||||
Position::none(),
|
||||
)
|
||||
})
|
||||
}
|
||||
/// Checked right-shift
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
fn shr<T: Display + CheckedShr>(x: T, y: INT) -> Result<T, EvalAltResult> {
|
||||
// Cannot shift by a negative number of bits
|
||||
if y < 0 {
|
||||
return Err(EvalAltResult::ErrorArithmetic(
|
||||
format!("Right-shift by a negative number: {} >> {}", x, y),
|
||||
@ -217,44 +244,49 @@ impl Engine<'_> {
|
||||
|
||||
CheckedShr::checked_shr(&x, y as u32).ok_or_else(|| {
|
||||
EvalAltResult::ErrorArithmetic(
|
||||
format!("Right-shift overflow: {} % {}", x, y),
|
||||
format!("Right-shift by too many bits: {} % {}", x, y),
|
||||
Position::none(),
|
||||
)
|
||||
})
|
||||
}
|
||||
/// Unchecked left-shift - may panic if shifting by a negative number of bits
|
||||
#[cfg(feature = "unchecked")]
|
||||
fn shl_u<T: Shl<T>>(x: T, y: T) -> <T as Shl<T>>::Output {
|
||||
x.shl(y)
|
||||
}
|
||||
/// Unchecked right-shift - may panic if shifting by a negative number of bits
|
||||
#[cfg(feature = "unchecked")]
|
||||
fn shr_u<T: Shr<T>>(x: T, y: T) -> <T as Shr<T>>::Output {
|
||||
x.shr(y)
|
||||
}
|
||||
/// Checked modulo
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
fn modulo<T: Display + CheckedRem>(x: T, y: T) -> Result<T, EvalAltResult> {
|
||||
x.checked_rem(&y).ok_or_else(|| {
|
||||
EvalAltResult::ErrorArithmetic(
|
||||
format!("Modulo division overflow: {} % {}", x, y),
|
||||
format!("Modulo division by zero or overflow: {} % {}", x, y),
|
||||
Position::none(),
|
||||
)
|
||||
})
|
||||
}
|
||||
/// Unchecked modulo - may panic if dividing by zero
|
||||
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
|
||||
fn modulo_u<T: Rem>(x: T, y: T) -> <T as Rem>::Output {
|
||||
x % y
|
||||
}
|
||||
/// Checked power
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
fn pow_i_i_u(x: INT, y: INT) -> Result<INT, EvalAltResult> {
|
||||
fn pow_i_i(x: INT, y: INT) -> Result<INT, EvalAltResult> {
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
{
|
||||
if y > (u32::MAX as INT) {
|
||||
Err(EvalAltResult::ErrorArithmetic(
|
||||
format!("Power overflow: {} ~ {}", x, y),
|
||||
format!("Integer raised to too large an index: {} ~ {}", x, y),
|
||||
Position::none(),
|
||||
))
|
||||
} else if y < 0 {
|
||||
Err(EvalAltResult::ErrorArithmetic(
|
||||
format!("Power underflow: {} ~ {}", x, y),
|
||||
format!("Integer raised to a negative index: {} ~ {}", x, y),
|
||||
Position::none(),
|
||||
))
|
||||
} else {
|
||||
@ -271,7 +303,7 @@ impl Engine<'_> {
|
||||
{
|
||||
if y < 0 {
|
||||
Err(EvalAltResult::ErrorArithmetic(
|
||||
format!("Power underflow: {} ~ {}", x, y),
|
||||
format!("Integer raised to a negative index: {} ~ {}", x, y),
|
||||
Position::none(),
|
||||
))
|
||||
} else {
|
||||
@ -284,29 +316,34 @@ impl Engine<'_> {
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX)
|
||||
#[cfg(feature = "unchecked")]
|
||||
fn pow_i_i(x: INT, y: INT) -> INT {
|
||||
fn pow_i_i_u(x: INT, y: INT) -> INT {
|
||||
x.pow(y as u32)
|
||||
}
|
||||
/// Floating-point power - always well-defined
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
fn pow_f_f(x: FLOAT, y: FLOAT) -> FLOAT {
|
||||
x.powf(y)
|
||||
}
|
||||
/// Checked power
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
fn pow_f_i_u(x: FLOAT, y: INT) -> Result<FLOAT, EvalAltResult> {
|
||||
fn pow_f_i(x: FLOAT, y: INT) -> Result<FLOAT, EvalAltResult> {
|
||||
// Raise to power that is larger than an i32
|
||||
if y > (i32::MAX as INT) {
|
||||
return Err(EvalAltResult::ErrorArithmetic(
|
||||
format!("Power overflow: {} ~ {}", x, y),
|
||||
format!("Number raised to too large an index: {} ~ {}", x, y),
|
||||
Position::none(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(x.powi(y as i32))
|
||||
}
|
||||
/// Unchecked power - may be incorrect if the power index is too high (> i32::MAX)
|
||||
#[cfg(feature = "unchecked")]
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
fn pow_f_i(x: FLOAT, y: INT) -> FLOAT {
|
||||
fn pow_f_i_u(x: FLOAT, y: INT) -> FLOAT {
|
||||
x.powi(y as i32)
|
||||
}
|
||||
|
||||
@ -390,8 +427,10 @@ impl Engine<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
// `&&` and `||` are treated specially as they short-circuit
|
||||
//reg_op!(self, "||", or, bool);
|
||||
//reg_op!(self, "&&", and, bool);
|
||||
|
||||
reg_op!(self, "|", or, bool);
|
||||
reg_op!(self, "&", and, bool);
|
||||
|
||||
@ -445,18 +484,18 @@ impl Engine<'_> {
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
{
|
||||
self.register_result_fn("~", pow_i_i_u);
|
||||
self.register_result_fn("~", pow_i_i);
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
self.register_result_fn("~", pow_f_i_u);
|
||||
self.register_result_fn("~", pow_f_i);
|
||||
}
|
||||
|
||||
#[cfg(feature = "unchecked")]
|
||||
{
|
||||
self.register_fn("~", pow_i_i);
|
||||
self.register_fn("~", pow_i_i_u);
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
self.register_fn("~", pow_f_i);
|
||||
self.register_fn("~", pow_f_i_u);
|
||||
}
|
||||
|
||||
{
|
||||
@ -625,9 +664,6 @@ macro_rules! reg_fn2y {
|
||||
impl Engine<'_> {
|
||||
#[cfg(not(feature = "no_stdlib"))]
|
||||
pub(crate) fn register_stdlib(&mut self) {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
use crate::fn_register::RegisterDynamicFn;
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
{
|
||||
// Advanced math functions
|
||||
|
235
src/optimize.rs
235
src/optimize.rs
@ -9,50 +9,54 @@ use crate::scope::{Scope, ScopeEntry, VariableType};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Level of optimization performed
|
||||
/// Level of optimization performed.
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
|
||||
pub enum OptimizationLevel {
|
||||
/// No optimization performed
|
||||
/// No optimization performed.
|
||||
None,
|
||||
/// Only perform simple optimizations without evaluating functions
|
||||
/// Only perform simple optimizations without evaluating functions.
|
||||
Simple,
|
||||
/// Full optimizations performed, including evaluating functions.
|
||||
/// Take care that this may cause side effects.
|
||||
/// Take care that this may cause side effects as it essentially assumes that all functions are pure.
|
||||
Full,
|
||||
}
|
||||
|
||||
/// Mutable state throughout an optimization pass.
|
||||
struct State<'a> {
|
||||
/// Has the AST been changed during this pass?
|
||||
changed: bool,
|
||||
/// Collection of constants to use for eager function evaluations.
|
||||
constants: Vec<(String, Expr)>,
|
||||
engine: Option<&'a Engine<'a>>,
|
||||
/// An `Engine` instance for eager function evaluation.
|
||||
engine: &'a Engine<'a>,
|
||||
}
|
||||
|
||||
impl State<'_> {
|
||||
pub fn new() -> Self {
|
||||
State {
|
||||
changed: false,
|
||||
constants: vec![],
|
||||
engine: None,
|
||||
}
|
||||
}
|
||||
/// Reset the state from dirty to clean.
|
||||
pub fn reset(&mut self) {
|
||||
self.changed = false;
|
||||
}
|
||||
/// Set the AST state to be dirty (i.e. changed).
|
||||
pub fn set_dirty(&mut self) {
|
||||
self.changed = true;
|
||||
}
|
||||
/// Is the AST dirty (i.e. changed)?
|
||||
pub fn is_dirty(&self) -> bool {
|
||||
self.changed
|
||||
}
|
||||
/// Does a constant exist?
|
||||
pub fn contains_constant(&self, name: &str) -> bool {
|
||||
self.constants.iter().any(|(n, _)| n == name)
|
||||
}
|
||||
/// Prune the list of constants back to a specified size.
|
||||
pub fn restore_constants(&mut self, len: usize) {
|
||||
self.constants.truncate(len)
|
||||
}
|
||||
/// Add a new constant to the list.
|
||||
pub fn push_constant(&mut self, name: &str, value: Expr) {
|
||||
self.constants.push((name.to_string(), value))
|
||||
}
|
||||
/// Look up a constant from the list.
|
||||
pub fn find_constant(&self, name: &str) -> Option<&Expr> {
|
||||
for (n, expr) in self.constants.iter().rev() {
|
||||
if n == name {
|
||||
@ -64,60 +68,68 @@ impl State<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Optimize a statement.
|
||||
fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -> Stmt {
|
||||
match stmt {
|
||||
Stmt::IfElse(expr, stmt1, None) if stmt1.is_noop() => {
|
||||
// if expr { Noop }
|
||||
Stmt::IfElse(expr, if_block, None) if matches!(*if_block, Stmt::Noop(_)) => {
|
||||
state.set_dirty();
|
||||
|
||||
let pos = expr.position();
|
||||
let expr = optimize_expr(*expr, state);
|
||||
|
||||
if matches!(expr, Expr::False(_) | Expr::True(_)) {
|
||||
Stmt::Noop(stmt1.position())
|
||||
if preserve_result {
|
||||
// -> { expr, Noop }
|
||||
Stmt::Block(vec![Stmt::Expr(Box::new(expr)), *if_block], pos)
|
||||
} else {
|
||||
let stmt = Stmt::Expr(Box::new(expr));
|
||||
|
||||
if preserve_result {
|
||||
Stmt::Block(vec![stmt, *stmt1], pos)
|
||||
} else {
|
||||
stmt
|
||||
}
|
||||
// -> expr
|
||||
Stmt::Expr(Box::new(expr))
|
||||
}
|
||||
}
|
||||
|
||||
Stmt::IfElse(expr, stmt1, None) => match *expr {
|
||||
// if expr { if_block }
|
||||
Stmt::IfElse(expr, if_block, None) => match *expr {
|
||||
// if false { if_block } -> Noop
|
||||
Expr::False(pos) => {
|
||||
state.set_dirty();
|
||||
Stmt::Noop(pos)
|
||||
}
|
||||
Expr::True(_) => optimize_stmt(*stmt1, state, true),
|
||||
// if true { if_block } -> if_block
|
||||
Expr::True(_) => optimize_stmt(*if_block, state, true),
|
||||
// if expr { if_block }
|
||||
expr => Stmt::IfElse(
|
||||
Box::new(optimize_expr(expr, state)),
|
||||
Box::new(optimize_stmt(*stmt1, state, true)),
|
||||
Box::new(optimize_stmt(*if_block, state, true)),
|
||||
None,
|
||||
),
|
||||
},
|
||||
|
||||
Stmt::IfElse(expr, stmt1, Some(stmt2)) => match *expr {
|
||||
Expr::False(_) => optimize_stmt(*stmt2, state, true),
|
||||
Expr::True(_) => optimize_stmt(*stmt1, state, true),
|
||||
// if expr { if_block } else { else_block }
|
||||
Stmt::IfElse(expr, if_block, Some(else_block)) => match *expr {
|
||||
// if false { if_block } else { else_block } -> else_block
|
||||
Expr::False(_) => optimize_stmt(*else_block, state, true),
|
||||
// if true { if_block } else { else_block } -> if_block
|
||||
Expr::True(_) => optimize_stmt(*if_block, state, true),
|
||||
// if expr { if_block } else { else_block }
|
||||
expr => Stmt::IfElse(
|
||||
Box::new(optimize_expr(expr, state)),
|
||||
Box::new(optimize_stmt(*stmt1, state, true)),
|
||||
match optimize_stmt(*stmt2, state, true) {
|
||||
stmt if stmt.is_noop() => None,
|
||||
Box::new(optimize_stmt(*if_block, state, true)),
|
||||
match optimize_stmt(*else_block, state, true) {
|
||||
stmt if matches!(stmt, Stmt::Noop(_)) => None, // Noop -> no else block
|
||||
stmt => Some(Box::new(stmt)),
|
||||
},
|
||||
),
|
||||
},
|
||||
|
||||
Stmt::While(expr, stmt) => match *expr {
|
||||
// while expr { block }
|
||||
Stmt::While(expr, block) => match *expr {
|
||||
// while false { block } -> Noop
|
||||
Expr::False(pos) => {
|
||||
state.set_dirty();
|
||||
Stmt::Noop(pos)
|
||||
}
|
||||
Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*stmt, state, false))),
|
||||
expr => match optimize_stmt(*stmt, state, false) {
|
||||
// while true { block } -> loop { block }
|
||||
Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*block, state, false))),
|
||||
// while expr { block }
|
||||
expr => match optimize_stmt(*block, state, false) {
|
||||
// while expr { break; } -> { expr; }
|
||||
Stmt::Break(pos) => {
|
||||
// Only a single break statement - turn into running the guard expression once
|
||||
state.set_dirty();
|
||||
@ -127,48 +139,50 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
|
||||
}
|
||||
Stmt::Block(statements, pos)
|
||||
}
|
||||
// while expr { block }
|
||||
stmt => Stmt::While(Box::new(optimize_expr(expr, state)), Box::new(stmt)),
|
||||
},
|
||||
},
|
||||
Stmt::Loop(stmt) => match optimize_stmt(*stmt, state, false) {
|
||||
// loop { block }
|
||||
Stmt::Loop(block) => match optimize_stmt(*block, state, false) {
|
||||
// loop { break; } -> Noop
|
||||
Stmt::Break(pos) => {
|
||||
// Only a single break statement
|
||||
state.set_dirty();
|
||||
Stmt::Noop(pos)
|
||||
}
|
||||
// loop { block }
|
||||
stmt => Stmt::Loop(Box::new(stmt)),
|
||||
},
|
||||
Stmt::For(id, expr, stmt) => Stmt::For(
|
||||
// for id in expr { block }
|
||||
Stmt::For(id, expr, block) => Stmt::For(
|
||||
id,
|
||||
Box::new(optimize_expr(*expr, state)),
|
||||
Box::new(match optimize_stmt(*stmt, state, false) {
|
||||
Stmt::Break(pos) => {
|
||||
// Only a single break statement
|
||||
state.set_dirty();
|
||||
Stmt::Noop(pos)
|
||||
}
|
||||
stmt => stmt,
|
||||
}),
|
||||
Box::new(optimize_stmt(*block, state, false)),
|
||||
),
|
||||
|
||||
// let id = expr;
|
||||
Stmt::Let(id, Some(expr), pos) => {
|
||||
Stmt::Let(id, Some(Box::new(optimize_expr(*expr, state))), pos)
|
||||
}
|
||||
// let id;
|
||||
Stmt::Let(_, None, _) => stmt,
|
||||
// { block }
|
||||
Stmt::Block(block, pos) => {
|
||||
let orig_len = block.len(); // Original number of statements in the block, for change detection
|
||||
let orig_constants_len = state.constants.len(); // Original number of constants in the state, for restore later
|
||||
|
||||
Stmt::Block(statements, pos) => {
|
||||
let orig_len = statements.len();
|
||||
let orig_constants_len = state.constants.len();
|
||||
|
||||
let mut result: Vec<_> = statements
|
||||
.into_iter() // For each statement
|
||||
// Optimize each statement in the block
|
||||
let mut result: Vec<_> = block
|
||||
.into_iter()
|
||||
.map(|stmt| {
|
||||
if let Stmt::Const(name, value, pos) = stmt {
|
||||
// Add constant into the state
|
||||
state.push_constant(&name, *value);
|
||||
state.set_dirty();
|
||||
Stmt::Noop(pos) // No need to keep constants
|
||||
} else {
|
||||
optimize_stmt(stmt, state, preserve_result) // Optimize the statement
|
||||
// Optimize the statement
|
||||
optimize_stmt(stmt, state, preserve_result)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
@ -203,11 +217,12 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
|
||||
result.push(Stmt::Noop(pos))
|
||||
}
|
||||
|
||||
// Optimize all the statements again
|
||||
result = result
|
||||
.into_iter()
|
||||
.rev()
|
||||
.enumerate()
|
||||
.map(|(i, s)| optimize_stmt(s, state, i == 0)) // Optimize all other statements again
|
||||
.map(|(i, s)| optimize_stmt(s, state, i == 0))
|
||||
.rev()
|
||||
.collect();
|
||||
}
|
||||
@ -230,10 +245,12 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
|
||||
true
|
||||
});
|
||||
|
||||
// Change detection
|
||||
if orig_len != result.len() {
|
||||
state.set_dirty();
|
||||
}
|
||||
|
||||
// Pop the stack and remove all the local constants
|
||||
state.restore_constants(orig_constants_len);
|
||||
|
||||
match result[..] {
|
||||
@ -250,44 +267,54 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
|
||||
_ => Stmt::Block(result, pos),
|
||||
}
|
||||
}
|
||||
|
||||
// expr;
|
||||
Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, state))),
|
||||
|
||||
// return expr;
|
||||
Stmt::ReturnWithVal(Some(expr), is_return, pos) => {
|
||||
Stmt::ReturnWithVal(Some(Box::new(optimize_expr(*expr, state))), is_return, pos)
|
||||
}
|
||||
|
||||
// All other statements - skip
|
||||
stmt => stmt,
|
||||
}
|
||||
}
|
||||
|
||||
/// Optimize an expression.
|
||||
fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
// These keywords are handled specially
|
||||
const SKIP_FUNC_KEYWORDS: [&str; 3] = [KEYWORD_PRINT, KEYWORD_DEBUG, KEYWORD_DUMP_AST];
|
||||
|
||||
match expr {
|
||||
// ( stmt )
|
||||
Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, state, true) {
|
||||
// ( Noop ) -> ()
|
||||
Stmt::Noop(_) => {
|
||||
state.set_dirty();
|
||||
Expr::Unit(pos)
|
||||
}
|
||||
// ( expr ) -> expr
|
||||
Stmt::Expr(expr) => {
|
||||
state.set_dirty();
|
||||
*expr
|
||||
}
|
||||
// ( stmt )
|
||||
stmt => Expr::Stmt(Box::new(stmt), pos),
|
||||
},
|
||||
Expr::Assignment(id1, expr1, pos1) => match *expr1 {
|
||||
Expr::Assignment(id2, expr2, pos2) => match (*id1, *id2) {
|
||||
(Expr::Variable(var1, _), Expr::Variable(var2, _)) if var1 == var2 => {
|
||||
// id = expr
|
||||
Expr::Assignment(id, expr, pos) => match *expr {
|
||||
//id = id2 = expr2
|
||||
Expr::Assignment(id2, expr2, pos2) => match (*id, *id2) {
|
||||
// var = var = expr2 -> var = expr2
|
||||
(Expr::Variable(var, _), Expr::Variable(var2, _)) if var == var2 => {
|
||||
// Assignment to the same variable - fold
|
||||
state.set_dirty();
|
||||
|
||||
Expr::Assignment(
|
||||
Box::new(Expr::Variable(var1, pos1)),
|
||||
Box::new(Expr::Variable(var, pos)),
|
||||
Box::new(optimize_expr(*expr2, state)),
|
||||
pos1,
|
||||
pos,
|
||||
)
|
||||
}
|
||||
// id1 = id2 = expr2
|
||||
(id1, id2) => Expr::Assignment(
|
||||
Box::new(id1),
|
||||
Box::new(Expr::Assignment(
|
||||
@ -295,19 +322,23 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
Box::new(optimize_expr(*expr2, state)),
|
||||
pos2,
|
||||
)),
|
||||
pos1,
|
||||
pos,
|
||||
),
|
||||
},
|
||||
expr => Expr::Assignment(id1, Box::new(optimize_expr(expr, state)), pos1),
|
||||
// id = expr
|
||||
expr => Expr::Assignment(id, Box::new(optimize_expr(expr, state)), pos),
|
||||
},
|
||||
// lhs.rhs
|
||||
Expr::Dot(lhs, rhs, pos) => Expr::Dot(
|
||||
Box::new(optimize_expr(*lhs, state)),
|
||||
Box::new(optimize_expr(*rhs, state)),
|
||||
pos,
|
||||
),
|
||||
|
||||
// lhs[rhs]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) {
|
||||
// array[int]
|
||||
(Expr::Array(mut items, _), Expr::IntegerConstant(i, _))
|
||||
if i >= 0 && (i as usize) < items.len() && items.iter().all(|x| x.is_pure()) =>
|
||||
{
|
||||
@ -316,6 +347,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
state.set_dirty();
|
||||
items.remove(i as usize)
|
||||
}
|
||||
// string[int]
|
||||
(Expr::StringConstant(s, pos), Expr::IntegerConstant(i, _))
|
||||
if i >= 0 && (i as usize) < s.chars().count() =>
|
||||
{
|
||||
@ -323,14 +355,14 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
state.set_dirty();
|
||||
Expr::CharConstant(s.chars().nth(i as usize).expect("should get char"), pos)
|
||||
}
|
||||
|
||||
// lhs[rhs]
|
||||
(lhs, rhs) => Expr::Index(
|
||||
Box::new(optimize_expr(lhs, state)),
|
||||
Box::new(optimize_expr(rhs, state)),
|
||||
pos,
|
||||
),
|
||||
},
|
||||
|
||||
// [ items .. ]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Array(items, pos) => {
|
||||
let orig_len = items.len();
|
||||
@ -346,38 +378,47 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
|
||||
Expr::Array(items, pos)
|
||||
}
|
||||
|
||||
// lhs && rhs
|
||||
Expr::And(lhs, rhs) => match (*lhs, *rhs) {
|
||||
// true && rhs -> rhs
|
||||
(Expr::True(_), rhs) => {
|
||||
state.set_dirty();
|
||||
rhs
|
||||
}
|
||||
// false && rhs -> false
|
||||
(Expr::False(pos), _) => {
|
||||
state.set_dirty();
|
||||
Expr::False(pos)
|
||||
}
|
||||
// lhs && true -> lhs
|
||||
(lhs, Expr::True(_)) => {
|
||||
state.set_dirty();
|
||||
lhs
|
||||
optimize_expr(lhs, state)
|
||||
}
|
||||
// lhs && rhs
|
||||
(lhs, rhs) => Expr::And(
|
||||
Box::new(optimize_expr(lhs, state)),
|
||||
Box::new(optimize_expr(rhs, state)),
|
||||
),
|
||||
},
|
||||
// lhs || rhs
|
||||
Expr::Or(lhs, rhs) => match (*lhs, *rhs) {
|
||||
// false || rhs -> rhs
|
||||
(Expr::False(_), rhs) => {
|
||||
state.set_dirty();
|
||||
rhs
|
||||
}
|
||||
// true || rhs -> true
|
||||
(Expr::True(pos), _) => {
|
||||
state.set_dirty();
|
||||
Expr::True(pos)
|
||||
}
|
||||
// lhs || false
|
||||
(lhs, Expr::False(_)) => {
|
||||
state.set_dirty();
|
||||
lhs
|
||||
optimize_expr(lhs, state)
|
||||
}
|
||||
// lhs || rhs
|
||||
(lhs, rhs) => Expr::Or(
|
||||
Box::new(optimize_expr(lhs, state)),
|
||||
Box::new(optimize_expr(rhs, state)),
|
||||
@ -388,24 +429,23 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
Expr::FunctionCall(id, args, def_value, pos) if SKIP_FUNC_KEYWORDS.contains(&id.as_str())=>
|
||||
Expr::FunctionCall(id, args, def_value, pos),
|
||||
|
||||
// Actually call function to optimize it
|
||||
// Eagerly call functions
|
||||
Expr::FunctionCall(id, args, def_value, pos)
|
||||
if state.engine.map(|eng| eng.optimization_level == OptimizationLevel::Full).unwrap_or(false) // full optimizations
|
||||
if state.engine.optimization_level == OptimizationLevel::Full // full optimizations
|
||||
&& args.iter().all(|expr| expr.is_constant()) // all arguments are constants
|
||||
=> {
|
||||
let engine = state.engine.expect("engine should be Some");
|
||||
let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect();
|
||||
let call_args: FnCallArgs = arg_values.iter_mut().map(Dynamic::as_mut).collect();
|
||||
|
||||
// Save the typename of the first argument if it is `type_of()`
|
||||
// This is to avoid `call_args` being passed into the closure
|
||||
let arg_for_type_of = if id == KEYWORD_TYPE_OF && call_args.len() == 1 {
|
||||
engine.map_type_name(call_args[0].type_name())
|
||||
state.engine.map_type_name(call_args[0].type_name())
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
engine.call_ext_fn_raw(&id, call_args, pos).ok().map(|r|
|
||||
state.engine.call_ext_fn_raw(&id, call_args, pos).ok().map(|r|
|
||||
r.or_else(|| {
|
||||
if !arg_for_type_of.is_empty() {
|
||||
// Handle `type_of()`
|
||||
@ -421,10 +461,10 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
})
|
||||
).flatten().unwrap_or_else(|| Expr::FunctionCall(id, args, def_value, pos))
|
||||
}
|
||||
// Optimize the function call arguments
|
||||
// id(args ..) -> optimize function call arguments
|
||||
Expr::FunctionCall(id, args, def_value, pos) =>
|
||||
Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos),
|
||||
|
||||
// constant-name
|
||||
Expr::Variable(ref name, _) if state.contains_constant(name) => {
|
||||
state.set_dirty();
|
||||
|
||||
@ -434,28 +474,25 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
.expect("should find constant in scope!")
|
||||
.clone()
|
||||
}
|
||||
|
||||
// All other expressions - skip
|
||||
expr => expr,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn optimize<'a>(
|
||||
statements: Vec<Stmt>,
|
||||
engine: Option<&Engine<'a>>,
|
||||
scope: &Scope,
|
||||
) -> Vec<Stmt> {
|
||||
pub(crate) fn optimize<'a>(statements: Vec<Stmt>, engine: &Engine<'a>, scope: &Scope) -> Vec<Stmt> {
|
||||
// If optimization level is None then skip optimizing
|
||||
if engine
|
||||
.map(|eng| eng.optimization_level == OptimizationLevel::None)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
if engine.optimization_level == OptimizationLevel::None {
|
||||
return statements;
|
||||
}
|
||||
|
||||
// Set up the state
|
||||
let mut state = State::new();
|
||||
state.engine = engine;
|
||||
let mut state = State {
|
||||
changed: false,
|
||||
constants: vec![],
|
||||
engine,
|
||||
};
|
||||
|
||||
// Add constants from the scope into the state
|
||||
scope
|
||||
.iter()
|
||||
.filter(|ScopeEntry { var_type, expr, .. }| {
|
||||
@ -472,9 +509,9 @@ pub(crate) fn optimize<'a>(
|
||||
|
||||
let orig_constants_len = state.constants.len();
|
||||
|
||||
// Optimization loop
|
||||
let mut result = statements;
|
||||
|
||||
// Optimization loop
|
||||
loop {
|
||||
state.reset();
|
||||
state.restore_constants(orig_constants_len);
|
||||
@ -492,7 +529,7 @@ pub(crate) fn optimize<'a>(
|
||||
} else {
|
||||
// Keep all variable declarations at this level
|
||||
// and always keep the last return value
|
||||
let keep = stmt.is_var() || i == num_statements - 1;
|
||||
let keep = matches!(stmt, Stmt::Let(_, _, _)) || i == num_statements - 1;
|
||||
|
||||
optimize_stmt(stmt, &mut state, keep)
|
||||
}
|
||||
@ -517,6 +554,7 @@ pub(crate) fn optimize<'a>(
|
||||
result
|
||||
}
|
||||
|
||||
/// Optimize an AST.
|
||||
pub fn optimize_ast(
|
||||
engine: &Engine,
|
||||
scope: &Scope,
|
||||
@ -526,8 +564,8 @@ pub fn optimize_ast(
|
||||
AST(
|
||||
match engine.optimization_level {
|
||||
OptimizationLevel::None => statements,
|
||||
OptimizationLevel::Simple => optimize(statements, None, &scope),
|
||||
OptimizationLevel::Full => optimize(statements, Some(engine), &scope),
|
||||
OptimizationLevel::Simple => optimize(statements, engine, &scope),
|
||||
OptimizationLevel::Full => optimize(statements, engine, &scope),
|
||||
},
|
||||
functions
|
||||
.into_iter()
|
||||
@ -536,14 +574,21 @@ pub fn optimize_ast(
|
||||
OptimizationLevel::None => (),
|
||||
OptimizationLevel::Simple | OptimizationLevel::Full => {
|
||||
let pos = fn_def.body.position();
|
||||
let mut body = optimize(vec![fn_def.body], None, &Scope::new());
|
||||
|
||||
// Optimize the function body
|
||||
let mut body = optimize(vec![fn_def.body], engine, &Scope::new());
|
||||
|
||||
// {} -> Noop
|
||||
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
|
||||
// { return val; } -> val
|
||||
Stmt::ReturnWithVal(Some(val), ReturnType::Return, _) => {
|
||||
Stmt::Expr(val)
|
||||
}
|
||||
// { return; } -> ()
|
||||
Stmt::ReturnWithVal(None, ReturnType::Return, pos) => {
|
||||
Stmt::Expr(Box::new(Expr::Unit(pos)))
|
||||
}
|
||||
// All others
|
||||
stmt => stmt,
|
||||
};
|
||||
}
|
||||
|
218
src/parser.rs
218
src/parser.rs
@ -19,13 +19,13 @@ use std::{
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
pub type INT = i64;
|
||||
|
||||
/// The system integer type
|
||||
/// The system integer type.
|
||||
///
|
||||
/// If the `only_i32` feature is not enabled, this will be `i64` instead.
|
||||
#[cfg(feature = "only_i32")]
|
||||
pub type INT = i32;
|
||||
|
||||
/// The system floating-point type
|
||||
/// The system floating-point type.
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
pub type FLOAT = f64;
|
||||
|
||||
@ -35,7 +35,9 @@ type PERR = ParseErrorType;
|
||||
/// A location (line number + character position) in the input script.
|
||||
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
|
||||
pub struct Position {
|
||||
/// Line number - 0 = none, MAX = EOF
|
||||
line: usize,
|
||||
/// Character position - 0 = BOL, MAX = EOF
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
@ -152,45 +154,69 @@ impl fmt::Debug for Position {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AST(pub(crate) Vec<Stmt>, pub(crate) Vec<Arc<FnDef>>);
|
||||
|
||||
/// A script-function definition.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FnDef {
|
||||
/// Function name.
|
||||
pub name: String,
|
||||
/// Names of function parameters.
|
||||
pub params: Vec<String>,
|
||||
/// Function body.
|
||||
pub body: Stmt,
|
||||
/// Position of the function definition.
|
||||
pub pos: Position,
|
||||
}
|
||||
|
||||
impl FnDef {
|
||||
/// Function to order two FnDef records, for binary search.
|
||||
pub fn compare(&self, name: &str, params_len: usize) -> Ordering {
|
||||
// First order by name
|
||||
match self.name.as_str().cmp(name) {
|
||||
// Then by number of parameters
|
||||
Ordering::Equal => self.params.len().cmp(¶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)
|
||||
@ -204,18 +230,7 @@ impl Stmt {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_noop(&self) -> bool {
|
||||
matches!(self, Stmt::Noop(_))
|
||||
}
|
||||
|
||||
pub fn is_op(&self) -> bool {
|
||||
!matches!(self, Stmt::Noop(_))
|
||||
}
|
||||
|
||||
pub fn is_var(&self) -> bool {
|
||||
matches!(self, Stmt::Let(_, _, _))
|
||||
}
|
||||
|
||||
/// Is this statement self-terminated (i.e. no need for a semicolon terminator)?
|
||||
pub fn is_self_terminated(&self) -> bool {
|
||||
match self {
|
||||
Stmt::Noop(_)
|
||||
@ -233,6 +248,7 @@ impl Stmt {
|
||||
}
|
||||
}
|
||||
|
||||
/// Is this statement _pure_?
|
||||
pub fn is_pure(&self) -> bool {
|
||||
match self {
|
||||
Stmt::Noop(_) => true,
|
||||
@ -252,31 +268,54 @@ impl Stmt {
|
||||
}
|
||||
}
|
||||
|
||||
/// An expression.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Expr {
|
||||
/// Integer constant.
|
||||
IntegerConstant(INT, Position),
|
||||
/// Floating-point constant.
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
FloatConstant(FLOAT, Position),
|
||||
Variable(String, Position),
|
||||
Property(String, Position),
|
||||
/// Character constant.
|
||||
CharConstant(char, Position),
|
||||
/// String constant.
|
||||
StringConstant(String, Position),
|
||||
/// Variable access.
|
||||
Variable(String, Position),
|
||||
/// Property access.
|
||||
Property(String, Position),
|
||||
/// { stmt }
|
||||
Stmt(Box<Stmt>, Position),
|
||||
/// func(expr, ... )
|
||||
FunctionCall(String, Vec<Expr>, Option<Dynamic>, Position),
|
||||
/// expr = expr
|
||||
Assignment(Box<Expr>, Box<Expr>, Position),
|
||||
/// lhs.rhs
|
||||
Dot(Box<Expr>, Box<Expr>, Position),
|
||||
/// expr[expr]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Index(Box<Expr>, Box<Expr>, Position),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
/// [ expr, ... ]
|
||||
Array(Vec<Expr>, Position),
|
||||
/// lhs && rhs
|
||||
And(Box<Expr>, Box<Expr>),
|
||||
/// lhs || rhs
|
||||
Or(Box<Expr>, Box<Expr>),
|
||||
/// true
|
||||
True(Position),
|
||||
/// false
|
||||
False(Position),
|
||||
/// ()
|
||||
Unit(Position),
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
/// Get the `Dynamic` value of a constant expression.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics when the expression is not constant.
|
||||
pub fn get_constant_value(&self) -> Dynamic {
|
||||
match self {
|
||||
Expr::IntegerConstant(i, _) => i.into_dynamic(),
|
||||
@ -300,6 +339,11 @@ impl Expr {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the display value of a constant expression.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics when the expression is not constant.
|
||||
pub fn get_constant_str(&self) -> String {
|
||||
match self {
|
||||
Expr::IntegerConstant(i, _) => i.to_string(),
|
||||
@ -319,6 +363,7 @@ impl Expr {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the `Position` of the expression.
|
||||
pub fn position(&self) -> Position {
|
||||
match self {
|
||||
Expr::IntegerConstant(_, pos)
|
||||
@ -348,7 +393,7 @@ impl Expr {
|
||||
}
|
||||
}
|
||||
|
||||
/// Is this expression pure?
|
||||
/// Is the expression pure?
|
||||
///
|
||||
/// A pure expression has no side effects.
|
||||
pub fn is_pure(&self) -> bool {
|
||||
@ -367,6 +412,7 @@ impl Expr {
|
||||
}
|
||||
}
|
||||
|
||||
/// Is the expression a constant?
|
||||
pub fn is_constant(&self) -> bool {
|
||||
match self {
|
||||
Expr::IntegerConstant(_, _)
|
||||
@ -379,6 +425,7 @@ impl Expr {
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Expr::FloatConstant(_, _) => true,
|
||||
|
||||
// An array literal is constant if all items are constant
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Array(expressions, _) => expressions.iter().all(Expr::is_constant),
|
||||
|
||||
@ -387,6 +434,7 @@ impl Expr {
|
||||
}
|
||||
}
|
||||
|
||||
/// Tokens.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Token {
|
||||
IntegerConstant(INT),
|
||||
@ -460,6 +508,7 @@ pub enum Token {
|
||||
}
|
||||
|
||||
impl Token {
|
||||
/// Get the syntax of the token.
|
||||
pub fn syntax<'a>(&'a self) -> Cow<'a, str> {
|
||||
use self::Token::*;
|
||||
|
||||
@ -540,8 +589,8 @@ impl Token {
|
||||
}
|
||||
}
|
||||
|
||||
// if another operator is after these, it's probably an unary operator
|
||||
// not sure about fn's name
|
||||
// If another operator is after these, it's probably an unary operator
|
||||
// (not sure about fn name).
|
||||
pub fn is_next_unary(&self) -> bool {
|
||||
use self::Token::*;
|
||||
|
||||
@ -602,6 +651,7 @@ impl Token {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the precedence number of the token.
|
||||
pub fn precedence(&self) -> u8 {
|
||||
match self {
|
||||
Self::Equals
|
||||
@ -642,8 +692,10 @@ impl Token {
|
||||
}
|
||||
}
|
||||
|
||||
/// Does an expression bind to the right (instead of left)?
|
||||
pub fn is_bind_right(&self) -> bool {
|
||||
match self {
|
||||
// Assignments bind to the right
|
||||
Self::Equals
|
||||
| Self::PlusAssign
|
||||
| Self::MinusAssign
|
||||
@ -657,6 +709,7 @@ impl Token {
|
||||
| Self::ModuloAssign
|
||||
| Self::PowerOfAssign => true,
|
||||
|
||||
// Property access binds to the right
|
||||
Self::Period => true,
|
||||
|
||||
_ => false,
|
||||
@ -664,24 +717,36 @@ impl Token {
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator on a `Token` stream.
|
||||
pub struct TokenIterator<'a> {
|
||||
/// The last token seen.
|
||||
last: Token,
|
||||
/// Current position.
|
||||
pos: Position,
|
||||
/// The input characters stream.
|
||||
char_stream: Peekable<Chars<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> TokenIterator<'a> {
|
||||
/// Move the current position one character ahead.
|
||||
fn advance(&mut self) {
|
||||
self.pos.advance();
|
||||
}
|
||||
/// Move the current position back one character.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if already at the beginning of a line - cannot rewind to the previous line.
|
||||
fn rewind(&mut self) {
|
||||
self.pos.rewind();
|
||||
}
|
||||
/// Move the current position to the next line.
|
||||
fn new_line(&mut self) {
|
||||
self.pos.new_line()
|
||||
}
|
||||
|
||||
pub fn parse_string_const(
|
||||
/// Parse a string literal wrapped by `enclosing_char`.
|
||||
pub fn parse_string_literal(
|
||||
&mut self,
|
||||
enclosing_char: char,
|
||||
) -> Result<String, (LexError, Position)> {
|
||||
@ -693,25 +758,31 @@ impl<'a> TokenIterator<'a> {
|
||||
self.advance();
|
||||
|
||||
match next_char.ok_or((LERR::UnterminatedString, Position::eof()))? {
|
||||
// \...
|
||||
'\\' if escape.is_empty() => {
|
||||
escape.push('\\');
|
||||
}
|
||||
// \\
|
||||
'\\' if !escape.is_empty() => {
|
||||
escape.clear();
|
||||
result.push('\\');
|
||||
}
|
||||
// \t
|
||||
't' if !escape.is_empty() => {
|
||||
escape.clear();
|
||||
result.push('\t');
|
||||
}
|
||||
// \n
|
||||
'n' if !escape.is_empty() => {
|
||||
escape.clear();
|
||||
result.push('\n');
|
||||
}
|
||||
// \r
|
||||
'r' if !escape.is_empty() => {
|
||||
escape.clear();
|
||||
result.push('\r');
|
||||
}
|
||||
// \x??, \u????, \U????????
|
||||
ch @ 'x' | ch @ 'u' | ch @ 'U' if !escape.is_empty() => {
|
||||
let mut seq = escape.clone();
|
||||
seq.push(ch);
|
||||
@ -744,15 +815,25 @@ impl<'a> TokenIterator<'a> {
|
||||
.ok_or_else(|| (LERR::MalformedEscapeSequence(seq), self.pos))?,
|
||||
);
|
||||
}
|
||||
|
||||
// \{enclosing_char} - escaped
|
||||
ch if enclosing_char == ch && !escape.is_empty() => result.push(ch),
|
||||
|
||||
// Close wrapper
|
||||
ch if enclosing_char == ch && escape.is_empty() => break,
|
||||
|
||||
// Unknown escape sequence
|
||||
_ if !escape.is_empty() => {
|
||||
return Err((LERR::MalformedEscapeSequence(escape), self.pos))
|
||||
}
|
||||
|
||||
// Cannot have new-lines inside string literals
|
||||
'\n' => {
|
||||
self.rewind();
|
||||
return Err((LERR::UnterminatedString, self.pos));
|
||||
}
|
||||
|
||||
// All other characters
|
||||
ch => {
|
||||
escape.clear();
|
||||
result.push(ch);
|
||||
@ -763,6 +844,7 @@ impl<'a> TokenIterator<'a> {
|
||||
Ok(result.iter().collect())
|
||||
}
|
||||
|
||||
/// Get the next token.
|
||||
fn inner_next(&mut self) -> Option<(Token, Position)> {
|
||||
let mut negated = false;
|
||||
|
||||
@ -772,7 +854,9 @@ impl<'a> TokenIterator<'a> {
|
||||
let pos = self.pos;
|
||||
|
||||
match c {
|
||||
// \n
|
||||
'\n' => self.new_line(),
|
||||
// digit ...
|
||||
'0'..='9' => {
|
||||
let mut result = Vec::new();
|
||||
let mut radix_base: Option<u32> = None;
|
||||
@ -801,6 +885,7 @@ impl<'a> TokenIterator<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 0x????, 0o????, 0b????
|
||||
ch @ 'x' | ch @ 'X' | ch @ 'o' | ch @ 'O' | ch @ 'b' | ch @ 'B'
|
||||
if c == '0' =>
|
||||
{
|
||||
@ -850,6 +935,7 @@ impl<'a> TokenIterator<'a> {
|
||||
result.insert(0, '-');
|
||||
}
|
||||
|
||||
// Parse number
|
||||
if let Some(radix) = radix_base {
|
||||
let out: String = result.iter().skip(2).filter(|&&c| c != '_').collect();
|
||||
|
||||
@ -865,6 +951,7 @@ impl<'a> TokenIterator<'a> {
|
||||
let out: String = result.iter().filter(|&&c| c != '_').collect();
|
||||
let num = INT::from_str(&out).map(Token::IntegerConstant);
|
||||
|
||||
// If integer parsing is unnecessary, try float instead
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
let num = num.or_else(|_| FLOAT::from_str(&out).map(Token::FloatConstant));
|
||||
|
||||
@ -876,6 +963,7 @@ impl<'a> TokenIterator<'a> {
|
||||
));
|
||||
}
|
||||
}
|
||||
// letter ...
|
||||
'A'..='Z' | 'a'..='z' | '_' => {
|
||||
let mut result = Vec::new();
|
||||
result.push(c);
|
||||
@ -920,13 +1008,15 @@ impl<'a> TokenIterator<'a> {
|
||||
pos,
|
||||
));
|
||||
}
|
||||
// " - string literal
|
||||
'"' => {
|
||||
return match self.parse_string_const('"') {
|
||||
return match self.parse_string_literal('"') {
|
||||
Ok(out) => Some((Token::StringConst(out), pos)),
|
||||
Err(e) => Some((Token::LexError(e.0), e.1)),
|
||||
}
|
||||
}
|
||||
'\'' => match self.parse_string_const('\'') {
|
||||
// ' - character literal
|
||||
'\'' => match self.parse_string_literal('\'') {
|
||||
Ok(result) => {
|
||||
let mut chars = result.chars();
|
||||
|
||||
@ -945,16 +1035,22 @@ impl<'a> TokenIterator<'a> {
|
||||
}
|
||||
Err(e) => return Some((Token::LexError(e.0), e.1)),
|
||||
},
|
||||
|
||||
// Braces
|
||||
'{' => return Some((Token::LeftBrace, pos)),
|
||||
'}' => return Some((Token::RightBrace, pos)),
|
||||
|
||||
// Parentheses
|
||||
'(' => return Some((Token::LeftParen, pos)),
|
||||
')' => return Some((Token::RightParen, pos)),
|
||||
|
||||
// Indexing
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
'[' => return Some((Token::LeftBracket, pos)),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
']' => return Some((Token::RightBracket, pos)),
|
||||
|
||||
// Operators
|
||||
'+' => {
|
||||
return Some((
|
||||
match self.char_stream.peek() {
|
||||
@ -1218,6 +1314,7 @@ impl<'a> Iterator for TokenIterator<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Tokenize an input text stream.
|
||||
pub fn lex(input: &str) -> TokenIterator<'_> {
|
||||
TokenIterator {
|
||||
last: Token::LexError(LERR::InputError("".into())),
|
||||
@ -1226,6 +1323,7 @@ pub fn lex(input: &str) -> TokenIterator<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse ( expr )
|
||||
fn parse_paren_expr<'a>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
begin: Position,
|
||||
@ -1242,13 +1340,16 @@ fn parse_paren_expr<'a>(
|
||||
let expr = parse_expr(input)?;
|
||||
|
||||
match input.next() {
|
||||
// ( xxx )
|
||||
Some((Token::RightParen, _)) => Ok(expr),
|
||||
// ( xxx ???
|
||||
Some((_, pos)) => {
|
||||
return Err(ParseError::new(
|
||||
PERR::MissingRightParen("a matching ( in the expression".into()),
|
||||
pos,
|
||||
))
|
||||
}
|
||||
// ( xxx
|
||||
None => Err(ParseError::new(
|
||||
PERR::MissingRightParen("a matching ( in the expression".into()),
|
||||
Position::eof(),
|
||||
@ -1256,6 +1357,7 @@ fn parse_paren_expr<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a function call.
|
||||
fn parse_call_expr<'a>(
|
||||
id: String,
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
@ -1263,6 +1365,7 @@ fn parse_call_expr<'a>(
|
||||
) -> Result<Expr, ParseError> {
|
||||
let mut args_expr_list = Vec::new();
|
||||
|
||||
// id()
|
||||
if let (Token::RightParen, _) = input.peek().ok_or_else(|| {
|
||||
ParseError::new(
|
||||
PERR::MissingRightParen(format!(
|
||||
@ -1308,6 +1411,7 @@ fn parse_call_expr<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse an indexing expression.s
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
fn parse_index_expr<'a>(
|
||||
lhs: Box<Expr>,
|
||||
@ -1318,6 +1422,7 @@ fn parse_index_expr<'a>(
|
||||
|
||||
// Check type of indexing - must be integer
|
||||
match &idx_expr {
|
||||
// lhs[int]
|
||||
Expr::IntegerConstant(i, pos) if *i < 0 => {
|
||||
return Err(ParseError::new(
|
||||
PERR::MalformedIndexExpr(format!(
|
||||
@ -1327,6 +1432,7 @@ fn parse_index_expr<'a>(
|
||||
*pos,
|
||||
))
|
||||
}
|
||||
// lhs[float]
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Expr::FloatConstant(_, pos) => {
|
||||
return Err(ParseError::new(
|
||||
@ -1334,6 +1440,7 @@ fn parse_index_expr<'a>(
|
||||
*pos,
|
||||
))
|
||||
}
|
||||
// lhs[char]
|
||||
Expr::CharConstant(_, pos) => {
|
||||
return Err(ParseError::new(
|
||||
PERR::MalformedIndexExpr(
|
||||
@ -1342,18 +1449,21 @@ fn parse_index_expr<'a>(
|
||||
*pos,
|
||||
))
|
||||
}
|
||||
// lhs[string]
|
||||
Expr::StringConstant(_, pos) => {
|
||||
return Err(ParseError::new(
|
||||
PERR::MalformedIndexExpr("Array access expects integer index, not a string".into()),
|
||||
*pos,
|
||||
))
|
||||
}
|
||||
// lhs[??? = ??? ], lhs[()]
|
||||
Expr::Assignment(_, _, pos) | Expr::Unit(pos) => {
|
||||
return Err(ParseError::new(
|
||||
PERR::MalformedIndexExpr("Array access expects integer index, not ()".into()),
|
||||
*pos,
|
||||
))
|
||||
}
|
||||
// lhs[??? && ???], lhs[??? || ???]
|
||||
Expr::And(lhs, _) | Expr::Or(lhs, _) => {
|
||||
return Err(ParseError::new(
|
||||
PERR::MalformedIndexExpr(
|
||||
@ -1362,6 +1472,7 @@ fn parse_index_expr<'a>(
|
||||
lhs.position(),
|
||||
))
|
||||
}
|
||||
// lhs[true], lhs[false]
|
||||
Expr::True(pos) | Expr::False(pos) => {
|
||||
return Err(ParseError::new(
|
||||
PERR::MalformedIndexExpr(
|
||||
@ -1370,6 +1481,7 @@ fn parse_index_expr<'a>(
|
||||
*pos,
|
||||
))
|
||||
}
|
||||
// All other expressions
|
||||
_ => (),
|
||||
}
|
||||
|
||||
@ -1393,29 +1505,35 @@ fn parse_index_expr<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse an expression that begins with an identifier.
|
||||
fn parse_ident_expr<'a>(
|
||||
id: String,
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
begin: Position,
|
||||
) -> Result<Expr, ParseError> {
|
||||
match input.peek() {
|
||||
// id(...) - function call
|
||||
Some((Token::LeftParen, _)) => {
|
||||
input.next();
|
||||
parse_call_expr(id, input, begin)
|
||||
}
|
||||
// id[...] - indexing
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Some((Token::LeftBracket, pos)) => {
|
||||
let pos = *pos;
|
||||
input.next();
|
||||
parse_index_expr(Box::new(Expr::Variable(id, begin)), input, pos)
|
||||
}
|
||||
// id - variable
|
||||
Some(_) => Ok(Expr::Variable(id, begin)),
|
||||
// EOF
|
||||
None => Ok(Expr::Variable(id, Position::eof())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse an array literal.
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
fn parse_array_expr<'a>(
|
||||
fn parse_array_literal<'a>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
begin: Position,
|
||||
) -> Result<Expr, ParseError> {
|
||||
@ -1462,8 +1580,9 @@ fn parse_array_expr<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a primary expression.
|
||||
fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> {
|
||||
// Block statement as expression
|
||||
// { - block statement as expression
|
||||
match input.peek() {
|
||||
Some((Token::LeftBrace, pos)) => {
|
||||
let pos = *pos;
|
||||
@ -1500,7 +1619,7 @@ fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pa
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
(Token::LeftBracket, pos) => {
|
||||
can_be_indexed = true;
|
||||
parse_array_expr(input, pos)
|
||||
parse_array_literal(input, pos)
|
||||
}
|
||||
(Token::True, pos) => Ok(Expr::True(pos)),
|
||||
(Token::False, pos) => Ok(Expr::False(pos)),
|
||||
@ -1524,11 +1643,13 @@ fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pa
|
||||
Ok(root_expr)
|
||||
}
|
||||
|
||||
/// Parse a potential unary operator.
|
||||
fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> {
|
||||
match input
|
||||
.peek()
|
||||
.ok_or_else(|| ParseError::new(PERR::InputPastEndOfFile, Position::eof()))?
|
||||
{
|
||||
// -expr
|
||||
(Token::UnaryMinus, pos) => {
|
||||
let pos = *pos;
|
||||
|
||||
@ -1561,10 +1682,12 @@ fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pars
|
||||
expr => Ok(Expr::FunctionCall("-".into(), vec![expr], None, pos)),
|
||||
}
|
||||
}
|
||||
// +expr
|
||||
(Token::UnaryPlus, _) => {
|
||||
input.next();
|
||||
parse_unary(input)
|
||||
}
|
||||
// !expr
|
||||
(Token::Bang, pos) => {
|
||||
let pos = *pos;
|
||||
|
||||
@ -1577,34 +1700,41 @@ fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pars
|
||||
pos,
|
||||
))
|
||||
}
|
||||
// All other tokens
|
||||
_ => parse_primary(input),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse an assignment.
|
||||
fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseError> {
|
||||
// Is the LHS in a valid format?
|
||||
fn valid_assignment_chain(expr: &Expr, is_top: bool) -> Option<ParseError> {
|
||||
match expr {
|
||||
// var
|
||||
Expr::Variable(_, _) => {
|
||||
assert!(is_top, "property expected but gets variable");
|
||||
None
|
||||
}
|
||||
// property
|
||||
Expr::Property(_, _) => {
|
||||
assert!(!is_top, "variable expected but gets property");
|
||||
None
|
||||
}
|
||||
|
||||
// var[...]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(idx_lhs, _, _) if matches!(idx_lhs.as_ref(), &Expr::Variable(_, _)) => {
|
||||
assert!(is_top, "property expected but gets variable");
|
||||
None
|
||||
}
|
||||
|
||||
// property[...]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(idx_lhs, _, _) if matches!(idx_lhs.as_ref(), &Expr::Property(_, _)) => {
|
||||
assert!(!is_top, "variable expected but gets property");
|
||||
None
|
||||
}
|
||||
|
||||
// idx_lhs[...]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(idx_lhs, _, pos) => Some(ParseError::new(
|
||||
match idx_lhs.as_ref() {
|
||||
@ -1614,22 +1744,27 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseEr
|
||||
*pos,
|
||||
)),
|
||||
|
||||
// dot_lhs.dot_rhs
|
||||
Expr::Dot(dot_lhs, dot_rhs, _) => match dot_lhs.as_ref() {
|
||||
// var.dot_rhs
|
||||
Expr::Variable(_, _) if is_top => valid_assignment_chain(dot_rhs, false),
|
||||
// property.dot_rhs
|
||||
Expr::Property(_, _) if !is_top => valid_assignment_chain(dot_rhs, false),
|
||||
|
||||
// var[...]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(idx_lhs, _, _)
|
||||
if matches!(idx_lhs.as_ref(), &Expr::Variable(_, _)) && is_top =>
|
||||
{
|
||||
valid_assignment_chain(dot_rhs, false)
|
||||
}
|
||||
// property[...]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(idx_lhs, _, _)
|
||||
if matches!(idx_lhs.as_ref(), &Expr::Property(_, _)) && !is_top =>
|
||||
{
|
||||
valid_assignment_chain(dot_rhs, false)
|
||||
}
|
||||
// idx_lhs[...]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(idx_lhs, _, _) => Some(ParseError::new(
|
||||
ParseErrorType::AssignmentToCopy,
|
||||
@ -1652,6 +1787,7 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseEr
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse an operator-assignment expression.
|
||||
fn parse_op_assignment(
|
||||
function: &str,
|
||||
lhs: Expr,
|
||||
@ -1667,6 +1803,7 @@ fn parse_op_assignment(
|
||||
)
|
||||
}
|
||||
|
||||
/// Parse a binary expression.
|
||||
fn parse_binary_op<'a>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
parent_precedence: u8,
|
||||
@ -1820,11 +1957,13 @@ fn parse_binary_op<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse an expression.
|
||||
fn parse_expr<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> {
|
||||
let lhs = parse_unary(input)?;
|
||||
parse_binary_op(input, 1, lhs)
|
||||
}
|
||||
|
||||
/// Parse an if statement.
|
||||
fn parse_if<'a>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
breakable: bool,
|
||||
@ -1849,6 +1988,7 @@ fn parse_if<'a>(
|
||||
Ok(Stmt::IfElse(Box::new(guard), Box::new(if_body), else_body))
|
||||
}
|
||||
|
||||
/// Parse a while loop.
|
||||
fn parse_while<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
||||
input.next();
|
||||
|
||||
@ -1858,6 +1998,7 @@ fn parse_while<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Pars
|
||||
Ok(Stmt::While(Box::new(guard), Box::new(body)))
|
||||
}
|
||||
|
||||
/// Parse a loop statement.
|
||||
fn parse_loop<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
||||
input.next();
|
||||
|
||||
@ -1866,6 +2007,7 @@ fn parse_loop<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Parse
|
||||
Ok(Stmt::Loop(Box::new(body)))
|
||||
}
|
||||
|
||||
/// Parse a for loop.
|
||||
fn parse_for<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
||||
input.next();
|
||||
|
||||
@ -1894,7 +2036,8 @@ fn parse_for<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseE
|
||||
Ok(Stmt::For(name, Box::new(expr), Box::new(body)))
|
||||
}
|
||||
|
||||
fn parse_var<'a>(
|
||||
/// Parse a variable definition statement.
|
||||
fn parse_let<'a>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
var_type: VariableType,
|
||||
) -> Result<Stmt, ParseError> {
|
||||
@ -1935,6 +2078,7 @@ fn parse_var<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a statement block.
|
||||
fn parse_block<'a>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
breakable: bool,
|
||||
@ -1995,10 +2139,12 @@ fn parse_block<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse an expression as a statement.
|
||||
fn parse_expr_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
||||
Ok(Stmt::Expr(Box::new(parse_expr(input)?)))
|
||||
}
|
||||
|
||||
/// Parse a single statement.
|
||||
fn parse_stmt<'a>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
breakable: bool,
|
||||
@ -2030,14 +2176,14 @@ fn parse_stmt<'a>(
|
||||
input.next();
|
||||
|
||||
match input.peek() {
|
||||
// return/throw at EOF
|
||||
// `return`/`throw` at EOF
|
||||
None => Ok(Stmt::ReturnWithVal(None, return_type, Position::eof())),
|
||||
// return; or throw;
|
||||
// `return;` or `throw;`
|
||||
Some((Token::SemiColon, pos)) => {
|
||||
let pos = *pos;
|
||||
Ok(Stmt::ReturnWithVal(None, return_type, pos))
|
||||
}
|
||||
// return or throw with expression
|
||||
// `return` or `throw` with expression
|
||||
Some((_, pos)) => {
|
||||
let pos = *pos;
|
||||
Ok(Stmt::ReturnWithVal(
|
||||
@ -2049,12 +2195,13 @@ fn parse_stmt<'a>(
|
||||
}
|
||||
}
|
||||
(Token::LeftBrace, _) => parse_block(input, breakable),
|
||||
(Token::Let, _) => parse_var(input, VariableType::Normal),
|
||||
(Token::Const, _) => parse_var(input, VariableType::Constant),
|
||||
(Token::Let, _) => parse_let(input, VariableType::Normal),
|
||||
(Token::Const, _) => parse_let(input, VariableType::Constant),
|
||||
_ => parse_expr_stmt(input),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a function definition.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseError> {
|
||||
let pos = input
|
||||
@ -2156,6 +2303,7 @@ fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseE
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse the global level statements.
|
||||
fn parse_global_level<'a, 'e>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
) -> Result<(Vec<Stmt>, Vec<FnDef>), ParseError> {
|
||||
@ -2204,6 +2352,7 @@ fn parse_global_level<'a, 'e>(
|
||||
Ok((statements, functions))
|
||||
}
|
||||
|
||||
/// Run the parser on an input stream, returning an AST.
|
||||
pub fn parse<'a, 'e>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
engine: &Engine<'e>,
|
||||
@ -2219,6 +2368,9 @@ pub fn parse<'a, 'e>(
|
||||
)
|
||||
}
|
||||
|
||||
/// Map a `Dynamic` value to an expression.
|
||||
///
|
||||
/// Returns Some(expression) if conversion is successful. Otherwise None.
|
||||
pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> (Option<Expr>, Dynamic) {
|
||||
if value.is::<INT>() {
|
||||
let value2 = value.clone();
|
||||
|
Loading…
Reference in New Issue
Block a user