modality_lang/
lexer.rs

1use lalrpop_util::lexer::Token;
2
3#[derive(Debug, Clone, PartialEq)]
4pub enum ModalityToken {
5    Model,
6    Graph,
7    Arrow,
8    Colon,
9    Plus,
10    Minus,
11    Ident(String),
12    Whitespace,
13    Comment,
14    Eof,
15}
16
17pub struct Lexer {
18    input: Vec<char>,
19    position: usize,
20}
21
22impl Lexer {
23    pub fn new(input: &str) -> Self {
24        Self {
25            input: input.chars().collect(),
26            position: 0,
27        }
28    }
29
30    fn peek(&self) -> Option<char> {
31        if self.position < self.input.len() {
32            Some(self.input[self.position])
33        } else {
34            None
35        }
36    }
37
38    fn advance(&mut self) {
39        self.position += 1;
40    }
41
42    fn skip_whitespace(&mut self) {
43        while let Some(ch) = self.peek() {
44            if ch.is_whitespace() {
45                self.advance();
46            } else {
47                break;
48            }
49        }
50    }
51
52    fn read_identifier(&mut self) -> String {
53        let mut ident = String::new();
54        while let Some(ch) = self.peek() {
55            if ch.is_alphanumeric() || ch == '_' {
56                ident.push(ch);
57                self.advance();
58            } else {
59                break;
60            }
61        }
62        ident
63    }
64
65    fn read_comment(&mut self) {
66        // Skip // and everything until newline
67        while let Some(ch) = self.peek() {
68            if ch == '\n' {
69                break;
70            }
71            self.advance();
72        }
73    }
74
75    pub fn next_token(&mut self) -> ModalityToken {
76        self.skip_whitespace();
77
78        if self.position >= self.input.len() {
79            return ModalityToken::Eof;
80        }
81
82        let ch = self.input[self.position];
83        self.advance();
84
85        match ch {
86            '/' => {
87                if let Some('/') = self.peek() {
88                    self.advance();
89                    self.read_comment();
90                    self.next_token() // Recursively get next token after comment
91                } else {
92                    // This shouldn't happen in valid Modality syntax
93                    ModalityToken::Eof
94                }
95            }
96            ':' => ModalityToken::Colon,
97            '+' => ModalityToken::Plus,
98            '-' => {
99                if let Some('>') = self.peek() {
100                    self.advance();
101                    ModalityToken::Arrow
102                } else {
103                    ModalityToken::Minus
104                }
105            }
106            'm' => {
107                let ident = self.read_identifier();
108                if ident == "model" {
109                    ModalityToken::Model
110                } else {
111                    ModalityToken::Ident(ident)
112                }
113            }
114            'g' => {
115                let ident = self.read_identifier();
116                if ident == "graph" {
117                    ModalityToken::Graph
118                } else {
119                    ModalityToken::Ident(ident)
120                }
121            }
122            _ if ch.is_alphabetic() || ch == '_' => {
123                let mut ident = String::from(ch);
124                ident.push_str(&self.read_identifier());
125                ModalityToken::Ident(ident)
126            }
127            _ => ModalityToken::Eof, // Skip unknown characters
128        }
129    }
130}
131
132impl Iterator for Lexer {
133    type Item = ModalityToken;
134
135    fn next(&mut self) -> Option<Self::Item> {
136        let token = self.next_token();
137        if token == ModalityToken::Eof {
138            None
139        } else {
140            Some(token)
141        }
142    }
143}