Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -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.
86 changes: 85 additions & 1 deletion evaluator/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ func TestClassProperties(t *testing.T) {
function constructor(area) {
this.area = area
}

function area() {
return math.pi * this.area * this.area
}
Expand All @@ -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

Expand Down
4 changes: 2 additions & 2 deletions evaluator/method.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion evaluator/use.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}