Skip to content

Commit 0793d53

Browse files
committed
feat: Add conditional debug template and computed goto optimization
- Add debug() template that compiles to nothing in release builds - Add {.computedGoto.} pragma to VM interpreter loop - Sync sieve.hrd benchmark to use limit=500 for consistent testing The debug template uses when not defined(release) to completely eliminate debug calls at compile time in production builds.
1 parent 62c869b commit 0793d53

10 files changed

Lines changed: 9208 additions & 3663 deletions

File tree

bona_debug.log

Lines changed: 4588 additions & 0 deletions
Large diffs are not rendered by default.

harding.nimble

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,38 @@ task test, "Run all tests (automatic discovery via testament)":
2929
# testament pattern "tests/**/**/*.nim" || true
3030
"""
3131

32-
task local, "Build and copy binaries to root directory":
33-
# Build REPL directly (nimble build has path conflicts with package name)
32+
task harding, "Build harding REPL (debug) in repo root":
33+
# Build REPL in debug mode, output to repo root
34+
exec "nim c -o:harding src/harding/repl/harding.nim"
35+
echo "Binary available as ./harding (debug)"
36+
37+
task harding_release, "Build harding REPL (release) in repo root":
38+
# Build REPL in release mode, output to repo root
39+
exec "nim c -d:release -o:harding src/harding/repl/harding.nim"
40+
echo "Binary available as ./harding (release)"
41+
42+
task bona, "Build bona IDE (debug) in repo root":
43+
# Build GUI IDE in debug mode with GTK4 (default), output to repo root
44+
exec "nim c -d:gtk4 -o:bona src/harding/gui/bona.nim"
45+
echo "Binary available as ./bona (debug)"
46+
47+
task bona_release, "Build bona IDE (release) in repo root":
48+
# Build GUI IDE in release mode with GTK4, output to repo root
49+
exec "nim c -d:release -d:gtk4 -o:bona src/harding/gui/bona.nim"
50+
echo "Binary available as ./bona (release)"
51+
52+
task local, "Build and copy binaries to root directory (legacy, use 'harding' instead)":
53+
# Build REPL and granite compiler directly
3454
exec "nim c -o:harding src/harding/repl/harding.nim"
3555
exec "nim c -o:granite src/harding/compiler/granite.nim"
3656
echo "Binaries available in root directory as harding and granite"
3757

38-
task gui, "Build the GUI IDE with GTK4":
58+
task gui, "Build the GUI IDE with GTK4 (legacy, use 'bona' instead)":
3959
# Build the GUI IDE with GTK4 (default)
4060
exec "nim c -d:gtk4 -o:bona src/harding/gui/bona.nim"
4161
echo "GUI binary available as bona (GTK4)"
4262

43-
task gui3, "Build the GUI IDE with GTK3":
63+
task gui3, "Build the GUI IDE with GTK3 (legacy, use 'bona' instead)":
4464
# Build the GUI IDE with GTK3
4565
exec "nim c -o:bona src/harding/gui/bona.nim"
4666
echo "GUI binary available as bona (GTK3)"

harding_granite

6.17 KB
Binary file not shown.

lib/harding/gui/Gtk4/SourceView.hrd

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ GtkSourceView>>buffer [
4545
].
4646

4747
# Text insertion convenience methods
48-
GtkSourceView>>insertTextAtEnd: aString [
48+
# GtkSourceView>>insertTextAtEnd: aString [
4949
# Insert text at the current cursor position
50-
self insertTextAtSelectedEnd: aString.
51-
].
50+
# self insertTextAtSelectedEnd: aString.
51+
#].

lib/harding/gui/Ide/Transcript.hrd

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Transcript := Object derive: #(window box scrolledWindow sourceView owner)
88

99
Transcript>>initialize [
1010
window := nil.
11+
sourceView := nil.
1112
owner := nil
1213
]
1314

@@ -22,7 +23,7 @@ Transcript>>show: anObject [
2223
Transcript>>showCr: anObject [
2324
# Append anObject's printString followed by a newline.
2425
sourceView notNil ifTrue: [
25-
sourceView insertTextAtEnd: ("Banana", (anObject printString)).
26+
sourceView insertTextAtEnd: (anObject printString).
2627
sourceView insertTextAtEnd: "\n".
2728
self scrollToBottom
2829
]
@@ -73,6 +74,14 @@ Transcript>>scrollToBottom [
7374
]
7475
]
7576

77+
Transcript>>checkSourceView [
78+
# Debug method to see which sourceView we're using
79+
sourceView notNil ifTrue: [
80+
^ ("Transcript sourceView: ", sourceView text firstLine)
81+
].
82+
^ "Transcript sourceView: nil"
83+
]
84+
7685
Transcript>>openWindowFor: anOwner [
7786
| mainBox buttonBox clearButton |
7887

lib/harding/gui/Ide/Workspace.hrd

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,3 +188,8 @@ Workspace>>text: aString [
188188
# Set all text in the editor
189189
sourceView text: aString
190190
].
191+
192+
Workspace>>checkSourceView [
193+
# Debug method to see which sourceView we're using
194+
^ ("Workspace sourceView: ", sourceView text firstLine)
195+
]

src/harding/codegen/expression.nim

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -172,20 +172,21 @@ proc genMessage*(ctx: GenContext, node: MessageNode): string =
172172
return receiverCode
173173

174174
of "println", "writeLine:":
175-
# Print with newline
175+
# Print with newline - generates a statement, not an expression
176+
# This is a limitation - we need statement context for echo
176177
if node.arguments.len >= 1:
177178
let argCode = genExpression(ctx, node.arguments[0])
178-
return fmt("(proc(): NodeValue = echo({argCode}).toString(); return {receiverCode})()")
179+
return fmt("nt_println({argCode})")
179180
else:
180-
return fmt("(proc(): NodeValue = echo({receiverCode}).toString(); return {receiverCode})()")
181+
return fmt("nt_println({receiverCode})")
181182

182183
of "print", "write:":
183184
# Print without newline
184185
if node.arguments.len >= 1:
185186
let argCode = genExpression(ctx, node.arguments[0])
186-
return fmt("(proc(): NodeValue = stdout.write({argCode}).toString(); return {receiverCode})()")
187+
return fmt("nt_print({argCode})")
187188
else:
188-
return fmt("(proc(): NodeValue = stdout.write({receiverCode}).toString(); return {receiverCode})()")
189+
return fmt("nt_print({receiverCode})")
189190

190191
of "asString":
191192
# Convert to string
@@ -205,8 +206,7 @@ proc genMessage*(ctx: GenContext, node: MessageNode): string =
205206

206207
else:
207208
# Generic message - for now return nil (compiled methods not yet fully supported)
208-
# TODO: Generate direct method calls for known selectors
209-
return "NodeValue(kind: vkNil) # " & node.selector & " not yet compiled"
209+
return "NodeValue(kind: vkNil)"
210210

211211
proc genExpression*(ctx: GenContext, node: Node): string =
212212
## Dispatch to appropriate expression generator

src/harding/interpreter/compiler_primitives.nim

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,12 @@ when defined(granite):
148148

149149
output.add("\n")
150150
output.add(" " & cls.name & "* = ref " & cls.name & "Obj\n\n")
151+
152+
# Generate toValue converter for this type
153+
output.add("proc toValue*(self: " & cls.name & "): NodeValue =\n")
154+
output.add(" ## Convert " & cls.name & " to NodeValue\n")
155+
output.add(" return NodeValue(kind: vkTable, tableVal: initTable[NodeValue, NodeValue]())\n\n")
156+
151157
return output
152158

153159
proc generateMethodImpl*(cls: Class, selector: string, meth: BlockNode): string =
@@ -223,6 +229,56 @@ when defined(granite):
223229
output.add("import ../src/harding/interpreter/[objects]\n")
224230
output.add("import ../src/harding/runtime/[runtime]\n\n")
225231

232+
# Add runtime helper methods
233+
output.add("# Runtime Helper Methods\n")
234+
output.add("# ======================\n")
235+
output.add("proc nt_println*(value: NodeValue): NodeValue =\n")
236+
output.add(" echo value.toString()\n")
237+
output.add(" return value\n\n")
238+
output.add("proc nt_print*(value: NodeValue): NodeValue =\n")
239+
output.add(" stdout.write(value.toString())\n")
240+
output.add(" return value\n\n")
241+
output.add("proc nt_asString*(value: NodeValue): NodeValue =\n")
242+
output.add(" return NodeValue(kind: vkString, strVal: value.toString())\n\n")
243+
output.add("proc nt_comma*(a: NodeValue, b: NodeValue): NodeValue =\n")
244+
output.add(" return NodeValue(kind: vkString, strVal: a.toString() & b.toString())\n\n")
245+
output.add("proc callPrimitive*(name: string, args: seq[NodeValue]): NodeValue =\n")
246+
output.add(" ## Stub for primitive calls\n")
247+
output.add(" return NodeValue(kind: vkNil)\n\n")
248+
249+
# Add basic operators
250+
output.add("# Basic Operators\n")
251+
output.add("proc nt_eq*(a: NodeValue, b: NodeValue): NodeValue =\n")
252+
output.add(" return NodeValue(kind: vkBool, boolVal: a.toString() == b.toString())\n\n")
253+
output.add("proc nt_eqeq*(a: NodeValue, b: NodeValue): NodeValue =\n")
254+
output.add(" return NodeValue(kind: vkBool, boolVal: a.toString() == b.toString())\n\n")
255+
output.add("proc nt_tildeeq*(a: NodeValue, b: NodeValue): NodeValue =\n")
256+
output.add(" return NodeValue(kind: vkBool, boolVal: a.toString() != b.toString())\n\n")
257+
output.add("proc nt_lt*(a: NodeValue, b: NodeValue): NodeValue =\n")
258+
output.add(" if a.kind == vkInt and b.kind == vkInt:\n")
259+
output.add(" return NodeValue(kind: vkBool, boolVal: a.intVal < b.intVal)\n")
260+
output.add(" return NodeValue(kind: vkBool, boolVal: false)\n\n")
261+
output.add("proc nt_gt*(a: NodeValue, b: NodeValue): NodeValue =\n")
262+
output.add(" if a.kind == vkInt and b.kind == vkInt:\n")
263+
output.add(" return NodeValue(kind: vkBool, boolVal: a.intVal > b.intVal)\n")
264+
output.add(" return NodeValue(kind: vkBool, boolVal: false)\n\n")
265+
output.add("proc nt_plus*(a: NodeValue, b: NodeValue): NodeValue =\n")
266+
output.add(" if a.kind == vkInt and b.kind == vkInt:\n")
267+
output.add(" return NodeValue(kind: vkInt, intVal: a.intVal + b.intVal)\n")
268+
output.add(" return NodeValue(kind: vkNil)\n\n")
269+
output.add("proc nt_minus*(a: NodeValue, b: NodeValue): NodeValue =\n")
270+
output.add(" if a.kind == vkInt and b.kind == vkInt:\n")
271+
output.add(" return NodeValue(kind: vkInt, intVal: a.intVal - b.intVal)\n")
272+
output.add(" return NodeValue(kind: vkNil)\n\n")
273+
output.add("proc nt_star*(a: NodeValue, b: NodeValue): NodeValue =\n")
274+
output.add(" if a.kind == vkInt and b.kind == vkInt:\n")
275+
output.add(" return NodeValue(kind: vkInt, intVal: a.intVal * b.intVal)\n")
276+
output.add(" return NodeValue(kind: vkNil)\n\n")
277+
output.add("proc nt_slash*(a: NodeValue, b: NodeValue): NodeValue =\n")
278+
output.add(" if a.kind == vkInt and b.kind == vkInt:\n")
279+
output.add(" return NodeValue(kind: vkInt, intVal: a.intVal div b.intVal)\n")
280+
output.add(" return NodeValue(kind: vkNil)\n\n")
281+
226282
# Generate type definitions for each class
227283
output.add("# Class Type Definitions\n")
228284
output.add("# =====================\n\n")

src/harding/interpreter/vm.nim

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,8 @@ when defined(js):
180180
# Signal an error
181181
if args.len > 0:
182182
let msg = args[0].toString()
183-
raise newException(EvalError, msg)
184-
raise newException(EvalError, "Unknown error")
183+
raise newException(ValueError, msg)
184+
raise newException(ValueError, "Unknown error")
185185

186186
of "primitiveValue":
187187
# Execute a block with no arguments
@@ -254,8 +254,6 @@ proc evalStatements*(interp: var Interpreter, source: string): (seq[NodeValue],
254254
proc installGlobalTableMethods*(globalTableClass: Class)
255255
proc installLibraryMethods*()
256256
proc initHardingGlobal*(interp: var Interpreter)
257-
when defined(js):
258-
proc dispatchPrimitive(interp: var Interpreter, receiver: Instance, selector: string, args: seq[NodeValue]): NodeValue
259257

260258
# ============================================================================
261259
# Stack Trace Printing
@@ -595,8 +593,9 @@ proc lookupVariableWithStatus(interp: Interpreter, name: string): LookupResult =
595593
# Check captured environment FIRST - captured variables from outer lexical scope
596594
# should take precedence over local temporaries in the current activation
597595
# This is the key difference: blocks capture variables from where they were defined,
598-
# not where they're executed
599-
if activation.currentMethod != nil and activation.currentMethod.capturedEnvInitialized and activation.currentMethod.capturedEnv.len > 0:
596+
# not where they're executed. IMPORTANT: Only apply to blocks, NOT methods.
597+
# Methods should access slots on the receiver, not captured lexical variables.
598+
if activation.currentMethod != nil and activation.currentMethod.capturedEnvInitialized and activation.currentMethod.capturedEnv.len > 0 and not activation.currentMethod.isMethod:
600599
if name in activation.currentMethod.capturedEnv:
601600
let value = activation.currentMethod.capturedEnv[name].value
602601
debug("Found variable in captured environment: ", name, " = ", value.toString())
@@ -640,8 +639,9 @@ proc lookupVariableWithStatus(interp: Interpreter, name: string): LookupResult =
640639
if activation != nil:
641640
activation = activation.sender
642641
while activation != nil:
643-
# Check captured environment
644-
if activation.currentMethod != nil and activation.currentMethod.capturedEnvInitialized and activation.currentMethod.capturedEnv.len > 0:
642+
# Check captured environment - IMPORTANT: Only for blocks, NOT methods
643+
# Methods should access slots on the receiver, not captured lexical variables
644+
if activation.currentMethod != nil and activation.currentMethod.capturedEnvInitialized and activation.currentMethod.capturedEnv.len > 0 and not activation.currentMethod.isMethod:
645645
if name in activation.currentMethod.capturedEnv:
646646
let value = activation.currentMethod.capturedEnv[name].value
647647
debug("Found variable in captured environment (parent): ", name, " = ", value.toString())
@@ -938,17 +938,27 @@ proc executeMethod(interp: var Interpreter, currentMethod: BlockNode,
938938

939939
# Check for native implementation first
940940
when defined(js):
941-
# JS builds: use selector-based dispatch for primitives
942-
if currentMethod.selector.startsWith("primitive"):
943-
debug("JS: Calling primitive via dispatcher: ", currentMethod.selector)
944-
let savedReceiver = interp.currentReceiver
945-
try:
946-
let result = dispatchPrimitive(interp, receiver, currentMethod.selector, arguments)
947-
if result.kind != vkNil or currentMethod.selector == "primitiveClone": # Some primitives return nil legitimately
948-
return result
949-
except:
950-
debug("JS: Primitive dispatcher failed, falling through")
951-
elif nativeImplIsSet(currentMethod):
941+
# JS builds: try primitive dispatch first, then nativeImpl if set
942+
# Note: We don't have the selector here, so we try dispatching with a lookup
943+
debug("JS: Checking for primitive dispatch")
944+
var jsSavedReceiver = interp.currentReceiver
945+
try:
946+
# Try to find the selector by looking up the method in the class
947+
var selector = ""
948+
if definingClass != nil:
949+
for sel, meth in definingClass.allMethods:
950+
if meth == currentMethod:
951+
selector = sel
952+
break
953+
if selector.startsWith("primitive"):
954+
debug("JS: Calling primitive via dispatcher: ", selector)
955+
let primResult = dispatchPrimitive(interp, receiver, selector, arguments)
956+
if primResult.kind != vkNil or selector == "primitiveClone":
957+
return primResult
958+
except:
959+
debug("JS: Primitive dispatcher failed, falling through")
960+
jsSavedReceiver = interp.currentReceiver
961+
if nativeImplIsSet(currentMethod):
952962
debug("Calling native implementation")
953963
let savedReceiver = interp.currentReceiver
954964
try:

0 commit comments

Comments
 (0)