df_ls_syntax_analysis 0.3.0-rc.1

A language server for Dwarf Fortress RAW files
Documentation
use super::super::{Argument, Token, TokenArgument, TryFromArgument, TryFromArgumentGroup};
use super::TokenDeserialize;
use df_ls_core::{Any, BangArgN, BangArgNOrValue, BangArgNSequence, Reference};
use df_ls_diagnostics::{hash_map, DMExtraInfo, DiagnosticsInfo};

// Deserialize a token with following pattern: `[REF:int]`, `[REF:text]` or ...
crate::token_deserialize_unary_token!(Any);

// ------------------------- Convert a group of arguments to Self -----------------------

impl TryFromArgumentGroup for Any {
    fn try_from_argument_group(
        token: &mut Token,
        source: &str,
        diagnostics: &mut DiagnosticsInfo,
        add_diagnostics_on_err: bool,
    ) -> Result<Self, ()> {
        token.check_as_required_argument(source, diagnostics, add_diagnostics_on_err)?;
        let arg = token.get_current_arg_opt();
        let result = Self::try_from_argument(arg, source, diagnostics, add_diagnostics_on_err);
        token.consume_argument();
        result
    }
}

// -------------------------Convert one argument to Self -----------------------

impl TryFromArgument for Any {
    fn try_from_argument(
        arg_opt: Option<&Argument>,
        _source: &str,
        diagnostics: &mut DiagnosticsInfo,
        add_diagnostics_on_err: bool,
    ) -> Result<Self, ()> {
        if let Some(arg) = arg_opt {
            from_token_arg_to_any(arg, &arg.value, diagnostics, add_diagnostics_on_err)
        } else {
            Err(())
        }
    }

    fn expected_argument_types() -> String {
        "Any".to_owned()
    }
}

// -------------------------Convert from TokenArgument -----------------------

pub(super) fn from_token_arg_to_any(
    arg: &Argument,
    token_arg: &TokenArgument,
    diagnostics: &mut DiagnosticsInfo,
    add_diagnostics_on_err: bool,
) -> Result<Any, ()> {
    match token_arg {
        TokenArgument::TVInteger(i64_value) => {
            if *i64_value > (u32::MAX as i64) {
                if add_diagnostics_on_err {
                    diagnostics.add_message(
                        DMExtraInfo {
                            range: arg.node.get_range(),
                            message_template_data: hash_map! {
                                "max_value" => u32::MAX.to_string(),
                            },
                        },
                        "too_large_int",
                    );
                }
                return Err(());
            }
            if *i64_value < (i32::MIN as i64) {
                if add_diagnostics_on_err {
                    diagnostics.add_message(
                        DMExtraInfo {
                            range: arg.node.get_range(),
                            message_template_data: hash_map! {
                                "min_value" => i32::MIN.to_string(),
                            },
                        },
                        "too_small_int",
                    );
                }
                return Err(());
            }
            Ok(Any::Integer(*i64_value))
        }
        TokenArgument::TVCharacter(v) => Ok(Any::Character(*v)),
        TokenArgument::TVArgN(v) => Ok(Any::ArgN(*v)),
        TokenArgument::TVString(v) => Ok(Any::String(v.clone())),
        TokenArgument::TVReference(v) => Ok(Any::Reference(Reference(v.clone()))),
        TokenArgument::TVPipeArguments(v) => Ok(Any::PipeArguments({
            let mut list = Vec::new();
            for item in v {
                list.push(match item {
                    TokenArgument::TVEmpty => None,
                    _ => Some(from_token_arg_to_any(
                        arg,
                        item,
                        diagnostics,
                        add_diagnostics_on_err,
                    )?),
                });
            }
            list
        })),
        TokenArgument::TVBangArgNSequence(v) => Ok(Any::BangArgNSequence({
            let mut list = Vec::new();
            for item in v {
                list.push(match item {
                    TokenArgument::TVBangArgN(n) => BangArgNOrValue::BangArgN(BangArgN(*n)),
                    TokenArgument::TVString(s) => BangArgNOrValue::Value(s.clone()),
                    _ => unreachable!("Only support BangArgN and String"),
                });
            }
            BangArgNSequence(list)
        })),
        _ => {
            if add_diagnostics_on_err {
                diagnostics.add_message(
                    DMExtraInfo {
                        range: arg.node.get_range(),
                        message_template_data: hash_map! {
                            "expected_parameters" => Any::expected_argument_types(),
                            "found_parameters" => arg.value.argument_to_token_type_name(),
                        },
                    },
                    "wrong_arg_type",
                );
            }
            Err(())
        }
    }
}

// -------------------------Convert to TokenArgument -----------------------

impl From<Any> for TokenArgument {
    fn from(item: Any) -> TokenArgument {
        match item {
            Any::Integer(v) => TokenArgument::TVInteger(v),
            Any::Character(v) => TokenArgument::TVCharacter(v),
            Any::ArgN(v) => TokenArgument::TVArgN(v),
            Any::String(v) => TokenArgument::TVString(v),
            Any::Reference(v) => TokenArgument::TVReference(v.into()),
            Any::PipeArguments(v) => {
                TokenArgument::TVPipeArguments(v.into_iter().map(|v| v.into()).collect())
            }
            Any::BangArgNSequence(v) => {
                TokenArgument::TVBangArgNSequence(v.0.into_iter().map(|v| v.into()).collect())
            }
        }
    }
}

impl From<Option<Any>> for TokenArgument {
    fn from(item: Option<Any>) -> TokenArgument {
        match item {
            Some(value) => match value {
                Any::Integer(v) => TokenArgument::TVInteger(v),
                Any::Character(v) => TokenArgument::TVCharacter(v),
                Any::ArgN(v) => TokenArgument::TVArgN(v),
                Any::String(v) => TokenArgument::TVString(v),
                Any::Reference(v) => TokenArgument::TVReference(v.into()),
                Any::PipeArguments(v) => {
                    TokenArgument::TVPipeArguments(v.into_iter().map(|v| v.into()).collect())
                }
                Any::BangArgNSequence(v) => {
                    TokenArgument::TVBangArgNSequence(v.0.into_iter().map(|v| v.into()).collect())
                }
            },
            None => TokenArgument::TVEmpty,
        }
    }
}

// ---------------------------- TESTS --------------------------
#[cfg(test)]
mod tests {
    use super::*;
    use crate::test_tree_structure;
    use crate::test_utils::SyntaxTestBuilder;
    use df_ls_diagnostics::{Position, Range};
    use df_ls_lexical_analysis::test_utils::LexerTestBuilder;

    #[test]
    fn test_any_correct() {
        let test_builder = SyntaxTestBuilder::from_lexer_test_builder(
            LexerTestBuilder::test_source(
                "header
                [REF:600]
                [REF:-2]
                [REF:'a']
                [REF:ARG2]
                [REF:text]
                [REF:ANY]
                [REF:string|pipe]",
            )
            .add_test_lexer_diagnostics_codes(vec![])
            .add_test_lexer_diagnostics_ranges(vec![]),
        )
        .add_test_syntax_diagnostics_codes(vec![])
        .add_test_syntax_diagnostics_ranges(vec![]);

        test_tree_structure!(
            test_builder,
            [
                Any => Any::Integer(600),
                Any => Any::Integer(-2),
                Any => Any::Character('a'),
                Any => Any::ArgN(2),
                Any => Any::String("text".to_owned()),
                Any => Any::Reference(Reference("ANY".to_owned())),
                Any => Any::PipeArguments(vec![
                    Some(Any::String("string".to_owned())),
                    Some(Any::String("pipe".to_owned()))
                ]),
            ]
        );
    }

    #[test]
    fn test_any_incorrect() {
        let test_builder = SyntaxTestBuilder::from_lexer_test_builder(
            LexerTestBuilder::test_source(
                "header
                [REF:]
                [REF:99999999999999]",
            )
            .add_test_lexer_diagnostics_codes(vec![])
            .add_test_lexer_diagnostics_ranges(vec![]),
        )
        .add_test_syntax_diagnostics_codes(vec!["wrong_arg_type", "too_large_int"])
        .add_test_syntax_diagnostics_ranges(vec![
            Range {
                start: Position {
                    line: 1,
                    character: 21,
                },
                end: Position {
                    line: 1,
                    character: 21,
                },
            },
            Range {
                start: Position {
                    line: 2,
                    character: 21,
                },
                end: Position {
                    line: 2,
                    character: 35,
                },
            },
        ]);

        test_tree_structure!(
            test_builder,
            [
                Option<Any> =!,
                Option<Any> =!,
            ]
        );
    }
}