diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 9beee6c1f1..48c0d05ab2 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -7,7 +7,7 @@ use nu_protocol::{ use crate::{ where_::Where, Alias, Benchmark, BuildString, Def, Do, Each, External, For, Git, GitCheckout, - If, Length, Let, LetEnv, Lines, ListGitBranches, Ls, Table, + If, Length, Let, LetEnv, Lines, ListGitBranches, Ls, Module, Table, Use, }; pub fn create_default_context() -> Rc> { @@ -46,6 +46,10 @@ pub fn create_default_context() -> Rc> { working_set.add_decl(Box::new(Ls)); + working_set.add_decl(Box::new(Module)); + + working_set.add_decl(Box::new(Use)); + working_set.add_decl(Box::new(Table)); working_set.add_decl(Box::new(External)); diff --git a/crates/nu-command/src/lib.rs b/crates/nu-command/src/lib.rs index 1f150c2c65..a96a41ec3b 100644 --- a/crates/nu-command/src/lib.rs +++ b/crates/nu-command/src/lib.rs @@ -15,8 +15,10 @@ mod let_env; mod lines; mod list_git_branches; mod ls; +mod module; mod run_external; mod table; +mod use_; mod where_; pub use alias::Alias; @@ -36,5 +38,7 @@ pub use let_env::LetEnv; pub use lines::Lines; pub use list_git_branches::ListGitBranches; pub use ls::Ls; +pub use module::Module; pub use run_external::External; pub use table::Table; +pub use use_::Use; diff --git a/crates/nu-command/src/module.rs b/crates/nu-command/src/module.rs new file mode 100644 index 0000000000..e2cec960d8 --- /dev/null +++ b/crates/nu-command/src/module.rs @@ -0,0 +1,34 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Signature, SyntaxShape, Value}; + +pub struct Module; + +impl Command for Module { + fn name(&self) -> &str { + "module" + } + + fn usage(&self) -> &str { + "Define a custom module" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("module") + .required("module_name", SyntaxShape::String, "module name") + .required( + "block", + SyntaxShape::Block(Some(vec![])), + "body of the module", + ) + } + + fn run( + &self, + _context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + Ok(Value::Nothing { span: call.head }) + } +} diff --git a/crates/nu-command/src/use_.rs b/crates/nu-command/src/use_.rs new file mode 100644 index 0000000000..30b5e3d0b0 --- /dev/null +++ b/crates/nu-command/src/use_.rs @@ -0,0 +1,28 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Signature, SyntaxShape, Value}; + +pub struct Use; + +impl Command for Use { + fn name(&self) -> &str { + "use" + } + + fn usage(&self) -> &str { + "Use definitions from a module" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("use").required("module_name", SyntaxShape::String, "module name") + } + + fn run( + &self, + _context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + Ok(Value::Nothing { span: call.head }) + } +} diff --git a/crates/nu-parser/src/errors.rs b/crates/nu-parser/src/errors.rs index 6d400f67b4..52f3983c1f 100644 --- a/crates/nu-parser/src/errors.rs +++ b/crates/nu-parser/src/errors.rs @@ -65,6 +65,10 @@ pub enum ParseError { #[diagnostic(code(nu::parser::variable_not_found), url(docsrs))] VariableNotFound(#[label = "variable not found"] Span), + #[error("Module not found.")] + #[diagnostic(code(nu::parser::module_not_found), url(docsrs))] + ModuleNotFound(#[label = "module not found"] Span), + #[error("Unknown command.")] #[diagnostic( code(nu::parser::unknown_command), diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 71b2005fa7..805c637484 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -10,7 +10,7 @@ use nu_protocol::{ RangeInclusion, RangeOperator, Statement, }, engine::StateWorkingSet, - span, Flag, PositionalArg, Signature, Span, SyntaxShape, Type, VarId, + span, DeclId, Flag, PositionalArg, Signature, Span, SyntaxShape, Type, VarId, }; #[derive(Debug, Clone)] @@ -2649,6 +2649,236 @@ pub fn parse_alias( ) } +pub fn parse_module( + working_set: &mut StateWorkingSet, + spans: &[Span], +) -> (Statement, Option) { + // TODO: Currently, module is closing over its parent scope (i.e., defs in the parent scope are + // visible and usable in this module's scope). We might want to disable that. How? + + let mut error = None; + let bytes = working_set.get_span_contents(spans[0]); + + // parse_def() equivalent + if bytes == b"module" && spans.len() >= 3 { + let (module_name_expr, err) = parse_string(working_set, spans[1]); + error = error.or(err); + + let module_name = module_name_expr + .as_string() + .expect("internal error: module name is not a string"); + + // parse_block_expression() equivalent + let block_span = spans[2]; + let block_bytes = working_set.get_span_contents(block_span); + let mut start = block_span.start; + let mut end = block_span.end; + + if block_bytes.starts_with(b"{") { + start += 1; + } else { + return ( + garbage_statement(spans), + Some(ParseError::Expected("block".into(), block_span)), + ); + } + + if block_bytes.ends_with(b"}") { + end -= 1; + } else { + error = error.or_else(|| { + Some(ParseError::Unclosed( + "}".into(), + Span { + start: end, + end: end + 1, + }, + )) + }); + } + + let block_span = Span { start, end }; + + let source = working_set.get_span_contents(block_span); + + let (output, err) = lex(source, start, &[], &[]); + error = error.or(err); + + working_set.enter_scope(); + + // Do we need block parameters? + + let (output, err) = lite_parse(&output); + error = error.or(err); + + // We probably don't need $it + + // we're doing parse_block() equivalent + // let (mut output, err) = parse_block(working_set, &output, false); + + for pipeline in &output.block { + if pipeline.commands.len() == 1 { + parse_def_predecl(working_set, &pipeline.commands[0].parts); + } + } + + let mut exports: Vec<(Vec, DeclId)> = vec![]; + + let block: Block = output + .block + .iter() + .map(|pipeline| { + if pipeline.commands.len() == 1 { + // this one here is doing parse_statement() equivalent + // let (stmt, err) = parse_statement(working_set, &pipeline.commands[0].parts); + let name = working_set.get_span_contents(pipeline.commands[0].parts[0]); + + let (stmt, err) = match name { + // TODO: Here we can add other stuff that's alowed for modules + b"def" => { + let (stmt, err) = parse_def(working_set, &pipeline.commands[0].parts); + + if err.is_none() { + let decl_name = + working_set.get_span_contents(pipeline.commands[0].parts[1]); + + let decl_id = working_set + .find_decl(decl_name) + .expect("internal error: failed to find added declaration"); + + // TODO: Later, we want to put this behind 'export' + exports.push((decl_name.into(), decl_id)); + } + + (stmt, err) + } + _ => ( + garbage_statement(&pipeline.commands[0].parts), + Some(ParseError::Expected("def".into(), block_span)), + ), + }; + + if error.is_none() { + error = err; + } + + stmt + } else { + error = Some(ParseError::Expected("not a pipeline".into(), block_span)); + garbage_statement(spans) + } + }) + .into(); + + let block = block.with_exports(exports); + + working_set.exit_scope(); + + let block_id = working_set.add_module(&module_name, block); + + let block_expr = Expression { + expr: Expr::Block(block_id), + span: block_span, + ty: Type::Block, + custom_completion: None, + }; + + let module_decl_id = working_set + .find_decl(b"module") + .expect("internal error: missing module command"); + + let call = Box::new(Call { + head: spans[0], + decl_id: module_decl_id, + positional: vec![module_name_expr, block_expr], + named: vec![], + }); + + ( + Statement::Pipeline(Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: span(spans), + ty: Type::Unknown, + custom_completion: None, + }])), + error, + ) + } else { + ( + garbage_statement(spans), + Some(ParseError::UnknownState( + "Expected structure: module {}".into(), + span(spans), + )), + ) + } +} + +pub fn parse_use( + working_set: &mut StateWorkingSet, + spans: &[Span], +) -> (Statement, Option) { + let mut error = None; + let bytes = working_set.get_span_contents(spans[0]); + + // TODO: Currently, this directly imports the module's definitions into the current scope. + // Later, we want to put them behind the module's name and add selective importing + if bytes == b"use" && spans.len() >= 2 { + let (module_name_expr, err) = parse_string(working_set, spans[1]); + error = error.or(err); + + let module_name = module_name_expr + .as_string() + .expect("internal error: module name is not a string"); + + let module_name_bytes = module_name.as_bytes().to_vec(); + + let exports = if let Some(block_id) = working_set.find_module(&module_name_bytes) { + // TODO: Since we don't use the Block at all, we might just as well create a separate + // Module that holds only the exports, without having Blocks in the way. + working_set.get_block(block_id).exports.clone() + } else { + return ( + garbage_statement(spans), + Some(ParseError::ModuleNotFound(spans[1])), + ); + }; + + // Extend the current scope with the module's exports + working_set.activate_overlay(exports); + + // Create the Use command call + let use_decl_id = working_set + .find_decl(b"use") + .expect("internal error: missing use command"); + + let call = Box::new(Call { + head: spans[0], + decl_id: use_decl_id, + positional: vec![module_name_expr], + named: vec![], + }); + + ( + Statement::Pipeline(Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: span(spans), + ty: Type::Unknown, + custom_completion: None, + }])), + error, + ) + } else { + ( + garbage_statement(spans), + Some(ParseError::UnknownState( + "Expected structure: use ".into(), + span(spans), + )), + ) + } +} + pub fn parse_let( working_set: &mut StateWorkingSet, spans: &[Span], @@ -2707,6 +2937,8 @@ pub fn parse_statement( b"def" => parse_def(working_set, spans), b"let" => parse_let(working_set, spans), b"alias" => parse_alias(working_set, spans), + b"module" => parse_module(working_set, spans), + b"use" => parse_use(working_set, spans), _ => { let (expr, err) = parse_expression(working_set, spans); (Statement::Pipeline(Pipeline::from_vec(vec![expr])), err) diff --git a/crates/nu-protocol/src/ast/block.rs b/crates/nu-protocol/src/ast/block.rs index e5db26da36..70273816b5 100644 --- a/crates/nu-protocol/src/ast/block.rs +++ b/crates/nu-protocol/src/ast/block.rs @@ -1,6 +1,6 @@ use std::ops::{Index, IndexMut}; -use crate::Signature; +use crate::{DeclId, Signature}; use super::Statement; @@ -8,6 +8,7 @@ use super::Statement; pub struct Block { pub signature: Box, pub stmts: Vec, + pub exports: Vec<(Vec, DeclId)>, // Assuming just defs for now } impl Block { @@ -45,6 +46,15 @@ impl Block { Self { signature: Box::new(Signature::new("")), stmts: vec![], + exports: vec![], + } + } + + pub fn with_exports(self, exports: Vec<(Vec, DeclId)>) -> Self { + Self { + signature: self.signature, + stmts: self.stmts, + exports, } } } @@ -57,6 +67,7 @@ where Self { signature: Box::new(Signature::new("")), stmts: stmts.collect(), + exports: vec![], } } } diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index 11941fe428..f466318970 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -17,6 +17,7 @@ pub struct ScopeFrame { vars: HashMap, VarId>, decls: HashMap, DeclId>, aliases: HashMap, Vec>, + modules: HashMap, BlockId>, } impl ScopeFrame { @@ -25,6 +26,7 @@ impl ScopeFrame { vars: HashMap::new(), decls: HashMap::new(), aliases: HashMap::new(), + modules: HashMap::new(), } } @@ -76,6 +78,9 @@ impl EngineState { for item in first.aliases.into_iter() { last.aliases.insert(item.0, item.1); } + for item in first.modules.into_iter() { + last.modules.insert(item.0, item.1); + } } } @@ -295,6 +300,37 @@ impl<'a> StateWorkingSet<'a> { self.num_blocks() - 1 } + pub fn add_module(&mut self, name: &str, block: Block) -> BlockId { + let name = name.as_bytes().to_vec(); + + self.delta.blocks.push(block); + let block_id = self.num_blocks() - 1; + + let scope_frame = self + .delta + .scope + .last_mut() + .expect("internal error: missing required scope frame"); + + scope_frame.modules.insert(name, block_id); + + block_id + } + + pub fn activate_overlay(&mut self, overlay: Vec<(Vec, DeclId)>) { + // TODO: This will overwrite all existing definitions in a scope. When we add deactivate, + // we need to re-think how make it recoverable. + let scope_frame = self + .delta + .scope + .last_mut() + .expect("internal error: missing required scope frame"); + + for (name, decl_id) in overlay { + scope_frame.decls.insert(name, decl_id); + } + } + pub fn next_span_start(&self) -> usize { self.permanent_state.next_span_start() + self.delta.file_contents.len() } @@ -380,6 +416,22 @@ impl<'a> StateWorkingSet<'a> { None } + pub fn find_module(&self, name: &[u8]) -> Option { + for scope in self.delta.scope.iter().rev() { + if let Some(block_id) = scope.modules.get(name) { + return Some(*block_id); + } + } + + for scope in self.permanent_state.scope.iter().rev() { + if let Some(block_id) = scope.modules.get(name) { + return Some(*block_id); + } + } + + None + } + // pub fn update_decl(&mut self, decl_id: usize, block: Option) { // let decl = self.get_decl_mut(decl_id); // decl.body = block;