pub(super) use crate::diagnostics::Diagnostic;
use crate::keywords;
pub(super) use crate::token::{Span, StrSegment, Token, TokenKind};
mod scan;
mod strings;
#[cfg(test)]
mod tests;
pub fn lex(path: &str, source: &str) -> Result<Vec<Token>, Diagnostic> {
Lexer::new(path, source).run().map(|(tokens, _)| tokens)
}
pub(crate) fn lex_with_comments(
path: &str,
source: &str,
) -> Result<(Vec<Token>, Vec<Comment>), Diagnostic> {
Lexer::new(path, source).run()
}
#[derive(Debug, Clone)]
pub(crate) struct Comment {
pub(crate) line: u32,
pub(crate) col: u32,
pub(crate) text: String,
}
struct Lexer {
path: String,
lines: Vec<String>,
indent_stack: Vec<usize>,
bracket_stack: Vec<Span>,
tokens: Vec<Token>,
comments: Vec<Comment>,
in_hole: bool,
}
impl Lexer {
fn new(path: &str, source: &str) -> Lexer {
let lines = crate::diagnostics::split_source_lines(source);
Lexer {
path: path.to_string(),
lines,
indent_stack: vec![0],
bracket_stack: Vec::new(),
tokens: Vec::new(),
comments: Vec::new(),
in_hole: false,
}
}
fn run(mut self) -> Result<(Vec<Token>, Vec<Comment>), Diagnostic> {
let lines = self.lines.clone();
for (idx, text) in lines.iter().enumerate() {
let ln = (idx + 1) as u32;
let chars: Vec<char> = text.chars().collect();
let start = if self.bracket_stack.is_empty() {
match self.begin_logical_line(&chars, ln)? {
None => continue,
Some(start) => start,
}
} else {
let mut i = 0;
while i < chars.len() && (chars[i] == ' ' || chars[i] == '\t') {
i += 1;
}
if i >= chars.len() {
continue;
}
if chars[i] == '#' {
self.record_comment(ln, i, &chars);
continue;
}
i
};
self.lex_line(&chars, ln, start)?;
if self.bracket_stack.is_empty() {
self.push(TokenKind::Newline, ln, (chars.len() + 1) as u32);
}
}
let last_line = self.lines.len() as u32;
if let Some(open) = self.bracket_stack.first() {
let span = *open;
return Err(self
.diag(span.line, span.col, "this bracket was never closed")
.with_headline("very open. much bracket.")
.with_hint("add the matching closing bracket"));
}
while *self
.indent_stack
.last()
.expect("compiler bug: indent stack keeps its base 0")
> 0
{
self.indent_stack.pop();
self.push(TokenKind::Dedent, last_line, 1);
}
self.push(TokenKind::Eof, last_line, 1);
Ok((self.tokens, self.comments))
}
fn begin_logical_line(&mut self, chars: &[char], ln: u32) -> Result<Option<usize>, Diagnostic> {
let mut i = 0;
let mut tab_col: Option<u32> = None;
while i < chars.len() && (chars[i] == ' ' || chars[i] == '\t') {
if chars[i] == '\t' && tab_col.is_none() {
tab_col = Some((i + 1) as u32);
}
i += 1;
}
if i >= chars.len() {
return Ok(None);
}
if chars[i] == '#' {
self.record_comment(ln, i, chars);
return Ok(None);
}
if let Some(col) = tab_col {
return Err(self
.diag(ln, col, "tabs cannot be used to indent")
.with_headline("very tab. much confuse.")
.with_hint("indent with spaces"));
}
let count = i; let top = *self
.indent_stack
.last()
.expect("compiler bug: indent stack keeps its base 0");
if count > top {
self.indent_stack.push(count);
self.push(TokenKind::Indent, ln, (count + 1) as u32);
} else if count < top {
while *self
.indent_stack
.last()
.expect("compiler bug: indent stack keeps its base 0")
> count
{
self.indent_stack.pop();
self.push(TokenKind::Dedent, ln, (count + 1) as u32);
}
if *self
.indent_stack
.last()
.expect("compiler bug: indent stack keeps its base 0")
!= count
{
return Err(self
.diag(
ln,
(count + 1) as u32,
"this line does not line up with any block",
)
.with_headline("very indent. much confuse.")
.with_hint("match the indentation of an outer block"));
}
}
Ok(Some(i))
}
fn lex_line(&mut self, chars: &[char], ln: u32, start: usize) -> Result<(), Diagnostic> {
self.lex_range(chars, ln, start, chars.len())
}
fn lex_range(
&mut self,
chars: &[char],
ln: u32,
start: usize,
end: usize,
) -> Result<(), Diagnostic> {
let mut i = start;
while i < end {
let c = chars[i];
let col = (i + 1) as u32;
if c == ' ' || c == '\t' {
i += 1;
continue;
}
if c == '#' {
if !self.in_hole {
self.record_comment(ln, i, chars);
}
break; }
if c.is_ascii_alphabetic() || c == '_' {
i = self.lex_word(chars, ln, i);
continue;
}
if c.is_ascii_digit() {
i = self.lex_number(chars, ln, i)?;
continue;
}
if c == '"' {
i = self.lex_string(chars, ln, i)?;
continue;
}
i = self.lex_operator(chars, ln, i, col)?;
}
Ok(())
}
fn push(&mut self, kind: TokenKind, line: u32, col: u32) {
self.tokens.push(Token {
kind,
span: Span { line, col },
});
}
fn record_comment(&mut self, ln: u32, hash_index: usize, chars: &[char]) {
let text: String = chars[hash_index + 1..].iter().collect();
self.comments.push(Comment {
line: ln,
col: (hash_index + 1) as u32,
text: text.trim_end().to_string(),
});
}
fn diag(&self, line: u32, col: u32, message: impl Into<String>) -> Diagnostic {
let source_line = crate::diagnostics::source_line(&self.lines, line);
Diagnostic::new(&self.path, line, col, source_line, message)
}
}