概述
Earley 算法是一种用于解析上下文无关文法(Context-Free Grammar, CFG)的算法。它能够处理左递归、右递归以及歧义文法,并且具有较好的时间复杂度(O(n^3))。本教程将详细介绍如何实现一个 Earley 解析器,并解释其核心逻辑。
1. Earley 算法的基本概念
Earley 算法的核心思想是通过动态规划的方式,逐步构建解析过程中的状态集合(chart)。每个状态表示一个产生式规则在某个位置的解析进度。Earley 算法通过三个主要操作来处理这些状态:
- 预测(Predict) :当遇到一个非终结符时,预测所有可能的产生式规则。
- 扫描(Scan) :当遇到一个终结符时,尝试匹配输入中的符号。
- 完成(Complete) :当一个产生式规则完全解析后,将其结果用于推进其他状态的解析进度。
2. Earley 解析器的实现
2.1 初始化
首先,我们需要初始化解析器的状态。解析器的核心数据结构是 chart
,它是一个数组,每个元素对应输入中的一个位置,存储该位置的所有解析状态。
class EarleyParser {
constructor(grammarMap) {
this.grammarMap = grammarMap; // 语法规则映射
this.chart = []; // 解析状态表
this.stateSet = []; // 用于快速查重的集合
}
initChart(input) {
this.chart = Array(input.length + 1).fill(null).map(() => []);
this.stateSet = Array(input.length + 1).fill(null).map(() => new Set()); // 初始化状态集合
}
}
2.2 状态管理
每个状态由 State
类表示,包含以下属性:
rule
:产生式规则。dot
:当前解析的位置(点的位置)。start
:该状态开始的位置。end
:该状态结束的位置。children
:子节点,用于生成抽象语法树(AST)。
class State {
constructor(rule, dot, start, end, children = []) {
this.rule = rule; // 产生式规则
this.dot = dot; // 点的位置
this.start = start; // 开始位置
this.end = end; // 结束位置
this.children = children; // 子节点,用于生成AST
}
isComplete() {
return this.dot >= this.rule.rhs.length;
}
nextSymbol() {
return this.dot < this.rule.rhs.length ? this.rule.rhs[this.dot] : null;
}
print() {
let rstr = ""
for(let i = 0; i< this.rule.rhs.length; i++) {
if (this.dot === i) {
rstr += " ·"
}
rstr += " " + this.rule.rhs[i];
}
if (this.dot >= this.rule.rhs.length) {
rstr += " ·"
}
return `${ this.rule.lhs } -> ${ rstr } ${this.isComplete()}`
}
}
2.3 添加状态
在添加状态时,我们需要确保不会重复添加相同的状态。通过 stateHash
方法将状态唯一化为字符串,并使用 stateSet
进行查重。
addState(state, position) {
const hash = this.stateHash(state);
if (!this.stateSet[position].has(hash)) {
console.log(`添加 State {${position}}: ${state.print()}`);
this.chart[position].push(state);
this.stateSet[position].add(hash);
return true;
} else {
console.log(`状态已存在,跳过添加: ${state.print()}`);
}
return false;
}
stateHash(state) {
return `${state.rule.lhs}->${state.rule.rhs.join(' ')}@${state.dot},${state.start},${state.end}`;
}
2.4 预测操作
当遇到一个非终结符时,预测所有可能的产生式规则,并将这些规则作为新的状态添加到当前解析位置。
predict(state, position) {
const symbol = state.nextSymbol();
if (this.grammarMap.has(symbol)) {
this.grammarMap.get(symbol).forEach(rule => {
console.log(`预测产生式: ${rule.lhs} -> ${rule.rhs.join(' ')}`);
this.addState(new State(rule, 0, position, position), position);
});
} else {
console.log(`无预测规则: ${symbol}`);
}
}
2.5 扫描操作
当遇到一个终结符时,尝试匹配输入中的符号。如果匹配成功,则推进解析状态。
scan(state, position, token) {
const nextSymbol = state.nextSymbol();
if (nextSymbol === token.type) {
const nextState = new State(state.rule, state.dot + 1, state.start, position + 1, [...state.children, token]);
console.log(`扫描匹配: ${token.type} -> ${token.value}`);
console.log(`Scan State {${position + 1}}: ${nextState.print()}`);
this.addState(nextState, position + 1);
} else {
console.log(`扫描失败: 期望 ${nextSymbol},但得到 ${token.type}`);
}
}
2.6 完成操作
当一个产生式规则完全解析后,将其结果用于推进其他状态的解析进度。
complete(state, position) {
console.log(`完成状态: ${state.print()}`);
for (let s of this.chart[state.start]) {
if (s.isComplete()) {
continue;
}
const nextSymbol = s.nextSymbol();
// 预测产生式中的预测项和当前已完成的规则相同
if (nextSymbol === state.rule.lhs) {
console.log(`完成状态触发: 当前未完成规则 ${s.print()},结合完成状态 ${state.print()} -> 推进点`);
const newState = new State(s.rule, s.dot + 1, s.start, position, [...s.children, state]);
this.addState(newState, position);
}
}
}
2.7 解析过程
解析过程通过遍历输入符号,逐步应用预测、扫描和完成操作,直到所有可能的解析路径都被探索完毕。
parse(input) {
this.initChart(input);
console.log(`初始化解析器,输入长度: ${input.length}`);
this.addState(new State({ lhs: 'S', rhs: ['E'] }, 0, 0, 0), 0);
for (let i = 0; i <= input.length; i++) {
let j = 0;
console.log(`------------ 解析位置: ${i} ------------`);
while (j < this.chart[i].length) {
const state = this.chart[i][j];
const isComplete = state.isComplete();
console.log(`处理状态: ${state.print()}`);
if (!isComplete) {
const nextSymbol = state.nextSymbol();
if (this.grammarMap.has(nextSymbol)) {
console.log(`预测: ${nextSymbol}`);
this.predict(state, i);
} else if (i < input.length) {
console.log(`扫描: ${nextSymbol}`);
this.scan(state, i, input[i]);
} else {
console.log(`无法处理的符号: ${nextSymbol}`);
}
} else {
console.log(`完成状态处理`);
this.complete(state, i);
}
j++;
}
}
return this.chart;
}
2.8 解析结果检查
解析完成后,我们可以检查解析是否成功,并判断是否存在歧义。
isParseSuccessful() {
const lastChart = this.chart[this.chart.length - 1];
const successfulStates = lastChart.filter(state =>
state.rule.lhs === 'S' &&
state.isComplete() &&
state.start === 0
);
const success = successfulStates.length > 0;
console.log(`解析${success ? '成功' : '失败'}`);
return success;
}
isParseAmbiguous() {
const lastChart = this.chart[this.chart.length - 1];
const successfulStates = lastChart.filter(state =>
state.rule.lhs === 'S' &&
state.isComplete() &&
state.start === 0
);
if (successfulStates.length > 1) {
console.warn('警告: 解析结果存在歧义');
return true;
}
return false;
}
2.9 生成抽象语法树(AST)
解析成功后,我们可以生成抽象语法树(AST)。
buildAST() {
const lastChart = this.chart[this.chart.length - 1];
const successfulStates = lastChart.filter(state =>
state.rule.lhs === 'S' &&
state.isComplete() &&
state.start === 0
);
if (successfulStates.length > 0) {
console.log('构建 AST 成功');
return this.buildASTNode(successfulStates[0]);
}
throw new Error('解析失败,无法生成AST');
}
buildASTNode(state) {
console.log(`生成 AST 节点: ${state.rule.lhs}`);
return {
type: state.rule.lhs,
children: state.children.map(child =>
child instanceof State ? this.buildASTNode(child) : { type: child.type, value: child.value }
)
};
}
3. 示例
以下是一个简单的示例,展示了如何使用 Earley 解析器解析输入 1+2
。
const grammar = {
rules: [
{ lhs: 'E', rhs: ['E', '+', 'T'], priority: 1 }, // E -> E + T
{ lhs: 'E', rhs: ['T'], priority: 2 }, // E -> T
{ lhs: 'T', rhs: ['T', '*', 'F'], priority: 1 }, // T -> T * F
{ lhs: 'T', rhs: ['F'], priority: 2 }, // T -> F
{ lhs: 'F', rhs: ['number'], priority: 3 } // F -> number
]
};
const grammarMap = new Map();
grammar.rules.forEach(rule => {
if (!grammarMap.has(rule.lhs)) {
grammarMap.set(rule.lhs, []);
}
grammarMap.get(rule.lhs).push(rule);
});
function tokenize(input) {
return input.split('').map(char => {
if (/[0-9]/.test(char)) {
return { type: 'number', value: parseInt(char) };
} else if (char === '+') {
return { type: '+', value: char };
} else if (char === '*') {
return { type: '*', value: char };
}
throw new Error(`未知字符: ${ char }`);
});
}
const parser = new EarleyParser(grammarMap);
const input = tokenize('1+2');
const chart = parser.parse(input);
console.log('解析成功:', parser.isParseSuccessful());
console.log('存在歧义:', parser.isParseAmbiguous());
4. 总结
Earley 算法是一种强大的解析算法,能够处理复杂的上下文无关文法。通过本教程,你应该能够理解 Earley 算法的基本原理,并实现一个简单的 Earley 解析器。希望这个教程对你有所帮助!
完整代码
// 改进状态去重逻辑
class EarleyParser {
constructor(grammarMap) {
this.grammarMap = grammarMap;
this.chart = [];
this.stateSet = []; // 用于快速查重的集合
}
initChart(input) {
this.chart = Array(input.length + 1).fill(null).map(() => []);
this.stateSet = Array(input.length + 1).fill(null).map(() => new Set()); // 初始化状态集合
}
stateHash(state) {
// 将 State 对象唯一化为字符串用于快速查重
return `${state.rule.lhs}->${state.rule.rhs.join(' ')}@${state.dot},${state.start},${state.end}`;
}
addState(state, position) {
const hash = this.stateHash(state);
if (!this.stateSet[position].has(hash)) {
console.log(`添加 State {${position}}: ${state.print()}`);
this.chart[position].push(state);
this.stateSet[position].add(hash);
return true;
} else {
console.log(`状态已存在,跳过添加: ${state.print()}`);
}
return false;
}
predict(state, position) {
const symbol = state.nextSymbol();
if (this.grammarMap.has(symbol)) {
this.grammarMap.get(symbol).forEach(rule => {
console.log(`预测产生式: ${rule.lhs} -> ${rule.rhs.join(' ')}`);
this.addState(new State(rule, 0, position, position), position);
});
} else {
console.log(`无预测规则: ${symbol}`);
}
}
scan(state, position, token) {
const nextSymbol = state.nextSymbol();
if (nextSymbol === token.type) {
const nextState = new State(state.rule, state.dot + 1, state.start, position + 1, [...state.children, token]);
console.log(`扫描匹配: ${token.type} -> ${token.value}`);
console.log(`Scan State {${position + 1}}: ${nextState.print()}`);
this.addState(nextState, position + 1);
} else {
console.log(`扫描失败: 期望 ${nextSymbol},但得到 ${token.type}`);
}
}
complete(state, position) {
console.log(`完成状态: ${state.print()}`);
for (let s of this.chart[state.start]) {
if (s.isComplete()) {
continue;
}
const nextSymbol = s.nextSymbol();
// 预测产生式中的预测项和当前已完成的规则相同
if (nextSymbol === state.rule.lhs) {
console.log(`完成状态触发: 当前未完成规则 ${s.print()},结合完成状态 ${state.print()} -> 推进点`);
const newState = new State(s.rule, s.dot + 1, s.start, position, [...s.children, state]);
this.addState(newState, position);
}
}
}
parse(input) {
this.initChart(input);
console.log(`初始化解析器,输入长度: ${input.length}`);
this.addState(new State({ lhs: 'S', rhs: ['E'] }, 0, 0, 0), 0);
for (let i = 0; i <= input.length; i++) {
let j = 0;
console.log(`------------ 解析位置: ${i} ------------`);
while (j < this.chart[i].length) {
const state = this.chart[i][j];
const isComplete = state.isComplete();
console.log(`处理状态: ${state.print()}`);
if (!isComplete) {
const nextSymbol = state.nextSymbol();
if (this.grammarMap.has(nextSymbol)) {
console.log(`预测: ${nextSymbol}`);
this.predict(state, i);
} else if (i < input.length) {
console.log(`扫描: ${nextSymbol}`);
this.scan(state, i, input[i]);
} else {
console.log(`无法处理的符号: ${nextSymbol}`);
}
} else {
console.log(`完成状态处理`);
this.complete(state, i);
}
j++;
}
}
return this.chart;
}
isParseSuccessful() {
const lastChart = this.chart[this.chart.length - 1];
const successfulStates = lastChart.filter(state =>
state.rule.lhs === 'S' &&
state.isComplete() &&
state.start === 0
);
const success = successfulStates.length > 0;
console.log(`解析${success ? '成功' : '失败'}`);
return success;
}
isParseAmbiguous() {
const lastChart = this.chart[this.chart.length - 1];
const successfulStates = lastChart.filter(state =>
state.rule.lhs === 'S' &&
state.isComplete() &&
state.start === 0
);
if (successfulStates.length > 1) {
console.warn('警告: 解析结果存在歧义');
return true;
}
return false;
}
buildAST() {
const lastChart = this.chart[this.chart.length - 1];
const successfulStates = lastChart.filter(state =>
state.rule.lhs === 'S' &&
state.isComplete() &&
state.start === 0
);
if (successfulStates.length > 0) {
console.log('构建 AST 成功');
return this.buildASTNode(successfulStates[0]);
}
throw new Error('解析失败,无法生成AST');
}
buildAllASTs() {
const lastChart = this.chart[this.chart.length - 1];
const successfulStates = lastChart.filter(state =>
state.rule.lhs === 'S' &&
state.isComplete() &&
state.start === 0
);
if (successfulStates.length > 0) {
console.log(`生成所有可能的 AST,共计 ${successfulStates.length} 个`);
return successfulStates.map(state => this.buildASTNode(state));
}
throw new Error('解析失败,无法生成AST');
}
buildASTNode(state) {
console.log(`生成 AST 节点: ${state.rule.lhs}`);
return {
type: state.rule.lhs,
children: state.children.map(child =>
child instanceof State ? this.buildASTNode(child) : { type: child.type, value: child.value }
)
};
}
}
// 定义语法规则
const grammar = {
rules: [
{ lhs: 'E', rhs: ['E', '+', 'T'], priority: 1 }, // E -> E + T
{ lhs: 'E', rhs: ['T'], priority: 2 }, // E -> T
{ lhs: 'T', rhs: ['T', '*', 'F'], priority: 1 }, // T -> T * F
{ lhs: 'T', rhs: ['F'], priority: 2 }, // T -> F
{ lhs: 'F', rhs: ['number'], priority: 3 } // F -> number
]
};
const grammarText = `
P -> E
E -> E + T
E -> T
T -> number
`
// 将语法规则转换为 Map
const grammarMap = new Map();
grammar.rules.forEach(rule => {
if (!grammarMap.has(rule.lhs)) {
grammarMap.set(rule.lhs, []);
}
grammarMap.get(rule.lhs).push(rule);
});
// 状态项类
class State {
constructor(rule, dot, start, end, children = []) {
this.rule = rule; // 产生式规则
this.dot = dot; // 点的位置
this.start = start; // 开始位置
this.end = end; // 结束位置
this.complete = false;
this.children = children; // 子节点,用于生成AST
}
isComplete() {
const c = this.dot >= this.rule.rhs.length;
this.complete = c;
return c;
}
nextSymbol() {
return this.dot < this.rule.rhs.length ? this.rule.rhs[this.dot] : null;
}
print() {
let rstr = ""
for(let i = 0; i< this.rule.rhs.length; i++) {
if (this.dot === i) {
rstr += " ·"
}
rstr += " " + this.rule.rhs[i];
}
if (this.dot >= this.rule.rhs.length) {
rstr += " ·"
}
return `${ this.rule.lhs } -> ${ rstr } ${this.isComplete()}`
}
}
function tokenize(input) {
return input.split('').map(char => {
if (/[0-9]/.test(char)) {
return { type: 'number', value: parseInt(char) };
} else if (char === '+') {
return { type: '+', value: char };
} else if (char === '*') {
return { type: '*', value: char };
}
throw new Error(`未知字符: ${ char }`);
});
}
// 使用示例
const parser = new EarleyParser(grammarMap);
const input = tokenize('1+2');
const chart = parser.parse(input);
console.log('解析成功:', parser.isParseSuccessful());
console.log('存在歧义:', parser.isParseAmbiguous());
## Output
```js
初始化解析器,输入长度: 3
添加 State {0}: S -> · E false
------------ 解析位置: 0 ------------
处理状态: S -> · E false
预测: E
预测产生式: E -> E + T
添加 State {0}: E -> · E + T false
预测产生式: E -> T
添加 State {0}: E -> · T false
处理状态: E -> · E + T false
预测: E
预测产生式: E -> E + T
状态已存在,跳过添加: E -> · E + T false
预测产生式: E -> T
状态已存在,跳过添加: E -> · T false
处理状态: E -> · T false
预测: T
预测产生式: T -> T * F
添加 State {0}: T -> · T * F false
预测产生式: T -> F
添加 State {0}: T -> · F false
处理状态: T -> · T * F false
预测: T
预测产生式: T -> T * F
状态已存在,跳过添加: T -> · T * F false
预测产生式: T -> F
状态已存在,跳过添加: T -> · F false
处理状态: T -> · F false
预测: F
预测产生式: F -> number
添加 State {0}: F -> · number false
处理状态: F -> · number false
扫描: number
扫描匹配: number -> 1
Scan State {1}: F -> number · true
添加 State {1}: F -> number · true
------------ 解析位置: 1 ------------
处理状态: F -> number · true
完成状态处理
完成状态: F -> number · true
完成状态触发: 当前未完成规则 T -> · F false,结合完成状态 F -> number · true -> 推进点
添加 State {1}: T -> F · true
处理状态: T -> F · true
完成状态处理
完成状态: T -> F · true
完成状态触发: 当前未完成规则 E -> · T false,结合完成状态 T -> F · true -> 推进点
添加 State {1}: E -> T · true
完成状态触发: 当前未完成规则 T -> · T * F false,结合完成状态 T -> F · true -> 推进点
添加 State {1}: T -> T · * F false
处理状态: E -> T · true
完成状态处理
完成状态: E -> T · true
完成状态触发: 当前未完成规则 S -> · E false,结合完成状态 E -> T · true -> 推进点
添加 State {1}: S -> E · true
完成状态触发: 当前未完成规则 E -> · E + T false,结合完成状态 E -> T · true -> 推进点
添加 State {1}: E -> E · + T false
处理状态: T -> T · * F false
扫描: *
扫描失败: 期望 *,但得到 +
处理状态: S -> E · true
完成状态处理
完成状态: S -> E · true
处理状态: E -> E · + T false
扫描: +
扫描匹配: + -> +
Scan State {2}: E -> E + · T false
添加 State {2}: E -> E + · T false
------------ 解析位置: 2 ------------
处理状态: E -> E + · T false
预测: T
预测产生式: T -> T * F
添加 State {2}: T -> · T * F false
预测产生式: T -> F
添加 State {2}: T -> · F false
处理状态: T -> · T * F false
预测: T
预测产生式: T -> T * F
状态已存在,跳过添加: T -> · T * F false
预测产生式: T -> F
状态已存在,跳过添加: T -> · F false
处理状态: T -> · F false
预测: F
预测产生式: F -> number
添加 State {2}: F -> · number false
处理状态: F -> · number false
扫描: number
扫描匹配: number -> 2
Scan State {3}: F -> number · true
添加 State {3}: F -> number · true
------------ 解析位置: 3 ------------
处理状态: F -> number · true
完成状态处理
完成状态: F -> number · true
完成状态触发: 当前未完成规则 T -> · F false,结合完成状态 F -> number · true -> 推进点
添加 State {3}: T -> F · true
处理状态: T -> F · true
完成状态处理
完成状态: T -> F · true
完成状态触发: 当前未完成规则 E -> E + · T false,结合完成状态 T -> F · true -> 推进点
添加 State {3}: E -> E + T · true
完成状态触发: 当前未完成规则 T -> · T * F false,结合完成状态 T -> F · true -> 推进点
添加 State {3}: T -> T · * F false
处理状态: E -> E + T · true
完成状态处理
完成状态: E -> E + T · true
完成状态触发: 当前未完成规则 S -> · E false,结合完成状态 E -> E + T · true -> 推进点
添加 State {3}: S -> E · true
完成状态触发: 当前未完成规则 E -> · E + T false,结合完成状态 E -> E + T · true -> 推进点
添加 State {3}: E -> E · + T false
处理状态: T -> T · * F false
无法处理的符号: *
处理状态: S -> E · true
完成状态处理
完成状态: S -> E · true
处理状态: E -> E · + T false
无法处理的符号: +
解析成功
解析成功: true
存在歧义: false