Initial commit of lexer
This commit is contained in:
		
							
								
								
									
										10
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| module git.geekproject.eu/halfdan/glox | ||||
|  | ||||
| go 1.17 | ||||
|  | ||||
| require ( | ||||
| 	golang.org/x/mod v0.5.1 // indirect | ||||
| 	golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect | ||||
| 	golang.org/x/tools v0.1.8 // indirect | ||||
| 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect | ||||
| ) | ||||
							
								
								
									
										8
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= | ||||
| golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= | ||||
| golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= | ||||
| golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w= | ||||
| golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= | ||||
| golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= | ||||
| golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
							
								
								
									
										33
									
								
								lox/lox.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								lox/lox.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| package lox | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| ) | ||||
|  | ||||
| func RunFile(filename string) { | ||||
| 	bytes, err := ioutil.ReadFile(filename) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	lexer := newLexer(string(bytes)) | ||||
| 	lexer.lex() | ||||
| } | ||||
|  | ||||
| func RunFormat(filename string) { | ||||
|  | ||||
| } | ||||
|  | ||||
| func RunPrompt() { | ||||
| 	scanner := bufio.NewScanner(os.Stdin) | ||||
| 	scanner.Split(bufio.ScanLines) | ||||
| 	fmt.Print("> ") | ||||
|  | ||||
| 	for scanner.Scan() { | ||||
| 		text := scanner.Text() | ||||
| 		fmt.Printf("Input was: %s\n", text) | ||||
| 		fmt.Print("> ") | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										140
									
								
								lox/scanner.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								lox/scanner.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,140 @@ | ||||
| package lox | ||||
|  | ||||
| type Lexer struct { | ||||
| 	start   int | ||||
| 	current int | ||||
| 	line    int | ||||
| 	column  int | ||||
| 	source  []rune | ||||
| 	tokens  []Token | ||||
| } | ||||
|  | ||||
| func (lexer Lexer) lex() { | ||||
|  | ||||
| 	for !lexer.eof() { | ||||
| 		lexer.start = lexer.current | ||||
| 		c := lexer.advance() | ||||
|  | ||||
| 		switch c { | ||||
| 		case '(': | ||||
| 			lexer.addToken(LeftParen) | ||||
| 		case ')': | ||||
| 			lexer.addToken(RightParen) | ||||
| 		case '{': | ||||
| 			lexer.addToken(LeftBrace) | ||||
| 		case '}': | ||||
| 			lexer.addToken(RightBrace) | ||||
| 		case ',': | ||||
| 			lexer.addToken(Comma) | ||||
| 		case '.': | ||||
| 			lexer.addToken(Dot) | ||||
| 		case '-': | ||||
| 			lexer.addToken(Minus) | ||||
| 		case '+': | ||||
| 			lexer.addToken(Plus) | ||||
| 		case ';': | ||||
| 			lexer.addToken(Semicolon) | ||||
| 		case '*': | ||||
| 			lexer.addToken(Star) | ||||
| 		case '!': | ||||
| 			if lexer.match('=') { | ||||
| 				lexer.addToken(BangEqual) | ||||
| 			} else { | ||||
| 				lexer.addToken(Bang) | ||||
| 			} | ||||
| 		case '=': | ||||
| 			if lexer.match('=') { | ||||
| 				lexer.addToken(EqualEqual) | ||||
| 			} else { | ||||
| 				lexer.addToken(Equal) | ||||
| 			} | ||||
| 		case '<': | ||||
| 			if lexer.match('=') { | ||||
| 				lexer.addToken(LessEqual) | ||||
| 			} else { | ||||
| 				lexer.addToken(Less) | ||||
| 			} | ||||
| 		case '>': | ||||
| 			if lexer.match('=') { | ||||
| 				lexer.addToken(GreaterEqual) | ||||
| 			} else { | ||||
| 				lexer.addToken(Greater) | ||||
| 			} | ||||
| 		case '/': | ||||
| 			if lexer.match('/') { | ||||
| 				for lexer.peek() != '\n' && !lexer.eof() { | ||||
| 					lexer.advance() | ||||
| 				} | ||||
| 			} else { | ||||
| 				lexer.addToken(Slash) | ||||
| 			} | ||||
| 		case ' ', '\r', '\t': // Ignore whitespace | ||||
| 			continue | ||||
| 		case '\n': | ||||
| 			lexer.line++ | ||||
| 			lexer.column = 0 | ||||
| 		case '"': | ||||
| 			for lexer.peek() != '"' && !lexer.eof() { | ||||
| 				if lexer.peek() == '\n' { | ||||
| 					lexer.line++ | ||||
| 					lexer.column-- | ||||
| 				} | ||||
| 				lexer.advance() | ||||
| 			} | ||||
|  | ||||
| 			// if lexer.eof() { | ||||
| 			// 	// Add error (unterminated string) | ||||
| 			// } | ||||
|  | ||||
| 			// Consume the terminating " | ||||
| 			lexer.advance() | ||||
|  | ||||
| 			//value := lexer.source[lexer.start+1 : lexer.current-1] | ||||
| 			//lexer.addToken(String, value) | ||||
| 		} | ||||
| 	} | ||||
| 	lexer.addToken(Eof) | ||||
| } | ||||
|  | ||||
| func (s *Lexer) addToken(tt TokenType) { | ||||
| 	s.tokens = append(s.tokens, *makeToken(tt, "", nil, s.line, s.column)) | ||||
| } | ||||
|  | ||||
| // eof returns true if the read pointer has reached the end of the source file | ||||
| func (s Lexer) eof() bool { | ||||
| 	return s.current >= len(s.source) | ||||
| } | ||||
|  | ||||
| // peek looks one rune ahead without advancing the cursor | ||||
| func (s Lexer) peek() rune { | ||||
| 	if s.eof() { | ||||
| 		return '\u0004' // emit end of file character | ||||
| 	} | ||||
| 	return s.source[s.current] | ||||
| } | ||||
|  | ||||
| // advance increments the read pointer and returns the rune at that position | ||||
| func (s *Lexer) advance() rune { | ||||
| 	r := s.source[s.current] | ||||
| 	s.current++ | ||||
| 	s.column++ | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| // match tried to match a single rune and advances the read pointer if it does. | ||||
| func (s *Lexer) match(expected rune) bool { | ||||
| 	if s.eof() { | ||||
| 		return false | ||||
| 	} | ||||
| 	if s.source[s.current] != expected { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	s.current++ | ||||
| 	s.column++ | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func newLexer(source string) *Lexer { | ||||
| 	return &Lexer{line: 1, column: 0, source: []rune(source)} | ||||
| } | ||||
							
								
								
									
										72
									
								
								lox/token.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								lox/token.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| package lox | ||||
|  | ||||
| import "fmt" | ||||
|  | ||||
| //go:generate stringer -type=TokenType | ||||
| type TokenType int | ||||
|  | ||||
| const ( | ||||
| 	// Single character tokens | ||||
| 	LeftParen TokenType = iota | ||||
| 	RightParen | ||||
| 	LeftBrace | ||||
| 	RightBrace | ||||
| 	Comma | ||||
| 	Dot | ||||
| 	Minus | ||||
| 	Plus | ||||
| 	Semicolon | ||||
| 	Slash | ||||
| 	Star | ||||
|  | ||||
| 	// One or two character tokens | ||||
| 	Bang | ||||
| 	BangEqual | ||||
| 	Equal | ||||
| 	EqualEqual | ||||
| 	Greater | ||||
| 	GreaterEqual | ||||
| 	Less | ||||
| 	LessEqual | ||||
|  | ||||
| 	// Literals | ||||
| 	Identifier | ||||
| 	String | ||||
| 	Number | ||||
|  | ||||
| 	// Keywords | ||||
| 	And | ||||
| 	Class | ||||
| 	Else | ||||
| 	False | ||||
| 	Fun | ||||
| 	For | ||||
| 	If | ||||
| 	Nil | ||||
| 	Or | ||||
| 	Print | ||||
| 	Return | ||||
| 	Super | ||||
| 	This | ||||
| 	True | ||||
| 	Var | ||||
| 	While | ||||
|  | ||||
| 	Eof | ||||
| ) | ||||
|  | ||||
| type Token struct { | ||||
| 	tokenType TokenType | ||||
| 	lexeme    string | ||||
| 	literal   interface{} | ||||
| 	line      int | ||||
| 	column    int | ||||
| } | ||||
|  | ||||
| func (t Token) String() string { | ||||
| 	return fmt.Sprintf("%v", t) | ||||
| } | ||||
|  | ||||
| func makeToken(tt TokenType, lexeme string, lit interface{}, line, column int) *Token { | ||||
| 	return &Token{tokenType: tt, lexeme: lexeme, literal: lit, line: line, column: column} | ||||
| } | ||||
							
								
								
									
										61
									
								
								lox/tokentype_string.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								lox/tokentype_string.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| // Code generated by "stringer -type=TokenType"; DO NOT EDIT. | ||||
|  | ||||
| package lox | ||||
|  | ||||
| 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[LeftParen-0] | ||||
| 	_ = x[RightParen-1] | ||||
| 	_ = x[LeftBrace-2] | ||||
| 	_ = x[RightBrace-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[BangEqual-12] | ||||
| 	_ = x[Equal-13] | ||||
| 	_ = x[EqualEqual-14] | ||||
| 	_ = x[Greater-15] | ||||
| 	_ = x[GreaterEqual-16] | ||||
| 	_ = x[Less-17] | ||||
| 	_ = x[LessEqual-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 = "LeftParenRightParenLeftBraceRightBraceCommaDotMinusPlusSemicolonSlashStarBangBangEqualEqualEqualEqualGreaterGreaterEqualLessLessEqualIdentifierStringNumberAndClassElseFalseFunForIfNilOrPrintReturnSuperThisTrueVarWhileEof" | ||||
|  | ||||
| var _TokenType_index = [...]uint8{0, 9, 19, 28, 38, 43, 46, 51, 55, 64, 69, 73, 77, 86, 91, 101, 108, 120, 124, 133, 143, 149, 155, 158, 163, 167, 172, 175, 178, 180, 183, 185, 190, 196, 201, 205, 209, 212, 217, 220} | ||||
|  | ||||
| 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]] | ||||
| } | ||||
							
								
								
									
										49
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
|  | ||||
| 	lox "git.geekproject.eu/halfdan/glox/lox" | ||||
| ) | ||||
|  | ||||
| // glox: | ||||
| //	run		- run a scripts | ||||
| //	build	- build a binary | ||||
| //	fmt		- format file | ||||
| //	sh 		- run interactive shell | ||||
| func main() { | ||||
| 	if len(os.Args) < 2 { | ||||
| 		usage() | ||||
| 	} | ||||
|  | ||||
| 	switch os.Args[1] { | ||||
| 	case "run": | ||||
| 		if len(os.Args) < 3 { | ||||
| 			fmt.Print("glox run: no lox file listed\n") | ||||
| 			os.Exit(2) | ||||
| 		} | ||||
| 		lox.RunFile(os.Args[2]) | ||||
| 	case "fmt": | ||||
| 		if len(os.Args) < 3 { | ||||
| 			fmt.Print("glox fmt: no lox file listed\n") | ||||
| 			os.Exit(2) | ||||
| 		} | ||||
| 		lox.RunFormat(os.Args[2]) | ||||
| 	case "sh": | ||||
| 		lox.RunPrompt() | ||||
| 	default: | ||||
| 		usage() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func usage() { | ||||
| 	fmt.Print("glox is a tool for running Lox code.\n\n") | ||||
| 	fmt.Print("Usage:\n\n") | ||||
| 	fmt.Print("\tglox <command> [arguments]\n\n") | ||||
| 	fmt.Print("The commands are:\n\n") | ||||
| 	fmt.Print("\trun\trun a lox script\n") | ||||
| 	fmt.Print("\tfmt\tformat input file\n") | ||||
| 	fmt.Print("\tsh\tstart interactive shell\n\n") | ||||
| 	os.Exit(2) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user