關(guān)于Databend源碼-token解析

一、 databend自定義token實(shí)現(xiàn)

舉個例子: 在databend中將sql進(jìn)行token化生成最終的AST

// 使用logos進(jìn)行l(wèi)exer
let tokens = tokenize_sql(case).unwrap();
let backtrace = Backtrace::new();
// 生成sql的AST
let stmts = parse_sql(&tokens, &backtrace).unwrap();
// 
for stmt in stmts {
      writeln!(file, "---------- Output ---------").unwrap();
      writeln!(file, "{}", stmt).unwrap();
      writeln!(file, "---------- AST ------------").unwrap();
      writeln!(file, "{:#?}", stmt).unwrap();
      writeln!(file, "\n").unwrap();
}

在databend中將一個sql進(jìn)行token化少不了的struct Tokenizer,主要是結(jié)合databend中定義token類型:enum TokenKind,底層使用logos來完成最終的詞法解析。

pub struct Tokenizer<'a> {
    // 要被token化的原始sql
    source: &'a str,  
    // 用于token化的lexer:使用logos來進(jìn)行詞法解析              
    lexer: Lexer<'a, TokenKind>,
   // 
    eoi: bool,
}

看一下databend自身結(jié)合logos定義的一些token類型:TokenKind詳情
主要是通過#[derive(Logos)] 使用logos;

#[derive(Logos, Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum TokenKind {
    // 省略代碼
    // 主要涉及:空白類、標(biāo)識符、塊類型、轉(zhuǎn)義類、符號類、關(guān)鍵字類等
}

而logos進(jìn)行詞法解析的入口TokenKind::lexer(sql), logos是使用過程宏(proc_macro_derive)的方式,來為不同的自定義token化提供了lexer操作;進(jìn)而得到每個token:kind(類型)/source(原始內(nèi)容)/span(在原始內(nèi)容串的范圍),具體代碼如下:

impl<'a> Iterator for Tokenizer<'a> {
    // 得到databend里面定義Token
    type Item = Result<Token<'a>>;

    fn next(&mut self) -> Option<Self::Item> {
        match self.lexer.next() { // 在logos中Lexer實(shí)現(xiàn)了Iterator
            Some(kind) if kind == TokenKind::Error => { // 遍歷token類型
                let rest_span = Token { // 不滿意定義token規(guī)則的錯誤:看TokenKind中Error
                    source: self.source,
                    kind: TokenKind::Error,
                    span: self.lexer.span().start..self.source.len(),
                };
                Some(Err(ErrorCode::SyntaxException(rest_span.display_error( // 語法解析錯誤
                    "unable to recognize the rest tokens".to_string(),
                ))))
            }
            Some(kind) => Some(Ok(Token { // token解析正常
                source: self.source,
                kind,
                span: self.lexer.span(),
            })),
            None if !self.eoi => { // ??? 解析結(jié)束標(biāo)識
                self.eoi = true;
                Some(Ok(Token {
                    source: self.source,
                    kind: TokenKind::EOI,
                    span: (self.lexer.span().end)..(self.lexer.span().end),
                }))
            }
            None => None,  // 沒有可遍歷的內(nèi)容
        }
    }
}

再看看databend中定義的Token:

#[derive(Clone, PartialEq)]
pub struct Token<'a> {
    pub source: &'a str,      // token在被解析字符串的原始內(nèi)容
    pub kind: TokenKind,   // token類型
    pub span: Span,          // token在解析字符串中的范圍
}

完成token化: 主要是完成databend中自定義的token化

 // Tokenizer本身也實(shí)現(xiàn)Iterator trait,可以使用collect完成轉(zhuǎn)為Result<Vec<Token>>
 Tokenizer::new(sql).collect::<Result<Vec<_>>>()

接下來就是將已經(jīng)token化的Token生成Statement:使用nom文本解析器來完成該部分的;

/// Parse a SQL string into `Statement`s.
pub fn parse_sql<'a>(
    sql_tokens: &'a [Token<'a>],
    backtrace: &'a Backtrace<'a>,
) -> Result<Vec<Statement<'a>>> {
    match statements(Input(sql_tokens, backtrace)) {
        Ok((rest, stmts)) if rest[0].kind == TokenKind::EOI => Ok(stmts), // 結(jié)束標(biāo)識
        Ok((rest, _)) => Err(ErrorCode::SyntaxException( // 語法解析異常
            rest[0].display_error("unable to parse rest of the sql".to_string()),
        )),
        Err(nom::Err::Error(err) | nom::Err::Failure(err)) => { // 解析異常
            Err(ErrorCode::SyntaxException(err.display_error(())))
        }
        Err(nom::Err::Incomplete(_)) => unreachable!(),
    }
}

最終輸出databend本身的sql ast:

---------- Input ----------
show tables
---------- Output ---------
SHOW TABLES
---------- AST ------------
ShowTables {
    database: None,
    full: false,
    limit: None,
}

二、關(guān)于logos部分

  • 準(zhǔn)備: cargo.toml中引入logos
[dependencies]
logos = "0.12.0"
  • 使用:自定義Token
#[derive(Logos, Debug, PartialEq)]
enum Token {
    #[token("fast")]
    Fast,

    #[token(".")]
    Period,

    #[regex("[a-zA-Z]+")]
    Text,

    #[error]
    #[regex(r"[ \t\n\f]+", logos::skip)]
    Error,

    #[regex("[0-9]+", |lex| lex.slice().parse())]
    #[regex("[0-9]+k", kilo)]
    #[regex("[0-9]+m", mega)]
    Number(u64),
}
  • 測試
// 解析簡單的文本
#[test]
fn test_lexer_token_demo () {
    let mut tokens = Token::lexer("Create ridiculously fast Lexers.");
    assert_eq!(tokens.next(), Some(Token::Text));
    assert_eq!(tokens.span(), 0..6);
    assert_eq!(tokens.slice(), "Create");

    assert_eq!(tokens.next(), Some(Token::Text));
    assert_eq!(tokens.span(), 7..19);
    assert_eq!(tokens.slice(), "ridiculously");

    assert_eq!(tokens.next(), Some(Token::Fast));
    assert_eq!(tokens.span(), 20..24);
    assert_eq!(tokens.slice(), "fast");

    assert_eq!(tokens.next(), Some(Token::Text));
    assert_eq!(tokens.span(), 25..31);
    assert_eq!(tokens.slice(), "Lexers");

    assert_eq!(tokens.next(), Some(Token::Period));
    assert_eq!(tokens.span(), 31..32);
    assert_eq!(tokens.slice(), ".");

    assert_eq!(tokens.next(), None);
}

在自定義token中,定義回調(diào)函數(shù):

// 使用自定義回調(diào)函數(shù)
#[regex("[0-9]+", |lex| lex.slice().parse())]
#[regex("[0-9]+k", kilo)]   
#[regex("[0-9]+m", mega)]
Number(u64),

回調(diào)函數(shù)如下:

fn kilo(lex: &mut Lexer<Token>) -> Option<u64> {
    eprintln!("==execute kilo==");
    let slice = lex.slice();
    let n: u64 = slice[..slice.len() - 1].parse().ok()?;
    Some(n * 1_000)
}

fn mega(lex: &mut Lexer<Token>) -> Option<u64> {
    eprintln!("==execute mega==");
    let slice = lex.slice();
    let n: u64 = slice[..slice.len() - 1].parse().ok()?;
    Some(n * 1_000_000)
}

用例:

#[test]
fn test_callback_lexer_demo() {
    let mut lexer = Token::lexer("5 42k 75m");
    assert_eq!(lexer.next(), Some(Token::Number(5)));
    assert_eq!(lexer.span(), 0..1);
    assert_eq!(lexer.slice(), "5");

    assert_eq!(lexer.next(), Some(Token::Number(42_000)));
    assert_eq!(lexer.span(), 2..5);
    assert_eq!(lexer.slice(), "42k");

    assert_eq!(lexer.next(), Some(Token::Number(75_000_000)));
    assert_eq!(lexer.span(), 6..9);
    assert_eq!(lexer.slice(), "75m");

    assert_eq!(lexer.next(), None);
}

三、引用

logos
nom

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容