something has started working
parent
c3ecc23b7c
commit
4482a76feb
@ -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
|
||||||
|
}
|
@ -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))
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue