CodeSky 代码之空

随手记录自己的学习过程

编译原理从零到一 写一个 JSON parser

2017-04-23 21:47分类: JavaScript评论: 0

最近在做 Chrome 插件的高级功能,突然想要试试可视化选择 key 的路径,再一想,咦,这不就是个编译原理的典型例题吗?

于是我就开始着手学编译原理了,主要参考 dalao 的以下两篇文章:

编译原理一共分为四个部分:词法分析,语法(文法)分析,语义分析,代码生成。

词法分析,就是逐个解析之后加上词性,语法分析则是将词法分析后的结果解析为抽象语法树,语义分析是在语法分析时针对具体的实例加上额外的动作解释。具体的可以见上面的链接。

JSON 的首页有非常完整的词法和语法分析的 token 列表甚至是流程图,所以可以省去了一些劳动。

在词法分析上,我们要首先抽象出哪些是我们 tokens,进行归类,然后一个个字符的进行 read() 。最简单的方法只要逐个阅读看看满足哪一个 token 的条件处理并存入就行了,上文的链接中也有词法分析器的介绍,写得非常清楚。

1class Lexer {
2  constructor() {
3    this.chunk = '';
4  }
5
6  lex(input) {
7    this.chunk = input;
8    this.tokens = [];
9    while(!this.readEOF()) {
10      let consumed = this.readWhiteSpace()
11                  || this.readString()
12                  || this.readNumber()
13                  || this.readBoolean()
14                  || this.readNull()
15                  || this.readLiteral()
16      this.chunk = this.chunk.slice(consumed);
17    }
18    return this.tokens;
19  }
20  pushToken(type, value) {
21    this.tokens.push([type, value]);
22  }
23
24  readEOF() {
25    if (!this.chunk) {
26      this.pushToken('EOF', '');
27      return true;
28    };
29  }
30
31  readWhiteSpace() {
32    return this.chunk.match(/^\s*/)[0].length;
33  }
34
35  readString() {
36    let match = this.chunk.match(/^"(?:[^"\\\x00-\x1F\x7F\x80-\x9F]|\\["\\/bfnrt]|\\u[0-9a-fA-F]{4})*"/);
37    if (match) {
38      this.pushToken('String', match[0]);
39      return match[0].length;
40    }
41  }
42
43  readNumber() {
44    let match = this.chunk.match(/^-?[1-9]*\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/);
45    if (match) {
46      this.pushToken('Number', match[0]);
47      return match[0].length;
48    }
49  }
50
51  readBoolean() {
52    let match = this.chunk.match(/^(?:true|false)/);
53    if (match) {
54      this.pushToken('Boolean', match[0].toLowerCase());
55      return match[0].length;
56    }
57  }
58
59  readNull() {
60    let match = this.chunk.match(/^null/);
61    if (match) {
62      this.pushToken('Null', match[0]);
63      return match[0].length;
64    }
65  }
66
67  readLiteral() {
68    const value = this.chunk.charAt(0);
69    this.pushToken(value, value);
70    return value.length;
71  }
72}
73

词法分析器的流程非常简单,只要一个不拉的列出 tokens 并且存入即可,之后我们就可以得到一个词法分析的结果数组。

parser 的部分原博并没有写,实际上也就是遍历并构造一颗抽象语法树,我们这里用自顶向下的构建法,深度优先遍历得出构造的结果。

从表面上来说和词法分析器是一样的,区别只是构造数组和构造树的区别——我们利用解析后的词法分析结果数组作为输入,逐个分析类型并且处理结果。

最后,我们需要做一些自定义的操作,比如我们想要格式化或者生成 JSON,就非常容易了:只需要根据结果的 token 类型匹配再进行一些操作即可:

1class Parser {
2  constructor() {
3    this.tokens = [];
4  }
5
6  parse(input) {
7    this.i = 0;
8    this.tokens = new Lexer().lex(input);
9    const value = this.getValue();
10    if (!value) throw new TypeError('failed to parse JSON, expect value');
11    return value;
12  }
13
14  format(input, indent = '  ') {
15    const ast = this.parse(input);
16    return this._format(ast, indent);
17  }
18
19  _format(node, indent) {
20    switch(node.type) {
21      case 'String':
22        return JSON.stringify(JSON.parse(node.value));
23      case 'Number':
24      case 'True':
25      case 'False':
26      case 'Null':
27        return node.value;
28    }
29    let [begin, end] = node.type === 'Object' ? ['{', '}'] : ['[', ']'];
30    let output = begin;
31    const innerMembers = node.value;
32    if (!innerMembers.length) return output + end;
33    output += '\n';
34    innerMembers.forEach((item, index, arr) => {
35      output += indent;
36      if (node.type === 'Object') output += item.key.value + ': ';
37      const value = node.type === 'Object' ?
38          this._format(item.value, indent).replace(/\n/g, '\n' + indent) :
39          this._format(item, indent).replace(/\n/g, '\n' + indent);
40        output += value;
41        if (index !== arr.length - 1) output += ',\n';
42    });
43    return output += '\n' + end;
44  }
45
46  getValue() {
47    return this.readString() || this.readNumber() || this.readObject() || this.readArray() || this['true']() || this['false']() || this['null']();
48  }
49
50  readString() {
51    const [tag, value] = this.tokens[this.i];
52    if (tag === 'String') {
53      this.i++;
54      return { type: 'String', value };
55    }
56  }
57
58  readNumber() {
59    const [tag, value] = this.tokens[this.i];
60    if (tag === 'Number') {
61      this.i++;
62      return { type: 'Number', value };
63    }
64  }
65
66  readObject() {
67    const [tag] = this.tokens[this.i];
68    if (tag === '{') {
69      this.i++;
70      const members = this.readMembers() || [];
71      const [tag] = this.tokens[this.i];
72      if (tag !== '}') throw new TypeError('failed to parse object, expect "}"');
73      this.i++;
74      return { type: 'Object', value: members };
75    }
76  }
77
78  readMembers() {
79    let pair = this.readPair();
80    if (!pair) return [];
81    let members = [ pair ];
82    while (true) {
83      const [tag] = this.tokens[this.i];
84      if (tag !== ',') break;
85      this.i++;
86      pair = this.readPair();
87      members.push(pair);
88    }
89    return members;
90  }
91
92  readPair() {
93    const string = this.readString();
94    if (!string) throw new TypeError('failed to parse pair, expect string');
95    const [tag] = this.tokens[this.i];
96    if (tag !== ':') throw new TypeError('failed to parse pair, expect colon');
97    this.i++;
98    const value = this.getValue();
99    if (!value) throw new TypeError('failed to parse pair, expect value');
100    return { key: string, value };
101  }
102
103  readArray() {
104    const [tag] = this.tokens[this.i];
105    if (tag === '[') {
106      this.i++;
107      const elements = this.readElements() || [];
108      const [tag] = this.tokens[this.i];
109      if (tag !== ']') throw new TypeError('failed to parse object, expect "]"');
110      this.i++;
111      return { type: 'Array', value: elements };
112    }
113  }
114  
115  readElements() {
116    let value = this.getValue();
117    if (!value) return [];
118    let elements = [ value ];
119    while (true) {
120      const [tag] = this.tokens[this.i];
121      if (tag !== ',') break;
122      this.i++;
123      value = this.getValue();
124      elements.push(value);
125    }
126    return elements;
127  }
128
129  'true'() {
130    const [tag, value] = this.tokens[this.i];
131    if (tag === 'Boolean' && value === 'true') {
132      this.i++;
133      return { type: 'True', value };
134    }
135  }
136
137  'false'() {
138    const [tag, value] = this.tokens[this.i];
139    if (tag === 'Boolean' && value === 'false') {
140      this.i++;
141      return { type: 'False', value };
142    }
143  }
144
145  'null'() {
146    const [tag, value] = this.tokens[this.i];
147    if (tag === 'Null') {
148      this.i++;
149      return { type: 'Null', value };
150    }
151  }
152}
153

这样就完成了一个 JSON parser。

评论 (0)