golang高效扫描解析LL(1)语法的插件库prattle的使用

Golang高效扫描解析LL(1)语法的插件库Prattle的使用

概述

Prattle是一个通用的、支持Unicode的词法扫描器和自上而下的运算符优先级解析器,适用于解析LL(1)语法。扫描器和解析器可以独立使用。

安装

go get -u github.com/askeladdk/prattle

快速开始

使用Scanner扫描文本

使用Scanner通过ScanFunc扫描源文本生成一系列Token。使用Expect*SkipAdvance方法来扫描标记。

// 准备扫描器
scanner := prattle.Scanner{
    // Scan扫描下一个标记并返回其类型
    Scan: func(s *prattle.Scanner) (kind int) {
        // 跳过所有空白字符
        s.ExpectAny(unicode.IsSpace)
        s.Skip()

        // 扫描下一个标记
        switch {
        case s.Done(): // 当所有输入都被消耗时停止
            return 0
        case s.Expect('+'): // 扫描加法运算符
            return 1
        case s.ExpectOne(unicode.IsDigit): // 扫描由一个或多个数字组成的数字
            s.ExpectAny(unicode.IsDigit)
            return 2
        }

        // 无效标记
        s.Advance()
        return -1
    },
}

使用Parser解析语法

使用ParserDriverScanner生成的标记与ParseFunc关联。首先定义Driver

// 定义解析Driver
type driver struct {
    stack []int
}

func (d *driver) push(i int) {
    d.stack = append(d.stack, i)
}

func (d *driver) pop() (i int) {
    n := len(d.stack)
    i, d.stack = d.stack[n-1], d.stack[:n-1]
    return
}

func (d *driver) number(p *prattle.Parser, t prattle.Token) error {
    n, _ := strconv.Atoi(t.Text)
    d.push(n)
    return nil
}

func (d *driver) add(p *prattle.Parser, t prattle.Token) error {
    // 解析右侧运算符
    _ = p.Parse(d.Precedence(t.Kind))

    right := d.pop()
    left := d.pop()
    sum := left + right
    fmt.Printf("%d + %d = %d\n", left, right, sum)
    d.push(sum)
    return nil
}

func (d *driver) Prefix(kind int) prattle.ParseFunc {
    return d.number
}

func (d *driver) Infix(kind int) prattle.ParseFunc {
    return d.add
}

func (d *driver) Precedence(kind int) int {
    return kind
}

func (d *driver) ParseError(t prattle.Token) error {
    return fmt.Errorf("%s", t)
}

// 准备解析器
parser := prattle.Parser{
    Driver: &driver{},
}

初始化并解析表达式

source := "1 + 23 + 456 + 7890"
scanner.InitWithString(source)
parser.Init(&scanner).Parse(0)

// 输出:
// 1 + 23 = 24
// 24 + 456 = 480
// 480 + 7890 = 8370

完整示例

以下是一个完整的计算器示例,展示了如何使用Prattle解析简单的数学表达式:

package main

import (
    "fmt"
    "strconv"
    "unicode"

    "github.com/askeladdk/prattle"
)

func main() {
    // 定义扫描器
    scanner := prattle.Scanner{
        Scan: func(s *prattle.Scanner) (kind int) {
            // 跳过空白
            s.ExpectAny(unicode.IsSpace)
            s.Skip()

            // 扫描标记
            switch {
            case s.Done():
                return 0
            case s.Expect('+'):
                return 1
            case s.ExpectOne(unicode.IsDigit):
                s.ExpectAny(unicode.IsDigit)
                return 2
            }

            // 无效标记
            s.Advance()
            return -1
        },
    }

    // 定义解析Driver
    type driver struct {
        stack []int
    }

    func (d *driver) push(i int) {
        d.stack = append(d.stack, i)
    }

    func (d *driver) pop() (i int) {
        n := len(d.stack)
        i, d.stack = d.stack[n-1], d.stack[:n-1]
        return
    }

    func (d *driver) number(p *prattle.Parser, t prattle.Token) error {
        n, _ := strconv.Atoi(t.Text)
        d.push(n)
        return nil
    }

    func (d *driver) add(p *prattle.Parser, t prattle.Token) error {
        _ = p.Parse(d.Precedence(t.Kind))
        right := d.pop()
        left := d.pop()
        sum := left + right
        fmt.Printf("%d + %d = %d\n", left, right, sum)
        d.push(sum)
        return nil
    }

    func (d *driver) Prefix(kind int) prattle.ParseFunc {
        return d.number
    }

    func (d *driver) Infix(kind int) prattle.ParseFunc {
        return d.add
    }

    func (d *driver) Precedence(kind int) int {
        return kind
    }

    func (d *driver) ParseError(t prattle.Token) error {
        return fmt.Errorf("%s", t)
    }

    // 初始化并解析
    parser := prattle.Parser{
        Driver: &driver{},
    }

    source := "1 + 23 + 456 + 7890"
    scanner.InitWithString(source)
    if err := parser.Init(&scanner).Parse(0); err != nil {
        fmt.Println("Parse error:", err)
    }
}

这个示例展示了Prattle的基本用法,包括如何定义扫描器、解析驱动以及如何将它们组合起来解析简单的加法表达式。


更多关于golang高效扫描解析LL(1)语法的插件库prattle的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang高效扫描解析LL(1)语法的插件库prattle的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Prattle - Golang中的高效LL(1)语法解析库

Prattle是一个轻量级的Go语言库,专门用于解析LL(1)语法。它设计简洁高效,适合构建小型领域特定语言(DSL)或配置文件解析器。

核心特性

  1. 纯Go实现,无外部依赖
  2. 支持LL(1)语法解析
  3. 轻量级且高效
  4. 易于集成到现有项目中

基本使用示例

首先安装Prattle:

go get github.com/fasterthanlime/prattle

简单算术表达式解析

package main

import (
	"fmt"
	"strings"

	"github.com/fasterthanlime/prattle"
)

func main() {
	// 定义语法规则
	grammar := `
		Expr    = Term (('+' | '-') Term)*
		Term    = Factor (('*' | '/') Factor)*
		Factor  = Number | '(' Expr ')'
		Number  = [0-9]+
	`

	// 创建解析器
	parser, err := prattle.NewParser(strings.NewReader(grammar))
	if err != nil {
		panic(err)
	}

	// 解析输入
	input := "3 + 5 * (10 - 4)"
	ast, err := parser.ParseString(input)
	if err != nil {
		panic(err)
	}

	// 打印AST
	fmt.Printf("AST: %#v\n", ast)
}

自定义AST处理

package main

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/fasterthanlime/prattle"
)

type Node struct {
	Type     string
	Value    string
	Children []*Node
}

func parse(input string) (float64, error) {
	grammar := `
		Expr    = Term (('+' | '-') Term)*
		Term    = Factor (('*' | '/') Factor)*
		Factor  = Number | '(' Expr ')'
		Number  = [0-9]+ ('.' [0-9]+)?
	`

	parser, err := prattle.NewParser(strings.NewReader(grammar))
	if err != nil {
		return 0, err
	}

	ast, err := parser.ParseString(input)
	if err != nil {
		return 0, err
	}

	return eval(ast), nil
}

func eval(node *prattle.Node) float64 {
	switch node.Type {
	case "Number":
		val, _ := strconv.ParseFloat(node.Value, 64)
		return val
	case "Expr", "Term":
		result := eval(node.Children[0])
		for i := 1; i < len(node.Children); i += 2 {
			op := node.Children[i].Value
			rhs := eval(node.Children[i+1])
			switch op {
			case "+":
				result += rhs
			case "-":
				result -= rhs
			case "*":
				result *= rhs
			case "/":
				result /= rhs
			}
		}
		return result
	case "Factor":
		if node.Children[0].Value == "(" {
			return eval(node.Children[1])
		}
		return eval(node.Children[0])
	default:
		return 0
	}
}

func main() {
	result, err := parse("3.5 + 2 * (10 - 4) / 3")
	if err != nil {
		panic(err)
	}
	fmt.Printf("Result: %.2f\n", result) // 输出: Result: 7.50
}

高级用法

错误处理

func parseWithErrorHandling(input string) (*prattle.Node, error) {
	grammar := `
		Expr = Term (('+' | '-') Term)*
		Term = Factor (('*' | '/') Factor)*
		Factor = Number | '(' Expr ')'
		Number = [0-9]+
	`

	parser, err := prattle.NewParser(strings.NewReader(grammar))
	if err != nil {
		return nil, fmt.Errorf("语法定义错误: %v", err)
	}

	ast, err := parser.ParseString(input)
	if err != nil {
		// 获取更详细的错误信息
		if parseErr, ok := err.(*prattle.ParseError); ok {
			return nil, fmt.Errorf("解析错误 (位置 %d): %v", parseErr.Pos, parseErr.Message)
		}
		return nil, fmt.Errorf("解析错误: %v", err)
	}

	return ast, nil
}

自定义词法分析

Prattle默认提供基本的词法分析功能,但也可以自定义:

func customLexer(input string) []prattle.Token {
	// 实现自定义的词法分析逻辑
	// 返回Token切片
	return nil
}

func main() {
	grammar := `...`
	parser, _ := prattle.NewParser(strings.NewReader(grammar))
	
	input := "custom input"
	tokens := customLexer(input)
	
	ast, err := parser.Parse(tokens)
	if err != nil {
		panic(err)
	}
	
	// 处理AST
}

性能优化技巧

  1. 重用Parser实例:创建Parser实例有一定开销,可以重用
  2. 预编译语法:如果语法不变,可以预编译并保存Parser
  3. 简化语法:LL(1)语法越简单,解析越快
var (
	grammar = `...`
	parser  *prattle.Parser
	once    sync.Once
)

func getParser() (*prattle.Parser, error) {
	var err error
	once.Do(func() {
		parser, err = prattle.NewParser(strings.NewReader(grammar))
	})
	return parser, err
}

限制

  1. 仅支持LL(1)语法,不适合更复杂的语法结构
  2. 错误报告相对基础
  3. 文档和社区支持不如更成熟的解析器生成器

对于更复杂的需求,可以考虑Antlr或Yacc等工具,但对于简单的LL(1)语法解析,Prattle是一个轻量高效的解决方案。

回到顶部