mirror of
https://github.com/nushell/nushell
synced 2024-12-26 04:53:09 +00:00
Module: support defining const and use const variables inside of function (#9773)
<!-- if this PR closes one or more issues, you can automatically link the PR with them by using one of the [*linking keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword), e.g. - this PR should close #xxxx - fixes #xxxx you can also mention related issues, PRs or discussions! --> # Description <!-- Thank you for improving Nushell. Please, check our [contributing guide](../CONTRIBUTING.md) and talk to the core team before making major changes. Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience. --> Relative: #8248 After this pr, user can define const variable inside a module. ![image](https://github.com/nushell/nushell/assets/22256154/e3e03e56-c4b5-4144-a944-d1b20bec1cbd) And user can export const variables, the following screenshot shows how it works (it follows https://github.com/nushell/nushell/issues/8248#issuecomment-1637442612): ![image](https://github.com/nushell/nushell/assets/22256154/b2c14760-3f27-41cc-af77-af70a4367f2a) ## About the change 1. To make module support const, we need to change `parse_module_block` to support `const` keyword. 2. To suport export `const`, we need to make module tracking variables, so we add `variables` attribute to `Module` 3. During eval, the const variable may not exists in `stack`, because we don't eval `const` when we define a module, so we need to find variables which are already registered in `engine_state` ## One more thing to note about the const value. Consider the following code ``` module foo { const b = 3; export def bar [] { $b } } use foo bar const b = 4; bar ``` The result will be 3 (which is defined in module) rather than 4. I think it's expected behavior. It's something like [dynamic binding](https://www.gnu.org/software/emacs/manual/html_node/elisp/Dynamic-Binding-Tips.html) vs [lexical binding](https://www.gnu.org/software/emacs/manual/html_node/elisp/Lexical-Binding.html) in lisp like language, and lexical binding should be right behavior which generates more predicable result, and it doesn't introduce really subtle bugs in nushell code. What if user want dynamic-binding?(For example: the example code returns `4`) There is no way to do this, user should consider passing the value as argument to custom command rather than const. ## TODO - [X] adding tests for the feature. - [X] support export const out of module to use. # User-Facing Changes <!-- List of all changes that impact the user experience here. This helps us keep track of breaking changes. --> # Tests + Formatting <!-- Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect -A clippy::result_large_err` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass - `cargo run -- -c "use std testing; testing run-tests --path crates/nu-std"` to run the tests for the standard library > **Note** > from `nushell` you can also use the `toolkit` as follows > ```bash > use toolkit.nu # or use an `env_change` hook to activate it automatically > toolkit check pr > ``` --> # After Submitting <!-- If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date. -->
This commit is contained in:
parent
583ef8674e
commit
f6033ac5af
13 changed files with 377 additions and 24 deletions
66
crates/nu-cmd-lang/src/core_commands/export_const.rs
Normal file
66
crates/nu-cmd-lang/src/core_commands/export_const.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ExportConst;
|
||||||
|
|
||||||
|
impl Command for ExportConst {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"export const"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Use parse-time constant from a module and export them from this module."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("export const")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
|
.required("const_name", SyntaxShape::VarWithOptType, "constant name")
|
||||||
|
.required(
|
||||||
|
"initial_value",
|
||||||
|
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::MathExpression)),
|
||||||
|
"equals sign followed by constant value",
|
||||||
|
)
|
||||||
|
.category(Category::Core)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
r#"This command is a parser keyword. For details, check:
|
||||||
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_parser_keyword(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_engine_state: &EngineState,
|
||||||
|
_stack: &mut Stack,
|
||||||
|
_call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
Ok(PipelineData::empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Re-export a command from another module",
|
||||||
|
example: r#"module spam { export const foo = 3; }
|
||||||
|
module eggs { export use spam foo }
|
||||||
|
use eggs foo
|
||||||
|
foo
|
||||||
|
"#,
|
||||||
|
result: Some(Value::test_int(3)),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["reexport", "import", "module"]
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ mod echo;
|
||||||
mod error_make;
|
mod error_make;
|
||||||
mod export;
|
mod export;
|
||||||
mod export_alias;
|
mod export_alias;
|
||||||
|
mod export_const;
|
||||||
mod export_def;
|
mod export_def;
|
||||||
mod export_def_env;
|
mod export_def_env;
|
||||||
mod export_extern;
|
mod export_extern;
|
||||||
|
@ -50,6 +51,7 @@ pub use echo::Echo;
|
||||||
pub use error_make::ErrorMake;
|
pub use error_make::ErrorMake;
|
||||||
pub use export::ExportCommand;
|
pub use export::ExportCommand;
|
||||||
pub use export_alias::ExportAlias;
|
pub use export_alias::ExportAlias;
|
||||||
|
pub use export_const::ExportConst;
|
||||||
pub use export_def::ExportDef;
|
pub use export_def::ExportDef;
|
||||||
pub use export_def_env::ExportDefEnv;
|
pub use export_def_env::ExportDefEnv;
|
||||||
pub use export_extern::ExportExtern;
|
pub use export_extern::ExportExtern;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use nu_engine::{eval_block, find_in_dirs_env, get_dirs_var_from_call, redirect_env};
|
use nu_engine::{eval_block, find_in_dirs_env, get_dirs_var_from_call, redirect_env};
|
||||||
use nu_protocol::ast::{Call, Expr, Expression};
|
use nu_protocol::ast::{Call, Expr, Expression, ImportPattern, ImportPatternMember};
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
Category, Example, Module, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -110,6 +110,14 @@ This command is a parser keyword. For details, check:
|
||||||
// Merge the block's environment to the current stack
|
// Merge the block's environment to the current stack
|
||||||
redirect_env(engine_state, caller_stack, &callee_stack);
|
redirect_env(engine_state, caller_stack, &callee_stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use_variables(
|
||||||
|
engine_state,
|
||||||
|
import_pattern,
|
||||||
|
module,
|
||||||
|
caller_stack,
|
||||||
|
call.head,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::GenericError(
|
return Err(ShellError::GenericError(
|
||||||
format!(
|
format!(
|
||||||
|
@ -162,6 +170,76 @@ This command is a parser keyword. For details, check:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn use_variables(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
import_pattern: &ImportPattern,
|
||||||
|
module: &Module,
|
||||||
|
caller_stack: &mut Stack,
|
||||||
|
head_span: Span,
|
||||||
|
) {
|
||||||
|
if !module.variables.is_empty() {
|
||||||
|
if import_pattern.members.is_empty() {
|
||||||
|
// add a record variable.
|
||||||
|
if let Some(var_id) = import_pattern.module_name_var_id {
|
||||||
|
let mut cols = vec![];
|
||||||
|
let mut vals = vec![];
|
||||||
|
for (var_name, var_id) in module.variables.iter() {
|
||||||
|
if let Some(val) = engine_state.get_var(*var_id).clone().const_val {
|
||||||
|
cols.push(String::from_utf8_lossy(var_name).to_string());
|
||||||
|
vals.push(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
caller_stack.add_var(
|
||||||
|
var_id,
|
||||||
|
Value::record(cols, vals, module.span.unwrap_or(head_span)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut have_glob = false;
|
||||||
|
for m in &import_pattern.members {
|
||||||
|
if matches!(m, ImportPatternMember::Glob { .. }) {
|
||||||
|
have_glob = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if have_glob {
|
||||||
|
// bring all variables into scope directly.
|
||||||
|
for (_, var_id) in module.variables.iter() {
|
||||||
|
if let Some(val) = engine_state.get_var(*var_id).clone().const_val {
|
||||||
|
caller_stack.add_var(*var_id, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut members = vec![];
|
||||||
|
for m in &import_pattern.members {
|
||||||
|
match m {
|
||||||
|
ImportPatternMember::List { names, .. } => {
|
||||||
|
for (n, _) in names {
|
||||||
|
if module.variables.contains_key(n) {
|
||||||
|
members.push(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImportPatternMember::Name { name, .. } => {
|
||||||
|
if module.variables.contains_key(name) {
|
||||||
|
members.push(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImportPatternMember::Glob { .. } => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for m in members {
|
||||||
|
if let Some(var_id) = module.variables.get(m) {
|
||||||
|
if let Some(val) = engine_state.get_var(*var_id).clone().const_val {
|
||||||
|
caller_stack.add_var(*var_id, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -29,6 +29,7 @@ pub fn create_default_context() -> EngineState {
|
||||||
ErrorMake,
|
ErrorMake,
|
||||||
ExportAlias,
|
ExportAlias,
|
||||||
ExportCommand,
|
ExportCommand,
|
||||||
|
ExportConst,
|
||||||
ExportDef,
|
ExportDef,
|
||||||
ExportDefEnv,
|
ExportDefEnv,
|
||||||
ExportExtern,
|
ExportExtern,
|
||||||
|
|
|
@ -58,6 +58,14 @@ pub fn eval_call(
|
||||||
let block = engine_state.get_block(block_id);
|
let block = engine_state.get_block(block_id);
|
||||||
|
|
||||||
let mut callee_stack = caller_stack.gather_captures(&block.captures);
|
let mut callee_stack = caller_stack.gather_captures(&block.captures);
|
||||||
|
// When the def is defined in module, relative captured variable doesn't go into stack
|
||||||
|
// so it can't be merged to callee_stack, but the variable is defined in `engine_state`
|
||||||
|
// then, to solve the issue, we also need to try to get relative const from `engine_state`
|
||||||
|
for cap in &block.captures {
|
||||||
|
if let Some(value) = engine_state.get_var(*cap).const_val.clone() {
|
||||||
|
callee_stack.vars.push((*cap, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (param_idx, param) in decl
|
for (param_idx, param) in decl
|
||||||
.signature()
|
.signature()
|
||||||
|
|
|
@ -940,9 +940,8 @@ pub fn parse_export_in_block(
|
||||||
let full_name = if lite_command.parts.len() > 1 {
|
let full_name = if lite_command.parts.len() > 1 {
|
||||||
let sub = working_set.get_span_contents(lite_command.parts[1]);
|
let sub = working_set.get_span_contents(lite_command.parts[1]);
|
||||||
match sub {
|
match sub {
|
||||||
b"alias" | b"def" | b"def-env" | b"extern" | b"extern-wrapped" | b"use" | b"module" => {
|
b"alias" | b"def" | b"def-env" | b"extern" | b"extern-wrapped" | b"use" | b"module"
|
||||||
[b"export ", sub].concat()
|
| b"const" => [b"export ", sub].concat(),
|
||||||
}
|
|
||||||
_ => b"export".to_vec(),
|
_ => b"export".to_vec(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1000,6 +999,7 @@ pub fn parse_export_in_block(
|
||||||
match full_name.as_slice() {
|
match full_name.as_slice() {
|
||||||
b"export alias" => parse_alias(working_set, lite_command, None),
|
b"export alias" => parse_alias(working_set, lite_command, None),
|
||||||
b"export def" | b"export def-env" => parse_def(working_set, lite_command, None),
|
b"export def" | b"export def-env" => parse_def(working_set, lite_command, None),
|
||||||
|
b"export const" => parse_const(working_set, &lite_command.parts[1..]),
|
||||||
b"export use" => {
|
b"export use" => {
|
||||||
let (pipeline, _) = parse_use(working_set, &lite_command.parts);
|
let (pipeline, _) = parse_use(working_set, &lite_command.parts);
|
||||||
pipeline
|
pipeline
|
||||||
|
@ -1400,6 +1400,59 @@ pub fn parse_export_in_module(
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
b"const" => {
|
||||||
|
let pipeline = parse_const(working_set, &spans[1..]);
|
||||||
|
let export_def_decl_id = if let Some(id) = working_set.find_decl(b"export const") {
|
||||||
|
id
|
||||||
|
} else {
|
||||||
|
working_set.error(ParseError::InternalError(
|
||||||
|
"missing 'export const' command".into(),
|
||||||
|
export_span,
|
||||||
|
));
|
||||||
|
return (garbage_pipeline(spans), vec![]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Trying to warp the 'const' call into the 'export const' in a very clumsy way
|
||||||
|
if let Some(PipelineElement::Expression(
|
||||||
|
_,
|
||||||
|
Expression {
|
||||||
|
expr: Expr::Call(ref def_call),
|
||||||
|
..
|
||||||
|
},
|
||||||
|
)) = pipeline.elements.get(0)
|
||||||
|
{
|
||||||
|
call = def_call.clone();
|
||||||
|
|
||||||
|
call.head = span(&spans[0..=1]);
|
||||||
|
call.decl_id = export_def_decl_id;
|
||||||
|
} else {
|
||||||
|
working_set.error(ParseError::InternalError(
|
||||||
|
"unexpected output from parsing a definition".into(),
|
||||||
|
span(&spans[1..]),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut result = vec![];
|
||||||
|
|
||||||
|
if let Some(decl_name_span) = spans.get(2) {
|
||||||
|
let decl_name = working_set.get_span_contents(*decl_name_span);
|
||||||
|
let decl_name = trim_quotes(decl_name);
|
||||||
|
|
||||||
|
if let Some(decl_id) = working_set.find_variable(decl_name) {
|
||||||
|
result.push(Exportable::VarDecl {
|
||||||
|
name: decl_name.to_vec(),
|
||||||
|
id: decl_id,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
working_set.error(ParseError::InternalError(
|
||||||
|
"failed to find added variable".into(),
|
||||||
|
span(&spans[1..]),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
working_set.error(ParseError::Expected(
|
working_set.error(ParseError::Expected(
|
||||||
"def, def-env, alias, use, module, or extern keyword",
|
"def, def-env, alias, use, module, or extern keyword",
|
||||||
|
@ -1589,6 +1642,9 @@ pub fn parse_module_block(
|
||||||
None, // using commands named as the module locally is OK
|
None, // using commands named as the module locally is OK
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
b"const" => block
|
||||||
|
.pipelines
|
||||||
|
.push(parse_const(working_set, &command.parts)),
|
||||||
b"extern" | b"extern-wrapped" => {
|
b"extern" | b"extern-wrapped" => {
|
||||||
block
|
block
|
||||||
.pipelines
|
.pipelines
|
||||||
|
@ -1713,6 +1769,9 @@ pub fn parse_module_block(
|
||||||
module.add_submodule(name, id);
|
module.add_submodule(name, id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Exportable::VarDecl { name, id } => {
|
||||||
|
module.add_variable(name, id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1730,7 +1789,7 @@ pub fn parse_module_block(
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
working_set.error(ParseError::ExpectedKeyword(
|
working_set.error(ParseError::ExpectedKeyword(
|
||||||
"def, def-env, extern, alias, use, module, export or export-env keyword".into(),
|
"def, const, def-env, extern, alias, use, module, export or export-env keyword".into(),
|
||||||
command.parts[0],
|
command.parts[0],
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -2208,7 +2267,7 @@ pub fn parse_use(working_set: &mut StateWorkingSet, spans: &[Span]) -> (Pipeline
|
||||||
return (garbage_pipeline(spans), vec![]);
|
return (garbage_pipeline(spans), vec![]);
|
||||||
};
|
};
|
||||||
|
|
||||||
let (import_pattern, module, module_id) = if let Some(module_id) = import_pattern.head.id {
|
let (mut import_pattern, module, module_id) = if let Some(module_id) = import_pattern.head.id {
|
||||||
let module = working_set.get_module(module_id).clone();
|
let module = working_set.get_module(module_id).clone();
|
||||||
(
|
(
|
||||||
ImportPattern {
|
ImportPattern {
|
||||||
|
@ -2219,6 +2278,7 @@ pub fn parse_use(working_set: &mut StateWorkingSet, spans: &[Span]) -> (Pipeline
|
||||||
},
|
},
|
||||||
members: import_pattern.members,
|
members: import_pattern.members,
|
||||||
hidden: HashSet::new(),
|
hidden: HashSet::new(),
|
||||||
|
module_name_var_id: None,
|
||||||
},
|
},
|
||||||
module,
|
module,
|
||||||
module_id,
|
module_id,
|
||||||
|
@ -2239,6 +2299,7 @@ pub fn parse_use(working_set: &mut StateWorkingSet, spans: &[Span]) -> (Pipeline
|
||||||
},
|
},
|
||||||
members: import_pattern.members,
|
members: import_pattern.members,
|
||||||
hidden: HashSet::new(),
|
hidden: HashSet::new(),
|
||||||
|
module_name_var_id: None,
|
||||||
},
|
},
|
||||||
module,
|
module,
|
||||||
module_id,
|
module_id,
|
||||||
|
@ -2276,12 +2337,29 @@ pub fn parse_use(working_set: &mut StateWorkingSet, spans: &[Span]) -> (Pipeline
|
||||||
id: *module_id,
|
id: *module_id,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
.chain(
|
||||||
|
definitions
|
||||||
|
.variables
|
||||||
|
.iter()
|
||||||
|
.map(|(name, variable_id)| Exportable::VarDecl {
|
||||||
|
name: name.clone(),
|
||||||
|
id: *variable_id,
|
||||||
|
}),
|
||||||
|
)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Extend the current scope with the module's exportables
|
// Extend the current scope with the module's exportables
|
||||||
working_set.use_decls(definitions.decls);
|
working_set.use_decls(definitions.decls);
|
||||||
working_set.use_modules(definitions.modules);
|
working_set.use_modules(definitions.modules);
|
||||||
|
working_set.use_variables(definitions.variables);
|
||||||
|
|
||||||
|
let module_name_var_id = working_set.add_variable(
|
||||||
|
module.name(),
|
||||||
|
module.span.unwrap_or(Span::unknown()),
|
||||||
|
Type::Any,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
import_pattern.module_name_var_id = Some(module_name_var_id);
|
||||||
// Create a new Use command call to pass the import pattern as parser info
|
// Create a new Use command call to pass the import pattern as parser info
|
||||||
let import_pattern_expr = Expression {
|
let import_pattern_expr = Expression {
|
||||||
expr: Expr::ImportPattern(import_pattern),
|
expr: Expr::ImportPattern(import_pattern),
|
||||||
|
@ -2703,7 +2781,7 @@ pub fn parse_overlay_use(working_set: &mut StateWorkingSet, call: Box<Call>) ->
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
(ResolvedImportPattern::new(vec![], vec![]), vec![])
|
(ResolvedImportPattern::new(vec![], vec![], vec![]), vec![])
|
||||||
};
|
};
|
||||||
|
|
||||||
if errors.is_empty() {
|
if errors.is_empty() {
|
||||||
|
|
|
@ -2976,6 +2976,7 @@ pub fn parse_import_pattern(working_set: &mut StateWorkingSet, spans: &[Span]) -
|
||||||
},
|
},
|
||||||
members: vec![],
|
members: vec![],
|
||||||
hidden: HashSet::new(),
|
hidden: HashSet::new(),
|
||||||
|
module_name_var_id: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
if spans.len() > 1 {
|
if spans.len() > 1 {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{span, ModuleId, Span};
|
use crate::{span, ModuleId, Span, VarId};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
@ -24,6 +24,7 @@ pub struct ImportPattern {
|
||||||
// communicate to eval which decls/aliases were hidden during `parse_hide()` so it does not
|
// communicate to eval which decls/aliases were hidden during `parse_hide()` so it does not
|
||||||
// interpret these as env var names:
|
// interpret these as env var names:
|
||||||
pub hidden: HashSet<Vec<u8>>,
|
pub hidden: HashSet<Vec<u8>>,
|
||||||
|
pub module_name_var_id: Option<VarId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImportPattern {
|
impl ImportPattern {
|
||||||
|
@ -36,6 +37,7 @@ impl ImportPattern {
|
||||||
},
|
},
|
||||||
members: vec![],
|
members: vec![],
|
||||||
hidden: HashSet::new(),
|
hidden: HashSet::new(),
|
||||||
|
module_name_var_id: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,6 +64,7 @@ impl ImportPattern {
|
||||||
head: self.head,
|
head: self.head,
|
||||||
members: self.members,
|
members: self.members,
|
||||||
hidden,
|
hidden,
|
||||||
|
module_name_var_id: self.module_name_var_id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1161,6 +1161,17 @@ impl<'a> StateWorkingSet<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn use_variables(&mut self, variables: Vec<(Vec<u8>, VarId)>) {
|
||||||
|
let overlay_frame = self.last_overlay_mut();
|
||||||
|
|
||||||
|
for (mut name, var_id) in variables {
|
||||||
|
if !name.starts_with(b"$") {
|
||||||
|
name.insert(0, b'$');
|
||||||
|
}
|
||||||
|
overlay_frame.insert_variable(name, var_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_predecl(&mut self, decl: Box<dyn Command>) -> Option<DeclId> {
|
pub fn add_predecl(&mut self, decl: Box<dyn Command>) -> Option<DeclId> {
|
||||||
let name = decl.name().as_bytes().to_vec();
|
let name = decl.name().as_bytes().to_vec();
|
||||||
|
|
||||||
|
@ -1530,11 +1541,15 @@ impl<'a> StateWorkingSet<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_variable(&self, name: &[u8]) -> Option<VarId> {
|
pub fn find_variable(&self, name: &[u8]) -> Option<VarId> {
|
||||||
|
let mut name = name.to_vec();
|
||||||
|
if !name.starts_with(b"$") {
|
||||||
|
name.insert(0, b'$');
|
||||||
|
}
|
||||||
let mut removed_overlays = vec![];
|
let mut removed_overlays = vec![];
|
||||||
|
|
||||||
for scope_frame in self.delta.scope.iter().rev() {
|
for scope_frame in self.delta.scope.iter().rev() {
|
||||||
for overlay_frame in scope_frame.active_overlays(&mut removed_overlays).rev() {
|
for overlay_frame in scope_frame.active_overlays(&mut removed_overlays).rev() {
|
||||||
if let Some(var_id) = overlay_frame.vars.get(name) {
|
if let Some(var_id) = overlay_frame.vars.get(&name) {
|
||||||
return Some(*var_id);
|
return Some(*var_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1545,7 +1560,7 @@ impl<'a> StateWorkingSet<'a> {
|
||||||
.active_overlays(&removed_overlays)
|
.active_overlays(&removed_overlays)
|
||||||
.rev()
|
.rev()
|
||||||
{
|
{
|
||||||
if let Some(var_id) = overlay_frame.vars.get(name) {
|
if let Some(var_id) = overlay_frame.vars.get(&name) {
|
||||||
return Some(*var_id);
|
return Some(*var_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -206,6 +206,10 @@ impl OverlayFrame {
|
||||||
self.modules.insert(name, module_id)
|
self.modules.insert(name, module_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn insert_variable(&mut self, name: Vec<u8>, variable_id: VarId) -> Option<VarId> {
|
||||||
|
self.vars.insert(name, variable_id)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_decl(&self, name: &[u8]) -> Option<DeclId> {
|
pub fn get_decl(&self, name: &[u8]) -> Option<DeclId> {
|
||||||
self.decls.get(name).cloned()
|
self.decls.get(name).cloned()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::{DeclId, ModuleId};
|
use crate::{DeclId, ModuleId, VarId};
|
||||||
|
|
||||||
pub enum Exportable {
|
pub enum Exportable {
|
||||||
Decl { name: Vec<u8>, id: DeclId },
|
Decl { name: Vec<u8>, id: DeclId },
|
||||||
Module { name: Vec<u8>, id: ModuleId },
|
Module { name: Vec<u8>, id: ModuleId },
|
||||||
|
VarDecl { name: Vec<u8>, id: VarId },
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::ImportPatternMember, engine::StateWorkingSet, BlockId, DeclId, ModuleId, ParseError, Span,
|
ast::ImportPatternMember, engine::StateWorkingSet, BlockId, DeclId, ModuleId, ParseError, Span,
|
||||||
|
VarId,
|
||||||
};
|
};
|
||||||
|
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
@ -7,11 +8,20 @@ use indexmap::IndexMap;
|
||||||
pub struct ResolvedImportPattern {
|
pub struct ResolvedImportPattern {
|
||||||
pub decls: Vec<(Vec<u8>, DeclId)>,
|
pub decls: Vec<(Vec<u8>, DeclId)>,
|
||||||
pub modules: Vec<(Vec<u8>, ModuleId)>,
|
pub modules: Vec<(Vec<u8>, ModuleId)>,
|
||||||
|
pub variables: Vec<(Vec<u8>, VarId)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResolvedImportPattern {
|
impl ResolvedImportPattern {
|
||||||
pub fn new(decls: Vec<(Vec<u8>, DeclId)>, modules: Vec<(Vec<u8>, ModuleId)>) -> Self {
|
pub fn new(
|
||||||
ResolvedImportPattern { decls, modules }
|
decls: Vec<(Vec<u8>, DeclId)>,
|
||||||
|
modules: Vec<(Vec<u8>, ModuleId)>,
|
||||||
|
variables: Vec<(Vec<u8>, VarId)>,
|
||||||
|
) -> Self {
|
||||||
|
ResolvedImportPattern {
|
||||||
|
decls,
|
||||||
|
modules,
|
||||||
|
variables,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +31,7 @@ pub struct Module {
|
||||||
pub name: Vec<u8>,
|
pub name: Vec<u8>,
|
||||||
pub decls: IndexMap<Vec<u8>, DeclId>,
|
pub decls: IndexMap<Vec<u8>, DeclId>,
|
||||||
pub submodules: IndexMap<Vec<u8>, ModuleId>,
|
pub submodules: IndexMap<Vec<u8>, ModuleId>,
|
||||||
|
pub variables: IndexMap<Vec<u8>, VarId>,
|
||||||
pub env_block: Option<BlockId>, // `export-env { ... }` block
|
pub env_block: Option<BlockId>, // `export-env { ... }` block
|
||||||
pub main: Option<DeclId>, // `export def main`
|
pub main: Option<DeclId>, // `export def main`
|
||||||
pub span: Option<Span>,
|
pub span: Option<Span>,
|
||||||
|
@ -32,6 +43,7 @@ impl Module {
|
||||||
name,
|
name,
|
||||||
decls: IndexMap::new(),
|
decls: IndexMap::new(),
|
||||||
submodules: IndexMap::new(),
|
submodules: IndexMap::new(),
|
||||||
|
variables: IndexMap::new(),
|
||||||
env_block: None,
|
env_block: None,
|
||||||
main: None,
|
main: None,
|
||||||
span: None,
|
span: None,
|
||||||
|
@ -43,6 +55,7 @@ impl Module {
|
||||||
name,
|
name,
|
||||||
decls: IndexMap::new(),
|
decls: IndexMap::new(),
|
||||||
submodules: IndexMap::new(),
|
submodules: IndexMap::new(),
|
||||||
|
variables: IndexMap::new(),
|
||||||
env_block: None,
|
env_block: None,
|
||||||
main: None,
|
main: None,
|
||||||
span: Some(span),
|
span: Some(span),
|
||||||
|
@ -61,6 +74,10 @@ impl Module {
|
||||||
self.submodules.insert(name, module_id)
|
self.submodules.insert(name, module_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_variable(&mut self, name: Vec<u8>, var_id: VarId) -> Option<VarId> {
|
||||||
|
self.variables.insert(name, var_id)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_env_block(&mut self, block_id: BlockId) {
|
pub fn add_env_block(&mut self, block_id: BlockId) {
|
||||||
self.env_block = Some(block_id);
|
self.env_block = Some(block_id);
|
||||||
}
|
}
|
||||||
|
@ -87,7 +104,8 @@ impl Module {
|
||||||
(head, rest)
|
(head, rest)
|
||||||
} else {
|
} else {
|
||||||
// Import pattern was just name without any members
|
// Import pattern was just name without any members
|
||||||
let mut results = vec![];
|
let mut decls = vec![];
|
||||||
|
let mut vars = vec![];
|
||||||
let mut errors = vec![];
|
let mut errors = vec![];
|
||||||
|
|
||||||
for (_, id) in &self.submodules {
|
for (_, id) in &self.submodules {
|
||||||
|
@ -101,14 +119,19 @@ impl Module {
|
||||||
new_name.push(b' ');
|
new_name.push(b' ');
|
||||||
new_name.extend(sub_name);
|
new_name.extend(sub_name);
|
||||||
|
|
||||||
results.push((new_name, sub_decl_id));
|
decls.push((new_name, sub_decl_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (sub_name, sub_var_id) in sub_results.variables {
|
||||||
|
vars.push((sub_name, sub_var_id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
results.extend(self.decls_with_head(&final_name));
|
decls.extend(self.decls_with_head(&final_name));
|
||||||
|
vars.extend(self.vars());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
ResolvedImportPattern::new(results, vec![(final_name, self_id)]),
|
ResolvedImportPattern::new(decls, vec![(final_name, self_id)], vars),
|
||||||
errors,
|
errors,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -118,18 +141,27 @@ impl Module {
|
||||||
if name == b"main" {
|
if name == b"main" {
|
||||||
if let Some(main_decl_id) = self.main {
|
if let Some(main_decl_id) = self.main {
|
||||||
(
|
(
|
||||||
ResolvedImportPattern::new(vec![(final_name, main_decl_id)], vec![]),
|
ResolvedImportPattern::new(
|
||||||
|
vec![(final_name, main_decl_id)],
|
||||||
|
vec![],
|
||||||
|
vec![],
|
||||||
|
),
|
||||||
vec![],
|
vec![],
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
ResolvedImportPattern::new(vec![], vec![]),
|
ResolvedImportPattern::new(vec![], vec![], vec![]),
|
||||||
vec![ParseError::ExportNotFound(*span)],
|
vec![ParseError::ExportNotFound(*span)],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if let Some(decl_id) = self.decls.get(name) {
|
} else if let Some(decl_id) = self.decls.get(name) {
|
||||||
(
|
(
|
||||||
ResolvedImportPattern::new(vec![(name.clone(), *decl_id)], vec![]),
|
ResolvedImportPattern::new(vec![(name.clone(), *decl_id)], vec![], vec![]),
|
||||||
|
vec![],
|
||||||
|
)
|
||||||
|
} else if let Some(var_id) = self.variables.get(name) {
|
||||||
|
(
|
||||||
|
ResolvedImportPattern::new(vec![], vec![], vec![(name.clone(), *var_id)]),
|
||||||
vec![],
|
vec![],
|
||||||
)
|
)
|
||||||
} else if let Some(submodule_id) = self.submodules.get(name) {
|
} else if let Some(submodule_id) = self.submodules.get(name) {
|
||||||
|
@ -137,7 +169,7 @@ impl Module {
|
||||||
submodule.resolve_import_pattern(working_set, *submodule_id, rest, None)
|
submodule.resolve_import_pattern(working_set, *submodule_id, rest, None)
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
ResolvedImportPattern::new(vec![], vec![]),
|
ResolvedImportPattern::new(vec![], vec![], vec![]),
|
||||||
vec![ParseError::ExportNotFound(*span)],
|
vec![ParseError::ExportNotFound(*span)],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -145,6 +177,7 @@ impl Module {
|
||||||
ImportPatternMember::Glob { .. } => {
|
ImportPatternMember::Glob { .. } => {
|
||||||
let mut decls = vec![];
|
let mut decls = vec![];
|
||||||
let mut submodules = vec![];
|
let mut submodules = vec![];
|
||||||
|
let mut variables = vec![];
|
||||||
let mut errors = vec![];
|
let mut errors = vec![];
|
||||||
|
|
||||||
for (_, id) in &self.submodules {
|
for (_, id) in &self.submodules {
|
||||||
|
@ -152,18 +185,25 @@ impl Module {
|
||||||
let (sub_results, sub_errors) =
|
let (sub_results, sub_errors) =
|
||||||
submodule.resolve_import_pattern(working_set, *id, &[], None);
|
submodule.resolve_import_pattern(working_set, *id, &[], None);
|
||||||
decls.extend(sub_results.decls);
|
decls.extend(sub_results.decls);
|
||||||
|
|
||||||
submodules.extend(sub_results.modules);
|
submodules.extend(sub_results.modules);
|
||||||
|
variables.extend(sub_results.variables);
|
||||||
errors.extend(sub_errors);
|
errors.extend(sub_errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
decls.extend(self.decls());
|
decls.extend(self.decls());
|
||||||
|
variables.extend(self.variables.clone());
|
||||||
submodules.extend(self.submodules());
|
submodules.extend(self.submodules());
|
||||||
|
|
||||||
(ResolvedImportPattern::new(decls, submodules), errors)
|
(
|
||||||
|
ResolvedImportPattern::new(decls, submodules, variables),
|
||||||
|
errors,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ImportPatternMember::List { names } => {
|
ImportPatternMember::List { names } => {
|
||||||
let mut decls = vec![];
|
let mut decls = vec![];
|
||||||
let mut submodules = vec![];
|
let mut submodules = vec![];
|
||||||
|
let mut variables = vec![];
|
||||||
let mut errors = vec![];
|
let mut errors = vec![];
|
||||||
|
|
||||||
for (name, span) in names {
|
for (name, span) in names {
|
||||||
|
@ -175,6 +215,8 @@ impl Module {
|
||||||
}
|
}
|
||||||
} else if let Some(decl_id) = self.decls.get(name) {
|
} else if let Some(decl_id) = self.decls.get(name) {
|
||||||
decls.push((name.clone(), *decl_id));
|
decls.push((name.clone(), *decl_id));
|
||||||
|
} else if let Some(var_id) = self.variables.get(name) {
|
||||||
|
variables.push((name.clone(), *var_id));
|
||||||
} else if let Some(submodule_id) = self.submodules.get(name) {
|
} else if let Some(submodule_id) = self.submodules.get(name) {
|
||||||
submodules.push((name.clone(), *submodule_id));
|
submodules.push((name.clone(), *submodule_id));
|
||||||
} else {
|
} else {
|
||||||
|
@ -182,7 +224,10 @@ impl Module {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(ResolvedImportPattern::new(decls, submodules), errors)
|
(
|
||||||
|
ResolvedImportPattern::new(decls, submodules, variables),
|
||||||
|
errors,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -217,6 +262,13 @@ impl Module {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn vars(&self) -> Vec<(Vec<u8>, VarId)> {
|
||||||
|
self.variables
|
||||||
|
.iter()
|
||||||
|
.map(|(name, id)| (name.to_vec(), *id))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn decl_names_with_head(&self, head: &[u8]) -> Vec<Vec<u8>> {
|
pub fn decl_names_with_head(&self, head: &[u8]) -> Vec<Vec<u8>> {
|
||||||
let mut result: Vec<Vec<u8>> = self
|
let mut result: Vec<Vec<u8>> = self
|
||||||
.decls
|
.decls
|
||||||
|
|
|
@ -111,3 +111,47 @@ fn export_alias() -> TestResult {
|
||||||
"hello",
|
"hello",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn export_consts() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
r#"module spam { export const b = 3; }; use spam b; $b"#,
|
||||||
|
"3",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn func_use_consts() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
r#"module spam { const b = 3; export def c [] { $b } }; use spam; spam c"#,
|
||||||
|
"3",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn export_module_which_defined_const() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
r#"module spam { export const b = 3; export const c = 4 }; use spam; $spam.b + $spam.c"#,
|
||||||
|
"7",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cannot_export_private_const() -> TestResult {
|
||||||
|
fail_test(
|
||||||
|
r#"module spam { const b = 3; export const c = 4 }; use spam; $spam.b + $spam.c"#,
|
||||||
|
"cannot find column 'b'",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lexical_binding() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
r#"module spam { const b = 3; export def c [] { $b } }; use spam c; const b = 4; c"#,
|
||||||
|
"3",
|
||||||
|
)?;
|
||||||
|
run_test(
|
||||||
|
r#"const b = 4; module spam { const b = 3; export def c [] { $b } }; use spam; spam c"#,
|
||||||
|
"3",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue