5个版本

0.2.0 2021年5月12日
0.1.3 2020年10月28日
0.1.2 2020年10月27日
0.1.1 2020年10月16日
0.1.0 2020年10月16日

177 in 解析器工具

自定义许可

89KB
926

词法分析器

此库提供了一个基于有限自动机的词法分析引擎,可以灵活地标记输入流。


lib.rs:

此模块导出基于确定有限状态自动机的简单词法分析器的API。

使用Flexer定义的词法分析器能够分析复杂程度较高的语言,虽然它们以简单的方式定义(类似于正则文法),但它们也可以与上下文相关语言一起工作。

定义词法分析器的过程涉及用户执行以下操作

  1. 创建一个包装FlexerLexer类型。
  2. 创建一个State类型,用于保存用户定义的词法分析状态。
  3. State类型实现State
  4. Lexer实现Definition,以及用于分析语言的词法转换规则。

使用Flexer定义词法分析器的结果是使用此库编写的代码与该库生成的用于特化您的词法分析器的代码的混合体。

编写词法分析器

由于Flexer是用于编写词法分析器的库,因此我们不会包括如何定义词法分析器的示例。以下示例定义了一个小型语言的词法分析器,并展示了如何将flexer代码生成步骤与项目的构建集成。

语言

我们将为以下EBNF语法定义一个词法分析器。

a-word      = 'a'+;
b-word      = 'b'+;
word        = a-word | b-word;
space       = ' ';
spaced-word = space, word;
language    = word, spaced-word*;

词法分析器的输出

每个词法分析器都需要具有将一系列标记写入其输出的能力。基于flexer的词法分析器可以使用任何它想要的输出类型,但此语言将使用一个非常简单的Token类型,封装在TokenStream中。

#[derive(Clone)]
pub enum Token {
    /// A word from the input, consisting of a sequence of all `a` or all `b`.
    Word(String),
    /// A token that the lexer is unable to recognise.
    Unrecognized(String)
}

#[derive(Clone,Default)]
pub struct TokenStream {
    tokens:Vec<Token>
}

impl TokenStream {
    pub fn push(&mut self,token:Token) {
        self.tokens.push(token)
    }
}

这些令牌将由我们的词法分析器在识别出我们语言的合法部分时插入到词法流中。

无论您选择将词法分析器的Output类型设为什么,它都需要实现std::clone::Clonestd::default::Default这两个功能。

词法分析器的状态

每个基于Flexer的词法分析器都操作在一个状态上,这个状态保存了定义特定词法分析器所需的所有用户定义状态信息。这个状态类型必须符合State特质,它定义了必须提供给flexer的重要功能。

在我们的语言中,我们希望在看到初始单词且该单词前面没有空格字符后,才允许匹配带有前导空格字符的单词。为此,我们需要在词法分析器中记录我们已经“看到”了第一个单词。根据State特质的要求,我们还需要向flexer提供初始状态、状态注册表以及我们使用的书签。

use enso_flexer::group;
use enso_flexer::prelude::reader::BookmarkManager;
use enso_flexer::State;
#
#
#
#
#


// === LexerState ===

#[derive(Debug)]
pub struct LexerState {
    /// The registry for groups in the lexer.
    lexer_states:group::Registry,
    /// The initial state of the lexer.
    initial_state:group::Identifier,
    /// The state entered when the first word has been seen.
    seen_first_word_state:group::Identifier,
    /// The bookmarks for this lexer.
    bookmarks:BookmarkManager
}

flexer库提供了一些有用的功能来帮助定义您的词法分析器状态,例如用于包含您的词法分析器可能转换的各种状态的group::Registry以及用于存储书签的prelude::reader::BookmarkManager

书签

为了启用任意长度的前瞻,flexer提供了一种“书签”输入流中某个点的系统,以便词法分析器可以稍后返回到该点。事实上,这个机制在实现中默认用于处理重叠规则,因此prelude::reader::BookmarkManager默认为您提供了某些书签。

然而,作为用户,您可以定义额外的书签作为您状态的一部分,并在词法分析器的转换函数中标记或返回到这些书签(关于这一点将在下面详细讨论)。

现在我们已经定义了我们的状态类型,我们需要为它定义一个State的实现。这基本上是一个简单的练习,但有两个函数([State::new()] 和 State::specialize)需要特别注意。我们将在下面详细讨论这两个函数。

use enso_flexer::generate;
use enso_flexer::generate::GenError;
use enso_flexer::prelude::AnyLogger;
#
#
#
#
#
#
#
#

impl enso_flexer::State for LexerState {
    fn new(_logger:&impl AnyLogger) -> Self {
        // Here we construct all of the elements needed for our lexer state. This function can
        // contain arbitrarily complex logic and is only called once at initialization time.
        let mut lexer_states      = group::Registry::default();
        let initial_state         = lexer_states.define_group("ROOT",None);
        let seen_first_word_state = lexer_states.define_group("SEEN FIRST WORD",None);
        let bookmarks             = BookmarkManager::new();
        Self{lexer_states,initial_state,seen_first_word_state,bookmarks}
    }

    fn initial_state(&self) -> group::Identifier {
        self.initial_state
    }

    fn groups(&self) -> &group::Registry {
        &self.lexer_states
    }

    fn groups_mut(&mut self) -> &mut group::Registry {
        &mut self.lexer_states
    }

    fn bookmarks(&self) -> &BookmarkManager {
        &self.bookmarks
    }

    fn bookmarks_mut(&mut self) -> &mut BookmarkManager {
        &mut self.bookmarks
    }

    fn specialize(&self) -> Result<String,GenError> {
        // It is very important to pass both the type name of your lexer and your output
        // correctly here. This function should always be implemented as a call to the
        // below-used function.
        generate::specialize(self,"TestLexer","Token")
    }
}

定义词法分析器类型

随着我们的状态类型定义完成,我们现在可以定义词法分析器本身了!

我们在flexer中定义词法分析器的方式背后的理念是,通过使用一系列std::ops::Deref实现的链来使不同的部分感觉像一个统一的整体。本身Flexer已经实现了对您状态类型的引用,所以剩下的只是做以下事情:

  1. 定义您自己的词法分析器结构体,它包含一个由您的状态和输出类型参数化的Flexer实例。
use enso_flexer::Flexer;
use enso_flexer::prelude::logger::Disabled;

type Logger = Disabled;
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#


// === Lexer ===

pub struct Lexer {
   lexer:Flexer<LexerState,TokenStream,Logger>
}

您会注意到,Flexer还从Enso日志库中获取一个日志实现作为类型参数。这允许库的用户配置他们词法分析器中的日志行为。我们建议为当前日志类型进行别名,以方便使用。

  1. 为您的词法分析器实现一个new()函数。
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#

impl Lexer {
    pub fn new() -> Self {
        let lexer = Flexer::new(Logger::new("Lexer"));
        Lexer{lexer}
    }
}
  1. 为您的词法分析器定义std::ops::Derefstd::ops::DerefMut
use std::ops::Deref;
use std::ops::DerefMut;
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#

impl Deref for Lexer {
    type Target = Flexer<LexerState,TokenStream,Logger> ;
    fn deref(&self) -> &Self::Target {
        &self.lexer
    }
}
impl DerefMut for Lexer {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.lexer
    }
}

您会注意到,在这里我们用Logger实例化了flexer。这用于在开发过程中提供调试信息,并且可以从您词法分析器的所有作用域中访问。然而,在发布模式下,"trace"、"debug"和"info"级别的日志调用被优化掉。

定义词法规则

基于Flexer的词法分析器通过匹配一系列描述它试图分析的语言的automata::pattern::Pattern来操作。它将这些模式与"转换函数"相结合,当模式在词法分析器的输入上匹配时,可以执行任意代码。

为了定义词法规则,我们需要为我们的词法分析器实现Definition,特别是[Definition::define()]函数。

use enso_flexer::automata::pattern::Pattern;
use enso_flexer::group::Registry;
use enso_flexer;
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#

impl enso_flexer::Definition for Lexer {
    fn define() -> Self {
        // First we instantiate our lexer. Definitions take place _directly_ on the lexer, and
        // manipulate runtime state.
        let mut lexer = Self::new();

        // Then, we define the patterns that we're going to use. For an overview of the p
        let a_word        = Pattern::char('a').many1();
        let b_word        = Pattern::char('b').many1();
        let space         = Pattern::char(' ');
        let spaced_a_word = &space >> &a_word;
        let spaced_b_word = &space >> &b_word;
        let any           = Pattern::any();
        let end           = Pattern::eof();

        // Next, we define groups of lexer rules. This uses the groups that we've defined in our
        // lexer's state, and the patterns we've defined above.
        let root_group_id = lexer.initial_state;
        let root_group    = lexer.groups_mut().group_mut(root_group_id);
        root_group.create_rule(&a_word,"self.on_first_word(reader)");
        root_group.create_rule(&b_word,"self.on_first_word(reader)");
        root_group.create_rule(&end,   "self.on_no_err_suffix_first_word(reader)");
        root_group.create_rule(&any,   "self.on_err_suffix_first_word(reader)");

        let seen_first_word_group_id = lexer.seen_first_word_state;
        let seen_first_word_group    = lexer.groups_mut().group_mut(seen_first_word_group_id);
        seen_first_word_group.create_rule(&spaced_a_word,"self.on_spaced_word(reader)");
        seen_first_word_group.create_rule(&spaced_b_word,"self.on_spaced_word(reader)");
        seen_first_word_group.create_rule(&end,          "self.on_no_err_suffix(reader)");
        seen_first_word_group.create_rule(&any,          "self.on_err_suffix(reader)");

        lexer
    }

    /// This function just returns the lexer's groups.
    fn groups(&self) -> &Registry {
        self.lexer.groups()
    }

    /// Code you want to run before lexing begins.
    fn set_up(&mut self) {}

    /// Code you want to run after lexing finishes.
    fn tear_down(&mut self) {}
}

转换函数

您可能想知道为什么转换函数被指定为字符串。这允许我们一旦定义了它,就为您的词法分析器生成高效、专门的代码。关于这一点,我们稍后再讨论。

词法分析器中的group::Group类似于一个在栈上操作的状态。转换函数可以任意激活或停用flexer栈上的组,允许您执行上下文相关的词法分析行为。有关更多信息(包括如何使用父组继承规则),请参阅相关模块。

有关上面使用的automata::pattern::Pattern API的更多信息,请参阅此crate中的相关模块。

定义转换函数

您可能会注意到,在上面,我们告诉了规则使用一些我们还没有讨论过的转换函数。这些函数可以定义在任何您喜欢的地方,只要它们在定义词法分析器的文件的作用域内。然而,我们建议在词法分析器本身上定义它们,这样它们就可以访问和操作词法分析器状态,这就是我们在这里要做的。

use enso_flexer::prelude::ReaderOps;
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#

impl Lexer {
     pub fn on_first_word<R:ReaderOps>(&mut self, _reader:&mut R) {
        let str = self.current_match.clone();
        let ast = Token::Word(str);
        self.output.push(ast);
        let id = self.seen_first_word_state;
        self.push_state(id);
    }

    pub fn on_spaced_word<R:ReaderOps>(&mut self, _reader:&mut R) {
        let str = self.current_match.clone();
        let ast = Token::Word(String::from(str.trim()));
        self.output.push(ast);
    }

    pub fn on_err_suffix_first_word<R:ReaderOps>(&mut self, _reader:&mut R) {
        let ast = Token::Unrecognized(self.current_match.clone());
        self.output.push(ast);
    }

    pub fn on_err_suffix<R:ReaderOps>(&mut self, reader:&mut R) {
        self.on_err_suffix_first_word(reader);
        self.pop_state();
    }

    pub fn on_no_err_suffix_first_word<R:ReaderOps>(&mut self, _reader:&mut R) {}

    pub fn on_no_err_suffix<R:ReaderOps>(&mut self, reader:&mut R) {
        self.on_no_err_suffix_first_word(reader);
        self.pop_state();
    }
}

魔法转换函数

转换函数可以说是Flexer的“秘方”,当规则匹配时会被调用,并允许任意代码操作词法分析器。这意味着Flexer可以用来定义非常复杂的语法,同时仍然保持简单的接口并确保执行性能。

您会注意到这些函数有几个共同点

  1. 它们有一个类型参数 R,它符合 prelude::LazyReader 特性。
  2. 它们接受一个类型为 R 的参数,即作为函数的第一个非 self 参数的词法分析器正在运行的读取器。
  3. 任何额外的参数都必须在特殊化规则将生成的范围内有效。

这两者结合起来,允许转换函数操作词法分析器正在读取的文本。

特殊化词法分析器

为了真正 使用 您定义的词法分析器,您需要将其特殊化到您定义的规则。不幸的是,cargo 没有支持后构建钩子,所以这个过程比我们希望的更复杂。

  1. 创建一个执行上述词法分析器定义的文件。只要它们公开暴露,它就可以在其crate中使用多个文件。
  2. 创建一个具有 build.rs 中的预构建钩子的单独 cargo 项目。
  3. 在那个 build.rs 中,您需要
    1. 导入词法分析器定义并使用 ::define() 实例化它。
    2. 在结果词法分析器上调用 [State::specialize()]。这将生成一个包含优化后的词法分析器实现的字符串。
    3. 将生成的代码和原始词法分析器定义的代码写入输出文件。
  4. 从您的 lib.rs 中重新导出此输出文件。

特殊化过程将生成相当多的代码,但最重要的是,它将生成 pub fn run<R:LazyReader>(&mut self, mut reader:R) -> Result<Output>,其中 Output 是您的词法分析器的令牌类型。所有这些函数都是在您的词法分析器类型上定义的(即提供给 specialize() 的名称)。

总结

该扩展器允许其客户端定义高度优化的词法分析器实现,能够分析复杂度较高的编程语言。

依赖项

~17–31MB
~468K SLoC