// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com) // All rights reserved. // Goroutine support for Five runtime. // Provides Go's goroutine, channel, and WaitGroup primitives // as first-class Harbour values. package hbrt import ( "fmt" "os" "sync" ) // --- Channel --- // HbChannel wraps Go's chan Value for use in PRG code. type HbChannel struct { Ch chan Value closeOnce sync.Once closed bool } // MakeChannel creates a channel Value with optional buffer size. func MakeChannel(size int) Value { return MakePointer(&HbChannel{Ch: make(chan Value, size)}) } // AsChannel extracts HbChannel from a Pointer value. func (v Value) AsChannel() *HbChannel { if !v.IsPointer() { return nil } if ch, ok := v.AsPointer().(*HbChannel); ok { return ch } return nil } // Send sends a value into the channel. Safe if channel is closed (recovers panic). func (ch *HbChannel) Send(val Value) { defer func() { if r := recover(); r != nil { // send on closed channel — silently ignore } }() ch.Ch <- val } // Receive receives a value from the channel. func (ch *HbChannel) Receive() Value { v, ok := <-ch.Ch if !ok { return MakeNil() // channel closed } return v } // TryReceive attempts non-blocking receive. Returns (value, true) or (nil, false). func (ch *HbChannel) TryReceive() (Value, bool) { select { case v, ok := <-ch.Ch: if !ok { return MakeNil(), false } return v, true default: return MakeNil(), false } } // Close closes the channel. Safe to call multiple times. func (ch *HbChannel) Close() { ch.closeOnce.Do(func() { ch.closed = true close(ch.Ch) }) } // IsClosed returns true if the channel has been closed. func (ch *HbChannel) IsClosed() bool { return ch.closed } // --- WaitGroup --- // HbWaitGroup wraps sync.WaitGroup. type HbWaitGroup struct { WG sync.WaitGroup } // MakeWaitGroup creates a WaitGroup Value with initial count. func MakeWaitGroup(n int) Value { wg := &HbWaitGroup{} if n > 0 { wg.WG.Add(n) } return MakePointer(wg) } // AsWaitGroup extracts HbWaitGroup from a Pointer value. func (v Value) AsWaitGroup() *HbWaitGroup { if !v.IsPointer() { return nil } if wg, ok := v.AsPointer().(*HbWaitGroup); ok { return wg } return nil } // --- Mutex --- // HbMutex wraps sync.Mutex. type HbMutex struct { Mu sync.Mutex } // MakeMutex creates a Mutex Value. func MakeMutex() Value { return MakePointer(&HbMutex{}) } // AsMutex extracts HbMutex from a Pointer value. func (v Value) AsMutex() *HbMutex { if !v.IsPointer() { return nil } if mu, ok := v.AsPointer().(*HbMutex); ok { return mu } return nil } // --- GoRoutine launcher --- // GoLaunch spawns a new goroutine that runs a function on a new Thread. func (vm *VM) GoLaunch(fn func(*Thread), args []Value) { go func() { defer func() { if r := recover(); r != nil { fmt.Fprintf(os.Stderr, "Five goroutine panic: %v\n", r) } }() t := vm.NewThread() for _, a := range args { t.push(a) } t.PendingParams2(len(args)) fn(t) }() } // GoLaunchBlock spawns a goroutine that evaluates a code block. func (vm *VM) GoLaunchBlock(blk *HbBlock, args []Value) { go func() { defer func() { if r := recover(); r != nil { fmt.Fprintf(os.Stderr, "Five goroutine panic: %v\n", r) } }() t := vm.NewThread() for _, a := range args { t.push(a) } t.PendingParams2(len(args)) blk.Fn(t) }() }