Restructure and streamline token expansion (#1123)

Restructure and streamline token expansion

The purpose of this commit is to streamline the token expansion code, by
removing aspects of the code that are no longer relevant, removing
pointless duplication, and eliminating the need to pass the same
arguments to `expand_syntax`.

The first big-picture change in this commit is that instead of a handful
of `expand_` functions, which take a TokensIterator and ExpandContext, a
smaller number of methods on the `TokensIterator` do the same job.

The second big-picture change in this commit is fully eliminating the
coloring traits, making coloring a responsibility of the base expansion
implementations. This also means that the coloring tracer is merged into
the expansion tracer, so you can follow a single expansion and see how
the expansion process produced colored tokens.

One side effect of this change is that the expander itself is marginally
more error-correcting. The error correction works by switching from
structured expansion to `BackoffColoringMode` when an unexpected token
is found, which guarantees that all spans of the source are colored, but
may not be the most optimal error recovery strategy.

That said, because `BackoffColoringMode` only extends as far as a
closing delimiter (`)`, `]`, `}`) or pipe (`|`), it does result in
fairly granular correction strategy.

The current code still produces an `Err` (plus a complete list of
colored shapes) from the parsing process if any errors are encountered,
but this could easily be addressed now that the underlying expansion is
error-correcting.

This commit also colors any spans that are syntax errors in red, and
causes the parser to include some additional information about what
tokens were expected at any given point where an error was encountered,
so that completions and hinting could be more robust in the future.

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
Co-authored-by: Andrés N. Robalino <andres@androbtech.com>
This commit is contained in:
Yehuda Katz
2020-01-21 17:45:03 -05:00
committed by Andrés N. Robalino
parent c8dd7838a8
commit 7efb31a4e4
81 changed files with 4145 additions and 4882 deletions

View File

@ -21,6 +21,7 @@ num-traits = "0.2.10"
serde = { version = "1.0.103", features = ["derive"] }
nom = "5.0.1"
nom_locate = "1.0.0"
getset = "0.0.9"
# implement conversions
subprocess = "0.1.18"

View File

@ -1,8 +1,11 @@
use ansi_term::Color;
use bigdecimal::BigDecimal;
use derive_new::new;
use getset::Getters;
use language_reporting::{Diagnostic, Label, Severity};
use nu_source::{b, DebugDocBuilder, PrettyDebug, Span, Spanned, SpannedItem, TracableContext};
use nu_source::{
b, DebugDocBuilder, HasFallibleSpan, PrettyDebug, Span, Spanned, SpannedItem, TracableContext,
};
use num_bigint::BigInt;
use num_traits::ToPrimitive;
use serde::{Deserialize, Serialize};
@ -12,16 +15,16 @@ use std::ops::Range;
/// A structured reason for a ParseError. Note that parsing in nu is more like macro expansion in
/// other languages, so the kinds of errors that can occur during parsing are more contextual than
/// you might expect.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum ParseErrorReason {
/// The parser encountered an EOF rather than what it was expecting
Eof { expected: &'static str, span: Span },
Eof { expected: String, span: Span },
/// The parser expected to see the end of a token stream (possibly the token
/// stream from inside a delimited token node), but found something else.
ExtraTokens { actual: Spanned<String> },
/// The parser encountered something other than what it was expecting
Mismatch {
expected: &'static str,
expected: String,
actual: Spanned<String>,
},
@ -37,16 +40,20 @@ pub enum ParseErrorReason {
}
/// A newtype for `ParseErrorReason`
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Eq, PartialEq, Getters)]
pub struct ParseError {
#[get = "pub"]
reason: ParseErrorReason,
}
impl ParseError {
/// Construct a [ParseErrorReason::Eof](ParseErrorReason::Eof)
pub fn unexpected_eof(expected: &'static str, span: Span) -> ParseError {
pub fn unexpected_eof(expected: impl Into<String>, span: Span) -> ParseError {
ParseError {
reason: ParseErrorReason::Eof { expected, span },
reason: ParseErrorReason::Eof {
expected: expected.into(),
span,
},
}
}
@ -62,12 +69,12 @@ impl ParseError {
}
/// Construct a [ParseErrorReason::Mismatch](ParseErrorReason::Mismatch)
pub fn mismatch(expected: &'static str, actual: Spanned<impl Into<String>>) -> ParseError {
pub fn mismatch(expected: impl Into<String>, actual: Spanned<impl Into<String>>) -> ParseError {
let Spanned { span, item } = actual;
ParseError {
reason: ParseErrorReason::Mismatch {
expected,
expected: expected.into(),
actual: item.into().spanned(span),
},
}
@ -728,6 +735,30 @@ impl ProximateShellError {
}
}
impl HasFallibleSpan for ShellError {
fn maybe_span(&self) -> Option<Span> {
self.error.maybe_span()
}
}
impl HasFallibleSpan for ProximateShellError {
fn maybe_span(&self) -> Option<Span> {
Some(match self {
ProximateShellError::SyntaxError { problem } => problem.span,
ProximateShellError::UnexpectedEof { span, .. } => *span,
ProximateShellError::TypeError { actual, .. } => actual.span,
ProximateShellError::MissingProperty { subpath, .. } => subpath.span,
ProximateShellError::InvalidIntegerIndex { subpath, .. } => subpath.span,
ProximateShellError::MissingValue { span, .. } => return *span,
ProximateShellError::ArgumentError { command, .. } => command.span,
ProximateShellError::RangeError { actual_kind, .. } => actual_kind.span,
ProximateShellError::Diagnostic(_) => return None,
ProximateShellError::CoerceError { left, right } => left.span.until(right.span),
ProximateShellError::UntaggedRuntimeError { .. } => return None,
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShellDiagnostic {
pub(crate) diagnostic: Diagnostic<Span>,