Allow multiple exports.
This commit is contained in:
parent
173f8474d6
commit
821e64adc4
@ -24,6 +24,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.
|
* 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.
|
* `export` keyword can now be tagged onto `let` and `const` statements as a short-hand.
|
||||||
|
* Variables can now be `export`-ed multiple times under different names.
|
||||||
* `index_of`, `==` and `!=` are defined for arrays.
|
* `index_of`, `==` and `!=` are defined for arrays.
|
||||||
* `==` and `!=` are defined for object maps.
|
* `==` and `!=` are defined for object maps.
|
||||||
|
|
||||||
|
@ -46,6 +46,18 @@ export x as answer; // the variable 'x' is exported under the alias 'answer'
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Multiple Exports
|
||||||
|
|
||||||
|
One `export` statement can export multiple variables, even under multiple names.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// The following exports three variables:
|
||||||
|
// - 'x' (as 'x' and 'hello')
|
||||||
|
// - 'y' (as 'foo' and 'bar')
|
||||||
|
// - 'z' (as 'z')
|
||||||
|
export x, x as hello, x as world, y as foo, y as bar, z;
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
Export Functions
|
Export Functions
|
||||||
----------------
|
----------------
|
||||||
|
@ -2124,7 +2124,7 @@ impl Engine {
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
if let Some(alias) = _alias {
|
if let Some(alias) = _alias {
|
||||||
scope.set_entry_alias(scope.len() - 1, alias);
|
scope.add_entry_alias(scope.len() - 1, alias);
|
||||||
}
|
}
|
||||||
Ok(Default::default())
|
Ok(Default::default())
|
||||||
}
|
}
|
||||||
@ -2178,7 +2178,7 @@ impl Engine {
|
|||||||
// Mark scope variables as public
|
// Mark scope variables as public
|
||||||
if let Some(index) = scope.get_index(name).map(|(i, _)| i) {
|
if let Some(index) = scope.get_index(name).map(|(i, _)| i) {
|
||||||
let alias = rename.as_ref().map(|x| &x.name).unwrap_or_else(|| name);
|
let alias = rename.as_ref().map(|x| &x.name).unwrap_or_else(|| name);
|
||||||
scope.set_entry_alias(index, alias.clone());
|
scope.add_entry_alias(index, alias.clone());
|
||||||
} else {
|
} else {
|
||||||
return EvalAltResult::ErrorVariableNotFound(name.into(), *id_pos).into();
|
return EvalAltResult::ErrorVariableNotFound(name.into(), *id_pos).into();
|
||||||
}
|
}
|
||||||
|
@ -1380,10 +1380,14 @@ impl Module {
|
|||||||
// Create new module
|
// Create new module
|
||||||
let mut module = Module::new();
|
let mut module = Module::new();
|
||||||
|
|
||||||
scope.into_iter().for_each(|(_, _, value, alias)| {
|
scope.into_iter().for_each(|(_, _, value, mut aliases)| {
|
||||||
// Variables with an alias left in the scope become module variables
|
// Variables with an alias left in the scope become module variables
|
||||||
if let Some(alias) = alias {
|
if aliases.len() > 1 {
|
||||||
module.variables.insert(alias, value);
|
aliases.into_iter().for_each(|alias| {
|
||||||
|
module.variables.insert(alias, value.clone());
|
||||||
|
});
|
||||||
|
} else if aliases.len() == 1 {
|
||||||
|
module.variables.insert(aliases.pop().unwrap(), value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -138,10 +138,6 @@ pub enum ParseErrorType {
|
|||||||
///
|
///
|
||||||
/// Never appears under the `no_function` feature.
|
/// Never appears under the `no_function` feature.
|
||||||
FnMissingBody(String),
|
FnMissingBody(String),
|
||||||
/// An export statement has duplicated names.
|
|
||||||
///
|
|
||||||
/// Never appears under the `no_module` feature.
|
|
||||||
DuplicatedExport(String),
|
|
||||||
/// Export statement not at global level.
|
/// Export statement not at global level.
|
||||||
///
|
///
|
||||||
/// Never appears under the `no_module` feature.
|
/// Never appears under the `no_module` feature.
|
||||||
@ -190,7 +186,6 @@ impl ParseErrorType {
|
|||||||
Self::FnDuplicatedParam(_,_) => "Duplicated parameters in function declaration",
|
Self::FnDuplicatedParam(_,_) => "Duplicated parameters in function declaration",
|
||||||
Self::FnMissingBody(_) => "Expecting body statement block for function declaration",
|
Self::FnMissingBody(_) => "Expecting body statement block for function declaration",
|
||||||
Self::WrongFnDefinition => "Function definitions must be at global level and cannot be inside a block or another function",
|
Self::WrongFnDefinition => "Function definitions must be at global level and cannot be inside a block or another function",
|
||||||
Self::DuplicatedExport(_) => "Duplicated variable/function in export statement",
|
|
||||||
Self::WrongExport => "Export statement can only appear at global level",
|
Self::WrongExport => "Export statement can only appear at global level",
|
||||||
Self::AssignmentToConstant(_) => "Cannot assign to a constant value",
|
Self::AssignmentToConstant(_) => "Cannot assign to a constant value",
|
||||||
Self::AssignmentToInvalidLHS(_) => "Expression cannot be assigned to",
|
Self::AssignmentToInvalidLHS(_) => "Expression cannot be assigned to",
|
||||||
@ -232,12 +227,6 @@ impl fmt::Display for ParseErrorType {
|
|||||||
write!(f, "Duplicated parameter '{}' for function '{}'", arg, s)
|
write!(f, "Duplicated parameter '{}' for function '{}'", arg, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::DuplicatedExport(s) => write!(
|
|
||||||
f,
|
|
||||||
"Duplicated variable/function '{}' in export statement",
|
|
||||||
s
|
|
||||||
),
|
|
||||||
|
|
||||||
Self::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s),
|
Self::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s),
|
||||||
|
|
||||||
Self::AssignmentToConstant(s) if s.is_empty() => f.write_str(self.desc()),
|
Self::AssignmentToConstant(s) if s.is_empty() => f.write_str(self.desc()),
|
||||||
|
@ -2076,19 +2076,6 @@ fn parse_export(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for duplicating parameters
|
|
||||||
exports
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.try_for_each(|(i, (Ident { name: id1, .. }, _))| {
|
|
||||||
exports
|
|
||||||
.iter()
|
|
||||||
.skip(i + 1)
|
|
||||||
.find(|(Ident { name: id2, .. }, _)| id2 == id1)
|
|
||||||
.map_or_else(|| Ok(()), |(Ident { name: id2, pos }, _)| Err((id2, *pos)))
|
|
||||||
})
|
|
||||||
.map_err(|(id2, pos)| PERR::DuplicatedExport(id2.to_string()).into_err(pos))?;
|
|
||||||
|
|
||||||
Ok(Stmt::Export(exports, token_pos))
|
Ok(Stmt::Export(exports, token_pos))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
19
src/scope.rs
19
src/scope.rs
@ -1,6 +1,7 @@
|
|||||||
//! Module that defines the `Scope` type representing a function call-stack scope.
|
//! Module that defines the `Scope` type representing a function call-stack scope.
|
||||||
|
|
||||||
use crate::dynamic::{Dynamic, Variant};
|
use crate::dynamic::{Dynamic, Variant};
|
||||||
|
use crate::StaticVec;
|
||||||
|
|
||||||
use crate::stdlib::{borrow::Cow, boxed::Box, iter, string::String, vec::Vec};
|
use crate::stdlib::{borrow::Cow, boxed::Box, iter, string::String, vec::Vec};
|
||||||
|
|
||||||
@ -70,8 +71,8 @@ pub struct Scope<'a> {
|
|||||||
values: Vec<Dynamic>,
|
values: Vec<Dynamic>,
|
||||||
/// Type of the entry.
|
/// Type of the entry.
|
||||||
types: Vec<EntryType>,
|
types: Vec<EntryType>,
|
||||||
/// (Name, alias) of the entry. The alias is Boxed because it occurs rarely.
|
/// (Name, aliases) of the entry. The list of aliases is Boxed because it occurs rarely.
|
||||||
names: Vec<(Cow<'a, str>, Option<Box<String>>)>,
|
names: Vec<(Cow<'a, str>, Box<StaticVec<String>>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Scope<'a> {
|
impl<'a> Scope<'a> {
|
||||||
@ -248,7 +249,7 @@ impl<'a> Scope<'a> {
|
|||||||
entry_type: EntryType,
|
entry_type: EntryType,
|
||||||
value: Dynamic,
|
value: Dynamic,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
self.names.push((name.into(), None));
|
self.names.push((name.into(), Box::new(Default::default())));
|
||||||
self.types.push(entry_type);
|
self.types.push(entry_type);
|
||||||
self.values.push(value.into());
|
self.values.push(value.into());
|
||||||
self
|
self
|
||||||
@ -387,9 +388,11 @@ impl<'a> Scope<'a> {
|
|||||||
/// Update the access type of an entry in the Scope.
|
/// Update the access type of an entry in the Scope.
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn set_entry_alias(&mut self, index: usize, alias: String) -> &mut Self {
|
pub(crate) fn add_entry_alias(&mut self, index: usize, alias: String) -> &mut Self {
|
||||||
let entry = self.names.get_mut(index).expect("invalid index in Scope");
|
let entry = self.names.get_mut(index).expect("invalid index in Scope");
|
||||||
entry.1 = Some(Box::new(alias));
|
if !entry.1.contains(&alias) {
|
||||||
|
entry.1.push(alias);
|
||||||
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
/// Clone the Scope, keeping only the last instances of each variable name.
|
/// Clone the Scope, keeping only the last instances of each variable name.
|
||||||
@ -416,11 +419,11 @@ impl<'a> Scope<'a> {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn into_iter(
|
pub(crate) fn into_iter(
|
||||||
self,
|
self,
|
||||||
) -> impl Iterator<Item = (Cow<'a, str>, EntryType, Dynamic, Option<String>)> {
|
) -> impl Iterator<Item = (Cow<'a, str>, EntryType, Dynamic, Vec<String>)> {
|
||||||
self.names
|
self.names
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(self.types.into_iter().zip(self.values.into_iter()))
|
.zip(self.types.into_iter().zip(self.values.into_iter()))
|
||||||
.map(|((name, alias), (typ, value))| (name, typ, value, alias.map(|v| *v)))
|
.map(|((name, alias), (typ, value))| (name, typ, value, alias.to_vec()))
|
||||||
}
|
}
|
||||||
/// Get an iterator to entries in the Scope.
|
/// Get an iterator to entries in the Scope.
|
||||||
/// Shared values are flatten-cloned.
|
/// Shared values are flatten-cloned.
|
||||||
@ -467,7 +470,7 @@ impl<'a, K: Into<Cow<'a, str>>> iter::Extend<(K, EntryType, Dynamic)> for Scope<
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn extend<T: IntoIterator<Item = (K, EntryType, Dynamic)>>(&mut self, iter: T) {
|
fn extend<T: IntoIterator<Item = (K, EntryType, Dynamic)>>(&mut self, iter: T) {
|
||||||
iter.into_iter().for_each(|(name, typ, value)| {
|
iter.into_iter().for_each(|(name, typ, value)| {
|
||||||
self.names.push((name.into(), None));
|
self.names.push((name.into(), Box::new(Default::default())));
|
||||||
self.types.push(typ);
|
self.types.push(typ);
|
||||||
self.values.push(value);
|
self.values.push(value);
|
||||||
});
|
});
|
||||||
|
@ -248,7 +248,7 @@ fn test_module_from_ast() -> Result<(), Box<EvalAltResult>> {
|
|||||||
import "another module" as extra;
|
import "another module" as extra;
|
||||||
|
|
||||||
// Variables defined at global level become module variables
|
// Variables defined at global level become module variables
|
||||||
const x = 123;
|
export const x = 123;
|
||||||
let foo = 41;
|
let foo = 41;
|
||||||
let hello;
|
let hello;
|
||||||
|
|
||||||
@ -258,6 +258,7 @@ fn test_module_from_ast() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
export
|
export
|
||||||
x as abc,
|
x as abc,
|
||||||
|
x as xxx,
|
||||||
foo,
|
foo,
|
||||||
hello;
|
hello;
|
||||||
"#,
|
"#,
|
||||||
@ -275,6 +276,14 @@ fn test_module_from_ast() -> Result<(), Box<EvalAltResult>> {
|
|||||||
engine.eval::<INT>(r#"import "testing" as ttt; ttt::abc"#)?,
|
engine.eval::<INT>(r#"import "testing" as ttt; ttt::abc"#)?,
|
||||||
123
|
123
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(r#"import "testing" as ttt; ttt::x"#)?,
|
||||||
|
123
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(r#"import "testing" as ttt; ttt::xxx"#)?,
|
||||||
|
123
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(r#"import "testing" as ttt; ttt::foo"#)?,
|
engine.eval::<INT>(r#"import "testing" as ttt; ttt::foo"#)?,
|
||||||
42
|
42
|
||||||
|
Loading…
Reference in New Issue
Block a user