#语法树 #语言 #WebGPU #着色器 #解析器 #零拷贝 #递归下降

wgsl-parser

WebGPU 着色语言的无拷贝递归下降解析器

5 个版本 (破坏性更新)

0.5.0 2024年6月1日
0.4.0 2024年5月25日
0.3.0 2024年5月11日
0.2.0 2024年5月8日
0.1.0 2024年5月6日

#2#着色器

Download history 408/week @ 2024-05-05 40/week @ 2024-05-12 175/week @ 2024-05-19 93/week @ 2024-05-26 161/week @ 2024-06-02 22/week @ 2024-06-09 12/week @ 2024-06-16 6/week @ 2024-06-23 8/week @ 2024-06-30 32/week @ 2024-07-14 24/week @ 2024-07-21 19/week @ 2024-07-28

每月75 次下载

MIT/Apache

175KB
5.5K SLoC

WGSL 解析器

使用 Gramatika 编写的针对 WebGPU 着色语言的零拷贝递归下降解析器。

API

解析源文件

use parser::{Parse, ParseStream, SyntaxTree};

const SOURCE_TEXT: &str = include_str!("path/to/some/shader.wgsl");

fn main() -> anyhow::Result<()> {
	let mut parser = ParseStream::from(SOURCE_TEXT);
	// The `SyntaxTree`
	let tree = parser.parse::<SyntaxTree>()?;
	// A tuple of `(ArcStr, Vec<Token>)`
	let (source, tokens) = parser.into_inner();

	Ok(())
}

对源文件进行标记化而不进行完全解析

use gramatika::Lexer as _;
use parser::Lexer;

const SOURCE_TEXT: &str = include_str!("path/to/some/shader.wgsl");

fn main() {
	let mut lexer = Lexer::new(SOURCE_TEXT.into());
	// `Vec<Token>`
	let tokens = lexer.scan();
}

语法树表示

SyntaxTree 包含一个表示由 WGSL 语法 定义的顶层语法类型的 Decl 的向量,例如。

  • Decl::Var(VarDecl{ .. })

     @group(1) @binding(2)
     var<uniform> uniforms: Uniforms;
    
  • Decl::Const(VarDecl{ .. })

     const FOO: u32 = 1u;
    
  • Decl::Struct(StructDecl{ .. })

     struct Foo {
     	foo: mat3x4<f32>,
     	bar: vec2<u32>,
     	baz: array<mat4x4<f32>, 256u>,
     }
    
  • Decl::Function(FunctionDecl{ .. })

     fn sum(a: f32, b: f32) -> f32 {
     	return a + b;
     }
    

那些声明包裹的结构可以包含子声明,例如。

  • StructDecl 中的 Decl::Field(FieldDecl { .. })
  • FunctionDecl 中的 Decl::Param(ParamDecl { .. })

FunctionDeclbody 包含一个 Stmt 的向量。

Stmt 是一个类似于 Decl 的枚举,其变体表示它所代表的语句类型,每个变体都包含一个内部结构来详细描述语法,通常递归地,例如。

// NOTE: This is not valid Rust code, just a pseudo-code example of what some
//       `Stmt` might look like on the inside

Stmt::If(IfStmt {
	..
	else_branch: Some(ElseStmt {
		..
		body: Arc(Stmt::Block(BlockStmt {
			..
			stmts: Arc<[Stmt]>,
		})),
	}),
})

最后,Expr 是树中“最低”的语法形式,采用与上面 DeclStmt 相同的一般形式。

检查语法树

语法树的每个节点都派生了一个定制的 Debug 实现来打印树,这种格式类似于 Lisp(常用于表示语法树的格式)和 Rust 语法。

该格式看起来像这样

max(4, 2) // The expression represented by the tree below
(Expr::Primary (PrimaryExpr
	expr: (Expr::FnCall (FnCallExpr
		ident: (IdentExpr::Leaf `max` (Function (1:1...1:4))),
		arguments: (ArgumentList
			brace_open: `(` (Brace (1:4...1:5)),
			arguments: [
				(Expr::Primary (PrimaryExpr
					expr: (Expr::Literal `4` (IntLiteral (1:5...1:6))),
				)),
				(Expr::Primary (PrimaryExpr
					expr: (Expr::Literal `2` (IntLiteral (1:8...1:9))),
				)),
			],
			brace_close: `)` (Brace (1:9...1:10)),
		),
	)),
))

遍历语法树

该包导出了一个Visitor特质,可以用来高效地遍历树。该Visitor为树中表示的每种语法类型定义了一个visit_方法。visit_方法对于包含子节点的节点必须返回FlowControl::Continue来遍历它们的子节点,或者返回FlowControl::Break来停止遍历当前分支。

默认的Visitor实现对于每个节点都返回FlowControl::Continue,因此你只需要实现特定用例需要的visit_方法。

use std::collections::HashMap;

use gramatika::{Substr, Token as _};
use parser::{
	decl::VarDecl,
	expr::{IdentExpr, NamespacedIdent},
	traversal::{FlowControl, Visitor, Walk},
	ParseStream, SyntaxTree,
};

#[derive(Default)]
struct ReferenceCounter {
	counts: HashMap<Substr, usize>,
}

impl Visitor for ReferenceCounter {
	fn visit_var_decl(&mut self, decl: &VarDecl) -> FlowControl {
		self.counts.insert(decl.name.lexeme(), 0);

		if let Some(ref expr) = decl.assignment {
			expr.walk(self);
		}

		FlowControl::Break
	}

	fn visit_ident_expr(&mut self, mut expr: &IdentExpr) {
		if let IdentExpr::Leaf(name) = expr {
			if let Some(count) = self.counts.get_mut(&name.lexeme()) {
				*count += 1;
			}
		}
	}
}

// Note: Not actually a robust implementation of a reference-counter,
//       but good enough for this toy example
pub fn count_references(tree: &SyntaxTree) -> HashMap<Substr, usize> {
	let mut visitor = ReferenceCounter::default();
	tree.walk(&mut visitor);

	visitor.counts
}

依赖关系

~1–6.5MB
~38K SLoC