Skip to content

Commit 0c2c3f9

Browse files
thejhhJaakko Heusala
andauthored
Feature/code primitive (#133)
* Added initial documentation for `code` primitive * Initial code primitive * Fixed unit tests --------- Co-authored-by: Jaakko Heusala <jhh@hg.fi>
1 parent f5729ca commit 0c2c3f9

File tree

12 files changed

+606
-33
lines changed

12 files changed

+606
-33
lines changed

cmd/gnd/main.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ package main
33
import (
44
"flag"
55
"fmt"
6-
"github.com/hyperifyio/gnd/pkg/primitive"
76
"os"
87
"path/filepath"
98

9+
"github.com/hyperifyio/gnd/pkg/primitive"
10+
1011
"github.com/hyperifyio/gnd/pkg/core"
1112
"github.com/hyperifyio/gnd/pkg/log"
1213
"github.com/hyperifyio/gnd/pkg/parsers"
@@ -31,6 +32,7 @@ var DefaultOpcodeMap = map[string]string{
3132
"debug": "/gnd/debug",
3233
"exit": "/gnd/exit",
3334
"return": "/gnd/return",
35+
"code": "/gnd/code",
3436
}
3537

3638
func printHelp() {

docs/code-syntax.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
The `code` operation produces an instruction array that Gendo can treat as
2+
ordinary data. Called with no arguments, it yields the instruction array of the
3+
routine that is currently executing. Called with one or more targets, it
4+
resolves each target's instructions in left-to-right order and returns a single
5+
array containing all of them, without altering the originals.
6+
7+
The syntax of the `code` operation is:
8+
9+
```
10+
[ $destination ] code [ target1 target2 ... ]
11+
```
12+
13+
The `$destination` is optional. If omitted, the result is assigned to the
14+
special slot `_`. When **no** target is supplied, the current routine's
15+
instructions are returned. When **one or more** targets are supplied, each
16+
target must be one of:
17+
18+
* `@` - represents the current routine's instructions
19+
* a string literal ending in `.gnd` – the file is loaded (and compiled if necessary);
20+
* an opcode identifier – returns a one-instruction array for that primitive;
21+
* a `$variable` already bound to a routine value.
22+
23+
Targets are resolved independently; the final result is a new instruction array
24+
consisting of the instructions from `target1`, followed by those of `target2`,
25+
and so on.
26+
27+
Examples
28+
29+
Return the current routine's code, compile it, and run in parallel:
30+
31+
```
32+
$compiled compile "debug hello world" # compile our own instructions
33+
$task async $compiled # run in background
34+
await _ $task # wait and get result
35+
```
36+
37+
Merge two external files and execute once:
38+
39+
```
40+
$math code math
41+
$string code string
42+
$all code $math $string # concatenate in this order
43+
_exec exec $all
44+
```
45+
46+
Combine a primitive with a helper routine and inspect length:
47+
48+
```
49+
$addOp code add
50+
$utils code helpers
51+
$merged code $addOp $utils
52+
$count len $merged
53+
```
54+
55+
Combine current routine with a helper routine:
56+
57+
```
58+
$current code @ # get current routine's instructions
59+
$utils code helpers # get helper routine's instructions
60+
$merged code @ $utils # combine current routine with helpers
61+
$count len $merged # get total instruction count
62+
```
63+
64+
Errors
65+
66+
* If any file target cannot be loaded or compiled, `code` raises an error.
67+
* If a variable target is unbound or not a routine value, an error is raised.
68+
* The operation never mutates its inputs; it always returns a fresh instruction array.
69+
70+
The returned array is immutable and can be stored, passed, compiled, or
71+
executed by other operations such as `compile`, `exec`, `async`, or further
72+
`code` concatenations.

examples/code-example.gnd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
code normalize

examples/llm.gnd

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
$args let
22
debug "Our input is:" $args
3-
return "This is test"
43
$persona let "You are a helpful assistant.\n---\n"
54
$instruction let "\n---\nIs the previous input acceptable? Reply 'true' or 'false'."
65
$fullPrompt concat $persona $args $instruction

pkg/core/interpreter.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,7 @@ type Interpreter interface {
2828

2929
// GetSlot gets a slot value
3030
GetSlot(name string) (interface{}, error)
31+
32+
// GetSubroutineInstructions retrieves the instructions for a subroutine
33+
GetSubroutineInstructions(path string) ([]*parsers.Instruction, error)
3134
}

pkg/core/interpreter_impl.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,15 +142,31 @@ func (i *InterpreterImpl) ExecuteInstructionBlock(source string, input interface
142142
lastResult := input
143143
for idx, op := range instructions {
144144
if op != nil {
145+
145146
i.LogDebug("[%s:%d]: ExecuteInstructionBlock: %v <- %s %v", source, idx, op.Destination, op.Opcode, op.Arguments)
147+
146148
result, err := i.ExecuteInstruction(op.Opcode, op.Destination, op.Arguments)
149+
147150
if err != nil {
148151
if returnValue, ok := primitive.GetReturnValue(err); ok {
149152
i.LogDebug("[%s:%d]: ExecuteInstructionBlock: return by ReturnValue: %v", source, idx, returnValue.Value)
150153
return returnValue.Value, nil
151154
}
152155
return nil, fmt.Errorf("\n %s:%d: %v", source, idx, err)
153156
}
157+
158+
if codeResult, ok := primitive.GetCodeResult(result); ok {
159+
codeInstructions, err := i.HandleCodeResult(source, codeResult, instructions)
160+
if err != nil {
161+
return nil, err
162+
}
163+
164+
// Store the codeInstructions in the destination slot
165+
i.LogDebug("[%s]: ExecutePrimitive: storing %d codeInstructions in %s", source, len(codeInstructions), op.Destination.Name)
166+
i.Slots[op.Destination.Name] = codeInstructions
167+
return codeInstructions, nil
168+
}
169+
154170
lastResult = result
155171
}
156172
}
@@ -257,13 +273,74 @@ func (i *InterpreterImpl) LoadArguments(source string, arguments []interface{})
257273
return resolvedArgs, nil
258274
}
259275

276+
// HandleCodeResult processes a CodeResult and returns the concatenated instructions
277+
func (i *InterpreterImpl) HandleCodeResult(source string, codeResult *primitive.CodeResult, block []*parsers.Instruction) ([]*parsers.Instruction, error) {
278+
i.LogDebug("[%s]: HandleCodeResult: processing targets: %v", source, codeResult.Targets)
279+
var allInstructions []*parsers.Instruction
280+
281+
// Process each target in order
282+
for _, target := range codeResult.Targets {
283+
var instructions []*parsers.Instruction
284+
285+
switch v := target.(type) {
286+
case string:
287+
if v == "@" {
288+
if block == nil {
289+
return nil, fmt.Errorf("[%s]: HandleCodeResult: no instructions provided for @", source)
290+
}
291+
instructions = append(instructions, block...)
292+
} else {
293+
294+
// Get instructions using existing subroutine logic
295+
pwd := i.GetScriptDir()
296+
i.LogDebug("[%s]: HandleCodeResult: pwd = %s", source, pwd)
297+
298+
// Check if the subroutine is already loaded
299+
subPath := SubroutinePath(v, pwd)
300+
i.LogDebug("[%s]: HandleCodeResult: subPath = %v", source, subPath)
301+
302+
var err error
303+
instructions, err = i.GetSubroutineInstructions(subPath)
304+
i.LogDebug("[%s]: HandleCodeResult: instructions = %v", source, instructions)
305+
if err != nil {
306+
return nil, fmt.Errorf("[%s]: HandleCodeResult: failed to get instructions for %v: %v", source, target, err)
307+
}
308+
if instructions == nil {
309+
return nil, fmt.Errorf("[%s]: HandleCodeResult: no instructions found for %v", source, target)
310+
}
311+
312+
}
313+
case []*parsers.Instruction:
314+
// Use the instructions directly
315+
instructions = v
316+
if instructions == nil {
317+
return nil, fmt.Errorf("[%s]: HandleCodeResult: no instructions found for %v", source, target)
318+
}
319+
case *parsers.Instruction:
320+
// Use the instructions directly
321+
instructions = []*parsers.Instruction{v}
322+
if instructions == nil {
323+
return nil, fmt.Errorf("[%s]: HandleCodeResult: no instructions found for %v", source, target)
324+
}
325+
default:
326+
return nil, fmt.Errorf("[%s]: HandleCodeResult: invalid target type: %T", source, target)
327+
}
328+
329+
// Append instructions to the result
330+
allInstructions = append(allInstructions, instructions...)
331+
}
332+
333+
return allInstructions, nil
334+
}
335+
260336
// ExecutePrimitive executes a single GND primitive and returns its result
261337
func (i *InterpreterImpl) ExecutePrimitive(prim primitive.Primitive, destination *parsers.PropertyRef, arguments []interface{}) (interface{}, error) {
262338

263339
// Log regular instruction
264340
i.LogDebug("[%s]: ExecutePrimitive: %v <- %s %v", prim.Name(), destination, prim.Name(), arguments)
265341

266342
result, err := prim.Execute(arguments)
343+
267344
if err != nil {
268345

269346
// Check if this is a ReturnValue
@@ -289,6 +366,12 @@ func (i *InterpreterImpl) ExecutePrimitive(prim primitive.Primitive, destination
289366
return nil, fmt.Errorf("[%s]: ExecutePrimitive: error: %v", prim.Name(), err)
290367
}
291368

369+
// Check if this is a CodeResult
370+
if codeResult, ok := primitive.GetCodeResult(result); ok {
371+
i.LogDebug("[%s]: ExecutePrimitive: code result detected: %v", prim.Name(), codeResult)
372+
return codeResult, nil
373+
}
374+
292375
// Store the result in the destination slot
293376
i.LogDebug("[%s]: ExecutePrimitive: %v <- %v", prim.Name(), destination, log.StringifyValue(result))
294377
i.Slots[destination.Name] = result

0 commit comments

Comments
 (0)