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