Language Features

Smalltalk Semantics

Harding preserves the essence of Smalltalk:

  • Everything is an object - Numbers, strings, blocks, classes
  • Everything happens via message passing - No function calls, only messages
  • Late binding - Method lookup happens at message send time
  • Blocks with non-local returns - True lexical closures with ^ return
Message Passing
3 + 4                    # binary message
obj size                 # unary message
dict at: key put: value  # keyword message

# Blocks with non-local return
findPositive := [:arr |
    arr do: [:n |
        (n > 0) ifTrue: [^ n]
    ].
    ^ nil
]

Modern Syntax

Optional periods, hash comments, double-quoted strings:

  • Newlines separate statements (periods optional)
  • Hash # for comments
  • Double quotes for strings
  • Clean, readable code
Modern Syntax
# This is a comment
x := 1                  # No period needed
y := 2
z := x + y              # But periods work too

"Double quotes for strings"

Class-Based with Multiple Inheritance

Create classes dynamically with slots (instance variables) and methods:

  • derive: for class creation with slots (instance variables)
  • deriveWithAccessors: for automatic getter/setter generation
  • Direct instance-variable access in methods
  • Multiple inheritance with automatic conflict detection
  • Qualified and unqualified super sends
Class Definition
# Declare a temporary variable to hold an instance
| p |

# Create a new class with two slots (instance variables)
Point := Object derive: #(x y)

# Add methods using >> syntax and direct slot access
Point >> x: val [ x := val ]
Point >> y: val [ y := val ]

# Add multiple methods at a time
Point extend: [
    self >> moveBy: dx and: dy [
        x := x + dx.
        y := y + dy
    ]
    self >> distanceFromOrigin [
        ^ ((x * x) + (y * y)) sqrt
    ]
]

# Create and use a Point
p := Point new
p x: 100; y: 200
p distanceFromOrigin println   # 223.6068...

Multiple Inheritance

Multi-Parent Classes

Inherit from multiple parents with conflict detection:

  • Use derive: to create a subclass
  • Use addSuperclass: to add additional parents
  • Automatic conflict detection
  • Flexible mixin patterns
Multiple Inheritance
# Inherit from multiple parents
ColoredPoint := Point derive: #(color)
ColoredPoint addSuperclass: Comparable
ColoredPoint addSuperclass: Printable

# ColoredPoint now has methods from:
# - Point (distanceFromOrigin, etc)
# - Comparable (>, <, =, etc)
# - Printable (asString, print, etc)

Automatic Accessors

Create classes with auto-generated getters and setters:

  • deriveWithAccessors: - Generate getters and setters for all slots (instance variables)
  • Getter: variableName returns the current value
  • Setter: variableName: updates the value

Selective Accessors

For complete control over which slots (instance variables) get accessors:

  • derive:getters:setters: - Specify which slots get getters/setters
  • Generate getters for both, setter only for specific slots

Conflict Detection

When multiple parents define the same method, Harding detects conflicts at class definition time. Override conflicting methods in the child class to resolve.

Accessors and Conflict Resolution
# Automatic accessors
Person := Object deriveWithAccessors: #(name age)
p := Person new
p name: "Alice"    # Auto-generated setter
p age: 30
p name             # Auto-generated getter

# Selective accessors
Account := Object derive: #(balance owner)
                       getters: #(balance owner)
                       setters: #(balance)

# Conflict detection
Parent1 := Object derive: #(a)
Parent1 >> foo [ ^ "foo1" ]

Parent2 := Object derive: #(b)
Parent2 >> foo [ ^ "foo2" ]

# Override first, then add parents
Child := Object derive: #(x)
Child >> foo [ ^ "child" ]
Child addSuperclass: Parent1
Child addSuperclass: Parent2

Mixins

Mixin is a state-free class designed for behavior composition. Mixins derive from Root (sibling to Object) and avoid the diamond problem since they carry no slots (instance variables):

Built-in Mixins

Mixin Requires Provides
Comparable compareTo: <, <=, >, >=, between:and:, min:, max:
Iterable do: collect:, select:, reject:, detect:, inject:into:
Printable printOn: printString, print, printCr
Synchronizable critical:, acquire, release
Mixins
# Create a mixin
Comparable := Mixin derive.
Comparable >> < other [ ^ (self compareTo: other) < 0 ]
Comparable >> > other [ ^ (self compareTo: other) > 0 ]
Comparable >> between: min and: max [
    ^ (self >= min) and: [ self <= max ]
]

# Add mixin to any class
Point := Object derive: #(x y)
Point addSuperclass: Comparable
Point >> compareTo: other [
    ^ ((x * x) + (y * y)) - ((other x * other x) + (other y * other y))
]
# Now Point supports <, >, between:and:, etc.

Advanced Features

Smalltalk-Style Exception Handling

Harding implements Smalltalk-style resumable exceptions with full signal point preservation. Unlike traditional exception handling that unwinds the stack, Harding's exceptions can be resumed, retried, or delegated.

Exception Actions

Message Effect
ex resume Restore signal point, signal returns nil
ex resume: value Restore signal point, signal returns the given value
ex retry Re-execute the protected block from the beginning
ex pass Delegate to the next outer matching handler
ex return: value Return value from the on:do: expression
Exception Handling
# Basic exception handling
result := [
    riskyOperation value
] on: Error do: [:ex |
    Transcript showCr: "Caught: " + ex message.
    ex resume: 42  # Resume with a value
]

# Resume execution
result := [
    10 // 0
] on: DivisionByZero do: [:ex |
    ex resume: 0
]

# Retry the operation
[
    attemptOperation
] on: RetryableError do: [:ex |
    fixTheIssue.
    ex retry
]

# Arithmetic exceptions
result := [ 10 // 0 ] on: DivisionByZero do: [:ex |
    ex resume: 0  # Return 0 instead
]

Arithmetic Exceptions

Division by zero signals a DivisionByZero exception that can be caught and resumed:

  • // (integer division) - Signals on zero divisor
  • / (float division) - Signals on zero divisor
  • % (modulo) - Signals on zero divisor

Handlers can resume with a default value to continue execution.

DivisionByZero
# Integer division by zero
result := [ 10 // 0 ] on: DivisionByZero do: [:ex |
    ex resume: 0
]
# result is 0

# Float division by zero
result := [ 10.0 / 0.0 ] on: DivisionByZero do: [:ex |
    "Cannot divide!" println.
    ex resume: 42
]
# result is 42

Primitives

Direct access to VM-level operations and Nim interop:

  • primitiveClone - Shallow copy
  • primitiveAt: - Access element
  • primitiveAt:put: - Set element
  • primitiveIdentity: - Object identity
  • primitiveCCall:with: - Call C libraries
Primitive Syntax
# Basic primitive syntax
Array>>at: index <primitive primitiveAt: index>

# With validation wrapper
Array>>at: index [
    (index < 1 or: [index > self size]) ifTrue: [
        self error: "Index out of bounds"
    ].
    ^ <primitive primitiveAt: index>
]

Super Sends

Both unqualified and qualified super sends for method overriding:

  • super methodName - Unqualified super
  • super<ParentClass> methodName - Qualified super

Qualified super is essential when using multiple inheritance to specify which parent's implementation to call.

Super Sends
# Unqualified super
ColoredRectangle>>area [
    baseArea := super area.
    ^ baseArea + colorAdjustment
]

# Qualified super (for multiple inheritance)
C>>foo [
    ^ super<A> foo, " from C"
]

C>>bar [
    ^ super<B> bar, " from C"
]

Class-Side Methods

Define methods on the class itself using class>> syntax:

  • Analogous to static methods in other languages
  • Classes are first-class objects
  • Alternative: use selector:put: on the class
Class-Side Methods
# Instance method
Person>>greet [
    ^ "Hello, I am " + name
]

# Class-side method
Person class>>newNamed: n aged: a [
    | p |
    p := self new.
    p name: n.
    p age: a.
    ^ p
]

# Usage
alice := Person newNamed: "Alice" aged: 30

Dynamic Dispatch & Introspection

Send messages and inspect objects at runtime:

  • perform: - Dynamic message send
  • perform:with: - With one argument
  • perform:with:with: - With two arguments
  • superclassNames - Get inheritance chain
  • class, slotNames - Object inspection
Reflection
# Dynamic dispatch
obj perform: #description
obj perform: #at: with: 5
obj perform: #at:put: with: 5 with: 'value'

# Introspection
Point superclassNames    # #(Object)
obj class               # Get object's class
obj slotNames           # List instance variables
obj respondsTo: #do:    # Check method existence

Runtime Features

System and File I/O

Harding includes practical process/file utilities in the stdlib:

  • System arguments for CLI/runtime arguments
  • System cwd, System stdin, System stdout, System stderr
  • File class convenience messages (readAll:, write:to:, append:to:, exists:)
  • FileStream for stream-style open/read/write
File and System Helpers
# File helpers
content := File readAll: "README.md"
File write: content to: "README.copy.md"

# Process info and stdio
System stdout writeline: ("args: " , (System arguments size) asString)

Green Threads

Cooperative multitasking with first-class Process objects:

worker := Processor fork: [
    1 to: 10 do: [:i |
        i println
        Processor yield
    ]
]

worker suspend
worker resume
worker terminate

Process States: ready, running, blocked, suspended, terminated

Scheduler: Processor yield, Scheduler listProcesses, process priority:

Synchronization Primitives

Coordinate between green processes using built-in primitives:

# Monitor - mutual exclusion
monitor := Monitor new
monitor critical: [
    sharedResource := sharedResource + 1
]

# SharedQueue - producer/consumer
queue := SharedQueue new
queue nextPut: "item"     # Producer
item := queue next        # Consumer (blocks if empty)

# Semaphore - counting/binary locks
sem := Semaphore forMutualExclusion
sem wait
sem signal

Stackless VM

Each Process runs in its own stackless VM, enabling lightweight processes and a path to true parallelism.

Live REPL

Interactive development with immediate feedback:

$ harding
Harding REPL (:help for commands, :quit to exit)
harding> 3 + 4
7

harding> "Hello, World!" println
Hello, World!

harding> numbers := #(1 2 3 4 5)
#(1 2 3 4 5)

harding> numbers collect: [:n | n * n]
#(1 4 9 16 25)

Collections

Rich Collection Library

Arrays, Tables, and all the classic Smalltalk collection methods:

  • Array - Ordered collection #(1 2 3)
  • Table - Dictionary #{"key" -> "value"}
  • Iteration - do:, collect:, select:
  • Reduction - inject:into:, detect:
Collections
# Array literal
numbers := #(1 2 3 4 5)

# Table literal
scores := #{"Alice" -> 95, "Bob" -> 87}

# Collection methods
numbers do: [:n | n println]
squares := numbers collect: [:n | n * n]
evens := numbers select: [:n | (n % 2) = 0]
sum := numbers inject: 0 into: [:acc :n | acc + n]

Interoperability

Nim Integration

Call Nim code directly via primitives. Access the entire Nim ecosystem including libraries, packages, and system APIs.

Native FFI Fields

Objects can hold references to Nim values:

  • isNimProxy - True if object wraps a Nim value
  • hardingType - Get the Harding type name
  • nimValue - Access underlying Nim value
Nim Interop
# Access Nim functions via primitives
Array>>at: index <primitive primitiveAt: index>

# Objects can wrap Nim values
obj isNimProxy      # true if wraps Nim value
obj hardingType     # Get Harding type name
obj nimValue        # Access Nim value

# C library access via Nim FFI
<primitive primitiveCCall: function with: args>

Nim-Harding Package Model

Harding supports packaging Nim primitives and Harding source files together:

  • Package .hrd files are embedded and loaded through the same load: message
  • Nim primitive registration is bundled with package install
  • One package version controls both Nim implementation and Harding API surface

For a full walkthrough, see the Nim Package Tutorial.

Compilation

Granite Compiler

Compile Harding code to native binaries via Nim → C:

  • Standalone binaries - No runtime dependencies
  • Native performance - True compiled speed
  • Full Nim ecosystem - Access all Nim libraries
  • Cross-platform - Leverage Nim's cross-compilation

Usage

granite compile program.hrd -o program
granite build program.hrd --release
granite run program.hrd
Compilation Pipeline
Harding source (.hrd)
       ↓
   Parser (AST)
       ↓
   Granite Compiler
       ↓
   Nim source (.nim)
       ↓
   C Compiler
       ↓
   Machine code
       ↓
   Native binary

# Compile to native binary
$ granite compile app.hrd -o myapp

# Result: standalone executable
$ ./myapp

Debugging Tools

Log Levels

Control verbosity of execution output:

harding --loglevel DEBUG script.hrd

Levels: DEBUG, INFO, WARN, ERROR, FATAL

DEBUG shows AST evaluation, message sends, method lookups, and activation stack operations.

AST Output

View the parsed Abstract Syntax Tree:

harding --ast script.hrd

Shows the hierarchical structure of parsed expressions before execution.

Process Inspection

Inspect running processes:

Scheduler listProcesses
process state      # ready, running, etc.
process pid        # Unique process ID
process name       # Process name
process priority   # Scheduling priority

Current Status: v0.7.0

Implemented

  • Functional interpreter with green threads and synchronization primitives
  • MIC/PIC method caching for performance
  • Smalltalk-style resumable exceptions with signal point preservation
  • GTK IDE (Bona) with Launcher, Workspace, and Transcript
  • VSCode extension with full LSP and DAP support
  • Granite compiler for native binary compilation
  • BitBarrel integration for persistent storage (optional)
  • Automatic accessor generation (deriveWithAccessors:)
  • Collections: Array, Table, Set, Interval, SortedCollection
  • System/File I/O stdlib (System, File, FileStream, stdio globals)
  • CLI/runtime arg forwarding via System arguments (including harding ... -- args)
  • Nim-Harding package model for bundled primitives + embedded .hrd sources

In Progress

  • Actor-based shared-nothing concurrency
  • Enhanced standard library
  • Bona System Browser and Inspector
  • Bona Debugger