455 lines
7.2 KiB
Go
455 lines
7.2 KiB
Go
package qcode
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/dosco/super-graph/util"
|
|
)
|
|
|
|
var (
|
|
errEOT = errors.New("end of tokens")
|
|
)
|
|
|
|
type parserType int16
|
|
|
|
const (
|
|
parserError parserType = iota
|
|
parserEOF
|
|
opQuery
|
|
opMutate
|
|
opSub
|
|
nodeStr
|
|
nodeInt
|
|
nodeFloat
|
|
nodeBool
|
|
nodeObj
|
|
nodeList
|
|
nodeVar
|
|
)
|
|
|
|
func (t parserType) String() string {
|
|
var v string
|
|
|
|
switch t {
|
|
case parserEOF:
|
|
v = "EOF"
|
|
case parserError:
|
|
v = "error"
|
|
case opQuery:
|
|
v = "query"
|
|
case opMutate:
|
|
v = "mutation"
|
|
case opSub:
|
|
v = "subscription"
|
|
case nodeStr:
|
|
v = "node-string"
|
|
case nodeInt:
|
|
v = "node-int"
|
|
case nodeFloat:
|
|
v = "node-float"
|
|
case nodeBool:
|
|
v = "node-bool"
|
|
case nodeObj:
|
|
v = "node-obj"
|
|
case nodeList:
|
|
v = "node-list"
|
|
}
|
|
return fmt.Sprintf("<%s>", v)
|
|
}
|
|
|
|
type Operation struct {
|
|
Type parserType
|
|
Name string
|
|
Args []*Arg
|
|
Fields []*Field
|
|
}
|
|
|
|
type Field struct {
|
|
Name string
|
|
Alias string
|
|
Args []*Arg
|
|
Parent *Field
|
|
Children []*Field
|
|
}
|
|
|
|
type Arg struct {
|
|
Name string
|
|
Val *Node
|
|
}
|
|
|
|
type Node struct {
|
|
Type parserType
|
|
Name string
|
|
Val string
|
|
Parent *Node
|
|
Children []*Node
|
|
}
|
|
|
|
type Parser struct {
|
|
pos int
|
|
items []item
|
|
depth int
|
|
err error
|
|
}
|
|
|
|
func Parse(gql string) (*Operation, error) {
|
|
if len(gql) == 0 {
|
|
return nil, errors.New("blank query")
|
|
}
|
|
|
|
l, err := lex(gql)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p := &Parser{
|
|
pos: -1,
|
|
items: l.items,
|
|
}
|
|
return p.parseOp()
|
|
}
|
|
|
|
func ParseQuery(gql string) (*Operation, error) {
|
|
return parseByType(gql, opQuery)
|
|
}
|
|
|
|
func ParseArgValue(argVal string) (*Node, error) {
|
|
l, err := lex(argVal)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p := &Parser{
|
|
pos: -1,
|
|
items: l.items,
|
|
}
|
|
|
|
return p.parseValue()
|
|
}
|
|
|
|
func parseByType(gql string, ty parserType) (*Operation, error) {
|
|
l, err := lex(gql)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p := &Parser{
|
|
pos: -1,
|
|
items: l.items,
|
|
}
|
|
return p.parseOpByType(ty)
|
|
}
|
|
|
|
func (p *Parser) next() item {
|
|
n := p.pos + 1
|
|
if n >= len(p.items) {
|
|
p.err = errEOT
|
|
return item{typ: itemEOF}
|
|
}
|
|
p.pos = n
|
|
return p.items[p.pos]
|
|
}
|
|
|
|
func (p *Parser) ignore() {
|
|
n := p.pos + 1
|
|
if n >= len(p.items) {
|
|
p.err = errEOT
|
|
return
|
|
}
|
|
p.pos = n
|
|
}
|
|
|
|
func (p *Parser) current() item {
|
|
return p.items[p.pos]
|
|
}
|
|
|
|
func (p *Parser) eof() bool {
|
|
n := p.pos + 1
|
|
return p.items[n].typ == itemEOF
|
|
}
|
|
|
|
func (p *Parser) peek(types ...itemType) bool {
|
|
n := p.pos + 1
|
|
if p.items[n].typ == itemEOF {
|
|
return false
|
|
}
|
|
if n >= len(p.items) {
|
|
return false
|
|
}
|
|
for i := 0; i < len(types); i++ {
|
|
if p.items[n].typ == types[i] {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (p *Parser) parseOpByType(ty parserType) (*Operation, error) {
|
|
op := &Operation{Type: ty}
|
|
var err error
|
|
|
|
if p.peek(itemName) {
|
|
op.Name = p.next().val
|
|
}
|
|
|
|
if p.peek(itemArgsOpen) {
|
|
p.ignore()
|
|
op.Args, err = p.parseArgs()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if p.peek(itemObjOpen) {
|
|
p.ignore()
|
|
op.Fields, err = p.parseFields()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if p.peek(itemObjClose) {
|
|
p.ignore()
|
|
}
|
|
|
|
return op, nil
|
|
}
|
|
|
|
func (p *Parser) parseOp() (*Operation, error) {
|
|
if p.peek(itemQuery, itemMutation, itemSub) == false {
|
|
err := fmt.Errorf("expecting a query, mutation or subscription (not '%s')", p.next().val)
|
|
return nil, err
|
|
}
|
|
|
|
item := p.next()
|
|
|
|
switch item.typ {
|
|
case itemQuery:
|
|
return p.parseOpByType(opQuery)
|
|
case itemMutation:
|
|
return p.parseOpByType(opMutate)
|
|
case itemSub:
|
|
return p.parseOpByType(opSub)
|
|
}
|
|
|
|
return nil, errors.New("unknown operation type")
|
|
}
|
|
|
|
func (p *Parser) parseFields() ([]*Field, error) {
|
|
var roots []*Field
|
|
st := util.NewStack()
|
|
|
|
for {
|
|
if p.peek(itemObjClose) {
|
|
p.ignore()
|
|
st.Pop()
|
|
|
|
if st.Len() == 0 {
|
|
break
|
|
}
|
|
continue
|
|
}
|
|
|
|
if p.peek(itemName) == false {
|
|
return nil, errors.New("expecting an alias or field name")
|
|
}
|
|
|
|
field, err := p.parseField()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if st.Len() == 0 {
|
|
roots = append(roots, field)
|
|
|
|
} else {
|
|
intf := st.Peek()
|
|
parent, ok := intf.(*Field)
|
|
if !ok || parent == nil {
|
|
return nil, fmt.Errorf("unexpected value encountered %v", intf)
|
|
}
|
|
field.Parent = parent
|
|
parent.Children = append(parent.Children, field)
|
|
}
|
|
|
|
if p.peek(itemObjOpen) {
|
|
p.ignore()
|
|
st.Push(field)
|
|
}
|
|
}
|
|
|
|
return roots, nil
|
|
}
|
|
|
|
func (p *Parser) parseField() (*Field, error) {
|
|
var err error
|
|
field := &Field{Name: p.next().val}
|
|
|
|
if p.peek(itemColon) {
|
|
p.ignore()
|
|
|
|
if p.peek(itemName) {
|
|
field.Alias = field.Name
|
|
field.Name = p.next().val
|
|
} else {
|
|
return nil, errors.New("expecting an aliased field name")
|
|
}
|
|
}
|
|
|
|
if p.peek(itemArgsOpen) {
|
|
p.ignore()
|
|
if field.Args, err = p.parseArgs(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return field, nil
|
|
}
|
|
|
|
func (p *Parser) parseArgs() ([]*Arg, error) {
|
|
var args []*Arg
|
|
var err error
|
|
|
|
for {
|
|
if p.peek(itemArgsClose) {
|
|
p.ignore()
|
|
break
|
|
}
|
|
if p.peek(itemName) == false {
|
|
return nil, errors.New("expecting an argument name")
|
|
}
|
|
arg := &Arg{Name: p.next().val}
|
|
|
|
if p.peek(itemColon) == false {
|
|
return nil, errors.New("missing ':' after argument name")
|
|
}
|
|
p.ignore()
|
|
|
|
arg.Val, err = p.parseValue()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
args = append(args, arg)
|
|
}
|
|
return args, nil
|
|
}
|
|
|
|
func (p *Parser) parseList(parent *Node) ([]*Node, error) {
|
|
var nodes []*Node
|
|
var ty parserType
|
|
|
|
if parent == nil {
|
|
return nil, errors.New("list needs a parent")
|
|
}
|
|
|
|
for {
|
|
if p.peek(itemListClose) {
|
|
p.ignore()
|
|
break
|
|
}
|
|
node, err := p.parseValue()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if ty == 0 {
|
|
ty = node.Type
|
|
} else {
|
|
if ty != node.Type {
|
|
return nil, errors.New("All values in a list must be of the same type")
|
|
}
|
|
}
|
|
nodes = append(nodes, node)
|
|
}
|
|
if len(nodes) == 0 {
|
|
return nil, errors.New("List cannot be empty")
|
|
}
|
|
|
|
parent.Type = nodeList
|
|
parent.Children = nodes
|
|
|
|
return nodes, nil
|
|
}
|
|
|
|
func (p *Parser) parseObj(parent *Node) ([]*Node, error) {
|
|
var nodes []*Node
|
|
|
|
if parent == nil {
|
|
return nil, errors.New("object needs a parent")
|
|
}
|
|
|
|
for {
|
|
if p.peek(itemObjClose) {
|
|
p.ignore()
|
|
break
|
|
}
|
|
|
|
if p.peek(itemName) == false {
|
|
return nil, errors.New("expecting an argument name")
|
|
}
|
|
nodeName := p.next().val
|
|
|
|
if p.peek(itemColon) == false {
|
|
return nil, errors.New("missing ':' after Field argument name")
|
|
}
|
|
p.ignore()
|
|
|
|
node, err := p.parseValue()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
node.Name = nodeName
|
|
node.Parent = parent
|
|
nodes = append(nodes, node)
|
|
}
|
|
|
|
parent.Type = nodeObj
|
|
parent.Children = nodes
|
|
|
|
return nodes, nil
|
|
}
|
|
|
|
func (p *Parser) parseValue() (*Node, error) {
|
|
node := &Node{}
|
|
|
|
var done bool
|
|
var err error
|
|
|
|
if p.peek(itemListOpen) {
|
|
p.ignore()
|
|
node.Children, err = p.parseList(node)
|
|
done = true
|
|
}
|
|
|
|
if p.peek(itemObjOpen) {
|
|
p.ignore()
|
|
node.Children, err = p.parseObj(node)
|
|
done = true
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !done {
|
|
item := p.next()
|
|
|
|
switch item.typ {
|
|
case itemIntVal:
|
|
node.Type = nodeInt
|
|
case itemFloatVal:
|
|
node.Type = nodeFloat
|
|
case itemStringVal:
|
|
node.Type = nodeStr
|
|
case itemBoolVal:
|
|
node.Type = nodeBool
|
|
case itemName:
|
|
node.Type = nodeStr
|
|
case itemVariable:
|
|
node.Type = nodeVar
|
|
default:
|
|
return nil, fmt.Errorf("expecting a number, string, object, list or variable as an argument value (not %s)", p.next().val)
|
|
}
|
|
node.Val = item.val
|
|
}
|
|
|
|
return node, nil
|
|
}
|