Initial commit: completed simple lexer for pj1
This commit is contained in:
		
						commit
						7f72ba59d5
					
				
					 6 changed files with 522 additions and 0 deletions
				
			
		
							
								
								
									
										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…
	
	Add table
		
		Reference in a new issue
	
	 Petar Kapriš
						Petar Kapriš