1use super::code_editor::{Token, TokenKind};
7
8const KEYWORDS: &[&str] = &[
11 "always", "always_comb", "always_ff", "always_latch", "and", "assert", "assign", "assume",
12 "automatic", "begin", "buf", "case", "casex", "casez", "cover", "deassign", "default",
13 "defparam", "disable", "edge", "else", "end", "endcase", "endfunction", "endgenerate",
14 "endmodule", "endprimitive", "endproperty", "endsequence", "endspecify", "endtable", "endtask",
15 "enum", "event", "for", "force", "forever", "fork", "function", "generate", "genvar", "if",
16 "initial", "inout", "input", "join", "localparam", "module", "nand", "negedge", "nor", "not",
17 "or", "output", "parameter", "posedge", "primitive", "property", "release", "repeat",
18 "sequence", "specify", "specparam", "struct", "table", "task", "typedef", "wait", "while",
19 "xnor", "xor", "bit", "byte", "int", "integer", "logic", "longint", "packed", "real",
20 "realtime", "reg", "shortint", "signed", "supply0", "supply1", "time", "tri", "tri0", "tri1",
21 "triand", "trior", "unsigned", "wand", "wire", "wor",
22];
23
24const OPERATORS: &[&str] = &[
26 "<<<", ">>>", "===", "!==", "<=", ">=", "==", "!=", "&&", "||", "<<", ">>", "~&", "~|", "~^",
27 "^~", "**", "->", "+:", "-:", "::", "+", "-", "*", "/", "%", "=", "<", ">", "!", "&", "|",
28 "^", "~", "?", ":", "@", "#",
29];
30
31const PUNCTUATION: &[char] = &['(', ')', '[', ']', '{', '}', ';', ',', '.'];
32
33pub(crate) fn tokens(source: &str) -> Vec<Token> {
35 let mut out: Vec<Token> = Vec::new();
36 let mut at = 0;
37 while at < source.len() {
38 let rest = &source[at..];
39 let (len, kind) = next_token(rest);
40 out.push(Token { text: rest[..len].to_string(), kind });
41 at += len;
42 }
43 out
44}
45
46fn next_token(rest: &str) -> (usize, TokenKind) {
48 let first = rest.chars().next().expect("a token starts somewhere");
49 let word = |from: usize| from + rest[from..].find(|c: char| !(c.is_ascii_alphanumeric() || c == '_' || c == '$')).unwrap_or(rest.len() - from);
50 if first.is_whitespace() {
51 return (rest.find(|c: char| !c.is_whitespace()).unwrap_or(rest.len()), TokenKind::Identifier);
52 }
53 if rest.starts_with("//") {
54 return (rest.find('\n').unwrap_or(rest.len()), TokenKind::Comment);
55 }
56 if rest.starts_with("/*") {
57 return (rest[2..].find("*/").map_or(rest.len(), |i| i + 4), TokenKind::Comment);
58 }
59 if first == '"' {
60 return (string_len(rest), TokenKind::String);
61 }
62 if (first == '$' || first == '`') && rest[1..].starts_with(|c: char| c.is_ascii_alphabetic() || c == '_') {
63 return (word(1), TokenKind::Builtin);
64 }
65 if first == '\\' {
66 return (rest.find(char::is_whitespace).unwrap_or(rest.len()), TokenKind::Identifier);
67 }
68 if let Some(len) = number_len(rest) {
69 return (len, TokenKind::Number);
70 }
71 if first.is_ascii_alphabetic() || first == '_' {
72 let len = word(0);
73 let text = &rest[..len];
74 let kind = if KEYWORDS.contains(&text) { TokenKind::Keyword } else { TokenKind::Identifier };
75 return (len, kind);
76 }
77 if let Some(op) = OPERATORS.iter().find(|op| rest.starts_with(**op)) {
78 return (op.len(), TokenKind::Operator);
79 }
80 let len = first.len_utf8();
81 if PUNCTUATION.contains(&first) {
82 (len, TokenKind::Punctuation)
83 } else {
84 (len, TokenKind::Identifier)
85 }
86}
87
88fn string_len(rest: &str) -> usize {
91 let mut escaped = false;
92 for (i, c) in rest.char_indices().skip(1) {
93 match c {
94 '\n' => return i,
95 '"' if !escaped => return i + 1,
96 '\\' if !escaped => escaped = true,
97 _ => escaped = false,
98 }
99 }
100 rest.len()
101}
102
103fn number_len(rest: &str) -> Option<usize> {
106 let bytes = rest.as_bytes();
107 let run = |from: usize, ok: &dyn Fn(u8) -> bool| from + bytes[from..].iter().take_while(|b| ok(**b)).count();
108 let decimal = |b: u8| b.is_ascii_digit() || b == b'_';
109 let mut at = run(0, &decimal);
110 if bytes.get(at) == Some(&b'\'') {
111 let mut base = at + 1;
112 if matches!(bytes.get(base), Some(b's' | b'S')) {
113 base += 1;
114 }
115 if matches!(bytes.get(base), Some(b'b' | b'B' | b'o' | b'O' | b'd' | b'D' | b'h' | b'H')) {
116 let value = |b: u8| b.is_ascii_hexdigit() || matches!(b, b'_' | b'x' | b'X' | b'z' | b'Z' | b'?');
117 return Some(run(base + 1, &value));
118 }
119 if at == 0 && matches!(bytes.get(1), Some(b'0' | b'1' | b'x' | b'X' | b'z' | b'Z')) {
120 return Some(2);
121 }
122 }
123 if at == 0 {
124 return None;
125 }
126 if bytes.get(at) == Some(&b'.') && bytes.get(at + 1).is_some_and(u8::is_ascii_digit) {
127 at = run(at + 1, &decimal);
128 }
129 if matches!(bytes.get(at), Some(b'e' | b'E')) {
130 let sign = at + 1 + usize::from(matches!(bytes.get(at + 1), Some(b'+' | b'-')));
131 if bytes.get(sign).is_some_and(u8::is_ascii_digit) {
132 at = run(sign, &decimal);
133 }
134 }
135 Some(at)
136}
137
138#[cfg(test)]
139#[path = "../../../../../crates/logicaffeine_tests/unit/logicaffeine_web/ui/components/verilog/tests.rs"]
140mod tests;