scream/vendor/src/github.com/yuin/gopher-lua/channellib.go

150 lines
3.0 KiB
Go

package lua
import (
"reflect"
)
func checkChannel(L *LState, idx int) reflect.Value {
ch := L.CheckChannel(idx)
return reflect.ValueOf(ch)
}
func checkGoroutineSafe(L *LState, idx int) LValue {
v := L.CheckAny(2)
if !isGoroutineSafe(v) {
L.ArgError(2, "can not send a function, userdata, thread or table that has a metatable")
}
return v
}
func channelOpen(L *LState) {
_, ok := L.G.builtinMts[int(LTChannel)]
if !ok {
L.RegisterModule("channel", channelFuncs)
mt := L.SetFuncs(L.NewTable(), channelMethods)
mt.RawSetString("__index", mt)
L.G.builtinMts[int(LTChannel)] = mt
}
}
var channelFuncs = map[string]LGFunction{
"make": channelMake,
"select": channelSelect,
}
func channelMake(L *LState) int {
buffer := L.OptInt(1, 0)
L.Push(LChannel(make(chan LValue, buffer)))
return 1
}
func channelSelect(L *LState) int {
//TODO check case table size
cases := make([]reflect.SelectCase, L.GetTop())
top := L.GetTop()
for i := 0; i < top; i++ {
cas := reflect.SelectCase{reflect.SelectSend, reflect.ValueOf(nil), reflect.ValueOf(nil)}
tbl := L.CheckTable(i + 1)
dir, ok1 := tbl.RawGetInt(1).(LString)
if !ok1 {
L.ArgError(i+1, "invalid select case")
}
switch string(dir) {
case "<-|":
ch, ok := tbl.RawGetInt(2).(LChannel)
if !ok {
L.ArgError(i+1, "invalid select case")
}
cas.Chan = reflect.ValueOf((chan LValue)(ch))
v := tbl.RawGetInt(3)
if !isGoroutineSafe(v) {
L.ArgError(i+1, "can not send a function, userdata, thread or table that has a metatable")
}
cas.Send = reflect.ValueOf(v)
case "|<-":
ch, ok := tbl.RawGetInt(2).(LChannel)
if !ok {
L.ArgError(i+1, "invalid select case")
}
cas.Chan = reflect.ValueOf((chan LValue)(ch))
cas.Dir = reflect.SelectRecv
case "default":
cas.Dir = reflect.SelectDefault
default:
L.ArgError(i+1, "invalid channel direction:"+string(dir))
}
cases[i] = cas
}
pos, recv, rok := reflect.Select(cases)
lv := LNil
if recv.Kind() != 0 {
lv, _ = recv.Interface().(LValue)
if lv == nil {
lv = LNil
}
}
tbl := L.Get(pos + 1).(*LTable)
last := tbl.RawGetInt(tbl.Len())
if last.Type() == LTFunction {
L.Push(last)
switch cases[pos].Dir {
case reflect.SelectRecv:
if rok {
L.Push(LTrue)
} else {
L.Push(LFalse)
}
L.Push(lv)
L.Call(2, 0)
case reflect.SelectSend:
L.Push(tbl.RawGetInt(3))
L.Call(1, 0)
case reflect.SelectDefault:
L.Call(0, 0)
}
}
L.Push(LNumber(pos + 1))
L.Push(lv)
if rok {
L.Push(LTrue)
} else {
L.Push(LFalse)
}
return 3
}
var channelMethods = map[string]LGFunction{
"receive": channelReceive,
"send": channelSend,
"close": channelClose,
}
func channelReceive(L *LState) int {
rch := checkChannel(L, 1)
v, ok := rch.Recv()
if ok {
L.Push(LTrue)
L.Push(v.Interface().(LValue))
} else {
L.Push(LFalse)
L.Push(LNil)
}
return 2
}
func channelSend(L *LState) int {
rch := checkChannel(L, 1)
v := checkGoroutineSafe(L, 2)
rch.Send(reflect.ValueOf(v))
return 0
}
func channelClose(L *LState) int {
rch := checkChannel(L, 1)
rch.Close()
return 0
}
//