Earley 算法解析器教程

121 阅读7分钟

概述

Earley 算法是一种用于解析上下文无关文法(Context-Free Grammar, CFG)的算法。它能够处理左递归、右递归以及歧义文法,并且具有较好的时间复杂度(O(n^3))。本教程将详细介绍如何实现一个 Earley 解析器,并解释其核心逻辑。

1. Earley 算法的基本概念

Earley 算法的核心思想是通过动态规划的方式,逐步构建解析过程中的状态集合(chart)。每个状态表示一个产生式规则在某个位置的解析进度。Earley 算法通过三个主要操作来处理这些状态:

  1. 预测(Predict) :当遇到一个非终结符时,预测所有可能的产生式规则。
  2. 扫描(Scan) :当遇到一个终结符时,尝试匹配输入中的符号。
  3. 完成(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