1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
//! The `\endlinechar` primitive

use texlang::traits::*;
use texlang::*;

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Component {
    end_line_char_raw: i32,
}

impl Default for Component {
    fn default() -> Self {
        Self {
            end_line_char_raw: ('\r' as u32).try_into().unwrap(),
        }
    }
}

/// Get the `\endlinechar` command.
pub fn get_endlinechar<S: HasComponent<Component>>() -> command::BuiltIn<S> {
    variable::Command::new_singleton(
        |state: &S, _: variable::Index| -> &i32 { &state.component().end_line_char_raw },
        |state: &mut S, _: variable::Index| -> &mut i32 {
            &mut state.component_mut().end_line_char_raw
        },
    )
    .into()
}

#[inline]
pub fn end_line_char<S: HasComponent<Component>>(state: &S) -> Option<char> {
    let raw = state.component().end_line_char_raw;
    if (0..128).contains(&raw) {
        // All of the conversions are guaranteed to succeed because
        // raw is in the ASCII range.
        Some(char::try_from(u32::try_from(raw).unwrap()).unwrap())
    } else {
        None
    }
}

#[cfg(test)]
mod test {
    use std::collections::HashMap;

    use super::*;
    use crate::{def, prefix};
    use texlang::vm::implement_has_component;
    use texlang_testing::*;

    #[derive(Default)]
    struct State {
        conditional: Component,
        prefix: prefix::Component,
        testing: TestingComponent,
    }

    impl TexlangState for State {
        fn end_line_char(&self) -> Option<char> {
            end_line_char(self)
        }
    }

    implement_has_component![State {
        conditional: Component,
        prefix: prefix::Component,
        testing: TestingComponent,
    }];

    fn built_in_commands() -> HashMap<&'static str, command::BuiltIn<State>> {
        HashMap::from([("def", def::get_def()), ("endlinechar", get_endlinechar())])
    }

    test_suite![expansion_equality_tests(
        (
            case_1,
            "\\endlinechar=`\\A Hello\nWorld\nMundo\n",
            "Hello WorldAMundoA"
        ),
        (
            case_2,
            "\\endlinechar=-1 Hello\nWorld\nMundo\n",
            "Hello WorldMundo"
        ),
        (
            case_3,
            "\\endlinechar=-1 Hello\nWorld  \nMundo\n",
            "Hello WorldMundo"
        ),
        (case_4, "\\endlinechar=`\\A\nHello\nWorld\n", "Hello WorldA"),
    ),];
}