use derive_new::new;
use getset::Getters;
use nu_source::{
span_for_spanned_list, DbgDocBldr, DebugDocBuilder, HasFallibleSpan, PrettyDebug, Span,
Spanned, SpannedItem,
};
use serde::{Deserialize, Serialize};
use crate::hir::{Expression, Literal, Member, SpannedExpression};
use nu_errors::ParseError;
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum UnspannedPathMember {
String(String),
Int(i64),
}
impl UnspannedPathMember {
pub fn into_path_member(self, span: impl Into<Span>) -> PathMember {
PathMember {
unspanned: self,
span: span.into(),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub struct PathMember {
pub unspanned: UnspannedPathMember,
pub span: Span,
}
impl PrettyDebug for &PathMember {
fn pretty(&self) -> DebugDocBuilder {
match &self.unspanned {
UnspannedPathMember::String(string) => DbgDocBldr::primitive(format!("{:?}", string)),
UnspannedPathMember::Int(int) => DbgDocBldr::primitive(int),
}
}
}
#[derive(
Debug, Hash, Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq, Getters, Clone, new,
)]
pub struct ColumnPath {
#[get = "pub"]
members: Vec<PathMember>,
}
impl ColumnPath {
pub fn iter(&self) -> impl Iterator<Item = &PathMember> {
self.members.iter()
}
pub fn is_empty(&self) -> bool {
self.members.is_empty()
}
pub fn split_last(&self) -> Option<(&PathMember, &[PathMember])> {
self.members.split_last()
}
pub fn last(&self) -> Option<&PathMember> {
self.iter().last()
}
pub fn path(&self) -> String {
let sep = std::path::MAIN_SEPARATOR;
let mut members = self.iter();
let mut f = String::from(sep);
if let Some(member) = members.next() {
f.push_str(&member.as_string());
}
for member in members {
f.push(sep);
f.push_str(&member.as_string());
}
f
}
pub fn build(text: &Spanned<String>) -> ColumnPath {
if let (
SpannedExpression {
expr: Expression::Literal(Literal::ColumnPath(path)),
span: _,
},
_,
) = parse(text)
{
ColumnPath {
members: path.iter().map(|member| member.to_path_member()).collect(),
}
} else {
ColumnPath { members: vec![] }
}
}
pub fn with_head(text: &Spanned<String>) -> Option<(String, ColumnPath)> {
match parse_full_column_path(text) {
(
SpannedExpression {
expr: Expression::FullColumnPath(path),
..
},
_,
) => {
if let crate::hir::FullColumnPath {
head:
SpannedExpression {
expr: Expression::Variable(name, _),
span: _,
},
tail,
} = *path
{
Some((
name,
ColumnPath {
members: tail.to_vec(),
},
))
} else {
None
}
}
_ => None,
}
}
}
impl PrettyDebug for ColumnPath {
fn pretty(&self) -> DebugDocBuilder {
let members: Vec<DebugDocBuilder> =
self.members.iter().map(|member| member.pretty()).collect();
DbgDocBldr::delimit(
"(",
DbgDocBldr::description("path")
+ DbgDocBldr::equals()
+ DbgDocBldr::intersperse(members, DbgDocBldr::space()),
")",
)
.nest()
}
}
impl HasFallibleSpan for ColumnPath {
fn maybe_span(&self) -> Option<Span> {
if self.members.is_empty() {
None
} else {
Some(span_for_spanned_list(self.members.iter().map(|m| m.span)))
}
}
}
impl<'a> IntoIterator for &'a ColumnPath {
type Item = &'a PathMember;
type IntoIter = std::slice::Iter<'a, PathMember>;
fn into_iter(self) -> Self::IntoIter {
self.members.iter()
}
}
impl PathMember {
pub fn string(string: impl Into<String>, span: impl Into<Span>) -> PathMember {
UnspannedPathMember::String(string.into()).into_path_member(span)
}
pub fn int(int: i64, span: impl Into<Span>) -> PathMember {
UnspannedPathMember::Int(int).into_path_member(span)
}
pub fn as_string(&self) -> String {
match &self.unspanned {
UnspannedPathMember::String(string) => string.clone(),
UnspannedPathMember::Int(int) => int.to_string(),
}
}
}
fn parse_full_column_path(
raw_column_path: &Spanned<String>,
) -> (SpannedExpression, Option<ParseError>) {
let mut inside_delimiter = vec![];
let mut output = vec![];
let mut current_part = String::new();
let mut start_index = 0;
let mut last_index = 0;
let error = None;
let mut head = None;
for (idx, c) in raw_column_path.item.char_indices() {
last_index = idx;
if c == '(' {
inside_delimiter.push(')');
} else if let Some(delimiter) = inside_delimiter.last() {
if c == *delimiter {
inside_delimiter.pop();
}
} else if c == '\'' || c == '"' {
inside_delimiter.push(c);
} else if c == '.' {
let part_span = Span::new(
raw_column_path.span.start() + start_index,
raw_column_path.span.start() + idx,
);
if head.is_none() && current_part.starts_with('$') {
head = Some(Expression::variable(current_part.clone(), part_span))
} else if let Ok(row_number) = current_part.parse::<i64>() {
output.push(UnspannedPathMember::Int(row_number).into_path_member(part_span));
} else {
let current_part = trim_quotes(¤t_part);
output.push(
UnspannedPathMember::String(current_part.clone()).into_path_member(part_span),
);
}
current_part.clear();
start_index = idx + '.'.len_utf8();
continue;
}
current_part.push(c);
}
if !current_part.is_empty() {
let part_span = Span::new(
raw_column_path.span.start() + start_index,
raw_column_path.span.start() + last_index + 1,
);
if head.is_none() {
if current_part.starts_with('$') {
head = Some(Expression::variable(current_part, raw_column_path.span));
} else if let Ok(row_number) = current_part.parse::<i64>() {
output.push(UnspannedPathMember::Int(row_number).into_path_member(part_span));
} else {
let current_part = trim_quotes(¤t_part);
output.push(UnspannedPathMember::String(current_part).into_path_member(part_span));
}
} else if let Ok(row_number) = current_part.parse::<i64>() {
output.push(UnspannedPathMember::Int(row_number).into_path_member(part_span));
} else {
let current_part = trim_quotes(¤t_part);
output.push(UnspannedPathMember::String(current_part).into_path_member(part_span));
}
}
if let Some(head) = head {
(
SpannedExpression::new(
Expression::path(SpannedExpression::new(head, raw_column_path.span), output),
raw_column_path.span,
),
error,
)
} else {
(
SpannedExpression::new(
Expression::path(
SpannedExpression::new(
Expression::variable("$it".into(), raw_column_path.span),
raw_column_path.span,
),
output,
),
raw_column_path.span,
),
error,
)
}
}
fn parse(raw_column_path: &Spanned<String>) -> (SpannedExpression, Option<ParseError>) {
let mut delimiter = '.';
let mut inside_delimiter = false;
let mut output = vec![];
let mut current_part = String::new();
let mut start_index = 0;
let mut last_index = 0;
for (idx, c) in raw_column_path.item.char_indices() {
last_index = idx;
if inside_delimiter {
if c == delimiter {
inside_delimiter = false;
}
} else if c == '\'' || c == '"' || c == '`' {
inside_delimiter = true;
delimiter = c;
} else if c == '.' {
let part_span = Span::new(
raw_column_path.span.start() + start_index,
raw_column_path.span.start() + idx,
);
if let Ok(row_number) = current_part.parse::<i64>() {
output.push(Member::Int(row_number, part_span));
} else {
let trimmed = trim_quotes(¤t_part);
output.push(Member::Bare(trimmed.clone().spanned(part_span)));
}
current_part.clear();
start_index = idx + '.'.len_utf8();
continue;
}
current_part.push(c);
}
if !current_part.is_empty() {
let part_span = Span::new(
raw_column_path.span.start() + start_index,
raw_column_path.span.start() + last_index + 1,
);
if let Ok(row_number) = current_part.parse::<i64>() {
output.push(Member::Int(row_number, part_span));
} else {
let current_part = trim_quotes(¤t_part);
output.push(Member::Bare(current_part.spanned(part_span)));
}
}
(
SpannedExpression::new(Expression::simple_column_path(output), raw_column_path.span),
None,
)
}
fn trim_quotes(input: &str) -> String {
let mut chars = input.chars();
match (chars.next(), chars.next_back()) {
(Some('\''), Some('\'')) => chars.collect(),
(Some('"'), Some('"')) => chars.collect(),
_ => input.to_string(),
}
}