diff --git a/crates/nu-cli/src/errors.rs b/crates/nu-cli/src/errors.rs index 77ddfd8536..b5470ead13 100644 --- a/crates/nu-cli/src/errors.rs +++ b/crates/nu-cli/src/errors.rs @@ -208,8 +208,9 @@ pub fn report_parsing_error( 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))]) + .with_labels(vec![ + Label::primary(diag_file_id, diag_range).with_message(name.to_string()) + ]) } ParseError::NonUtf8(span) => { let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index d1dada0fb9..1c44c6bbad 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -26,6 +26,10 @@ fn garbage(span: Span) -> Expression { Expression::garbage(span) } +fn garbage_statement(spans: &[Span]) -> Statement { + Statement::Pipeline(Pipeline::from_vec(vec![garbage(span(spans))])) +} + fn is_identifier_byte(b: u8) -> bool { b != b'.' && b != b'[' && b != b'(' && b != b'{' } @@ -59,6 +63,22 @@ fn check_call(command: Span, sig: &Signature, call: &Call) -> Option } } +fn check_name(working_set: &mut StateWorkingSet, spans: &[Span]) -> Option { + if spans[1..].len() < 2 { + Some(ParseError::UnknownState( + "missing definition name".into(), + span(spans), + )) + } else if working_set.get_span_contents(spans[2]) != b"=" { + Some(ParseError::UnknownState( + "missing equal sign in definition".into(), + span(spans), + )) + } else { + None + } +} + pub fn parse_external_call( _working_set: &mut StateWorkingSet, spans: &[Span], @@ -580,6 +600,23 @@ pub fn parse_call( name = new_name; pos += 1; } + + // Before the internal parsing we check if there is no let or alias declarations + // that are missing their name, e.g.: let = 1 or alias = 2 + if spans.len() > 1 { + let test_equal = working_set.get_span_contents(spans[1]); + + if test_equal == [b'='] { + return ( + garbage(Span::new(0, 0)), + Some(ParseError::UnknownState( + "Incomplete statement".into(), + span(spans), + )), + ); + } + } + // parse internal command let (call, _, err) = parse_internal_call(working_set, span(&spans[0..pos]), &spans[pos..], decl_id); @@ -592,6 +629,14 @@ pub fn parse_call( err, ) } else { + // We might be parsing left-unbounded range ("..10") + let bytes = working_set.get_span_contents(spans[0]); + if let (Some(b'.'), Some(b'.')) = (bytes.get(0), bytes.get(1)) { + let (range_expr, range_err) = parse_range(working_set, spans[0]); + if range_err.is_none() { + return (range_expr, range_err); + } + } parse_external_call(working_set, spans) } } @@ -2391,6 +2436,13 @@ pub fn parse_def( error = error.or(err); working_set.exit_scope(); + if error.is_some() { + return ( + Statement::Pipeline(Pipeline::from_vec(vec![garbage(span(spans))])), + error, + ); + } + let name = name_expr.as_string(); let signature = sig.as_signature(); @@ -2430,23 +2482,15 @@ pub fn parse_def( ) } _ => ( - Statement::Pipeline(Pipeline::from_vec(vec![Expression { - expr: Expr::Garbage, - span: span(spans), - ty: Type::Unknown, - }])), + Statement::Pipeline(Pipeline::from_vec(vec![garbage(span(spans))])), error, ), } } else { ( - Statement::Pipeline(Pipeline::from_vec(vec![Expression { - expr: Expr::Garbage, - span: span(spans), - ty: Type::Unknown, - }])), + garbage_statement(spans), Some(ParseError::UnknownState( - "internal error: definition unparseable".into(), + "definition unparseable. Expected structure: def [] {}".into(), span(spans), )), ) @@ -2460,6 +2504,13 @@ pub fn parse_alias( let name = working_set.get_span_contents(spans[0]); if name == b"alias" { + if let Some(err) = check_name(working_set, spans) { + return ( + Statement::Pipeline(Pipeline::from_vec(vec![garbage(span(spans))])), + Some(err), + ); + } + if let Some(decl_id) = working_set.find_decl(b"alias") { let (call, call_span, _) = parse_internal_call(working_set, spans[0], &spans[1..], decl_id); @@ -2496,13 +2547,9 @@ pub fn parse_alias( } ( - Statement::Pipeline(Pipeline::from_vec(vec![Expression { - expr: Expr::Garbage, - span: span(spans), - ty: Type::Unknown, - }])), + garbage_statement(spans), Some(ParseError::UnknownState( - "internal error: let statement unparseable".into(), + "internal error: alias statement unparseable".into(), span(spans), )), ) @@ -2515,6 +2562,13 @@ pub fn parse_let( let name = working_set.get_span_contents(spans[0]); if name == b"let" { + if let Some(err) = check_name(working_set, spans) { + return ( + Statement::Pipeline(Pipeline::from_vec(vec![garbage(span(spans))])), + Some(err), + ); + } + if let Some(decl_id) = working_set.find_decl(b"let") { let (call, call_span, err) = parse_internal_call(working_set, spans[0], &spans[1..], decl_id); @@ -2540,11 +2594,7 @@ pub fn parse_let( } } ( - Statement::Pipeline(Pipeline::from_vec(vec![Expression { - expr: Expr::Garbage, - span: span(spans), - ty: Type::Unknown, - }])), + garbage_statement(spans), Some(ParseError::UnknownState( "internal error: let statement unparseable".into(), span(spans), @@ -2574,13 +2624,10 @@ pub fn parse_block( lite_block: &LiteBlock, scoped: bool, ) -> (Block, Option) { - let mut error = None; if scoped { working_set.enter_scope(); } - let mut block = Block::new(); - // Pre-declare any definition so that definitions // that share the same block can see each other for pipeline in &lite_block.block { @@ -2589,25 +2636,41 @@ pub fn parse_block( } } - for pipeline in &lite_block.block { - if pipeline.commands.len() > 1 { - let mut output = vec![]; - for command in &pipeline.commands { - let (expr, err) = parse_expression(working_set, &command.parts); - error = error.or(err); + let mut error = None; - output.push(expr); + let block: Block = lite_block + .block + .iter() + .map(|pipeline| { + if pipeline.commands.len() > 1 { + let output = pipeline + .commands + .iter() + .map(|command| { + let (expr, err) = parse_expression(working_set, &command.parts); + + if error.is_none() { + error = err; + } + + expr + }) + .collect::>(); + + Statement::Pipeline(Pipeline { + expressions: output, + }) + } else { + let (stmt, err) = parse_statement(working_set, &pipeline.commands[0].parts); + + if error.is_none() { + error = err; + } + + stmt } - block.stmts.push(Statement::Pipeline(Pipeline { - expressions: output, - })); - } else { - let (stmt, err) = parse_statement(working_set, &pipeline.commands[0].parts); - error = error.or(err); - - block.stmts.push(stmt); - } - } + }) + .into(); if scoped { working_set.exit_scope(); diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index 2015f356c0..cdc6f5a8de 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -371,6 +371,38 @@ mod range { } } + #[test] + fn parse_left_unbounded_range() { + let engine_state = EngineState::new(); + let mut working_set = StateWorkingSet::new(&engine_state); + + let (block, err) = parse(&mut working_set, None, b"..10", true); + + assert!(err.is_none()); + assert!(block.len() == 1); + match &block[0] { + Statement::Pipeline(Pipeline { expressions }) => { + assert!(expressions.len() == 1); + assert!(matches!( + expressions[0], + Expression { + expr: Expr::Range( + None, + None, + Some(_), + RangeOperator { + inclusion: RangeInclusion::Inclusive, + .. + } + ), + .. + } + )) + } + _ => panic!("No match"), + } + } + #[test] fn parse_negative_range() { let engine_state = EngineState::new(); @@ -403,6 +435,38 @@ mod range { } } + #[test] + fn parse_float_range() { + let engine_state = EngineState::new(); + let mut working_set = StateWorkingSet::new(&engine_state); + + let (block, err) = parse(&mut working_set, None, b"2.0..4.0..10.0", true); + + assert!(err.is_none()); + assert!(block.len() == 1); + match &block[0] { + Statement::Pipeline(Pipeline { expressions }) => { + assert!(expressions.len() == 1); + assert!(matches!( + expressions[0], + Expression { + expr: Expr::Range( + Some(_), + Some(_), + Some(_), + RangeOperator { + inclusion: RangeInclusion::Inclusive, + .. + } + ), + .. + } + )) + } + _ => panic!("No match"), + } + } + #[test] fn bad_parse_does_crash() { let engine_state = EngineState::new(); diff --git a/crates/nu-protocol/src/ast/block.rs b/crates/nu-protocol/src/ast/block.rs index d36e43d8ec..e5db26da36 100644 --- a/crates/nu-protocol/src/ast/block.rs +++ b/crates/nu-protocol/src/ast/block.rs @@ -48,3 +48,15 @@ impl Block { } } } + +impl From for Block +where + T: Iterator, +{ + fn from(stmts: T) -> Self { + Self { + signature: Box::new(Signature::new("")), + stmts: stmts.collect(), + } + } +} diff --git a/crates/nu-protocol/src/value/range.rs b/crates/nu-protocol/src/value/range.rs index ff138e4af6..80eb568827 100644 --- a/crates/nu-protocol/src/value/range.rs +++ b/crates/nu-protocol/src/value/range.rs @@ -1,3 +1,5 @@ +use std::cmp::Ordering; + use crate::{ ast::{RangeInclusion, RangeOperator}, *, @@ -150,31 +152,52 @@ impl RangeIterator { } } +// Compare two floating point numbers. The decision interval for equality is dynamically scaled +// as the value being compared increases in magnitude. +fn compare_floats(val: f64, other: f64) -> Option { + let prec = f64::EPSILON.max(val.abs() * f64::EPSILON); + + if (other - val).abs() < prec { + return Some(Ordering::Equal); + } + + val.partial_cmp(&other) +} + impl Iterator for RangeIterator { type Item = Value; fn next(&mut self) -> Option { - use std::cmp::Ordering; if self.done { return None; } let ordering = if matches!(self.end, Value::Nothing { .. }) { - Ordering::Less + Some(Ordering::Less) } else { match (&self.curr, &self.end) { - (Value::Int { val: curr, .. }, Value::Int { val: end, .. }) => curr.cmp(end), - // (Value::Float { val: curr, .. }, Value::Float { val: end, .. }) => curr.cmp(end), - // (Value::Float { val: curr, .. }, Value::Int { val: end, .. }) => curr.cmp(end), - // (Value::Int { val: curr, .. }, Value::Float { val: end, .. }) => curr.cmp(end), - _ => { - self.done = true; - return Some(Value::Error { - error: ShellError::CannotCreateRange(self.span), - }); + (Value::Int { val: curr, .. }, Value::Int { val: end, .. }) => Some(curr.cmp(end)), + (Value::Float { val: curr, .. }, Value::Float { val: end, .. }) => { + compare_floats(*curr, *end) } + (Value::Float { val: curr, .. }, Value::Int { val: end, .. }) => { + compare_floats(*curr, *end as f64) + } + (Value::Int { val: curr, .. }, Value::Float { val: end, .. }) => { + compare_floats(*curr as f64, *end) + } + _ => None, } }; + let ordering = if let Some(ord) = ordering { + ord + } else { + self.done = true; + return Some(Value::Error { + error: ShellError::CannotCreateRange(self.span), + }); + }; + let desired_ordering = if self.moves_up { Ordering::Less } else {