Skip to main content

mos_parse/
directive.rs

1use mos_core::Diagnostic;
2use mos_core::codes;
3
4use crate::parser::Parser;
5use crate::support::{
6    next_char_boundary, normalize_raw_text, skip_set_ws, skip_to_comma, strip_leading_label,
7};
8use crate::{DirectiveKind, Item, LengthUnit, RawBlockKind, SetArg, SetValue};
9
10impl Parser<'_> {
11    pub(crate) fn parse_directive_block(&mut self, kw: &'static str) {
12        if kw == "set" {
13            self.parse_set_block();
14        } else if kw == "pre" || kw == "code" {
15            self.parse_raw_block(kw);
16        } else {
17            self.parse_call_block(kw);
18        }
19    }
20
21    fn parse_raw_block(&mut self, kw: &'static str) {
22        let (line_start, _content_end, _line_end) = self.current_line_bounds();
23        let bytes = self.src.as_bytes();
24        debug_assert!(self.src[line_start + 1..].starts_with(kw));
25        let mut i = line_start + 1 + kw.len();
26        while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
27            i += 1;
28        }
29        let mut args = Vec::new();
30        if i < bytes.len() && bytes[i] == b'(' {
31            let Some(args_end) = self.scan_balanced_parens(i) else {
32                self.diagnostics.push(
33                    Diagnostic::simple(
34                        &codes::MOS0016,
35                        None,
36                        format!("unterminated `#{kw}(...)` block"),
37                    )
38                    .with_span(self.span(line_start, bytes.len())),
39                );
40                self.pos = bytes.len();
41                return;
42            };
43            args = self.parse_set_body(i + 1, args_end - 1, true);
44            i = args_end;
45            while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
46                i += 1;
47            }
48        }
49        if i >= bytes.len() || bytes[i] != b'[' {
50            self.diagnostics.push(
51                Diagnostic::simple(
52                    &codes::MOS0013,
53                    None,
54                    format!(
55                        "expected long-bracket raw body after `#{kw}` (for example `#{kw}[[...]]`)"
56                    ),
57                )
58                .with_span(self.span(line_start, i)),
59            );
60            self.skip_line();
61            return;
62        }
63        let Some((body_start, eq_count)) = self.scan_long_raw_open(i) else {
64            self.diagnostics.push(
65                Diagnostic::simple(
66                    &codes::MOS0013,
67                    None,
68                    format!("raw `#{kw}` blocks require long brackets like `#{kw}[[...]]`"),
69                )
70                .with_span(self.span(line_start, i + 1)),
71            );
72            self.skip_line();
73            return;
74        };
75        if let Some((body_end, close_end)) = self.scan_long_raw_close(body_start, eq_count) {
76            let text = normalize_raw_text(&self.src[body_start..body_end]);
77            let (_, content_end, _) = self.line_bounds_from(close_end);
78            let (after_label, parsed_label) = strip_leading_label(self.src, close_end, content_end);
79            let label_span = parsed_label
80                .as_ref()
81                .map(|label| self.span(label.start, label.end));
82            let label = parsed_label.map(|label| label.text);
83            let kind = if kw == "code" {
84                RawBlockKind::Code
85            } else {
86                RawBlockKind::Pre
87            };
88            self.items.push(Item::RawBlock {
89                kind,
90                args,
91                text,
92                label,
93                label_span,
94                span: self.span(line_start, after_label),
95            });
96            self.pos = after_label;
97            self.finish_raw_block_line(bytes, kw, content_end);
98        } else {
99            self.diagnostics.push(
100                Diagnostic::simple(
101                    &codes::MOS0016,
102                    None,
103                    format!("unterminated raw `#{kw}` long-bracket block"),
104                )
105                .with_span(self.span(line_start, bytes.len())),
106            );
107            self.pos = bytes.len();
108        }
109    }
110
111    fn finish_raw_block_line(&mut self, bytes: &[u8], kw: &'static str, content_end: usize) {
112        while self.pos < bytes.len() && (bytes[self.pos] == b' ' || bytes[self.pos] == b'\t') {
113            self.pos += 1;
114        }
115        if self.pos >= bytes.len() {
116        } else if bytes[self.pos] == b'\n' {
117            self.pos += 1;
118        } else if bytes[self.pos] == b'\r' && bytes.get(self.pos + 1) == Some(&b'\n') {
119            self.pos += 2;
120        } else {
121            self.diagnostics.push(
122                Diagnostic::simple(
123                    &codes::MOS0019,
124                    None,
125                    format!("unexpected trailing content after raw `#{kw}` block"),
126                )
127                .with_span(self.span(self.pos, content_end)),
128            );
129        }
130    }
131
132    fn parse_set_block(&mut self) {
133        let (line_start, _content_end, _line_end) = self.current_line_bounds();
134        let bytes = self.src.as_bytes();
135        debug_assert!(self.src[line_start..].starts_with("#set"));
136        let mut i = line_start + "#set".len();
137        while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
138            i += 1;
139        }
140        let name_start = i;
141        while i < bytes.len() && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'_') {
142            i += 1;
143        }
144        let name = self.src[name_start..i].to_owned();
145        if name.is_empty() {
146            self.diagnostics.push(
147                Diagnostic::simple(&codes::MOS0010, None, "expected an identifier after `#set`")
148                    .with_span(self.span(line_start, line_start + "#set".len())),
149            );
150            self.skip_line();
151            return;
152        }
153        while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
154            i += 1;
155        }
156        if i >= bytes.len() || bytes[i] != b'(' {
157            self.diagnostics.push(
158                Diagnostic::simple(
159                    &codes::MOS0013,
160                    None,
161                    format!("expected `(` after `#set {name}`"),
162                )
163                .with_span(self.span(name_start, i)),
164            );
165            self.skip_line();
166            return;
167        }
168        self.finish_directive_block(line_start, i, DirectiveKind::Set, name, "set", false);
169    }
170
171    fn parse_call_block(&mut self, kw: &'static str) {
172        let (line_start, _content_end, _line_end) = self.current_line_bounds();
173        let bytes = self.src.as_bytes();
174        debug_assert!(self.src[line_start + 1..].starts_with(kw));
175        let mut i = line_start + 1 + kw.len();
176        while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
177            i += 1;
178        }
179        if i >= bytes.len() || bytes[i] != b'(' {
180            self.diagnostics.push(
181                Diagnostic::simple(&codes::MOS0013, None, format!("expected `(` after `#{kw}`"))
182                    .with_span(self.span(line_start, i)),
183            );
184            self.skip_line();
185            return;
186        }
187        let kind = match kw {
188            "image" => DirectiveKind::Image,
189            "figure" => DirectiveKind::Figure,
190            "bibliography" => DirectiveKind::Bibliography,
191            other => {
192                debug_assert!(false, "parse_call_block: unexpected keyword `{other}`");
193                DirectiveKind::Set
194            }
195        };
196        self.finish_directive_block(line_start, i, kind, kw.to_owned(), kw, true);
197    }
198
199    fn finish_directive_block(
200        &mut self,
201        line_start: usize,
202        paren_pos: usize,
203        kind: DirectiveKind,
204        name: String,
205        display_kw: &str,
206        allow_positional: bool,
207    ) {
208        let bytes = self.src.as_bytes();
209        if let Some(end) = self.scan_balanced_parens(paren_pos) {
210            let args = self.parse_set_body(paren_pos + 1, end - 1, allow_positional);
211            self.items.push(Item::Set {
212                kind,
213                name,
214                args,
215                span: self.span(line_start, end),
216            });
217            self.pos = end;
218            while self.pos < bytes.len() && (bytes[self.pos] == b' ' || bytes[self.pos] == b'\t') {
219                self.pos += 1;
220            }
221            if self.pos >= bytes.len() {
222            } else if bytes[self.pos] == b'\n' {
223                self.pos += 1;
224            } else if bytes[self.pos] == b'\r' && bytes.get(self.pos + 1) == Some(&b'\n') {
225                self.pos += 2;
226            } else {
227                let (_, content_end, _) = self.current_line_bounds();
228                self.diagnostics.push(
229                    Diagnostic::simple(
230                        &codes::MOS0019,
231                        None,
232                        format!("unexpected trailing content after `#{display_kw} ... )`"),
233                    )
234                    .with_span(self.span(self.pos, content_end)),
235                );
236            }
237        } else {
238            self.diagnostics.push(
239                Diagnostic::simple(
240                    &codes::MOS0016,
241                    None,
242                    format!("unterminated `#{display_kw}(...)` block"),
243                )
244                .with_span(self.span(line_start, bytes.len())),
245            );
246            self.pos = bytes.len();
247        }
248    }
249
250    fn scan_balanced_parens(&self, start: usize) -> Option<usize> {
251        let bytes = self.src.as_bytes();
252        debug_assert_eq!(bytes.get(start), Some(&b'('));
253        let mut depth: u32 = 0;
254        let mut i = start;
255        let mut in_string = false;
256        while i < bytes.len() {
257            let b = bytes[i];
258            if in_string {
259                if b == b'\\' && i + 1 < bytes.len() {
260                    i += 2;
261                    continue;
262                }
263                if b == b'"' {
264                    in_string = false;
265                }
266                i += 1;
267                continue;
268            }
269            match b {
270                b'"' => in_string = true,
271                b'(' => depth += 1,
272                b')' => {
273                    depth -= 1;
274                    if depth == 0 {
275                        return Some(i + 1);
276                    }
277                }
278                _ => {}
279            }
280            i += 1;
281        }
282        None
283    }
284
285    fn scan_long_raw_open(&self, start: usize) -> Option<(usize, usize)> {
286        let bytes = self.src.as_bytes();
287        debug_assert_eq!(bytes.get(start), Some(&b'['));
288        let mut i = start + 1;
289        while i < bytes.len() && bytes[i] == b'=' {
290            i += 1;
291        }
292        if i >= bytes.len() || bytes[i] != b'[' {
293            return None;
294        }
295        Some((i + 1, i - start - 1))
296    }
297
298    fn scan_long_raw_close(&self, start: usize, eq_count: usize) -> Option<(usize, usize)> {
299        let bytes = self.src.as_bytes();
300        let mut i = start;
301        while i < bytes.len() {
302            if bytes[i] == b']' {
303                let eq_start = i + 1;
304                let eq_end = eq_start + eq_count;
305                if eq_end < bytes.len()
306                    && bytes[eq_start..eq_end].iter().all(|b| *b == b'=')
307                    && bytes[eq_end] == b']'
308                {
309                    return Some((i, eq_end + 1));
310                }
311            }
312            i += 1;
313        }
314        None
315    }
316
317    fn parse_set_body(&mut self, start: usize, end: usize, allow_positional: bool) -> Vec<SetArg> {
318        let bytes = self.src.as_bytes();
319        let mut args: Vec<SetArg> = Vec::new();
320        let mut i = start;
321        let mut first = true;
322        loop {
323            i = skip_set_ws(bytes, i, end);
324            if i >= end {
325                break;
326            }
327            if allow_positional && first && bytes[i] == b'"' {
328                let value_start = i;
329                let parsed = self.parse_set_value(&mut i, end);
330                let value_span = self.span(value_start, i);
331                if let Some(value) = parsed {
332                    args.push(SetArg::Positional { value, value_span });
333                }
334                first = false;
335                i = self.consume_arg_separator(bytes, i, end);
336                continue;
337            }
338            first = false;
339            let key_start = i;
340            while i < end && (bytes[i].is_ascii_alphanumeric() || matches!(bytes[i], b'_' | b'-')) {
341                i += 1;
342            }
343            if i == key_start {
344                self.diagnostics.push(
345                    Diagnostic::simple(
346                        &codes::MOS0025,
347                        None,
348                        "expected `key: value` in directive arguments",
349                    )
350                    .with_span(self.span(i, next_char_boundary(self.src, i).min(end))),
351                );
352                i = skip_to_comma(bytes, i, end);
353                if i < end && bytes[i] == b',' {
354                    i += 1;
355                }
356                continue;
357            }
358            let key = self.src[key_start..i].to_owned();
359            let key_span = self.span(key_start, i);
360            i = skip_set_ws(bytes, i, end);
361            if i >= end || bytes[i] != b':' {
362                self.diagnostics.push(
363                    Diagnostic::simple(
364                        &codes::MOS0025,
365                        None,
366                        format!("expected `:` after `{key}` in directive arguments"),
367                    )
368                    .with_span(key_span.clone()),
369                );
370                i = skip_to_comma(bytes, i, end);
371                if i < end && bytes[i] == b',' {
372                    i += 1;
373                }
374                continue;
375            }
376            i += 1;
377            i = skip_set_ws(bytes, i, end);
378            let value_start = i;
379            let parsed = self.parse_set_value(&mut i, end);
380            let value_span = self.span(value_start, i);
381            if let Some(value) = parsed {
382                args.push(SetArg::Named {
383                    key,
384                    value,
385                    key_span,
386                    value_span,
387                });
388            }
389            i = self.consume_arg_separator(bytes, i, end);
390        }
391        args
392    }
393
394    fn consume_arg_separator(&mut self, bytes: &[u8], mut i: usize, end: usize) -> usize {
395        i = skip_set_ws(bytes, i, end);
396        if i < end {
397            if bytes[i] == b',' {
398                i += 1;
399            } else {
400                self.diagnostics.push(
401                    Diagnostic::simple(
402                        &codes::MOS0025,
403                        None,
404                        "expected `,` or `)` between directive arguments",
405                    )
406                    .with_span(self.span(i, next_char_boundary(self.src, i).min(end))),
407                );
408                i = skip_to_comma(bytes, i, end);
409                if i < end && bytes[i] == b',' {
410                    i += 1;
411                }
412            }
413        }
414        i
415    }
416
417    fn parse_set_value(&mut self, i: &mut usize, end: usize) -> Option<SetValue> {
418        let bytes = self.src.as_bytes();
419        if *i >= end {
420            self.diagnostics.push(
421                Diagnostic::simple(
422                    &codes::MOS0022,
423                    None,
424                    "expected a value in directive arguments",
425                )
426                .with_span(self.span(*i, *i)),
427            );
428            return None;
429        }
430        let b = bytes[*i];
431        if b == b'"' {
432            return self.parse_string_value(i, end);
433        }
434        if b == b'-' || b.is_ascii_digit() {
435            return self.parse_number_value(i, end);
436        }
437        if b.is_ascii_alphabetic() {
438            let id_start = *i;
439            while *i < end
440                && (bytes[*i].is_ascii_alphanumeric() || matches!(bytes[*i], b'_' | b'-'))
441            {
442                *i += 1;
443            }
444            return Some(SetValue::Ident(self.src[id_start..*i].to_owned()));
445        }
446        let ch_end = next_char_boundary(self.src, *i).min(end);
447        self.diagnostics.push(
448            Diagnostic::simple(
449                &codes::MOS0022,
450                None,
451                format!(
452                    "unexpected character `{}` in directive value",
453                    &self.src[*i..ch_end]
454                ),
455            )
456            .with_span(self.span(*i, ch_end)),
457        );
458        *i = ch_end;
459        None
460    }
461
462    fn parse_string_value(&mut self, i: &mut usize, end: usize) -> Option<SetValue> {
463        let bytes = self.src.as_bytes();
464        let start = *i;
465        *i += 1;
466        let mut out = String::new();
467        while *i < end {
468            let c = bytes[*i];
469            if c == b'\\' && *i + 1 < end {
470                let esc = bytes[*i + 1];
471                match esc {
472                    b'\\' => out.push('\\'),
473                    b'"' => out.push('"'),
474                    b'n' => out.push('\n'),
475                    b't' => out.push('\t'),
476                    b'r' => out.push('\r'),
477                    _ => {
478                        let esc_start = *i + 1;
479                        let esc_end = next_char_boundary(self.src, esc_start);
480                        self.diagnostics.push(
481                            Diagnostic::simple(
482                                &codes::MOS0022,
483                                None,
484                                format!(
485                                    "unknown escape sequence `\\{}` in string",
486                                    &self.src[esc_start..esc_end]
487                                ),
488                            )
489                            .with_span(self.span(*i, esc_end)),
490                        );
491                        out.push_str(&self.src[esc_start..esc_end]);
492                        *i = esc_end;
493                        continue;
494                    }
495                }
496                *i += 2;
497                continue;
498            }
499            if c == b'"' {
500                *i += 1;
501                return Some(SetValue::Str(out));
502            }
503            let ch_start = *i;
504            let ch_end = next_char_boundary(self.src, ch_start);
505            out.push_str(&self.src[ch_start..ch_end]);
506            *i = ch_end;
507        }
508        self.diagnostics.push(
509            Diagnostic::simple(&codes::MOS0022, None, "unterminated string literal")
510                .with_span(self.span(start, end)),
511        );
512        None
513    }
514
515    fn parse_number_value(&mut self, i: &mut usize, end: usize) -> Option<SetValue> {
516        let bytes = self.src.as_bytes();
517        let num_start = *i;
518        if bytes[*i] == b'-' {
519            *i += 1;
520        }
521        let int_start = *i;
522        while *i < end && bytes[*i].is_ascii_digit() {
523            *i += 1;
524        }
525        let mut is_float = false;
526        if *i < end && bytes[*i] == b'.' && *i + 1 < end && bytes[*i + 1].is_ascii_digit() {
527            is_float = true;
528            *i += 1;
529            while *i < end && bytes[*i].is_ascii_digit() {
530                *i += 1;
531            }
532        }
533        if *i == int_start {
534            self.diagnostics.push(
535                Diagnostic::simple(
536                    &codes::MOS0022,
537                    None,
538                    "expected a number after `-` in directive value",
539                )
540                .with_span(self.span(num_start, *i)),
541            );
542            return None;
543        }
544        let num_end = *i;
545        let unit_start = *i;
546        while *i < end && bytes[*i].is_ascii_alphabetic() {
547            *i += 1;
548        }
549        let unit = &self.src[unit_start..*i];
550        if unit.is_empty() {
551            let text = &self.src[num_start..num_end];
552            if is_float {
553                return text.parse::<f64>().ok().map(SetValue::Float).or_else(|| {
554                    self.diagnostics.push(
555                        Diagnostic::simple(
556                            &codes::MOS0022,
557                            None,
558                            format!("malformed number `{text}`"),
559                        )
560                        .with_span(self.span(num_start, num_end)),
561                    );
562                    None
563                });
564            }
565            return text.parse::<i64>().ok().map(SetValue::Int).or_else(|| {
566                self.diagnostics.push(
567                    Diagnostic::simple(
568                        &codes::MOS0022,
569                        None,
570                        format!("malformed integer `{text}`"),
571                    )
572                    .with_span(self.span(num_start, num_end)),
573                );
574                None
575            });
576        }
577        let length_unit = match unit {
578            "mm" => LengthUnit::Mm,
579            "pt" => LengthUnit::Pt,
580            "em" => LengthUnit::Em,
581            _ => {
582                self.diagnostics.push(
583                    Diagnostic::simple(
584                        &codes::MOS0022,
585                        None,
586                        format!("unknown length unit `{unit}` (expected mm, pt, or em)"),
587                    )
588                    .with_span(self.span(unit_start, *i)),
589                );
590                return None;
591            }
592        };
593        let value = self.src[num_start..num_end].parse::<f64>().ok();
594        value.map(|v| SetValue::Length(v, length_unit)).or_else(|| {
595            self.diagnostics.push(
596                Diagnostic::simple(
597                    &codes::MOS0022,
598                    None,
599                    format!("malformed length value `{}`", &self.src[num_start..num_end]),
600                )
601                .with_span(self.span(num_start, num_end)),
602            );
603            None
604        })
605    }
606}