use super::db::IndentHost;
use crate::js::chars::{is_digit, is_hex, is_space, is_word, trim, unit, unit_at};
pub(crate) const END: usize = 0;
pub(crate) const ERROR: usize = 1;
pub(crate) const EOF: usize = 2;
pub(crate) const NEWLINE: usize = 3;
pub(crate) const VENN: usize = 4;
pub(crate) const TITLE: usize = 5;
pub(crate) const SET: usize = 6;
pub(crate) const UNION: usize = 7;
pub(crate) const TEXT: usize = 8;
pub(crate) const INDENT_TEXT: usize = 9;
pub(crate) const STYLE: usize = 10;
pub(crate) const BRACKET_LABEL: usize = 11;
pub(crate) const NUMERIC: usize = 12;
pub(crate) const HEXCOLOR: usize = 13;
pub(crate) const RGBCOLOR: usize = 14;
pub(crate) const RGBACOLOR: usize = 15;
pub(crate) const IDENTIFIER: usize = 16;
pub(crate) const STRING: usize = 17;
pub(crate) const COMMA: usize = 18;
pub(crate) const COLON: usize = 19;
pub(crate) const TOKEN_NAMES: [&str; 20] = [
"$end",
"INVALID",
"EOF",
"NEWLINE",
"VENN",
"TITLE",
"SET",
"UNION",
"TEXT",
"INDENT_TEXT",
"STYLE",
"BRACKET_LABEL",
"NUMERIC",
"HEXCOLOR",
"RGBCOLOR",
"RGBACOLOR",
"IDENTIFIER",
"STRING",
"COMMA",
"COLON",
];
const KEYWORDS: [(&str, usize); 5] = [
("venn-beta", VENN),
("set", SET),
("union", UNION),
("text", TEXT),
("style", STYLE),
];
pub(crate) struct VennLexer<'a> {
src: &'a str,
pub(crate) text: String,
pub(crate) start: usize,
at: usize,
line_start: bool,
ended: bool,
}
impl<'a> VennLexer<'a> {
pub(crate) fn new(src: &'a str) -> VennLexer<'a> {
VennLexer {
src,
text: String::new(),
start: 0,
at: 0,
line_start: false,
ended: false,
}
}
fn word(&self, at: usize, word: &str) -> bool {
word.bytes().enumerate().all(|(k, w)| {
let c = unit(self.src, at + k);
let w = i32::from(w);
c == w || c + 32 == w && (65..=90).contains(&c)
})
}
fn keyword(&self, at: usize, word: &str) -> bool {
self.word(at, word) && !is_word(unit(self.src, at + word.len()))
}
fn color(&self, at: usize, parts: usize) -> Option<usize> {
let src = self.src;
let spaces = |mut i: usize| {
loop {
let (c, size) = unit_at(src, i);
if !is_space(c) {
return i;
}
i += size;
}
};
let mut i = at + 1;
for part in 0..parts {
i = spaces(i);
let from = i;
while is_digit(unit(src, i)) || unit(src, i) == 46 {
i += 1;
}
if i == from {
return None;
}
i = spaces(i);
if unit(src, i) != if part == parts - 1 { 41 } else { 44 } {
return None;
}
i += 1;
}
Some(i)
}
fn after_line_feed(&self, at: usize) -> bool {
let b = self.src.as_bytes();
let mut k = at;
while k > 0 && (b[k - 1] == 32 || b[k - 1] == 9) {
k -= 1;
}
k > 0 && b[k - 1] == 10
}
fn line_end(&self, from: usize) -> usize {
self.src[from..]
.find('\n')
.map_or(self.src.len(), |e| from + e)
}
pub(crate) fn next(&mut self, host: &mut dyn IndentHost) -> usize {
let src = self.src;
let n = src.len();
loop {
let mut i = self.at;
self.start = i;
self.text.clear();
if i >= n {
if self.ended {
return END;
}
self.ended = true;
return EOF;
}
let c = unit(src, i);
if c == 37 && unit(src, i + 1) == 37 && unit(src, i + 2) != 123 {
self.at = self.line_end(i + 2);
continue;
}
if self.line_start
&& self.word(i, "title")
&& self.after_line_feed(i)
&& trim(&src[i + 5..self.line_end(i + 5)]).is_empty()
{
let mut j = self.line_end(i + 5);
while unit(src, j) == 10 || unit(src, j) == 13 {
j += 1;
}
self.at = j;
continue;
}
if self.line_start {
if c == 32 || c == 9 {
let mut j = i + 1;
while unit(src, j) == 32 || unit(src, j) == 9 {
j += 1;
}
self.at = j;
if self.keyword(j, "text") && host.indent_mode() {
self.at = j + 4;
self.line_start = false;
self.text = src[i..j].to_string();
return INDENT_TEXT;
}
continue;
}
if c != 10 && c != 13 {
host.set_indent_mode(false);
self.line_start = false;
}
}
if c == 10 || c == 13 {
i += 1;
while unit(src, i) == 10 || unit(src, i) == 13 {
i += 1;
}
self.line_start = true;
return self.token(NEWLINE, i);
}
if c == 37 && unit(src, i + 1) == 37 {
self.at = self.line_end(i);
continue;
}
if c == 32 || c == 9 {
i += 1;
while unit(src, i) == 32 || unit(src, i) == 9 {
i += 1;
}
self.at = i;
continue;
}
if ((c | 32) >= 97 && (c | 32) <= 122) || c == 95 {
let (sep, size) = unit_at(src, i + 5);
if self.word(i, "title") && is_space(sep) && sep != 10 && sep != 13 {
let from = i + 5 + size;
let mut end = from;
while end < n {
let ch = src.as_bytes()[end];
if ch == 35 || ch == 10 || ch == 59 {
break;
}
end += 1;
}
if end > from {
return self.token(TITLE, end);
}
}
for (word, kind) in KEYWORDS {
if self.keyword(i, word) && unit(src, i + word.len()) != 45 {
return self.token(kind, i + word.len());
}
}
if self.word(i, "rgb") {
let alpha = (unit(src, i + 3) | 32) == 97;
let open = i + if alpha { 4 } else { 3 };
let end = if unit(src, open) == 40 {
self.color(open, if alpha { 4 } else { 3 })
} else {
None
};
if let Some(end) = end {
return self.token(if alpha { RGBACOLOR } else { RGBCOLOR }, end);
}
}
i += 1;
while is_word(unit(src, i)) || unit(src, i) == 45 {
i += 1;
}
return self.token(IDENTIFIER, i);
}
if c == 91 {
if unit(src, i + 1) == 34 {
let Some(quote) = src[i + 2..].find('"').map(|q| i + 2 + q) else {
return ERROR;
};
if unit(src, quote + 1) != 93 {
return ERROR;
}
self.at = quote + 2;
self.text = src[i + 2..quote].to_string();
return BRACKET_LABEL;
}
let b = src.as_bytes();
let mut end = i + 1;
while end < n && b[end] != 93 && b[end] != 34 {
end += 1;
}
if end == i + 1 || unit(src, end) != 93 {
return ERROR;
}
self.at = end + 1;
self.text = trim(&src[i + 1..end]).to_string();
return BRACKET_LABEL;
}
if is_digit(c) || c == 46 || c == 43 || c == 45 {
if c == 43 || c == 45 {
i += 1;
}
let from = i;
while is_digit(unit(src, i)) {
i += 1;
}
if i > from || unit(src, i) == 46 && is_digit(unit(src, i + 1)) {
if unit(src, i) == 46 && is_digit(unit(src, i + 1)) {
i += 2;
while is_digit(unit(src, i)) {
i += 1;
}
}
} else {
return ERROR;
}
return self.token(NUMERIC, i);
}
if c == 35 {
let mut end = i + 1;
while end < i + 9 && is_hex(unit(src, end)) {
end += 1;
}
return if end >= i + 4 {
self.token(HEXCOLOR, end)
} else {
ERROR
};
}
if c == 34 {
return match src[i + 1..].find('"') {
Some(q) => self.token(STRING, i + 1 + q + 1),
None => ERROR,
};
}
if c == 44 {
return self.token(COMMA, i + 1);
}
if c == 58 {
return self.token(COLON, i + 1);
}
return ERROR;
}
}
fn token(&mut self, kind: usize, end: usize) -> usize {
self.text = self.src[self.at..end].to_string();
self.at = end;
kind
}
}