surrealpatch/sql/scanner.go

765 lines
14 KiB
Go
Raw Normal View History

2016-02-26 17:27:07 +00:00
// Copyright © 2016 Abcum Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sql
import (
"bufio"
"bytes"
"io"
2016-09-06 13:30:59 +00:00
"regexp"
2016-02-26 17:27:07 +00:00
"strings"
"time"
)
// Scanner represents a lexical scanner.
type Scanner struct {
b []rune // any runes before
a []rune // any runes after
p *Parser
2016-02-26 17:27:07 +00:00
r *bufio.Reader
}
// NewScanner returns a new instance of Scanner.
func NewScanner(p *Parser, r io.Reader) *Scanner {
return &Scanner{p: p, r: bufio.NewReader(r)}
2016-02-26 17:27:07 +00:00
}
// Scan returns the next token and literal value.
func (s *Scanner) Scan() (tok Token, lit string, val interface{}) {
2016-02-26 17:27:07 +00:00
// Read the next rune.
2016-09-06 13:30:59 +00:00
ch := s.next()
2016-02-26 17:27:07 +00:00
// If we see whitespace then consume all contiguous whitespace.
2016-09-07 15:58:50 +00:00
if isBlank(ch) {
2016-09-06 13:30:59 +00:00
return s.scanBlank(ch)
2016-02-26 17:27:07 +00:00
}
2016-09-07 15:58:50 +00:00
// If we see a letter then consume as a string.
2016-02-26 17:27:07 +00:00
if isLetter(ch) {
2016-09-06 13:30:59 +00:00
return s.scanIdent(ch)
2016-02-26 17:27:07 +00:00
}
// If we see a number then consume as a number.
if isNumber(ch) {
2016-09-06 13:30:59 +00:00
return s.scanNumber(ch)
2016-02-26 17:27:07 +00:00
}
// Otherwise read the individual character.
switch ch {
case eof:
return EOF, "", val
2016-02-26 17:27:07 +00:00
case '*':
return ALL, string(ch), val
2016-09-06 13:30:59 +00:00
case '×':
return MUL, string(ch), val
2016-09-06 13:30:59 +00:00
case '∙':
return MUL, string(ch), val
2016-09-06 13:30:59 +00:00
case '÷':
return DIV, string(ch), val
2016-02-26 17:27:07 +00:00
case ',':
return COMMA, string(ch), val
2016-02-26 17:27:07 +00:00
case '.':
return DOT, string(ch), val
case '@':
return s.scanThing(ch)
2016-02-26 17:27:07 +00:00
case '"':
2016-09-06 13:30:59 +00:00
return s.scanString(ch)
2016-02-26 17:27:07 +00:00
case '\'':
2016-09-06 13:30:59 +00:00
return s.scanString(ch)
2016-02-26 17:27:07 +00:00
case '`':
2016-09-06 13:30:59 +00:00
return s.scanQuoted(ch)
2016-05-23 12:32:02 +00:00
case '⟨':
2016-09-06 13:30:59 +00:00
return s.scanQuoted(ch)
2016-02-26 17:27:07 +00:00
case '{':
2016-09-06 13:30:59 +00:00
return s.scanObject(ch)
2016-02-26 17:27:07 +00:00
case '[':
2016-09-06 13:30:59 +00:00
return s.scanObject(ch)
case '$':
return s.scanParams(ch)
2016-02-26 17:27:07 +00:00
case ':':
return COLON, string(ch), val
2016-02-26 17:27:07 +00:00
case ';':
return SEMICOLON, string(ch), val
2016-02-26 17:27:07 +00:00
case '(':
return LPAREN, string(ch), val
2016-02-26 17:27:07 +00:00
case ')':
return RPAREN, string(ch), val
2016-09-06 13:30:59 +00:00
case '¬':
return NEQ, string(ch), val
2016-09-06 13:30:59 +00:00
case '≤':
return LTE, string(ch), val
2016-09-06 13:30:59 +00:00
case '≥':
return GTE, string(ch), val
2016-09-06 13:30:59 +00:00
case '~':
return SIN, string(ch), val
2016-09-06 13:30:59 +00:00
case '∋':
return SIN, string(ch), val
2016-09-06 13:30:59 +00:00
case '∌':
return SNI, string(ch), val
2016-09-06 13:30:59 +00:00
case '⊇':
return CONTAINSALL, string(ch), val
2016-09-06 13:30:59 +00:00
case '⊃':
return CONTAINSSOME, string(ch), val
2016-09-06 13:30:59 +00:00
case '⊅':
return CONTAINSNONE, string(ch), val
2016-09-06 13:30:59 +00:00
case '∈':
return INS, string(ch), val
2016-09-06 13:30:59 +00:00
case '∉':
return NIS, string(ch), val
2016-09-06 13:30:59 +00:00
case '⊆':
return ALLCONTAINEDIN, string(ch), val
2016-09-06 13:30:59 +00:00
case '⊂':
return SOMECONTAINEDIN, string(ch), val
2016-09-06 13:30:59 +00:00
case '⊄':
return NONECONTAINEDIN, string(ch), val
2016-09-06 13:30:59 +00:00
case '#':
return s.scanCommentSingle(ch)
2016-07-21 21:45:35 +00:00
case '/':
chn := s.next()
switch {
case chn == '*':
return s.scanCommentMultiple(ch)
2016-09-06 13:30:59 +00:00
case chn == ' ':
s.undo()
return DIV, string(ch), val
2016-07-21 21:45:35 +00:00
default:
2016-09-06 13:30:59 +00:00
s.undo()
2016-07-21 21:45:35 +00:00
return s.scanRegexp(ch)
2016-07-04 10:37:37 +00:00
}
2016-09-06 13:30:59 +00:00
case '=':
chn := s.next()
switch {
case chn == '~':
return SIN, "=~", val
2016-09-06 13:30:59 +00:00
case chn == '=':
return EEQ, "==", val
2016-09-06 13:30:59 +00:00
default:
s.undo()
return EQ, string(ch), val
2016-02-26 17:27:07 +00:00
}
2016-09-06 13:30:59 +00:00
case '?':
chn := s.next()
switch {
case chn == '=':
return ANY, "?=", val
2016-09-06 13:30:59 +00:00
default:
s.undo()
return QMARK, string(ch), val
2016-02-26 17:27:07 +00:00
}
2016-09-06 13:30:59 +00:00
case '!':
chn := s.next()
switch {
case chn == '=':
if s.next() == '=' {
return NEE, "!==", val
2016-09-06 13:30:59 +00:00
} else {
s.undo()
return NEQ, "!=", val
2016-07-04 10:37:37 +00:00
}
2016-09-06 13:30:59 +00:00
case chn == '~':
return SNI, "!~", val
2016-09-06 13:30:59 +00:00
default:
s.undo()
return EXC, string(ch), val
2016-07-04 10:37:37 +00:00
}
2016-09-06 13:30:59 +00:00
case '+':
chn := s.next()
switch {
case chn == '=':
return INC, "+=", val
2016-09-06 13:30:59 +00:00
case isNumber(chn):
return s.scanNumber(ch, chn)
default:
s.undo()
return ADD, string(ch), val
2016-09-06 13:30:59 +00:00
}
case '-':
chn := s.next()
switch {
case chn == '=':
return DEC, "-=", val
2016-09-06 13:30:59 +00:00
case chn == '>':
return OEDGE, "->", val
2016-09-06 13:30:59 +00:00
case chn == '-':
return s.scanCommentSingle(ch)
case isNumber(chn):
return s.scanNumber(ch, chn)
default:
s.undo()
return SUB, string(ch), val
2016-02-26 17:27:07 +00:00
}
case '>':
2016-09-06 13:30:59 +00:00
chn := s.next()
switch {
case chn == '=':
return GTE, ">=", val
2016-09-06 13:30:59 +00:00
default:
s.undo()
return GT, string(ch), val
2016-09-06 13:30:59 +00:00
}
case '<':
chn := s.next()
switch {
case chn == '>':
return NEQ, "<>", val
2016-09-06 13:30:59 +00:00
case chn == '=':
return LTE, "<=", val
2016-09-06 13:30:59 +00:00
case chn == '-':
if s.next() == '>' {
return BEDGE, "<->", val
2016-09-06 13:30:59 +00:00
} else {
s.undo()
return IEDGE, "<-", val
2016-09-06 13:30:59 +00:00
}
default:
s.undo()
return LT, string(ch), val
2016-02-26 17:27:07 +00:00
}
}
return ILLEGAL, string(ch), val
2016-02-26 17:27:07 +00:00
}
2016-09-06 13:30:59 +00:00
// scanBlank consumes the current rune and all contiguous whitespace.
func (s *Scanner) scanBlank(chp ...rune) (tok Token, lit string, val interface{}) {
2016-09-06 13:30:59 +00:00
tok = WS
2016-02-26 17:27:07 +00:00
2016-09-06 13:30:59 +00:00
// Create a buffer
2016-02-26 17:27:07 +00:00
var buf bytes.Buffer
2016-09-06 13:30:59 +00:00
// Read passed in runes
for _, ch := range chp {
buf.WriteRune(ch)
}
// Read subsequent characters
2016-02-26 17:27:07 +00:00
for {
2016-09-06 13:30:59 +00:00
if ch := s.next(); ch == eof {
2016-02-26 17:27:07 +00:00
break
2016-09-07 15:58:50 +00:00
} else if !isBlank(ch) {
2016-09-06 13:30:59 +00:00
s.undo()
2016-02-26 17:27:07 +00:00
break
} else {
buf.WriteRune(ch)
}
}
return tok, buf.String(), val
2016-09-06 13:30:59 +00:00
}
// scanCommentSingle consumes the current rune and all contiguous whitespace.
func (s *Scanner) scanCommentSingle(chp ...rune) (tok Token, lit string, val interface{}) {
2016-09-06 13:30:59 +00:00
tok = WS
// Create a buffer
var buf bytes.Buffer
// Read passed in runes
for _, ch := range chp {
buf.WriteRune(ch)
}
// Read subsequent characters
for {
if ch := s.next(); ch == '\n' || ch == '\r' {
buf.WriteRune(ch)
break
} else {
buf.WriteRune(ch)
}
}
return tok, buf.String(), val
2016-09-06 13:30:59 +00:00
}
// scanCommentMultiple consumes the current rune and all contiguous whitespace.
func (s *Scanner) scanCommentMultiple(chp ...rune) (tok Token, lit string, val interface{}) {
2016-09-06 13:30:59 +00:00
tok = WS
// Create a buffer
var buf bytes.Buffer
// Read passed in runes
for _, ch := range chp {
buf.WriteRune(ch)
}
// Read subsequent characters
for {
if ch := s.next(); ch == eof {
break
} else if ch == '*' {
if chn := s.next(); chn == '/' {
buf.WriteRune(chn)
break
}
buf.WriteRune(ch)
} else {
buf.WriteRune(ch)
}
}
return tok, buf.String(), val
2016-09-06 13:30:59 +00:00
}
func (s *Scanner) scanParams(chp ...rune) (tok Token, lit string, val interface{}) {
2016-09-06 13:30:59 +00:00
tok, lit, val = s.scanIdent(chp...)
2016-09-06 13:30:59 +00:00
if s.p.is(tok, IDENT) {
return PARAM, lit, nil
2016-09-06 13:30:59 +00:00
}
return
2016-02-26 17:27:07 +00:00
}
// scanIdent consumes the current rune and all contiguous ident runes.
func (s *Scanner) scanIdent(chp ...rune) (tok Token, lit string, val interface{}) {
2016-09-06 13:30:59 +00:00
tok = IDENT
2016-02-26 17:27:07 +00:00
2016-09-06 13:30:59 +00:00
// Create a buffer
2016-02-26 17:27:07 +00:00
var buf bytes.Buffer
2016-09-06 13:30:59 +00:00
// Read passed in runes
for _, ch := range chp {
buf.WriteRune(ch)
}
// Read subsequent characters
2016-02-26 17:27:07 +00:00
for {
2016-09-06 13:30:59 +00:00
if ch := s.next(); ch == eof {
2016-02-26 17:27:07 +00:00
break
} else if !isIdentChar(ch) {
2016-09-06 13:30:59 +00:00
s.undo()
2016-02-26 17:27:07 +00:00
break
} else {
buf.WriteRune(ch)
}
}
// If the string matches a keyword then return that keyword.
if tok := keywords[strings.ToUpper(buf.String())]; tok > 0 {
return tok, buf.String(), val
2016-02-26 17:27:07 +00:00
}
if val, err := time.ParseDuration(buf.String()); err == nil {
return DURATION, buf.String(), val
2016-05-23 12:32:02 +00:00
}
2016-02-26 17:27:07 +00:00
// Otherwise return as a regular identifier.
return tok, buf.String(), val
2016-02-26 17:27:07 +00:00
}
// scanThing consumes the current rune and all contiguous ident runes.
func (s *Scanner) scanThing(chp ...rune) (tok Token, lit string, val interface{}) {
tok = THING
// Create a buffer
var buf bytes.Buffer
var beg bytes.Buffer
var mid bytes.Buffer
var end bytes.Buffer
// Read passed in runes
for _, ch := range chp {
buf.WriteRune(ch)
}
for {
if ch := s.next(); ch == eof {
break
} else if isLetter(ch) {
tok, lit, _ = s.scanIdent(ch)
beg.WriteString(lit)
} else if isNumber(ch) {
tok, lit, _ = s.scanNumber(ch)
beg.WriteString(lit)
} else if ch == '`' {
tok, lit, _ = s.scanQuoted(ch)
beg.WriteString(lit)
} else if ch == '{' {
tok, lit, _ = s.scanQuoted(ch)
beg.WriteString(lit)
} else if ch == '⟨' {
tok, lit, _ = s.scanQuoted(ch)
beg.WriteString(lit)
} else {
s.undo()
break
}
}
if beg.Len() < 1 {
return ILLEGAL, buf.String() + beg.String() + mid.String() + end.String(), val
}
for {
if ch := s.next(); ch != ':' {
s.undo()
break
} else {
mid.WriteRune(ch)
break
}
}
if mid.Len() < 1 {
return ILLEGAL, buf.String() + beg.String() + mid.String() + end.String(), val
}
for {
if ch := s.next(); ch == eof {
break
} else if isLetter(ch) {
tok, lit, _ = s.scanIdent(ch)
end.WriteString(lit)
} else if isNumber(ch) {
tok, lit, _ = s.scanNumber(ch)
end.WriteString(lit)
} else if ch == '`' {
tok, lit, _ = s.scanQuoted(ch)
beg.WriteString(lit)
} else if ch == '{' {
tok, lit, _ = s.scanQuoted(ch)
end.WriteString(lit)
} else if ch == '⟨' {
tok, lit, _ = s.scanQuoted(ch)
end.WriteString(lit)
} else {
s.undo()
break
}
}
if end.Len() < 1 {
return ILLEGAL, buf.String() + beg.String() + mid.String() + end.String(), val
}
val = &Thing{
TB: beg.String(),
ID: end.String(),
}
switch tok {
case DATE, TIME, NUMBER, DOUBLE:
val.(*Thing).ID, _ = s.p.declare(tok, end.String())
case REGION:
return ILLEGAL, buf.String() + beg.String() + mid.String() + end.String(), val
}
// Otherwise return as a regular thing.
return THING, buf.String() + beg.String() + mid.String() + end.String(), val
}
func (s *Scanner) scanNumber(chp ...rune) (tok Token, lit string, val interface{}) {
2016-05-23 12:32:02 +00:00
tok = NUMBER
2016-09-06 13:30:59 +00:00
// Create a buffer
2016-05-23 12:32:02 +00:00
var buf bytes.Buffer
2016-09-06 13:30:59 +00:00
// Read passed in runes
for _, ch := range chp {
buf.WriteRune(ch)
}
// Read subsequent characters
2016-05-23 12:32:02 +00:00
for {
2016-09-06 13:30:59 +00:00
if ch := s.next(); ch == eof {
2016-05-23 12:32:02 +00:00
break
} else if isNumber(ch) {
buf.WriteRune(ch)
} else if isLetter(ch) {
tok = IDENT
buf.WriteRune(ch)
} else if ch == '.' {
if tok == DOUBLE {
tok = IDENT
}
if tok == NUMBER {
tok = DOUBLE
}
buf.WriteRune(ch)
} else {
2016-09-06 13:30:59 +00:00
s.undo()
2016-05-23 12:32:02 +00:00
break
}
}
return tok, buf.String(), nil
2016-05-23 12:32:02 +00:00
}
func (s *Scanner) scanQuoted(chp ...rune) (tok Token, lit string, val interface{}) {
2016-02-26 17:27:07 +00:00
tok, lit, val = s.scanString(chp...)
2016-02-26 17:27:07 +00:00
if s.p.is(tok, STRING) {
return IDENT, lit, nil
2016-02-26 17:27:07 +00:00
}
return
2016-02-26 17:27:07 +00:00
}
func (s *Scanner) scanString(chp ...rune) (tok Token, lit string, val interface{}) {
2016-02-26 17:27:07 +00:00
2016-09-06 13:30:59 +00:00
beg := chp[0]
2016-05-23 12:32:02 +00:00
end := beg
if beg == '"' {
end = '"'
}
if beg == '`' {
end = '`'
}
if beg == '⟨' {
end = '⟩'
}
2016-02-26 17:27:07 +00:00
if beg == '{' {
end = '}'
}
2016-09-06 13:30:59 +00:00
tok = STRING
// Create a buffer
var buf bytes.Buffer
// Ignore passed in runes
// Read subsequent characters
2016-02-26 17:27:07 +00:00
for {
2016-09-06 13:30:59 +00:00
if ch := s.next(); ch == end {
2016-02-26 17:27:07 +00:00
break
} else if ch == eof {
return ILLEGAL, buf.String(), val
2016-02-26 17:27:07 +00:00
} else if ch == '\n' {
tok = REGION
buf.WriteRune(ch)
2016-05-23 12:32:02 +00:00
} else if ch == '\r' {
tok = REGION
buf.WriteRune(ch)
2016-02-26 17:27:07 +00:00
} else if ch == '\\' {
2016-09-06 13:30:59 +00:00
switch chn := s.next(); chn {
2016-05-23 12:32:02 +00:00
default:
buf.WriteRune(chn)
case 'b':
continue
case 't':
2016-09-06 13:30:59 +00:00
tok = REGION
2016-05-23 12:32:02 +00:00
buf.WriteRune('\t')
case 'r':
tok = REGION
buf.WriteRune('\r')
case 'n':
tok = REGION
2016-02-26 17:27:07 +00:00
buf.WriteRune('\n')
}
} else {
buf.WriteRune(ch)
}
}
if val, err := time.ParseDuration(buf.String()); err == nil {
return DURATION, buf.String(), val
2016-02-26 17:27:07 +00:00
}
2016-09-14 20:57:42 +00:00
if val, err := time.Parse(RFCDate, buf.String()); err == nil {
return DATE, buf.String(), val.UTC()
2016-02-26 17:27:07 +00:00
}
2016-09-14 20:57:42 +00:00
if val, err := time.Parse(RFCTime, buf.String()); err == nil {
return TIME, buf.String(), val.UTC()
}
if val, err := time.Parse(RFCNorm, buf.String()); err == nil {
return TIME, buf.String(), val.UTC()
}
if val, err := time.Parse(RFCText, buf.String()); err == nil {
return TIME, buf.String(), val.UTC()
2016-02-26 17:27:07 +00:00
}
return tok, buf.String(), val
2016-02-26 17:27:07 +00:00
}
func (s *Scanner) scanRegexp(chp ...rune) (tok Token, lit string, val interface{}) {
2016-02-26 17:27:07 +00:00
tok = IDENT
2016-09-06 13:30:59 +00:00
// Create a buffer
2016-02-26 17:27:07 +00:00
var buf bytes.Buffer
2016-09-06 13:30:59 +00:00
// Ignore passed in runes
// Read subsequent characters
for {
if ch := s.next(); ch == chp[0] {
break
} else if ch == eof {
return ILLEGAL, buf.String(), val
2016-09-06 13:30:59 +00:00
} else if ch == '\\' {
chn := s.next()
buf.WriteRune(ch)
buf.WriteRune(chn)
} else {
buf.WriteRune(ch)
}
}
if val, err := regexp.Compile(buf.String()); err == nil {
return REGEX, buf.String(), val
2016-09-06 13:30:59 +00:00
}
return tok, buf.String(), val
2016-09-06 13:30:59 +00:00
}
func (s *Scanner) scanObject(chp ...rune) (tok Token, lit string, val interface{}) {
2016-09-06 13:30:59 +00:00
beg := chp[0]
2016-02-26 17:27:07 +00:00
end := beg
2016-05-23 12:32:02 +00:00
sub := 0
2016-02-26 17:27:07 +00:00
if beg == '{' {
end = '}'
}
if beg == '[' {
end = ']'
}
2016-09-06 13:30:59 +00:00
tok = IDENT
// Create a buffer
var buf bytes.Buffer
// Read passed in runes
for _, ch := range chp {
buf.WriteRune(ch)
}
// Read subsequent characters
2016-02-26 17:27:07 +00:00
for {
2016-09-06 13:30:59 +00:00
if ch := s.next(); ch == end && sub == 0 {
buf.WriteRune(ch)
2016-02-26 17:27:07 +00:00
break
2016-05-23 12:32:02 +00:00
} else if ch == beg {
sub++
buf.WriteRune(ch)
} else if ch == end {
sub--
buf.WriteRune(ch)
2016-02-26 17:27:07 +00:00
} else if ch == eof {
return ILLEGAL, buf.String(), val
2016-02-26 17:27:07 +00:00
} else if ch == '\\' {
2016-09-06 13:30:59 +00:00
switch chn := s.next(); chn {
2016-05-23 12:32:02 +00:00
default:
return ILLEGAL, buf.String(), val
2016-05-23 12:32:02 +00:00
case 'b', 't', 'r', 'n', 'f', '"', '\\':
buf.WriteRune(ch)
buf.WriteRune(chn)
2016-02-26 17:27:07 +00:00
}
} else {
buf.WriteRune(ch)
}
}
2016-05-23 12:32:02 +00:00
if beg == '{' {
return JSON, buf.String(), val
2016-09-06 13:30:59 +00:00
}
if beg == '[' {
return ARRAY, buf.String(), val
2016-05-23 12:32:02 +00:00
}
return ILLEGAL, buf.String(), val
2016-02-26 17:27:07 +00:00
}
2016-09-06 13:30:59 +00:00
// next reads the next rune from the bufferred reader.
2016-02-26 17:27:07 +00:00
// Returns the rune(0) if an error occurs (or io.EOF is returned).
2016-09-06 13:30:59 +00:00
func (s *Scanner) next() rune {
if len(s.a) > 0 {
2016-09-06 13:30:59 +00:00
var r rune
r, s.a = s.a[len(s.a)-1], s.a[:len(s.a)-1]
s.b = append(s.b, r)
2016-09-06 13:30:59 +00:00
return r
}
r, _, err := s.r.ReadRune()
2016-02-26 17:27:07 +00:00
if err != nil {
return eof
}
s.b = append(s.b, r)
2016-09-06 13:30:59 +00:00
return r
2016-02-26 17:27:07 +00:00
}
2016-09-06 13:30:59 +00:00
// undo places the previously read rune back on the reader.
func (s *Scanner) undo() {
if len(s.b) > 0 {
2016-09-06 13:30:59 +00:00
var r rune
r, s.b = s.b[len(s.b)-1], s.b[:len(s.b)-1]
s.a = append(s.a, r)
2016-09-06 13:30:59 +00:00
return
}
2016-02-26 17:27:07 +00:00
_ = s.r.UnreadRune()
2016-09-06 13:30:59 +00:00
2016-02-26 17:27:07 +00:00
}
2016-09-07 15:58:50 +00:00
// isBlank returns true if the rune is a space, tab, or newline.
func isBlank(ch rune) bool {
2016-05-23 12:32:02 +00:00
return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'
2016-02-26 17:27:07 +00:00
}
// isNumber returns true if the rune is a number.
func isNumber(ch rune) bool {
return (ch >= '0' && ch <= '9')
}
2016-09-06 13:30:59 +00:00
// isLetter returns true if the rune is a letter.
func isLetter(ch rune) bool {
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
2016-02-26 17:27:07 +00:00
}
2016-09-06 13:30:59 +00:00
// isIdentChar returns true if the rune is allowed in a IDENT.
2016-02-26 17:27:07 +00:00
func isIdentChar(ch rune) bool {
2016-09-06 13:30:59 +00:00
return isLetter(ch) || isNumber(ch) || ch == '.' || ch == '_' || ch == '*'
2016-02-26 17:27:07 +00:00
}
// eof represents a marker rune for the end of the reader.
var eof = rune(0)