1use crate::token::*;
8
9#[derive(Debug, Clone, PartialEq)]
11pub struct Spanned {
12 pub tok: Token,
13 pub line: u32,
14 pub col: u32,
15}
16
17#[derive(Debug, Clone, PartialEq)]
19pub struct LexError {
20 pub msg: String,
21 pub line: u32,
22 pub col: u32,
23}
24
25impl std::fmt::Display for LexError {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 write!(f, "{}:{}: {}", self.line, self.col, self.msg)
28 }
29}
30
31pub fn lex(src: &str) -> Result<Vec<Spanned>, LexError> {
33 Lexer::new(src).run()
34}
35
36struct Lexer {
37 chars: Vec<char>,
38 pos: usize,
39 line: u32,
40 col: u32,
41 out: Vec<Spanned>,
42}
43
44impl Lexer {
45 fn new(src: &str) -> Self {
46 Lexer {
47 chars: src.chars().collect(),
48 pos: 0,
49 line: 1,
50 col: 1,
51 out: Vec::new(),
52 }
53 }
54
55 fn peek(&self) -> Option<char> {
56 self.chars.get(self.pos).copied()
57 }
58 fn peek_at(&self, n: usize) -> Option<char> {
59 self.chars.get(self.pos + n).copied()
60 }
61
62 fn bump(&mut self) -> Option<char> {
64 let c = self.chars.get(self.pos).copied()?;
65 self.pos += 1;
66 if c == '\n' {
67 self.line += 1;
68 self.col = 1;
69 } else {
70 self.col += 1;
71 }
72 Some(c)
73 }
74
75 fn err<T>(&self, msg: impl Into<String>) -> Result<T, LexError> {
76 Err(LexError {
77 msg: msg.into(),
78 line: self.line,
79 col: self.col,
80 })
81 }
82
83 fn run(mut self) -> Result<Vec<Spanned>, LexError> {
84 loop {
85 loop {
88 match self.peek() {
89 Some(' ') | Some('\t') | Some('\r') => {
90 self.bump();
91 }
92 Some('#') => {
93 while let Some(c) = self.peek() {
94 if c == '\n' {
95 break;
96 }
97 self.bump();
98 }
99 }
100 Some('\\') => {
101 let save = (self.pos, self.line, self.col);
103 self.bump();
104 while matches!(self.peek(), Some(' ') | Some('\t') | Some('\r')) {
105 self.bump();
106 }
107 if self.peek() == Some('\n') {
108 self.bump();
109 } else {
110 self.pos = save.0;
113 self.line = save.1;
114 self.col = save.2;
115 break;
116 }
117 }
118 _ => break,
119 }
120 }
121
122 let (line, col) = (self.line, self.col);
123 let c = match self.peek() {
124 None => {
125 self.push(Token::Eof, line, col);
126 return Ok(self.out);
127 }
128 Some(c) => c,
129 };
130
131 let tok = if c == '\n' || c == ';' {
132 self.bump();
133 Token::Newline
134 } else if c == '"' {
135 self.lex_string()?
136 } else if c == '$' {
137 self.lex_arg()?
138 } else if c.is_ascii_digit()
139 || (c == '.' && self.peek_at(1).is_some_and(|d| d.is_ascii_digit()))
140 {
141 self.lex_number()?
142 } else if c == '.' {
143 self.lex_dot()?
144 } else if c.is_alphabetic() || c == '_' {
145 self.lex_word()
146 } else {
147 match self.lex_operator()? {
148 Some(t) => t,
149 None => continue, }
151 };
152 self.push(tok, line, col);
153 }
154 }
155
156 fn push(&mut self, tok: Token, line: u32, col: u32) {
157 self.out.push(Spanned { tok, line, col });
158 }
159
160 fn lex_string(&mut self) -> Result<Token, LexError> {
161 self.bump(); let mut s = String::new();
163 loop {
164 match self.bump() {
165 None => return self.err("unterminated string literal"),
166 Some('"') => break,
167 Some('\\') => {
168 s.push('\\');
171 match self.bump() {
172 Some(c) => s.push(c),
173 None => return self.err("unterminated string literal"),
174 }
175 }
176 Some(c) => s.push(c),
177 }
178 }
179 Ok(Token::Str(s))
180 }
181
182 fn lex_arg(&mut self) -> Result<Token, LexError> {
183 self.bump(); let mut n = String::new();
185 while let Some(c) = self.peek() {
186 if c.is_ascii_digit() {
187 n.push(c);
188 self.bump();
189 } else {
190 break;
191 }
192 }
193 if n.is_empty() {
194 return self.err("expected digit after `$`");
195 }
196 Ok(Token::Arg(n.parse().unwrap()))
197 }
198
199 fn lex_number(&mut self) -> Result<Token, LexError> {
200 let mut s = String::new();
201 if self.peek() == Some('.') {
202 s.push('0'); }
204 while let Some(c) = self.peek() {
205 if c.is_ascii_digit() {
206 s.push(c);
207 self.bump();
208 } else {
209 break;
210 }
211 }
212 if self.peek() == Some('.') && self.peek_at(1).is_some_and(|d| d.is_ascii_digit()) {
215 s.push('.');
216 self.bump();
217 while let Some(c) = self.peek() {
218 if c.is_ascii_digit() {
219 s.push(c);
220 self.bump();
221 } else {
222 break;
223 }
224 }
225 }
226 if matches!(self.peek(), Some('e') | Some('E'))
228 && (self.peek_at(1).is_some_and(|d| d.is_ascii_digit())
229 || (matches!(self.peek_at(1), Some('+') | Some('-'))
230 && self.peek_at(2).is_some_and(|d| d.is_ascii_digit())))
231 {
232 s.push('e');
233 self.bump();
234 if matches!(self.peek(), Some('+') | Some('-')) {
235 s.push(self.bump().unwrap());
236 }
237 while let Some(c) = self.peek() {
238 if c.is_ascii_digit() {
239 s.push(c);
240 self.bump();
241 } else {
242 break;
243 }
244 }
245 }
246 match s.parse::<f64>() {
247 Ok(v) => Ok(Token::Float(v)),
248 Err(_) => self.err(format!("invalid number `{s}`")),
249 }
250 }
251
252 fn lex_dot(&mut self) -> Result<Token, LexError> {
255 let save = (self.pos, self.line, self.col);
256 self.bump(); let mut w = String::new();
259 while let Some(c) = self.peek() {
260 if c.is_alphanumeric() || c == '_' {
261 w.push(c);
262 self.bump();
263 } else {
264 break;
265 }
266 }
267 if let Some(tok) = dot_keyword(&w) {
268 Ok(tok)
269 } else {
270 self.pos = save.0 + 1;
273 self.line = save.1;
274 self.col = save.2 + 1;
275 Ok(Token::Dot)
276 }
277 }
278
279 fn lex_word(&mut self) -> Token {
280 let mut w = String::new();
281 while let Some(c) = self.peek() {
282 if c.is_alphanumeric() || c == '_' {
283 w.push(c);
284 self.bump();
285 } else {
286 break;
287 }
288 }
289 word_keyword(&w)
290 }
291
292 fn lex_operator(&mut self) -> Result<Option<Token>, LexError> {
293 let c = self.bump().unwrap();
294 let next = self.peek();
295 let tok = match c {
296 '(' => Token::Lparen,
297 ')' => Token::Rparen,
298 ',' => Token::Comma,
299 '^' => Token::Caret,
300 '{' => Token::LeftBrace,
301 '}' => Token::RightBrace,
302 ']' => Token::RightBrack,
303 '`' => Token::LeftQuote,
304 '\'' => Token::RightQuote,
305 '[' => {
306 if next == Some(']') {
307 self.bump();
308 Token::Block
309 } else {
310 Token::LeftBrack
311 }
312 }
313 ':' => self.two('=', Token::ColonEq, Token::Colon),
314 '=' => self.two('=', Token::EqEq, Token::Eq),
315 '!' => self.two('=', Token::Neq, Token::Not),
316 '+' => self.two('=', Token::PlusEq, Token::Plus),
317 '*' => self.two('=', Token::MultEq, Token::Mult),
318 '/' => self.two('=', Token::DivEq, Token::Div),
319 '%' => self.two('=', Token::RemEq, Token::Percent),
320 '>' => self.two('=', Token::Ge, Token::Gt),
321 '&' => self.two('&', Token::AndAnd, Token::Ampersand),
322 '|' => {
323 if next == Some('|') {
324 self.bump();
325 Token::OrOr
326 } else {
327 return self.err("unexpected `|`");
328 }
329 }
330 '-' => match next {
331 Some('>') => {
332 self.bump();
333 Token::Arrow(Arrow::Right)
334 }
335 Some('=') => {
336 self.bump();
337 Token::MinusEq
338 }
339 _ => Token::Minus,
340 },
341 '<' => match next {
342 Some('=') => {
343 self.bump();
344 Token::Le
345 }
346 Some('-') => {
347 self.bump();
348 if self.peek() == Some('>') {
349 self.bump();
350 Token::Arrow(Arrow::Double)
351 } else {
352 Token::Arrow(Arrow::Left)
353 }
354 }
355 _ => Token::Lt,
356 },
357 other => return self.err(format!("unexpected character `{other}`")),
358 };
359 Ok(Some(tok))
360 }
361
362 fn two(&mut self, c: char, yes: Token, no: Token) -> Token {
364 if self.peek() == Some(c) {
365 self.bump();
366 yes
367 } else {
368 no
369 }
370 }
371}
372
373fn word_keyword(w: &str) -> Token {
376 use Token::*;
377 match w {
378 "box" => Prim(self::Prim::Box),
380 "circle" => Prim(self::Prim::Circle),
381 "ellipse" => Prim(self::Prim::Ellipse),
382 "arc" => Prim(self::Prim::Arc),
383 "line" => Prim(self::Prim::Line),
384 "arrow" => Prim(self::Prim::Arrow),
385 "move" => Prim(self::Prim::Move),
386 "spline" => Prim(self::Prim::Spline),
387 "up" => Dir(self::Dir::Up),
389 "down" => Dir(self::Dir::Down),
390 "right" => Dir(self::Dir::Right),
391 "left" => Dir(self::Dir::Left),
392 "height" | "ht" => Kw(self::Kw::Ht),
394 "width" | "wid" => Kw(self::Kw::Wid),
395 "radius" | "rad" => Kw(self::Kw::Rad),
396 "diameter" | "diam" => Kw(self::Kw::Diam),
397 "thickness" | "thick" => Kw(self::Kw::Thick),
398 "scaled" => Kw(self::Kw::Scaled),
399 "from" => Kw(self::Kw::From),
400 "to" => Kw(self::Kw::To),
401 "at" => Kw(self::Kw::At),
402 "with" => Kw(self::Kw::With),
403 "by" => Kw(self::Kw::By),
404 "then" => Kw(self::Kw::Then),
405 "cw" => Kw(self::Kw::Cw),
406 "ccw" => Kw(self::Kw::Ccw),
407 "continue" => Kw(self::Kw::Continue),
408 "chop" => Kw(self::Kw::Chop),
409 "same" => Kw(self::Kw::Same),
410 "of" => Kw(self::Kw::Of),
411 "the" => Kw(self::Kw::The),
412 "way" => Kw(self::Kw::Way),
413 "between" => Kw(self::Kw::Between),
414 "and" => Kw(self::Kw::And),
415 "last" => Kw(self::Kw::Last),
416 "fill" | "filled" => Kw(self::Kw::Fill),
417 "st" | "nd" | "rd" | "th" => Kw(self::Kw::Nth),
418 "Here" => Kw(self::Kw::Here),
419 "top" => Corner(self::Corner::N),
421 "bottom" => Corner(self::Corner::S),
422 "start" => Corner(self::Corner::Start),
423 "end" => Corner(self::Corner::End),
424 "print" => Kw(self::Kw::Print),
426 "copy" => Kw(self::Kw::Copy),
427 "reset" => Kw(self::Kw::Reset),
428 "exec" => Kw(self::Kw::Exec),
429 "sh" => Kw(self::Kw::Sh),
430 "command" => Kw(self::Kw::Command),
431 "define" => Kw(self::Kw::Define),
432 "undefine" | "undef" => Kw(self::Kw::Undef),
433 "rand" => Kw(self::Kw::Rand),
434 "if" => Kw(self::Kw::If),
435 "else" => Kw(self::Kw::Else),
436 "for" => Kw(self::Kw::For),
437 "do" => Kw(self::Kw::Do),
438 "sprintf" => Kw(self::Kw::Sprintf),
439 "animate" => Kw(self::Kw::Animate),
441 "after" => Kw(self::Kw::After),
442 "delay" => Kw(self::Kw::Delay),
443 "solid" => LineType(self::LineType::Solid),
445 "dotted" => LineType(self::LineType::Dotted),
446 "dashed" => LineType(self::LineType::Dashed),
447 "invis" | "invisible" => LineType(self::LineType::Invis),
448 "color" | "colour" | "colored" | "coloured" => Color(self::Color::Colored),
450 "outline" | "outlined" => Color(self::Color::Outlined),
451 "shade" | "shaded" => Color(self::Color::Shaded),
452 "center" | "centre" => TextPos(self::TextPos::Center),
454 "ljust" => TextPos(self::TextPos::Ljust),
455 "rjust" => TextPos(self::TextPos::Rjust),
456 "above" => TextPos(self::TextPos::Above),
457 "below" => TextPos(self::TextPos::Below),
458 "abs" => Func1(self::Func1::Abs),
460 "acos" => Func1(self::Func1::Acos),
461 "asin" => Func1(self::Func1::Asin),
462 "cos" => Func1(self::Func1::Cos),
463 "exp" => Func1(self::Func1::Exp),
464 "expe" => Func1(self::Func1::Expe),
465 "int" => Func1(self::Func1::Int),
466 "log" => Func1(self::Func1::Log),
467 "loge" => Func1(self::Func1::Loge),
468 "sign" => Func1(self::Func1::Sign),
469 "sin" => Func1(self::Func1::Sin),
470 "sqrt" => Func1(self::Func1::Sqrt),
471 "tan" => Func1(self::Func1::Tan),
472 "floor" => Func1(self::Func1::Floor),
473 "atan2" => Func2(self::Func2::Atan2),
475 "max" => Func2(self::Func2::Max),
476 "min" => Func2(self::Func2::Min),
477 "pmod" => Func2(self::Func2::Pmod),
478 "arcrad" => EnvVar(self::EnvVar::Arcrad),
480 "arrowht" => EnvVar(self::EnvVar::Arrowht),
481 "arrowwid" => EnvVar(self::EnvVar::Arrowwid),
482 "boxht" => EnvVar(self::EnvVar::Boxht),
483 "boxrad" => EnvVar(self::EnvVar::Boxrad),
484 "boxwid" => EnvVar(self::EnvVar::Boxwid),
485 "circlerad" => EnvVar(self::EnvVar::Circlerad),
486 "dashwid" => EnvVar(self::EnvVar::Dashwid),
487 "ellipseht" => EnvVar(self::EnvVar::Ellipseht),
488 "ellipsewid" => EnvVar(self::EnvVar::Ellipsewid),
489 "lineht" => EnvVar(self::EnvVar::Lineht),
490 "linewid" => EnvVar(self::EnvVar::Linewid),
491 "moveht" => EnvVar(self::EnvVar::Moveht),
492 "movewid" => EnvVar(self::EnvVar::Movewid),
493 "textht" => EnvVar(self::EnvVar::Textht),
494 "textoffset" => EnvVar(self::EnvVar::Textoffset),
495 "textwid" => EnvVar(self::EnvVar::Textwid),
496 "arrowhead" => EnvVar(self::EnvVar::Arrowhead),
497 "fillval" => EnvVar(self::EnvVar::Fillval),
498 "linethick" => EnvVar(self::EnvVar::Linethick),
499 "maxpsht" => EnvVar(self::EnvVar::Maxpsht),
500 "maxpswid" => EnvVar(self::EnvVar::Maxpswid),
501 "scale" => EnvVar(self::EnvVar::Scale),
502 _ => {
504 if w.chars().next().is_some_and(|c| c.is_ascii_uppercase()) {
505 Label(w.to_string())
506 } else {
507 Name(w.to_string())
508 }
509 }
510 }
511}
512
513fn dot_keyword(w: &str) -> Option<Token> {
516 use Token::*;
517 let t = match w {
518 "PS" => DotPS,
519 "PE" => DotPE,
520 "x" => DotX,
521 "y" => DotY,
522 "ne" => Corner(self::Corner::Ne),
524 "se" => Corner(self::Corner::Se),
525 "nw" => Corner(self::Corner::Nw),
526 "sw" => Corner(self::Corner::Sw),
527 "n" | "t" | "top" | "north" => Corner(self::Corner::N),
528 "s" | "b" | "bot" | "bottom" | "south" => Corner(self::Corner::S),
529 "e" | "r" | "east" | "right" => Corner(self::Corner::E),
530 "w" | "l" | "west" | "left" => Corner(self::Corner::W),
531 "start" => Corner(self::Corner::Start),
532 "end" => Corner(self::Corner::End),
533 "c" | "center" | "centre" => Corner(self::Corner::Center),
534 "ht" | "height" => Param(self::Param::Height),
536 "wid" | "width" => Param(self::Param::Width),
537 "rad" | "radius" => Param(self::Param::Radius),
538 "diam" | "diameter" => Param(self::Param::Diameter),
539 "thick" | "thickness" => Param(self::Param::Thickness),
540 "len" | "length" => Param(self::Param::Length),
541 _ => return None,
542 };
543 Some(t)
544}
545
546#[cfg(test)]
547mod tests {
548 use super::*;
549
550 fn toks(src: &str) -> Vec<Token> {
551 lex(src).unwrap().into_iter().map(|s| s.tok).collect()
552 }
553
554 #[test]
555 fn basic_box() {
556 assert_eq!(
557 toks("box \"hi\""),
558 vec![Token::Prim(Prim::Box), Token::Str("hi".into()), Token::Eof]
559 );
560 }
561
562 #[test]
563 fn label_vs_name() {
564 assert_eq!(
565 toks("Start: boxwid"),
566 vec![
567 Token::Label("Start".into()),
568 Token::Colon,
569 Token::EnvVar(EnvVar::Boxwid),
570 Token::Eof
571 ]
572 );
573 assert_eq!(toks("myvar"), vec![Token::Name("myvar".into()), Token::Eof]);
575 }
576
577 #[test]
578 fn numbers() {
579 assert_eq!(toks("0.5"), vec![Token::Float(0.5), Token::Eof]);
580 assert_eq!(toks(".25"), vec![Token::Float(0.25), Token::Eof]);
581 assert_eq!(toks("1e3"), vec![Token::Float(1000.0), Token::Eof]);
582 assert_eq!(toks("2.5e-1"), vec![Token::Float(0.25), Token::Eof]);
583 }
584
585 #[test]
586 fn operators_and_arrows() {
587 assert_eq!(
588 toks("a := b <= c -> d <- e <-> f"),
589 vec![
590 Token::Name("a".into()),
591 Token::ColonEq,
592 Token::Name("b".into()),
593 Token::Le,
594 Token::Name("c".into()),
595 Token::Arrow(Arrow::Right),
596 Token::Name("d".into()),
597 Token::Arrow(Arrow::Left),
598 Token::Name("e".into()),
599 Token::Arrow(Arrow::Double),
600 Token::Name("f".into()),
601 Token::Eof
602 ]
603 );
604 }
605
606 #[test]
607 fn minus_is_not_arrow_without_gt() {
608 assert_eq!(
609 toks("2-3"),
610 vec![
611 Token::Float(2.0),
612 Token::Minus,
613 Token::Float(3.0),
614 Token::Eof
615 ]
616 );
617 }
618
619 #[test]
620 fn compass_and_params() {
621 assert_eq!(
622 toks("last box.ne A.ht .center"),
623 vec![
624 Token::Kw(Kw::Last),
625 Token::Prim(Prim::Box),
626 Token::Corner(Corner::Ne),
627 Token::Label("A".into()),
628 Token::Param(Param::Height),
629 Token::Corner(Corner::Center),
630 Token::Eof
631 ]
632 );
633 }
634
635 #[test]
636 fn dot_then_label() {
637 assert_eq!(
639 toks("B.A"),
640 vec![
641 Token::Label("B".into()),
642 Token::Dot,
643 Token::Label("A".into()),
644 Token::Eof
645 ]
646 );
647 }
648
649 #[test]
650 fn statement_separators_and_comments() {
651 assert_eq!(
652 toks("box; circle # a comment\narc"),
653 vec![
654 Token::Prim(Prim::Box),
655 Token::Newline,
656 Token::Prim(Prim::Circle),
657 Token::Newline,
658 Token::Prim(Prim::Arc),
659 Token::Eof
660 ]
661 );
662 }
663
664 #[test]
665 fn line_continuation() {
666 assert_eq!(
667 toks("box \\\n wid 2"),
668 vec![
669 Token::Prim(Prim::Box),
670 Token::Kw(Kw::Wid),
671 Token::Float(2.0),
672 Token::Eof
673 ]
674 );
675 }
676
677 #[test]
678 fn ps_pe_and_block() {
679 assert_eq!(
680 toks(".PS\nbox\n.PE"),
681 vec![
682 Token::DotPS,
683 Token::Newline,
684 Token::Prim(Prim::Box),
685 Token::Newline,
686 Token::DotPE,
687 Token::Eof
688 ]
689 );
690 assert_eq!(toks("[]"), vec![Token::Block, Token::Eof]);
691 assert_eq!(
692 toks("[ box ]"),
693 vec![
694 Token::LeftBrack,
695 Token::Prim(Prim::Box),
696 Token::RightBrack,
697 Token::Eof
698 ]
699 );
700 }
701
702 #[test]
703 fn macro_arg() {
704 assert_eq!(
705 toks("box $1"),
706 vec![Token::Prim(Prim::Box), Token::Arg(1), Token::Eof]
707 );
708 }
709
710 #[test]
711 fn func_and_envvar() {
712 assert_eq!(
713 toks("sqrt(2) atan2 scale"),
714 vec![
715 Token::Func1(Func1::Sqrt),
716 Token::Lparen,
717 Token::Float(2.0),
718 Token::Rparen,
719 Token::Func2(Func2::Atan2),
720 Token::EnvVar(EnvVar::Scale),
721 Token::Eof
722 ]
723 );
724 }
725}