use std::ops::Range;
use arcstr::Substr;
use crate::syntax::{
buildctx::BuildCtx,
diagnostic::Severity,
lexer::token::Kw,
location::{Pos, Span},
streams::CharStream,
util::{
bsv_binary_underscore, bsv_decimal_underscore, bsv_hexadecimal_underscore,
bsv_horizontal_whitespace, bsv_ident, bsv_ident_start, bsv_octal_underscore, bsv_symbol,
bsv_whitespace,
},
};
use super::token::{Bit, NumLit, Sym, Token, TokenKind};
pub struct BsvLexer<'ctx> {
ctx: &'ctx mut BuildCtx,
}
impl<'ctx> BsvLexer<'ctx> {
pub fn new(ctx: &'ctx mut BuildCtx) -> Self {
Self { ctx }
}
fn err_token(&mut self, out: &mut Vec<Token>, range: Range<Pos>, message: String) {
let span = Span::cross_origin(range.start, range.end, &self.ctx.origins);
out.push(Token {
span,
kind: TokenKind::Error,
});
self.ctx
.diagnostics
.push(span, Severity::Error, "blues-lsp", message);
}
fn diag(&mut self, range: Range<Pos>, severity: Severity, message: String) {
self.ctx.diagnostics.push(
Span::cross_origin(range.start, range.end, &self.ctx.origins),
severity,
"blues-lsp",
message,
);
}
fn token(&mut self, out: &mut Vec<Token>, range: Range<Pos>, kind: TokenKind) {
out.push(Token {
span: Span::cross_origin(range.start, range.end, &self.ctx.origins),
kind,
});
}
pub fn run<'a>(&mut self, mut inp: impl CharStream<'a>, out: &mut Vec<Token>) {
while let Some(c) = inp.peek() {
match c {
'"' => self.scan_string(&mut inp, out),
'\\' => self.scan_esc_ident(&mut inp, out),
'$' => self.scan_sys_ident(&mut inp, out),
'\'' => self.scan_tick(&mut inp, out),
c if c.is_ascii_digit() => self.scan_num(&mut inp, out),
c if bsv_ident_start(c) => self.scan_ident(&mut inp, out),
c if bsv_symbol(c) => self.process_syms(&mut inp, out),
c if bsv_whitespace(c) => {
inp.next();
}
c => {
let start = inp.pos();
inp.next();
self.err_token(out, start..inp.pos(), format!("Unexpected character '{c}'"));
}
}
}
}
fn scan_tick<'a>(&mut self, inp: &mut impl CharStream<'a>, out: &mut Vec<Token>) {
let mut la = inp.clone();
la.expect('\'');
match la.peek() {
Some(c) if Bit::parse(c).is_some() => {
let start = inp.pos();
inp.expect('\'');
inp.next();
self.token(out, start..inp.pos(), TokenKind::Num(NumLit::Repeated));
}
Some('s') | Some('S') | Some('d') | Some('D') | Some('h') | Some('H') | Some('o')
| Some('O') | Some('b') | Some('B') => self.scan_based_num(inp, out),
_ => {
let start = inp.pos();
inp.expect('\'');
self.token(out, start..inp.pos(), TokenKind::Sym(Sym::Tick));
}
}
}
fn scan_based_num<'a>(&mut self, inp: &mut impl CharStream<'a>, out: &mut Vec<Token>) {
self.scan_based_num_inner(inp, out, inp.pos());
}
fn scan_num<'a>(&mut self, inp: &mut impl CharStream<'a>, out: &mut Vec<Token>) {
let start = inp.pos();
inp.skip_while(bsv_decimal_underscore);
let end = inp.pos();
match inp.peek() {
Some('.') => self.scan_real_fraction(inp, out, start),
Some('e') | Some('E') => self.scan_real_exponent(inp, out, start),
_ => {
inp.skip_while(bsv_horizontal_whitespace);
match inp.peek() {
Some('\'') => self.scan_based_num_inner(inp, out, start),
_ => {
self.token(out, start..end, TokenKind::Num(NumLit::Int));
}
}
}
}
}
fn scan_real_fraction<'a>(
&mut self,
inp: &mut impl CharStream<'a>,
out: &mut Vec<Token>,
start: Pos,
) {
inp.expect('.');
inp.skip_while(bsv_decimal_underscore);
match inp.peek() {
Some('e') | Some('E') => self.scan_real_exponent(inp, out, start),
_ => self.token(out, start..inp.pos(), TokenKind::Num(NumLit::Real)),
}
}
fn scan_real_exponent<'a>(
&mut self,
inp: &mut impl CharStream<'a>,
out: &mut Vec<Token>,
start: Pos,
) {
assert!(matches!(inp.next(), Some('e') | Some('E')));
if let Some('+') | Some('-') = inp.peek() {
inp.next();
}
inp.skip_while(bsv_decimal_underscore);
self.token(out, start..inp.pos(), TokenKind::Num(NumLit::Real));
}
fn scan_based_num_inner<'a>(
&mut self,
inp: &mut impl CharStream<'a>,
out: &mut Vec<Token>,
start: Pos,
) {
inp.expect('\'');
if let Some('s') | Some('S') = inp.peek() {
inp.next();
}
match inp.peek() {
Some(c) if Bit::parse(c).is_some() => {
inp.next();
self.token(out, start..inp.pos(), TokenKind::Num(NumLit::Repeated));
}
Some('d') | Some('D') => {
inp.next();
self.scan_based_digits(inp, out, start, bsv_decimal_underscore, false)
}
Some('h') | Some('H') => {
inp.next();
self.scan_based_digits(inp, out, start, bsv_hexadecimal_underscore, true)
}
Some('o') | Some('O') => {
inp.next();
self.scan_based_digits(inp, out, start, bsv_octal_underscore, true)
}
Some('b') | Some('B') => {
inp.next();
self.scan_based_digits(inp, out, start, bsv_binary_underscore, true)
}
_ => {
self.err_token(out, start..inp.pos(), "Invalid literal base".to_string());
}
};
}
fn scan_based_digits<'a>(
&mut self,
inp: &mut impl CharStream<'a>,
out: &mut Vec<Token>,
start: Pos,
mut pred: impl FnMut(char) -> bool,
allow_mixed: bool,
) {
let mut mixed = false;
while let Some(c) = inp.peek() {
match c {
c if allow_mixed && Bit::parse(c).is_some() => {
mixed = true;
}
c if pred(c) => (),
_ => break,
}
inp.next();
}
if mixed {
self.token(out, start..inp.pos(), TokenKind::Num(NumLit::Mixed));
} else {
self.token(out, start..inp.pos(), TokenKind::Num(NumLit::Int));
}
}
fn scan_sys_ident<'a>(&mut self, inp: &mut impl CharStream<'a>, out: &mut Vec<Token>) {
let start = inp.pos();
let str = inp.read_while(bsv_ident);
self.token(out, start..inp.pos(), TokenKind::SysIdent(str));
}
fn scan_esc_ident<'a>(&mut self, inp: &mut impl CharStream<'a>, out: &mut Vec<Token>) {
let start = inp.pos();
inp.expect('\\');
let str = inp.read_while(|c| !bsv_whitespace(c));
self.token(out, start..inp.pos(), TokenKind::Ident(str));
}
fn scan_ident<'a>(&mut self, inp: &mut impl CharStream<'a>, out: &mut Vec<Token>) {
assert!(matches!(inp.peek(), Some(c) if bsv_ident_start(c)));
let start = inp.pos();
let str = inp.read_while(bsv_ident);
if let Some(kw) = Kw::parse(&str) {
self.token(out, start..inp.pos(), TokenKind::Kw(kw));
} else {
self.token(out, start..inp.pos(), TokenKind::Ident(str));
}
}
fn scan_string<'a>(&mut self, inp: &mut impl CharStream<'a>, out: &mut Vec<Token>) {
let start = inp.pos();
inp.expect('"');
let mut contents = inp.read_while(|c| !matches!(c, '\\' | '"' | '\n'));
if !matches!(inp.peek(), Some('"') | None) {
contents = self.parse_string_escapes(inp, contents);
}
if let Some('"') = inp.peek() {
inp.next();
} else {
self.diag(
start..inp.pos(),
Severity::Error,
"Unterminated string".into(),
);
}
self.token(out, start..inp.pos(), TokenKind::Str(contents));
}
fn parse_string_escapes<'a>(&mut self, inp: &mut impl CharStream<'a>, start: Substr) -> Substr {
let mut contents = start.to_string();
while let Some(c) = inp.peek() {
match c {
'\\' => {
let esc_start = inp.pos();
inp.next();
match inp.peek() {
Some('0'..='7') => {
let code = inp.read_while(|c| matches!(c, '0'..='7'));
if code.len() != 3 {
self.diag(
esc_start..inp.pos(),
Severity::Error,
"Octal escape sequence must contain exactly 3 digts".into(),
);
}
let Ok(code) = u32::from_str_radix(&code, 8) else {
continue;
};
let code = char::from_u32(code).unwrap_or(char::REPLACEMENT_CHARACTER);
contents.push(code);
}
Some('x') => {
inp.next();
let code = inp.read_while(|c| c.is_ascii_hexdigit());
if code.len() != 2 {
self.diag(
esc_start..inp.pos(),
Severity::Error,
"Hexadecimal escape sequence must contain exactly 2 digts"
.into(),
);
}
let Ok(code) = u32::from_str_radix(&code, 16) else {
continue;
};
let code = char::from_u32(code).unwrap_or(char::REPLACEMENT_CHARACTER);
contents.push(code);
}
Some('n') | Some('\n') => {
inp.next();
contents.push('\n');
}
Some('t') => {
inp.next();
contents.push('\t');
}
Some('v') => {
inp.next();
contents.push('\x0b');
}
Some('a') => {
inp.next();
contents.push('\x0b');
}
Some('f') => {
inp.next();
contents.push('\x0c');
}
Some('"') => {
inp.next();
contents.push('"');
}
Some('\\') => {
inp.next();
contents.push('\\');
}
Some(c) => {
inp.next();
contents.push(c);
self.diag(
esc_start..inp.pos(),
Severity::Error,
"Bad escape sequence".into(),
);
}
None => (),
}
}
'"' | '\n' => {
break;
}
c => {
inp.next();
contents.push(c);
}
}
}
Substr::from(contents)
}
fn process_syms<'a>(&mut self, inp: &mut impl CharStream<'a>, out: &mut Vec<Token>) {
let mut start = inp.pos();
let mut la = inp.clone();
let mut syms = la.read_while(|c| bsv_symbol(c) && c != '\'');
while !syms.is_empty() {
if syms.starts_with("//") {
return self.scan_line_comment(inp, out);
} else if syms.starts_with("/*") {
return self.scan_block_comment(inp, out);
}
let sym = Sym::longest_match(&syms);
let len = sym.map_or(syms.len(), |s| s.1.len());
for _ in 0..len {
inp.next();
}
let end = inp.pos();
if let Some((sym, _)) = sym {
self.token(out, start..end, TokenKind::Sym(sym));
} else {
self.err_token(
out,
start..end,
format!("Unrecognized symbol sequence '{}'", &syms[..len]),
);
}
start = end;
syms = syms.substr(len..);
}
}
fn scan_line_comment<'a>(&mut self, inp: &mut impl CharStream<'a>, out: &mut Vec<Token>) {
let start = inp.pos();
inp.expect('/');
inp.expect('/');
inp.read_while(|c| c != '\n');
if let Some('n') = inp.peek() {
inp.next();
}
self.token(out, start..inp.pos(), TokenKind::Comment);
}
fn scan_block_comment<'a>(&mut self, inp: &mut impl CharStream<'a>, out: &mut Vec<Token>) {
let start = inp.pos();
inp.expect('/');
inp.expect('*');
while let Some(c) = inp.peek() {
match c {
'*' => {
inp.next();
if let Some('/') = inp.peek() {
inp.next();
break;
}
}
_ => {
inp.next();
}
}
}
if inp.peek().is_none() {
self.diag(
start..inp.pos(),
Severity::Error,
"Unterminated block comment".into(),
);
}
self.token(out, start..inp.pos(), TokenKind::Comment);
}
}