First commit
This commit is contained in:
391
qcode/lex.go
Normal file
391
qcode/lex.go
Normal file
@ -0,0 +1,391 @@
|
||||
package qcode
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Pos represents a byte position in the original input text from which
|
||||
// this template was parsed.
|
||||
type Pos int
|
||||
|
||||
func (p Pos) Position() Pos {
|
||||
return p
|
||||
}
|
||||
|
||||
// item represents a token or text string returned from the scanner.
|
||||
type item struct {
|
||||
typ itemType // The type of this item.
|
||||
pos Pos // The starting position, in bytes, of this item in the input string.
|
||||
val string // The value of this item.
|
||||
line int // The line number at the start of this item.
|
||||
}
|
||||
|
||||
func (i *item) String() string {
|
||||
var v string
|
||||
|
||||
switch i.typ {
|
||||
case itemEOF:
|
||||
v = "EOF"
|
||||
case itemError:
|
||||
v = "error"
|
||||
case itemName:
|
||||
v = "name"
|
||||
case itemQuery:
|
||||
v = "query"
|
||||
case itemMutation:
|
||||
v = "mutation"
|
||||
case itemSub:
|
||||
v = "subscription"
|
||||
case itemPunctuator:
|
||||
v = "punctuator"
|
||||
case itemDirective:
|
||||
v = "directive"
|
||||
case itemVariable:
|
||||
v = "variable"
|
||||
case itemIntVal:
|
||||
v = "int"
|
||||
case itemFloatVal:
|
||||
v = "float"
|
||||
case itemStringVal:
|
||||
v = "string"
|
||||
}
|
||||
return fmt.Sprintf("%s %q", v, i.val)
|
||||
}
|
||||
|
||||
// itemType identifies the type of lex items.
|
||||
type itemType int
|
||||
|
||||
const (
|
||||
itemError itemType = iota // error occurred; value is text of error
|
||||
itemEOF
|
||||
itemName
|
||||
itemQuery
|
||||
itemMutation
|
||||
itemSub
|
||||
itemPunctuator
|
||||
itemArgsOpen
|
||||
itemArgsClose
|
||||
itemListOpen
|
||||
itemListClose
|
||||
itemObjOpen
|
||||
itemObjClose
|
||||
itemColon
|
||||
itemEquals
|
||||
itemDirective
|
||||
itemVariable
|
||||
itemSpread
|
||||
itemIntVal
|
||||
itemFloatVal
|
||||
itemStringVal
|
||||
itemBoolVal
|
||||
)
|
||||
|
||||
// !$():=@[]{|}
|
||||
var punctuators = map[rune]itemType{
|
||||
'{': itemObjOpen,
|
||||
'}': itemObjClose,
|
||||
'[': itemListOpen,
|
||||
']': itemListClose,
|
||||
'(': itemArgsOpen,
|
||||
')': itemArgsClose,
|
||||
':': itemColon,
|
||||
'=': itemEquals,
|
||||
}
|
||||
|
||||
const eof = -1
|
||||
|
||||
// stateFn represents the state of the scanner as a function that returns the next state.
|
||||
type stateFn func(*lexer) stateFn
|
||||
|
||||
// lexer holds the state of the scanner.
|
||||
type lexer struct {
|
||||
name string // the name of the input; used only for error reports
|
||||
input string // the string being scanned
|
||||
pos Pos // current position in the input
|
||||
start Pos // start position of this item
|
||||
width Pos // width of last rune read from input
|
||||
items []item // array of scanned items
|
||||
line int // 1+number of newlines seen
|
||||
}
|
||||
|
||||
// next returns the next rune in the input.
|
||||
func (l *lexer) next() rune {
|
||||
if int(l.pos) >= len(l.input) {
|
||||
l.width = 0
|
||||
return eof
|
||||
}
|
||||
r, w := utf8.DecodeRuneInString(l.input[l.pos:])
|
||||
l.width = Pos(w)
|
||||
l.pos += l.width
|
||||
if r == '\n' {
|
||||
l.line++
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// peek returns but does not consume the next rune in the input.
|
||||
func (l *lexer) peek() rune {
|
||||
r := l.next()
|
||||
l.backup()
|
||||
return r
|
||||
}
|
||||
|
||||
// backup steps back one rune. Can only be called once per call of next.
|
||||
func (l *lexer) backup() {
|
||||
l.pos -= l.width
|
||||
// Correct newline count.
|
||||
if l.width == 1 && l.input[l.pos] == '\n' {
|
||||
l.line--
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lexer) current() string {
|
||||
return l.input[l.start:l.pos]
|
||||
}
|
||||
|
||||
// emit passes an item back to the client.
|
||||
func (l *lexer) emit(t itemType) {
|
||||
l.items = append(l.items, item{t, l.start, l.input[l.start:l.pos], l.line})
|
||||
// Some items contain text internally. If so, count their newlines.
|
||||
switch t {
|
||||
case itemName:
|
||||
l.line += strings.Count(l.input[l.start:l.pos], "\n")
|
||||
}
|
||||
l.start = l.pos
|
||||
}
|
||||
|
||||
// ignore skips over the pending input before this point.
|
||||
func (l *lexer) ignore() {
|
||||
l.line += strings.Count(l.input[l.start:l.pos], "\n")
|
||||
l.start = l.pos
|
||||
}
|
||||
|
||||
// accept consumes the next rune if it's from the valid set.
|
||||
func (l *lexer) accept(valid string) bool {
|
||||
if strings.ContainsRune(valid, l.next()) {
|
||||
return true
|
||||
}
|
||||
l.backup()
|
||||
return false
|
||||
}
|
||||
|
||||
// accept onsumes a run of runes while they are alpha nums
|
||||
func (l *lexer) acceptAlphaNum() bool {
|
||||
n := 0
|
||||
for r := l.next(); isAlphaNumeric(r); r = l.next() {
|
||||
n++
|
||||
}
|
||||
l.backup()
|
||||
return (n != 0)
|
||||
}
|
||||
|
||||
// acceptRun consumes a run of runes from the valid set.
|
||||
func (l *lexer) acceptRun(valid string) {
|
||||
for strings.ContainsRune(valid, l.next()) {
|
||||
}
|
||||
l.backup()
|
||||
}
|
||||
|
||||
// errorf returns an error token and terminates the scan by passing
|
||||
// back a nil pointer that will be the next state, terminating l.nextItem.
|
||||
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
|
||||
l.items = append(l.items, item{itemError, l.start,
|
||||
fmt.Sprintf(format, args...), l.line})
|
||||
return nil
|
||||
}
|
||||
|
||||
// lex creates a new scanner for the input string.
|
||||
func lex(input string) (*lexer, error) {
|
||||
l := &lexer{
|
||||
input: input,
|
||||
items: make([]item, 0, 100),
|
||||
line: 1,
|
||||
}
|
||||
l.run()
|
||||
|
||||
if last := l.items[len(l.items)-1]; last.typ == itemError {
|
||||
return nil, fmt.Errorf(last.val)
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// run runs the state machine for the lexer.
|
||||
func (l *lexer) run() {
|
||||
for state := lexRoot; state != nil; {
|
||||
state = state(l)
|
||||
}
|
||||
}
|
||||
|
||||
// lexInsideAction scans the elements inside action delimiters.
|
||||
func lexRoot(l *lexer) stateFn {
|
||||
r := l.next()
|
||||
|
||||
switch {
|
||||
case r == eof:
|
||||
l.emit(itemEOF)
|
||||
return nil
|
||||
case isEndOfLine(r):
|
||||
l.ignore()
|
||||
case isSpace(r):
|
||||
l.ignore()
|
||||
case r == '@':
|
||||
l.ignore()
|
||||
if l.acceptAlphaNum() {
|
||||
l.emit(itemDirective)
|
||||
}
|
||||
case r == '$':
|
||||
l.ignore()
|
||||
if l.acceptAlphaNum() {
|
||||
l.emit(itemVariable)
|
||||
}
|
||||
case strings.ContainsRune("!():=[]{|}", r):
|
||||
if item, ok := punctuators[r]; ok {
|
||||
l.emit(item)
|
||||
} else {
|
||||
l.emit(itemPunctuator)
|
||||
}
|
||||
case r == '"' || r == '\'':
|
||||
l.backup()
|
||||
return lexString
|
||||
case r == '.':
|
||||
if len(l.input) >= 3 {
|
||||
if strings.HasSuffix(l.input[:l.pos], "...") {
|
||||
l.emit(itemSpread)
|
||||
return lexRoot
|
||||
}
|
||||
}
|
||||
fallthrough // '.' can start a number.
|
||||
case r == '+' || r == '-' || ('0' <= r && r <= '9'):
|
||||
l.backup()
|
||||
return lexNumber
|
||||
case isAlphaNumeric(r):
|
||||
l.backup()
|
||||
return lexName
|
||||
default:
|
||||
return l.errorf("unrecognized character in action: %#U", r)
|
||||
}
|
||||
return lexRoot
|
||||
}
|
||||
|
||||
// lexName scans a name.
|
||||
func lexName(l *lexer) stateFn {
|
||||
for {
|
||||
r := l.next()
|
||||
if r == eof {
|
||||
l.emit(itemEOF)
|
||||
return nil
|
||||
}
|
||||
if !isAlphaNumeric(r) {
|
||||
l.backup()
|
||||
v := l.current()
|
||||
|
||||
if len(v) == 0 {
|
||||
switch {
|
||||
case strings.EqualFold(v, "query"):
|
||||
l.emit(itemQuery)
|
||||
break
|
||||
case strings.EqualFold(v, "mutation"):
|
||||
l.emit(itemMutation)
|
||||
break
|
||||
case strings.EqualFold(v, "subscription"):
|
||||
l.emit(itemSub)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.EqualFold(v, "true"):
|
||||
l.emit(itemBoolVal)
|
||||
case strings.EqualFold(v, "false"):
|
||||
l.emit(itemBoolVal)
|
||||
default:
|
||||
l.emit(itemName)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return lexRoot
|
||||
}
|
||||
|
||||
// lexString scans a string.
|
||||
func lexString(l *lexer) stateFn {
|
||||
if l.accept("\"'") {
|
||||
l.ignore()
|
||||
|
||||
for {
|
||||
r := l.next()
|
||||
if r == eof {
|
||||
l.emit(itemEOF)
|
||||
return nil
|
||||
}
|
||||
if r == '\'' || r == '"' {
|
||||
l.backup()
|
||||
l.emit(itemStringVal)
|
||||
if l.accept("\"'") {
|
||||
l.ignore()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return lexRoot
|
||||
}
|
||||
|
||||
// lexNumber scans a number: decimal, octal, hex, float, or imaginary. This
|
||||
// isn't a perfect number scanner - for instance it accepts "." and "0x0.2"
|
||||
// and "089" - but when it's wrong the input is invalid and the parser (via
|
||||
// strconv) will notice.
|
||||
func lexNumber(l *lexer) stateFn {
|
||||
var it itemType
|
||||
// Optional leading sign.
|
||||
l.accept("+-")
|
||||
|
||||
// Is it integer
|
||||
digits := "0123456789"
|
||||
if l.accept(digits) {
|
||||
l.acceptRun(digits)
|
||||
it = itemIntVal
|
||||
}
|
||||
|
||||
// Is it float
|
||||
if l.peek() == '.' {
|
||||
if l.accept(".") {
|
||||
if l.accept(digits) {
|
||||
l.acceptRun(digits)
|
||||
it = itemFloatVal
|
||||
}
|
||||
} else {
|
||||
l.backup()
|
||||
}
|
||||
}
|
||||
|
||||
// Next thing mustn't be alphanumeric.
|
||||
if isAlphaNumeric(l.peek()) {
|
||||
l.next()
|
||||
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
|
||||
}
|
||||
|
||||
if it != 0 {
|
||||
l.emit(it)
|
||||
}
|
||||
|
||||
return lexRoot
|
||||
}
|
||||
|
||||
// isSpace reports whether r is a space character.
|
||||
func isSpace(r rune) bool {
|
||||
return r == ',' || r == ' ' || r == '\t'
|
||||
}
|
||||
|
||||
// isEndOfLine reports whether r is an end-of-line character.
|
||||
func isEndOfLine(r rune) bool {
|
||||
return r == '\r' || r == '\n'
|
||||
}
|
||||
|
||||
// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore.
|
||||
func isAlphaNumeric(r rune) bool {
|
||||
return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
|
||||
}
|
454
qcode/parse.go
Normal file
454
qcode/parse.go
Normal file
@ -0,0 +1,454 @@
|
||||
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
|
||||
}
|
53
qcode/parse_test.go
Normal file
53
qcode/parse_test.go
Normal file
@ -0,0 +1,53 @@
|
||||
package qcode
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func compareOp(op1, op2 Operation) error {
|
||||
if op1.Type != op2.Type {
|
||||
return errors.New("operator type mismatch")
|
||||
}
|
||||
|
||||
if op1.Name != op2.Name {
|
||||
return errors.New("operator name mismatch")
|
||||
}
|
||||
|
||||
if len(op1.Args) != len(op2.Args) {
|
||||
return errors.New("operator args length mismatch")
|
||||
}
|
||||
|
||||
for i := range op1.Args {
|
||||
if !reflect.DeepEqual(op1.Args[i], op2.Args[i]) {
|
||||
return fmt.Errorf("operator args: %v != %v", op1.Args[i], op2.Args[i])
|
||||
}
|
||||
}
|
||||
|
||||
if len(op1.Fields) != len(op2.Fields) {
|
||||
return errors.New("operator field length mismatch")
|
||||
}
|
||||
|
||||
for i := range op1.Fields {
|
||||
if !reflect.DeepEqual(op1.Fields[i].Args, op2.Fields[i].Args) {
|
||||
return fmt.Errorf("operator field args: %v != %v", op1.Fields[i].Args, op2.Fields[i].Args)
|
||||
}
|
||||
}
|
||||
|
||||
for i := range op1.Fields {
|
||||
if !reflect.DeepEqual(op1.Fields[i].Children, op2.Fields[i].Children) {
|
||||
return fmt.Errorf("operator field fields: %v != %v", op1.Fields[i].Children, op2.Fields[i].Children)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
}
|
||||
|
||||
func BenchmarkParse(b *testing.B) {
|
||||
|
||||
}
|
619
qcode/qcode.go
Normal file
619
qcode/qcode.go
Normal file
@ -0,0 +1,619 @@
|
||||
package qcode
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/dosco/super-graph/util"
|
||||
"github.com/jinzhu/inflection"
|
||||
)
|
||||
|
||||
type QCode struct {
|
||||
Query *Query
|
||||
}
|
||||
|
||||
type Query struct {
|
||||
Select *Select
|
||||
}
|
||||
|
||||
type Column struct {
|
||||
Table string
|
||||
Name string
|
||||
FieldName string
|
||||
}
|
||||
|
||||
type Select struct {
|
||||
ID int32
|
||||
AsList bool
|
||||
Table string
|
||||
Singular string
|
||||
FieldName string
|
||||
Cols []*Column
|
||||
Where *Exp
|
||||
OrderBy []*OrderBy
|
||||
DistinctOn []string
|
||||
Paging Paging
|
||||
Joins []*Select
|
||||
}
|
||||
|
||||
type Exp struct {
|
||||
Op ExpOp
|
||||
Col string
|
||||
NestedCol bool
|
||||
Type ValType
|
||||
Val string
|
||||
ListType ValType
|
||||
ListVal []string
|
||||
Children []*Exp
|
||||
}
|
||||
|
||||
type OrderBy struct {
|
||||
Col string
|
||||
Order Order
|
||||
}
|
||||
|
||||
type Paging struct {
|
||||
Limit string
|
||||
Offset string
|
||||
}
|
||||
|
||||
type ExpOp int
|
||||
|
||||
const (
|
||||
OpAnd ExpOp = iota + 1
|
||||
OpOr
|
||||
OpNot
|
||||
OpEquals
|
||||
OpNotEquals
|
||||
OpGreaterOrEquals
|
||||
OpLesserOrEquals
|
||||
OpGreaterThan
|
||||
OpLesserThan
|
||||
OpIn
|
||||
OpNotIn
|
||||
OpLike
|
||||
OpNotLike
|
||||
OpILike
|
||||
OpNotILike
|
||||
OpSimilar
|
||||
OpNotSimilar
|
||||
OpContains
|
||||
OpContainedIn
|
||||
OpHasKey
|
||||
OpHasKeyAny
|
||||
OpHasKeyAll
|
||||
OpIsNull
|
||||
)
|
||||
|
||||
type ValType int
|
||||
|
||||
const (
|
||||
ValStr ValType = iota + 1
|
||||
ValInt
|
||||
ValFloat
|
||||
ValBool
|
||||
ValList
|
||||
ValVar
|
||||
)
|
||||
|
||||
type AggregrateOp int
|
||||
|
||||
const (
|
||||
AgCount AggregrateOp = iota + 1
|
||||
AgSum
|
||||
AgAvg
|
||||
AgMax
|
||||
AgMin
|
||||
)
|
||||
|
||||
type Order int
|
||||
|
||||
const (
|
||||
OrderAsc Order = iota + 1
|
||||
OrderDesc
|
||||
OrderAscNullsFirst
|
||||
OrderAscNullsLast
|
||||
OrderDescNullsFirst
|
||||
OrderDescNullsLast
|
||||
)
|
||||
|
||||
type FilterMap map[string]*Exp
|
||||
type Blacklist *regexp.Regexp
|
||||
|
||||
func CompileFilter(filter string) (*Exp, error) {
|
||||
node, err := ParseArgValue(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return (&Compiler{}).compileArgNode(node)
|
||||
}
|
||||
|
||||
type Compiler struct {
|
||||
fm FilterMap
|
||||
bl *regexp.Regexp
|
||||
}
|
||||
|
||||
func NewCompiler(fm FilterMap, bl Blacklist) *Compiler {
|
||||
return &Compiler{fm, bl}
|
||||
}
|
||||
|
||||
func (com *Compiler) CompileQuery(query string) (*QCode, error) {
|
||||
var qc QCode
|
||||
var err error
|
||||
|
||||
op, err := ParseQuery(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch op.Type {
|
||||
case opQuery:
|
||||
qc.Query, err = com.compileQuery(op)
|
||||
case opMutate:
|
||||
case opSub:
|
||||
default:
|
||||
err = fmt.Errorf("Unknown operation type %d", op.Type)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &qc, nil
|
||||
}
|
||||
|
||||
func (com *Compiler) compileQuery(op *Operation) (*Query, error) {
|
||||
var selRoot *Select
|
||||
|
||||
st := util.NewStack()
|
||||
id := int32(0)
|
||||
fmap := make(map[*Field]*Select)
|
||||
|
||||
for i := range op.Fields {
|
||||
st.Push(op.Fields[i])
|
||||
}
|
||||
|
||||
for {
|
||||
if st.Len() == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
intf := st.Pop()
|
||||
field, ok := intf.(*Field)
|
||||
|
||||
if !ok || field == nil {
|
||||
return nil, fmt.Errorf("unexpected value poped out %v", intf)
|
||||
}
|
||||
|
||||
if com.bl != nil && com.bl.MatchString(field.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
fn := strings.ToLower(field.Name)
|
||||
|
||||
s := &Select{
|
||||
ID: id,
|
||||
Table: inflection.Plural(fn),
|
||||
Singular: inflection.Singular(fn),
|
||||
}
|
||||
|
||||
if fn == s.Table {
|
||||
s.AsList = true
|
||||
} else {
|
||||
s.Paging.Limit = "1"
|
||||
}
|
||||
|
||||
if len(field.Alias) != 0 {
|
||||
s.FieldName = field.Alias
|
||||
} else if s.AsList {
|
||||
s.FieldName = s.Table
|
||||
} else {
|
||||
s.FieldName = s.Singular
|
||||
}
|
||||
|
||||
id++
|
||||
fmap[field] = s
|
||||
|
||||
err := com.compileArgs(s, field.Args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range field.Children {
|
||||
f := field.Children[i]
|
||||
|
||||
if com.bl != nil && com.bl.MatchString(f.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
if f.Children == nil {
|
||||
col := &Column{Name: f.Name}
|
||||
if len(f.Alias) != 0 {
|
||||
col.FieldName = f.Alias
|
||||
} else {
|
||||
col.FieldName = f.Name
|
||||
}
|
||||
s.Cols = append(s.Cols, col)
|
||||
} else {
|
||||
st.Push(f)
|
||||
}
|
||||
}
|
||||
|
||||
if field.Parent == nil {
|
||||
selRoot = s
|
||||
} else if sp, ok := fmap[field.Parent]; ok {
|
||||
sp.Joins = append(sp.Joins, s)
|
||||
} else {
|
||||
return nil, fmt.Errorf("no select found for parent %#v", field.Parent)
|
||||
}
|
||||
}
|
||||
|
||||
if fil, ok := com.fm[selRoot.Table]; ok {
|
||||
if selRoot.Where != nil {
|
||||
selRoot.Where = &Exp{Op: OpAnd, Children: []*Exp{fil, selRoot.Where}}
|
||||
} else {
|
||||
selRoot.Where = fil
|
||||
}
|
||||
}
|
||||
|
||||
return &Query{selRoot}, nil
|
||||
}
|
||||
|
||||
func (com *Compiler) compileArgs(sel *Select, args []*Arg) error {
|
||||
var err error
|
||||
|
||||
for i := range args {
|
||||
if args[i] == nil {
|
||||
return fmt.Errorf("[Args] unexpected nil argument found")
|
||||
}
|
||||
switch strings.ToLower(args[i].Name) {
|
||||
case "where":
|
||||
err = com.compileArgWhere(sel, args[i])
|
||||
case "orderby", "order_by", "order":
|
||||
err = com.compileArgOrderBy(sel, args[i])
|
||||
case "distinct_on", "distinct":
|
||||
err = com.compileArgDistinctOn(sel, args[i])
|
||||
case "limit":
|
||||
err = com.compileArgLimit(sel, args[i])
|
||||
case "offset":
|
||||
err = com.compileArgOffset(sel, args[i])
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
type expT struct {
|
||||
parent *Exp
|
||||
node *Node
|
||||
}
|
||||
|
||||
func (com *Compiler) compileArgObj(arg *Arg) (*Exp, error) {
|
||||
if arg.Val.Type != nodeObj {
|
||||
return nil, fmt.Errorf("[Where] expecting an object")
|
||||
}
|
||||
|
||||
return com.compileArgNode(arg.Val)
|
||||
}
|
||||
|
||||
func (com *Compiler) compileArgNode(val *Node) (*Exp, error) {
|
||||
st := util.NewStack()
|
||||
var root *Exp
|
||||
|
||||
st.Push(&expT{nil, val.Children[0]})
|
||||
|
||||
for {
|
||||
if st.Len() == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
intf := st.Pop()
|
||||
eT, ok := intf.(*expT)
|
||||
if !ok || eT == nil {
|
||||
return nil, fmt.Errorf("[Where] unexpected value poped out %v", intf)
|
||||
}
|
||||
node := eT.node
|
||||
|
||||
if com.bl != nil && com.bl.MatchString(node.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
ex := &Exp{}
|
||||
|
||||
name := strings.ToLower(node.Name)
|
||||
if name[0] == '_' {
|
||||
name = name[1:]
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "and":
|
||||
ex.Op = OpAnd
|
||||
pushChildren(st, ex, node)
|
||||
case "or":
|
||||
ex.Op = OpOr
|
||||
pushChildren(st, ex, node)
|
||||
case "not":
|
||||
ex.Op = OpNot
|
||||
pushChildren(st, ex, node)
|
||||
case "eq", "equals":
|
||||
ex.Op = OpEquals
|
||||
ex.Val = node.Val
|
||||
case "neq", "not_equals":
|
||||
ex.Op = OpNotEquals
|
||||
ex.Val = node.Val
|
||||
case "gt", "greater_than":
|
||||
ex.Op = OpGreaterThan
|
||||
ex.Val = node.Val
|
||||
case "lt", "lesser_than":
|
||||
ex.Op = OpLesserThan
|
||||
ex.Val = node.Val
|
||||
case "gte", "greater_or_equals":
|
||||
ex.Op = OpGreaterOrEquals
|
||||
ex.Val = node.Val
|
||||
case "lte", "lesser_or_equals":
|
||||
ex.Op = OpLesserOrEquals
|
||||
ex.Val = node.Val
|
||||
case "in":
|
||||
ex.Op = OpIn
|
||||
setListVal(ex, node)
|
||||
case "nin", "not_in":
|
||||
ex.Op = OpNotIn
|
||||
setListVal(ex, node)
|
||||
case "like":
|
||||
ex.Op = OpLike
|
||||
ex.Val = node.Val
|
||||
case "nlike", "not_like":
|
||||
ex.Op = OpNotLike
|
||||
ex.Val = node.Val
|
||||
case "ilike":
|
||||
ex.Op = OpILike
|
||||
ex.Val = node.Val
|
||||
case "nilike", "not_ilike":
|
||||
ex.Op = OpILike
|
||||
ex.Val = node.Val
|
||||
case "similar":
|
||||
ex.Op = OpSimilar
|
||||
ex.Val = node.Val
|
||||
case "nsimilar", "not_similar":
|
||||
ex.Op = OpNotSimilar
|
||||
ex.Val = node.Val
|
||||
case "contains":
|
||||
ex.Op = OpContains
|
||||
ex.Val = node.Val
|
||||
case "contained_in":
|
||||
ex.Op = OpContainedIn
|
||||
ex.Val = node.Val
|
||||
case "has_key":
|
||||
ex.Op = OpHasKey
|
||||
ex.Val = node.Val
|
||||
case "has_key_any":
|
||||
ex.Op = OpHasKeyAny
|
||||
ex.Val = node.Val
|
||||
case "has_key_all":
|
||||
ex.Op = OpHasKeyAll
|
||||
ex.Val = node.Val
|
||||
case "is_null":
|
||||
ex.Op = OpIsNull
|
||||
ex.Val = node.Val
|
||||
default:
|
||||
pushChildren(st, eT.parent, node)
|
||||
continue // skip node
|
||||
}
|
||||
|
||||
if ex.Op != OpAnd && ex.Op != OpOr && ex.Op != OpNot {
|
||||
switch node.Type {
|
||||
case nodeStr:
|
||||
ex.Type = ValStr
|
||||
case nodeInt:
|
||||
ex.Type = ValInt
|
||||
case nodeBool:
|
||||
ex.Type = ValBool
|
||||
case nodeFloat:
|
||||
ex.Type = ValFloat
|
||||
case nodeList:
|
||||
ex.Type = ValList
|
||||
case nodeVar:
|
||||
ex.Type = ValVar
|
||||
default:
|
||||
return nil, fmt.Errorf("[Where] valid values include string, int, float, boolean and list: %s", node.Type)
|
||||
}
|
||||
setWhereColName(ex, node)
|
||||
}
|
||||
|
||||
if eT.parent == nil {
|
||||
root = ex
|
||||
} else {
|
||||
eT.parent.Children = append(eT.parent.Children, ex)
|
||||
}
|
||||
}
|
||||
|
||||
return root, nil
|
||||
}
|
||||
|
||||
func (com *Compiler) compileArgWhere(sel *Select, arg *Arg) error {
|
||||
var err error
|
||||
|
||||
sel.Where, err = com.compileArgObj(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (com *Compiler) compileArgOrderBy(sel *Select, arg *Arg) error {
|
||||
if arg.Val.Type != nodeObj {
|
||||
return fmt.Errorf("expecting an object")
|
||||
}
|
||||
|
||||
st := util.NewStack()
|
||||
|
||||
for i := range arg.Val.Children {
|
||||
st.Push(arg.Val.Children[i])
|
||||
}
|
||||
|
||||
for {
|
||||
if st.Len() == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
intf := st.Pop()
|
||||
node, ok := intf.(*Node)
|
||||
|
||||
if !ok || node == nil {
|
||||
return fmt.Errorf("OrderBy: unexpected value poped out %v", intf)
|
||||
}
|
||||
|
||||
if com.bl != nil && com.bl.MatchString(node.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
if node.Type == nodeObj {
|
||||
for i := range node.Children {
|
||||
st.Push(node.Children[i])
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
ob := &OrderBy{}
|
||||
|
||||
val := strings.ToLower(node.Val)
|
||||
switch val {
|
||||
case "asc":
|
||||
ob.Order = OrderAsc
|
||||
case "desc":
|
||||
ob.Order = OrderDesc
|
||||
case "asc_nulls_first":
|
||||
ob.Order = OrderAscNullsFirst
|
||||
case "desc_nulls_first":
|
||||
ob.Order = OrderDescNullsFirst
|
||||
case "asc_nulls_last":
|
||||
ob.Order = OrderAscNullsLast
|
||||
case "desc_nulls_last":
|
||||
ob.Order = OrderDescNullsLast
|
||||
default:
|
||||
return fmt.Errorf("valid values include asc, desc, asc_nulls_first and desc_nulls_first")
|
||||
}
|
||||
|
||||
setOrderByColName(ob, node)
|
||||
sel.OrderBy = append(sel.OrderBy, ob)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (com *Compiler) compileArgDistinctOn(sel *Select, arg *Arg) error {
|
||||
node := arg.Val
|
||||
|
||||
if com.bl != nil && com.bl.MatchString(node.Name) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if node.Type != nodeList && node.Type != nodeStr {
|
||||
return fmt.Errorf("expecting a list of strings or just a string")
|
||||
}
|
||||
|
||||
if node.Type == nodeStr {
|
||||
sel.DistinctOn = append(sel.DistinctOn, node.Val)
|
||||
}
|
||||
|
||||
for i := range node.Children {
|
||||
sel.DistinctOn = append(sel.DistinctOn, node.Children[i].Val)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (com *Compiler) compileArgLimit(sel *Select, arg *Arg) error {
|
||||
node := arg.Val
|
||||
|
||||
if node.Type != nodeInt {
|
||||
return fmt.Errorf("expecting an integer")
|
||||
}
|
||||
|
||||
sel.Paging.Limit = node.Val
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (com *Compiler) compileArgOffset(sel *Select, arg *Arg) error {
|
||||
node := arg.Val
|
||||
|
||||
if node.Type != nodeInt {
|
||||
return fmt.Errorf("expecting an integer")
|
||||
}
|
||||
|
||||
sel.Paging.Offset = node.Val
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func compileMutate() (*Query, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func compileSub() (*Query, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func setListVal(ex *Exp, node *Node) {
|
||||
if len(node.Children) != 0 {
|
||||
switch node.Children[0].Type {
|
||||
case nodeStr:
|
||||
ex.ListType = ValStr
|
||||
case nodeInt:
|
||||
ex.ListType = ValInt
|
||||
case nodeBool:
|
||||
ex.ListType = ValBool
|
||||
case nodeFloat:
|
||||
ex.ListType = ValFloat
|
||||
}
|
||||
}
|
||||
for i := range node.Children {
|
||||
ex.ListVal = append(ex.ListVal, node.Children[i].Val)
|
||||
}
|
||||
}
|
||||
|
||||
func setWhereColName(ex *Exp, node *Node) {
|
||||
var list []string
|
||||
for n := node.Parent; n != nil; n = n.Parent {
|
||||
if n.Type != nodeObj {
|
||||
continue
|
||||
}
|
||||
k := strings.ToLower(n.Name)
|
||||
if k == "and" || k == "or" || k == "not" ||
|
||||
k == "_and" || k == "_or" || k == "_not" {
|
||||
continue
|
||||
}
|
||||
if len(k) != 0 {
|
||||
list = append([]string{k}, list...)
|
||||
}
|
||||
}
|
||||
if len(list) == 1 {
|
||||
ex.Col = list[0]
|
||||
|
||||
} else if len(list) > 2 {
|
||||
ex.Col = strings.Join(list, ".")
|
||||
ex.NestedCol = true
|
||||
}
|
||||
}
|
||||
|
||||
func setOrderByColName(ob *OrderBy, node *Node) {
|
||||
var list []string
|
||||
for n := node; n != nil; n = n.Parent {
|
||||
k := strings.ToLower(n.Name)
|
||||
if len(k) != 0 {
|
||||
list = append([]string{k}, list...)
|
||||
}
|
||||
}
|
||||
if len(list) != 0 {
|
||||
ob.Col = strings.Join(list, ".")
|
||||
}
|
||||
}
|
||||
|
||||
func pushChildren(st *util.Stack, ex *Exp, node *Node) {
|
||||
for i := range node.Children {
|
||||
st.Push(&expT{ex, node.Children[i]})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user