commit
b6538c45fa
@ -23,6 +23,7 @@ Enhancements
|
||||
------------
|
||||
|
||||
* Modules imported via `import` statements at global level can now be used in functions. There is no longer any need to re-`import` the modules at the beginning of each function block.
|
||||
* `export` keyword can now be tagged onto `let` and `const` statements as a short-hand.
|
||||
* `index_of`, `==` and `!=` are defined for arrays.
|
||||
* `==` and `!=` are defined for object maps.
|
||||
|
||||
|
@ -32,14 +32,17 @@ let x = 42; // this will be exported below
|
||||
|
||||
export x; // the variable 'x' is exported under its own name
|
||||
|
||||
export let x = 42; // convenient short-hand to declare a variable and export it
|
||||
// under its own name
|
||||
|
||||
export x as answer; // the variable 'x' is exported under the alias 'answer'
|
||||
// another script can load this module and access 'x' as 'module::answer'
|
||||
|
||||
{
|
||||
let inner = 0; // local variable - it disappears when the statement block ends,
|
||||
// therefore it is not 'global' and is not exported
|
||||
// therefore it is not 'global' and cannot be exported
|
||||
|
||||
export inner; // exporting an temporary variable has no effect
|
||||
export inner; // <- syntax error: cannot export a local variable
|
||||
}
|
||||
```
|
||||
|
||||
|
30
src/ast.rs
30
src/ast.rs
@ -608,10 +608,10 @@ pub enum Stmt {
|
||||
Loop(Box<Stmt>, Position),
|
||||
/// for id in expr { stmt }
|
||||
For(Expr, Box<(String, Stmt)>, Position),
|
||||
/// let id = expr
|
||||
Let(Box<Ident>, Option<Expr>, Position),
|
||||
/// const id = expr
|
||||
Const(Box<Ident>, Option<Expr>, Position),
|
||||
/// [export] let id = expr
|
||||
Let(Box<Ident>, Option<Expr>, bool, Position),
|
||||
/// [export] const id = expr
|
||||
Const(Box<Ident>, Option<Expr>, bool, Position),
|
||||
/// expr op= expr
|
||||
Assignment(Box<(Expr, Cow<'static, str>, Expr)>, Position),
|
||||
/// { stmt; ... }
|
||||
@ -665,10 +665,10 @@ impl Stmt {
|
||||
| Self::While(_, _, pos)
|
||||
| Self::Loop(_, pos)
|
||||
| Self::For(_, _, pos)
|
||||
| Self::ReturnWithVal((_, pos), _, _) => *pos,
|
||||
|
||||
Self::Let(x, _, _) | Self::Const(x, _, _) => x.pos,
|
||||
Self::TryCatch(_, pos, _) => *pos,
|
||||
| Self::ReturnWithVal((_, pos), _, _)
|
||||
| Self::Let(_, _, _, pos)
|
||||
| Self::Const(_, _, _, pos)
|
||||
| Self::TryCatch(_, pos, _) => *pos,
|
||||
|
||||
Self::Expr(x) => x.position(),
|
||||
|
||||
@ -694,10 +694,10 @@ impl Stmt {
|
||||
| Self::While(_, _, pos)
|
||||
| Self::Loop(_, pos)
|
||||
| Self::For(_, _, pos)
|
||||
| Self::ReturnWithVal((_, pos), _, _) => *pos = new_pos,
|
||||
|
||||
Self::Let(x, _, _) | Self::Const(x, _, _) => x.pos = new_pos,
|
||||
Self::TryCatch(_, pos, _) => *pos = new_pos,
|
||||
| Self::ReturnWithVal((_, pos), _, _)
|
||||
| Self::Let(_, _, _, pos)
|
||||
| Self::Const(_, _, _, pos)
|
||||
| Self::TryCatch(_, pos, _) => *pos = new_pos,
|
||||
|
||||
Self::Expr(x) => {
|
||||
x.set_position(new_pos);
|
||||
@ -728,8 +728,8 @@ impl Stmt {
|
||||
// A No-op requires a semicolon in order to know it is an empty statement!
|
||||
Self::Noop(_) => false,
|
||||
|
||||
Self::Let(_, _, _)
|
||||
| Self::Const(_, _, _)
|
||||
Self::Let(_, _, _, _)
|
||||
| Self::Const(_, _, _, _)
|
||||
| Self::Assignment(_, _)
|
||||
| Self::Expr(_)
|
||||
| Self::Continue(_)
|
||||
@ -756,7 +756,7 @@ impl Stmt {
|
||||
Self::While(condition, block, _) => condition.is_pure() && block.is_pure(),
|
||||
Self::Loop(block, _) => block.is_pure(),
|
||||
Self::For(iterable, x, _) => iterable.is_pure() && x.1.is_pure(),
|
||||
Self::Let(_, _, _) | Self::Const(_, _, _) | Self::Assignment(_, _) => false,
|
||||
Self::Let(_, _, _, _) | Self::Const(_, _, _, _) | Self::Assignment(_, _) => false,
|
||||
Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()),
|
||||
Self::Continue(_) | Self::Break(_) | Self::ReturnWithVal(_, _, _) => false,
|
||||
Self::TryCatch(x, _, _) => x.0.is_pure() && x.2.is_pure(),
|
||||
|
@ -3,7 +3,7 @@
|
||||
use crate::ast::{BinaryExpr, Expr, FnCallInfo, Ident, IdentX, ReturnType, Stmt};
|
||||
use crate::dynamic::{map_std_type_name, Dynamic, Union, Variant};
|
||||
use crate::fn_call::run_builtin_op_assignment;
|
||||
use crate::fn_native::{shared_try_take, Callback, FnPtr, OnVarCallback, Shared};
|
||||
use crate::fn_native::{Callback, FnPtr, OnVarCallback, Shared};
|
||||
use crate::module::{Module, ModuleRef};
|
||||
use crate::optimize::OptimizationLevel;
|
||||
use crate::packages::{Package, PackagesCollection, StandardPackage};
|
||||
@ -18,10 +18,11 @@ use crate::{calc_native_fn_hash, StaticVec};
|
||||
use crate::INT;
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
use crate::module::ModuleResolver;
|
||||
use crate::{fn_native::shared_try_take, module::ModuleResolver};
|
||||
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::module::resolvers;
|
||||
|
||||
#[cfg(any(not(feature = "no_object"), not(feature = "no_module")))]
|
||||
@ -2092,10 +2093,10 @@ impl Engine {
|
||||
}
|
||||
|
||||
// Let/const statement
|
||||
Stmt::Let(var_def, expr, _) | Stmt::Const(var_def, expr, _) => {
|
||||
Stmt::Let(var_def, expr, export, _) | Stmt::Const(var_def, expr, export, _) => {
|
||||
let entry_type = match stmt {
|
||||
Stmt::Let(_, _, _) => ScopeEntryType::Normal,
|
||||
Stmt::Const(_, _, _) => ScopeEntryType::Constant,
|
||||
Stmt::Let(_, _, _, _) => ScopeEntryType::Normal,
|
||||
Stmt::Const(_, _, _, _) => ScopeEntryType::Constant,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
@ -2105,12 +2106,26 @@ impl Engine {
|
||||
} else {
|
||||
().into()
|
||||
};
|
||||
let var_name: Cow<'_, str> = if state.is_global() {
|
||||
var_def.name.clone().into()
|
||||
let (var_name, _alias): (Cow<'_, str>, _) = if state.is_global() {
|
||||
(
|
||||
var_def.name.clone().into(),
|
||||
if *export {
|
||||
Some(var_def.name.to_string())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
} else if *export {
|
||||
unreachable!();
|
||||
} else {
|
||||
unsafe_cast_var_name_to_lifetime(&var_def.name).into()
|
||||
(unsafe_cast_var_name_to_lifetime(&var_def.name).into(), None)
|
||||
};
|
||||
scope.push_dynamic_value(var_name, entry_type, val);
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
if let Some(alias) = _alias {
|
||||
scope.set_entry_alias(scope.len() - 1, alias);
|
||||
}
|
||||
Ok(Default::default())
|
||||
}
|
||||
|
||||
|
@ -266,9 +266,11 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
||||
)
|
||||
}
|
||||
// let id = expr;
|
||||
Stmt::Let(name, Some(expr), pos) => Stmt::Let(name, Some(optimize_expr(expr, state)), pos),
|
||||
Stmt::Let(name, Some(expr), export, pos) => {
|
||||
Stmt::Let(name, Some(optimize_expr(expr, state)), export, pos)
|
||||
}
|
||||
// let id;
|
||||
stmt @ Stmt::Let(_, None, _) => stmt,
|
||||
stmt @ Stmt::Let(_, None, _, _) => stmt,
|
||||
// import expr as var;
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Stmt::Import(expr, alias, pos) => Stmt::Import(optimize_expr(expr, state), alias, pos),
|
||||
@ -282,16 +284,12 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
||||
.into_iter()
|
||||
.map(|stmt| match stmt {
|
||||
// Add constant literals into the state
|
||||
Stmt::Const(var_def, Some(expr), pos) if expr.is_literal() => {
|
||||
Stmt::Const(var_def, Some(expr), _, pos) if expr.is_literal() => {
|
||||
state.set_dirty();
|
||||
state.push_constant(&var_def.name, expr);
|
||||
Stmt::Noop(pos) // No need to keep constants
|
||||
}
|
||||
Stmt::Const(var_def, Some(expr), pos) if expr.is_literal() => {
|
||||
let expr = optimize_expr(expr, state);
|
||||
Stmt::Const(var_def, Some(expr), pos)
|
||||
}
|
||||
Stmt::Const(var_def, None, pos) => {
|
||||
Stmt::Const(var_def, None, _, pos) => {
|
||||
state.set_dirty();
|
||||
state.push_constant(&var_def.name, Expr::Unit(var_def.pos));
|
||||
Stmt::Noop(pos) // No need to keep constants
|
||||
@ -317,7 +315,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
||||
|
||||
while let Some(expr) = result.pop() {
|
||||
match expr {
|
||||
Stmt::Let(_, expr, _) => {
|
||||
Stmt::Let(_, expr, _, _) => {
|
||||
removed = expr.as_ref().map(Expr::is_pure).unwrap_or(true)
|
||||
}
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
@ -375,7 +373,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
||||
Stmt::Noop(pos)
|
||||
}
|
||||
// Only one let statement - leave it alone
|
||||
[x] if matches!(x, Stmt::Let(_, _, _)) => Stmt::Block(result, pos),
|
||||
[x] if matches!(x, Stmt::Let(_, _, _, _)) => Stmt::Block(result, pos),
|
||||
// Only one import statement - leave it alone
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
[x] if matches!(x, Stmt::Import(_, _, _)) => Stmt::Block(result, pos),
|
||||
@ -780,7 +778,7 @@ fn optimize(
|
||||
.enumerate()
|
||||
.map(|(i, stmt)| {
|
||||
match stmt {
|
||||
Stmt::Const(var_def, Some(expr), pos) => {
|
||||
Stmt::Const(var_def, Some(expr), export, pos) => {
|
||||
// Load constants
|
||||
let expr = optimize_expr(expr, &mut state);
|
||||
|
||||
@ -791,12 +789,12 @@ fn optimize(
|
||||
// Keep it in the global scope
|
||||
if expr.is_unit() {
|
||||
state.set_dirty();
|
||||
Stmt::Const(var_def, None, pos)
|
||||
Stmt::Const(var_def, None, export, pos)
|
||||
} else {
|
||||
Stmt::Const(var_def, Some(expr), pos)
|
||||
Stmt::Const(var_def, Some(expr), export, pos)
|
||||
}
|
||||
}
|
||||
Stmt::Const(ref var_def, None, _) => {
|
||||
Stmt::Const(ref var_def, None, _, _) => {
|
||||
state.push_constant(&var_def.name, Expr::Unit(var_def.pos));
|
||||
|
||||
// Keep it in the global scope
|
||||
@ -806,7 +804,7 @@ fn optimize(
|
||||
// Keep all variable declarations at this level
|
||||
// and always keep the last return value
|
||||
let keep = match stmt {
|
||||
Stmt::Let(_, _, _) => true,
|
||||
Stmt::Let(_, _, _, _) => true,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Stmt::Import(_, _, _) => true,
|
||||
_ => i == num_statements - 1,
|
||||
|
@ -1918,6 +1918,7 @@ fn parse_let(
|
||||
state: &mut ParseState,
|
||||
lib: &mut FunctionsLib,
|
||||
var_type: ScopeEntryType,
|
||||
export: bool,
|
||||
mut settings: ParseSettings,
|
||||
) -> Result<Stmt, ParseError> {
|
||||
// let/const... (specified in `var_type`)
|
||||
@ -1938,7 +1939,7 @@ fn parse_let(
|
||||
};
|
||||
|
||||
// let name = ...
|
||||
let init_value = if match_token(input, Token::Equals).0 {
|
||||
let init_expr = if match_token(input, Token::Equals).0 {
|
||||
// let name = expr
|
||||
Some(parse_expr(input, state, lib, settings.level_up())?)
|
||||
} else {
|
||||
@ -1949,20 +1950,14 @@ fn parse_let(
|
||||
// let name = expr
|
||||
ScopeEntryType::Normal => {
|
||||
state.stack.push((name.clone(), ScopeEntryType::Normal));
|
||||
Ok(Stmt::Let(
|
||||
Box::new(Ident::new(name, pos)),
|
||||
init_value,
|
||||
token_pos,
|
||||
))
|
||||
let ident = Ident::new(name, pos);
|
||||
Ok(Stmt::Let(Box::new(ident), init_expr, export, token_pos))
|
||||
}
|
||||
// const name = { expr:constant }
|
||||
ScopeEntryType::Constant => {
|
||||
state.stack.push((name.clone(), ScopeEntryType::Constant));
|
||||
Ok(Stmt::Const(
|
||||
Box::new(Ident::new(name, pos)),
|
||||
init_value,
|
||||
token_pos,
|
||||
))
|
||||
let ident = Ident::new(name, pos);
|
||||
Ok(Stmt::Const(Box::new(ident), init_expr, export, token_pos))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2013,15 +2008,31 @@ fn parse_import(
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
fn parse_export(
|
||||
input: &mut TokenStream,
|
||||
_state: &mut ParseState,
|
||||
_lib: &mut FunctionsLib,
|
||||
state: &mut ParseState,
|
||||
lib: &mut FunctionsLib,
|
||||
mut settings: ParseSettings,
|
||||
) -> Result<Stmt, ParseError> {
|
||||
let token_pos = eat_token(input, Token::Export);
|
||||
settings.pos = token_pos;
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
settings.ensure_level_within_max_limit(_state.max_expr_depth)?;
|
||||
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
||||
|
||||
match input.peek().unwrap() {
|
||||
(Token::Let, pos) => {
|
||||
let pos = *pos;
|
||||
let mut stmt = parse_let(input, state, lib, ScopeEntryType::Normal, true, settings)?;
|
||||
stmt.set_position(pos);
|
||||
return Ok(stmt);
|
||||
}
|
||||
(Token::Const, pos) => {
|
||||
let pos = *pos;
|
||||
let mut stmt = parse_let(input, state, lib, ScopeEntryType::Constant, true, settings)?;
|
||||
stmt.set_position(pos);
|
||||
return Ok(stmt);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let mut exports = Vec::new();
|
||||
|
||||
@ -2311,8 +2322,10 @@ fn parse_stmt(
|
||||
|
||||
Token::Try => parse_try_catch(input, state, lib, settings.level_up()).map(Some),
|
||||
|
||||
Token::Let => parse_let(input, state, lib, Normal, settings.level_up()).map(Some),
|
||||
Token::Const => parse_let(input, state, lib, Constant, settings.level_up()).map(Some),
|
||||
Token::Let => parse_let(input, state, lib, Normal, false, settings.level_up()).map(Some),
|
||||
Token::Const => {
|
||||
parse_let(input, state, lib, Constant, false, settings.level_up()).map(Some)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Token::Import => parse_import(input, state, lib, settings.level_up()).map(Some),
|
||||
|
Loading…
Reference in New Issue
Block a user