Mako/lexer/lexer.go
2025-05-06 18:13:24 -05:00

257 lines
5.0 KiB
Go

package lexer
type TokenType byte
const (
TokenEOF TokenType = iota
TokenIdentifier
TokenString
TokenNumber
TokenEqual
TokenEcho
TokenSemicolon
TokenLeftBrace
TokenRightBrace
TokenLeftBracket
TokenRightBracket
TokenComma
TokenPlus
TokenMinus
TokenStar
TokenSlash
TokenLeftParen
TokenRightParen
TokenIf
TokenThen
TokenElse
TokenTrue
TokenFalse
TokenEqualEqual
TokenNotEqual
TokenLessThan
TokenGreaterThan
TokenLessEqual
TokenGreaterEqual
TokenEnd
TokenAnd
TokenOr
TokenNot
TokenElseIf
TokenNil
TokenFunction
TokenReturn
)
type Token struct {
Type TokenType
Value string
Line int
}
type Lexer struct {
input string
pos int
readPos int
ch byte
line int
}
func New(input string) *Lexer {
l := &Lexer{input: input, line: 1}
l.readChar()
return l
}
func (l *Lexer) readChar() {
if l.readPos >= len(l.input) {
l.ch = 0
} else {
l.ch = l.input[l.readPos]
}
l.pos = l.readPos
l.readPos++
if l.ch == '\n' {
l.line++
}
}
func (l *Lexer) NextToken() Token {
var tok Token
l.skipWhitespace()
l.skipComment()
switch l.ch {
case '=':
if l.peekChar() == '=' {
l.readChar() // consume the current '='
tok = Token{Type: TokenEqualEqual, Value: "==", Line: l.line}
} else {
tok = Token{Type: TokenEqual, Value: "=", Line: l.line}
}
case '!':
if l.peekChar() == '=' {
l.readChar() // consume the current '!'
tok = Token{Type: TokenNotEqual, Value: "!=", Line: l.line}
} else {
tok = Token{Type: TokenEOF, Value: "", Line: l.line} // Not supported yet
}
case '<':
if l.peekChar() == '=' {
l.readChar() // consume the current '<'
tok = Token{Type: TokenLessEqual, Value: "<=", Line: l.line}
} else {
tok = Token{Type: TokenLessThan, Value: "<", Line: l.line}
}
case '>':
if l.peekChar() == '=' {
l.readChar() // consume the current '>'
tok = Token{Type: TokenGreaterEqual, Value: ">=", Line: l.line}
} else {
tok = Token{Type: TokenGreaterThan, Value: ">", Line: l.line}
}
case '"':
tok = Token{Type: TokenString, Value: l.readString(), Line: l.line}
return tok
case '{':
tok = Token{Type: TokenLeftBrace, Value: "{", Line: l.line}
case '}':
tok = Token{Type: TokenRightBrace, Value: "}", Line: l.line}
case '[':
tok = Token{Type: TokenLeftBracket, Value: "[", Line: l.line}
case ']':
tok = Token{Type: TokenRightBracket, Value: "]", Line: l.line}
case ',':
tok = Token{Type: TokenComma, Value: ",", Line: l.line}
case '+':
tok = Token{Type: TokenPlus, Value: "+", Line: l.line}
case '-':
tok = Token{Type: TokenMinus, Value: "-", Line: l.line}
case '*':
tok = Token{Type: TokenStar, Value: "*", Line: l.line}
case '/':
if l.peekChar() == '/' {
// Comment line, skip it
l.skipComment()
return l.NextToken()
}
tok = Token{Type: TokenSlash, Value: "/", Line: l.line}
case '(':
tok = Token{Type: TokenLeftParen, Value: "(", Line: l.line}
case ')':
tok = Token{Type: TokenRightParen, Value: ")", Line: l.line}
case 0:
tok = Token{Type: TokenEOF, Value: "", Line: l.line}
default:
if isLetter(l.ch) {
tok.Value = l.readIdentifier()
switch tok.Value {
case "echo":
tok.Type = TokenEcho
case "if":
tok.Type = TokenIf
case "then":
tok.Type = TokenThen
case "else":
tok.Type = TokenElse
case "elseif":
tok.Type = TokenElseIf
case "true":
tok.Type = TokenTrue
case "false":
tok.Type = TokenFalse
case "end":
tok.Type = TokenEnd
case "and":
tok.Type = TokenAnd
case "or":
tok.Type = TokenOr
case "not":
tok.Type = TokenNot
case "nil":
tok.Type = TokenNil
case "function":
tok.Type = TokenFunction
case "return":
tok.Type = TokenReturn
default:
tok.Type = TokenIdentifier
}
return tok
} else if isDigit(l.ch) {
tok.Type = TokenNumber
tok.Value = l.readNumber()
return tok
} else {
tok = Token{Type: TokenEOF, Value: "", Line: l.line}
}
}
l.readChar()
return tok
}
func (l *Lexer) skipWhitespace() {
for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' {
l.readChar()
}
}
func (l *Lexer) readIdentifier() string {
pos := l.pos
for isLetter(l.ch) || isDigit(l.ch) {
l.readChar()
}
return l.input[pos:l.pos]
}
func (l *Lexer) readNumber() string {
pos := l.pos
for isDigit(l.ch) {
l.readChar()
}
return l.input[pos:l.pos]
}
func (l *Lexer) readString() string {
pos := l.pos + 1
for {
l.readChar()
if l.ch == '"' || l.ch == 0 {
break
}
}
str := l.input[pos:l.pos]
l.readChar() // Skip closing quote
return str
}
func isLetter(ch byte) bool {
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'
}
func isDigit(ch byte) bool {
return '0' <= ch && ch <= '9'
}
func (l *Lexer) peekChar() byte {
if l.readPos >= len(l.input) {
return 0
}
return l.input[l.readPos]
}
func (l *Lexer) skipComment() {
if l.ch == '/' && l.peekChar() == '/' {
l.readChar() // Skip first '/'
l.readChar() // Skip second '/'
for l.ch != '\n' && l.ch != 0 {
l.readChar()
}
l.skipWhitespace()
}
}