#编译器 #miden #diagnostics #spans #source #infrastructure #polygon

miden-diagnostics

Polygon Miden 编译器的诊断基础设施

1 个不稳定版本

0.1.0 2023年7月12日

编程语言 中排名 408

Download history 26/week @ 2024-04-20 23/week @ 2024-04-27 20/week @ 2024-05-04 18/week @ 2024-05-11 22/week @ 2024-05-18 14/week @ 2024-05-25 16/week @ 2024-06-01 12/week @ 2024-06-08 20/week @ 2024-06-15 23/week @ 2024-06-22 3/week @ 2024-06-29 3/week @ 2024-07-06 23/week @ 2024-07-13 26/week @ 2024-07-20 48/week @ 2024-07-27 102/week @ 2024-08-03

每月下载量 199
用于 7 个crate(6个直接使用)

MIT 许可证

65KB
1K SLoC

miden-diagnostics

此crate提供有用的编译器诊断基础设施,旨在在执行某种类型编译的各种Miden组件之间共享/重用,例如AirScript、(开发中)的Miden IR和Miden汇编。

请参阅 miden-parsing 以获取构建在所提供低级工具之上的解析实用工具。完整的编译器前端应在该crate的 Scanner 类型之上构建一个词法分析器,并实现它的 Parser 特性。许多涉及生成包含源span的AST的细节都由该crate处理,但也可以自行构建替代方案。

组件

此crate提供两个不同但相关的用例的功能

  • 跟踪编译器源,这些源中的位置/范围,以及支持将范围关联到Rust数据结构的基础设施,例如 #[derive(Spanned)]
  • 构建、生成和显示/捕获编译器诊断,可选地装饰有源span以渲染类似rustc的消息/警告/错误。

源级调试信息

我们基于codespan crate提供的某些原语,以尽可能高效的方式提供跟踪源、位置和范围的丰富功能。意图是确保装饰编译器结构以包含源位置的成本尽可能低,同时保留轻松获取有关这些结构的有用信息的能力,例如给定对象是从哪个文件/行/列派生出来的。

以下支持此用例的关键特性

  • SourceId 是指向加载到内存中的特定源文件的紧凑引用
  • SourceIndex 是对源文件中某个特定位置的紧凑引用。
  • SourceSpan 是一个紧凑的结构体,它引用源文件中某个特定范围的多个位置。此类型是您将与之交互的最常见的值类型,用于生成指向源文件中特定字符范围的漂亮诊断信息。
  • Span<T> 是一种类型,用于非侵入性地将 SourceSpan 与类型 T 关联;解引用到 T,并实现一系列其他特质,这些特质以传递方式委托给 T,例如 PartialEq
  • Spanned 是一个特质,类型可以实施以在请求时生成 SourceSpan。类型 Span<T> 实现了这个特质,并且对于所有 Box<T>,其中 T: Spanned,这个特质将被自动实现。
  • CodeMap 是一个线程安全的数据结构,由编译器驱动程序构建一次,并在所有子线程之间共享。它存储读取到内存中的文件,通过源文件名称(无论是否真实或合成)进行去重。它提供了API,可用于从 SourceIdSourceIndexSourceSpan 获取有用的高级信息,例如文件名、行和列号;以及获取原始源内容的一个切片。

诊断

我们利用由 codespan_reporting 包提供的某些实用程序,提供更丰富的功能集来生成、显示和/或捕获编译器诊断。

以下支持此用例的关键特性

  • Diagnostic 是一个类型,表示编译器诊断,包括严重性、消息以及一个(可选的)标签/注释集合。
  • ToDiagnostic 是一个特质,它表示从类型生成 Diagnostic 的能力。在实际应用中,这通常用于将错误转换为诊断,当编译应该继续进行,因为错误是非致命的时候。例如,在解析/语义分析期间,您通常希望在编译任务失败之前捕获尽可能多的错误,而不是在遇到第一个错误时退出。
  • Severity 表示诊断是错误、警告、缺陷还是简单的备注。
  • Label 用于将源位置与包含描述性文本的诊断相关联。标签有主/次两种口味,这会影响在显示时标签的顺序/渲染方式。
  • InFlightDiagnostic 提供了一个流畅的、构建器模式API,用于动态构建和发出诊断,下面有示例。
  • Emitter 是一个特质,可以用来控制如何发出诊断。这可以用来做有用的事情,比如禁用诊断、捕获它们用于测试,或控制它们的显示方式;内置的 NullEmitterCaptureEmitterDefaultEmitter 类型执行相应的功能。
  • DiagnosticsHandler 是一个线程安全类型,由编译器驱动程序构建一次,然后在所有执行线程之间共享。它可以配置为使用特定的 Emitter 实现类型,控制发出诊断的最小严重性,将警告转换为错误,等等。

示例

抽象语法树

use miden_diagnostics::{SourceSpan, Span, Spanned};

#[derive(Clone, PartialEq, Eq, Hash, Spanned)]
pub struct Ident(#[span] Span<String>);

#[derive(Spanned)]
pub enum Expr {
    Var(#[span] Ident),
    Int(#[span] Span<i64>),
    Let(#[span] Let),
    Binary(#[span] BinaryExpr),
    Unary(#[span] UnaryExpr),
}

#[derive(Spanned)]
pub struct Let {
    pub span: SourceSpan,
    pub var: Ident,
    pub body: Box<Expr>,
}

#[derive(Spanned)]
pub struct BinaryExpr {
    pub span: SourceSpan,
    pub op: BinaryOp,
    pub lhs: Box<Expr>,
    pub rhs: Box<Expr>,
}

#[derive(Copy, Clone, PartialEq, Eq)]
pub enum BinaryOp {
    Add,
    Sub,
    Mul,
    Div,
}

#[derived(Spanned)]
pub struct UnaryExpr {
    pub span: SourceSpan,
    pub op: UnaryOp,
    pub rhs: Box<Expr>,
}

#[derive(Copy, Clone, PartialEq, Eq)]
pub enum UnaryOp {
    Neg,
}

诊断

use std::sync::Arc;

use miden_diagnostics::*;

const INPUT_FILE = r#"
let x = 42
in
  let y = x * 2
  in
    x + y
"#;

pub fn main() -> Result<(), ()> {
    // The codemap is where parsed inputs are stored and is the base for all source locations
    let codemap = Arc::new(CodeMap::new());
    // The emitter defines how diagnostics will be emitted/displayed
    let emitter = Arc::new(DefaultEmitter::new(ColorChoice::Auto));
    // The config provides some control over what diagnostics to display and how
    let config = DiagnosticsConfig::default();
    // The diagnostics handler itself is used to emit diagnostics
    let diagnostics = Arc::new(DiagnosticsHandler::new(config, codemap.clone(), emitter));

    // In our example, we're adding an input to the codemap, and then requesting the compiler compile it
    codemap.add("nofile", INPUT_FILE.to_string());
    compiler::compile(codemap, diagnostics, "nofile")
}

mod compiler {
    use miden_diagnostics::*;

    pub fn compile<F: Into<FileName>>(codemap: Arc<CodeMap>, diagnostics: Arc<DiagnosticsHandler>, filename: F) -> Result<(), ()> {
        let filename = filename.into();
        let file = codemap.get_by_name(&filename).unwrap();

        // The details of parsing are left as an exercise for the reader, but it is expected
        // that for Miden projects that this crate will be combined with `miden-parsing` to
        // handle many of the details involved in producing a stream of tokens from raw sources
        //
        // In this case, we're parsing an Expr, or returning an error that has an associated source span
        match parser::parse(file.source(), &diagnostics)? {
            Ok(_expr) => Ok(()),
            Err(err) => {
                diagnostics.diagnostic(Severity::Error)
                  .with_message("parsing failed")
                  .with_primary_label(err.span(), err.to_string())
                  .emit();
                Err(())
            }
        }
    }
}

依赖项

~3–10MB
~99K SLoC