aboutsummaryrefslogblamecommitdiffstats
path: root/exprparser.go
blob: 0d62dbcc3f18614620b53d59775e4e554a114303 (plain) (tree)














































































































































































































                                                                      
package main

import (
	"bufio"
	"bytes"
	"io"
)

type Token int

const eof = rune(0)

const (
	ILLEGAL Token = iota
	EOF
	WS

	IDENT

	ASTERISK
	COMMA
	SEMICOLON
	GT
	LT
	GET
	LET
	EQ
	PLUS
	MINUS
	DIVISION
	R_PAREN
	L_PAREN
	PERIOD
)

type Scanner struct {
	r *bufio.Reader
}

func newScanner(r io.Reader) *Scanner {
	return &Scanner{r: bufio.NewReader(r)}
}

func (s *Scanner) read() rune {
	ch, _, err := s.r.ReadRune()
	if err != nil {
		return eof
	}

	return ch
}

func isWhiteSpace(ch rune) bool {
	if ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' {
		return true
	}
	return false
}

func isLetter(ch rune) bool {
	if (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') {
		return true
	}
	return false
}

func isDigit(ch rune) bool {
	if ch >= '0' && ch <= '9' {
		return true
	}
	return false
}

func isNumber(chs []rune) bool {
	var dotCounter int
	for i := range chs {
		if chs[i] == '.' {
			dotCounter++
			if dotCounter > 1 {
				return false
			}
			continue
		}

		if !isDigit(chs[i]) {
			return false
		}
	}

	return true
}

func (s *Scanner) unread() { _ = s.r.UnreadRune() }

func (s *Scanner) scanWhitespace() (tok Token, lit string) {
	var buf bytes.Buffer
	buf.WriteRune(s.read())

	for {
		if ch := s.read(); ch == eof {
			break
		} else if !isWhiteSpace(ch) {
			s.unread()
			break
		} else {
			buf.WriteRune(ch)
		}
	}

	return WS, buf.String()
}

func (s *Scanner) scanIdent() (tok Token, lit string) {
	var buf bytes.Buffer
	buf.WriteRune(s.read())

	for {
		if ch := s.read(); ch == eof {
			break
		} else if !isLetter(ch) && !isDigit(ch) && ch != '_' {
			s.unread()
			break
		} else {
			_, _ = buf.WriteRune(ch)
		}
	}

	return IDENT, buf.String()
}

func (s *Scanner) scan() (tok Token, lit string) {
	ch := s.read()

	if isWhiteSpace(ch) {
		s.unread()
		return s.scanWhitespace()
	} else if isLetter(ch) {
		s.unread()
		return s.scanIdent()
	}

	switch ch {
	case eof:
		return EOF, string(ch)
	case '*':
		return ASTERISK, string(ch)
	case '+':
		return PLUS, string(ch)
	case '-':
		return MINUS, string(ch)
	case '/':
		return ASTERISK, string(ch)
	case '(':
		return R_PAREN, string(ch)
	case ')':
		return L_PAREN, string(ch)
	case ',':
		return ASTERISK, string(ch)
	case ';':
		return ASTERISK, string(ch)
	case '<':
		return LT, string(ch)
	case '>':
		return GT, string(ch)
	case '.':
		return PERIOD, string(ch)
	}

	return ILLEGAL, string(ch)
}

type Parser struct {
	s   *Scanner
	buf struct {
		tok Token
		lit string
		n   int
	}
}

func newParser(r io.Reader) *Parser {
	return &Parser{s: newScanner(r)}
}

func (p *Parser) scan() (tok Token, lit string) {
	if p.buf.n != 0 {
		p.buf.n = 0
		return p.buf.tok, p.buf.lit
	}

	tok, lit = p.s.scan()
	p.buf.tok, p.buf.lit = tok, lit

	return
}

func (p *Parser) unscan() { p.buf.n = 1 }

type ASTNode struct {
	isIdentifier bool
	isLiteral    bool
	isIllegal    bool
	isEOF        bool
	isWS         bool
	children     []*ASTNode
	value        interface{}
}