add ast
This commit is contained in:
		
							parent
							
								
									7e49d405f5
								
							
						
					
					
						commit
						c12d2047b1
					
				
					 3 changed files with 411 additions and 380 deletions
				
			
		
							
								
								
									
										93
									
								
								ast.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								ast.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,93 @@ | ||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type Expr interface { | ||||||
|  | 	expr() | ||||||
|  | 	accept(v Visitor) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Unary struct { | ||||||
|  | 	op    Token | ||||||
|  | 	right Expr | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (u *Unary) expr() {} | ||||||
|  | func (u *Unary) accept(v Visitor) { | ||||||
|  | 	v.visitUnary(u) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Grouping struct { | ||||||
|  | 	expression Expr | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (g *Grouping) expr() {} | ||||||
|  | func (g *Grouping) accept(v Visitor) { | ||||||
|  | 	v.visitGrouping(g) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Literal struct { | ||||||
|  | 	value any | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l *Literal) expr() {} | ||||||
|  | func (l *Literal) accept(v Visitor) { | ||||||
|  | 	v.visitLiteral(l) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Binary struct { | ||||||
|  | 	left  Expr | ||||||
|  | 	op    Token | ||||||
|  | 	right Expr | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (b *Binary) expr() {} | ||||||
|  | func (b *Binary) accept(v Visitor) { | ||||||
|  | 	v.visitBinary(b) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Visitor interface { | ||||||
|  | 	visitBinary(b *Binary) | ||||||
|  | 	visitLiteral(l *Literal) | ||||||
|  | 	visitGrouping(g *Grouping) | ||||||
|  | 	visitUnary(u *Unary) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type AstStringer struct { | ||||||
|  | 	str strings.Builder | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (as AstStringer) String(expr Expr) string { | ||||||
|  | 	expr.accept(&as) | ||||||
|  | 	return as.str.String() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (as *AstStringer) visitBinary(b *Binary) { | ||||||
|  | 	as.str.WriteString("(") | ||||||
|  | 	as.str.WriteString(b.op.lexeme) | ||||||
|  | 	as.str.WriteString(" ") | ||||||
|  | 	b.left.accept(as) | ||||||
|  | 	as.str.WriteString(" ") | ||||||
|  | 	b.right.accept(as) | ||||||
|  | 	as.str.WriteString(")") | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (as *AstStringer) visitLiteral(l *Literal) { | ||||||
|  | 	as.str.WriteString(fmt.Sprintf("%v", l.value)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (as *AstStringer) visitGrouping(g *Grouping) { | ||||||
|  | 	as.str.WriteString("(group ") | ||||||
|  | 	g.expression.accept(as) | ||||||
|  | 	as.str.WriteString(")") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (as *AstStringer) visitUnary(u *Unary) { | ||||||
|  | 	as.str.WriteString(fmt.Sprintf("(%s ", u.op.lexeme)) | ||||||
|  | 	u.right.accept(as) | ||||||
|  | 	as.str.WriteString(")") | ||||||
|  | } | ||||||
							
								
								
									
										396
									
								
								glox.go
									
										
									
									
									
								
							
							
						
						
									
										396
									
								
								glox.go
									
										
									
									
									
								
							|  | @ -2,39 +2,31 @@ package main | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"bufio" | 	"bufio" | ||||||
| 	"fmt" |  | ||||||
| 	"log" | 	"log" | ||||||
| 	"os" | 	"os" | ||||||
| 	"regexp" |  | ||||||
| 	"strconv" |  | ||||||
| 	"strings" |  | ||||||
| 	"unicode" |  | ||||||
| 	"unicode/utf8" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func main() { | func main() { | ||||||
| 
 |  | ||||||
| 	expr := &Binary{ | 	expr := &Binary{ | ||||||
| 		left: &Literal{value: 1}, | 		&Unary{Token{MINUS, "-", nil, 1}, &Literal{123}}, | ||||||
| 		op:   Token{typ: PLUS, lexeme: "+", literal: "", line: 1}, | 		Token{STAR, "*", nil, 1}, | ||||||
| 		right: &Binary{ | 		&Grouping{&Grouping{&Binary{ | ||||||
| 			left:  &Literal{value: 3}, | 			&Unary{Token{MINUS, "-", nil, 1}, &Literal{123}}, | ||||||
| 			op:    Token{typ: PLUS, lexeme: "+", literal: "", line: 1}, | 			Token{STAR, "*", nil, 1}, | ||||||
| 			right: &Literal{value: 4}, | 			&Grouping{&Grouping{&Literal{45.67}}}}}}, | ||||||
| 		}, |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	println((&AstStringer{}).String(expr)) | 	println(AstStringer{}.String(expr)) | ||||||
| 
 | 
 | ||||||
| 	// switch len(os.Args) { | 	switch len(os.Args) { | ||||||
| 	// case 1: | 	case 1: | ||||||
| 	// 	runPrompt() | 		runPrompt() | ||||||
| 	// case 2: | 	case 2: | ||||||
| 	// 	runFile(os.Args[1]) | 		runFile(os.Args[1]) | ||||||
| 	// default: | 	default: | ||||||
| 	// 	println("Usage: glox [file]") | 		println("Usage: glox [file]") | ||||||
| 	// 	os.Exit(1) | 		os.Exit(1) | ||||||
| 	// } | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func runPrompt() { | func runPrompt() { | ||||||
|  | @ -73,362 +65,6 @@ func run(source []byte) { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 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 Expr interface { |  | ||||||
| 	expr() |  | ||||||
| 	accept(v Visitor) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type Unary struct { |  | ||||||
| 	op    Token |  | ||||||
| 	right Expr |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type Grouping struct { |  | ||||||
| 	expr Expr |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type Literal struct { |  | ||||||
| 	value any |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (l *Literal) expr() {} |  | ||||||
| func (l *Literal) accept(v Visitor) { |  | ||||||
| 	v.visitLiteral(l) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type Binary struct { |  | ||||||
| 	left  Expr |  | ||||||
| 	op    Token |  | ||||||
| 	right Expr |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (b *Binary) expr() {} |  | ||||||
| func (b *Binary) accept(v Visitor) { |  | ||||||
| 	v.visitBinary(b) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type Visitor interface { |  | ||||||
| 	visitBinary(b *Binary) |  | ||||||
| 	visitLiteral(l *Literal) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type AstStringer struct { |  | ||||||
| 	str strings.Builder |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (as *AstStringer) String(expr Expr) string { |  | ||||||
| 	expr.accept(as) |  | ||||||
| 	return as.str.String() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (as *AstStringer) visitBinary(b *Binary) { |  | ||||||
| 	as.str.WriteString("(") |  | ||||||
| 	as.str.WriteString(b.op.lexeme) |  | ||||||
| 	as.str.WriteString(" ") |  | ||||||
| 	b.left.accept(as) |  | ||||||
| 	as.str.WriteString(" ") |  | ||||||
| 	b.right.accept(as) |  | ||||||
| 	as.str.WriteString(")") |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (as *AstStringer) visitLiteral(l *Literal) { |  | ||||||
| 	as.str.WriteString(fmt.Sprintf("%v", l.value)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 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) { | func panic(err error) { | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Fatal(err) | 		log.Fatal(err) | ||||||
|  |  | ||||||
							
								
								
									
										302
									
								
								scanner.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										302
									
								
								scanner.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,302 @@ | ||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"regexp" | ||||||
|  | 	"strconv" | ||||||
|  | 	"unicode" | ||||||
|  | 	"unicode/utf8" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | 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) 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) 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) 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) 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 (s *Scanner) printError(message string) { | ||||||
|  | 	fmt.Printf("[line %d] Error: %s\n", s.line, message) | ||||||
|  | 	hadError = true | ||||||
|  | } | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue