Browse Source

Merge pull request #24 from brettlangdon/dev/lists.sqwished

Add support for lists
pull/26/head v0.1.5
Brett Langdon 11 years ago
parent
commit
4c620b835a
10 changed files with 283 additions and 14 deletions
  1. +2
    -0
      README.md
  2. +13
    -2
      forge.go
  3. +17
    -0
      forge_test.go
  4. +153
    -0
      list.go
  5. +58
    -10
      parser.go
  6. +8
    -2
      scanner.go
  7. +15
    -0
      section.go
  8. +8
    -0
      test.cfg
  9. +6
    -0
      token/token.go
  10. +3
    -0
      value.go

+ 2
- 0
README.md View File

@ -35,6 +35,8 @@ primary {
negative = FALSE
nothing = NULL
list = [50.5, true, false, "hello", 'world'];
# Include external files
include "./include*.cfg";
# Primary-sub section


+ 13
- 2
forge.go View File

@ -14,6 +14,13 @@
// secondary_bool = true;
// secondary_null = null;
//
// list = [
// "any",
// 'value',
// true,
// 55.5,
// ]
//
// # Reference other config value
// local_ref = .secondary_null;
// global_ref = primary.sub_section.sub_float;
@ -36,9 +43,10 @@
// STRING: ['"] .* ['"]
// REFERENCE: (IDENTIFIER)? ('.' IDENTIFIER)+
// VALUE: BOOL | NULL | INTEGER | FLOAT | STRING | REFERENCE
// LIST: '[' (VALUE | LIST) (',' NEWLINE* (VALUE | LIST))+ ']'
//
// INCLUDE: 'include ' STRING END
// DIRECTIVE: (IDENTIFIER '=' VALUE | INCLUDE) END
// DIRECTIVE: (IDENTIFIER '=' (VALUE | LIST) | INCLUDE) END
// SECTION: IDENTIFIER '{' (DIRECTIVE | SECTION)* '}'
// COMMENT: '#' .* '\n'
//
@ -57,6 +65,9 @@
// The identifiers 'true' or 'false' of any case (e.g. TRUE, True, true, FALSE, False, false)
// * Null:
// The identifier 'null' of any case (e.g. NULL, Null, null)
// * List:
// A list value is any number of other values separated by commas and surrounded by brackets.
// (e.g. [50.5, 'some', "string", true, false])
// * Global reference:
// An identifier which may contain periods, the references are resolved from the global
// section (e.g. global_value, section.sub_section.value)
@ -74,7 +85,7 @@
// All directives must end in either a semicolon or newline. The value can be any of the types defined above.
// * Section:
// A section is a grouping of directives under a common name. They are in the format '<section_name> { <directives> }'.
// All sections must be wrapped in brackets ('{', '}') and must all have a name. They do not end in a semicolon.
// All sections must be wrapped in braces ('{', '}') and must all have a name. They do not end in a semicolon.
// Sections may be left empty, they do not have to contain any directives.
// * Include:
// An include statement tells the config parser to include the contents of another config file where the include


+ 17
- 0
forge_test.go View File

@ -26,6 +26,14 @@ primary {
not_true = FALSE
nothing = NULL
list = [
TRUE,
FALSE,
50.5,
"hello",
'list',
]
# Reference secondary._under (which hasn't been defined yet)
sec_ref = secondary._under;
# Primary-sub stuff
@ -71,6 +79,15 @@ func assertDirectives(values map[string]interface{}, t *testing.T) {
assertEqual(primary["nothing"], nil, t)
assertEqual(primary["sec_ref"], int64(50), t)
// Primary list
list := primary["list"].([]interface{})
assertEqual(len(list), 5, t)
assertEqual(list[0], true, t)
assertEqual(list[1], false, t)
assertEqual(list[2], float64(50.5), t)
assertEqual(list[3], "hello", t)
assertEqual(list[4], "list", t)
// Primary Sub
sub := primary["sub"].(map[string]interface{})
assertEqual(sub["key"], "primary sub key value", t)


+ 153
- 0
list.go View File

@ -0,0 +1,153 @@
package forge
import (
"errors"
"fmt"
)
// List struct used for holding data neede for Reference data type
type List struct {
values []Value
}
// NewList will create and initialize a new List value
func NewList() *List {
return &List{
values: make([]Value, 0),
}
}
// GetType will simply return back LIST
func (list *List) GetType() ValueType {
return LIST
}
// GetValue will resolve and return the value from the underlying list
// this is necessary to inherit from Value
func (list *List) GetValue() interface{} {
var values []interface{}
for _, val := range list.values {
values = append(values, val.GetValue())
}
return values
}
// GetValues will return back the list of underlygin values
func (list *List) GetValues() []Value {
return list.values
}
// UpdateValue will set the underlying list value
func (list *List) UpdateValue(value interface{}) error {
// Valid types
switch value.(type) {
case []Value:
list.values = value.([]Value)
default:
msg := fmt.Sprintf("Unsupported type, %s must be of type []Value", value)
return errors.New(msg)
}
return nil
}
// Get will return the Value at the index
func (list *List) Get(idx int) (Value, error) {
if idx > list.Length() {
return nil, errors.New("index out of range")
}
return list.values[idx], nil
}
// GetBoolean will try to get the value stored at the index as a bool
// will respond with an error if the value does not exist or cannot be converted to a bool
func (list *List) GetBoolean(idx int) (bool, error) {
value, err := list.Get(idx)
if err != nil {
return false, err
}
switch value.(type) {
case *Primative:
return value.(*Primative).AsBoolean()
}
return false, errors.New("could not convert unknown value to boolean")
}
// GetFloat will try to get the value stored at the index as a float64
// will respond with an error if the value does not exist or cannot be converted to a float64
func (list *List) GetFloat(idx int) (float64, error) {
value, err := list.Get(idx)
if err != nil {
return float64(0), err
}
switch value.(type) {
case *Primative:
return value.(*Primative).AsFloat()
}
return float64(0), errors.New("could not convert non-primative value to float")
}
// GetInteger will try to get the value stored at the index as a int64
// will respond with an error if the value does not exist or cannot be converted to a int64
func (list *List) GetInteger(idx int) (int64, error) {
value, err := list.Get(idx)
if err != nil {
return int64(0), err
}
switch value.(type) {
case *Primative:
return value.(*Primative).AsInteger()
}
return int64(0), errors.New("could not convert non-primative value to integer")
}
// GetList will try to get the value stored at the index as a List
// will respond with an error if the value does not exist or is not a List
func (list *List) GetList(idx int) (*List, error) {
value, err := list.Get(idx)
if err != nil {
return nil, err
}
if value.GetType() == LIST {
return value.(*List), nil
}
return nil, errors.New("could not fetch value as list")
}
// GetString will try to get the value stored at the index as a string
// will respond with an error if the value does not exist or cannot be converted to a string
func (list *List) GetString(idx int) (string, error) {
value, err := list.Get(idx)
if err != nil {
return "", err
}
switch value.(type) {
case *Primative:
return value.(*Primative).AsString()
}
return "", errors.New("could not convert non-primative value to string")
}
// Set will set the new Value at the index
func (list *List) Set(idx int, value Value) {
list.values[idx] = value
}
// Append will append a new Value on the end of the internal list
func (list *List) Append(value Value) {
list.values = append(list.values, value)
}
// Length will return back the total number of items in the list
func (list *List) Length() int {
return len(list.values)
}

+ 58
- 10
parser.go View File

@ -77,6 +77,38 @@ func (parser *Parser) readToken() token.Token {
return parser.curTok
}
func (parser *Parser) skipNewlines() {
for parser.curTok.ID == token.NEWLINE {
parser.readToken()
}
}
func (parser *Parser) parseList() ([]Value, error) {
var values []Value
for {
parser.skipNewlines()
value, err := parser.parseSettingValue()
if err != nil {
return nil, err
}
values = append(values, value)
if parser.curTok.ID == token.COMMA {
parser.readToken()
}
parser.skipNewlines()
if parser.curTok.ID == token.RBRACKET {
parser.readToken()
break
}
}
return values, nil
}
func (parser *Parser) parseReference(startingSection *Section, period bool) (Value, error) {
name := ""
if period == false {
@ -112,9 +144,8 @@ func (parser *Parser) parseReference(startingSection *Section, period bool) (Val
return NewReference(name, startingSection), nil
}
func (parser *Parser) parseSetting(name string) error {
func (parser *Parser) parseSettingValue() (Value, error) {
var value Value
parser.readToken()
readNext := true
switch parser.curTok.ID {
@ -123,7 +154,7 @@ func (parser *Parser) parseSetting(name string) error {
case token.BOOLEAN:
boolVal, err := strconv.ParseBool(parser.curTok.Literal)
if err != nil {
return nil
return value, nil
}
value = NewBoolean(boolVal)
case token.NULL:
@ -131,31 +162,39 @@ func (parser *Parser) parseSetting(name string) error {
case token.INTEGER:
intVal, err := strconv.ParseInt(parser.curTok.Literal, 10, 64)
if err != nil {
return err
return value, err
}
value = NewInteger(intVal)
case token.FLOAT:
floatVal, err := strconv.ParseFloat(parser.curTok.Literal, 64)
if err != nil {
return err
return value, err
}
value = NewFloat(floatVal)
case token.PERIOD:
reference, err := parser.parseReference(parser.curSection, true)
if err != nil {
return err
return value, err
}
value = reference
readNext = false
case token.IDENTIFIER:
reference, err := parser.parseReference(parser.settings, false)
if err != nil {
return err
return value, err
}
value = reference
readNext = false
case token.LBRACKET:
parser.readToken()
listVal, err := parser.parseList()
if err != nil {
return value, err
}
value = NewList()
value.UpdateValue(listVal)
default:
return parser.syntaxError(
return value, parser.syntaxError(
fmt.Sprintf("expected STRING, INTEGER, FLOAT, BOOLEAN or IDENTIFIER, instead found %s", parser.curTok.ID),
)
}
@ -163,6 +202,15 @@ func (parser *Parser) parseSetting(name string) error {
if readNext {
parser.readToken()
}
return value, nil
}
func (parser *Parser) parseSetting(name string) error {
parser.readToken()
value, err := parser.parseSettingValue()
if err != nil {
return err
}
if isSemicolonOrNewline(parser.curTok.ID) == false {
msg := fmt.Sprintf("expected ';' or '\n' instead found '%s'", parser.curTok.Literal)
return parser.syntaxError(msg)
@ -247,7 +295,7 @@ func (parser *Parser) parse() error {
case token.INCLUDE:
parser.parseInclude()
case token.IDENTIFIER:
if parser.curTok.ID == token.LBRACKET {
if parser.curTok.ID == token.LBRACE {
err := parser.parseSection(tok.Literal)
if err != nil {
return err
@ -259,7 +307,7 @@ func (parser *Parser) parse() error {
return err
}
}
case token.RBRACKET:
case token.RBRACE:
err := parser.endSection()
if err != nil {
return err


+ 8
- 2
scanner.go View File

@ -207,14 +207,20 @@ func (scanner *Scanner) NextToken() token.Token {
scanner.readRune()
scanner.curTok.Literal = string(ch)
switch ch {
case ',':
scanner.curTok.ID = token.COMMA
case '=':
scanner.curTok.ID = token.EQUAL
case '"', '\'':
scanner.parseString(ch)
case '{':
case '[':
scanner.curTok.ID = token.LBRACKET
case '}':
case ']':
scanner.curTok.ID = token.RBRACKET
case '{':
scanner.curTok.ID = token.LBRACE
case '}':
scanner.curTok.ID = token.RBRACE
case ';':
scanner.curTok.ID = token.SEMICOLON
case '\n':


+ 15
- 0
section.go View File

@ -150,6 +150,21 @@ func (section *Section) GetInteger(name string) (int64, error) {
return int64(0), errors.New("could not convert non-primative value to integer")
}
// GetList will try to get the value stored under name as a List
// will respond with an error if the value does not exist or is not a List
func (section *Section) GetList(name string) (*List, error) {
value, err := section.Get(name)
if err != nil {
return nil, err
}
if value.GetType() == LIST {
return value.(*List), nil
}
return nil, errors.New("could not fetch value as list")
}
// GetSection will try to get the value stored under name as a Section
// will respond with an error if the value does not exist or is not a Section
func (section *Section) GetSection(name string) (*Section, error) {


+ 8
- 0
test.cfg View File

@ -15,6 +15,14 @@ primary {
not_true = FALSE
nothing = NULL
list = [
TRUE,
FALSE,
50.5,
"hello",
'list',
]
# Reference secondary._under (which hasn't been defined yet)
sec_ref = secondary._under;
# Primary-sub stuff


+ 6
- 0
token/token.go View File

@ -22,11 +22,14 @@ const (
ILLEGAL TokenID = iota
EOF
LBRACE
RBRACE
LBRACKET
RBRACKET
EQUAL
SEMICOLON
NEWLINE
COMMA
PERIOD
IDENTIFIER
@ -42,11 +45,14 @@ const (
var tokenNames = [...]string{
ILLEGAL: "ILLEGAL",
EOF: "EOF",
LBRACE: "LBRACE",
RBRACE: "RBRACE",
LBRACKET: "LBRACKET",
RBRACKET: "RBRACKET",
EQUAL: "EQUAL",
SEMICOLON: "SEMICOLON",
NEWLINE: "NEWLINE",
COMMA: "COMMA",
PERIOD: "PERIOD",
IDENTIFIER: "IDENTIFIER",
BOOLEAN: "BOOLEAN",


+ 3
- 0
value.go View File

@ -21,6 +21,8 @@ const (
primativesDnd
complexStart
// LIST ValueType
LIST
// REFERENCE ValueType
REFERENCE
// SECTION ValueType
@ -35,6 +37,7 @@ var valueTypes = [...]string{
NULL: "NULL",
STRING: "STRING",
LIST: "LIST",
REFERENCE: "REFERENCE",
SECTION: "SECTION",
}


Loading…
Cancel
Save