init + lexer
This commit is contained in:
commit
9bebc6e307
5 changed files with 493 additions and 0 deletions
3
go.mod
Normal file
3
go.mod
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
module git.red-panda.pet/pandaware/lox-go
|
||||
|
||||
go 1.23.2
|
||||
78
main.go
Normal file
78
main.go
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
file string
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
args := flag.Args()
|
||||
|
||||
if len(args) > 1 {
|
||||
fmt.Printf("Usage: %s [script]", args[0])
|
||||
os.Exit(64)
|
||||
} else if len(args) == 1 {
|
||||
runFile(args[0])
|
||||
} else {
|
||||
repl()
|
||||
}
|
||||
}
|
||||
|
||||
func runFile(filename string) {
|
||||
bs, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
fmt.Printf("unable to read file '%s':\n\t%s", filename, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if run(string(bs)) {
|
||||
os.Exit(65)
|
||||
}
|
||||
}
|
||||
|
||||
func repl() {
|
||||
s := bufio.NewScanner(os.Stdin)
|
||||
for {
|
||||
fmt.Printf("repl> ")
|
||||
s.Scan()
|
||||
text := s.Text()
|
||||
|
||||
if text == ":q" {
|
||||
return
|
||||
}
|
||||
|
||||
run(text)
|
||||
if err := s.Err(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func run(source string) bool {
|
||||
s := newScanner(source)
|
||||
tokens, ok := s.ScanTokens()
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, token := range tokens {
|
||||
fmt.Println(token)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func reportErr(line int, message string) {
|
||||
report(line, "", message)
|
||||
}
|
||||
|
||||
func report(line int, where, message string) {
|
||||
fmt.Printf("[line %d] Error%s: %s\n", line, where, message)
|
||||
}
|
||||
280
scanner.go
Normal file
280
scanner.go
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var keywords = map[string]tokenType{
|
||||
"and": tokenTypeAnd,
|
||||
"class": tokenTypeClass,
|
||||
"else": tokenTypeElse,
|
||||
"false": tokenTypeFalse,
|
||||
"fun": tokenTypeFun,
|
||||
"for": tokenTypeFor,
|
||||
"if": tokenTypeIf,
|
||||
"nil": tokenTypeNil,
|
||||
"or": tokenTypeOr,
|
||||
"print": tokenTypePrint,
|
||||
"return": tokenTypeReturn,
|
||||
"super": tokenTypeSuper,
|
||||
"this": tokenTypeThis,
|
||||
"true": tokenTypeTrue,
|
||||
"var": tokenTypeVar,
|
||||
"while": tokenTypeWhile,
|
||||
}
|
||||
|
||||
func isDigit(r rune) bool {
|
||||
return r >= '0' && r <= '9'
|
||||
}
|
||||
|
||||
func isAlpha(r rune) bool {
|
||||
return (r >= 'a' && r <= 'z') ||
|
||||
(r >= 'A' && r <= 'Z') ||
|
||||
r == '_'
|
||||
}
|
||||
|
||||
func isAlphaNumeric(r rune) bool {
|
||||
return isDigit(r) || isAlpha(r)
|
||||
}
|
||||
|
||||
type scanner struct {
|
||||
source []rune
|
||||
tokens []*token
|
||||
|
||||
start int
|
||||
current int
|
||||
line int
|
||||
}
|
||||
|
||||
func newScanner(source string) *scanner {
|
||||
s := new(scanner)
|
||||
|
||||
s.source = []rune(source)
|
||||
s.tokens = []*token{}
|
||||
|
||||
s.start = 0
|
||||
s.current = 0
|
||||
s.line = 1
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *scanner) isAtEnd() bool {
|
||||
return s.current >= len(s.source)
|
||||
}
|
||||
|
||||
func (s *scanner) advance() rune {
|
||||
r := s.source[s.current]
|
||||
s.current += 1
|
||||
return r
|
||||
}
|
||||
|
||||
func (s *scanner) addToken(t tokenType, literal any) {
|
||||
s.tokens = append(s.tokens, &token{
|
||||
Type: t,
|
||||
Lexeme: string(s.source[s.start:s.current]),
|
||||
Literal: literal,
|
||||
Line: s.line,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *scanner) match(expected rune) bool {
|
||||
if s.isAtEnd() {
|
||||
return false
|
||||
}
|
||||
c := s.source[s.current]
|
||||
if c != expected {
|
||||
return false
|
||||
}
|
||||
|
||||
s.current += 1
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *scanner) peek() rune {
|
||||
if s.isAtEnd() {
|
||||
return rune(0)
|
||||
}
|
||||
return s.source[s.current]
|
||||
}
|
||||
|
||||
func (s *scanner) peekNext() rune {
|
||||
if s.current+1 > len(s.source) {
|
||||
return rune(0)
|
||||
}
|
||||
return s.source[s.current+1]
|
||||
}
|
||||
|
||||
func (s *scanner) scanToken() bool {
|
||||
r := s.advance()
|
||||
|
||||
switch r {
|
||||
// simple 1 character tokens
|
||||
case '(':
|
||||
s.addToken(tokenTypeLeftParen, nil)
|
||||
case ')':
|
||||
s.addToken(tokenTypeRightParen, nil)
|
||||
case '{':
|
||||
s.addToken(tokenTypeLeftBrace, nil)
|
||||
case '}':
|
||||
s.addToken(tokenTypeRightBrace, nil)
|
||||
case ',':
|
||||
s.addToken(tokenTypeComma, nil)
|
||||
case '.':
|
||||
s.addToken(tokenTypeDot, nil)
|
||||
case '-':
|
||||
s.addToken(tokenTypeMinus, nil)
|
||||
case '+':
|
||||
s.addToken(tokenTypePlus, nil)
|
||||
case ';':
|
||||
s.addToken(tokenTypeSemicolon, nil)
|
||||
case '*':
|
||||
s.addToken(tokenTypeStar, nil)
|
||||
|
||||
// simple 2 character tokens
|
||||
case '!':
|
||||
if s.match('=') {
|
||||
s.addToken(tokenTypeBangEq, nil)
|
||||
} else {
|
||||
s.addToken(tokenTypeBang, nil)
|
||||
}
|
||||
case '=':
|
||||
if s.match('=') {
|
||||
s.addToken(tokenTypeEqualEqual, nil)
|
||||
} else {
|
||||
s.addToken(tokenTypeEqual, nil)
|
||||
}
|
||||
case '<':
|
||||
if s.match('=') {
|
||||
s.addToken(tokenTypeLessEq, nil)
|
||||
} else {
|
||||
s.addToken(tokenTypeLess, nil)
|
||||
}
|
||||
case '>':
|
||||
if s.match('=') {
|
||||
s.addToken(tokenTypeGreaterEq, nil)
|
||||
} else {
|
||||
s.addToken(tokenTypeGreater, nil)
|
||||
}
|
||||
|
||||
case '/':
|
||||
// match comments
|
||||
if s.match('/') {
|
||||
// we scan until the end of line/file (whichever comes first :p)
|
||||
for s.peek() != '\n' && !s.isAtEnd() {
|
||||
s.advance()
|
||||
}
|
||||
} else {
|
||||
s.addToken(tokenTypeSlash, nil)
|
||||
}
|
||||
|
||||
// ignore whitespace
|
||||
case ' ':
|
||||
break
|
||||
case '\r':
|
||||
break
|
||||
case '\t':
|
||||
break
|
||||
|
||||
// advance the line counter :D
|
||||
case '\n':
|
||||
s.line += 1
|
||||
|
||||
// string literals
|
||||
case '"':
|
||||
return s.string()
|
||||
|
||||
default:
|
||||
if isDigit(r) {
|
||||
return s.number()
|
||||
} else if isAlpha(r) {
|
||||
s.identifier()
|
||||
return false
|
||||
}
|
||||
|
||||
reportErr(s.line, fmt.Sprintf("Unexpected character %c", r))
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *scanner) string() bool {
|
||||
// peek until we hit the end of the string or file, whichever is first
|
||||
for s.peek() != '"' && !s.isAtEnd() {
|
||||
// support strings with new lines :D
|
||||
if s.peek() == '\n' {
|
||||
s.line += 1
|
||||
}
|
||||
s.advance()
|
||||
}
|
||||
|
||||
// if the token didn't end before the file we report and err
|
||||
// and return that we got one
|
||||
if s.isAtEnd() {
|
||||
reportErr(s.line, "Unterminated string")
|
||||
return true
|
||||
}
|
||||
|
||||
s.advance()
|
||||
|
||||
// todo: escape sequences
|
||||
value := s.source[s.start+1 : s.current-1]
|
||||
s.addToken(tokenTypeString, value)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *scanner) number() bool {
|
||||
for isDigit(s.peek()) {
|
||||
s.advance()
|
||||
}
|
||||
|
||||
if s.peek() == '.' && isDigit(s.peekNext()) {
|
||||
s.advance()
|
||||
|
||||
for isDigit(s.peek()) {
|
||||
s.advance()
|
||||
}
|
||||
}
|
||||
|
||||
literal, _ := strconv.ParseFloat(string(s.source[s.start:s.current]), 64)
|
||||
s.addToken(tokenTypeNumber, literal)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *scanner) identifier() {
|
||||
for isAlphaNumeric(s.peek()) {
|
||||
s.advance()
|
||||
}
|
||||
|
||||
text := s.source[s.start:s.current]
|
||||
tt, ok := keywords[string(text)]
|
||||
|
||||
if !ok {
|
||||
tt = tokenTypeIdentifier
|
||||
}
|
||||
|
||||
s.addToken(tt, nil)
|
||||
}
|
||||
|
||||
func (s *scanner) ScanTokens() ([]*token, bool) {
|
||||
isErr := false
|
||||
|
||||
for !s.isAtEnd() {
|
||||
s.start = s.current
|
||||
isErr = isErr || s.scanToken()
|
||||
}
|
||||
|
||||
s.tokens = append(s.tokens, &token{
|
||||
Type: tokenTypeEOF,
|
||||
Lexeme: "",
|
||||
Literal: nil,
|
||||
Line: s.line,
|
||||
})
|
||||
|
||||
return s.tokens, !isErr
|
||||
}
|
||||
71
tokentype.go
Normal file
71
tokentype.go
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
//go:generate stringer -type tokenType -linecomment -trimprefix tokenType
|
||||
type tokenType int
|
||||
|
||||
const (
|
||||
// single char tokens
|
||||
|
||||
tokenTypeLeftParen tokenType = iota
|
||||
tokenTypeRightParen
|
||||
tokenTypeLeftBrace
|
||||
tokenTypeRightBrace
|
||||
tokenTypeComma
|
||||
tokenTypeDot
|
||||
tokenTypeMinus
|
||||
tokenTypePlus
|
||||
tokenTypeSemicolon
|
||||
tokenTypeSlash
|
||||
tokenTypeStar
|
||||
|
||||
// 1-2 char token
|
||||
|
||||
tokenTypeBang
|
||||
tokenTypeBangEq
|
||||
tokenTypeEqual
|
||||
tokenTypeEqualEqual
|
||||
tokenTypeGreater
|
||||
tokenTypeGreaterEq
|
||||
tokenTypeLess
|
||||
tokenTypeLessEq
|
||||
|
||||
// literals
|
||||
|
||||
tokenTypeIdentifier
|
||||
tokenTypeString
|
||||
tokenTypeNumber
|
||||
|
||||
// keywords
|
||||
|
||||
tokenTypeAnd
|
||||
tokenTypeClass
|
||||
tokenTypeElse
|
||||
tokenTypeFalse
|
||||
tokenTypeFun
|
||||
tokenTypeFor
|
||||
tokenTypeIf
|
||||
tokenTypeNil
|
||||
tokenTypeOr
|
||||
tokenTypePrint
|
||||
tokenTypeReturn
|
||||
tokenTypeSuper
|
||||
tokenTypeThis
|
||||
tokenTypeTrue
|
||||
tokenTypeVar
|
||||
tokenTypeWhile
|
||||
|
||||
tokenTypeEOF
|
||||
)
|
||||
|
||||
type token struct {
|
||||
Type tokenType
|
||||
Lexeme string
|
||||
Literal any
|
||||
Line int
|
||||
}
|
||||
|
||||
func (t token) String() string {
|
||||
return fmt.Sprintf("%s %s %+v", t.Type, t.Lexeme, t.Literal)
|
||||
}
|
||||
61
tokentype_string.go
Normal file
61
tokentype_string.go
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// Code generated by "stringer -type tokenType -linecomment -trimprefix tokenType"; DO NOT EDIT.
|
||||
|
||||
package main
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[tokenTypeLeftParen-0]
|
||||
_ = x[tokenTypeRightParen-1]
|
||||
_ = x[tokenTypeLeftBrace-2]
|
||||
_ = x[tokenTypeRightBrace-3]
|
||||
_ = x[tokenTypeComma-4]
|
||||
_ = x[tokenTypeDot-5]
|
||||
_ = x[tokenTypeMinus-6]
|
||||
_ = x[tokenTypePlus-7]
|
||||
_ = x[tokenTypeSemicolon-8]
|
||||
_ = x[tokenTypeSlash-9]
|
||||
_ = x[tokenTypeStar-10]
|
||||
_ = x[tokenTypeBang-11]
|
||||
_ = x[tokenTypeBangEq-12]
|
||||
_ = x[tokenTypeEqual-13]
|
||||
_ = x[tokenTypeEqualEqual-14]
|
||||
_ = x[tokenTypeGreater-15]
|
||||
_ = x[tokenTypeGreaterEq-16]
|
||||
_ = x[tokenTypeLess-17]
|
||||
_ = x[tokenTypeLessEq-18]
|
||||
_ = x[tokenTypeIdentifier-19]
|
||||
_ = x[tokenTypeString-20]
|
||||
_ = x[tokenTypeNumber-21]
|
||||
_ = x[tokenTypeAnd-22]
|
||||
_ = x[tokenTypeClass-23]
|
||||
_ = x[tokenTypeElse-24]
|
||||
_ = x[tokenTypeFalse-25]
|
||||
_ = x[tokenTypeFun-26]
|
||||
_ = x[tokenTypeFor-27]
|
||||
_ = x[tokenTypeIf-28]
|
||||
_ = x[tokenTypeNil-29]
|
||||
_ = x[tokenTypeOr-30]
|
||||
_ = x[tokenTypePrint-31]
|
||||
_ = x[tokenTypeReturn-32]
|
||||
_ = x[tokenTypeSuper-33]
|
||||
_ = x[tokenTypeThis-34]
|
||||
_ = x[tokenTypeTrue-35]
|
||||
_ = x[tokenTypeVar-36]
|
||||
_ = x[tokenTypeWhile-37]
|
||||
_ = x[tokenTypeEOF-38]
|
||||
}
|
||||
|
||||
const _tokenType_name = "LeftParenRightParenLeftBraceRightBraceCommaDotMinusPlusSemicolonSlashStarBangBangEqEqualEqualEqualGreaterGreaterEqLessLessEqIdentifierStringNumberAndClassElseFalseFunForIfNilOrPrintReturnSuperThisTrueVarWhileEOF"
|
||||
|
||||
var _tokenType_index = [...]uint8{0, 9, 19, 28, 38, 43, 46, 51, 55, 64, 69, 73, 77, 83, 88, 98, 105, 114, 118, 124, 134, 140, 146, 149, 154, 158, 163, 166, 169, 171, 174, 176, 181, 187, 192, 196, 200, 203, 208, 211}
|
||||
|
||||
func (i tokenType) String() string {
|
||||
if i < 0 || i >= tokenType(len(_tokenType_index)-1) {
|
||||
return "tokenType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _tokenType_name[_tokenType_index[i]:_tokenType_index[i+1]]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue