Skip to content

Instantly share code, notes, and snippets.

@Acconut
Created August 23, 2015 17:56
Show Gist options
  • Save Acconut/3b562958db3adfbc19b0 to your computer and use it in GitHub Desktop.
Save Acconut/3b562958db3adfbc19b0 to your computer and use it in GitHub Desktop.
package language
import (
"fmt"
)
type Parser struct {
s *BufferedScanner
}
func NewParser(scanner *Scanner) *Parser {
return &Parser{
s: NewBufferedScanner(scanner),
}
}
func (parser *Parser) Parse() (doc Document, err error) {
defer func() {
if r := recover(); r != nil {
err = r.(error)
}
}()
for {
tok, lit := parser.expect(QUERY, MUTATION, BRACE_L)
if tok == EOF {
return
}
if tok == QUERY || tok == MUTATION || tok == BRACE_L {
parser.s.Unscan()
op, err := parser.parseOperation()
if err != nil {
return doc, err
}
doc.Operations = append(doc.Operations, op)
continue
}
return
}
}
func (parser *Parser) scanIgnoring() (tok Token, lit string) {
for {
tok, lit = parser.s.Scan()
if tok == WS || tok == LT || tok == COMMA || tok == COMMENT {
continue
}
return
}
}
func (parser *Parser) scanIdent() (tok Token, lit string) {
tok, lit = parser.scanIgnoring()
if isKeyword(tok) {
tok = IDENT
}
return
}
func (parser *Parser) expect(expectedToks Token...) (tok Token, lit string) {
tok, lit := parser.scanIgnoring()
for _, expectedTok = range expectedToks {
if tok == expectedTok {
return
}
}
str := ""
for index, tok := range expectedToks {
if index != 0 {
str += " or "
}
str += tok.String()
}
panic(fmt.Errorf(
`expected %s but found "%s" (%s)`,
str,
lit,
tok.String()))
}
func (parser *Parser) error(expectedTok, foundTok Token, foundLit string) error {
return fmt.Errorf(
`expected %s but found "%s" (%s)`,
expectedTok.String(),
foundLit,
foundTok.String())
}
func (parser *Parser) errorm(expectedToks []Token, foundTok Token, foundLit string) error {
str := ""
for index, tok := range expectedToks {
if index != 0 {
str += " or "
}
str += tok.String()
}
return fmt.Errorf(
`expected %s but found "%s" (%s)`,
str,
foundLit,
foundTok.String())
}
func (parser *Parser) parseOperation() (op Operation) {
tok, lit := parser.expect(QUERY, )
if tok == QUERY || tok == BRACE_L {
op.Type = OpQuery
} else if tok == MUTATION {
op.Type = OpMutation
}
if tok != BRACE_L {
tok, lit = parser.scanIdent()
if tok != IDENT {
err = parser.error(IDENT, tok, lit)
return
}
op.Name = lit
tok, lit = parser.scanIgnoring()
if tok == PARENT_L {
vars, err := parser.parseVarsDef()
if err != nil {
return op, err
}
op.Variables = vars
tok, lit = parser.scanIgnoring()
}
if tok != BRACE_L {
err = parser.error(BRACE_L, tok, lit)
return
}
sels, err = parser.parseSelections()
if err != nil {
return op, err
}
op.Selections = sels
}
return
}
func (parser *Parser) parseVarsDef() (vars []Variable, err error) {
for {
tok, lit := parser.scanIgnoring()
if tok == PARENT_R {
return
} else if tok == EOF {
err = parser.errorm([]Token{PARENT_R, IDENT}, tok, lit)
return
} else {
parser.s.Unscan()
}
varName, err := parser.parseVarName()
if err != nil {
return vars, err
}
tok, lit = parser.scanIgnoring()
if tok != COLON {
err = parser.error(COLON, tok, lit)
return vars, err
}
typ, err := parser.parseType()
if err != nil {
return vars, err
}
// TODO: default value
vars = append(vars, Variable{
Name: varName,
Type: typ,
})
}
return
}
func (parser *Parser) parseVarName() (name string, err error) {
tok, lit := parser.scanIgnoring()
if tok != DOLLAR {
err = parser.error(DOLLAR, tok, lit)
return
}
// TODO: whitespace is currently permitted between the dollar sign and the
// identifier, e.g. $ foo. This behaviour may be changed.
tok, lit = parser.scanIdent()
if tok != IDENT {
err = parser.error(IDENT, tok, lit)
return
}
name = lit
return
}
func (parser *Parser) parseType() (typ Type, err error) {
tok, lit := parser.scanIdent()
if tok == BRACKET_L {
typ.List = true
listTyp, err := parser.parseType()
if err != nil {
return typ, err
}
typ.ListType = &listTyp
tok, lit = parser.scanIgnoring()
if tok != BRACKET_R {
err = parser.error(BRACKET_R, tok, lit)
return typ, err
}
} else if tok == IDENT {
typ.Name = lit
} else {
err = parser.errorm([]Token{BRACKET_L, IDENT}, tok, lit)
return
}
if tok, _ = parser.scanIgnoring(); tok == BANG {
typ.NotNull = true
} else {
parser.s.Unscan()
}
return
}
func (parser *Parser) parseSelections() (sels []Selection, err error) {
for {
tok, lit := parser.scanIgnoring()
if tok == BRACE_R {
return
}
var sel Selection
if tok == IDENT {
parser.s.Unscan()
sel, err = parseField()
} else if tok == SPREAD {
//sel, err = parseAfterSpread()
} else {
err = parser.errorm([]Token{IDENT, SPREAD, BRACE_R})
}
if err != nil {
return sels, err
}
sels = append(sels, sel)
}
return
}
func (parser *Parser) parseField() (sel Field, err error) {
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment