diff options
author | Michael Muré <batolettre@gmail.com> | 2018-09-14 12:40:31 +0200 |
---|---|---|
committer | Michael Muré <batolettre@gmail.com> | 2018-09-14 12:41:59 +0200 |
commit | b478cd1bcb4756b20f7f4b15fcf81f23e1a60a02 (patch) | |
tree | 8ce232dcab3dd00708f8ba66c334472457e5980d /vendor/github.com/vektah/gqlparser/lexer/lexer.go | |
parent | a3fc9abb921f5ce7084d6ab7473442d0b72b1d78 (diff) | |
download | git-bug-b478cd1bcb4756b20f7f4b15fcf81f23e1a60a02.tar.gz |
graphql: update gqlgen to 0.5.1
fix #6
Diffstat (limited to 'vendor/github.com/vektah/gqlparser/lexer/lexer.go')
-rw-r--r-- | vendor/github.com/vektah/gqlparser/lexer/lexer.go | 510 |
1 files changed, 510 insertions, 0 deletions
diff --git a/vendor/github.com/vektah/gqlparser/lexer/lexer.go b/vendor/github.com/vektah/gqlparser/lexer/lexer.go new file mode 100644 index 00000000..3aaa7102 --- /dev/null +++ b/vendor/github.com/vektah/gqlparser/lexer/lexer.go @@ -0,0 +1,510 @@ +package lexer + +import ( + "bytes" + "unicode/utf8" + + "github.com/vektah/gqlparser/ast" + "github.com/vektah/gqlparser/gqlerror" +) + +// Lexer turns graphql request and schema strings into tokens +type Lexer struct { + *ast.Source + // An offset into the string in bytes + start int + // An offset into the string in runes + startRunes int + // An offset into the string in bytes + end int + // An offset into the string in runes + endRunes int + // the current line number + line int + // An offset into the string in rune + lineStartRunes int +} + +func New(src *ast.Source) Lexer { + return Lexer{ + Source: src, + line: 1, + } +} + +// take one rune from input and advance end +func (s *Lexer) peek() (rune, int) { + return utf8.DecodeRuneInString(s.Input[s.end:]) +} + +func (s *Lexer) makeToken(kind Type) (Token, *gqlerror.Error) { + return s.makeValueToken(kind, s.Input[s.start:s.end]) +} + +func (s *Lexer) makeValueToken(kind Type, value string) (Token, *gqlerror.Error) { + return Token{ + Kind: kind, + Value: value, + Pos: ast.Position{ + Start: s.startRunes, + End: s.endRunes, + Line: s.line, + Column: s.startRunes - s.lineStartRunes + 1, + Src: s.Source, + }, + }, nil +} + +func (s *Lexer) makeError(format string, args ...interface{}) (Token, *gqlerror.Error) { + column := s.endRunes - s.lineStartRunes + 1 + return Token{ + Kind: Invalid, + Pos: ast.Position{ + Start: s.startRunes, + End: s.endRunes, + Line: s.line, + Column: column, + Src: s.Source, + }, + }, gqlerror.ErrorLocf(s.Source.Name, s.line, column, format, args...) +} + +// ReadToken gets the next token from the source starting at the given position. +// +// This skips over whitespace and comments until it finds the next lexable +// token, then lexes punctuators immediately or calls the appropriate helper +// function for more complicated tokens. +func (s *Lexer) ReadToken() (token Token, err *gqlerror.Error) { + + s.ws() + s.start = s.end + s.startRunes = s.endRunes + + if s.end >= len(s.Input) { + return s.makeToken(EOF) + } + r := s.Input[s.start] + s.end++ + s.endRunes++ + switch r { + case '!': + return s.makeValueToken(Bang, "") + + case '$': + return s.makeValueToken(Dollar, "") + case '&': + return s.makeValueToken(Amp, "") + case '(': + return s.makeValueToken(ParenL, "") + case ')': + return s.makeValueToken(ParenR, "") + case '.': + if len(s.Input) > s.start+2 && s.Input[s.start:s.start+3] == "..." { + s.end += 2 + s.endRunes += 2 + return s.makeValueToken(Spread, "") + } + case ':': + return s.makeValueToken(Colon, "") + case '=': + return s.makeValueToken(Equals, "") + case '@': + return s.makeValueToken(At, "") + case '[': + return s.makeValueToken(BracketL, "") + case ']': + return s.makeValueToken(BracketR, "") + case '{': + return s.makeValueToken(BraceL, "") + case '}': + return s.makeValueToken(BraceR, "") + case '|': + return s.makeValueToken(Pipe, "") + case '#': + s.readComment() + return s.ReadToken() + + case '_', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z': + return s.readName() + + case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + return s.readNumber() + + case '"': + if len(s.Input) > s.start+2 && s.Input[s.start:s.start+3] == `"""` { + return s.readBlockString() + } + + return s.readString() + } + + s.end-- + s.endRunes-- + + if r < 0x0020 && r != 0x0009 && r != 0x000a && r != 0x000d { + return s.makeError(`Cannot contain the invalid character "\u%04d"`, r) + } + + if r == '\'' { + return s.makeError(`Unexpected single quote character ('), did you mean to use a double quote (")?`) + } + + return s.makeError(`Cannot parse the unexpected character "%s".`, string(r)) +} + +// ws reads from body starting at startPosition until it finds a non-whitespace +// or commented character, and updates the token end to include all whitespace +func (s *Lexer) ws() { + for s.end < len(s.Input) { + switch s.Input[s.end] { + case '\t', ' ', ',': + s.end++ + s.endRunes++ + case '\n': + s.end++ + s.endRunes++ + s.line++ + s.lineStartRunes = s.endRunes + case '\r': + s.end++ + s.endRunes++ + s.line++ + s.lineStartRunes = s.endRunes + // skip the following newline if its there + if s.end < len(s.Input) && s.Input[s.end] == '\n' { + s.end++ + s.endRunes++ + } + // byte order mark, given ws is hot path we aren't relying on the unicode package here. + case 0xef: + if s.end+2 < len(s.Input) && s.Input[s.end+1] == 0xBB && s.Input[s.end+2] == 0xBF { + s.end += 3 + s.endRunes++ + } else { + return + } + default: + return + } + } +} + +// readComment from the input +// +// #[\u0009\u0020-\uFFFF]* +func (s *Lexer) readComment() (Token, *gqlerror.Error) { + for s.end < len(s.Input) { + r, w := s.peek() + + // SourceCharacter but not LineTerminator + if r > 0x001f || r == '\t' { + s.end += w + s.endRunes++ + } else { + break + } + } + + return s.makeToken(Comment) +} + +// readNumber from the input, either a float +// or an int depending on whether a decimal point appears. +// +// Int: -?(0|[1-9][0-9]*) +// Float: -?(0|[1-9][0-9]*)(\.[0-9]+)?((E|e)(+|-)?[0-9]+)? +func (s *Lexer) readNumber() (Token, *gqlerror.Error) { + float := false + + // backup to the first digit + s.end-- + s.endRunes-- + + s.acceptByte('-') + + if s.acceptByte('0') { + if consumed := s.acceptDigits(); consumed != 0 { + s.end -= consumed + s.endRunes -= consumed + return s.makeError("Invalid number, unexpected digit after 0: %s.", s.describeNext()) + } + } else { + if consumed := s.acceptDigits(); consumed == 0 { + return s.makeError("Invalid number, expected digit but got: %s.", s.describeNext()) + } + } + + if s.acceptByte('.') { + float = true + + if consumed := s.acceptDigits(); consumed == 0 { + return s.makeError("Invalid number, expected digit but got: %s.", s.describeNext()) + } + } + + if s.acceptByte('e', 'E') { + float = true + + s.acceptByte('-', '+') + + if consumed := s.acceptDigits(); consumed == 0 { + return s.makeError("Invalid number, expected digit but got: %s.", s.describeNext()) + } + } + + if float { + return s.makeToken(Float) + } else { + return s.makeToken(Int) + } +} + +// acceptByte if it matches any of given bytes, returning true if it found anything +func (s *Lexer) acceptByte(bytes ...uint8) bool { + if s.end >= len(s.Input) { + return false + } + + for _, accepted := range bytes { + if s.Input[s.end] == accepted { + s.end++ + s.endRunes++ + return true + } + } + return false +} + +// acceptDigits from the input, returning the number of digits it found +func (s *Lexer) acceptDigits() int { + consumed := 0 + for s.end < len(s.Input) && s.Input[s.end] >= '0' && s.Input[s.end] <= '9' { + s.end++ + s.endRunes++ + consumed++ + } + + return consumed +} + +// describeNext peeks at the input and returns a human readable string. This should will alloc +// and should only be used in errors +func (s *Lexer) describeNext() string { + if s.end < len(s.Input) { + return `"` + string(s.Input[s.end]) + `"` + } + return "<EOF>" +} + +// readString from the input +// +// "([^"\\\u000A\u000D]|(\\(u[0-9a-fA-F]{4}|["\\/bfnrt])))*" +func (s *Lexer) readString() (Token, *gqlerror.Error) { + inputLen := len(s.Input) + + // this buffer is lazily created only if there are escape characters. + var buf *bytes.Buffer + + // skip the opening quote + s.start++ + s.startRunes++ + + for s.end < inputLen { + r := s.Input[s.end] + if r == '\n' || r == '\r' { + break + } + if r < 0x0020 && r != '\t' { + return s.makeError(`Invalid character within String: "\u%04d".`, r) + } + switch r { + default: + var char = rune(r) + var w = 1 + + // skip unicode overhead if we are in the ascii range + if r >= 127 { + char, w = utf8.DecodeRuneInString(s.Input[s.end:]) + } + s.end += w + s.endRunes++ + + if buf != nil { + buf.WriteRune(char) + } + + case '"': + t, err := s.makeToken(String) + // the token should not include the quotes in its value, but should cover them in its position + t.Pos.Start-- + t.Pos.End++ + + if buf != nil { + t.Value = buf.String() + } + + // skip the close quote + s.end++ + s.endRunes++ + + return t, err + + case '\\': + if s.end+1 >= inputLen { + s.end++ + s.endRunes++ + return s.makeError(`Invalid character escape sequence.`) + } + + if buf == nil { + buf = bytes.NewBufferString(s.Input[s.start:s.end]) + } + + escape := s.Input[s.end+1] + + if escape == 'u' { + if s.end+6 >= inputLen { + s.end++ + s.endRunes++ + return s.makeError("Invalid character escape sequence: \\%s.", s.Input[s.end:]) + } + + r, ok := unhex(s.Input[s.end+2 : s.end+6]) + if !ok { + s.end++ + s.endRunes++ + return s.makeError("Invalid character escape sequence: \\%s.", s.Input[s.end:s.end+5]) + } + buf.WriteRune(r) + s.end += 6 + s.endRunes += 6 + } else { + switch escape { + case '"', '/', '\\': + buf.WriteByte(escape) + case 'b': + buf.WriteByte('\b') + case 'f': + buf.WriteByte('\f') + case 'n': + buf.WriteByte('\n') + case 'r': + buf.WriteByte('\r') + case 't': + buf.WriteByte('\t') + default: + s.end += 1 + s.endRunes += 1 + return s.makeError("Invalid character escape sequence: \\%s.", string(escape)) + } + s.end += 2 + s.endRunes += 2 + } + } + } + + return s.makeError("Unterminated string.") +} + +// readBlockString from the input +// +// """("?"?(\\"""|\\(?!=""")|[^"\\]))*""" +func (s *Lexer) readBlockString() (Token, *gqlerror.Error) { + inputLen := len(s.Input) + + var buf bytes.Buffer + + // skip the opening quote + s.start += 3 + s.startRunes += 3 + s.end += 2 + s.endRunes += 2 + + for s.end < inputLen { + r := s.Input[s.end] + + // Closing triple quote (""") + if r == '"' && s.end+3 <= inputLen && s.Input[s.end:s.end+3] == `"""` { + t, err := s.makeValueToken(BlockString, blockStringValue(buf.String())) + + // the token should not include the quotes in its value, but should cover them in its position + t.Pos.Start -= 3 + t.Pos.End += 3 + + // skip the close quote + s.end += 3 + s.endRunes += 3 + + return t, err + } + + // SourceCharacter + if r < 0x0020 && r != '\t' && r != '\n' && r != '\r' { + return s.makeError(`Invalid character within String: "\u%04d".`, r) + } + + if r == '\\' && s.end+4 <= inputLen && s.Input[s.end:s.end+4] == `\"""` { + buf.WriteString(`"""`) + s.end += 4 + s.endRunes += 4 + } else if r == '\r' { + if s.end+1 <= inputLen && s.Input[s.end+1] == '\n' { + s.end++ + s.endRunes++ + } + + buf.WriteByte('\n') + s.end++ + s.endRunes++ + } else { + var char = rune(r) + var w = 1 + + // skip unicode overhead if we are in the ascii range + if r >= 127 { + char, w = utf8.DecodeRuneInString(s.Input[s.end:]) + } + s.end += w + s.endRunes++ + buf.WriteRune(char) + } + } + + return s.makeError("Unterminated string.") +} + +func unhex(b string) (v rune, ok bool) { + for _, c := range b { + v <<= 4 + switch { + case '0' <= c && c <= '9': + v |= c - '0' + case 'a' <= c && c <= 'f': + v |= c - 'a' + 10 + case 'A' <= c && c <= 'F': + v |= c - 'A' + 10 + default: + return 0, false + } + } + + return v, true +} + +// readName from the input +// +// [_A-Za-z][_0-9A-Za-z]* +func (s *Lexer) readName() (Token, *gqlerror.Error) { + for s.end < len(s.Input) { + r, w := s.peek() + + if (r >= '0' && r <= '9') || (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || r == '_' { + s.end += w + s.endRunes++ + } else { + break + } + } + + return s.makeToken(Name) +} |