This document describes the "bootstrap Harding" - the absolute minimum that is hard-coded into the VM to allow it to parse and load the standard library (.hrd files).
Harding uses a two-phase bootstrap process:
- Nim Bootstrap Phase: VM initialization creates core classes and registers essential methods
- Stdlib Loading Phase: Bootstrap.hrd is evaluated, defining methods using primitive syntax
The key insight is that only what's absolutely needed for the VM to start and parse .hrd files is hard-coded in Nim. Everything else is defined in Harding code (.hrd files) using the primitive syntax.
The initCoreClasses() procedure in src/harding/interpreter/objects.nim creates the core class hierarchy:
Root (empty - for DNU proxies/wrappers)
├── Object (core methods)
│ ├── Integer
│ ├── Float
│ ├── String
│ ├── Array
│ ├── Table
│ ├── Block
│ ├── Boolean (parent for True and False)
│ ├── Library
│ └── Set
│
└── Mixin (slotless - for behavior composition)
├── Comparable
├── Equatable
├── Iterable
└── Printable
| Class | Purpose | Location of Methods |
|---|---|---|
Root |
Empty base class for DNU proxies/wrappers | None |
Object |
Root of all user-defined classes | lib/core/Object.hrd |
Integer |
Integer numbers | lib/core/Integer.hrd |
Float |
Floating-point numbers | lib/core/Float.hrd |
String |
String values | lib/core/String.hrd |
Array |
Ordered collections | lib/core/Array.hrd |
Table |
Hash-based dictionaries | lib/core/Table.hrd |
Block |
Closures/anonymous functions | lib/core/Block.hrd |
Boolean |
Base class for true/false | lib/core/Boolean.hrd |
Library |
FFI library bindings | various .hrd files |
Set |
Unordered collections | lib/core/Set.hrd |
Symbol |
Interned strings | lib/core/Symbol.hrd |
These are the methods that MUST be defined in Nim because they're needed before .hrd files can be loaded:
| Selector | Purpose | Why Bootstrap? |
|---|---|---|
selector:put: |
Define instance method (used by >> syntax) |
Needed to parse method definitions in .hrd files |
classSelector:put: |
Define class method (used by class>> syntax) |
Needed to parse class method definitions |
derive: |
Create subclass with slots | Needed to define new classes |
derivePublic: |
Create subclass with slots + accessors | Needed to define classes with convenient API |
derive:read:write: |
Create subclass with selective accessors | Needed for classes with read-only or write-only slots |
derive |
Create subclass without slots | Needed to define new classes |
new |
Create instance | Needed before .hrd files can define initialization |
basicNew |
Core object creation primitive | Underlying implementation for new |
| Selector | Purpose | Location |
|---|---|---|
load: |
Load and evaluate a .hrd file (filesystem or embedded package source) |
vm.nim - required to load stdlib |
import: |
Import a foreign library | vm.nim - FFI support |
eval: |
Evaluate Harding code string | vm.nim - REPL support |
| Selector | Purpose | Location |
|---|---|---|
primitiveClone |
Clone an instance | objects.nim, registerPrimitivesOnObjectClass() |
Note: The above primitive selector is registered so that the declarative primitive syntax can work via standard method lookup.
It's important to understand the distinction:
- Required for VM to start and parse
.hrdfiles - Cannot be defined in
.hrdfiles (chicken-and-egg problem) - Registered directly on classes in
objects.nimandvm.nim
- Not required, but provide efficient implementations
- Defined in
.hrdfiles using<primitive>syntax - The Nim-registered primitive selector (
primitivePlus:,primitiveMinus:, etc.) is what actually executes - Methods like
+,-,*call these primitive selectors
# In Integer.hrd:
Integer>>+ other <primitive primitivePlus: other>
# What happens when evaluating "3 + 4":
1. Parser creates MessageNode for "+" with arg "4"
2. VM looks up method "+" on Integer class
3. Returns method from Integer.hrd (a BlockNode with primitive selector)
4. VM executes primitive by looking up `primitivePlus:` selector
5. Finds Nim implementation in `integerClass["primitivePlus:"]`
6. Calls the native implementation directly
The lib/core/Bootstrap.hrd file loads core, standard, and granite layers in order:
Harding load: "lib/core/Object.hrd"
Harding load: "lib/core/Boolean.hrd"
Harding load: "lib/core/True.hrd"
Harding load: "lib/core/False.hrd"
Harding load: "lib/core/UndefinedObject.hrd"
Harding load: "lib/core/Block.hrd"
Harding load: "lib/core/Library.hrd"
Harding load: "lib/core/Number.hrd"
Harding load: "lib/core/Integer.hrd"
Harding load: "lib/core/Float.hrd"
Harding load: "lib/core/String.hrd"
Harding load: "lib/core/Symbol.hrd"
Harding load: "lib/core/Array.hrd"
Harding load: "lib/core/Table.hrd"
Harding load: "lib/core/Set.hrd"
Harding load: "lib/core/System.hrd"
Harding load: "lib/core/Exception.hrd"
Harding load: "lib/core/Error.hrd"
Harding load: "lib/core/Notification.hrd"
Harding load: "lib/core/MessageNotUnderstood.hrd"
Harding load: "lib/core/SubscriptOutOfBounds.hrd"
Harding load: "lib/core/DivisionByZero.hrd"
Harding load: "lib/core/UnhandledError.hrd"
Harding load: "lib/core/Comparable.hrd"
Harding load: "lib/core/Equatable.hrd"
Harding load: "lib/core/Iterable.hrd"
Harding load: "lib/core/Printable.hrd"
Harding load: "lib/process/Bootstrap.hrd"
Harding load: "lib/standard/Bootstrap.hrd"
Harding load: "lib/granite/Bootstrap.hrd"
Loading order matters because:
Object.hrdmust define the base class methods before subclasses can inheritNumber.hrddefines shared numeric behavior beforeIntegerandFloat- Primitives must be registered in
vm.nimBEFORE the.hrdfile that uses them is loaded
The same load: mechanism is also used for packaged libraries that provide embedded .hrd sources (via HardingPackageSpec).
The initGlobals() procedure in src/harding/interpreter/vm.nim registers primitive selectors:
# Integer arithmetic primitives
let intPlusMethod = createCoreMethod("primitivePlus:")
intPlusMethod.setNativeImpl(plusImpl)
intCls.methods["primitivePlus:"] = intPlusMethodThese registrations happen after class creation but during VM initialization, so when .hrd files are loaded, the primitive selectors are available.
| Category | Location | Count | Example |
|---|---|---|---|
| Bootstrap Methods | objects.nim, vm.nim |
~10 | selector:put:, new, load: |
| Primitive Selectors | Registered in vm.nim, used by .hrd |
~70 | primitivePlus:, primitiveStringSize |
| User-Facing Methods | .hrd files |
~200 | +, -, printString, size, at:put: |
The user-facing methods (what users actually call) are defined in .hrd files. The primitive selectors are the backing implementations.
- Creates the class objects (Class instances)
- Sets up the class hierarchy
- Registers bootstrap methods needed to define other classes/methods
- Registers a few essential primitives (
primitiveClone) - Called once at VM startup
- Registers primitive selectors for efficient implementations
- Sets up global values (true, false, nil)
- Registers I/O methods (print, println)
- Registers special operators (backslash \ for modulo)
- Can be called to re-initialize globals for testing
When adding new features:
- If it requires loading
.hrdfiles: Define primitive selector invm.nim, use in.hrdfiles - If it's needed for bootstrapping: Define bootstrap method in
objects.nimorvm.nim - If it's just new functionality: Define method directly in
.hrdfile using primitive syntax or Harding code
The bootstrap Harding is minimal by design:
- ~10 bootstrap methods in Nim (required to start and parse)
- ~10 core classes created in Nim (Object, Integer, Float, String, Array, Table, Block, Boolean, Library, Set)
- ~70 primitive selectors registered in Nim (efficient implementations)
- ~200 user-facing methods defined in
.hrdfiles (what users actually call)
This provides a clean separation:
- Nim code: Foundation mechanism (bootstrapping and performance-critical primitives)
- Harding code (
.hrd): Language definition and user-facing API