#html-css #编译 #语言 #cui #语法 #进程宏 #进程

cascading-ui

基于CSS语法的Web语言,编译成HTML/JS/Wasm

1个不稳定版本

使用旧的Rust 2015

0.0.1 2022年11月29日

#4 in #cui

MIT许可

64KB
2K SLoC

CUI是一种用于构建UI的语言,它使用Rust进程宏在这个存储库中实现。从CUI编译的代码在浏览器中运行,由一个HTML组件和一个Webassembly二进制文件组成。

这个存储库主要面向对CUI语言本身开发感兴趣的人!如果您对使用CUI构建应用程序感兴趣,请查看cui-tools存储库的README。

这个存储库将cui-tools作为git子模块包含在内,以便更容易测试库。

安装

您需要rustc和wasm-pack来构建库和运行测试

  1. rustc/cargo
  2. 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

一切设置完成后,有两种方式可以测试库

  1. 使用包含的cui-tools构建一个使用库的应用程序。编辑cui-tools/app/src中的文件来更改应用程序,然后在cui-tools目录中运行cargo run
  2. 使用tests目录中的测试。编辑或添加测试,然后使用wasm-pack test --headless --firefox(或--chrome,或--safari)来运行它们。

有关每个工作流程的更多信息,请分别查看cui-toolstests中的README文件。

理解代码

执行步骤

Rust文件夹结构

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

src/lib.rs

代码的执行从lib.rs导出的3个进程宏之一开始。cui是主要的,test_setuptest_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.rsinitialize/render.rs中的函数在构建时运行,而runtime/register.rsruntime/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