texlang_stdlib/
the.rs

1//! The `\the` primitive
2
3use std::char;
4use texlang::prelude as txl;
5use texlang::traits::*;
6use texlang::*;
7
8pub const THE_DOC: &str = "Output text describing some inputted tokens";
9
10/// Trait satisfied by states that can be used with the control sequence `\the`
11pub trait TheCompatible: TexlangState {
12    fn get_command_ref_for_font(&self, font: types::Font) -> Option<token::CommandRef> {
13        _ = font;
14        None
15    }
16}
17
18/// Get the `\the` expansion primitive.
19pub fn get_the<S: TheCompatible>() -> command::BuiltIn<S> {
20    command::BuiltIn::new_expansion(the_primitive_fn)
21}
22
23fn the_primitive_fn<S: TheCompatible>(
24    the_token: token::Token,
25    input: &mut vm::ExpansionInput<S>,
26) -> txl::Result<()> {
27    let token = input.next_or_err(EndOfInputError {})?;
28    // TeX.2021.465
29    match &token.value() {
30        token::Value::CommandRef(command_ref) => {
31            match input.commands_map().get_command(command_ref) {
32                Some(command::Command::Variable(cmd)) => {
33                    let variable = cmd.clone().resolve(token, input.as_mut())?;
34                    let (state, expansions) = input.state_and_expansions_mut();
35                    match variable.value(state) {
36                        variable::ValueRef::Int(i) => {
37                            write(expansions, the_token, *i);
38                        }
39                        variable::ValueRef::SmallInt(i) => {
40                            write(expansions, the_token, *i);
41                        }
42                        variable::ValueRef::CatCode(i) => {
43                            write(expansions, the_token, (*i as u8) as i32);
44                        }
45                        variable::ValueRef::MathCode(i) => write(expansions, the_token, i.0 as i32),
46                        variable::ValueRef::Dimen(d) => {
47                            write(expansions, the_token, *d);
48                        }
49                        variable::ValueRef::Glue(g) => {
50                            write(expansions, the_token, *g);
51                        }
52                        variable::ValueRef::Font(font) => {
53                            let font = *font;
54                            let command_ref = input.state().get_command_ref_for_font(font).unwrap();
55                            let font_token =
56                                token::Token::new_command_ref(command_ref, the_token.trace_key());
57                            input.back(font_token);
58                        }
59                        variable::ValueRef::TokenList(t) => {
60                            expansions.extend(t.iter().rev());
61                        }
62                    };
63                }
64                Some(command::Command::Character(c)) => {
65                    let i = *c as i32;
66                    write(input.expansions_mut(), the_token, i);
67                }
68                Some(command::Command::MathCharacter(c)) => {
69                    let i = c.0 as i32;
70                    write(input.expansions_mut(), the_token, i);
71                }
72                Some(command::Command::Font(font)) => {
73                    // \the is a no-op for font commands?
74                    // It may return the frozen control sequence and this would
75                    // be visible e.g. in macro matching and equality conditions
76                    // in general.
77                    font_to_tokens(the_token, input, *font);
78                }
79                Some(command::Command::Execution(_, Some(tag))) => {
80                    if input.state().is_current_font_command(*tag) {
81                        font_to_tokens(the_token, input, input.vm().current_font());
82                    } else {
83                        todo!("should return an error")
84                    }
85                }
86                None
87                | Some(
88                    command::Command::Expansion(..)
89                    | command::Command::Macro(..)
90                    | command::Command::Execution(..)
91                    | command::Command::CharacterTokenAlias(..),
92                ) => {
93                    todo!("should return an error")
94                }
95            }
96        }
97        _ => todo!("should return an error"),
98    };
99    Ok(())
100}
101
102fn font_to_tokens<S: TexlangState + TheCompatible>(
103    the_token: token::Token,
104    input: &mut vm::ExpansionInput<S>,
105    font: types::Font,
106) {
107    let command_ref = input.state().get_command_ref_for_font(font).unwrap();
108    let font_token = token::Token::new_command_ref(command_ref, the_token.trace_key());
109    input.back(font_token);
110}
111
112/// Implementation of [`std::fmt::Write`] that writes to a token buffer.
113///
114/// As well as being sort of elegant (?), converting values to tokens this way
115/// avoids all allocations outside of the buffer.
116struct TokenWrite<'a>(&'a mut Vec<token::Token>, token::trace::Key);
117
118impl<'a> std::fmt::Write for TokenWrite<'a> {
119    fn write_str(&mut self, s: &str) -> std::fmt::Result {
120        for c in s.chars() {
121            self.write_char(c)?;
122        }
123        Ok(())
124    }
125    fn write_char(&mut self, c: char) -> std::fmt::Result {
126        let token = if c == ' ' {
127            token::Token::new_space(c, self.1)
128        } else if c.is_ascii_alphabetic() {
129            token::Token::new_letter(c, self.1)
130        } else {
131            token::Token::new_other(c, self.1)
132        };
133        self.0.push(token);
134        Ok(())
135    }
136}
137
138fn write<D: std::fmt::Display>(buffer: &mut Vec<token::Token>, the_token: token::Token, value: D) {
139    let start = buffer.len();
140    use std::fmt::Write;
141    let mut t = TokenWrite(buffer, the_token.trace_key());
142    write!(t, "{value}").expect("the token writer cannot error");
143    buffer[start..].reverse();
144}
145
146#[derive(Debug)]
147struct EndOfInputError;
148
149impl error::EndOfInputError for EndOfInputError {
150    fn doing(&self) -> String {
151        r"determining the argument to \the".into()
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158    impl TheCompatible for texlang_testing::State {}
159}