如何编写解析器为表达式 a.b[c.d][e].f[g[h[i.j]]] 构造 JavaScript AST MemberExpression?
How to write a parser to construct the JavaScript AST MemberExpression for the expression a.b[c.d][e].f[g[h[i.j]]]?
按照 How to clearly represent a.b[c.d][e].f[g[h[i.j]]] as an object tree? 的思路,您将如何编写算法从表达式 a.b[c.d][e].f[g[h[i.j]]]
生成 JS AST?我正在尝试编写一个解析器以从该表达式生成某种对象结构(理想情况下比 JS AST MemberExpression
更直观,因此出现了另一个问题)。我想看看该算法如何构建 JavaScript MemberExpression
树。
目前我有这种算法可以生成某种树(但目前似乎不正确):
const patterns = [
[/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*/, 'name'],
[/^\[/, 'open'],
[/^\]/, 'close'],
[/^\./, 'stem']
]
console.log(parsePath('a.b[c.d][e].f[g[h[i.j]]]'))
function parsePath(str) {
let node
let nest = []
let result = nest
let stack = [nest]
while (str.length) {
nest = stack[stack.length - 1]
p:
for (let pattern of patterns) {
let match = str.match(pattern[0])
if (match) {
if (pattern[1] === 'name') {
node = {
form: `term`,
name: match[0],
link: []
}
nest.push(node)
} else if (pattern[1] === 'stem') {
stack.push(node.link)
} else if (pattern[1] === 'open') {
node = {
form: 'read',
link: []
}
nest.push(node)
stack.push(node.link)
} else if (pattern[1] === 'close') {
stack.pop()
}
str = str.substr(match[0].length)
break p
}
}
}
return result[0]
}
期望的结果是这样的(或者如果您愿意创建一个更好、更直观的数据结构):
{
"type": "MemberExpression",
"object": {
"type": "MemberExpression",
"object": {
"type": "MemberExpression",
"object": {
"type": "MemberExpression",
"object": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"name": "a"
},
"property": {
"type": "Identifier",
"name": "b"
},
"computed": false
},
"property": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"name": "c"
},
"property": {
"type": "Identifier",
"name": "d"
},
"computed": false
},
"computed": true
},
"property": {
"type": "Identifier",
"name": "e"
},
"computed": true
},
"property": {
"type": "Identifier",
"name": "f"
},
"computed": false
},
"property": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"name": "g"
},
"property": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"name": "h"
},
"property": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"name": "i"
},
"property": {
"type": "Identifier",
"name": "j"
},
"computed": false
},
"computed": true
},
"computed": true
},
"computed": true
}
我挣扎(部分)的原因是我不喜欢这种 MemberExpression
树结构,感觉落后而且不是很直观。因此,如果您可以构建一个更简单、更直接的数据结构,那将是理想的(那是另一个问题),但如果不能,那么只需要一个构建它的算法就可以让我前进。
就个人而言,我宁愿尝试生成这种结构,因为我觉得它更直观:
{
type: 'site',
site: [
{
type: 'term',
term: 'a'
},
{
type: 'term',
term: 'b'
},
{
type: 'sink',
sink: [
{
type: 'term',
term: 'c'
},
{
type: 'term',
term: 'd'
}
]
},
{
type: 'sink',
sink: [
{
type: 'term',
term: 'e'
}
]
},
{
type: 'term',
term: 'f'
},
{
type: 'sink',
sink: [
{
type: 'term',
term: 'g'
},
{
type: 'sink',
sink: [
{
type: 'term',
term: 'h'
},
{
type: 'sink',
sink: [
{
type: 'term',
term: 'i'
},
{
type: 'term',
term: 'j'
}
]
}
]
}
]
}
]
}
但其中一个适合我(或两者)。
如果我们使用第二个,我的下一个问题将是如何将该数据结构转换为 MemberExpression
tree/data 结构 :) 但我会先尝试自己做。所以在这个问题中构建 MemberExpression 可能更好,然后我可以解决这个问题。
将字符串分成第一层的对象和属性组,如
[
"a",
"b",
"[c.d]",
"[e]",
"f",
"[g[h[i.j]]]"
]
获取对象
- 取最后一项为属性。
- 检查 属性 是否以括号开头然后将
computed
设置为 true
并从周围的括号中删除 属性。
- Return 一个对象
type: "MemberExpression"
,
object
与对象 (2.),
property
调用主函数的结果getAST
(1.),
computed
.
function getAST(string) {
function getObject(parts) {
if (parts.length === 1) return { type: "Identifier", name: parts[0] };
let property = parts.pop(),
computed = false;
if (property.startsWith('[')) {
computed = true;
property = property.slice(1, -1);
}
return {
type: "MemberExpression",
object: getObject(parts),
property: getAST(property),
computed
};
}
let i = 0,
dot,
bracket,
parts = [];
while (i < string.length) {
dot = string.indexOf('.', i);
bracket = string.indexOf('[', i);
if (dot !== -1 && (bracket === -1 || dot < bracket)) {
const temp = string.slice(i, dot);
if (temp) parts.push(temp);
i = dot + 1;
continue;
}
if (bracket !== -1 && (dot === -1 || bracket < dot)) {
const temp = string.slice(i, bracket);
if (temp) parts.push(temp);
i = bracket;
let open = 1,
j = i;
while (++j < string.length) {
if (string[j] === '[') open++;
if (string[j] === ']') open--;
if (!open) break;
}
j++;
parts.push(string.slice(i, j));
i = j;
continue;
}
parts.push(string.slice(i));
break;
}
return getObject(parts);
}
console.log(getAST('a.b[c.d][e].f[g[h[i.j]]]'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
按照 How to clearly represent a.b[c.d][e].f[g[h[i.j]]] as an object tree? 的思路,您将如何编写算法从表达式 a.b[c.d][e].f[g[h[i.j]]]
生成 JS AST?我正在尝试编写一个解析器以从该表达式生成某种对象结构(理想情况下比 JS AST MemberExpression
更直观,因此出现了另一个问题)。我想看看该算法如何构建 JavaScript MemberExpression
树。
目前我有这种算法可以生成某种树(但目前似乎不正确):
const patterns = [
[/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*/, 'name'],
[/^\[/, 'open'],
[/^\]/, 'close'],
[/^\./, 'stem']
]
console.log(parsePath('a.b[c.d][e].f[g[h[i.j]]]'))
function parsePath(str) {
let node
let nest = []
let result = nest
let stack = [nest]
while (str.length) {
nest = stack[stack.length - 1]
p:
for (let pattern of patterns) {
let match = str.match(pattern[0])
if (match) {
if (pattern[1] === 'name') {
node = {
form: `term`,
name: match[0],
link: []
}
nest.push(node)
} else if (pattern[1] === 'stem') {
stack.push(node.link)
} else if (pattern[1] === 'open') {
node = {
form: 'read',
link: []
}
nest.push(node)
stack.push(node.link)
} else if (pattern[1] === 'close') {
stack.pop()
}
str = str.substr(match[0].length)
break p
}
}
}
return result[0]
}
期望的结果是这样的(或者如果您愿意创建一个更好、更直观的数据结构):
{
"type": "MemberExpression",
"object": {
"type": "MemberExpression",
"object": {
"type": "MemberExpression",
"object": {
"type": "MemberExpression",
"object": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"name": "a"
},
"property": {
"type": "Identifier",
"name": "b"
},
"computed": false
},
"property": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"name": "c"
},
"property": {
"type": "Identifier",
"name": "d"
},
"computed": false
},
"computed": true
},
"property": {
"type": "Identifier",
"name": "e"
},
"computed": true
},
"property": {
"type": "Identifier",
"name": "f"
},
"computed": false
},
"property": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"name": "g"
},
"property": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"name": "h"
},
"property": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"name": "i"
},
"property": {
"type": "Identifier",
"name": "j"
},
"computed": false
},
"computed": true
},
"computed": true
},
"computed": true
}
我挣扎(部分)的原因是我不喜欢这种 MemberExpression
树结构,感觉落后而且不是很直观。因此,如果您可以构建一个更简单、更直接的数据结构,那将是理想的(那是另一个问题),但如果不能,那么只需要一个构建它的算法就可以让我前进。
就个人而言,我宁愿尝试生成这种结构,因为我觉得它更直观:
{
type: 'site',
site: [
{
type: 'term',
term: 'a'
},
{
type: 'term',
term: 'b'
},
{
type: 'sink',
sink: [
{
type: 'term',
term: 'c'
},
{
type: 'term',
term: 'd'
}
]
},
{
type: 'sink',
sink: [
{
type: 'term',
term: 'e'
}
]
},
{
type: 'term',
term: 'f'
},
{
type: 'sink',
sink: [
{
type: 'term',
term: 'g'
},
{
type: 'sink',
sink: [
{
type: 'term',
term: 'h'
},
{
type: 'sink',
sink: [
{
type: 'term',
term: 'i'
},
{
type: 'term',
term: 'j'
}
]
}
]
}
]
}
]
}
但其中一个适合我(或两者)。
如果我们使用第二个,我的下一个问题将是如何将该数据结构转换为 MemberExpression
tree/data 结构 :) 但我会先尝试自己做。所以在这个问题中构建 MemberExpression 可能更好,然后我可以解决这个问题。
将字符串分成第一层的对象和属性组,如
[ "a", "b", "[c.d]", "[e]", "f", "[g[h[i.j]]]" ]
获取对象
- 取最后一项为属性。
- 检查 属性 是否以括号开头然后将
computed
设置为true
并从周围的括号中删除 属性。 - Return 一个对象
type: "MemberExpression"
,object
与对象 (2.),property
调用主函数的结果getAST
(1.),computed
.
function getAST(string) {
function getObject(parts) {
if (parts.length === 1) return { type: "Identifier", name: parts[0] };
let property = parts.pop(),
computed = false;
if (property.startsWith('[')) {
computed = true;
property = property.slice(1, -1);
}
return {
type: "MemberExpression",
object: getObject(parts),
property: getAST(property),
computed
};
}
let i = 0,
dot,
bracket,
parts = [];
while (i < string.length) {
dot = string.indexOf('.', i);
bracket = string.indexOf('[', i);
if (dot !== -1 && (bracket === -1 || dot < bracket)) {
const temp = string.slice(i, dot);
if (temp) parts.push(temp);
i = dot + 1;
continue;
}
if (bracket !== -1 && (dot === -1 || bracket < dot)) {
const temp = string.slice(i, bracket);
if (temp) parts.push(temp);
i = bracket;
let open = 1,
j = i;
while (++j < string.length) {
if (string[j] === '[') open++;
if (string[j] === ']') open--;
if (!open) break;
}
j++;
parts.push(string.slice(i, j));
i = j;
continue;
}
parts.push(string.slice(i));
break;
}
return getObject(parts);
}
console.log(getAST('a.b[c.d][e].f[g[h[i.j]]]'));
.as-console-wrapper { max-height: 100% !important; top: 0; }