lexer
This commit is contained in:
		
						commit
						fc5f2f1172
					
				
					 4 changed files with 436 additions and 0 deletions
				
			
		
							
								
								
									
										360
									
								
								glox.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										360
									
								
								glox.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,360 @@ | ||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bufio" | ||||||
|  | 	"fmt" | ||||||
|  | 	"log" | ||||||
|  | 	"os" | ||||||
|  | 	"regexp" | ||||||
|  | 	"strconv" | ||||||
|  | 	"unicode" | ||||||
|  | 	"unicode/utf8" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func main() { | ||||||
|  | 
 | ||||||
|  | 	switch len(os.Args) { | ||||||
|  | 	case 1: | ||||||
|  | 		runPrompt() | ||||||
|  | 	case 2: | ||||||
|  | 		runFile(os.Args[1]) | ||||||
|  | 	default: | ||||||
|  | 		println("Usage: glox [file]") | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var hadError = false | ||||||
|  | 
 | ||||||
|  | //go:generate go run golang.org/x/tools/cmd/stringer -type=TokenType | ||||||
|  | type TokenType int | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// one char | ||||||
|  | 	LEFT_PAREN TokenType = iota | ||||||
|  | 	RIGHT_PAREN | ||||||
|  | 	LEFT_BRACE | ||||||
|  | 	RIGHT_BRACE | ||||||
|  | 	COMMA | ||||||
|  | 	DOT | ||||||
|  | 	MINUS | ||||||
|  | 	PLUS | ||||||
|  | 	SEMICOLON | ||||||
|  | 	SLASH | ||||||
|  | 	STAR | ||||||
|  | 
 | ||||||
|  | 	// one or two chars | ||||||
|  | 	BANG | ||||||
|  | 	BANG_EQUAL | ||||||
|  | 	EQUAL | ||||||
|  | 	EQUAL_EQUAL | ||||||
|  | 	GREATER | ||||||
|  | 	GREATER_EQUAL | ||||||
|  | 	LESS | ||||||
|  | 	LESS_EQUAL | ||||||
|  | 
 | ||||||
|  | 	// Literals | ||||||
|  | 	IDENTIFIER | ||||||
|  | 	STRING | ||||||
|  | 	NUMBER | ||||||
|  | 
 | ||||||
|  | 	// keywords | ||||||
|  | 	AND | ||||||
|  | 	CLASS | ||||||
|  | 	ELSE | ||||||
|  | 	FALSE | ||||||
|  | 	FUN | ||||||
|  | 	FOR | ||||||
|  | 	IF | ||||||
|  | 	NIL | ||||||
|  | 	OR | ||||||
|  | 	PRINT | ||||||
|  | 	RETURN | ||||||
|  | 	SUPER | ||||||
|  | 	THIS | ||||||
|  | 	TRUE | ||||||
|  | 	VAR | ||||||
|  | 	WHILE | ||||||
|  | 
 | ||||||
|  | 	EOF | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | 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 { | ||||||
|  | 	typ     TokenType | ||||||
|  | 	lexeme  string | ||||||
|  | 	literal any | ||||||
|  | 	line    int | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (t *Token) string() string { | ||||||
|  | 	return fmt.Sprintf("%s - %s - %v", t.typ, t.lexeme, t.literal) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Scanner struct { | ||||||
|  | 	source  []byte | ||||||
|  | 	tokens  []Token | ||||||
|  | 	start   int | ||||||
|  | 	current int | ||||||
|  | 	line    int | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newScanner(source []byte) *Scanner { | ||||||
|  | 	return &Scanner{source: source, start: 0, current: 0, line: 1} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Scanner) scan() []Token { | ||||||
|  | 
 | ||||||
|  | 	for !s.isAtEnd() { | ||||||
|  | 		s.start = s.current | ||||||
|  | 		s.scanToken() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	s.tokens = append(s.tokens, Token{EOF, "EOF", struct{}{}, s.line}) | ||||||
|  | 
 | ||||||
|  | 	return s.tokens | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Scanner) printError(message string) { | ||||||
|  | 	fmt.Printf("[line %d] Error: %s\n", s.line, message) | ||||||
|  | 	hadError = true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Scanner) scanToken() { | ||||||
|  | 	c := s.advance() | ||||||
|  | 	switch c { | ||||||
|  | 	case '(': | ||||||
|  | 		s.addToken(LEFT_PAREN, struct{}{}) | ||||||
|  | 	case ')': | ||||||
|  | 		s.addToken(RIGHT_PAREN, struct{}{}) | ||||||
|  | 	case '{': | ||||||
|  | 		s.addToken(LEFT_BRACE, struct{}{}) | ||||||
|  | 	case '}': | ||||||
|  | 		s.addToken(RIGHT_BRACE, struct{}{}) | ||||||
|  | 	case ',': | ||||||
|  | 		s.addToken(COMMA, struct{}{}) | ||||||
|  | 	case '.': | ||||||
|  | 		s.addToken(DOT, struct{}{}) | ||||||
|  | 	case '-': | ||||||
|  | 		s.addToken(MINUS, struct{}{}) | ||||||
|  | 	case '+': | ||||||
|  | 		s.addToken(PLUS, struct{}{}) | ||||||
|  | 	case ';': | ||||||
|  | 		s.addToken(SEMICOLON, struct{}{}) | ||||||
|  | 	case '*': | ||||||
|  | 		s.addToken(STAR, struct{}{}) | ||||||
|  | 
 | ||||||
|  | 	case '!': | ||||||
|  | 		if s.match('=') { | ||||||
|  | 			s.addToken(BANG_EQUAL, struct{}{}) | ||||||
|  | 		} else { | ||||||
|  | 			s.addToken(BANG, struct{}{}) | ||||||
|  | 		} | ||||||
|  | 	case '=': | ||||||
|  | 		if s.match('=') { | ||||||
|  | 			s.addToken(EQUAL_EQUAL, struct{}{}) | ||||||
|  | 		} else { | ||||||
|  | 			s.addToken(EQUAL, struct{}{}) | ||||||
|  | 		} | ||||||
|  | 	case '<': | ||||||
|  | 		if s.match('=') { | ||||||
|  | 			s.addToken(LESS_EQUAL, struct{}{}) | ||||||
|  | 		} else { | ||||||
|  | 			s.addToken(LESS, struct{}{}) | ||||||
|  | 		} | ||||||
|  | 	case '>': | ||||||
|  | 		if s.match('=') { | ||||||
|  | 			s.addToken(GREATER_EQUAL, struct{}{}) | ||||||
|  | 		} else { | ||||||
|  | 			s.addToken(GREATER, struct{}{}) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	case '/': | ||||||
|  | 		if s.match('/') { | ||||||
|  | 			for s.peek() != '\n' && !s.isAtEnd() { | ||||||
|  | 				s.advance() | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			s.addToken(SLASH, struct{}{}) | ||||||
|  | 		} | ||||||
|  | 	case '"': | ||||||
|  | 		s.string() | ||||||
|  | 	case ' ': | ||||||
|  | 	case '\t': | ||||||
|  | 	case '\r': | ||||||
|  | 		break | ||||||
|  | 	case '\n': | ||||||
|  | 		s.line++ | ||||||
|  | 	default: | ||||||
|  | 		if unicode.IsDigit(c) { | ||||||
|  | 			s.number() | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if s.isAlpha(c) { | ||||||
|  | 			s.identifier() | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		s.printError(fmt.Sprintf("Unexpected character %s", string(c))) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Scanner) identifier() { | ||||||
|  | 	for unicode.IsDigit(s.peek()) || s.isAlpha(s.peek()) { | ||||||
|  | 		s.advance() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	str := s.source[s.start:s.current] | ||||||
|  | 
 | ||||||
|  | 	if id, found := keywords[string(str)]; found { | ||||||
|  | 		s.addToken(id, struct{}{}) | ||||||
|  | 	} else { | ||||||
|  | 		s.addToken(IDENTIFIER, struct{}{}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Scanner) string() { | ||||||
|  | 	for s.peek() != '"' && !s.isAtEnd() { | ||||||
|  | 		if s.peek() == '\n' { | ||||||
|  | 			s.line++ | ||||||
|  | 		} | ||||||
|  | 		s.advance() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if s.isAtEnd() { | ||||||
|  | 		s.printError("Unterminated string") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	s.advance() | ||||||
|  | 	str := string(s.source[s.start+1 : s.current-1]) | ||||||
|  | 	s.addToken(STRING, str) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Scanner) number() { | ||||||
|  | 	for unicode.IsDigit(s.peek()) { | ||||||
|  | 		s.advance() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if s.peek() == '.' && unicode.IsDigit(s.peekNext()) { | ||||||
|  | 		s.advance() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for unicode.IsDigit(s.peek()) { | ||||||
|  | 		s.advance() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	num, err := strconv.ParseFloat(string(s.source[s.start:s.current]), 64) | ||||||
|  | 
 | ||||||
|  | 	if err != nil { | ||||||
|  | 		s.printError(err.Error()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	s.addToken(NUMBER, num) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Scanner) isAlpha(ch rune) bool { | ||||||
|  | 	return regexp.MustCompile(`^[A-Za-z_]+$`).MatchString(string(ch)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Scanner) addToken(typ TokenType, literal any) { | ||||||
|  | 	text := string(s.source[s.start:s.current]) | ||||||
|  | 	s.tokens = append(s.tokens, Token{typ: typ, lexeme: text, literal: literal, line: s.line}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Scanner) advance() rune { | ||||||
|  | 	char, size := utf8.DecodeRune(s.source[s.current:]) | ||||||
|  | 	s.current += size | ||||||
|  | 	return char | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Scanner) peek() rune { | ||||||
|  | 	char, _ := utf8.DecodeRune(s.source[s.current:]) | ||||||
|  | 	return char | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Scanner) peekNext() rune { | ||||||
|  | 	_, size := utf8.DecodeRune(s.source[s.current+1:]) | ||||||
|  | 	if s.current+size >= len(s.source) { | ||||||
|  | 		return '\000' | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	next, _ := utf8.DecodeRune(s.source[s.current+size:]) | ||||||
|  | 	return next | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Scanner) match(ch rune) bool { | ||||||
|  | 	if s.isAtEnd() { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	decoded, size := utf8.DecodeRune(s.source[s.current:]) | ||||||
|  | 	s.current += size | ||||||
|  | 	return ch == decoded | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Scanner) isAtEnd() bool { | ||||||
|  | 	return s.current >= len(s.source) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func panic(err error) { | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatal(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func runPrompt() { | ||||||
|  | 	scanner := bufio.NewScanner(os.Stdin) | ||||||
|  | 	scanner.Split(bufio.ScanLines) | ||||||
|  | 
 | ||||||
|  | 	for { | ||||||
|  | 		print("> ") | ||||||
|  | 		scanner.Scan() | ||||||
|  | 		line := scanner.Text() | ||||||
|  | 		if len(line) == 0 { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 		run([]byte(scanner.Text())) | ||||||
|  | 		hadError = false | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func runFile(path string) { | ||||||
|  | 	file, err := os.ReadFile(path) | ||||||
|  | 
 | ||||||
|  | 	panic(err) | ||||||
|  | 
 | ||||||
|  | 	run(file) | ||||||
|  | 
 | ||||||
|  | 	if hadError { | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func run(source []byte) { | ||||||
|  | 	tokens := newScanner(source).scan() | ||||||
|  | 
 | ||||||
|  | 	for _, token := range tokens { | ||||||
|  | 		println(token.string()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								go.mod
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								go.mod
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | ||||||
|  | module fotonmoton/glox | ||||||
|  | 
 | ||||||
|  | go 1.23.1 | ||||||
|  | 
 | ||||||
|  | require ( | ||||||
|  | 	golang.org/x/mod v0.21.0 // indirect | ||||||
|  | 	golang.org/x/sync v0.8.0 // indirect | ||||||
|  | 	golang.org/x/tools v0.25.0 // indirect | ||||||
|  | ) | ||||||
							
								
								
									
										6
									
								
								go.sum
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								go.sum
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | ||||||
|  | golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= | ||||||
|  | golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= | ||||||
|  | golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= | ||||||
|  | golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= | ||||||
|  | golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= | ||||||
|  | golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= | ||||||
							
								
								
									
										61
									
								
								tokentype_string.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								tokentype_string.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,61 @@ | ||||||
|  | // 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[SLASH-9] | ||||||
|  | 	_ = x[STAR-10] | ||||||
|  | 	_ = x[BANG-11] | ||||||
|  | 	_ = x[BANG_EQUAL-12] | ||||||
|  | 	_ = x[EQUAL-13] | ||||||
|  | 	_ = x[EQUAL_EQUAL-14] | ||||||
|  | 	_ = x[GREATER-15] | ||||||
|  | 	_ = x[GREATER_EQUAL-16] | ||||||
|  | 	_ = x[LESS-17] | ||||||
|  | 	_ = x[LESS_EQUAL-18] | ||||||
|  | 	_ = x[IDENTIFIER-19] | ||||||
|  | 	_ = x[STRING-20] | ||||||
|  | 	_ = x[NUMBER-21] | ||||||
|  | 	_ = x[AND-22] | ||||||
|  | 	_ = x[CLASS-23] | ||||||
|  | 	_ = x[ELSE-24] | ||||||
|  | 	_ = x[FALSE-25] | ||||||
|  | 	_ = x[FUN-26] | ||||||
|  | 	_ = x[FOR-27] | ||||||
|  | 	_ = x[IF-28] | ||||||
|  | 	_ = x[NIL-29] | ||||||
|  | 	_ = x[OR-30] | ||||||
|  | 	_ = x[PRINT-31] | ||||||
|  | 	_ = x[RETURN-32] | ||||||
|  | 	_ = x[SUPER-33] | ||||||
|  | 	_ = x[THIS-34] | ||||||
|  | 	_ = x[TRUE-35] | ||||||
|  | 	_ = x[VAR-36] | ||||||
|  | 	_ = x[WHILE-37] | ||||||
|  | 	_ = x[EOF-38] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const _TokenType_name = "LEFT_PARENRIGHT_PARENLEFT_BRACERIGHT_BRACECOMMADOTMINUSPLUSSEMICOLONSLASHSTARBANGBANG_EQUALEQUALEQUAL_EQUALGREATERGREATER_EQUALLESSLESS_EQUALIDENTIFIERSTRINGNUMBERANDCLASSELSEFALSEFUNFORIFNILORPRINTRETURNSUPERTHISTRUEVARWHILEEOF" | ||||||
|  | 
 | ||||||
|  | var _TokenType_index = [...]uint8{0, 10, 21, 31, 42, 47, 50, 55, 59, 68, 73, 77, 81, 91, 96, 107, 114, 127, 131, 141, 151, 157, 163, 166, 171, 175, 180, 183, 186, 188, 191, 193, 198, 204, 209, 213, 217, 220, 225, 228} | ||||||
|  | 
 | ||||||
|  | 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
		
		Reference in a new issue