From d20d4eef6d54f53b37b35494c647ade061a01608 Mon Sep 17 00:00:00 2001 From: Fontinalis Date: Wed, 6 May 2020 21:13:52 +0200 Subject: [PATCH 01/10] add full language ast --- pkg/language/ast/doc.go | 15 +++ pkg/language/ast/{ast.go => executable.go} | 12 --- pkg/language/ast/typesystem.go | 117 +++++++++++++++++++++ 3 files changed, 132 insertions(+), 12 deletions(-) create mode 100644 pkg/language/ast/doc.go rename pkg/language/ast/{ast.go => executable.go} (96%) create mode 100644 pkg/language/ast/typesystem.go diff --git a/pkg/language/ast/doc.go b/pkg/language/ast/doc.go new file mode 100644 index 0000000..94138d9 --- /dev/null +++ b/pkg/language/ast/doc.go @@ -0,0 +1,15 @@ +package ast + +type Document struct { + Operations []*Operation + Fragments []*Fragment + Definitions []Definition +} + +func NewDocument() *Document { + return &Document{ + Operations: []*Operation{}, + Fragments: []*Fragment{}, + Definitions: []Definition{}, + } +} diff --git a/pkg/language/ast/ast.go b/pkg/language/ast/executable.go similarity index 96% rename from pkg/language/ast/ast.go rename to pkg/language/ast/executable.go index b3c569a..a9fcd11 100644 --- a/pkg/language/ast/ast.go +++ b/pkg/language/ast/executable.go @@ -5,18 +5,6 @@ type Location struct { Line int } -type Document struct { - Operations []*Operation - Fragments []*Fragment -} - -func NewDocument() *Document { - return &Document{ - Operations: []*Operation{}, - Fragments: []*Fragment{}, - } -} - type Fragment struct { Name string TypeCondition string diff --git a/pkg/language/ast/typesystem.go b/pkg/language/ast/typesystem.go new file mode 100644 index 0000000..7ebfacb --- /dev/null +++ b/pkg/language/ast/typesystem.go @@ -0,0 +1,117 @@ +package ast + +type DefinitionKind uint + +const ( + SchemaKind DefinitionKind = iota + ScalarKind + ObjectKind + InterfaceKind + UnionKind + EnumKind + InputObjectKind + DirectiveKinnd +) + +type Definition interface { + Kind() DefinitionKind +} + +type ScalarDefinition struct { + Description string + Name string + Directives []*Directive +} + +func (d *ScalarDefinition) Kind() DefinitionKind { + return ScalarKind +} + +type ObjectDefinition struct { + Description string + Name string + Implements []*NamedType + Directives []*Directive + Fields []*FieldDefinition +} + +func (d *ObjectDefinition) Kind() DefinitionKind { + return ObjectKind +} + +type FieldDefinition struct { + Description string + Name string + Arguments []*InputValueDefinition + Type Type + Directives []*Directive +} + +type InputValueDefinition struct { + Description string + Name string + Type Type + DefaultValue Value + Directives []*Directive +} + +type InterfaceDefinition struct { + Description string + Name string + Directives []*Directive + Fields []*FieldDefinition +} + +func (d *InterfaceDefinition) Kind() DefinitionKind { + return InterfaceKind +} + +type UnionDefinition struct { + Description string + Name string + Directives []*Directive + Members []*NamedType +} + +func (d *UnionDefinition) Kind() DefinitionKind { + return UnionKind +} + +type EnumDefinition struct { + Description string + Name string + Directives []*Directive + Values []*EnumValueDefinition +} + +func (d *EnumDefinition) Kind() DefinitionKind { + return EnumKind +} + +type EnumValueDefinition struct { + Description string + Value *EnumValue + Directives []*Directive +} + +type InputObjectDefinition struct { + Description string + Name string + Directives []*Directive + Fields []*InputValueDefinition +} + +func (d *InputObjectDefinition) Kind() DefinitionKind { + return InputObjectKind +} + +type DirectiveDefinition struct { + Description string + Name string + Directives []*Directive + Arguments []*InputValueDefinition +} + +func (d *DirectiveDefinition) Kind() DefinitionKind { + return DirectiveKinnd +} From 6f8d7d8f9b25fbd1d51506271edc9f81f91296cd Mon Sep 17 00:00:00 2001 From: Fontinalis Date: Wed, 6 May 2020 21:14:43 +0200 Subject: [PATCH 02/10] add scalar and object parsing --- pkg/language/lexer/lexer.go | 4 +- pkg/language/parser/parser.go | 289 +++++++++++++++++++++++++++-- pkg/language/parser/parser_test.go | 43 +++++ 3 files changed, 320 insertions(+), 16 deletions(-) diff --git a/pkg/language/lexer/lexer.go b/pkg/language/lexer/lexer.go index 8bb1431..dd1b532 100644 --- a/pkg/language/lexer/lexer.go +++ b/pkg/language/lexer/lexer.go @@ -24,7 +24,7 @@ const ( // BadToken is bad.. too bad.. BadToken TokenKind = iota // PunctuatorToken has special characters - PunctuatorToken // ! $ ( ) ... : = @ [ ] { | } + PunctuatorToken // ! $ ( ) ... : = @ [ ] { | } & // NameToken has names NameToken // /[_A-Za-z][_0-9A-Za-z]*/ // IntValueToken has iteger numbers @@ -149,7 +149,7 @@ func eatSpace(src *bufio.Reader, tokens chan<- Token, line, col int) (lexFn, int case 'a' <= r && r <= 'z', 'A' <= r && r <= 'Z', r == '_': return lexName, line, col // Checking for Punctation - case strings.ContainsRune("!$().:=@[]{|}", r): + case strings.ContainsRune("!$().:=@[]{|}&", r): if r == '.' { return lexThreeDot(src, tokens, line, col) } diff --git a/pkg/language/parser/parser.go b/pkg/language/parser/parser.go index 922add2..1b5d57f 100644 --- a/pkg/language/parser/parser.go +++ b/pkg/language/parser/parser.go @@ -4,6 +4,8 @@ import ( "bufio" "bytes" "fmt" + "log" + "strings" "github.com/rigglo/gql/pkg/language/ast" "github.com/rigglo/gql/pkg/language/lexer" @@ -17,7 +19,7 @@ func Parse(query []byte) (lexer.Token, *ast.Document, error) { go lexer.Lex(readr, tokens) t, doc, err := parseDocument(tokens) if err != nil && t.Value == "" { - return t, nil, fmt.Errorf("unexpected EOF") + return t, nil, fmt.Errorf("unexpected error: %v", err) } return t, doc, err } @@ -28,21 +30,51 @@ func parseDocument(tokens chan lexer.Token) (lexer.Token, *ast.Document, error) token := <-tokens for { switch { - case token.Kind == lexer.NameToken && token.Value == "fragment": - f := new(ast.Fragment) - token, f, err = parseFragment(tokens) - if err != nil { - return token, nil, err + case token.Kind == lexer.NameToken: + switch token.Value { + case "fragment": + { + f := new(ast.Fragment) + token, f, err = parseFragment(tokens) + if err != nil { + return token, nil, err + } + doc.Fragments = append(doc.Fragments, f) + } + case "query", "mutation", "subscription": + { + op := new(ast.Operation) + token, op, err = parseOperation(token, tokens) + if err != nil { + return token, nil, err + } + doc.Operations = append(doc.Operations, op) + } + case "schema", "scalar", "type", "interface", "union", "enum", "input", "directive": + var def ast.Definition + token, def, err = parseDefinition(token, tokens, "") + if err != nil { + return token, nil, err + } + doc.Definitions = append(doc.Definitions, def) + default: + return token, nil, fmt.Errorf("unexpected token: %s...", token.Value) } - doc.Fragments = append(doc.Fragments, f) break - case token.Kind == lexer.NameToken: - op := new(ast.Operation) - token, op, err = parseOperation(token, tokens) - if err != nil { - return token, nil, err + case token.Kind == lexer.StringValueToken: + desc := strings.Trim(token.Value, `"`) + token = <-tokens + switch token.Value { + case "schema", "scalar", "type", "interface", "union", "enum", "input", "directive": + var def ast.Definition + token, def, err = parseDefinition(token, tokens, desc) + if err != nil { + return token, nil, err + } + doc.Definitions = append(doc.Definitions, def) + default: + return token, nil, fmt.Errorf("unexpected token: '%s', kind: %v", token.Value, token.Kind) } - doc.Operations = append(doc.Operations, op) break case token.Kind == lexer.PunctuatorToken && token.Value == "{": set := []ast.Selection{} @@ -58,9 +90,238 @@ func parseDocument(tokens chan lexer.Token) (lexer.Token, *ast.Document, error) case token.Kind == lexer.BadToken && token.Err == nil: return token, doc, nil default: - return token, nil, fmt.Errorf("unexpected token: %s", token.Value) + return token, nil, fmt.Errorf("unexpected token: %s, kind: %v", token.Value, token.Kind) + } + } +} + +func parseDefinition(token lexer.Token, tokens chan lexer.Token, desc string) (lexer.Token, ast.Definition, error) { + switch token.Value { + case "scalar": + return parseScalar(<-tokens, tokens, desc) + case "type": + return parseObject(<-tokens, tokens, desc) + } + return token, nil, fmt.Errorf("expected a schema, type or directive definition, got: '%s', err: '%v'", token.Value, token.Err) +} + +func parseScalar(token lexer.Token, tokens chan lexer.Token, desc string) (lexer.Token, ast.Definition, error) { + def := &ast.ScalarDefinition{ + Description: desc, + } + + // parse Name + if token.Kind != lexer.NameToken { + log.Println(token.Kind) + return token, nil, fmt.Errorf("expected NameToken, got: '%s', err: '%v'", token.Value, token.Err) + } + def.Name = token.Value + token = <-tokens + + if token.Kind == lexer.PunctuatorToken && token.Value == "@" { + token, ds, err := parseDirectives(tokens) + if err != nil { + return token, nil, err + } + def.Directives = ds + return token, def, nil + } + + return token, def, nil +} + +func parseObject(token lexer.Token, tokens chan lexer.Token, desc string) (lexer.Token, ast.Definition, error) { + def := &ast.ObjectDefinition{ + Description: desc, + Fields: []*ast.FieldDefinition{}, + } + + // parse Name + if token.Kind != lexer.NameToken { + log.Println(token.Kind) + return token, nil, fmt.Errorf("expected NameToken, got: '%s', err: '%v'", token.Value, token.Err) + } + def.Name = token.Value + token = <-tokens + + if token.Kind == lexer.NameToken && token.Value == "implements" { + token = <-tokens + ints := []*ast.NamedType{} + for { + if token.Kind == lexer.NameToken { + ints = append(ints, &ast.NamedType{ + Name: token.Value, + Location: ast.Location{ + Column: token.Col, + Line: token.Line, + }, + }) + token = <-tokens + } else if token.Kind == lexer.PunctuatorToken && token.Value == "&" { + token = <-tokens + } else { + break + } + } + if len(ints) == 0 { + return token, nil, fmt.Errorf("language error..") + } + def.Implements = ints + } + + if token.Kind == lexer.PunctuatorToken && token.Value == "@" { + var ( + ds []*ast.Directive + err error + ) + token, ds, err = parseDirectives(tokens) + if err != nil { + return token, nil, fmt.Errorf("couldn't parse directives: %v", err) + } + def.Directives = ds + } + + if token.Kind == lexer.PunctuatorToken && token.Value == "{" { + token = <-tokens + for { + // quit if it's the end of the field definition list + if token.Kind == lexer.PunctuatorToken && token.Value == "}" { + return <-tokens, def, nil + } + field := &ast.FieldDefinition{} + + // parse optional description + if token.Kind == lexer.StringValueToken { + field.Description = strings.Trim(token.Value, `"`) + token = <-tokens + } + if token.Kind == lexer.NameToken { + field.Name = token.Value + token = <-tokens + } else { + return token, nil, fmt.Errorf("expected NameToken, got token kind '%v', with value '%s'", token.Kind, token.Value) + } + + // parse arguments definition + if token.Kind == lexer.PunctuatorToken && token.Value == "(" { + token = <-tokens + field.Arguments = []*ast.InputValueDefinition{} + for { + if token.Kind == lexer.PunctuatorToken && token.Value == ")" { + token = <-tokens + break + } + var ( + inputDef *ast.InputValueDefinition + err error + ) + token, inputDef, err = parseInputValueDefinition(token, tokens) + if err != nil { + return token, nil, err + } + field.Arguments = append(field.Arguments, inputDef) + } + } + + if token.Kind == lexer.PunctuatorToken && token.Value == ":" { + token = <-tokens + } else { + return token, nil, fmt.Errorf("expected ':', got token kind '%v', with value '%s'", token.Kind, token.Value) + } + + // parse the type of the field + var ( + t ast.Type + err error + ) + token, t, err = parseType(token, tokens) + if err != nil { + return token, nil, err + } + field.Type = t + + // parse directives for field + if token.Kind == lexer.PunctuatorToken && token.Value == "@" { + var ( + ds []*ast.Directive + err error + ) + token, ds, err = parseDirectives(tokens) + if err != nil { + return token, nil, err + } + field.Directives = ds + } + + def.Fields = append(def.Fields, field) + } + } + + return token, def, nil +} + +func parseInputValueDefinition(token lexer.Token, tokens chan lexer.Token) (lexer.Token, *ast.InputValueDefinition, error) { + val := &ast.InputValueDefinition{} + + // parse description for input + if token.Kind == lexer.StringValueToken { + val.Description = strings.Trim(token.Value, `"`) + token = <-tokens + } + + // parse name of the input + if token.Kind == lexer.NameToken { + val.Name = token.Value + token = <-tokens + } else { + return token, nil, fmt.Errorf("expected NameToken, got token kind '%v', with value '%s'", token.Kind, token.Value) + } + + // parse type for the input + if token.Kind == lexer.PunctuatorToken && token.Value == ":" { + token = <-tokens + var ( + t ast.Type + err error + ) + token, t, err = parseType(token, tokens) + if err != nil { + return token, nil, err + } + val.Type = t + } else { + // raise error since ':' and type are required in SDL + return token, nil, fmt.Errorf("expected ':', got token kind '%v', with value '%s'", token.Kind, token.Value) + } + + // parse default value () + if token.Kind == lexer.PunctuatorToken && token.Value == "=" { + token = <-tokens + var ( + v ast.Value + err error + ) + token, v, err = parseValue(token, tokens) + if err != nil { + return token, nil, err } + val.DefaultValue = v } + + // parse directives for input + if token.Kind == lexer.PunctuatorToken && token.Value == "@" { + var ( + ds []*ast.Directive + err error + ) + token, ds, err = parseDirectives(tokens) + if err != nil { + return token, nil, err + } + val.Directives = ds + } + + return token, val, nil } func parseFragment(tokens chan lexer.Token) (lexer.Token, *ast.Fragment, error) { diff --git a/pkg/language/parser/parser_test.go b/pkg/language/parser/parser_test.go index 4fce65a..82c707a 100644 --- a/pkg/language/parser/parser_test.go +++ b/pkg/language/parser/parser_test.go @@ -1,8 +1,11 @@ package parser_test import ( + "log" "testing" + "github.com/rigglo/gql/pkg/language/ast" + "github.com/rigglo/gql/pkg/language/parser" ) @@ -27,3 +30,43 @@ query { // spew.Dump(doc) } + +func TestParseScalar(t *testing.T) { + query := ` + """some scalar""" scalar foo @depricated(reason: "just") + scalar bar` + token, doc, err := parser.Parse([]byte(query)) + if err != nil { + t.Errorf("error: %v, at Line: %v, Col: %v", err, token.Line, token.Col) + return + } + log.Printf("%#v", doc.Definitions[0]) + log.Printf("%#v", doc.Definitions[1]) + // spew.Dump(doc) + +} + +func TestParseObject(t *testing.T) { + query := ` + type Person { + name( + "some example arg" + bar: String + + "some other arg" + foo: Int + ): String + age: Int + picture: Url + } + ` + token, doc, err := parser.Parse([]byte(query)) + if err != nil { + t.Errorf("error: %v, at Line: %v, Col: %v", err, token.Line, token.Col) + return + } + log.Printf("%#v", doc.Definitions[0].(*ast.ObjectDefinition).Fields[0].Arguments[0]) + //log.Printf("%#v", doc.Definitions[1]) + // spew.Dump(doc) + +} From 64c82c7f19606e44263eb1ff610050d9967f7646 Mon Sep 17 00:00:00 2001 From: Fontinalis Date: Wed, 6 May 2020 21:26:06 +0200 Subject: [PATCH 03/10] add interface parsing --- pkg/language/parser/parser.go | 107 +++++++++++++++++++++++++++++ pkg/language/parser/parser_test.go | 25 +++++++ 2 files changed, 132 insertions(+) diff --git a/pkg/language/parser/parser.go b/pkg/language/parser/parser.go index 1b5d57f..1125675 100644 --- a/pkg/language/parser/parser.go +++ b/pkg/language/parser/parser.go @@ -101,6 +101,8 @@ func parseDefinition(token lexer.Token, tokens chan lexer.Token, desc string) (l return parseScalar(<-tokens, tokens, desc) case "type": return parseObject(<-tokens, tokens, desc) + case "interface": + return parseInterface(<-tokens, tokens, desc) } return token, nil, fmt.Errorf("expected a schema, type or directive definition, got: '%s', err: '%v'", token.Value, token.Err) } @@ -260,6 +262,111 @@ func parseObject(token lexer.Token, tokens chan lexer.Token, desc string) (lexer return token, def, nil } +func parseInterface(token lexer.Token, tokens chan lexer.Token, desc string) (lexer.Token, ast.Definition, error) { + def := &ast.InterfaceDefinition{ + Description: desc, + Fields: []*ast.FieldDefinition{}, + } + + // parse Name + if token.Kind != lexer.NameToken { + log.Println(token.Kind) + return token, nil, fmt.Errorf("expected NameToken, got: '%s', err: '%v'", token.Value, token.Err) + } + def.Name = token.Value + token = <-tokens + + if token.Kind == lexer.PunctuatorToken && token.Value == "@" { + var ( + ds []*ast.Directive + err error + ) + token, ds, err = parseDirectives(tokens) + if err != nil { + return token, nil, fmt.Errorf("couldn't parse directives: %v", err) + } + def.Directives = ds + } + + if token.Kind == lexer.PunctuatorToken && token.Value == "{" { + token = <-tokens + for { + // quit if it's the end of the field definition list + if token.Kind == lexer.PunctuatorToken && token.Value == "}" { + return <-tokens, def, nil + } + field := &ast.FieldDefinition{} + + // parse optional description + if token.Kind == lexer.StringValueToken { + field.Description = strings.Trim(token.Value, `"`) + token = <-tokens + } + if token.Kind == lexer.NameToken { + field.Name = token.Value + token = <-tokens + } else { + return token, nil, fmt.Errorf("expected NameToken, got token kind '%v', with value '%s'", token.Kind, token.Value) + } + + // parse arguments definition + if token.Kind == lexer.PunctuatorToken && token.Value == "(" { + token = <-tokens + field.Arguments = []*ast.InputValueDefinition{} + for { + if token.Kind == lexer.PunctuatorToken && token.Value == ")" { + token = <-tokens + break + } + var ( + inputDef *ast.InputValueDefinition + err error + ) + token, inputDef, err = parseInputValueDefinition(token, tokens) + if err != nil { + return token, nil, err + } + field.Arguments = append(field.Arguments, inputDef) + } + } + + if token.Kind == lexer.PunctuatorToken && token.Value == ":" { + token = <-tokens + } else { + return token, nil, fmt.Errorf("expected ':', got token kind '%v', with value '%s'", token.Kind, token.Value) + } + + // parse the type of the field + var ( + t ast.Type + err error + ) + token, t, err = parseType(token, tokens) + if err != nil { + return token, nil, err + } + field.Type = t + + // parse directives for field + if token.Kind == lexer.PunctuatorToken && token.Value == "@" { + var ( + ds []*ast.Directive + err error + ) + token, ds, err = parseDirectives(tokens) + if err != nil { + return token, nil, err + } + field.Directives = ds + } + + def.Fields = append(def.Fields, field) + } + } + + return token, def, nil +} + func parseInputValueDefinition(token lexer.Token, tokens chan lexer.Token) (lexer.Token, *ast.InputValueDefinition, error) { val := &ast.InputValueDefinition{} diff --git a/pkg/language/parser/parser_test.go b/pkg/language/parser/parser_test.go index 82c707a..f6f0dd6 100644 --- a/pkg/language/parser/parser_test.go +++ b/pkg/language/parser/parser_test.go @@ -70,3 +70,28 @@ func TestParseObject(t *testing.T) { // spew.Dump(doc) } + +func TestParseInterface(t *testing.T) { + query := ` + interface Person { + name( + "some example arg" + bar: String + + "some other arg" + foo: Int + ): String + age: Int + picture: Url + } + ` + token, doc, err := parser.Parse([]byte(query)) + if err != nil { + t.Errorf("error: %v, at Line: %v, Col: %v", err, token.Line, token.Col) + return + } + log.Printf("%#v", doc.Definitions[0].(*ast.InterfaceDefinition).Fields[0].Arguments[0]) + //log.Printf("%#v", doc.Definitions[1]) + // spew.Dump(doc) + +} From fc5b2d64fefee7ec9c91f8ed6ba35dd2a6a2c648 Mon Sep 17 00:00:00 2001 From: Fontinalis Date: Fri, 8 May 2020 08:01:17 +0200 Subject: [PATCH 04/10] add parser.ParseDefinition funcion --- pkg/language/parser/parser.go | 27 ++++++++++++++++++++++++--- pkg/language/parser/parser_test.go | 12 +++++++----- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/pkg/language/parser/parser.go b/pkg/language/parser/parser.go index 1125675..3e11c8c 100644 --- a/pkg/language/parser/parser.go +++ b/pkg/language/parser/parser.go @@ -11,10 +11,10 @@ import ( "github.com/rigglo/gql/pkg/language/lexer" ) -// Parse parses a gql query -func Parse(query []byte) (lexer.Token, *ast.Document, error) { +// Parse parses a gql document +func Parse(document []byte) (lexer.Token, *ast.Document, error) { tokens := make(chan lexer.Token) - src := bytes.NewReader(query) + src := bytes.NewReader(document) readr := bufio.NewReader(src) go lexer.Lex(readr, tokens) t, doc, err := parseDocument(tokens) @@ -24,6 +24,27 @@ func Parse(query []byte) (lexer.Token, *ast.Document, error) { return t, doc, err } +// ParseDefinition parses a single schema, type, directive definition +func ParseDefinition(definition []byte) (ast.Definition, error) { + tokens := make(chan lexer.Token) + src := bytes.NewReader(definition) + readr := bufio.NewReader(src) + go lexer.Lex(readr, tokens) + token := <-tokens + desc := "" + if token.Kind == lexer.StringValueToken { + desc = strings.Trim(token.Value, `"`) + token = <-tokens + } + t, def, err := parseDefinition(token, tokens, desc) + if err != nil { + return nil, err + } else if t.Kind != lexer.BadToken && err == nil { + return nil, fmt.Errorf("invalid token after definition: '%s'", t.Value) + } + return def, nil +} + func parseDocument(tokens chan lexer.Token) (lexer.Token, *ast.Document, error) { doc := ast.NewDocument() var err error diff --git a/pkg/language/parser/parser_test.go b/pkg/language/parser/parser_test.go index f6f0dd6..9437861 100644 --- a/pkg/language/parser/parser_test.go +++ b/pkg/language/parser/parser_test.go @@ -33,7 +33,9 @@ query { func TestParseScalar(t *testing.T) { query := ` - """some scalar""" scalar foo @depricated(reason: "just") + """some scalar""" + scalar foo @depricated(reason: "just") + scalar bar` token, doc, err := parser.Parse([]byte(query)) if err != nil { @@ -65,7 +67,7 @@ func TestParseObject(t *testing.T) { t.Errorf("error: %v, at Line: %v, Col: %v", err, token.Line, token.Col) return } - log.Printf("%#v", doc.Definitions[0].(*ast.ObjectDefinition).Fields[0].Arguments[0]) + log.Printf("%#v", doc.Definitions[0].(*ast.ObjectDefinition)) //log.Printf("%#v", doc.Definitions[1]) // spew.Dump(doc) @@ -85,12 +87,12 @@ func TestParseInterface(t *testing.T) { picture: Url } ` - token, doc, err := parser.Parse([]byte(query)) + def, err := parser.ParseDefinition([]byte(query)) if err != nil { - t.Errorf("error: %v, at Line: %v, Col: %v", err, token.Line, token.Col) + t.Errorf("error: %v", err) return } - log.Printf("%#v", doc.Definitions[0].(*ast.InterfaceDefinition).Fields[0].Arguments[0]) + log.Printf("%#v", def.(*ast.InterfaceDefinition)) //log.Printf("%#v", doc.Definitions[1]) // spew.Dump(doc) From 74b9254d75b1e164ad963c80b824a8324df0bcb0 Mon Sep 17 00:00:00 2001 From: Fontinalis Date: Fri, 8 May 2020 11:06:30 +0200 Subject: [PATCH 05/10] add union parsing --- pkg/language/parser/parser.go | 68 ++++++++++++++++++++++++++++++ pkg/language/parser/parser_test.go | 15 +++++++ 2 files changed, 83 insertions(+) diff --git a/pkg/language/parser/parser.go b/pkg/language/parser/parser.go index 3e11c8c..3785199 100644 --- a/pkg/language/parser/parser.go +++ b/pkg/language/parser/parser.go @@ -124,6 +124,8 @@ func parseDefinition(token lexer.Token, tokens chan lexer.Token, desc string) (l return parseObject(<-tokens, tokens, desc) case "interface": return parseInterface(<-tokens, tokens, desc) + case "union": + return parseUnion(<-tokens, tokens, desc) } return token, nil, fmt.Errorf("expected a schema, type or directive definition, got: '%s', err: '%v'", token.Value, token.Err) } @@ -388,6 +390,72 @@ func parseInterface(token lexer.Token, tokens chan lexer.Token, desc string) (le return token, def, nil } +func parseUnion(token lexer.Token, tokens chan lexer.Token, desc string) (lexer.Token, ast.Definition, error) { + def := &ast.UnionDefinition{ + Description: desc, + } + + // parse Name + if token.Kind != lexer.NameToken { + log.Println(token.Kind) + return token, nil, fmt.Errorf("expected NameToken, got: '%s', err: '%v'", token.Value, token.Err) + } + def.Name = token.Value + token = <-tokens + + if token.Kind == lexer.PunctuatorToken && token.Value == "@" { + var ( + ds []*ast.Directive + err error + ) + token, ds, err = parseDirectives(tokens) + if err != nil { + return token, nil, fmt.Errorf("couldn't parse directives: %v", err) + } + def.Directives = ds + } + + if token.Kind == lexer.PunctuatorToken && token.Value == "=" { + def.Members = []*ast.NamedType{} + token = <-tokens + if token.Kind == lexer.PunctuatorToken && token.Value == "|" { + token = <-tokens + } + if token.Kind == lexer.NameToken { + def.Members = append(def.Members, &ast.NamedType{ + Location: ast.Location{ + Column: token.Col, + Line: token.Line, + }, + Name: token.Value, + }) + token = <-tokens + } else { + return token, nil, fmt.Errorf("expected Name token, got '%s'", token.Value) + } + for { + if token.Kind == lexer.PunctuatorToken && token.Value == "|" { + token = <-tokens + } else { + return token, def, nil + } + if token.Kind == lexer.NameToken { + def.Members = append(def.Members, &ast.NamedType{ + Location: ast.Location{ + Column: token.Col, + Line: token.Line, + }, + Name: token.Value, + }) + token = <-tokens + } else { + return token, nil, fmt.Errorf("expected Name token, got '%s'", token.Value) + } + } + } + return token, def, nil +} + func parseInputValueDefinition(token lexer.Token, tokens chan lexer.Token) (lexer.Token, *ast.InputValueDefinition, error) { val := &ast.InputValueDefinition{} diff --git a/pkg/language/parser/parser_test.go b/pkg/language/parser/parser_test.go index 9437861..1d0cee7 100644 --- a/pkg/language/parser/parser_test.go +++ b/pkg/language/parser/parser_test.go @@ -97,3 +97,18 @@ func TestParseInterface(t *testing.T) { // spew.Dump(doc) } + +func TestParseUnion(t *testing.T) { + query := ` + union Entity @asd = Person | Company | Animal + ` + def, err := parser.ParseDefinition([]byte(query)) + if err != nil { + t.Errorf("error: %v", err) + return + } + log.Printf("%#v", def.(*ast.UnionDefinition)) + //log.Printf("%#v", doc.Definitions[1]) + // spew.Dump(doc) + +} From 0cf281dd0e2cc761f0b1571ced262b25348595da Mon Sep 17 00:00:00 2001 From: Fontinalis Date: Fri, 8 May 2020 11:24:15 +0200 Subject: [PATCH 06/10] add enum parser --- pkg/language/parser/parser.go | 72 ++++++++++++++++++++++++++++++ pkg/language/parser/parser_test.go | 22 +++++++++ 2 files changed, 94 insertions(+) diff --git a/pkg/language/parser/parser.go b/pkg/language/parser/parser.go index 3785199..0f6702d 100644 --- a/pkg/language/parser/parser.go +++ b/pkg/language/parser/parser.go @@ -126,6 +126,8 @@ func parseDefinition(token lexer.Token, tokens chan lexer.Token, desc string) (l return parseInterface(<-tokens, tokens, desc) case "union": return parseUnion(<-tokens, tokens, desc) + case "enum": + return parseEnum(<-tokens, tokens, desc) } return token, nil, fmt.Errorf("expected a schema, type or directive definition, got: '%s', err: '%v'", token.Value, token.Err) } @@ -456,6 +458,76 @@ func parseUnion(token lexer.Token, tokens chan lexer.Token, desc string) (lexer. return token, def, nil } +func parseEnum(token lexer.Token, tokens chan lexer.Token, desc string) (lexer.Token, ast.Definition, error) { + def := &ast.EnumDefinition{ + Description: desc, + } + + // parse Name + if token.Kind != lexer.NameToken { + log.Println(token.Kind) + return token, nil, fmt.Errorf("expected NameToken, got: '%s', err: '%v'", token.Value, token.Err) + } + def.Name = token.Value + token = <-tokens + + // parse directives for the enum type + if token.Kind == lexer.PunctuatorToken && token.Value == "@" { + var ( + ds []*ast.Directive + err error + ) + token, ds, err = parseDirectives(tokens) + if err != nil { + return token, nil, fmt.Errorf("couldn't parse directives: %v", err) + } + def.Directives = ds + } + + if token.Kind == lexer.PunctuatorToken && token.Value == "{" { + def.Values = []*ast.EnumValueDefinition{} + token = <-tokens + + // parse all the enum value definitions + for { + if token.Kind == lexer.PunctuatorToken && token.Value == "}" { + return <-tokens, def, nil + } + + enumV := &ast.EnumValueDefinition{} + if token.Kind == lexer.StringValueToken { + enumV.Description = strings.Trim(token.Value, `"`) + token = <-tokens + } + if token.Kind == lexer.NameToken { + enumV.Value = &ast.EnumValue{ + Location: ast.Location{ + Column: token.Col, + Line: token.Line, + }, + Value: token.Value, + } + token = <-tokens + } else { + return token, nil, fmt.Errorf("expected Name token, got '%v'", token.Value) + } + if token.Kind == lexer.PunctuatorToken && token.Value == "@" { + var ( + ds []*ast.Directive + err error + ) + token, ds, err = parseDirectives(tokens) + if err != nil { + return token, nil, fmt.Errorf("couldn't parse directives on enum value: %v", err) + } + enumV.Directives = ds + } + def.Values = append(def.Values, enumV) + } + } + return token, def, nil +} + func parseInputValueDefinition(token lexer.Token, tokens chan lexer.Token) (lexer.Token, *ast.InputValueDefinition, error) { val := &ast.InputValueDefinition{} diff --git a/pkg/language/parser/parser_test.go b/pkg/language/parser/parser_test.go index 1d0cee7..8a1b2ff 100644 --- a/pkg/language/parser/parser_test.go +++ b/pkg/language/parser/parser_test.go @@ -112,3 +112,25 @@ func TestParseUnion(t *testing.T) { // spew.Dump(doc) } + +func TestParseEnum(t *testing.T) { + query := ` + enum Direction { + "the north remembers.." + NORTH @depricated(reason: "got is over.. :(") + + EAST + SOUTH + WEST + } + ` + def, err := parser.ParseDefinition([]byte(query)) + if err != nil { + t.Errorf("error: %v", err) + return + } + log.Printf("%#v", def.(*ast.EnumDefinition)) + //log.Printf("%#v", doc.Definitions[1]) + // spew.Dump(doc) + +} From ceaf77160272d847a34cf159b8a94699b7730bc6 Mon Sep 17 00:00:00 2001 From: Fontinalis Date: Fri, 8 May 2020 11:32:40 +0200 Subject: [PATCH 07/10] add input object definition parsning --- pkg/language/parser/parser.go | 51 ++++++++++++++++++++++++++++++ pkg/language/parser/parser_test.go | 18 +++++++++++ 2 files changed, 69 insertions(+) diff --git a/pkg/language/parser/parser.go b/pkg/language/parser/parser.go index 0f6702d..ca1859c 100644 --- a/pkg/language/parser/parser.go +++ b/pkg/language/parser/parser.go @@ -128,6 +128,8 @@ func parseDefinition(token lexer.Token, tokens chan lexer.Token, desc string) (l return parseUnion(<-tokens, tokens, desc) case "enum": return parseEnum(<-tokens, tokens, desc) + case "input": + return parseInputObject(<-tokens, tokens, desc) } return token, nil, fmt.Errorf("expected a schema, type or directive definition, got: '%s', err: '%v'", token.Value, token.Err) } @@ -528,6 +530,55 @@ func parseEnum(token lexer.Token, tokens chan lexer.Token, desc string) (lexer.T return token, def, nil } +func parseInputObject(token lexer.Token, tokens chan lexer.Token, desc string) (lexer.Token, ast.Definition, error) { + def := &ast.InputObjectDefinition{ + Description: desc, + } + + // parse Name + if token.Kind != lexer.NameToken { + log.Println(token.Kind) + return token, nil, fmt.Errorf("expected NameToken, got: '%s', err: '%v'", token.Value, token.Err) + } + def.Name = token.Value + token = <-tokens + + // parse directives for the enum type + if token.Kind == lexer.PunctuatorToken && token.Value == "@" { + var ( + ds []*ast.Directive + err error + ) + token, ds, err = parseDirectives(tokens) + if err != nil { + return token, nil, fmt.Errorf("couldn't parse directives: %v", err) + } + def.Directives = ds + } + + // parse input object field definitions + if token.Kind == lexer.PunctuatorToken && token.Value == "{" { + token = <-tokens + def.Fields = []*ast.InputValueDefinition{} + for { + if token.Kind == lexer.PunctuatorToken && token.Value == "}" { + token = <-tokens + break + } + var ( + inputDef *ast.InputValueDefinition + err error + ) + token, inputDef, err = parseInputValueDefinition(token, tokens) + if err != nil { + return token, nil, err + } + def.Fields = append(def.Fields, inputDef) + } + } + return token, def, nil +} + func parseInputValueDefinition(token lexer.Token, tokens chan lexer.Token) (lexer.Token, *ast.InputValueDefinition, error) { val := &ast.InputValueDefinition{} diff --git a/pkg/language/parser/parser_test.go b/pkg/language/parser/parser_test.go index 8a1b2ff..80fe993 100644 --- a/pkg/language/parser/parser_test.go +++ b/pkg/language/parser/parser_test.go @@ -134,3 +134,21 @@ func TestParseEnum(t *testing.T) { // spew.Dump(doc) } + +func TestParseInputObject(t *testing.T) { + query := ` + input Point2D { + x: Float + y: Float + } + ` + def, err := parser.ParseDefinition([]byte(query)) + if err != nil { + t.Errorf("error: %v", err) + return + } + log.Printf("%#v", def.(*ast.InputObjectDefinition)) + //log.Printf("%#v", doc.Definitions[1]) + // spew.Dump(doc) + +} From e37d853c5431dec27b63d53cba232737fdefbfaf Mon Sep 17 00:00:00 2001 From: Fontinalis Date: Fri, 8 May 2020 12:58:15 +0200 Subject: [PATCH 08/10] parse directive definitions --- pkg/language/ast/directive.go | 34 +++++++++++++++++++++ pkg/language/ast/typesystem.go | 2 +- pkg/language/parser/parser.go | 49 ++++++++++++++++++++++++++++++ pkg/language/parser/parser_test.go | 14 +++++++++ 4 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 pkg/language/ast/directive.go diff --git a/pkg/language/ast/directive.go b/pkg/language/ast/directive.go new file mode 100644 index 0000000..798acbe --- /dev/null +++ b/pkg/language/ast/directive.go @@ -0,0 +1,34 @@ +package ast + +import ( + "fmt" + "strings" +) + +const ( + // Executable directive locations + QueryDirectiveLocation string = "QUERY" + MutationDirectiveLocation string = "MUTATION" + SubscriptionDirectiveLocation string = "SUBSCRIPTION" + FieldDirectiveLocation string = "FIELD" + FragmentDefinitionDirectiveLocation string = "FRAGMENT_DEFINITION" + FragmentSpreadDirectiveLocation string = "FRAGMENT_SPREAD" + InlineFragmentDirectiveLocation string = "INLINE_FRAGMENT" + + // TypeSystem directive locations + SchemaDirectiveLocation string = "SCHEMA" + ScalarDirectiveLocation string = "SCALAR" + ObjectDirectiveLocation string = "OBJECT" + FieldDefinitionDirectiveLocation string = "FIELD_DEFINITION" + ArguentDefinitionDirectiveLocation string = "ARGUMENT_DEFINITION" + InterfaceDirectiveLocation string = "INTERFACE" + UnionDirectiveLocation string = "UNION" + EnumDirectiveLocation string = "ENUM" + EnumValueDirectiveLocation string = "ENUM_VALUE" + InputObjectDirectiveLocation string = "INPUT_OBJECT" + InputFieldDefinitionDirectiveLocation string = "INPUT_FIELD_DEFINITION" +) + +func IsValidDirective(d string) bool { + return strings.Contains("QUERY MUTATION SUBSCRIPTION FIELD FRAGMENT_DEFINITION FRAGMENT_SPREAD INLINE_FRAGMENT SCHEMA SCALAR OBJECT FIELD_DEFINITION ARGUMENT_DEFINITION INTERFACE UNION ENUM ENUM_VALUE INPUT_OBJECT INPUT_FIELD_DEFINITION", fmt.Sprintf(" %s ", d)) +} diff --git a/pkg/language/ast/typesystem.go b/pkg/language/ast/typesystem.go index 7ebfacb..e1c139d 100644 --- a/pkg/language/ast/typesystem.go +++ b/pkg/language/ast/typesystem.go @@ -108,7 +108,7 @@ func (d *InputObjectDefinition) Kind() DefinitionKind { type DirectiveDefinition struct { Description string Name string - Directives []*Directive + Locations []string Arguments []*InputValueDefinition } diff --git a/pkg/language/parser/parser.go b/pkg/language/parser/parser.go index ca1859c..88b47a6 100644 --- a/pkg/language/parser/parser.go +++ b/pkg/language/parser/parser.go @@ -130,6 +130,8 @@ func parseDefinition(token lexer.Token, tokens chan lexer.Token, desc string) (l return parseEnum(<-tokens, tokens, desc) case "input": return parseInputObject(<-tokens, tokens, desc) + case "directive": + return parseDirectiveDefinition(<-tokens, tokens, desc) } return token, nil, fmt.Errorf("expected a schema, type or directive definition, got: '%s', err: '%v'", token.Value, token.Err) } @@ -530,6 +532,53 @@ func parseEnum(token lexer.Token, tokens chan lexer.Token, desc string) (lexer.T return token, def, nil } +func parseDirectiveDefinition(token lexer.Token, tokens chan lexer.Token, desc string) (lexer.Token, ast.Definition, error) { + def := &ast.DirectiveDefinition{ + Description: desc, + } + + // parse Name + if token.Kind == lexer.PunctuatorToken && token.Value == "@" { + token = <-tokens + } else { + return token, nil, fmt.Errorf("expected token '@', got: '%s'", token.Value) + } + if token.Kind != lexer.NameToken { + return token, nil, fmt.Errorf("expected NameToken, got: '%s', err: '%v'", token.Value, token.Err) + } + def.Name = token.Value + token = <-tokens + + if token.Kind == lexer.NameToken && token.Value == "on" { + def.Locations = []string{} + token = <-tokens + + if token.Kind == lexer.PunctuatorToken && token.Value == "|" { + token = <-tokens + } + if token.Kind == lexer.NameToken { + def.Locations = append(def.Locations, token.Value) + token = <-tokens + } else { + return token, nil, fmt.Errorf("expected Name token, got '%s'", token.Value) + } + for { + if token.Kind == lexer.PunctuatorToken && token.Value == "|" { + token = <-tokens + } else { + return token, def, nil + } + if token.Kind == lexer.NameToken && ast.IsValidDirective(token.Value) { + def.Locations = append(def.Locations, token.Value) + token = <-tokens + } else { + return token, nil, fmt.Errorf("expected Name token with a valid directive locaton, got '%s'", token.Value) + } + } + } + return token, def, nil +} + func parseInputObject(token lexer.Token, tokens chan lexer.Token, desc string) (lexer.Token, ast.Definition, error) { def := &ast.InputObjectDefinition{ Description: desc, diff --git a/pkg/language/parser/parser_test.go b/pkg/language/parser/parser_test.go index 80fe993..7ae845f 100644 --- a/pkg/language/parser/parser_test.go +++ b/pkg/language/parser/parser_test.go @@ -152,3 +152,17 @@ func TestParseInputObject(t *testing.T) { // spew.Dump(doc) } + +func TestParseDirective(t *testing.T) { + query := ` + directive @foo on FIELD_DEFINITION + ` + def, err := parser.ParseDefinition([]byte(query)) + if err != nil { + t.Errorf("error: %v", err) + return + } + log.Printf("%#v", def.(*ast.DirectiveDefinition)) + //log.Printf("%#v", doc.Definitions[1]) + // spew.Dump(doc) +} From c43b04b7d758b1df7913aa65dabe84e742e0e961 Mon Sep 17 00:00:00 2001 From: Fontinalis Date: Sun, 10 May 2020 16:52:48 +0200 Subject: [PATCH 09/10] finish full gql language support --- pkg/language/ast/directive.go | 34 ---------- pkg/language/ast/doc.go | 33 +++++++++ pkg/language/ast/typesystem.go | 14 +++- pkg/language/parser/parser.go | 104 ++++++++++++++++++++++++++++- pkg/language/parser/parser_test.go | 17 +++++ 5 files changed, 163 insertions(+), 39 deletions(-) delete mode 100644 pkg/language/ast/directive.go diff --git a/pkg/language/ast/directive.go b/pkg/language/ast/directive.go deleted file mode 100644 index 798acbe..0000000 --- a/pkg/language/ast/directive.go +++ /dev/null @@ -1,34 +0,0 @@ -package ast - -import ( - "fmt" - "strings" -) - -const ( - // Executable directive locations - QueryDirectiveLocation string = "QUERY" - MutationDirectiveLocation string = "MUTATION" - SubscriptionDirectiveLocation string = "SUBSCRIPTION" - FieldDirectiveLocation string = "FIELD" - FragmentDefinitionDirectiveLocation string = "FRAGMENT_DEFINITION" - FragmentSpreadDirectiveLocation string = "FRAGMENT_SPREAD" - InlineFragmentDirectiveLocation string = "INLINE_FRAGMENT" - - // TypeSystem directive locations - SchemaDirectiveLocation string = "SCHEMA" - ScalarDirectiveLocation string = "SCALAR" - ObjectDirectiveLocation string = "OBJECT" - FieldDefinitionDirectiveLocation string = "FIELD_DEFINITION" - ArguentDefinitionDirectiveLocation string = "ARGUMENT_DEFINITION" - InterfaceDirectiveLocation string = "INTERFACE" - UnionDirectiveLocation string = "UNION" - EnumDirectiveLocation string = "ENUM" - EnumValueDirectiveLocation string = "ENUM_VALUE" - InputObjectDirectiveLocation string = "INPUT_OBJECT" - InputFieldDefinitionDirectiveLocation string = "INPUT_FIELD_DEFINITION" -) - -func IsValidDirective(d string) bool { - return strings.Contains("QUERY MUTATION SUBSCRIPTION FIELD FRAGMENT_DEFINITION FRAGMENT_SPREAD INLINE_FRAGMENT SCHEMA SCALAR OBJECT FIELD_DEFINITION ARGUMENT_DEFINITION INTERFACE UNION ENUM ENUM_VALUE INPUT_OBJECT INPUT_FIELD_DEFINITION", fmt.Sprintf(" %s ", d)) -} diff --git a/pkg/language/ast/doc.go b/pkg/language/ast/doc.go index 94138d9..0fe0d70 100644 --- a/pkg/language/ast/doc.go +++ b/pkg/language/ast/doc.go @@ -1,5 +1,10 @@ package ast +import ( + "fmt" + "strings" +) + type Document struct { Operations []*Operation Fragments []*Fragment @@ -13,3 +18,31 @@ func NewDocument() *Document { Definitions: []Definition{}, } } + +const ( + // Executable directive locations + QueryDirectiveLocation string = "QUERY" + MutationDirectiveLocation string = "MUTATION" + SubscriptionDirectiveLocation string = "SUBSCRIPTION" + FieldDirectiveLocation string = "FIELD" + FragmentDefinitionDirectiveLocation string = "FRAGMENT_DEFINITION" + FragmentSpreadDirectiveLocation string = "FRAGMENT_SPREAD" + InlineFragmentDirectiveLocation string = "INLINE_FRAGMENT" + + // TypeSystem directive locations + SchemaDirectiveLocation string = "SCHEMA" + ScalarDirectiveLocation string = "SCALAR" + ObjectDirectiveLocation string = "OBJECT" + FieldDefinitionDirectiveLocation string = "FIELD_DEFINITION" + ArguentDefinitionDirectiveLocation string = "ARGUMENT_DEFINITION" + InterfaceDirectiveLocation string = "INTERFACE" + UnionDirectiveLocation string = "UNION" + EnumDirectiveLocation string = "ENUM" + EnumValueDirectiveLocation string = "ENUM_VALUE" + InputObjectDirectiveLocation string = "INPUT_OBJECT" + InputFieldDefinitionDirectiveLocation string = "INPUT_FIELD_DEFINITION" +) + +func IsValidDirective(d string) bool { + return strings.Contains("QUERY MUTATION SUBSCRIPTION FIELD FRAGMENT_DEFINITION FRAGMENT_SPREAD INLINE_FRAGMENT SCHEMA SCALAR OBJECT FIELD_DEFINITION ARGUMENT_DEFINITION INTERFACE UNION ENUM ENUM_VALUE INPUT_OBJECT INPUT_FIELD_DEFINITION", fmt.Sprintf(" %s ", d)) +} diff --git a/pkg/language/ast/typesystem.go b/pkg/language/ast/typesystem.go index e1c139d..432a671 100644 --- a/pkg/language/ast/typesystem.go +++ b/pkg/language/ast/typesystem.go @@ -10,13 +10,23 @@ const ( UnionKind EnumKind InputObjectKind - DirectiveKinnd + DirectiveKind ) type Definition interface { Kind() DefinitionKind } +type SchemaDefinition struct { + Name string + Directives []*Directive + RootOperations map[OperationType]*NamedType +} + +func (d *SchemaDefinition) Kind() DefinitionKind { + return SchemaKind +} + type ScalarDefinition struct { Description string Name string @@ -113,5 +123,5 @@ type DirectiveDefinition struct { } func (d *DirectiveDefinition) Kind() DefinitionKind { - return DirectiveKinnd + return DirectiveKind } diff --git a/pkg/language/parser/parser.go b/pkg/language/parser/parser.go index 88b47a6..dcfcb4d 100644 --- a/pkg/language/parser/parser.go +++ b/pkg/language/parser/parser.go @@ -31,10 +31,23 @@ func ParseDefinition(definition []byte) (ast.Definition, error) { readr := bufio.NewReader(src) go lexer.Lex(readr, tokens) token := <-tokens + desc := "" if token.Kind == lexer.StringValueToken { desc = strings.Trim(token.Value, `"`) token = <-tokens + if token.Kind == lexer.NameToken && token.Value == "schema" { + return nil, fmt.Errorf("expected everything but 'schema'.. a Schema does NOT have description") + } + } + if token.Kind == lexer.NameToken && token.Value == "schema" { + t, def, err := parseSchema(<-tokens, tokens) + if err != nil { + return nil, err + } else if t.Kind != lexer.BadToken && err == nil { + return nil, fmt.Errorf("invalid token after schema definition: '%s'", t.Value) + } + return def, nil } t, def, err := parseDefinition(token, tokens, desc) if err != nil { @@ -71,22 +84,29 @@ func parseDocument(tokens chan lexer.Token) (lexer.Token, *ast.Document, error) } doc.Operations = append(doc.Operations, op) } - case "schema", "scalar", "type", "interface", "union", "enum", "input", "directive": + case "scalar", "type", "interface", "union", "enum", "input", "directive": var def ast.Definition token, def, err = parseDefinition(token, tokens, "") if err != nil { return token, nil, err } doc.Definitions = append(doc.Definitions, def) + case "schema": + var def ast.Definition + token, def, err = parseSchema(token, tokens) + if err != nil { + return token, nil, err + } + doc.Definitions = append(doc.Definitions, def) default: - return token, nil, fmt.Errorf("unexpected token: %s...", token.Value) + return token, nil, fmt.Errorf("unexpected token: %s", token.Value) } break case token.Kind == lexer.StringValueToken: desc := strings.Trim(token.Value, `"`) token = <-tokens switch token.Value { - case "schema", "scalar", "type", "interface", "union", "enum", "input", "directive": + case "scalar", "type", "interface", "union", "enum", "input", "directive": var def ast.Definition token, def, err = parseDefinition(token, tokens, desc) if err != nil { @@ -136,6 +156,84 @@ func parseDefinition(token lexer.Token, tokens chan lexer.Token, desc string) (l return token, nil, fmt.Errorf("expected a schema, type or directive definition, got: '%s', err: '%v'", token.Value, token.Err) } +func parseSchema(token lexer.Token, tokens chan lexer.Token) (lexer.Token, ast.Definition, error) { + def := new(ast.SchemaDefinition) + + // parse Name + if token.Kind != lexer.NameToken { + log.Println(token.Kind) + return token, nil, fmt.Errorf("expected NameToken, got: '%s', err: '%v'", token.Value, token.Err) + } + def.Name = token.Value + token = <-tokens + + if token.Kind == lexer.PunctuatorToken && token.Value == "@" { + var ( + ds []*ast.Directive + err error + ) + token, ds, err = parseDirectives(tokens) + if err != nil { + return token, nil, err + } + def.Directives = ds + } + + if token.Kind == lexer.PunctuatorToken && token.Value == "{" { + def.RootOperations = map[ast.OperationType]*ast.NamedType{} + + token = <-tokens + for { + // quit if it's the end of the field definition list + if token.Kind == lexer.PunctuatorToken && token.Value == "}" { + return <-tokens, def, nil + } + + var ot ast.OperationType + + // parse operation type + if token.Kind == lexer.NameToken { + switch token.Value { + case "query": + ot = ast.Query + case "mutation": + ot = ast.Mutation + case "subscription": + ot = ast.Subscription + default: + return token, nil, fmt.Errorf("expected root operation type, one of 'query', 'mutation' or 'subscription', got '%s'", token.Value) + } + if _, ok := def.RootOperations[ot]; ok { + return token, nil, fmt.Errorf("the given operation type '%s' is already defined in the schema definition", token.Value) + } + token = <-tokens + } else { + return token, nil, fmt.Errorf("expected NameToken, got '%s'", token.Value) + } + + if token.Kind == lexer.PunctuatorToken && token.Value == ":" { + token = <-tokens + } else { + return token, nil, fmt.Errorf("expected token ':', got '%s'", token.Value) + } + + if token.Kind == lexer.NameToken { + def.RootOperations[ot] = &ast.NamedType{ + Name: token.Value, + Location: ast.Location{ + Column: token.Col, + Line: token.Line, + }, + } + token = <-tokens + } else { + return token, nil, fmt.Errorf("expected NameToken, got '%s'", token.Value) + } + } + } + return token, nil, fmt.Errorf("expected '{', and a list of root operation types, got '%s'", token.Value) +} + func parseScalar(token lexer.Token, tokens chan lexer.Token, desc string) (lexer.Token, ast.Definition, error) { def := &ast.ScalarDefinition{ Description: desc, diff --git a/pkg/language/parser/parser_test.go b/pkg/language/parser/parser_test.go index 7ae845f..8e8d92d 100644 --- a/pkg/language/parser/parser_test.go +++ b/pkg/language/parser/parser_test.go @@ -166,3 +166,20 @@ func TestParseDirective(t *testing.T) { //log.Printf("%#v", doc.Definitions[1]) // spew.Dump(doc) } + +func TestParseSchema(t *testing.T) { + query := ` + schema MySchema @somedirective { + query: MyRootQuery + mutation: MyRootMutation + } + ` + def, err := parser.ParseDefinition([]byte(query)) + if err != nil { + t.Errorf("error: %v", err) + return + } + log.Printf("%#v", def.(*ast.SchemaDefinition)) + //log.Printf("%#v", doc.Definitions[1]) + // spew.Dump(doc) +} From af37fd7b02b4d79fd893b04b730a05e9e5e0212f Mon Sep 17 00:00:00 2001 From: Fontinalis Date: Sun, 10 May 2020 17:02:31 +0200 Subject: [PATCH 10/10] update parser with custom error and removed token from Parse results --- execution.go | 6 ++-- pkg/language/parser/parser.go | 50 +++++++++++++++++++++++++----- pkg/language/parser/parser_test.go | 12 +++---- 3 files changed, 52 insertions(+), 16 deletions(-) diff --git a/execution.go b/execution.go index a252d0f..c75864c 100644 --- a/execution.go +++ b/execution.go @@ -51,7 +51,7 @@ func (e *Executor) Execute(ctx context.Context, p Params) *Result { } callExtensions(ctx, e.config.Extensions, EventParseStart, nil) - t, doc, err := parser.Parse([]byte(p.Query)) + doc, err := parser.Parse([]byte(p.Query)) callExtensions(ctx, e.config.Extensions, EventParseFinish, err) if err != nil { return &Result{ @@ -60,8 +60,8 @@ func (e *Executor) Execute(ctx context.Context, p Params) *Result { err.Error(), []*ErrorLocation{ { - Column: t.Col, - Line: t.Line, + Column: err.(*parser.ParserError).Column, + Line: err.(*parser.ParserError).Line, }, }, nil, diff --git a/pkg/language/parser/parser.go b/pkg/language/parser/parser.go index dcfcb4d..ceff854 100644 --- a/pkg/language/parser/parser.go +++ b/pkg/language/parser/parser.go @@ -11,17 +11,33 @@ import ( "github.com/rigglo/gql/pkg/language/lexer" ) +type ParserError struct { + Message string + Line int + Column int + Token lexer.Token +} + +func (e *ParserError) Error() string { + return e.Message +} + // Parse parses a gql document -func Parse(document []byte) (lexer.Token, *ast.Document, error) { +func Parse(document []byte) (*ast.Document, error) { tokens := make(chan lexer.Token) src := bytes.NewReader(document) readr := bufio.NewReader(src) go lexer.Lex(readr, tokens) t, doc, err := parseDocument(tokens) if err != nil && t.Value == "" { - return t, nil, fmt.Errorf("unexpected error: %v", err) + return nil, &ParserError{ + Message: err.Error(), + Line: t.Line, + Column: t.Col, + Token: t, + } } - return t, doc, err + return doc, nil } // ParseDefinition parses a single schema, type, directive definition @@ -37,7 +53,12 @@ func ParseDefinition(definition []byte) (ast.Definition, error) { desc = strings.Trim(token.Value, `"`) token = <-tokens if token.Kind == lexer.NameToken && token.Value == "schema" { - return nil, fmt.Errorf("expected everything but 'schema'.. a Schema does NOT have description") + return nil, &ParserError{ + Message: "expected everything but 'schema'.. a Schema does NOT have description", + Line: token.Line, + Column: token.Col, + Token: token, + } } } if token.Kind == lexer.NameToken && token.Value == "schema" { @@ -45,15 +66,30 @@ func ParseDefinition(definition []byte) (ast.Definition, error) { if err != nil { return nil, err } else if t.Kind != lexer.BadToken && err == nil { - return nil, fmt.Errorf("invalid token after schema definition: '%s'", t.Value) + return nil, &ParserError{ + Message: fmt.Sprintf("invalid token after schema definition: '%s'", t.Value), + Line: token.Line, + Column: token.Col, + Token: token, + } } return def, nil } t, def, err := parseDefinition(token, tokens, desc) if err != nil { - return nil, err + return nil, &ParserError{ + Message: err.Error(), + Line: token.Line, + Column: token.Col, + Token: token, + } } else if t.Kind != lexer.BadToken && err == nil { - return nil, fmt.Errorf("invalid token after definition: '%s'", t.Value) + return nil, &ParserError{ + Message: fmt.Sprintf("invalid token after definition: '%s'", t.Value), + Line: token.Line, + Column: token.Col, + Token: token, + } } return def, nil } diff --git a/pkg/language/parser/parser_test.go b/pkg/language/parser/parser_test.go index 8e8d92d..1223075 100644 --- a/pkg/language/parser/parser_test.go +++ b/pkg/language/parser/parser_test.go @@ -22,9 +22,9 @@ query { } } ` - token, _, err := parser.Parse([]byte(query)) + _, err := parser.Parse([]byte(query)) if err != nil { - t.Errorf("error: %v, at Line: %v, Col: %v", err, token.Line, token.Col) + t.Errorf("error: %v", err) return } // spew.Dump(doc) @@ -37,9 +37,9 @@ func TestParseScalar(t *testing.T) { scalar foo @depricated(reason: "just") scalar bar` - token, doc, err := parser.Parse([]byte(query)) + doc, err := parser.Parse([]byte(query)) if err != nil { - t.Errorf("error: %v, at Line: %v, Col: %v", err, token.Line, token.Col) + t.Errorf("error: %v", err) return } log.Printf("%#v", doc.Definitions[0]) @@ -62,9 +62,9 @@ func TestParseObject(t *testing.T) { picture: Url } ` - token, doc, err := parser.Parse([]byte(query)) + doc, err := parser.Parse([]byte(query)) if err != nil { - t.Errorf("error: %v, at Line: %v, Col: %v", err, token.Line, token.Col) + t.Errorf("error: %v", err) return } log.Printf("%#v", doc.Definitions[0].(*ast.ObjectDefinition))