commit
cb596a0fc3
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@ -23,10 +23,10 @@ jobs:
|
|||||||
- "--features sync"
|
- "--features sync"
|
||||||
- "--features no_optimize"
|
- "--features no_optimize"
|
||||||
- "--features no_float"
|
- "--features no_float"
|
||||||
- "--features f32_float"
|
- "--features f32_float,serde,metadata,internals"
|
||||||
- "--features decimal"
|
- "--features decimal"
|
||||||
- "--features no_float,decimal"
|
- "--features no_float,decimal"
|
||||||
- "--tests --features only_i32"
|
- "--tests --features only_i32,serde,metadata,internals"
|
||||||
- "--features only_i64"
|
- "--features only_i64"
|
||||||
- "--features no_index"
|
- "--features no_index"
|
||||||
- "--features no_object"
|
- "--features no_object"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
Rhai Release Notes
|
Rhai Release Notes
|
||||||
==================
|
==================
|
||||||
|
|
||||||
This version adds string interpolation.
|
This version adds string interpolation with `` `... ${`` ... ``} ...` `` syntax.
|
||||||
|
|
||||||
Version 0.19.16
|
Version 0.19.16
|
||||||
===============
|
===============
|
||||||
|
@ -93,8 +93,8 @@ fn bench_parse_primes(bench: &mut Bencher) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
print("Total " + total_primes_found + " primes.");
|
print(`Total ${total_primes_found} primes.`);
|
||||||
print("Run time = " + now.elapsed + " seconds.");
|
print(`Run time = ${now.elapsed} seconds.`);
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
@ -40,9 +40,9 @@ fn main() {
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
let x = get();
|
let x = get();
|
||||||
print("Script Read: " + x);
|
print(`Script Read: ${x}`);
|
||||||
x += 1;
|
x += 1;
|
||||||
print("Script Write: " + x);
|
print(`Script Write: ${x}`);
|
||||||
put(x);
|
put(x);
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
|
@ -21,9 +21,9 @@ for n in range(0, 5) {
|
|||||||
result = fib(target);
|
result = fib(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
print("Finished. Run time = " + now.elapsed + " seconds.");
|
print(`Finished. Run time = ${now.elapsed} seconds.`);
|
||||||
|
|
||||||
print("Fibonacci number #" + target + " = " + result);
|
print(`Fibonacci number #${target} = ${result}`);
|
||||||
|
|
||||||
if result != 317_811 {
|
if result != 317_811 {
|
||||||
print("The answer is WRONG! Should be 317,811!");
|
print("The answer is WRONG! Should be 317,811!");
|
||||||
|
@ -4,7 +4,7 @@ let arr = [1, 2, 3, 4];
|
|||||||
|
|
||||||
for a in arr {
|
for a in arr {
|
||||||
for b in [10, 20] {
|
for b in [10, 20] {
|
||||||
print(a + "," + b);
|
print(`${a}, ${b}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if a == 3 { break; }
|
if a == 3 { break; }
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const MAX = 1_000_000;
|
const MAX = 1_000_000;
|
||||||
|
|
||||||
print("Iterating an array with " + MAX + " items...");
|
print(`Iterating an array with ${MAX} items...`);
|
||||||
|
|
||||||
print("Ready... Go!");
|
print("Ready... Go!");
|
||||||
|
|
||||||
@ -18,5 +18,5 @@ for i in list {
|
|||||||
sum += i;
|
sum += i;
|
||||||
}
|
}
|
||||||
|
|
||||||
print("Sum = " + sum);
|
print(`Sum = ${sum}`);
|
||||||
print("Finished. Run time = " + now.elapsed + " seconds.");
|
print(`Finished. Run time = ${now.elapsed} seconds.`);
|
||||||
|
@ -68,4 +68,4 @@ for i in range(0, SIZE) {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
print("Finished. Run time = " + now.elapsed + " seconds.");
|
print(`Finished. Run time = ${now.elapsed} seconds.`);
|
||||||
|
@ -7,7 +7,7 @@ let last_value = ();
|
|||||||
let obj1 = #{
|
let obj1 = #{
|
||||||
_data: 42, // data field
|
_data: 42, // data field
|
||||||
get_data: || this._data, // property getter
|
get_data: || this._data, // property getter
|
||||||
action: || print("Data=" + this._data), // method
|
action: || print(`Data=${this._data}`), // method
|
||||||
update: |x| { // property setter
|
update: |x| { // property setter
|
||||||
this._data = x;
|
this._data = x;
|
||||||
last_value = this._data; // capture 'last_value'
|
last_value = this._data; // capture 'last_value'
|
||||||
@ -38,4 +38,4 @@ if obj2.get_data() > 0 { // property access
|
|||||||
obj2.update(42); // call method
|
obj2.update(42); // call method
|
||||||
}
|
}
|
||||||
|
|
||||||
print("Should be 84: " + last_value);
|
print(`Should be 84: ${last_value}`);
|
||||||
|
@ -25,8 +25,8 @@ for p in range(2, MAX_NUMBER_TO_CHECK) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
print("Total " + total_primes_found + " primes <= " + MAX_NUMBER_TO_CHECK);
|
print(`Total ${total_primes_found} primes <= ${MAX_NUMBER_TO_CHECK}`);
|
||||||
print("Run time = " + now.elapsed + " seconds.");
|
print(`Run time = ${now.elapsed} seconds.`);
|
||||||
|
|
||||||
if total_primes_found != 78_498 {
|
if total_primes_found != 78_498 {
|
||||||
print("The answer is WRONG! Should be 78,498!");
|
print("The answer is WRONG! Should be 78,498!");
|
||||||
|
@ -10,4 +10,4 @@ while x > 0 {
|
|||||||
x -= 1;
|
x -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
print("Finished. Run time = " + now.elapsed + " seconds.");
|
print(`Finished. Run time = ${now.elapsed} seconds.`);
|
||||||
|
@ -11,17 +11,18 @@ print("foo" >= "bar"); // string comparison
|
|||||||
print("the answer is " + 42); // string building using non-string types
|
print("the answer is " + 42); // string building using non-string types
|
||||||
|
|
||||||
let s = "\u2764 hello, world! \U0001F603"; // string variable
|
let s = "\u2764 hello, world! \U0001F603"; // string variable
|
||||||
print("length=" + s.len); // should be 17
|
print(`length=${s.len}`); // should be 17
|
||||||
|
|
||||||
s[s.len-3] = '?'; // change the string
|
s[s.len-3] = '?'; // change the string
|
||||||
print("Question: " + s); // should print 'Question: hello, world?'
|
print(`Question: ${s}`); // should print 'Question: hello, world?'
|
||||||
|
|
||||||
// Line continuation:
|
// Line continuation:
|
||||||
let s = "This is a long \
|
let s = "This is a long \
|
||||||
string constructed using \
|
string constructed using \
|
||||||
line continuation";
|
line continuation";
|
||||||
|
|
||||||
print("One string: " + s);
|
// String interpolation
|
||||||
|
print(`One string: ${s}`);
|
||||||
|
|
||||||
// Multi-line literal string:
|
// Multi-line literal string:
|
||||||
let s = `
|
let s = `
|
||||||
|
@ -75,7 +75,7 @@ let keys = [];
|
|||||||
for animal in animals {
|
for animal in animals {
|
||||||
for adjective in adjectives {
|
for adjective in adjectives {
|
||||||
for adverb in adverbs {
|
for adverb in adverbs {
|
||||||
keys.push(adverb + " " + adjective + " " + animal)
|
keys.push(`${adverb} ${adjective} ${animal}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,5 +99,5 @@ for key in keys {
|
|||||||
map.remove(key);
|
map.remove(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
print("Sum = " + sum);
|
print(`Sum = ${sum}`);
|
||||||
print("Finished. Run time = " + now.elapsed + " seconds.");
|
print(`Finished. Run time = ${now.elapsed} seconds.`);
|
||||||
|
203
src/ast.rs
203
src/ast.rs
@ -9,7 +9,7 @@ use crate::stdlib::{
|
|||||||
fmt,
|
fmt,
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
iter::empty,
|
iter::empty,
|
||||||
num::NonZeroUsize,
|
num::{NonZeroU8, NonZeroUsize},
|
||||||
ops::{Add, AddAssign},
|
ops::{Add, AddAssign},
|
||||||
string::String,
|
string::String,
|
||||||
vec,
|
vec,
|
||||||
@ -25,6 +25,9 @@ use crate::{
|
|||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
use crate::{stdlib::str::FromStr, FLOAT};
|
use crate::{stdlib::str::FromStr, FLOAT};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
use num_traits::Float;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
use crate::Array;
|
use crate::Array;
|
||||||
|
|
||||||
@ -384,7 +387,7 @@ impl AST {
|
|||||||
/// "#)?;
|
/// "#)?;
|
||||||
///
|
///
|
||||||
/// let ast2 = engine.compile(r#"
|
/// let ast2 = engine.compile(r#"
|
||||||
/// fn foo(n) { "hello" + n }
|
/// fn foo(n) { `hello${n}` }
|
||||||
/// foo("!")
|
/// foo("!")
|
||||||
/// "#)?;
|
/// "#)?;
|
||||||
///
|
///
|
||||||
@ -395,7 +398,7 @@ impl AST {
|
|||||||
///
|
///
|
||||||
/// // 'ast' is essentially:
|
/// // 'ast' is essentially:
|
||||||
/// //
|
/// //
|
||||||
/// // fn foo(n) { "hello" + n } // <- definition of first 'foo' is overwritten
|
/// // fn foo(n) { `hello${n}` } // <- definition of first 'foo' is overwritten
|
||||||
/// // foo(1) // <- notice this will be "hello1" instead of 43,
|
/// // foo(1) // <- notice this will be "hello1" instead of 43,
|
||||||
/// // // but it is no longer the return value
|
/// // // but it is no longer the return value
|
||||||
/// // foo("!") // returns "hello!"
|
/// // foo("!") // returns "hello!"
|
||||||
@ -436,7 +439,7 @@ impl AST {
|
|||||||
/// "#)?;
|
/// "#)?;
|
||||||
///
|
///
|
||||||
/// let ast2 = engine.compile(r#"
|
/// let ast2 = engine.compile(r#"
|
||||||
/// fn foo(n) { "hello" + n }
|
/// fn foo(n) { `hello${n}` }
|
||||||
/// foo("!")
|
/// foo("!")
|
||||||
/// "#)?;
|
/// "#)?;
|
||||||
///
|
///
|
||||||
@ -447,7 +450,7 @@ impl AST {
|
|||||||
///
|
///
|
||||||
/// // 'ast1' is essentially:
|
/// // 'ast1' is essentially:
|
||||||
/// //
|
/// //
|
||||||
/// // fn foo(n) { "hello" + n } // <- definition of first 'foo' is overwritten
|
/// // fn foo(n) { `hello${n}` } // <- definition of first 'foo' is overwritten
|
||||||
/// // foo(1) // <- notice this will be "hello1" instead of 43,
|
/// // foo(1) // <- notice this will be "hello1" instead of 43,
|
||||||
/// // // but it is no longer the return value
|
/// // // but it is no longer the return value
|
||||||
/// // foo("!") // returns "hello!"
|
/// // foo("!") // returns "hello!"
|
||||||
@ -490,7 +493,7 @@ impl AST {
|
|||||||
/// "#)?;
|
/// "#)?;
|
||||||
///
|
///
|
||||||
/// let ast2 = engine.compile(r#"
|
/// let ast2 = engine.compile(r#"
|
||||||
/// fn foo(n) { "hello" + n }
|
/// fn foo(n) { `hello${n}` }
|
||||||
/// fn error() { 0 }
|
/// fn error() { 0 }
|
||||||
/// foo("!")
|
/// foo("!")
|
||||||
/// "#)?;
|
/// "#)?;
|
||||||
@ -574,7 +577,7 @@ impl AST {
|
|||||||
/// "#)?;
|
/// "#)?;
|
||||||
///
|
///
|
||||||
/// let ast2 = engine.compile(r#"
|
/// let ast2 = engine.compile(r#"
|
||||||
/// fn foo(n) { "hello" + n }
|
/// fn foo(n) { `hello${n}` }
|
||||||
/// fn error() { 0 }
|
/// fn error() { 0 }
|
||||||
/// foo("!")
|
/// foo("!")
|
||||||
/// "#)?;
|
/// "#)?;
|
||||||
@ -776,7 +779,7 @@ pub struct Ident {
|
|||||||
impl fmt::Debug for Ident {
|
impl fmt::Debug for Ident {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "Ident({:?} @ {:?})", self.name, self.pos)
|
write!(f, "{:?} @ {:?}", self.name, self.pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1440,13 +1443,13 @@ impl FnCallExpr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A type that wraps a [`FLOAT`] and implements [`Hash`].
|
/// A type that wraps a floating-point number and implements [`Hash`].
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
#[derive(Clone, Copy, PartialEq, PartialOrd)]
|
#[derive(Clone, Copy, PartialEq, PartialOrd)]
|
||||||
pub struct FloatWrapper(FLOAT);
|
pub struct FloatWrapper<F>(F);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
impl Hash for FloatWrapper {
|
impl Hash for FloatWrapper<FLOAT> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn hash<H: crate::stdlib::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: crate::stdlib::hash::Hasher>(&self, state: &mut H) {
|
||||||
self.0.to_ne_bytes().hash(state);
|
self.0.to_ne_bytes().hash(state);
|
||||||
@ -1454,24 +1457,24 @@ impl Hash for FloatWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
impl AsRef<FLOAT> for FloatWrapper {
|
impl<F: Float> AsRef<F> for FloatWrapper<F> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn as_ref(&self) -> &FLOAT {
|
fn as_ref(&self) -> &F {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
impl AsMut<FLOAT> for FloatWrapper {
|
impl<F: Float> AsMut<F> for FloatWrapper<F> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn as_mut(&mut self) -> &mut FLOAT {
|
fn as_mut(&mut self) -> &mut F {
|
||||||
&mut self.0
|
&mut self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
impl crate::stdlib::ops::Deref for FloatWrapper {
|
impl<F: Float> crate::stdlib::ops::Deref for FloatWrapper<F> {
|
||||||
type Target = FLOAT;
|
type Target = F;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
@ -1480,7 +1483,7 @@ impl crate::stdlib::ops::Deref for FloatWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
impl crate::stdlib::ops::DerefMut for FloatWrapper {
|
impl<F: Float> crate::stdlib::ops::DerefMut for FloatWrapper<F> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
&mut self.0
|
&mut self.0
|
||||||
@ -1488,52 +1491,68 @@ impl crate::stdlib::ops::DerefMut for FloatWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
impl fmt::Debug for FloatWrapper {
|
impl<F: Float + fmt::Display> fmt::Debug for FloatWrapper<F> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
fmt::Display::fmt(self, f)
|
fmt::Display::fmt(&self.0, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
impl fmt::Display for FloatWrapper {
|
impl<F: Float + fmt::Display + fmt::LowerExp + From<f32>> fmt::Display for FloatWrapper<F> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
#[cfg(feature = "no_std")]
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
|
||||||
use num_traits::Float;
|
|
||||||
|
|
||||||
let abs = self.0.abs();
|
let abs = self.0.abs();
|
||||||
if abs > 10000000000000.0 || abs < 0.0000000000001 {
|
if abs > Self::MAX_NATURAL_FLOAT_FOR_DISPLAY.into()
|
||||||
|
|| abs < Self::MIN_NATURAL_FLOAT_FOR_DISPLAY.into()
|
||||||
|
{
|
||||||
write!(f, "{:e}", self.0)
|
write!(f, "{:e}", self.0)
|
||||||
} else {
|
} else {
|
||||||
self.0.fmt(f)
|
fmt::Display::fmt(&self.0, f)?;
|
||||||
|
if abs.fract().is_zero() {
|
||||||
|
f.write_str(".0")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
impl From<FLOAT> for FloatWrapper {
|
impl<F: Float> From<F> for FloatWrapper<F> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn from(value: FLOAT) -> Self {
|
fn from(value: F) -> Self {
|
||||||
Self::new(value)
|
Self::new(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
impl FromStr for FloatWrapper {
|
impl<F: Float + FromStr> FromStr for FloatWrapper<F> {
|
||||||
type Err = <FLOAT as FromStr>::Err;
|
type Err = <F as FromStr>::Err;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
FLOAT::from_str(s).map(Into::<Self>::into)
|
F::from_str(s).map(Into::<Self>::into)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
impl FloatWrapper {
|
impl<F: Float> FloatWrapper<F> {
|
||||||
|
/// Maximum floating-point number for natural display before switching to scientific notation.
|
||||||
|
pub const MAX_NATURAL_FLOAT_FOR_DISPLAY: f32 = 10000000000000.0;
|
||||||
|
|
||||||
|
/// Minimum floating-point number for natural display before switching to scientific notation.
|
||||||
|
pub const MIN_NATURAL_FLOAT_FOR_DISPLAY: f32 = 0.0000000000001;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub const fn new(value: FLOAT) -> Self {
|
pub fn new(value: F) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
impl FloatWrapper<FLOAT> {
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) const fn const_new(value: FLOAT) -> Self {
|
||||||
Self(value)
|
Self(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1544,7 +1563,7 @@ impl FloatWrapper {
|
|||||||
/// # Volatile Data Structure
|
/// # Volatile Data Structure
|
||||||
///
|
///
|
||||||
/// This type is volatile and may change.
|
/// This type is volatile and may change.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Clone, Hash)]
|
||||||
pub enum Expr {
|
pub enum Expr {
|
||||||
/// Dynamic constant.
|
/// Dynamic constant.
|
||||||
/// Used to hold either an [`Array`] or [`Map`][crate::Map] literal for quick cloning.
|
/// Used to hold either an [`Array`] or [`Map`][crate::Map] literal for quick cloning.
|
||||||
@ -1556,7 +1575,7 @@ pub enum Expr {
|
|||||||
IntegerConstant(INT, Position),
|
IntegerConstant(INT, Position),
|
||||||
/// Floating-point constant.
|
/// Floating-point constant.
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
FloatConstant(FloatWrapper, Position),
|
FloatConstant(FloatWrapper<FLOAT>, Position),
|
||||||
/// Character constant.
|
/// Character constant.
|
||||||
CharConstant(char, Position),
|
CharConstant(char, Position),
|
||||||
/// [String][ImmutableString] constant.
|
/// [String][ImmutableString] constant.
|
||||||
@ -1574,8 +1593,20 @@ pub enum Expr {
|
|||||||
),
|
),
|
||||||
/// ()
|
/// ()
|
||||||
Unit(Position),
|
Unit(Position),
|
||||||
/// Variable access - (optional index, optional (hash, modules), variable name)
|
/// Variable access - optional short index, position, (optional index, optional (hash, modules), variable name)
|
||||||
Variable(Box<(Option<NonZeroUsize>, Option<(u64, NamespaceRef)>, Ident)>),
|
///
|
||||||
|
/// The short index is [`u8`] which is used when the index is <= 255, which should be the vast
|
||||||
|
/// majority of cases (unless there are more than 255 variables defined!).
|
||||||
|
/// This is to avoid reading a pointer redirection during each variable access.
|
||||||
|
Variable(
|
||||||
|
Option<NonZeroU8>,
|
||||||
|
Position,
|
||||||
|
Box<(
|
||||||
|
Option<NonZeroUsize>,
|
||||||
|
Option<(u64, NamespaceRef)>,
|
||||||
|
Identifier,
|
||||||
|
)>,
|
||||||
|
),
|
||||||
/// Property access - ((getter, hash), (setter, hash), prop)
|
/// Property access - ((getter, hash), (setter, hash), prop)
|
||||||
Property(Box<((Identifier, u64), (Identifier, u64), Ident)>),
|
Property(Box<((Identifier, u64), (Identifier, u64), Ident)>),
|
||||||
/// { [statement][Stmt] ... }
|
/// { [statement][Stmt] ... }
|
||||||
@ -1601,6 +1632,80 @@ impl Default for Expr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Expr {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::DynamicConstant(value, pos) => write!(f, "{:?} @ {:?}", value, pos),
|
||||||
|
Self::BoolConstant(value, pos) => write!(f, "{} @ {:?}", value, pos),
|
||||||
|
Self::IntegerConstant(value, pos) => write!(f, "{} @ {:?}", value, pos),
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
Self::FloatConstant(value, pos) => write!(f, "{} @ {:?}", value, pos),
|
||||||
|
Self::CharConstant(value, pos) => write!(f, "{:?} @ {:?}", value, pos),
|
||||||
|
Self::StringConstant(value, pos) => write!(f, "{:?} @ {:?}", value, pos),
|
||||||
|
Self::FnPointer(value, pos) => write!(f, "Fn({:?}) @ {:?}", value, pos),
|
||||||
|
Self::Unit(pos) => write!(f, "() @ {:?}", pos),
|
||||||
|
Self::InterpolatedString(x) => {
|
||||||
|
f.write_str("InterpolatedString")?;
|
||||||
|
f.debug_list().entries(x.iter()).finish()
|
||||||
|
}
|
||||||
|
Self::Array(x, pos) => {
|
||||||
|
f.write_str("Array")?;
|
||||||
|
f.debug_list().entries(x.iter()).finish()?;
|
||||||
|
write!(f, " @ {:?}", pos)
|
||||||
|
}
|
||||||
|
Self::Map(x, pos) => {
|
||||||
|
f.write_str("Map")?;
|
||||||
|
f.debug_map()
|
||||||
|
.entries(x.0.iter().map(|(k, v)| (k, v)))
|
||||||
|
.finish()?;
|
||||||
|
write!(f, " @ {:?}", pos)
|
||||||
|
}
|
||||||
|
Self::Variable(i, pos, x) => {
|
||||||
|
f.write_str("Variable(")?;
|
||||||
|
match x.1 {
|
||||||
|
Some((_, ref namespace)) => write!(f, "{}::", namespace)?,
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
write!(f, "{}", x.2)?;
|
||||||
|
match i.map_or_else(|| x.0, |n| NonZeroUsize::new(n.get() as usize)) {
|
||||||
|
Some(n) => write!(f, " [{}]", n)?,
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
write!(f, ") @ {:?}", pos)
|
||||||
|
}
|
||||||
|
Self::Property(x) => write!(f, "Property({:?} @ {:?})", x.2.name, x.2.pos),
|
||||||
|
Self::Stmt(x) => {
|
||||||
|
f.write_str("Stmt")?;
|
||||||
|
f.debug_list().entries(x.statements.iter()).finish()?;
|
||||||
|
write!(f, " @ {:?}", x.pos)
|
||||||
|
}
|
||||||
|
Self::FnCall(x, pos) => {
|
||||||
|
f.debug_tuple("FnCall").field(x).finish()?;
|
||||||
|
write!(f, " @ {:?}", pos)
|
||||||
|
}
|
||||||
|
Self::Dot(x, pos) | Self::Index(x, pos) | Self::And(x, pos) | Self::Or(x, pos) => {
|
||||||
|
let op = match self {
|
||||||
|
Self::Dot(_, _) => "Dot",
|
||||||
|
Self::Index(_, _) => "Index",
|
||||||
|
Self::And(_, _) => "And",
|
||||||
|
Self::Or(_, _) => "Or",
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
f.debug_struct(op)
|
||||||
|
.field("lhs", &x.lhs)
|
||||||
|
.field("rhs", &x.rhs)
|
||||||
|
.finish()?;
|
||||||
|
write!(f, " @ {:?}", pos)
|
||||||
|
}
|
||||||
|
Self::Custom(x, pos) => {
|
||||||
|
f.debug_tuple("Custom").field(x).finish()?;
|
||||||
|
write!(f, " @ {:?}", pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Expr {
|
impl Expr {
|
||||||
/// Get the [`Dynamic`] value of a constant expression.
|
/// Get the [`Dynamic`] value of a constant expression.
|
||||||
///
|
///
|
||||||
@ -1640,11 +1745,19 @@ impl Expr {
|
|||||||
_ => return None,
|
_ => return None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/// Is the expression a simple variable access?
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn is_variable_access(&self, non_qualified: bool) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Variable(_, _, x) => !non_qualified || x.1.is_none(),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Return the variable name if the expression a simple variable access.
|
/// Return the variable name if the expression a simple variable access.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn get_variable_access(&self, non_qualified: bool) -> Option<&str> {
|
pub(crate) fn get_variable_name(&self, non_qualified: bool) -> Option<&str> {
|
||||||
match self {
|
match self {
|
||||||
Self::Variable(x) if !non_qualified || x.1.is_none() => Some((x.2).name.as_str()),
|
Self::Variable(_, _, x) if !non_qualified || x.1.is_none() => Some(x.2.as_str()),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1666,7 +1779,7 @@ impl Expr {
|
|||||||
Self::Map(_, pos) => *pos,
|
Self::Map(_, pos) => *pos,
|
||||||
Self::Property(x) => (x.2).pos,
|
Self::Property(x) => (x.2).pos,
|
||||||
Self::Stmt(x) => x.pos,
|
Self::Stmt(x) => x.pos,
|
||||||
Self::Variable(x) => (x.2).pos,
|
Self::Variable(_, pos, _) => *pos,
|
||||||
Self::FnCall(_, pos) => *pos,
|
Self::FnCall(_, pos) => *pos,
|
||||||
|
|
||||||
Self::And(x, _) | Self::Or(x, _) => x.lhs.position(),
|
Self::And(x, _) | Self::Or(x, _) => x.lhs.position(),
|
||||||
@ -1696,7 +1809,7 @@ impl Expr {
|
|||||||
Self::FnPointer(_, pos) => *pos = new_pos,
|
Self::FnPointer(_, pos) => *pos = new_pos,
|
||||||
Self::Array(_, pos) => *pos = new_pos,
|
Self::Array(_, pos) => *pos = new_pos,
|
||||||
Self::Map(_, pos) => *pos = new_pos,
|
Self::Map(_, pos) => *pos = new_pos,
|
||||||
Self::Variable(x) => (x.2).pos = new_pos,
|
Self::Variable(_, pos, _) => *pos = new_pos,
|
||||||
Self::Property(x) => (x.2).pos = new_pos,
|
Self::Property(x) => (x.2).pos = new_pos,
|
||||||
Self::Stmt(x) => x.pos = new_pos,
|
Self::Stmt(x) => x.pos = new_pos,
|
||||||
Self::FnCall(_, pos) => *pos = new_pos,
|
Self::FnCall(_, pos) => *pos = new_pos,
|
||||||
@ -1724,7 +1837,7 @@ impl Expr {
|
|||||||
|
|
||||||
Self::Stmt(x) => x.statements.iter().all(Stmt::is_pure),
|
Self::Stmt(x) => x.statements.iter().all(Stmt::is_pure),
|
||||||
|
|
||||||
Self::Variable(_) => true,
|
Self::Variable(_, _, _) => true,
|
||||||
|
|
||||||
_ => self.is_constant(),
|
_ => self.is_constant(),
|
||||||
}
|
}
|
||||||
@ -1794,7 +1907,7 @@ impl Expr {
|
|||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
|
|
||||||
Self::Variable(_) => match token {
|
Self::Variable(_, _, _) => match token {
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Token::LeftBracket => true,
|
Token::LeftBracket => true,
|
||||||
Token::LeftParen => true,
|
Token::LeftParen => true,
|
||||||
@ -1894,7 +2007,7 @@ mod tests {
|
|||||||
assert_eq!(size_of::<Option<ast::Expr>>(), 16);
|
assert_eq!(size_of::<Option<ast::Expr>>(), 16);
|
||||||
assert_eq!(size_of::<ast::Stmt>(), 32);
|
assert_eq!(size_of::<ast::Stmt>(), 32);
|
||||||
assert_eq!(size_of::<Option<ast::Stmt>>(), 32);
|
assert_eq!(size_of::<Option<ast::Stmt>>(), 32);
|
||||||
assert_eq!(size_of::<FnPtr>(), 80);
|
assert_eq!(size_of::<FnPtr>(), 96);
|
||||||
assert_eq!(size_of::<Scope>(), 288);
|
assert_eq!(size_of::<Scope>(), 288);
|
||||||
assert_eq!(size_of::<LexError>(), 56);
|
assert_eq!(size_of::<LexError>(), 56);
|
||||||
assert_eq!(size_of::<ParseError>(), 16);
|
assert_eq!(size_of::<ParseError>(), 16);
|
||||||
|
@ -159,7 +159,7 @@ pub enum Union {
|
|||||||
Int(INT, AccessMode),
|
Int(INT, AccessMode),
|
||||||
/// A floating-point value.
|
/// A floating-point value.
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Float(FloatWrapper, AccessMode),
|
Float(FloatWrapper<FLOAT>, AccessMode),
|
||||||
/// A fixed-precision decimal value.
|
/// A fixed-precision decimal value.
|
||||||
#[cfg(feature = "decimal")]
|
#[cfg(feature = "decimal")]
|
||||||
Decimal(Box<Decimal>, AccessMode),
|
Decimal(Box<Decimal>, AccessMode),
|
||||||
@ -682,16 +682,22 @@ impl Dynamic {
|
|||||||
pub const NEGATIVE_ONE: Dynamic = Self(Union::Int(-1, AccessMode::ReadWrite));
|
pub const NEGATIVE_ONE: Dynamic = Self(Union::Int(-1, AccessMode::ReadWrite));
|
||||||
/// A [`Dynamic`] containing the floating-point zero.
|
/// A [`Dynamic`] containing the floating-point zero.
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
pub const FLOAT_ZERO: Dynamic =
|
pub const FLOAT_ZERO: Dynamic = Self(Union::Float(
|
||||||
Self(Union::Float(FloatWrapper::new(0.0), AccessMode::ReadWrite));
|
FloatWrapper::const_new(0.0),
|
||||||
|
AccessMode::ReadWrite,
|
||||||
|
));
|
||||||
/// A [`Dynamic`] containing the floating-point one.
|
/// A [`Dynamic`] containing the floating-point one.
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
pub const FLOAT_ONE: Dynamic =
|
pub const FLOAT_ONE: Dynamic = Self(Union::Float(
|
||||||
Self(Union::Float(FloatWrapper::new(1.0), AccessMode::ReadWrite));
|
FloatWrapper::const_new(1.0),
|
||||||
|
AccessMode::ReadWrite,
|
||||||
|
));
|
||||||
/// A [`Dynamic`] containing the floating-point negative one.
|
/// A [`Dynamic`] containing the floating-point negative one.
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
pub const FLOAT_NEGATIVE_ONE: Dynamic =
|
pub const FLOAT_NEGATIVE_ONE: Dynamic = Self(Union::Float(
|
||||||
Self(Union::Float(FloatWrapper::new(-1.0), AccessMode::ReadWrite));
|
FloatWrapper::const_new(-1.0),
|
||||||
|
AccessMode::ReadWrite,
|
||||||
|
));
|
||||||
|
|
||||||
/// Get the [`AccessMode`] for this [`Dynamic`].
|
/// Get the [`AccessMode`] for this [`Dynamic`].
|
||||||
pub(crate) fn access_mode(&self) -> AccessMode {
|
pub(crate) fn access_mode(&self) -> AccessMode {
|
||||||
@ -1670,9 +1676,9 @@ impl From<FLOAT> for Dynamic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
impl From<FloatWrapper> for Dynamic {
|
impl From<FloatWrapper<FLOAT>> for Dynamic {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn from(value: FloatWrapper) -> Self {
|
fn from(value: FloatWrapper<FLOAT>) -> Self {
|
||||||
Self(Union::Float(value, AccessMode::ReadWrite))
|
Self(Union::Float(value, AccessMode::ReadWrite))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
115
src/engine.rs
115
src/engine.rs
@ -52,7 +52,7 @@ pub type Precedence = NonZeroU8;
|
|||||||
// We cannot use Cow<str> here because `eval` may load a [module][Module] and
|
// We cannot use Cow<str> here because `eval` may load a [module][Module] and
|
||||||
// the module name will live beyond the AST of the eval script text.
|
// the module name will live beyond the AST of the eval script text.
|
||||||
// The best we can do is a shared reference.
|
// The best we can do is a shared reference.
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct Imports(StaticVec<Identifier>, StaticVec<Shared<Module>>);
|
pub struct Imports(StaticVec<Identifier>, StaticVec<Shared<Module>>);
|
||||||
|
|
||||||
impl Imports {
|
impl Imports {
|
||||||
@ -147,6 +147,20 @@ impl Imports {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Imports {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str("Imports")?;
|
||||||
|
|
||||||
|
if self.is_empty() {
|
||||||
|
f.debug_map().finish()
|
||||||
|
} else {
|
||||||
|
f.debug_map()
|
||||||
|
.entries(self.0.iter().zip(self.1.iter()))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
@ -959,9 +973,14 @@ impl Engine {
|
|||||||
expr: &Expr,
|
expr: &Expr,
|
||||||
) -> Result<(Target<'s>, Position), Box<EvalAltResult>> {
|
) -> Result<(Target<'s>, Position), Box<EvalAltResult>> {
|
||||||
match expr {
|
match expr {
|
||||||
Expr::Variable(v) => match v.as_ref() {
|
Expr::Variable(Some(_), _, _) => {
|
||||||
|
self.search_scope_only(scope, mods, state, lib, this_ptr, expr)
|
||||||
|
}
|
||||||
|
Expr::Variable(None, var_pos, v) => match v.as_ref() {
|
||||||
|
// Normal variable access
|
||||||
|
(_, None, _) => self.search_scope_only(scope, mods, state, lib, this_ptr, expr),
|
||||||
// Qualified variable
|
// Qualified variable
|
||||||
(_, Some((hash_var, modules)), Ident { name, pos, .. }) => {
|
(_, Some((hash_var, modules)), var_name) => {
|
||||||
let module = self.search_imports(mods, state, modules).ok_or_else(|| {
|
let module = self.search_imports(mods, state, modules).ok_or_else(|| {
|
||||||
EvalAltResult::ErrorModuleNotFound(
|
EvalAltResult::ErrorModuleNotFound(
|
||||||
modules[0].name.to_string(),
|
modules[0].name.to_string(),
|
||||||
@ -971,20 +990,18 @@ impl Engine {
|
|||||||
let target = module.get_qualified_var(*hash_var).map_err(|mut err| {
|
let target = module.get_qualified_var(*hash_var).map_err(|mut err| {
|
||||||
match *err {
|
match *err {
|
||||||
EvalAltResult::ErrorVariableNotFound(ref mut err_name, _) => {
|
EvalAltResult::ErrorVariableNotFound(ref mut err_name, _) => {
|
||||||
*err_name = format!("{}{}", modules, name);
|
*err_name = format!("{}{}", modules, var_name);
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
err.fill_position(*pos)
|
err.fill_position(*var_pos)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Module variables are constant
|
// Module variables are constant
|
||||||
let mut target = target.clone();
|
let mut target = target.clone();
|
||||||
target.set_access_mode(AccessMode::ReadOnly);
|
target.set_access_mode(AccessMode::ReadOnly);
|
||||||
Ok((target.into(), *pos))
|
Ok((target.into(), *var_pos))
|
||||||
}
|
}
|
||||||
// Normal variable access
|
|
||||||
_ => self.search_scope_only(scope, mods, state, lib, this_ptr, expr),
|
|
||||||
},
|
},
|
||||||
_ => unreachable!("Expr::Variable expected, but gets {:?}", expr),
|
_ => unreachable!("Expr::Variable expected, but gets {:?}", expr),
|
||||||
}
|
}
|
||||||
@ -1000,26 +1017,25 @@ impl Engine {
|
|||||||
this_ptr: &'s mut Option<&mut Dynamic>,
|
this_ptr: &'s mut Option<&mut Dynamic>,
|
||||||
expr: &Expr,
|
expr: &Expr,
|
||||||
) -> Result<(Target<'s>, Position), Box<EvalAltResult>> {
|
) -> Result<(Target<'s>, Position), Box<EvalAltResult>> {
|
||||||
let (index, _, Ident { name, pos, .. }) = match expr {
|
// Make sure that the pointer indirection is taken only when absolutely necessary.
|
||||||
Expr::Variable(v) => v.as_ref(),
|
|
||||||
|
let (index, var_pos) = match expr {
|
||||||
|
// Check if the variable is `this`
|
||||||
|
Expr::Variable(None, pos, v) if v.0.is_none() && v.2 == KEYWORD_THIS => {
|
||||||
|
return if let Some(val) = this_ptr {
|
||||||
|
Ok(((*val).into(), *pos))
|
||||||
|
} else {
|
||||||
|
EvalAltResult::ErrorUnboundThis(*pos).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ if state.always_search => (0, expr.position()),
|
||||||
|
Expr::Variable(Some(i), pos, _) => (i.get() as usize, *pos),
|
||||||
|
Expr::Variable(None, pos, v) => (v.0.map(NonZeroUsize::get).unwrap_or(0), *pos),
|
||||||
_ => unreachable!("Expr::Variable expected, but gets {:?}", expr),
|
_ => unreachable!("Expr::Variable expected, but gets {:?}", expr),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if the variable is `this`
|
|
||||||
if *name == KEYWORD_THIS {
|
|
||||||
return if let Some(val) = this_ptr {
|
|
||||||
Ok(((*val).into(), *pos))
|
|
||||||
} else {
|
|
||||||
EvalAltResult::ErrorUnboundThis(*pos).into()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if it is directly indexed
|
|
||||||
let index = if state.always_search { &None } else { index };
|
|
||||||
|
|
||||||
// Check the variable resolver, if any
|
// Check the variable resolver, if any
|
||||||
if let Some(ref resolve_var) = self.resolve_var {
|
if let Some(ref resolve_var) = self.resolve_var {
|
||||||
let index = index.map(NonZeroUsize::get).unwrap_or(0);
|
|
||||||
let context = EvalContext {
|
let context = EvalContext {
|
||||||
engine: self,
|
engine: self,
|
||||||
scope,
|
scope,
|
||||||
@ -1030,26 +1046,28 @@ impl Engine {
|
|||||||
level: 0,
|
level: 0,
|
||||||
};
|
};
|
||||||
if let Some(mut result) =
|
if let Some(mut result) =
|
||||||
resolve_var(name, index, &context).map_err(|err| err.fill_position(*pos))?
|
resolve_var(expr.get_variable_name(true).unwrap(), index, &context)
|
||||||
|
.map_err(|err| err.fill_position(var_pos))?
|
||||||
{
|
{
|
||||||
result.set_access_mode(AccessMode::ReadOnly);
|
result.set_access_mode(AccessMode::ReadOnly);
|
||||||
return Ok((result.into(), *pos));
|
return Ok((result.into(), var_pos));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let index = if let Some(index) = index {
|
let index = if index > 0 {
|
||||||
scope.len() - index.get()
|
scope.len() - index
|
||||||
} else {
|
} else {
|
||||||
// Find the variable in the scope
|
// Find the variable in the scope
|
||||||
|
let var_name = expr.get_variable_name(true).unwrap();
|
||||||
scope
|
scope
|
||||||
.get_index(name)
|
.get_index(var_name)
|
||||||
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(name.to_string(), *pos))?
|
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(var_name.to_string(), var_pos))?
|
||||||
.0
|
.0
|
||||||
};
|
};
|
||||||
|
|
||||||
let val = scope.get_mut_by_index(index);
|
let val = scope.get_mut_by_index(index);
|
||||||
|
|
||||||
Ok((val.into(), *pos))
|
Ok((val.into(), var_pos))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Chain-evaluate a dot/index chain.
|
/// Chain-evaluate a dot/index chain.
|
||||||
@ -1401,13 +1419,7 @@ impl Engine {
|
|||||||
|
|
||||||
match lhs {
|
match lhs {
|
||||||
// id.??? or id[???]
|
// id.??? or id[???]
|
||||||
Expr::Variable(x) => {
|
Expr::Variable(_, var_pos, x) => {
|
||||||
let Ident {
|
|
||||||
name: var_name,
|
|
||||||
pos: var_pos,
|
|
||||||
..
|
|
||||||
} = &x.2;
|
|
||||||
|
|
||||||
self.inc_operations(state, *var_pos)?;
|
self.inc_operations(state, *var_pos)?;
|
||||||
|
|
||||||
let (target, pos) =
|
let (target, pos) =
|
||||||
@ -1415,8 +1427,7 @@ impl Engine {
|
|||||||
|
|
||||||
// Constants cannot be modified
|
// Constants cannot be modified
|
||||||
if target.as_ref().is_read_only() && new_val.is_some() {
|
if target.as_ref().is_read_only() && new_val.is_some() {
|
||||||
return EvalAltResult::ErrorAssignmentToConstant(var_name.to_string(), pos)
|
return EvalAltResult::ErrorAssignmentToConstant(x.2.to_string(), pos).into();
|
||||||
.into();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let obj_ptr = &mut target.into();
|
let obj_ptr = &mut target.into();
|
||||||
@ -1562,7 +1573,7 @@ impl Engine {
|
|||||||
state: &mut State,
|
state: &mut State,
|
||||||
_lib: &[&Module],
|
_lib: &[&Module],
|
||||||
target: &'t mut Dynamic,
|
target: &'t mut Dynamic,
|
||||||
mut idx: Dynamic,
|
mut _idx: Dynamic,
|
||||||
idx_pos: Position,
|
idx_pos: Position,
|
||||||
_create: bool,
|
_create: bool,
|
||||||
_is_ref: bool,
|
_is_ref: bool,
|
||||||
@ -1575,7 +1586,7 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Dynamic(Union::Array(arr, _)) => {
|
Dynamic(Union::Array(arr, _)) => {
|
||||||
// val_array[idx]
|
// val_array[idx]
|
||||||
let index = idx
|
let index = _idx
|
||||||
.as_int()
|
.as_int()
|
||||||
.map_err(|err| self.make_type_mismatch_err::<crate::INT>(err, idx_pos))?;
|
.map_err(|err| self.make_type_mismatch_err::<crate::INT>(err, idx_pos))?;
|
||||||
|
|
||||||
@ -1595,8 +1606,8 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Dynamic(Union::Map(map, _)) => {
|
Dynamic(Union::Map(map, _)) => {
|
||||||
// val_map[idx]
|
// val_map[idx]
|
||||||
let index = &*idx.read_lock::<ImmutableString>().ok_or_else(|| {
|
let index = &*_idx.read_lock::<ImmutableString>().ok_or_else(|| {
|
||||||
self.make_type_mismatch_err::<ImmutableString>(idx.type_name(), idx_pos)
|
self.make_type_mismatch_err::<ImmutableString>(_idx.type_name(), idx_pos)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if _create && !map.contains_key(index.as_str()) {
|
if _create && !map.contains_key(index.as_str()) {
|
||||||
@ -1613,7 +1624,7 @@ impl Engine {
|
|||||||
Dynamic(Union::Str(s, _)) => {
|
Dynamic(Union::Str(s, _)) => {
|
||||||
// val_string[idx]
|
// val_string[idx]
|
||||||
let chars_len = s.chars().count();
|
let chars_len = s.chars().count();
|
||||||
let index = idx
|
let index = _idx
|
||||||
.as_int()
|
.as_int()
|
||||||
.map_err(|err| self.make_type_mismatch_err::<crate::INT>(err, idx_pos))?;
|
.map_err(|err| self.make_type_mismatch_err::<crate::INT>(err, idx_pos))?;
|
||||||
|
|
||||||
@ -1631,7 +1642,7 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
_ if _indexers => {
|
_ if _indexers => {
|
||||||
let type_name = target.type_name();
|
let type_name = target.type_name();
|
||||||
let args = &mut [target, &mut idx];
|
let args = &mut [target, &mut _idx];
|
||||||
let hash_get = FnCallHash::from_native(calc_fn_hash(empty(), FN_IDX_GET, 2));
|
let hash_get = FnCallHash::from_native(calc_fn_hash(empty(), FN_IDX_GET, 2));
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
_mods, state, _lib, FN_IDX_GET, hash_get, args, _is_ref, true, idx_pos, None,
|
_mods, state, _lib, FN_IDX_GET, hash_get, args, _is_ref, true, idx_pos, None,
|
||||||
@ -1679,11 +1690,11 @@ impl Engine {
|
|||||||
Expr::CharConstant(x, _) => Ok((*x).into()),
|
Expr::CharConstant(x, _) => Ok((*x).into()),
|
||||||
Expr::FnPointer(x, _) => Ok(FnPtr::new_unchecked(x.clone(), Default::default()).into()),
|
Expr::FnPointer(x, _) => Ok(FnPtr::new_unchecked(x.clone(), Default::default()).into()),
|
||||||
|
|
||||||
Expr::Variable(x) if (x.2).name == KEYWORD_THIS => this_ptr
|
Expr::Variable(None, var_pos, x) if x.0.is_none() && x.2 == KEYWORD_THIS => this_ptr
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.cloned()
|
.cloned()
|
||||||
.ok_or_else(|| EvalAltResult::ErrorUnboundThis((x.2).pos).into()),
|
.ok_or_else(|| EvalAltResult::ErrorUnboundThis(*var_pos).into()),
|
||||||
Expr::Variable(_) => self
|
Expr::Variable(_, _, _) => self
|
||||||
.search_namespace(scope, mods, state, lib, this_ptr, expr)
|
.search_namespace(scope, mods, state, lib, this_ptr, expr)
|
||||||
.map(|(val, _)| val.take_or_clone()),
|
.map(|(val, _)| val.take_or_clone()),
|
||||||
|
|
||||||
@ -2010,7 +2021,7 @@ impl Engine {
|
|||||||
.flatten()),
|
.flatten()),
|
||||||
|
|
||||||
// var op= rhs
|
// var op= rhs
|
||||||
Stmt::Assignment(x, op_pos) if x.0.get_variable_access(false).is_some() => {
|
Stmt::Assignment(x, op_pos) if x.0.is_variable_access(false) => {
|
||||||
let (lhs_expr, op_info, rhs_expr) = x.as_ref();
|
let (lhs_expr, op_info, rhs_expr) = x.as_ref();
|
||||||
let rhs_val = self
|
let rhs_val = self
|
||||||
.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?
|
.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?
|
||||||
@ -2020,7 +2031,7 @@ impl Engine {
|
|||||||
|
|
||||||
if !lhs_ptr.is_ref() {
|
if !lhs_ptr.is_ref() {
|
||||||
return EvalAltResult::ErrorAssignmentToConstant(
|
return EvalAltResult::ErrorAssignmentToConstant(
|
||||||
lhs_expr.get_variable_access(false).unwrap().to_string(),
|
lhs_expr.get_variable_name(false).unwrap().to_string(),
|
||||||
pos,
|
pos,
|
||||||
)
|
)
|
||||||
.into();
|
.into();
|
||||||
@ -2031,7 +2042,7 @@ impl Engine {
|
|||||||
if lhs_ptr.as_ref().is_read_only() {
|
if lhs_ptr.as_ref().is_read_only() {
|
||||||
// Assignment to constant variable
|
// Assignment to constant variable
|
||||||
EvalAltResult::ErrorAssignmentToConstant(
|
EvalAltResult::ErrorAssignmentToConstant(
|
||||||
lhs_expr.get_variable_access(false).unwrap().to_string(),
|
lhs_expr.get_variable_name(false).unwrap().to_string(),
|
||||||
pos,
|
pos,
|
||||||
)
|
)
|
||||||
.into()
|
.into()
|
||||||
@ -2061,7 +2072,7 @@ impl Engine {
|
|||||||
// Must be either `var[index] op= val` or `var.prop op= val`
|
// Must be either `var[index] op= val` or `var.prop op= val`
|
||||||
match lhs_expr {
|
match lhs_expr {
|
||||||
// name op= rhs (handled above)
|
// name op= rhs (handled above)
|
||||||
Expr::Variable(_) => {
|
Expr::Variable(_, _, _) => {
|
||||||
unreachable!("Expr::Variable case should already been handled")
|
unreachable!("Expr::Variable case should already been handled")
|
||||||
}
|
}
|
||||||
// idx_lhs[idx_expr] op= rhs
|
// idx_lhs[idx_expr] op= rhs
|
||||||
|
@ -9,7 +9,6 @@ use crate::parser::ParseState;
|
|||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
any::{type_name, TypeId},
|
any::{type_name, TypeId},
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
num::NonZeroUsize,
|
|
||||||
string::String,
|
string::String,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -1158,16 +1157,8 @@ impl Engine {
|
|||||||
scripts: &[&str],
|
scripts: &[&str],
|
||||||
optimization_level: OptimizationLevel,
|
optimization_level: OptimizationLevel,
|
||||||
) -> Result<AST, ParseError> {
|
) -> Result<AST, ParseError> {
|
||||||
let (stream, buffer) = self.lex_raw(scripts, None);
|
let (stream, tokenizer_control) = self.lex_raw(scripts, None);
|
||||||
let mut state = ParseState::new(
|
let mut state = ParseState::new(self, tokenizer_control);
|
||||||
self,
|
|
||||||
buffer,
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
NonZeroUsize::new(self.max_expr_depth()),
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
NonZeroUsize::new(self.max_function_expr_depth()),
|
|
||||||
);
|
|
||||||
self.parse(
|
self.parse(
|
||||||
&mut stream.peekable(),
|
&mut stream.peekable(),
|
||||||
&mut state,
|
&mut state,
|
||||||
@ -1347,7 +1338,7 @@ impl Engine {
|
|||||||
.into());
|
.into());
|
||||||
};
|
};
|
||||||
|
|
||||||
let (stream, buffer) = self.lex_raw(
|
let (stream, tokenizer_control) = self.lex_raw(
|
||||||
&scripts,
|
&scripts,
|
||||||
Some(if has_null {
|
Some(if has_null {
|
||||||
|token| match token {
|
|token| match token {
|
||||||
@ -1360,15 +1351,7 @@ impl Engine {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut state = ParseState::new(
|
let mut state = ParseState::new(self, tokenizer_control);
|
||||||
self,
|
|
||||||
buffer,
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
NonZeroUsize::new(self.max_expr_depth()),
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
NonZeroUsize::new(self.max_function_expr_depth()),
|
|
||||||
);
|
|
||||||
|
|
||||||
let ast = self.parse_global_expr(
|
let ast = self.parse_global_expr(
|
||||||
&mut stream.peekable(),
|
&mut stream.peekable(),
|
||||||
@ -1454,18 +1437,10 @@ impl Engine {
|
|||||||
script: &str,
|
script: &str,
|
||||||
) -> Result<AST, ParseError> {
|
) -> Result<AST, ParseError> {
|
||||||
let scripts = [script];
|
let scripts = [script];
|
||||||
let (stream, buffer) = self.lex_raw(&scripts, None);
|
let (stream, tokenizer_control) = self.lex_raw(&scripts, None);
|
||||||
|
|
||||||
let mut peekable = stream.peekable();
|
let mut peekable = stream.peekable();
|
||||||
let mut state = ParseState::new(
|
let mut state = ParseState::new(self, tokenizer_control);
|
||||||
self,
|
|
||||||
buffer,
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
NonZeroUsize::new(self.max_expr_depth()),
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
NonZeroUsize::new(self.max_function_expr_depth()),
|
|
||||||
);
|
|
||||||
self.parse_global_expr(&mut peekable, &mut state, scope, self.optimization_level)
|
self.parse_global_expr(&mut peekable, &mut state, scope, self.optimization_level)
|
||||||
}
|
}
|
||||||
/// Evaluate a script file.
|
/// Evaluate a script file.
|
||||||
@ -1624,16 +1599,8 @@ impl Engine {
|
|||||||
script: &str,
|
script: &str,
|
||||||
) -> Result<T, Box<EvalAltResult>> {
|
) -> Result<T, Box<EvalAltResult>> {
|
||||||
let scripts = [script];
|
let scripts = [script];
|
||||||
let (stream, buffer) = self.lex_raw(&scripts, None);
|
let (stream, tokenizer_control) = self.lex_raw(&scripts, None);
|
||||||
let mut state = ParseState::new(
|
let mut state = ParseState::new(self, tokenizer_control);
|
||||||
self,
|
|
||||||
buffer,
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
NonZeroUsize::new(self.max_expr_depth()),
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
NonZeroUsize::new(self.max_function_expr_depth()),
|
|
||||||
);
|
|
||||||
|
|
||||||
// No need to optimize a lone expression
|
// No need to optimize a lone expression
|
||||||
let ast = self.parse_global_expr(
|
let ast = self.parse_global_expr(
|
||||||
@ -1779,16 +1746,8 @@ impl Engine {
|
|||||||
script: &str,
|
script: &str,
|
||||||
) -> Result<(), Box<EvalAltResult>> {
|
) -> Result<(), Box<EvalAltResult>> {
|
||||||
let scripts = [script];
|
let scripts = [script];
|
||||||
let (stream, buffer) = self.lex_raw(&scripts, None);
|
let (stream, tokenizer_control) = self.lex_raw(&scripts, None);
|
||||||
let mut state = ParseState::new(
|
let mut state = ParseState::new(self, tokenizer_control);
|
||||||
self,
|
|
||||||
buffer,
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
NonZeroUsize::new(self.max_expr_depth()),
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
NonZeroUsize::new(self.max_function_expr_depth()),
|
|
||||||
);
|
|
||||||
|
|
||||||
let ast = self.parse(
|
let ast = self.parse(
|
||||||
&mut stream.peekable(),
|
&mut stream.peekable(),
|
||||||
|
@ -41,7 +41,7 @@ pub trait FuncArgs {
|
|||||||
///
|
///
|
||||||
/// let ast = engine.compile(r#"
|
/// let ast = engine.compile(r#"
|
||||||
/// fn hello(x, y, z) {
|
/// fn hello(x, y, z) {
|
||||||
/// if x { "hello " + y } else { y + z }
|
/// if x { `hello ${y}` } else { y + z }
|
||||||
/// }
|
/// }
|
||||||
/// "#)?;
|
/// "#)?;
|
||||||
///
|
///
|
||||||
|
@ -750,7 +750,7 @@ impl Engine {
|
|||||||
// Method call of script function - map first argument to `this`
|
// Method call of script function - map first argument to `this`
|
||||||
let (first, rest) = args.split_first_mut().unwrap();
|
let (first, rest) = args.split_first_mut().unwrap();
|
||||||
|
|
||||||
let orig_source = mem::take(&mut state.source);
|
let orig_source = state.source.take();
|
||||||
state.source = source;
|
state.source = source;
|
||||||
|
|
||||||
let level = _level + 1;
|
let level = _level + 1;
|
||||||
@ -780,7 +780,7 @@ impl Engine {
|
|||||||
backup.as_mut().unwrap().change_first_arg_to_copy(args);
|
backup.as_mut().unwrap().change_first_arg_to_copy(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
let orig_source = mem::take(&mut state.source);
|
let orig_source = state.source.take();
|
||||||
state.source = source;
|
state.source = source;
|
||||||
|
|
||||||
let level = _level + 1;
|
let level = _level + 1;
|
||||||
@ -1293,10 +1293,7 @@ impl Engine {
|
|||||||
// If the first argument is a variable, and there is no curried arguments,
|
// If the first argument is a variable, and there is no curried arguments,
|
||||||
// convert to method-call style in order to leverage potential &mut first argument and
|
// convert to method-call style in order to leverage potential &mut first argument and
|
||||||
// avoid cloning the value
|
// avoid cloning the value
|
||||||
if curry.is_empty()
|
if curry.is_empty() && !args_expr.is_empty() && args_expr[0].is_variable_access(false) {
|
||||||
&& !args_expr.is_empty()
|
|
||||||
&& args_expr[0].get_variable_access(false).is_some()
|
|
||||||
{
|
|
||||||
// func(x, ...) -> x.func(...)
|
// func(x, ...) -> x.func(...)
|
||||||
arg_values = args_expr
|
arg_values = args_expr
|
||||||
.iter()
|
.iter()
|
||||||
@ -1378,7 +1375,7 @@ impl Engine {
|
|||||||
// See if the first argument is a variable (not namespace-qualified).
|
// See if the first argument is a variable (not namespace-qualified).
|
||||||
// If so, convert to method-call style in order to leverage potential
|
// If so, convert to method-call style in order to leverage potential
|
||||||
// &mut first argument and avoid cloning the value
|
// &mut first argument and avoid cloning the value
|
||||||
if !args_expr.is_empty() && args_expr[0].get_variable_access(true).is_some() {
|
if !args_expr.is_empty() && args_expr[0].is_variable_access(true) {
|
||||||
// func(x, ...) -> x.func(...)
|
// func(x, ...) -> x.func(...)
|
||||||
arg_values = args_expr
|
arg_values = args_expr
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -13,8 +13,8 @@ use crate::stdlib::{
|
|||||||
};
|
};
|
||||||
use crate::token::is_valid_identifier;
|
use crate::token::is_valid_identifier;
|
||||||
use crate::{
|
use crate::{
|
||||||
calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ImmutableString, Module, Position,
|
calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, Identifier, ImmutableString, Module,
|
||||||
RhaiResult, StaticVec,
|
Position, RhaiResult, StaticVec,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Trait that maps to `Send + Sync` only under the `sync` feature.
|
/// Trait that maps to `Send + Sync` only under the `sync` feature.
|
||||||
@ -252,20 +252,17 @@ pub type FnCallArgs<'a> = [&'a mut Dynamic];
|
|||||||
/// A general function pointer, which may carry additional (i.e. curried) argument values
|
/// A general function pointer, which may carry additional (i.e. curried) argument values
|
||||||
/// to be passed onto a function during a call.
|
/// to be passed onto a function during a call.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FnPtr(ImmutableString, StaticVec<Dynamic>);
|
pub struct FnPtr(Identifier, StaticVec<Dynamic>);
|
||||||
|
|
||||||
impl FnPtr {
|
impl FnPtr {
|
||||||
/// Create a new function pointer.
|
/// Create a new function pointer.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn new(name: impl Into<ImmutableString>) -> Result<Self, Box<EvalAltResult>> {
|
pub fn new(name: impl Into<Identifier>) -> Result<Self, Box<EvalAltResult>> {
|
||||||
name.into().try_into()
|
name.into().try_into()
|
||||||
}
|
}
|
||||||
/// Create a new function pointer without checking its parameters.
|
/// Create a new function pointer without checking its parameters.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn new_unchecked(
|
pub(crate) fn new_unchecked(name: impl Into<Identifier>, curry: StaticVec<Dynamic>) -> Self {
|
||||||
name: impl Into<ImmutableString>,
|
|
||||||
curry: StaticVec<Dynamic>,
|
|
||||||
) -> Self {
|
|
||||||
Self(name.into(), curry)
|
Self(name.into(), curry)
|
||||||
}
|
}
|
||||||
/// Get the name of the function.
|
/// Get the name of the function.
|
||||||
@ -275,12 +272,12 @@ impl FnPtr {
|
|||||||
}
|
}
|
||||||
/// Get the name of the function.
|
/// Get the name of the function.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn get_fn_name(&self) -> &ImmutableString {
|
pub(crate) fn get_fn_name(&self) -> &Identifier {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
/// Get the underlying data of the function pointer.
|
/// Get the underlying data of the function pointer.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn take_data(self) -> (ImmutableString, StaticVec<Dynamic>) {
|
pub(crate) fn take_data(self) -> (Identifier, StaticVec<Dynamic>) {
|
||||||
(self.0, self.1)
|
(self.0, self.1)
|
||||||
}
|
}
|
||||||
/// Get the curried arguments.
|
/// Get the curried arguments.
|
||||||
@ -362,11 +359,11 @@ impl fmt::Display for FnPtr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<ImmutableString> for FnPtr {
|
impl TryFrom<Identifier> for FnPtr {
|
||||||
type Error = Box<EvalAltResult>;
|
type Error = Box<EvalAltResult>;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn try_from(value: ImmutableString) -> Result<Self, Self::Error> {
|
fn try_from(value: Identifier) -> Result<Self, Self::Error> {
|
||||||
if is_valid_identifier(value.chars()) {
|
if is_valid_identifier(value.chars()) {
|
||||||
Ok(Self(value, Default::default()))
|
Ok(Self(value, Default::default()))
|
||||||
} else {
|
} else {
|
||||||
@ -375,12 +372,22 @@ impl TryFrom<ImmutableString> for FnPtr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<ImmutableString> for FnPtr {
|
||||||
|
type Error = Box<EvalAltResult>;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn try_from(value: ImmutableString) -> Result<Self, Self::Error> {
|
||||||
|
let s: Identifier = value.into();
|
||||||
|
Self::try_from(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<String> for FnPtr {
|
impl TryFrom<String> for FnPtr {
|
||||||
type Error = Box<EvalAltResult>;
|
type Error = Box<EvalAltResult>;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
let s: ImmutableString = value.into();
|
let s: Identifier = value.into();
|
||||||
Self::try_from(s)
|
Self::try_from(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -390,7 +397,7 @@ impl TryFrom<&str> for FnPtr {
|
|||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
let s: ImmutableString = value.into();
|
let s: Identifier = value.into();
|
||||||
Self::try_from(s)
|
Self::try_from(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,7 +211,10 @@ pub use dynamic::Variant;
|
|||||||
// Expose internal data structures.
|
// Expose internal data structures.
|
||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
#[deprecated = "this type is volatile and may change"]
|
#[deprecated = "this type is volatile and may change"]
|
||||||
pub use token::{get_next_token, parse_string_literal, InputStream, Token, TokenizeState};
|
pub use token::{
|
||||||
|
get_next_token, parse_string_literal, InputStream, Token, TokenizeState, TokenizerControl,
|
||||||
|
TokenizerControlBlock,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
#[deprecated = "this type is volatile and may change"]
|
#[deprecated = "this type is volatile and may change"]
|
||||||
|
@ -7,8 +7,8 @@ use crate::fn_register::RegisterNativeFunction;
|
|||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
collections::BTreeMap,
|
collections::{BTreeMap, BTreeSet},
|
||||||
fmt, format,
|
fmt,
|
||||||
iter::empty,
|
iter::empty,
|
||||||
num::NonZeroUsize,
|
num::NonZeroUsize,
|
||||||
ops::{Add, AddAssign, Deref, DerefMut},
|
ops::{Add, AddAssign, Deref, DerefMut},
|
||||||
@ -173,50 +173,36 @@ impl Default for Module {
|
|||||||
|
|
||||||
impl fmt::Debug for Module {
|
impl fmt::Debug for Module {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(
|
let mut d = f.debug_struct("Module");
|
||||||
f,
|
|
||||||
"Module({}\n{}{}{})",
|
if let Some(ref id) = self.id {
|
||||||
self.id
|
d.field("id", id);
|
||||||
.as_ref()
|
}
|
||||||
.map(|id| format!("id: {:?},", id))
|
|
||||||
.unwrap_or_default(),
|
if !self.modules.is_empty() {
|
||||||
if !self.modules.is_empty() {
|
d.field(
|
||||||
format!(
|
"modules",
|
||||||
" modules: {}\n",
|
&self
|
||||||
self.modules
|
.modules
|
||||||
.keys()
|
.keys()
|
||||||
.map(|m| m.as_str())
|
.map(|m| m.as_str())
|
||||||
.collect::<Vec<_>>()
|
.collect::<BTreeSet<_>>(),
|
||||||
.join(", ")
|
);
|
||||||
)
|
}
|
||||||
} else {
|
if !self.variables.is_empty() {
|
||||||
Default::default()
|
d.field("vars", &self.variables);
|
||||||
},
|
}
|
||||||
if !self.variables.is_empty() {
|
if !self.functions.is_empty() {
|
||||||
format!(
|
d.field(
|
||||||
" vars: {}\n",
|
"functions",
|
||||||
self.variables
|
&self
|
||||||
.iter()
|
.functions
|
||||||
.map(|(k, v)| format!("{}={:?}", k, v))
|
.values()
|
||||||
.collect::<Vec<_>>()
|
.map(|f| crate::stdlib::string::ToString::to_string(&f.func))
|
||||||
.join(", ")
|
.collect::<BTreeSet<_>>(),
|
||||||
)
|
);
|
||||||
} else {
|
}
|
||||||
Default::default()
|
d.finish()
|
||||||
},
|
|
||||||
if !self.functions.is_empty() {
|
|
||||||
format!(
|
|
||||||
" functions: {}\n",
|
|
||||||
self.functions
|
|
||||||
.values()
|
|
||||||
.map(|f| crate::stdlib::string::ToString::to_string(&f.func))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", ")
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Default::default()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use crate::ast::{Expr, Stmt, StmtBlock};
|
use crate::ast::{Expr, Stmt, StmtBlock};
|
||||||
use crate::dynamic::AccessMode;
|
use crate::dynamic::AccessMode;
|
||||||
use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF};
|
use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF};
|
||||||
use crate::fn_builtin::get_builtin_binary_op_fn;
|
use crate::fn_builtin::get_builtin_binary_op_fn;
|
||||||
use crate::parser::map_dynamic_to_expr;
|
use crate::parser::map_dynamic_to_expr;
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
@ -17,8 +17,8 @@ use crate::stdlib::{
|
|||||||
};
|
};
|
||||||
use crate::utils::get_hasher;
|
use crate::utils::get_hasher;
|
||||||
use crate::{
|
use crate::{
|
||||||
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, Module, Position, Scope,
|
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, ImmutableString, Module,
|
||||||
StaticVec, AST,
|
Position, Scope, StaticVec, AST,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Level of optimization performed.
|
/// Level of optimization performed.
|
||||||
@ -385,7 +385,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
|||||||
match stmt {
|
match stmt {
|
||||||
// expr op= expr
|
// expr op= expr
|
||||||
Stmt::Assignment(x, _) => match x.0 {
|
Stmt::Assignment(x, _) => match x.0 {
|
||||||
Expr::Variable(_) => optimize_expr(&mut x.2, state),
|
Expr::Variable(_, _, _) => optimize_expr(&mut x.2, state),
|
||||||
_ => {
|
_ => {
|
||||||
optimize_expr(&mut x.0, state);
|
optimize_expr(&mut x.0, state);
|
||||||
optimize_expr(&mut x.2, state);
|
optimize_expr(&mut x.2, state);
|
||||||
@ -546,19 +546,21 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
|||||||
Stmt::Import(expr, _, _) => optimize_expr(expr, state),
|
Stmt::Import(expr, _, _) => optimize_expr(expr, state),
|
||||||
// { block }
|
// { block }
|
||||||
Stmt::Block(statements, pos) => {
|
Stmt::Block(statements, pos) => {
|
||||||
let block = mem::take(statements);
|
let mut block =
|
||||||
*stmt = match optimize_stmt_block(block, state, preserve_result, true, false) {
|
optimize_stmt_block(mem::take(statements), state, preserve_result, true, false);
|
||||||
statements if statements.is_empty() => {
|
|
||||||
|
match block.as_mut_slice() {
|
||||||
|
[] => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
Stmt::Noop(*pos)
|
*stmt = Stmt::Noop(*pos);
|
||||||
}
|
}
|
||||||
// Only one statement - promote
|
// Only one statement - promote
|
||||||
mut statements if statements.len() == 1 => {
|
[s] => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
statements.pop().unwrap()
|
*stmt = mem::take(s);
|
||||||
}
|
}
|
||||||
statements => Stmt::Block(statements, *pos),
|
_ => *stmt = Stmt::Block(block, *pos),
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
// try { pure try_block } catch ( var ) { catch_block } -> try_block
|
// try { pure try_block } catch ( var ) { catch_block } -> try_block
|
||||||
Stmt::TryCatch(x, _, _) if x.0.statements.iter().all(Stmt::is_pure) => {
|
Stmt::TryCatch(x, _, _) if x.0.statements.iter().all(Stmt::is_pure) => {
|
||||||
@ -609,18 +611,16 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
|||||||
match expr {
|
match expr {
|
||||||
// {}
|
// {}
|
||||||
Expr::Stmt(x) if x.statements.is_empty() => { state.set_dirty(); *expr = Expr::Unit(x.pos) }
|
Expr::Stmt(x) if x.statements.is_empty() => { state.set_dirty(); *expr = Expr::Unit(x.pos) }
|
||||||
// { Stmt(Expr) }
|
// { stmt; ... } - do not count promotion as dirty because it gets turned back into an array
|
||||||
Expr::Stmt(x) if x.statements.len() == 1 && x.statements[0].is_pure() && matches!(x.statements[0], Stmt::Expr(_)) =>
|
Expr::Stmt(x) => {
|
||||||
{
|
x.statements = optimize_stmt_block(mem::take(&mut x.statements).into_vec(), state, true, true, false).into();
|
||||||
state.set_dirty();
|
|
||||||
if let Stmt::Expr(e) = mem::take(&mut x.statements[0]) {
|
// { Stmt(Expr) } - promote
|
||||||
*expr = e;
|
match x.statements.as_mut() {
|
||||||
} else {
|
[ Stmt::Expr(e) ] => { state.set_dirty(); *expr = mem::take(e); }
|
||||||
unreachable!();
|
_ => ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// { stmt; ... } - do not count promotion as dirty because it gets turned back into an array
|
|
||||||
Expr::Stmt(x) => x.statements = optimize_stmt_block(mem::take(&mut x.statements).into_vec(), state, true, true, false).into(),
|
|
||||||
// lhs.rhs
|
// lhs.rhs
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::Dot(x, _) => match (&mut x.lhs, &mut x.rhs) {
|
Expr::Dot(x, _) => match (&mut x.lhs, &mut x.rhs) {
|
||||||
@ -635,7 +635,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
|||||||
.unwrap_or_else(|| Expr::Unit(*pos));
|
.unwrap_or_else(|| Expr::Unit(*pos));
|
||||||
}
|
}
|
||||||
// var.rhs
|
// var.rhs
|
||||||
(Expr::Variable(_), rhs) => optimize_expr(rhs, state),
|
(Expr::Variable(_, _, _), rhs) => optimize_expr(rhs, state),
|
||||||
// lhs.rhs
|
// lhs.rhs
|
||||||
(lhs, rhs) => { optimize_expr(lhs, state); optimize_expr(rhs, state); }
|
(lhs, rhs) => { optimize_expr(lhs, state); optimize_expr(rhs, state); }
|
||||||
}
|
}
|
||||||
@ -670,7 +670,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
|||||||
*expr = Expr::CharConstant(s.chars().nth(*i as usize).unwrap(), *pos);
|
*expr = Expr::CharConstant(s.chars().nth(*i as usize).unwrap(), *pos);
|
||||||
}
|
}
|
||||||
// var[rhs]
|
// var[rhs]
|
||||||
(Expr::Variable(_), rhs) => optimize_expr(rhs, state),
|
(Expr::Variable(_, _, _), rhs) => optimize_expr(rhs, state),
|
||||||
// lhs[rhs]
|
// lhs[rhs]
|
||||||
(lhs, rhs) => { optimize_expr(lhs, state); optimize_expr(rhs, state); }
|
(lhs, rhs) => { optimize_expr(lhs, state); optimize_expr(rhs, state); }
|
||||||
},
|
},
|
||||||
@ -794,6 +794,19 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
|||||||
Expr::FnCall(x, _) if x.name == KEYWORD_EVAL => {
|
Expr::FnCall(x, _) if x.name == KEYWORD_EVAL => {
|
||||||
state.propagate_constants = false;
|
state.propagate_constants = false;
|
||||||
}
|
}
|
||||||
|
// Fn
|
||||||
|
Expr::FnCall(x, pos)
|
||||||
|
if x.namespace.is_none() // Non-qualified
|
||||||
|
&& state.optimization_level == OptimizationLevel::Simple // simple optimizations
|
||||||
|
&& x.num_args() == 1
|
||||||
|
&& x.constant_args.len() == 1
|
||||||
|
&& x.constant_args[0].0.is::<ImmutableString>()
|
||||||
|
&& x.name == KEYWORD_FN_PTR
|
||||||
|
=> {
|
||||||
|
state.set_dirty();
|
||||||
|
*expr = Expr::FnPointer(mem::take(&mut x.constant_args[0].0).take_immutable_string().unwrap(), *pos);
|
||||||
|
}
|
||||||
|
|
||||||
// Do not call some special keywords
|
// Do not call some special keywords
|
||||||
Expr::FnCall(x, _) if DONT_EVAL_KEYWORDS.contains(&x.name.as_ref()) => {
|
Expr::FnCall(x, _) if DONT_EVAL_KEYWORDS.contains(&x.name.as_ref()) => {
|
||||||
x.args.iter_mut().for_each(|a| optimize_expr(a, state));
|
x.args.iter_mut().for_each(|a| optimize_expr(a, state));
|
||||||
@ -901,12 +914,12 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// constant-name
|
// constant-name
|
||||||
Expr::Variable(x) if x.1.is_none() && state.find_constant(&x.2.name).is_some() => {
|
Expr::Variable(_, pos, x) if x.1.is_none() && state.find_constant(&x.2).is_some() => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
|
|
||||||
// Replace constant with value
|
// Replace constant with value
|
||||||
let mut result = state.find_constant(&x.2.name).unwrap().clone();
|
let mut result = state.find_constant(&x.2).unwrap().clone();
|
||||||
result.set_position(x.2.pos);
|
result.set_position(*pos);
|
||||||
*expr = result;
|
*expr = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, {
|
|||||||
mod fn_ptr_functions {
|
mod fn_ptr_functions {
|
||||||
#[rhai_fn(name = "name", get = "name", pure)]
|
#[rhai_fn(name = "name", get = "name", pure)]
|
||||||
pub fn name(f: &mut FnPtr) -> ImmutableString {
|
pub fn name(f: &mut FnPtr) -> ImmutableString {
|
||||||
f.get_fn_name().clone()
|
f.get_fn_name().as_str().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
@ -73,31 +73,23 @@ mod print_debug_functions {
|
|||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
use num_traits::Float;
|
use num_traits::Float;
|
||||||
|
|
||||||
|
use crate::ast::FloatWrapper;
|
||||||
|
|
||||||
#[rhai_fn(name = "print", name = "to_string")]
|
#[rhai_fn(name = "print", name = "to_string")]
|
||||||
pub fn print_f64(number: f64) -> ImmutableString {
|
pub fn print_f64(number: f64) -> ImmutableString {
|
||||||
let abs = number.abs();
|
FloatWrapper::new(number).to_string().into()
|
||||||
if abs > 10000000000000.0 || abs < 0.0000000000001 {
|
|
||||||
format!("{:e}", number).into()
|
|
||||||
} else {
|
|
||||||
number.to_string().into()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#[rhai_fn(name = "print", name = "to_string")]
|
#[rhai_fn(name = "print", name = "to_string")]
|
||||||
pub fn print_f32(number: f32) -> ImmutableString {
|
pub fn print_f32(number: f32) -> ImmutableString {
|
||||||
let abs = number.abs();
|
FloatWrapper::new(number).to_string().into()
|
||||||
if abs > 10000000000000.0 || abs < 0.0000000000001 {
|
|
||||||
format!("{:e}", number).into()
|
|
||||||
} else {
|
|
||||||
number.to_string().into()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#[rhai_fn(name = "debug", name = "to_debug")]
|
#[rhai_fn(name = "debug", name = "to_debug")]
|
||||||
pub fn debug_f64(number: f64) -> ImmutableString {
|
pub fn debug_f64(number: f64) -> ImmutableString {
|
||||||
number.to_string().into()
|
format!("{:?}", FloatWrapper::new(number)).into()
|
||||||
}
|
}
|
||||||
#[rhai_fn(name = "debug", name = "to_debug")]
|
#[rhai_fn(name = "debug", name = "to_debug")]
|
||||||
pub fn debug_f32(number: f32) -> ImmutableString {
|
pub fn debug_f32(number: f32) -> ImmutableString {
|
||||||
number.to_string().into()
|
format!("{:?}", FloatWrapper::new(number)).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,6 +56,10 @@ mod string_functions {
|
|||||||
pub fn add_append_str(string1: ImmutableString, string2: ImmutableString) -> ImmutableString {
|
pub fn add_append_str(string1: ImmutableString, string2: ImmutableString) -> ImmutableString {
|
||||||
string1 + string2
|
string1 + string2
|
||||||
}
|
}
|
||||||
|
#[rhai_fn(name = "+", name = "append")]
|
||||||
|
pub fn add_append_char(string: ImmutableString, ch: char) -> ImmutableString {
|
||||||
|
string + ch
|
||||||
|
}
|
||||||
|
|
||||||
#[rhai_fn(name = "+", name = "append")]
|
#[rhai_fn(name = "+", name = "append")]
|
||||||
pub fn add_append_unit(string: ImmutableString, _item: ()) -> ImmutableString {
|
pub fn add_append_unit(string: ImmutableString, _item: ()) -> ImmutableString {
|
||||||
|
229
src/parser.rs
229
src/parser.rs
@ -11,18 +11,19 @@ use crate::optimize::optimize_into_ast;
|
|||||||
use crate::optimize::OptimizationLevel;
|
use crate::optimize::OptimizationLevel;
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
cell::Cell,
|
|
||||||
collections::BTreeMap,
|
collections::BTreeMap,
|
||||||
format,
|
format,
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
iter::empty,
|
iter::empty,
|
||||||
num::NonZeroUsize,
|
num::{NonZeroU8, NonZeroUsize},
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
vec,
|
vec,
|
||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
use crate::syntax::{CustomSyntax, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT};
|
use crate::syntax::{CustomSyntax, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT};
|
||||||
use crate::token::{is_keyword_function, is_valid_identifier, Token, TokenStream};
|
use crate::token::{
|
||||||
|
is_keyword_function, is_valid_identifier, Token, TokenStream, TokenizerControl,
|
||||||
|
};
|
||||||
use crate::utils::{get_hasher, IdentifierBuilder};
|
use crate::utils::{get_hasher, IdentifierBuilder};
|
||||||
use crate::{
|
use crate::{
|
||||||
calc_fn_hash, Dynamic, Engine, Identifier, LexError, ParseError, ParseErrorType, Position,
|
calc_fn_hash, Dynamic, Engine, Identifier, LexError, ParseError, ParseErrorType, Position,
|
||||||
@ -45,7 +46,7 @@ pub struct ParseState<'e> {
|
|||||||
/// Reference to the scripting [`Engine`].
|
/// Reference to the scripting [`Engine`].
|
||||||
engine: &'e Engine,
|
engine: &'e Engine,
|
||||||
/// Input stream buffer containing the next character to read.
|
/// Input stream buffer containing the next character to read.
|
||||||
buffer: Shared<Cell<Option<char>>>,
|
tokenizer_control: TokenizerControl,
|
||||||
/// Interned strings.
|
/// Interned strings.
|
||||||
interned_strings: IdentifierBuilder,
|
interned_strings: IdentifierBuilder,
|
||||||
/// Encapsulates a local stack with variable names to simulate an actual runtime scope.
|
/// Encapsulates a local stack with variable names to simulate an actual runtime scope.
|
||||||
@ -76,22 +77,15 @@ pub struct ParseState<'e> {
|
|||||||
impl<'e> ParseState<'e> {
|
impl<'e> ParseState<'e> {
|
||||||
/// Create a new [`ParseState`].
|
/// Create a new [`ParseState`].
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn new(
|
pub fn new(engine: &'e Engine, tokenizer_control: TokenizerControl) -> Self {
|
||||||
engine: &'e Engine,
|
|
||||||
buffer: Shared<Cell<Option<char>>>,
|
|
||||||
#[cfg(not(feature = "unchecked"))] max_expr_depth: Option<NonZeroUsize>,
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
max_function_expr_depth: Option<NonZeroUsize>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
engine,
|
engine,
|
||||||
buffer,
|
tokenizer_control,
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
max_expr_depth,
|
max_expr_depth: NonZeroUsize::new(engine.max_expr_depth()),
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
max_function_expr_depth,
|
max_function_expr_depth: NonZeroUsize::new(engine.max_function_expr_depth()),
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
external_vars: Default::default(),
|
external_vars: Default::default(),
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
@ -231,17 +225,20 @@ impl Expr {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn into_property(self, state: &mut ParseState) -> Self {
|
fn into_property(self, state: &mut ParseState) -> Self {
|
||||||
match self {
|
match self {
|
||||||
Self::Variable(x) if x.1.is_none() => {
|
Self::Variable(_, pos, x) if x.1.is_none() => {
|
||||||
let ident = x.2;
|
let ident = x.2;
|
||||||
let getter = state.get_identifier(crate::engine::make_getter(&ident.name));
|
let getter = state.get_identifier(crate::engine::make_getter(&ident));
|
||||||
let hash_get = calc_fn_hash(empty(), &getter, 1);
|
let hash_get = calc_fn_hash(empty(), &getter, 1);
|
||||||
let setter = state.get_identifier(crate::engine::make_setter(&ident.name));
|
let setter = state.get_identifier(crate::engine::make_setter(&ident));
|
||||||
let hash_set = calc_fn_hash(empty(), &setter, 2);
|
let hash_set = calc_fn_hash(empty(), &setter, 2);
|
||||||
|
|
||||||
Self::Property(Box::new((
|
Self::Property(Box::new((
|
||||||
(getter, hash_get),
|
(getter, hash_get),
|
||||||
(setter, hash_set),
|
(setter, hash_set),
|
||||||
ident.into(),
|
Ident {
|
||||||
|
name: state.get_identifier(ident),
|
||||||
|
pos,
|
||||||
|
},
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
_ => self,
|
_ => self,
|
||||||
@ -716,6 +713,7 @@ fn parse_map_literal(
|
|||||||
}
|
}
|
||||||
(s, pos)
|
(s, pos)
|
||||||
}
|
}
|
||||||
|
(Token::InterpolatedString(_), pos) => return Err(PERR::PropertyExpected.into_err(pos)),
|
||||||
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
||||||
return Err(PERR::Reserved(s).into_err(pos));
|
return Err(PERR::Reserved(s).into_err(pos));
|
||||||
}
|
}
|
||||||
@ -982,14 +980,12 @@ fn parse_primary(
|
|||||||
// | ...
|
// | ...
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
Token::Pipe | Token::Or if settings.allow_anonymous_fn => {
|
Token::Pipe | Token::Or if settings.allow_anonymous_fn => {
|
||||||
let mut new_state = ParseState::new(
|
let mut new_state = ParseState::new(state.engine, state.tokenizer_control.clone());
|
||||||
state.engine,
|
|
||||||
state.buffer.clone(),
|
#[cfg(not(feature = "unchecked"))]
|
||||||
#[cfg(not(feature = "unchecked"))]
|
{
|
||||||
state.max_function_expr_depth,
|
new_state.max_expr_depth = new_state.max_function_expr_depth;
|
||||||
#[cfg(not(feature = "unchecked"))]
|
}
|
||||||
state.max_function_expr_depth,
|
|
||||||
);
|
|
||||||
|
|
||||||
let settings = ParseSettings {
|
let settings = ParseSettings {
|
||||||
allow_if_expr: true,
|
allow_if_expr: true,
|
||||||
@ -1034,7 +1030,9 @@ fn parse_primary(
|
|||||||
segments.push(expr);
|
segments.push(expr);
|
||||||
|
|
||||||
// Make sure to parse the following as text
|
// Make sure to parse the following as text
|
||||||
state.buffer.set(Some('`'));
|
let mut control = state.tokenizer_control.get();
|
||||||
|
control.is_within_text = true;
|
||||||
|
state.tokenizer_control.set(control);
|
||||||
|
|
||||||
match input.next().unwrap() {
|
match input.next().unwrap() {
|
||||||
(Token::StringConstant(s), pos) => {
|
(Token::StringConstant(s), pos) => {
|
||||||
@ -1082,11 +1080,11 @@ fn parse_primary(
|
|||||||
// Once the identifier consumed we must enable next variables capturing
|
// Once the identifier consumed we must enable next variables capturing
|
||||||
state.allow_capture = true;
|
state.allow_capture = true;
|
||||||
}
|
}
|
||||||
let var_name_def = Ident {
|
Expr::Variable(
|
||||||
name: state.get_identifier(s),
|
None,
|
||||||
pos: settings.pos,
|
settings.pos,
|
||||||
};
|
Box::new((None, None, state.get_identifier(s))),
|
||||||
Expr::Variable(Box::new((None, None, var_name_def)))
|
)
|
||||||
}
|
}
|
||||||
// Namespace qualification
|
// Namespace qualification
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
@ -1096,20 +1094,27 @@ fn parse_primary(
|
|||||||
// Once the identifier consumed we must enable next variables capturing
|
// Once the identifier consumed we must enable next variables capturing
|
||||||
state.allow_capture = true;
|
state.allow_capture = true;
|
||||||
}
|
}
|
||||||
let var_name_def = Ident {
|
Expr::Variable(
|
||||||
name: state.get_identifier(s),
|
None,
|
||||||
pos: settings.pos,
|
settings.pos,
|
||||||
};
|
Box::new((None, None, state.get_identifier(s))),
|
||||||
Expr::Variable(Box::new((None, None, var_name_def)))
|
)
|
||||||
}
|
}
|
||||||
// Normal variable access
|
// Normal variable access
|
||||||
_ => {
|
_ => {
|
||||||
let index = state.access_var(&s, settings.pos);
|
let index = state.access_var(&s, settings.pos);
|
||||||
let var_name_def = Ident {
|
let short_index = index.and_then(|x| {
|
||||||
name: state.get_identifier(s),
|
if x.get() <= u8::MAX as usize {
|
||||||
pos: settings.pos,
|
NonZeroU8::new(x.get() as u8)
|
||||||
};
|
} else {
|
||||||
Expr::Variable(Box::new((index, None, var_name_def)))
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Expr::Variable(
|
||||||
|
short_index,
|
||||||
|
settings.pos,
|
||||||
|
Box::new((index, None, state.get_identifier(s))),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1123,21 +1128,17 @@ fn parse_primary(
|
|||||||
|
|
||||||
match input.peek().unwrap().0 {
|
match input.peek().unwrap().0 {
|
||||||
// Function call is allowed to have reserved keyword
|
// Function call is allowed to have reserved keyword
|
||||||
Token::LeftParen | Token::Bang if is_keyword_function(&s) => {
|
Token::LeftParen | Token::Bang if is_keyword_function(&s) => Expr::Variable(
|
||||||
let var_name_def = Ident {
|
None,
|
||||||
name: state.get_identifier(s),
|
settings.pos,
|
||||||
pos: settings.pos,
|
Box::new((None, None, state.get_identifier(s))),
|
||||||
};
|
),
|
||||||
Expr::Variable(Box::new((None, None, var_name_def)))
|
|
||||||
}
|
|
||||||
// Access to `this` as a variable is OK within a function scope
|
// Access to `this` as a variable is OK within a function scope
|
||||||
_ if s == KEYWORD_THIS && settings.is_function_scope => {
|
_ if s == KEYWORD_THIS && settings.is_function_scope => Expr::Variable(
|
||||||
let var_name_def = Ident {
|
None,
|
||||||
name: state.get_identifier(s),
|
settings.pos,
|
||||||
pos: settings.pos,
|
Box::new((None, None, state.get_identifier(s))),
|
||||||
};
|
),
|
||||||
Expr::Variable(Box::new((None, None, var_name_def)))
|
|
||||||
}
|
|
||||||
// Cannot access to `this` as a variable not in a function scope
|
// Cannot access to `this` as a variable not in a function scope
|
||||||
_ if s == KEYWORD_THIS => {
|
_ if s == KEYWORD_THIS => {
|
||||||
let msg = format!("'{}' can only be used in functions", s);
|
let msg = format!("'{}' can only be used in functions", s);
|
||||||
@ -1173,7 +1174,7 @@ fn parse_primary(
|
|||||||
|
|
||||||
root_expr = match (root_expr, tail_token) {
|
root_expr = match (root_expr, tail_token) {
|
||||||
// Qualified function call with !
|
// Qualified function call with !
|
||||||
(Expr::Variable(x), Token::Bang) if x.1.is_some() => {
|
(Expr::Variable(_, _, x), Token::Bang) if x.1.is_some() => {
|
||||||
return Err(if !match_token(input, Token::LeftParen).0 {
|
return Err(if !match_token(input, Token::LeftParen).0 {
|
||||||
LexError::UnexpectedInput(Token::Bang.syntax().to_string()).into_err(tail_pos)
|
LexError::UnexpectedInput(Token::Bang.syntax().to_string()).into_err(tail_pos)
|
||||||
} else {
|
} else {
|
||||||
@ -1185,7 +1186,7 @@ fn parse_primary(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Function call with !
|
// Function call with !
|
||||||
(Expr::Variable(x), Token::Bang) => {
|
(Expr::Variable(_, var_pos, x), Token::Bang) => {
|
||||||
let (matched, pos) = match_token(input, Token::LeftParen);
|
let (matched, pos) = match_token(input, Token::LeftParen);
|
||||||
if !matched {
|
if !matched {
|
||||||
return Err(PERR::MissingToken(
|
return Err(PERR::MissingToken(
|
||||||
@ -1195,37 +1196,40 @@ fn parse_primary(
|
|||||||
.into_err(pos));
|
.into_err(pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
let (_, namespace, Ident { name, pos, .. }) = *x;
|
let (_, namespace, name) = *x;
|
||||||
settings.pos = pos;
|
settings.pos = var_pos;
|
||||||
let ns = namespace.map(|(_, ns)| ns);
|
let ns = namespace.map(|(_, ns)| ns);
|
||||||
parse_fn_call(input, state, lib, name, true, ns, settings.level_up())?
|
parse_fn_call(input, state, lib, name, true, ns, settings.level_up())?
|
||||||
}
|
}
|
||||||
// Function call
|
// Function call
|
||||||
(Expr::Variable(x), Token::LeftParen) => {
|
(Expr::Variable(_, var_pos, x), Token::LeftParen) => {
|
||||||
let (_, namespace, Ident { name, pos, .. }) = *x;
|
let (_, namespace, name) = *x;
|
||||||
settings.pos = pos;
|
settings.pos = var_pos;
|
||||||
let ns = namespace.map(|(_, ns)| ns);
|
let ns = namespace.map(|(_, ns)| ns);
|
||||||
parse_fn_call(input, state, lib, name, false, ns, settings.level_up())?
|
parse_fn_call(input, state, lib, name, false, ns, settings.level_up())?
|
||||||
}
|
}
|
||||||
// module access
|
// module access
|
||||||
(Expr::Variable(x), Token::DoubleColon) => match input.next().unwrap() {
|
(Expr::Variable(_, var_pos, x), Token::DoubleColon) => match input.next().unwrap() {
|
||||||
(Token::Identifier(id2), pos2) => {
|
(Token::Identifier(id2), pos2) => {
|
||||||
let (index, mut namespace, var_name_def) = *x;
|
let (_, mut namespace, var_name) = *x;
|
||||||
|
let var_name_def = Ident {
|
||||||
|
name: var_name,
|
||||||
|
pos: var_pos,
|
||||||
|
};
|
||||||
|
|
||||||
if let Some((_, ref mut namespace)) = namespace {
|
if let Some((_, ref mut namespace)) = namespace {
|
||||||
namespace.push(var_name_def);
|
namespace.push(var_name_def);
|
||||||
} else {
|
} else {
|
||||||
let mut ns: NamespaceRef = Default::default();
|
let mut ns: NamespaceRef = Default::default();
|
||||||
ns.push(var_name_def);
|
ns.push(var_name_def);
|
||||||
let index = 42; // Dummy
|
namespace = Some((42, ns));
|
||||||
namespace = Some((index, ns));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let var_name_def = Ident {
|
Expr::Variable(
|
||||||
name: state.get_identifier(id2),
|
None,
|
||||||
pos: pos2,
|
pos2,
|
||||||
};
|
Box::new((None, namespace, state.get_identifier(id2))),
|
||||||
Expr::Variable(Box::new((index, namespace, var_name_def)))
|
)
|
||||||
}
|
}
|
||||||
(Token::Reserved(id2), pos2) if is_valid_identifier(id2.chars()) => {
|
(Token::Reserved(id2), pos2) if is_valid_identifier(id2.chars()) => {
|
||||||
return Err(PERR::Reserved(id2).into_err(pos2));
|
return Err(PERR::Reserved(id2).into_err(pos2));
|
||||||
@ -1267,16 +1271,16 @@ fn parse_primary(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cache the hash key for namespace-qualified variables
|
// Cache the hash key for namespace-qualified variables
|
||||||
match &mut root_expr {
|
match root_expr {
|
||||||
Expr::Variable(x) if x.1.is_some() => Some(x),
|
Expr::Variable(_, _, ref mut x) if x.1.is_some() => Some(x),
|
||||||
Expr::Index(x, _) | Expr::Dot(x, _) => match &mut x.lhs {
|
Expr::Index(ref mut x, _) | Expr::Dot(ref mut x, _) => match &mut x.lhs {
|
||||||
Expr::Variable(x) if x.1.is_some() => Some(x),
|
Expr::Variable(_, _, x) if x.1.is_some() => Some(x),
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
.map(|x| match x.as_mut() {
|
.map(|x| match x.as_mut() {
|
||||||
(_, Some((ref mut hash, ref mut namespace)), Ident { name, .. }) => {
|
(_, Some((hash, namespace)), name) => {
|
||||||
*hash = calc_fn_hash(namespace.iter().map(|v| v.name.as_str()), name, 0);
|
*hash = calc_fn_hash(namespace.iter().map(|v| v.name.as_str()), name, 0);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
@ -1428,19 +1432,20 @@ fn make_assignment_stmt<'a>(
|
|||||||
Err(PERR::AssignmentToConstant("".into()).into_err(lhs.position()))
|
Err(PERR::AssignmentToConstant("".into()).into_err(lhs.position()))
|
||||||
}
|
}
|
||||||
// var (non-indexed) = rhs
|
// var (non-indexed) = rhs
|
||||||
Expr::Variable(x) if x.0.is_none() => {
|
Expr::Variable(None, _, x) if x.0.is_none() => {
|
||||||
Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos))
|
Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos))
|
||||||
}
|
}
|
||||||
// var (indexed) = rhs
|
// var (indexed) = rhs
|
||||||
Expr::Variable(x) => {
|
Expr::Variable(i, var_pos, x) => {
|
||||||
let (index, _, Ident { name, pos, .. }) = x.as_ref();
|
let (index, _, name) = x.as_ref();
|
||||||
match state.stack[(state.stack.len() - index.unwrap().get())].1 {
|
let index = i.map_or_else(|| index.unwrap().get(), |n| n.get() as usize);
|
||||||
|
match state.stack[state.stack.len() - index].1 {
|
||||||
AccessMode::ReadWrite => {
|
AccessMode::ReadWrite => {
|
||||||
Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos))
|
Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos))
|
||||||
}
|
}
|
||||||
// Constant values cannot be assigned to
|
// Constant values cannot be assigned to
|
||||||
AccessMode::ReadOnly => {
|
AccessMode::ReadOnly => {
|
||||||
Err(PERR::AssignmentToConstant(name.to_string()).into_err(*pos))
|
Err(PERR::AssignmentToConstant(name.to_string()).into_err(*var_pos))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1449,19 +1454,20 @@ fn make_assignment_stmt<'a>(
|
|||||||
match check_lvalue(&x.rhs, matches!(lhs, Expr::Dot(_, _))) {
|
match check_lvalue(&x.rhs, matches!(lhs, Expr::Dot(_, _))) {
|
||||||
Position::NONE => match &x.lhs {
|
Position::NONE => match &x.lhs {
|
||||||
// var[???] (non-indexed) = rhs, var.??? (non-indexed) = rhs
|
// var[???] (non-indexed) = rhs, var.??? (non-indexed) = rhs
|
||||||
Expr::Variable(x) if x.0.is_none() => {
|
Expr::Variable(None, _, x) if x.0.is_none() => {
|
||||||
Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos))
|
Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos))
|
||||||
}
|
}
|
||||||
// var[???] (indexed) = rhs, var.??? (indexed) = rhs
|
// var[???] (indexed) = rhs, var.??? (indexed) = rhs
|
||||||
Expr::Variable(x) => {
|
Expr::Variable(i, var_pos, x) => {
|
||||||
let (index, _, Ident { name, pos, .. }) = x.as_ref();
|
let (index, _, name) = x.as_ref();
|
||||||
match state.stack[(state.stack.len() - index.unwrap().get())].1 {
|
let index = i.map_or_else(|| index.unwrap().get(), |n| n.get() as usize);
|
||||||
|
match state.stack[state.stack.len() - index].1 {
|
||||||
AccessMode::ReadWrite => {
|
AccessMode::ReadWrite => {
|
||||||
Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos))
|
Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos))
|
||||||
}
|
}
|
||||||
// Constant values cannot be assigned to
|
// Constant values cannot be assigned to
|
||||||
AccessMode::ReadOnly => {
|
AccessMode::ReadOnly => {
|
||||||
Err(PERR::AssignmentToConstant(name.to_string()).into_err(*pos))
|
Err(PERR::AssignmentToConstant(name.to_string()).into_err(*var_pos))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1537,19 +1543,26 @@ fn make_dot_expr(
|
|||||||
Expr::Index(x, pos)
|
Expr::Index(x, pos)
|
||||||
}
|
}
|
||||||
// lhs.id
|
// lhs.id
|
||||||
(lhs, Expr::Variable(x)) if x.1.is_none() => {
|
(lhs, Expr::Variable(_, var_pos, x)) if x.1.is_none() => {
|
||||||
let ident = x.2;
|
let ident = x.2;
|
||||||
let getter = state.get_identifier(crate::engine::make_getter(&ident.name));
|
let getter = state.get_identifier(crate::engine::make_getter(&ident));
|
||||||
let hash_get = calc_fn_hash(empty(), &getter, 1);
|
let hash_get = calc_fn_hash(empty(), &getter, 1);
|
||||||
let setter = state.get_identifier(crate::engine::make_setter(&ident.name));
|
let setter = state.get_identifier(crate::engine::make_setter(&ident));
|
||||||
let hash_set = calc_fn_hash(empty(), &setter, 2);
|
let hash_set = calc_fn_hash(empty(), &setter, 2);
|
||||||
|
|
||||||
let rhs = Expr::Property(Box::new(((getter, hash_get), (setter, hash_set), ident)));
|
let rhs = Expr::Property(Box::new((
|
||||||
|
(getter, hash_get),
|
||||||
|
(setter, hash_set),
|
||||||
|
Ident {
|
||||||
|
name: state.get_identifier(ident),
|
||||||
|
pos: var_pos,
|
||||||
|
},
|
||||||
|
)));
|
||||||
|
|
||||||
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos)
|
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos)
|
||||||
}
|
}
|
||||||
// lhs.module::id - syntax error
|
// lhs.module::id - syntax error
|
||||||
(_, Expr::Variable(x)) if x.1.is_some() => {
|
(_, Expr::Variable(_, _, x)) if x.1.is_some() => {
|
||||||
return Err(PERR::PropertyExpected.into_err(x.1.unwrap().1[0].pos))
|
return Err(PERR::PropertyExpected.into_err(x.1.unwrap().1[0].pos))
|
||||||
}
|
}
|
||||||
// lhs.prop
|
// lhs.prop
|
||||||
@ -1558,7 +1571,7 @@ fn make_dot_expr(
|
|||||||
}
|
}
|
||||||
// lhs.dot_lhs.dot_rhs
|
// lhs.dot_lhs.dot_rhs
|
||||||
(lhs, Expr::Dot(x, pos)) => match x.lhs {
|
(lhs, Expr::Dot(x, pos)) => match x.lhs {
|
||||||
Expr::Variable(_) | Expr::Property(_) => {
|
Expr::Variable(_, _, _) | Expr::Property(_) => {
|
||||||
let rhs = Expr::Dot(
|
let rhs = Expr::Dot(
|
||||||
Box::new(BinaryExpr {
|
Box::new(BinaryExpr {
|
||||||
lhs: x.lhs.into_property(state),
|
lhs: x.lhs.into_property(state),
|
||||||
@ -1873,8 +1886,7 @@ fn parse_custom_syntax(
|
|||||||
let name = state.get_identifier(s);
|
let name = state.get_identifier(s);
|
||||||
segments.push(name.clone().into());
|
segments.push(name.clone().into());
|
||||||
tokens.push(state.get_identifier(MARKER_IDENT));
|
tokens.push(state.get_identifier(MARKER_IDENT));
|
||||||
let var_name_def = Ident { name, pos };
|
keywords.push(Expr::Variable(None, pos, Box::new((None, None, name))));
|
||||||
keywords.push(Expr::Variable(Box::new((None, None, var_name_def))));
|
|
||||||
}
|
}
|
||||||
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
||||||
return Err(PERR::Reserved(s).into_err(pos));
|
return Err(PERR::Reserved(s).into_err(pos));
|
||||||
@ -2540,14 +2552,13 @@ fn parse_stmt(
|
|||||||
|
|
||||||
match input.next().unwrap() {
|
match input.next().unwrap() {
|
||||||
(Token::Fn, pos) => {
|
(Token::Fn, pos) => {
|
||||||
let mut new_state = ParseState::new(
|
let mut new_state =
|
||||||
state.engine,
|
ParseState::new(state.engine, state.tokenizer_control.clone());
|
||||||
state.buffer.clone(),
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
state.max_function_expr_depth,
|
{
|
||||||
#[cfg(not(feature = "unchecked"))]
|
new_state.max_expr_depth = new_state.max_function_expr_depth;
|
||||||
state.max_function_expr_depth,
|
}
|
||||||
);
|
|
||||||
|
|
||||||
let settings = ParseSettings {
|
let settings = ParseSettings {
|
||||||
allow_if_expr: true,
|
allow_if_expr: true,
|
||||||
@ -2827,11 +2838,11 @@ fn make_curry_from_externals(
|
|||||||
args.push(fn_expr);
|
args.push(fn_expr);
|
||||||
|
|
||||||
externals.iter().for_each(|x| {
|
externals.iter().for_each(|x| {
|
||||||
let var_def = Ident {
|
args.push(Expr::Variable(
|
||||||
name: x.clone(),
|
None,
|
||||||
pos: Position::NONE,
|
Position::NONE,
|
||||||
};
|
Box::new((None, None, x.clone())),
|
||||||
args.push(Expr::Variable(Box::new((None, None, var_def))));
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
let expr = Expr::FnCall(
|
let expr = Expr::FnCall(
|
||||||
|
@ -26,7 +26,7 @@ impl Serialize for Dynamic {
|
|||||||
Union::Float(x, _) => ser.serialize_f64(**x),
|
Union::Float(x, _) => ser.serialize_f64(**x),
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
#[cfg(feature = "f32_float")]
|
#[cfg(feature = "f32_float")]
|
||||||
Union::Float(x, _) => ser.serialize_f32(*x),
|
Union::Float(x, _) => ser.serialize_f32(**x),
|
||||||
|
|
||||||
#[cfg(feature = "decimal")]
|
#[cfg(feature = "decimal")]
|
||||||
#[cfg(not(feature = "f32_float"))]
|
#[cfg(not(feature = "f32_float"))]
|
||||||
|
@ -45,7 +45,7 @@ impl Expression<'_> {
|
|||||||
/// If this expression is a variable name, return it. Otherwise [`None`].
|
/// If this expression is a variable name, return it. Otherwise [`None`].
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn get_variable_name(&self) -> Option<&str> {
|
pub fn get_variable_name(&self) -> Option<&str> {
|
||||||
self.0.get_variable_access(true)
|
self.0.get_variable_name(true)
|
||||||
}
|
}
|
||||||
/// Get the expression.
|
/// Get the expression.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
204
src/token.rs
204
src/token.rs
@ -11,13 +11,14 @@ use crate::stdlib::{
|
|||||||
iter::{FusedIterator, Peekable},
|
iter::{FusedIterator, Peekable},
|
||||||
num::NonZeroUsize,
|
num::NonZeroUsize,
|
||||||
ops::{Add, AddAssign},
|
ops::{Add, AddAssign},
|
||||||
|
rc::Rc,
|
||||||
str::{Chars, FromStr},
|
str::{Chars, FromStr},
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
};
|
};
|
||||||
use crate::{Engine, LexError, Shared, StaticVec, INT};
|
use crate::{Engine, LexError, StaticVec, INT};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
use crate::ast::FloatWrapper;
|
use crate::{ast::FloatWrapper, FLOAT};
|
||||||
|
|
||||||
#[cfg(feature = "decimal")]
|
#[cfg(feature = "decimal")]
|
||||||
use rust_decimal::Decimal;
|
use rust_decimal::Decimal;
|
||||||
@ -25,6 +26,17 @@ use rust_decimal::Decimal;
|
|||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
use crate::engine::KEYWORD_IS_DEF_FN;
|
use crate::engine::KEYWORD_IS_DEF_FN;
|
||||||
|
|
||||||
|
/// _(INTERNALS)_ A type containing commands to control the tokenizer.
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Hash, Copy, Default)]
|
||||||
|
pub struct TokenizerControlBlock {
|
||||||
|
/// Is the current tokenizer position within an interpolated text string?
|
||||||
|
/// This flag allows switching the tokenizer back to _text_ parsing after an interpolation stream.
|
||||||
|
pub is_within_text: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// _(INTERNALS)_ A shared object that allows control of the tokenizer from outside.
|
||||||
|
pub type TokenizerControl = Rc<Cell<TokenizerControlBlock>>;
|
||||||
|
|
||||||
type LERR = LexError;
|
type LERR = LexError;
|
||||||
|
|
||||||
/// Separator character for numbers.
|
/// Separator character for numbers.
|
||||||
@ -198,7 +210,7 @@ pub enum Token {
|
|||||||
///
|
///
|
||||||
/// Reserved under the `no_float` feature.
|
/// Reserved under the `no_float` feature.
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
FloatConstant(FloatWrapper),
|
FloatConstant(FloatWrapper<FLOAT>),
|
||||||
/// A [`Decimal`] constant.
|
/// A [`Decimal`] constant.
|
||||||
///
|
///
|
||||||
/// Requires the `decimal` feature.
|
/// Requires the `decimal` feature.
|
||||||
@ -815,7 +827,7 @@ impl From<Token> for String {
|
|||||||
/// This type is volatile and may change.
|
/// This type is volatile and may change.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Default)]
|
#[derive(Debug, Clone, Eq, PartialEq, Default)]
|
||||||
pub struct TokenizeState {
|
pub struct TokenizeState {
|
||||||
/// Maximum length of a string (0 = unlimited).
|
/// Maximum length of a string.
|
||||||
pub max_string_size: Option<NonZeroUsize>,
|
pub max_string_size: Option<NonZeroUsize>,
|
||||||
/// Can the next token be a unary operator?
|
/// Can the next token be a unary operator?
|
||||||
pub non_unary: bool,
|
pub non_unary: bool,
|
||||||
@ -827,6 +839,8 @@ pub struct TokenizeState {
|
|||||||
pub include_comments: bool,
|
pub include_comments: bool,
|
||||||
/// Disable doc-comments?
|
/// Disable doc-comments?
|
||||||
pub disable_doc_comments: bool,
|
pub disable_doc_comments: bool,
|
||||||
|
/// Is the current tokenizer position within the text stream of an interpolated string?
|
||||||
|
pub is_within_text_terminated_by: Option<char>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// _(INTERNALS)_ Trait that encapsulates a peekable character input stream.
|
/// _(INTERNALS)_ Trait that encapsulates a peekable character input stream.
|
||||||
@ -849,6 +863,9 @@ pub trait InputStream {
|
|||||||
/// _(INTERNALS)_ Parse a string literal ended by `termination_char`.
|
/// _(INTERNALS)_ Parse a string literal ended by `termination_char`.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
///
|
///
|
||||||
|
/// Returns the parsed string and a boolean indicating whether the string is
|
||||||
|
/// terminated by an interpolation `${`.
|
||||||
|
///
|
||||||
/// # Volatile API
|
/// # Volatile API
|
||||||
///
|
///
|
||||||
/// This function is volatile and may change.
|
/// This function is volatile and may change.
|
||||||
@ -859,6 +876,7 @@ pub fn parse_string_literal(
|
|||||||
termination_char: char,
|
termination_char: char,
|
||||||
continuation: bool,
|
continuation: bool,
|
||||||
verbatim: bool,
|
verbatim: bool,
|
||||||
|
skip_first_new_line: bool,
|
||||||
allow_interpolation: bool,
|
allow_interpolation: bool,
|
||||||
) -> Result<(String, bool), (LexError, Position)> {
|
) -> Result<(String, bool), (LexError, Position)> {
|
||||||
let mut result: smallvec::SmallVec<[char; 16]> = Default::default();
|
let mut result: smallvec::SmallVec<[char; 16]> = Default::default();
|
||||||
@ -868,6 +886,25 @@ pub fn parse_string_literal(
|
|||||||
let mut skip_whitespace_until = 0;
|
let mut skip_whitespace_until = 0;
|
||||||
let mut interpolated = false;
|
let mut interpolated = false;
|
||||||
|
|
||||||
|
if skip_first_new_line {
|
||||||
|
// Start from the next line if at the end of line
|
||||||
|
match stream.peek_next() {
|
||||||
|
// `\r - start from next line
|
||||||
|
Some('\r') => {
|
||||||
|
eat_next(stream, pos);
|
||||||
|
// `\r\n
|
||||||
|
if stream.peek_next().map(|ch| ch == '\n').unwrap_or(false) {
|
||||||
|
eat_next(stream, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// `\n - start from next line
|
||||||
|
Some('\n') => {
|
||||||
|
eat_next(stream, pos);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let next_char = stream.get_next().ok_or((LERR::UnterminatedString, start))?;
|
let next_char = stream.get_next().ok_or((LERR::UnterminatedString, start))?;
|
||||||
|
|
||||||
@ -1148,6 +1185,22 @@ fn get_next_token_inner(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Within text?
|
||||||
|
if let Some(ch) = state.is_within_text_terminated_by.take() {
|
||||||
|
let start_pos = *pos;
|
||||||
|
|
||||||
|
return parse_string_literal(stream, state, pos, ch, false, true, true, true).map_or_else(
|
||||||
|
|(err, err_pos)| Some((Token::LexError(err), err_pos)),
|
||||||
|
|(result, interpolated)| {
|
||||||
|
if interpolated {
|
||||||
|
Some((Token::InterpolatedString(result), start_pos))
|
||||||
|
} else {
|
||||||
|
Some((Token::StringConstant(result), start_pos))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let mut negated = false;
|
let mut negated = false;
|
||||||
|
|
||||||
while let Some(c) = stream.get_next() {
|
while let Some(c) = stream.get_next() {
|
||||||
@ -1262,42 +1315,42 @@ fn get_next_token_inner(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse number
|
// Parse number
|
||||||
if let Some(radix) = radix_base {
|
return Some((
|
||||||
let out: String = result.iter().skip(2).filter(|&&c| c != NUM_SEP).collect();
|
if let Some(radix) = radix_base {
|
||||||
|
let out: String =
|
||||||
|
result.iter().skip(2).filter(|&&c| c != NUM_SEP).collect();
|
||||||
|
|
||||||
return Some((
|
|
||||||
INT::from_str_radix(&out, radix)
|
INT::from_str_radix(&out, radix)
|
||||||
.map(Token::IntegerConstant)
|
.map(Token::IntegerConstant)
|
||||||
.unwrap_or_else(|_| {
|
.unwrap_or_else(|_| {
|
||||||
Token::LexError(LERR::MalformedNumber(result.into_iter().collect()))
|
Token::LexError(LERR::MalformedNumber(result.into_iter().collect()))
|
||||||
}),
|
})
|
||||||
start_pos,
|
} else {
|
||||||
));
|
let out: String = result.iter().filter(|&&c| c != NUM_SEP).collect();
|
||||||
} else {
|
let num = INT::from_str(&out).map(Token::IntegerConstant);
|
||||||
let out: String = result.iter().filter(|&&c| c != NUM_SEP).collect();
|
|
||||||
let num = INT::from_str(&out).map(Token::IntegerConstant);
|
|
||||||
|
|
||||||
// If integer parsing is unnecessary, try float instead
|
// If integer parsing is unnecessary, try float instead
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
let num =
|
let num =
|
||||||
num.or_else(|_| FloatWrapper::from_str(&out).map(Token::FloatConstant));
|
num.or_else(|_| FloatWrapper::from_str(&out).map(Token::FloatConstant));
|
||||||
|
|
||||||
// Then try decimal
|
// Then try decimal
|
||||||
#[cfg(feature = "decimal")]
|
#[cfg(feature = "decimal")]
|
||||||
let num = num.or_else(|_| Decimal::from_str(&out).map(Token::DecimalConstant));
|
let num =
|
||||||
|
num.or_else(|_| Decimal::from_str(&out).map(Token::DecimalConstant));
|
||||||
|
|
||||||
// Then try decimal in scientific notation
|
// Then try decimal in scientific notation
|
||||||
#[cfg(feature = "decimal")]
|
#[cfg(feature = "decimal")]
|
||||||
let num =
|
let num = num.or_else(|_| {
|
||||||
num.or_else(|_| Decimal::from_scientific(&out).map(Token::DecimalConstant));
|
Decimal::from_scientific(&out).map(Token::DecimalConstant)
|
||||||
|
});
|
||||||
|
|
||||||
return Some((
|
|
||||||
num.unwrap_or_else(|_| {
|
num.unwrap_or_else(|_| {
|
||||||
Token::LexError(LERR::MalformedNumber(result.into_iter().collect()))
|
Token::LexError(LERR::MalformedNumber(result.into_iter().collect()))
|
||||||
}),
|
})
|
||||||
start_pos,
|
},
|
||||||
));
|
start_pos,
|
||||||
}
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// letter or underscore ...
|
// letter or underscore ...
|
||||||
@ -1312,41 +1365,25 @@ fn get_next_token_inner(
|
|||||||
|
|
||||||
// " - string literal
|
// " - string literal
|
||||||
('"', _) => {
|
('"', _) => {
|
||||||
return parse_string_literal(stream, state, pos, c, true, false, false)
|
return parse_string_literal(stream, state, pos, c, true, false, false, false)
|
||||||
.map_or_else(
|
.map_or_else(
|
||||||
|err| Some((Token::LexError(err.0), err.1)),
|
|(err, err_pos)| Some((Token::LexError(err), err_pos)),
|
||||||
|(result, _)| Some((Token::StringConstant(result), start_pos)),
|
|(result, _)| Some((Token::StringConstant(result), start_pos)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// ` - string literal
|
// ` - string literal
|
||||||
('`', _) => {
|
('`', _) => {
|
||||||
// Start from the next line if ` at the end of line
|
return parse_string_literal(stream, state, pos, c, false, true, true, true)
|
||||||
match stream.peek_next() {
|
.map_or_else(
|
||||||
// `\r - start from next line
|
|(err, err_pos)| Some((Token::LexError(err), err_pos)),
|
||||||
Some('\r') => {
|
|(result, interpolated)| {
|
||||||
eat_next(stream, pos);
|
if interpolated {
|
||||||
// `\r\n
|
Some((Token::InterpolatedString(result), start_pos))
|
||||||
if stream.peek_next().map(|ch| ch == '\n').unwrap_or(false) {
|
} else {
|
||||||
eat_next(stream, pos);
|
Some((Token::StringConstant(result), start_pos))
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
// `\n - start from next line
|
);
|
||||||
Some('\n') => {
|
|
||||||
eat_next(stream, pos);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
return parse_string_literal(stream, state, pos, c, false, true, true).map_or_else(
|
|
||||||
|err| Some((Token::LexError(err.0), err.1)),
|
|
||||||
|(result, interpolated)| {
|
|
||||||
if interpolated {
|
|
||||||
Some((Token::InterpolatedString(result), start_pos))
|
|
||||||
} else {
|
|
||||||
Some((Token::StringConstant(result), start_pos))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ' - character literal
|
// ' - character literal
|
||||||
@ -1358,19 +1395,20 @@ fn get_next_token_inner(
|
|||||||
}
|
}
|
||||||
('\'', _) => {
|
('\'', _) => {
|
||||||
return Some(
|
return Some(
|
||||||
parse_string_literal(stream, state, pos, c, false, false, false).map_or_else(
|
parse_string_literal(stream, state, pos, c, false, false, false, false)
|
||||||
|err| (Token::LexError(err.0), err.1),
|
.map_or_else(
|
||||||
|(result, _)| {
|
|(err, err_pos)| (Token::LexError(err), err_pos),
|
||||||
let mut chars = result.chars();
|
|(result, _)| {
|
||||||
let first = chars.next().unwrap();
|
let mut chars = result.chars();
|
||||||
|
let first = chars.next().unwrap();
|
||||||
|
|
||||||
if chars.next().is_some() {
|
if chars.next().is_some() {
|
||||||
(Token::LexError(LERR::MalformedChar(result)), start_pos)
|
(Token::LexError(LERR::MalformedChar(result)), start_pos)
|
||||||
} else {
|
} else {
|
||||||
(Token::CharConstant(first), start_pos)
|
(Token::CharConstant(first), start_pos)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1840,8 +1878,8 @@ pub struct TokenIterator<'a> {
|
|||||||
state: TokenizeState,
|
state: TokenizeState,
|
||||||
/// Current position.
|
/// Current position.
|
||||||
pos: Position,
|
pos: Position,
|
||||||
/// Buffer containing the next character to read, if any.
|
/// External buffer containing the next character to read, if any.
|
||||||
buffer: Shared<Cell<Option<char>>>,
|
tokenizer_control: TokenizerControl,
|
||||||
/// Input character stream.
|
/// Input character stream.
|
||||||
stream: MultiInputsStream<'a>,
|
stream: MultiInputsStream<'a>,
|
||||||
/// A processor function that maps a token to another.
|
/// A processor function that maps a token to another.
|
||||||
@ -1852,9 +1890,14 @@ impl<'a> Iterator for TokenIterator<'a> {
|
|||||||
type Item = (Token, Position);
|
type Item = (Token, Position);
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
if let Some(ch) = self.buffer.take() {
|
let mut control = self.tokenizer_control.get();
|
||||||
self.stream.unget(ch);
|
|
||||||
self.pos.rewind();
|
if control.is_within_text {
|
||||||
|
// Switch to text mode terminated by back-tick
|
||||||
|
self.state.is_within_text_terminated_by = Some('`');
|
||||||
|
// Reset it
|
||||||
|
control.is_within_text = false;
|
||||||
|
self.tokenizer_control.set(control);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (token, pos) = match get_next_token(&mut self.stream, &mut self.state, &mut self.pos) {
|
let (token, pos) = match get_next_token(&mut self.stream, &mut self.state, &mut self.pos) {
|
||||||
@ -1945,7 +1988,7 @@ impl Engine {
|
|||||||
pub fn lex<'a>(
|
pub fn lex<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
input: impl IntoIterator<Item = &'a &'a str>,
|
input: impl IntoIterator<Item = &'a &'a str>,
|
||||||
) -> (TokenIterator<'a>, Shared<Cell<Option<char>>>) {
|
) -> (TokenIterator<'a>, TokenizerControl) {
|
||||||
self.lex_raw(input, None)
|
self.lex_raw(input, None)
|
||||||
}
|
}
|
||||||
/// _(INTERNALS)_ Tokenize an input text stream with a mapping function.
|
/// _(INTERNALS)_ Tokenize an input text stream with a mapping function.
|
||||||
@ -1956,7 +1999,7 @@ impl Engine {
|
|||||||
&'a self,
|
&'a self,
|
||||||
input: impl IntoIterator<Item = &'a &'a str>,
|
input: impl IntoIterator<Item = &'a &'a str>,
|
||||||
map: fn(Token) -> Token,
|
map: fn(Token) -> Token,
|
||||||
) -> (TokenIterator<'a>, Shared<Cell<Option<char>>>) {
|
) -> (TokenIterator<'a>, TokenizerControl) {
|
||||||
self.lex_raw(input, Some(map))
|
self.lex_raw(input, Some(map))
|
||||||
}
|
}
|
||||||
/// Tokenize an input text stream with an optional mapping function.
|
/// Tokenize an input text stream with an optional mapping function.
|
||||||
@ -1965,8 +2008,8 @@ impl Engine {
|
|||||||
&'a self,
|
&'a self,
|
||||||
input: impl IntoIterator<Item = &'a &'a str>,
|
input: impl IntoIterator<Item = &'a &'a str>,
|
||||||
map: Option<fn(Token) -> Token>,
|
map: Option<fn(Token) -> Token>,
|
||||||
) -> (TokenIterator<'a>, Shared<Cell<Option<char>>>) {
|
) -> (TokenIterator<'a>, TokenizerControl) {
|
||||||
let buffer: Shared<Cell<Option<char>>> = Cell::new(None).into();
|
let buffer: TokenizerControl = Default::default();
|
||||||
let buffer2 = buffer.clone();
|
let buffer2 = buffer.clone();
|
||||||
|
|
||||||
(
|
(
|
||||||
@ -1982,9 +2025,10 @@ impl Engine {
|
|||||||
end_with_none: false,
|
end_with_none: false,
|
||||||
include_comments: false,
|
include_comments: false,
|
||||||
disable_doc_comments: self.disable_doc_comments,
|
disable_doc_comments: self.disable_doc_comments,
|
||||||
|
is_within_text_terminated_by: None,
|
||||||
},
|
},
|
||||||
pos: Position::new(1, 0),
|
pos: Position::new(1, 0),
|
||||||
buffer,
|
tokenizer_control: buffer,
|
||||||
stream: MultiInputsStream {
|
stream: MultiInputsStream {
|
||||||
buf: None,
|
buf: None,
|
||||||
streams: input.into_iter().map(|s| s.chars().peekable()).collect(),
|
streams: input.into_iter().map(|s| s.chars().peekable()).collect(),
|
||||||
|
@ -81,7 +81,7 @@ fn test_call_fn_args() -> Result<(), Box<EvalAltResult>> {
|
|||||||
let ast = engine.compile(
|
let ast = engine.compile(
|
||||||
r#"
|
r#"
|
||||||
fn hello(x, y, z) {
|
fn hello(x, y, z) {
|
||||||
if x { "hello " + y } else { y + z }
|
if x { `hello ${y}` } else { y + z }
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#![cfg(not(feature = "no_function"))]
|
#![cfg(not(feature = "no_function"))]
|
||||||
use rhai::{Engine, EvalAltResult, FnNamespace, Module, ParseErrorType, Shared, INT};
|
use rhai::{Engine, EvalAltResult, FnNamespace, Module, Shared, INT};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -35,8 +35,27 @@ fn test_map_indexing() -> Result<(), Box<EvalAltResult>> {
|
|||||||
engine.eval::<INT>("let y = #{a: 1, b: 2, c: 3}; y.a = 5; y.a")?,
|
engine.eval::<INT>("let y = #{a: 1, b: 2, c: 3}; y.a = 5; y.a")?,
|
||||||
5
|
5
|
||||||
);
|
);
|
||||||
|
|
||||||
engine.eval::<()>("let y = #{a: 1, b: 2, c: 3}; y.z")?;
|
engine.eval::<()>("let y = #{a: 1, b: 2, c: 3}; y.z")?;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(
|
||||||
|
r#"
|
||||||
|
let y = #{`a
|
||||||
|
b`: 1}; y["a\nb"]
|
||||||
|
"#
|
||||||
|
)?,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
*engine
|
||||||
|
.eval::<INT>("let y = #{`a${1}`: 1}; y.a1")
|
||||||
|
.expect_err("should error"),
|
||||||
|
EvalAltResult::ErrorParsing(ParseErrorType::PropertyExpected, _)
|
||||||
|
));
|
||||||
|
|
||||||
assert!(engine.eval::<bool>(r#"let y = #{a: 1, b: 2, c: 3}; "c" in y"#)?);
|
assert!(engine.eval::<bool>(r#"let y = #{a: 1, b: 2, c: 3}; "c" in y"#)?);
|
||||||
assert!(engine.eval::<bool>(r#"let y = #{a: 1, b: 2, c: 3}; "b" in y"#)?);
|
assert!(engine.eval::<bool>(r#"let y = #{a: 1, b: 2, c: 3}; "b" in y"#)?);
|
||||||
assert!(!engine.eval::<bool>(r#"let y = #{a: 1, b: 2, c: 3}; "z" in y"#)?);
|
assert!(!engine.eval::<bool>(r#"let y = #{a: 1, b: 2, c: 3}; "z" in y"#)?);
|
||||||
|
@ -315,7 +315,7 @@ fn test_module_from_ast() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
// Final variable values become constant module variable values
|
// Final variable values become constant module variable values
|
||||||
foo = calc(foo);
|
foo = calc(foo);
|
||||||
hello = "hello, " + foo + " worlds!";
|
hello = `hello, ${foo} worlds!`;
|
||||||
|
|
||||||
export
|
export
|
||||||
x as abc,
|
x as abc,
|
||||||
|
@ -48,6 +48,7 @@ fn test_optimizer_run() -> Result<(), Box<EvalAltResult>> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
|
fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
@ -55,24 +56,33 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?;
|
let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?;
|
||||||
|
|
||||||
assert!(format!("{:?}", ast).starts_with(
|
assert_eq!(
|
||||||
r#"AST { source: None, body: [Expr(IntegerConstant(123, 1:53))], functions: Module("#
|
format!("{:?}", ast),
|
||||||
));
|
"AST { source: None, body: [Expr(123 @ 1:53)], functions: Module, resolver: None }"
|
||||||
|
);
|
||||||
|
|
||||||
let ast = engine.compile("const DECISION = false; if DECISION { 42 } else { 123 }")?;
|
let ast = engine.compile("const DECISION = false; if DECISION { 42 } else { 123 }")?;
|
||||||
|
|
||||||
assert!(format!("{:?}", ast).starts_with(r#"AST { source: None, body: [Const(BoolConstant(false, 1:18), Ident("DECISION" @ 1:7), false, 1:1), Expr(IntegerConstant(123, 1:51))], functions: Module("#));
|
assert_eq!(
|
||||||
|
format!("{:?}", ast),
|
||||||
|
r#"AST { source: None, body: [Const(false @ 1:18, "DECISION" @ 1:7, false, 1:1), Expr(123 @ 1:51)], functions: Module, resolver: None }"#
|
||||||
|
);
|
||||||
|
|
||||||
let ast = engine.compile("if 1 == 2 { 42 }")?;
|
let ast = engine.compile("if 1 == 2 { 42 }")?;
|
||||||
|
|
||||||
assert!(format!("{:?}", ast).starts_with("AST { source: None, body: [], functions: Module("));
|
assert_eq!(
|
||||||
|
format!("{:?}", ast),
|
||||||
|
"AST { source: None, body: [], functions: Module, resolver: None }"
|
||||||
|
);
|
||||||
|
|
||||||
engine.set_optimization_level(OptimizationLevel::Full);
|
engine.set_optimization_level(OptimizationLevel::Full);
|
||||||
|
|
||||||
let ast = engine.compile("abs(-42)")?;
|
let ast = engine.compile("abs(-42)")?;
|
||||||
|
|
||||||
assert!(format!("{:?}", ast)
|
assert_eq!(
|
||||||
.starts_with(r"AST { source: None, body: [Expr(IntegerConstant(42, 1:1))]"));
|
format!("{:?}", ast),
|
||||||
|
"AST { source: None, body: [Expr(42 @ 1:1)], functions: Module, resolver: None }"
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,8 @@ use serde::{Deserialize, Serialize};
|
|||||||
use rhai::Array;
|
use rhai::Array;
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
use rhai::Map;
|
use rhai::Map;
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
use rhai::FLOAT;
|
||||||
#[cfg(feature = "decimal")]
|
#[cfg(feature = "decimal")]
|
||||||
use rust_decimal::Decimal;
|
use rust_decimal::Decimal;
|
||||||
|
|
||||||
@ -358,7 +360,7 @@ fn test_serde_de_primary_types() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
{
|
{
|
||||||
assert_eq!(123.456, from_dynamic::<f64>(&123.456_f64.into())?);
|
assert_eq!(123.456, from_dynamic::<FLOAT>(&123.456.into())?);
|
||||||
assert_eq!(123.456, from_dynamic::<f32>(&Dynamic::from(123.456_f32))?);
|
assert_eq!(123.456, from_dynamic::<f32>(&Dynamic::from(123.456_f32))?);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -447,8 +449,8 @@ fn test_serde_de_struct() -> Result<(), Box<EvalAltResult>> {
|
|||||||
fn test_serde_de_script() -> Result<(), Box<EvalAltResult>> {
|
fn test_serde_de_script() -> Result<(), Box<EvalAltResult>> {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct Point {
|
struct Point {
|
||||||
x: f64,
|
x: FLOAT,
|
||||||
y: f64,
|
y: FLOAT,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -325,6 +325,16 @@ fn test_string_interpolated() -> Result<(), Box<EvalAltResult>> {
|
|||||||
"hello 42 worlds!"
|
"hello 42 worlds!"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<String>(
|
||||||
|
r#"
|
||||||
|
let x = 40;
|
||||||
|
"hello ${x+2} worlds!"
|
||||||
|
"#
|
||||||
|
)?,
|
||||||
|
"hello ${x+2} worlds!"
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<String>(
|
engine.eval::<String>(
|
||||||
r"
|
r"
|
||||||
|
Loading…
Reference in New Issue
Block a user