forked from extern/nushell
refactor to subcrates
This commit is contained in:
122
crates/nu-cli/src/default_context.rs
Normal file
122
crates/nu-cli/src/default_context.rs
Normal file
@ -0,0 +1,122 @@
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use nu_parser::{ParserState, ParserWorkingSet, Signature, SyntaxShape};
|
||||
|
||||
pub fn create_default_context() -> Rc<RefCell<ParserState>> {
|
||||
let parser_state = Rc::new(RefCell::new(ParserState::new()));
|
||||
let delta = {
|
||||
let parser_state = parser_state.borrow();
|
||||
let mut working_set = ParserWorkingSet::new(&*parser_state);
|
||||
|
||||
let sig =
|
||||
Signature::build("where").required("cond", SyntaxShape::RowCondition, "condition");
|
||||
working_set.add_decl(sig.into());
|
||||
|
||||
let sig = Signature::build("if")
|
||||
.required("cond", SyntaxShape::Expression, "condition")
|
||||
.required("then_block", SyntaxShape::Block, "then block")
|
||||
.optional(
|
||||
"else",
|
||||
SyntaxShape::Keyword(b"else".to_vec(), Box::new(SyntaxShape::Expression)),
|
||||
"optional else followed by else block",
|
||||
);
|
||||
working_set.add_decl(sig.into());
|
||||
|
||||
let sig = Signature::build("let")
|
||||
.required("var_name", SyntaxShape::VarWithOptType, "variable name")
|
||||
.required(
|
||||
"initial_value",
|
||||
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
|
||||
"equals sign followed by value",
|
||||
);
|
||||
working_set.add_decl(sig.into());
|
||||
|
||||
let sig = Signature::build("let-env")
|
||||
.required("var_name", SyntaxShape::String, "variable name")
|
||||
.required(
|
||||
"initial_value",
|
||||
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::String)),
|
||||
"equals sign followed by value",
|
||||
);
|
||||
working_set.add_decl(sig.into());
|
||||
|
||||
let sig = Signature::build("alias")
|
||||
.required("name", SyntaxShape::String, "name of the alias")
|
||||
.required(
|
||||
"initial_value",
|
||||
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
|
||||
"equals sign followed by value",
|
||||
);
|
||||
working_set.add_decl(sig.into());
|
||||
|
||||
let sig = Signature::build("sum").required(
|
||||
"arg",
|
||||
SyntaxShape::List(Box::new(SyntaxShape::Number)),
|
||||
"list of numbers",
|
||||
);
|
||||
working_set.add_decl(sig.into());
|
||||
|
||||
let sig = Signature::build("build-string").rest(SyntaxShape::String, "list of string");
|
||||
working_set.add_decl(sig.into());
|
||||
|
||||
let sig = Signature::build("def")
|
||||
.required("def_name", SyntaxShape::String, "definition name")
|
||||
.required("params", SyntaxShape::Signature, "parameters")
|
||||
.required("block", SyntaxShape::Block, "body of the definition");
|
||||
working_set.add_decl(sig.into());
|
||||
|
||||
let sig = Signature::build("for")
|
||||
.required(
|
||||
"var_name",
|
||||
SyntaxShape::Variable,
|
||||
"name of the looping variable",
|
||||
)
|
||||
.required(
|
||||
"range",
|
||||
SyntaxShape::Keyword(b"in".to_vec(), Box::new(SyntaxShape::Int)),
|
||||
"range of the loop",
|
||||
)
|
||||
.required("block", SyntaxShape::Block, "the block to run");
|
||||
working_set.add_decl(sig.into());
|
||||
|
||||
let sig =
|
||||
Signature::build("benchmark").required("block", SyntaxShape::Block, "the block to run");
|
||||
working_set.add_decl(sig.into());
|
||||
|
||||
// let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j'));
|
||||
// working_set.add_decl(sig.into());
|
||||
|
||||
// let sig = Signature::build("bar")
|
||||
// .named("--jazz", SyntaxShape::Int, "jazz!!", Some('j'))
|
||||
// .switch("--rock", "rock!!", Some('r'));
|
||||
// working_set.add_decl(sig.into());
|
||||
let sig = Signature::build("exit");
|
||||
working_set.add_decl(sig.into());
|
||||
let sig = Signature::build("vars");
|
||||
working_set.add_decl(sig.into());
|
||||
let sig = Signature::build("decls");
|
||||
working_set.add_decl(sig.into());
|
||||
let sig = Signature::build("blocks");
|
||||
working_set.add_decl(sig.into());
|
||||
let sig = Signature::build("stack");
|
||||
working_set.add_decl(sig.into());
|
||||
|
||||
let sig = Signature::build("add");
|
||||
working_set.add_decl(sig.into());
|
||||
let sig = Signature::build("add it");
|
||||
working_set.add_decl(sig.into());
|
||||
|
||||
let sig = Signature::build("add it together")
|
||||
.required("x", SyntaxShape::Int, "x value")
|
||||
.required("y", SyntaxShape::Int, "y value");
|
||||
working_set.add_decl(sig.into());
|
||||
|
||||
working_set.render()
|
||||
};
|
||||
|
||||
{
|
||||
ParserState::merge_delta(&mut *parser_state.borrow_mut(), delta);
|
||||
}
|
||||
|
||||
parser_state
|
||||
}
|
249
crates/nu-cli/src/errors.rs
Normal file
249
crates/nu-cli/src/errors.rs
Normal file
@ -0,0 +1,249 @@
|
||||
use core::ops::Range;
|
||||
|
||||
use codespan_reporting::diagnostic::{Diagnostic, Label};
|
||||
use codespan_reporting::term::termcolor::{ColorChoice, StandardStream};
|
||||
use nu_engine::ShellError;
|
||||
use nu_parser::{ParseError, ParserWorkingSet, Span};
|
||||
|
||||
fn convert_span_to_diag(
|
||||
working_set: &ParserWorkingSet,
|
||||
span: &Span,
|
||||
) -> Result<(usize, Range<usize>), Box<dyn std::error::Error>> {
|
||||
for (file_id, (_, start, end)) in working_set.files().enumerate() {
|
||||
if span.start >= *start && span.end <= *end {
|
||||
let new_start = span.start - start;
|
||||
let new_end = span.end - start;
|
||||
|
||||
return Ok((file_id, new_start..new_end));
|
||||
}
|
||||
}
|
||||
|
||||
panic!("internal error: can't find span in parser state")
|
||||
}
|
||||
|
||||
pub fn report_parsing_error(
|
||||
working_set: &ParserWorkingSet,
|
||||
error: &ParseError,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let writer = StandardStream::stderr(ColorChoice::Always);
|
||||
let config = codespan_reporting::term::Config::default();
|
||||
|
||||
let diagnostic =
|
||||
match error {
|
||||
ParseError::Mismatch(missing, span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Type mismatch during operation")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message(format!("expected {}", missing))])
|
||||
}
|
||||
ParseError::ExtraTokens(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Extra tokens in code")
|
||||
.with_labels(vec![
|
||||
Label::primary(diag_file_id, diag_range).with_message("extra tokens")
|
||||
])
|
||||
}
|
||||
ParseError::ExtraPositional(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Extra positional argument")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message("extra positional argument")])
|
||||
}
|
||||
ParseError::UnexpectedEof(s, span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Unexpected end of code")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message(format!("expected {}", s))])
|
||||
}
|
||||
ParseError::Unclosed(delim, span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Unclosed delimiter")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message(format!("unclosed {}", delim))])
|
||||
}
|
||||
ParseError::UnknownStatement(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Unknown statement")
|
||||
.with_labels(vec![
|
||||
Label::primary(diag_file_id, diag_range).with_message("unknown statement")
|
||||
])
|
||||
}
|
||||
ParseError::MultipleRestParams(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Multiple rest params")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message("multiple rest params")])
|
||||
}
|
||||
ParseError::VariableNotFound(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Variable not found")
|
||||
.with_labels(vec![
|
||||
Label::primary(diag_file_id, diag_range).with_message("variable not found")
|
||||
])
|
||||
}
|
||||
ParseError::UnknownCommand(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Unknown command")
|
||||
.with_labels(vec![
|
||||
Label::primary(diag_file_id, diag_range).with_message("unknown command")
|
||||
])
|
||||
}
|
||||
ParseError::UnknownFlag(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Unknown flag")
|
||||
.with_labels(vec![
|
||||
Label::primary(diag_file_id, diag_range).with_message("unknown flag")
|
||||
])
|
||||
}
|
||||
ParseError::UnknownType(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Unknown type")
|
||||
.with_labels(vec![
|
||||
Label::primary(diag_file_id, diag_range).with_message("unknown type")
|
||||
])
|
||||
}
|
||||
ParseError::MissingFlagParam(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Missing flag param")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message("flag missing parameter")])
|
||||
}
|
||||
ParseError::ShortFlagBatchCantTakeArg(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Batches of short flags can't take arguments")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message("short flag batches can't take args")])
|
||||
}
|
||||
ParseError::MissingPositional(name, span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Missing required positional arg")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message(format!("missing {}", name))])
|
||||
}
|
||||
ParseError::MissingType(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Missing type")
|
||||
.with_labels(vec![
|
||||
Label::primary(diag_file_id, diag_range).with_message("expected type")
|
||||
])
|
||||
}
|
||||
ParseError::TypeMismatch(ty, span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Type mismatch")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message(format!("expected {:?}", ty))])
|
||||
}
|
||||
ParseError::MissingRequiredFlag(name, span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Missing required flag")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message(format!("missing required flag {}", name))])
|
||||
}
|
||||
ParseError::IncompleteMathExpression(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Incomplete math expresssion")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message("incomplete math expression")])
|
||||
}
|
||||
ParseError::UnknownState(name, span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Unknown state")
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message(format!("unknown state {}", name))])
|
||||
}
|
||||
ParseError::NonUtf8(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Non-UTF8 code")
|
||||
.with_labels(vec![
|
||||
Label::primary(diag_file_id, diag_range).with_message("non-UTF8 code")
|
||||
])
|
||||
}
|
||||
};
|
||||
|
||||
// println!("DIAG");
|
||||
// println!("{:?}", diagnostic);
|
||||
codespan_reporting::term::emit(&mut writer.lock(), &config, working_set, &diagnostic)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn report_shell_error(
|
||||
working_set: &ParserWorkingSet,
|
||||
error: &ShellError,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let writer = StandardStream::stderr(ColorChoice::Always);
|
||||
let config = codespan_reporting::term::Config::default();
|
||||
|
||||
let diagnostic = match error {
|
||||
ShellError::OperatorMismatch {
|
||||
op_span,
|
||||
lhs_ty,
|
||||
lhs_span,
|
||||
rhs_ty,
|
||||
rhs_span,
|
||||
} => {
|
||||
let (lhs_file_id, lhs_range) = convert_span_to_diag(working_set, lhs_span)?;
|
||||
let (rhs_file_id, rhs_range) = convert_span_to_diag(working_set, rhs_span)?;
|
||||
let (op_file_id, op_range) = convert_span_to_diag(working_set, op_span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Type mismatch during operation")
|
||||
.with_labels(vec![
|
||||
Label::primary(op_file_id, op_range).with_message("type mismatch for operator"),
|
||||
Label::secondary(lhs_file_id, lhs_range).with_message(lhs_ty.to_string()),
|
||||
Label::secondary(rhs_file_id, rhs_range).with_message(rhs_ty.to_string()),
|
||||
])
|
||||
}
|
||||
ShellError::Unsupported(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Unsupported operation")
|
||||
.with_labels(vec![
|
||||
Label::primary(diag_file_id, diag_range).with_message("unsupported operation")
|
||||
])
|
||||
}
|
||||
ShellError::InternalError(s) => {
|
||||
Diagnostic::error().with_message(format!("Internal error: {}", s))
|
||||
}
|
||||
ShellError::VariableNotFound(span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message("Variable not found")
|
||||
.with_labels(vec![
|
||||
Label::primary(diag_file_id, diag_range).with_message("variable not found")
|
||||
])
|
||||
}
|
||||
ShellError::CantConvert(s, span) => {
|
||||
let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
|
||||
Diagnostic::error()
|
||||
.with_message(format!("Can't convert to {}", s))
|
||||
.with_labels(vec![Label::primary(diag_file_id, diag_range)
|
||||
.with_message(format!("can't convert to {}", s))])
|
||||
}
|
||||
};
|
||||
|
||||
// println!("DIAG");
|
||||
// println!("{:?}", diagnostic);
|
||||
codespan_reporting::term::emit(&mut writer.lock(), &config, working_set, &diagnostic)?;
|
||||
|
||||
Ok(())
|
||||
}
|
7
crates/nu-cli/src/lib.rs
Normal file
7
crates/nu-cli/src/lib.rs
Normal file
@ -0,0 +1,7 @@
|
||||
mod default_context;
|
||||
mod errors;
|
||||
mod syntax_highlight;
|
||||
|
||||
pub use default_context::create_default_context;
|
||||
pub use errors::{report_parsing_error, report_shell_error};
|
||||
pub use syntax_highlight::NuHighlighter;
|
92
crates/nu-cli/src/syntax_highlight.rs
Normal file
92
crates/nu-cli/src/syntax_highlight.rs
Normal file
@ -0,0 +1,92 @@
|
||||
use nu_ansi_term::Style;
|
||||
use nu_parser::{FlatShape, ParserState, ParserWorkingSet};
|
||||
use reedline::{Highlighter, StyledText};
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
pub struct NuHighlighter {
|
||||
pub parser_state: Rc<RefCell<ParserState>>,
|
||||
}
|
||||
|
||||
impl Highlighter for NuHighlighter {
|
||||
fn highlight(&self, line: &str) -> StyledText {
|
||||
let (shapes, global_span_offset) = {
|
||||
let parser_state = self.parser_state.borrow();
|
||||
let mut working_set = ParserWorkingSet::new(&*parser_state);
|
||||
let (block, _) = working_set.parse_source(line.as_bytes(), false);
|
||||
|
||||
let shapes = working_set.flatten_block(&block);
|
||||
(shapes, parser_state.next_span_start())
|
||||
};
|
||||
|
||||
let mut output = StyledText::default();
|
||||
let mut last_seen_span = global_span_offset;
|
||||
|
||||
for shape in &shapes {
|
||||
if shape.0.end <= last_seen_span {
|
||||
// We've already output something for this span
|
||||
// so just skip this one
|
||||
continue;
|
||||
}
|
||||
if shape.0.start > last_seen_span {
|
||||
let gap = line
|
||||
[(last_seen_span - global_span_offset)..(shape.0.start - global_span_offset)]
|
||||
.to_string();
|
||||
output.push((Style::new(), gap));
|
||||
}
|
||||
|
||||
let next_token = line
|
||||
[(shape.0.start - global_span_offset)..(shape.0.end - global_span_offset)]
|
||||
.to_string();
|
||||
match shape.1 {
|
||||
FlatShape::External => output.push((Style::new().bold(), next_token)),
|
||||
FlatShape::Garbage => output.push((
|
||||
Style::new()
|
||||
.fg(nu_ansi_term::Color::White)
|
||||
.on(nu_ansi_term::Color::Red)
|
||||
.bold(),
|
||||
next_token,
|
||||
)),
|
||||
FlatShape::InternalCall => output.push((
|
||||
Style::new().fg(nu_ansi_term::Color::LightBlue).bold(),
|
||||
next_token,
|
||||
)),
|
||||
FlatShape::Int => {
|
||||
output.push((Style::new().fg(nu_ansi_term::Color::Green), next_token))
|
||||
}
|
||||
FlatShape::Float => {
|
||||
output.push((Style::new().fg(nu_ansi_term::Color::Green), next_token))
|
||||
}
|
||||
FlatShape::Bool => {
|
||||
output.push((Style::new().fg(nu_ansi_term::Color::LightCyan), next_token))
|
||||
}
|
||||
FlatShape::Literal => {
|
||||
output.push((Style::new().fg(nu_ansi_term::Color::Blue), next_token))
|
||||
}
|
||||
FlatShape::Operator => output.push((
|
||||
Style::new().fg(nu_ansi_term::Color::LightPurple).bold(),
|
||||
next_token,
|
||||
)),
|
||||
FlatShape::Signature => output.push((
|
||||
Style::new().fg(nu_ansi_term::Color::Green).bold(),
|
||||
next_token,
|
||||
)),
|
||||
FlatShape::String => output.push((
|
||||
Style::new().fg(nu_ansi_term::Color::Yellow).bold(),
|
||||
next_token,
|
||||
)),
|
||||
FlatShape::Variable => output.push((
|
||||
Style::new().fg(nu_ansi_term::Color::Blue).bold(),
|
||||
next_token,
|
||||
)),
|
||||
}
|
||||
last_seen_span = shape.0.end;
|
||||
}
|
||||
|
||||
let remainder = line[(last_seen_span - global_span_offset)..].to_string();
|
||||
if !remainder.is_empty() {
|
||||
output.push((Style::new(), remainder));
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user