前言
缘起于偶然间看见的同事写的一个CSS书写顺序
通常我们在写css,less,scss样式文件的时候,每次都会书写不同顺序,这样的代码不仅修改麻烦而且一点也不酷。但是css书写本身是一个随机性的开发,如果刻意去注意书写顺序会很累。 当这些杂乱无序的属性在书写时遵从一定规律后,改起来会很方便,会有一种,哲学上的美:)
那为啥要手动实现呢?
- 当时没有找到(后来发现是有插件的 ̄□ ̄||)
- 工程上的利器babel,webpack,react等都需要字符串解析的能力,为了感受一下词法分析,菜鸟也是有梦想的:)
词法分析
基本概念
在上文中我们提到 词法分析。词法分析是啥呢?
词法分析(英语:lexical analysis)是计算机科学中将字符序列转换为单词(Token)序列的过程。进行词法分析的程序或者函数叫作词法分析器(Lexical analyzer,简称Lexer),也叫扫描器(Scanner)。词法分析器一般以函数的形式存在,供语法分析器调用。 完成词法分析任务的程序称为词法分析程序或词法分析器或扫描器。
var a = 1;
如上这一行代码,所谓的词法分析,我们就是把他们其中的每一个组成都拆成对应的类别。那么上面一行代码的组成就有如下的成员空格
+ var
+ a
+ =
+ 1
+ ;
通常情况下组成代码串的成员可以有如下图所示的几种类别
我们为啥要进行词法分析呢?这是一个比较底层的操作,通常我们很多时候需要实现ast。而实现语法树的前置条件就是要拆分出 每一个单元才能进行词法分析,最终产出抽象语法树
抽象语法树(Abstract Syntax Tree,AST)或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
示例:原始代码
#main {
border: 1px solid black;
background-color:red;
}
解析后的语法树
{
"parentStyleSheet": null,
"cssRules": [
{
"parentRule": null,
"parentStyleSheet": "[Circular ~]",
"selectorText": "#main",
"style": {
"0": "border",
"1": "background-color",
"length": 2,
"parentRule": "[Circular ~.cssRules.0]",
"_importants": {
"border": "",
"background-color": ""
},
"__starts": 113,
"border": "1px solid black",
"background-color": "red"
},
"__starts": 107,
"__ends": 171
}
]
}
水平有限,只做简单分享
关于词法分析和ast有兴趣可以去看
- 一个贼好的项目 编译器demo https://github.com/jamiebuilds/the-super-tiny-compiler
- LangSandbox(演示了如何创造一门语言) https://github.com/ftomassetti/LangSandbox
如何实现
词法分析器 Tokenizer 本质就是一个状态机。
关于状态机我们以Leetcode一道题目来切入理解,去手动做一下会有更深刻的感受
这个题目如果完全用if else来实现,那边界条件将会极其复杂
但是我们换个思路,所有这些变化可以总结按照下面这张表中的状态进行切换,将会让整个过程变的清晰。
怎么理解下面这个表 举个例子:
当我在存在符号
这个状态下遇到数字整个状态机就切换到数字转换
状态
当我在存在符号
这个状态下遇到空
整个状态机就切换到结束
状态
… 以此类推
空 | +/- | 数字 | 其他 | |
---|---|---|---|---|
开始 | 开始 | 存在符号 | 数字转换 | 结束 |
存在符号 | 结束 | 结束 | 数字转换 | 结束 |
数字转换 | 结束 | 结束 | 数字转换 | 结束 |
结束 | 结束 | 结束 | 结束 | 结束 |
附上一个我的题解,方便理解
/**
* @param {string} str
* @return {number}
*/
var myAtoi = function(str) {
let map = [
[0,1,2,3],
[3,3,2,3],
[3,3,2,3],
]
let reg =/[0-9]/
let r='';
let state = 0;
for(let i=0,l=str.length;i<l;i++){
if(str[i]==' '){
state = map[state][0];
}else if(str[i]=='+'||str[i]=='-'){
state = map[state][1];
}else if(reg.test(str[i])){
state = map[state][2];
}else{
state = map[state][3];
}
if(state==0){
continue
}
if(state==1||state==2){
r +=str[i]
}
if(state==3){
break
}
}
if(r==''||r=='-'||r=='+') return 0;
let num = Number(r);
if (num > 2147483647) {
return 2147483647
}
if (num < -2147483648) {
return -2147483648
}
return num
};
实现一个简易的词法分析器
回到正题,我们来分析一下我们需要的简单的词法分析器
不管是less 还是css 还是scss 它的语法结构大致上都如下图所示。因为我们的目标是仅仅是对css属性进行一定的规则排序,所以可以忽略掉大量的细节。
在遍历字符串的时候我们以{
,}
这两个作为标识符使用上面所提到的状态机先提取我们需要的部分
然后用状态机去识别 图中的几个不同的单元。进行策略上的排序就可以了。
排序的方式可以是设定一张map表,key对应属性,value表示所处位置,值小的排在最前面。排序方式可以选择使用快速排序
。
最终实现的效果
可以去vscode插件市场中搜索 compose-style安装体验