1个不稳定版本
使用旧的Rust 2015
0.0.1 | 2022年11月29日 |
---|
#4 in #cui
64KB
2K SLoC
CUI是一种用于构建UI的语言,它使用Rust进程宏在这个存储库中实现。从CUI编译的代码在浏览器中运行,由一个HTML组件和一个Webassembly二进制文件组成。
这个存储库主要面向对CUI语言本身开发感兴趣的人!如果您对使用CUI构建应用程序感兴趣,请查看cui-tools存储库的README。
这个存储库将cui-tools作为git子模块包含在内,以便更容易测试库。
安装
您需要rustc和wasm-pack来构建库和运行测试
然后
git clone https://github.com/thisminute/cascading-ui.git
cd cascading-ui
# you only need to do this submodule step if you plan to use cui-tools
git submodule update --init --recursive
Windows用户可能还需要在根目录下运行以下操作
rustup toolchain install stable-x86_64-pc-windows-gnu
rustup default stable-x86_64-pc-windows-gnu
一切设置完成后,有两种方式可以测试库
- 使用包含的cui-tools构建一个使用库的应用程序。编辑
cui-tools/app/src
中的文件来更改应用程序,然后在cui-tools目录中运行cargo run
。 - 使用
tests
目录中的测试。编辑或添加测试,然后使用wasm-pack test --headless --firefox
(或--chrome
,或--safari
)来运行它们。
有关每个工作流程的更多信息,请分别查看cui-tools
和tests
中的README文件。
理解代码
执行步骤
Rust文件夹结构
"lib.rs"和"mod.rs"是特殊名称,它们是它们所在的文件夹的入口点。顶层是./src/lib.rs
。整个项目是一个库,每个后续目录都包含从那里包含的私有模块。
src/lib.rs
代码的执行从lib.rs导出的3个进程宏之一开始。cui
是主要的,test_setup
和test_header
是用于编写测试的辅助工具。
数据流
这张图有助于记住步骤发生的顺序,参考它来帮助理解本节内容,并在导航代码时迷失方向时使用它!
cui -> lex ->
tokens -> parse* ->
ast -> analyze ->
semantics -> render ->
semantics -> compile ->
html/wasm
* 这个库从解析步骤开始(src/transform/parse.rs
),因为词法分析由一些库处理,所以我们有一些标记来开始
用文字来说:当一个CUI代码块被编译时,它首先被词法分析成标记,然后这些标记被解析成一个抽象语法树(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代码。每个输出都通过在语义结构上实现不同的特性来生成。
这是CUI的核心(并且可以很容易地适应其他语法!)。在src/misc
中是核心流程之外的一些文件,例如在语义分析期间使用的辅助context
。
转换的详细说明
转换之间在概念上并非完全不同 - 一些转换执行的任务可能被放置在不同的转换中以达到相同的结果。例如,在解析过程中,不同的块(类、元素或事件)被写入不同的数组,但同样的事情可以通过在解析期间写入单个blocks
数组并稍后在分析期间确定每个块是哪种类型的块来实现。
一个一般原则是尽早放置逻辑,而不会丢失我们以后需要的任何信息。在我们的块解析示例中,通过在解析期间确定一个块是类、元素还是事件,我们就失去了告诉特定类是先于还是后于另一个元素的能力。例如
// 1
.some_rule {}
some_rule {}
// 2
some_rule {}
.some_rule {}
这两段代码解析成完全相同的AST,因为它们都创建了一个包含一个项目的classes
数组和包含一个项目的elements
数组,并且在解析后无法判断它们原始的顺序。这实际上是一种期望的行为!CUI语法不应该要求我们在应用规则之前将类放在元素之前,并且由于目前没有计划区分将元素和类以任何顺序放置(注意:另一方面,任何元素相对于其他元素的顺序非常重要,并且保存在elements
数组中),我们可以在第一个转换中完全消除该信息,这使我们不必担心该信息会影响后续转换中的任何内容。
解析
解析过程从某些CUI输入中提取令牌,这些令牌由我们提供的syn
crate提供,并将其转换成抽象语法树(AST)。AST是输入的最小表示,它与原始代码非常接近1:1,但与输入代码不同,它是一个树结构而不是令牌序列。AST应表示生成CUI程序所需的最小信息。例如
.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
分析遍历AST(我们说它由块组成)并创建一个与之相似的树(我们说它由组组成),然后运行我们称之为渲染的步骤,这些步骤在组中收集和填充信息,除了正常的树结构之外,还在元素节点和类节点之间创建双向链接。在这种情况下,这些链接使我们能够从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代码令牌流。我们在compile/html.rs
中创建HTML文档。这一步骤描述了如何递归地生成一个包含HTML文档的单个字符串,然后将其写入文件,利用compile/css.rs
来完成样式标签和属性的填充。在单独的步骤中,令牌流由compile/wasm
中的复杂过程生成,最后由Rust在宏调用处编译。与Webassembly编译步骤相比,HTML和CSS静态内容编译步骤相当简单。
组装的二进制文件需要了解三种类型的组——元素、类和监听器——以及我们可以对每个组执行的三件事——什么都不做、将其渲染到DOM中,或者将其注册为在以后创建匹配某些类的元素时发生。如果渲染期间已经发生并且被静态内容捕获,我们可能对某些组不采取任何行动。
我们在这里要解决的最后一个复杂问题是,二进制文件在页面初始化步骤中仅对已渲染的组执行有限的操作。这段代码由“initialize”注册和渲染函数生成,这些函数在构建期间运行,生成简单的程序代码,而“runtime”注册和渲染命令是整个函数,在运行时被调用。
为了尽可能清晰:initialize/register.rs
和initialize/render.rs
中的函数在构建时运行,而runtime/register.rs
和runtime/render.rs
中的函数将在浏览器中由运行时调用。注意每个文件中的quote!
宏内部的内容。
测试
./tests
包含一组用于展示不同功能的 CUI 示例,然后检查 DOM 是否按预期渲染!使用以下命令运行它们:wasm-pack test --headless --firefox --chrome
(或者仅使用一个浏览器;chrome 运行速度较快)。详细信息请参阅 ./tests/README.md
。
调试
我们使用 cui-tools
提供服务器和一些调试工具来处理 CUI。使用以下命令来拉取目录内容(如果目录为空):git submodule update --init --recursive
。然后,您可以通过 cd cui-tools
和 ./run.sh
运行开发服务器。
依赖项
~0.7–11MB
~77K SLoC