Merge pull request #288 from schungx/master

Enable export let/const.
This commit is contained in:
Stephen Chung 2020-11-09 13:29:37 +08:00 committed by GitHub
commit b6538c45fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 86 additions and 56 deletions

View File

@ -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.

View File

@ -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
}
```

View File

@ -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(),

View File

@ -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())
}

View File

@ -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,

View File

@ -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),