blob: 10016c101caeccb0df36b705c764ca706117e566 [file] [log] [blame]
package calc
import (
"fmt"
"strings"
"unicode"
)
type itemType int
const (
itemError itemType = iota
itemIdentifier
itemNum
itemString
itemLParen
itemRParen
itemComma
itemEOF
)
const eof byte = 0xff
// item is returned by the lexer.nextItem() as it parses in the input.
type item struct {
typ itemType
val string
}
// stateFn is a function that represents the current state of the lexer.
type stateFn func(*lexer) stateFn
// lexer parses an input string and returns items for each lexeme that's found.
type lexer struct {
input string // The string being parsed.
start int // The offset of the current lexical item.
pos int // Current position in input.
items chan item // Channel by which items are delivered.
state stateFn // The next lexing function.
peekBuffer []item // A peekBuffer for peek'd items.
}
// nextItem returns the next item from the input.
func (l *lexer) nextItem() item {
if len(l.peekBuffer) > 0 {
item := l.peekBuffer[0]
l.peekBuffer = l.peekBuffer[:0]
return item
}
item := <-l.items
return item
}
// peekItem allows the caller to look ahead and see the next item that
// nextItem() will return.
func (l *lexer) peekItem() item {
item := <-l.items
l.peekBuffer = append(l.peekBuffer, item)
return item
}
// accept consumes the next char if it's from the valid set.
func (l *lexer) accept(valid string) bool {
if strings.IndexByte(valid, l.next()) >= 0 {
return true
}
l.backUp()
return false
}
// acceptRun consumes a run of chars from the valid set.
func (l *lexer) acceptRun(valid string) {
for strings.IndexByte(valid, l.next()) >= 0 {
}
l.backUp()
}
// ingore the current text.
func (l *lexer) ignore() {
l.start = l.pos
}
// errorf returns an error token and terminates the scan by passing
// back a nil pointer that will be the next state, terminating l.run.
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
l.items <- item{typ: itemError, val: fmt.Sprintf(format, args...)}
return nil
}
// newLexer returns a new lexer for the given string.
func newLexer(input string) *lexer {
l := &lexer{
input: input,
start: 0,
pos: 0,
items: make(chan item, 2),
state: lexExp,
peekBuffer: []item{},
}
go l.run()
return l
}
// next returns the next char in the input.
func (l *lexer) next() byte {
if int(l.pos) >= len(l.input) {
return eof
}
ch := l.input[l.pos]
l.pos += 1
return ch
}
// backUp steps back one rune. Can only be called once per call of next.
func (l *lexer) backUp() {
l.pos -= 1
}
// run runs the state machine for the lexer.
func (l *lexer) run() {
for l.state = lexExp; l.state != nil; l.state = l.state(l) {
}
}
// emit puts a new item on the channel.
func (l *lexer) emit(t itemType) {
l.items <- item{
typ: t,
val: l.input[l.start:l.pos],
}
l.start = l.pos
}
// lexExp parses the input expression.
func lexExp(l *lexer) stateFn {
switch r := l.next(); {
case r == eof:
l.emit(itemEOF)
return nil
case r == '"':
return lexString
case unicode.IsLetter(rune(r)):
return lexIdentifier
case r == ')':
l.emit(itemRParen)
return lexExp
case r == '(':
l.emit(itemLParen)
return lexExp
case r == ',':
l.emit(itemComma)
return lexExp
case unicode.IsSpace(rune(r)):
l.ignore()
return lexExp
case r == '+' || r == '-' || ('0' <= r && r <= '9'):
l.backUp()
return lexNumber
default:
return l.errorf("unrecognized char: %#U", r)
}
}
// lexString parses double-quote delimited strings.
func lexString(l *lexer) stateFn {
l.ignore()
r := l.next()
for ; r != eof; r = l.next() {
if r == '"' {
l.backUp()
l.emit(itemString)
l.next()
l.ignore()
break
}
}
if r == eof {
l.errorf("Unterminated string: %s", l.input[l.start:l.pos])
}
return lexExp
}
// lexNumber parses numbers, things that looks like ints and floats.
func lexNumber(l *lexer) stateFn {
// Optional leading sign.
l.accept("+-")
// Is it hex?
digits := "0123456789"
if l.accept("0") && l.accept("xX") {
digits = "0123456789abcdefABCDEF"
}
l.acceptRun(digits)
if l.accept(".") {
l.acceptRun(digits)
}
if l.accept("eE") {
l.accept("+-")
l.acceptRun("0123456789")
}
l.emit(itemNum)
return lexExp
}
// lexIdentifier parses function names.
func lexIdentifier(l *lexer) stateFn {
for {
r := l.next()
if !unicode.IsLetter(rune(r)) && !unicode.IsDigit(rune(r)) && r != '_' {
l.backUp()
break
}
}
l.emit(itemIdentifier)
return lexExp
}