mirror of
https://github.com/42wim/matterbridge
synced 2024-11-17 03:26:07 +00:00
230 lines
5.5 KiB
Go
230 lines
5.5 KiB
Go
package common
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"text/scanner"
|
|
|
|
"github.com/graph-gophers/graphql-go/errors"
|
|
"github.com/graph-gophers/graphql-go/types"
|
|
)
|
|
|
|
type syntaxError string
|
|
|
|
type Lexer struct {
|
|
sc *scanner.Scanner
|
|
next rune
|
|
comment bytes.Buffer
|
|
useStringDescriptions bool
|
|
}
|
|
|
|
type Ident struct {
|
|
Name string
|
|
Loc errors.Location
|
|
}
|
|
|
|
func NewLexer(s string, useStringDescriptions bool) *Lexer {
|
|
sc := &scanner.Scanner{
|
|
Mode: scanner.ScanIdents | scanner.ScanInts | scanner.ScanFloats | scanner.ScanStrings,
|
|
}
|
|
sc.Init(strings.NewReader(s))
|
|
|
|
l := Lexer{sc: sc, useStringDescriptions: useStringDescriptions}
|
|
l.sc.Error = l.CatchScannerError
|
|
|
|
return &l
|
|
}
|
|
|
|
func (l *Lexer) CatchSyntaxError(f func()) (errRes *errors.QueryError) {
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
if err, ok := err.(syntaxError); ok {
|
|
errRes = errors.Errorf("syntax error: %s", err)
|
|
errRes.Locations = []errors.Location{l.Location()}
|
|
return
|
|
}
|
|
panic(err)
|
|
}
|
|
}()
|
|
|
|
f()
|
|
return
|
|
}
|
|
|
|
func (l *Lexer) Peek() rune {
|
|
return l.next
|
|
}
|
|
|
|
// ConsumeWhitespace consumes whitespace and tokens equivalent to whitespace (e.g. commas and comments).
|
|
//
|
|
// Consumed comment characters will build the description for the next type or field encountered.
|
|
// The description is available from `DescComment()`, and will be reset every time `ConsumeWhitespace()` is
|
|
// executed unless l.useStringDescriptions is set.
|
|
func (l *Lexer) ConsumeWhitespace() {
|
|
l.comment.Reset()
|
|
for {
|
|
l.next = l.sc.Scan()
|
|
|
|
if l.next == ',' {
|
|
// Similar to white space and line terminators, commas (',') are used to improve the
|
|
// legibility of source text and separate lexical tokens but are otherwise syntactically and
|
|
// semantically insignificant within GraphQL documents.
|
|
//
|
|
// http://facebook.github.io/graphql/draft/#sec-Insignificant-Commas
|
|
continue
|
|
}
|
|
|
|
if l.next == '#' {
|
|
// GraphQL source documents may contain single-line comments, starting with the '#' marker.
|
|
//
|
|
// A comment can contain any Unicode code point except `LineTerminator` so a comment always
|
|
// consists of all code points starting with the '#' character up to but not including the
|
|
// line terminator.
|
|
l.consumeComment()
|
|
continue
|
|
}
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
// consumeDescription optionally consumes a description based on the June 2018 graphql spec if any are present.
|
|
//
|
|
// Single quote strings are also single line. Triple quote strings can be multi-line. Triple quote strings
|
|
// whitespace trimmed on both ends.
|
|
// If a description is found, consume any following comments as well
|
|
//
|
|
// http://facebook.github.io/graphql/June2018/#sec-Descriptions
|
|
func (l *Lexer) consumeDescription() string {
|
|
// If the next token is not a string, we don't consume it
|
|
if l.next != scanner.String {
|
|
return ""
|
|
}
|
|
// Triple quote string is an empty "string" followed by an open quote due to the way the parser treats strings as one token
|
|
var desc string
|
|
if l.sc.Peek() == '"' {
|
|
desc = l.consumeTripleQuoteComment()
|
|
} else {
|
|
desc = l.consumeStringComment()
|
|
}
|
|
l.ConsumeWhitespace()
|
|
return desc
|
|
}
|
|
|
|
func (l *Lexer) ConsumeIdent() string {
|
|
name := l.sc.TokenText()
|
|
l.ConsumeToken(scanner.Ident)
|
|
return name
|
|
}
|
|
|
|
func (l *Lexer) ConsumeIdentWithLoc() types.Ident {
|
|
loc := l.Location()
|
|
name := l.sc.TokenText()
|
|
l.ConsumeToken(scanner.Ident)
|
|
return types.Ident{Name: name, Loc: loc}
|
|
}
|
|
|
|
func (l *Lexer) ConsumeKeyword(keyword string) {
|
|
if l.next != scanner.Ident || l.sc.TokenText() != keyword {
|
|
l.SyntaxError(fmt.Sprintf("unexpected %q, expecting %q", l.sc.TokenText(), keyword))
|
|
}
|
|
l.ConsumeWhitespace()
|
|
}
|
|
|
|
func (l *Lexer) ConsumeLiteral() *types.PrimitiveValue {
|
|
lit := &types.PrimitiveValue{Type: l.next, Text: l.sc.TokenText()}
|
|
l.ConsumeWhitespace()
|
|
return lit
|
|
}
|
|
|
|
func (l *Lexer) ConsumeToken(expected rune) {
|
|
if l.next != expected {
|
|
l.SyntaxError(fmt.Sprintf("unexpected %q, expecting %s", l.sc.TokenText(), scanner.TokenString(expected)))
|
|
}
|
|
l.ConsumeWhitespace()
|
|
}
|
|
|
|
func (l *Lexer) DescComment() string {
|
|
comment := l.comment.String()
|
|
desc := l.consumeDescription()
|
|
if l.useStringDescriptions {
|
|
return desc
|
|
}
|
|
return comment
|
|
}
|
|
|
|
func (l *Lexer) SyntaxError(message string) {
|
|
panic(syntaxError(message))
|
|
}
|
|
|
|
func (l *Lexer) Location() errors.Location {
|
|
return errors.Location{
|
|
Line: l.sc.Line,
|
|
Column: l.sc.Column,
|
|
}
|
|
}
|
|
|
|
func (l *Lexer) consumeTripleQuoteComment() string {
|
|
l.next = l.sc.Next()
|
|
if l.next != '"' {
|
|
panic("consumeTripleQuoteComment used in wrong context: no third quote?")
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
var numQuotes int
|
|
for {
|
|
l.next = l.sc.Next()
|
|
if l.next == '"' {
|
|
numQuotes++
|
|
} else {
|
|
numQuotes = 0
|
|
}
|
|
buf.WriteRune(l.next)
|
|
if numQuotes == 3 || l.next == scanner.EOF {
|
|
break
|
|
}
|
|
}
|
|
val := buf.String()
|
|
val = val[:len(val)-numQuotes]
|
|
return blockString(val)
|
|
}
|
|
|
|
func (l *Lexer) consumeStringComment() string {
|
|
val, err := strconv.Unquote(l.sc.TokenText())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return val
|
|
}
|
|
|
|
// consumeComment consumes all characters from `#` to the first encountered line terminator.
|
|
// The characters are appended to `l.comment`.
|
|
func (l *Lexer) consumeComment() {
|
|
if l.next != '#' {
|
|
panic("consumeComment used in wrong context")
|
|
}
|
|
|
|
// TODO: count and trim whitespace so we can dedent any following lines.
|
|
if l.sc.Peek() == ' ' {
|
|
l.sc.Next()
|
|
}
|
|
|
|
if l.comment.Len() > 0 {
|
|
l.comment.WriteRune('\n')
|
|
}
|
|
|
|
for {
|
|
next := l.sc.Next()
|
|
if next == '\r' || next == '\n' || next == scanner.EOF {
|
|
break
|
|
}
|
|
l.comment.WriteRune(next)
|
|
}
|
|
}
|
|
|
|
func (l *Lexer) CatchScannerError(s *scanner.Scanner, msg string) {
|
|
l.SyntaxError(msg)
|
|
}
|