separate core from cli into submodules, add support for boolean and and or operations, make more fields public, fix passing arguments to functions
This commit is contained in:
parent
7498c85424
commit
62656c6dff
19 changed files with 202 additions and 99 deletions
408
core/lexer.go
Normal file
408
core/lexer.go
Normal file
|
|
@ -0,0 +1,408 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type Token struct {
|
||||
Type TokenType
|
||||
Start Pos
|
||||
Length Pos
|
||||
Line Pos
|
||||
Lexeme string
|
||||
}
|
||||
|
||||
func (t Token) String() string {
|
||||
return fmt.Sprintf("token %s, '%s' %d -> %d, line %d", t.Type.String(), t.Lexeme, t.Start, t.Length, t.Line)
|
||||
}
|
||||
|
||||
type TokenType uint64
|
||||
|
||||
const (
|
||||
TokenPlus TokenType = iota
|
||||
TokenMinus
|
||||
TokenStar
|
||||
TokenSlash
|
||||
TokenBang
|
||||
TokenSemicolon
|
||||
|
||||
TokenNumber
|
||||
TokenString
|
||||
TokenName
|
||||
|
||||
TokenOpenParenthesis
|
||||
TokenCloseParenthesis
|
||||
TokenOpenBrace
|
||||
TokenCloseBrace
|
||||
|
||||
TokenTrue
|
||||
TokenFalse
|
||||
TokenNil
|
||||
|
||||
TokenFunc
|
||||
TokenReturn
|
||||
TokenWhile
|
||||
TokenVar
|
||||
TokenIf
|
||||
TokenElse
|
||||
|
||||
TokenComma
|
||||
TokenDot
|
||||
|
||||
TokenAssign
|
||||
TokenDeclare
|
||||
TokenBangEquals
|
||||
TokenEquals
|
||||
TokenGreaterThan
|
||||
TokenLessThan
|
||||
TokenGreaterThanOrEqual
|
||||
TokenLessThanOrEqual
|
||||
|
||||
TokenDoubleAmpersand
|
||||
TokenDoublePipe
|
||||
|
||||
TokenBreakpoint
|
||||
TokenEOF
|
||||
TokenError
|
||||
)
|
||||
|
||||
func (t TokenType) String() string {
|
||||
switch t {
|
||||
case TokenPlus:
|
||||
return "plus"
|
||||
case TokenMinus:
|
||||
return "minus"
|
||||
case TokenStar:
|
||||
return "star"
|
||||
case TokenSlash:
|
||||
return "slash"
|
||||
case TokenBang:
|
||||
return "bang"
|
||||
case TokenNumber:
|
||||
return "number"
|
||||
case TokenString:
|
||||
return "string"
|
||||
case TokenTrue:
|
||||
return "true"
|
||||
case TokenFalse:
|
||||
return "false"
|
||||
case TokenNil:
|
||||
return "nil"
|
||||
case TokenOpenParenthesis:
|
||||
return "open parenthesis"
|
||||
case TokenCloseParenthesis:
|
||||
return "close parenthesis"
|
||||
case TokenOpenBrace:
|
||||
return "open brace"
|
||||
case TokenCloseBrace:
|
||||
return "close brace"
|
||||
case TokenVar:
|
||||
return "var"
|
||||
case TokenIf:
|
||||
return "if"
|
||||
case TokenElse:
|
||||
return "else"
|
||||
case TokenAssign:
|
||||
return "equals"
|
||||
case TokenBangEquals:
|
||||
return "equals"
|
||||
case TokenEquals:
|
||||
return "double equals"
|
||||
case TokenGreaterThan:
|
||||
return "greater than"
|
||||
case TokenLessThan:
|
||||
return "less than"
|
||||
case TokenGreaterThanOrEqual:
|
||||
return "greater than or equal"
|
||||
case TokenLessThanOrEqual:
|
||||
return "less than or equal"
|
||||
case TokenName:
|
||||
return "name"
|
||||
case TokenEOF:
|
||||
return "EOF"
|
||||
case TokenError:
|
||||
return "error"
|
||||
case TokenSemicolon:
|
||||
return "semicolon"
|
||||
case TokenDeclare:
|
||||
return "declare"
|
||||
case TokenFunc:
|
||||
return "func"
|
||||
case TokenReturn:
|
||||
return "return"
|
||||
case TokenWhile:
|
||||
return "while"
|
||||
case TokenComma:
|
||||
return "comma"
|
||||
case TokenDot:
|
||||
return "dot"
|
||||
case TokenBreakpoint:
|
||||
return "breakpoint"
|
||||
case TokenDoubleAmpersand:
|
||||
return "double ampersand"
|
||||
case TokenDoublePipe:
|
||||
return "double pipe"
|
||||
}
|
||||
|
||||
return "UNDEFINED TOKENTYPE STRING CONVERSION"
|
||||
}
|
||||
|
||||
type Lexer struct {
|
||||
src string
|
||||
start Pos
|
||||
current Pos
|
||||
line Pos
|
||||
}
|
||||
|
||||
func NewLexer(src string) *Lexer {
|
||||
return &Lexer{
|
||||
src: src,
|
||||
start: 0,
|
||||
current: 0,
|
||||
line: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Lexer) NextToken() (Token, error) {
|
||||
l.skipWhitespace()
|
||||
|
||||
// if at end of source
|
||||
if l.isAtEnd() {
|
||||
return l.makeToken(TokenEOF), nil
|
||||
}
|
||||
|
||||
// skip comments
|
||||
if l.match('#') {
|
||||
for !l.match('\n') {
|
||||
l.advance()
|
||||
}
|
||||
|
||||
return l.NextToken()
|
||||
}
|
||||
|
||||
l.start = l.current
|
||||
|
||||
var c = []rune(l.src)[l.current]
|
||||
l.advance()
|
||||
|
||||
switch c {
|
||||
case '+':
|
||||
return l.makeToken(TokenPlus), nil
|
||||
case '-':
|
||||
return l.makeToken(TokenMinus), nil
|
||||
case '*':
|
||||
return l.makeToken(TokenStar), nil
|
||||
case '/':
|
||||
if l.accept('*') {
|
||||
for !l.isAtEnd() {
|
||||
if l.accept('*') && l.accept('/') {
|
||||
break
|
||||
}
|
||||
l.advance()
|
||||
}
|
||||
return l.NextToken()
|
||||
}
|
||||
|
||||
return l.makeToken(TokenSlash), nil
|
||||
case '(':
|
||||
return l.makeToken(TokenOpenParenthesis), nil
|
||||
case ')':
|
||||
return l.makeToken(TokenCloseParenthesis), nil
|
||||
case '{':
|
||||
return l.makeToken(TokenOpenBrace), nil
|
||||
case '}':
|
||||
return l.makeToken(TokenCloseBrace), nil
|
||||
case ';':
|
||||
return l.makeToken(TokenSemicolon), nil
|
||||
case ',':
|
||||
return l.makeToken(TokenComma), nil
|
||||
case '.':
|
||||
return l.makeToken(TokenDot), nil
|
||||
case ':':
|
||||
if !l.accept('=') {
|
||||
return l.makeToken(TokenError), errors.New("malformed token (got ':', expected '=' to follow)")
|
||||
}
|
||||
|
||||
return l.makeToken(TokenDeclare), nil
|
||||
case '!':
|
||||
if l.accept('=') {
|
||||
return l.makeToken(TokenBangEquals), nil
|
||||
}
|
||||
|
||||
return l.makeToken(TokenBang), nil
|
||||
case '=':
|
||||
if l.accept('=') {
|
||||
return l.makeToken(TokenEquals), nil
|
||||
}
|
||||
|
||||
return l.makeToken(TokenAssign), nil
|
||||
case '>':
|
||||
if l.accept('=') {
|
||||
return l.makeToken(TokenGreaterThanOrEqual), nil
|
||||
}
|
||||
|
||||
return l.makeToken(TokenGreaterThan), nil
|
||||
case '<':
|
||||
if l.accept('=') {
|
||||
return l.makeToken(TokenLessThanOrEqual), nil
|
||||
}
|
||||
|
||||
return l.makeToken(TokenLessThan), nil
|
||||
|
||||
case '&':
|
||||
if l.accept('&') {
|
||||
return l.makeToken(TokenDoubleAmpersand), nil
|
||||
}
|
||||
|
||||
return l.makeToken(TokenError), errors.New("malformed token (got '&', expected '&' to follow)")
|
||||
|
||||
case '|':
|
||||
if l.accept('|') {
|
||||
return l.makeToken(TokenDoublePipe), nil
|
||||
}
|
||||
|
||||
return l.makeToken(TokenError), errors.New("malformed token (got '|', expected '|' to follow)")
|
||||
|
||||
case '"':
|
||||
// include ending quote
|
||||
for !l.accept('"') {
|
||||
if l.match('\n') {
|
||||
return l.makeToken(TokenError), errors.New("string did not end in current line")
|
||||
}
|
||||
|
||||
if l.isAtEnd() {
|
||||
return l.makeToken(TokenError), errors.New("string did not before end of source")
|
||||
}
|
||||
|
||||
l.advance()
|
||||
}
|
||||
|
||||
return l.makeToken(TokenString), nil
|
||||
|
||||
default:
|
||||
if unicode.IsLetter(c) || c == '_' {
|
||||
// assemble variable
|
||||
for l.isAlpha(l.peek()) {
|
||||
l.advance()
|
||||
}
|
||||
|
||||
switch l.src[l.start:l.current] {
|
||||
case "true":
|
||||
return l.makeToken(TokenTrue), nil
|
||||
case "false":
|
||||
return l.makeToken(TokenFalse), nil
|
||||
case "nil":
|
||||
return l.makeToken(TokenNil), nil
|
||||
case "if":
|
||||
return l.makeToken(TokenIf), nil
|
||||
case "else":
|
||||
return l.makeToken(TokenElse), nil
|
||||
case "var":
|
||||
return l.makeToken(TokenVar), nil
|
||||
case "func":
|
||||
return l.makeToken(TokenFunc), nil
|
||||
case "while":
|
||||
return l.makeToken(TokenWhile), nil
|
||||
case "breakpoint":
|
||||
return l.makeToken(TokenBreakpoint), nil
|
||||
case "return":
|
||||
return l.makeToken(TokenReturn), nil
|
||||
default:
|
||||
return l.makeToken(TokenName), nil
|
||||
}
|
||||
} else if unicode.IsDigit(c) {
|
||||
for unicode.IsDigit(l.peek()) {
|
||||
l.advance()
|
||||
}
|
||||
|
||||
// if the number has a float-part
|
||||
if l.accept('.') {
|
||||
for unicode.IsDigit(l.peek()) {
|
||||
l.advance()
|
||||
}
|
||||
}
|
||||
|
||||
return l.makeToken(TokenNumber), nil
|
||||
}
|
||||
|
||||
return l.makeToken(TokenError), errors.New(fmt.Sprintf("invalid token %c", c))
|
||||
}
|
||||
}
|
||||
|
||||
func NewToken(t TokenType, start Pos, length Pos, line Pos, lexeme string) Token {
|
||||
return Token{
|
||||
Type: t,
|
||||
Start: start,
|
||||
Length: length,
|
||||
Line: line,
|
||||
Lexeme: lexeme,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Lexer) Tokenize() ([]Token, error) {
|
||||
tokens := make([]Token, 0)
|
||||
|
||||
tok, err := l.NextToken()
|
||||
for ; err == nil; tok, err = l.NextToken() {
|
||||
tokens = append(tokens, tok)
|
||||
|
||||
if tok.Type == TokenEOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return tokens, err
|
||||
}
|
||||
|
||||
func (l *Lexer) makeToken(t TokenType) Token {
|
||||
return NewToken(t, l.start, l.current-l.start, l.line, l.src[l.start:l.current])
|
||||
}
|
||||
|
||||
func (l *Lexer) peek() rune {
|
||||
if l.isAtEnd() {
|
||||
return 0
|
||||
}
|
||||
|
||||
return []rune(l.src)[l.current]
|
||||
}
|
||||
|
||||
func (l *Lexer) match(c rune) bool {
|
||||
return l.peek() == c
|
||||
}
|
||||
|
||||
func (l *Lexer) accept(c rune) bool {
|
||||
if l.match(c) {
|
||||
l.advance()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (l *Lexer) isAlpha(c rune) bool {
|
||||
return unicode.IsLetter(c) || unicode.IsDigit(c) || c == '_'
|
||||
}
|
||||
|
||||
func (l *Lexer) advance() {
|
||||
if l.isAtEnd() {
|
||||
return
|
||||
}
|
||||
|
||||
if []rune(l.src)[l.current] == '\n' {
|
||||
l.line++
|
||||
}
|
||||
|
||||
l.current++
|
||||
}
|
||||
|
||||
func (l *Lexer) isAtEnd() bool {
|
||||
return l.current >= Pos(len([]rune(l.src)))
|
||||
}
|
||||
|
||||
func (l *Lexer) skipWhitespace() {
|
||||
for !l.isAtEnd() && unicode.IsSpace(l.peek()) {
|
||||
l.advance()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue