Created
August 23, 2015 17:56
-
-
Save Acconut/3b562958db3adfbc19b0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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