From 04d982c3d935ce3a5a386085fb198a95ab5bb248 Mon Sep 17 00:00:00 2001 From: Jordan Orelli Date: Sun, 22 Mar 2015 08:39:13 -0400 Subject: [PATCH] parse an object --- lex.go | 41 +++++++++++++++++++++++++++++++---------- lex_test.go | 12 ++++++++++++ node.go | 2 ++ parse.go | 39 +++++++++++++++++++++++++++++++++++++++ parse_test.go | 30 ++++++++++++++++++++++++++++++ 5 files changed, 114 insertions(+), 10 deletions(-) diff --git a/lex.go b/lex.go index 3f47c2e..111df2f 100644 --- a/lex.go +++ b/lex.go @@ -33,22 +33,31 @@ func (t tokenType) String() string { return "t_list_end" case t_list_separator: return "t_list_separator" + case t_object_start: + return "t_object_start" + case t_object_separator: + return "t_object_separator" + case t_object_end: + return "t_object_end" default: panic(fmt.Sprintf("unknown token type: %v", t)) } } const ( - t_error tokenType = iota // a stored lex error - t_eof // end of file token - t_string // a string literal - t_name // a name - t_type // a type - t_equals // equals sign - t_comment // a comment - t_list_start // [ - t_list_end // ] - t_list_separator // , + t_error tokenType = iota // a stored lex error + t_eof // end of file token + t_string // a string literal + t_name // a name + t_type // a type + t_equals // equals sign + t_comment // a comment + t_list_start // [ + t_list_end // ] + t_list_separator // , + t_object_start // { + t_object_end // } + t_object_separator // : ) type stateFn func(*lexer) (stateFn, error) @@ -161,6 +170,18 @@ func lexRoot(l *lexer) (stateFn, error) { l.keep(r) l.emit(t_list_separator) return lexRoot, nil + case r == '{': + l.keep(r) + l.emit(t_object_start) + return lexRoot, nil + case r == '}': + l.keep(r) + l.emit(t_object_end) + return lexRoot, nil + case r == ':': + l.keep(r) + l.emit(t_object_separator) + return lexRoot, nil case unicode.IsSpace(r): return lexRoot, nil case unicode.IsLower(r): diff --git a/lex_test.go b/lex_test.go index db669a5..2a0426b 100644 --- a/lex_test.go +++ b/lex_test.go @@ -37,6 +37,18 @@ var primitivesTests = []struct { `, []token{{t_comment, " comment line one"}, {t_comment, " comment line two"}}}, {`[]`, []token{{t_list_start, "["}, {t_list_end, "]"}}}, {`["item"]`, []token{{t_list_start, "["}, {t_string, "item"}, {t_list_end, "]"}}}, + {`{}`, []token{{t_object_start, "{"}, {t_object_end, "}"}}}, + {`{first_name: "jordan", last_name: "orelli"}`, []token{ + {t_object_start, "{"}, + {t_name, "first_name"}, + {t_object_separator, ":"}, + {t_string, "jordan"}, + {t_list_separator, ","}, + {t_name, "last_name"}, + {t_object_separator, ":"}, + {t_string, "orelli"}, + {t_object_end, "}"}, + }}, } func TestLexPrimities(t *testing.T) { diff --git a/node.go b/node.go index cf3b4a1..370c6c1 100644 --- a/node.go +++ b/node.go @@ -157,3 +157,5 @@ type listElem struct { prev *listElem next *listElem } + +type object map[string]interface{} diff --git a/parse.go b/parse.go index 4b6f0e8..5e0a693 100644 --- a/parse.go +++ b/parse.go @@ -55,6 +55,13 @@ func (p *parser) unread(t token) { p.backup = append(p.backup, t) } +func (p *parser) ensureNext(tt tokenType, context string) error { + if p.peek().t != tt { + return fmt.Errorf("unexpected %v in %s: expected %v", p.peek().t, context, tt) + } + return nil +} + // parse the next value. This is to be executed in a context where we know we // want something that is a value to come next, such as after an equals sign. func (p *parser) parseValue() (interface{}, error) { @@ -69,6 +76,8 @@ func (p *parser) parseValue() (interface{}, error) { return t.s, nil case t_list_start: return p.parseList(new(list)) + case t_object_start: + return p.parseObject(make(object)) default: return nil, fmt.Errorf("parse error: unexpected %v token while looking for value", t.t) } @@ -96,3 +105,33 @@ func (p *parser) parseList(l *list) (*list, error) { return nil, fmt.Errorf("parse error: unexpected %v token while scanning for list", t.t) } } + +func (p *parser) parseObject(obj object) (object, error) { + if p.peek().t == t_object_end { + p.next() + return obj, nil + } + if err := p.ensureNext(t_name, "looking for object field name in parseObject"); err != nil { + return nil, err + } + field_name := p.next().s + if err := p.ensureNext(t_object_separator, "looking for object separator in parseObject"); err != nil { + return nil, err + } + p.next() + + if v, err := p.parseValue(); err != nil { + return nil, err + } else { + obj[field_name] = v + } + + switch t := p.next(); t.t { + case t_list_separator: + return p.parseObject(obj) + case t_object_end: + return obj, nil + default: + return nil, fmt.Errorf("parse error: unexpected %v token while scanning for object", t.t) + } +} diff --git a/parse_test.go b/parse_test.go index ef123e8..51567f4 100644 --- a/parse_test.go +++ b/parse_test.go @@ -59,6 +59,36 @@ var parseTests = []parseTest{ `, root: &rootNode{}, }, + { + source: ` + nested = [["one", "two"], ["three", "four"]] + `, + root: &rootNode{}, + }, + { + source: ` + nested = [ + ["one", "two"], + ["three", "four"], + ] + `, + root: &rootNode{}, + }, + { + source: ` + admin = {first_name: "jordan", last_name: "orelli"} + `, + root: &rootNode{}, + }, + { + source: ` + http = { + port: "9000", + routes: "/path/to/some/file", + } + `, + root: &rootNode{}, + }, } type parseTest struct {