Go - 如何创建解析器

时间:2011-12-07 20:34:39

标签: go

我想构建一个解析器,但在理解如何执行此操作时遇到一些问题。

我要解析的示例字符串

{key1 = value1 | key2 = {key3 = value3} | key4 = {key5 = { key6 = value6 }}}

最好我想获得类似于嵌套地图的输出

map[key1] = value1
map[key2] = (map[key3] = value3)
map[key4] = (map[key5] = (map[key6] = value6))

怎么可以这样做?我的目标是错误的吗?

5 个答案:

答案 0 :(得分:34)

编写解析器是一个复杂的主题,太大而无法覆盖在单个答案中。

Rob Pike发表了一篇精彩的演讲,在Go中写了一个词法分析器(这是解析器的一半):http://www.youtube.com/watch?v=HxaD_trXwRE

你也应该看看,例如Go标准库中的解析器代码,以获取有关如何执行此操作的示例:http://golang.org/src/pkg/go/parser/parser.go

在互联网上解析也有很多资源。他们可能在其他语言中有示例,但这只是将语法转换为Go。

我建议您阅读递归下降解析(例如http://www.cs.binghamton.edu/~zdu/parsdemo/recintro.html)或自上而下解析(例如http://javascript.crockford.com/tdop/tdop.htmlhttp://effbot.org/zone/simple-top-down-parsing.htm)。

答案 1 :(得分:31)

使用标准goyacc工具怎么样?这是一个骨架:

main.y

%{
package main

import (
    "fmt"
    "log"
)
%}

%union{
    tok int
    val interface{}
    pair struct{key, val interface{}}
    pairs map[interface{}]interface{}
}

%token KEY
%token VAL

%type <val> KEY VAL
%type <pair> pair
%type <pairs> pairs

%%

goal:
    '{' pairs '}'
    {
        yylex.(*lex).m = $2
    }

pairs:
    pair
    {
        $$ = map[interface{}]interface{}{$1.key: $1.val}
    }
|   pairs '|' pair
    {
        $$[$3.key] = $3.val
    }

pair:
    KEY '=' VAL
    {
        $$.key, $$.val = $1, $3
    }
|   KEY '=' '{' pairs '}'
    {
        $$.key, $$.val = $1, $4
    }


%%

type token struct {
    tok int
    val interface{}
}

type lex struct {
    tokens []token
    m map[interface{}]interface{}
}

func (l *lex) Lex(lval *yySymType) int {
    if len(l.tokens) == 0 {
        return 0
    }

    v := l.tokens[0]
    l.tokens = l.tokens[1:]
    lval.val = v.val
    return v.tok
}

func (l *lex) Error(e string) {
    log.Fatal(e)
}

func main() {
    l := &lex{
        // {key1 = value1 | key2 = {key3 = value3} | key4 = {key5 = { key6 = value6 }}}
        []token{
            {'{', ""},
            {KEY, "key1"},
            {'=', ""},
            {VAL, "value1"},
            {'|', ""},
            {KEY, "key2"},
            {'=', ""}, 
            {'{', ""},
            {KEY, "key3"},
            {'=', ""},
            {VAL, "value3"},
            {'}', ""},
            {'|', ""},
            {KEY, "key4"},
            {'=', ""},
            {'{', ""},
            {KEY, "key5"},
            {'=', ""},
            {'{', ""},
            {KEY, "key6"},
            {'=', ""},
            {VAL, "value6"},
            {'}', ""},
            {'}', ""},
            {'}', ""},
        },
        map[interface{}]interface{}{},
    }
    yyParse(l)
    fmt.Println(l.m)
}

输出

$ go tool yacc -o main.go main.y && go run main.go
map[key4:map[key5:map[key6:value6]] key1:value1 key2:map[key3:value3]]
$ 

答案 2 :(得分:2)

这种特殊格式与json非常相似。您可以使用以下代码来利用这种相似性:

    var txt = `{key1 = "\"value1\"\n" | key2 = { key3 = 10 } | key4 = {key5 = { key6 = value6}}}`
    var s scanner.Scanner
    s.Init(strings.NewReader(txt))
    var b []byte

loop:
    for {
        switch tok := s.Scan(); tok {
        case scanner.EOF:
            break loop
        case '|':
            b = append(b, ',')
        case '=':
            b = append(b, ':')
        case scanner.Ident:
            b = append(b, strconv.Quote(s.TokenText())...)
        default:
            b = append(b, s.TokenText()...)
        }
    }

    var m map[string]interface{}
    err := json.Unmarshal(b, &m)
    if err != nil {
        // handle error
    }

    fmt.Printf("%#v\n",m)

答案 3 :(得分:1)

你想尝试使用golang版的parsec吗?我写了一个符文(对于unicode)goparsec(https://github.com/sanyaade-buildtools/goparsec)的分支是什么https://github.com/Dwarfartisan/goparsec

Haskell parsec是make解析器的强大工具。名为pugs的第一个perl6解析器是由它编写的。我的golang版并不比yacc简单,但它比yacc更容易。

对于这个例子,我编写了代码:

parser.go

package main

import (
    "fmt"
    psc "github.com/Dwarfartisan/goparsec"
)

type kv struct {
    key   string
    value interface{}
}

var tchar = psc.NoneOf("|{}= ")

func escaped(st psc.ParseState) (interface{}, error) {
    _, err := psc.Try(psc.Rune('\\'))(st)
    if err == nil {
        r, err := psc.AnyRune(st)
        if err == nil {
            switch r.(rune) {
            case 't':
                return '\t', nil
            case '"':
                return '"', nil
            case 'n':
                return '\n', nil
            case '\\':
                return '\\', nil
            default:
                return nil, st.Trap("Unknown escape \\%r", r)
            }
        } else {
            return nil, err
        }
    } else {
        return psc.NoneOf("\"")(st)
    }
}

var token = psc.Either(
    psc.Between(psc.Rune('"'), psc.Rune('"'),
        psc.Try(psc.Bind(psc.Many1(escaped), psc.ReturnString))),
    psc.Bind(psc.Many1(tchar), psc.ReturnString))

// rune with skip spaces
func syms(r rune) psc.Parser {
    return func(st psc.ParseState) (interface{}, error) {
        _, err := psc.Bind_(psc.Bind_(psc.Many(psc.Space), psc.Rune(r)), psc.Many(psc.Space))(st)
        if err == nil {
            return r, nil
        } else {
            return nil, err
        }
    }
}

var lbracket = syms('{')
var rbracket = syms('}')
var eql = syms('=')
var vbar = syms('|')

func pair(st psc.ParseState) (interface{}, error) {
    left, err := token(st)
    if err != nil {
        return nil, err
    }

    right, err := psc.Bind_(eql, psc.Either(psc.Try(token), mapExpr))(st)
    if err != nil {
        return nil, err
    }
    return kv{left.(string), right}, nil
}
func pairs(st psc.ParseState) (interface{}, error) {
    return psc.SepBy1(pair, vbar)(st)
}
func mapExpr(st psc.ParseState) (interface{}, error) {
    p, err := psc.Try(psc.Between(lbracket, rbracket, pair))(st)
    if err == nil {
        return p, nil
    }
    ps, err := psc.Between(lbracket, rbracket, pairs)(st)
    if err == nil {
        return ps, nil
    } else {
        return nil, err
    }
}

func makeMap(data interface{}) interface{} {
    ret := make(map[string]interface{})
    switch val := data.(type) {
    case kv:
        ret[val.key] = makeMap(val.value)
    case string:
        return data
    case []interface{}:
        for _, item := range val {
            it := item.(kv)
            ret[it.key] = makeMap(it.value)
        }
    }
    return ret
}

func main() {
    input := `{key1 = "\"value1\"\n" | key2 = { key3 = 10 } | key4 = {key5 = { key6 = value6}}}`
    st := psc.MemoryParseState(input)
    ret, err := mapExpr(makeMap(st))
    if err == nil {
        fmt.Println(ret)
    } else {
        fmt.Println(err)
    }
}

RUN

go run parser.go

输出

map[key1:"value1"
  key2:map[key3:10] key4:map[key5:map[key6:value6]]]

此演示包括转义,令牌,字符串和键/值映射。您可以将解析器创建为包或应用程序。

答案 4 :(得分:1)

如果您愿意将输入转换为标准JSON格式,为什么在Go库为您做繁重的工作时创建解析器呢?

给出以下输入文件(/Users/lex/dev/go/data/jsoncfgo/fritjof.json):

输入文件

{
   "key1": "value1",
   "key2" :  {
      "key3": "value3"
   },
   "key4": {
      "key5": {
         "key6": "value6"
      }
   }
}

代码示例

package main

import (
    "fmt"
    "log"
    "github.com/l3x/jsoncfgo"
)


func main() {

    configPath := "/Users/lex/dev/go/data/jsoncfgo/fritjof.json"
    cfg, err := jsoncfgo.ReadFile(configPath)
    if err != nil {
        log.Fatal(err.Error())  // Handle error here
    }

    key1 := cfg.RequiredString("key1")
    fmt.Printf("key1: %v\n\n", key1)

    key2 := cfg.OptionalObject("key2")
    fmt.Printf("key2: %v\n\n", key2)

    key4 := cfg.OptionalObject("key4")
    fmt.Printf("key4: %v\n\n", key4)

    if err := cfg.Validate(); err != nil {
        defer log.Fatalf("ERROR - Invalid config file...\n%v", err)
        return
    }
}

<强>输出

key1: value1

key2: map[key3:value3]

key4: map[key5:map[key6:value6]]

备注

jsoncfgo可以处理任何级别的嵌套JSON对象。

详见: