//! Lexer for the ISLE language. use crate::error::{Error, Errors, Span}; use std::borrow::Cow; use std::path::{Path, PathBuf}; use std::sync::Arc; type Result = std::result::Result; /// The lexer. /// /// Breaks source text up into a sequence of tokens (with source positions). #[derive(Clone, Debug)] pub struct Lexer<'a> { /// Arena of filenames from the input source. /// /// Indexed via `Pos::file`. pub filenames: Vec>, /// Arena of file source texts. /// /// Indexed via `Pos::file`. pub file_texts: Vec>, file_starts: Vec, buf: Cow<'a, [u8]>, pos: Pos, lookahead: Option<(Pos, Token)>, } /// A source position. #[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Hash, PartialOrd, Ord)] pub struct Pos { /// This source position's file. /// /// Indexes into `Lexer::filenames` early in the compiler pipeline, and /// later into `TypeEnv::filenames` once we get into semantic analysis. pub file: usize, /// This source position's byte offset in the file. pub offset: usize, /// This source position's line number in the file. pub line: usize, /// This source position's column number in the file. pub col: usize, } impl Pos { /// Print this source position as `file.isle line 12`. pub fn pretty_print_line(&self, filenames: &[Arc]) -> String { format!("{} line {}", filenames[self.file], self.line) } } /// A token of ISLE source. #[derive(Clone, Debug, PartialEq, Eq)] pub enum Token { /// Left paren. LParen, /// Right paren. RParen, /// A symbol, e.g. `Foo`. Symbol(String), /// An integer. Int(i128), /// `@` At, } impl<'a> Lexer<'a> { /// Create a new lexer for the given source contents and filename. pub fn from_str(s: &'a str, filename: &'a str) -> Result> { let mut l = Lexer { filenames: vec![filename.into()], file_texts: vec![s.into()], file_starts: vec![0], buf: Cow::Borrowed(s.as_bytes()), pos: Pos { file: 0, offset: 0, line: 1, col: 0, }, lookahead: None, }; l.reload()?; Ok(l) } /// Create a new lexer from the given files. pub fn from_files

(file_paths: impl IntoIterator) -> Result> where P: AsRef, { let mut files = vec![]; for f in file_paths.into_iter() { let f = f.as_ref().to_path_buf(); let s = std::fs::read_to_string(f.as_path()) .map_err(|e| Errors::from_io(e, format!("failed to read file: {}", f.display())))?; files.push((f, s)); } Self::from_file_contents(files) } /// Create a new lexer from the given files and contents. pub fn from_file_contents(files: Vec<(PathBuf, String)>) -> Result> { let mut filenames = Vec::>::new(); let mut file_texts = Vec::>::new(); for (f, content) in files.iter() { filenames.push(f.display().to_string().into()); file_texts.push(content.as_str().into()); } assert!(!filenames.is_empty()); let mut file_starts = vec![]; let mut buf = String::new(); for text in &file_texts { file_starts.push(buf.len()); buf += text; buf += "\n"; } let mut l = Lexer { filenames, file_texts, buf: Cow::Owned(buf.into_bytes()), file_starts, pos: Pos { file: 0, offset: 0, line: 1, col: 0, }, lookahead: None, }; l.reload()?; Ok(l) } /// Get the lexer's current source position. pub fn pos(&self) -> Pos { Pos { file: self.pos.file, offset: self.pos.offset - self.file_starts[self.pos.file], line: self.pos.line, col: self.pos.col, } } fn advance_pos(&mut self) { self.pos.col += 1; if self.buf[self.pos.offset] == b'\n' { self.pos.line += 1; self.pos.col = 0; } self.pos.offset += 1; if self.pos.file + 1 < self.file_starts.len() { let next_start = self.file_starts[self.pos.file + 1]; if self.pos.offset >= next_start { assert!(self.pos.offset == next_start); self.pos.file += 1; self.pos.line = 1; } } } fn error(&self, pos: Pos, msg: impl Into) -> Errors { Errors { errors: vec![Error::ParseError { msg: msg.into(), span: Span::new_single(pos), }], filenames: self.filenames.clone(), file_texts: self.file_texts.clone(), } } fn next_token(&mut self) -> Result> { fn is_sym_first_char(c: u8) -> bool { match c { b'-' | b'0'..=b'9' | b'(' | b')' | b';' | b'<' | b'>' => false, c if c.is_ascii_whitespace() => false, _ => true, } } fn is_sym_other_char(c: u8) -> bool { match c { b'(' | b')' | b';' | b'@' | b'<' => false, c if c.is_ascii_whitespace() => false, _ => true, } } // Skip any whitespace and any comments. while self.pos.offset < self.buf.len() { if self.buf[self.pos.offset].is_ascii_whitespace() { self.advance_pos(); continue; } if self.buf[self.pos.offset] == b';' { while self.pos.offset < self.buf.len() && self.buf[self.pos.offset] != b'\n' { self.advance_pos(); } continue; } break; } if self.pos.offset == self.buf.len() { return Ok(None); } let char_pos = self.pos(); match self.buf[self.pos.offset] { b'(' => { self.advance_pos(); Ok(Some((char_pos, Token::LParen))) } b')' => { self.advance_pos(); Ok(Some((char_pos, Token::RParen))) } b'@' => { self.advance_pos(); Ok(Some((char_pos, Token::At))) } c if is_sym_first_char(c) => { let start = self.pos.offset; let start_pos = self.pos(); while self.pos.offset < self.buf.len() && is_sym_other_char(self.buf[self.pos.offset]) { self.advance_pos(); } let end = self.pos.offset; let s = std::str::from_utf8(&self.buf[start..end]) .expect("Only ASCII characters, should be UTF-8"); debug_assert!(!s.is_empty()); Ok(Some((start_pos, Token::Symbol(s.to_string())))) } c @ (b'0'..=b'9' | b'-') => { let start_pos = self.pos(); let neg = if c == b'-' { self.advance_pos(); true } else { false }; let mut radix = 10; // Check for prefixed literals. match ( self.buf.get(self.pos.offset), self.buf.get(self.pos.offset + 1), ) { (Some(b'0'), Some(b'x')) | (Some(b'0'), Some(b'X')) => { self.advance_pos(); self.advance_pos(); radix = 16; } (Some(b'0'), Some(b'o')) => { self.advance_pos(); self.advance_pos(); radix = 8; } (Some(b'0'), Some(b'b')) => { self.advance_pos(); self.advance_pos(); radix = 2; } _ => {} } // Find the range in the buffer for this integer literal. We'll // pass this range to `i64::from_str_radix` to do the actual // string-to-integer conversion. let mut s = vec![]; while self.pos.offset < self.buf.len() && ((radix <= 10 && self.buf[self.pos.offset].is_ascii_digit()) || (radix == 16 && self.buf[self.pos.offset].is_ascii_hexdigit()) || self.buf[self.pos.offset] == b'_') { if self.buf[self.pos.offset] != b'_' { s.push(self.buf[self.pos.offset]); } self.advance_pos(); } let s_utf8 = std::str::from_utf8(&s[..]).unwrap(); // Support either signed range (-2^127..2^127) or // unsigned range (0..2^128). let num = i128::from_str_radix(s_utf8, radix) .or_else(|_| u128::from_str_radix(s_utf8, radix).map(|val| val as i128)) .map_err(|e| self.error(start_pos, e.to_string()))?; let tok = if neg { Token::Int(num.checked_neg().ok_or_else(|| { self.error(start_pos, "integer literal cannot fit in i128") })?) } else { Token::Int(num) }; Ok(Some((start_pos, tok))) } c => Err(self.error(self.pos, format!("Unexpected character '{}'", c))), } } /// Get the next token from this lexer's token stream, if any. pub fn next(&mut self) -> Result> { let tok = self.lookahead.take(); self.reload()?; Ok(tok) } fn reload(&mut self) -> Result<()> { if self.lookahead.is_none() && self.pos.offset < self.buf.len() { self.lookahead = self.next_token()?; } Ok(()) } /// Peek ahead at the next token. pub fn peek(&self) -> Option<&(Pos, Token)> { self.lookahead.as_ref() } /// Are we at the end of the source input? pub fn eof(&self) -> bool { self.lookahead.is_none() } } impl Token { /// Is this an `Int` token? pub fn is_int(&self) -> bool { matches!(self, Token::Int(_)) } /// Is this a `Sym` token? pub fn is_sym(&self) -> bool { matches!(self, Token::Symbol(_)) } } #[cfg(test)] mod test { use super::*; fn lex(s: &str, file: &str) -> Vec { let mut toks = vec![]; let mut lexer = Lexer::from_str(s, file).unwrap(); while let Some((_, tok)) = lexer.next().unwrap() { toks.push(tok); } toks } #[test] fn lexer_basic() { assert_eq!( lex( ";; comment\n; another\r\n \t(one two three 23 -568 )\n", "lexer_basic" ), vec![ Token::LParen, Token::Symbol("one".to_string()), Token::Symbol("two".to_string()), Token::Symbol("three".to_string()), Token::Int(23), Token::Int(-568), Token::RParen ] ); } #[test] fn ends_with_sym() { assert_eq!( lex("asdf", "ends_with_sym"), vec![Token::Symbol("asdf".to_string()),] ); } #[test] fn ends_with_num() { assert_eq!(lex("23", "ends_with_num"), vec![Token::Int(23)],); } #[test] fn weird_syms() { assert_eq!( lex("(+ [] => !! _test!;comment\n)", "weird_syms"), vec![ Token::LParen, Token::Symbol("+".to_string()), Token::Symbol("[]".to_string()), Token::Symbol("=>".to_string()), Token::Symbol("!!".to_string()), Token::Symbol("_test!".to_string()), Token::RParen, ] ); } }