package ast
import (
"github.com/hutcho66/glox/src/pkg/token"
)
type Expression interface {
Accept(ExpressionVisitor) any
}
type ExpressionVisitor interface {
VisitBinaryExpression(*BinaryExpression) any
VisitTernaryExpression(*TernaryExpression) any
VisitLogicalExpression(*LogicalExpression) any
VisitGroupedExpression(*GroupingExpression) any
VisitUnaryExpression(*UnaryExpression) any
VisitLiteralExpression(*LiteralExpression) any
VisitVariableExpression(*VariableExpression) any
VisitAssignmentExpression(*AssignmentExpression) any
VisitCallExpression(*CallExpression) any
VisitLambdaExpression(*LambdaExpression) any
VisitSequenceExpression(*SequenceExpression) any
VisitArrayExpression(*ArrayExpression) any
VisitMapExpression(*MapExpression) any
VisitIndexExpression(*IndexExpression) any
VisitIndexedAssignmentExpression(*IndexedAssignmentExpression) any
VisitGetExpression(*GetExpression) any
VisitSetExpression(*SetExpression) any
VisitThisExpression(*ThisExpression) any
VisitSuperGetExpression(*SuperGetExpression) any
VisitSuperSetExpression(*SuperSetExpression) any
}
type BinaryExpression struct {
Left, Right Expression
Operator *token.Token
}
func (b *BinaryExpression) Accept(v ExpressionVisitor) any {
return v.VisitBinaryExpression(b)
}
type TernaryExpression struct {
Condition, Consequence, Alternative Expression
Operator *token.Token
}
func (e *TernaryExpression) Accept(v ExpressionVisitor) any {
return v.VisitTernaryExpression(e)
}
type LogicalExpression struct {
Left, Right Expression
Operator *token.Token
}
func (b *LogicalExpression) Accept(v ExpressionVisitor) any {
return v.VisitLogicalExpression(b)
}
type UnaryExpression struct {
Expr Expression
Operator *token.Token
}
func (u *UnaryExpression) Accept(v ExpressionVisitor) any {
return v.VisitUnaryExpression(u)
}
type GroupingExpression struct {
Expr Expression
}
func (g *GroupingExpression) Accept(v ExpressionVisitor) any {
return v.VisitGroupedExpression(g)
}
type LiteralExpression struct {
Value any
}
func (l *LiteralExpression) Accept(v ExpressionVisitor) any {
return v.VisitLiteralExpression(l)
}
type VariableExpression struct {
Name *token.Token
}
func (e *VariableExpression) Accept(v ExpressionVisitor) any {
return v.VisitVariableExpression(e)
}
type AssignmentExpression struct {
Name *token.Token
Value Expression
}
func (e *AssignmentExpression) Accept(v ExpressionVisitor) any {
return v.VisitAssignmentExpression(e)
}
type IndexedAssignmentExpression struct {
Left *IndexExpression
Value Expression
}
func (e *IndexedAssignmentExpression) Accept(v ExpressionVisitor) any {
return v.VisitIndexedAssignmentExpression(e)
}
type CallExpression struct {
Callee Expression
Arguments []Expression
ClosingParen *token.Token
}
func (e *CallExpression) Accept(v ExpressionVisitor) any {
return v.VisitCallExpression(e)
}
type LambdaExpression struct {
Operator *token.Token
Function *FunctionStatement
}
func (e *LambdaExpression) Accept(v ExpressionVisitor) any {
return v.VisitLambdaExpression(e)
}
type SequenceExpression struct {
Items []Expression
}
func (e *SequenceExpression) Accept(v ExpressionVisitor) any {
return v.VisitSequenceExpression(e)
}
type ArrayExpression struct {
Items []Expression
}
func (e *ArrayExpression) Accept(v ExpressionVisitor) any {
return v.VisitArrayExpression(e)
}
type MapExpression struct {
OpeningBrace *token.Token
Keys []Expression
Values []Expression
}
func (e *MapExpression) Accept(v ExpressionVisitor) any {
return v.VisitMapExpression(e)
}
type IndexExpression struct {
Object Expression
LeftIndex Expression
RightIndex Expression
ClosingBracket *token.Token
}
func (e *IndexExpression) Accept(v ExpressionVisitor) any {
return v.VisitIndexExpression(e)
}
type GetExpression struct {
Object Expression
Name *token.Token
}
func (e *GetExpression) Accept(v ExpressionVisitor) any {
return v.VisitGetExpression(e)
}
type SetExpression struct {
Object, Value Expression
Name *token.Token
}
func (e *SetExpression) Accept(v ExpressionVisitor) any {
return v.VisitSetExpression(e)
}
type ThisExpression struct {
Keyword *token.Token
}
func (e *ThisExpression) Accept(v ExpressionVisitor) any {
return v.VisitThisExpression(e)
}
type SuperGetExpression struct {
Keyword, Method *token.Token
}
func (e *SuperGetExpression) Accept(v ExpressionVisitor) any {
return v.VisitSuperGetExpression(e)
}
type SuperSetExpression struct {
Keyword, Method *token.Token
Value Expression
}
func (e *SuperSetExpression) Accept(v ExpressionVisitor) any {
return v.VisitSuperSetExpression(e)
}
package ast
import (
"github.com/hutcho66/glox/src/pkg/token"
)
type Statement interface {
Accept(StatementVisitor)
}
type StatementVisitor interface {
VisitExpressionStatement(*ExpressionStatement)
VisitVarStatement(*VarStatement)
VisitBlockStatement(*BlockStatement)
VisitIfStatement(*IfStatement)
VisitLoopStatement(*LoopStatement)
VisitForEachStatement(*ForEachStatement)
VisitFunctionStatement(*FunctionStatement)
VisitReturnStatement(*ReturnStatement)
VisitBreakStatement(*BreakStatement)
VisitContinueStatement(*ContinueStatement)
VisitClassStatement(*ClassStatement)
}
type ExpressionStatement struct {
Expr Expression
}
func (s *ExpressionStatement) Accept(v StatementVisitor) {
v.VisitExpressionStatement(s)
}
type VarStatement struct {
Name *token.Token
Initializer Expression
}
func (s *VarStatement) Accept(v StatementVisitor) {
v.VisitVarStatement(s)
}
type BlockStatement struct {
Statements []Statement
}
func (s *BlockStatement) Accept(v StatementVisitor) {
v.VisitBlockStatement(s)
}
type IfStatement struct {
Condition Expression
Consequence, Alternative Statement
}
func (s *IfStatement) Accept(v StatementVisitor) {
v.VisitIfStatement(s)
}
type LoopStatement struct {
Condition Expression
Body Statement
Increment Expression
}
func (s *LoopStatement) Accept(v StatementVisitor) {
v.VisitLoopStatement(s)
}
type ForEachStatement struct {
VariableName *token.Token
Array Expression
Body Statement
}
func (s *ForEachStatement) Accept(v StatementVisitor) {
v.VisitForEachStatement(s)
}
type MethodType int
const (
NOT_METHOD = iota
NORMAL_METHOD
STATIC_METHOD
GETTER_METHOD
SETTER_METHOD
)
type FunctionStatement struct {
Name *token.Token
Params []*token.Token
Body []Statement
Kind MethodType
}
func (s *FunctionStatement) Accept(v StatementVisitor) {
v.VisitFunctionStatement(s)
}
type ReturnStatement struct {
Keyword *token.Token
Value Expression
}
func (s *ReturnStatement) Accept(v StatementVisitor) {
v.VisitReturnStatement(s)
}
type BreakStatement struct {
Keyword *token.Token
}
func (s *BreakStatement) Accept(v StatementVisitor) {
v.VisitBreakStatement(s)
}
type ContinueStatement struct {
Keyword *token.Token
}
func (s *ContinueStatement) Accept(v StatementVisitor) {
v.VisitContinueStatement(s)
}
type ClassStatement struct {
Name *token.Token
Methods []*FunctionStatement
Superclass *VariableExpression
}
func (s *ClassStatement) Accept(v StatementVisitor) {
v.VisitClassStatement(s)
}
package interpreter
import (
"errors"
"github.com/hutcho66/glox/src/pkg/ast"
"github.com/hutcho66/glox/src/pkg/token"
)
type LoxClass struct {
Name string
Methods map[string]*LoxFunction
Super *LoxClass
}
func (c LoxClass) Arity() int {
initializer := c.findMethod("init")
if initializer == nil {
return 0
}
return initializer.Arity()
}
func (c *LoxClass) Call(interpreter *Interpreter, arguments []any) (any, error) {
instance := NewLoxInstance(c)
initializer := c.findMethod("init")
if initializer != nil {
initializer.bind(instance).Call(interpreter, arguments)
}
return instance, nil
}
func (c *LoxClass) findMethod(name string) *LoxFunction {
if method, ok := c.Methods[name]; ok {
return method
}
if c.Super != nil {
return c.Super.findMethod(name)
}
return nil
}
func (c *LoxClass) get(name *token.Token) (any, error) {
method := c.findMethod(name.Lexeme)
if method == nil || method.declaration.Kind != ast.STATIC_METHOD {
return nil, errors.New("Undefined property '" + name.Lexeme + "'.")
}
if method.declaration.Kind != ast.STATIC_METHOD {
return nil, errors.New("Cannot call non-static method '" + name.Lexeme + "' directly on class.")
}
return method.bind(c), nil
}
package interpreter
type ControlType string
const (
RETURN ControlType = "RETURN"
BREAK = "BREAK"
CONTINUE = "CONTINUE"
)
type LoxControl struct {
controlType ControlType
value any
}
func LoxReturn(value any) *LoxControl {
return &LoxControl{controlType: RETURN, value: value}
}
var LoxBreak = &LoxControl{controlType: BREAK}
var LoxContinue = &LoxControl{controlType: CONTINUE}
package interpreter
import (
"github.com/hutcho66/glox/src/pkg/token"
)
type Environment struct {
enclosing *Environment
values map[string]any
}
func NewEnvironment() *Environment {
return &Environment{
enclosing: nil,
values: map[string]any{},
}
}
func NewEnclosingEnvironment(enclosing *Environment) *Environment {
return &Environment{
enclosing: enclosing,
values: map[string]any{},
}
}
func (e *Environment) get(name *token.Token) (any, bool) {
val, ok := e.values[name.Lexeme]
return val, ok
}
func (e *Environment) getAt(distance int, name string) any {
return e.ancestor(distance).values[name]
}
func (e *Environment) ancestor(distance int) *Environment {
environment := e
for i := 0; i < distance; i++ {
environment = environment.enclosing
}
return environment
}
func (e *Environment) define(name string, value any) {
e.values[name] = value
}
func (e *Environment) assign(name *token.Token, value any) {
e.values[name.Lexeme] = value
}
func (e *Environment) assignAt(distance int, name *token.Token, value any) {
e.ancestor(distance).values[name.Lexeme] = value
}
package interpreter
import "github.com/hutcho66/glox/src/pkg/ast"
type LoxFunction struct {
declaration *ast.FunctionStatement
closure *Environment
isInitializer bool
}
func (f *LoxFunction) Call(interpreter *Interpreter, arguments []any) (returnValue any, err error) {
enclosingEnvironment := interpreter.environment
environment := NewEnclosingEnvironment(f.closure)
defer func() {
if val := recover(); val != nil {
rv, ok := val.(*LoxControl)
if !ok || rv.controlType != RETURN {
// repanic
panic(val)
}
if f.isInitializer {
returnValue = f.closure.getAt(0, "this")
} else {
returnValue = rv.value
}
interpreter.environment = enclosingEnvironment
return
}
}()
for i, param := range f.declaration.Params {
environment.define(param.Lexeme, arguments[i])
}
interpreter.executeBlock(f.declaration.Body, environment)
if f.isInitializer {
return f.closure.getAt(0, "this"), nil
}
return nil, nil
}
func (f LoxFunction) Arity() int {
return len(f.declaration.Params)
}
func (f *LoxFunction) bind(instance LoxObject) *LoxFunction {
environment := NewEnclosingEnvironment(f.closure)
environment.define("this", instance)
return &LoxFunction{f.declaration, environment, f.isInitializer}
}
package interpreter
import (
"errors"
"github.com/hutcho66/glox/src/pkg/token"
)
type LoxInstance struct {
Class *LoxClass
Fields map[string]any
}
func NewLoxInstance(class *LoxClass) *LoxInstance {
return &LoxInstance{
Class: class,
Fields: make(map[string]any),
}
}
func (i *LoxInstance) get(name *token.Token) (any, error) {
if field, ok := i.Fields[name.Lexeme]; ok {
return field, nil
}
method := i.Class.findMethod(name.Lexeme)
if method != nil {
return method.bind(i), nil
}
return nil, errors.New("Undefined property '" + name.Lexeme + "'.")
}
func (i *LoxInstance) set(name *token.Token, value any) {
i.Fields[name.Lexeme] = value
}
package interpreter
import (
"errors"
"fmt"
"hash/fnv"
"strconv"
"strings"
"github.com/hutcho66/glox/src/pkg/ast"
"github.com/hutcho66/glox/src/pkg/lox_error"
"github.com/hutcho66/glox/src/pkg/token"
)
type Interpreter struct {
errors *lox_error.LoxErrors
globals *Environment
environment *Environment
locals map[ast.Expression]int
}
func NewInterpreter(errors *lox_error.LoxErrors) *Interpreter {
globals := NewEnvironment()
// add native functions
for _, fn := range Natives {
globals.define(fn.Name(), fn)
}
return &Interpreter{
errors: errors,
globals: globals,
environment: globals,
locals: make(map[ast.Expression]int),
}
}
func (i *Interpreter) Interpret(statements []ast.Statement) (value any, ok bool) {
defer func() {
// catch any errors
if err := recover(); err != nil {
ok = false
return
}
}()
for idx, s := range statements {
if len(statements) >= 1 && idx == len(statements)-1 {
if es, ok := s.(*ast.ExpressionStatement); ok {
// if the last statement is an expression statement, return its value
return i.executeFinalExpressionStatement(es)
} else {
// execute as normal if not expression statement
i.execute(s)
}
} else {
i.execute(s)
}
}
// last statement is not expression statement, so has no return value
return nil, false
}
func (i *Interpreter) Resolve(expression ast.Expression, depth int) {
i.locals[expression] = depth
}
func (i *Interpreter) execute(s ast.Statement) (ok bool) {
s.Accept(i)
return true
}
func (i *Interpreter) executeFinalExpressionStatement(s *ast.ExpressionStatement) (result any, ok bool) {
// instead of using ExpressionStatement visitor which returns nil,
// visit the Expression itself
return s.Expr.Accept(i), true
}
func (i *Interpreter) executeBlock(s []ast.Statement, environment *Environment) {
previous := i.environment
i.environment = environment
for _, statement := range s {
if ok := i.execute(statement); !ok {
// if statement didn't parse, end execution here
i.environment = previous
return
}
}
i.environment = previous
}
func (i *Interpreter) VisitBlockStatement(s *ast.BlockStatement) {
i.executeBlock(s.Statements, NewEnclosingEnvironment(i.environment))
}
func (i *Interpreter) VisitExpressionStatement(s *ast.ExpressionStatement) {
i.evaluate(s.Expr)
}
func (i *Interpreter) VisitIfStatement(s *ast.IfStatement) {
conditionResult := i.evaluate(s.Condition)
if isTruthy(conditionResult) {
i.execute(s.Consequence)
} else if s.Alternative != nil {
i.execute(s.Alternative)
}
}
func (i *Interpreter) VisitLoopStatement(s *ast.LoopStatement) {
environment := i.environment
// catch break statement
defer func() {
if val := recover(); val != nil {
if val != LoxBreak {
// repanic - not a break statement
panic(val)
}
// this is necessary because break is usually called inside a block
// and this panic will stop that block exiting properly
i.environment = environment
}
}()
for isTruthy(i.evaluate(s.Condition)) {
// this needs to be pushed to a function so that
// panic-defer works with continue statements
i.executeLoopBody(s.Body, s.Increment)
}
}
func (i *Interpreter) VisitForEachStatement(s *ast.ForEachStatement) {
outerEnvironment := i.environment
// catch break statement
defer func() {
if val := recover(); val != nil {
if val != LoxBreak {
// repanic - not a break statement
panic(val)
}
// this is necessary because break is usually called inside a block
// and this panic will stop that block exiting properly
i.environment = outerEnvironment
}
}()
// retrieve the array, it must exists in the outer scope
a := i.evaluate(s.Array)
array, ok := a.(LoxArray)
if !ok {
panic(i.errors.RuntimeError(s.VariableName, "for-of loops are only valid on arrays"))
}
if len(array) == 0 {
return
}
// start a new scope and create the loop variable, initialized to first element of array
i.environment = NewEnclosingEnvironment(i.environment)
loop_position := 0
i.environment.define(s.VariableName.Lexeme, array[loop_position])
// loop through array
for {
// execute the loop
i.executeLoopBody(s.Body, nil)
// reassign loop variable to next element of array
loop_position += 1
if loop_position < len(array) {
i.environment.assign(s.VariableName, array[loop_position])
} else {
// exit loop, all done
break
}
}
// restore environment
i.environment = outerEnvironment
}
func (i *Interpreter) executeLoopBody(body ast.Statement, increment ast.Expression) {
environment := i.environment
// catch any continue statement - this will only end current loop iteration
defer func() {
if val := recover(); val != nil {
if val != LoxContinue {
// repanic - not a continue statement
panic(val)
}
// this is necessary because break is usually called inside a block
// and this panic will stop that block exiting properly
i.environment = environment
// ensure increment is run after continue
if increment != nil {
i.evaluate(increment)
}
}
}()
i.execute(body)
if increment != nil {
i.evaluate(increment)
}
}
func (i *Interpreter) VisitVarStatement(s *ast.VarStatement) {
var value any = nil
if s.Initializer != nil {
value = i.evaluate(s.Initializer)
}
i.environment.define(s.Name.Lexeme, value)
}
func (i *Interpreter) VisitFunctionStatement(s *ast.FunctionStatement) {
function := &LoxFunction{declaration: s, closure: i.environment}
i.environment.define(s.Name.Lexeme, function)
}
func (i *Interpreter) VisitClassStatement(s *ast.ClassStatement) {
var superclass *LoxClass = nil
if s.Superclass != nil {
value := i.evaluate(s.Superclass)
var ok bool
superclass, ok = value.(*LoxClass)
if !ok {
panic(i.errors.RuntimeError(s.Superclass.Name, "Superclass must be a class."))
}
}
i.environment.define(s.Name.Lexeme, nil)
if superclass != nil {
i.environment = NewEnclosingEnvironment(i.environment)
i.environment.define("super", superclass)
}
methods := map[string]*LoxFunction{}
for _, method := range s.Methods {
function := &LoxFunction{method, i.environment, method.Name.Lexeme == "init"}
methods[method.Name.Lexeme] = function
}
class := &LoxClass{Name: s.Name.Lexeme, Methods: methods, Super: superclass}
if superclass != nil {
i.environment = i.environment.enclosing
}
i.environment.assign(s.Name, class)
}
func (i *Interpreter) VisitReturnStatement(s *ast.ReturnStatement) {
var value any = nil
if s.Value != nil {
value = i.evaluate(s.Value)
}
// Using panic to wind back call stack
panic(LoxReturn(value))
}
func (i *Interpreter) VisitBreakStatement(s *ast.BreakStatement) {
// Using panic to wind back call stack
panic(LoxBreak)
}
func (i *Interpreter) VisitContinueStatement(s *ast.ContinueStatement) {
// Using panic to wind back call stack
panic(LoxContinue)
}
func (i *Interpreter) evaluate(e ast.Expression) any {
return e.Accept(i)
}
func (i *Interpreter) VisitTernaryExpression(e *ast.TernaryExpression) any {
condition := i.evaluate(e.Condition)
if isTruthy(condition) {
return i.evaluate(e.Consequence)
} else {
return i.evaluate(e.Alternative)
}
}
func (i *Interpreter) VisitAssignmentExpression(e *ast.AssignmentExpression) any {
value := i.evaluate(e.Value)
distance, ok := i.locals[e]
if ok {
i.environment.assignAt(distance, e.Name, value)
} else {
i.globals.assign(e.Name, value)
}
return value
}
func (i *Interpreter) VisitVariableExpression(e *ast.VariableExpression) any {
return i.lookupVariable(e.Name, e)
}
func (*Interpreter) VisitLiteralExpression(le *ast.LiteralExpression) any {
return le.Value
}
func (i *Interpreter) VisitGroupedExpression(ge *ast.GroupingExpression) any {
return i.evaluate(ge.Expr)
}
func (i *Interpreter) VisitSequenceExpression(e *ast.SequenceExpression) any {
// evaluate all items but only return final one
var result any
for _, item := range e.Items {
result = i.evaluate(item)
}
return result
}
func (i *Interpreter) VisitArrayExpression(e *ast.ArrayExpression) any {
// represent arrays by slices of any
array := make(LoxArray, len(e.Items))
for idx, item := range e.Items {
array[idx] = i.evaluate(item)
}
return array
}
func (i *Interpreter) VisitMapExpression(e *ast.MapExpression) any {
m := make(LoxMap, len(e.Keys))
for idx := range e.Keys {
key, isString := i.evaluate(e.Keys[idx]).(string)
if !isString {
panic(i.errors.RuntimeError(e.OpeningBrace, "map keys must be strings"))
}
hash := Hash(key)
value := i.evaluate(e.Values[idx])
m[hash] = MapPair{Key: key, Value: value}
}
return m
}
func (i *Interpreter) VisitGetExpression(e *ast.GetExpression) any {
object := i.evaluate(e.Object)
if instance, ok := object.(LoxObject); ok {
property, err := instance.get(e.Name)
if err != nil {
panic(i.errors.RuntimeError(e.Name, err.Error()))
}
// if field is a getter method, call it immediately
if method, ok := property.(*LoxFunction); ok {
if method.declaration.Kind == ast.GETTER_METHOD {
value, err := method.Call(i, []any{})
if err != nil {
panic(i.errors.RuntimeError(e.Name, err.Error()))
}
return value
}
}
// not a getter method, simple return the property
return property
}
panic(i.errors.RuntimeError(e.Name, "Only instances have properties."))
}
func (i *Interpreter) VisitSetExpression(e *ast.SetExpression) any {
object := i.evaluate(e.Object)
if instance, ok := object.(*LoxInstance); ok {
value := i.evaluate(e.Value)
// check if name refers to a setter
method := instance.Class.findMethod(e.Name.Lexeme)
if method != nil && method.declaration.Kind == ast.SETTER_METHOD {
// bind and call setter method with value
boundMethod := method.bind(instance)
_, err := boundMethod.Call(i, []any{value})
if err != nil {
panic(i.errors.RuntimeError(e.Name, err.Error()))
}
} else {
instance.set(e.Name, value)
}
return value
}
panic(i.errors.RuntimeError(e.Name, "Can only set fields on instances."))
}
func (i *Interpreter) VisitThisExpression(e *ast.ThisExpression) any {
return i.lookupVariable(e.Keyword, e)
}
func (i *Interpreter) VisitSuperGetExpression(e *ast.SuperGetExpression) any {
distance := i.locals[e]
superclass := i.environment.getAt(distance, "super")
sc := superclass.(*LoxClass)
object := i.environment.getAt(distance-1, "this").(LoxObject)
method := sc.findMethod(e.Method.Lexeme)
if method == nil {
panic(i.errors.RuntimeError(e.Method, "Undefined property '"+e.Method.Lexeme+"'."))
}
// if field is a getter method, call it immediately
if method.declaration.Kind == ast.GETTER_METHOD {
boundMethod := method.bind(object)
value, err := boundMethod.Call(i, []any{})
if err != nil {
panic(i.errors.RuntimeError(e.Keyword, err.Error()))
}
return value
}
return method.bind(object)
}
func (i *Interpreter) VisitSuperSetExpression(e *ast.SuperSetExpression) any {
distance := i.locals[e]
superclass := i.environment.getAt(distance, "super")
sc := superclass.(*LoxClass)
object := i.environment.getAt(distance-1, "this").(LoxObject)
method := sc.findMethod(e.Method.Lexeme)
if method == nil {
panic(i.errors.RuntimeError(e.Method, "Undefined setter '"+e.Method.Lexeme+"'."))
}
// the only case where it makes sense to use a super set expression
// is if the method is a setter
if method.declaration.Kind == ast.SETTER_METHOD {
value := i.evaluate(e.Value)
boundMethod := method.bind(object)
value, err := boundMethod.Call(i, []any{value})
if err != nil {
panic(i.errors.RuntimeError(e.Keyword, err.Error()))
}
return value
}
panic(i.errors.RuntimeError(e.Keyword, "Method is not a setter"))
}
func (i *Interpreter) arrayIndexExpression(e *ast.IndexExpression) any {
object := i.evaluate(e.Object)
leftIndex, leftIsNumber := i.evaluate(e.LeftIndex).(float64)
var (
rightIndex float64
rightIsNumber bool = false
)
if e.RightIndex != nil {
rightIndex, rightIsNumber = i.evaluate(e.RightIndex).(float64)
}
if !leftIsNumber || !isInteger(leftIndex) {
panic(i.errors.RuntimeError(e.ClosingBracket, "Index must be integer"))
}
if rightIsNumber && (!rightIsNumber || !isInteger(rightIndex)) {
panic(i.errors.RuntimeError(e.ClosingBracket, "Index must be integer"))
}
switch val := object.(type) {
case LoxArray:
{
if leftIndex < 0 || int(leftIndex) >= len(val) ||
(rightIsNumber && (rightIndex < 0 || int(rightIndex) > len(val))) {
panic(i.errors.RuntimeError(e.ClosingBracket, "Index is out of range"))
}
if rightIsNumber && (leftIndex > rightIndex) {
panic(i.errors.RuntimeError(e.ClosingBracket, "Right index of slice must be greater or equal to left index"))
}
if rightIsNumber {
return val[int(leftIndex):int(rightIndex)]
} else {
return val[int(leftIndex)]
}
}
case string:
{
if leftIndex < 0 || int(leftIndex) >= len(val) ||
(rightIsNumber && (rightIndex < 0 || int(rightIndex) > len(val))) {
panic(i.errors.RuntimeError(e.ClosingBracket, "Index is out of range"))
}
if rightIsNumber && (leftIndex > rightIndex) {
panic(i.errors.RuntimeError(e.ClosingBracket, "Right index of slice must be greater or equal to left index"))
}
if rightIsNumber {
return val[int(leftIndex):int(rightIndex)]
} else {
return string(val[int(leftIndex)]) // go will return a byte
}
}
default:
panic(i.errors.RuntimeError(e.ClosingBracket, "Unreachable"))
}
}
func (i *Interpreter) mapIndexExpression(e *ast.IndexExpression) any {
object := i.evaluate(e.Object).(LoxMap)
key, isString := i.evaluate(e.LeftIndex).(string)
if e.RightIndex != nil {
panic(i.errors.RuntimeError(e.ClosingBracket, "Cannot slice maps"))
}
if !isString {
panic(i.errors.RuntimeError(e.ClosingBracket, "Maps can only be indexed with strings"))
}
hash := Hash(key)
return object[hash].Value
}
func (i *Interpreter) VisitIndexExpression(e *ast.IndexExpression) any {
object := i.evaluate(e.Object)
switch object.(type) {
case LoxArray, string:
return i.arrayIndexExpression(e)
case LoxMap:
return i.mapIndexExpression(e)
}
panic(i.errors.RuntimeError(e.ClosingBracket, "Can only index arrays, strings and maps"))
}
func (i *Interpreter) arrayIndexedAssignmentExpression(e *ast.IndexedAssignmentExpression) any {
array, _ := i.evaluate(e.Left.Object).(LoxArray)
index, isNumber := i.evaluate(e.Left.LeftIndex).(float64)
// don't need to check for right index as using a slice for assignment is a parser error
if !isNumber || !isInteger(index) {
panic(i.errors.RuntimeError(e.Left.ClosingBracket, "Index must be integer"))
}
if index < 0 || int(index) >= len(array) {
panic(i.errors.RuntimeError(e.Left.ClosingBracket, "Index is out of range for array"))
}
value := i.evaluate(e.Value)
array[int(index)] = value
return value
}
func (i *Interpreter) mapIndexedAssignmentExpression(e *ast.IndexedAssignmentExpression) any {
m, _ := i.evaluate(e.Left.Object).(LoxMap)
key, isString := i.evaluate(e.Left.LeftIndex).(string)
if !isString {
panic(i.errors.RuntimeError(e.Left.ClosingBracket, "map keys must be strings"))
}
hash := Hash(key)
value := i.evaluate(e.Value)
m[hash] = MapPair{Key: key, Value: value}
return value
}
func (i *Interpreter) VisitIndexedAssignmentExpression(e *ast.IndexedAssignmentExpression) any {
object := i.evaluate(e.Left.Object)
switch object.(type) {
case LoxArray:
return i.arrayIndexedAssignmentExpression(e)
case LoxMap:
return i.mapIndexedAssignmentExpression(e)
}
panic(i.errors.RuntimeError(e.Left.ClosingBracket, "Can only assign to arrays and maps"))
}
func (i *Interpreter) VisitLogicalExpression(le *ast.LogicalExpression) any {
left := i.evaluate(le.Left)
if le.Operator.Type == token.OR {
if isTruthy(left) {
return left
}
} else {
if !isTruthy(left) {
return left
}
}
return i.evaluate(le.Right)
}
func (i *Interpreter) VisitUnaryExpression(ue *ast.UnaryExpression) any {
right := i.evaluate(ue.Expr)
operator := ue.Operator
switch operator.Type {
case token.BANG:
return !isTruthy(right)
case token.MINUS:
{
if r, ok := right.(float64); ok {
return -r
}
panic(i.errors.RuntimeError(operator, "Operand must be a number"))
}
}
// Unreachable
panic(i.errors.RuntimeError(operator, "Unreachable"))
}
func (i *Interpreter) VisitBinaryExpression(be *ast.BinaryExpression) any {
left := i.evaluate(be.Left)
right := i.evaluate(be.Right)
operator := be.Operator
switch operator.Type {
// can compare any type with == or != and don't need to type check
case token.EQUAL_EQUAL:
return left == right
case token.BANG_EQUAL:
return left != right
// concatenate can be used on any basic types as long as one or more is a string
case token.PLUS:
{
leftNum, leftIsNumber := left.(float64)
rightNum, rightIsNumber := right.(float64)
if leftIsNumber && rightIsNumber {
return leftNum + rightNum
}
leftArr, leftIsArray := left.(LoxArray)
rightArr, rightIsArray := right.(LoxArray)
if leftIsArray && rightIsArray {
return append(leftArr, rightArr...)
}
leftStr, leftIsString := left.(string)
rightStr, rightIsString := right.(string)
if leftIsString && rightIsString {
return leftStr + rightStr
} else if leftIsString {
if s, err := concatenate(operator, leftStr, right, false); err == nil {
return s
} else {
panic(i.errors.RuntimeError(operator, err.Error()))
}
} else if rightIsString {
if s, err := concatenate(operator, rightStr, left, true); err == nil {
return s
} else {
panic(i.errors.RuntimeError(operator, err.Error()))
}
} else {
panic(i.errors.RuntimeError(operator, "only valid for two numbers, two strings, two arrays, or one string and a number or boolean"))
}
}
// all other binary operations are only valid on numbers
default:
{
l, lok := left.(float64)
r, rok := right.(float64)
if !lok || !rok {
panic(i.errors.RuntimeError(operator, "only valid for numbers"))
}
switch operator.Type {
case token.MINUS:
return l - r
case token.SLASH:
return l / r
case token.STAR:
return l * r
case token.GREATER:
return l > r
case token.GREATER_EQUAL:
return l >= r
case token.LESS:
return l < r
case token.LESS_EQUAL:
return l <= r
}
}
}
// Unreachable
panic(i.errors.RuntimeError(operator, "Unreachable"))
}
func (i *Interpreter) VisitLambdaExpression(e *ast.LambdaExpression) any {
return &LoxFunction{declaration: e.Function, closure: i.environment}
}
func (i *Interpreter) VisitCallExpression(e *ast.CallExpression) any {
callee := i.evaluate(e.Callee)
argValues := LoxArray{}
for _, argExpr := range e.Arguments {
argValues = append(argValues, i.evaluate(argExpr))
}
if function, ok := callee.(LoxCallable); ok {
if len(argValues) != function.Arity() {
panic(i.errors.RuntimeError(e.ClosingParen, fmt.Sprintf("Expected %d arguments but got %d", function.Arity(), len(argValues))))
}
value, err := function.Call(i, argValues)
if err != nil {
panic(i.errors.RuntimeError(e.ClosingParen, err.Error()))
}
return value
}
panic(i.errors.RuntimeError(e.ClosingParen, "Can only call functions and classes"))
}
func (i *Interpreter) lookupVariable(name *token.Token, expression ast.Expression) any {
if distance, ok := i.locals[expression]; ok {
// safe to not check for error as the resolver should have done its job...
return i.environment.getAt(distance, name.Lexeme)
} else {
val, ok := i.globals.get(name)
if !ok {
i.errors.RuntimeError(name, "Undefined variable '"+name.Lexeme+"'")
}
return val
}
}
func concatenate(operator *token.Token, stringValue string, otherValue any, reverse bool) (string, error) {
var other string
switch otherValue.(type) {
case float64, bool:
other = Representation(otherValue)
default:
return "", errors.New(fmt.Sprintf("cannot concatenate string with type %s", Representation(otherValue)))
}
if reverse {
return other + stringValue, nil
}
return stringValue + other, nil
}
func isInteger(value float64) bool {
return value == float64(int(value))
}
func isTruthy(value any) bool {
if value == nil {
return false
}
if v, ok := value.(bool); ok {
return v
}
return true
}
func Representation(v any) string {
switch v := v.(type) {
case nil:
return "nil"
case string:
return fmt.Sprintf("\"%s\"", v)
case bool:
return fmt.Sprintf("%t", v)
case float64:
return strconv.FormatFloat(v, 'f', -1, 64)
case LoxArray:
{
itemStrings := make([]string, len(v))
for i, item := range v {
itemStrings[i] = Representation(item)
}
return "[" + strings.Join(itemStrings, ", ") + "]"
}
case LoxMap:
return "<map>"
case *LoxFunction:
if v.declaration.Name != nil {
return "<fn " + v.declaration.Name.Lexeme + ">"
} else {
return "<lambda>"
}
case *LoxClass:
return "<class " + v.Name + ">"
case *LoxInstance:
return "<object " + v.Class.Name + ">"
case LoxNative:
return "<native fn " + v.Name() + ">"
}
return "<object>"
}
func PrintRepresentation(v any) string {
switch v := v.(type) {
case string:
return fmt.Sprint(v)
case nil, bool, float64, LoxArray, LoxCallable, LoxMap:
return Representation(v)
}
return "<object>"
}
func Hash(v string) int {
h := fnv.New64a()
h.Write([]byte(v))
return int(h.Sum64())
}
package interpreter
import (
"errors"
"fmt"
"time"
"golang.org/x/exp/maps"
)
type LoxNative interface {
LoxCallable
Name() string
}
var Natives = []LoxNative{
&Clock{},
&Print{},
&String{},
&Length{},
&Map{},
&Filter{},
&Reduce{},
&HasKey{},
&Size{},
&Values{},
&Keys{},
}
type Clock struct{}
func (Clock) Arity() int {
return 0
}
func (Clock) Call(interpreter *Interpreter, arguments []any) (any, error) {
return float64(time.Now().UnixMilli() / 1000.0), nil
}
func (Clock) Name() string {
return "clock"
}
type Print struct{}
func (Print) Arity() int {
return 1
}
func (Print) Call(interpreter *Interpreter, arguments []any) (any, error) {
fmt.Println(PrintRepresentation(arguments[0]))
return nil, nil
}
func (Print) Name() string {
return "print"
}
type String struct{}
func (String) Arity() int {
return 1
}
func (String) Call(interpreter *Interpreter, arguments []any) (any, error) {
if s, ok := arguments[0].(string); ok {
return s, nil
}
return Representation(arguments[0]), nil
}
func (String) Name() string {
return "string"
}
type Length struct{}
func (Length) Arity() int {
return 1
}
func (Length) Call(interpreter *Interpreter, arguments []any) (any, error) {
switch val := arguments[0].(type) {
case LoxArray:
return float64(len(val)), nil
case string:
return float64(len(val)), nil
}
return nil, errors.New("can only call len on arrays or strings")
}
func (Length) Name() string {
return "len"
}
type Size struct{}
func (Size) Arity() int {
return 1
}
func (Size) Call(interpreter *Interpreter, arguments []any) (any, error) {
switch val := arguments[0].(type) {
case LoxMap:
return float64(len(val)), nil
}
return nil, errors.New("can only call size on maps")
}
func (Size) Name() string {
return "size"
}
type Map struct{}
func (Map) Arity() int {
return 2
}
func (Map) Call(interpreter *Interpreter, arguments []any) (any, error) {
array, isArray := arguments[0].(LoxArray)
function, isFunction := arguments[1].(LoxCallable)
if !isArray {
return nil, errors.New("first argument of map must be an array")
}
if !isFunction || function.Arity() != 1 {
return nil, errors.New("second argument of map must be an function taking a single parameter")
}
results := make(LoxArray, len(array))
for i, element := range array {
result, err := function.Call(interpreter, []any{element})
if err != nil {
return nil, err
}
results[i] = result
}
return results, nil
}
func (Map) Name() string {
return "map"
}
type Reduce struct{}
func (Reduce) Arity() int {
return 3
}
func (Reduce) Call(interpreter *Interpreter, arguments []any) (any, error) {
initializer := arguments[0]
array, isArray := arguments[1].(LoxArray)
function, isFunction := arguments[2].(LoxCallable)
if !isArray {
return nil, errors.New("second argument of reduce must be an array")
}
if !isFunction || function.Arity() != 2 {
return nil, errors.New("third argument of reduce must be an function taking two parameters - the accumulator and the current element")
}
accumulator := initializer
var err error
for _, element := range array {
accumulator, err = function.Call(interpreter, []any{accumulator, element})
if err != nil {
return nil, err
}
}
return accumulator, nil
}
func (Reduce) Name() string {
return "reduce"
}
type Filter struct{}
func (Filter) Arity() int {
return 2
}
func (Filter) Call(interpreter *Interpreter, arguments []any) (any, error) {
array, isArray := arguments[0].(LoxArray)
function, isFunction := arguments[1].(LoxCallable)
if !isArray {
return nil, errors.New("first argument of map must be an array")
}
if !isFunction || function.Arity() != 1 {
return nil, errors.New("second argument of map must be an function taking a single parameter")
}
results := make(LoxArray, 0, len(array))
for _, element := range array {
result, err := function.Call(interpreter, []any{element})
if err != nil {
return nil, err
}
if isTruthy(result) {
results = append(results, element)
}
}
return results, nil
}
func (Filter) Name() string {
return "filter"
}
type HasKey struct{}
func (HasKey) Arity() int {
return 2
}
func (HasKey) Call(interpreter *Interpreter, arguments []any) (any, error) {
m, isMap := arguments[0].(LoxMap)
key, isString := arguments[1].(string)
if !isMap {
return nil, errors.New("first argument of hasKey must be a map")
}
if !isString {
return nil, errors.New("second argument of hasKey must be a string")
}
hash := Hash(key)
_, ok := m[hash]
return ok, nil
}
func (HasKey) Name() string {
return "hasKey"
}
type Values struct{}
func (Values) Arity() int {
return 1
}
func (Values) Call(interpreter *Interpreter, arguments []any) (any, error) {
m, isMap := arguments[0].(LoxMap)
if !isMap {
return nil, errors.New("argument of values must be a map")
}
pairs := maps.Values(m)
values := make(LoxArray, len(pairs))
for i, pair := range pairs {
values[i] = pair.Value
}
return values, nil
}
func (Values) Name() string {
return "values"
}
type Keys struct{}
func (Keys) Arity() int {
return 1
}
func (Keys) Call(interpreter *Interpreter, arguments []any) (any, error) {
m, isMap := arguments[0].(LoxMap)
if !isMap {
return nil, errors.New("argument of keys must be a map")
}
pairs := maps.Values(m)
keys := make(LoxArray, len(pairs))
for i, pair := range pairs {
keys[i] = pair.Key
}
return keys, nil
}
func (Keys) Name() string {
return "keys"
}
package lox_error
import (
"errors"
"github.com/fatih/color"
"github.com/hutcho66/glox/src/pkg/token"
)
type Reporter interface {
Report(line int, where, message string)
}
type LoxReporter struct{}
func (LoxReporter) Report(line int, where, message string) {
color.Red("[line %d] Error%s: %s\n", line, where, message)
}
type LoxErrors struct {
hadScanningError, hadParsingError, hadResolutionError, hadRuntimeError bool
reporter Reporter
}
func NewLoxErrors(reporter Reporter) *LoxErrors {
return &LoxErrors{reporter: reporter}
}
func (l *LoxErrors) ScannerError(line int, message string) {
l.hadParsingError = true
l.reporter.Report(line, "", message)
}
func (l *LoxErrors) ParserError(t *token.Token, message string) error {
l.hadParsingError = true
if t.Type == token.EOF {
l.reporter.Report(t.Line, " at end", message)
} else {
l.reporter.Report(t.Line, " at '"+t.Lexeme+"'", message)
}
return errors.New("")
}
func (l *LoxErrors) ResolutionError(t *token.Token, message string) error {
l.hadResolutionError = true
if t.Type == token.EOF {
l.reporter.Report(t.Line, " at end", message)
} else {
l.reporter.Report(t.Line, " at '"+t.Lexeme+"'", message)
}
return errors.New("")
}
func (l *LoxErrors) RuntimeError(t *token.Token, message string) error {
l.hadRuntimeError = true
l.reporter.Report(t.Line, " at '"+t.Lexeme+"'", message)
return errors.New("")
}
func (l *LoxErrors) HadScanningError() bool {
return l.hadParsingError
}
func (l *LoxErrors) HadParsingError() bool {
return l.hadParsingError
}
func (l *LoxErrors) HadRuntimeError() bool {
return l.hadRuntimeError
}
func (l *LoxErrors) HadResolutionError() bool {
return l.hadResolutionError
}
func (l *LoxErrors) ResetError() {
l.hadScanningError = false
l.hadParsingError = false
l.hadRuntimeError = false
l.hadResolutionError = false
}
package parser
import (
"github.com/hutcho66/glox/src/pkg/ast"
"github.com/hutcho66/glox/src/pkg/lox_error"
"github.com/hutcho66/glox/src/pkg/token"
)
type Parser struct {
errors *lox_error.LoxErrors
tokens []token.Token
current int
}
func NewParser(tokens []token.Token, errors *lox_error.LoxErrors) *Parser {
return &Parser{
errors: errors,
tokens: tokens,
current: 0,
}
}
func (p *Parser) Parse() []ast.Statement {
statements := []ast.Statement{}
for !p.isAtEnd() {
if !p.match(token.NEW_LINE) {
statements = append(statements, p.declaration())
}
}
return statements
}
func (p *Parser) declaration() (declaration ast.Statement) {
// catch any panics and synchronize to recover
defer func() {
if err := recover(); err != nil {
p.synchronize()
// return nil for this statement
declaration = nil
return
}
}()
if p.match(token.VAR) {
return p.varDeclaration()
} else if p.match(token.CLASS) {
return p.classDeclaration()
} else if p.match(token.FUN) {
return p.funDeclaration("function")
} else {
return p.statement()
}
}
func (p *Parser) varDeclaration() ast.Statement {
name := p.consume(token.IDENTIFIER, "Expect variable name.")
var initializer ast.Expression = nil
if p.match(token.EQUAL) {
initializer = p.expression()
}
p.endStatement()
return &ast.VarStatement{Name: name, Initializer: initializer}
}
func (p *Parser) classDeclaration() ast.Statement {
name := p.consume(token.IDENTIFIER, "Expect class name.")
var super *ast.VariableExpression = nil
if p.match(token.LESS) {
p.consume(token.IDENTIFIER, "Expect superclass name.")
super = &ast.VariableExpression{Name: p.previous()}
}
p.consume(token.LEFT_BRACE, "Exepct '{' before class body.")
methods := []*ast.FunctionStatement{}
p.eatNewLines()
for !p.check(token.RIGHT_BRACE) && !p.isAtEnd() {
if p.match(token.GET) {
// this is a getter
name := p.consume(token.IDENTIFIER, "Expect getter name.")
p.consume(token.LEFT_BRACE, "Expect '{' after getter name")
body := p.block()
getter := &ast.FunctionStatement{Name: name, Params: []*token.Token{}, Body: body, Kind: ast.GETTER_METHOD}
methods = append(methods, getter)
} else if p.match(token.SET) {
name := p.consume(token.IDENTIFIER, "Expect setter name.")
p.consume(token.LEFT_PAREN, "Expect '(' after setter name.")
value := p.consume(token.IDENTIFIER, "Expect parameter name.")
p.consume(token.RIGHT_PAREN, "Expect ')' after setter parameter")
p.consume(token.LEFT_BRACE, "Expect '{' before setter body.")
body := p.block()
setter := &ast.FunctionStatement{Name: name, Params: []*token.Token{value}, Body: body, Kind: ast.SETTER_METHOD}
methods = append(methods, setter)
} else {
method := p.funDeclaration("method").(*ast.FunctionStatement)
methods = append(methods, method)
}
p.eatNewLines()
}
p.consume(token.RIGHT_BRACE, "Expect '}' after class body.")
return &ast.ClassStatement{Name: name, Methods: methods, Superclass: super}
}
func (p *Parser) funDeclaration(kind string) ast.Statement {
var methodKind ast.MethodType = ast.NOT_METHOD
if p.match(token.STATIC) {
methodKind = ast.STATIC_METHOD
} else if kind == "method" {
methodKind = ast.NORMAL_METHOD
}
name := p.consume(token.IDENTIFIER, "Expect "+kind+" name")
p.consume(token.LEFT_PAREN, "Expect '(' after "+kind+" name")
parameters := []*token.Token{}
if !p.check(token.RIGHT_PAREN) {
for ok := true; ok; ok = p.match(token.COMMA) {
p.eatNewLines()
if len(parameters) >= 255 {
panic(p.errors.ParserError(p.peek(), "Can't have more than 255 parameters"))
}
parameters = append(parameters, p.consume(token.IDENTIFIER, "Expect parameter name"))
}
}
p.consume(token.RIGHT_PAREN, "Expect ')' after parameters")
p.consume(token.LEFT_BRACE, "Expect '{' before "+kind+" body")
body := p.block()
return &ast.FunctionStatement{Name: name, Params: parameters, Body: body, Kind: methodKind}
}
func (p *Parser) statement() ast.Statement {
// if p.match(token.NEW_LINE) {
// // consume and retry
// return p.statement()
// }
if p.match(token.RETURN) {
return p.returnStatement()
}
if p.match(token.BREAK) {
return p.breakStatement()
}
if p.match(token.CONTINUE) {
return p.continueStatement()
}
if p.match(token.IF) {
return p.ifStatement()
}
if p.match(token.WHILE) {
return p.whileStatement()
}
if p.match(token.FOR) {
return p.forStatement()
}
if p.check(token.LEFT_BRACE) {
if p.checkAhead(token.RIGHT_BRACE, 1) || (p.checkAhead(token.STRING, 1) && p.checkAhead(token.COLON, 2)) {
// this looks like a map
return p.expressionStatement()
}
p.match(token.LEFT_BRACE)
return &ast.BlockStatement{Statements: p.block()}
}
return p.expressionStatement()
}
func (p *Parser) block() []ast.Statement {
statements := []ast.Statement{}
for !p.check(token.RIGHT_BRACE) && !p.isAtEnd() {
if !p.match(token.NEW_LINE) {
statement := p.declaration()
statements = append(statements, statement)
}
}
p.consume(token.RIGHT_BRACE, "Expect '}' after block")
return statements
}
func (p *Parser) returnStatement() ast.Statement {
keyword := p.previous()
var value ast.Expression = nil
if !p.check(token.SEMICOLON) && !p.check(token.NEW_LINE) {
value = p.expression()
}
p.endStatement()
return &ast.ReturnStatement{Keyword: keyword, Value: value}
}
func (p *Parser) breakStatement() ast.Statement {
keyword := p.previous()
p.endStatement()
return &ast.BreakStatement{Keyword: keyword}
}
func (p *Parser) continueStatement() ast.Statement {
keyword := p.previous()
p.endStatement()
return &ast.ContinueStatement{Keyword: keyword}
}
func (p *Parser) ifStatement() ast.Statement {
p.consume(token.LEFT_PAREN, "Expect '(' after 'if'")
condition := p.expression()
p.consume(token.RIGHT_PAREN, "Expect ')' after if condition")
consequence := p.statement()
var alternative ast.Statement = nil
if p.match(token.ELSE) {
alternative = p.statement()
}
return &ast.IfStatement{Condition: condition, Consequence: consequence, Alternative: alternative}
}
func (p *Parser) whileStatement() ast.Statement {
p.consume(token.LEFT_PAREN, "Expect '(' after 'while'")
condition := p.expression()
p.consume(token.RIGHT_PAREN, "Expect ')' after while condition")
body := p.statement()
// while statements have no increment
return &ast.LoopStatement{Condition: condition, Body: body, Increment: nil}
}
func (p *Parser) forStatement() ast.Statement {
p.consume(token.LEFT_PAREN, "Expect '(' after 'for'")
if p.check(token.VAR) && p.checkAhead(token.OF, 2) {
// for (IDENT of ARRAY)format
p.match(token.VAR)
name := p.consume(token.IDENTIFIER, "Expect variable name after var")
p.consume(token.OF, "Expect 'of' after variable name")
array := p.expression()
p.consume(token.RIGHT_PAREN, "Expect ')' after for clauses")
body := p.statement()
return &ast.ForEachStatement{VariableName: name, Array: array, Body: body}
}
// else continue with c-style loop
var initializer ast.Statement
if p.match(token.SEMICOLON) {
initializer = nil
} else if p.match(token.VAR) {
initializer = p.varDeclaration()
} else {
initializer = p.expressionStatement()
}
var condition ast.Expression = nil
if !p.check(token.SEMICOLON) {
condition = p.expression()
}
p.consume(token.SEMICOLON, "Expect ';' after loop condition")
var increment ast.Expression = nil
if !p.check(token.RIGHT_PAREN) {
increment = p.expression()
}
p.consume(token.RIGHT_PAREN, "Expect ')' after for clauses")
body := p.statement()
if condition == nil {
// if there is no condition, set it to 'true' to make infinite loop
condition = &ast.LiteralExpression{Value: true}
}
// create LoopStatement using condition, body and increment
body = &ast.LoopStatement{Condition: condition, Body: body, Increment: increment}
// if there is an initializer, add before loop statement
if initializer != nil {
body = &ast.BlockStatement{
Statements: []ast.Statement{
initializer,
body,
},
}
}
return body
}
func (p *Parser) expressionStatement() ast.Statement {
expr := p.expression()
p.endStatement()
return &ast.ExpressionStatement{Expr: expr}
}
func (p *Parser) expression() ast.Expression {
if p.check(token.LEFT_PAREN) {
// need to check ahead to test if this is a lambda
if p.checkAhead(token.RIGHT_PAREN, 1) {
// must be lambda with no params
return p.lambda()
}
if p.checkAhead(token.IDENTIFIER, 1) {
// presence of comma indicates a lambda
// as does a right paren and then the arrow operator
if p.checkAhead(token.COMMA, 2) || p.checkAhead(token.RIGHT_PAREN, 2) && p.checkAhead(token.LAMBDA_ARROW, 3) {
return p.lambda()
}
}
}
if p.check(token.IDENTIFIER) && p.checkAhead(token.LAMBDA_ARROW, 1) {
// x => <expression>
return p.lambda()
}
return p.ternary()
}
func (p *Parser) lambda() ast.Expression {
parameters := []*token.Token{}
if p.match(token.IDENTIFIER) {
// x => <expression> form
parameters = append(parameters, p.previous())
} else {
p.consume(token.LEFT_PAREN, "unexpected error") // already checked
if !p.check(token.RIGHT_PAREN) {
for ok := true; ok; ok = p.match(token.COMMA) {
p.eatNewLines()
if len(parameters) >= 255 {
panic(p.errors.ParserError(p.peek(), "Can't have more than 255 parameters"))
}
parameters = append(parameters, p.consume(token.IDENTIFIER, "Expect parameter name"))
}
}
p.consume(token.RIGHT_PAREN, "Expect ')' after parameters")
}
operator := p.consume(token.LAMBDA_ARROW, "Expect '=>' after lambda parameters")
var body []ast.Statement
if !p.check(token.LEFT_BRACE) || (p.checkAhead(token.STRING, 1) && p.checkAhead(token.COLON, 2)) {
// this is an expression return lambda
line := p.peek().Line
expression := p.expression()
// add implicit return statement
token := &token.Token{Type: token.RETURN, Lexeme: "return", Literal: nil, Line: line}
body = []ast.Statement{
&ast.ReturnStatement{Keyword: token, Value: expression},
}
} else {
// this is a block lambda
p.match(token.LEFT_BRACE)
body = p.block()
}
function := &ast.FunctionStatement{Name: nil, Params: parameters, Body: body}
return &ast.LambdaExpression{Operator: operator, Function: function}
}
func (p *Parser) ternary() ast.Expression {
condition := p.assignment()
if p.match(token.QUESTION) {
operator := p.previous()
consequence := p.expression()
p.consume(token.COLON, "Expect ':' after expression following '?'")
alternative := p.expression()
return &ast.TernaryExpression{Condition: condition, Consequence: consequence, Alternative: alternative, Operator: operator}
}
return condition
}
func (p *Parser) assignment() ast.Expression {
expr := p.or()
if p.match(token.EQUAL) {
equals := p.previous()
value := p.assignment()
switch e := expr.(type) {
case *ast.VariableExpression:
return &ast.AssignmentExpression{Name: e.Name, Value: value}
case *ast.GetExpression:
return &ast.SetExpression{Object: e.Object, Name: e.Name, Value: value}
case *ast.SuperGetExpression:
return &ast.SuperSetExpression{Keyword: e.Keyword, Method: e.Method, Value: value}
case *ast.IndexExpression:
if e.RightIndex != nil {
panic(p.errors.ParserError(equals, "Cannot assign to array slice"))
}
return &ast.IndexedAssignmentExpression{Left: e, Value: value}
}
panic(p.errors.ParserError(equals, "Invalid assignment target"))
}
return expr
}
func (p *Parser) or() ast.Expression {
expr := p.and()
for p.match(token.OR) {
operator := p.previous()
right := p.and()
expr = &ast.LogicalExpression{Left: expr, Right: right, Operator: operator}
}
return expr
}
func (p *Parser) and() ast.Expression {
expr := p.equality()
for p.match(token.AND) {
operator := p.previous()
right := p.equality()
expr = &ast.LogicalExpression{Left: expr, Right: right, Operator: operator}
}
return expr
}
func (p *Parser) equality() ast.Expression {
expr := p.comparison()
for p.match(token.BANG_EQUAL, token.EQUAL_EQUAL) {
operator := p.previous()
right := p.comparison()
expr = &ast.BinaryExpression{Left: expr, Right: right, Operator: operator}
}
return expr
}
func (p *Parser) comparison() ast.Expression {
expr := p.term()
for p.match(token.GREATER, token.GREATER_EQUAL, token.LESS, token.LESS_EQUAL) {
operator := p.previous()
right := p.term()
expr = &ast.BinaryExpression{Left: expr, Right: right, Operator: operator}
}
return expr
}
func (p *Parser) term() ast.Expression {
expr := p.factor()
for p.match(token.MINUS, token.PLUS) {
operator := p.previous()
right := p.factor()
expr = &ast.BinaryExpression{Left: expr, Right: right, Operator: operator}
}
return expr
}
func (p *Parser) factor() ast.Expression {
expr := p.unary()
for p.match(token.SLASH, token.STAR) {
operator := p.previous()
right := p.unary()
expr = &ast.BinaryExpression{Left: expr, Right: right, Operator: operator}
}
return expr
}
func (p *Parser) unary() ast.Expression {
if p.match(token.BANG, token.MINUS) {
operator := p.previous()
right := p.unary()
return &ast.UnaryExpression{Expr: right, Operator: operator}
}
return p.call_index()
}
func (p *Parser) call_index() ast.Expression {
expr := p.primary()
for {
if p.match(token.LEFT_PAREN) {
expr = p.finishCall(expr)
} else if p.match(token.LEFT_BRACKET) {
expr = p.finishIndex(expr)
} else if p.match(token.DOT) {
name := p.consume(token.IDENTIFIER, "Expect property name after '.'")
expr = &ast.GetExpression{Object: expr, Name: name}
} else {
break
}
}
return expr
}
func (p *Parser) primary() ast.Expression {
if p.match(token.FALSE) {
return &ast.LiteralExpression{Value: false}
}
if p.match(token.TRUE) {
return &ast.LiteralExpression{Value: true}
}
if p.match(token.NIL) {
return &ast.LiteralExpression{Value: nil}
}
if p.match(token.NUMBER, token.STRING) {
return &ast.LiteralExpression{Value: p.previous().Literal}
}
if p.match(token.IDENTIFIER) {
return &ast.VariableExpression{Name: p.previous()}
}
if p.match(token.THIS) {
return &ast.ThisExpression{Keyword: p.previous()}
}
if p.match(token.SUPER) {
keyword := p.previous()
p.consume(token.DOT, "Expect '.' after 'super'")
method := p.consume(token.IDENTIFIER, "Expect superclass method name.")
return &ast.SuperGetExpression{Keyword: keyword, Method: method}
}
if p.match(token.LEFT_PAREN) {
if p.match(token.RIGHT_PAREN) {
// empty sequence expression
return &ast.SequenceExpression{Items: []ast.Expression{}}
}
exprs := p.expressionList()
p.consume(token.RIGHT_PAREN, "Expect ')' after expression")
if len(exprs) == 1 {
return &ast.GroupingExpression{Expr: exprs[0]}
} else {
return &ast.SequenceExpression{Items: exprs}
}
}
if p.match(token.LEFT_BRACKET) {
if p.match(token.RIGHT_BRACKET) {
// empty array
return &ast.ArrayExpression{Items: []ast.Expression{}}
}
exprs := p.expressionList()
p.consume(token.RIGHT_BRACKET, "Expect ']' after array literal")
return &ast.ArrayExpression{Items: exprs}
}
if p.match(token.LEFT_BRACE) {
openingBrace := p.previous()
// eat any newlines, they are allowed before first key-pair
p.eatNewLines()
if p.match(token.RIGHT_BRACE) {
// empty array
return &ast.MapExpression{OpeningBrace: openingBrace, Keys: []ast.Expression{}, Values: []ast.Expression{}}
}
keys := []ast.Expression{}
values := []ast.Expression{}
for ok := true; ok; ok = p.match(token.COMMA) {
p.eatNewLines()
keys = append(keys, p.expression())
p.consume(token.COLON, "Expect ':' between key and value in map literal")
values = append(values, p.expression())
p.eatNewLines()
}
p.consume(token.RIGHT_BRACE, "Expect '}' after map literal")
return &ast.MapExpression{OpeningBrace: openingBrace, Keys: keys, Values: values}
}
panic(p.errors.ParserError(p.peek(), "Expect expression."))
}
func (p *Parser) expressionList() []ast.Expression {
// eat any newlines, they are allowed before first expression in list
p.eatNewLines()
exprs := []ast.Expression{}
for ok := true; ok; ok = p.match(token.COMMA) {
p.eatNewLines()
exprs = append(exprs, p.expression())
p.eatNewLines()
}
return exprs
}
func (p *Parser) finishIndex(array ast.Expression) ast.Expression {
leftIndex := p.expression()
var rightIndex ast.Expression
if p.match(token.COLON) {
rightIndex = p.expression()
}
closingBracket := p.consume(token.RIGHT_BRACKET, "Expect ']' after index")
return &ast.IndexExpression{Object: array, LeftIndex: leftIndex, RightIndex: rightIndex, ClosingBracket: closingBracket}
}
func (p *Parser) finishCall(callee ast.Expression) ast.Expression {
args := []ast.Expression{}
if !p.check(token.RIGHT_PAREN) {
for ok := true; ok; ok = p.match(token.COMMA) {
p.eatNewLines()
if len(args) >= 255 {
panic(p.errors.ParserError(p.peek(), "Can't have more than 255 arguments"))
}
args = append(args, p.expression())
}
}
closingParen := p.consume(token.RIGHT_PAREN, "Expect ')' after arguments")
return &ast.CallExpression{Callee: callee, Arguments: args, ClosingParen: closingParen}
}
func (p *Parser) consume(tokenType token.TokenType, message string) *token.Token {
if p.check(tokenType) {
return p.advance()
}
err := p.errors.ParserError(p.peek(), message)
panic(err)
}
func (p *Parser) endStatement() {
// a closing brace is a valid statement ending, to allow statements like this on one line
// `if (true) { var x = 5; print(x) }`
if p.check(token.RIGHT_BRACE) {
return
}
// Otherwise, must have at least one semicolon or newline to terminate a statement
if terminated := p.match(token.SEMICOLON, token.NEW_LINE); !terminated && !p.isAtEnd() {
panic(p.errors.ParserError(p.peek(), "Improperly terminated statement"))
}
// Consume as many extra newlines as possible
p.eatNewLines()
}
func (p *Parser) eatNewLines() {
for p.match(token.NEW_LINE) {
}
}
func (p *Parser) match(tokenTypes ...token.TokenType) bool {
for _, t := range tokenTypes {
if p.check(t) {
p.advance()
return true
}
}
return false
}
func (p *Parser) check(tokenType token.TokenType) bool {
if p.isAtEnd() {
return false
}
return p.peek().Type == tokenType
}
func (p *Parser) checkAhead(tokenType token.TokenType, lookahead int) bool {
position := p.current + lookahead
return p.tokens[position].Type == tokenType
}
func (p *Parser) advance() *token.Token {
if !p.isAtEnd() {
p.current++
}
return p.previous()
}
func (p *Parser) isAtEnd() bool {
return p.peek().Type == token.EOF
}
func (p *Parser) peek() *token.Token {
return &p.tokens[p.current]
}
func (p *Parser) previous() *token.Token {
return &p.tokens[p.current-1]
}
func (p *Parser) synchronize() {
p.advance()
for !p.isAtEnd() {
if p.previous().Type == token.SEMICOLON {
return
}
switch p.peek().Type {
case token.CLASS, token.FUN, token.VAR, token.FOR, token.IF, token.WHILE, token.RETURN:
return
}
p.advance()
}
}
package resolver
import (
"github.com/hutcho66/glox/src/pkg/ast"
"github.com/hutcho66/glox/src/pkg/interpreter"
"github.com/hutcho66/glox/src/pkg/lox_error"
"github.com/hutcho66/glox/src/pkg/token"
)
type FunctionType int
type ClassType int
const (
NOT_FUNCTION FunctionType = iota
FUNCTION
METHOD
INITIALIZER
)
const (
NOT_CLASS ClassType = iota
CLASS
SUBCLASS
)
type Resolver struct {
errors *lox_error.LoxErrors
interpreter *interpreter.Interpreter
scopes []map[string]bool
currentFunction FunctionType
currentClass ClassType
currentMethod ast.MethodType
loop bool
}
func NewResolver(interpreter *interpreter.Interpreter, errors *lox_error.LoxErrors) *Resolver {
return &Resolver{
errors: errors,
interpreter: interpreter,
scopes: []map[string]bool{},
currentFunction: NOT_FUNCTION,
currentClass: NOT_CLASS,
currentMethod: ast.NOT_METHOD,
loop: false,
}
}
func (r *Resolver) Resolve(statements []ast.Statement) (ok bool) {
defer func() {
// catch any errors
if err := recover(); err != nil {
ok = false
return
}
}()
r.resolveStatements(statements)
return true
}
func (r *Resolver) resolveStatements(statements []ast.Statement) {
for _, s := range statements {
r.resolveStatement(s)
}
}
func (r *Resolver) resolveStatement(statement ast.Statement) {
statement.Accept(r)
}
func (r *Resolver) resolveExpression(expression ast.Expression) {
expression.Accept(r)
}
func (r *Resolver) resolveLocal(expression ast.Expression, name *token.Token) {
for i := range r.scopes {
i = len(r.scopes) - 1 - i // reverse order!
if _, ok := r.scopes[i][name.Lexeme]; ok {
r.interpreter.Resolve(expression, len(r.scopes)-1-i)
return
}
}
}
func (r *Resolver) resolveFunction(function *ast.FunctionStatement, functionType FunctionType) {
if function.Kind == ast.STATIC_METHOD && r.currentClass == NOT_CLASS {
panic(r.errors.ResolutionError(function.Name, "Cannot declare function as static outside of class declaration."))
}
enclosingFunction := r.currentFunction
r.currentFunction = functionType
r.beginScope()
for _, param := range function.Params {
r.declare(param)
r.define(param)
}
r.resolveStatements(function.Body)
r.endScope()
r.currentFunction = enclosingFunction
}
func (r *Resolver) beginScope() {
r.scopes = append(r.scopes, make(map[string]bool))
}
func (r *Resolver) endScope() {
// remove last element of scope
r.scopes = r.scopes[:len(r.scopes)-1]
}
func (r *Resolver) peekScope() map[string]bool {
return r.scopes[len(r.scopes)-1]
}
func (r *Resolver) declare(name *token.Token) {
if len(r.scopes) == 0 {
return
}
scope := r.peekScope()
if _, ok := scope[name.Lexeme]; ok {
panic(r.errors.ResolutionError(name, "Already a variable with this name in scope"))
}
scope[name.Lexeme] = false
}
func (r *Resolver) define(name *token.Token) {
if len(r.scopes) == 0 {
return
}
r.peekScope()[name.Lexeme] = true
}
// Resolver implements ast.StatementVisitor.
func (r *Resolver) VisitBlockStatement(s *ast.BlockStatement) {
r.beginScope()
r.resolveStatements(s.Statements)
r.endScope()
}
func (r *Resolver) VisitExpressionStatement(e *ast.ExpressionStatement) {
r.resolveExpression(e.Expr)
}
func (r *Resolver) VisitFunctionStatement(s *ast.FunctionStatement) {
r.declare(s.Name)
r.define(s.Name)
r.resolveFunction(s, FUNCTION)
}
func (r *Resolver) VisitClassStatement(s *ast.ClassStatement) {
enclosingClass := r.currentClass
r.currentClass = CLASS
r.declare(s.Name)
r.define(s.Name)
if s.Superclass != nil {
if s.Name.Lexeme == s.Superclass.Name.Lexeme {
panic(r.errors.ResolutionError(s.Superclass.Name, "A class can't inherit from itself."))
}
r.currentClass = SUBCLASS
r.resolveExpression(s.Superclass)
}
if s.Superclass != nil {
r.beginScope()
r.peekScope()["super"] = true
}
r.beginScope()
r.peekScope()["this"] = true
for _, method := range s.Methods {
if method.Name.Lexeme == "init" {
if method.Kind != ast.NORMAL_METHOD {
panic(r.errors.ResolutionError(s.Name, "init method cannot be static, getter or setter"))
}
r.resolveFunction(method, INITIALIZER)
} else {
r.currentMethod = method.Kind
r.resolveFunction(method, METHOD)
r.currentMethod = ast.NOT_METHOD
}
}
r.endScope()
if s.Superclass != nil {
r.endScope()
}
r.currentClass = enclosingClass
}
func (r *Resolver) VisitIfStatement(s *ast.IfStatement) {
r.resolveExpression(s.Condition)
r.resolveStatement(s.Consequence)
if s.Alternative != nil {
r.resolveStatement(s.Alternative)
}
}
func (r *Resolver) VisitReturnStatement(s *ast.ReturnStatement) {
if r.currentFunction == NOT_FUNCTION {
panic(r.errors.ResolutionError(s.Keyword, "Can't return from top level code"))
}
if s.Value != nil {
if r.currentFunction == INITIALIZER {
panic(r.errors.ResolutionError(s.Keyword, "Can't return a value from an initializer"))
}
if r.currentMethod == ast.SETTER_METHOD {
panic(r.errors.ResolutionError(s.Keyword, "Can't return a value from a setter"))
}
r.resolveExpression(s.Value)
}
}
func (r *Resolver) VisitBreakStatement(s *ast.BreakStatement) {
if r.loop == false {
panic(r.errors.ResolutionError(s.Keyword, "Can't break when not in loop"))
}
}
func (r *Resolver) VisitContinueStatement(s *ast.ContinueStatement) {
if r.loop == false {
panic(r.errors.ResolutionError(s.Keyword, "Can't continue when not in loop"))
}
}
func (r *Resolver) VisitVarStatement(s *ast.VarStatement) {
r.declare(s.Name)
if s.Initializer != nil {
r.resolveExpression(s.Initializer)
}
r.define(s.Name)
}
func (r *Resolver) VisitLoopStatement(s *ast.LoopStatement) {
r.resolveExpression(s.Condition)
r.loop = true
r.resolveStatement(s.Body)
if s.Increment != nil {
r.resolveExpression(s.Increment)
}
r.loop = false
}
func (r *Resolver) VisitForEachStatement(s *ast.ForEachStatement) {
r.resolveExpression(s.Array)
// we need to begin a new scope here to contain the loop variable
r.beginScope()
r.declare(s.VariableName)
r.define(s.VariableName)
r.loop = true
r.resolveStatement(s.Body)
r.loop = false
r.endScope()
}
// Resolver implements ast.ExprVisitor.
func (r *Resolver) VisitAssignmentExpression(e *ast.AssignmentExpression) any {
r.resolveExpression(e.Value)
r.resolveLocal(e, e.Name)
return nil
}
func (r *Resolver) VisitTernaryExpression(e *ast.TernaryExpression) any {
r.resolveExpression(e.Condition)
r.resolveExpression(e.Consequence)
r.resolveExpression(e.Alternative)
return nil
}
func (r *Resolver) VisitBinaryExpression(e *ast.BinaryExpression) any {
r.resolveExpression(e.Left)
r.resolveExpression(e.Right)
return nil
}
func (r *Resolver) VisitCallExpression(e *ast.CallExpression) any {
r.resolveExpression(e.Callee)
for _, arg := range e.Arguments {
r.resolveExpression(arg)
}
return nil
}
func (r *Resolver) VisitIndexExpression(e *ast.IndexExpression) any {
r.resolveExpression(e.Object)
r.resolveExpression(e.LeftIndex)
if e.RightIndex != nil {
r.resolveExpression(e.RightIndex)
}
return nil
}
func (r *Resolver) VisitGetExpression(e *ast.GetExpression) any {
r.resolveExpression(e.Object)
return nil
}
func (r *Resolver) VisitSetExpression(e *ast.SetExpression) any {
r.resolveExpression(e.Value)
r.resolveExpression(e.Object)
return nil
}
func (r *Resolver) VisitThisExpression(e *ast.ThisExpression) any {
if r.currentClass == NOT_CLASS {
panic(r.errors.ResolutionError(e.Keyword, "Can't use 'this' outside of a class."))
}
r.resolveLocal(e, e.Keyword)
return nil
}
func (r *Resolver) VisitSuperGetExpression(e *ast.SuperGetExpression) any {
if r.currentClass == NOT_CLASS {
panic(r.errors.ResolutionError(e.Keyword, "Can't use 'super' outside of a class."))
} else if r.currentClass != SUBCLASS {
panic(r.errors.ResolutionError(e.Keyword, "Can't use 'super' in a class with no superclass."))
}
r.resolveLocal(e, e.Keyword)
return nil
}
func (r *Resolver) VisitSuperSetExpression(e *ast.SuperSetExpression) any {
if r.currentClass == NOT_CLASS {
panic(r.errors.ResolutionError(e.Keyword, "Can't use 'super' outside of a class."))
} else if r.currentClass != SUBCLASS {
panic(r.errors.ResolutionError(e.Keyword, "Can't use 'super' in a class with no superclass."))
}
r.resolveLocal(e, e.Keyword)
r.resolveExpression(e.Value)
return nil
}
func (r *Resolver) VisitArrayExpression(e *ast.ArrayExpression) any {
for _, item := range e.Items {
r.resolveExpression(item)
}
return nil
}
func (r *Resolver) VisitMapExpression(e *ast.MapExpression) any {
for i := range e.Keys {
r.resolveExpression(e.Keys[i])
r.resolveExpression(e.Values[i])
}
return nil
}
func (r *Resolver) VisitIndexedAssignmentExpression(e *ast.IndexedAssignmentExpression) any {
r.resolveExpression(e.Left)
r.resolveExpression(e.Value)
return nil
}
func (r *Resolver) VisitSequenceExpression(e *ast.SequenceExpression) any {
for _, item := range e.Items {
r.resolveExpression(item)
}
return nil
}
func (r *Resolver) VisitGroupedExpression(e *ast.GroupingExpression) any {
r.resolveExpression(e.Expr)
return nil
}
func (r *Resolver) VisitLambdaExpression(e *ast.LambdaExpression) any {
r.resolveFunction(e.Function, FUNCTION)
return nil
}
func (r *Resolver) VisitLiteralExpression(e *ast.LiteralExpression) any {
return nil
}
func (r *Resolver) VisitLogicalExpression(e *ast.LogicalExpression) any {
r.resolveExpression(e.Left)
r.resolveExpression(e.Right)
return nil
}
func (r *Resolver) VisitUnaryExpression(e *ast.UnaryExpression) any {
r.resolveExpression(e.Expr)
return nil
}
func (r *Resolver) VisitVariableExpression(e *ast.VariableExpression) any {
if len(r.scopes) > 0 {
if val, ok := r.peekScope()[e.Name.Lexeme]; ok && val == false {
// visiting declared but not yet defined variable is an error
panic(r.errors.ResolutionError(e.Name, "Can't read local variable in its own initializer"))
}
}
r.resolveLocal(e, e.Name)
return nil
}
package scanner
import (
"strconv"
"github.com/hutcho66/glox/src/pkg/lox_error"
"github.com/hutcho66/glox/src/pkg/token"
)
type Scanner struct {
errors *lox_error.LoxErrors
source string
tokens []token.Token
start, current, line int
}
// Public methods
func NewScanner(source string, errors *lox_error.LoxErrors) *Scanner {
return &Scanner{
errors: errors,
source: source,
tokens: []token.Token{},
start: 0,
current: 0,
line: 1,
}
}
func (s *Scanner) ScanTokens() []token.Token {
for !s.isAtEnd() {
s.start = s.current
s.scanToken()
}
s.tokens = append(s.tokens, token.Token{Type: token.EOF, Lexeme: "", Literal: nil, Line: s.line})
return s.tokens
}
// Private methods
func (s *Scanner) scanToken() {
c := s.advance()
switch c {
// Symbols
case '(':
s.addToken(token.LEFT_PAREN)
case ')':
s.addToken(token.RIGHT_PAREN)
case '{':
s.addToken(token.LEFT_BRACE)
case '}':
s.addToken(token.RIGHT_BRACE)
case '[':
s.addToken(token.LEFT_BRACKET)
case ']':
s.addToken(token.RIGHT_BRACKET)
case ',':
s.addToken(token.COMMA)
case '.':
s.addToken(token.DOT)
case '-':
s.addToken(token.MINUS)
case '+':
s.addToken(token.PLUS)
case ';':
s.addToken(token.SEMICOLON)
case '*':
s.addToken(token.STAR)
case '?':
s.addToken(token.QUESTION)
case ':':
s.addToken(token.COLON)
case '!':
s.addTokenConditional('=', token.BANG_EQUAL, token.BANG)
case '=':
{
if s.match('=') {
s.addToken(token.EQUAL_EQUAL)
} else if s.match('>') {
s.addToken(token.LAMBDA_ARROW)
} else {
s.addToken(token.EQUAL)
}
}
case '<':
s.addTokenConditional('=', token.LESS_EQUAL, token.LESS)
case '>':
s.addTokenConditional('=', token.GREATER_EQUAL, token.GREATER)
case '/':
{
if s.match('/') {
// Comment goes to the end of the line
for s.peek() != '\n' && !s.isAtEnd() {
s.advance()
}
} else {
s.addToken(token.SLASH)
}
}
// Ignore whitespace
case ' ':
break
case '\r':
break
case '\t':
break
// Newlines are a token
case '\n':
{
s.addToken(token.NEW_LINE)
s.line++
}
// Literals
case '"':
s.string()
default:
{
if isDigit(c) {
s.number()
} else if isAlpha(c) {
s.identifier()
} else {
s.errors.ScannerError(s.line, "Unexpected character.")
}
}
}
}
func (s *Scanner) string() {
// Advance until either EOF or closing quote, incrementing line count when necessary
for s.peek() != '"' && !s.isAtEnd() {
if s.peek() == '\n' {
s.line++
}
s.advance()
}
if s.isAtEnd() {
s.errors.ScannerError(s.line, "Unterminated string.")
return
}
// consume closing quote
s.advance()
// trim quote symbols
value := s.source[s.start+1 : s.current-1]
s.addTokenWithLiteral(token.STRING, value)
}
func (s *Scanner) number() {
for isDigit(s.peek()) {
s.advance()
}
if s.peek() == '.' {
s.advance()
if s.isAtEnd() {
s.errors.ScannerError(s.line, "Unterminated number literal.")
return
}
for isDigit(s.peek()) {
s.advance()
}
}
value, _ := strconv.ParseFloat(s.source[s.start:s.current], 64)
s.addTokenWithLiteral(token.NUMBER, value)
}
func (s *Scanner) identifier() {
for isAlphaNumeric(s.peek()) {
s.advance()
}
word := s.source[s.start:s.current]
s.addToken(token.LookupKeyword(word))
}
func (s *Scanner) isAtEnd() bool {
return s.current >= len(s.source)
}
func (s *Scanner) match(expected byte) bool {
if s.isAtEnd() || s.source[s.current] != expected {
return false
}
s.current++
return true
}
func (s *Scanner) peek() byte {
if s.isAtEnd() {
return '\x00'
}
return s.source[s.current]
}
func (s *Scanner) advance() byte {
ch := s.source[s.current]
s.current++
return ch
}
func (s *Scanner) addToken(tokenType token.TokenType) {
s.addTokenWithLiteral(tokenType, nil)
}
func (s *Scanner) addTokenConditional(expected byte, matchType, elseType token.TokenType) {
if s.match(expected) {
s.addToken(matchType)
} else {
s.addToken(elseType)
}
}
func (s *Scanner) addTokenWithLiteral(tokenType token.TokenType, literal any) {
lexeme := s.source[s.start:s.current]
s.tokens = append(s.tokens, token.Token{Type: tokenType, Lexeme: lexeme, Literal: literal, Line: s.line})
}
func isDigit(ch byte) bool {
return ch >= '0' && ch <= '9'
}
func isAlpha(ch byte) bool {
return (ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||
ch == '_'
}
func isAlphaNumeric(ch byte) bool {
return isAlpha(ch) || isDigit(ch)
}
package token
var keywords = map[string]TokenType{
"and": AND,
"break": BREAK,
"class": CLASS,
"continue": CONTINUE,
"else": ELSE,
"false": FALSE,
"for": FOR,
"fun": FUN,
"get": GET,
"if": IF,
"nil": NIL,
"of": OF,
"or": OR,
"return": RETURN,
"set": SET,
"static": STATIC,
"super": SUPER,
"this": THIS,
"true": TRUE,
"var": VAR,
"while": WHILE,
}
func LookupKeyword(word string) TokenType {
if tokenType, ok := keywords[word]; ok {
return tokenType
}
return IDENTIFIER
}