Merge branch 'master' into plugins
This commit is contained in:
@@ -212,9 +212,11 @@ pub(crate) fn map_std_type_name(name: &str) -> &str {
|
||||
"string"
|
||||
} else if name == type_name::<FnPtr>() {
|
||||
"Fn"
|
||||
} else if name == type_name::<Instant>() {
|
||||
"timestamp"
|
||||
} else {
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
if name == type_name::<Instant>() {
|
||||
return "timestamp";
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
if name == type_name::<Array>() {
|
||||
return "array";
|
||||
|
@@ -26,7 +26,7 @@ use crate::stdlib::{
|
||||
boxed::Box,
|
||||
collections::{HashMap, HashSet},
|
||||
convert::TryFrom,
|
||||
format,
|
||||
fmt, format,
|
||||
iter::{empty, once},
|
||||
mem,
|
||||
string::{String, ToString},
|
||||
@@ -96,6 +96,7 @@ pub const MARKER_BLOCK: &str = "$block$";
|
||||
pub const MARKER_IDENT: &str = "$ident$";
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct Expression<'a>(&'a Expr);
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
@@ -346,6 +347,15 @@ pub struct Engine {
|
||||
pub(crate) max_map_size: usize,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Engine {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.id.as_ref() {
|
||||
Some(id) => write!(f, "Engine({})", id),
|
||||
None => f.write_str("Engine"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Engine {
|
||||
fn default() -> Self {
|
||||
// Create the new scripting Engine
|
||||
@@ -855,7 +865,7 @@ impl Engine {
|
||||
fn_name,
|
||||
args.iter()
|
||||
.map(|name| if name.is::<ImmutableString>() {
|
||||
"&str | ImmutableString"
|
||||
"&str | ImmutableString | String"
|
||||
} else {
|
||||
self.map_type_name((*name).type_name())
|
||||
})
|
||||
@@ -934,7 +944,7 @@ impl Engine {
|
||||
}
|
||||
|
||||
// Has a system function an override?
|
||||
fn has_override(&self, lib: &Module, (hash_fn, hash_script): (u64, u64)) -> bool {
|
||||
fn has_override(&self, lib: &Module, hash_fn: u64, hash_script: u64) -> bool {
|
||||
// NOTE: We skip script functions for global_module and packages, and native functions for lib
|
||||
|
||||
// First check script-defined functions
|
||||
@@ -976,13 +986,15 @@ impl Engine {
|
||||
|
||||
match fn_name {
|
||||
// type_of
|
||||
KEYWORD_TYPE_OF if args.len() == 1 && !self.has_override(lib, hashes) => Ok((
|
||||
self.map_type_name(args[0].type_name()).to_string().into(),
|
||||
false,
|
||||
)),
|
||||
KEYWORD_TYPE_OF if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => {
|
||||
Ok((
|
||||
self.map_type_name(args[0].type_name()).to_string().into(),
|
||||
false,
|
||||
))
|
||||
}
|
||||
|
||||
// Fn
|
||||
KEYWORD_FN_PTR if args.len() == 1 && !self.has_override(lib, hashes) => {
|
||||
KEYWORD_FN_PTR if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => {
|
||||
Err(Box::new(EvalAltResult::ErrorRuntime(
|
||||
"'Fn' should not be called in method style. Try Fn(...);".into(),
|
||||
Position::none(),
|
||||
@@ -990,7 +1002,7 @@ impl Engine {
|
||||
}
|
||||
|
||||
// eval - reaching this point it must be a method-style call
|
||||
KEYWORD_EVAL if args.len() == 1 && !self.has_override(lib, hashes) => {
|
||||
KEYWORD_EVAL if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => {
|
||||
Err(Box::new(EvalAltResult::ErrorRuntime(
|
||||
"'eval' should not be called in method style. Try eval(...);".into(),
|
||||
Position::none(),
|
||||
@@ -1076,10 +1088,10 @@ impl Engine {
|
||||
let idx = idx_val.downcast_mut::<StaticVec<Dynamic>>().unwrap();
|
||||
let mut fn_name = name.as_ref();
|
||||
|
||||
// Check if it is a FnPtr call
|
||||
let (result, updated) = if fn_name == KEYWORD_FN_PTR_CALL && obj.is::<FnPtr>() {
|
||||
// FnPtr call
|
||||
// Redirect function name
|
||||
fn_name = obj.as_str().unwrap();
|
||||
let fn_name = obj.as_str().unwrap();
|
||||
// Recalculate hash
|
||||
let hash = calc_fn_hash(empty(), fn_name, idx.len(), empty());
|
||||
// Arguments are passed as-is
|
||||
@@ -1090,11 +1102,32 @@ impl Engine {
|
||||
self.exec_fn_call(
|
||||
state, lib, fn_name, *native, hash, args, false, false, def_val, level,
|
||||
)
|
||||
} else if fn_name == KEYWORD_FN_PTR_CALL && idx.len() > 0 && idx[0].is::<FnPtr>() {
|
||||
// FnPtr call on object
|
||||
// Redirect function name
|
||||
let fn_name = idx[0]
|
||||
.downcast_ref::<FnPtr>()
|
||||
.unwrap()
|
||||
.get_fn_name()
|
||||
.clone();
|
||||
// Recalculate hash
|
||||
let hash = calc_fn_hash(empty(), &fn_name, idx.len() - 1, empty());
|
||||
// Replace the first argument with the object pointer
|
||||
let mut arg_values = once(obj)
|
||||
.chain(idx.iter_mut().skip(1))
|
||||
.collect::<StaticVec<_>>();
|
||||
let args = arg_values.as_mut();
|
||||
|
||||
// Map it to name(args) in function-call style
|
||||
self.exec_fn_call(
|
||||
state, lib, &fn_name, *native, hash, args, is_ref, true, def_val, level,
|
||||
)
|
||||
} else {
|
||||
let redirected: Option<ImmutableString>;
|
||||
let mut hash = *hash;
|
||||
|
||||
// Check if it is a map method call in OOP style
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
if let Some(map) = obj.downcast_ref::<Map>() {
|
||||
if let Some(val) = map.get(fn_name) {
|
||||
if let Some(f) = val.downcast_ref::<FnPtr>() {
|
||||
@@ -1915,7 +1948,7 @@ impl Engine {
|
||||
let hash_fn =
|
||||
calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>()));
|
||||
|
||||
if !self.has_override(lib, (hash_fn, *hash)) {
|
||||
if !self.has_override(lib, hash_fn, *hash) {
|
||||
// Fn - only in function call style
|
||||
let expr = args_expr.get(0);
|
||||
let arg_value =
|
||||
@@ -1941,7 +1974,7 @@ impl Engine {
|
||||
let hash_fn =
|
||||
calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>()));
|
||||
|
||||
if !self.has_override(lib, (hash_fn, *hash)) {
|
||||
if !self.has_override(lib, hash_fn, *hash) {
|
||||
// eval - only in function call style
|
||||
let prev_len = scope.len();
|
||||
let expr = args_expr.get(0);
|
||||
@@ -1961,6 +1994,36 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle call() - Redirect function call
|
||||
let redirected;
|
||||
let mut name = name.as_ref();
|
||||
let mut args_expr = args_expr.as_ref();
|
||||
let mut hash = *hash;
|
||||
|
||||
if name == KEYWORD_FN_PTR_CALL
|
||||
&& args_expr.len() >= 1
|
||||
&& !self.has_override(lib, 0, hash)
|
||||
{
|
||||
let expr = args_expr.get(0).unwrap();
|
||||
let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||
|
||||
if fn_ptr.is::<FnPtr>() {
|
||||
// Redirect function name
|
||||
redirected = Some(fn_ptr.cast::<FnPtr>().take_fn_name());
|
||||
name = redirected.as_ref().unwrap();
|
||||
// Skip the first argument
|
||||
args_expr = &args_expr.as_ref()[1..];
|
||||
// Recalculate hash
|
||||
hash = calc_fn_hash(empty(), name, args_expr.len(), empty());
|
||||
} else {
|
||||
return Err(Box::new(EvalAltResult::ErrorMismatchOutputType(
|
||||
self.map_type_name(type_name::<FnPtr>()).into(),
|
||||
fn_ptr.type_name().into(),
|
||||
expr.position(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
// Normal function call - except for Fn and eval (handled above)
|
||||
let mut arg_values: StaticVec<Dynamic>;
|
||||
let mut args: StaticVec<_>;
|
||||
@@ -1972,7 +2035,7 @@ impl Engine {
|
||||
} else {
|
||||
// See if the first argument is a variable, if so, convert to method-call style
|
||||
// in order to leverage potential &mut first argument and avoid cloning the value
|
||||
match args_expr.get(0) {
|
||||
match args_expr.get(0).unwrap() {
|
||||
// func(x, ...) -> x.func(...)
|
||||
lhs @ Expr::Variable(_) => {
|
||||
arg_values = args_expr
|
||||
@@ -2009,7 +2072,7 @@ impl Engine {
|
||||
|
||||
let args = args.as_mut();
|
||||
self.exec_fn_call(
|
||||
state, lib, name, *native, *hash, args, is_ref, false, def_val, level,
|
||||
state, lib, name, *native, hash, args, is_ref, false, def_val, level,
|
||||
)
|
||||
.map(|(v, _)| v)
|
||||
.map_err(|err| err.new_position(*pos))
|
||||
|
@@ -98,6 +98,8 @@ pub enum ParseErrorType {
|
||||
PropertyExpected,
|
||||
/// Missing a variable name after the `let`, `const` or `for` keywords.
|
||||
VariableExpected,
|
||||
/// An identifier is a reserved keyword.
|
||||
Reserved(String),
|
||||
/// Missing an expression. Wrapped value is the expression type.
|
||||
ExprExpected(String),
|
||||
/// Defining a function `fn` in an appropriate place (e.g. inside another function).
|
||||
@@ -163,6 +165,7 @@ impl ParseErrorType {
|
||||
Self::ForbiddenConstantExpr(_) => "Expecting a constant",
|
||||
Self::PropertyExpected => "Expecting name of a property",
|
||||
Self::VariableExpected => "Expecting name of a variable",
|
||||
Self::Reserved(_) => "Invalid use of reserved keyword",
|
||||
Self::ExprExpected(_) => "Expecting an expression",
|
||||
Self::FnMissingName => "Expecting name in function declaration",
|
||||
Self::FnMissingParams(_) => "Expecting parameters in function declaration",
|
||||
@@ -224,6 +227,7 @@ impl fmt::Display for ParseErrorType {
|
||||
Self::LiteralTooLarge(typ, max) => {
|
||||
write!(f, "{} exceeds the maximum limit ({})", typ, max)
|
||||
}
|
||||
Self::Reserved(s) => write!(f, "'{}' is a reserved keyword", s),
|
||||
_ => f.write_str(self.desc()),
|
||||
}
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ use crate::result::EvalAltResult;
|
||||
use crate::token::{is_valid_identifier, Position};
|
||||
use crate::utils::ImmutableString;
|
||||
|
||||
use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, rc::Rc, sync::Arc};
|
||||
use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, rc::Rc, string::String, sync::Arc};
|
||||
|
||||
/// Trait that maps to `Send + Sync` only under the `sync` feature.
|
||||
#[cfg(feature = "sync")]
|
||||
@@ -87,7 +87,7 @@ impl TryFrom<ImmutableString> for FnPtr {
|
||||
Ok(Self(value))
|
||||
} else {
|
||||
Err(Box::new(EvalAltResult::ErrorFunctionNotFound(
|
||||
value.to_string(),
|
||||
value.into(),
|
||||
Position::none(),
|
||||
)))
|
||||
}
|
||||
|
@@ -7,10 +7,16 @@ use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync};
|
||||
use crate::module::Module;
|
||||
use crate::parser::FnAccess;
|
||||
use crate::plugin::Plugin;
|
||||
use crate::r#unsafe::unsafe_cast_box;
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::utils::ImmutableString;
|
||||
|
||||
use crate::stdlib::{any::TypeId, boxed::Box, mem};
|
||||
use crate::stdlib::{
|
||||
any::TypeId,
|
||||
boxed::Box,
|
||||
mem,
|
||||
string::{String, ToString},
|
||||
};
|
||||
|
||||
/// A trait to register custom plugins with the `Engine`.
|
||||
///
|
||||
@@ -185,6 +191,9 @@ pub fn by_value<T: Variant + Clone>(data: &mut Dynamic) -> T {
|
||||
let ref_str = data.as_str().unwrap();
|
||||
let ref_T = unsafe { mem::transmute::<_, &T>(&ref_str) };
|
||||
ref_T.clone()
|
||||
} else if TypeId::of::<T>() == TypeId::of::<String>() {
|
||||
// If T is String, data must be ImmutableString, so map directly to it
|
||||
*unsafe_cast_box(Box::new(data.as_str().unwrap().to_string())).unwrap()
|
||||
} else {
|
||||
// We consume the argument and then replace it with () - the argument is not supposed to be used again.
|
||||
// This way, we avoid having to clone the argument again, because it is already a clone when passed here.
|
||||
@@ -240,13 +249,15 @@ pub fn map_result(
|
||||
data
|
||||
}
|
||||
|
||||
/// Remap `&str` to `ImmutableString`.
|
||||
/// Remap `&str` | `String` to `ImmutableString`.
|
||||
#[inline(always)]
|
||||
fn map_type_id<T: 'static>() -> TypeId {
|
||||
let id = TypeId::of::<T>();
|
||||
|
||||
if id == TypeId::of::<&str>() {
|
||||
TypeId::of::<ImmutableString>()
|
||||
} else if id == TypeId::of::<String>() {
|
||||
TypeId::of::<ImmutableString>()
|
||||
} else {
|
||||
id
|
||||
}
|
||||
|
@@ -25,11 +25,13 @@ use crate::stdlib::{
|
||||
num::NonZeroUsize,
|
||||
ops::{Deref, DerefMut},
|
||||
string::{String, ToString},
|
||||
sync::RwLock,
|
||||
vec,
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
use crate::stdlib::sync::RwLock;
|
||||
|
||||
/// Return type of module-level Rust function.
|
||||
pub type FuncReturn<T> = Result<T, Box<EvalAltResult>>;
|
||||
|
||||
|
@@ -52,7 +52,7 @@ pub type PackageLibrary = Shared<Module>;
|
||||
|
||||
/// Type containing a collection of `PackageLibrary` instances.
|
||||
/// All function and type iterator keys in the loaded packages are indexed for fast access.
|
||||
#[derive(Clone, Default)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(crate) struct PackagesCollection(StaticVec<PackageLibrary>);
|
||||
|
||||
impl PackagesCollection {
|
||||
|
192
src/parser.rs
192
src/parser.rs
@@ -7,7 +7,7 @@ use crate::error::{LexError, ParseError, ParseErrorType};
|
||||
use crate::module::{Module, ModuleRef};
|
||||
use crate::optimize::{optimize_into_ast, OptimizationLevel};
|
||||
use crate::scope::{EntryType as ScopeEntryType, Scope};
|
||||
use crate::token::{Position, Token, TokenStream};
|
||||
use crate::token::{is_valid_identifier, Position, Token, TokenStream};
|
||||
use crate::utils::{StaticVec, StraightHasherBuilder};
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
@@ -25,6 +25,7 @@ use crate::stdlib::{
|
||||
char,
|
||||
collections::HashMap,
|
||||
fmt, format,
|
||||
hash::Hash,
|
||||
iter::empty,
|
||||
mem,
|
||||
num::NonZeroUsize,
|
||||
@@ -340,7 +341,7 @@ impl fmt::Display for FnAccess {
|
||||
}
|
||||
|
||||
/// A scripted function definition.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct ScriptFnDef {
|
||||
/// Function name.
|
||||
pub name: String,
|
||||
@@ -374,7 +375,7 @@ impl fmt::Display for ScriptFnDef {
|
||||
}
|
||||
|
||||
/// `return`/`throw` statement.
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
|
||||
pub enum ReturnType {
|
||||
/// `return` statement.
|
||||
Return,
|
||||
@@ -440,6 +441,8 @@ struct ParseSettings {
|
||||
pos: Position,
|
||||
/// Is the construct being parsed located at global level?
|
||||
is_global: bool,
|
||||
/// Is the construct being parsed located at function definition level?
|
||||
is_function_scope: bool,
|
||||
/// Is the current position inside a loop?
|
||||
is_breakable: bool,
|
||||
/// Is anonymous function allowed?
|
||||
@@ -476,7 +479,7 @@ impl ParseSettings {
|
||||
///
|
||||
/// Each variant is at most one pointer in size (for speed),
|
||||
/// with everything being allocated together in one single tuple.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub enum Stmt {
|
||||
/// No-op.
|
||||
Noop(Position),
|
||||
@@ -588,6 +591,13 @@ impl fmt::Debug for CustomExpr {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
impl Hash for CustomExpr {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.0.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// An expression.
|
||||
///
|
||||
/// Each variant is at most one pointer in size (for speed),
|
||||
@@ -663,6 +673,18 @@ impl Default for Expr {
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Expr {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
match self {
|
||||
Self::FloatConstant(x) => {
|
||||
state.write(&x.0.to_le_bytes());
|
||||
x.1.hash(state);
|
||||
}
|
||||
_ => self.hash(state),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
/// Get the `Dynamic` value of a constant expression.
|
||||
///
|
||||
@@ -1343,56 +1365,57 @@ fn parse_map_literal(
|
||||
eat_token(input, Token::RightBrace);
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
let (name, pos) = match input.next().unwrap() {
|
||||
(Token::Identifier(s), pos) => (s, pos),
|
||||
(Token::StringConstant(s), pos) => (s, pos),
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||
(_, pos) if map.is_empty() => {
|
||||
return Err(PERR::MissingToken(
|
||||
Token::RightBrace.into(),
|
||||
MISSING_RBRACE.into(),
|
||||
)
|
||||
.into_err(pos))
|
||||
}
|
||||
(Token::EOF, pos) => {
|
||||
return Err(PERR::MissingToken(
|
||||
Token::RightBrace.into(),
|
||||
MISSING_RBRACE.into(),
|
||||
)
|
||||
.into_err(pos))
|
||||
}
|
||||
(_, pos) => return Err(PERR::PropertyExpected.into_err(pos)),
|
||||
};
|
||||
|
||||
match input.next().unwrap() {
|
||||
(Token::Colon, _) => (),
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||
(_, pos) => {
|
||||
return Err(PERR::MissingToken(
|
||||
Token::Colon.into(),
|
||||
format!(
|
||||
"to follow the property '{}' in this object map literal",
|
||||
name
|
||||
),
|
||||
)
|
||||
.into_err(pos))
|
||||
}
|
||||
};
|
||||
|
||||
if state.engine.max_map_size > 0 && map.len() >= state.engine.max_map_size {
|
||||
return Err(PERR::LiteralTooLarge(
|
||||
"Number of properties in object map literal".to_string(),
|
||||
state.engine.max_map_size,
|
||||
)
|
||||
.into_err(input.peek().unwrap().1));
|
||||
}
|
||||
|
||||
let expr = parse_expr(input, state, lib, settings.level_up())?;
|
||||
map.push(((Into::<ImmutableString>::into(name), pos), expr));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let (name, pos) = match input.next().unwrap() {
|
||||
(Token::Identifier(s), pos) => (s, pos),
|
||||
(Token::StringConstant(s), pos) => (s, pos),
|
||||
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
||||
return Err(PERR::Reserved(s).into_err(pos));
|
||||
}
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||
(_, pos) if map.is_empty() => {
|
||||
return Err(
|
||||
PERR::MissingToken(Token::RightBrace.into(), MISSING_RBRACE.into())
|
||||
.into_err(pos),
|
||||
);
|
||||
}
|
||||
(Token::EOF, pos) => {
|
||||
return Err(
|
||||
PERR::MissingToken(Token::RightBrace.into(), MISSING_RBRACE.into())
|
||||
.into_err(pos),
|
||||
);
|
||||
}
|
||||
(_, pos) => return Err(PERR::PropertyExpected.into_err(pos)),
|
||||
};
|
||||
|
||||
match input.next().unwrap() {
|
||||
(Token::Colon, _) => (),
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||
(_, pos) => {
|
||||
return Err(PERR::MissingToken(
|
||||
Token::Colon.into(),
|
||||
format!(
|
||||
"to follow the property '{}' in this object map literal",
|
||||
name
|
||||
),
|
||||
)
|
||||
.into_err(pos))
|
||||
}
|
||||
};
|
||||
|
||||
if state.engine.max_map_size > 0 && map.len() >= state.engine.max_map_size {
|
||||
return Err(PERR::LiteralTooLarge(
|
||||
"Number of properties in object map literal".to_string(),
|
||||
state.engine.max_map_size,
|
||||
)
|
||||
.into_err(input.peek().unwrap().1));
|
||||
}
|
||||
|
||||
let expr = parse_expr(input, state, lib, settings.level_up())?;
|
||||
map.push(((Into::<ImmutableString>::into(name), pos), expr));
|
||||
|
||||
match input.peek().unwrap() {
|
||||
(Token::Comma, _) => {
|
||||
eat_token(input, Token::Comma);
|
||||
@@ -1460,6 +1483,24 @@ fn parse_primary(
|
||||
let index = state.find_var(&s);
|
||||
Expr::Variable(Box::new(((s, settings.pos), None, 0, index)))
|
||||
}
|
||||
// Function call is allowed to have reserved keyword
|
||||
Token::Reserved(s) if s != KEYWORD_THIS && input.peek().unwrap().0 == Token::LeftParen => {
|
||||
Expr::Variable(Box::new(((s, settings.pos), None, 0, None)))
|
||||
}
|
||||
// Access to `this` as a variable is OK
|
||||
Token::Reserved(s) if s == KEYWORD_THIS && input.peek().unwrap().0 != Token::LeftParen => {
|
||||
if !settings.is_function_scope {
|
||||
return Err(
|
||||
PERR::BadInput(format!("'{}' can only be used in functions", s))
|
||||
.into_err(settings.pos),
|
||||
);
|
||||
} else {
|
||||
Expr::Variable(Box::new(((s, settings.pos), None, 0, None)))
|
||||
}
|
||||
}
|
||||
Token::Reserved(s) if is_valid_identifier(s.chars()) => {
|
||||
return Err(PERR::Reserved(s).into_err(settings.pos));
|
||||
}
|
||||
Token::LeftParen => parse_paren_expr(input, state, lib, settings.level_up())?,
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Token::LeftBracket => parse_array_literal(input, state, lib, settings.level_up())?,
|
||||
@@ -1471,7 +1512,7 @@ fn parse_primary(
|
||||
_ => {
|
||||
return Err(
|
||||
PERR::BadInput(format!("Unexpected '{}'", token.syntax())).into_err(settings.pos)
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1509,6 +1550,9 @@ fn parse_primary(
|
||||
|
||||
Expr::Variable(Box::new(((id2, pos2), modules, 0, index)))
|
||||
}
|
||||
(Token::Reserved(id2), pos2) if is_valid_identifier(id2.chars()) => {
|
||||
return Err(PERR::Reserved(id2).into_err(pos2));
|
||||
}
|
||||
(_, pos2) => return Err(PERR::VariableExpected.into_err(pos2)),
|
||||
},
|
||||
// Indexing
|
||||
@@ -1538,6 +1582,7 @@ fn parse_primary(
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// Make sure identifiers are valid
|
||||
Ok(root_expr)
|
||||
}
|
||||
|
||||
@@ -2094,6 +2139,9 @@ fn parse_expr(
|
||||
(Token::Identifier(s), pos) => {
|
||||
exprs.push(Expr::Variable(Box::new(((s, pos), None, 0, None))));
|
||||
}
|
||||
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
||||
return Err(PERR::Reserved(s).into_err(pos));
|
||||
}
|
||||
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
||||
},
|
||||
MARKER_EXPR => exprs.push(parse_expr(input, state, lib, settings)?),
|
||||
@@ -2261,10 +2309,12 @@ fn parse_for(
|
||||
let name = match input.next().unwrap() {
|
||||
// Variable name
|
||||
(Token::Identifier(s), _) => s,
|
||||
// Reserved keyword
|
||||
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
||||
return Err(PERR::Reserved(s).into_err(pos));
|
||||
}
|
||||
// Bad identifier
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||
// EOF
|
||||
(Token::EOF, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
||||
// Not a variable name
|
||||
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
||||
};
|
||||
@@ -2311,20 +2361,13 @@ fn parse_let(
|
||||
// let name ...
|
||||
let (name, pos) = match input.next().unwrap() {
|
||||
(Token::Identifier(s), pos) => (s, pos),
|
||||
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
||||
return Err(PERR::Reserved(s).into_err(pos));
|
||||
}
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
||||
};
|
||||
|
||||
// Check if the name is allowed
|
||||
match name.as_str() {
|
||||
KEYWORD_THIS => {
|
||||
return Err(
|
||||
PERR::BadInput(LexError::MalformedIdentifier(name).to_string()).into_err(pos),
|
||||
)
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// let name = ...
|
||||
if match_token(input, Token::Equals)? {
|
||||
// let name = expr
|
||||
@@ -2390,6 +2433,9 @@ fn parse_import(
|
||||
// import expr as name ...
|
||||
let (name, _) = match input.next().unwrap() {
|
||||
(Token::Identifier(s), pos) => (s, pos),
|
||||
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
||||
return Err(PERR::Reserved(s).into_err(pos));
|
||||
}
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
||||
};
|
||||
@@ -2414,6 +2460,9 @@ fn parse_export(
|
||||
loop {
|
||||
let (id, id_pos) = match input.next().unwrap() {
|
||||
(Token::Identifier(s), pos) => (s.clone(), pos),
|
||||
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
||||
return Err(PERR::Reserved(s).into_err(pos));
|
||||
}
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
||||
};
|
||||
@@ -2421,6 +2470,10 @@ fn parse_export(
|
||||
let rename = if match_token(input, Token::As)? {
|
||||
match input.next().unwrap() {
|
||||
(Token::Identifier(s), pos) => Some((s.clone(), pos)),
|
||||
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
||||
return Err(PERR::Reserved(s).into_err(pos));
|
||||
}
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
||||
}
|
||||
} else {
|
||||
@@ -2598,6 +2651,7 @@ fn parse_stmt(
|
||||
allow_stmt_expr: true,
|
||||
allow_anonymous_fn: true,
|
||||
is_global: false,
|
||||
is_function_scope: true,
|
||||
is_breakable: false,
|
||||
level: 0,
|
||||
pos: pos,
|
||||
@@ -2695,7 +2749,11 @@ fn parse_fn(
|
||||
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
||||
|
||||
let name = match input.next().unwrap() {
|
||||
(Token::Identifier(s), _) | (Token::Custom(s), _) => s,
|
||||
(Token::Identifier(s), _) | (Token::Custom(s), _) | (Token::Reserved(s), _)
|
||||
if s != KEYWORD_THIS && is_valid_identifier(s.chars()) =>
|
||||
{
|
||||
s
|
||||
}
|
||||
(_, pos) => return Err(PERR::FnMissingName.into_err(pos)),
|
||||
};
|
||||
|
||||
@@ -2790,6 +2848,7 @@ impl Engine {
|
||||
allow_stmt_expr: false,
|
||||
allow_anonymous_fn: false,
|
||||
is_global: true,
|
||||
is_function_scope: false,
|
||||
is_breakable: false,
|
||||
level: 0,
|
||||
pos: Position::none(),
|
||||
@@ -2829,6 +2888,7 @@ impl Engine {
|
||||
allow_stmt_expr: true,
|
||||
allow_anonymous_fn: true,
|
||||
is_global: true,
|
||||
is_function_scope: false,
|
||||
is_breakable: false,
|
||||
level: 0,
|
||||
pos: Position::none(),
|
||||
|
@@ -4,6 +4,8 @@ use crate::optimize::OptimizationLevel;
|
||||
use crate::packages::PackageLibrary;
|
||||
use crate::token::is_valid_identifier;
|
||||
|
||||
use crate::stdlib::{boxed::Box, format, string::String};
|
||||
|
||||
impl Engine {
|
||||
/// Load a new package into the `Engine`.
|
||||
///
|
||||
|
@@ -16,7 +16,7 @@ mod inner {
|
||||
pub use core_error as error;
|
||||
|
||||
pub mod collections {
|
||||
pub use hashbrown::HashMap;
|
||||
pub use hashbrown::{HashMap, HashSet};
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -75,7 +75,7 @@ impl Engine {
|
||||
) -> Result<Dynamic, Box<EvalAltResult>>
|
||||
+ SendSync
|
||||
+ 'static,
|
||||
) -> Result<self, Box<LexError>> {
|
||||
) -> Result<&mut Self, Box<LexError>> {
|
||||
if value.is_empty() {
|
||||
return Err(Box::new(LexError::ImproperSymbol("".to_string())));
|
||||
}
|
||||
|
34
src/token.rs
34
src/token.rs
@@ -1,6 +1,10 @@
|
||||
//! Main module defining the lexer and parser.
|
||||
|
||||
use crate::engine::Engine;
|
||||
use crate::engine::{
|
||||
Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_PRINT,
|
||||
KEYWORD_THIS, KEYWORD_TYPE_OF,
|
||||
};
|
||||
|
||||
use crate::error::LexError;
|
||||
use crate::parser::INT;
|
||||
use crate::utils::StaticVec;
|
||||
@@ -13,7 +17,7 @@ use crate::stdlib::{
|
||||
boxed::Box,
|
||||
char,
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
fmt, format,
|
||||
iter::Peekable,
|
||||
str::{Chars, FromStr},
|
||||
string::{String, ToString},
|
||||
@@ -275,8 +279,6 @@ impl Token {
|
||||
Or => "||",
|
||||
Ampersand => "&",
|
||||
And => "&&",
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Fn => "fn",
|
||||
Continue => "continue",
|
||||
Break => "break",
|
||||
Return => "return",
|
||||
@@ -297,8 +299,12 @@ impl Token {
|
||||
ModuloAssign => "%=",
|
||||
PowerOf => "~",
|
||||
PowerOfAssign => "~=",
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Fn => "fn",
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Private => "private",
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Import => "import",
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
@@ -355,8 +361,6 @@ impl Token {
|
||||
"||" => Or,
|
||||
"&" => Ampersand,
|
||||
"&&" => And,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
"fn" => Fn,
|
||||
"continue" => Continue,
|
||||
"break" => Break,
|
||||
"return" => Return,
|
||||
@@ -377,17 +381,30 @@ impl Token {
|
||||
"%=" => ModuloAssign,
|
||||
"~" => PowerOf,
|
||||
"~=" => PowerOfAssign,
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
"fn" => Fn,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
"private" => Private,
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
"import" => Import,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
"export" => Export,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
"as" => As,
|
||||
|
||||
#[cfg(feature = "no_function")]
|
||||
"fn" | "private" => Reserved(syntax.into()),
|
||||
|
||||
#[cfg(feature = "no_module")]
|
||||
"import" | "export" | "as" => Reserved(syntax.into()),
|
||||
|
||||
"===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" => {
|
||||
Reserved(syntax.into())
|
||||
}
|
||||
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
||||
| KEYWORD_FN_PTR_CALL | KEYWORD_THIS => Reserved(syntax.into()),
|
||||
|
||||
_ => return None,
|
||||
})
|
||||
@@ -1353,9 +1370,10 @@ impl<'a> Iterator for TokenIterator<'a, '_> {
|
||||
"'#' is not a valid symbol. Should it be '#{'?"
|
||||
.to_string(),
|
||||
))),
|
||||
token => Token::LexError(Box::new(LERR::ImproperSymbol(
|
||||
format!("'{}' is not a valid symbol.", token)
|
||||
token if !is_valid_identifier(token.chars()) => Token::LexError(Box::new(LERR::ImproperSymbol(
|
||||
format!("'{}' is a reserved symbol.", token)
|
||||
))),
|
||||
_ => Token::Reserved(s)
|
||||
}, pos)),
|
||||
(r @ Some(_), None, None) => r,
|
||||
(Some((Token::Identifier(s), pos)), _, Some(custom)) if custom.contains_key(&s) => {
|
||||
|
@@ -156,6 +156,12 @@ impl<T> Drop for StaticVec<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Hash> Hash for StaticVec<T> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.iter().for_each(|x| x.hash(state));
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for StaticVec<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
|
Reference in New Issue
Block a user