1use texform_argspec::{ArgForm, ArgSpec, DelimiterToken, ValueKind};
2use texform_interface::syntax_node::ContentMode;
3
4#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
5pub struct ValidateArgspecResult {
6 pub valid: bool,
7 pub error: Option<String>,
8 pub arg_count: Option<usize>,
9 pub parsed: Option<Vec<ParsedArgSpecSlot>>,
10}
11
12#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
13pub struct ParsedArgSpecSlot {
14 pub required: bool,
15 pub no_leading_space: bool,
16 pub nullable: bool,
17 pub kind: ArgSpecKindInfo,
18 pub form: ArgSpecFormInfo,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
22#[serde(tag = "type", rename_all = "lowercase")]
23pub enum ArgSpecKindInfo {
24 Content {
25 mode: RuntimeContentModeInfo,
26 },
27 Delimiter,
28 #[serde(rename = "csname")]
29 CsName,
30 Dimension,
31 Integer,
32 #[serde(rename = "keyval")]
33 KeyVal,
34 Column,
35 Star,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
39#[serde(rename_all = "lowercase")]
40pub enum RuntimeContentModeInfo {
41 Math,
42 Text,
43}
44
45#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
46#[serde(tag = "type", rename_all = "lowercase")]
47pub enum ArgSpecFormInfo {
48 Standard,
49 Star,
50 Group,
51 Delimited {
52 open: DelimiterTokenInfo,
53 close: DelimiterTokenInfo,
54 },
55 Paired {
56 pairs: Vec<DelimiterTokenPairInfo>,
57 },
58}
59
60#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
61#[serde(tag = "type", rename_all = "kebab-case")]
62pub enum DelimiterTokenInfo {
63 Char { value: char },
64 ControlSeq { value: String },
65}
66
67#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
68pub struct DelimiterTokenPairInfo {
69 pub open: DelimiterTokenInfo,
70 pub close: DelimiterTokenInfo,
71}
72
73pub fn validate_argspec(spec: &str) -> ValidateArgspecResult {
74 match texform_argspec::parse_arg_specs(spec, "validate_argspec") {
75 Ok(parsed) => ValidateArgspecResult {
76 valid: true,
77 error: None,
78 arg_count: Some(parsed.len()),
79 parsed: Some(parsed.iter().map(parsed_arg_spec_slot).collect()),
80 },
81 Err(error) => ValidateArgspecResult {
82 valid: false,
83 error: Some(error.to_string()),
84 arg_count: None,
85 parsed: None,
86 },
87 }
88}
89
90pub fn parsed_arg_spec_slot(spec: &ArgSpec) -> ParsedArgSpecSlot {
91 ParsedArgSpecSlot {
92 required: spec.required,
93 no_leading_space: spec.no_leading_space,
94 nullable: spec.nullable,
95 kind: arg_spec_kind_info(spec.kind),
96 form: arg_spec_form_info(&spec.form),
97 }
98}
99
100fn arg_spec_kind_info(kind: ValueKind) -> ArgSpecKindInfo {
101 match kind {
102 ValueKind::Content { mode } => ArgSpecKindInfo::Content {
103 mode: runtime_content_mode_info(mode),
104 },
105 ValueKind::Delimiter => ArgSpecKindInfo::Delimiter,
106 ValueKind::CSName => ArgSpecKindInfo::CsName,
107 ValueKind::Dimension => ArgSpecKindInfo::Dimension,
108 ValueKind::Integer => ArgSpecKindInfo::Integer,
109 ValueKind::KeyVal => ArgSpecKindInfo::KeyVal,
110 ValueKind::Column => ArgSpecKindInfo::Column,
111 ValueKind::Star => ArgSpecKindInfo::Star,
112 }
113}
114
115fn runtime_content_mode_info(mode: ContentMode) -> RuntimeContentModeInfo {
116 match mode {
117 ContentMode::Math => RuntimeContentModeInfo::Math,
118 ContentMode::Text => RuntimeContentModeInfo::Text,
119 }
120}
121
122fn arg_spec_form_info(form: &ArgForm) -> ArgSpecFormInfo {
123 match form {
124 ArgForm::Standard => ArgSpecFormInfo::Standard,
125 ArgForm::Star => ArgSpecFormInfo::Star,
126 ArgForm::Group => ArgSpecFormInfo::Group,
127 ArgForm::Delimited { open, close } => ArgSpecFormInfo::Delimited {
128 open: delimiter_token_info(open),
129 close: delimiter_token_info(close),
130 },
131 ArgForm::Paired { pairs } => ArgSpecFormInfo::Paired {
132 pairs: pairs
133 .iter()
134 .map(|(open, close)| DelimiterTokenPairInfo {
135 open: delimiter_token_info(open),
136 close: delimiter_token_info(close),
137 })
138 .collect(),
139 },
140 }
141}
142
143fn delimiter_token_info(token: &DelimiterToken) -> DelimiterTokenInfo {
144 match token {
145 DelimiterToken::Char(value) => DelimiterTokenInfo::Char { value: *value },
146 DelimiterToken::ControlSeq(value) => DelimiterTokenInfo::ControlSeq {
147 value: value.to_string(),
148 },
149 }
150}