#language #syntax #css #proc-macro #web #compile #procedural

cascading-wasm-language

基于CSS语法的编译型网络语言

3个版本

使用旧的Rust 2015

0.0.2 2021年5月3日
0.0.1 2020年8月3日
0.0.0 2020年7月25日

过程宏中排名第2099


用于create-cwl-app

MIT许可证

46KB
1K SLoC

Cwl是一个使用Rust过程宏实现的Web前端语言。

要安装,您需要

  1. rustc/cargo
  2. node/npm
  3. wasm-pack

然后

git clone https://github.com/thisminute/cascading-wasm-language.git
cd cascading-wasm-language
git submodule init
git submodule update
cd create-cwl-app/www
npm install
npm start

对于Windows用户,在运行 npm start 之前在根目录下运行

rustup toolchain install stable-x86_64-pc-windows-gnu
rustup default stable-x86_64-pc-windows-gnu

理解代码

执行步骤

Rust文件夹结构

"lib.rs" 和 "mod.rs" 是特殊名称,是它们所在文件夹的入口点。顶级是 ./src/lib.rs。整个项目是一个库,每个后续目录都包含一个从那里导入的私有模块。

src/lib.rs

代码的执行从lib.rs导出的3个过程宏之一开始。 cwl 是主要的,cwl_documentcwl_header 是用于编写测试的辅助工具。

数据流

此图有助于记住步骤发生的顺序,请参考此部分并帮助您在导航代码时不会迷失方向!

cwl       -> lex     ->
tokens    -> parse*  ->
ast       -> analyze ->
semantics -> render  ->
semantics -> write   ->
compiled code!

* 此库从解析步骤开始(src/transform/parse.rs),因为一些库已经处理了词法分析,所以我们有一些标记来开始

用词说:当一段CWL代码被编译时,它首先被词法分析成标记,然后被解析成一个抽象语法树(AST)。在AST上执行语义分析以生成表示代码含义的对象。这个“语义”对象被渲染成HTML DOM的抽象表示,最终编译成包含HTML和CSS的文本字符串和包含Webassembly的可执行文件。

数据结构(图左的 data 模块)之间通过结构之间的转换(图右的 transform 模块)进行数据流动。所有代码都位于 src 目录中,从 lib.rs 开始,然后传递到 transform/parse.rs 创建由 data/ast.rs 描述的对象,然后传递到 transform/analyze.rs 创建由 data/semantics.rs 定义的语义对象,依此类推。write 转换定义在几个部分,每个部分对应几个不同的输出——HTML、CSS和编译成 Wasm 二进制的 Rust 代码。每个输出都使用在 Semantics 结构上实现的不同 trait 生成。

这是 CWL 的核心(也可以很容易地适应其他语法!)。在 src/misc 中有一些不属于这个核心流程的文件,例如在语义分析期间使用的辅助 context

转换的详细情况

转换之间并不完全独立,有些转换执行的任务可能可以放在不同的转换中以达到相同的结果。例如,在解析过程中,不同类型的块(类、元素或事件)被写入不同的数组中,但同样的事情可以通过在解析时将内容写入单个 blocks 数组来完成,然后在分析过程中确定每个块的类型。

遵守的一个基本原则是,尽可能早地放置逻辑,同时不丢失我们之后需要的信息。在我们的块解析示例中,通过在解析过程中确定一个块是类、元素还是事件,我们就失去了判断某个类是出现在另一个元素之前还是之后的能力。例如

// 1
.some_rule {}
some_rule {}

// 2
some_rule {}
.some_rule {}

这两段代码解析成完全相同的 AST,因为它们都创建了一个包含一个项目的 classes 数组和包含一个项目的 elements 数组,在解析后无法判断它们原始的顺序。这实际上是一种期望的行为!CWL 语法不应该要求我们在应用规则之前将类放在元素之前,并且由于目前没有计划在将元素和类以任意顺序放置之间进行区分(注意:另一方面,任何元素相对于其他 元素 的顺序非常重要,并且保存在 elements 数组中),我们可以在非常早期的转换中完全消除这些信息,这可以让我们不必担心这些信息会影响后续的转换。

解析

解析从某些 CWL 输入中提取的令牌,并由 syn 包提供给我们,然后将它们转换成 AST。AST 是输入的最小表示——它与原始代码接近 1:1 的对应,但它不像输入代码那样是一个令牌序列,而是一个树。AST 应该代表生成 CWL 程序所需的最小信息。例如

.box {
     content {}
}
box {}
box {}

从这段代码解析出的 AST 的根将包含一个包含内部元素块的类块,然后是 2 个单独的元素块,如下所示

// AST (made of blocks)
     / .box - content
page - box
     \ box

分析和渲染

分析 AST 生成一个不同的树结构,我们称之为“语义”,它以反映输入结构的方式表示代码的意义,但填充了将树转换成不同形状所需的信息,以及我们在过程中需要收集的任何其他信息。为了说明为什么必须转换树,我们可以查看上述代码期望输出的树结构

// DOM (made of elements)
page - box - content
     \ box - content

分析遍历抽象语法树(我们称之为由块组成),并创建一个根据其构建的树(我们称之为由组组成),然后运行我们称之为渲染的步骤,这些步骤在组中收集和填写信息,在元素节点和类节点之间创建双向链接,除了正常的树结构之外。在这种情况下,这些允许我们从 box 组遍历到 .box 组,然后从那里到 content 元素组

// semantics (made of groups, looks like AST)
     / .box - content
page - box
     \ box

// also has paths to be walked in this order
page - box - .box - content
     \ box /

对于 .box 类只存在一个组节点,对于 content 元素也只存在一个,但这些元素可以从两个地方访问。现在可以使用这个语义树在渲染时生成 DOM。

编译

语义树编译成 HTML 文档和要编译成 WebAssembly 的 Rust 代码 TokenStream。我们创建 HTML 文档的步骤在 compile/html.rs 中描述了如何递归生成包含 HTML 文档的单个字符串,然后将其写入文件,利用 compile/css.rs 完成样式标签属性的填充。在单独的步骤中,TokenStream 由 compile/wasm 中的几个文件中概述的过程生成,并在宏调用的地方由 Rust 编译。HTML 编译步骤相对简单,但 WebAssembly 编译步骤是整个项目中最为复杂的部分之一(待续)。

测试

由于添加了全局静态 CLASSES 变量,当前测试已损坏 :)

./tests 包含一组 CWL 示例,这些示例渲染不同的功能,然后检查 DOM 是否按照预期渲染!使用 wasm-pack test --headless --firefox 运行它们。 --chrome 也可以使用,您必须安装您选择的浏览器。

调试

我们使用 create-cwl-app 提供服务器和一些调试工具来处理 cwl。使用 git submodule update 拉取目录的内容。在 cd create-cwl-app/www 之后,您将可以访问一些 npm 命令。首先运行 npm install。使用 npm start 编译测试应用程序,并使用 npm run debug 运行一些步骤,这些步骤将在 create-cwl-app/target/cwl_macro_output_formatted.rs 中生成宏输出并尝试编译它。这在 cwl 代码中存在构建错误时很有用,但库可以编译,因为它是通过宏生成的代码,错误消息只指向宏,以查看生成的代码中的错误消息。

依赖项

~2MB
~45K SLoC