Readd source files. I need coffee now

This commit is contained in:
Thomas Pelletier
2013-02-25 10:57:12 +01:00
parent 0f6c9649f4
commit 8783fed844
7 changed files with 1028 additions and 0 deletions
+29
View File
@@ -0,0 +1,29 @@
# This is a TOML document. Boom.
title = "TOML Example"
[owner]
name = "Tom Preston-Werner"
organization = "GitHub"
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
dob = 1979-05-27T07:32:00Z # First class dates? Why not?
[database]
server = "192.168.1.1"
ports = [ 8001, 8001, 8002 ]
connection_max = 5000
enabled = true
[servers]
# You can indent as you please. Tabs or spaces. TOML don't care.
[servers.alpha]
ip = "10.0.0.1"
dc = "eqdc10"
[servers.beta]
ip = "10.0.0.2"
dc = "eqdc10"
[clients]
data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
+400
View File
@@ -0,0 +1,400 @@
// TOML lexer.// Written using the principles developped by Rob Pike in
// http://www.youtube.com/watch?v=HxaD_trXwRE
package toml
import (
"fmt"
"regexp"
"strings"
"unicode/utf8"
)
var dateRegexp *regexp.Regexp
// Define tokens
type tokenType int
const (
eof = -(iota + 1)
)
const (
tokenError tokenType = iota
tokenEOF
tokenComment
tokenKey
tokenEqual
tokenString
tokenInteger
tokenTrue
tokenFalse
tokenFloat
tokenLeftBracket
tokenRightBracket
tokenDate
tokenKeyGroup
tokenComma
tokenEOL
)
type token struct {
typ tokenType
val string
}
func (i token) String() string {
switch i.typ {
case tokenEOF:
return "EOF"
case tokenError:
return i.val
}
if len(i.val) > 10 {
return fmt.Sprintf("%.10q...", i.val)
}
return fmt.Sprintf("%q", i.val)
}
func isSpace(r rune) bool {
return r == ' ' || r == '\t'
}
func isAlpha(r rune) bool {
return r >= 'a' && r <= 'z'
}
func isDigit(r rune) bool {
return r >= '0' && r <= '9'
}
// Define lexer
type lexer struct {
input string
start int
pos int
width int
tokens chan token
depth int
}
func (l *lexer) run() {
for state := lexVoid; state != nil; {
state = state(l)
}
close(l.tokens)
}
func (l *lexer) emit(t tokenType) {
l.tokens <- token{t, l.input[l.start:l.pos]}
l.start = l.pos
}
func (l *lexer) emitWithValue(t tokenType, value string) {
l.tokens <- token{t, value}
l.start = l.pos
}
func (l *lexer) next() rune {
if l.pos >= len(l.input) {
l.width = 0
return eof
}
var r rune
r, l.width = utf8.DecodeRuneInString(l.input[l.pos:])
l.pos += l.width
return r
}
func (l *lexer) ignore() {
l.start = l.pos
}
func (l *lexer) backup() {
l.pos -= l.width
}
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
l.tokens <- token{
tokenError,
fmt.Sprintf(format, args...),
}
return nil
}
func (l *lexer) peek() rune {
r := l.next()
l.backup()
return r
}
func (l *lexer) accept(valid string) bool {
if strings.IndexRune(valid, l.next()) >= 0 {
return true
}
l.backup()
return false
}
func (l *lexer) follow(next string) bool {
return strings.HasPrefix(l.input[l.pos:], next)
}
// Define state functions
type stateFn func(*lexer) stateFn
func lexVoid(l *lexer) stateFn {
for {
next := l.peek()
switch next {
case '[':
return lexKeyGroup
case '#':
return lexComment
case '=':
return lexEqual
}
if isAlpha(next) {
return lexKey
}
if isSpace(next) {
l.ignore()
}
if l.next() == eof {
break
}
}
l.emit(tokenEOF)
return nil
}
func lexRvalue(l *lexer) stateFn {
for {
next := l.peek()
switch next {
case '[':
l.depth += 1
return lexLeftBracket
case ']':
l.depth -= 1
return lexRightBracket
case '#':
return lexComment
case '"':
return lexString
case ',':
return lexComma
case '\n':
l.ignore()
l.pos += 1
if l.depth == 0 {
return lexVoid
} else {
return lexRvalue
}
}
if l.follow("true") {
return lexTrue
}
if l.follow("false") {
return lexFalse
}
if isAlpha(next) {
return lexKey
}
if dateRegexp.FindString(l.input[l.pos:]) != "" {
return lexDate
}
if next == '+' || next == '-' || isDigit(next) {
return lexNumber
}
if isSpace(next) {
l.ignore()
}
if l.next() == eof {
break
}
}
l.emit(tokenEOF)
return nil
}
func lexDate(l *lexer) stateFn {
l.ignore()
l.pos += 20 // Fixed size of a date in TOML
l.emit(tokenDate)
return lexRvalue
}
func lexTrue(l *lexer) stateFn {
l.ignore()
l.pos += 4
l.emit(tokenTrue)
return lexRvalue
}
func lexFalse(l *lexer) stateFn {
l.ignore()
l.pos += 5
l.emit(tokenFalse)
return lexRvalue
}
func lexEqual(l *lexer) stateFn {
l.ignore()
l.accept("=")
l.emit(tokenEqual)
return lexRvalue
}
func lexComma(l *lexer) stateFn {
l.ignore()
l.accept(",")
l.emit(tokenComma)
return lexRvalue
}
func lexKey(l *lexer) stateFn {
l.ignore()
for isAlpha(l.next()) {
}
l.backup()
l.emit(tokenKey)
return lexVoid
}
func lexComment(l *lexer) stateFn {
for {
next := l.next()
if next == '\n' || next == eof {
break
}
}
l.ignore()
return lexVoid
}
func lexLeftBracket(l *lexer) stateFn {
l.ignore()
l.pos += 1
l.emit(tokenLeftBracket)
return lexRvalue
}
func lexString(l *lexer) stateFn {
l.pos += 1
l.ignore()
growing_string := ""
for {
if l.peek() == '"' {
l.emitWithValue(tokenString, growing_string)
l.pos += 1
l.ignore()
return lexVoid
}
if l.follow("\\\"") {
l.pos += 1
growing_string += "\""
} else {
growing_string += string(l.peek())
}
if l.next() == eof {
break
}
}
return l.errorf("unclosed string")
}
func lexKeyGroup(l *lexer) stateFn {
l.ignore()
l.pos += 1
l.emit(tokenLeftBracket)
return lexInsideKeyGroup
}
func lexInsideKeyGroup(l *lexer) stateFn {
for {
if l.peek() == ']' {
if l.pos > l.start {
l.emit(tokenKeyGroup)
}
l.ignore()
l.pos += 1
l.emit(tokenRightBracket)
return lexVoid
}
if l.next() == eof {
break
}
}
return l.errorf("unclosed key group")
}
func lexRightBracket(l *lexer) stateFn {
l.ignore()
l.pos += 1
l.emit(tokenRightBracket)
return lexRvalue
}
func lexNumber(l *lexer) stateFn {
l.ignore()
if !l.accept("+") {
l.accept("-")
}
point_seen := false
digit_seen := false
for {
next := l.next()
if next == '.' {
point_seen = true
} else if isDigit(next) {
digit_seen = true
} else {
l.backup()
break
}
}
if !digit_seen {
return l.errorf("no digit in that number")
}
if point_seen {
l.emit(tokenFloat)
} else {
l.emit(tokenInteger)
}
return lexRvalue
}
func init() {
dateRegexp = regexp.MustCompile("^\\d{1,4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z")
}
// Entry point
func lex(input string) (*lexer, chan token) {
l := &lexer{
input: input,
tokens: make(chan token),
}
go l.run()
return l, l.tokens
}
+231
View File
@@ -0,0 +1,231 @@
package toml
import "testing"
func testFlow(t *testing.T, input string, expectedFlow []token) {
_, ch := lex(input)
for _, expected := range expectedFlow {
token := <-ch
if token != expected {
t.Log("compared", token, "to", expected)
t.Log(token.val, "<->", expected.val)
t.Log(token.typ, "<->", expected.typ)
t.FailNow()
}
}
tok, ok := <-ch
if ok {
t.Log("channel is not closed!")
t.Log(len(ch)+1, "tokens remaining:")
t.Log("token ->", tok)
for token := range ch {
t.Log("token ->", token)
}
t.FailNow()
}
}
func TestValidKeyGroup(t *testing.T) {
testFlow(t, "[hello world]", []token{
token{tokenLeftBracket, "["},
token{tokenKeyGroup, "hello world"},
token{tokenRightBracket, "]"},
token{tokenEOF, ""},
})
}
func TestUnclosedKeyGroup(t *testing.T) {
testFlow(t, "[hello world", []token{
token{tokenLeftBracket, "["},
token{tokenError, "unclosed key group"},
})
}
func TestComment(t *testing.T) {
testFlow(t, "# blahblah", []token{
token{tokenEOF, ""},
})
}
func TestKeyGroupComment(t *testing.T) {
testFlow(t, "[hello world] # blahblah", []token{
token{tokenLeftBracket, "["},
token{tokenKeyGroup, "hello world"},
token{tokenRightBracket, "]"},
token{tokenEOF, ""},
})
}
func TestMultipleKeyGroupsComment(t *testing.T) {
testFlow(t, "[hello world] # blahblah\n[test]", []token{
token{tokenLeftBracket, "["},
token{tokenKeyGroup, "hello world"},
token{tokenRightBracket, "]"},
token{tokenLeftBracket, "["},
token{tokenKeyGroup, "test"},
token{tokenRightBracket, "]"},
token{tokenEOF, ""},
})
}
func TestBasicKey(t *testing.T) {
testFlow(t, "hello", []token{
token{tokenKey, "hello"},
token{tokenEOF, ""},
})
}
func TestBasicKeyAndEqual(t *testing.T) {
testFlow(t, "hello =", []token{
token{tokenKey, "hello"},
token{tokenEqual, "="},
token{tokenEOF, ""},
})
}
func TestKeyEqualStringEscape(t *testing.T) {
testFlow(t, "foo = \"hello\\\"\"", []token{
token{tokenKey, "foo"},
token{tokenEqual, "="},
token{tokenString, "hello\""},
token{tokenEOF, ""},
})
}
func TestKeyEqualStringUnfinished(t *testing.T) {
testFlow(t, "foo = \"bar", []token{
token{tokenKey, "foo"},
token{tokenEqual, "="},
token{tokenError, "unclosed string"},
})
}
func TestKeyEqualString(t *testing.T) {
testFlow(t, "foo = \"bar\"", []token{
token{tokenKey, "foo"},
token{tokenEqual, "="},
token{tokenString, "bar"},
token{tokenEOF, ""},
})
}
func TestKeyEqualTrue(t *testing.T) {
testFlow(t, "foo = true", []token{
token{tokenKey, "foo"},
token{tokenEqual, "="},
token{tokenTrue, "true"},
token{tokenEOF, ""},
})
}
func TestKeyEqualFalse(t *testing.T) {
testFlow(t, "foo = false", []token{
token{tokenKey, "foo"},
token{tokenEqual, "="},
token{tokenFalse, "false"},
token{tokenEOF, ""},
})
}
func TestKeyEqualArrayBools(t *testing.T) {
testFlow(t, "foo = [true, false, true]", []token{
token{tokenKey, "foo"},
token{tokenEqual, "="},
token{tokenLeftBracket, "["},
token{tokenTrue, "true"},
token{tokenComma, ","},
token{tokenFalse, "false"},
token{tokenComma, ","},
token{tokenTrue, "true"},
token{tokenRightBracket, "]"},
token{tokenEOF, ""},
})
}
func TestKeyEqualArrayBoolsWithComments(t *testing.T) {
testFlow(t, "foo = [true, false, true] # YEAH", []token{
token{tokenKey, "foo"},
token{tokenEqual, "="},
token{tokenLeftBracket, "["},
token{tokenTrue, "true"},
token{tokenComma, ","},
token{tokenFalse, "false"},
token{tokenComma, ","},
token{tokenTrue, "true"},
token{tokenRightBracket, "]"},
token{tokenEOF, ""},
})
}
func TestDateRegexp(t *testing.T) {
if dateRegexp.FindString("1979-05-27T07:32:00Z") == "" {
t.Fail()
}
}
func TestKeyEqualDate(t *testing.T) {
testFlow(t, "foo = 1979-05-27T07:32:00Z", []token{
token{tokenKey, "foo"},
token{tokenEqual, "="},
token{tokenDate, "1979-05-27T07:32:00Z"},
token{tokenEOF, ""},
})
}
func TestKeyEqualNumber(t *testing.T) {
testFlow(t, "foo = 42", []token{
token{tokenKey, "foo"},
token{tokenEqual, "="},
token{tokenInteger, "42"},
token{tokenEOF, ""},
})
testFlow(t, "foo = +42", []token{
token{tokenKey, "foo"},
token{tokenEqual, "="},
token{tokenInteger, "+42"},
token{tokenEOF, ""},
})
testFlow(t, "foo = -42", []token{
token{tokenKey, "foo"},
token{tokenEqual, "="},
token{tokenInteger, "-42"},
token{tokenEOF, ""},
})
testFlow(t, "foo = 4.2", []token{
token{tokenKey, "foo"},
token{tokenEqual, "="},
token{tokenFloat, "4.2"},
token{tokenEOF, ""},
})
testFlow(t, "foo = +4.2", []token{
token{tokenKey, "foo"},
token{tokenEqual, "="},
token{tokenFloat, "+4.2"},
token{tokenEOF, ""},
})
testFlow(t, "foo = -4.2", []token{
token{tokenKey, "foo"},
token{tokenEqual, "="},
token{tokenFloat, "-4.2"},
token{tokenEOF, ""},
})
}
func TestMultiline(t *testing.T) {
testFlow(t, "foo = 42\nbar=21", []token{
token{tokenKey, "foo"},
token{tokenEqual, "="},
token{tokenInteger, "42"},
token{tokenKey, "bar"},
token{tokenEqual, "="},
token{tokenInteger, "21"},
token{tokenEOF, ""},
})
}
+184
View File
@@ -0,0 +1,184 @@
// TOML Parser.
package toml
import (
"fmt"
"strconv"
"time"
)
type parser struct {
flow chan token
tree *TomlTree
tokensBuffer []token
currentGroup string
}
type parserStateFn func(*parser) parserStateFn
func (p *parser) run() {
for state := parseStart; state != nil; {
state = state(p)
}
}
func (p *parser) peek() *token {
if len(p.tokensBuffer) != 0 {
return &(p.tokensBuffer[0])
}
tok, ok := <-p.flow
if !ok {
return nil
}
p.tokensBuffer = append(p.tokensBuffer, tok)
return &tok
}
func (p *parser) assume(typ tokenType) {
tok := p.getToken()
if tok == nil {
panic(fmt.Sprintf("was expecting token %s, but token stream is empty", typ))
}
if tok.typ != typ {
panic(fmt.Sprintf("was expecting token %s, but got %s", typ, tok.typ))
}
}
func (p *parser) getToken() *token {
if len(p.tokensBuffer) != 0 {
tok := p.tokensBuffer[0]
p.tokensBuffer = p.tokensBuffer[1:]
return &tok
}
tok, ok := <-p.flow
if !ok {
return nil
}
return &tok
}
func parseStart(p *parser) parserStateFn {
tok := p.peek()
// end of stream, parsing is finished
if tok == nil {
return nil
}
switch tok.typ {
case tokenLeftBracket:
return parseGroup
case tokenKey:
return parseAssign
case tokenEOF:
return nil
default:
panic("unexpected token")
}
return nil
}
func parseGroup(p *parser) parserStateFn {
p.getToken() // discard the [
key := p.getToken()
if key.typ != tokenKeyGroup {
panic(fmt.Sprintf("unexpected token %s, was expecting a key group", key))
}
p.tree.createSubTree(key.val)
p.assume(tokenRightBracket)
p.currentGroup = key.val
return parseStart(p)
}
func parseAssign(p *parser) parserStateFn {
key := p.getToken()
p.assume(tokenEqual)
value := parseRvalue(p)
final_key := key.val
if p.currentGroup != "" {
final_key = p.currentGroup + "." + key.val
}
p.tree.Set(final_key, value)
return parseStart(p)
}
func parseRvalue(p *parser) interface{} {
tok := p.getToken()
if tok == nil {
panic("expecting a value")
}
switch tok.typ {
case tokenString:
return tok.val
case tokenTrue:
return true
case tokenFalse:
return false
case tokenInteger:
val, err := strconv.ParseInt(tok.val, 10, 64)
if err != nil {
panic(err)
}
return val
case tokenFloat:
val, err := strconv.ParseFloat(tok.val, 64)
if err != nil {
panic(err)
}
return val
case tokenDate:
val, err := time.Parse(time.RFC3339, tok.val)
if err != nil {
panic(err)
}
return val
case tokenLeftBracket:
return parseArray(p)
}
panic("never reached")
return nil
}
func parseArray(p *parser) []interface{} {
array := make([]interface{}, 0)
for {
follow := p.peek()
if follow == nil {
panic("unterminated array")
}
if follow.typ == tokenRightBracket {
p.getToken()
return array
}
val := parseRvalue(p)
array = append(array, val)
follow = p.peek()
if follow == nil {
panic("unterminated array")
}
if follow.typ != tokenRightBracket && follow.typ != tokenComma {
panic("missing comma")
}
if follow.typ == tokenComma {
p.getToken()
}
}
return array
}
func parse(flow chan token) *TomlTree {
result := make(TomlTree)
parser := &parser{
flow: flow,
tree: &result,
tokensBuffer: make([]token, 0),
currentGroup: "",
}
parser.run()
return parser.tree
}
+103
View File
@@ -0,0 +1,103 @@
package toml
import (
"fmt"
"testing"
"time"
)
func assertTree(t *testing.T, tree *TomlTree, ref map[string]interface{}) {
for k, v := range ref {
if fmt.Sprintf("%v", tree.Get(k)) != fmt.Sprintf("%v", v) {
t.Log("was expecting", v, "at", k, "but got", tree.Get(k))
t.Fail()
}
}
}
func TestCreateSubTree(t *testing.T) {
tree := make(TomlTree)
tree.createSubTree("a.b.c")
tree.Set("a.b.c", 42)
if tree.Get("a.b.c") != 42 {
t.Fail()
}
}
func TestSimpleKV(t *testing.T) {
tree := Load("a = 42")
assertTree(t, tree, map[string]interface{}{
"a": int64(42),
})
tree = Load("a = 42\nb = 21")
assertTree(t, tree, map[string]interface{}{
"a": int64(42),
"b": int64(21),
})
}
func TestSimpleNumbers(t *testing.T) {
tree := Load("a = +42\nb = -21\nc = +4.2\nd = -2.1")
assertTree(t, tree, map[string]interface{}{
"a": int64(42),
"b": int64(-21),
"c": float64(4.2),
"d": float64(-2.1),
})
}
func TestSimpleDate(t *testing.T) {
tree := Load("a = 1979-05-27T07:32:00Z")
assertTree(t, tree, map[string]interface{}{
"a": time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC),
})
}
func TestSimpleString(t *testing.T) {
tree := Load("a = \"hello world\"")
assertTree(t, tree, map[string]interface{}{
"a": "hello world",
})
}
func TestBools(t *testing.T) {
tree := Load("a = true\nb = false")
assertTree(t, tree, map[string]interface{}{
"a": true,
"b": false,
})
}
func TestNestedKeys(t *testing.T) {
tree := Load("[a.b.c]\nd = 42")
assertTree(t, tree, map[string]interface{}{
"a.b.c.d": int64(42),
})
}
func TestArraySimple(t *testing.T) {
tree := Load("a = [42, 21, 10]")
assertTree(t, tree, map[string]interface{}{
"a": []int64{int64(42), int64(21), int64(10)},
})
tree = Load("a = [42, 21, 10,]")
assertTree(t, tree, map[string]interface{}{
"a": []int64{int64(42), int64(21), int64(10)},
})
}
func TestArrayMultiline(t *testing.T) {
tree := Load("a = [42,\n21, 10,]")
assertTree(t, tree, map[string]interface{}{
"a": []int64{int64(42), int64(21), int64(10)},
})
}
func TestArrayNested(t *testing.T) {
tree := Load("a = [[42, 21], [10]]")
assertTree(t, tree, map[string]interface{}{
"a": [][]int64{[]int64{int64(42), int64(21)}, []int64{int64(10)}},
})
}
+80
View File
@@ -0,0 +1,80 @@
// TOML markup language parser.
//
// This version supports the specification as described in
// https://github.com/mojombo/toml/tree/e3656ad493400895f4460f1244a25f8f8e31a32a
package toml
import (
"strings"
)
// Definition of a TomlTree.
// This is the result of the parsing of a TOML file.
type TomlTree map[string]interface{}
// Keys returns the keys of the toplevel tree.
// Warning: this is a costly operation.
func (t *TomlTree) Keys() []string {
keys := make([]string, 0)
mp := (map[string]interface{})(*t)
for k, _ := range mp {
keys = append(keys, k)
}
return keys
}
// Get the value at key in the TomlTree.
// Key is a dot-separated path (e.g. a.b.c).
// Returns nil if the path does not exist in the tree.
func (t *TomlTree) Get(key string) interface{} {
subtree := t
keys := strings.Split(key, ".")
for _, intermediate_key := range keys[:len(keys)-1] {
_, exists := (*subtree)[intermediate_key]
if !exists {
return nil
}
subtree = (*subtree)[intermediate_key].(*TomlTree)
}
return (*subtree)[keys[len(keys)-1]]
}
// Set an element in the tree.
// Key is a dot-separated path (e.g. a.b.c).
// Creates all necessary intermediates trees, if needed.
func (t *TomlTree) Set(key string, value interface{}) {
subtree := t
keys := strings.Split(key, ".")
for _, intermediate_key := range keys[:len(keys)-1] {
_, exists := (*subtree)[intermediate_key]
if !exists {
var new_tree TomlTree = make(TomlTree)
(*subtree)[intermediate_key] = &new_tree
}
subtree = (*subtree)[intermediate_key].(*TomlTree)
}
(*subtree)[keys[len(keys)-1]] = value
}
// createSubTree takes a tree and a key andcreate the necessary intermediate
// subtrees to create a subtree at that point. In-place.
//
// e.g. passing a.b.c will create (assuming tree is empty) tree[a], tree[a][b]
// and tree[a][b][c]
func (t *TomlTree) createSubTree(key string) {
subtree := t
for _, intermediate_key := range strings.Split(key, ".") {
_, exists := (*subtree)[intermediate_key]
if !exists {
var new_tree TomlTree = make(TomlTree)
(*subtree)[intermediate_key] = &new_tree
}
subtree = ((*subtree)[intermediate_key]).(*TomlTree)
}
}
// Create a TomlTree from a string.
func Load(content string) *TomlTree {
_, flow := lex(content)
return parse(flow)
}
+1
View File
@@ -0,0 +1 @@
package toml