从零到1写一个简单的sass编译器(2)

392 阅读2分钟

上一篇已经实现 源代码(sass)字符流Token流的处理

本篇主要专注 Token流到AST(Abstract Syntax Tree)语法树的生成, 可以在astexplorer体验下各种源码跟 AST 的映射关系

目标例子

输入:

$primary-color: #333;
.test{
  color: $primary-color;
}

输出:

.test {
    color: #333;
}

实现parse函数将Token流转为AST语法树:

以下对Token流的AST解析是参照前一篇的AST节点定义来实现的,有兴趣的可以先移步上一篇


function parse(input: LexicalStream) {

    function delimited(start: puncType, stop: puncType, separator: puncType, parser: Function) {// FIFO
        let statements: any[] = [], first = true;

        skipPunc(start);

        while (!input.eof()) {
            if (isPuncToken(stop)) break;
            if (first) {
                first = false;
            } else {
                if (separator === ';') {
                    skipPuncSilent(separator)
                } else {
                    skipPunc(separator);
                }
            }
            if (isPuncToken(stop)) break;

            statements.push(parser());
        }
        skipPunc(stop);

        return statements;
    }

    // Token的解析分发

    function dispatchParser() {
        // predict 下一个Token的类型来判定下一步的解析
        let tok = input.peek();

        // VARIABLE类型就直接返回Token作为语法树的一部分,(说明 VARIABLE既可以是Token的类型,也可以是AST Node类型)
        if (tok.type === NodeTypes.VARIABLE) {
            return input.next();
        }

        // 同上
        if (tok.type === NodeTypes.PUNC) {
            return input.next()
        }

        if (tok.type === NodeTypes.TEXT) {
            return input.next()
        }
    }

    // 解析 DECLARATION 节点
    function parseDeclaration(left: DeclarationStatement['left']): DeclarationStatement {
        input.next(); // skip ':'
        return {
            type: NodeTypes.DECLARATION,
            left: left,
            // 读取 Text value
            right: input.next()
        }
    }

    // 解析 RULE节点
    function parseRule(selector: RuleStatement['selector']): RuleStatement {
        let children = delimited("{", "}", ";", parseStatement);
        return {
            type: NodeTypes.RULE,
            selector,
            children
        }
    }

    // 通过predict下一个 Token类型来判断解析的 AST Node类型
    function maybeDeclaration(exp) {
        let expr = exp();
         if (isAssignToken()) {
            if (expr.type === NodeTypes.VARIABLE) {
                return parseDeclaration(expr)
            }
         }
        if (isPuncToken('{')) {
            return parseRule({
                type: NodeTypes.SELECTOR,
                value: expr
            }) //passin selector
        }

        return expr;
    }

    // 基础 Statement节点的 parser
    function parseStatement() {
        return maybeDeclaration(function () {
            return dispatchParser()
        })
    }

    // parse 入口的 children,可以参见上一篇的 RootNode节点定义
    function parsechildren(): Statement[] {
        let children: Statement[] = [];
        while (!isEnd()) {
            children.push(parseStatement());
            skipPuncSilent(";");
        }
        return children
    }

    // parser的入口
    function parseProgram(): RootNode {
        return {
            type: NodeTypes.RootNode,
            children: parsechildren()
        }
    }
    return parseProgram()
}

parse出源码(sass源代码)对应的抽象语法树如下:

{
  "type": "RootNode",
  "children": [
    {
      "type": "DECLARATION",
      "left": {
        "type": "VARIABLE",
        "value": "$primary-color"
      },
      "right": {
        "type": "TEXT",
        "value": "#333"
      }
    },
    {
      "type": "RULE",
      "selector": {
        "type": "SELECTOR",
        "value": {
          "type": "TEXT",
          "value": ".test"
        }
      },
      "children": [
        {
          "type": "DECLARATION",
          "left": {
            "type": "TEXT",
            "value": "color"
          },
          "right": {
            "value": {
              "type": "VARIABLE",
              "value": "$primary-color"
            }
          }
        }
      ]
    }
  ]
}

可以看出是由第一篇里面定义 的AST节点组合而成

结语

以上是伪代码,实际会比这个复杂一些,完整代码可以查看这里

最后预祝大家元旦快乐

下一篇实现Transform把源码(sass)关联的AST转换为目标代码(css)关联的 AST