Rust代码生成与AST操作库swc_ecma_quote_macros的使用,高效实现JavaScript语法树宏转换
Rust代码生成与AST操作库swc_ecma_quote_macros的使用,高效实现JavaScript语法树宏转换
安装
在项目目录中运行以下Cargo命令:
cargo add swc_ecma_quote_macros
或者将以下行添加到Cargo.toml中:
swc_ecma_quote_macros = "22.0.0"
基本使用示例
swc_ecma_quote_macros提供了quote!宏,可以方便地生成JavaScript语法树节点。以下是一个基本示例:
use swc_ecma_quote_macros::quote;
use swc_ecma_ast::*;
fn create_function() -> Function {
quote!("function add(a, b) { return a + b; }" as Function)
}
完整示例
下面是一个更完整的示例,展示了如何使用swc_ecma_quote_macros库来转换JavaScript代码:
use swc_ecma_quote_macros::quote;
use swc_ecma_ast::*;
use swc_common::sync::Lrc;
use swc_common::SourceMap;
use swc_ecma_codegen::{Emitter, Config};
fn main() {
// 创建AST节点
let function = quote!("function add(a, b) { return a + b; }" as Function);
// 创建模块,包含我们的函数
let module = Module {
span: Default::default(),
body: vec![
ModuleItem::Stmt(Stmt::Decl(Decl::Fn(FnDecl {
ident: Ident::new("add".into(), Default::default()),
declare: false,
function: Box::new(function),
}))),
],
shebang: None,
};
// 设置代码生成器
let cm: Lrc<SourceMap> = Default::default();
let mut buf = vec![];
{
let mut emitter = Emitter {
cfg: Config {
..Default::default()
},
cm: cm.clone(),
wr: Box::new(swc_ecma_codegen::text_writer::JsWriter::new(
cm.clone(),
"\n",
&mut buf,
None,
)),
comments: None,
};
emitter.emit_module(&module).unwrap();
}
// 打印生成的代码
println!("{}", String::from_utf8(buf).unwrap());
}
动态生成AST节点
swc_ecma_quote_macros还支持动态插入变量到生成的AST中:
use swc_ecma_quote_macros::quote;
use swc_ecma_ast::*;
fn create_function_with_params(params: Vec<Pat>) -> Function {
let body = BlockStmt {
span: Default::default(),
stmts: vec![
Stmt::Return(ReturnStmt {
span: Default::default(),
arg: Some(Box::new(Expr::Bin(BinExpr {
span: Default::default(),
op: BinaryOp::Add,
left: Box::new(Expr::Ident(Ident::new("a".into(), Default::default()))),
right: Box::new(Expr::Ident(Ident::new("b".into(), Default::default()))),
}))),
}),
],
};
quote!("function add($params) $body" as Function, params = params, body = body)
}
宏转换示例
以下示例展示了如何将简单的函数调用转换为更复杂的表达式:
use swc_ecma_quote_macros::quote;
use swc_ecma_ast::*;
use swc_common::DUMMY_SP;
fn transform_call_expr(call: CallExpr) -> Expr {
match &call.callee {
Callee::Expr(expr) => {
if let Expr::Ident(ident) = &**expr {
if ident.sym == "log" {
// 将简单的log调用转换为console.log
return Expr::Call(CallExpr {
callee: quote!("console.log" as Expr).into(),
args: call.args,
type_args: call.type_args,
span: DUMMY_SP,
});
}
}
}
_ => {}
}
Expr::Call(call)
}
完整示例Demo
以下是一个完整的代码转换示例,展示了如何使用swc_ecma_quote_macros来创建和转换JavaScript AST:
use swc_ecma_quote_macros::quote;
use swc_ecma_ast::*;
use swc_common::sync::Lrc;
use swc_common::SourceMap;
use swc_ecma_codegen::{Emitter, Config};
use swc_ecma_visit::{VisitMut, VisitMutWith};
// 自定义访问器,用于转换AST
struct LogToConsoleLog;
impl VisitMut for LogToConsoleLog {
fn visit_mut_call_expr(&mut self, call: &mut CallExpr) {
// 先访问子节点
call.visit_mut_children_with(self);
// 转换log调用为console.log
if let Callee::Expr(expr) = &call.callee {
if let Expr::Ident(ident) = &**expr {
if ident.sym == "log" {
*call = CallExpr {
callee: quote!("console.log" as Expr).into(),
args: call.args.clone(),
type_args: call.type_args.clone(),
span: call.span,
};
}
}
}
}
}
fn main() {
// 创建带有log调用的函数AST
let mut module = Module {
span: Default::default(),
body: vec![
ModuleItem::Stmt(Stmt::Decl(Decl::Fn(FnDecl {
ident: Ident::new("test".into(), Default::default()),
declare: false,
function: Box::new(quote!(
"function test() {
log('Hello');
log('World');
}" as Function
)),
}))),
],
shebang: None,
};
// 应用转换
module.visit_mut_with(&mut LogToConsoleLog);
// 设置代码生成器
let cm: Lrc<SourceMap> = Default::default();
let mut buf = vec![];
{
let mut emitter = Emitter {
cfg: Config {
..Default::default()
},
cm: cm.clone(),
wr: Box::new(swc_ecma_codegen::text_writer::JsWriter::new(
cm.clone(),
"\n",
&mut buf,
None,
)),
comments: None,
};
emitter.emit_module(&module).unwrap();
}
// 打印转换后的代码
println!("转换后的代码:\n{}", String::from_utf8(buf).unwrap());
}
这个示例演示了:
- 使用quote!宏创建JavaScript AST
- 自定义访问器实现AST转换
- 将log调用转换为console.log调用
- 将修改后的AST重新生成为JavaScript代码
输出结果应该是:
转换后的代码:
function test() {
console.log('Hello');
console.log('World');
}
1 回复
Rust代码生成与AST操作库swc_ecma_quote_macros使用指南
swc_ecma_quote_macros
是 SWC (Speedy Web Compiler) 生态系统中的一个强大工具,用于在 Rust 中高效地生成和操作 JavaScript 抽象语法树(AST)。它提供了一种类似 JSX 的宏语法来创建 AST 节点,极大简化了代码生成和转换工作。
核心功能
- 通过类似 JavaScript 的语法直接生成 AST 节点
- 支持 ECMAScript 所有语法结构
- 类型安全的 AST 节点构建
- 与 SWC 生态无缝集成
安装方法
在 Cargo.toml
中添加依赖:
[dependencies]
swc_ecma_quote = "0.105"
swc_ecma_ast = "0.105"
基本使用方法
1. 创建简单表达式
use swc_ecma_quote::quote;
use swc_ecma_ast::Expr;
let expr: Expr = quote!("1 + 1" as Expr);
2. 创建函数声明
use swc_ecma_quote::quote;
use swc_ecma_ast::FnDecl;
let func: FnDecl = quote!(
"function add(a, b) { return a + b; }" as FnDecl
);
3. 创建带变量的模板
use swc_ecma_quote::quote;
use swc_ecma_ast::{Expr, Ident};
let x = Ident::new("x".into());
let y = Ident::new("y".into());
let expr: Expr = quote!(
"$x + $y" as Expr,
x: Expr = x.into(),
y: Expr = y.into()
);
高级用法
1. 条件语句生成
use swc_ecma_quote::quote;
use swc_ecma_ast::Stmt;
let condition = quote!("a > b" as Expr);
let then_branch = quote!("console.log('a is greater')" as Stmt);
let else_branch = quote!("console.log('b is greater or equal')" as Stmt);
let if_stmt = quote!(
"if ($cond) { $then } else { $else }" as Stmt,
cond: Expr = condition,
then: Stmt = then_branch,
else: Stmt = else_branch
);
2. 循环语句生成
use swc_ecma_quote::quote;
use swc_ecma_ast::Stmt;
let init = quote!("let i = 极客" as VarDecl);
let test = quote!("i < 10" as Expr);
let update = quote!("i++" as Expr);
let body = quote!("console.log(i)" as Stmt);
let for_loop = quote!(
"for ($init; $test; $update) { $body }" as Stmt,
init: VarDecl = init,
test: Expr = test,
update: Expr = update,
body: Stmt = body
);
3. 类定义生成
use swc_ecma_quote::quote;
use swc_ecma_ast::ClassDecl;
let class_decl = quote!(
"class Person {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, ${this.name}!`;
}
}" as ClassDecl
);
与 SWC 转换器集成
use swc_ecma_quote::quote;
use swc_ecma_visit::{VisitMut, VisitMutWith};
struct MyVisitor;
impl VisitMut for MyVisitor {
fn visit_mut_expr(&mut self, expr: &mut Expr) {
// 将所有的数字字面量乘以2
if let Expr::Lit(Lit::Num(num)) = expr {
*expr = quote!(
"${value} * 2" as Expr,
value: Expr = Expr::Lit(Lit::Num(*num))
);
}
expr.visit_mut_children_with(self);
}
}
完整示例代码
下面是一个完整的示例,展示了如何使用 swc_ecma_quote_macros
来生成一个简单的 JavaScript 程序:
use swc_ecma_ast::{Expr, FnDecl, Stmt, Ident, VarDecl, ClassDecl};
use swc_ecma_quote::quote;
fn main() {
// 1. 创建简单表达式
let expr: Expr = quote!("1 + 1" as Expr);
println!("生成的表达式: {:?}", expr);
// 2. 创建函数声明
let func: FnDecl = quote!(
"function add(a, b) { return a + b; }" as FnDecl
);
println!("生成的函数: {:?}", func);
// 3. 创建带变量的模板
let x = Ident::new("x".into());
let y = Ident::new("y".into());
let expr_with_vars: Expr = quote!(
"$x + $y" as Expr,
x: Expr = x.into(),
y: Expr = y.into()
);
println!("带变量的表达式: {:?}", expr_with_vars);
// 4. 创建条件语句
let condition = quote!("a > b" as Expr);
let then_branch = quote!("console.log('a is greater')" as Stmt);
let else_branch = quote!("console.log('b is greater or equal')" as Stmt);
let if_stmt = quote!(
"if ($cond) { $then } else { $else }" as Stmt,
cond: Expr = condition,
then: Stmt = then_branch,
else: Stmt = else_branch
);
println!("生成的条件语句: {:?}", if_stmt);
// 5. 创建类定义
let class_decl = quote!(
"class Person {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, ${this.name}!`;
}
}" as ClassDecl
);
println!("生成的类定义: {:?}", class_decl);
}
最佳实践
- 复用 AST 片段:将常用模式抽象为函数返回 AST 节点
- 类型安全:充分利用 Rust 的类型系统确保生成的 AST 节点正确
- 性能考虑:对于大量 AST 生成,考虑使用
swc_ecma_codegen
直接生成代码 - 调试:使用
swc_ecma_utils::dump_ast
调试生成的 AST
注意事项
- 宏中的字符串模板必须使用有效的 JavaScript 语法
- 变量替换使用
$var
语法 - 需要显式指定目标 AST 节点类型 (
as Type
) - 对于复杂转换,建议结合
swc_ecma_visit
使用
swc_ecma_quote_macros
提供了一种声明式、类型安全的方式来操作 JavaScript AST,特别适合构建代码转换工具、语法扩展和 DSL 实现。