Backup work.

This commit is contained in:
Ubuntu 2019-05-15 11:08:14 +00:00
parent f6efd34b31
commit 866a928f57
5 changed files with 76 additions and 73 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.vscode

View File

@ -7,13 +7,12 @@ type itemType int
// Definition of all the lexer item types for the TOML lexer. // Definition of all the lexer item types for the TOML lexer.
const ( const (
ItemError itemType = iota // An error occurred ItemError itemType = iota // An error occurred
ItemEOF // End of input reached ItemEOF // End of input reached
ItemComment // Comment string, starts with # till en of line ItemComment // Comment string, starts with # till en of line
ItemKey // Key of a key/value pair ItemKey // Key of a key/value pair
ItemKeyDot // Dot for a dotted key ItemKeyDot // Dot for a dotted key
ItemKeyValueAssignment // Equal sign for a key/value pair assignment ItemString // A value of type string
ItemStringValue // A value of type string
) )
// Item represents a lexer item returned from the scanner. // Item represents a lexer item returned from the scanner.
@ -44,10 +43,8 @@ func (i itemType) String() string {
return "Key" return "Key"
case ItemKeyDot: case ItemKeyDot:
return "KeyDot" return "KeyDot"
case ItemKeyValueAssignment: case ItemString:
return "Assignment" return "String"
case ItemStringValue:
return "StringValue"
default: default:
return fmt.Sprintf("<type id %d>", i) return fmt.Sprintf("<type id %d>", i)
} }

View File

@ -116,6 +116,10 @@ func (l *Lexer) ignore() {
l.start = l.pos l.start = l.pos
} }
func (l *Lexer) atEndOfFile() bool {
return l.pos >= len(l.input)
}
// backup steps back one rune // backup steps back one rune
// Can be called only once per call of next. // Can be called only once per call of next.
func (l *Lexer) backup() { func (l *Lexer) backup() {
@ -194,14 +198,20 @@ func (l *Lexer) skipUntil(runes string) {
} }
} }
func (l *Lexer) newString() { // resetStringBuild initializes a new string builder, used for building
// string by interpreting input data, e.g. for translating
// double quoted strings with escape codes into an actual
// Go string value.
func (l *Lexer) resetStringBuilder() {
l.strValue.Reset() l.strValue.Reset()
} }
// addToString adds a rune to the string builder.
func (l *Lexer) addToString(r rune) { func (l *Lexer) addToString(r rune) {
l.strValue.WriteRune(r) l.strValue.WriteRune(r)
} }
// getString returns the runes in the string builder as a string value.
func (l *Lexer) getString() string { func (l *Lexer) getString() string {
return l.strValue.String() return l.strValue.String()
} }
@ -210,9 +220,9 @@ var endOfFile rune = -1
// next returns the next rune in the input. // next returns the next rune in the input.
func (l *Lexer) next() rune { func (l *Lexer) next() rune {
if l.pos >= len(l.input) { if l.atEndOfFile() {
l.width = 0 l.width = 0
return endOfFile return endOfFile // TODO phase out this bizarro rune?
} }
r, w := utf8.DecodeRuneInString(l.input[l.pos:]) r, w := utf8.DecodeRuneInString(l.input[l.pos:])
l.width = w l.width = w
@ -242,3 +252,7 @@ func (l *Lexer) unexpectedTokenError(expected string) stateFn {
} }
return l.errorf("Unexpected %s (expected %s)", actual, expected) return l.errorf("Unexpected %s (expected %s)", actual, expected)
} }
func (l *Lexer) unexpectedEndOfFile(expected string) stateFn {
return l.errorf("Unexpected end of file (expected %s)", expected)
}

View File

@ -35,7 +35,7 @@ func TestWhitespacePlusComment(t *testing.T) {
func TestBareKeyWithoutValue(t *testing.T) { func TestBareKeyWithoutValue(t *testing.T) {
err := "Unexpected end of file (expected an '=' value assignment)" err := "Unexpected end of file (expected an '=' value assignment)"
assertFailureAndCheck(t, "=", []string{`Key("a")`}, err) assertFailureAndCheck(t, "a", []string{`Key("a")`}, err)
assertFailureAndCheck(t, " a", []string{`Key("a")`}, err) assertFailureAndCheck(t, " a", []string{`Key("a")`}, err)
assertFailureAndCheck(t, " a ", []string{`Key("a")`}, err) assertFailureAndCheck(t, " a ", []string{`Key("a")`}, err)
assertFailureAndCheck(t, "ab", []string{`Key("ab")`}, err) assertFailureAndCheck(t, "ab", []string{`Key("ab")`}, err)
@ -54,33 +54,40 @@ func TestDottedKey(t *testing.T) {
func TestKeyWithAssignmentButNoValue(t *testing.T) { func TestKeyWithAssignmentButNoValue(t *testing.T) {
err := "Unexpected end of file (expected a value)" err := "Unexpected end of file (expected a value)"
assertFailureAndCheck(t, " some_cool_key = ", []string{`Key("some_cool_key")`, `Assignment("=")`}, err) assertFailureAndCheck(t, " some_cool_key = ", []string{`Key("some_cool_key")`}, err)
} }
func TestEmptyBasicStringValue(t *testing.T) { func TestUnterminatedBasicString(t *testing.T) {
assertSuccessAndCheck(t, `a=""`, []string{`Key("a")`, `Assignment("=")`, `StringValue("")`}) assertFailure(t, `key="value`, "Unexpected end of file (expected basic string token)")
assertSuccessAndCheck(t, `a=""#hi`, []string{`Key("a")`, `Assignment("=")`, `StringValue("")`, `Comment("#hi")`})
assertSuccessAndCheck(t, `a = ""`, []string{`Key("a")`, `Assignment("=")`, `StringValue("")`})
assertSuccessAndCheck(t, `a.b = ""`, []string{`Key("a")`, `KeyDot(".")`, `Key("b")`, `Assignment("=")`, `StringValue("")`})
} }
func TestBasicStringValue(t *testing.T) {
func TestBasicStringWithNewline(t *testing.T) {
assertFailure(t, "key=\"value\nwith\nnewlines\"", "ohoh")
}
func TestEmptyBasicString(t *testing.T) {
assertSuccessAndCheck(t, `a=""`, []string{`Key("a")`, `String("")`})
assertSuccessAndCheck(t, `a=""#hi`, []string{`Key("a")`, `String("")`, `Comment("#hi")`})
assertSuccessAndCheck(t, `a = ""`, []string{`Key("a")`, `String("")`})
assertSuccessAndCheck(t, `a.b = ""`, []string{`Key("a")`, `KeyDot(".")`, `Key("b")`, `String("")`})
assertSuccessAndCheck(t, `a=""b=""`, []string{`Key("a")`, `String("")`, `Key("b")`, `String("")`})
}
func TestBasicString(t *testing.T) {
assertSuccessAndCheck(t, `_ = "b"`, assertSuccessAndCheck(t, `_ = "b"`,
[]string{ []string{
`Key("_")`, `Key("_")`,
`Assignment("=")`, `String("b")`})
`StringValue("b")`})
assertSuccessAndCheck(t, `thing = "A cool ʎǝʞ" # huh, it's up-side down!!`, assertSuccessAndCheck(t, `thing = "A cool ʎǝʞ" # huh, it's up-side down!!`,
[]string{ []string{
`Key("thing")`, `Key("thing")`,
`Assignment("=")`, `String("A cool ʎǝʞ")`,
`StringValue("A cool ʎǝʞ")`,
`Comment("# huh, it's up-side down!!")`}) `Comment("# huh, it's up-side down!!")`})
} }
func TestInvalidEscapeSequence(t *testing.T) { func TestInvalidEscapeSequence(t *testing.T) {
assertFailure(t, `a="\x"`, `Invalid escape sequence \x in string value`) assertFailure(t, `a="\x"`, `Invalid escape sequence \x in string value`)
} }
func TestBasicStringValueEscapes(t *testing.T) { func TestBasicStringEscapes(t *testing.T) {
for in, out := range map[string]string{ for in, out := range map[string]string{
`\b`: "\b", `\b`: "\b",
`\t`: "\t", `\t`: "\t",
@ -91,8 +98,7 @@ func TestBasicStringValueEscapes(t *testing.T) {
`\b\t\n\f\r\"`: "\b\t\n\f\r\"", `\b\t\n\f\r\"`: "\b\t\n\f\r\"",
} { } {
l := assertSuccess(t, fmt.Sprintf(`x="%s"`, in)) l := assertSuccess(t, fmt.Sprintf(`x="%s"`, in))
s := l[2] if out != l[1].Value {
if out != s.Value {
t.Fatalf("Unexpected result when parsing '%s'", in) t.Fatalf("Unexpected result when parsing '%s'", in)
} }
} }
@ -114,12 +120,10 @@ func TestTwoKeyValuePairs(t *testing.T) {
assertSuccessAndCheck(t, "a=\"Hello\" #comment1\nb=\"World!\"#comment2\r\n", assertSuccessAndCheck(t, "a=\"Hello\" #comment1\nb=\"World!\"#comment2\r\n",
[]string{ []string{
`Key("a")`, `Key("a")`,
`Assignment("=")`, `String("Hello")`,
`StringValue("Hello")`,
`Comment("#comment1")`, `Comment("#comment1")`,
`Key("b")`, `Key("b")`,
`Assignment("=")`, `String("World!")`,
`StringValue("World!")`,
`Comment("#comment2")`}) `Comment("#comment2")`})
} }

View File

@ -6,8 +6,9 @@ type stateFn func(*Lexer) stateFn
const ( const (
whitespace string = " \t" whitespace string = " \t"
newline string = "\r\n" carriageReturn string = "\r"
startOfComment string = "#" newline string = "\n"
hash string = "#"
equal string = "=" equal string = "="
lower string = "abcdefghijklmnopqrstuvwxyz" lower string = "abcdefghijklmnopqrstuvwxyz"
upper string = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" upper string = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
@ -19,15 +20,13 @@ const (
doubleQuote string = "\"" doubleQuote string = "\""
backslash string = "\\" backslash string = "\\"
someQuote string = singleQuote + doubleQuote someQuote string = singleQuote + doubleQuote
singleQuote3 string = singleQuote + singleQuote + singleQuote
doubleQuote3 string = doubleQuote + doubleQuote + doubleQuote
bareKey string = lower + upper + digits + underscore + dash bareKey string = lower + upper + digits + underscore + dash
startOfKey string = bareKey + someQuote startOfKey string = bareKey + someQuote
) )
func stateKeyValuePair(l *Lexer) stateFn { func stateKeyValuePair(l *Lexer) stateFn {
l.skip(whitespace + newline) l.skip(whitespace + carriageReturn + newline)
if l.upcoming(startOfComment) { if l.upcoming(hash) {
return stateComment return stateComment
} }
if l.upcoming(startOfKey) { if l.upcoming(startOfKey) {
@ -36,12 +35,20 @@ func stateKeyValuePair(l *Lexer) stateFn {
return stateEndOfFile return stateEndOfFile
} }
// A hash symbol marks the rest of the line as a comment. // A '#' hash symbol marks the rest of the line as a comment.
func stateComment(l *Lexer) stateFn { func stateComment(l *Lexer) stateFn {
l.acceptUntil(newline) l.resetStringBuilder()
l.emit(ItemComment, l.getAcceptedString()) for {
l.skip(newline) switch {
return stateKeyValuePair case l.atEndOfFile() || l.accept(newline):
l.emit(ItemComment, l.getString())
return stateKeyValuePair
case l.accept(carriageReturn):
l.ignore()
default:
l.addToString(l.next())
}
}
} }
// A key may be either bare, quoted or dotted. // A key may be either bare, quoted or dotted.
@ -83,7 +90,6 @@ func stateEndOfKeyOrKeyDot(l *Lexer) stateFn {
func stateKeyAssignment(l *Lexer) stateFn { func stateKeyAssignment(l *Lexer) stateFn {
l.skip(whitespace) l.skip(whitespace)
if l.accept(equal) { if l.accept(equal) {
l.emit(ItemKeyValueAssignment, "=")
l.skip(whitespace) l.skip(whitespace)
return stateValue return stateValue
} }
@ -118,7 +124,7 @@ func stateBasicStringValue(l *Lexer) stateFn {
} }
// An "" empty string. // An "" empty string.
l.ignore() l.ignore()
l.emit(ItemStringValue, "") l.emit(ItemString, "")
return stateKeyValuePair return stateKeyValuePair
} }
l.ignore() l.ignore()
@ -160,9 +166,9 @@ var basicEscapes = map[rune]rune{
func stateParseBasicString(l *Lexer) stateFn { func stateParseBasicString(l *Lexer) stateFn {
for { for {
switch { switch {
case l.upcoming(endOfFile): case l.atEndOfFile():
l.unexpectedTokenError("basic string token") return l.unexpectedEndOfFile("basic string token")
case l.upcoming(doubleQuote): case l.accept(doubleQuote):
return l.popState() return l.popState()
case l.accept(backslash): case l.accept(backslash):
r := l.next() r := l.next()
@ -178,31 +184,12 @@ func stateParseBasicString(l *Lexer) stateFn {
} }
func stateBasicString(l *Lexer) stateFn { func stateBasicString(l *Lexer) stateFn {
l.newString() l.resetStringBuilder()
l.pushState(stateBasicStringEnd) l.pushState(func(l *Lexer) stateFn {
l.emit(ItemString, l.getString())
return stateKeyValuePair
})
return stateParseBasicString return stateParseBasicString
parsing:
for {
r := l.next()
if r == endOfFile {
break
}
if r == '"' {
l.emit(ItemStringValue, l.getString())
return stateKeyValuePair
}
if r == '\\' {
r = l.next()
if escaped, ok := basicEscapes[r]; ok {
l.addToString(escaped)
continue parsing
}
return l.errorf("Invalid escape sequence \\%c in string value", r)
}
l.addToString(r)
}
return l.unexpectedTokenError("valid basic string rune")
} }
func stateMultiLineBasicString(l *Lexer) stateFn { func stateMultiLineBasicString(l *Lexer) stateFn {