use nu_protocol::{ ast::{Expr, Expression, MatchPattern, Pattern}, engine::StateWorkingSet, ParseError, Span, SyntaxShape, Type, VarId, }; use crate::{ lex, lite_parse, parser::{is_variable, parse_value}, LiteElement, }; pub fn garbage(span: Span) -> MatchPattern { MatchPattern { pattern: Pattern::Garbage, span, } } pub fn parse_match_pattern(working_set: &mut StateWorkingSet, span: Span) -> Expression { working_set.enter_scope(); let output = parse_pattern(working_set, span); working_set.exit_scope(); Expression { expr: Expr::MatchPattern(Box::new(output)), span, ty: Type::Any, custom_completion: None, } } pub fn parse_pattern(working_set: &mut StateWorkingSet, span: Span) -> MatchPattern { let bytes = working_set.get_span_contents(span); if bytes.starts_with(b"$") { // Variable pattern parse_variable_pattern(working_set, span) } else if bytes.starts_with(b"{") { // Record pattern parse_record_pattern(working_set, span) } else if bytes.starts_with(b"[") { // List pattern parse_list_pattern(working_set, span) } else if bytes == b"_" { MatchPattern { pattern: Pattern::IgnoreValue, span, } } else { // Literal value let value = parse_value(working_set, span, &SyntaxShape::Any); MatchPattern { pattern: Pattern::Value(value), span, } } } fn parse_variable_pattern_helper(working_set: &mut StateWorkingSet, span: Span) -> Option { let bytes = working_set.get_span_contents(span); if is_variable(bytes) { if let Some(var_id) = working_set.find_variable_in_current_frame(bytes) { Some(var_id) } else { let var_id = working_set.add_variable(bytes.to_vec(), span, Type::Any, false); Some(var_id) } } else { None } } pub fn parse_variable_pattern(working_set: &mut StateWorkingSet, span: Span) -> MatchPattern { if let Some(var_id) = parse_variable_pattern_helper(working_set, span) { MatchPattern { pattern: Pattern::Variable(var_id), span, } } else { working_set.error(ParseError::Expected("valid variable name".into(), span)); garbage(span) } } pub fn parse_list_pattern(working_set: &mut StateWorkingSet, span: Span) -> MatchPattern { let bytes = working_set.get_span_contents(span); let mut start = span.start; let mut end = span.end; if bytes.starts_with(b"[") { start += 1; } if bytes.ends_with(b"]") { end -= 1; } else { working_set.error(ParseError::Unclosed("]".into(), Span::new(end, end))); } let inner_span = Span::new(start, end); let source = working_set.get_span_contents(inner_span); let (output, err) = lex(source, inner_span.start, &[b'\n', b'\r', b','], &[], true); if let Some(err) = err { working_set.error(err); } let (output, err) = lite_parse(&output); if let Some(err) = err { working_set.error(err); } let mut args = vec![]; if !output.block.is_empty() { for arg in &output.block[0].commands { let mut spans_idx = 0; if let LiteElement::Command(_, command) = arg { while spans_idx < command.parts.len() { let contents = working_set.get_span_contents(command.parts[spans_idx]); if contents == b".." { args.push(MatchPattern { pattern: Pattern::IgnoreRest, span: command.parts[spans_idx], }); break; } else if contents.starts_with(b"..$") { if let Some(var_id) = parse_variable_pattern_helper( working_set, Span::new( command.parts[spans_idx].start + 2, command.parts[spans_idx].end, ), ) { args.push(MatchPattern { pattern: Pattern::Rest(var_id), span: command.parts[spans_idx], }); break; } else { args.push(garbage(command.parts[spans_idx])); working_set.error(ParseError::Expected( "valid variable name".into(), command.parts[spans_idx], )); } } else { let arg = parse_pattern(working_set, command.parts[spans_idx]); args.push(arg); }; spans_idx += 1; } } } } MatchPattern { pattern: Pattern::List(args), span, } } pub fn parse_record_pattern(working_set: &mut StateWorkingSet, span: Span) -> MatchPattern { let mut bytes = working_set.get_span_contents(span); let mut start = span.start; let mut end = span.end; if bytes.starts_with(b"{") { start += 1; } else { working_set.error(ParseError::Expected( "{".into(), Span::new(start, start + 1), )); bytes = working_set.get_span_contents(span); } if bytes.ends_with(b"}") { end -= 1; } else { working_set.error(ParseError::Unclosed("}".into(), Span::new(end, end))); } let inner_span = Span::new(start, end); let source = working_set.get_span_contents(inner_span); let (tokens, err) = lex(source, start, &[b'\n', b'\r', b','], &[b':'], true); if let Some(err) = err { working_set.error(err); } let mut output = vec![]; let mut idx = 0; while idx < tokens.len() { let bytes = working_set.get_span_contents(tokens[idx].span); let (field, pattern) = if !bytes.is_empty() && bytes[0] == b'$' { // If this is a variable, treat it as both the name of the field and the pattern let field = String::from_utf8_lossy(&bytes[1..]).to_string(); let pattern = parse_variable_pattern(working_set, tokens[idx].span); (field, pattern) } else { let field = String::from_utf8_lossy(bytes).to_string(); idx += 1; if idx == tokens.len() { working_set.error(ParseError::Expected("record".into(), span)); return garbage(span); } let colon = working_set.get_span_contents(tokens[idx].span); idx += 1; if idx == tokens.len() || colon != b":" { //FIXME: need better error working_set.error(ParseError::Expected("record".into(), span)); return garbage(span); } let pattern = parse_pattern(working_set, tokens[idx].span); (field, pattern) }; idx += 1; output.push((field, pattern)); } MatchPattern { pattern: Pattern::Record(output), span, } }