0

1

前   言

在前端日益发展的今天,抽象语法树(AST,Abstract Syntax Tree)已经成为了前端学者们耳熟能详的概念。趋于好奇心,在百忙之中窥探 PostCss 源码,好在代码量不多,消化整理了一下,望大牛们多多指教。

0

2

词法分析是什么

首先我们要搞清楚抽象语法树的解析流程,抽象语法树从形成一共经历了如下阶段:

字符流=>词法分析=>语法分析=>抽象语法树

是不是很简单?词法分析主要是读取字符流并根据词法规则组成一个个 Token,以便于语法分析阶段基于Token 流进行语法分析。

此处先拿 PostCss 的 String Token 来举例子,String 我们知道,其组成规则是两个成对的引号包裹起来的字符序列,词法分析阶段要做的是分析识别出当前读取的字符流是一个类型为 String 的 Token,并且其内容是匹配到的字符序列。这样语法分析器只要针对 Token 类型来判断是否合法,而不需要直接针对字符流来判断。

总结一下,词法分析要读取字符流并且提供Token的类型、内容,然后由语法分析通过读取 Token 流来判断当前 Token 组合是否合法,一切都检查通过后,最终由语法解析阶段生成抽象语法树(AST,Abstract Syntax Tree)

0

3

PostCss 词法分析

试想一下,除了前面我们提到的 String Token,还有哪儿些 Token 需要被解析出来?

我们不妨可以访问 https://astexplorer.net ,来辅助我们寻找答案。我们在该网站中,我们选中要解析的语言为 Css,并指定使用的解析库为 PostCss,并且输入以下内容:

.a {background: url(/*\\));};.b {background: url("asdfasdf");};

在前面我们说到,语法分析是通过 Token 流来判断合法。那我们试想一下,我们平时开发遇到过的 CSS 语法错误都有哪儿些?

我们随意地删掉一些内容,比如删掉最后一行的 } ,此时我们可以看到报错::1:1: Unclosed block,明眼人一看就明白:这个 { } 得成对出现,无论他们之间是否包裹了css代码,都需要去检查。

那我们粗略地得出结论:有一个Token,类型为brackets,其内容是被{}包裹起来的字符序列,在本例中就是 background: url(/*));

OK,这乍一看没啥问题,但是有个潜在问题,因为语法分析的对象是Token,负责判断Token组合是否合法,你将background: url(/*)); 作为Token brackets的一部分,那么其内容代码是否合法,语法解析器是不管的。

所以咱们得乖乖拆分出来,将上面代码分为三个部分:

1、名叫 { 的Token,其值和名字一样也是 {

2、other-tokens

3、名叫 } 的Token,其值和名字一样也是 }

这样才合理,当other-tokens存在语法错误时,语法分析器将会吐出错误。

那么我们是否可以推出字符串的解析也是分为这样的三个部分:

1、名叫 " 的Token,其值和名字一样也是 "

2、字符序列

3、名叫 " 的Token,其值和名字一样也是 "

其实不然,因为字符串里的内容不能由语法解析器去解析语法问题,即使你在里头写了错误的CSS代码也不管。所以需要在词法解析器阶段就专门设置一个Token,名字为String,这样就规避了语法解析器去检查字符串内的CSS语法问题了。

综合以上,我们可以猜测PostCss提供了如下token:

1、String2、{3、}4、(5、)6、[7、]

现在,我们在 https://astexplorer.net 额外输入代码后如下:

.a {background: url(/*\\));};.b {background: url("asdfasdf");};.c {background: url((();};.d {background: url());};.e {background: ()));};.f {background: ((();};

关于 Token:( )、[ ] 的存在,不必多说,因为( ),[ ] 内的语法需要语法解析器去检查语法,但是以上的代码报错位置却是 选择器 .f 的内容,难不成在此之前的代码语法都是对的吗?

其实 PostCss 还新增了一种 Token,叫做 brackets,它的生成条件如下:

1、以 url(开头,并且后面紧跟的字符不为特定的不可见字符并且不为单引号,双引号时,会生成 brackets Token

2、以(开头,并且直到匹配到)时的字符序列不符合正则 /.[/("'\n]/ 时,会生成一个 brackets

而生成一个 brackets 有什么效果?就是在语法解析阶段不会去检查 brackets 内容的语法,就不会报错了。

这就解释了上面的现象。

还有一些 Token,比如 space,at-word 等等,这里就不再赘述,这里列出所有 Token 后直接上代码解释:

  • space

  • string

  • at-word

  • word

  • comment

  • { } : ; ( )

  • brackets

0

4

PostCss Token 解析流程浅析

1

解析过程之 space

case NEWLINE:case SPACE:case TAB:case CR:case FEED:next = posdo {next += 1code = css.charCodeAt(next)if (code === NEWLINE) {offset = nextline += 1}} while (code === SPACE ||code === NEWLINE ||code === TAB ||code === CR ||code === FEED) {currentToken = ['space', css.slice(pos, next)]}pos = next - 1break

这里的代码主要是将连续的 空格( ),制表符(\t),换行符(\n),回车符(\r),换页符(\f)转化为 space token,代码比较简单。

2

解析过程之 string

case SINGLE_QUOTE:case DOUBLE_QUOTE:quote = code === SINGLE_QUOTE ? '\'' : '"'next = posdo {escaped = falsenext = css.indexOf(quote, next + 1)if (next === -1) {if (ignore || ignoreUnclosed) {next = pos + 1break} else {unclosed('string')
}
}
escapePos = nextwhile (css.charCodeAt(escapePos - 1) === BACKSLASH) {escapePos -= 1escaped = !escaped}} while (escaped)content = css.slice(pos, next + 1)lines = content.split('\n')last = lines.length - 1if (last > 0) {nextLine = line + lastnextOffset = next - lines[last].length} else {nextLine = linenextOffset = offset}currentToken = ['string', css.slice(pos, next + 1), line, pos - offset, nextLine, next - nextOffset]offset = nextOffsetline = nextLinepos = nextbreak

这里可能有的理解困难点是针对转义字符的处理。代码如下:

do {escaped = falsenext = css.indexOf(quote, next + 1)if (next === -1) {if (ignore || ignoreUnclosed) {next = pos + 1break} else {unclosed('string')}}escapePos = nextwhile (css.charCodeAt(escapePos - 1) === BACKSLASH) {escapePos -= 1escaped = !escaped}} while (escaped)

比如有字符串:

"你好,我是\\"小小前端攻城狮,我未来要成为\\"大大前端攻城狮" 外循环是不断寻找",内循环不断判断是否为转义引号,从而进行string token 的解析。

3

解析过程之 at-word

case AT:RE_AT_END.lastIndex = pos + 1RE_AT_END.test(css)if (RE_AT_END.lastIndex === 0) {next = css.length - 1} else {next = RE_AT_END.lastIndex - 2}currentToken = ['at-word', css.slice(pos, next + 1), line, pos - offset, line, next - offset]pos = nextbreak

其中:

const RE_AT_END = /[ \n\t\r\f{}()'";/[]#]/g

这里主要是通过@字符来解析出跟随在其后的name,比如

@import 解析出对应的Token为 ['at-word','import', line, pos - offset,line, next - offset]

4

解析过程之 word

case BACKSLASH:next = posescape = truewhile (css.charCodeAt(next + 1) === BACKSLASH) {next += 1escape = !escape}code = css.charCodeAt(next + 1)if (escape &&code !== SLASH &&code !== SPACE &&code !== NEWLINE &&code !== TAB &&code !== CR &&code !== FEED) {next += 1if (RE_HEX_ESCAPE.test(css.charAt(next))) {while (RE_HEX_ESCAPE.test(css.charAt(next + 1))) {next += 1}if (css.charCodeAt(next + 1) === SPACE) {next += 1}}}currentToken = ['word', css.slice(pos, next + 1), line, pos - offset, line, next - offset]pos = nextbreakdefault:if (code === SLASH && css.charCodeAt(pos + 1) === ASTERISK) {……} else {RE_WORD_END.lastIndex = pos + 1RE_WORD_END.test(css)if (RE_WORD_END.lastIndex === 0) {next = css.length - 1} else {next = RE_WORD_END.lastIndex - 2}currentToken = ['word', css.slice(pos, next + 1),line, pos - offset,line, next - offset]buffer.push(currentToken)pos = next}break

这里的 BACKSLASH 就是反斜杆\,以反斜杆开头的,有如下可能:

1、转义字符

2、转义序列

转义序列,就好比 Unicode 编码,就是转义序列。

这里的代码比较简单,不再赘述。

5

解析过程之 comment

default:if (code === SLASH && css.charCodeAt(pos + 1) === ASTERISK) {next = css.indexOf('*/', pos + 2) + 1if (next === 0) {if (ignore || ignoreUnclosed) {next = css.lengthelse {unclosed('comment')}}content = css.slice(pos, next + 1)lines = content.split('\n')last = lines.length - 1if (last > 0) {nextLine = line + lastnextOffset = next - lines[last].length} else {nextLine = linenextOffset = offset}currentToken = ['comment', content, line, pos - offset, nextLine, next - nextOffset]offset = nextOffsetline = nextLinepos = next} else {……}break;

对comment的解析,就是直接对 /* */ 进行匹配。

[ ] { } : ; )

case OPEN_SQUARE:case CLOSE_SQUARE:case OPEN_CURLY:case CLOSE_CURLY:case COLON:case SEMICOLON:case CLOSE_PARENTHESES:let controlChar = String.fromCharCode(code)currentToken = [controlChar, controlChar, line, pos - offset]break

对这类的处理就比较简单,直接将读到的字符作为Token返回给语法解析器。

6

解析过程之(和 brackets

case OPEN_PARENTHESES:prev = buffer.length ? buffer.pop()[1] : ''n = css.charCodeAt(pos + 1)if (prev === 'url' &&n !== SINGLE_QUOTE && n !== DOUBLE_QUOTE &&n !== SPACE && n !== NEWLINE && n !== TAB &&n !== FEED && n !== CR) {next = posdo {escaped = falsenext = css.indexOf(')', next + 1)if (next === -1) {if (ignore || ignoreUnclosed) {next = posbreak} else {unclosed('bracket')}}escapePos = nextwhile (css.charCodeAt(escapePos - 1) === BACKSLASH) {escapePos -= 1escaped = !escaped}} while (escaped)currentToken = ['brackets', css.slice(pos, next + 1), line, pos - offset, line, next - offset]pos = next} else {next = css.indexOf(')', pos + 1)content = css.slice(pos, next + 1)if (next === -1 || RE_BAD_BRACKET.test(content)) {currentToken = ['(', '(', line, pos - offset]} else {currentToken = ['brackets', content,line, pos - offset,line, next - offset]pos = next}}break

0

5

PostCss 总结

PostCss 将 括号归类为:普通括号、带有url前缀的括号

普通括号:如果括号范围内的字符串符合正则 /.[\/("'\n]/ ,就将括号内的语法合法判断交给语法解析器,否则就生成一个 brackets token,这样就间接性规避了语法解析器去检查括号内的内容,这也就说明了为什么 background: (red)))))); 语法解析器认为是合法的。

带有url前缀的括号:括号内部语法检查只进行括号关闭校验,其他的统一默认合法,并生成一个 brackets token,这样语法解析器就不会解析你内部语法是否合法,即使你在括号里头输入 background: url(/\)); ,但是这种语法在普通括号里是非法的,比如  background: (/\));。

关于极光

极光(Aurora Mobile,纳斯达克股票代码:JG)成立于2011年,是中国领先的开发者服务提供商。极光专注于为移动应用开发者提供稳定高效的消息推送、即时通讯、统计分析、极光分享、短信、一键认证、深度链接等开发者服务。截止到2019年12月份,极光已经为超过50万移动开发者和145.2万款移动应用提供服务,其开发工具包(SDK)安装量累计336亿,月度独立活跃设备13.6亿部。同时,极光持续赋能开发者和传统行业客户,推出精准营销、金融风控、市场洞察、商业地理服务产品,致力于为社会和各行各业提高运营效率,优化决策制定。

极光小课堂 | PostCss浅析之词法分析相关推荐

  1. 极光小课堂 | 极光一键登录集成手册--Android

    前言 某天翻阅极光文档时,发现极光也推出了一键登录,先来看个效果: 说句实在话,而今的互联网变更速度,简直让人目不暇接,单单从用户体验来讲,着实让人各种眼光一亮,闪闪冒金星.现在还能记得 Androi ...

  2. 极光小课堂 | 极光短信与 Java 整合指南

    前言 今天大Boss给小优优说,你给咱们的注册系统增加一下短信验证吧.后来一it好友告诉小优优,说用极光短信吧,毕竟人家就是做消息推送出身的,技术以及实时性肯定没的说. 小优优觉得说的对,毕竟人家是专 ...

  3. 极光推送 请检查参数合法性_极光小课堂 | 极光推送在人脸识别终端管理系统中的应用...

    项目背景 最近开发的一款人脸识别终端管理系统,主要包括运营平台.企业后台管理系统.APP 端.智能人脸识别终端模块. 下图是系统的架构图: 其中各个模块之间都需要即时通讯,比如: APP 端用户注册完 ...

  4. 极光小课堂 | 极光推送之 Android 客户端使用指南——基础篇

    " 本文中涉及到的所有代码现已在 Github 上开源,地址:https://github.com/xuexiangjys/JPushSample" 01 前言 - 极光推送是国内 ...

  5. 极光小课堂 | 极光推送集成解决方案

    1. 极光推送集成背景 最近在研究推送和长连接,调研了市场上的几家平台,综合考虑选择了极光推送.长连接保活一直是一个大问题,尤其是 Android 方面.在最近谷歌公司的几次更新之后,Android ...

  6. 接口入参形式_极光小课堂|手把手教你做接口测试

    接口测试是项目测试过程中非常重要的一环,测试的对象是接口,所以提早介入测试,对代码逻辑进行全面验证,就会更早的发现程序的问题.同时,接口测试比UI测试效率更高,并且更容易验证极端和异常的情况. 那么什 ...

  7. 【修真院java小课堂】ArrayList浅析

    大家好,我是IT修真院郑州分院第8期学员,一枚正直善良的java程序员.今天给大家分享一下,修真院官网 java任务中可能会使用到的知识点: ArrayList浅析 width="640&q ...

  8. python print 换行_Python小课堂第21课:规整一下我们的输出之打印格式化与字符串...

    整齐的输出,不仅美观,还能方便我们更容易的定位问题的重点.所以我们有必要将我们的输出内容美化一下! 请点击右上角"关注"按钮关注我们,跟着木辛老师深入浅出的掌握输出格式化的方法吧! ...

  9. 饱和气压与温度的关系_凯米斯小课堂 | 溶解氧与水产养殖的关系

    点击上方蓝字关注我们 随着物联网技术的高速发展,传感器和软件通过移动平台或者电脑平台对农业生产进行控制,使传统农业更具智慧.这其中也包括我们的水产养殖业.众所周知,养鱼先养水,水是鱼类赖以生存的环境, ...

最新文章

  1. linux 下的dd,Linux中的dd命令
  2. 85.路由器和电脑的设置
  3. 径向基函数(RBF)神经网络
  4. why SAP OData default 100 entries are returned
  5. ASP.NET AJAX(服务器回调)
  6. 【Spring实战】—— 1 入门讲解
  7. POJ 2728 Desert King(最优比率生成树)
  8. jQuery jQuery on()方法
  9. C#对文件的操作(创建、获取文件数量、删除)(读、写文件)
  10. MySQL数据库通过cmd窗口导入sql文件
  11. Office365 PPT加载本地模板
  12. XX市核酸检测软件开发基本方案
  13. thinkpad重装系统不引导_联想品牌机重装系统无法引导原因分析以及解决方法(全面分析)...
  14. reflections歌词翻译_Reflections歌词
  15. [UE] 在虚幻中使用动画序列和分层骨骼混合简单实现角色看向
  16. 使用 PD Recover 恢复 PD 集群
  17. s60v5用java qq_S60V5手机QQ终于来了,试用感受!
  18. 查询数据库表空间文件位置及扩充表空间,查询表空间容量
  19. c语言18之鸡兔同笼,共有98个头,386只脚,编程求鸡兔各多少只
  20. win7 微信 代理服务器,Win7系统使用电脑版微信如何@别人

热门文章

  1. 月结2 - 维护汇率[OB08]
  2. 2022-08-22 第六小组 瞒春 学习笔记
  3. zabbix mysql trapper_zabbix的trapper(补获器)使用
  4. 怎么一次性给多段视频制作画中画,并进行智能合并
  5. Mogrt是什么?如何在PR中安装.Mogrt文件并使用 Premiere基本图形MOGRT文件
  6. 论文解读《ResRep: Lossless CNN Pruning via Decoupling Remembering and Forgetting》
  7. 课外知识:人工智能简明入门学习指南
  8. 查询出部门名称、部门的员工数、部门的平均工资、部门的最低收入雇员姓名和最高收入雇员的姓名
  9. R语言基于mediation包行中介效应分析(2)
  10. Codeforces 869 C. The Intriguing Obsession (组合数学)