mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 09:55:42 +02:00
Infer types from regular delimited plain text unstructured files. (#1494)
* Infer types from regular delimited plain text unstructured files. * Nothing resolves to an empty string.
This commit is contained in:
committed by
GitHub
parent
d8c4565413
commit
b36d21e76f
@ -1,6 +1,6 @@
|
||||
use crate::hir;
|
||||
use crate::hir::syntax_shape::{
|
||||
expand_atom, expand_syntax, BareShape, ExpandContext, ExpandSyntax, ExpansionRule,
|
||||
ExpandSyntax, expand_atom, expand_syntax, BareShape, ExpandContext, ExpandSyntax, ExpansionRule,
|
||||
UnspannedAtomicToken, WhitespaceShape,
|
||||
};
|
||||
use crate::hir::tokens_iterator::TokensIterator;
|
||||
|
@ -477,18 +477,6 @@ impl ExpandSyntax for MemberShape {
|
||||
return Ok(Member::Bare(bare.span()));
|
||||
}
|
||||
|
||||
/* KATZ */
|
||||
/* let number = NumberShape.test(token_nodes, context);
|
||||
|
||||
if let Some(peeked) = number {
|
||||
let node = peeked.not_eof("column")?.commit();
|
||||
let (n, span) = node.as_number().ok_or_else(|| {
|
||||
ParseError::internal_error("can't convert node to number".spanned(node.span()))
|
||||
})?;
|
||||
|
||||
return Ok(Member::Number(n, span))
|
||||
}*/
|
||||
|
||||
let string = token_nodes.expand_syntax(StringShape);
|
||||
|
||||
if let Ok(syntax) = string {
|
||||
|
@ -3,9 +3,6 @@ pub(crate) mod into_shapes;
|
||||
pub(crate) mod pattern;
|
||||
pub(crate) mod state;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use self::debug::ExpandTracer;
|
||||
use self::into_shapes::IntoShapes;
|
||||
use self::state::{Peeked, TokensIteratorState};
|
||||
@ -510,7 +507,7 @@ impl<'content> TokensIterator<'content> {
|
||||
/// The purpose of `expand_infallible` is to clearly mark the infallible path through
|
||||
/// and entire list of tokens that produces a fully colored version of the source.
|
||||
///
|
||||
/// If the `ExpandSyntax` can poroduce a `Result`, make sure to use `expand_syntax`,
|
||||
/// If the `ExpandSyntax` can produce a `Result`, make sure to use `expand_syntax`,
|
||||
/// which will correctly show the error in the trace.
|
||||
pub fn expand_infallible<U>(&mut self, shape: impl ExpandSyntax<Output = U>) -> U
|
||||
where
|
||||
@ -536,7 +533,7 @@ impl<'content> TokensIterator<'content> {
|
||||
})
|
||||
}
|
||||
|
||||
fn expand<U>(&mut self, shape: impl ExpandSyntax<Output = U>) -> (U, usize)
|
||||
pub fn expand<U>(&mut self, shape: impl ExpandSyntax<Output = U>) -> (U, usize)
|
||||
where
|
||||
U: std::fmt::Debug + Clone + 'static,
|
||||
{
|
||||
|
@ -1,46 +0,0 @@
|
||||
use crate::hir::{syntax_shape::ExpandContext, syntax_shape::SignatureRegistry, TokensIterator};
|
||||
use crate::parse::token_tree_builder::TokenTreeBuilder as b;
|
||||
use nu_protocol::Signature;
|
||||
use nu_source::{Span, Text};
|
||||
|
||||
use derive_new::new;
|
||||
|
||||
#[derive(Debug, Clone, new)]
|
||||
struct TestRegistry {
|
||||
#[new(default)]
|
||||
signatures: indexmap::IndexMap<String, Signature>,
|
||||
}
|
||||
|
||||
impl TestRegistry {}
|
||||
|
||||
impl SignatureRegistry for TestRegistry {
|
||||
fn has(&self, name: &str) -> bool {
|
||||
self.signatures.contains_key(name)
|
||||
}
|
||||
fn get(&self, name: &str) -> Option<Signature> {
|
||||
self.signatures.get(name).cloned()
|
||||
}
|
||||
fn clone_box(&self) -> Box<dyn SignatureRegistry> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supplies_tokens() {
|
||||
let token = b::it_var();
|
||||
|
||||
let (tokens, source) = b::build(token);
|
||||
|
||||
let tokens = vec![tokens];
|
||||
let source = Text::from(&source);
|
||||
|
||||
let mut iterator = TokensIterator::new(
|
||||
&tokens,
|
||||
ExpandContext::new(Box::new(TestRegistry::new()), &source, None),
|
||||
Span::unknown(),
|
||||
);
|
||||
|
||||
let token = iterator.next().expect("Token expected.");
|
||||
|
||||
token.expect_var();
|
||||
}
|
@ -6,6 +6,9 @@ pub mod hir;
|
||||
pub mod parse;
|
||||
pub mod parse_command;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test_support;
|
||||
|
||||
pub use crate::commands::classified::{
|
||||
external::ExternalCommand, internal::InternalCommand, ClassifiedCommand, ClassifiedPipeline,
|
||||
};
|
||||
@ -20,6 +23,11 @@ pub use crate::parse::parser::{module, pipeline};
|
||||
pub use crate::parse::token_tree::{Delimiter, SpannedToken, Token};
|
||||
pub use crate::parse::token_tree_builder::TokenTreeBuilder;
|
||||
|
||||
pub mod utils {
|
||||
pub use crate::parse::util::parse_line_with_separator;
|
||||
pub use crate::parse::util::LineSeparatedShape;
|
||||
}
|
||||
|
||||
use log::log_enabled;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{errln, outln};
|
||||
|
@ -7,3 +7,49 @@ macro_rules! return_ok {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
macro_rules! equal_tokens {
|
||||
($source:tt -> $tokens:expr) => {
|
||||
let result = apply(pipeline, "pipeline", $source);
|
||||
let (expected_tree, expected_source) = TokenTreeBuilder::build($tokens);
|
||||
|
||||
if result != expected_tree {
|
||||
let debug_result = format!("{}", result.debug($source));
|
||||
let debug_expected = format!("{}", expected_tree.debug(&expected_source));
|
||||
|
||||
if debug_result == debug_expected {
|
||||
assert_eq!(
|
||||
result, expected_tree,
|
||||
"NOTE: actual and expected had equivalent debug serializations, source={:?}, debug_expected={:?}",
|
||||
$source,
|
||||
debug_expected
|
||||
)
|
||||
} else {
|
||||
assert_eq!(debug_result, debug_expected)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(<$parser:tt> $source:tt -> $tokens:expr) => {
|
||||
let result = apply($parser, stringify!($parser), $source);
|
||||
|
||||
let (expected_tree, expected_source) = TokenTreeBuilder::build($tokens);
|
||||
|
||||
if result != expected_tree {
|
||||
let debug_result = format!("{}", result.debug($source));
|
||||
let debug_expected = format!("{}", expected_tree.debug(&expected_source));
|
||||
|
||||
if debug_result == debug_expected {
|
||||
assert_eq!(
|
||||
result, expected_tree,
|
||||
"NOTE: actual and expected had equivalent debug serializations, source={:?}, debug_expected={:?}",
|
||||
$source,
|
||||
debug_expected
|
||||
)
|
||||
} else {
|
||||
assert_eq!(debug_result, debug_expected)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
#![allow(unused)]
|
||||
|
||||
use crate::parse::{
|
||||
call_node::*, flag::*, number::*, operator::*, pipeline::*, token_tree::*,
|
||||
token_tree_builder::*, unit::*,
|
||||
@ -318,6 +317,7 @@ pub fn dq_string(input: NomSpan) -> IResult<NomSpan, SpannedToken> {
|
||||
let (input, _) = char('"')(input)?;
|
||||
let start1 = input.offset;
|
||||
let (input, _) = many0(none_of("\""))(input)?;
|
||||
|
||||
let end1 = input.offset;
|
||||
let (input, _) = char('"')(input)?;
|
||||
let end = input.offset;
|
||||
@ -939,7 +939,7 @@ pub fn tight_node(input: NomSpan) -> IResult<NomSpan, Vec<SpannedToken>> {
|
||||
))(input)
|
||||
}
|
||||
|
||||
fn to_list(
|
||||
pub fn to_list(
|
||||
parser: impl Fn(NomSpan) -> IResult<NomSpan, SpannedToken>,
|
||||
) -> impl Fn(NomSpan) -> IResult<NomSpan, Vec<SpannedToken>> {
|
||||
move |input| {
|
||||
@ -1017,7 +1017,7 @@ fn parse_int<T>(frag: &str, neg: Option<T>) -> i64 {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_boundary(c: Option<char>) -> bool {
|
||||
pub fn is_boundary(c: Option<char>) -> bool {
|
||||
match c {
|
||||
None => true,
|
||||
Some(')') | Some(']') | Some('}') | Some('(') => true,
|
||||
@ -1140,59 +1140,13 @@ fn is_member_start(c: char) -> bool {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::parse::token_tree_builder::TokenTreeBuilder as b;
|
||||
use crate::parse::token_tree_builder::{CurriedToken, TokenTreeBuilder};
|
||||
use crate::parse::parser::{module, nodes, pipeline};
|
||||
use crate::parse::token_tree_builder::TokenTreeBuilder::{self, self as b};
|
||||
use crate::test_support::apply;
|
||||
use nu_source::PrettyDebugWithSource;
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
pub type CurriedNode<T> = Box<dyn FnOnce(&mut TokenTreeBuilder) -> T + 'static>;
|
||||
|
||||
macro_rules! equal_tokens {
|
||||
($source:tt -> $tokens:expr) => {
|
||||
let result = apply(pipeline, "pipeline", $source);
|
||||
let (expected_tree, expected_source) = TokenTreeBuilder::build($tokens);
|
||||
|
||||
if result != expected_tree {
|
||||
let debug_result = format!("{}", result.debug($source));
|
||||
let debug_expected = format!("{}", expected_tree.debug(&expected_source));
|
||||
|
||||
if debug_result == debug_expected {
|
||||
assert_eq!(
|
||||
result, expected_tree,
|
||||
"NOTE: actual and expected had equivalent debug serializations, source={:?}, debug_expected={:?}",
|
||||
$source,
|
||||
debug_expected
|
||||
)
|
||||
} else {
|
||||
assert_eq!(debug_result, debug_expected)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(<$parser:tt> $source:tt -> $tokens:expr) => {
|
||||
let result = apply($parser, stringify!($parser), $source);
|
||||
|
||||
let (expected_tree, expected_source) = TokenTreeBuilder::build($tokens);
|
||||
|
||||
if result != expected_tree {
|
||||
let debug_result = format!("{}", result.debug($source));
|
||||
let debug_expected = format!("{}", expected_tree.debug(&expected_source));
|
||||
|
||||
if debug_result == debug_expected {
|
||||
assert_eq!(
|
||||
result, expected_tree,
|
||||
"NOTE: actual and expected had equivalent debug serializations, source={:?}, debug_expected={:?}",
|
||||
$source,
|
||||
debug_expected
|
||||
)
|
||||
} else {
|
||||
assert_eq!(debug_result, debug_expected)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_integer() {
|
||||
equal_tokens! {
|
||||
@ -1339,7 +1293,7 @@ mod tests {
|
||||
fn test_flag() {
|
||||
equal_tokens! {
|
||||
<nodes>
|
||||
"--amigos" -> b::token_list(vec![b::flag("arepas")])
|
||||
"--amigos" -> b::token_list(vec![b::flag("amigos")])
|
||||
}
|
||||
|
||||
equal_tokens! {
|
||||
@ -1721,119 +1675,4 @@ mod tests {
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_smoke_pipeline() {
|
||||
// let _ = pretty_env_logger::try_init();
|
||||
|
||||
// assert_eq!(
|
||||
// apply(
|
||||
// pipeline,
|
||||
// "pipeline",
|
||||
// r#"git branch --merged | split-row "`n" | where $it != "* master""#
|
||||
// ),
|
||||
// build_token(b::pipeline(vec![
|
||||
// (
|
||||
// None,
|
||||
// b::call(
|
||||
// b::bare("git"),
|
||||
// vec![b::sp(), b::bare("branch"), b::sp(), b::flag("merged")]
|
||||
// ),
|
||||
// Some(" ")
|
||||
// ),
|
||||
// (
|
||||
// Some(" "),
|
||||
// b::call(b::bare("split-row"), vec![b::sp(), b::string("`n")]),
|
||||
// Some(" ")
|
||||
// ),
|
||||
// (
|
||||
// Some(" "),
|
||||
// b::call(
|
||||
// b::bare("where"),
|
||||
// vec![
|
||||
// b::sp(),
|
||||
// b::it_var(),
|
||||
// b::sp(),
|
||||
// b::op("!="),
|
||||
// b::sp(),
|
||||
// b::string("* master")
|
||||
// ]
|
||||
// ),
|
||||
// None
|
||||
// )
|
||||
// ]))
|
||||
// );
|
||||
|
||||
// assert_eq!(
|
||||
// apply(pipeline, "pipeline", "ls | where { $it.size > 100 }"),
|
||||
// build_token(b::pipeline(vec![
|
||||
// (None, b::call(b::bare("ls"), vec![]), Some(" ")),
|
||||
// (
|
||||
// Some(" "),
|
||||
// b::call(
|
||||
// b::bare("where"),
|
||||
// vec![
|
||||
// b::sp(),
|
||||
// b::braced(vec![
|
||||
// b::path(b::it_var(), vec![b::member("size")]),
|
||||
// b::sp(),
|
||||
// b::op(">"),
|
||||
// b::sp(),
|
||||
// b::int(100)
|
||||
// ])
|
||||
// ]
|
||||
// ),
|
||||
// None
|
||||
// )
|
||||
// ]))
|
||||
// )
|
||||
// }
|
||||
|
||||
fn apply(
|
||||
f: impl Fn(
|
||||
NomSpan,
|
||||
)
|
||||
-> Result<(NomSpan, SpannedToken), nom::Err<(NomSpan, nom::error::ErrorKind)>>,
|
||||
desc: &str,
|
||||
string: &str,
|
||||
) -> SpannedToken {
|
||||
let result = f(nom_input(string));
|
||||
|
||||
match result {
|
||||
Ok(value) => value.1,
|
||||
Err(err) => {
|
||||
let err = nu_errors::ShellError::parse_error(err);
|
||||
|
||||
println!("{:?}", string);
|
||||
crate::hir::baseline_parse::tests::print_err(err, &nu_source::Text::from(string));
|
||||
panic!("test failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn span((left, right): (usize, usize)) -> Span {
|
||||
Span::new(left, right)
|
||||
}
|
||||
|
||||
fn delimited(
|
||||
delimiter: Spanned<Delimiter>,
|
||||
children: Vec<SpannedToken>,
|
||||
left: usize,
|
||||
right: usize,
|
||||
) -> SpannedToken {
|
||||
let start = Span::for_char(left);
|
||||
let end = Span::for_char(right);
|
||||
|
||||
let node = DelimitedNode::new(delimiter.item, (start, end), children);
|
||||
Token::Delimited(node).into_spanned((left, right))
|
||||
}
|
||||
|
||||
fn build<T>(block: CurriedNode<T>) -> T {
|
||||
let mut builder = TokenTreeBuilder::new();
|
||||
block(&mut builder)
|
||||
}
|
||||
|
||||
fn build_token(block: CurriedToken) -> SpannedToken {
|
||||
TokenTreeBuilder::build(block).0
|
||||
}
|
||||
}
|
||||
|
@ -306,6 +306,13 @@ impl SpannedToken {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_int(&self) -> bool {
|
||||
match self.unspanned() {
|
||||
Token::Number(RawNumber::Int(_)) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_string(&self) -> Option<(Span, Span)> {
|
||||
match self.unspanned() {
|
||||
Token::String(inner_span) => Some((self.span(), *inner_span)),
|
||||
@ -327,16 +334,16 @@ impl SpannedToken {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_int(&self) -> bool {
|
||||
pub fn is_dot(&self) -> bool {
|
||||
match self.unspanned() {
|
||||
Token::Number(RawNumber::Int(_)) => true,
|
||||
Token::EvaluationOperator(EvaluationOperator::Dot) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_dot(&self) -> bool {
|
||||
pub fn is_separator(&self) -> bool {
|
||||
match self.unspanned() {
|
||||
Token::EvaluationOperator(EvaluationOperator::Dot) => true,
|
||||
Token::Separator => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -479,6 +486,13 @@ impl SpannedToken {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_number(&self) -> RawNumber {
|
||||
match self.unspanned() {
|
||||
Token::Number(raw_number) => *raw_number,
|
||||
other => panic!("Expected number, found {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_string(&self) -> (Span, Span) {
|
||||
match self.unspanned() {
|
||||
Token::String(inner_span) => (self.span(), *inner_span),
|
||||
|
@ -1 +0,0 @@
|
||||
|
2
crates/nu-parser/src/parse/util/line_delimited_parser.rs
Normal file
2
crates/nu-parser/src/parse/util/line_delimited_parser.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub(crate) mod parser;
|
||||
pub(crate) mod shape;
|
272
crates/nu-parser/src/parse/util/line_delimited_parser/parser.rs
Normal file
272
crates/nu-parser/src/parse/util/line_delimited_parser/parser.rs
Normal file
@ -0,0 +1,272 @@
|
||||
use crate::parse::number::RawNumber;
|
||||
use crate::parse::parser::{is_boundary, to_list};
|
||||
use crate::parse::token_tree::SpannedToken;
|
||||
use crate::parse::token_tree_builder::TokenTreeBuilder;
|
||||
use nu_source::{HasSpan, NomSpan, Span, Spanned, SpannedItem};
|
||||
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::{escaped, tag};
|
||||
use nom::character::complete::*;
|
||||
use nom::combinator::*;
|
||||
use nom::multi::*;
|
||||
use nom::IResult;
|
||||
use nom_tracable::tracable_parser;
|
||||
|
||||
#[tracable_parser]
|
||||
pub fn parse_line_with_separator<'a, 'b>(
|
||||
separator: &'b str,
|
||||
input: NomSpan<'a>,
|
||||
) -> IResult<NomSpan<'a>, Spanned<Vec<SpannedToken>>> {
|
||||
let start = input.offset;
|
||||
let mut nodes = vec![];
|
||||
let mut next_input = input;
|
||||
|
||||
loop {
|
||||
let node_result = to_list(leaf(separator))(next_input);
|
||||
|
||||
let (after_node_input, next_nodes) = match node_result {
|
||||
Err(_) => break,
|
||||
Ok((after_node_input, next_node)) => (after_node_input, next_node),
|
||||
};
|
||||
|
||||
nodes.extend(next_nodes);
|
||||
|
||||
match separated_by(separator)(after_node_input) {
|
||||
Err(_) => {
|
||||
next_input = after_node_input;
|
||||
break;
|
||||
}
|
||||
Ok((input, s)) => {
|
||||
nodes.push(s);
|
||||
next_input = input;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let end = next_input.offset;
|
||||
|
||||
Ok((next_input, nodes.spanned(Span::new(start, end))))
|
||||
}
|
||||
|
||||
#[tracable_parser]
|
||||
pub fn fallback_number_without(c: char) -> impl Fn(NomSpan) -> IResult<NomSpan, SpannedToken> {
|
||||
move |input| {
|
||||
let (input, number) = fallback_raw_number_without(c)(input)?;
|
||||
|
||||
Ok((
|
||||
input,
|
||||
TokenTreeBuilder::spanned_number(number, number.span()),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[tracable_parser]
|
||||
pub fn fallback_raw_number_without(c: char) -> impl Fn(NomSpan) -> IResult<NomSpan, RawNumber> {
|
||||
move |input| {
|
||||
let _anchoral = input;
|
||||
let start = input.offset;
|
||||
let (input, _neg) = opt(tag("-"))(input)?;
|
||||
let (input, _head) = digit1(input)?;
|
||||
let after_int_head = input;
|
||||
|
||||
match input.fragment.chars().next() {
|
||||
None => return Ok((input, RawNumber::int(Span::new(start, input.offset)))),
|
||||
Some('.') => (),
|
||||
other if is_boundary(other) || other == Some(c) => {
|
||||
return Ok((input, RawNumber::int(Span::new(start, input.offset))))
|
||||
}
|
||||
_ => {
|
||||
return Err(nom::Err::Error(nom::error::make_error(
|
||||
input,
|
||||
nom::error::ErrorKind::Tag,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
let dot: IResult<NomSpan, NomSpan, (NomSpan, nom::error::ErrorKind)> = tag(".")(input);
|
||||
|
||||
let input = match dot {
|
||||
Ok((input, _dot)) => input,
|
||||
|
||||
// it's just an integer
|
||||
Err(_) => return Ok((input, RawNumber::int(Span::new(start, input.offset)))),
|
||||
};
|
||||
|
||||
let tail_digits_result: IResult<NomSpan, _> = digit1(input);
|
||||
|
||||
let (input, _tail) = match tail_digits_result {
|
||||
Ok((input, tail)) => (input, tail),
|
||||
Err(_) => {
|
||||
return Ok((
|
||||
after_int_head,
|
||||
RawNumber::int((start, after_int_head.offset)),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let end = input.offset;
|
||||
|
||||
let next = input.fragment.chars().next();
|
||||
|
||||
if is_boundary(next) || next == Some(c) {
|
||||
Ok((input, RawNumber::decimal(Span::new(start, end))))
|
||||
} else {
|
||||
Err(nom::Err::Error(nom::error::make_error(
|
||||
input,
|
||||
nom::error::ErrorKind::Tag,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tracable_parser]
|
||||
pub fn leaf(c: &str) -> impl Fn(NomSpan) -> IResult<NomSpan, SpannedToken> + '_ {
|
||||
move |input| {
|
||||
let separator = c.chars().next().unwrap_or_else(|| ',');
|
||||
|
||||
let (input, node) = alt((
|
||||
fallback_number_without(separator),
|
||||
string,
|
||||
fallback_string_without(c),
|
||||
))(input)?;
|
||||
|
||||
Ok((input, node))
|
||||
}
|
||||
}
|
||||
|
||||
#[tracable_parser]
|
||||
pub fn separated_by(c: &str) -> impl Fn(NomSpan) -> IResult<NomSpan, SpannedToken> + '_ {
|
||||
move |input| {
|
||||
let left = input.offset;
|
||||
let (input, _) = tag(c)(input)?;
|
||||
let right = input.offset;
|
||||
|
||||
Ok((input, TokenTreeBuilder::spanned_sep(Span::new(left, right))))
|
||||
}
|
||||
}
|
||||
|
||||
#[tracable_parser]
|
||||
pub fn dq_string(input: NomSpan) -> IResult<NomSpan, SpannedToken> {
|
||||
let start = input.offset;
|
||||
let (input, _) = char('"')(input)?;
|
||||
let start1 = input.offset;
|
||||
let (input, _) = escaped(
|
||||
none_of(r#"\""#),
|
||||
'\\',
|
||||
nom::character::complete::one_of(r#"\"rnt"#),
|
||||
)(input)?;
|
||||
|
||||
let end1 = input.offset;
|
||||
let (input, _) = char('"')(input)?;
|
||||
let end = input.offset;
|
||||
Ok((
|
||||
input,
|
||||
TokenTreeBuilder::spanned_string(Span::new(start1, end1), Span::new(start, end)),
|
||||
))
|
||||
}
|
||||
|
||||
#[tracable_parser]
|
||||
pub fn sq_string(input: NomSpan) -> IResult<NomSpan, SpannedToken> {
|
||||
let start = input.offset;
|
||||
let (input, _) = char('\'')(input)?;
|
||||
let start1 = input.offset;
|
||||
let (input, _) = many0(none_of("\'"))(input)?;
|
||||
let end1 = input.offset;
|
||||
let (input, _) = char('\'')(input)?;
|
||||
let end = input.offset;
|
||||
|
||||
Ok((
|
||||
input,
|
||||
TokenTreeBuilder::spanned_string(Span::new(start1, end1), Span::new(start, end)),
|
||||
))
|
||||
}
|
||||
|
||||
#[tracable_parser]
|
||||
pub fn string(input: NomSpan) -> IResult<NomSpan, SpannedToken> {
|
||||
alt((sq_string, dq_string))(input)
|
||||
}
|
||||
|
||||
#[tracable_parser]
|
||||
pub fn fallback_string_without(c: &str) -> impl Fn(NomSpan) -> IResult<NomSpan, SpannedToken> + '_ {
|
||||
move |input| {
|
||||
let start = input.offset;
|
||||
let (input, _) = many0(none_of(c))(input)?;
|
||||
let end = input.offset;
|
||||
|
||||
Ok((
|
||||
input,
|
||||
TokenTreeBuilder::spanned_string(Span::new(start, end), Span::new(start, end)),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::parse::token_tree_builder::TokenTreeBuilder::{self, self as b};
|
||||
use crate::parse::util::parse_line_with_separator;
|
||||
use crate::test_support::apply;
|
||||
use nom::IResult;
|
||||
|
||||
use crate::parse::pipeline::PipelineElement;
|
||||
use crate::parse::token_tree::SpannedToken;
|
||||
use nu_source::NomSpan;
|
||||
use nu_source::PrettyDebugWithSource;
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
pub fn nodes(input: NomSpan) -> IResult<NomSpan, SpannedToken> {
|
||||
let (input, tokens) = parse_line_with_separator(",", input)?;
|
||||
let span = tokens.span;
|
||||
|
||||
Ok((
|
||||
input,
|
||||
TokenTreeBuilder::spanned_pipeline(vec![PipelineElement::new(None, tokens)], span),
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn separators() {
|
||||
equal_tokens! {
|
||||
<nodes>
|
||||
r#""name","lastname","age""# -> b::token_list(vec![
|
||||
b::string("name"),
|
||||
b::sep(","),
|
||||
b::string("lastname"),
|
||||
b::sep(","),
|
||||
b::string("age")
|
||||
])
|
||||
}
|
||||
|
||||
equal_tokens! {
|
||||
<nodes>
|
||||
r#""Andrés","Robalino",12"# -> b::token_list(vec![
|
||||
b::string("Andrés"),
|
||||
b::sep(","),
|
||||
b::string("Robalino"),
|
||||
b::sep(","),
|
||||
b::int(12)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strings() {
|
||||
equal_tokens! {
|
||||
<nodes>
|
||||
r#""andres""# -> b::token_list(vec![b::string("andres")])
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn numbers() {
|
||||
equal_tokens! {
|
||||
<nodes>
|
||||
"123" -> b::token_list(vec![b::int(123)])
|
||||
}
|
||||
|
||||
equal_tokens! {
|
||||
<nodes>
|
||||
"-123" -> b::token_list(vec![b::int(-123)])
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
use crate::hir::{
|
||||
self, syntax_shape::ExpandSyntax, syntax_shape::FlatShape, syntax_shape::NumberExpressionShape,
|
||||
syntax_shape::StringShape,
|
||||
};
|
||||
use crate::hir::{Expression, TokensIterator};
|
||||
use crate::parse::token_tree::SeparatorType;
|
||||
|
||||
use nu_errors::ParseError;
|
||||
use nu_protocol::UntaggedValue;
|
||||
use nu_source::Span;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct LineSeparatedShape;
|
||||
|
||||
impl ExpandSyntax for LineSeparatedShape {
|
||||
type Output = Result<Vec<UntaggedValue>, ParseError>;
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"any string line separated by"
|
||||
}
|
||||
|
||||
fn expand<'a, 'b>(
|
||||
&self,
|
||||
token_nodes: &mut TokensIterator<'_>,
|
||||
) -> Result<Vec<UntaggedValue>, ParseError> {
|
||||
let source = token_nodes.source();
|
||||
|
||||
if token_nodes.at_end() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
let mut entries = vec![];
|
||||
|
||||
loop {
|
||||
let field = {
|
||||
token_nodes
|
||||
.expand_syntax(NumberExpressionShape)
|
||||
.or_else(|_| {
|
||||
token_nodes
|
||||
.expand_syntax(StringShape)
|
||||
.map(|syntax| Expression::string(syntax.inner).into_expr(syntax.span))
|
||||
})
|
||||
};
|
||||
|
||||
if let Ok(field) = field {
|
||||
match &field.expr {
|
||||
Expression::Literal(hir::Literal::Number(crate::Number::Int(i))) => {
|
||||
entries.push(UntaggedValue::int(i.clone()))
|
||||
}
|
||||
Expression::Literal(hir::Literal::Number(crate::Number::Decimal(d))) => {
|
||||
entries.push(UntaggedValue::decimal(d.clone()))
|
||||
}
|
||||
Expression::Literal(hir::Literal::String(span)) => {
|
||||
if span.is_closed() {
|
||||
entries.push(UntaggedValue::nothing())
|
||||
} else {
|
||||
entries.push(UntaggedValue::string(span.slice(&source)))
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
match token_nodes.expand_infallible(SeparatorShape) {
|
||||
Err(err) if !token_nodes.at_end() => return Err(err),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if token_nodes.at_end() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(entries)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct SeparatorShape;
|
||||
|
||||
impl ExpandSyntax for SeparatorShape {
|
||||
type Output = Result<Span, ParseError>;
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"separated"
|
||||
}
|
||||
|
||||
fn expand<'a, 'b>(&self, token_nodes: &'b mut TokensIterator<'a>) -> Result<Span, ParseError> {
|
||||
token_nodes.expand_token(SeparatorType, |span| Ok((FlatShape::Separator, span)))
|
||||
}
|
||||
}
|
4
crates/nu-parser/src/parse/util/mod.rs
Normal file
4
crates/nu-parser/src/parse/util/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
mod line_delimited_parser;
|
||||
|
||||
pub use line_delimited_parser::parser::parse_line_with_separator;
|
||||
pub use line_delimited_parser::shape::LineSeparatedShape;
|
104
crates/nu-parser/src/test_support/mod.rs
Normal file
104
crates/nu-parser/src/test_support/mod.rs
Normal file
@ -0,0 +1,104 @@
|
||||
use crate::hir::{syntax_shape::ExpandContext, syntax_shape::SignatureRegistry};
|
||||
|
||||
use crate::parse::files::Files;
|
||||
use crate::parse::token_tree::{DelimitedNode, Delimiter, SpannedToken, Token};
|
||||
use crate::parse::token_tree_builder::{CurriedToken, TokenTreeBuilder};
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::Signature;
|
||||
use nu_source::{nom_input, NomSpan, Span, Spanned, Text};
|
||||
|
||||
pub use nu_source::PrettyDebug;
|
||||
|
||||
use derive_new::new;
|
||||
|
||||
pub type CurriedNode<T> = Box<dyn FnOnce(&mut TokenTreeBuilder) -> T + 'static>;
|
||||
|
||||
#[derive(Debug, Clone, new)]
|
||||
pub struct TestRegistry {
|
||||
#[new(default)]
|
||||
signatures: indexmap::IndexMap<String, Signature>,
|
||||
}
|
||||
|
||||
impl TestRegistry {}
|
||||
|
||||
impl SignatureRegistry for TestRegistry {
|
||||
fn has(&self, name: &str) -> bool {
|
||||
self.signatures.contains_key(name)
|
||||
}
|
||||
fn get(&self, name: &str) -> Option<Signature> {
|
||||
self.signatures.get(name).cloned()
|
||||
}
|
||||
fn clone_box(&self) -> Box<dyn SignatureRegistry> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_empty_context(source: &Text, callback: impl FnOnce(ExpandContext)) {
|
||||
let registry = TestRegistry::new();
|
||||
callback(ExpandContext::new(Box::new(registry), source, None))
|
||||
}
|
||||
|
||||
pub fn inner_string_span(span: Span) -> Span {
|
||||
Span::new(span.start() + 1, span.end() - 1)
|
||||
}
|
||||
|
||||
pub fn print_err(err: ShellError, source: &Text) {
|
||||
let diag = err.into_diagnostic();
|
||||
|
||||
let writer = termcolor::StandardStream::stderr(termcolor::ColorChoice::Auto);
|
||||
let mut source = source.to_string();
|
||||
source.push_str(" ");
|
||||
let files = Files::new(source);
|
||||
let _ = language_reporting::emit(
|
||||
&mut writer.lock(),
|
||||
&files,
|
||||
&diag,
|
||||
&language_reporting::DefaultConfig,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn apply(
|
||||
f: impl Fn(NomSpan) -> Result<(NomSpan, SpannedToken), nom::Err<(NomSpan, nom::error::ErrorKind)>>,
|
||||
_desc: &str,
|
||||
string: &str,
|
||||
) -> SpannedToken {
|
||||
let result = f(nom_input(string));
|
||||
|
||||
match result {
|
||||
Ok(value) => value.1,
|
||||
Err(err) => {
|
||||
let err = nu_errors::ShellError::parse_error(err);
|
||||
|
||||
println!("{:?}", string);
|
||||
crate::hir::baseline_parse::tests::print_err(err, &nu_source::Text::from(string));
|
||||
panic!("test failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn span((left, right): (usize, usize)) -> Span {
|
||||
Span::new(left, right)
|
||||
}
|
||||
|
||||
pub fn delimited(
|
||||
delimiter: Spanned<Delimiter>,
|
||||
children: Vec<SpannedToken>,
|
||||
left: usize,
|
||||
right: usize,
|
||||
) -> SpannedToken {
|
||||
let start = Span::for_char(left);
|
||||
let end = Span::for_char(right);
|
||||
|
||||
let node = DelimitedNode::new(delimiter.item, (start, end), children);
|
||||
Token::Delimited(node).into_spanned((left, right))
|
||||
}
|
||||
|
||||
pub fn build<T>(block: CurriedNode<T>) -> T {
|
||||
let mut builder = TokenTreeBuilder::new();
|
||||
block(&mut builder)
|
||||
}
|
||||
|
||||
pub fn build_token(block: CurriedToken) -> SpannedToken {
|
||||
TokenTreeBuilder::build(block).0
|
||||
}
|
Reference in New Issue
Block a user