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 ( | ||||
| 	"bufio" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"regexp" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"unicode" | ||||
| 	"unicode/utf8" | ||||
| ) | ||||
| 
 | ||||
| func main() { | ||||
| 
 | ||||
| 	expr := &Binary{ | ||||
| 		left: &Literal{value: 1}, | ||||
| 		op:   Token{typ: PLUS, lexeme: "+", literal: "", line: 1}, | ||||
| 		right: &Binary{ | ||||
| 			left:  &Literal{value: 3}, | ||||
| 			op:    Token{typ: PLUS, lexeme: "+", literal: "", line: 1}, | ||||
| 			right: &Literal{value: 4}, | ||||
| 		}, | ||||
| 		&Unary{Token{MINUS, "-", nil, 1}, &Literal{123}}, | ||||
| 		Token{STAR, "*", nil, 1}, | ||||
| 		&Grouping{&Grouping{&Binary{ | ||||
| 			&Unary{Token{MINUS, "-", nil, 1}, &Literal{123}}, | ||||
| 			Token{STAR, "*", nil, 1}, | ||||
| 			&Grouping{&Grouping{&Literal{45.67}}}}}}, | ||||
| 	} | ||||
| 
 | ||||
| 	println((&AstStringer{}).String(expr)) | ||||
| 	println(AstStringer{}.String(expr)) | ||||
| 
 | ||||
| 	// switch len(os.Args) { | ||||
| 	// case 1: | ||||
| 	// 	runPrompt() | ||||
| 	// case 2: | ||||
| 	// 	runFile(os.Args[1]) | ||||
| 	// default: | ||||
| 	// 	println("Usage: glox [file]") | ||||
| 	// 	os.Exit(1) | ||||
| 	// } | ||||
| 	switch len(os.Args) { | ||||
| 	case 1: | ||||
| 		runPrompt() | ||||
| 	case 2: | ||||
| 		runFile(os.Args[1]) | ||||
| 	default: | ||||
| 		println("Usage: glox [file]") | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 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) { | ||||
| 	if err != nil { | ||||
| 		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