From 4482a76febe2d8ee06401e21602c29675ca8037d Mon Sep 17 00:00:00 2001 From: Jordan Orelli Date: Fri, 20 Mar 2015 12:48:37 -0400 Subject: [PATCH] something has started working --- config.go | 32 ++++++++++++ lex.go | 17 +++++++ lex_test.go | 6 +++ parse.go | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++ parse_test.go | 111 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 300 insertions(+) create mode 100644 config.go create mode 100644 parse.go create mode 100644 parse_test.go diff --git a/config.go b/config.go new file mode 100644 index 0000000..e0138f6 --- /dev/null +++ b/config.go @@ -0,0 +1,32 @@ +package main + +import ( + "fmt" +) + +type Config struct { + items map[string]interface{} +} + +func (c *Config) hasKey(key string) bool { + if c.items == nil { + return false + } + _, ok := c.items[key] + return ok +} + +func (c *Config) setUnique(key string, value interface{}) error { + if c.hasKey(key) { + return fmt.Errorf("the name %s is already defined in this scope", key) + } + c.set(key, value) + return nil +} + +func (c *Config) set(key string, value interface{}) { + if c.items == nil { + c.items = make(map[string]interface{}, 12) + } + c.items[key] = value +} diff --git a/lex.go b/lex.go index 4b200a9..01b9d0f 100644 --- a/lex.go +++ b/lex.go @@ -10,6 +10,23 @@ import ( type tokenType int +func (t tokenType) String() string { + switch t { + case t_error: + return "t_error" + case t_string: + return "t_string" + case t_name: + return "t_name" + case t_type: + return "t_type" + case t_equals: + return "t_equals" + default: + panic(fmt.Sprintf("unknown token type: %v", t)) + } +} + const ( t_error tokenType = iota // a stored lex error t_string // a string literal diff --git a/lex_test.go b/lex_test.go index d515634..f891b1e 100644 --- a/lex_test.go +++ b/lex_test.go @@ -24,6 +24,12 @@ var primitivesTests = []struct { {`Type_1_2`, []token{{t_type, "Type_1_2"}}}, {`=`, []token{{t_equals, "="}}}, {` = `, []token{{t_equals, "="}}}, + {`"x" "y"`, []token{{t_string, "x"}, {t_string, "y"}}}, + {`x = "sam"`, []token{ + {t_name, "x"}, + {t_equals, "="}, + {t_string, "sam"}, + }}, } func TestLexPrimities(t *testing.T) { diff --git a/parse.go b/parse.go new file mode 100644 index 0000000..a34e850 --- /dev/null +++ b/parse.go @@ -0,0 +1,134 @@ +package main + +import ( + "fmt" + "io" +) + +const ( + e_no_error parseErrorType = iota + e_lex_error + e_unexpected_eof + e_unexpected_token +) + +type parseErrorType int + +type parseError struct { + t parseErrorType + m string +} + +func (p parseError) Error() string { + return fmt.Sprintf("parse error: %s", p.m) +} + +func parseErrorf(t parseErrorType, tpl string, args ...interface{}) error { + return parseError{t: t, m: fmt.Sprintf(tpl, args...)} +} + +type parseFn func(*parser) (parseFn, error) + +func parseRoot(p *parser) (parseFn, error) { + if err := p.next(); err != nil { + return nil, err + } + switch p.cur.t { + case t_name: + return parseAfterName(p.cur.s), nil + default: + return nil, parseErrorf(e_unexpected_token, "unexpected %s token in parseRoot", p.cur.t) + } +} + +func parseAfterName(name string) parseFn { + return func(p *parser) (parseFn, error) { + switch err := p.next(); err { + case io.EOF: + return nil, parseErrorf(e_unexpected_eof, "unexpected eof after name %s", name) + case nil: + default: + return nil, err + } + + switch p.cur.t { + case t_equals: + return parseAssign(name), nil + default: + return nil, parseErrorf(e_unexpected_token, "unexpected %s token in parseAfterName", p.cur.t) + } + } +} + +func parseAssign(name string) parseFn { + return func(p *parser) (parseFn, error) { + switch err := p.next(); err { + case io.EOF: + return nil, parseErrorf(e_unexpected_eof, "unexpected eof when trying to parse value for name %s", name) + case nil: + default: + return nil, err + } + + switch p.cur.t { + case t_string: + p.out.setUnique(name, p.cur.s) + return parseRoot, nil + default: + return nil, parseErrorf(e_unexpected_token, "unexpected %s token in parseAssign", p.cur.t) + } + } +} + +type parser struct { + in chan token + cur token + out *Config +} + +func (p *parser) next() error { + t, ok := <-p.in + if !ok { + return io.EOF + } + if t.t == t_error { + return parseError{e_lex_error, t.s} + } + p.cur = t + return nil +} + +func (p *parser) run() error { + fn := parseRoot + var err error + for { + fn, err = fn(p) + switch err { + case io.EOF: + return nil + case nil: + default: + return err + } + } +} + +type assignment struct { + name string + value interface{} +} + +func parse(in chan token) (*Config, error) { + p := &parser{ + in: in, + out: new(Config), + } + if err := p.run(); err != nil { + return nil, err + } + return p.out, nil +} + +func parseString(in string) (*Config, error) { + return parse(lexString(in)) +} diff --git a/parse_test.go b/parse_test.go new file mode 100644 index 0000000..53688e1 --- /dev/null +++ b/parse_test.go @@ -0,0 +1,111 @@ +package main + +import ( + "testing" +) + +// a boolean statement about a config struct +type configPredicate func(*Config) bool + +// a suite of tests for parsing potm input +type parseTest struct { + in string + desc string + configTests []configTest + errorType parseErrorType +} + +func (p *parseTest) run(t *testing.T) { + c, err := parseString(p.in) + if err != nil { + t.Logf("test %s has error %v", p.desc, err) + e, ok := err.(parseError) + if !ok { + t.Errorf("unexpected error: %s", e) + return + } + if p.errorType == e.t { + t.Logf("OK: got expected error type %v for %s", e.t, p.desc) + } else { + t.Errorf("unexpected parse error: %s", e) + return + } + } + t.Logf("parsed config for %s", p.desc) + t.Log(c) + p.runConfigTests(t, c) +} + +func (p *parseTest) runConfigTests(t *testing.T, c *Config) { + ok := true + for _, test := range p.configTests { + if test.pass(c) { + t.Logf("OK: %s", test.desc) + } else { + t.Errorf("config predicate failed: %s", test.desc) + ok = false + } + } + if ok { + t.Logf("OK: %s", p.desc) + } +} + +// an individual test for confirming that a parsed config struct meets some +// predicate +type configTest struct { + desc string + pass configPredicate +} + +var parseTests = []parseTest{ + { + in: ``, + desc: "an empty string is a valid config", + configTests: []configTest{ + { + desc: "undefined name field should not exist", + pass: inv(hasKey("name")), + }, + }, + }, + { + in: `name `, + desc: "a name alone is not a valid config", + errorType: e_unexpected_eof, + }, + { + in: `name = `, + desc: "dangling assignment", + errorType: e_unexpected_eof, + }, + { + in: `name = "jordan"`, + desc: "assign a value", + configTests: []configTest{ + { + desc: "should have name", + pass: hasKey("name"), + }, + }, + }, +} + +// inverts a given config predicate +func inv(fn configPredicate) configPredicate { + return func(c *Config) bool { + return !fn(c) + } +} + +func hasKey(s string) configPredicate { + return func(c *Config) bool { + return c.hasKey(s) + } +} + +func TestParse(t *testing.T) { + for _, test := range parseTests { + test.run(t) + } +}