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}