func (l *lexer) number() token {
	start := l.pos
	n := 0
	if l.input[0] == '-' {
		n++
	}
	if n >= len(l.input) || !isDigit(l.input[n]) {
		return token{
			kind:   unknown,
			source: l.consume(n),
			start:  start,
		}
	}
	n++
	if l.input[n-1] != '0' {
		for n < len(l.input) && isDigit(l.input[n]) {
			n++
		}
	}
	if n+1 >= len(l.input) {
		return token{
			kind:   intValue,
			source: l.consume(n),
			start:  start,
		}
	}
	switch l.input[n] {
	case '.':
		if !isDigit(l.input[n+1]) {
			return token{
				kind:   intValue,
				source: l.consume(n),
				start:  start,
			}
		}
		n += 2
		for n < len(l.input) && isDigit(l.input[n]) {
			n++
		}
		if n < len(l.input) && (l.input[n] == 'e' || l.input[n] == 'E') {
			if expEnd := l.scanExponentPart(n); expEnd != -1 {
				n = expEnd
			}
		}
		return token{
			kind:   floatValue,
			source: l.consume(n),
			start:  start,
		}
	case 'e', 'E':
		expEnd := l.scanExponentPart(n)
		if expEnd == -1 {
			return token{
				kind:   intValue,
				source: l.consume(n),
				start:  start,
			}
		}
		return token{
			kind:   floatValue,
			source: l.consume(expEnd),
			start:  start,
		}
	default:
		return token{
			kind:   intValue,
			source: l.consume(n),
			start:  start,
		}
	}
}
number parses either an integer or a floating-point literal (both start with an integer).
https://graphql.github.io/graphql-spec/June2018/#sec-Int-Value
https://graphql.github.io/graphql-spec/June2018/#sec-Float-Value
package gqlang
import (
	"fmt"
	"strings"
	"golang.org/x/xerrors"
)
type lexer struct {
	input string
	pos   Pos
}
func lex(input string) []token {
	l := &lexer{input: input}
	var tokens []token
	for {
		tok := l.next()
		if tok.source == "" {
			break
		}
		tokens = append(tokens, tok)
	}
	return tokens
}
func (l *lexer) next() token {
	l.skipIgnored()
	start := l.pos
	if len(l.input) == 0 {
		return token{start: start}
	}
	for pat, kind := range punctuators {
		if strings.HasPrefix(l.input, pat) {
			return token{
				kind:   kind,
				source: l.consume(len(pat)),
				start:  start,
			}
		}
	}
	switch c := l.input[0]; {
	case isNameChar(c):
		n := 1
		for ; n < len(l.input); n++ {
			if c := l.input[n]; !isNameChar(c) && !isDigit(c) {
				break
			}
		}
		return token{
			kind:   name,
			source: l.consume(n),
			start:  start,
		}
	case c == '"':
		if strings.HasPrefix(l.input, `"""`) {
			return l.blockString()
		}
		return l.simpleString()
	case c == '-' || isDigit(c):
		return l.number()
	default:
		return token{
			kind:   unknown,
			source: l.consume(1),
			start:  start,
		}
	}
}
// simpleString parses a quoted string.
// https://graphql.github.io/graphql-spec/June2018/#sec-String-Value
func (l *lexer) simpleString() token {
	start := l.pos
	for n := 1; n < len(l.input); n++ {
		switch l.input[n] {
		case '\\':
			n++
		case '\n':
			// Stop at end of line.
			return token{
				kind:   stringValue,
				source: l.consume(n),
				start:  start,
			}
		case '"':
			return token{
				kind:   stringValue,
				source: l.consume(n + 1),
				start:  start,
			}
		}
	}
	// Unexpected EOF.
	return token{
		kind:   stringValue,
		source: l.consume(len(l.input)),
		start:  start,
	}
}
// blockString parses a triple-quoted string.
// https://graphql.github.io/graphql-spec/June2018/#sec-String-Value
func (l *lexer) blockString() token {
	const marker = `"""`
	start := l.pos
	for i := len(marker); ; {
		j := strings.Index(l.input[i:], `"""`)
		if j == -1 {
			// Unexpected EOF.
			return token{
				kind:   stringValue,
				source: l.consume(len(l.input)),
				start:  start,
			}
		}
		if l.input[i+j-1] != '\\' {
			return token{
				kind:   stringValue,
				source: l.consume(i + j + len(marker)),
				start:  start,
			}
		}
		// Move past escape.
		i += j + len(marker)
	}
}
func validateStringToken(input string, tok token) []error {
	if strings.HasPrefix(tok.source, `"""`) {
		rest := tok.source[3:]
		if strings.HasSuffix(rest, `\"""`) || !strings.HasSuffix(rest, `"""`) {
			return []error{&posError{
				input: input,
				pos:   tok.end(),
				err:   xerrors.New("unterminated string constant"),
			}}
		}
		return nil
	}
	rest := tok.source[1:]
	var errs []error
findEscapes:
	for {
		backslash := strings.IndexByte(rest, '\\')
		if backslash == -1 {
			break
		}
		if backslash+2 > len(rest) {
			rest = ""
			break
		}
		const escapeLen = 2
		switch rest[backslash+1] {
		case '"', '\\', '/', 'b', 'f', 'n', 'r', 't':
			rest = rest[backslash+escapeLen:]
		case 'u':
			const unicodeEscapeLen = escapeLen + 4
			for i := backslash + escapeLen; i < backslash+unicodeEscapeLen && i < len(rest); i++ {
				switch c := rest[i]; {
				case '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F':
					// Good
				case c == '"':
					// Should only happen at end of string
					errs = append(errs, &posError{
						input: input,
						pos:   tok.start + Pos(len(tok.source)-len(rest)+i),
						err:   xerrors.New("expected hex digit, got '\"'"),
					})
					return errs
				default:
					errs = append(errs, &posError{
						input: input,
						pos:   tok.start + Pos(len(tok.source)-len(rest)+i),
						err:   xerrors.Errorf("expected hex digit, found %q", c),
					})
				}
			}
			if backslash+unicodeEscapeLen > len(rest) {
				rest = ""
				break findEscapes
			}
			rest = rest[backslash+unicodeEscapeLen:]
		default:
			errs = append(errs, &posError{
				input: input,
				pos:   tok.start + Pos(len(tok.source)-len(rest)+backslash+1),
				err:   xerrors.Errorf("invalid string escape character %q", rest[backslash+1]),
			})
			rest = rest[backslash+escapeLen:]
		}
	}
	if !strings.HasSuffix(rest, `"`) {
		errs = append(errs, &posError{
			input: input,
			pos:   tok.end(),
			err:   xerrors.New("unterminated string constant"),
		})
	}
	return errs
}