mirror of
https://github.com/nushell/nushell
synced 2024-11-10 23:24:14 +00:00
Merge pull request #69 from kubouch/simple-module
Primitive module implementation
This commit is contained in:
commit
47421e9ca7
8 changed files with 372 additions and 3 deletions
|
@ -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<RefCell<EngineState>> {
|
||||
|
@ -46,6 +46,10 @@ pub fn create_default_context() -> Rc<RefCell<EngineState>> {
|
|||
|
||||
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));
|
||||
|
|
|
@ -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;
|
||||
|
|
34
crates/nu-command/src/module.rs
Normal file
34
crates/nu-command/src/module.rs
Normal file
|
@ -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<nu_protocol::Value, nu_protocol::ShellError> {
|
||||
Ok(Value::Nothing { span: call.head })
|
||||
}
|
||||
}
|
28
crates/nu-command/src/use_.rs
Normal file
28
crates/nu-command/src/use_.rs
Normal file
|
@ -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<nu_protocol::Value, nu_protocol::ShellError> {
|
||||
Ok(Value::Nothing { span: call.head })
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
|
|
|
@ -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<ParseError>) {
|
||||
// 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<u8>, 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 <name> {}".into(),
|
||||
span(spans),
|
||||
)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_use(
|
||||
working_set: &mut StateWorkingSet,
|
||||
spans: &[Span],
|
||||
) -> (Statement, Option<ParseError>) {
|
||||
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 <name>".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)
|
||||
|
|
|
@ -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<Signature>,
|
||||
pub stmts: Vec<Statement>,
|
||||
pub exports: Vec<(Vec<u8>, 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<u8>, 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![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ pub struct ScopeFrame {
|
|||
vars: HashMap<Vec<u8>, VarId>,
|
||||
decls: HashMap<Vec<u8>, DeclId>,
|
||||
aliases: HashMap<Vec<u8>, Vec<Span>>,
|
||||
modules: HashMap<Vec<u8>, 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<u8>, 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<BlockId> {
|
||||
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<BlockId>) {
|
||||
// let decl = self.get_decl_mut(decl_id);
|
||||
// decl.body = block;
|
||||
|
|
Loading…
Reference in a new issue