目标
输入:
$primary-color: #333;
.test{
color: $primary-color;
}
输出:
.test {
color: #333;
}
Step1:定义基本的 AST 结构
定义AST
这里的AST定义针对例子做了简化,先看后面的运用再回过头来看定义会更好理解些:
export const enum NodeTypes {
TEXT: "TEXT",
VARIABLE: "VARIABLE",
SELECTOR: "SELECTOR",
DECLARATION: "DECLARATION",
RULE: "RULE",
RootNode: "RootNode",
}
interface Node {
[key: string]: any
type: NodeTypes
}
interface VariableNode extends Node{
type: NodeTypes.VARIABLE
value: string
}
interface TextNode extends Node {
type: NodeTypes.TEXT
value: string
}
interface SelectorNode extends Node {
type: NodeTypes.SELECTOR
value: TextNode
}
export interface DeclarationStatement extends Node {
type: NodeTypes.DECLARATION
left: VariableNode | TextNode
right: VariableNode | TextNode
}
export interface RuleStatement extends Node {
type: NodeTypes.RULE
selector: SelectorNode
children: DeclarationStatement[]
}
// RootNode 是最外层的节点类型
export interface RootNode extends Node {
type: NodeTypes.RootNode
children: (RuleStatement | DeclarationStatement)[]
}
源码跟AST的对应关系
根据以上的AST定义,例子解析出的AST目标:
$primary-color: #333;
需要翻译成:
{
"type": "DECLARATION",
"left": {
"type": "VARIABLE",
"value": "$primary-color",
},
"right": {
"type": "TEXT",
"value": "#333",
}
}
.test{
color: $primary-color;
}
需要翻译成:
{
"type": "RULE",
"selector": {
"type": "SELECTOR",
"value": {
"type": "TEXT",
"value": ".test",
}
},
"children": [
{
"type": "DECLARATION",
"left": {
"type": "TEXT",
"value": "color",
},
"right": {
"type": "VARIABLE",
"value": "$primary-color",
},
}
]
}
Step2: sass字符串读取成为目标 AST
目标: 实现如下的调用
let ast:RootNode = parse(lexical(input_stream(scss)))
实现input_stream函数读取输入字符串流:
function input_stream(input: string):InputStream{
let offset = 0, line = 1, column = 1;
return {
next,
peek,
setCoordination,
getCoordination,
eof
}
function next():string {
let ch = input.charAt(offset++);
if (ch == "\n") line++, column = 1; else column++;
return ch;
}
// 手动设置当前位置信息
function setCoordination(coordination: Position) {
offset = coordination.offset;
line = coordination.line;
column = coordination.column;
}
// 获取当前读取的位置
function getCoordination() {
return {
offset,
line,
column
}
}
// 预先读取下一个字符的内容,但是不做位置移动
function peek():string {
return input.charAt(offset);
}
function eof() {
return peek() === "";
}
}
实现lex函数将字符串流转为 token 流
export type Token = {
type: Node['type']
value: string
}
function lex(input: InputStream):TokenStream {
return {
next,
peek,
eof
}
function is_whitespace(ch) {
return " \t\n".indexOf(ch) >= 0;
}
// Variable的可能标识
function is_id_start(ch) {
return /[$]/.test(ch);
}
// declaration的可能标识
function is_assign_char(ch) {
return ":".indexOf(ch) >= 0;
}
// 普通字符串读取
function is_base_char(ch) {
return /[a-z0-9_\.\#\@\%\-"'&\[\]]/i.test(ch);
}
// sass变量名限制
function is_id_char_limit(ch) {
return is_id_start(ch) || /[a-z0-9_-]/i.test(ch);
}
function read_assign_char():Token {
return {
type: NodeTypes.DECLARATION,
value: input.next()
}
}
function read_string():Token {
/**
* '#' end eg:
* .icon-#{$size} {}
*/
let str = read_end(/[,;{}():#\s]/);
if (internalCallIdentifiers.includes(str)) {//possible internal url
let callStr = readInternalCall(str);
return callStr;
}
return {
type: NodeTypes.TEXT,
value: str
};
}
// 根据条件限制读取消费掉尽可能多的字符
function read_while(predicate) {
let str = "";
while (!input.eof() && predicate(input.peek()))
str += input.next();
return str;
}
// 产出变量 token
function read_ident(): Token {
let id = read_while(is_id_char_limit);
return {
type: NodeTypes.VARIABLE,
value: id
};
}
// 读取下一个 token 并移动位置
function read_next(): Token {
// 跳过空白字符
read_while(is_whitespace);
if (is_assign_char(ch)) return read_assign_char();
if (is_id_start(ch)) return read_ident();
if (is_base_char(ch)) return read_string();
}
//读取下一个 token,但是不改变读取游标信息,所以有先获取信息,读取token后还原位置信息
function ll(n = 1): Token {
let coordination = input.getCoordination()
let tok = read_next();
input.setCoordination(coordination)
return tok;
}
// 预测下一个 Token 类型
function peek(n = 1): Token {
return ll(n);
}
function next(): Token {
return read_next();
}
}
结语
以上是伪代码,实际会比这个复杂一些,比如还需要考虑很多:
- 节点位置信息,方便后续做source-map
- 节点所属文件信息,会有模块依赖关系整理
- ...
完整可运行的代码可以查看这里,还是比较通俗易懂的,覆盖了sass基本特性,以及编译基本流程:
- 词法分析
- 语法分析
- AST优化转换
- 源码生成(+sourceMap))
文章整理起来有点麻烦,需要对很多完整代码做删减,后续待完成。。。
最后祝大家?圣诞节快乐。。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!