diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..eb9cacc --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,46 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Build and Development Commands + +```bash +make run # Build and run Ghost REPL +make build # Build for all platforms (mac/linux/windows) +make test # Run all tests with colored output +go test -v ./evaluator/... # Run tests for a specific package +``` + +## Architecture Overview + +Ghost is a tree-walking interpreter written in Go. The execution pipeline follows this flow: + +**Source Code → Scanner → Parser → AST → Evaluator → Object** + +### Core Packages + +- **scanner/** - Lexical analysis. Transforms source into tokens. Keywords defined in `scanner/scanner.go:21-48`. +- **token/** - Token type definitions and the Token struct containing lexeme, literal, position info. +- **parser/** - Recursive descent parser using Pratt parsing. Each AST node type has its own parsing function (e.g., `parser/function.go`, `parser/if.go`). +- **ast/** - Abstract syntax tree node definitions. Base interfaces in `ast/ast.go`: `Node`, `StatementNode`, `ExpressionNode`. +- **evaluator/** - Tree-walking evaluation. Main entry point is `Evaluate()` in `evaluator/evaluator.go:15`. Each AST node type has a corresponding `evaluate*` function. +- **object/** - Runtime value types (Number, String, Boolean, List, Map, Function, Class, etc.). The `Object` interface (`object/object.go:15-19`) requires `Type()`, `String()`, and `Method()`. +- **ghost/** - Main Ghost struct that orchestrates the pipeline (`ghost/ghost.go`). Entry point for embedding Ghost in Go applications. + +### Key Design Patterns + +- **Scope**: Wraps Environment and tracks `Self` for method calls (`object/scope.go`). +- **Environment**: Variable storage with parent chain for lexical scoping (`object/environment.go`). +- **Library system**: Native functions and modules registered via `library.RegisterFunction()` and `library.RegisterModule()`. Built-in modules in `library/modules/`. + +### Object Method System + +All object types implement the `Method(method string, args []Object) (Object, bool)` interface. Methods are defined directly on object types (e.g., string methods in `object/string.go`). + +## Language Features + +Ghost supports: classes with inheritance (`extends`), traits (`trait`/`use`), first-class functions, closures, lists, maps, for/for-in/while loops, switch statements, imports, and compound operators (`+=`, `++`, etc.). + +## Version + +Update `version/version.go` when releasing. GoReleaser handles binary distribution. diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index e8dcbf0..e9c905a 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -207,7 +207,7 @@ func TestClassProperties(t *testing.T) { function constructor(area) { this.area = area } - + function area() { return math.pi * this.area * this.area } @@ -223,6 +223,90 @@ func TestClassProperties(t *testing.T) { isNumberObject(t, result, 314) } +func TestTraitMethodLookup(t *testing.T) { + tests := []struct { + name string + input string + expected int64 + }{ + { + name: "method from single trait", + input: ` + trait Greet { + function greet() { + return 1 + } + } + + class Person { + use Greet + } + + p = Person.new() + p.greet() + `, + expected: 1, + }, + { + name: "method from second trait", + input: ` + trait First { + function first() { + return 1 + } + } + + trait Second { + function second() { + return 2 + } + } + + class Thing { + use First + use Second + } + + t = Thing.new() + t.second() + `, + expected: 2, + }, + { + name: "methods from both traits", + input: ` + trait Add { + function add() { + return 10 + } + } + + trait Multiply { + function multiply() { + return 20 + } + } + + class Calculator { + use Add + use Multiply + } + + c = Calculator.new() + c.add() + c.multiply() + `, + expected: 30, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := evaluate(tt.input) + isNumberObject(t, result, tt.expected) + }) + } +} + // ============================================================================= // Helper functions diff --git a/evaluator/method.go b/evaluator/method.go index 2894d4a..5899258 100644 --- a/evaluator/method.go +++ b/evaluator/method.go @@ -79,8 +79,8 @@ func evaluateInstanceMethod(node *ast.Method, receiver *object.Instance, name st for _, trait := range receiver.Class.Traits { method, ok = trait.Environment.Get(name) - if !ok { - return object.NewError("%d:%d:%s: runtime error: undefined method %s for class %s", node.Token.Line, node.Token.Column, node.Token.File, name, receiver.Class.Name.Value) + if ok { + break } } } diff --git a/evaluator/use.go b/evaluator/use.go index 4d5c0eb..458a3b4 100644 --- a/evaluator/use.go +++ b/evaluator/use.go @@ -31,7 +31,7 @@ func evaluateUse(node *ast.Use, scope *object.Scope) object.Object { traits = append(traits, t) } - class.Traits = traits + class.Traits = append(class.Traits, traits...) return nil }