diff --git a/README.md b/README.md index a095904..8ae7d35 100644 --- a/README.md +++ b/README.md @@ -27,11 +27,14 @@ global = "global value"; primary { string = "primary string value"; single = 'single quotes are allowed too'; - integer = 500; - float = 80.80; - boolean = true; - negative = FALSE; - nothing = NULL; + + # Semicolons are optional + integer = 500 + float = 80.80 + boolean = true + negative = FALSE + nothing = NULL + # Include external files include "./include*.cfg"; # Primary-sub section diff --git a/forge.go b/forge.go index bf40833..543b470 100644 --- a/forge.go +++ b/forge.go @@ -27,6 +27,7 @@ // // IDENTIFIER: [_a-zA-Z]+ // NUMBERS: [0-9]+ +// END: ';' | '\n' // // BOOL: 'true' | 'false' // NULL: 'null' @@ -36,10 +37,10 @@ // REFERENCE: (IDENTIFIER)? ('.' IDENTIFIER)+ // VALUE: BOOL | NULL | INTEGER | FLOAT | STRING | REFERENCE // -// INCLUDE: 'include ' STRING ';' -// DIRECTIVE: (IDENTIFIER '=' VALUE | INCLUDE) ';' +// INCLUDE: 'include ' STRING END +// DIRECTIVE: (IDENTIFIER '=' VALUE | INCLUDE) END // SECTION: IDENTIFIER '{' (DIRECTIVE | SECTION)* '}' -// COMMENT: '#' .* NEWLINE '\n' +// COMMENT: '#' .* '\n' // // CONFIG_FILE: (COMMENT | DIRECTIVE | SECTION)* // @@ -70,7 +71,7 @@ // until after the newline. // * Directive: // A directive is a setting, a identifier and a value. They are in the format ' = ;' -// All directives must end in a semicolon. The value can be any of the types defined above. +// 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 ' { }'. // All sections must be wrapped in brackets ('{', '}') and must all have a name. They do not end in a semicolon. diff --git a/forge_test.go b/forge_test.go index c210941..153b7c1 100644 --- a/forge_test.go +++ b/forge_test.go @@ -17,12 +17,15 @@ primary { string_with_quote = "some \"quoted\" str\\ing"; single = 'hello world'; single_with_quote = '\'hello\' "world"'; - integer = 500; - float = 80.80; - negative = -50; - boolean = true; - not_true = FALSE; - nothing = NULL; + + # Semicolons are optional + integer = 500 + float = 80.80 + negative = -50 + boolean = true + not_true = FALSE + nothing = NULL + # Reference secondary._under (which hasn't been defined yet) sec_ref = secondary._under; # Primary-sub stuff diff --git a/parser.go b/parser.go index 93259b8..b2d81bd 100644 --- a/parser.go +++ b/parser.go @@ -11,6 +11,10 @@ import ( "github.com/brettlangdon/forge/token" ) +func isSemicolonOrNewline(id token.TokenID) bool { + return id == token.SEMICOLON || id == token.NEWLINE +} + // Parser is a struct to hold data necessary for parsing a config from a scanner type Parser struct { files []string @@ -88,10 +92,10 @@ func (parser *Parser) parseReference(startingSection *Section, period bool) (Val } name += parser.curTok.Literal period = false - } else if parser.curTok.ID == token.SEMICOLON { + } else if isSemicolonOrNewline(parser.curTok.ID) { break } else { - msg := fmt.Sprintf("expected ';' instead found '%s'", parser.curTok.Literal) + msg := fmt.Sprintf("expected ';' or '\n' instead found '%s'", parser.curTok.Literal) return nil, parser.syntaxError(msg) } } @@ -159,8 +163,8 @@ func (parser *Parser) parseSetting(name string) error { if readNext { parser.readToken() } - if parser.curTok.ID != token.SEMICOLON { - msg := fmt.Sprintf("expected ';' instead found '%s'", parser.curTok.Literal) + if isSemicolonOrNewline(parser.curTok.ID) == false { + msg := fmt.Sprintf("expected ';' or '\n' instead found '%s'", parser.curTok.Literal) return parser.syntaxError(msg) } parser.readToken() @@ -177,8 +181,8 @@ func (parser *Parser) parseInclude() error { pattern := parser.curTok.Literal parser.readToken() - if parser.curTok.ID != token.SEMICOLON { - msg := fmt.Sprintf("expected ';' instead found '%s'", parser.curTok.Literal) + if isSemicolonOrNewline(parser.curTok.ID) == false { + msg := fmt.Sprintf("expected ';' or '\n' instead found '%s'", parser.curTok.Literal) return parser.syntaxError(msg) } @@ -260,6 +264,9 @@ func (parser *Parser) parse() error { if err != nil { return err } + case token.NEWLINE: + // Ignore extra newlines + continue default: return parser.syntaxError(fmt.Sprintf("unexpected token %s", tok)) } diff --git a/scanner.go b/scanner.go index dff54aa..9d7a8c7 100644 --- a/scanner.go +++ b/scanner.go @@ -18,8 +18,8 @@ func isDigit(ch rune) bool { return ('0' <= ch && ch <= '9') } -func isWhitespace(ch rune) bool { - return (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r') +func isNonNewlineWhitespace(ch rune) bool { + return (ch == ' ' || ch == '\t' || ch == '\r') } func isBoolean(str string) bool { @@ -171,10 +171,10 @@ func (scanner *Scanner) parseComment() { scanner.readRune() } -func (scanner *Scanner) skipWhitespace() { +func (scanner *Scanner) skipNonNewlineWhitespace() { for { scanner.readRune() - if !isWhitespace(scanner.curCh) { + if !isNonNewlineWhitespace(scanner.curCh) { break } } @@ -182,8 +182,8 @@ func (scanner *Scanner) skipWhitespace() { // NextToken will read in the next valid token from the Scanner func (scanner *Scanner) NextToken() token.Token { - if isWhitespace(scanner.curCh) { - scanner.skipWhitespace() + if isNonNewlineWhitespace(scanner.curCh) { + scanner.skipNonNewlineWhitespace() } scanner.curTok = token.Token{ @@ -217,6 +217,8 @@ func (scanner *Scanner) NextToken() token.Token { scanner.curTok.ID = token.RBRACKET case ';': scanner.curTok.ID = token.SEMICOLON + case '\n': + scanner.curTok.ID = token.NEWLINE case '.': scanner.curTok.ID = token.PERIOD case '-': diff --git a/test.cfg b/test.cfg index 0ba9d11..160b1c5 100644 --- a/test.cfg +++ b/test.cfg @@ -6,12 +6,15 @@ primary { string_with_quote = "some \"quoted\" str\\ing"; single = 'hello world'; single_with_quote = '\'hello\' "world"'; - integer = 500; - float = 80.80; - negative = -50; - boolean = true; - not_true = FALSE; - nothing = NULL; + + # Semicolons are optional + integer = 500 + float = 80.80 + negative = -50 + boolean = true + not_true = FALSE + nothing = NULL + # Reference secondary._under (which hasn't been defined yet) sec_ref = secondary._under; # Primary-sub stuff diff --git a/token/token.go b/token/token.go index 563d400..75efa92 100644 --- a/token/token.go +++ b/token/token.go @@ -26,6 +26,7 @@ const ( RBRACKET EQUAL SEMICOLON + NEWLINE PERIOD IDENTIFIER @@ -45,6 +46,7 @@ var tokenNames = [...]string{ RBRACKET: "RBRACKET", EQUAL: "EQUAL", SEMICOLON: "SEMICOLON", + NEWLINE: "NEWLINE", PERIOD: "PERIOD", IDENTIFIER: "IDENTIFIER", BOOLEAN: "BOOLEAN",