use super::super::{Argument, Token, TokenArgument, TryFromArgument, TryFromArgumentGroup};
use super::{LoopControl, TokenDeserialize};
use df_ls_core::{ArgN, BangArgN, BangArgNSequence, Clamp};
use df_ls_diagnostics::{hash_map, DMExtraInfo, DiagnosticsInfo};
use df_ls_lexical_analysis::TreeCursor;
use std::convert::TryFrom;
macro_rules! token_de_clamp {
( $x:ty ) => {
impl<const L: isize, const H: isize> TokenDeserialize for Clamp<$x, L, H> {
fn deserialize_tokens(
cursor: &mut TreeCursor,
source: &str,
diagnostics: &mut DiagnosticsInfo,
) -> Result<Box<Self>, ()> {
let mut token = match Token::deserialize_tokens(cursor, source, diagnostics) {
Ok(token) => token,
Err(err) => {
Token::consume_token(cursor)?;
return Err(err);
}
};
Token::consume_token(cursor)?;
token.check_token_name(source, diagnostics, true)?;
let result = Self::try_from_argument_group(&mut token, source, diagnostics, true);
if result.is_ok() {
token.check_all_arg_consumed(source, diagnostics, true)?;
} else {
crate::mark_rest_of_token_as_unchecked(diagnostics, &token);
}
Ok(Box::new(result?))
}
fn deserialize_general_token(
_cursor: &mut TreeCursor,
_source: &str,
_diagnostics: &mut DiagnosticsInfo,
new_self: Box<Self>,
) -> (LoopControl, Box<Self>) {
(LoopControl::DoNothing, new_self)
}
fn get_vec_loopcontrol() -> LoopControl {
LoopControl::DoNothing
}
fn get_allowed_tokens() -> Option<Vec<String>> {
None
}
}
};
}
token_de_clamp!(i64);
token_de_clamp!(i32);
token_de_clamp!(i16);
token_de_clamp!(i8);
token_de_clamp!(u32);
token_de_clamp!(u16);
token_de_clamp!(u8);
token_de_clamp!(ArgN);
token_de_clamp!(BangArgN);
token_de_clamp!(BangArgNSequence);
macro_rules! try_from_argument_group {
( $x:ty ) => {
impl<const L: isize, const H: isize> TryFromArgumentGroup for Clamp<$x, L, H> {
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
}
}
};
}
try_from_argument_group!(i64);
try_from_argument_group!(i32);
try_from_argument_group!(i16);
try_from_argument_group!(i8);
try_from_argument_group!(u32);
try_from_argument_group!(u16);
try_from_argument_group!(u8);
try_from_argument_group!(ArgN);
try_from_argument_group!(BangArgN);
try_from_argument_group!(BangArgNSequence);
macro_rules! token_argument_into_clamp {
( $x:ty, $t:literal ) => {
impl<const L: isize, const H: isize> TryFromArgument for Clamp<$x, L, H> {
fn try_from_argument(
arg_opt: Option<&Argument>,
_source: &str,
diagnostics: &mut DiagnosticsInfo,
add_diagnostics_on_err: bool,
) -> Result<Self, ()> {
#[cfg(debug_assertions)]
{
if (H as i64) > (<$x>::MAX as i64) {
panic!(
"Clamp exceeds range of type. High: {} > {}::MAX = {} \
for Clamp<{}, {}, {}>", H, $t, <$x>::MAX, $t, L, H
);
}
if (L as i64) < (<$x>::MIN as i64) {
panic!(
"Clamp exceeds range of type. Low: {} < {}::MIN = {} \
for Clamp<{}, {}, {}>", L, $t, <$x>::MIN, $t, L, H
);
}
}
if let Some(arg) = arg_opt {
match arg.value {
TokenArgument::TVInteger(value_i64) => {
let low = std::cmp::max(L as i64, <$x>::MIN as i64);
let high = std::cmp::min(H as i64, <$x>::MAX as i64);
if value_i64 > high {
if add_diagnostics_on_err {
diagnostics.add_message(
DMExtraInfo {
range: arg.node.get_range(),
message_template_data: hash_map! {
"max_value" => high.to_string(),
},
},
"too_large_int",
);
}
return Err(());
}
if value_i64 < low {
if add_diagnostics_on_err {
diagnostics.add_message(
DMExtraInfo {
range: arg.node.get_range(),
message_template_data: hash_map! {
"min_value" => low.to_string(),
},
},
"too_small_int",
);
}
return Err(());
}
Ok(
Self::from(
match <$x>::try_from(value_i64) {
Ok(v) => v,
Err(err) => {
log::error!("{}", err);
panic!("Number conversion failed, `{}` to `{}`", value_i64, stringify!($x));
}
}
)
)
}
_ => {
if add_diagnostics_on_err {
diagnostics.add_message(
DMExtraInfo {
range: arg.node.get_range(),
message_template_data: hash_map! {
"expected_parameters" => Self::expected_argument_types(),
"found_parameters" => arg.value.argument_to_token_type_name(),
},
},
"wrong_arg_type",
);
}
Err(())
}
}
} else {
Err(())
}
}
fn expected_argument_types() -> String {
let low = std::cmp::max(L as i64, <$x>::MIN as i64);
let high = std::cmp::min(H as i64, <$x>::MAX as i64);
format!("Integer ({}, limited to min: {}, max: {})", $t, low, high)
}
}
};
}
token_argument_into_clamp!(i64, "i64");
token_argument_into_clamp!(i32, "i32");
token_argument_into_clamp!(i16, "i16");
token_argument_into_clamp!(i8, "i8");
token_argument_into_clamp!(u32, "u32");
token_argument_into_clamp!(u16, "u16");
token_argument_into_clamp!(u8, "u8");
macro_rules! token_argument_from_clamp {
( $x:ty ) => {
impl<const L: isize, const H: isize> From<Clamp<$x, L, H>> for TokenArgument {
fn from(item: Clamp<$x, L, H>) -> TokenArgument {
TokenArgument::TVInteger(item.value as i64)
}
}
impl<const L: isize, const H: isize> From<Option<Clamp<$x, L, H>>> for TokenArgument {
fn from(item: Option<Clamp<$x, L, H>>) -> TokenArgument {
match item {
Some(v) => TokenArgument::TVInteger(v.value as i64),
None => TokenArgument::TVEmpty,
}
}
}
};
}
token_argument_from_clamp!(i64);
token_argument_from_clamp!(i32);
token_argument_from_clamp!(i16);
token_argument_from_clamp!(i8);
token_argument_from_clamp!(u32);
token_argument_from_clamp!(u16);
token_argument_from_clamp!(u8);
#[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_clamp_basic() {
let test_builder = SyntaxTestBuilder::from_lexer_test_builder(
LexerTestBuilder::test_source(
"header
[REF:20]",
)
.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,
[
Clamp<u8, 10, 30> => Clamp::new(20),
]
);
let test_builder = SyntaxTestBuilder::from_lexer_test_builder(
LexerTestBuilder::test_source(
"header
[REF:83]",
)
.add_test_lexer_diagnostics_codes(vec![])
.add_test_lexer_diagnostics_ranges(vec![]),
)
.add_test_syntax_diagnostics_codes(vec!["too_large_int"])
.add_test_syntax_diagnostics_ranges(vec![Range {
start: Position {
line: 1,
character: 21,
},
end: Position {
line: 1,
character: 23,
},
}]);
test_tree_structure!(
test_builder,
[
Clamp<u8, 10, 30> =!,
]
);
}
#[test]
fn test_clamp_low_high() {
let test_builder = SyntaxTestBuilder::from_lexer_test_builder(
LexerTestBuilder::test_source(
"header
[REF:-50]
[REF:300]",
)
.add_test_lexer_diagnostics_codes(vec![])
.add_test_lexer_diagnostics_ranges(vec![]),
)
.add_test_syntax_diagnostics_codes(vec!["too_small_int", "too_large_int"])
.add_test_syntax_diagnostics_ranges(vec![
Range {
start: Position {
line: 1,
character: 21,
},
end: Position {
line: 1,
character: 24,
},
},
Range {
start: Position {
line: 2,
character: 21,
},
end: Position {
line: 2,
character: 24,
},
},
]);
test_tree_structure!(
test_builder,
[
Option<Clamp<i8, -10, 60>> =!,
Option<Clamp<i8, -10, 120>> =!,
]
);
}
#[test]
#[should_panic]
#[cfg(debug_assertions)]
fn test_clamp_value_out_of_type_range() {
let test_builder = SyntaxTestBuilder::from_lexer_test_builder(
LexerTestBuilder::test_source(
"header
[REF:0]",
)
.add_test_lexer_diagnostics_codes(vec![])
.add_test_lexer_diagnostics_ranges(vec![]),
)
.add_test_syntax_diagnostics_codes(vec!["too_small_int"])
.add_test_syntax_diagnostics_ranges(vec![Range {
start: Position {
line: 1,
character: 21,
},
end: Position {
line: 1,
character: 22,
},
}]);
test_tree_structure!(
test_builder,
[
Option<Clamp<i8, 10, 300>> =!,
]
);
}
}