72 个版本

0.12.0-alpha.1 2024 年 8 月 18 日
0.11.6 2023 年 6 月 3 日
0.11.5 2023 年 1 月 22 日
0.11.4 2022 年 1 月 5 日
0.3.0 2018 年 7 月 16 日

#101解析器实现

Download history 226/week @ 2024-05-01 148/week @ 2024-05-08 118/week @ 2024-05-15 167/week @ 2024-05-22 128/week @ 2024-05-29 138/week @ 2024-06-05 140/week @ 2024-06-12 82/week @ 2024-06-19 65/week @ 2024-06-26 47/week @ 2024-07-03 91/week @ 2024-07-10 126/week @ 2024-07-17 82/week @ 2024-07-24 94/week @ 2024-07-31 73/week @ 2024-08-07 196/week @ 2024-08-14

460 每月下载量
14crate(5 个直接)中使用

MIT 许可协议

585KB
7.5K SLoC

RESS

Rusty EcmaScript 扫描器

Github Actions crates.io last commit master

用 Rust 编写的 JS 扫描器/标记化器

使用方法

与 ress 交互的主要方式是通过实现 Scanner 结构体,该结构体对 Item 结构体进行迭代。 Item 有三个字段:用于找到的 Tokentoken,表示原始字符串中字节位置开始和结束的 span,以及表示行和列的起始和结束字符位置的 location。其定义如下。

Item {
    token: Token::Punct(Punct::Bang),
    span: Span {
        start: 0,
        end: 1,
    },
    location: SourceLocation {
        start: Position {
            line: 1,
            column: 1,
        },
        end: Position {
            line: 1,
            column: 2,
        }
    }
}

注意:EcmaScript 规范允许有 4 个换行符,但现代文本编辑器通常只渲染其中的两个,位置行号将计算这些未渲染的行。

以下是一个示例,它将检查一些 JS 文本是否存在分号,如果找到则引发 panic。

use ress::Scanner;

static JS: &str = include_str!("index.js");

fn main() {
    let s = Scanner::new(JS);
    for item in s {
        let token = item.unwrap().token;
        if token.matches_punct_str(";") {
            panic!("A semi-colon!? Heathen!");
        }
    }
    println!("Good show! Why use something that's optional?")
}

Item 的最重要的部分是 Token 枚举,它将代表由 ECMAScript 规范 支持的 11 种不同类型的标记。

在 JavaScript 中 很难判断一个正斜杠是除法还是正则表达式的开始。上面的 Scanner 将通过跟踪先前解析的标记来自动检测正则表达式,这使得事情变得非常方便,但是如果您正在将 JavaScript 解析为 AST,您可能已经需要跟踪相同的信息。在这种情况下,您可能不想承担自动正则表达式检测的性能成本,您会想要使用 ManualScanner。它不是暴露基本 Iterator 接口,而是暴露了两个主要的驱动扫描器的方法:next_tokennext_regex。第一个方法在遇到正则表达式时将始终返回 //=,后者如果下一个标记不是正则表达式将失败。

use ress::{ManualScanner, prelude::*};

fn main() {
    let mut s = ManualScanner::new("let x = /[a-z]+/g");
    while let Some(Ok(item)) = s.next_token() {
        if item.token.matches_punct(Punct::ForwardSlash)
        || item.token.matches_punct(Punct::ForwardSlashEqual) {
            // it could be a 1 or 2 length prefix
            let regex = s.next_regex(1).unwrap().unwrap();
            println!("{:?}", regex);
        } else {
            println!("{:?}", item);
        }
    }
}

ES 标记

  • 布尔文字
  • 文件结束
  • 标识符
  • 关键字
  • 空文字
  • 数值文字
  • 标点符号
  • 字符串文字
  • 正则表达式文字
  • 模板字符串
  • 注释

请注意,关键字在JavaScript中从ES3到ES2019之间有很多变化,所以在ES2019环境中可能会发现一些在ES3环境中不存在的解析为关键字的项目,这应该在更高层次上处理。一个很好的例子是yield,它有时是关键字,有时是标识符,这个包总是将其解析为关键字。截至本README的编写,ress支持所有第二阶段和第三阶段ECMAScript提议中的标记,除了#!注释和数字分隔符。

对于每个标记情况,都有一个结构或枚举来提供额外的信息,除了NullLiteralEoF,这两个应该很容易理解。更复杂的项目实现了ToString,它应该能将您带回到该标记的原始JavaScript文本。此外,Token枚举还提供了一些辅助函数,用于构建该图片而无需从枚举中拉取内部数据。以Punct情况为例,辅助函数看起来像这样。

fn is_punct(&self) -> bool;
fn matches_punct(&self, p: Punct) -> bool;
fn matches_punct_str(&self, s: &str) -> bool;

每个情况都有类似的一组函数。

类似于所有IteratorsScanner有一个next方法,它还有一个look_ahead方法,允许您在不前进的情况下解析下一个值。使用这个方法可以是一个方便的方式来获取下一个标记,而无需执行可变借用,但是您将承担解析该标记两次的成本。所有Iterators都可以通过一个peek方法转换成一个Peekable迭代器,这允许您只支付一次成本就向前看,但是peek执行可变借用,这意味着它需要在与next调用不同的作用域中。

// look_ahead
let js = "function() { return; }";
let mut s = Scanner::new(js);
let current = s.next();
let next = s.look_ahead();
let new_current = s.next();
assert_eq!(next, new_current);
// peekable (fails to compile)
let p = Scanner::new(js).peekable();
let current = s.next(); // <-- first mutable borrow
let next = p.peek(); // <-- second mutable borrow

对于更复杂的向前看场景,Scanner提供了get_stateset_state方法。这些方法允许您捕获当前位置和任何上下文的快照,然后稍后重置到该位置和上下文。

let js = "function() {
    return 0;
};";
let mut s = Scanner::new(js);
let start = s.get_state();
assert_eq!(s.next().unwrap().unwrap().token, Token::Keyword(Keyword::Function));
assert_eq!(s.next().unwrap().unwrap().token, Token::Punct(Punct::OpenParen));
assert_eq!(s.next().unwrap().unwrap().token, Token::Punct(Punct::CloseParen));
s.set_state(start);
assert_eq!(s.next().unwrap().unwrap().token, Token::Keyword(Keyword::Function));

为什么?

编写新的JavaScript开发工具不是很好吗?clear-comments示例证明了您如何使用这个crate来完成这项工作。这个示例将接受一个JavaScript文件,并输出一个带有所有注释都已被删除的版本。下面是如何看到它在行动的示例(假设在项目根目录中有一个名为in.js的文件)。

cargo run --example clear-comments -- ./in.js ./out.js

性能

以下统计数据是在运行cargo +nightly bench在MBP(2.9 GHz i9-8850H & 16bg RAM)上得到的。

大小 时间 +/-
Angular 1.5 1.16mb 18.991 ms 4.393 ms
jQuery 271.75kb 7.218 ms 577.236 μs
React 59.09kb 1.976 ms 116.139 μs
ReactDOM 641.51kb 16.880 ms 3.614 ms
Vue 289.30kb 9.675 ms 1.402 ms

如果您对性能感兴趣,而无需等待cargo bench完成,您可以运行以下命令。

cargo run --example major_libs

贡献

查看contributing.md

依赖关系