variables
This commit is contained in:
parent
50ecb3dc2e
commit
6fad12456c
14 changed files with 485 additions and 228 deletions
10
.vscode/launch.json
vendored
10
.vscode/launch.json
vendored
|
@ -3,5 +3,13 @@
|
||||||
// Hover to view descriptions of existing attributes.
|
// Hover to view descriptions of existing attributes.
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": []
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Attach to Process",
|
||||||
|
"type": "go",
|
||||||
|
"request": "attach",
|
||||||
|
"mode": "local",
|
||||||
|
"processId": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
|
@ -1,5 +1,7 @@
|
||||||
{
|
{
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"glox"
|
"glox",
|
||||||
|
"PAREN",
|
||||||
|
"stmts"
|
||||||
]
|
]
|
||||||
}
|
}
|
53
ast.go
53
ast.go
|
@ -1,53 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
type Visitor interface {
|
|
||||||
visitUnary(u *Unary) any
|
|
||||||
visitBinary(b *Binary) any
|
|
||||||
visitLiteral(l *Literal) any
|
|
||||||
visitGrouping(g *Grouping) any
|
|
||||||
}
|
|
||||||
|
|
||||||
type Expr interface {
|
|
||||||
expr()
|
|
||||||
accept(v Visitor) any
|
|
||||||
}
|
|
||||||
|
|
||||||
type Unary struct {
|
|
||||||
op Token
|
|
||||||
right Expr
|
|
||||||
}
|
|
||||||
|
|
||||||
type Binary struct {
|
|
||||||
left Expr
|
|
||||||
op Token
|
|
||||||
right Expr
|
|
||||||
}
|
|
||||||
|
|
||||||
type Literal struct {
|
|
||||||
value any
|
|
||||||
}
|
|
||||||
|
|
||||||
type Grouping struct {
|
|
||||||
expression Expr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *Unary) expr() {}
|
|
||||||
func (b *Binary) expr() {}
|
|
||||||
func (l *Literal) expr() {}
|
|
||||||
func (g *Grouping) expr() {}
|
|
||||||
|
|
||||||
func (u *Unary) accept(v Visitor) any {
|
|
||||||
return v.visitUnary(u)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Binary) accept(v Visitor) any {
|
|
||||||
return v.visitBinary(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Literal) accept(v Visitor) any {
|
|
||||||
return v.visitLiteral(l)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *Grouping) accept(v Visitor) any {
|
|
||||||
return v.visitGrouping(g)
|
|
||||||
}
|
|
46
ast_rpn.go
46
ast_rpn.go
|
@ -1,46 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AstToRPN struct {
|
|
||||||
str strings.Builder
|
|
||||||
}
|
|
||||||
|
|
||||||
func (as AstToRPN) String(expr Expr) string {
|
|
||||||
|
|
||||||
if expr == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
expr.accept(&as)
|
|
||||||
return as.str.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (as *AstToRPN) visitBinary(b *Binary) any {
|
|
||||||
b.left.accept(as)
|
|
||||||
as.str.WriteString(" ")
|
|
||||||
b.right.accept(as)
|
|
||||||
as.str.WriteString(" ")
|
|
||||||
as.str.WriteString(b.op.lexeme)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (as *AstToRPN) visitLiteral(l *Literal) any {
|
|
||||||
as.str.WriteString(fmt.Sprintf("%v", l.value))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (as *AstToRPN) visitGrouping(g *Grouping) any {
|
|
||||||
g.expression.accept(as)
|
|
||||||
as.str.WriteString(" group")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (as *AstToRPN) visitUnary(u *Unary) any {
|
|
||||||
u.right.accept(as)
|
|
||||||
as.str.WriteString(fmt.Sprintf(" %s", u.op.lexeme))
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -9,13 +9,12 @@ type AstStringer struct {
|
||||||
str strings.Builder
|
str strings.Builder
|
||||||
}
|
}
|
||||||
|
|
||||||
func (as AstStringer) String(expr Expr) string {
|
func (as AstStringer) String(stmts []Stmt) string {
|
||||||
|
|
||||||
if expr == nil {
|
for _, stmt := range stmts {
|
||||||
return ""
|
stmt.accept(&as)
|
||||||
}
|
}
|
||||||
|
|
||||||
expr.accept(&as)
|
|
||||||
return as.str.String()
|
return as.str.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,3 +48,35 @@ func (as *AstStringer) visitUnary(u *Unary) any {
|
||||||
as.str.WriteString(")")
|
as.str.WriteString(")")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (as *AstStringer) visitVariable(va *Variable) any {
|
||||||
|
as.str.WriteString(va.name.lexeme)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (as *AstStringer) visitAssignment(a *Assign) any {
|
||||||
|
as.str.WriteString(fmt.Sprintf("(= %s ", a.variable.lexeme))
|
||||||
|
a.value.accept(as)
|
||||||
|
as.str.WriteString(")")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (as *AstStringer) visitPrintStmt(p *PrintStmt) {
|
||||||
|
as.str.WriteString("(print ")
|
||||||
|
p.val.accept(as)
|
||||||
|
as.str.WriteString(")")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (as *AstStringer) visitExprStmt(se *ExprStmt) {
|
||||||
|
se.expr.accept(as)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (as *AstStringer) visitVarStmt(vs *VarStmt) {
|
||||||
|
if vs.initializer != nil {
|
||||||
|
as.str.WriteString(fmt.Sprintf("(var %v ", vs.name.literal))
|
||||||
|
vs.initializer.accept(as)
|
||||||
|
as.str.WriteString(")")
|
||||||
|
} else {
|
||||||
|
as.str.WriteString(fmt.Sprintf("(var %v)", vs.name.literal))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
27
error.go
27
error.go
|
@ -1,27 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
var hadError = false
|
|
||||||
var hadRuntimeError = false
|
|
||||||
|
|
||||||
func printError(token Token, message string) {
|
|
||||||
if token.typ == EOF {
|
|
||||||
report(token.line, " at and", message)
|
|
||||||
} else {
|
|
||||||
report(token.line, fmt.Sprintf(" at '%s'", token.lexeme), message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func report(line int, where string, message string) {
|
|
||||||
log.Printf("[%d] Error %s: %s", line, where, message)
|
|
||||||
hadError = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func reportRuntimeError(token Token, message string) {
|
|
||||||
log.Printf("[%d] Error: %s", token.line, message)
|
|
||||||
hadRuntimeError = true
|
|
||||||
}
|
|
74
expr.go
Normal file
74
expr.go
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
type ExprVisitor interface {
|
||||||
|
visitUnary(u *Unary) any
|
||||||
|
visitBinary(b *Binary) any
|
||||||
|
visitLiteral(l *Literal) any
|
||||||
|
visitGrouping(g *Grouping) any
|
||||||
|
visitVariable(v *Variable) any
|
||||||
|
visitAssignment(a *Assign) any
|
||||||
|
}
|
||||||
|
|
||||||
|
type Expr interface {
|
||||||
|
expr()
|
||||||
|
accept(v ExprVisitor) any
|
||||||
|
}
|
||||||
|
|
||||||
|
type Unary struct {
|
||||||
|
op Token
|
||||||
|
right Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
type Binary struct {
|
||||||
|
left Expr
|
||||||
|
op Token
|
||||||
|
right Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
type Literal struct {
|
||||||
|
value any
|
||||||
|
}
|
||||||
|
|
||||||
|
type Grouping struct {
|
||||||
|
expression Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
type Variable struct {
|
||||||
|
name Token
|
||||||
|
}
|
||||||
|
|
||||||
|
type Assign struct {
|
||||||
|
variable Token
|
||||||
|
value Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Unary) expr() {}
|
||||||
|
func (b *Binary) expr() {}
|
||||||
|
func (l *Literal) expr() {}
|
||||||
|
func (g *Grouping) expr() {}
|
||||||
|
func (v *Variable) expr() {}
|
||||||
|
func (a *Assign) expr() {}
|
||||||
|
|
||||||
|
func (u *Unary) accept(v ExprVisitor) any {
|
||||||
|
return v.visitUnary(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Binary) accept(v ExprVisitor) any {
|
||||||
|
return v.visitBinary(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Literal) accept(v ExprVisitor) any {
|
||||||
|
return v.visitLiteral(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Grouping) accept(v ExprVisitor) any {
|
||||||
|
return v.visitGrouping(g)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (va *Variable) accept(v ExprVisitor) any {
|
||||||
|
return v.visitVariable(va)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Assign) accept(v ExprVisitor) any {
|
||||||
|
return v.visitAssignment(a)
|
||||||
|
}
|
56
expr_rpn.go
Normal file
56
expr_rpn.go
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExprToRPN struct {
|
||||||
|
str strings.Builder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (as ExprToRPN) String(expr Expr) string {
|
||||||
|
|
||||||
|
if expr == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
expr.accept(&as)
|
||||||
|
return as.str.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (as *ExprToRPN) visitBinary(b *Binary) any {
|
||||||
|
b.left.accept(as)
|
||||||
|
as.str.WriteString(" ")
|
||||||
|
b.right.accept(as)
|
||||||
|
as.str.WriteString(" ")
|
||||||
|
as.str.WriteString(b.op.lexeme)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (as *ExprToRPN) visitLiteral(l *Literal) any {
|
||||||
|
as.str.WriteString(fmt.Sprintf("%v", l.value))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (as *ExprToRPN) visitGrouping(g *Grouping) any {
|
||||||
|
g.expression.accept(as)
|
||||||
|
as.str.WriteString(" group")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (as *ExprToRPN) visitUnary(u *Unary) any {
|
||||||
|
u.right.accept(as)
|
||||||
|
as.str.WriteString(fmt.Sprintf(" %s", u.op.lexeme))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (as *ExprToRPN) visitVariable(va *Variable) any {
|
||||||
|
as.str.WriteString(va.name.lexeme)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (as *ExprToRPN) visitAssignment(a *Assign) any {
|
||||||
|
as.str.WriteString(fmt.Sprintf("%v %s =", a.value, a.variable.lexeme))
|
||||||
|
return nil
|
||||||
|
}
|
BIN
glox
Executable file
BIN
glox
Executable file
Binary file not shown.
90
glox.go
90
glox.go
|
@ -2,83 +2,73 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Glox struct {
|
||||||
|
Interpreter *Interpreter
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
glox := &Glox{newInterpreter()}
|
||||||
switch len(os.Args) {
|
switch len(os.Args) {
|
||||||
case 1:
|
case 1:
|
||||||
runPrompt()
|
glox.runPrompt()
|
||||||
case 2:
|
case 2:
|
||||||
runFile(os.Args[1])
|
glox.runFile(os.Args[1])
|
||||||
default:
|
default:
|
||||||
println("Usage: glox [file]")
|
println("Usage: glox [file]")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPrompt() {
|
func (gl *Glox) runPrompt() {
|
||||||
scanner := bufio.NewScanner(os.Stdin)
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
scanner.Split(bufio.ScanLines)
|
scanner.Split(bufio.ScanLines)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
print("> ")
|
print("> ")
|
||||||
scanner.Scan()
|
if !scanner.Scan() {
|
||||||
line := scanner.Text()
|
|
||||||
if len(line) == 0 {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
run([]byte(scanner.Text()))
|
gl.run(scanner.Bytes(), true)
|
||||||
hadError = false
|
|
||||||
hadRuntimeError = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runFile(path string) {
|
func (gl *Glox) runFile(path string) {
|
||||||
file, err := os.ReadFile(path)
|
file, err := os.ReadFile(path)
|
||||||
|
|
||||||
try(err)
|
|
||||||
|
|
||||||
run(file)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case hadError:
|
|
||||||
os.Exit(65)
|
|
||||||
case hadRuntimeError:
|
|
||||||
os.Exit(70)
|
|
||||||
default:
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func run(source []byte) {
|
|
||||||
tokens := newScanner(source).scan()
|
|
||||||
|
|
||||||
if hadError {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ast := newParser(tokens).parse()
|
|
||||||
|
|
||||||
if hadError {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
println(AstStringer{}.String(ast))
|
|
||||||
|
|
||||||
res := newInterpreter().evaluate(ast)
|
|
||||||
|
|
||||||
if hadRuntimeError {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("%v\n", res)
|
|
||||||
}
|
|
||||||
|
|
||||||
func try(err error) {
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
runErrors := gl.run(file, false)
|
||||||
|
|
||||||
|
if len(runErrors) != 0 {
|
||||||
|
for _, e := range runErrors {
|
||||||
|
log.Print(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gl *Glox) run(source []byte, interactive bool) []error {
|
||||||
|
tokens, err := newScanner(source).scan()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return []error{err}
|
||||||
|
}
|
||||||
|
|
||||||
|
stmts, parseErrs := newParser(tokens).parse()
|
||||||
|
|
||||||
|
if len(parseErrs) != 0 && !interactive {
|
||||||
|
return parseErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
println(AstStringer{}.String(stmts))
|
||||||
|
|
||||||
|
return gl.Interpreter.interpret(stmts)
|
||||||
}
|
}
|
||||||
|
|
105
interpreter.go
105
interpreter.go
|
@ -2,67 +2,79 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"reflect"
|
"reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Interpreter struct{}
|
type Interpreter struct {
|
||||||
|
env map[string]any
|
||||||
|
errors []error
|
||||||
|
}
|
||||||
|
|
||||||
type RuntimeError struct {
|
type RuntimeError struct {
|
||||||
token Token
|
token Token
|
||||||
msg string
|
msg string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (re RuntimeError) Error() string {
|
func (re *RuntimeError) Error() string {
|
||||||
return re.msg
|
return fmt.Sprintf("RuntimeError [%d][%s] Error: %s", re.token.line, re.token.typ, re.msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newInterpreter() *Interpreter {
|
func newInterpreter() *Interpreter {
|
||||||
return &Interpreter{}
|
return &Interpreter{env: make(map[string]any)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Interpreter) evaluate(e Expr) any {
|
func (i *Interpreter) interpret(stmts []Stmt) []error {
|
||||||
defer i.recover()
|
defer i.recover()
|
||||||
return e.accept(i)
|
|
||||||
|
i.errors = []error{}
|
||||||
|
|
||||||
|
for _, stmt := range stmts {
|
||||||
|
stmt.accept(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
return i.errors
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Interpreter) recover() {
|
func (i *Interpreter) recover() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
pe, ok := err.(RuntimeError)
|
_, ok := err.(*RuntimeError)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
reportRuntimeError(pe.token, pe.msg)
|
|
||||||
hadRuntimeError = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Interpreter) evaluate(e Expr) any {
|
||||||
|
return e.accept(i)
|
||||||
|
}
|
||||||
|
|
||||||
func (i *Interpreter) visitBinary(b *Binary) any {
|
func (i *Interpreter) visitBinary(b *Binary) any {
|
||||||
left := i.evaluate(b.left)
|
left := i.evaluate(b.left)
|
||||||
right := i.evaluate(b.right)
|
right := i.evaluate(b.right)
|
||||||
|
|
||||||
switch b.op.typ {
|
switch b.op.typ {
|
||||||
case MINUS:
|
case MINUS:
|
||||||
checkIfFloats(b.op, left, right)
|
i.checkIfFloats(b.op, left, right)
|
||||||
return left.(float64) - right.(float64)
|
return left.(float64) - right.(float64)
|
||||||
case SLASH:
|
case SLASH:
|
||||||
checkIfFloats(b.op, left, right)
|
i.checkIfFloats(b.op, left, right)
|
||||||
return left.(float64) / right.(float64)
|
return left.(float64) / right.(float64)
|
||||||
case STAR:
|
case STAR:
|
||||||
checkIfFloats(b.op, left, right)
|
i.checkIfFloats(b.op, left, right)
|
||||||
return left.(float64) * right.(float64)
|
return left.(float64) * right.(float64)
|
||||||
case GREATER:
|
case GREATER:
|
||||||
checkIfFloats(b.op, left, right)
|
i.checkIfFloats(b.op, left, right)
|
||||||
return left.(float64) > right.(float64)
|
return left.(float64) > right.(float64)
|
||||||
case LESS:
|
case LESS:
|
||||||
checkIfFloats(b.op, left, right)
|
i.checkIfFloats(b.op, left, right)
|
||||||
return left.(float64) < right.(float64)
|
return left.(float64) < right.(float64)
|
||||||
case GREATER_EQUAL:
|
case GREATER_EQUAL:
|
||||||
checkIfFloats(b.op, left, right)
|
i.checkIfFloats(b.op, left, right)
|
||||||
return left.(float64) >= right.(float64)
|
return left.(float64) >= right.(float64)
|
||||||
case LESS_EQUAL:
|
case LESS_EQUAL:
|
||||||
checkIfFloats(b.op, left, right)
|
i.checkIfFloats(b.op, left, right)
|
||||||
return left.(float64) <= right.(float64)
|
return left.(float64) <= right.(float64)
|
||||||
case BANG_EQUAL:
|
case BANG_EQUAL:
|
||||||
return !reflect.DeepEqual(left, right)
|
return !reflect.DeepEqual(left, right)
|
||||||
|
@ -78,7 +90,7 @@ func (i *Interpreter) visitBinary(b *Binary) any {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
panic(RuntimeError{b.op, fmt.Sprintf("Operands must be numbers or strings: %v %s %v", left, b.op.lexeme, right)})
|
i.panic(&RuntimeError{b.op, fmt.Sprintf("Operands must be numbers or strings: %v %s %v", left, b.op.lexeme, right)})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -96,7 +108,7 @@ func (i *Interpreter) visitUnary(u *Unary) any {
|
||||||
|
|
||||||
switch u.op.typ {
|
switch u.op.typ {
|
||||||
case MINUS:
|
case MINUS:
|
||||||
checkIfFloat(u.op, val)
|
i.checkIfFloat(u.op, val)
|
||||||
return -val.(float64)
|
return -val.(float64)
|
||||||
case BANG:
|
case BANG:
|
||||||
return !isTruthy(val)
|
return !isTruthy(val)
|
||||||
|
@ -105,20 +117,67 @@ func (i *Interpreter) visitUnary(u *Unary) any {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkIfFloat(op Token, val any) {
|
func (i *Interpreter) visitVariable(v *Variable) any {
|
||||||
|
if found, ok := i.env[v.name.lexeme]; ok {
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
i.panic(&RuntimeError{v.name, fmt.Sprintf("Undefined variable '%s'.", v.name.lexeme)})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Interpreter) visitAssignment(a *Assign) any {
|
||||||
|
if _, ok := i.env[a.variable.lexeme]; ok {
|
||||||
|
val := i.evaluate(a.value)
|
||||||
|
i.env[a.variable.lexeme] = val
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
i.panic(&RuntimeError{a.variable, fmt.Sprintf("Undefined variable '%s'.", a.variable.lexeme)})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Interpreter) visitPrintStmt(p *PrintStmt) {
|
||||||
|
fmt.Printf("%v\n", i.evaluate(p.val))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Interpreter) visitExprStmt(se *ExprStmt) {
|
||||||
|
i.evaluate(se.expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Interpreter) visitVarStmt(v *VarStmt) {
|
||||||
|
|
||||||
|
var val any = nil
|
||||||
|
|
||||||
|
if v.initializer != nil {
|
||||||
|
val = i.evaluate(v.initializer)
|
||||||
|
}
|
||||||
|
|
||||||
|
i.env[v.name.lexeme] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Interpreter) panic(re *RuntimeError) {
|
||||||
|
i.errors = append(i.errors, re)
|
||||||
|
log.Println(re)
|
||||||
|
panic(re)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Interpreter) checkIfFloat(op Token, val any) {
|
||||||
if _, ok := val.(float64); ok {
|
if _, ok := val.(float64); ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
panic(RuntimeError{op, "value must ne a number."})
|
i.panic(&RuntimeError{op, "value must be a number."})
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkIfFloats(op Token, a any, b any) {
|
func (i *Interpreter) checkIfFloats(op Token, a any, b any) {
|
||||||
if isFloats(a, b) {
|
if isFloats(a, b) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
panic(RuntimeError{op, fmt.Sprintf("Operands must be numbers: %v %s %v", a, op.lexeme, b)})
|
i.panic(&RuntimeError{op, fmt.Sprintf("Operands must be numbers: %v %s %v", a, op.lexeme, b)})
|
||||||
}
|
}
|
||||||
|
|
||||||
func isFloats(a any, b any) bool {
|
func isFloats(a any, b any) bool {
|
||||||
|
|
134
parser.go
134
parser.go
|
@ -2,11 +2,13 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Parser struct {
|
type Parser struct {
|
||||||
tokens []Token
|
tokens []Token
|
||||||
current int
|
current int
|
||||||
|
errors []error
|
||||||
}
|
}
|
||||||
|
|
||||||
type ParseError struct {
|
type ParseError struct {
|
||||||
|
@ -14,8 +16,8 @@ type ParseError struct {
|
||||||
message string
|
message string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pe ParseError) Error() string {
|
func (pe *ParseError) Error() string {
|
||||||
return fmt.Sprintf("%s: %s", pe.token.lexeme, pe.message)
|
return fmt.Sprintf("ParseError [%d][%s]: %s", pe.token.line, pe.token.typ, pe.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newParser(tokens []Token) *Parser {
|
func newParser(tokens []Token) *Parser {
|
||||||
|
@ -25,21 +27,88 @@ func newParser(tokens []Token) *Parser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) parse() Expr {
|
// program -> declaration* EOF
|
||||||
|
func (p *Parser) parse() ([]Stmt, []error) {
|
||||||
defer p.recover()
|
defer p.recover()
|
||||||
return p.expression()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Parser) recover() {
|
stmts := []Stmt{}
|
||||||
if err := recover(); err != nil {
|
|
||||||
pe := err.(ParseError)
|
for !p.isAtEnd() {
|
||||||
printError(pe.token, pe.message)
|
|
||||||
|
if stmt := p.declaration(); stmt != nil {
|
||||||
|
stmts = append(stmts, stmt)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stmts, p.errors
|
||||||
}
|
}
|
||||||
|
|
||||||
// expression -> equality
|
// declaration -> varDecl | statement
|
||||||
|
func (p *Parser) declaration() Stmt {
|
||||||
|
defer p.synchronize()
|
||||||
|
if p.match(VAR) {
|
||||||
|
return p.varDecl()
|
||||||
|
}
|
||||||
|
return p.statement()
|
||||||
|
}
|
||||||
|
|
||||||
|
// varDecl -> "var" IDENTIFIER ("=" expression)? ";"
|
||||||
|
func (p *Parser) varDecl() Stmt {
|
||||||
|
name := p.consume(IDENTIFIER, "expect identifier for variable")
|
||||||
|
|
||||||
|
var initializer Expr = nil
|
||||||
|
if p.match(EQUAL) {
|
||||||
|
initializer = p.expression()
|
||||||
|
}
|
||||||
|
|
||||||
|
p.consume(SEMICOLON, "Expect ';' after expression.")
|
||||||
|
|
||||||
|
return &VarStmt{name, initializer}
|
||||||
|
}
|
||||||
|
|
||||||
|
// statement -> exprStmt | printStmt
|
||||||
|
func (p *Parser) statement() Stmt {
|
||||||
|
if p.match(PRINT) {
|
||||||
|
return p.printStmt()
|
||||||
|
}
|
||||||
|
return p.exprStmt()
|
||||||
|
}
|
||||||
|
|
||||||
|
// exprStmt -> expression ";"
|
||||||
|
func (p *Parser) exprStmt() Stmt {
|
||||||
|
expr := p.expression()
|
||||||
|
p.consume(SEMICOLON, "Expect ';' after expression.")
|
||||||
|
return &ExprStmt{expr}
|
||||||
|
}
|
||||||
|
|
||||||
|
// printStmt -> "print" expression ";"
|
||||||
|
func (p *Parser) printStmt() Stmt {
|
||||||
|
expr := p.expression()
|
||||||
|
p.consume(SEMICOLON, "Expect ';' after expression.")
|
||||||
|
return &PrintStmt{expr}
|
||||||
|
}
|
||||||
|
|
||||||
|
// expression -> assignment
|
||||||
func (p *Parser) expression() Expr {
|
func (p *Parser) expression() Expr {
|
||||||
return p.equality()
|
return p.assignment()
|
||||||
|
}
|
||||||
|
|
||||||
|
// assignment -> IDENTIFIER "=" assignment | equality
|
||||||
|
func (p *Parser) assignment() Expr {
|
||||||
|
expr := p.equality()
|
||||||
|
|
||||||
|
if p.match(EQUAL) {
|
||||||
|
eq := p.previous()
|
||||||
|
val := p.assignment()
|
||||||
|
|
||||||
|
if variable, ok := expr.(*Variable); ok {
|
||||||
|
return &Assign{variable.name, val}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.panic(&ParseError{eq, "Invalid assignment target."})
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr
|
||||||
}
|
}
|
||||||
|
|
||||||
// equality -> comparison ( ( "==" | "!=" ) comparison )*
|
// equality -> comparison ( ( "==" | "!=" ) comparison )*
|
||||||
|
@ -105,7 +174,7 @@ func (p *Parser) unary() Expr {
|
||||||
return p.primary()
|
return p.primary()
|
||||||
}
|
}
|
||||||
|
|
||||||
// primary -> NUMBER | STRING | "true" | "false" | "nil" | "(" expression ")"
|
// primary -> NUMBER | STRING | "true" | "false" | "nil" | "(" expression ")" | IDENTIFIER
|
||||||
func (p *Parser) primary() Expr {
|
func (p *Parser) primary() Expr {
|
||||||
switch {
|
switch {
|
||||||
case p.match(FALSE):
|
case p.match(FALSE):
|
||||||
|
@ -120,13 +189,17 @@ func (p *Parser) primary() Expr {
|
||||||
return &Literal{p.previous().literal}
|
return &Literal{p.previous().literal}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.match(IDENTIFIER) {
|
||||||
|
return &Variable{p.previous()}
|
||||||
|
}
|
||||||
|
|
||||||
if p.match(LEFT_PAREN) {
|
if p.match(LEFT_PAREN) {
|
||||||
expr := p.expression()
|
expr := p.expression()
|
||||||
p.consume(RIGHT_PAREN, "Expect ')' after expression")
|
p.consume(RIGHT_PAREN, "Expect ')' after expression")
|
||||||
return &Grouping{expr}
|
return &Grouping{expr}
|
||||||
}
|
}
|
||||||
|
|
||||||
panic(ParseError{p.peek(), "Expect expression"})
|
p.panic(&ParseError{p.peek(), "Expect expression"})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -176,12 +249,20 @@ func (p *Parser) consume(typ TokenType, mes string) Token {
|
||||||
return p.advance()
|
return p.advance()
|
||||||
}
|
}
|
||||||
|
|
||||||
panic(ParseError{p.peek(), mes})
|
p.panic(&ParseError{p.peek(), mes})
|
||||||
|
|
||||||
return Token{}
|
return Token{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) synchronize() {
|
func (p *Parser) synchronize() {
|
||||||
|
err := recover()
|
||||||
|
|
||||||
|
pe := p.isParseError(err)
|
||||||
|
|
||||||
|
if pe == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
p.advance()
|
p.advance()
|
||||||
|
|
||||||
for !p.isAtEnd() {
|
for !p.isAtEnd() {
|
||||||
|
@ -196,4 +277,29 @@ func (p *Parser) synchronize() {
|
||||||
|
|
||||||
p.advance()
|
p.advance()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) recover() {
|
||||||
|
p.isParseError(recover())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) panic(pe *ParseError) {
|
||||||
|
p.errors = append(p.errors, pe)
|
||||||
|
log.Println(pe)
|
||||||
|
panic(pe)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) isParseError(err any) *ParseError {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pe, ok := err.(*ParseError)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pe
|
||||||
}
|
}
|
||||||
|
|
34
scanner.go
34
scanner.go
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
@ -91,19 +92,29 @@ func (t *Token) String() string {
|
||||||
return fmt.Sprintf("%s - %s - %v", t.typ, t.lexeme, t.literal)
|
return fmt.Sprintf("%s - %s - %v", t.typ, t.lexeme, t.literal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ScanError struct {
|
||||||
|
line int
|
||||||
|
message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (se *ScanError) Error() string {
|
||||||
|
return fmt.Sprintf("ScanError [%d] Error: %s", se.line, se.message)
|
||||||
|
}
|
||||||
|
|
||||||
type Scanner struct {
|
type Scanner struct {
|
||||||
source []byte
|
source []byte
|
||||||
tokens []Token
|
tokens []Token
|
||||||
start int
|
start int
|
||||||
current int
|
current int
|
||||||
line int
|
line int
|
||||||
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func newScanner(source []byte) *Scanner {
|
func newScanner(source []byte) *Scanner {
|
||||||
return &Scanner{source: source, start: 0, current: 0, line: 1}
|
return &Scanner{source: source, start: 0, current: 0, line: 1, err: nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Scanner) scan() []Token {
|
func (s *Scanner) scan() ([]Token, error) {
|
||||||
|
|
||||||
for !s.isAtEnd() {
|
for !s.isAtEnd() {
|
||||||
s.start = s.current
|
s.start = s.current
|
||||||
|
@ -112,7 +123,7 @@ func (s *Scanner) scan() []Token {
|
||||||
|
|
||||||
s.tokens = append(s.tokens, Token{EOF, "EOF", struct{}{}, s.line})
|
s.tokens = append(s.tokens, Token{EOF, "EOF", struct{}{}, s.line})
|
||||||
|
|
||||||
return s.tokens
|
return s.tokens, s.err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Scanner) scanToken() {
|
func (s *Scanner) scanToken() {
|
||||||
|
@ -191,7 +202,7 @@ func (s *Scanner) scanToken() {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
report(s.line, "", fmt.Sprintf("Unexpected character %s", string(c)))
|
s.error(&ScanError{s.line, fmt.Sprintf("Unexpected character %s", string(c))})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,12 +216,12 @@ func (s *Scanner) identifier() {
|
||||||
s.advance()
|
s.advance()
|
||||||
}
|
}
|
||||||
|
|
||||||
str := s.source[s.start:s.current]
|
str := string(s.source[s.start:s.current])
|
||||||
|
|
||||||
if id, found := keywords[string(str)]; found {
|
if id, found := keywords[string(str)]; found {
|
||||||
s.addToken(id, struct{}{})
|
s.addToken(id, str)
|
||||||
} else {
|
} else {
|
||||||
s.addToken(IDENTIFIER, struct{}{})
|
s.addToken(IDENTIFIER, str)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -224,7 +235,7 @@ func (s *Scanner) string() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.isAtEnd() {
|
if s.isAtEnd() {
|
||||||
report(s.line, "", "Unterminated string")
|
s.error(&ScanError{s.line, "Unterminated string"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,7 +260,7 @@ func (s *Scanner) number() {
|
||||||
num, err := strconv.ParseFloat(string(s.source[s.start:s.current]), 64)
|
num, err := strconv.ParseFloat(string(s.source[s.start:s.current]), 64)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
report(s.line, "", err.Error())
|
s.error(&ScanError{s.line, err.Error()})
|
||||||
}
|
}
|
||||||
|
|
||||||
s.addToken(NUMBER, num)
|
s.addToken(NUMBER, num)
|
||||||
|
@ -298,3 +309,8 @@ func (s *Scanner) match(ch rune) bool {
|
||||||
func (s *Scanner) isAtEnd() bool {
|
func (s *Scanner) isAtEnd() bool {
|
||||||
return s.current >= len(s.source)
|
return s.current >= len(s.source)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) error(err *ScanError) {
|
||||||
|
log.Print(err)
|
||||||
|
s.err = err
|
||||||
|
}
|
||||||
|
|
41
stmt.go
Normal file
41
stmt.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
type StmtVisitor interface {
|
||||||
|
visitPrintStmt(p *PrintStmt)
|
||||||
|
visitExprStmt(es *ExprStmt)
|
||||||
|
visitVarStmt(v *VarStmt)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Stmt interface {
|
||||||
|
stmt()
|
||||||
|
accept(v StmtVisitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
type PrintStmt struct {
|
||||||
|
val Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExprStmt struct {
|
||||||
|
expr Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
type VarStmt struct {
|
||||||
|
name Token
|
||||||
|
initializer Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PrintStmt) stmt() {}
|
||||||
|
func (es *ExprStmt) stmt() {}
|
||||||
|
func (vs *VarStmt) stmt() {}
|
||||||
|
|
||||||
|
func (p *PrintStmt) accept(v StmtVisitor) {
|
||||||
|
v.visitPrintStmt(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (se *ExprStmt) accept(v StmtVisitor) {
|
||||||
|
v.visitExprStmt(se)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vs *VarStmt) accept(v StmtVisitor) {
|
||||||
|
v.visitVarStmt(vs)
|
||||||
|
}
|
Loading…
Reference in a new issue