golang用Go编写的Go语言编译器工具包插件库gocc的使用

Golang用Go编写的Go语言编译器工具包插件库gocc的使用

简介

Gocc是一个用Go编写的Go编译器工具包。Gocc可以从BNF生成词法分析器和语法分析器,或者生成独立的DFA或语法分析器。

  • 词法分析器是DFA,可以识别正则语言。Gocc词法分析器接受UTF-8输入
  • Gocc语法分析器是PDA,可以识别LR-1语言。可选的LR1冲突处理可以自动解决shift/reduce和reduce/reduce冲突

安装

  1. 首先从Go官网下载并安装Go
  2. 设置GOPATH环境变量
  3. 在命令行运行:go get github.com/goccmack/gocc (go get会克隆gocc到GOPATH/src/github.com/goccmack/gocc并运行go install)
  4. 或者克隆源码:git clone https://github.com/goccmack/gocc,然后运行go install github.com/goccmack/gocc
  5. 最后确保gocc二进制文件所在的bin目录在你的PATH环境变量中

快速开始

安装完成后,首先在包文件夹中创建你的BNF文件。

例如GOPATH/src/foo/bar.bnf:

/* 词法部分 */

id : 'a'-'z' {'a'-'z'} ;

!whitespace : ' ' | '\t' | '\n' | '\r' ;

/* 语法部分 */

<< import "foo/ast" >>

Hello:  "hello" id << ast.NewWorld($1) >> ;

然后使用gocc运行:

cd $GOPATH/src/foo
gocc bar.bnf

这将在GOPATH/src/foo中生成扫描器、解析器和token包。

接下来在$GOPATH/src/foo/ast创建ast.go文件:

package ast

import (
    "foo/token"
)

type Attrib interface {}

type World struct {
    Name string
}

func NewWorld(id Attrib) (*World, error) {
    return &World{string(id.(*token.Token).Lit)}, nil
}

func (this *World) String() string {
    return "hello " + this.Name
}

最后,我们编写一个测试文件$GOPATH/src/foo/test/parse_test.go:

package test

import (
    "foo/ast"
    "foo/lexer"
    "foo/parser"
    "testing"
)

func TestWorld(t *testing.T) {
    input := []byte(`hello gocc`)
    lex := lexer.NewLexer(input)
    p := parser.NewParser()
    st, err := p.Parse(lex)
    if err != nil {
        panic(err)
    }
    w, ok := st.(*ast.World)
    if !ok {
        t.Fatalf("This is not a world")
    }
    if w.Name != `gocc` {
        t.Fatalf("Wrong world %v", w.Name)
    }
}

运行测试:

cd $GOPATH/src/foo/test
go test -v

BNF

Gocc BNF规范可以参考示例。

动作表达式和AST

动作表达式格式为<< goccExpressionList >>。goccExpressionList相当于goExpressionList。这个表达式列表应该返回一个Attrib和一个error,其中Attrib是:

type Attrib interface {}

解析后的BNF规则元素可以在expressionList中表示为$+数字。

一些动作表达式示例:

<< $0, nil >>
<< ast.NewFoo($1) >>
<< ast.NewBar($3, $1) >>
<< ast.TRUE, nil >>

一些函数示例:

func NewFoo(a Attrib) (*Foo, error) { ... }
func NewBar(a, b Attrib) (*Bar, error) { ... }

示例项目

这些项目使用了gocc:

  • gogo - Go到MIPS的编译器
  • gonum/gonum - DOT解码器
  • llir/llvm - LLVM IR库
  • mewmew/uc - µC语言编译器
  • gographviz - Graphviz DOT语言解析器
  • katydid - 编码无关的验证语言
  • skius/stringlang - StringLang语言的解释器
  • miller - 类似awk、sed、cut的工具
  • nesgo - NES的Go编译器

更多关于golang用Go编写的Go语言编译器工具包插件库gocc的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang用Go编写的Go语言编译器工具包插件库gocc的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


gocc - Go语言编译器工具包插件库使用指南

gocc 是一个用 Go 编写的 Go 语言编译器工具包插件库,它提供了一套完整的工具来构建编译器前端,包括词法分析器和语法分析器生成器。下面我将详细介绍 gocc 的使用方法。

安装 gocc

首先需要安装 gocc 工具:

go get github.com/goccmack/gocc

安装完成后,gocc 命令将被添加到您的 $GOPATH/bin 目录中。

基本概念

gocc 主要由以下几个部分组成:

  1. 词法分析器 (Lexer) - 将输入文本转换为标记(token)序列
  2. 语法分析器 (Parser) - 根据语法规则分析标记序列
  3. 抽象语法树 (AST) - 表示源代码结构的树状数据结构

创建语法定义文件

gocc 使用 .bnf 文件定义语法规则。例如,创建一个简单的计算器语法 calc.bnf

/* 词法定义 */
!whitespace : ' ' | '\t' | '\n' | '\r' ;
!comment : '#' {.} '\n' ;

digit : '0'-'9' ;
number : digit { digit } ;

/* 语法定义 */
<< 
import (
    "github.com/goccmack/gocc/example/calc/ast"
)
>>

Calc : Expr;

Expr :
      Expr "+" Term << ast.NewPlus($0, $2) >>
    | Term
;

Term :
      Term "*" Factor << ast.NewMult($0, $2) >>
    | Factor
;

Factor :
      "(" Expr ")" << $1 >>
    | number      << ast.NewNum($0) >>
;

生成解析器

使用 gocc 命令生成解析器:

gocc calc.bnf

这将生成以下目录结构:

calc/
├── ast/        # 抽象语法树定义
├── lexer/      # 词法分析器
├── parser/     # 语法分析器
├── token/      # 标记定义
└── util/       # 工具函数

实现 AST 节点

ast/ast.go 中定义 AST 节点:

package ast

type Expr interface {
    isExpr()
}

type (
    Plus struct {
        Left  Expr
        Right Expr
    }
    
    Mult struct {
        Left  Expr
        Right Expr
    }
    
    Num struct {
        Value string
    }
)

func (Plus) isExpr() {}
func (Mult) isExpr() {}
func (Num) isExpr() {}

func NewPlus(left, right interface{}) Expr {
    return Plus{left.(Expr), right.(Expr)}
}

func NewMult(left, right interface{}) Expr {
    return Mult{left.(Expr), right.(Expr)}
}

func NewNum(num interface{}) Expr {
    return Num{string(num.(*token.Token).Lit)}
}

使用生成的解析器

创建一个 main.go 来使用生成的解析器:

package main

import (
    "fmt"
    "os"
    
    "github.com/goccmack/gocc/example/calc/lexer"
    "github.com/goccmack/gocc/example/calc/parser"
)

func main() {
    if len(os.Args) < 2 {
        fmt.Println("Usage: calc 'expression'")
        return
    }
    
    input := []byte(os.Args[1])
    lex := lexer.NewLexer(input)
    p := parser.NewParser()
    
    ast, err := p.Parse(lex)
    if err != nil {
        fmt.Println("Parse error:", err)
        return
    }
    
    fmt.Printf("AST: %#v\n", ast)
}

高级特性

错误处理

gocc 提供了详细的错误报告功能,可以定位语法错误的位置。

自定义词法分析

可以通过实现 Lexer 接口来自定义词法分析行为。

语法冲突解决

gocc 支持通过优先级和结合性规则解决语法冲突:

%left '+';
%left '*';

实际应用示例

下面是一个更完整的计算器实现,包含求值功能:

// ast/eval.go
package ast

func (p Plus) Eval() int {
    return p.Left.Eval() + p.Right.Eval()
}

func (m Mult) Eval() int {
    return m.Left.Eval() * m.Right.Eval()
}

func (n Num) Eval() int {
    var val int
    fmt.Sscan(n.Value, &val)
    return val
}

// main.go
func main() {
    // ... 前面的解析代码
    
    result := ast.(ast.Expr).Eval()
    fmt.Printf("Result: %d\n", result)
}

总结

gocc 是一个功能强大且灵活的编译器工具包,特别适合需要自定义领域特定语言(DSL)的场景。它的主要优点包括:

  1. 纯 Go 实现,易于集成到 Go 项目中
  2. 生成的代码可读性强
  3. 支持复杂的语法规则
  4. 提供详细的错误报告

通过定义合适的语法规则和 AST 结构,您可以使用 gocc 构建各种语言的解析器,从简单的配置文件解析器到复杂的编程语言前端。

希望这个指南能帮助您开始使用 gocc。如需更复杂的功能,可以参考 gocc 的官方文档和示例代码。

回到顶部