nu-parser 0.114.0

Nushell's parser
Documentation
use crate::{
    is_math_expression_like,
    lite_parser::LiteCommand,
    parse_helpers::{garbage, garbage_pipeline},
    parse_pipelines::redirecting_builtin_error,
    parser::{
        ArgumentParsingLevel, CallKind, ParsedInternalCall, parse_call, parse_expression,
        parse_internal_call,
    },
};

use nu_protocol::{
    Alias, ParseError, Span,
    ast::{Argument, Expr, Expression, Pipeline},
    engine::StateWorkingSet,
};

use crate::ALIASABLE_PARSER_KEYWORDS;

fn check_alias_name<'a>(working_set: &mut StateWorkingSet, spans: &'a [Span]) -> Option<&'a Span> {
    let command_len = if !spans.is_empty() {
        if working_set.get_span_contents(spans[0]) == b"export" {
            2
        } else {
            1
        }
    } else {
        return None;
    };

    if spans.len() == command_len {
        None
    } else if spans.len() < command_len + 3 {
        if working_set.get_span_contents(spans[command_len]) == b"=" {
            let name = String::from_utf8_lossy(
                working_set.get_span_contents(Span::concat(&spans[..command_len])),
            );
            working_set.error(ParseError::AssignmentMismatch(
                format!("{name} missing name"),
                "missing name".into(),
                spans[command_len],
            ));
            Some(&spans[command_len])
        } else {
            None
        }
    } else if working_set.get_span_contents(spans[command_len + 1]) != b"=" {
        let name = String::from_utf8_lossy(
            working_set.get_span_contents(Span::concat(&spans[..command_len])),
        );
        working_set.error(ParseError::AssignmentMismatch(
            format!("{name} missing sign"),
            "missing equal sign".into(),
            spans[command_len + 1],
        ));
        Some(&spans[command_len + 1])
    } else {
        None
    }
}

pub fn parse_alias(
    working_set: &mut StateWorkingSet,
    lite_command: &LiteCommand,
    module_name: Option<&[u8]>,
) -> Pipeline {
    let spans = &lite_command.parts;

    let (name_span, split_id) =
        if spans.len() > 1 && working_set.get_span_contents(spans[0]) == b"export" {
            (spans[1], 2)
        } else {
            (spans[0], 1)
        };

    let name = working_set.get_span_contents(name_span);

    if name != b"alias" {
        working_set.error(ParseError::InternalError(
            "Alias statement unparsable".into(),
            Span::concat(spans),
        ));
        return garbage_pipeline(working_set, spans);
    }
    if let Some(redirection) = lite_command.redirection.as_ref() {
        working_set.error(redirecting_builtin_error("alias", redirection));
        return garbage_pipeline(working_set, spans);
    }

    if let Some(span) = check_alias_name(working_set, spans) {
        return Pipeline::from_vec(vec![garbage(working_set, *span)]);
    }

    if let Some(decl_id) = working_set.find_decl(b"alias") {
        let (command_spans, rest_spans) = spans.split_at(split_id);

        let original_starting_error_count = working_set.parse_errors.len();

        let ParsedInternalCall {
            call: alias_call,
            output,
            call_kind,
        } = parse_internal_call(
            working_set,
            Span::concat(command_spans),
            rest_spans,
            decl_id,
            ArgumentParsingLevel::Full,
            None,
        );

        working_set
            .parse_errors
            .truncate(original_starting_error_count);

        let alias_pipeline = Pipeline::from_vec(vec![Expression::new(
            working_set,
            Expr::Call(alias_call.clone()),
            Span::concat(spans),
            output,
        )]);

        if call_kind == CallKind::Help {
            return alias_pipeline;
        }

        let Some(alias_name_expr) = alias_call.positional_iter().next() else {
            working_set.error(ParseError::UnknownState(
                "Missing positional after call check".to_string(),
                Span::concat(spans),
            ));
            return garbage_pipeline(working_set, spans);
        };

        let alias_name = if let Some(name) = alias_name_expr.as_string() {
            if name.contains('#')
                || name.contains('^')
                || name.contains('%')
                || name.parse::<bytesize::ByteSize>().is_ok()
                || name.parse::<f64>().is_ok()
            {
                working_set.error(ParseError::AliasNotValid(alias_name_expr.span));
                return garbage_pipeline(working_set, spans);
            } else {
                name
            }
        } else {
            working_set.error(ParseError::AliasNotValid(alias_name_expr.span));
            return garbage_pipeline(working_set, spans);
        };

        if spans.len() >= split_id + 3 {
            if let Some(mod_name) = module_name {
                if alias_name.as_bytes() == mod_name {
                    working_set.error(ParseError::NamedAsModule(
                        "alias".to_string(),
                        alias_name,
                        "main".to_string(),
                        spans[split_id],
                    ));

                    return alias_pipeline;
                }

                if alias_name == "main" {
                    working_set.error(ParseError::ExportMainAliasNotAllowed(spans[split_id]));
                    return alias_pipeline;
                }
            }

            let _equals = working_set.get_span_contents(spans[split_id + 1]);

            let replacement_spans = &spans[(split_id + 2)..];
            let first_bytes = working_set.get_span_contents(replacement_spans[0]);

            if first_bytes != b"if"
                && first_bytes != b"match"
                && is_math_expression_like(working_set, replacement_spans[0])
            {
                let starting_error_count = working_set.parse_errors.len();
                let expr = parse_expression(working_set, replacement_spans, None);
                working_set.parse_errors.truncate(starting_error_count);

                working_set.error(ParseError::CantAliasExpression(
                    expr.expr.description().to_string(),
                    replacement_spans[0],
                ));
                return alias_pipeline;
            }

            let starting_error_count = working_set.parse_errors.len();
            working_set.search_predecls = false;

            let expr = parse_call(working_set, replacement_spans, replacement_spans[0], None);

            working_set.search_predecls = true;

            if starting_error_count != working_set.parse_errors.len()
                && let Some(e) = working_set.parse_errors.get(starting_error_count)
            {
                if let ParseError::MissingPositional(..)
                | ParseError::MissingRequiredFlag(..)
                | ParseError::MissingFlagParam(..) = e
                {
                    working_set
                        .parse_errors
                        .truncate(original_starting_error_count);
                } else {
                    return garbage_pipeline(working_set, replacement_spans);
                }
            }

            let (command, wrapped_call) = match expr {
                Expression {
                    expr: Expr::Call(ref rhs_call),
                    ..
                } => {
                    let cmd = working_set.get_decl(rhs_call.decl_id);

                    if cmd.is_keyword()
                        && !ALIASABLE_PARSER_KEYWORDS.contains(&cmd.name().as_bytes())
                    {
                        working_set.error(ParseError::CantAliasKeyword(
                            ALIASABLE_PARSER_KEYWORDS
                                .iter()
                                .map(|bytes| String::from_utf8_lossy(bytes).to_string())
                                .collect::<Vec<String>>()
                                .join(", "),
                            rhs_call.head,
                        ));
                        return alias_pipeline;
                    }

                    (Some(cmd.clone_box()), expr)
                }
                Expression {
                    expr: Expr::ExternalCall(..),
                    ..
                } => (None, expr),
                _ => {
                    working_set.error(ParseError::InternalError(
                        "Parsed call not a call".into(),
                        expr.span,
                    ));
                    return alias_pipeline;
                }
            };

            let (description, extra_description) = match lite_command.comments.is_empty() {
                false => working_set.build_desc(&lite_command.comments),
                true => match alias_call.arguments.get(1) {
                    Some(Argument::Positional(Expression {
                        expr: Expr::Keyword(kw),
                        ..
                    })) => {
                        let aliased = working_set.get_span_contents(kw.expr.span);
                        (
                            format!("Alias for `{}`", String::from_utf8_lossy(aliased)),
                            String::new(),
                        )
                    }
                    _ => ("User declared alias".into(), String::new()),
                },
            };

            let decl = Alias {
                name: alias_name,
                command,
                wrapped_call,
                description,
                extra_description,
            };

            working_set.add_decl(Box::new(decl));
        }

        if spans.len() == 2 && working_set.get_span_contents(spans[1]).contains(&b'=') {
            let arg = String::from_utf8_lossy(working_set.get_span_contents(spans[1]));

            let (name, initial_value) = arg.split_once('=').unwrap_or((&arg, ""));

            let name = if name.is_empty() { "{name}" } else { name };
            let initial_value = if initial_value.is_empty() {
                "{initial_value}"
            } else {
                initial_value
            };

            working_set.error(ParseError::IncorrectValue(
                "alias argument".into(),
                spans[1],
                format!("Make sure to put spaces around '=': alias {name} = {initial_value}"),
            ))
        } else if spans.len() < 4 {
            working_set.error(ParseError::IncorrectValue(
                "Incomplete alias".into(),
                Span::concat(&spans[..split_id]),
                "incomplete alias".into(),
            ));
        }

        return alias_pipeline;
    }

    working_set.error(ParseError::InternalError(
        "Alias statement unparsable".into(),
        Span::concat(spans),
    ));

    garbage_pipeline(working_set, spans)
}