Подтвердить что ты не робот

Перейти - Как создать парсер

Я хочу создать парсер, но у меня есть некоторые проблемы с пониманием того, как это сделать.

Пример строки, которую я хотел бы проанализировать

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

Предпочтительно, я хотел бы получить результат, похожий на вложенную карту

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

Как это можно сделать? Я направляюсь в неправильном направлении?

4b9b3361

Ответ 1

Написание парсера - сложная тема, которая слишком велика для покрытия в одном ответе.

Роб Пайк дал прекрасную беседу, которая просматривает написанный лексер (который является половиной парсера) в 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.html, http://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]]
$ 

Ответ 3

Сообщаем, что с Go 1.8 (в настоящее время в бета-версии Q4 2016, выпущенной в Q1 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

Вы хотите попробовать parsec для golang edition? Я пишу руну (для unicode) fork goparsec (https://github.com/sanyaade-buildtools/goparsec) что https://github.com/Dwarfartisan/goparsec.

Haskell parsec - это инструмент для синтаксического анализатора. Первый синтаксический анализатор perl6 по имени pugs был написан им. Моя версия 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

OUTPUT

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

В эту демонстрацию входят карта побега, токена, строка и ключ/значение. Вы можете создать парсер в виде пакета или приложения.

Ответ 6

Если вы хотите преобразовать свой вход в стандартный формат 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.

Подробнее см.: