Skip to content

Commit fc024ce

Browse files
committed
feat(chunk): add OP_SWAP opcode to swap top two stack elements for better stack manipulation
feat(compiler): implement logic for handling reference type assignments and updates for global variables feat(vm): add support for OP_SWAP in the virtual machine to facilitate stack operations docs(ref_example): create examples demonstrating reference updates and rebindings in the language docs(test_global_ref): add test case for verifying reference updates on global variables
1 parent c3bdf2d commit fc024ce

5 files changed

Lines changed: 108 additions & 0 deletions

File tree

internal/chunk/chunk.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ const (
7676
OP_STORE_VIA_REF
7777
OP_STORE_REF
7878
OP_SET_PROPERTY_DEREF
79+
OP_SWAP
7980
OP_COPY
8081
)
8182

@@ -409,6 +410,8 @@ func (c *Chunk) disassembleInstruction(offset int) int {
409410
return c.simpleInstruction("OP_STORE_REF", offset)
410411
case OP_SET_PROPERTY_DEREF:
411412
return c.constantInstruction("OP_SET_PROPERTY_DEREF", offset)
413+
case OP_SWAP:
414+
return c.simpleInstruction("OP_SWAP", offset)
412415
case OP_COPY:
413416
return c.simpleInstruction("OP_COPY", offset)
414417
default:

internal/compiler/compiler.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,65 @@ func (c *Compiler) Compile(node ast.Node) (*chunk.Chunk, ast.NoxyType, error) {
239239
} else {
240240
// Global Logic
241241
if globalType, exists := c.globals[ident.Value]; exists {
242+
// Check if global is a reference type
243+
if refType, isRef := globalType.(*ast.RefType); isRef {
244+
// Type-Based Assignment for Global Refs
245+
_, isRefVal := valType.(*ast.RefType)
246+
if isRefVal || valType == nil {
247+
// REBIND: ref = ref -> Just update the global with new ref
248+
if valType != nil && !c.areTypesCompatible(globalType, valType) {
249+
return nil, nil, fmt.Errorf("[line %d] type mismatch in rebind to global '%s': expected %s, got %s", c.currentLine, ident.Value, globalType.String(), valType.String())
250+
}
251+
// Standard global set (rebind)
252+
nameConstant := c.makeConstant(value.NewString(ident.Value))
253+
c.emitBytes(byte(chunk.OP_SET_GLOBAL), byte(nameConstant))
254+
c.emitByte(byte(chunk.OP_POP))
255+
} else {
256+
// UPDATE: ref = val -> Write value to target
257+
if !c.areTypesCompatible(refType.ElementType, valType) {
258+
return nil, nil, fmt.Errorf("[line %d] type mismatch in update to global '%s': expected %s (rebind) or %s (update), got %s", c.currentLine, ident.Value, refType.String(), refType.ElementType.String(), valType.String())
259+
}
260+
// Emit: Get global ref, then store value into it
261+
nameConstant := c.makeConstant(value.NewString(ident.Value))
262+
c.emitBytes(byte(chunk.OP_GET_GLOBAL), byte(nameConstant))
263+
// Stack: [val, ref] - need to swap
264+
// Use OP_STORE_REF which expects [ref, val]
265+
// But we have [val, ref]. We need to emit differently.
266+
// Actually, we compiled Value first, then GET_GLOBAL.
267+
// Stack: [val, ref]
268+
// OP_STORE_REF expects: pop val, pop ref. So [ref, val] order.
269+
// We have [val, ref]. We need OP_STORE_REF_SWAPPED or different approach.
270+
// Alternative: Emit GET_GLOBAL first, then compile value.
271+
// But we already compiled value. Can't undo.
272+
// Use OP_STORE_REF with swapped logic? Or add OP_SWAP?
273+
// For now, let's just emit the global name and use a new opcode.
274+
// Actually, I'll recompile: emit GET first, then value.
275+
// NO - value already compiled.
276+
// HACK: pop both, swap, push back? No such ops.
277+
// Simplest: Just do the store in VM with swapped operands.
278+
// I'll create OP_STORE_GLOBAL_DEREF which takes name as operand.
279+
// Stack: [val] -> reads global ref, writes val to it.
280+
// That's cleaner. Let's use that.
281+
// Actually, I don't have that opcode. Let me improvise.
282+
// Emit: swap then store_ref? No swap opcode.
283+
// For now, just error and require wrapping in function.
284+
// TODO: Add proper support later.
285+
// Actually, simpler: just use existing logic but different stack order.
286+
// Re-emit: POP val (save in temp), GET_GLOBAL, push val back, STORE_REF
287+
// No temp storage in VM.
288+
// Let's just emit correct order by recompiling:
289+
// This is hacky but works: emit a synthetic sequence.
290+
// GET_GLOBAL pushes ref. Then we need val on top.
291+
// We already have val on stack. GET_GLOBAL added ref.
292+
// Stack: [val, ref]. STORE_REF wants [ref, val].
293+
// I'll add a simple OP_SWAP.
294+
c.emitByte(byte(chunk.OP_SWAP))
295+
c.emitByte(byte(chunk.OP_STORE_REF))
296+
// No OP_POP needed - STORE_REF consumes both values
297+
}
298+
return c.currentChunk, nil, nil
299+
}
300+
// Standard type check for non-ref globals
242301
if !c.areTypesCompatible(globalType, valType) {
243302
return nil, nil, fmt.Errorf("[line %d] type mismatch in assignment to global '%s': expected %s, got %s", c.currentLine, ident.Value, globalType.String(), valType.String())
244303
}

internal/vm/vm.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4426,6 +4426,13 @@ func (vm *VM) run(minFrameCount int) error {
44264426
}
44274427
vm.push(val)
44284428

4429+
case chunk.OP_SWAP:
4430+
// Swap top two stack elements: [a, b] -> [b, a]
4431+
b := vm.pop()
4432+
a := vm.pop()
4433+
vm.push(b)
4434+
vm.push(a)
4435+
44294436
case chunk.OP_COPY:
44304437
val := vm.pop()
44314438
vm.push(vm.copyValue(val))

noxy_examples/ref_example.nx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
struct Box
2+
val: ref int
3+
end
4+
let x: int = 10
5+
let y: int = 20
6+
7+
print("Step 1: Calling update(ref x)")
8+
func update(r: ref int)
9+
r = 99 // UPDATE: escreve 99 no alvo
10+
end
11+
update(ref x) // x agora é 99
12+
print(fmt("After update: x = %d", x))
13+
14+
print("Step 2: Local ref operations")
15+
let local_ref: ref int = ref x
16+
local_ref = 50 // UPDATE: x = 50
17+
print(fmt("After local_ref = 50: x = %d", x))
18+
19+
local_ref = ref y // REBIND: agora aponta para y
20+
local_ref = 100 // UPDATE: y = 100
21+
print(fmt("After local_ref = 100: y = %d", y))
22+
23+
print("Step 3: Struct field operations")
24+
let box: Box = Box(ref x)
25+
box.val = 200 // UPDATE: x = 200
26+
print(fmt("After box.val = 200: x = %d", x))
27+
28+
box.val = ref y // REBIND: agora aponta para y
29+
box.val = 300 // UPDATE: y = 300
30+
print(fmt("After box.val = 300: y = %d", y))
31+
32+
print("Done!")

noxy_examples/test_global_ref.nx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
let x: int = 10
2+
let r: ref int = ref x
3+
4+
// This should UPDATE x
5+
r = 50
6+
7+
print(fmt("x = %d (expected 50)", x))

0 commit comments

Comments
 (0)