自己从零实现一个脚本语言_ 01_scanner
在阅读 阅读了 制作解释器 http://www.craftinginterpreters.com/
中文版 非常感谢 中文译者 https://github.com/GuoYaxiang/craftinginterpreters_zh
参考了 https://github.com/mariosangiorgio/rulox
看着他的代码, 我在思考他为甚这么实现, 随后模仿了他的实现, 我有没有更好的办法呢?
收获了 增加了一些rust的熟练度, 提高了自己手撕源码的能力, 理解了编程语言的本质, 掌握了字符串的处理, 加深了自己对递归的使用
use std::str::Chars;
use itertools::{multipeek, MultiPeek};
// 判断是否是空白字符
fn is_whitespace(c: char) -> bool {
match c {
' ' | '\r' | '\t' | '\n' => true,
_ => false,
}
}
// 判断是否是数字字符
fn is_digit(c: char) -> bool {
c >= '0' && c <= '9'
}
// 判断是否是英文字母或者下划线
fn is_alpha(c: char) -> bool {
(c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'
}
// 判断是否是字母数字或者下划线
fn is_alphanumeric(c: char) -> bool {
is_digit(c) || is_alpha(c)
}
#[derive(Debug, PartialEq)]
pub enum Token {
LeftParen, // (
RightParen, // )
LeftBrace, // {
RightBrace, // }
Comma, // ,
Dot, // .
Minus, // -
Plus, // +
Semicolon, // ;
Slash, // /
Star, // *
Bang, // !
BangEqual, // !=
Equal, // =
EqualEqual, // ==
Greater, // >
GreaterEqual,// >=
Less, // <
LessEqual, // <=
Identifier(String), // 标识符或者变量名
StringLiteral(String), // 字符串
NumberLiteral(f64), // 数值
And, // and
Class, // class
Else, // else
False, // false
Def, // def
For, // for
If, // if
Null, // Null
Or, // or
Print, // print
Return, // return
Super, // super
This, // This
True, // true
Var, // var
While, // while
Comment, // 注释
Whitespace, // 空白
Continue, // continue
Break // break
}
// 表示源文件中 token所在的位置
#[derive(Debug, Clone, Copy)]
pub struct TokenPosition {
pub line: usize,
column: usize,
}
impl TokenPosition {
fn new() -> Self {
TokenPosition {
line: 1,
column: 1,
}
}
}
#[derive(Debug, Clone)]
pub enum ScannerError {
MissingStringTerminator(TokenPosition), // 字符串没有以 " 结尾
UnexpectedCharacter(char, TokenPosition), // 未知字符
}
pub type Lexeme = String;
// 记号流
#[derive(Debug)]
pub struct TokenWithContext {
pub token: Token, // 记号具体的类型
pub lexeme: String, // 源字符
pub position: TokenPosition, // 标记该记号第一个字符在源文件的位置
}
pub struct Scanner<'a> {
source: MultiPeek<Chars<'a>>, // 源文件
current_position: TokenPosition, // 扫描器当前位置
current_lexeme: String, // 扫描器当前扫描的片段
}
impl<'a> Scanner<'a> {
pub fn new(source: &'a str) -> Self {
Scanner {
source: multipeek(source.chars()),
current_position: TokenPosition::new(),
current_lexeme: String::new(),
}
}
}
impl<'a> Scanner<'a> {
// 获得数字字符
fn number(&mut self) -> Token {
// 从当前位置 推进 最远非数字字符
self.advance_while(&is_digit);
// 如果后面跟着一个小数点, 且小数点后跟着一个数字, 则继续推进
if self.peek_check2(&|c| c == '.', &is_digit) {
// 推进一个小数点
self.advance();
// 循环小数结尾
self.advance_while(&is_digit);
}
let num = self.current_lexeme.parse::<f64>().unwrap();
Token::NumberLiteral(num)
}
// 获得 双引号内 字符串字符
fn string(&mut self) -> Result<Token, ScannerError> {
// 从 " 开始 一直找到 " 或者 \n 的前一个字符
self.advance_while(&|c| c != '\n' && c != '"');
// 校验下一个字符是否为 "
if !self.advance_if_match('"') { // 如果 不为 " 未知字符出错
return Err(ScannerError::MissingStringTerminator(self.current_position));
}
// 获得 双引号 内的字符串字符
// let s = self.current_lexeme
// .chars()
// .skip(1)
// .take(self.current_lexeme.len() - 2)
// .collect::<String>();
let s = self.current_lexeme[1..self.current_lexeme.len() - 1].to_string();
return Ok(Token::StringLiteral(s));
}
// 判断是否是关键字
fn identifier(&mut self) -> Token {
// 循环推进字符
self.advance_while(&is_alphanumeric);
match self.current_lexeme.as_str() {
"and" => Token::And,
"class" => Token::Class,
"else" => Token::Else,
"false" => Token::False,
"for" => Token::For,
"def" => Token::Def,
"if" => Token::If,
"Null" => Token::Null,
"or" => Token::Or,
"print" => Token::Print,
"return" => Token::Return,
"super" => Token::Super,
"this" => Token::This,
"true" => Token::True,
"var" => Token::Var,
"while" => Token::While,
"break" => Token::Break,
"continue" => Token::Continue,
identifier => Token::Identifier(identifier.into()),
}
}
// 推进一个字符, 并返回外部
fn advance(&mut self) -> Option<char> {
let next_char = self.source.next();
if let Some(c) = next_char {
self.current_lexeme.push(c); // 增加该字符到当前扫描的片段
if c == '\n' { // 换行 或者空行处理
self.current_position.line += 1;
self.current_position.column = 1;
} else {
self.current_position.column += 1;
}
}
next_char
}
// 接收一个闭包, 传入一个前看字符 到闭包中进行校验
fn peek_check1(&mut self, closure: &Fn(char) -> bool) -> bool {
self.source.reset_peek();
match self.source.peek() {
Some(&c) => {
return closure(c);
}
None => {
return false;
}
}
}
// 接收两个闭包, 传入前看两个字符, 依次校验
fn peek_check2(&mut self, closure1: &Fn(char) -> bool, closure2: &Fn(char) -> bool) -> bool {
self.source.reset_peek();
match self.source.peek() {
Some(&c1) => {
match self.source.peek() {
Some(&c2) => {
return closure1(c1) && closure2(c2);
}
None => return false
}
}
None => {
return false;
}
}
}
// 外部传进来一个字符, 和前看字符比较, 如果相同, 消耗掉返回true
fn advance_if_match(&mut self, expected: char) -> bool {
if self.peek_check1(&|c| c == expected) {
self.advance();
return true;
}
false
}
// 循环 前看字符 并传入闭包中, 如果前看字符满足闭包条件返回true, 则推进一个字符, 直到 闭包返回 false
fn advance_while(&mut self, closure: &Fn(char) -> bool) {
while self.peek_check1(closure) {
self.advance(); // 如果前看1个字符 符合闭包, 则推进一个字符
}
}
fn scan_next(&mut self) -> Option<Result<TokenWithContext, ScannerError>> {
let current_position = self.current_position;
self.current_lexeme.clear(); // 每次执行扫描, 清除上次扫描保存的字符
// 获得下个字符
let next_char = match self.advance() {
Some(c) => c,
None => return None,
};
// 根据字符分类
let token_ret = match next_char {
'(' => Ok(Token::LeftParen),
')' => Ok(Token::RightParen),
'{' => Ok(Token::LeftBrace),
'}' => Ok(Token::RightBrace),
',' => Ok(Token::Comma),
'.' => Ok(Token::Dot),
'-' => Ok(Token::Minus),
'+' => Ok(Token::Plus),
';' => Ok(Token::Semicolon),
'*' => Ok(Token::Star),
'!' => {
if self.advance_if_match('=') {
Ok(Token::BangEqual)
} else {
Ok(Token::Bang)
}
}
'=' => {
if self.advance_if_match('=') {
Ok(Token::EqualEqual)
} else {
Ok(Token::Equal)
}
}
'>' => {
if self.advance_if_match('=') {
Ok(Token::GreaterEqual)
} else {
Ok(Token::Greater)
}
}
'<' => {
if self.advance_if_match('=') {
Ok(Token::LessEqual)
} else {
Ok(Token::Less)
}
}
'/' => {
if self.advance_if_match('/') {
// 注释 需要获取到这行的最后一个字符
self.advance_while(&|c| c != '\n');
Ok(Token::Comment)
} else {
Ok(Token::Slash)
}
}
'"' => self.string(),
c if is_whitespace(c) => {
Ok(Token::Whitespace)
}
c if is_digit(c) => Ok(self.number()),
c if is_alpha(c) => Ok(self.identifier()), // 最后校验是否是关键字
c => Err(ScannerError::UnexpectedCharacter(c, self.current_position))
};
Some(token_ret.map(|token| {
TokenWithContext {
token: token,
lexeme: self.current_lexeme.clone(),
position: current_position,
}
}))
}
}
// 迭代获取扫描到的 TokenWithContext
impl<'a> Iterator for Scanner<'a> {
type Item = Result<TokenWithContext, ScannerError>;
fn next(&mut self) -> Option<Self::Item> {
self.scan_next()
}
}
#[cfg(test)]
mod tests {
use crate::Scanner;
#[test]
fn it_works() {
let mut s = Scanner::new(r#"1+2+3"#);
for i in s {
println!("{:?}", i);
}
}
}