Initial commit: completed simple lexer for pj1
This commit is contained in:
commit
7f72ba59d5
10
pj1-go/go.mod
Normal file
10
pj1-go/go.mod
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
module git.bonsai.cool/kayprish/pj1/pj1-go
|
||||||
|
|
||||||
|
go 1.18
|
||||||
|
|
||||||
|
require golang.org/x/tools v0.1.13-0.20220917004541-4d18923f060e
|
||||||
|
|
||||||
|
require (
|
||||||
|
golang.org/x/mod v0.12.0 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
|
||||||
|
)
|
6
pj1-go/go.sum
Normal file
6
pj1-go/go.sum
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
||||||
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/tools v0.1.13-0.20220917004541-4d18923f060e h1:K/LreqAwv7hZaSPyj5LvaiQd2wROouJDabf2r+oBqUw=
|
||||||
|
golang.org/x/tools v0.1.13-0.20220917004541-4d18923f060e/go.mod h1:VsjNM1dMo+Ofkp5d7y7fOdQZD8MTXSQ4w3EPk65AvKU=
|
360
pj1-go/lexer.go
Normal file
360
pj1-go/lexer.go
Normal file
|
@ -0,0 +1,360 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TokenType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Single-character tokens.
|
||||||
|
LEFT_PAREN TokenType = iota
|
||||||
|
RIGHT_PAREN
|
||||||
|
LEFT_BRACE
|
||||||
|
RIGHT_BRACE
|
||||||
|
COMMA
|
||||||
|
DOT
|
||||||
|
MINUS
|
||||||
|
PLUS
|
||||||
|
SEMICOLON
|
||||||
|
STAR
|
||||||
|
|
||||||
|
// One or two character tokens.
|
||||||
|
BANG
|
||||||
|
BANG_EQUAL
|
||||||
|
EQUAL
|
||||||
|
EQUAL_EQUAL
|
||||||
|
GREATER
|
||||||
|
GREATER_EQUAL
|
||||||
|
LESS
|
||||||
|
LESS_EQUAL
|
||||||
|
SLASH
|
||||||
|
SLASH_DOT
|
||||||
|
SLASH_UNDERSCORE
|
||||||
|
|
||||||
|
// Literals
|
||||||
|
IDENTIFIER
|
||||||
|
STRING
|
||||||
|
INTEGER
|
||||||
|
FLOAT
|
||||||
|
|
||||||
|
// Keywords.
|
||||||
|
AND
|
||||||
|
CLASS
|
||||||
|
ELSE
|
||||||
|
FALSE
|
||||||
|
FOR
|
||||||
|
FUN
|
||||||
|
IF
|
||||||
|
NIL
|
||||||
|
OR
|
||||||
|
PRINT
|
||||||
|
RETURN
|
||||||
|
SUPER
|
||||||
|
THIS
|
||||||
|
TRUE
|
||||||
|
VAR
|
||||||
|
WHILE
|
||||||
|
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate go run golang.org/x/tools/cmd/stringer -type=TokenType
|
||||||
|
|
||||||
|
var keywords = map[string]TokenType{
|
||||||
|
"and": AND,
|
||||||
|
"class": CLASS,
|
||||||
|
"else": ELSE,
|
||||||
|
"false": FALSE,
|
||||||
|
"for": FOR,
|
||||||
|
"fun": FUN,
|
||||||
|
"if": IF,
|
||||||
|
"nil": NIL,
|
||||||
|
"or": OR,
|
||||||
|
"print": PRINT,
|
||||||
|
"return": RETURN,
|
||||||
|
"super": SUPER,
|
||||||
|
"this": THIS,
|
||||||
|
"true": TRUE,
|
||||||
|
"var": VAR,
|
||||||
|
"while": WHILE,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Token struct {
|
||||||
|
ttype TokenType
|
||||||
|
lexeme string
|
||||||
|
literal interface{}
|
||||||
|
line int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewToken(ttype TokenType, lexeme string, literal interface{}, line int) Token {
|
||||||
|
t := Token{ttype, lexeme, literal, line}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Token) String() string {
|
||||||
|
return fmt.Sprintf("%v %v %v", t.ttype, t.lexeme, t.literal)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Lexer struct {
|
||||||
|
source string
|
||||||
|
tokens []Token
|
||||||
|
|
||||||
|
startByte int
|
||||||
|
currentByte int
|
||||||
|
startRune int
|
||||||
|
currentRune int
|
||||||
|
line int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLexer(source string) Lexer {
|
||||||
|
l := Lexer{source, []Token{}, 0, 0, 0, 0, 1}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) ScanTokens() {
|
||||||
|
for !l.atEnd() {
|
||||||
|
l.startByte = l.currentByte
|
||||||
|
l.startRune = l.currentRune
|
||||||
|
l.scanToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
l.tokens = append(l.tokens, NewToken(EOF, "", nil, l.line))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Lexer) atEnd() bool {
|
||||||
|
return l.currentByte >= len(l.source)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) scanToken() {
|
||||||
|
c := l.advance()
|
||||||
|
switch c {
|
||||||
|
case '(':
|
||||||
|
l.addSimpleToken(LEFT_PAREN)
|
||||||
|
case ')':
|
||||||
|
l.addSimpleToken(RIGHT_PAREN)
|
||||||
|
case '{':
|
||||||
|
l.addSimpleToken(LEFT_BRACE)
|
||||||
|
case '}':
|
||||||
|
l.addSimpleToken(RIGHT_BRACE)
|
||||||
|
case ',':
|
||||||
|
l.addSimpleToken(COMMA)
|
||||||
|
case '.':
|
||||||
|
l.addSimpleToken(DOT)
|
||||||
|
case '-':
|
||||||
|
l.addSimpleToken(MINUS)
|
||||||
|
case '+':
|
||||||
|
l.addSimpleToken(PLUS)
|
||||||
|
case ';':
|
||||||
|
l.addSimpleToken(SEMICOLON)
|
||||||
|
case '*':
|
||||||
|
l.addSimpleToken(STAR)
|
||||||
|
case '!':
|
||||||
|
if l.match('=') {
|
||||||
|
l.addSimpleToken(BANG_EQUAL)
|
||||||
|
} else {
|
||||||
|
l.addSimpleToken(BANG)
|
||||||
|
}
|
||||||
|
case '=':
|
||||||
|
if l.match('=') {
|
||||||
|
l.addSimpleToken(EQUAL_EQUAL)
|
||||||
|
} else {
|
||||||
|
l.addSimpleToken(EQUAL)
|
||||||
|
}
|
||||||
|
case '<':
|
||||||
|
if l.match('=') {
|
||||||
|
l.addSimpleToken(LESS_EQUAL)
|
||||||
|
} else {
|
||||||
|
l.addSimpleToken(LESS)
|
||||||
|
}
|
||||||
|
case '>':
|
||||||
|
if l.match('=') {
|
||||||
|
l.addSimpleToken(GREATER_EQUAL)
|
||||||
|
} else {
|
||||||
|
l.addSimpleToken(GREATER)
|
||||||
|
}
|
||||||
|
case '/':
|
||||||
|
if l.match('/') {
|
||||||
|
// A comment goes until the end of the line
|
||||||
|
for l.peek() != '\n' && !l.atEnd() {
|
||||||
|
l.advance()
|
||||||
|
}
|
||||||
|
} else if l.match('.') {
|
||||||
|
l.addSimpleToken(SLASH_DOT)
|
||||||
|
} else if l.match('_') {
|
||||||
|
l.addSimpleToken(SLASH_UNDERSCORE)
|
||||||
|
} else {
|
||||||
|
l.addSimpleToken(SLASH)
|
||||||
|
}
|
||||||
|
case ' ', '\r', '\t':
|
||||||
|
case '\n':
|
||||||
|
l.line++
|
||||||
|
case '"':
|
||||||
|
l.str()
|
||||||
|
|
||||||
|
default:
|
||||||
|
if isDigit(c) {
|
||||||
|
l.number()
|
||||||
|
} else if isAlpha(c) {
|
||||||
|
l.identifier()
|
||||||
|
} else {
|
||||||
|
// TODO: if there are multiple bad characters
|
||||||
|
// coalesce similar errors into one
|
||||||
|
error(l.line, fmt.Sprintf("Unexpected character, %v.", c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) identifier() {
|
||||||
|
for isAlphaNumeric(l.peek()) {
|
||||||
|
l.advance()
|
||||||
|
}
|
||||||
|
|
||||||
|
text := l.source[l.startByte:l.currentByte]
|
||||||
|
ttype, ok := keywords[text]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
ttype = IDENTIFIER
|
||||||
|
}
|
||||||
|
|
||||||
|
l.addSimpleToken(ttype)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) number() {
|
||||||
|
isInt := true
|
||||||
|
for isDigit(l.peek()) {
|
||||||
|
l.advance()
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.peek() == '.' && isDigit(l.peekNext()) {
|
||||||
|
l.advance()
|
||||||
|
isInt = false
|
||||||
|
|
||||||
|
for isDigit(l.peek()) {
|
||||||
|
l.advance()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only allow integer and float literals, other values can be
|
||||||
|
// made by combining these
|
||||||
|
if isInt {
|
||||||
|
var bigint big.Int
|
||||||
|
bigint.SetString(l.source[l.startByte:l.currentByte], 10)
|
||||||
|
l.addToken(INTEGER, bigint)
|
||||||
|
} else {
|
||||||
|
float, _ := strconv.ParseFloat(l.source[l.startByte:l.currentByte], 64)
|
||||||
|
l.addToken(FLOAT, float)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) match(expected rune) bool {
|
||||||
|
if l.atEnd() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ch, width := utf8.DecodeRuneInString(l.source[l.currentByte:])
|
||||||
|
if ch == expected {
|
||||||
|
l.currentRune++
|
||||||
|
l.currentByte += width
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Lexer) peek() rune {
|
||||||
|
if l.atEnd() {
|
||||||
|
return '\x00'
|
||||||
|
}
|
||||||
|
ch, _ := utf8.DecodeRuneInString(l.source[l.currentByte:])
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Lexer) peekNext() rune {
|
||||||
|
_, width1 := utf8.DecodeRuneInString(l.source[l.currentByte:])
|
||||||
|
// "width1 == 0" signifies we reached the end of the string with the first character
|
||||||
|
if width1 == 0 || l.currentByte+width1 >= len(l.source) {
|
||||||
|
return '\x00'
|
||||||
|
}
|
||||||
|
ch2, _ := utf8.DecodeRuneInString(l.source[l.currentByte+width1:])
|
||||||
|
return ch2
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAlpha(c rune) bool {
|
||||||
|
return (c >= 'a' && c <= 'z') ||
|
||||||
|
(c >= 'A' && c <= 'Z') ||
|
||||||
|
c == '_'
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAlphaNumeric(c rune) bool {
|
||||||
|
return isAlpha(c) || isDigit(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDigit(c rune) bool {
|
||||||
|
return c >= '0' && c <= '9'
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) advance() rune {
|
||||||
|
ch, width := utf8.DecodeRuneInString(l.source[l.currentByte:])
|
||||||
|
l.currentRune++
|
||||||
|
l.currentByte += width
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) str() {
|
||||||
|
var sb strings.Builder
|
||||||
|
for l.peek() != '"' && !l.atEnd() {
|
||||||
|
c := l.peek()
|
||||||
|
if c == '\n' {
|
||||||
|
l.line++
|
||||||
|
} else if c == '\\' {
|
||||||
|
l.advance()
|
||||||
|
// TODO: add more escape sequences, including \xNN
|
||||||
|
switch l.peek() {
|
||||||
|
case 'n':
|
||||||
|
c = '\n'
|
||||||
|
case 'r':
|
||||||
|
c = '\r'
|
||||||
|
case 't':
|
||||||
|
c = '\t'
|
||||||
|
case '\\':
|
||||||
|
c = '\\'
|
||||||
|
case '"':
|
||||||
|
c = '"'
|
||||||
|
case '\'':
|
||||||
|
c = '\''
|
||||||
|
case 'e':
|
||||||
|
c = '\x1b'
|
||||||
|
default:
|
||||||
|
error(l.line, fmt.Sprintf("Invalid escape sequence \\%v.", l.peek()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.WriteRune(c)
|
||||||
|
l.advance()
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.atEnd() {
|
||||||
|
error(l.line, "Unterminated string.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Closing ".
|
||||||
|
l.advance()
|
||||||
|
|
||||||
|
value := sb.String()
|
||||||
|
l.addToken(STRING, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple refers to "having no literal" (TODO: rename function)
|
||||||
|
func (l *Lexer) addSimpleToken(ttype TokenType) {
|
||||||
|
l.addToken(ttype, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) addToken(ttype TokenType, literal interface{}) {
|
||||||
|
text := l.source[l.startByte:l.currentByte]
|
||||||
|
l.tokens = append(l.tokens, NewToken(ttype, text, literal, l.line))
|
||||||
|
}
|
74
pj1-go/pj1.go
Normal file
74
pj1-go/pj1.go
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
hadError bool = false
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) > 2 {
|
||||||
|
fmt.Println("Usage: pj1-go [script]")
|
||||||
|
os.Exit(64)
|
||||||
|
} else if len(os.Args) == 2 {
|
||||||
|
runFile(os.Args[0])
|
||||||
|
} else {
|
||||||
|
runPrompt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runFile(path string) {
|
||||||
|
bytes, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
run(string(bytes[:]))
|
||||||
|
|
||||||
|
if hadError {
|
||||||
|
os.Exit(65)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runPrompt() {
|
||||||
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
|
for {
|
||||||
|
fmt.Print("> ")
|
||||||
|
if !scanner.Scan() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
line := scanner.Text()
|
||||||
|
fmt.Println(line)
|
||||||
|
run(line)
|
||||||
|
|
||||||
|
hadError = false
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "reading standard input:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(source string) {
|
||||||
|
lexer := NewLexer(source)
|
||||||
|
lexer.ScanTokens()
|
||||||
|
var tokens []Token = lexer.tokens
|
||||||
|
|
||||||
|
for _, token := range tokens {
|
||||||
|
fmt.Println(token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: might have to rename
|
||||||
|
func error(line int, msg string) {
|
||||||
|
report(line, "", msg)
|
||||||
|
hadError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func report(line int, where string, msg string) {
|
||||||
|
fmt.Fprintln(os.Stderr, "[line "+fmt.Sprint(line)+"] Error"+where+": "+msg)
|
||||||
|
}
|
64
pj1-go/tokentype_string.go
Normal file
64
pj1-go/tokentype_string.go
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
// Code generated by "stringer -type=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[LEFT_PAREN-0]
|
||||||
|
_ = x[RIGHT_PAREN-1]
|
||||||
|
_ = x[LEFT_BRACE-2]
|
||||||
|
_ = x[RIGHT_BRACE-3]
|
||||||
|
_ = x[COMMA-4]
|
||||||
|
_ = x[DOT-5]
|
||||||
|
_ = x[MINUS-6]
|
||||||
|
_ = x[PLUS-7]
|
||||||
|
_ = x[SEMICOLON-8]
|
||||||
|
_ = x[STAR-9]
|
||||||
|
_ = x[BANG-10]
|
||||||
|
_ = x[BANG_EQUAL-11]
|
||||||
|
_ = x[EQUAL-12]
|
||||||
|
_ = x[EQUAL_EQUAL-13]
|
||||||
|
_ = x[GREATER-14]
|
||||||
|
_ = x[GREATER_EQUAL-15]
|
||||||
|
_ = x[LESS-16]
|
||||||
|
_ = x[LESS_EQUAL-17]
|
||||||
|
_ = x[SLASH-18]
|
||||||
|
_ = x[SLASH_DOT-19]
|
||||||
|
_ = x[SLASH_UNDERSCORE-20]
|
||||||
|
_ = x[IDENTIFIER-21]
|
||||||
|
_ = x[STRING-22]
|
||||||
|
_ = x[INTEGER-23]
|
||||||
|
_ = x[FLOAT-24]
|
||||||
|
_ = x[AND-25]
|
||||||
|
_ = x[CLASS-26]
|
||||||
|
_ = x[ELSE-27]
|
||||||
|
_ = x[FALSE-28]
|
||||||
|
_ = x[FOR-29]
|
||||||
|
_ = x[FUN-30]
|
||||||
|
_ = x[IF-31]
|
||||||
|
_ = x[NIL-32]
|
||||||
|
_ = x[OR-33]
|
||||||
|
_ = x[PRINT-34]
|
||||||
|
_ = x[RETURN-35]
|
||||||
|
_ = x[SUPER-36]
|
||||||
|
_ = x[THIS-37]
|
||||||
|
_ = x[TRUE-38]
|
||||||
|
_ = x[VAR-39]
|
||||||
|
_ = x[WHILE-40]
|
||||||
|
_ = x[EOF-41]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _TokenType_name = "LEFT_PARENRIGHT_PARENLEFT_BRACERIGHT_BRACECOMMADOTMINUSPLUSSEMICOLONSTARBANGBANG_EQUALEQUALEQUAL_EQUALGREATERGREATER_EQUALLESSLESS_EQUALSLASHSLASH_DOTSLASH_UNDERSCOREIDENTIFIERSTRINGINTEGERFLOATANDCLASSELSEFALSEFORFUNIFNILORPRINTRETURNSUPERTHISTRUEVARWHILEEOF"
|
||||||
|
|
||||||
|
var _TokenType_index = [...]uint16{0, 10, 21, 31, 42, 47, 50, 55, 59, 68, 72, 76, 86, 91, 102, 109, 122, 126, 136, 141, 150, 166, 176, 182, 189, 194, 197, 202, 206, 211, 214, 217, 219, 222, 224, 229, 235, 240, 244, 248, 251, 256, 259}
|
||||||
|
|
||||||
|
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]]
|
||||||
|
}
|
8
pj1-go/tools.go
Normal file
8
pj1-go/tools.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
//go:build tools
|
||||||
|
// +build tools
|
||||||
|
|
||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "golang.org/x/tools/cmd/stringer"
|
||||||
|
)
|
Loading…
Reference in a new issue