something has started working

master
Jordan Orelli 10 years ago
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
}

@ -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

@ -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) {

@ -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…
Cancel
Save