Перейти - Как создать парсер
Я хочу создать парсер, но у меня есть некоторые проблемы с пониманием того, как это сделать.
Пример строки, которую я хотел бы проанализировать
{key1 = value1 | key2 = {key3 = value3} | key4 = {key5 = { key6 = value6 }}}
Предпочтительно, я хотел бы получить результат, похожий на вложенную карту
map[key1] = value1
map[key2] = (map[key3] = value3)
map[key4] = (map[key5] = (map[key6] = value6))
Как это можно сделать? Я направляюсь в неправильном направлении?
Ответы
Ответ 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.
Подробнее см.: