diff --git a/gql/state.go b/gql/state.go index 9313671b021fc2f8ff01e63c7732f232a80c6226..2370108d8f51fd21e3b8f9654f3b4756d0750b22 100644 --- a/gql/state.go +++ b/gql/state.go @@ -19,22 +19,26 @@ package gql import "github.com/dgraph-io/dgraph/lex" const ( - leftCurl = '{' - rightCurl = '}' + leftCurl = '{' + rightCurl = '}' + queryMode = 1 + mutationMode = 2 ) const ( - itemText lex.ItemType = 5 + iota // plain text - itemLeftCurl // left curly bracket - itemRightCurl // right curly bracket - itemComment // comment - itemName // [9] names - itemOpType // operation type - itemString // quoted string - itemLeftRound // left round bracket - itemRightRound // right round bracket - itemArgName // argument name - itemArgVal // argument val + itemText lex.ItemType = 5 + iota // plain text + itemLeftCurl // left curly bracket + itemRightCurl // right curly bracket + itemComment // comment + itemName // [9] names + itemOpType // operation type + itemString // quoted string + itemLeftRound // left round bracket + itemRightRound // right round bracket + itemArgName // argument name + itemArgVal // argument val + itemMutationOp // mutation operation + itemMutationContent // mutation content ) func lexText(l *lex.Lexer) lex.StateFn { @@ -47,7 +51,11 @@ Loop: l.Next() // advance one to get back to where we saw leftCurl. l.Depth += 1 // one level down. l.Emit(itemLeftCurl) - return lexInside // we're in. + if l.Mode == mutationMode { + return lexInsideMutation + } else { + return lexInside + } case r == rightCurl: return l.Errorf("Too many right characters") @@ -79,7 +87,7 @@ func lexInside(l *lex.Lexer) lex.StateFn { l.Depth += 1 l.Emit(itemLeftCurl) case r == lex.EOF: - return l.Errorf("unclosed action") + return l.Errorf("Unclosed action") case isSpace(r) || isEndOfLine(r) || r == ',': l.Ignore() case isNameBegin(r): @@ -128,6 +136,64 @@ func lexComment(l *lex.Lexer) lex.StateFn { return nil // Stop the run loop. } +func lexInsideMutation(l *lex.Lexer) lex.StateFn { + for { + switch r := l.Next(); { + case r == rightCurl: + l.Depth -= 1 + l.Emit(itemRightCurl) + if l.Depth == 0 { + return lexText + } + case r == leftCurl: + l.Depth += 1 + l.Emit(itemLeftCurl) + if l.Depth >= 2 { + return lexTextMutation + } + case r == lex.EOF: + return l.Errorf("Unclosed mutation action") + case isSpace(r) || isEndOfLine(r): + l.Ignore() + case isNameBegin(r): + return lexNameMutation + default: + return l.Errorf("Unrecognized character in lexInsideMutation: %#U", r) + } + } +} + +func lexNameMutation(l *lex.Lexer) lex.StateFn { + for { + // The caller already checked isNameBegin, and absorbed one rune. + r := l.Next() + if isNameBegin(r) { + continue + } + l.Backup() + l.Emit(itemMutationOp) + break + } + return lexInsideMutation +} + +func lexTextMutation(l *lex.Lexer) lex.StateFn { + for { + r := l.Next() + if r == lex.EOF { + return l.Errorf("Unclosed mutation text") + } + if r != rightCurl { + // Absorb everything until we find '}'. + continue + } + l.Backup() + l.Emit(itemMutationContent) + break + } + return lexInsideMutation +} + func lexOperationType(l *lex.Lexer) lex.StateFn { for { r := l.Next() @@ -136,8 +202,13 @@ func lexOperationType(l *lex.Lexer) lex.StateFn { } l.Backup() word := l.Input[l.Start:l.Pos] - if word == "query" || word == "mutation" { + if word == "mutation" { + l.Emit(itemOpType) + l.Mode = mutationMode + + } else if word == "query" { l.Emit(itemOpType) + l.Mode = queryMode } break } diff --git a/gql/state_test.go b/gql/state_test.go index a1ce13d61805ea6f4c56a8acac731792f76c2da8..69ff92730141c52d6282dc956dd4dc359fc9bd39 100644 --- a/gql/state_test.go +++ b/gql/state_test.go @@ -17,7 +17,6 @@ package gql import ( - "fmt" "testing" "github.com/dgraph-io/dgraph/lex" @@ -25,7 +24,7 @@ import ( func TestNewLexer(t *testing.T) { input := ` - mutation { + query { me( id: 10, xid: rick ) { name0 # my name _city, # 0what would fail lex. @@ -38,6 +37,56 @@ func TestNewLexer(t *testing.T) { l := lex.NewLexer(input) go run(l) for item := range l.Items { - fmt.Println(item.String()) + if item.Typ == lex.ItemError { + t.Error(item.String()) + } + t.Log(item.String()) + } +} + +func TestNewLexerMutation(t *testing.T) { + input := ` + mutation { + set { + What is <this> . + Why is this #!!? + How is this? + } + delete { + Why is this + } + } + query { + me(xid: rick) { + _city + } + }` + l := lex.NewLexer(input) + go run(l) + for item := range l.Items { + if item.Typ == lex.ItemError { + t.Error(item.String()) + } + t.Log(item.String()) + } +} + +func TestAbruptMutation(t *testing.T) { + input := ` + mutation { + set { + What is <this> . + Why is this #!!? + How is this? + }` + l := lex.NewLexer(input) + go run(l) + var typ lex.ItemType + for item := range l.Items { + t.Log(item.String()) + typ = item.Typ + } + if typ != lex.ItemError { + t.Error("This should fail.") } } diff --git a/lex/lexer.go b/lex/lexer.go index 2b4b3cabd56b4b5e1f2df588a1f6a0f37da7bd03..9027e11044c0e08449a18179af0902d37abe3702 100644 --- a/lex/lexer.go +++ b/lex/lexer.go @@ -63,6 +63,7 @@ type Lexer struct { Width int // width of last rune read from input. Items chan item // channel of scanned items. Depth int // nesting of {} + Mode int // mode based on information so far. } func NewLexer(input string) *Lexer {