【问题标题】:How to create a parser如何创建解析器
【发布时间】:2012-01-15 08:49:12
【问题描述】:

我想构建一个解析器,但在理解如何做到这一点时遇到了一些问题。

我要解析的示例字符串

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

我希望得到类似于嵌套地图的输出

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

如何做到这一点?我的目标是不是错了?

【问题讨论】:

标签: go


【解决方案1】:

编写解析器是一个复杂的话题,一个答案无法涵盖。

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)。

【讨论】:

    【解决方案2】:

    使用标准的 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]]
    $ 
    

    【讨论】:

    • 当前第一个命令是:$go tool yacc -o main.go main.y
    【解决方案3】:

    请注意,with Go 1.8(目前在 2016 年第四季度处于测试阶段,于 2017 年第一季度发布)

    yacc 工具(之前可通过运行“go tool yacc”获得)已被删除
    从 Go 1.7 开始,Go 编译器不再使用它。

    它已移至“tools”存储库,现在可在golang.org/x/tools/cmd/goyacc 获得。

    【讨论】:

      【解决方案4】:

      该特定格式与 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)
      

      【讨论】:

      • 感谢大家的回答。我还没有时间研究它,但很快就会有更新。再次感谢!
      【解决方案5】:

      如果您愿意将输入转换为标准 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 对象。

      详情见:

      【讨论】:

        【解决方案6】:

        你想尝试解析 golang 版本吗?我写了一个 goparsec(https://github.com/sanyaade-buildtools/goparsec) 的符文(用于 unicode)fork 什么是 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)
            }
        }
        

        运行

        go run parser.go
        

        输出

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

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

        【讨论】:

        • 这是haskells parsec的“端口”吗?那真的很酷\o/。不知道 Go 存在 parsec。它提供大致相同的功能还是只是 Parsec 的一小部分?
        • 是的。嗯...等我回来。今年我遇到了一些问题,比如抑郁症、新工作等。让我们……我写了一个改进版的 goparsec,名为 goparsec2。 github.com/Dwarfartisan/goparsec2 。 Goparsec2 更干净,更快速。但是我还没有写文档,测试显示如何使用它。请接受我对文件缺席的抱歉。我相信我会写的。
        • Go parsec 是对 haskell parsec 的模拟。它支持一些 parsec 组件和状态定义。所以可以使用它创建规则解析器,如github.com/Dwarfartisan/gisp2
        猜你喜欢
        • 2022-01-16
        • 2011-04-18
        • 2015-11-15
        • 2018-12-02
        • 2023-03-16
        • 1970-01-01
        • 2021-11-24
        • 2021-10-19
        • 2020-07-03
        相关资源
        最近更新 更多