texlang_stdlib/
the.rs

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