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…
Reference in a new issue