From 39119211518d853afb710f5c6786d3955ea98872 Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Sat, 20 Jun 2015 10:55:57 -0400 Subject: [PATCH] huge refactor I am a fool and should have found a better way to break this into many commits shame... shame on the poor choices I have made --- config/boolean.go | 9 -- config/config.go | 39 ----- config/float.go | 9 -- config/integer.go | 9 -- config/null.go | 9 -- config/section.go | 132 ----------------- config/string.go | 9 -- conversions.go | 88 +++++++++++ example/main.go | 15 +- example_test.go | 4 +- forge.go | 34 +++-- parser/parser.go => parser.go | 122 +++++++--------- primative.go | 89 +++++++++++ token/tokenizer.go => scanner.go | 62 ++++---- section.go | 244 +++++++++++++++++++++++++++++++ token/token.go | 53 +++++++ token/tokenid.go | 54 ------- value.go | 48 ++++++ 18 files changed, 634 insertions(+), 395 deletions(-) delete mode 100644 config/boolean.go delete mode 100644 config/config.go delete mode 100644 config/float.go delete mode 100644 config/integer.go delete mode 100644 config/null.go delete mode 100644 config/section.go delete mode 100644 config/string.go create mode 100644 conversions.go rename parser/parser.go => parser.go (66%) create mode 100644 primative.go rename token/tokenizer.go => scanner.go (72%) create mode 100644 section.go delete mode 100644 token/tokenid.go create mode 100644 value.go diff --git a/config/boolean.go b/config/boolean.go deleted file mode 100644 index 35882c1..0000000 --- a/config/boolean.go +++ /dev/null @@ -1,9 +0,0 @@ -package config - -type BooleanValue struct { - Name string - Value bool -} - -func (this BooleanValue) GetType() ConfigType { return BOOLEAN } -func (this BooleanValue) GetValue() interface{} { return this.Value } diff --git a/config/config.go b/config/config.go deleted file mode 100644 index e5fdc3e..0000000 --- a/config/config.go +++ /dev/null @@ -1,39 +0,0 @@ -package config - -type ConfigType int - -const ( - SECTION ConfigType = iota - INTEGER - BOOLEAN - FLOAT - STRING - NULL -) - -var configTypes = [...]string{ - SECTION: "SECTION", - BOOLEAN: "BOOLEAN", - INTEGER: "INTEGER", - FLOAT: "FLOAT", - STRING: "STRING", - NULL: "NULL", -} - -func (this ConfigType) String() string { - s := "" - if 0 <= this && this < ConfigType(len(configTypes)) { - s = configTypes[this] - } - - if s == "" { - s = "UNKNOWN" - } - - return s -} - -type ConfigValue interface { - GetType() ConfigType - GetValue() interface{} -} diff --git a/config/float.go b/config/float.go deleted file mode 100644 index df29db5..0000000 --- a/config/float.go +++ /dev/null @@ -1,9 +0,0 @@ -package config - -type FloatValue struct { - Name string - Value float64 -} - -func (this FloatValue) GetType() ConfigType { return INTEGER } -func (this FloatValue) GetValue() interface{} { return this.Value } diff --git a/config/integer.go b/config/integer.go deleted file mode 100644 index 1ea1eec..0000000 --- a/config/integer.go +++ /dev/null @@ -1,9 +0,0 @@ -package config - -type IntegerValue struct { - Name string - Value int64 -} - -func (this IntegerValue) GetType() ConfigType { return INTEGER } -func (this IntegerValue) GetValue() interface{} { return this.Value } diff --git a/config/null.go b/config/null.go deleted file mode 100644 index fa665c1..0000000 --- a/config/null.go +++ /dev/null @@ -1,9 +0,0 @@ -package config - -type NullValue struct { - Name string - Value interface{} -} - -func (this NullValue) GetType() ConfigType { return NULL } -func (this NullValue) GetValue() interface{} { return nil } diff --git a/config/section.go b/config/section.go deleted file mode 100644 index 640ffd0..0000000 --- a/config/section.go +++ /dev/null @@ -1,132 +0,0 @@ -package config - -import ( - "encoding/json" - "errors" - "fmt" - "strings" -) - -type SectionValue struct { - Name string - Value map[string]ConfigValue - Comments []string - Includes []string -} - -func NewNamedSection(name string) *SectionValue { - return &SectionValue{ - Name: name, - Value: make(map[string]ConfigValue), - Comments: make([]string, 0), - Includes: make([]string, 0), - } -} - -func NewAnonymousSection() *SectionValue { - return &SectionValue{ - Value: make(map[string]ConfigValue), - Comments: make([]string, 0), - Includes: make([]string, 0), - } -} - -func (this SectionValue) GetType() ConfigType { return SECTION } -func (this SectionValue) GetValue() interface{} { return this.Value } - -func (this *SectionValue) AddComment(comment string) { - this.Comments = append(this.Comments, comment) -} - -func (this *SectionValue) AddInclude(include string) { - this.Includes = append(this.Includes, include) -} - -func (this *SectionValue) Set(name string, value ConfigValue) { - this.Value[name] = value -} - -func (this *SectionValue) Get(name string) ConfigValue { - return this.Value[name] -} - -func (this *SectionValue) GetSection(name string) SectionValue { - value := this.Value[name] - return value.(SectionValue) -} - -func (this *SectionValue) GetString(name string) StringValue { - value := this.Value[name] - return value.(StringValue) -} - -func (this *SectionValue) GetInteger(name string) IntegerValue { - value := this.Value[name] - return value.(IntegerValue) -} - -func (this *SectionValue) GetFloat(name string) FloatValue { - value := this.Value[name] - return value.(FloatValue) -} - -func (this *SectionValue) Contains(name string) bool { - _, ok := this.Value[name] - return ok -} - -func (this *SectionValue) Resolve(setting string) (ConfigValue, error) { - parts := strings.Split(setting, ".") - var reference ConfigValue - reference = this - visited := []string{} - for { - if len(parts) == 0 { - break - } - if reference.GetType() != SECTION { - name := strings.Join(visited, ".") - return nil, errors.New(fmt.Sprintf("'%s' is a %s not a SECTION", name, reference.GetType())) - } - part := parts[0] - parts = parts[1:] - section := reference.(*SectionValue) - if section.Contains(part) == false { - name := strings.Join(visited, ".") - if len(name) > 0 { - return nil, errors.New(fmt.Sprintf("'%s' does not have setting '%s'", name, part)) - } else { - return nil, errors.New(fmt.Sprintf("setting '%s' does not exist", part)) - } - } - reference = section.Get(part) - visited = append(visited, part) - } - - return reference, nil -} - -func (this *SectionValue) ToJSON() ([]byte, error) { - data, err := this.ToMap() - if err != nil { - return nil, err - } - return json.Marshal(data) -} - -func (this *SectionValue) ToMap() (map[string]interface{}, error) { - settings := make(map[string]interface{}) - for name, value := range this.Value { - if value.GetType() == SECTION { - data, err := value.(*SectionValue).ToMap() - if err != nil { - return nil, err - } - settings[name] = data - } else { - settings[name] = value.GetValue() - } - } - - return settings, nil -} diff --git a/config/string.go b/config/string.go deleted file mode 100644 index 1fe8c5d..0000000 --- a/config/string.go +++ /dev/null @@ -1,9 +0,0 @@ -package config - -type StringValue struct { - Name string - Value string -} - -func (this StringValue) GetType() ConfigType { return STRING } -func (this StringValue) GetValue() interface{} { return this.Value } diff --git a/conversions.go b/conversions.go new file mode 100644 index 0000000..60725d4 --- /dev/null +++ b/conversions.go @@ -0,0 +1,88 @@ +package forge + +import ( + "errors" + "fmt" + "math" + "strconv" +) + +func asBoolean(value interface{}) (bool, error) { + switch val := value.(type) { + case bool: + return val, nil + case float64: + return val != 0, nil + case int64: + return val != 0, nil + case nil: + return false, nil + case string: + return val != "", nil + } + + msg := fmt.Sprintf("Could not convert value %s to type BOOLEAN", value) + return false, errors.New(msg) +} + +func asFloat(value interface{}) (float64, error) { + switch val := value.(type) { + case bool: + if val { + return float64(1), nil + } else { + return float64(0), nil + } + case float64: + return val, nil + case int64: + return float64(val), nil + case string: + return strconv.ParseFloat(val, 64) + } + + msg := fmt.Sprintf("Could not convert value %s to type FLOAT", value) + return 0, errors.New(msg) +} + +func asInteger(value interface{}) (int64, error) { + switch val := value.(type) { + case bool: + if val { + return int64(1), nil + } else { + return int64(0), nil + } + case float64: + return int64(math.Trunc(val)), nil + case int64: + return val, nil + case string: + return strconv.ParseInt(val, 10, 64) + } + + msg := fmt.Sprintf("Could not convert value %s to type INTEGER", value) + return 0, errors.New(msg) +} + +func asString(value interface{}) (string, error) { + switch val := value.(type) { + case bool: + if val { + return "True", nil + } else { + return "False", nil + } + case float64: + return strconv.FormatFloat(val, 10, -1, 64), nil + case int64: + return strconv.FormatInt(val, 10), nil + case nil: + return "Null", nil + case string: + return val, nil + } + + msg := fmt.Sprintf("Could not convert value %s to type STRING", value) + return "", errors.New(msg) +} diff --git a/example/main.go b/example/main.go index 4a3ef7f..3b90a36 100644 --- a/example/main.go +++ b/example/main.go @@ -13,19 +13,18 @@ func main() { panic(err) } - // Get a single value - if settings.Contains("global") { - // Get `global` casted as `StringValue` - value := settings.GetString("global") - fmt.Printf("global = \"%s\"\r\n", value.GetValue()) + str_val, err := settings.GetString("global") + if err != nil { + panic(err) } + fmt.Printf("global = \"%s\"\r\n", str_val) // Get a nested value - value, err := settings.Resolve("primary.included_setting") - fmt.Printf("primary.included_setting = \"%s\"\r\n", value.GetValue()) + // value, err := settings.Resolve("primary.included_setting") + // fmt.Printf("primary.included_setting = \"%s\"\r\n", value.GetValue()) // Convert settings to a map - settingsMap, err := settings.ToMap() + settingsMap := settings.ToMap() fmt.Printf("global = \"%s\"\r\n", settingsMap["global"]) // Convert settings to JSON diff --git a/example_test.go b/example_test.go index 3f631fb..4393a76 100644 --- a/example_test.go +++ b/example_test.go @@ -15,7 +15,7 @@ func Example() { } // Get a single value - if settings.Contains("global") { + if settings.Exists("global") { // Get `global` casted as `StringValue` value := settings.GetString("global") fmt.Printf("global = \"%s\"\r\n", value.GetValue()) @@ -28,7 +28,7 @@ func Example() { // You can also traverse down the sections manually primary, err := settings.GetSection("primary") value, err := primary.GetString("included_setting") - fmt.Printf("primary.included_setting = \"%s\"\r\n", value.GetValue()) + fmt.Printf("primary.included_setting = \"%s\"\r\n", value) // Convert settings to a map settingsMap, err := settings.ToMap() diff --git a/forge.go b/forge.go index 4342774..bcc9770 100644 --- a/forge.go +++ b/forge.go @@ -83,28 +83,32 @@ package forge import ( "bytes" "io" + "os" "strings" - - "github.com/brettlangdon/forge/config" - "github.com/brettlangdon/forge/parser" ) -// ParseString will parse a forge SectionValue from a string -func ParseString(data string) (*config.SectionValue, error) { - return parser.ParseReader(strings.NewReader(data)) +func ParseBytes(data []byte) (*Section, error) { + return ParseReader(bytes.NewReader(data)) } -// ParseBytes will parse a forge SectionValue from a byte array -func ParseBytes(data []byte) (*config.SectionValue, error) { - return parser.ParseReader(bytes.NewReader(data)) +func ParseFile(filename string) (*Section, error) { + reader, err := os.Open(filename) + if err != nil { + return nil, err + } + return ParseReader(reader) } -// ParseFile will parse a forge SectionValue from a filename -func ParseFile(filename string) (*config.SectionValue, error) { - return parser.ParseFile(filename) +func ParseReader(reader io.Reader) (*Section, error) { + parser := NewParser(reader) + err := parser.Parse() + if err != nil { + return nil, err + } + + return parser.GetSettings(), nil } -// ParseReader will parse a forge SectionValue from a io.Reader -func ParseReader(reader io.Reader) (*config.SectionValue, error) { - return parser.ParseReader(reader) +func ParseString(data string) (*Section, error) { + return ParseReader(strings.NewReader(data)) } diff --git a/parser/parser.go b/parser.go similarity index 66% rename from parser/parser.go rename to parser.go index 9a0c2bd..1b17baa 100644 --- a/parser/parser.go +++ b/parser.go @@ -1,4 +1,4 @@ -package parser +package forge import ( "errors" @@ -8,19 +8,28 @@ import ( "path/filepath" "strconv" - "github.com/brettlangdon/forge/config" "github.com/brettlangdon/forge/token" ) type Parser struct { - settings *config.SectionValue - tokenizer *token.Tokenizer + settings *Section + scanner *Scanner cur_tok token.Token - cur_section *config.SectionValue - previous []*config.SectionValue + cur_section *Section + previous []*Section } -func (this *Parser) SyntaxError(msg string) error { +func NewParser(reader io.Reader) *Parser { + settings := NewSection() + return &Parser{ + scanner: NewScanner(reader), + settings: settings, + cur_section: settings, + previous: make([]*Section, 0), + } +} + +func (this *Parser) syntaxError(msg string) error { msg = fmt.Sprintf( "Syntax error line <%d> column <%d>: %s", this.cur_tok.Line, @@ -31,11 +40,11 @@ func (this *Parser) SyntaxError(msg string) error { } func (this *Parser) readToken() token.Token { - this.cur_tok = this.tokenizer.NextToken() + this.cur_tok = this.scanner.NextToken() return this.cur_tok } -func (this *Parser) parseReference(starting_section *config.SectionValue, period bool) (config.ConfigValue, error) { +func (this *Parser) parseReference(starting_section *Section, period bool) (Value, error) { name := "" if period == false { name = this.cur_tok.Literal @@ -54,69 +63,54 @@ func (this *Parser) parseReference(starting_section *config.SectionValue, period break } else { msg := fmt.Sprintf("expected ';' instead found '%s'", this.cur_tok.Literal) - return nil, this.SyntaxError(msg) + return nil, this.syntaxError(msg) } } if len(name) == 0 { - return nil, this.SyntaxError( + return nil, this.syntaxError( fmt.Sprintf("expected IDENTIFIER instead found %s", this.cur_tok.Literal), ) } if period { - return nil, this.SyntaxError(fmt.Sprintf("expected IDENTIFIER after PERIOD")) + return nil, this.syntaxError(fmt.Sprintf("expected IDENTIFIER after PERIOD")) } value, err := starting_section.Resolve(name) if err != nil { err = errors.New("Reference error, " + err.Error()) } - return value, err + return value, nil } func (this *Parser) parseSetting(name string) error { - var value config.ConfigValue + var value Value this.readToken() read_next := true switch this.cur_tok.ID { case token.STRING: - value = config.StringValue{ - Name: name, - Value: this.cur_tok.Literal, - } + value = NewString(this.cur_tok.Literal) case token.BOOLEAN: bool_val, err := strconv.ParseBool(this.cur_tok.Literal) if err != nil { return nil } - value = config.BooleanValue{ - Name: name, - Value: bool_val, - } + value = NewBoolean(bool_val) case token.NULL: - value = config.NullValue{ - Name: name, - Value: nil, - } + value = NewNull() case token.INTEGER: int_val, err := strconv.ParseInt(this.cur_tok.Literal, 10, 64) if err != nil { return err } - value = config.IntegerValue{ - Name: name, - Value: int_val, - } + value = NewInteger(int_val) case token.FLOAT: float_val, err := strconv.ParseFloat(this.cur_tok.Literal, 64) if err != nil { return err } - value = config.FloatValue{ - Name: name, - Value: float_val, - } + value = NewFloat(float_val) case token.PERIOD: reference, err := this.parseReference(this.cur_section, true) if err != nil { @@ -132,7 +126,7 @@ func (this *Parser) parseSetting(name string) error { value = reference read_next = false default: - return this.SyntaxError( + return this.syntaxError( fmt.Sprintf("expected STRING, INTEGER, FLOAT, BOOLEAN or IDENTIFIER, instead found %s", this.cur_tok.ID), ) } @@ -142,7 +136,7 @@ func (this *Parser) parseSetting(name string) error { } if this.cur_tok.ID != token.SEMICOLON { msg := fmt.Sprintf("expected ';' instead found '%s'", this.cur_tok.Literal) - return this.SyntaxError(msg) + return this.syntaxError(msg) } this.readToken() @@ -153,38 +147,37 @@ func (this *Parser) parseSetting(name string) error { func (this *Parser) parseInclude() error { if this.cur_tok.ID != token.STRING { msg := fmt.Sprintf("expected STRING instead found '%s'", this.cur_tok.ID) - return this.SyntaxError(msg) + return this.syntaxError(msg) } pattern := this.cur_tok.Literal this.readToken() if this.cur_tok.ID != token.SEMICOLON { msg := fmt.Sprintf("expected ';' instead found '%s'", this.cur_tok.Literal) - return this.SyntaxError(msg) + return this.syntaxError(msg) } filenames, err := filepath.Glob(pattern) if err != nil { return err } - old_tokenizer := this.tokenizer + old_scanner := this.scanner for _, filename := range filenames { reader, err := os.Open(filename) if err != nil { return err } - this.cur_section.AddInclude(filename) - this.tokenizer = token.NewTokenizer(reader) - this.Parse() + // this.cur_section.AddInclude(filename) + this.scanner = NewScanner(reader) + this.parse() } - this.tokenizer = old_tokenizer + this.scanner = old_scanner this.readToken() return nil } func (this *Parser) parseSection(name string) error { - section := config.NewNamedSection(name) - this.cur_section.Set(name, section) + section := this.cur_section.AddSection(name) this.previous = append(this.previous, this.cur_section) this.cur_section = section return nil @@ -192,7 +185,7 @@ func (this *Parser) parseSection(name string) error { func (this *Parser) endSection() error { if len(this.previous) == 0 { - return this.SyntaxError("unexpected section end '}'") + return this.syntaxError("unexpected section end '}'") } p_len := len(this.previous) @@ -202,7 +195,11 @@ func (this *Parser) endSection() error { return nil } -func (this *Parser) Parse() error { +func (this *Parser) GetSettings() *Section { + return this.settings +} + +func (this *Parser) parse() error { this.readToken() for { if this.cur_tok.ID == token.EOF { @@ -212,7 +209,7 @@ func (this *Parser) Parse() error { this.readToken() switch tok.ID { case token.COMMENT: - this.cur_section.AddComment(tok.Literal) + // this.cur_section.AddComment(tok.Literal) case token.INCLUDE: this.parseInclude() case token.IDENTIFIER: @@ -234,36 +231,21 @@ func (this *Parser) Parse() error { return err } default: - return this.SyntaxError(fmt.Sprintf("unexpected token %s", tok)) + return this.syntaxError(fmt.Sprintf("unexpected token %s", tok)) } } return nil } -func ParseFile(filename string) (settings *config.SectionValue, err error) { - reader, err := os.Open(filename) - if err != nil { - return settings, err - } - return ParseReader(reader) -} - -func ParseReader(reader io.Reader) (*config.SectionValue, error) { - settings := config.NewAnonymousSection() - parser := &Parser{ - tokenizer: token.NewTokenizer(reader), - settings: settings, - cur_section: settings, - previous: make([]*config.SectionValue, 0), - } - err := parser.Parse() +func (this *Parser) Parse() error { + err := this.parse() if err != nil { - return nil, err + return err } - if len(parser.previous) > 0 { - return nil, parser.SyntaxError("expected end of section, instead found EOF") + if len(this.previous) > 0 { + return this.syntaxError("expected end of section, instead found EOF") } - return settings, nil + return nil } diff --git a/primative.go b/primative.go new file mode 100644 index 0000000..0018aa6 --- /dev/null +++ b/primative.go @@ -0,0 +1,89 @@ +package forge + +import ( + "errors" + "fmt" +) + +type Primative struct { + valueType ValueType + value interface{} +} + +func NewPrimative(valueType ValueType, value interface{}) *Primative { + return &Primative{ + valueType: valueType, + value: value, + } +} + +func NewBoolean(value bool) *Primative { + return NewPrimative(BOOLEAN, value) +} + +func NewFloat(value float64) *Primative { + return NewPrimative(FLOAT, value) +} + +func NewInteger(value int64) *Primative { + return NewPrimative(INTEGER, value) +} + +func NewNull() *Primative { + return NewPrimative(NULL, nil) +} + +func NewString(value string) *Primative { + return NewPrimative(STRING, value) +} + +func (this *Primative) GetType() ValueType { + return this.valueType +} + +func (this *Primative) GetValue() interface{} { + return this.value +} + +func (this *Primative) UpdateValue(value interface{}) error { + // Valid types + switch value.(type) { + case bool: + this.valueType = BOOLEAN + case float64: + this.valueType = FLOAT + case int64: + this.valueType = INTEGER + case nil: + this.valueType = NULL + case string: + this.valueType = STRING + default: + msg := fmt.Sprintf("Unsupported type, %s must be of (bool, float64, int64, nil, string)", value) + return errors.New(msg) + + } + this.value = value + return nil +} + +func (this *Primative) AsBoolean() (bool, error) { + return asBoolean(this.value) +} + +func (this *Primative) AsFloat() (float64, error) { + return asFloat(this.value) +} + +func (this *Primative) AsInteger() (int64, error) { + return asInteger(this.value) +} + +func (this *Primative) AsString() (string, error) { + return asString(this.value) +} + +func (this *Primative) String() string { + str, _ := this.AsString() + return str +} diff --git a/token/tokenizer.go b/scanner.go similarity index 72% rename from token/tokenizer.go rename to scanner.go index e6f5405..b65ce50 100644 --- a/token/tokenizer.go +++ b/scanner.go @@ -1,9 +1,11 @@ -package token +package forge import ( "bufio" "io" "strings" + + "github.com/brettlangdon/forge/token" ) var eof = rune(0) @@ -33,27 +35,27 @@ func isInclude(str string) bool { return strings.ToLower(str) == "include" } -type Tokenizer struct { +type Scanner struct { cur_line int cur_col int - cur_tok Token + cur_tok token.Token cur_ch rune newline bool reader *bufio.Reader } -func NewTokenizer(reader io.Reader) *Tokenizer { - tokenizer := &Tokenizer{ +func NewScanner(reader io.Reader) *Scanner { + scanner := &Scanner{ reader: bufio.NewReader(reader), cur_line: 0, cur_col: 0, newline: false, } - tokenizer.readRune() - return tokenizer + scanner.readRune() + return scanner } -func (this *Tokenizer) readRune() { +func (this *Scanner) readRune() { if this.newline { this.cur_line += 1 this.cur_col = 0 @@ -75,8 +77,8 @@ func (this *Tokenizer) readRune() { } } -func (this *Tokenizer) parseIdentifier() { - this.cur_tok.ID = IDENTIFIER +func (this *Scanner) parseIdentifier() { + this.cur_tok.ID = token.IDENTIFIER this.cur_tok.Literal = string(this.cur_ch) for { this.readRune() @@ -87,22 +89,22 @@ func (this *Tokenizer) parseIdentifier() { } if isBoolean(this.cur_tok.Literal) { - this.cur_tok.ID = BOOLEAN + this.cur_tok.ID = token.BOOLEAN } else if isNull(this.cur_tok.Literal) { - this.cur_tok.ID = NULL + this.cur_tok.ID = token.NULL } else if isInclude(this.cur_tok.Literal) { - this.cur_tok.ID = INCLUDE + this.cur_tok.ID = token.INCLUDE } } -func (this *Tokenizer) parseNumber() { - this.cur_tok.ID = INTEGER +func (this *Scanner) parseNumber() { + this.cur_tok.ID = token.INTEGER this.cur_tok.Literal = string(this.cur_ch) digit := false for { this.readRune() if this.cur_ch == '.' && digit == false { - this.cur_tok.ID = FLOAT + this.cur_tok.ID = token.FLOAT digit = true } else if !isDigit(this.cur_ch) { break @@ -111,8 +113,8 @@ func (this *Tokenizer) parseNumber() { } } -func (this *Tokenizer) parseString() { - this.cur_tok.ID = STRING +func (this *Scanner) parseString() { + this.cur_tok.ID = token.STRING this.cur_tok.Literal = string(this.cur_ch) for { this.readRune() @@ -124,8 +126,8 @@ func (this *Tokenizer) parseString() { this.readRune() } -func (this *Tokenizer) parseComment() { - this.cur_tok.ID = COMMENT +func (this *Scanner) parseComment() { + this.cur_tok.ID = token.COMMENT this.cur_tok.Literal = "" for { this.readRune() @@ -137,7 +139,7 @@ func (this *Tokenizer) parseComment() { this.readRune() } -func (this *Tokenizer) skipWhitespace() { +func (this *Scanner) skipWhitespace() { for { this.readRune() if !isWhitespace(this.cur_ch) { @@ -146,13 +148,13 @@ func (this *Tokenizer) skipWhitespace() { } } -func (this *Tokenizer) NextToken() Token { +func (this *Scanner) NextToken() token.Token { if isWhitespace(this.cur_ch) { this.skipWhitespace() } - this.cur_tok = Token{ - ID: ILLEGAL, + this.cur_tok = token.Token{ + ID: token.ILLEGAL, Literal: string(this.cur_ch), Line: this.cur_line, Column: this.cur_col, @@ -166,24 +168,24 @@ func (this *Tokenizer) NextToken() Token { case ch == '#': this.parseComment() case ch == eof: - this.cur_tok.ID = EOF + this.cur_tok.ID = token.EOF this.cur_tok.Literal = "EOF" default: this.readRune() this.cur_tok.Literal = string(ch) switch ch { case '=': - this.cur_tok.ID = EQUAL + this.cur_tok.ID = token.EQUAL case '"': this.parseString() case '{': - this.cur_tok.ID = LBRACKET + this.cur_tok.ID = token.LBRACKET case '}': - this.cur_tok.ID = RBRACKET + this.cur_tok.ID = token.RBRACKET case ';': - this.cur_tok.ID = SEMICOLON + this.cur_tok.ID = token.SEMICOLON case '.': - this.cur_tok.ID = PERIOD + this.cur_tok.ID = token.PERIOD } } diff --git a/section.go b/section.go new file mode 100644 index 0000000..6ea6ca7 --- /dev/null +++ b/section.go @@ -0,0 +1,244 @@ +package forge + +import ( + "encoding/json" + "errors" + "fmt" + "strings" +) + +type Section struct { + parent *Section + values map[string]Value +} + +func NewSection() *Section { + return &Section{ + values: make(map[string]Value), + } +} + +func NewChildSection(parent *Section) *Section { + return &Section{ + parent: parent, + values: make(map[string]Value), + } +} + +func (this *Section) GetType() ValueType { + return SECTION +} + +func (this *Section) GetValue() interface{} { + return this.values +} + +func (this *Section) UpdateValue(value interface{}) error { + switch value.(type) { + case map[string]Value: + this.values = value.(map[string]Value) + return nil + } + + msg := fmt.Sprintf("Unsupported type, %s must be of type `map[string]Value`", value) + return errors.New(msg) +} + +func (this *Section) AddSection(name string) *Section { + section := NewChildSection(this) + this.values[name] = section + return section +} + +func (this *Section) Exists(name string) bool { + _, err := this.Get(name) + return err == nil +} + +func (this *Section) Get(name string) (Value, error) { + value, ok := this.values[name] + var err error + if ok == false { + err = errors.New("Value does not exist") + } + return value, err +} + +func (this *Section) GetBoolean(name string) (bool, error) { + value, err := this.Get(name) + if err != nil { + return false, err + } + + switch value.(type) { + case *Primative: + return value.(*Primative).AsBoolean() + case *Section: + return true, nil + } + + return false, errors.New("Could not convert unknown value to boolean") +} + +func (this *Section) GetFloat(name string) (float64, error) { + value, err := this.Get(name) + 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") +} + +func (this *Section) GetInteger(name string) (int64, error) { + value, err := this.Get(name) + 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") +} + +func (this *Section) GetSection(name string) (*Section, error) { + value, err := this.Get(name) + if err != nil { + return nil, err + } + + if value.GetType() == SECTION { + return value.(*Section), nil + } + return nil, errors.New("Could not fetch value as section") +} + +func (this *Section) GetString(name string) (string, error) { + value, err := this.Get(name) + 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") +} + +func (this *Section) GetParent() *Section { + return this.parent +} + +func (this *Section) HasParent() bool { + return this.parent != nil +} + +func (this *Section) Set(name string, value Value) { + this.values[name] = value +} + +func (this *Section) SetBoolean(name string, value bool) { + current, err := this.Get(name) + + // Exists just update the value/type + if err == nil { + current.UpdateValue(value) + } else { + this.values[name] = NewBoolean(value) + } +} + +func (this *Section) SetFloat(name string, value float64) { + current, err := this.Get(name) + + // Exists just update the value/type + if err == nil { + current.UpdateValue(value) + } else { + this.values[name] = NewFloat(value) + } +} + +func (this *Section) SetInteger(name string, value int64) { + current, err := this.Get(name) + + // Exists just update the value/type + if err == nil { + current.UpdateValue(value) + } else { + this.values[name] = NewInteger(value) + } +} + +func (this *Section) SetNull(name string) { + current, err := this.Get(name) + + // Already is a Null, nothing to do + if err == nil && current.GetType() == NULL { + return + } + this.Set(name, NewNull()) +} + +func (this *Section) SetString(name string, value string) { + current, err := this.Get(name) + + // Exists just update the value/type + if err == nil { + current.UpdateValue(value) + } else { + this.Set(name, NewString(value)) + } +} + +func (this *Section) Resolve(name string) (Value, error) { + // Used only in error state return value + var value Value + + parts := strings.Split(name, ".") + if len(parts) == 0 { + return value, errors.New("No name provided") + } + + var current Value + current = this + for _, part := range parts { + if current.GetType() != SECTION { + return value, errors.New("Trying to resolve value from non-section") + } + + next_current, err := current.(*Section).Get(part) + if err != nil { + return value, errors.New("Could not find value in section") + } + current = next_current + } + return current, nil +} + +func (this *Section) ToJSON() ([]byte, error) { + data := this.ToMap() + return json.Marshal(data) +} + +func (this *Section) ToMap() map[string]interface{} { + output := make(map[string]interface{}) + + for key, value := range this.values { + if value.GetType() == SECTION { + output[key] = value.(*Section).ToMap() + } else { + output[key] = value.GetValue() + } + } + return output +} diff --git a/token/token.go b/token/token.go index 06aedc9..563d400 100644 --- a/token/token.go +++ b/token/token.go @@ -15,3 +15,56 @@ func (this Token) String() string { this.ID, this.Literal, this.Line, this.Column, ) } + +type TokenID int + +const ( + ILLEGAL TokenID = iota + EOF + + LBRACKET + RBRACKET + EQUAL + SEMICOLON + PERIOD + + IDENTIFIER + BOOLEAN + INTEGER + FLOAT + STRING + NULL + COMMENT + INCLUDE +) + +var tokenNames = [...]string{ + ILLEGAL: "ILLEGAL", + EOF: "EOF", + LBRACKET: "LBRACKET", + RBRACKET: "RBRACKET", + EQUAL: "EQUAL", + SEMICOLON: "SEMICOLON", + PERIOD: "PERIOD", + IDENTIFIER: "IDENTIFIER", + BOOLEAN: "BOOLEAN", + INTEGER: "INTEGER", + FLOAT: "FLOAT", + STRING: "STRING", + NULL: "NULL", + COMMENT: "COMMENT", + INCLUDE: "INCLUDE", +} + +func (this TokenID) String() string { + s := "" + if 0 <= this && this < TokenID(len(tokenNames)) { + s = tokenNames[this] + } + + if s == "" { + s = "UNKNOWN" + } + + return s +} diff --git a/token/tokenid.go b/token/tokenid.go deleted file mode 100644 index 8144eb0..0000000 --- a/token/tokenid.go +++ /dev/null @@ -1,54 +0,0 @@ -package token - -type TokenID int - -const ( - ILLEGAL TokenID = iota - EOF - - LBRACKET - RBRACKET - EQUAL - SEMICOLON - PERIOD - - IDENTIFIER - BOOLEAN - INTEGER - FLOAT - STRING - NULL - COMMENT - INCLUDE -) - -var tokenNames = [...]string{ - ILLEGAL: "ILLEGAL", - EOF: "EOF", - LBRACKET: "LBRACKET", - RBRACKET: "RBRACKET", - EQUAL: "EQUAL", - SEMICOLON: "SEMICOLON", - PERIOD: "PERIOD", - IDENTIFIER: "IDENTIFIER", - BOOLEAN: "BOOLEAN", - INTEGER: "INTEGER", - FLOAT: "FLOAT", - STRING: "STRING", - NULL: "NULL", - COMMENT: "COMMENT", - INCLUDE: "INCLUDE", -} - -func (this TokenID) String() string { - s := "" - if 0 <= this && this < TokenID(len(tokenNames)) { - s = tokenNames[this] - } - - if s == "" { - s = "UNKNOWN" - } - - return s -} diff --git a/value.go b/value.go new file mode 100644 index 0000000..241604f --- /dev/null +++ b/value.go @@ -0,0 +1,48 @@ +package forge + +type ValueType int + +const ( + UNKNOWN ValueType = iota + + // Primative values + BOOLEAN + FLOAT + INTEGER + NULL + STRING + + // Complex values + REFERENCE + SECTION +) + +var valueTypes = [...]string{ + BOOLEAN: "BOOLEAN", + FLOAT: "FLOAT", + INTEGER: "INTEGER", + NULL: "NULL", + STRING: "STRING", + + REFERENCE: "REFERENCE", + SECTION: "SECTION", +} + +func (this ValueType) String() string { + str := "" + if 0 <= this && this < ValueType(len(valueTypes)) { + str = valueTypes[this] + } + + if str == "" { + str = "UNKNOWN" + } + + return str +} + +type Value interface { + GetType() ValueType + GetValue() interface{} + UpdateValue(interface{}) error +}