Merge pull request #2731 from jameszhangyukun/lua-layer
Resource Interpreter framework introduce Lua runtime layer
This commit is contained in:
commit
31f97acae3
2
go.mod
2
go.mod
|
@ -19,6 +19,7 @@ require (
|
|||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/vektra/mockery/v2 v2.10.0
|
||||
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64
|
||||
go.uber.org/atomic v1.7.0
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8
|
||||
|
@ -40,6 +41,7 @@ require (
|
|||
k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42
|
||||
k8s.io/kubectl v0.24.2
|
||||
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9
|
||||
layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf
|
||||
sigs.k8s.io/cluster-api v1.0.1
|
||||
sigs.k8s.io/controller-runtime v0.12.2
|
||||
sigs.k8s.io/kind v0.15.0
|
||||
|
|
4
go.sum
4
go.sum
|
@ -847,6 +847,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
|||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ=
|
||||
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
|
||||
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
|
||||
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
|
||||
|
@ -1582,6 +1584,8 @@ k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/
|
|||
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc=
|
||||
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf h1:rRz0YsF7VXj9fXRF6yQgFI7DzST+hsI3TeFSGupntu0=
|
||||
layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf/go.mod h1:ivKkcY8Zxw5ba0jldhZCYYQfGdb2K6u9tbYK1AwMIBc=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
|
|
@ -0,0 +1,443 @@
|
|||
package luavm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/klog/v2"
|
||||
luajson "layeh.com/gopher-json"
|
||||
|
||||
configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
|
||||
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
|
||||
"github.com/karmada-io/karmada/pkg/util/helper"
|
||||
"github.com/karmada-io/karmada/pkg/util/lifted"
|
||||
)
|
||||
|
||||
// VM Defines a struct that implements the luaVM.
|
||||
type VM struct {
|
||||
// UseOpenLibs flag to enable open libraries. Libraries are disabled by default while running, but enabled during testing to allow the use of print statements.
|
||||
UseOpenLibs bool
|
||||
}
|
||||
|
||||
// GetReplicas returns the desired replicas of the object as well as the requirements of each replica by lua script.
|
||||
func (vm VM) GetReplicas(obj *unstructured.Unstructured, script string) (replica int32, requires *workv1alpha2.ReplicaRequirements, err error) {
|
||||
l := lua.NewState(lua.Options{
|
||||
SkipOpenLibs: !vm.UseOpenLibs,
|
||||
})
|
||||
defer l.Close()
|
||||
// Opens table library to allow access to functions to manipulate tables
|
||||
err = vm.setLib(l)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
// preload our 'safe' version of the OS library. Allows the 'local os = require("os")' to work
|
||||
l.PreloadModule(lua.OsLibName, lifted.SafeOsLoader)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
l.SetContext(ctx)
|
||||
|
||||
err = l.DoString(script)
|
||||
f := l.GetGlobal("GetReplicas")
|
||||
|
||||
if f.Type() == lua.LTNil {
|
||||
return 0, nil, fmt.Errorf("can't get function ReviseReplica pleace check the function name")
|
||||
}
|
||||
|
||||
args := make([]lua.LValue, 1)
|
||||
args[0] = decodeValue(l, obj.Object)
|
||||
err = l.CallByParam(lua.P{Fn: f, NRet: 2, Protect: true}, args...)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
replicaRequirementResult := l.Get(l.GetTop())
|
||||
l.Pop(1)
|
||||
|
||||
requires = &workv1alpha2.ReplicaRequirements{}
|
||||
if replicaRequirementResult.Type() == lua.LTTable {
|
||||
err = ConvertLuaResultInto(replicaRequirementResult, requires)
|
||||
if err != nil {
|
||||
klog.Errorf("ConvertLuaResultToReplicaRequirements err %v", err.Error())
|
||||
return 0, nil, err
|
||||
}
|
||||
} else if replicaRequirementResult.Type() == lua.LTNil {
|
||||
requires = nil
|
||||
} else {
|
||||
return 0, nil, fmt.Errorf("expect the returned requires type is table but got %s", replicaRequirementResult.Type())
|
||||
}
|
||||
|
||||
luaReplica := l.Get(l.GetTop())
|
||||
replica, err = ConvertLuaResultToInt(luaReplica)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ReviseReplica revises the replica of the given object by lua.
|
||||
func (vm VM) ReviseReplica(object *unstructured.Unstructured, replica int64, script string) (*unstructured.Unstructured, error) {
|
||||
l := lua.NewState(lua.Options{
|
||||
SkipOpenLibs: !vm.UseOpenLibs,
|
||||
})
|
||||
defer l.Close()
|
||||
// Opens table library to allow access to functions to manipulate tables
|
||||
err := vm.setLib(l)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// preload our 'safe' version of the OS library. Allows the 'local os = require("os")' to work
|
||||
l.PreloadModule(lua.OsLibName, lifted.SafeOsLoader)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
l.SetContext(ctx)
|
||||
|
||||
err = l.DoString(script)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reviseReplicaLuaFunc := l.GetGlobal("ReviseReplica")
|
||||
if reviseReplicaLuaFunc.Type() == lua.LTNil {
|
||||
return nil, fmt.Errorf("can't get function ReviseReplica pleace check the function name")
|
||||
}
|
||||
|
||||
args := make([]lua.LValue, 2)
|
||||
args[0] = decodeValue(l, object.Object)
|
||||
args[1] = decodeValue(l, replica)
|
||||
err = l.CallByParam(lua.P{Fn: reviseReplicaLuaFunc, NRet: 1, Protect: true}, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
luaResult := l.Get(l.GetTop())
|
||||
reviseReplicaResult := &unstructured.Unstructured{}
|
||||
if luaResult.Type() == lua.LTTable {
|
||||
err := ConvertLuaResultInto(luaResult, reviseReplicaResult)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return reviseReplicaResult, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("expect the returned requires type is table but got %s", luaResult.Type())
|
||||
}
|
||||
|
||||
func (vm VM) setLib(l *lua.LState) error {
|
||||
for _, pair := range []struct {
|
||||
n string
|
||||
f lua.LGFunction
|
||||
}{
|
||||
{lua.LoadLibName, lua.OpenPackage},
|
||||
{lua.BaseLibName, lua.OpenBase},
|
||||
{lua.TabLibName, lua.OpenTable},
|
||||
// load our 'safe' version of the OS library
|
||||
{lua.OsLibName, lifted.OpenSafeOs},
|
||||
} {
|
||||
if err := l.CallByParam(lua.P{
|
||||
Fn: l.NewFunction(pair.f),
|
||||
NRet: 0,
|
||||
Protect: true,
|
||||
}, lua.LString(pair.n)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Retain returns the objects that based on the "desired" object but with values retained from the "observed" object by lua.
|
||||
func (vm VM) Retain(desired *unstructured.Unstructured, observed *unstructured.Unstructured, script string) (retained *unstructured.Unstructured, err error) {
|
||||
l := lua.NewState(lua.Options{
|
||||
SkipOpenLibs: !vm.UseOpenLibs,
|
||||
})
|
||||
defer l.Close()
|
||||
// Opens table library to allow access to functions to manipulate tables
|
||||
err = vm.setLib(l)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// preload our 'safe' version of the OS library. Allows the 'local os = require("os")' to work
|
||||
l.PreloadModule(lua.OsLibName, lifted.SafeOsLoader)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
l.SetContext(ctx)
|
||||
|
||||
err = l.DoString(script)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
retainLuaFunc := l.GetGlobal("Retain")
|
||||
if retainLuaFunc.Type() == lua.LTNil {
|
||||
return nil, fmt.Errorf("can't get function Retatin pleace check the function ")
|
||||
}
|
||||
|
||||
args := make([]lua.LValue, 2)
|
||||
args[0] = decodeValue(l, desired.Object)
|
||||
args[1] = decodeValue(l, observed.Object)
|
||||
err = l.CallByParam(lua.P{Fn: retainLuaFunc, NRet: 1, Protect: true}, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
luaResult := l.Get(l.GetTop())
|
||||
retainResult := &unstructured.Unstructured{}
|
||||
if luaResult.Type() == lua.LTTable {
|
||||
err := ConvertLuaResultInto(luaResult, retainResult)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return retainResult, nil
|
||||
}
|
||||
return nil, fmt.Errorf("expect the returned requires type is table but got %s", luaResult.Type())
|
||||
}
|
||||
|
||||
// AggregateStatus returns the objects that based on the 'object' but with status aggregated by lua.
|
||||
func (vm VM) AggregateStatus(object *unstructured.Unstructured, item []map[string]interface{}, script string) (*unstructured.Unstructured, error) {
|
||||
l := lua.NewState(lua.Options{
|
||||
SkipOpenLibs: !vm.UseOpenLibs,
|
||||
})
|
||||
defer l.Close()
|
||||
// Opens table library to allow access to functions to manipulate tables
|
||||
err := vm.setLib(l)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// preload our 'safe' version of the OS library. Allows the 'local os = require("os")' to work
|
||||
l.PreloadModule(lua.OsLibName, lifted.SafeOsLoader)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
l.SetContext(ctx)
|
||||
|
||||
err = l.DoString(script)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f := l.GetGlobal("AggregateStatus")
|
||||
if f.Type() == lua.LTNil {
|
||||
return nil, fmt.Errorf("can't get function AggregateStatus pleace check the function ")
|
||||
}
|
||||
args := make([]lua.LValue, 2)
|
||||
args[0] = decodeValue(l, object.Object)
|
||||
args[1] = decodeValue(l, item)
|
||||
err = l.CallByParam(lua.P{Fn: f, NRet: 1, Protect: true}, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
luaResult := l.Get(l.GetTop())
|
||||
aggregateStatus := &unstructured.Unstructured{}
|
||||
if luaResult.Type() == lua.LTTable {
|
||||
err := ConvertLuaResultInto(luaResult, aggregateStatus)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return aggregateStatus, nil
|
||||
}
|
||||
return nil, fmt.Errorf("expect the returned requires type is table but got %s", luaResult.Type())
|
||||
}
|
||||
|
||||
// InterpretHealth returns the health state of the object by lua.
|
||||
func (vm VM) InterpretHealth(object *unstructured.Unstructured, script string) (bool, error) {
|
||||
l := lua.NewState(lua.Options{
|
||||
SkipOpenLibs: !vm.UseOpenLibs,
|
||||
})
|
||||
defer l.Close()
|
||||
// Opens table library to allow access to functions to manipulate tables
|
||||
err := vm.setLib(l)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// preload our 'safe' version of the OS library. Allows the 'local os = require("os")' to work
|
||||
l.PreloadModule(lua.OsLibName, lifted.SafeOsLoader)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
l.SetContext(ctx)
|
||||
|
||||
err = l.DoString(script)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
f := l.GetGlobal("InterpretHealth")
|
||||
if f.Type() == lua.LTNil {
|
||||
return false, fmt.Errorf("can't get function InterpretHealth pleace check the function ")
|
||||
}
|
||||
|
||||
args := make([]lua.LValue, 1)
|
||||
args[0] = decodeValue(l, object.Object)
|
||||
err = l.CallByParam(lua.P{Fn: f, NRet: 1, Protect: true}, args...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var health bool
|
||||
luaResult := l.Get(l.GetTop())
|
||||
health, err = ConvertLuaResultToBool(luaResult)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return health, nil
|
||||
}
|
||||
|
||||
// ReflectStatus returns the status of the object by lua.
|
||||
func (vm VM) ReflectStatus(object *unstructured.Unstructured, script string) (status *runtime.RawExtension, err error) {
|
||||
l := lua.NewState(lua.Options{
|
||||
SkipOpenLibs: !vm.UseOpenLibs,
|
||||
})
|
||||
defer l.Close()
|
||||
// Opens table library to allow access to functions to manipulate tables
|
||||
err = vm.setLib(l)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// preload our 'safe' version of the OS library. Allows the 'local os = require("os")' to work
|
||||
l.PreloadModule(lua.OsLibName, lifted.SafeOsLoader)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
l.SetContext(ctx)
|
||||
|
||||
err = l.DoString(script)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f := l.GetGlobal("ReflectStatus")
|
||||
if f.Type() == lua.LTNil {
|
||||
return nil, fmt.Errorf("can't get function ReflectStatus pleace check the function ")
|
||||
}
|
||||
|
||||
args := make([]lua.LValue, 1)
|
||||
args[0] = decodeValue(l, object.Object)
|
||||
err = l.CallByParam(lua.P{Fn: f, NRet: 2, Protect: true}, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
luaStatusResult := l.Get(l.GetTop())
|
||||
l.Pop(1)
|
||||
if luaStatusResult.Type() != lua.LTTable {
|
||||
return nil, fmt.Errorf("expect the returned replica type is table but got %s", luaStatusResult.Type())
|
||||
}
|
||||
|
||||
luaExistResult := l.Get(l.GetTop())
|
||||
var exist bool
|
||||
exist, err = ConvertLuaResultToBool(luaExistResult)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if exist {
|
||||
resultMap := make(map[string]interface{})
|
||||
jsonBytes, err := luajson.Encode(luaStatusResult)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal(jsonBytes, &resultMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return helper.BuildStatusRawExtension(resultMap)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// GetDependencies returns the dependent resources of the given object by lua.
|
||||
func (vm VM) GetDependencies(object *unstructured.Unstructured, script string) (dependencies []configv1alpha1.DependentObjectReference, err error) {
|
||||
l := lua.NewState(lua.Options{
|
||||
SkipOpenLibs: !vm.UseOpenLibs,
|
||||
})
|
||||
defer l.Close()
|
||||
// Opens table library to allow access to functions to manipulate tables
|
||||
err = vm.setLib(l)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// preload our 'safe' version of the OS library. Allows the 'local os = require("os")' to work
|
||||
l.PreloadModule(lua.OsLibName, lifted.SafeOsLoader)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
l.SetContext(ctx)
|
||||
|
||||
err = l.DoString(script)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f := l.GetGlobal("GetDependencies")
|
||||
if f.Type() == lua.LTNil {
|
||||
return nil, fmt.Errorf("can't get function Retatin pleace check the function ")
|
||||
}
|
||||
|
||||
args := make([]lua.LValue, 1)
|
||||
args[0] = decodeValue(l, object.Object)
|
||||
err = l.CallByParam(lua.P{Fn: f, NRet: 1, Protect: true}, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
luaResult := l.Get(l.GetTop())
|
||||
if luaResult.Type() == lua.LTTable {
|
||||
jsonBytes, err := luajson.Encode(luaResult)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal(jsonBytes, &dependencies)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("expect the returned requires type is table but got %s", luaResult.Type())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Took logic from the link below and added the int, int32, and int64 types since the value would have type int64
|
||||
// while actually running in the controller and it was not reproducible through testing.
|
||||
// https://github.com/layeh/gopher-json/blob/97fed8db84274c421dbfffbb28ec859901556b97/json.go#L154
|
||||
func decodeValue(L *lua.LState, value interface{}) lua.LValue {
|
||||
switch converted := value.(type) {
|
||||
case bool:
|
||||
return lua.LBool(converted)
|
||||
case float64:
|
||||
return lua.LNumber(converted)
|
||||
case string:
|
||||
return lua.LString(converted)
|
||||
case json.Number:
|
||||
return lua.LString(converted)
|
||||
case int:
|
||||
return lua.LNumber(converted)
|
||||
case int32:
|
||||
return lua.LNumber(converted)
|
||||
case int64:
|
||||
return lua.LNumber(converted)
|
||||
case []interface{}:
|
||||
arr := L.CreateTable(len(converted), 0)
|
||||
for _, item := range converted {
|
||||
arr.Append(decodeValue(L, item))
|
||||
}
|
||||
return arr
|
||||
case []map[string]interface{}:
|
||||
arr := L.CreateTable(len(converted), 0)
|
||||
for _, item := range converted {
|
||||
arr.Append(decodeValue(L, item))
|
||||
}
|
||||
return arr
|
||||
case map[string]interface{}:
|
||||
tbl := L.CreateTable(0, len(converted))
|
||||
for key, item := range converted {
|
||||
tbl.RawSetH(lua.LString(key), decodeValue(L, item))
|
||||
}
|
||||
return tbl
|
||||
case nil:
|
||||
return lua.LNil
|
||||
}
|
||||
|
||||
return lua.LNil
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package luavm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"k8s.io/apimachinery/pkg/conversion"
|
||||
luajson "layeh.com/gopher-json"
|
||||
)
|
||||
|
||||
// ConvertLuaResultInto convert lua result to obj
|
||||
func ConvertLuaResultInto(luaResult lua.LValue, obj interface{}) error {
|
||||
t, err := conversion.EnforcePtr(obj)
|
||||
if err != nil {
|
||||
return fmt.Errorf("obj is not pointer")
|
||||
}
|
||||
jsonBytes, err := luajson.Encode(luaResult)
|
||||
if err != nil {
|
||||
return fmt.Errorf("json Encode obj eroor %v", err)
|
||||
}
|
||||
|
||||
// for lua an empty object by json encode be [] not {}
|
||||
if t.Kind() == reflect.Struct && len(jsonBytes) > 1 && jsonBytes[0] == '[' {
|
||||
jsonBytes[0], jsonBytes[len(jsonBytes)-1] = '{', '}'
|
||||
}
|
||||
|
||||
err = json.Unmarshal(jsonBytes, obj)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can not unmarshal %v to %#v", string(jsonBytes), obj)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConvertLuaResultToInt convert lua result to int.
|
||||
func ConvertLuaResultToInt(luaResult lua.LValue) (int32, error) {
|
||||
if luaResult.Type() != lua.LTNumber {
|
||||
return 0, fmt.Errorf("result type %#v is not number", luaResult.Type())
|
||||
}
|
||||
return int32(luaResult.(lua.LNumber)), nil
|
||||
}
|
||||
|
||||
// ConvertLuaResultToBool convert lua result to bool.
|
||||
func ConvertLuaResultToBool(luaResult lua.LValue) (bool, error) {
|
||||
if luaResult.Type() != lua.LTBool {
|
||||
return false, fmt.Errorf("result type %#v is not bool", luaResult.Type())
|
||||
}
|
||||
return bool(luaResult.(lua.LBool)), nil
|
||||
}
|
|
@ -0,0 +1,476 @@
|
|||
package luavm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
|
||||
"github.com/karmada-io/karmada/pkg/util/helper"
|
||||
)
|
||||
|
||||
func TestGetReplicas(t *testing.T) {
|
||||
var replicas int32 = 1
|
||||
//quantity := *resource.NewQuantity(1000, resource.BinarySI)
|
||||
vm := VM{UseOpenLibs: false}
|
||||
tests := []struct {
|
||||
name string
|
||||
deploy *appsv1.Deployment
|
||||
luaScript string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "Test GetReplica",
|
||||
deploy: &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Deployment",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "foo",
|
||||
Name: "bar",
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Replicas: &replicas,
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
luaScript: ` function GetReplicas(desiredObj)
|
||||
nodeClaim = {}
|
||||
resourceRequest = {}
|
||||
result = {}
|
||||
replica = desiredObj.spec.replicas
|
||||
result.resourceRequest = desiredObj.spec.template.spec.containers[1].resources.limits
|
||||
nodeClaim.nodeSelector = desiredObj.spec.template.spec.nodeSelector
|
||||
nodeClaim.tolerations = desiredObj.spec.template.spec.tolerations
|
||||
result.nodeClaim = {}
|
||||
result.nodeClaim = nil
|
||||
return replica, {}
|
||||
end`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
toUnstructured, _ := helper.ToUnstructured(tt.deploy)
|
||||
replicas, requires, err := vm.GetReplicas(toUnstructured, tt.luaScript)
|
||||
klog.Infof("replicas %v", replicas)
|
||||
klog.Infof("requires %v", requires)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReviseDeploymentReplica(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
object *unstructured.Unstructured
|
||||
replica int32
|
||||
expected *unstructured.Unstructured
|
||||
expectError bool
|
||||
luaScript string
|
||||
}{
|
||||
{
|
||||
name: "Test ReviseDeploymentReplica",
|
||||
object: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "fake-deployment",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"replicas": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
replica: 3,
|
||||
expectError: true,
|
||||
luaScript: `function ReviseReplica(desiredObj, desiredReplica)
|
||||
desiredObj.spec.replicas = desiredReplica
|
||||
return desiredObj
|
||||
end`,
|
||||
},
|
||||
{
|
||||
name: "revise deployment replica",
|
||||
object: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "fake-deployment",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"replicas": int64(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
replica: 3,
|
||||
expected: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "fake-deployment",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"replicas": int64(2),
|
||||
},
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
luaScript: `function ReviseReplica(desiredObj, desiredReplica)
|
||||
desiredObj.spec.replicas = desiredReplica
|
||||
return desiredObj
|
||||
end`,
|
||||
},
|
||||
}
|
||||
vm := VM{UseOpenLibs: false}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
res, err := vm.ReviseReplica(tt.object, int64(tt.replica), tt.luaScript)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
deploy := &appsv1.Deployment{}
|
||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(res.UnstructuredContent(), deploy)
|
||||
if err == nil && *deploy.Spec.Replicas == tt.replica {
|
||||
t.Log("Success Test")
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateDeploymentStatus(t *testing.T) {
|
||||
statusMap := map[string]interface{}{
|
||||
"replicas": 0,
|
||||
"readyReplicas": 0,
|
||||
"updatedReplicas": 0,
|
||||
"availableReplicas": 1,
|
||||
"unavailableReplicas": 0,
|
||||
}
|
||||
raw, _ := helper.BuildStatusRawExtension(statusMap)
|
||||
aggregatedStatusItems := []workv1alpha2.AggregatedStatusItem{
|
||||
{ClusterName: "member1", Status: raw, Applied: true},
|
||||
{ClusterName: "member2", Status: raw, Applied: true},
|
||||
}
|
||||
|
||||
oldDeploy := &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Deployment",
|
||||
APIVersion: "apps/v1",
|
||||
},
|
||||
}
|
||||
oldDeploy.Status = appsv1.DeploymentStatus{
|
||||
Replicas: 0, ReadyReplicas: 1, UpdatedReplicas: 0, AvailableReplicas: 0, UnavailableReplicas: 0}
|
||||
|
||||
newDeploy := &appsv1.Deployment{Status: appsv1.DeploymentStatus{Replicas: 0, ReadyReplicas: 0, UpdatedReplicas: 0, AvailableReplicas: 2, UnavailableReplicas: 0}}
|
||||
oldObj, _ := helper.ToUnstructured(oldDeploy)
|
||||
newObj, _ := helper.ToUnstructured(newDeploy)
|
||||
|
||||
var aggregateItem []map[string]interface{}
|
||||
for _, item := range aggregatedStatusItems {
|
||||
if item.Status == nil {
|
||||
continue
|
||||
}
|
||||
temp := make(map[string]interface{})
|
||||
if err := json.Unmarshal(item.Status.Raw, &temp); err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
aggregateItem = append(aggregateItem, temp)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
curObj *unstructured.Unstructured
|
||||
aggregatedStatusItems []map[string]interface{}
|
||||
expectedObj *unstructured.Unstructured
|
||||
luaScript string
|
||||
}{
|
||||
{
|
||||
name: "Test AggregateDeploymentStatus",
|
||||
curObj: oldObj,
|
||||
aggregatedStatusItems: aggregateItem,
|
||||
expectedObj: newObj,
|
||||
luaScript: `function AggregateStatus(desiredObj, statusItems)
|
||||
for i = 1, #statusItems do
|
||||
desiredObj.status.readyReplicas = desiredObj.status.readyReplicas + statusItems[i].readyReplicas
|
||||
end
|
||||
return desiredObj
|
||||
end`,
|
||||
},
|
||||
}
|
||||
vm := VM{UseOpenLibs: false}
|
||||
|
||||
for _, tt := range tests {
|
||||
actualObj, _ := vm.AggregateStatus(tt.curObj, tt.aggregatedStatusItems, tt.luaScript)
|
||||
actualDeploy := appsv1.DeploymentStatus{}
|
||||
err := helper.ConvertToTypedObject(actualObj.Object["status"], &actualDeploy)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
expectDeploy := appsv1.DeploymentStatus{}
|
||||
err = helper.ConvertToTypedObject(tt.expectedObj.Object["status"], &expectDeploy)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
if reflect.DeepEqual(expectDeploy, actualDeploy) {
|
||||
t.Log("Success \n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHealthDeploymentStatus(t *testing.T) {
|
||||
var cnt int32 = 2
|
||||
newDeploy := &appsv1.Deployment{
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Replicas: &cnt,
|
||||
},
|
||||
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Generation: 1,
|
||||
},
|
||||
Status: appsv1.DeploymentStatus{ObservedGeneration: 1, Replicas: 2, ReadyReplicas: 2, UpdatedReplicas: 2, AvailableReplicas: 2}}
|
||||
newObj, _ := helper.ToUnstructured(newDeploy)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
curObj *unstructured.Unstructured
|
||||
expectedObj bool
|
||||
luaScript string
|
||||
}{
|
||||
{
|
||||
name: "Test HealthDeploymentStatus",
|
||||
curObj: newObj,
|
||||
expectedObj: true,
|
||||
luaScript: `function InterpretHealth(observedObj)
|
||||
return (observedObj.status.updatedReplicas == observedObj.spec.replicas) and (observedObj.metadata.generation == observedObj.status.observedGeneration)
|
||||
end `,
|
||||
},
|
||||
}
|
||||
vm := VM{UseOpenLibs: false}
|
||||
|
||||
for _, tt := range tests {
|
||||
flag, _ := vm.InterpretHealth(tt.curObj, tt.luaScript)
|
||||
|
||||
if reflect.DeepEqual(flag, tt.expectedObj) {
|
||||
t.Log("Success \n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetainDeployment(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
desiredObj *unstructured.Unstructured
|
||||
observedObj *unstructured.Unstructured
|
||||
expectError bool
|
||||
luaScript string
|
||||
}{
|
||||
{
|
||||
name: "Test RetainDeployment",
|
||||
desiredObj: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "fake-deployment",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"replicas": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
observedObj: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "fake-deployment",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"replicas": int64(2),
|
||||
},
|
||||
},
|
||||
},
|
||||
expectError: true,
|
||||
luaScript: "function Retain(desiredObj, observedObj)\n desiredObj = observedObj\n return desiredObj\n end",
|
||||
},
|
||||
{
|
||||
name: "revise deployment replica",
|
||||
desiredObj: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "fake-deployment",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"replicas": int64(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
observedObj: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "fake-deployment",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"replicas": int64(2),
|
||||
},
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
luaScript: `function Retain(desiredObj, observedObj)
|
||||
desiredObj = observedObj
|
||||
return desiredObj
|
||||
end`,
|
||||
},
|
||||
}
|
||||
vm := VM{UseOpenLibs: false}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
res, err := vm.Retain(tt.desiredObj, tt.observedObj, tt.luaScript)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
deploy := &appsv1.Deployment{}
|
||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(res.UnstructuredContent(), deploy)
|
||||
if err == nil && reflect.DeepEqual(deploy, tt.observedObj) {
|
||||
t.Log("Success Test")
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatusReflection(t *testing.T) {
|
||||
testMap := map[string]interface{}{"key": "value"}
|
||||
wantRawExtension, _ := helper.BuildStatusRawExtension(testMap)
|
||||
type args struct {
|
||||
object *unstructured.Unstructured
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *runtime.RawExtension
|
||||
wantErr bool
|
||||
luaScript string
|
||||
}{
|
||||
{
|
||||
"Test StatusReflection",
|
||||
args{
|
||||
&unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"status": testMap,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantRawExtension,
|
||||
false,
|
||||
`function ReflectStatus (observedObj)
|
||||
if observedObj.status == nil then
|
||||
return false, nil
|
||||
end
|
||||
return true, observedObj.status
|
||||
end`,
|
||||
},
|
||||
}
|
||||
|
||||
vm := VM{UseOpenLibs: false}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := vm.ReflectStatus(tt.args.object, tt.luaScript)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("reflectWholeStatus() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("reflectWholeStatus() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDeployPodDependencies(t *testing.T) {
|
||||
newDeploy := appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test",
|
||||
Namespace: "test",
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
ServiceAccountName: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
newObj, _ := helper.ToUnstructured(&newDeploy)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
curObj *unstructured.Unstructured
|
||||
luaScript string
|
||||
}{
|
||||
{
|
||||
name: "Get GetDeployPodDependencies",
|
||||
curObj: newObj,
|
||||
luaScript: `function GetDependencies(desiredObj)
|
||||
dependentSas = {}
|
||||
refs = {}
|
||||
if desiredObj.spec.template.spec.serviceAccountName ~= \"\" and desiredObj.spec.template.spec.serviceAccountName ~= \"default\" then
|
||||
dependentSas[desiredObj.spec.template.spec.serviceAccountName] = true
|
||||
end
|
||||
local idx = 1
|
||||
for key, value in pairs(dependentSas) do
|
||||
dependObj = {}
|
||||
dependObj.apiVersion = \"v1\"
|
||||
dependObj.kind = \"ServiceAccount\"
|
||||
dependObj.name = key
|
||||
dependObj.namespace = desiredObj.namespace
|
||||
refs[idx] = {}
|
||||
refs[idx] = dependObj
|
||||
idx = idx + 1
|
||||
end
|
||||
return refs
|
||||
end`,
|
||||
},
|
||||
}
|
||||
|
||||
vm := VM{UseOpenLibs: false}
|
||||
|
||||
for _, tt := range tests {
|
||||
res, _ := vm.GetDependencies(tt.curObj, tt.luaScript)
|
||||
t.Logf("res %v", res)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
package lifted
|
||||
|
||||
// This code is directly lifted from the argo-cd codebase in order to avoid relying on the lua package.
|
||||
// For reference:
|
||||
// https://github.com/argoproj/argo-cd/blob/master/util/lua/oslib_safe.go
|
||||
|
||||
// oslib_safe contains a subset of the lua OS library. For security reasons, we do not expose
|
||||
// the entirety of lua OS library to custom actions, such as ones which can exit, read files, etc.
|
||||
// Only the safe functions like os.time(), os.date() are exposed. Implementation was copied from
|
||||
// github.com/yuin/gopher-lua.
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
)
|
||||
|
||||
// OpenSafeOs open safe os
|
||||
func OpenSafeOs(L *lua.LState) int {
|
||||
tabmod := L.RegisterModule(lua.TabLibName, osFuncs)
|
||||
L.Push(tabmod)
|
||||
return 1
|
||||
}
|
||||
|
||||
// SafeOsLoader sofe laoder
|
||||
func SafeOsLoader(L *lua.LState) int {
|
||||
mod := L.SetFuncs(L.NewTable(), osFuncs)
|
||||
L.Push(mod)
|
||||
return 1
|
||||
}
|
||||
|
||||
var osFuncs = map[string]lua.LGFunction{
|
||||
"time": osTime,
|
||||
"date": osDate,
|
||||
}
|
||||
|
||||
func osTime(L *lua.LState) int {
|
||||
if L.GetTop() == 0 {
|
||||
L.Push(lua.LNumber(time.Now().Unix()))
|
||||
} else {
|
||||
tbl := L.CheckTable(1)
|
||||
sec := getIntField(tbl, "sec", 0)
|
||||
min := getIntField(tbl, "min", 0)
|
||||
hour := getIntField(tbl, "hour", 12)
|
||||
day := getIntField(tbl, "day", -1)
|
||||
month := getIntField(tbl, "month", -1)
|
||||
year := getIntField(tbl, "year", -1)
|
||||
isdst := getBoolField(tbl, "isdst", false)
|
||||
t := time.Date(year, time.Month(month), day, hour, min, sec, 0, time.Local)
|
||||
// TODO dst
|
||||
if false {
|
||||
print(isdst)
|
||||
}
|
||||
L.Push(lua.LNumber(t.Unix()))
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func getIntField(tb *lua.LTable, key string, v int) int {
|
||||
ret := tb.RawGetString(key)
|
||||
if ln, ok := ret.(lua.LNumber); ok {
|
||||
return int(ln)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func getBoolField(tb *lua.LTable, key string, v bool) bool {
|
||||
ret := tb.RawGetString(key)
|
||||
if lb, ok := ret.(lua.LBool); ok {
|
||||
return bool(lb)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func osDate(L *lua.LState) int {
|
||||
t := time.Now()
|
||||
cfmt := "%c"
|
||||
if L.GetTop() >= 1 {
|
||||
cfmt = L.CheckString(1)
|
||||
if strings.HasPrefix(cfmt, "!") {
|
||||
t = time.Now().UTC()
|
||||
cfmt = strings.TrimLeft(cfmt, "!")
|
||||
}
|
||||
if L.GetTop() >= 2 {
|
||||
t = time.Unix(L.CheckInt64(2), 0)
|
||||
}
|
||||
if strings.HasPrefix(cfmt, "*t") {
|
||||
ret := L.NewTable()
|
||||
ret.RawSetString("year", lua.LNumber(t.Year()))
|
||||
ret.RawSetString("month", lua.LNumber(t.Month()))
|
||||
ret.RawSetString("day", lua.LNumber(t.Day()))
|
||||
ret.RawSetString("hour", lua.LNumber(t.Hour()))
|
||||
ret.RawSetString("min", lua.LNumber(t.Minute()))
|
||||
ret.RawSetString("sec", lua.LNumber(t.Second()))
|
||||
ret.RawSetString("wday", lua.LNumber(t.Weekday()+1))
|
||||
// TODO yday & dst
|
||||
ret.RawSetString("yday", lua.LNumber(0))
|
||||
ret.RawSetString("isdst", lua.LFalse)
|
||||
L.Push(ret)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
L.Push(lua.LString(strftime(t, cfmt)))
|
||||
return 1
|
||||
}
|
||||
|
||||
var cDateFlagToGo = map[byte]string{
|
||||
'a': "mon", 'A': "Monday", 'b': "Jan", 'B': "January", 'c': "02 Jan 06 15:04 MST", 'd': "02",
|
||||
'F': "2006-01-02", 'H': "15", 'I': "03", 'm': "01", 'M': "04", 'p': "PM", 'P': "pm", 'S': "05",
|
||||
'x': "15/04/05", 'X': "15:04:05", 'y': "06", 'Y': "2006", 'z': "-0700", 'Z': "MST"}
|
||||
|
||||
func strftime(t time.Time, cfmt string) string {
|
||||
sc := newFlagScanner('%', "", "", cfmt)
|
||||
for c, eos := sc.Next(); !eos; c, eos = sc.Next() {
|
||||
if !sc.ChangeFlag {
|
||||
if sc.HasFlag {
|
||||
if v, ok := cDateFlagToGo[c]; ok {
|
||||
sc.AppendString(t.Format(v))
|
||||
} else {
|
||||
switch c {
|
||||
case 'w':
|
||||
sc.AppendString(fmt.Sprint(int(t.Weekday())))
|
||||
default:
|
||||
sc.AppendChar('%')
|
||||
sc.AppendChar(c)
|
||||
}
|
||||
}
|
||||
sc.HasFlag = false
|
||||
} else {
|
||||
sc.AppendChar(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sc.String()
|
||||
}
|
||||
|
||||
type flagScanner struct {
|
||||
flag byte
|
||||
start string
|
||||
end string
|
||||
buf []byte
|
||||
str string
|
||||
Length int
|
||||
Pos int
|
||||
HasFlag bool
|
||||
ChangeFlag bool
|
||||
}
|
||||
|
||||
func newFlagScanner(flag byte, start, end, str string) *flagScanner {
|
||||
return &flagScanner{flag, start, end, make([]byte, 0, len(str)), str, len(str), 0, false, false}
|
||||
}
|
||||
|
||||
// AppendString append string to fs.buf
|
||||
func (fs *flagScanner) AppendString(str string) { fs.buf = append(fs.buf, str...) }
|
||||
|
||||
// AppendChar append char to fs.buf
|
||||
func (fs *flagScanner) AppendChar(ch byte) { fs.buf = append(fs.buf, ch) }
|
||||
|
||||
// String return the fs.buf of string
|
||||
func (fs *flagScanner) String() string { return string(fs.buf) }
|
||||
|
||||
// Next iterate fs
|
||||
func (fs *flagScanner) Next() (byte, bool) {
|
||||
c := byte('\000')
|
||||
fs.ChangeFlag = false
|
||||
if fs.Pos == fs.Length {
|
||||
if fs.HasFlag {
|
||||
fs.AppendString(fs.end)
|
||||
}
|
||||
return c, true
|
||||
}
|
||||
c = fs.str[fs.Pos]
|
||||
if c == fs.flag {
|
||||
if fs.Pos < (fs.Length-1) && fs.str[fs.Pos+1] == fs.flag {
|
||||
fs.HasFlag = false
|
||||
fs.AppendChar(fs.flag)
|
||||
fs.Pos += 2
|
||||
return fs.Next()
|
||||
} else if fs.Pos != fs.Length-1 {
|
||||
if fs.HasFlag {
|
||||
fs.AppendString(fs.end)
|
||||
}
|
||||
fs.AppendString(fs.start)
|
||||
fs.ChangeFlag = true
|
||||
fs.HasFlag = true
|
||||
}
|
||||
}
|
||||
fs.Pos++
|
||||
return c, false
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- "1.9.x"
|
||||
- "1.10.x"
|
||||
- "1.11.x"
|
||||
env:
|
||||
global:
|
||||
GO111MODULE=off
|
||||
|
||||
before_install:
|
||||
- go get github.com/axw/gocov/gocov
|
||||
- go get github.com/mattn/goveralls
|
||||
- if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
|
||||
install:
|
||||
- go get -u -v $(go list -f '{{join .Imports "\n"}}{{"\n"}}{{join .TestImports "\n"}}' ./... | sort | uniq | grep '\.' | grep -v gopher-lua)
|
||||
script:
|
||||
- $HOME/gopath/bin/goveralls -service=travis-ci
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Yusuke Inuzuka
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,10 @@
|
|||
.PHONY: build test glua
|
||||
|
||||
build:
|
||||
./_tools/go-inline *.go && go fmt . && go build
|
||||
|
||||
glua: *.go pm/*.go cmd/glua/glua.go
|
||||
./_tools/go-inline *.go && go fmt . && go build cmd/glua/glua.go
|
||||
|
||||
test:
|
||||
./_tools/go-inline *.go && go fmt . && go test
|
|
@ -0,0 +1,888 @@
|
|||
|
||||
===============================================================================
|
||||
GopherLua: VM and compiler for Lua in Go.
|
||||
===============================================================================
|
||||
|
||||
.. image:: https://godoc.org/github.com/yuin/gopher-lua?status.svg
|
||||
:target: http://godoc.org/github.com/yuin/gopher-lua
|
||||
|
||||
.. image:: https://travis-ci.org/yuin/gopher-lua.svg
|
||||
:target: https://travis-ci.org/yuin/gopher-lua
|
||||
|
||||
.. image:: https://coveralls.io/repos/yuin/gopher-lua/badge.svg
|
||||
:target: https://coveralls.io/r/yuin/gopher-lua
|
||||
|
||||
.. image:: https://badges.gitter.im/Join%20Chat.svg
|
||||
:alt: Join the chat at https://gitter.im/yuin/gopher-lua
|
||||
:target: https://gitter.im/yuin/gopher-lua?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
|
||||
|
||||
|
|
||||
|
||||
|
||||
GopherLua is a Lua5.1 VM and compiler written in Go. GopherLua has a same goal
|
||||
with Lua: **Be a scripting language with extensible semantics** . It provides
|
||||
Go APIs that allow you to easily embed a scripting language to your Go host
|
||||
programs.
|
||||
|
||||
.. contents::
|
||||
:depth: 1
|
||||
|
||||
----------------------------------------------------------------
|
||||
Design principle
|
||||
----------------------------------------------------------------
|
||||
|
||||
- Be a scripting language with extensible semantics.
|
||||
- User-friendly Go API
|
||||
- The stack based API like the one used in the original Lua
|
||||
implementation will cause a performance improvements in GopherLua
|
||||
(It will reduce memory allocations and concrete type <-> interface conversions).
|
||||
GopherLua API is **not** the stack based API.
|
||||
GopherLua give preference to the user-friendliness over the performance.
|
||||
|
||||
----------------------------------------------------------------
|
||||
How about performance?
|
||||
----------------------------------------------------------------
|
||||
GopherLua is not fast but not too slow, I think.
|
||||
|
||||
GopherLua has almost equivalent ( or little bit better ) performance as Python3 on micro benchmarks.
|
||||
|
||||
There are some benchmarks on the `wiki page <https://github.com/yuin/gopher-lua/wiki/Benchmarks>`_ .
|
||||
|
||||
----------------------------------------------------------------
|
||||
Installation
|
||||
----------------------------------------------------------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
go get github.com/yuin/gopher-lua
|
||||
|
||||
GopherLua supports >= Go1.9.
|
||||
|
||||
----------------------------------------------------------------
|
||||
Usage
|
||||
----------------------------------------------------------------
|
||||
GopherLua APIs perform in much the same way as Lua, **but the stack is used only
|
||||
for passing arguments and receiving returned values.**
|
||||
|
||||
GopherLua supports channel operations. See **"Goroutines"** section.
|
||||
|
||||
Import a package.
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
import (
|
||||
"github.com/yuin/gopher-lua"
|
||||
)
|
||||
|
||||
Run scripts in the VM.
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
L := lua.NewState()
|
||||
defer L.Close()
|
||||
if err := L.DoString(`print("hello")`); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
L := lua.NewState()
|
||||
defer L.Close()
|
||||
if err := L.DoFile("hello.lua"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
Refer to `Lua Reference Manual <http://www.lua.org/manual/5.1/>`_ and `Go doc <http://godoc.org/github.com/yuin/gopher-lua>`_ for further information.
|
||||
|
||||
Note that elements that are not commented in `Go doc <http://godoc.org/github.com/yuin/gopher-lua>`_ equivalent to `Lua Reference Manual <http://www.lua.org/manual/5.1/>`_ , except GopherLua uses objects instead of Lua stack indices.
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Data model
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
All data in a GopherLua program is an ``LValue`` . ``LValue`` is an interface
|
||||
type that has following methods.
|
||||
|
||||
- ``String() string``
|
||||
- ``Type() LValueType``
|
||||
|
||||
|
||||
Objects implement an LValue interface are
|
||||
|
||||
================ ========================= ================== =======================
|
||||
Type name Go type Type() value Constants
|
||||
================ ========================= ================== =======================
|
||||
``LNilType`` (constants) ``LTNil`` ``LNil``
|
||||
``LBool`` (constants) ``LTBool`` ``LTrue``, ``LFalse``
|
||||
``LNumber`` float64 ``LTNumber`` ``-``
|
||||
``LString`` string ``LTString`` ``-``
|
||||
``LFunction`` struct pointer ``LTFunction`` ``-``
|
||||
``LUserData`` struct pointer ``LTUserData`` ``-``
|
||||
``LState`` struct pointer ``LTThread`` ``-``
|
||||
``LTable`` struct pointer ``LTTable`` ``-``
|
||||
``LChannel`` chan LValue ``LTChannel`` ``-``
|
||||
================ ========================= ================== =======================
|
||||
|
||||
You can test an object type in Go way(type assertion) or using a ``Type()`` value.
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
lv := L.Get(-1) // get the value at the top of the stack
|
||||
if str, ok := lv.(lua.LString); ok {
|
||||
// lv is LString
|
||||
fmt.Println(string(str))
|
||||
}
|
||||
if lv.Type() != lua.LTString {
|
||||
panic("string required.")
|
||||
}
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
lv := L.Get(-1) // get the value at the top of the stack
|
||||
if tbl, ok := lv.(*lua.LTable); ok {
|
||||
// lv is LTable
|
||||
fmt.Println(L.ObjLen(tbl))
|
||||
}
|
||||
|
||||
Note that ``LBool`` , ``LNumber`` , ``LString`` is not a pointer.
|
||||
|
||||
To test ``LNilType`` and ``LBool``, You **must** use pre-defined constants.
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
lv := L.Get(-1) // get the value at the top of the stack
|
||||
|
||||
if lv == lua.LTrue { // correct
|
||||
}
|
||||
|
||||
if bl, ok := lv.(lua.LBool); ok && bool(bl) { // wrong
|
||||
}
|
||||
|
||||
In Lua, both ``nil`` and ``false`` make a condition false. ``LVIsFalse`` and ``LVAsBool`` implement this specification.
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
lv := L.Get(-1) // get the value at the top of the stack
|
||||
if lua.LVIsFalse(lv) { // lv is nil or false
|
||||
}
|
||||
|
||||
if lua.LVAsBool(lv) { // lv is neither nil nor false
|
||||
}
|
||||
|
||||
Objects that based on go structs(``LFunction``. ``LUserData``, ``LTable``)
|
||||
have some public methods and fields. You can use these methods and fields for
|
||||
performance and debugging, but there are some limitations.
|
||||
|
||||
- Metatable does not work.
|
||||
- No error handlings.
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Callstack & Registry size
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
The size of an ``LState``'s callstack controls the maximum call depth for Lua functions within a script (Go function calls do not count).
|
||||
|
||||
The registry of an ``LState`` implements stack storage for calling functions (both Lua and Go functions) and also for temporary variables in expressions. Its storage requirements will increase with callstack usage and also with code complexity.
|
||||
|
||||
Both the registry and the callstack can be set to either a fixed size or to auto size.
|
||||
|
||||
When you have a large number of ``LStates`` instantiated in a process, it's worth taking the time to tune the registry and callstack options.
|
||||
|
||||
+++++++++
|
||||
Registry
|
||||
+++++++++
|
||||
|
||||
The registry can have an initial size, a maximum size and a step size configured on a per ``LState`` basis. This will allow the registry to grow as needed. It will not shrink again after growing.
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
L := lua.NewState(lua.Options{
|
||||
RegistrySize: 1024 * 20, // this is the initial size of the registry
|
||||
RegistryMaxSize: 1024 * 80, // this is the maximum size that the registry can grow to. If set to `0` (the default) then the registry will not auto grow
|
||||
RegistryGrowStep: 32, // this is how much to step up the registry by each time it runs out of space. The default is `32`.
|
||||
})
|
||||
defer L.Close()
|
||||
|
||||
A registry which is too small for a given script will ultimately result in a panic. A registry which is too big will waste memory (which can be significant if many ``LStates`` are instantiated).
|
||||
Auto growing registries incur a small performance hit at the point they are resized but will not otherwise affect performance.
|
||||
|
||||
+++++++++
|
||||
Callstack
|
||||
+++++++++
|
||||
|
||||
The callstack can operate in two different modes, fixed or auto size.
|
||||
A fixed size callstack has the highest performance and has a fixed memory overhead.
|
||||
An auto sizing callstack will allocate and release callstack pages on demand which will ensure the minimum amount of memory is in use at any time. The downside is it will incur a small performance impact every time a new page of callframes is allocated.
|
||||
By default an ``LState`` will allocate and free callstack frames in pages of 8, so the allocation overhead is not incurred on every function call. It is very likely that the performance impact of an auto resizing callstack will be negligible for most use cases.
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
L := lua.NewState(lua.Options{
|
||||
CallStackSize: 120, // this is the maximum callstack size of this LState
|
||||
MinimizeStackMemory: true, // Defaults to `false` if not specified. If set, the callstack will auto grow and shrink as needed up to a max of `CallStackSize`. If not set, the callstack will be fixed at `CallStackSize`.
|
||||
})
|
||||
defer L.Close()
|
||||
|
||||
++++++++++++++++
|
||||
Option defaults
|
||||
++++++++++++++++
|
||||
|
||||
The above examples show how to customize the callstack and registry size on a per ``LState`` basis. You can also adjust some defaults for when options are not specified by altering the values of ``lua.RegistrySize``, ``lua.RegistryGrowStep`` and ``lua.CallStackSize``.
|
||||
|
||||
An ``LState`` object that has been created by ``*LState#NewThread()`` inherits the callstack & registry size from the parent ``LState`` object.
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Miscellaneous lua.NewState options
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- **Options.SkipOpenLibs bool(default false)**
|
||||
- By default, GopherLua opens all built-in libraries when new LState is created.
|
||||
- You can skip this behaviour by setting this to ``true`` .
|
||||
- Using the various `OpenXXX(L *LState) int` functions you can open only those libraries that you require, for an example see below.
|
||||
- **Options.IncludeGoStackTrace bool(default false)**
|
||||
- By default, GopherLua does not show Go stack traces when panics occur.
|
||||
- You can get Go stack traces by setting this to ``true`` .
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
API
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Refer to `Lua Reference Manual <http://www.lua.org/manual/5.1/>`_ and `Go doc(LState methods) <http://godoc.org/github.com/yuin/gopher-lua>`_ for further information.
|
||||
|
||||
+++++++++++++++++++++++++++++++++++++++++
|
||||
Calling Go from Lua
|
||||
+++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
func Double(L *lua.LState) int {
|
||||
lv := L.ToInt(1) /* get argument */
|
||||
L.Push(lua.LNumber(lv * 2)) /* push result */
|
||||
return 1 /* number of results */
|
||||
}
|
||||
|
||||
func main() {
|
||||
L := lua.NewState()
|
||||
defer L.Close()
|
||||
L.SetGlobal("double", L.NewFunction(Double)) /* Original lua_setglobal uses stack... */
|
||||
}
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
print(double(20)) -- > "40"
|
||||
|
||||
Any function registered with GopherLua is a ``lua.LGFunction``, defined in ``value.go``
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
type LGFunction func(*LState) int
|
||||
|
||||
Working with coroutines.
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
co, _ := L.NewThread() /* create a new thread */
|
||||
fn := L.GetGlobal("coro").(*lua.LFunction) /* get function from lua */
|
||||
for {
|
||||
st, err, values := L.Resume(co, fn)
|
||||
if st == lua.ResumeError {
|
||||
fmt.Println("yield break(error)")
|
||||
fmt.Println(err.Error())
|
||||
break
|
||||
}
|
||||
|
||||
for i, lv := range values {
|
||||
fmt.Printf("%v : %v\n", i, lv)
|
||||
}
|
||||
|
||||
if st == lua.ResumeOK {
|
||||
fmt.Println("yield break(ok)")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
+++++++++++++++++++++++++++++++++++++++++
|
||||
Opening a subset of builtin modules
|
||||
+++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
The following demonstrates how to open a subset of the built-in modules in Lua, say for example to avoid enabling modules with access to local files or system calls.
|
||||
|
||||
main.go
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
func main() {
|
||||
L := lua.NewState(lua.Options{SkipOpenLibs: true})
|
||||
defer L.Close()
|
||||
for _, pair := range []struct {
|
||||
n string
|
||||
f lua.LGFunction
|
||||
}{
|
||||
{lua.LoadLibName, lua.OpenPackage}, // Must be first
|
||||
{lua.BaseLibName, lua.OpenBase},
|
||||
{lua.TabLibName, lua.OpenTable},
|
||||
} {
|
||||
if err := L.CallByParam(lua.P{
|
||||
Fn: L.NewFunction(pair.f),
|
||||
NRet: 0,
|
||||
Protect: true,
|
||||
}, lua.LString(pair.n)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
if err := L.DoFile("main.lua"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
+++++++++++++++++++++++++++++++++++++++++
|
||||
Creating a module by Go
|
||||
+++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
mymodule.go
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
package mymodule
|
||||
|
||||
import (
|
||||
"github.com/yuin/gopher-lua"
|
||||
)
|
||||
|
||||
func Loader(L *lua.LState) int {
|
||||
// register functions to the table
|
||||
mod := L.SetFuncs(L.NewTable(), exports)
|
||||
// register other stuff
|
||||
L.SetField(mod, "name", lua.LString("value"))
|
||||
|
||||
// returns the module
|
||||
L.Push(mod)
|
||||
return 1
|
||||
}
|
||||
|
||||
var exports = map[string]lua.LGFunction{
|
||||
"myfunc": myfunc,
|
||||
}
|
||||
|
||||
func myfunc(L *lua.LState) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
mymain.go
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"./mymodule"
|
||||
"github.com/yuin/gopher-lua"
|
||||
)
|
||||
|
||||
func main() {
|
||||
L := lua.NewState()
|
||||
defer L.Close()
|
||||
L.PreloadModule("mymodule", mymodule.Loader)
|
||||
if err := L.DoFile("main.lua"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
main.lua
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
local m = require("mymodule")
|
||||
m.myfunc()
|
||||
print(m.name)
|
||||
|
||||
|
||||
+++++++++++++++++++++++++++++++++++++++++
|
||||
Calling Lua from Go
|
||||
+++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
L := lua.NewState()
|
||||
defer L.Close()
|
||||
if err := L.DoFile("double.lua"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := L.CallByParam(lua.P{
|
||||
Fn: L.GetGlobal("double"),
|
||||
NRet: 1,
|
||||
Protect: true,
|
||||
}, lua.LNumber(10)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ret := L.Get(-1) // returned value
|
||||
L.Pop(1) // remove received value
|
||||
|
||||
If ``Protect`` is false, GopherLua will panic instead of returning an ``error`` value.
|
||||
|
||||
+++++++++++++++++++++++++++++++++++++++++
|
||||
User-Defined types
|
||||
+++++++++++++++++++++++++++++++++++++++++
|
||||
You can extend GopherLua with new types written in Go.
|
||||
``LUserData`` is provided for this purpose.
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
type Person struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
const luaPersonTypeName = "person"
|
||||
|
||||
// Registers my person type to given L.
|
||||
func registerPersonType(L *lua.LState) {
|
||||
mt := L.NewTypeMetatable(luaPersonTypeName)
|
||||
L.SetGlobal("person", mt)
|
||||
// static attributes
|
||||
L.SetField(mt, "new", L.NewFunction(newPerson))
|
||||
// methods
|
||||
L.SetField(mt, "__index", L.SetFuncs(L.NewTable(), personMethods))
|
||||
}
|
||||
|
||||
// Constructor
|
||||
func newPerson(L *lua.LState) int {
|
||||
person := &Person{L.CheckString(1)}
|
||||
ud := L.NewUserData()
|
||||
ud.Value = person
|
||||
L.SetMetatable(ud, L.GetTypeMetatable(luaPersonTypeName))
|
||||
L.Push(ud)
|
||||
return 1
|
||||
}
|
||||
|
||||
// Checks whether the first lua argument is a *LUserData with *Person and returns this *Person.
|
||||
func checkPerson(L *lua.LState) *Person {
|
||||
ud := L.CheckUserData(1)
|
||||
if v, ok := ud.Value.(*Person); ok {
|
||||
return v
|
||||
}
|
||||
L.ArgError(1, "person expected")
|
||||
return nil
|
||||
}
|
||||
|
||||
var personMethods = map[string]lua.LGFunction{
|
||||
"name": personGetSetName,
|
||||
}
|
||||
|
||||
// Getter and setter for the Person#Name
|
||||
func personGetSetName(L *lua.LState) int {
|
||||
p := checkPerson(L)
|
||||
if L.GetTop() == 2 {
|
||||
p.Name = L.CheckString(2)
|
||||
return 0
|
||||
}
|
||||
L.Push(lua.LString(p.Name))
|
||||
return 1
|
||||
}
|
||||
|
||||
func main() {
|
||||
L := lua.NewState()
|
||||
defer L.Close()
|
||||
registerPersonType(L)
|
||||
if err := L.DoString(`
|
||||
p = person.new("Steeve")
|
||||
print(p:name()) -- "Steeve"
|
||||
p:name("Alice")
|
||||
print(p:name()) -- "Alice"
|
||||
`); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
+++++++++++++++++++++++++++++++++++++++++
|
||||
Terminating a running LState
|
||||
+++++++++++++++++++++++++++++++++++++++++
|
||||
GopherLua supports the `Go Concurrency Patterns: Context <https://blog.golang.org/context>`_ .
|
||||
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
L := lua.NewState()
|
||||
defer L.Close()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
// set the context to our LState
|
||||
L.SetContext(ctx)
|
||||
err := L.DoString(`
|
||||
local clock = os.clock
|
||||
function sleep(n) -- seconds
|
||||
local t0 = clock()
|
||||
while clock() - t0 <= n do end
|
||||
end
|
||||
sleep(3)
|
||||
`)
|
||||
// err.Error() contains "context deadline exceeded"
|
||||
|
||||
With coroutines
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
L := lua.NewState()
|
||||
defer L.Close()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
L.SetContext(ctx)
|
||||
defer cancel()
|
||||
L.DoString(`
|
||||
function coro()
|
||||
local i = 0
|
||||
while true do
|
||||
coroutine.yield(i)
|
||||
i = i+1
|
||||
end
|
||||
return i
|
||||
end
|
||||
`)
|
||||
co, cocancel := L.NewThread()
|
||||
defer cocancel()
|
||||
fn := L.GetGlobal("coro").(*LFunction)
|
||||
|
||||
_, err, values := L.Resume(co, fn) // err is nil
|
||||
|
||||
cancel() // cancel the parent context
|
||||
|
||||
_, err, values = L.Resume(co, fn) // err is NOT nil : child context was canceled
|
||||
|
||||
**Note that using a context causes performance degradation.**
|
||||
|
||||
.. code-block::
|
||||
|
||||
time ./glua-with-context.exe fib.lua
|
||||
9227465
|
||||
0.01s user 0.11s system 1% cpu 7.505 total
|
||||
|
||||
time ./glua-without-context.exe fib.lua
|
||||
9227465
|
||||
0.01s user 0.01s system 0% cpu 5.306 total
|
||||
|
||||
+++++++++++++++++++++++++++++++++++++++++
|
||||
Sharing Lua byte code between LStates
|
||||
+++++++++++++++++++++++++++++++++++++++++
|
||||
Calling ``DoFile`` will load a Lua script, compile it to byte code and run the byte code in a ``LState``.
|
||||
|
||||
If you have multiple ``LStates`` which are all required to run the same script, you can share the byte code between them,
|
||||
which will save on memory.
|
||||
Sharing byte code is safe as it is read only and cannot be altered by lua scripts.
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
// CompileLua reads the passed lua file from disk and compiles it.
|
||||
func CompileLua(filePath string) (*lua.FunctionProto, error) {
|
||||
file, err := os.Open(filePath)
|
||||
defer file.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reader := bufio.NewReader(file)
|
||||
chunk, err := parse.Parse(reader, filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
proto, err := lua.Compile(chunk, filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return proto, nil
|
||||
}
|
||||
|
||||
// DoCompiledFile takes a FunctionProto, as returned by CompileLua, and runs it in the LState. It is equivalent
|
||||
// to calling DoFile on the LState with the original source file.
|
||||
func DoCompiledFile(L *lua.LState, proto *lua.FunctionProto) error {
|
||||
lfunc := L.NewFunctionFromProto(proto)
|
||||
L.Push(lfunc)
|
||||
return L.PCall(0, lua.MultRet, nil)
|
||||
}
|
||||
|
||||
// Example shows how to share the compiled byte code from a lua script between multiple VMs.
|
||||
func Example() {
|
||||
codeToShare := CompileLua("mylua.lua")
|
||||
a := lua.NewState()
|
||||
b := lua.NewState()
|
||||
c := lua.NewState()
|
||||
DoCompiledFile(a, codeToShare)
|
||||
DoCompiledFile(b, codeToShare)
|
||||
DoCompiledFile(c, codeToShare)
|
||||
}
|
||||
|
||||
+++++++++++++++++++++++++++++++++++++++++
|
||||
Goroutines
|
||||
+++++++++++++++++++++++++++++++++++++++++
|
||||
The ``LState`` is not goroutine-safe. It is recommended to use one LState per goroutine and communicate between goroutines by using channels.
|
||||
|
||||
Channels are represented by ``channel`` objects in GopherLua. And a ``channel`` table provides functions for performing channel operations.
|
||||
|
||||
Some objects can not be sent over channels due to having non-goroutine-safe objects inside itself.
|
||||
|
||||
- a thread(state)
|
||||
- a function
|
||||
- an userdata
|
||||
- a table with a metatable
|
||||
|
||||
You **must not** send these objects from Go APIs to channels.
|
||||
|
||||
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
func receiver(ch, quit chan lua.LValue) {
|
||||
L := lua.NewState()
|
||||
defer L.Close()
|
||||
L.SetGlobal("ch", lua.LChannel(ch))
|
||||
L.SetGlobal("quit", lua.LChannel(quit))
|
||||
if err := L.DoString(`
|
||||
local exit = false
|
||||
while not exit do
|
||||
channel.select(
|
||||
{"|<-", ch, function(ok, v)
|
||||
if not ok then
|
||||
print("channel closed")
|
||||
exit = true
|
||||
else
|
||||
print("received:", v)
|
||||
end
|
||||
end},
|
||||
{"|<-", quit, function(ok, v)
|
||||
print("quit")
|
||||
exit = true
|
||||
end}
|
||||
)
|
||||
end
|
||||
`); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func sender(ch, quit chan lua.LValue) {
|
||||
L := lua.NewState()
|
||||
defer L.Close()
|
||||
L.SetGlobal("ch", lua.LChannel(ch))
|
||||
L.SetGlobal("quit", lua.LChannel(quit))
|
||||
if err := L.DoString(`
|
||||
ch:send("1")
|
||||
ch:send("2")
|
||||
`); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ch <- lua.LString("3")
|
||||
quit <- lua.LTrue
|
||||
}
|
||||
|
||||
func main() {
|
||||
ch := make(chan lua.LValue)
|
||||
quit := make(chan lua.LValue)
|
||||
go receiver(ch, quit)
|
||||
go sender(ch, quit)
|
||||
time.Sleep(3 * time.Second)
|
||||
}
|
||||
|
||||
'''''''''''''''
|
||||
Go API
|
||||
'''''''''''''''
|
||||
|
||||
``ToChannel``, ``CheckChannel``, ``OptChannel`` are available.
|
||||
|
||||
Refer to `Go doc(LState methods) <http://godoc.org/github.com/yuin/gopher-lua>`_ for further information.
|
||||
|
||||
'''''''''''''''
|
||||
Lua API
|
||||
'''''''''''''''
|
||||
|
||||
- **channel.make([buf:int]) -> ch:channel**
|
||||
- Create new channel that has a buffer size of ``buf``. By default, ``buf`` is 0.
|
||||
|
||||
- **channel.select(case:table [, case:table, case:table ...]) -> {index:int, recv:any, ok}**
|
||||
- Same as the ``select`` statement in Go. It returns the index of the chosen case and, if that
|
||||
case was a receive operation, the value received and a boolean indicating whether the channel has been closed.
|
||||
- ``case`` is a table that outlined below.
|
||||
- receiving: `{"|<-", ch:channel [, handler:func(ok, data:any)]}`
|
||||
- sending: `{"<-|", ch:channel, data:any [, handler:func(data:any)]}`
|
||||
- default: `{"default" [, handler:func()]}`
|
||||
|
||||
``channel.select`` examples:
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
local idx, recv, ok = channel.select(
|
||||
{"|<-", ch1},
|
||||
{"|<-", ch2}
|
||||
)
|
||||
if not ok then
|
||||
print("closed")
|
||||
elseif idx == 1 then -- received from ch1
|
||||
print(recv)
|
||||
elseif idx == 2 then -- received from ch2
|
||||
print(recv)
|
||||
end
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
channel.select(
|
||||
{"|<-", ch1, function(ok, data)
|
||||
print(ok, data)
|
||||
end},
|
||||
{"<-|", ch2, "value", function(data)
|
||||
print(data)
|
||||
end},
|
||||
{"default", function()
|
||||
print("default action")
|
||||
end}
|
||||
)
|
||||
|
||||
- **channel:send(data:any)**
|
||||
- Send ``data`` over the channel.
|
||||
- **channel:receive() -> ok:bool, data:any**
|
||||
- Receive some data over the channel.
|
||||
- **channel:close()**
|
||||
- Close the channel.
|
||||
|
||||
''''''''''''''''''''''''''''''
|
||||
The LState pool pattern
|
||||
''''''''''''''''''''''''''''''
|
||||
To create per-thread LState instances, You can use the ``sync.Pool`` like mechanism.
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
type lStatePool struct {
|
||||
m sync.Mutex
|
||||
saved []*lua.LState
|
||||
}
|
||||
|
||||
func (pl *lStatePool) Get() *lua.LState {
|
||||
pl.m.Lock()
|
||||
defer pl.m.Unlock()
|
||||
n := len(pl.saved)
|
||||
if n == 0 {
|
||||
return pl.New()
|
||||
}
|
||||
x := pl.saved[n-1]
|
||||
pl.saved = pl.saved[0 : n-1]
|
||||
return x
|
||||
}
|
||||
|
||||
func (pl *lStatePool) New() *lua.LState {
|
||||
L := lua.NewState()
|
||||
// setting the L up here.
|
||||
// load scripts, set global variables, share channels, etc...
|
||||
return L
|
||||
}
|
||||
|
||||
func (pl *lStatePool) Put(L *lua.LState) {
|
||||
pl.m.Lock()
|
||||
defer pl.m.Unlock()
|
||||
pl.saved = append(pl.saved, L)
|
||||
}
|
||||
|
||||
func (pl *lStatePool) Shutdown() {
|
||||
for _, L := range pl.saved {
|
||||
L.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Global LState pool
|
||||
var luaPool = &lStatePool{
|
||||
saved: make([]*lua.LState, 0, 4),
|
||||
}
|
||||
|
||||
Now, you can get per-thread LState objects from the ``luaPool`` .
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
func MyWorker() {
|
||||
L := luaPool.Get()
|
||||
defer luaPool.Put(L)
|
||||
/* your code here */
|
||||
}
|
||||
|
||||
func main() {
|
||||
defer luaPool.Shutdown()
|
||||
go MyWorker()
|
||||
go MyWorker()
|
||||
/* etc... */
|
||||
}
|
||||
|
||||
|
||||
----------------------------------------------------------------
|
||||
Differences between Lua and GopherLua
|
||||
----------------------------------------------------------------
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Goroutines
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- GopherLua supports channel operations.
|
||||
- GopherLua has a type named ``channel``.
|
||||
- The ``channel`` table provides functions for performing channel operations.
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Unsupported functions
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- ``string.dump``
|
||||
- ``os.setlocale``
|
||||
- ``lua_Debug.namewhat``
|
||||
- ``package.loadlib``
|
||||
- debug hooks
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Miscellaneous notes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- ``collectgarbage`` does not take any arguments and runs the garbage collector for the entire Go program.
|
||||
- ``file:setvbuf`` does not support a line buffering.
|
||||
- Daylight saving time is not supported.
|
||||
- GopherLua has a function to set an environment variable : ``os.setenv(name, value)``
|
||||
|
||||
----------------------------------------------------------------
|
||||
Standalone interpreter
|
||||
----------------------------------------------------------------
|
||||
Lua has an interpreter called ``lua`` . GopherLua has an interpreter called ``glua`` .
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
go get github.com/yuin/gopher-lua/cmd/glua
|
||||
|
||||
``glua`` has same options as ``lua`` .
|
||||
|
||||
----------------------------------------------------------------
|
||||
How to Contribute
|
||||
----------------------------------------------------------------
|
||||
See `Guidlines for contributors <https://github.com/yuin/gopher-lua/tree/master/.github/CONTRIBUTING.md>`_ .
|
||||
|
||||
----------------------------------------------------------------
|
||||
Libraries for GopherLua
|
||||
----------------------------------------------------------------
|
||||
|
||||
- `gopher-luar <https://github.com/layeh/gopher-luar>`_ : Simplifies data passing to and from gopher-lua
|
||||
- `gluamapper <https://github.com/yuin/gluamapper>`_ : Mapping a Lua table to a Go struct
|
||||
- `gluare <https://github.com/yuin/gluare>`_ : Regular expressions for gopher-lua
|
||||
- `gluahttp <https://github.com/cjoudrey/gluahttp>`_ : HTTP request module for gopher-lua
|
||||
- `gopher-json <https://github.com/layeh/gopher-json>`_ : A simple JSON encoder/decoder for gopher-lua
|
||||
- `gluayaml <https://github.com/kohkimakimoto/gluayaml>`_ : Yaml parser for gopher-lua
|
||||
- `glua-lfs <https://github.com/layeh/gopher-lfs>`_ : Partially implements the luafilesystem module for gopher-lua
|
||||
- `gluaurl <https://github.com/cjoudrey/gluaurl>`_ : A url parser/builder module for gopher-lua
|
||||
- `gluahttpscrape <https://github.com/felipejfc/gluahttpscrape>`_ : A simple HTML scraper module for gopher-lua
|
||||
- `gluaxmlpath <https://github.com/ailncode/gluaxmlpath>`_ : An xmlpath module for gopher-lua
|
||||
- `gmoonscript <https://github.com/rucuriousyet/gmoonscript>`_ : Moonscript Compiler for the Gopher Lua VM
|
||||
- `loguago <https://github.com/rucuriousyet/loguago>`_ : Zerolog wrapper for Gopher-Lua
|
||||
- `gluacrypto <https://github.com/tengattack/gluacrypto>`_ : A native Go implementation of crypto library for the GopherLua VM.
|
||||
- `gluasql <https://github.com/tengattack/gluasql>`_ : A native Go implementation of SQL client for the GopherLua VM.
|
||||
- `purr <https://github.com/leyafo/purr>`_ : A http mock testing tool.
|
||||
- `vadv/gopher-lua-libs <https://github.com/vadv/gopher-lua-libs>`_ : Some usefull libraries for GopherLua VM.
|
||||
- `gluaperiphery <https://github.com/BixData/gluaperiphery>`_ : A periphery library for the GopherLua VM (GPIO, SPI, I2C, MMIO, and Serial peripheral I/O for Linux).
|
||||
- `glua-async <https://github.com/CuberL/glua-async>`_ : An async/await implement for gopher-lua.
|
||||
- `gopherlua-debugger <https://github.com/edolphin-ydf/gopherlua-debugger>`_ : A debugger for gopher-lua
|
||||
- `gluamahonia <https://github.com/super1207/gluamahonia>`_ : An encoding converter for gopher-lua
|
||||
----------------------------------------------------------------
|
||||
Donation
|
||||
----------------------------------------------------------------
|
||||
|
||||
BTC: 1NEDSyUmo4SMTDP83JJQSWi1MvQUGGNMZB
|
||||
|
||||
----------------------------------------------------------------
|
||||
License
|
||||
----------------------------------------------------------------
|
||||
MIT
|
||||
|
||||
----------------------------------------------------------------
|
||||
Author
|
||||
----------------------------------------------------------------
|
||||
Yusuke Inuzuka
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,79 @@
|
|||
package lua
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// iface is an internal representation of the go-interface.
|
||||
type iface struct {
|
||||
itab unsafe.Pointer
|
||||
word unsafe.Pointer
|
||||
}
|
||||
|
||||
const preloadLimit LNumber = 128
|
||||
|
||||
var _fv float64
|
||||
var _uv uintptr
|
||||
|
||||
var preloads [int(preloadLimit)]LValue
|
||||
|
||||
func init() {
|
||||
for i := 0; i < int(preloadLimit); i++ {
|
||||
preloads[i] = LNumber(i)
|
||||
}
|
||||
}
|
||||
|
||||
// allocator is a fast bulk memory allocator for the LValue.
|
||||
type allocator struct {
|
||||
size int
|
||||
fptrs []float64
|
||||
fheader *reflect.SliceHeader
|
||||
|
||||
scratchValue LValue
|
||||
scratchValueP *iface
|
||||
}
|
||||
|
||||
func newAllocator(size int) *allocator {
|
||||
al := &allocator{
|
||||
size: size,
|
||||
fptrs: make([]float64, 0, size),
|
||||
fheader: nil,
|
||||
}
|
||||
al.fheader = (*reflect.SliceHeader)(unsafe.Pointer(&al.fptrs))
|
||||
al.scratchValue = LNumber(0)
|
||||
al.scratchValueP = (*iface)(unsafe.Pointer(&al.scratchValue))
|
||||
|
||||
return al
|
||||
}
|
||||
|
||||
// LNumber2I takes a number value and returns an interface LValue representing the same number.
|
||||
// Converting an LNumber to a LValue naively, by doing:
|
||||
// `var val LValue = myLNumber`
|
||||
// will result in an individual heap alloc of 8 bytes for the float value. LNumber2I amortizes the cost and memory
|
||||
// overhead of these allocs by allocating blocks of floats instead.
|
||||
// The downside of this is that all of the floats on a given block have to become eligible for gc before the block
|
||||
// as a whole can be gc-ed.
|
||||
func (al *allocator) LNumber2I(v LNumber) LValue {
|
||||
// first check for shared preloaded numbers
|
||||
if v >= 0 && v < preloadLimit && float64(v) == float64(int64(v)) {
|
||||
return preloads[int(v)]
|
||||
}
|
||||
|
||||
// check if we need a new alloc page
|
||||
if cap(al.fptrs) == len(al.fptrs) {
|
||||
al.fptrs = make([]float64, 0, al.size)
|
||||
al.fheader = (*reflect.SliceHeader)(unsafe.Pointer(&al.fptrs))
|
||||
}
|
||||
|
||||
// alloc a new float, and store our value into it
|
||||
al.fptrs = append(al.fptrs, float64(v))
|
||||
fptr := &al.fptrs[len(al.fptrs)-1]
|
||||
|
||||
// hack our scratch LValue to point to our allocated value
|
||||
// this scratch lvalue is copied when this function returns meaning the scratch value can be reused
|
||||
// on the next call
|
||||
al.scratchValueP.word = unsafe.Pointer(fptr)
|
||||
|
||||
return al.scratchValue
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package ast
|
||||
|
||||
type PositionHolder interface {
|
||||
Line() int
|
||||
SetLine(int)
|
||||
LastLine() int
|
||||
SetLastLine(int)
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
line int
|
||||
lastline int
|
||||
}
|
||||
|
||||
func (self *Node) Line() int {
|
||||
return self.line
|
||||
}
|
||||
|
||||
func (self *Node) SetLine(line int) {
|
||||
self.line = line
|
||||
}
|
||||
|
||||
func (self *Node) LastLine() int {
|
||||
return self.lastline
|
||||
}
|
||||
|
||||
func (self *Node) SetLastLine(line int) {
|
||||
self.lastline = line
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
package ast
|
||||
|
||||
type Expr interface {
|
||||
PositionHolder
|
||||
exprMarker()
|
||||
}
|
||||
|
||||
type ExprBase struct {
|
||||
Node
|
||||
}
|
||||
|
||||
func (expr *ExprBase) exprMarker() {}
|
||||
|
||||
/* ConstExprs {{{ */
|
||||
|
||||
type ConstExpr interface {
|
||||
Expr
|
||||
constExprMarker()
|
||||
}
|
||||
|
||||
type ConstExprBase struct {
|
||||
ExprBase
|
||||
}
|
||||
|
||||
func (expr *ConstExprBase) constExprMarker() {}
|
||||
|
||||
type TrueExpr struct {
|
||||
ConstExprBase
|
||||
}
|
||||
|
||||
type FalseExpr struct {
|
||||
ConstExprBase
|
||||
}
|
||||
|
||||
type NilExpr struct {
|
||||
ConstExprBase
|
||||
}
|
||||
|
||||
type NumberExpr struct {
|
||||
ConstExprBase
|
||||
|
||||
Value string
|
||||
}
|
||||
|
||||
type StringExpr struct {
|
||||
ConstExprBase
|
||||
|
||||
Value string
|
||||
}
|
||||
|
||||
/* ConstExprs }}} */
|
||||
|
||||
type Comma3Expr struct {
|
||||
ExprBase
|
||||
AdjustRet bool
|
||||
}
|
||||
|
||||
type IdentExpr struct {
|
||||
ExprBase
|
||||
|
||||
Value string
|
||||
}
|
||||
|
||||
type AttrGetExpr struct {
|
||||
ExprBase
|
||||
|
||||
Object Expr
|
||||
Key Expr
|
||||
}
|
||||
|
||||
type TableExpr struct {
|
||||
ExprBase
|
||||
|
||||
Fields []*Field
|
||||
}
|
||||
|
||||
type FuncCallExpr struct {
|
||||
ExprBase
|
||||
|
||||
Func Expr
|
||||
Receiver Expr
|
||||
Method string
|
||||
Args []Expr
|
||||
AdjustRet bool
|
||||
}
|
||||
|
||||
type LogicalOpExpr struct {
|
||||
ExprBase
|
||||
|
||||
Operator string
|
||||
Lhs Expr
|
||||
Rhs Expr
|
||||
}
|
||||
|
||||
type RelationalOpExpr struct {
|
||||
ExprBase
|
||||
|
||||
Operator string
|
||||
Lhs Expr
|
||||
Rhs Expr
|
||||
}
|
||||
|
||||
type StringConcatOpExpr struct {
|
||||
ExprBase
|
||||
|
||||
Lhs Expr
|
||||
Rhs Expr
|
||||
}
|
||||
|
||||
type ArithmeticOpExpr struct {
|
||||
ExprBase
|
||||
|
||||
Operator string
|
||||
Lhs Expr
|
||||
Rhs Expr
|
||||
}
|
||||
|
||||
type UnaryMinusOpExpr struct {
|
||||
ExprBase
|
||||
Expr Expr
|
||||
}
|
||||
|
||||
type UnaryNotOpExpr struct {
|
||||
ExprBase
|
||||
Expr Expr
|
||||
}
|
||||
|
||||
type UnaryLenOpExpr struct {
|
||||
ExprBase
|
||||
Expr Expr
|
||||
}
|
||||
|
||||
type FunctionExpr struct {
|
||||
ExprBase
|
||||
|
||||
ParList *ParList
|
||||
Stmts []Stmt
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package ast
|
||||
|
||||
type Field struct {
|
||||
Key Expr
|
||||
Value Expr
|
||||
}
|
||||
|
||||
type ParList struct {
|
||||
HasVargs bool
|
||||
Names []string
|
||||
}
|
||||
|
||||
type FuncName struct {
|
||||
Func Expr
|
||||
Receiver Expr
|
||||
Method string
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
package ast
|
||||
|
||||
type Stmt interface {
|
||||
PositionHolder
|
||||
stmtMarker()
|
||||
}
|
||||
|
||||
type StmtBase struct {
|
||||
Node
|
||||
}
|
||||
|
||||
func (stmt *StmtBase) stmtMarker() {}
|
||||
|
||||
type AssignStmt struct {
|
||||
StmtBase
|
||||
|
||||
Lhs []Expr
|
||||
Rhs []Expr
|
||||
}
|
||||
|
||||
type LocalAssignStmt struct {
|
||||
StmtBase
|
||||
|
||||
Names []string
|
||||
Exprs []Expr
|
||||
}
|
||||
|
||||
type FuncCallStmt struct {
|
||||
StmtBase
|
||||
|
||||
Expr Expr
|
||||
}
|
||||
|
||||
type DoBlockStmt struct {
|
||||
StmtBase
|
||||
|
||||
Stmts []Stmt
|
||||
}
|
||||
|
||||
type WhileStmt struct {
|
||||
StmtBase
|
||||
|
||||
Condition Expr
|
||||
Stmts []Stmt
|
||||
}
|
||||
|
||||
type RepeatStmt struct {
|
||||
StmtBase
|
||||
|
||||
Condition Expr
|
||||
Stmts []Stmt
|
||||
}
|
||||
|
||||
type IfStmt struct {
|
||||
StmtBase
|
||||
|
||||
Condition Expr
|
||||
Then []Stmt
|
||||
Else []Stmt
|
||||
}
|
||||
|
||||
type NumberForStmt struct {
|
||||
StmtBase
|
||||
|
||||
Name string
|
||||
Init Expr
|
||||
Limit Expr
|
||||
Step Expr
|
||||
Stmts []Stmt
|
||||
}
|
||||
|
||||
type GenericForStmt struct {
|
||||
StmtBase
|
||||
|
||||
Names []string
|
||||
Exprs []Expr
|
||||
Stmts []Stmt
|
||||
}
|
||||
|
||||
type FuncDefStmt struct {
|
||||
StmtBase
|
||||
|
||||
Name *FuncName
|
||||
Func *FunctionExpr
|
||||
}
|
||||
|
||||
type ReturnStmt struct {
|
||||
StmtBase
|
||||
|
||||
Exprs []Expr
|
||||
}
|
||||
|
||||
type BreakStmt struct {
|
||||
StmtBase
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Position struct {
|
||||
Source string
|
||||
Line int
|
||||
Column int
|
||||
}
|
||||
|
||||
type Token struct {
|
||||
Type int
|
||||
Name string
|
||||
Str string
|
||||
Pos Position
|
||||
}
|
||||
|
||||
func (self *Token) String() string {
|
||||
return fmt.Sprintf("<type:%v, str:%v>", self.Name, self.Str)
|
||||
}
|
|
@ -0,0 +1,460 @@
|
|||
package lua
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/* checkType {{{ */
|
||||
|
||||
func (ls *LState) CheckAny(n int) LValue {
|
||||
if n > ls.GetTop() {
|
||||
ls.ArgError(n, "value expected")
|
||||
}
|
||||
return ls.Get(n)
|
||||
}
|
||||
|
||||
func (ls *LState) CheckInt(n int) int {
|
||||
v := ls.Get(n)
|
||||
if intv, ok := v.(LNumber); ok {
|
||||
return int(intv)
|
||||
}
|
||||
ls.TypeError(n, LTNumber)
|
||||
return 0
|
||||
}
|
||||
|
||||
func (ls *LState) CheckInt64(n int) int64 {
|
||||
v := ls.Get(n)
|
||||
if intv, ok := v.(LNumber); ok {
|
||||
return int64(intv)
|
||||
}
|
||||
ls.TypeError(n, LTNumber)
|
||||
return 0
|
||||
}
|
||||
|
||||
func (ls *LState) CheckNumber(n int) LNumber {
|
||||
v := ls.Get(n)
|
||||
if lv, ok := v.(LNumber); ok {
|
||||
return lv
|
||||
}
|
||||
ls.TypeError(n, LTNumber)
|
||||
return 0
|
||||
}
|
||||
|
||||
func (ls *LState) CheckString(n int) string {
|
||||
v := ls.Get(n)
|
||||
if lv, ok := v.(LString); ok {
|
||||
return string(lv)
|
||||
} else if LVCanConvToString(v) {
|
||||
return ls.ToString(n)
|
||||
}
|
||||
ls.TypeError(n, LTString)
|
||||
return ""
|
||||
}
|
||||
|
||||
func (ls *LState) CheckBool(n int) bool {
|
||||
v := ls.Get(n)
|
||||
if lv, ok := v.(LBool); ok {
|
||||
return bool(lv)
|
||||
}
|
||||
ls.TypeError(n, LTBool)
|
||||
return false
|
||||
}
|
||||
|
||||
func (ls *LState) CheckTable(n int) *LTable {
|
||||
v := ls.Get(n)
|
||||
if lv, ok := v.(*LTable); ok {
|
||||
return lv
|
||||
}
|
||||
ls.TypeError(n, LTTable)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ls *LState) CheckFunction(n int) *LFunction {
|
||||
v := ls.Get(n)
|
||||
if lv, ok := v.(*LFunction); ok {
|
||||
return lv
|
||||
}
|
||||
ls.TypeError(n, LTFunction)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ls *LState) CheckUserData(n int) *LUserData {
|
||||
v := ls.Get(n)
|
||||
if lv, ok := v.(*LUserData); ok {
|
||||
return lv
|
||||
}
|
||||
ls.TypeError(n, LTUserData)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ls *LState) CheckThread(n int) *LState {
|
||||
v := ls.Get(n)
|
||||
if lv, ok := v.(*LState); ok {
|
||||
return lv
|
||||
}
|
||||
ls.TypeError(n, LTThread)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ls *LState) CheckType(n int, typ LValueType) {
|
||||
v := ls.Get(n)
|
||||
if v.Type() != typ {
|
||||
ls.TypeError(n, typ)
|
||||
}
|
||||
}
|
||||
|
||||
func (ls *LState) CheckTypes(n int, typs ...LValueType) {
|
||||
vt := ls.Get(n).Type()
|
||||
for _, typ := range typs {
|
||||
if vt == typ {
|
||||
return
|
||||
}
|
||||
}
|
||||
buf := []string{}
|
||||
for _, typ := range typs {
|
||||
buf = append(buf, typ.String())
|
||||
}
|
||||
ls.ArgError(n, strings.Join(buf, " or ")+" expected, got "+ls.Get(n).Type().String())
|
||||
}
|
||||
|
||||
func (ls *LState) CheckOption(n int, options []string) int {
|
||||
str := ls.CheckString(n)
|
||||
for i, v := range options {
|
||||
if v == str {
|
||||
return i
|
||||
}
|
||||
}
|
||||
ls.ArgError(n, fmt.Sprintf("invalid option: %s (must be one of %s)", str, strings.Join(options, ",")))
|
||||
return 0
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
/* optType {{{ */
|
||||
|
||||
func (ls *LState) OptInt(n int, d int) int {
|
||||
v := ls.Get(n)
|
||||
if v == LNil {
|
||||
return d
|
||||
}
|
||||
if intv, ok := v.(LNumber); ok {
|
||||
return int(intv)
|
||||
}
|
||||
ls.TypeError(n, LTNumber)
|
||||
return 0
|
||||
}
|
||||
|
||||
func (ls *LState) OptInt64(n int, d int64) int64 {
|
||||
v := ls.Get(n)
|
||||
if v == LNil {
|
||||
return d
|
||||
}
|
||||
if intv, ok := v.(LNumber); ok {
|
||||
return int64(intv)
|
||||
}
|
||||
ls.TypeError(n, LTNumber)
|
||||
return 0
|
||||
}
|
||||
|
||||
func (ls *LState) OptNumber(n int, d LNumber) LNumber {
|
||||
v := ls.Get(n)
|
||||
if v == LNil {
|
||||
return d
|
||||
}
|
||||
if lv, ok := v.(LNumber); ok {
|
||||
return lv
|
||||
}
|
||||
ls.TypeError(n, LTNumber)
|
||||
return 0
|
||||
}
|
||||
|
||||
func (ls *LState) OptString(n int, d string) string {
|
||||
v := ls.Get(n)
|
||||
if v == LNil {
|
||||
return d
|
||||
}
|
||||
if lv, ok := v.(LString); ok {
|
||||
return string(lv)
|
||||
}
|
||||
ls.TypeError(n, LTString)
|
||||
return ""
|
||||
}
|
||||
|
||||
func (ls *LState) OptBool(n int, d bool) bool {
|
||||
v := ls.Get(n)
|
||||
if v == LNil {
|
||||
return d
|
||||
}
|
||||
if lv, ok := v.(LBool); ok {
|
||||
return bool(lv)
|
||||
}
|
||||
ls.TypeError(n, LTBool)
|
||||
return false
|
||||
}
|
||||
|
||||
func (ls *LState) OptTable(n int, d *LTable) *LTable {
|
||||
v := ls.Get(n)
|
||||
if v == LNil {
|
||||
return d
|
||||
}
|
||||
if lv, ok := v.(*LTable); ok {
|
||||
return lv
|
||||
}
|
||||
ls.TypeError(n, LTTable)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ls *LState) OptFunction(n int, d *LFunction) *LFunction {
|
||||
v := ls.Get(n)
|
||||
if v == LNil {
|
||||
return d
|
||||
}
|
||||
if lv, ok := v.(*LFunction); ok {
|
||||
return lv
|
||||
}
|
||||
ls.TypeError(n, LTFunction)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ls *LState) OptUserData(n int, d *LUserData) *LUserData {
|
||||
v := ls.Get(n)
|
||||
if v == LNil {
|
||||
return d
|
||||
}
|
||||
if lv, ok := v.(*LUserData); ok {
|
||||
return lv
|
||||
}
|
||||
ls.TypeError(n, LTUserData)
|
||||
return nil
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
/* error operations {{{ */
|
||||
|
||||
func (ls *LState) ArgError(n int, message string) {
|
||||
ls.RaiseError("bad argument #%v to %v (%v)", n, ls.rawFrameFuncName(ls.currentFrame), message)
|
||||
}
|
||||
|
||||
func (ls *LState) TypeError(n int, typ LValueType) {
|
||||
ls.RaiseError("bad argument #%v to %v (%v expected, got %v)", n, ls.rawFrameFuncName(ls.currentFrame), typ.String(), ls.Get(n).Type().String())
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
/* debug operations {{{ */
|
||||
|
||||
func (ls *LState) Where(level int) string {
|
||||
return ls.where(level, false)
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
/* table operations {{{ */
|
||||
|
||||
func (ls *LState) FindTable(obj *LTable, n string, size int) LValue {
|
||||
names := strings.Split(n, ".")
|
||||
curobj := obj
|
||||
for _, name := range names {
|
||||
if curobj.Type() != LTTable {
|
||||
return LNil
|
||||
}
|
||||
nextobj := ls.RawGet(curobj, LString(name))
|
||||
if nextobj == LNil {
|
||||
tb := ls.CreateTable(0, size)
|
||||
ls.RawSet(curobj, LString(name), tb)
|
||||
curobj = tb
|
||||
} else if nextobj.Type() != LTTable {
|
||||
return LNil
|
||||
} else {
|
||||
curobj = nextobj.(*LTable)
|
||||
}
|
||||
}
|
||||
return curobj
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
/* register operations {{{ */
|
||||
|
||||
func (ls *LState) RegisterModule(name string, funcs map[string]LGFunction) LValue {
|
||||
tb := ls.FindTable(ls.Get(RegistryIndex).(*LTable), "_LOADED", 1)
|
||||
mod := ls.GetField(tb, name)
|
||||
if mod.Type() != LTTable {
|
||||
newmod := ls.FindTable(ls.Get(GlobalsIndex).(*LTable), name, len(funcs))
|
||||
if newmodtb, ok := newmod.(*LTable); !ok {
|
||||
ls.RaiseError("name conflict for module(%v)", name)
|
||||
} else {
|
||||
for fname, fn := range funcs {
|
||||
newmodtb.RawSetString(fname, ls.NewFunction(fn))
|
||||
}
|
||||
ls.SetField(tb, name, newmodtb)
|
||||
return newmodtb
|
||||
}
|
||||
}
|
||||
return mod
|
||||
}
|
||||
|
||||
func (ls *LState) SetFuncs(tb *LTable, funcs map[string]LGFunction, upvalues ...LValue) *LTable {
|
||||
for fname, fn := range funcs {
|
||||
tb.RawSetString(fname, ls.NewClosure(fn, upvalues...))
|
||||
}
|
||||
return tb
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
/* metatable operations {{{ */
|
||||
|
||||
func (ls *LState) NewTypeMetatable(typ string) *LTable {
|
||||
regtable := ls.Get(RegistryIndex)
|
||||
mt := ls.GetField(regtable, typ)
|
||||
if tb, ok := mt.(*LTable); ok {
|
||||
return tb
|
||||
}
|
||||
mtnew := ls.NewTable()
|
||||
ls.SetField(regtable, typ, mtnew)
|
||||
return mtnew
|
||||
}
|
||||
|
||||
func (ls *LState) GetMetaField(obj LValue, event string) LValue {
|
||||
return ls.metaOp1(obj, event)
|
||||
}
|
||||
|
||||
func (ls *LState) GetTypeMetatable(typ string) LValue {
|
||||
return ls.GetField(ls.Get(RegistryIndex), typ)
|
||||
}
|
||||
|
||||
func (ls *LState) CallMeta(obj LValue, event string) LValue {
|
||||
op := ls.metaOp1(obj, event)
|
||||
if op.Type() == LTFunction {
|
||||
ls.reg.Push(op)
|
||||
ls.reg.Push(obj)
|
||||
ls.Call(1, 1)
|
||||
return ls.reg.Pop()
|
||||
}
|
||||
return LNil
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
/* load and function call operations {{{ */
|
||||
|
||||
func (ls *LState) LoadFile(path string) (*LFunction, error) {
|
||||
var file *os.File
|
||||
var err error
|
||||
if len(path) == 0 {
|
||||
file = os.Stdin
|
||||
} else {
|
||||
file, err = os.Open(path)
|
||||
defer file.Close()
|
||||
if err != nil {
|
||||
return nil, newApiErrorE(ApiErrorFile, err)
|
||||
}
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(file)
|
||||
// get the first character.
|
||||
c, err := reader.ReadByte()
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, newApiErrorE(ApiErrorFile, err)
|
||||
}
|
||||
if c == byte('#') {
|
||||
// Unix exec. file?
|
||||
// skip first line
|
||||
_, err, _ = readBufioLine(reader)
|
||||
if err != nil {
|
||||
return nil, newApiErrorE(ApiErrorFile, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err != io.EOF {
|
||||
// if the file is not empty,
|
||||
// unread the first character of the file or newline character(readBufioLine's last byte).
|
||||
err = reader.UnreadByte()
|
||||
if err != nil {
|
||||
return nil, newApiErrorE(ApiErrorFile, err)
|
||||
}
|
||||
}
|
||||
|
||||
return ls.Load(reader, path)
|
||||
}
|
||||
|
||||
func (ls *LState) LoadString(source string) (*LFunction, error) {
|
||||
return ls.Load(strings.NewReader(source), "<string>")
|
||||
}
|
||||
|
||||
func (ls *LState) DoFile(path string) error {
|
||||
if fn, err := ls.LoadFile(path); err != nil {
|
||||
return err
|
||||
} else {
|
||||
ls.Push(fn)
|
||||
return ls.PCall(0, MultRet, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (ls *LState) DoString(source string) error {
|
||||
if fn, err := ls.LoadString(source); err != nil {
|
||||
return err
|
||||
} else {
|
||||
ls.Push(fn)
|
||||
return ls.PCall(0, MultRet, nil)
|
||||
}
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
/* GopherLua original APIs {{{ */
|
||||
|
||||
// ToStringMeta returns string representation of given LValue.
|
||||
// This method calls the `__tostring` meta method if defined.
|
||||
func (ls *LState) ToStringMeta(lv LValue) LValue {
|
||||
if fn, ok := ls.metaOp1(lv, "__tostring").assertFunction(); ok {
|
||||
ls.Push(fn)
|
||||
ls.Push(lv)
|
||||
ls.Call(1, 1)
|
||||
return ls.reg.Pop()
|
||||
} else {
|
||||
return LString(lv.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Set a module loader to the package.preload table.
|
||||
func (ls *LState) PreloadModule(name string, loader LGFunction) {
|
||||
preload := ls.GetField(ls.GetField(ls.Get(EnvironIndex), "package"), "preload")
|
||||
if _, ok := preload.(*LTable); !ok {
|
||||
ls.RaiseError("package.preload must be a table")
|
||||
}
|
||||
ls.SetField(preload, name, ls.NewFunction(loader))
|
||||
}
|
||||
|
||||
// Checks whether the given index is an LChannel and returns this channel.
|
||||
func (ls *LState) CheckChannel(n int) chan LValue {
|
||||
v := ls.Get(n)
|
||||
if ch, ok := v.(LChannel); ok {
|
||||
return (chan LValue)(ch)
|
||||
}
|
||||
ls.TypeError(n, LTChannel)
|
||||
return nil
|
||||
}
|
||||
|
||||
// If the given index is a LChannel, returns this channel. If this argument is absent or is nil, returns ch. Otherwise, raises an error.
|
||||
func (ls *LState) OptChannel(n int, ch chan LValue) chan LValue {
|
||||
v := ls.Get(n)
|
||||
if v == LNil {
|
||||
return ch
|
||||
}
|
||||
if ch, ok := v.(LChannel); ok {
|
||||
return (chan LValue)(ch)
|
||||
}
|
||||
ls.TypeError(n, LTChannel)
|
||||
return nil
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
//
|
|
@ -0,0 +1,597 @@
|
|||
package lua
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/* basic functions {{{ */
|
||||
|
||||
func OpenBase(L *LState) int {
|
||||
global := L.Get(GlobalsIndex).(*LTable)
|
||||
L.SetGlobal("_G", global)
|
||||
L.SetGlobal("_VERSION", LString(LuaVersion))
|
||||
L.SetGlobal("_GOPHER_LUA_VERSION", LString(PackageName+" "+PackageVersion))
|
||||
basemod := L.RegisterModule("_G", baseFuncs)
|
||||
global.RawSetString("ipairs", L.NewClosure(baseIpairs, L.NewFunction(ipairsaux)))
|
||||
global.RawSetString("pairs", L.NewClosure(basePairs, L.NewFunction(pairsaux)))
|
||||
L.Push(basemod)
|
||||
return 1
|
||||
}
|
||||
|
||||
var baseFuncs = map[string]LGFunction{
|
||||
"assert": baseAssert,
|
||||
"collectgarbage": baseCollectGarbage,
|
||||
"dofile": baseDoFile,
|
||||
"error": baseError,
|
||||
"getfenv": baseGetFEnv,
|
||||
"getmetatable": baseGetMetatable,
|
||||
"load": baseLoad,
|
||||
"loadfile": baseLoadFile,
|
||||
"loadstring": baseLoadString,
|
||||
"next": baseNext,
|
||||
"pcall": basePCall,
|
||||
"print": basePrint,
|
||||
"rawequal": baseRawEqual,
|
||||
"rawget": baseRawGet,
|
||||
"rawset": baseRawSet,
|
||||
"select": baseSelect,
|
||||
"_printregs": base_PrintRegs,
|
||||
"setfenv": baseSetFEnv,
|
||||
"setmetatable": baseSetMetatable,
|
||||
"tonumber": baseToNumber,
|
||||
"tostring": baseToString,
|
||||
"type": baseType,
|
||||
"unpack": baseUnpack,
|
||||
"xpcall": baseXPCall,
|
||||
// loadlib
|
||||
"module": loModule,
|
||||
"require": loRequire,
|
||||
// hidden features
|
||||
"newproxy": baseNewProxy,
|
||||
}
|
||||
|
||||
func baseAssert(L *LState) int {
|
||||
if !L.ToBool(1) {
|
||||
L.RaiseError(L.OptString(2, "assertion failed!"))
|
||||
return 0
|
||||
}
|
||||
return L.GetTop()
|
||||
}
|
||||
|
||||
func baseCollectGarbage(L *LState) int {
|
||||
runtime.GC()
|
||||
return 0
|
||||
}
|
||||
|
||||
func baseDoFile(L *LState) int {
|
||||
src := L.ToString(1)
|
||||
top := L.GetTop()
|
||||
fn, err := L.LoadFile(src)
|
||||
if err != nil {
|
||||
L.Push(LString(err.Error()))
|
||||
L.Panic(L)
|
||||
}
|
||||
L.Push(fn)
|
||||
L.Call(0, MultRet)
|
||||
return L.GetTop() - top
|
||||
}
|
||||
|
||||
func baseError(L *LState) int {
|
||||
obj := L.CheckAny(1)
|
||||
level := L.OptInt(2, 1)
|
||||
L.Error(obj, level)
|
||||
return 0
|
||||
}
|
||||
|
||||
func baseGetFEnv(L *LState) int {
|
||||
var value LValue
|
||||
if L.GetTop() == 0 {
|
||||
value = LNumber(1)
|
||||
} else {
|
||||
value = L.Get(1)
|
||||
}
|
||||
|
||||
if fn, ok := value.(*LFunction); ok {
|
||||
if !fn.IsG {
|
||||
L.Push(fn.Env)
|
||||
} else {
|
||||
L.Push(L.G.Global)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
if number, ok := value.(LNumber); ok {
|
||||
level := int(float64(number))
|
||||
if level <= 0 {
|
||||
L.Push(L.Env)
|
||||
} else {
|
||||
cf := L.currentFrame
|
||||
for i := 0; i < level && cf != nil; i++ {
|
||||
cf = cf.Parent
|
||||
}
|
||||
if cf == nil || cf.Fn.IsG {
|
||||
L.Push(L.G.Global)
|
||||
} else {
|
||||
L.Push(cf.Fn.Env)
|
||||
}
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
L.Push(L.G.Global)
|
||||
return 1
|
||||
}
|
||||
|
||||
func baseGetMetatable(L *LState) int {
|
||||
L.Push(L.GetMetatable(L.CheckAny(1)))
|
||||
return 1
|
||||
}
|
||||
|
||||
func ipairsaux(L *LState) int {
|
||||
tb := L.CheckTable(1)
|
||||
i := L.CheckInt(2)
|
||||
i++
|
||||
v := tb.RawGetInt(i)
|
||||
if v == LNil {
|
||||
return 0
|
||||
} else {
|
||||
L.Pop(1)
|
||||
L.Push(LNumber(i))
|
||||
L.Push(LNumber(i))
|
||||
L.Push(v)
|
||||
return 2
|
||||
}
|
||||
}
|
||||
|
||||
func baseIpairs(L *LState) int {
|
||||
tb := L.CheckTable(1)
|
||||
L.Push(L.Get(UpvalueIndex(1)))
|
||||
L.Push(tb)
|
||||
L.Push(LNumber(0))
|
||||
return 3
|
||||
}
|
||||
|
||||
func loadaux(L *LState, reader io.Reader, chunkname string) int {
|
||||
if fn, err := L.Load(reader, chunkname); err != nil {
|
||||
L.Push(LNil)
|
||||
L.Push(LString(err.Error()))
|
||||
return 2
|
||||
} else {
|
||||
L.Push(fn)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
func baseLoad(L *LState) int {
|
||||
fn := L.CheckFunction(1)
|
||||
chunkname := L.OptString(2, "?")
|
||||
top := L.GetTop()
|
||||
buf := []string{}
|
||||
for {
|
||||
L.SetTop(top)
|
||||
L.Push(fn)
|
||||
L.Call(0, 1)
|
||||
ret := L.reg.Pop()
|
||||
if ret == LNil {
|
||||
break
|
||||
} else if LVCanConvToString(ret) {
|
||||
str := ret.String()
|
||||
if len(str) > 0 {
|
||||
buf = append(buf, string(str))
|
||||
} else {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
L.Push(LNil)
|
||||
L.Push(LString("reader function must return a string"))
|
||||
return 2
|
||||
}
|
||||
}
|
||||
return loadaux(L, strings.NewReader(strings.Join(buf, "")), chunkname)
|
||||
}
|
||||
|
||||
func baseLoadFile(L *LState) int {
|
||||
var reader io.Reader
|
||||
var chunkname string
|
||||
var err error
|
||||
if L.GetTop() < 1 {
|
||||
reader = os.Stdin
|
||||
chunkname = "<stdin>"
|
||||
} else {
|
||||
chunkname = L.CheckString(1)
|
||||
reader, err = os.Open(chunkname)
|
||||
if err != nil {
|
||||
L.Push(LNil)
|
||||
L.Push(LString(fmt.Sprintf("can not open file: %v", chunkname)))
|
||||
return 2
|
||||
}
|
||||
defer reader.(*os.File).Close()
|
||||
}
|
||||
return loadaux(L, reader, chunkname)
|
||||
}
|
||||
|
||||
func baseLoadString(L *LState) int {
|
||||
return loadaux(L, strings.NewReader(L.CheckString(1)), L.OptString(2, "<string>"))
|
||||
}
|
||||
|
||||
func baseNext(L *LState) int {
|
||||
tb := L.CheckTable(1)
|
||||
index := LNil
|
||||
if L.GetTop() >= 2 {
|
||||
index = L.Get(2)
|
||||
}
|
||||
key, value := tb.Next(index)
|
||||
if key == LNil {
|
||||
L.Push(LNil)
|
||||
return 1
|
||||
}
|
||||
L.Push(key)
|
||||
L.Push(value)
|
||||
return 2
|
||||
}
|
||||
|
||||
func pairsaux(L *LState) int {
|
||||
tb := L.CheckTable(1)
|
||||
key, value := tb.Next(L.Get(2))
|
||||
if key == LNil {
|
||||
return 0
|
||||
} else {
|
||||
L.Pop(1)
|
||||
L.Push(key)
|
||||
L.Push(key)
|
||||
L.Push(value)
|
||||
return 2
|
||||
}
|
||||
}
|
||||
|
||||
func basePairs(L *LState) int {
|
||||
tb := L.CheckTable(1)
|
||||
L.Push(L.Get(UpvalueIndex(1)))
|
||||
L.Push(tb)
|
||||
L.Push(LNil)
|
||||
return 3
|
||||
}
|
||||
|
||||
func basePCall(L *LState) int {
|
||||
L.CheckAny(1)
|
||||
v := L.Get(1)
|
||||
if v.Type() != LTFunction && L.GetMetaField(v, "__call").Type() != LTFunction {
|
||||
L.Push(LFalse)
|
||||
L.Push(LString("attempt to call a " + v.Type().String() + " value"))
|
||||
return 2
|
||||
}
|
||||
nargs := L.GetTop() - 1
|
||||
if err := L.PCall(nargs, MultRet, nil); err != nil {
|
||||
L.Push(LFalse)
|
||||
if aerr, ok := err.(*ApiError); ok {
|
||||
L.Push(aerr.Object)
|
||||
} else {
|
||||
L.Push(LString(err.Error()))
|
||||
}
|
||||
return 2
|
||||
} else {
|
||||
L.Insert(LTrue, 1)
|
||||
return L.GetTop()
|
||||
}
|
||||
}
|
||||
|
||||
func basePrint(L *LState) int {
|
||||
top := L.GetTop()
|
||||
for i := 1; i <= top; i++ {
|
||||
fmt.Print(L.ToStringMeta(L.Get(i)).String())
|
||||
if i != top {
|
||||
fmt.Print("\t")
|
||||
}
|
||||
}
|
||||
fmt.Println("")
|
||||
return 0
|
||||
}
|
||||
|
||||
func base_PrintRegs(L *LState) int {
|
||||
L.printReg()
|
||||
return 0
|
||||
}
|
||||
|
||||
func baseRawEqual(L *LState) int {
|
||||
if L.CheckAny(1) == L.CheckAny(2) {
|
||||
L.Push(LTrue)
|
||||
} else {
|
||||
L.Push(LFalse)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func baseRawGet(L *LState) int {
|
||||
L.Push(L.RawGet(L.CheckTable(1), L.CheckAny(2)))
|
||||
return 1
|
||||
}
|
||||
|
||||
func baseRawSet(L *LState) int {
|
||||
L.RawSet(L.CheckTable(1), L.CheckAny(2), L.CheckAny(3))
|
||||
return 0
|
||||
}
|
||||
|
||||
func baseSelect(L *LState) int {
|
||||
L.CheckTypes(1, LTNumber, LTString)
|
||||
switch lv := L.Get(1).(type) {
|
||||
case LNumber:
|
||||
idx := int(lv)
|
||||
num := L.GetTop()
|
||||
if idx < 0 {
|
||||
idx = num + idx
|
||||
} else if idx > num {
|
||||
idx = num
|
||||
}
|
||||
if 1 > idx {
|
||||
L.ArgError(1, "index out of range")
|
||||
}
|
||||
return num - idx
|
||||
case LString:
|
||||
if string(lv) != "#" {
|
||||
L.ArgError(1, "invalid string '"+string(lv)+"'")
|
||||
}
|
||||
L.Push(LNumber(L.GetTop() - 1))
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func baseSetFEnv(L *LState) int {
|
||||
var value LValue
|
||||
if L.GetTop() == 0 {
|
||||
value = LNumber(1)
|
||||
} else {
|
||||
value = L.Get(1)
|
||||
}
|
||||
env := L.CheckTable(2)
|
||||
|
||||
if fn, ok := value.(*LFunction); ok {
|
||||
if fn.IsG {
|
||||
L.RaiseError("cannot change the environment of given object")
|
||||
} else {
|
||||
fn.Env = env
|
||||
L.Push(fn)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
if number, ok := value.(LNumber); ok {
|
||||
level := int(float64(number))
|
||||
if level <= 0 {
|
||||
L.Env = env
|
||||
return 0
|
||||
}
|
||||
|
||||
cf := L.currentFrame
|
||||
for i := 0; i < level && cf != nil; i++ {
|
||||
cf = cf.Parent
|
||||
}
|
||||
if cf == nil || cf.Fn.IsG {
|
||||
L.RaiseError("cannot change the environment of given object")
|
||||
} else {
|
||||
cf.Fn.Env = env
|
||||
L.Push(cf.Fn)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
L.RaiseError("cannot change the environment of given object")
|
||||
return 0
|
||||
}
|
||||
|
||||
func baseSetMetatable(L *LState) int {
|
||||
L.CheckTypes(2, LTNil, LTTable)
|
||||
obj := L.Get(1)
|
||||
if obj == LNil {
|
||||
L.RaiseError("cannot set metatable to a nil object.")
|
||||
}
|
||||
mt := L.Get(2)
|
||||
if m := L.metatable(obj, true); m != LNil {
|
||||
if tb, ok := m.(*LTable); ok && tb.RawGetString("__metatable") != LNil {
|
||||
L.RaiseError("cannot change a protected metatable")
|
||||
}
|
||||
}
|
||||
L.SetMetatable(obj, mt)
|
||||
L.SetTop(1)
|
||||
return 1
|
||||
}
|
||||
|
||||
func baseToNumber(L *LState) int {
|
||||
base := L.OptInt(2, 10)
|
||||
noBase := L.Get(2) == LNil
|
||||
|
||||
switch lv := L.CheckAny(1).(type) {
|
||||
case LNumber:
|
||||
L.Push(lv)
|
||||
case LString:
|
||||
str := strings.Trim(string(lv), " \n\t")
|
||||
if strings.Index(str, ".") > -1 {
|
||||
if v, err := strconv.ParseFloat(str, LNumberBit); err != nil {
|
||||
L.Push(LNil)
|
||||
} else {
|
||||
L.Push(LNumber(v))
|
||||
}
|
||||
} else {
|
||||
if noBase && strings.HasPrefix(strings.ToLower(str), "0x") {
|
||||
base, str = 16, str[2:] // Hex number
|
||||
}
|
||||
if v, err := strconv.ParseInt(str, base, LNumberBit); err != nil {
|
||||
L.Push(LNil)
|
||||
} else {
|
||||
L.Push(LNumber(v))
|
||||
}
|
||||
}
|
||||
default:
|
||||
L.Push(LNil)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func baseToString(L *LState) int {
|
||||
v1 := L.CheckAny(1)
|
||||
L.Push(L.ToStringMeta(v1))
|
||||
return 1
|
||||
}
|
||||
|
||||
func baseType(L *LState) int {
|
||||
L.Push(LString(L.CheckAny(1).Type().String()))
|
||||
return 1
|
||||
}
|
||||
|
||||
func baseUnpack(L *LState) int {
|
||||
tb := L.CheckTable(1)
|
||||
start := L.OptInt(2, 1)
|
||||
end := L.OptInt(3, tb.Len())
|
||||
for i := start; i <= end; i++ {
|
||||
L.Push(tb.RawGetInt(i))
|
||||
}
|
||||
ret := end - start + 1
|
||||
if ret < 0 {
|
||||
return 0
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func baseXPCall(L *LState) int {
|
||||
fn := L.CheckFunction(1)
|
||||
errfunc := L.CheckFunction(2)
|
||||
|
||||
top := L.GetTop()
|
||||
L.Push(fn)
|
||||
if err := L.PCall(0, MultRet, errfunc); err != nil {
|
||||
L.Push(LFalse)
|
||||
if aerr, ok := err.(*ApiError); ok {
|
||||
L.Push(aerr.Object)
|
||||
} else {
|
||||
L.Push(LString(err.Error()))
|
||||
}
|
||||
return 2
|
||||
} else {
|
||||
L.Insert(LTrue, top+1)
|
||||
return L.GetTop() - top
|
||||
}
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
/* load lib {{{ */
|
||||
|
||||
func loModule(L *LState) int {
|
||||
name := L.CheckString(1)
|
||||
loaded := L.GetField(L.Get(RegistryIndex), "_LOADED")
|
||||
tb := L.GetField(loaded, name)
|
||||
if _, ok := tb.(*LTable); !ok {
|
||||
tb = L.FindTable(L.Get(GlobalsIndex).(*LTable), name, 1)
|
||||
if tb == LNil {
|
||||
L.RaiseError("name conflict for module: %v", name)
|
||||
}
|
||||
L.SetField(loaded, name, tb)
|
||||
}
|
||||
if L.GetField(tb, "_NAME") == LNil {
|
||||
L.SetField(tb, "_M", tb)
|
||||
L.SetField(tb, "_NAME", LString(name))
|
||||
names := strings.Split(name, ".")
|
||||
pname := ""
|
||||
if len(names) > 1 {
|
||||
pname = strings.Join(names[:len(names)-1], ".") + "."
|
||||
}
|
||||
L.SetField(tb, "_PACKAGE", LString(pname))
|
||||
}
|
||||
|
||||
caller := L.currentFrame.Parent
|
||||
if caller == nil {
|
||||
L.RaiseError("no calling stack.")
|
||||
} else if caller.Fn.IsG {
|
||||
L.RaiseError("module() can not be called from GFunctions.")
|
||||
}
|
||||
L.SetFEnv(caller.Fn, tb)
|
||||
|
||||
top := L.GetTop()
|
||||
for i := 2; i <= top; i++ {
|
||||
L.Push(L.Get(i))
|
||||
L.Push(tb)
|
||||
L.Call(1, 0)
|
||||
}
|
||||
L.Push(tb)
|
||||
return 1
|
||||
}
|
||||
|
||||
var loopdetection = &LUserData{}
|
||||
|
||||
func loRequire(L *LState) int {
|
||||
name := L.CheckString(1)
|
||||
loaded := L.GetField(L.Get(RegistryIndex), "_LOADED")
|
||||
lv := L.GetField(loaded, name)
|
||||
if LVAsBool(lv) {
|
||||
if lv == loopdetection {
|
||||
L.RaiseError("loop or previous error loading module: %s", name)
|
||||
}
|
||||
L.Push(lv)
|
||||
return 1
|
||||
}
|
||||
loaders, ok := L.GetField(L.Get(RegistryIndex), "_LOADERS").(*LTable)
|
||||
if !ok {
|
||||
L.RaiseError("package.loaders must be a table")
|
||||
}
|
||||
messages := []string{}
|
||||
var modasfunc LValue
|
||||
for i := 1; ; i++ {
|
||||
loader := L.RawGetInt(loaders, i)
|
||||
if loader == LNil {
|
||||
L.RaiseError("module %s not found:\n\t%s, ", name, strings.Join(messages, "\n\t"))
|
||||
}
|
||||
L.Push(loader)
|
||||
L.Push(LString(name))
|
||||
L.Call(1, 1)
|
||||
ret := L.reg.Pop()
|
||||
switch retv := ret.(type) {
|
||||
case *LFunction:
|
||||
modasfunc = retv
|
||||
goto loopbreak
|
||||
case LString:
|
||||
messages = append(messages, string(retv))
|
||||
}
|
||||
}
|
||||
loopbreak:
|
||||
L.SetField(loaded, name, loopdetection)
|
||||
L.Push(modasfunc)
|
||||
L.Push(LString(name))
|
||||
L.Call(1, 1)
|
||||
ret := L.reg.Pop()
|
||||
modv := L.GetField(loaded, name)
|
||||
if ret != LNil && modv == loopdetection {
|
||||
L.SetField(loaded, name, ret)
|
||||
L.Push(ret)
|
||||
} else if modv == loopdetection {
|
||||
L.SetField(loaded, name, LTrue)
|
||||
L.Push(LTrue)
|
||||
} else {
|
||||
L.Push(modv)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
/* hidden features {{{ */
|
||||
|
||||
func baseNewProxy(L *LState) int {
|
||||
ud := L.NewUserData()
|
||||
L.SetTop(1)
|
||||
if L.Get(1) == LTrue {
|
||||
L.SetMetatable(ud, L.NewTable())
|
||||
} else if d, ok := L.Get(1).(*LUserData); ok {
|
||||
L.SetMetatable(ud, L.GetMetatable(d))
|
||||
}
|
||||
L.Push(ud)
|
||||
return 1
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
//
|
|
@ -0,0 +1,184 @@
|
|||
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 OpenChannel(L *LState) int {
|
||||
var mod LValue
|
||||
//_, ok := L.G.builtinMts[int(LTChannel)]
|
||||
// if !ok {
|
||||
mod = L.RegisterModule(ChannelLibName, channelFuncs)
|
||||
mt := L.SetFuncs(L.NewTable(), channelMethods)
|
||||
mt.RawSetString("__index", mt)
|
||||
L.G.builtinMts[int(LTChannel)] = mt
|
||||
// }
|
||||
L.Push(mod)
|
||||
return 1
|
||||
}
|
||||
|
||||
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{
|
||||
Dir: reflect.SelectSend,
|
||||
Chan: reflect.ValueOf(nil),
|
||||
Send: 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
|
||||
}
|
||||
|
||||
if L.ctx != nil {
|
||||
cases = append(cases, reflect.SelectCase{
|
||||
Dir: reflect.SelectRecv,
|
||||
Chan: reflect.ValueOf(L.ctx.Done()),
|
||||
Send: reflect.ValueOf(nil),
|
||||
})
|
||||
}
|
||||
|
||||
pos, recv, rok := reflect.Select(cases)
|
||||
|
||||
if L.ctx != nil && pos == L.GetTop() {
|
||||
return 0
|
||||
}
|
||||
|
||||
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)
|
||||
var v reflect.Value
|
||||
var ok bool
|
||||
if L.ctx != nil {
|
||||
cases := []reflect.SelectCase{{
|
||||
Dir: reflect.SelectRecv,
|
||||
Chan: reflect.ValueOf(L.ctx.Done()),
|
||||
Send: reflect.ValueOf(nil),
|
||||
}, {
|
||||
Dir: reflect.SelectRecv,
|
||||
Chan: rch,
|
||||
Send: reflect.ValueOf(nil),
|
||||
}}
|
||||
_, v, ok = reflect.Select(cases)
|
||||
} else {
|
||||
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
|
||||
}
|
||||
|
||||
//
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,43 @@
|
|||
package lua
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
var CompatVarArg = true
|
||||
var FieldsPerFlush = 50
|
||||
var RegistrySize = 256 * 20
|
||||
var RegistryGrowStep = 32
|
||||
var CallStackSize = 256
|
||||
var MaxTableGetLoop = 100
|
||||
var MaxArrayIndex = 67108864
|
||||
|
||||
type LNumber float64
|
||||
|
||||
const LNumberBit = 64
|
||||
const LNumberScanFormat = "%f"
|
||||
const LuaVersion = "Lua 5.1"
|
||||
|
||||
var LuaPath = "LUA_PATH"
|
||||
var LuaLDir string
|
||||
var LuaPathDefault string
|
||||
var LuaOS string
|
||||
var LuaDirSep string
|
||||
var LuaPathSep = ";"
|
||||
var LuaPathMark = "?"
|
||||
var LuaExecDir = "!"
|
||||
var LuaIgMark = "-"
|
||||
|
||||
func init() {
|
||||
if os.PathSeparator == '/' { // unix-like
|
||||
LuaOS = "unix"
|
||||
LuaLDir = "/usr/local/share/lua/5.1"
|
||||
LuaDirSep = "/"
|
||||
LuaPathDefault = "./?.lua;" + LuaLDir + "/?.lua;" + LuaLDir + "/?/init.lua"
|
||||
} else { // windows
|
||||
LuaOS = "windows"
|
||||
LuaLDir = "!\\lua"
|
||||
LuaDirSep = "\\"
|
||||
LuaPathDefault = ".\\?.lua;" + LuaLDir + "\\?.lua;" + LuaLDir + "\\?\\init.lua"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
package lua
|
||||
|
||||
func OpenCoroutine(L *LState) int {
|
||||
// TODO: Tie module name to contents of linit.go?
|
||||
mod := L.RegisterModule(CoroutineLibName, coFuncs)
|
||||
L.Push(mod)
|
||||
return 1
|
||||
}
|
||||
|
||||
var coFuncs = map[string]LGFunction{
|
||||
"create": coCreate,
|
||||
"yield": coYield,
|
||||
"resume": coResume,
|
||||
"running": coRunning,
|
||||
"status": coStatus,
|
||||
"wrap": coWrap,
|
||||
}
|
||||
|
||||
func coCreate(L *LState) int {
|
||||
fn := L.CheckFunction(1)
|
||||
newthread, _ := L.NewThread()
|
||||
base := 0
|
||||
newthread.stack.Push(callFrame{
|
||||
Fn: fn,
|
||||
Pc: 0,
|
||||
Base: base,
|
||||
LocalBase: base + 1,
|
||||
ReturnBase: base,
|
||||
NArgs: 0,
|
||||
NRet: MultRet,
|
||||
Parent: nil,
|
||||
TailCall: 0,
|
||||
})
|
||||
L.Push(newthread)
|
||||
return 1
|
||||
}
|
||||
|
||||
func coYield(L *LState) int {
|
||||
return -1
|
||||
}
|
||||
|
||||
func coResume(L *LState) int {
|
||||
th := L.CheckThread(1)
|
||||
if L.G.CurrentThread == th {
|
||||
msg := "can not resume a running thread"
|
||||
if th.wrapped {
|
||||
L.RaiseError(msg)
|
||||
return 0
|
||||
}
|
||||
L.Push(LFalse)
|
||||
L.Push(LString(msg))
|
||||
return 2
|
||||
}
|
||||
if th.Dead {
|
||||
msg := "can not resume a dead thread"
|
||||
if th.wrapped {
|
||||
L.RaiseError(msg)
|
||||
return 0
|
||||
}
|
||||
L.Push(LFalse)
|
||||
L.Push(LString(msg))
|
||||
return 2
|
||||
}
|
||||
th.Parent = L
|
||||
L.G.CurrentThread = th
|
||||
if !th.isStarted() {
|
||||
cf := th.stack.Last()
|
||||
th.currentFrame = cf
|
||||
th.SetTop(0)
|
||||
nargs := L.GetTop() - 1
|
||||
L.XMoveTo(th, nargs)
|
||||
cf.NArgs = nargs
|
||||
th.initCallFrame(cf)
|
||||
th.Panic = panicWithoutTraceback
|
||||
} else {
|
||||
nargs := L.GetTop() - 1
|
||||
L.XMoveTo(th, nargs)
|
||||
}
|
||||
top := L.GetTop()
|
||||
threadRun(th)
|
||||
return L.GetTop() - top
|
||||
}
|
||||
|
||||
func coRunning(L *LState) int {
|
||||
if L.G.MainThread == L {
|
||||
L.Push(LNil)
|
||||
return 1
|
||||
}
|
||||
L.Push(L.G.CurrentThread)
|
||||
return 1
|
||||
}
|
||||
|
||||
func coStatus(L *LState) int {
|
||||
L.Push(LString(L.Status(L.CheckThread(1))))
|
||||
return 1
|
||||
}
|
||||
|
||||
func wrapaux(L *LState) int {
|
||||
L.Insert(L.ToThread(UpvalueIndex(1)), 1)
|
||||
return coResume(L)
|
||||
}
|
||||
|
||||
func coWrap(L *LState) int {
|
||||
coCreate(L)
|
||||
L.CheckThread(L.GetTop()).wrapped = true
|
||||
v := L.Get(L.GetTop())
|
||||
L.Pop(1)
|
||||
L.Push(L.NewClosure(wrapaux, v))
|
||||
return 1
|
||||
}
|
||||
|
||||
//
|
|
@ -0,0 +1,173 @@
|
|||
package lua
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func OpenDebug(L *LState) int {
|
||||
dbgmod := L.RegisterModule(DebugLibName, debugFuncs)
|
||||
L.Push(dbgmod)
|
||||
return 1
|
||||
}
|
||||
|
||||
var debugFuncs = map[string]LGFunction{
|
||||
"getfenv": debugGetFEnv,
|
||||
"getinfo": debugGetInfo,
|
||||
"getlocal": debugGetLocal,
|
||||
"getmetatable": debugGetMetatable,
|
||||
"getupvalue": debugGetUpvalue,
|
||||
"setfenv": debugSetFEnv,
|
||||
"setlocal": debugSetLocal,
|
||||
"setmetatable": debugSetMetatable,
|
||||
"setupvalue": debugSetUpvalue,
|
||||
"traceback": debugTraceback,
|
||||
}
|
||||
|
||||
func debugGetFEnv(L *LState) int {
|
||||
L.Push(L.GetFEnv(L.CheckAny(1)))
|
||||
return 1
|
||||
}
|
||||
|
||||
func debugGetInfo(L *LState) int {
|
||||
L.CheckTypes(1, LTFunction, LTNumber)
|
||||
arg1 := L.Get(1)
|
||||
what := L.OptString(2, "Slunf")
|
||||
var dbg *Debug
|
||||
var fn LValue
|
||||
var err error
|
||||
var ok bool
|
||||
switch lv := arg1.(type) {
|
||||
case *LFunction:
|
||||
dbg = &Debug{}
|
||||
fn, err = L.GetInfo(">"+what, dbg, lv)
|
||||
case LNumber:
|
||||
dbg, ok = L.GetStack(int(lv))
|
||||
if !ok {
|
||||
L.Push(LNil)
|
||||
return 1
|
||||
}
|
||||
fn, err = L.GetInfo(what, dbg, LNil)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
L.Push(LNil)
|
||||
return 1
|
||||
}
|
||||
tbl := L.NewTable()
|
||||
if len(dbg.Name) > 0 {
|
||||
tbl.RawSetString("name", LString(dbg.Name))
|
||||
} else {
|
||||
tbl.RawSetString("name", LNil)
|
||||
}
|
||||
tbl.RawSetString("what", LString(dbg.What))
|
||||
tbl.RawSetString("source", LString(dbg.Source))
|
||||
tbl.RawSetString("currentline", LNumber(dbg.CurrentLine))
|
||||
tbl.RawSetString("nups", LNumber(dbg.NUpvalues))
|
||||
tbl.RawSetString("linedefined", LNumber(dbg.LineDefined))
|
||||
tbl.RawSetString("lastlinedefined", LNumber(dbg.LastLineDefined))
|
||||
tbl.RawSetString("func", fn)
|
||||
L.Push(tbl)
|
||||
return 1
|
||||
}
|
||||
|
||||
func debugGetLocal(L *LState) int {
|
||||
level := L.CheckInt(1)
|
||||
idx := L.CheckInt(2)
|
||||
dbg, ok := L.GetStack(level)
|
||||
if !ok {
|
||||
L.ArgError(1, "level out of range")
|
||||
}
|
||||
name, value := L.GetLocal(dbg, idx)
|
||||
if len(name) > 0 {
|
||||
L.Push(LString(name))
|
||||
L.Push(value)
|
||||
return 2
|
||||
}
|
||||
L.Push(LNil)
|
||||
return 1
|
||||
}
|
||||
|
||||
func debugGetMetatable(L *LState) int {
|
||||
L.Push(L.GetMetatable(L.CheckAny(1)))
|
||||
return 1
|
||||
}
|
||||
|
||||
func debugGetUpvalue(L *LState) int {
|
||||
fn := L.CheckFunction(1)
|
||||
idx := L.CheckInt(2)
|
||||
name, value := L.GetUpvalue(fn, idx)
|
||||
if len(name) > 0 {
|
||||
L.Push(LString(name))
|
||||
L.Push(value)
|
||||
return 2
|
||||
}
|
||||
L.Push(LNil)
|
||||
return 1
|
||||
}
|
||||
|
||||
func debugSetFEnv(L *LState) int {
|
||||
L.SetFEnv(L.CheckAny(1), L.CheckAny(2))
|
||||
return 0
|
||||
}
|
||||
|
||||
func debugSetLocal(L *LState) int {
|
||||
level := L.CheckInt(1)
|
||||
idx := L.CheckInt(2)
|
||||
value := L.CheckAny(3)
|
||||
dbg, ok := L.GetStack(level)
|
||||
if !ok {
|
||||
L.ArgError(1, "level out of range")
|
||||
}
|
||||
name := L.SetLocal(dbg, idx, value)
|
||||
if len(name) > 0 {
|
||||
L.Push(LString(name))
|
||||
} else {
|
||||
L.Push(LNil)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func debugSetMetatable(L *LState) int {
|
||||
L.CheckTypes(2, LTNil, LTTable)
|
||||
obj := L.Get(1)
|
||||
mt := L.Get(2)
|
||||
L.SetMetatable(obj, mt)
|
||||
L.SetTop(1)
|
||||
return 1
|
||||
}
|
||||
|
||||
func debugSetUpvalue(L *LState) int {
|
||||
fn := L.CheckFunction(1)
|
||||
idx := L.CheckInt(2)
|
||||
value := L.CheckAny(3)
|
||||
name := L.SetUpvalue(fn, idx, value)
|
||||
if len(name) > 0 {
|
||||
L.Push(LString(name))
|
||||
} else {
|
||||
L.Push(LNil)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func debugTraceback(L *LState) int {
|
||||
msg := ""
|
||||
level := L.OptInt(2, 1)
|
||||
ls := L
|
||||
if L.GetTop() > 0 {
|
||||
if s, ok := L.Get(1).assertString(); ok {
|
||||
msg = s
|
||||
}
|
||||
if l, ok := L.Get(1).(*LState); ok {
|
||||
ls = l
|
||||
msg = ""
|
||||
}
|
||||
}
|
||||
|
||||
traceback := strings.TrimSpace(ls.stackTrace(level))
|
||||
if len(msg) > 0 {
|
||||
traceback = fmt.Sprintf("%s\n%s", msg, traceback)
|
||||
}
|
||||
L.Push(LString(traceback))
|
||||
return 1
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
package lua
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
VarArgHasArg uint8 = 1
|
||||
VarArgIsVarArg uint8 = 2
|
||||
VarArgNeedsArg uint8 = 4
|
||||
)
|
||||
|
||||
type DbgLocalInfo struct {
|
||||
Name string
|
||||
StartPc int
|
||||
EndPc int
|
||||
}
|
||||
|
||||
type DbgCall struct {
|
||||
Name string
|
||||
Pc int
|
||||
}
|
||||
|
||||
type FunctionProto struct {
|
||||
SourceName string
|
||||
LineDefined int
|
||||
LastLineDefined int
|
||||
NumUpvalues uint8
|
||||
NumParameters uint8
|
||||
IsVarArg uint8
|
||||
NumUsedRegisters uint8
|
||||
Code []uint32
|
||||
Constants []LValue
|
||||
FunctionPrototypes []*FunctionProto
|
||||
|
||||
DbgSourcePositions []int
|
||||
DbgLocals []*DbgLocalInfo
|
||||
DbgCalls []DbgCall
|
||||
DbgUpvalues []string
|
||||
|
||||
stringConstants []string
|
||||
}
|
||||
|
||||
/* Upvalue {{{ */
|
||||
|
||||
type Upvalue struct {
|
||||
next *Upvalue
|
||||
reg *registry
|
||||
index int
|
||||
value LValue
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (uv *Upvalue) Value() LValue {
|
||||
//if uv.IsClosed() {
|
||||
if uv.closed || uv.reg == nil {
|
||||
return uv.value
|
||||
}
|
||||
//return uv.reg.Get(uv.index)
|
||||
return uv.reg.array[uv.index]
|
||||
}
|
||||
|
||||
func (uv *Upvalue) SetValue(value LValue) {
|
||||
if uv.IsClosed() {
|
||||
uv.value = value
|
||||
} else {
|
||||
uv.reg.Set(uv.index, value)
|
||||
}
|
||||
}
|
||||
|
||||
func (uv *Upvalue) Close() {
|
||||
value := uv.Value()
|
||||
uv.closed = true
|
||||
uv.value = value
|
||||
}
|
||||
|
||||
func (uv *Upvalue) IsClosed() bool {
|
||||
return uv.closed || uv.reg == nil
|
||||
}
|
||||
|
||||
func UpvalueIndex(i int) int {
|
||||
return GlobalsIndex - i
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
/* FunctionProto {{{ */
|
||||
|
||||
func newFunctionProto(name string) *FunctionProto {
|
||||
return &FunctionProto{
|
||||
SourceName: name,
|
||||
LineDefined: 0,
|
||||
LastLineDefined: 0,
|
||||
NumUpvalues: 0,
|
||||
NumParameters: 0,
|
||||
IsVarArg: 0,
|
||||
NumUsedRegisters: 2,
|
||||
Code: make([]uint32, 0, 128),
|
||||
Constants: make([]LValue, 0, 32),
|
||||
FunctionPrototypes: make([]*FunctionProto, 0, 16),
|
||||
|
||||
DbgSourcePositions: make([]int, 0, 128),
|
||||
DbgLocals: make([]*DbgLocalInfo, 0, 16),
|
||||
DbgCalls: make([]DbgCall, 0, 128),
|
||||
DbgUpvalues: make([]string, 0, 16),
|
||||
|
||||
stringConstants: make([]string, 0, 32),
|
||||
}
|
||||
}
|
||||
|
||||
func (fp *FunctionProto) String() string {
|
||||
return fp.str(1, 0)
|
||||
}
|
||||
|
||||
func (fp *FunctionProto) str(level int, count int) string {
|
||||
indent := strings.Repeat(" ", level-1)
|
||||
buf := []string{}
|
||||
buf = append(buf, fmt.Sprintf("%v; function [%v] definition (level %v)\n",
|
||||
indent, count, level))
|
||||
buf = append(buf, fmt.Sprintf("%v; %v upvalues, %v params, %v stacks\n",
|
||||
indent, fp.NumUpvalues, fp.NumParameters, fp.NumUsedRegisters))
|
||||
for reg, linfo := range fp.DbgLocals {
|
||||
buf = append(buf, fmt.Sprintf("%v.local %v ; %v\n", indent, linfo.Name, reg))
|
||||
}
|
||||
for reg, upvalue := range fp.DbgUpvalues {
|
||||
buf = append(buf, fmt.Sprintf("%v.upvalue %v ; %v\n", indent, upvalue, reg))
|
||||
}
|
||||
for reg, conzt := range fp.Constants {
|
||||
buf = append(buf, fmt.Sprintf("%v.const %v ; %v\n", indent, conzt.String(), reg))
|
||||
}
|
||||
buf = append(buf, "\n")
|
||||
|
||||
protono := 0
|
||||
for no, code := range fp.Code {
|
||||
inst := opGetOpCode(code)
|
||||
if inst == OP_CLOSURE {
|
||||
buf = append(buf, "\n")
|
||||
buf = append(buf, fp.FunctionPrototypes[protono].str(level+1, protono))
|
||||
buf = append(buf, "\n")
|
||||
protono++
|
||||
}
|
||||
buf = append(buf, fmt.Sprintf("%v[%03d] %v (line:%v)\n",
|
||||
indent, no+1, opToString(code), fp.DbgSourcePositions[no]))
|
||||
|
||||
}
|
||||
buf = append(buf, fmt.Sprintf("%v; end of function\n", indent))
|
||||
return strings.Join(buf, "")
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
/* LFunction {{{ */
|
||||
|
||||
func newLFunctionL(proto *FunctionProto, env *LTable, nupvalue int) *LFunction {
|
||||
return &LFunction{
|
||||
IsG: false,
|
||||
Env: env,
|
||||
|
||||
Proto: proto,
|
||||
GFunction: nil,
|
||||
Upvalues: make([]*Upvalue, nupvalue),
|
||||
}
|
||||
}
|
||||
|
||||
func newLFunctionG(gfunc LGFunction, env *LTable, nupvalue int) *LFunction {
|
||||
return &LFunction{
|
||||
IsG: true,
|
||||
Env: env,
|
||||
|
||||
Proto: nil,
|
||||
GFunction: gfunc,
|
||||
Upvalues: make([]*Upvalue, nupvalue),
|
||||
}
|
||||
}
|
||||
|
||||
func (fn *LFunction) LocalName(regno, pc int) (string, bool) {
|
||||
if fn.IsG {
|
||||
return "", false
|
||||
}
|
||||
p := fn.Proto
|
||||
for i := 0; i < len(p.DbgLocals) && p.DbgLocals[i].StartPc < pc; i++ {
|
||||
if pc < p.DbgLocals[i].EndPc {
|
||||
regno--
|
||||
if regno == 0 {
|
||||
return p.DbgLocals[i].Name, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
/* }}} */
|
|
@ -0,0 +1,746 @@
|
|||
package lua
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var ioFuncs = map[string]LGFunction{
|
||||
"close": ioClose,
|
||||
"flush": ioFlush,
|
||||
"lines": ioLines,
|
||||
"input": ioInput,
|
||||
"output": ioOutput,
|
||||
"open": ioOpenFile,
|
||||
"popen": ioPopen,
|
||||
"read": ioRead,
|
||||
"type": ioType,
|
||||
"tmpfile": ioTmpFile,
|
||||
"write": ioWrite,
|
||||
}
|
||||
|
||||
const lFileClass = "FILE*"
|
||||
|
||||
type lFile struct {
|
||||
fp *os.File
|
||||
pp *exec.Cmd
|
||||
writer io.Writer
|
||||
reader *bufio.Reader
|
||||
stdout io.ReadCloser
|
||||
closed bool
|
||||
}
|
||||
|
||||
type lFileType int
|
||||
|
||||
const (
|
||||
lFileFile lFileType = iota
|
||||
lFileProcess
|
||||
)
|
||||
|
||||
const fileDefOutIndex = 1
|
||||
const fileDefInIndex = 2
|
||||
const fileDefaultWriteBuffer = 4096
|
||||
const fileDefaultReadBuffer = 4096
|
||||
|
||||
func checkFile(L *LState) *lFile {
|
||||
ud := L.CheckUserData(1)
|
||||
if file, ok := ud.Value.(*lFile); ok {
|
||||
return file
|
||||
}
|
||||
L.ArgError(1, "file expected")
|
||||
return nil
|
||||
}
|
||||
|
||||
func errorIfFileIsClosed(L *LState, file *lFile) {
|
||||
if file.closed {
|
||||
L.ArgError(1, "file is closed")
|
||||
}
|
||||
}
|
||||
|
||||
func newFile(L *LState, file *os.File, path string, flag int, perm os.FileMode, writable, readable bool) (*LUserData, error) {
|
||||
ud := L.NewUserData()
|
||||
var err error
|
||||
if file == nil {
|
||||
file, err = os.OpenFile(path, flag, perm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
lfile := &lFile{fp: file, pp: nil, writer: nil, reader: nil, stdout: nil, closed: false}
|
||||
ud.Value = lfile
|
||||
if writable {
|
||||
lfile.writer = file
|
||||
}
|
||||
if readable {
|
||||
lfile.reader = bufio.NewReaderSize(file, fileDefaultReadBuffer)
|
||||
}
|
||||
L.SetMetatable(ud, L.GetTypeMetatable(lFileClass))
|
||||
return ud, nil
|
||||
}
|
||||
|
||||
func newProcess(L *LState, cmd string, writable, readable bool) (*LUserData, error) {
|
||||
ud := L.NewUserData()
|
||||
c, args := popenArgs(cmd)
|
||||
pp := exec.Command(c, args...)
|
||||
lfile := &lFile{fp: nil, pp: pp, writer: nil, reader: nil, stdout: nil, closed: false}
|
||||
ud.Value = lfile
|
||||
|
||||
var err error
|
||||
if writable {
|
||||
lfile.writer, err = pp.StdinPipe()
|
||||
}
|
||||
if readable {
|
||||
lfile.stdout, err = pp.StdoutPipe()
|
||||
lfile.reader = bufio.NewReaderSize(lfile.stdout, fileDefaultReadBuffer)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = pp.Start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
L.SetMetatable(ud, L.GetTypeMetatable(lFileClass))
|
||||
return ud, nil
|
||||
}
|
||||
|
||||
func (file *lFile) Type() lFileType {
|
||||
if file.fp == nil {
|
||||
return lFileProcess
|
||||
}
|
||||
return lFileFile
|
||||
}
|
||||
|
||||
func (file *lFile) Name() string {
|
||||
switch file.Type() {
|
||||
case lFileFile:
|
||||
return fmt.Sprintf("file %s", file.fp.Name())
|
||||
case lFileProcess:
|
||||
return fmt.Sprintf("process %s", file.pp.Path)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (file *lFile) AbandonReadBuffer() error {
|
||||
if file.Type() == lFileFile && file.reader != nil {
|
||||
_, err := file.fp.Seek(-int64(file.reader.Buffered()), 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file.reader = bufio.NewReaderSize(file.fp, fileDefaultReadBuffer)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func fileDefOut(L *LState) *LUserData {
|
||||
return L.Get(UpvalueIndex(1)).(*LTable).RawGetInt(fileDefOutIndex).(*LUserData)
|
||||
}
|
||||
|
||||
func fileDefIn(L *LState) *LUserData {
|
||||
return L.Get(UpvalueIndex(1)).(*LTable).RawGetInt(fileDefInIndex).(*LUserData)
|
||||
}
|
||||
|
||||
func fileIsWritable(L *LState, file *lFile) int {
|
||||
if file.writer == nil {
|
||||
L.Push(LNil)
|
||||
L.Push(LString(fmt.Sprintf("%s is opened for only reading.", file.Name())))
|
||||
L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack
|
||||
return 3
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func fileIsReadable(L *LState, file *lFile) int {
|
||||
if file.reader == nil {
|
||||
L.Push(LNil)
|
||||
L.Push(LString(fmt.Sprintf("%s is opened for only writing.", file.Name())))
|
||||
L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack
|
||||
return 3
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var stdFiles = []struct {
|
||||
name string
|
||||
file *os.File
|
||||
writable bool
|
||||
readable bool
|
||||
}{
|
||||
{"stdout", os.Stdout, true, false},
|
||||
{"stdin", os.Stdin, false, true},
|
||||
{"stderr", os.Stderr, true, false},
|
||||
}
|
||||
|
||||
func OpenIo(L *LState) int {
|
||||
mod := L.RegisterModule(IoLibName, map[string]LGFunction{}).(*LTable)
|
||||
mt := L.NewTypeMetatable(lFileClass)
|
||||
mt.RawSetString("__index", mt)
|
||||
L.SetFuncs(mt, fileMethods)
|
||||
mt.RawSetString("lines", L.NewClosure(fileLines, L.NewFunction(fileLinesIter)))
|
||||
|
||||
for _, finfo := range stdFiles {
|
||||
file, _ := newFile(L, finfo.file, "", 0, os.FileMode(0), finfo.writable, finfo.readable)
|
||||
mod.RawSetString(finfo.name, file)
|
||||
}
|
||||
uv := L.CreateTable(2, 0)
|
||||
uv.RawSetInt(fileDefOutIndex, mod.RawGetString("stdout"))
|
||||
uv.RawSetInt(fileDefInIndex, mod.RawGetString("stdin"))
|
||||
for name, fn := range ioFuncs {
|
||||
mod.RawSetString(name, L.NewClosure(fn, uv))
|
||||
}
|
||||
mod.RawSetString("lines", L.NewClosure(ioLines, uv, L.NewClosure(ioLinesIter, uv)))
|
||||
// Modifications are being made in-place rather than returned?
|
||||
L.Push(mod)
|
||||
return 1
|
||||
}
|
||||
|
||||
var fileMethods = map[string]LGFunction{
|
||||
"__tostring": fileToString,
|
||||
"write": fileWrite,
|
||||
"close": fileClose,
|
||||
"flush": fileFlush,
|
||||
"lines": fileLines,
|
||||
"read": fileRead,
|
||||
"seek": fileSeek,
|
||||
"setvbuf": fileSetVBuf,
|
||||
}
|
||||
|
||||
func fileToString(L *LState) int {
|
||||
file := checkFile(L)
|
||||
if file.Type() == lFileFile {
|
||||
if file.closed {
|
||||
L.Push(LString("file (closed)"))
|
||||
} else {
|
||||
L.Push(LString("file"))
|
||||
}
|
||||
} else {
|
||||
if file.closed {
|
||||
L.Push(LString("process (closed)"))
|
||||
} else {
|
||||
L.Push(LString("process"))
|
||||
}
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func fileWriteAux(L *LState, file *lFile, idx int) int {
|
||||
if n := fileIsWritable(L, file); n != 0 {
|
||||
return n
|
||||
}
|
||||
errorIfFileIsClosed(L, file)
|
||||
top := L.GetTop()
|
||||
out := file.writer
|
||||
var err error
|
||||
for i := idx; i <= top; i++ {
|
||||
L.CheckTypes(i, LTNumber, LTString)
|
||||
s := LVAsString(L.Get(i))
|
||||
if _, err = out.Write(unsafeFastStringToReadOnlyBytes(s)); err != nil {
|
||||
goto errreturn
|
||||
}
|
||||
}
|
||||
|
||||
file.AbandonReadBuffer()
|
||||
L.Push(LTrue)
|
||||
return 1
|
||||
errreturn:
|
||||
|
||||
file.AbandonReadBuffer()
|
||||
L.Push(LNil)
|
||||
L.Push(LString(err.Error()))
|
||||
L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack
|
||||
return 3
|
||||
}
|
||||
|
||||
func fileCloseAux(L *LState, file *lFile) int {
|
||||
file.closed = true
|
||||
var err error
|
||||
if file.writer != nil {
|
||||
if bwriter, ok := file.writer.(*bufio.Writer); ok {
|
||||
if err = bwriter.Flush(); err != nil {
|
||||
goto errreturn
|
||||
}
|
||||
}
|
||||
}
|
||||
file.AbandonReadBuffer()
|
||||
|
||||
switch file.Type() {
|
||||
case lFileFile:
|
||||
if err = file.fp.Close(); err != nil {
|
||||
goto errreturn
|
||||
}
|
||||
L.Push(LTrue)
|
||||
return 1
|
||||
case lFileProcess:
|
||||
if file.stdout != nil {
|
||||
file.stdout.Close() // ignore errors
|
||||
}
|
||||
err = file.pp.Wait()
|
||||
var exitStatus int // Initialised to zero value = 0
|
||||
if err != nil {
|
||||
if e2, ok := err.(*exec.ExitError); ok {
|
||||
if s, ok := e2.Sys().(syscall.WaitStatus); ok {
|
||||
exitStatus = s.ExitStatus()
|
||||
} else {
|
||||
err = errors.New("Unimplemented for system where exec.ExitError.Sys() is not syscall.WaitStatus.")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
exitStatus = 0
|
||||
}
|
||||
L.Push(LNumber(exitStatus))
|
||||
return 1
|
||||
}
|
||||
|
||||
errreturn:
|
||||
L.RaiseError(err.Error())
|
||||
return 0
|
||||
}
|
||||
|
||||
func fileFlushAux(L *LState, file *lFile) int {
|
||||
if n := fileIsWritable(L, file); n != 0 {
|
||||
return n
|
||||
}
|
||||
errorIfFileIsClosed(L, file)
|
||||
|
||||
if bwriter, ok := file.writer.(*bufio.Writer); ok {
|
||||
if err := bwriter.Flush(); err != nil {
|
||||
L.Push(LNil)
|
||||
L.Push(LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
}
|
||||
L.Push(LTrue)
|
||||
return 1
|
||||
}
|
||||
|
||||
func fileReadAux(L *LState, file *lFile, idx int) int {
|
||||
if n := fileIsReadable(L, file); n != 0 {
|
||||
return n
|
||||
}
|
||||
errorIfFileIsClosed(L, file)
|
||||
if L.GetTop() == idx-1 {
|
||||
L.Push(LString("*l"))
|
||||
}
|
||||
var err error
|
||||
top := L.GetTop()
|
||||
for i := idx; i <= top; i++ {
|
||||
switch lv := L.Get(i).(type) {
|
||||
case LNumber:
|
||||
size := int64(lv)
|
||||
if size == 0 {
|
||||
_, err = file.reader.ReadByte()
|
||||
if err == io.EOF {
|
||||
L.Push(LNil)
|
||||
goto normalreturn
|
||||
}
|
||||
file.reader.UnreadByte()
|
||||
}
|
||||
var buf []byte
|
||||
var iseof bool
|
||||
buf, err, iseof = readBufioSize(file.reader, size)
|
||||
if iseof {
|
||||
L.Push(LNil)
|
||||
goto normalreturn
|
||||
}
|
||||
if err != nil {
|
||||
goto errreturn
|
||||
}
|
||||
L.Push(LString(string(buf)))
|
||||
case LString:
|
||||
options := L.CheckString(i)
|
||||
if len(options) > 0 && options[0] != '*' {
|
||||
L.ArgError(2, "invalid options:"+options)
|
||||
}
|
||||
for _, opt := range options[1:] {
|
||||
switch opt {
|
||||
case 'n':
|
||||
var v LNumber
|
||||
_, err = fmt.Fscanf(file.reader, LNumberScanFormat, &v)
|
||||
if err == io.EOF {
|
||||
L.Push(LNil)
|
||||
goto normalreturn
|
||||
}
|
||||
if err != nil {
|
||||
goto errreturn
|
||||
}
|
||||
L.Push(v)
|
||||
case 'a':
|
||||
var buf []byte
|
||||
buf, err = ioutil.ReadAll(file.reader)
|
||||
if err == io.EOF {
|
||||
L.Push(emptyLString)
|
||||
goto normalreturn
|
||||
}
|
||||
if err != nil {
|
||||
goto errreturn
|
||||
}
|
||||
L.Push(LString(string(buf)))
|
||||
case 'l':
|
||||
var buf []byte
|
||||
var iseof bool
|
||||
buf, err, iseof = readBufioLine(file.reader)
|
||||
if iseof {
|
||||
L.Push(LNil)
|
||||
goto normalreturn
|
||||
}
|
||||
if err != nil {
|
||||
goto errreturn
|
||||
}
|
||||
L.Push(LString(string(buf)))
|
||||
default:
|
||||
L.ArgError(2, "invalid options:"+string(opt))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
normalreturn:
|
||||
return L.GetTop() - top
|
||||
|
||||
errreturn:
|
||||
L.RaiseError(err.Error())
|
||||
//L.Push(LNil)
|
||||
//L.Push(LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
|
||||
var fileSeekOptions = []string{"set", "cur", "end"}
|
||||
|
||||
func fileSeek(L *LState) int {
|
||||
file := checkFile(L)
|
||||
if file.Type() != lFileFile {
|
||||
L.Push(LNil)
|
||||
L.Push(LString("can not seek a process."))
|
||||
return 2
|
||||
}
|
||||
|
||||
top := L.GetTop()
|
||||
if top == 1 {
|
||||
L.Push(LString("cur"))
|
||||
L.Push(LNumber(0))
|
||||
} else if top == 2 {
|
||||
L.Push(LNumber(0))
|
||||
}
|
||||
|
||||
var pos int64
|
||||
var err error
|
||||
|
||||
err = file.AbandonReadBuffer()
|
||||
if err != nil {
|
||||
goto errreturn
|
||||
}
|
||||
|
||||
pos, err = file.fp.Seek(L.CheckInt64(3), L.CheckOption(2, fileSeekOptions))
|
||||
if err != nil {
|
||||
goto errreturn
|
||||
}
|
||||
|
||||
L.Push(LNumber(pos))
|
||||
return 1
|
||||
|
||||
errreturn:
|
||||
L.Push(LNil)
|
||||
L.Push(LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
|
||||
func fileWrite(L *LState) int {
|
||||
return fileWriteAux(L, checkFile(L), 2)
|
||||
}
|
||||
|
||||
func fileClose(L *LState) int {
|
||||
return fileCloseAux(L, checkFile(L))
|
||||
}
|
||||
|
||||
func fileFlush(L *LState) int {
|
||||
return fileFlushAux(L, checkFile(L))
|
||||
}
|
||||
|
||||
func fileLinesIter(L *LState) int {
|
||||
var file *lFile
|
||||
if ud, ok := L.Get(1).(*LUserData); ok {
|
||||
file = ud.Value.(*lFile)
|
||||
} else {
|
||||
file = L.Get(UpvalueIndex(2)).(*LUserData).Value.(*lFile)
|
||||
}
|
||||
buf, _, err := file.reader.ReadLine()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
L.Push(LNil)
|
||||
return 1
|
||||
}
|
||||
L.RaiseError(err.Error())
|
||||
}
|
||||
L.Push(LString(string(buf)))
|
||||
return 1
|
||||
}
|
||||
|
||||
func fileLines(L *LState) int {
|
||||
file := checkFile(L)
|
||||
ud := L.CheckUserData(1)
|
||||
if n := fileIsReadable(L, file); n != 0 {
|
||||
return 0
|
||||
}
|
||||
L.Push(L.NewClosure(fileLinesIter, L.Get(UpvalueIndex(1)), ud))
|
||||
return 1
|
||||
}
|
||||
|
||||
func fileRead(L *LState) int {
|
||||
return fileReadAux(L, checkFile(L), 2)
|
||||
}
|
||||
|
||||
var filebufOptions = []string{"no", "full"}
|
||||
|
||||
func fileSetVBuf(L *LState) int {
|
||||
var err error
|
||||
var writer io.Writer
|
||||
file := checkFile(L)
|
||||
if n := fileIsWritable(L, file); n != 0 {
|
||||
return n
|
||||
}
|
||||
switch filebufOptions[L.CheckOption(2, filebufOptions)] {
|
||||
case "no":
|
||||
switch file.Type() {
|
||||
case lFileFile:
|
||||
file.writer = file.fp
|
||||
case lFileProcess:
|
||||
file.writer, err = file.pp.StdinPipe()
|
||||
if err != nil {
|
||||
goto errreturn
|
||||
}
|
||||
}
|
||||
case "full", "line": // TODO line buffer not supported
|
||||
bufsize := L.OptInt(3, fileDefaultWriteBuffer)
|
||||
switch file.Type() {
|
||||
case lFileFile:
|
||||
file.writer = bufio.NewWriterSize(file.fp, bufsize)
|
||||
case lFileProcess:
|
||||
writer, err = file.pp.StdinPipe()
|
||||
if err != nil {
|
||||
goto errreturn
|
||||
}
|
||||
file.writer = bufio.NewWriterSize(writer, bufsize)
|
||||
}
|
||||
}
|
||||
L.Push(LTrue)
|
||||
return 1
|
||||
errreturn:
|
||||
L.Push(LNil)
|
||||
L.Push(LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
|
||||
func ioInput(L *LState) int {
|
||||
if L.GetTop() == 0 {
|
||||
L.Push(fileDefIn(L))
|
||||
return 1
|
||||
}
|
||||
switch lv := L.Get(1).(type) {
|
||||
case LString:
|
||||
file, err := newFile(L, nil, string(lv), os.O_RDONLY, 0600, false, true)
|
||||
if err != nil {
|
||||
L.RaiseError(err.Error())
|
||||
}
|
||||
L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefInIndex, file)
|
||||
L.Push(file)
|
||||
return 1
|
||||
case *LUserData:
|
||||
if _, ok := lv.Value.(*lFile); ok {
|
||||
L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefInIndex, lv)
|
||||
L.Push(lv)
|
||||
return 1
|
||||
}
|
||||
|
||||
}
|
||||
L.ArgError(1, "string or file expedted, but got "+L.Get(1).Type().String())
|
||||
return 0
|
||||
}
|
||||
|
||||
func ioClose(L *LState) int {
|
||||
if L.GetTop() == 0 {
|
||||
return fileCloseAux(L, fileDefOut(L).Value.(*lFile))
|
||||
}
|
||||
return fileClose(L)
|
||||
}
|
||||
|
||||
func ioFlush(L *LState) int {
|
||||
return fileFlushAux(L, fileDefOut(L).Value.(*lFile))
|
||||
}
|
||||
|
||||
func ioLinesIter(L *LState) int {
|
||||
var file *lFile
|
||||
toclose := false
|
||||
if ud, ok := L.Get(1).(*LUserData); ok {
|
||||
file = ud.Value.(*lFile)
|
||||
} else {
|
||||
file = L.Get(UpvalueIndex(2)).(*LUserData).Value.(*lFile)
|
||||
toclose = true
|
||||
}
|
||||
buf, _, err := file.reader.ReadLine()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
if toclose {
|
||||
fileCloseAux(L, file)
|
||||
}
|
||||
L.Push(LNil)
|
||||
return 1
|
||||
}
|
||||
L.RaiseError(err.Error())
|
||||
}
|
||||
L.Push(LString(string(buf)))
|
||||
return 1
|
||||
}
|
||||
|
||||
func ioLines(L *LState) int {
|
||||
if L.GetTop() == 0 {
|
||||
L.Push(L.Get(UpvalueIndex(2)))
|
||||
L.Push(fileDefIn(L))
|
||||
return 2
|
||||
}
|
||||
|
||||
path := L.CheckString(1)
|
||||
ud, err := newFile(L, nil, path, os.O_RDONLY, os.FileMode(0600), false, true)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
L.Push(L.NewClosure(ioLinesIter, L.Get(UpvalueIndex(1)), ud))
|
||||
return 1
|
||||
}
|
||||
|
||||
var ioOpenOpions = []string{"r", "rb", "w", "wb", "a", "ab", "r+", "rb+", "w+", "wb+", "a+", "ab+"}
|
||||
|
||||
func ioOpenFile(L *LState) int {
|
||||
path := L.CheckString(1)
|
||||
if L.GetTop() == 1 {
|
||||
L.Push(LString("r"))
|
||||
}
|
||||
mode := os.O_RDONLY
|
||||
perm := 0600
|
||||
writable := true
|
||||
readable := true
|
||||
switch ioOpenOpions[L.CheckOption(2, ioOpenOpions)] {
|
||||
case "r", "rb":
|
||||
mode = os.O_RDONLY
|
||||
writable = false
|
||||
case "w", "wb":
|
||||
mode = os.O_WRONLY | os.O_CREATE
|
||||
readable = false
|
||||
case "a", "ab":
|
||||
mode = os.O_WRONLY | os.O_APPEND | os.O_CREATE
|
||||
case "r+", "rb+":
|
||||
mode = os.O_RDWR
|
||||
case "w+", "wb+":
|
||||
mode = os.O_RDWR | os.O_TRUNC | os.O_CREATE
|
||||
case "a+", "ab+":
|
||||
mode = os.O_APPEND | os.O_RDWR | os.O_CREATE
|
||||
}
|
||||
file, err := newFile(L, nil, path, mode, os.FileMode(perm), writable, readable)
|
||||
if err != nil {
|
||||
L.Push(LNil)
|
||||
L.Push(LString(err.Error()))
|
||||
L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack
|
||||
return 3
|
||||
}
|
||||
L.Push(file)
|
||||
return 1
|
||||
|
||||
}
|
||||
|
||||
var ioPopenOptions = []string{"r", "w"}
|
||||
|
||||
func ioPopen(L *LState) int {
|
||||
cmd := L.CheckString(1)
|
||||
if L.GetTop() == 1 {
|
||||
L.Push(LString("r"))
|
||||
}
|
||||
var file *LUserData
|
||||
var err error
|
||||
|
||||
switch ioPopenOptions[L.CheckOption(2, ioPopenOptions)] {
|
||||
case "r":
|
||||
file, err = newProcess(L, cmd, false, true)
|
||||
case "w":
|
||||
file, err = newProcess(L, cmd, true, false)
|
||||
}
|
||||
if err != nil {
|
||||
L.Push(LNil)
|
||||
L.Push(LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
L.Push(file)
|
||||
return 1
|
||||
}
|
||||
|
||||
func ioRead(L *LState) int {
|
||||
return fileReadAux(L, fileDefIn(L).Value.(*lFile), 1)
|
||||
}
|
||||
|
||||
func ioType(L *LState) int {
|
||||
ud, udok := L.Get(1).(*LUserData)
|
||||
if !udok {
|
||||
L.Push(LNil)
|
||||
return 1
|
||||
}
|
||||
file, ok := ud.Value.(*lFile)
|
||||
if !ok {
|
||||
L.Push(LNil)
|
||||
return 1
|
||||
}
|
||||
if file.closed {
|
||||
L.Push(LString("closed file"))
|
||||
return 1
|
||||
}
|
||||
L.Push(LString("file"))
|
||||
return 1
|
||||
}
|
||||
|
||||
func ioTmpFile(L *LState) int {
|
||||
file, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
L.Push(LNil)
|
||||
L.Push(LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
L.G.tempFiles = append(L.G.tempFiles, file)
|
||||
ud, _ := newFile(L, file, "", 0, os.FileMode(0), true, true)
|
||||
L.Push(ud)
|
||||
return 1
|
||||
}
|
||||
|
||||
func ioOutput(L *LState) int {
|
||||
if L.GetTop() == 0 {
|
||||
L.Push(fileDefOut(L))
|
||||
return 1
|
||||
}
|
||||
switch lv := L.Get(1).(type) {
|
||||
case LString:
|
||||
file, err := newFile(L, nil, string(lv), os.O_WRONLY|os.O_CREATE, 0600, true, false)
|
||||
if err != nil {
|
||||
L.RaiseError(err.Error())
|
||||
}
|
||||
L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefOutIndex, file)
|
||||
L.Push(file)
|
||||
return 1
|
||||
case *LUserData:
|
||||
if _, ok := lv.Value.(*lFile); ok {
|
||||
L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefOutIndex, lv)
|
||||
L.Push(lv)
|
||||
return 1
|
||||
}
|
||||
|
||||
}
|
||||
L.ArgError(1, "string or file expedted, but got "+L.Get(1).Type().String())
|
||||
return 0
|
||||
}
|
||||
|
||||
func ioWrite(L *LState) int {
|
||||
return fileWriteAux(L, fileDefOut(L).Value.(*lFile), 1)
|
||||
}
|
||||
|
||||
//
|
|
@ -0,0 +1,54 @@
|
|||
package lua
|
||||
|
||||
const (
|
||||
// BaseLibName is here for consistency; the base functions have no namespace/library.
|
||||
BaseLibName = ""
|
||||
// LoadLibName is here for consistency; the loading system has no namespace/library.
|
||||
LoadLibName = "package"
|
||||
// TabLibName is the name of the table Library.
|
||||
TabLibName = "table"
|
||||
// IoLibName is the name of the io Library.
|
||||
IoLibName = "io"
|
||||
// OsLibName is the name of the os Library.
|
||||
OsLibName = "os"
|
||||
// StringLibName is the name of the string Library.
|
||||
StringLibName = "string"
|
||||
// MathLibName is the name of the math Library.
|
||||
MathLibName = "math"
|
||||
// DebugLibName is the name of the debug Library.
|
||||
DebugLibName = "debug"
|
||||
// ChannelLibName is the name of the channel Library.
|
||||
ChannelLibName = "channel"
|
||||
// CoroutineLibName is the name of the coroutine Library.
|
||||
CoroutineLibName = "coroutine"
|
||||
)
|
||||
|
||||
type luaLib struct {
|
||||
libName string
|
||||
libFunc LGFunction
|
||||
}
|
||||
|
||||
var luaLibs = []luaLib{
|
||||
luaLib{LoadLibName, OpenPackage},
|
||||
luaLib{BaseLibName, OpenBase},
|
||||
luaLib{TabLibName, OpenTable},
|
||||
luaLib{IoLibName, OpenIo},
|
||||
luaLib{OsLibName, OpenOs},
|
||||
luaLib{StringLibName, OpenString},
|
||||
luaLib{MathLibName, OpenMath},
|
||||
luaLib{DebugLibName, OpenDebug},
|
||||
luaLib{ChannelLibName, OpenChannel},
|
||||
luaLib{CoroutineLibName, OpenCoroutine},
|
||||
}
|
||||
|
||||
// OpenLibs loads the built-in libraries. It is equivalent to running OpenLoad,
|
||||
// then OpenBase, then iterating over the other OpenXXX functions in any order.
|
||||
func (ls *LState) OpenLibs() {
|
||||
// NB: Map iteration order in Go is deliberately randomised, so must open Load/Base
|
||||
// prior to iterating.
|
||||
for _, lib := range luaLibs {
|
||||
ls.Push(ls.NewFunction(lib.libFunc))
|
||||
ls.Push(LString(lib.libName))
|
||||
ls.Call(1, 0)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
package lua
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/* load lib {{{ */
|
||||
|
||||
var loLoaders = []LGFunction{loLoaderPreload, loLoaderLua}
|
||||
|
||||
func loGetPath(env string, defpath string) string {
|
||||
path := os.Getenv(env)
|
||||
if len(path) == 0 {
|
||||
path = defpath
|
||||
}
|
||||
path = strings.Replace(path, ";;", ";"+defpath+";", -1)
|
||||
if os.PathSeparator != '/' {
|
||||
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
path = strings.Replace(path, "!", dir, -1)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func loFindFile(L *LState, name, pname string) (string, string) {
|
||||
name = strings.Replace(name, ".", string(os.PathSeparator), -1)
|
||||
lv := L.GetField(L.GetField(L.Get(EnvironIndex), "package"), pname)
|
||||
path, ok := lv.(LString)
|
||||
if !ok {
|
||||
L.RaiseError("package.%s must be a string", pname)
|
||||
}
|
||||
messages := []string{}
|
||||
for _, pattern := range strings.Split(string(path), ";") {
|
||||
luapath := strings.Replace(pattern, "?", name, -1)
|
||||
if _, err := os.Stat(luapath); err == nil {
|
||||
return luapath, ""
|
||||
} else {
|
||||
messages = append(messages, err.Error())
|
||||
}
|
||||
}
|
||||
return "", strings.Join(messages, "\n\t")
|
||||
}
|
||||
|
||||
func OpenPackage(L *LState) int {
|
||||
packagemod := L.RegisterModule(LoadLibName, loFuncs)
|
||||
|
||||
L.SetField(packagemod, "preload", L.NewTable())
|
||||
|
||||
loaders := L.CreateTable(len(loLoaders), 0)
|
||||
for i, loader := range loLoaders {
|
||||
L.RawSetInt(loaders, i+1, L.NewFunction(loader))
|
||||
}
|
||||
L.SetField(packagemod, "loaders", loaders)
|
||||
L.SetField(L.Get(RegistryIndex), "_LOADERS", loaders)
|
||||
|
||||
loaded := L.NewTable()
|
||||
L.SetField(packagemod, "loaded", loaded)
|
||||
L.SetField(L.Get(RegistryIndex), "_LOADED", loaded)
|
||||
|
||||
L.SetField(packagemod, "path", LString(loGetPath(LuaPath, LuaPathDefault)))
|
||||
L.SetField(packagemod, "cpath", emptyLString)
|
||||
|
||||
L.SetField(packagemod, "config", LString(LuaDirSep+"\n"+LuaPathSep+
|
||||
"\n"+LuaPathMark+"\n"+LuaExecDir+"\n"+LuaIgMark+"\n"))
|
||||
|
||||
L.Push(packagemod)
|
||||
return 1
|
||||
}
|
||||
|
||||
var loFuncs = map[string]LGFunction{
|
||||
"loadlib": loLoadLib,
|
||||
"seeall": loSeeAll,
|
||||
}
|
||||
|
||||
func loLoaderPreload(L *LState) int {
|
||||
name := L.CheckString(1)
|
||||
preload := L.GetField(L.GetField(L.Get(EnvironIndex), "package"), "preload")
|
||||
if _, ok := preload.(*LTable); !ok {
|
||||
L.RaiseError("package.preload must be a table")
|
||||
}
|
||||
lv := L.GetField(preload, name)
|
||||
if lv == LNil {
|
||||
L.Push(LString(fmt.Sprintf("no field package.preload['%s']", name)))
|
||||
return 1
|
||||
}
|
||||
L.Push(lv)
|
||||
return 1
|
||||
}
|
||||
|
||||
func loLoaderLua(L *LState) int {
|
||||
name := L.CheckString(1)
|
||||
path, msg := loFindFile(L, name, "path")
|
||||
if len(path) == 0 {
|
||||
L.Push(LString(msg))
|
||||
return 1
|
||||
}
|
||||
fn, err1 := L.LoadFile(path)
|
||||
if err1 != nil {
|
||||
L.RaiseError(err1.Error())
|
||||
}
|
||||
L.Push(fn)
|
||||
return 1
|
||||
}
|
||||
|
||||
func loLoadLib(L *LState) int {
|
||||
L.RaiseError("loadlib is not supported")
|
||||
return 0
|
||||
}
|
||||
|
||||
func loSeeAll(L *LState) int {
|
||||
mod := L.CheckTable(1)
|
||||
mt := L.GetMetatable(mod)
|
||||
if mt == LNil {
|
||||
mt = L.CreateTable(0, 1)
|
||||
L.SetMetatable(mod, mt)
|
||||
}
|
||||
L.SetField(mt, "__index", L.Get(GlobalsIndex))
|
||||
return 0
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
//
|
|
@ -0,0 +1,231 @@
|
|||
package lua
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
func OpenMath(L *LState) int {
|
||||
mod := L.RegisterModule(MathLibName, mathFuncs).(*LTable)
|
||||
mod.RawSetString("pi", LNumber(math.Pi))
|
||||
mod.RawSetString("huge", LNumber(math.MaxFloat64))
|
||||
L.Push(mod)
|
||||
return 1
|
||||
}
|
||||
|
||||
var mathFuncs = map[string]LGFunction{
|
||||
"abs": mathAbs,
|
||||
"acos": mathAcos,
|
||||
"asin": mathAsin,
|
||||
"atan": mathAtan,
|
||||
"atan2": mathAtan2,
|
||||
"ceil": mathCeil,
|
||||
"cos": mathCos,
|
||||
"cosh": mathCosh,
|
||||
"deg": mathDeg,
|
||||
"exp": mathExp,
|
||||
"floor": mathFloor,
|
||||
"fmod": mathFmod,
|
||||
"frexp": mathFrexp,
|
||||
"ldexp": mathLdexp,
|
||||
"log": mathLog,
|
||||
"log10": mathLog10,
|
||||
"max": mathMax,
|
||||
"min": mathMin,
|
||||
"mod": mathMod,
|
||||
"modf": mathModf,
|
||||
"pow": mathPow,
|
||||
"rad": mathRad,
|
||||
"random": mathRandom,
|
||||
"randomseed": mathRandomseed,
|
||||
"sin": mathSin,
|
||||
"sinh": mathSinh,
|
||||
"sqrt": mathSqrt,
|
||||
"tan": mathTan,
|
||||
"tanh": mathTanh,
|
||||
}
|
||||
|
||||
func mathAbs(L *LState) int {
|
||||
L.Push(LNumber(math.Abs(float64(L.CheckNumber(1)))))
|
||||
return 1
|
||||
}
|
||||
|
||||
func mathAcos(L *LState) int {
|
||||
L.Push(LNumber(math.Acos(float64(L.CheckNumber(1)))))
|
||||
return 1
|
||||
}
|
||||
|
||||
func mathAsin(L *LState) int {
|
||||
L.Push(LNumber(math.Asin(float64(L.CheckNumber(1)))))
|
||||
return 1
|
||||
}
|
||||
|
||||
func mathAtan(L *LState) int {
|
||||
L.Push(LNumber(math.Atan(float64(L.CheckNumber(1)))))
|
||||
return 1
|
||||
}
|
||||
|
||||
func mathAtan2(L *LState) int {
|
||||
L.Push(LNumber(math.Atan2(float64(L.CheckNumber(1)), float64(L.CheckNumber(2)))))
|
||||
return 1
|
||||
}
|
||||
|
||||
func mathCeil(L *LState) int {
|
||||
L.Push(LNumber(math.Ceil(float64(L.CheckNumber(1)))))
|
||||
return 1
|
||||
}
|
||||
|
||||
func mathCos(L *LState) int {
|
||||
L.Push(LNumber(math.Cos(float64(L.CheckNumber(1)))))
|
||||
return 1
|
||||
}
|
||||
|
||||
func mathCosh(L *LState) int {
|
||||
L.Push(LNumber(math.Cosh(float64(L.CheckNumber(1)))))
|
||||
return 1
|
||||
}
|
||||
|
||||
func mathDeg(L *LState) int {
|
||||
L.Push(LNumber(float64(L.CheckNumber(1)) * 180 / math.Pi))
|
||||
return 1
|
||||
}
|
||||
|
||||
func mathExp(L *LState) int {
|
||||
L.Push(LNumber(math.Exp(float64(L.CheckNumber(1)))))
|
||||
return 1
|
||||
}
|
||||
|
||||
func mathFloor(L *LState) int {
|
||||
L.Push(LNumber(math.Floor(float64(L.CheckNumber(1)))))
|
||||
return 1
|
||||
}
|
||||
|
||||
func mathFmod(L *LState) int {
|
||||
L.Push(LNumber(math.Mod(float64(L.CheckNumber(1)), float64(L.CheckNumber(2)))))
|
||||
return 1
|
||||
}
|
||||
|
||||
func mathFrexp(L *LState) int {
|
||||
v1, v2 := math.Frexp(float64(L.CheckNumber(1)))
|
||||
L.Push(LNumber(v1))
|
||||
L.Push(LNumber(v2))
|
||||
return 2
|
||||
}
|
||||
|
||||
func mathLdexp(L *LState) int {
|
||||
L.Push(LNumber(math.Ldexp(float64(L.CheckNumber(1)), L.CheckInt(2))))
|
||||
return 1
|
||||
}
|
||||
|
||||
func mathLog(L *LState) int {
|
||||
L.Push(LNumber(math.Log(float64(L.CheckNumber(1)))))
|
||||
return 1
|
||||
}
|
||||
|
||||
func mathLog10(L *LState) int {
|
||||
L.Push(LNumber(math.Log10(float64(L.CheckNumber(1)))))
|
||||
return 1
|
||||
}
|
||||
|
||||
func mathMax(L *LState) int {
|
||||
if L.GetTop() == 0 {
|
||||
L.RaiseError("wrong number of arguments")
|
||||
}
|
||||
max := L.CheckNumber(1)
|
||||
top := L.GetTop()
|
||||
for i := 2; i <= top; i++ {
|
||||
v := L.CheckNumber(i)
|
||||
if v > max {
|
||||
max = v
|
||||
}
|
||||
}
|
||||
L.Push(max)
|
||||
return 1
|
||||
}
|
||||
|
||||
func mathMin(L *LState) int {
|
||||
if L.GetTop() == 0 {
|
||||
L.RaiseError("wrong number of arguments")
|
||||
}
|
||||
min := L.CheckNumber(1)
|
||||
top := L.GetTop()
|
||||
for i := 2; i <= top; i++ {
|
||||
v := L.CheckNumber(i)
|
||||
if v < min {
|
||||
min = v
|
||||
}
|
||||
}
|
||||
L.Push(min)
|
||||
return 1
|
||||
}
|
||||
|
||||
func mathMod(L *LState) int {
|
||||
lhs := L.CheckNumber(1)
|
||||
rhs := L.CheckNumber(2)
|
||||
L.Push(luaModulo(lhs, rhs))
|
||||
return 1
|
||||
}
|
||||
|
||||
func mathModf(L *LState) int {
|
||||
v1, v2 := math.Modf(float64(L.CheckNumber(1)))
|
||||
L.Push(LNumber(v1))
|
||||
L.Push(LNumber(v2))
|
||||
return 2
|
||||
}
|
||||
|
||||
func mathPow(L *LState) int {
|
||||
L.Push(LNumber(math.Pow(float64(L.CheckNumber(1)), float64(L.CheckNumber(2)))))
|
||||
return 1
|
||||
}
|
||||
|
||||
func mathRad(L *LState) int {
|
||||
L.Push(LNumber(float64(L.CheckNumber(1)) * math.Pi / 180))
|
||||
return 1
|
||||
}
|
||||
|
||||
func mathRandom(L *LState) int {
|
||||
switch L.GetTop() {
|
||||
case 0:
|
||||
L.Push(LNumber(rand.Float64()))
|
||||
case 1:
|
||||
n := L.CheckInt(1)
|
||||
L.Push(LNumber(rand.Intn(n) + 1))
|
||||
default:
|
||||
min := L.CheckInt(1)
|
||||
max := L.CheckInt(2) + 1
|
||||
L.Push(LNumber(rand.Intn(max-min) + min))
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func mathRandomseed(L *LState) int {
|
||||
rand.Seed(L.CheckInt64(1))
|
||||
return 0
|
||||
}
|
||||
|
||||
func mathSin(L *LState) int {
|
||||
L.Push(LNumber(math.Sin(float64(L.CheckNumber(1)))))
|
||||
return 1
|
||||
}
|
||||
|
||||
func mathSinh(L *LState) int {
|
||||
L.Push(LNumber(math.Sinh(float64(L.CheckNumber(1)))))
|
||||
return 1
|
||||
}
|
||||
|
||||
func mathSqrt(L *LState) int {
|
||||
L.Push(LNumber(math.Sqrt(float64(L.CheckNumber(1)))))
|
||||
return 1
|
||||
}
|
||||
|
||||
func mathTan(L *LState) int {
|
||||
L.Push(LNumber(math.Tan(float64(L.CheckNumber(1)))))
|
||||
return 1
|
||||
}
|
||||
|
||||
func mathTanh(L *LState) int {
|
||||
L.Push(LNumber(math.Tanh(float64(L.CheckNumber(1)))))
|
||||
return 1
|
||||
}
|
||||
|
||||
//
|
|
@ -0,0 +1,371 @@
|
|||
package lua
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
/*
|
||||
gopherlua uses Lua 5.1.4's opcodes.
|
||||
Lua 5.1.4 opcodes layout:
|
||||
|
||||
instruction = 32bit(fixed length)
|
||||
|
||||
+---------------------------------------------+
|
||||
|0-5(6bits)|6-13(8bit)|14-22(9bit)|23-31(9bit)|
|
||||
|==========+==========+===========+===========|
|
||||
| opcode | A | C | B |
|
||||
|----------+----------+-----------+-----------|
|
||||
| opcode | A | Bx(unsigned) |
|
||||
|----------+----------+-----------+-----------|
|
||||
| opcode | A | sBx(signed) |
|
||||
+---------------------------------------------+
|
||||
*/
|
||||
|
||||
const opInvalidInstruction = ^uint32(0)
|
||||
|
||||
const opSizeCode = 6
|
||||
const opSizeA = 8
|
||||
const opSizeB = 9
|
||||
const opSizeC = 9
|
||||
const opSizeBx = 18
|
||||
const opSizesBx = 18
|
||||
|
||||
const opMaxArgsA = (1 << opSizeA) - 1
|
||||
const opMaxArgsB = (1 << opSizeB) - 1
|
||||
const opMaxArgsC = (1 << opSizeC) - 1
|
||||
const opMaxArgBx = (1 << opSizeBx) - 1
|
||||
const opMaxArgSbx = opMaxArgBx >> 1
|
||||
|
||||
const (
|
||||
OP_MOVE int = iota /* A B R(A) := R(B) */
|
||||
OP_MOVEN /* A B R(A) := R(B); followed by R(C) MOVE ops */
|
||||
OP_LOADK /* A Bx R(A) := Kst(Bx) */
|
||||
OP_LOADBOOL /* A B C R(A) := (Bool)B; if (C) pc++ */
|
||||
OP_LOADNIL /* A B R(A) := ... := R(B) := nil */
|
||||
OP_GETUPVAL /* A B R(A) := UpValue[B] */
|
||||
|
||||
OP_GETGLOBAL /* A Bx R(A) := Gbl[Kst(Bx)] */
|
||||
OP_GETTABLE /* A B C R(A) := R(B)[RK(C)] */
|
||||
OP_GETTABLEKS /* A B C R(A) := R(B)[RK(C)] ; RK(C) is constant string */
|
||||
|
||||
OP_SETGLOBAL /* A Bx Gbl[Kst(Bx)] := R(A) */
|
||||
OP_SETUPVAL /* A B UpValue[B] := R(A) */
|
||||
OP_SETTABLE /* A B C R(A)[RK(B)] := RK(C) */
|
||||
OP_SETTABLEKS /* A B C R(A)[RK(B)] := RK(C) ; RK(B) is constant string */
|
||||
|
||||
OP_NEWTABLE /* A B C R(A) := {} (size = BC) */
|
||||
|
||||
OP_SELF /* A B C R(A+1) := R(B); R(A) := R(B)[RK(C)] */
|
||||
|
||||
OP_ADD /* A B C R(A) := RK(B) + RK(C) */
|
||||
OP_SUB /* A B C R(A) := RK(B) - RK(C) */
|
||||
OP_MUL /* A B C R(A) := RK(B) * RK(C) */
|
||||
OP_DIV /* A B C R(A) := RK(B) / RK(C) */
|
||||
OP_MOD /* A B C R(A) := RK(B) % RK(C) */
|
||||
OP_POW /* A B C R(A) := RK(B) ^ RK(C) */
|
||||
OP_UNM /* A B R(A) := -R(B) */
|
||||
OP_NOT /* A B R(A) := not R(B) */
|
||||
OP_LEN /* A B R(A) := length of R(B) */
|
||||
|
||||
OP_CONCAT /* A B C R(A) := R(B).. ... ..R(C) */
|
||||
|
||||
OP_JMP /* sBx pc+=sBx */
|
||||
|
||||
OP_EQ /* A B C if ((RK(B) == RK(C)) ~= A) then pc++ */
|
||||
OP_LT /* A B C if ((RK(B) < RK(C)) ~= A) then pc++ */
|
||||
OP_LE /* A B C if ((RK(B) <= RK(C)) ~= A) then pc++ */
|
||||
|
||||
OP_TEST /* A C if not (R(A) <=> C) then pc++ */
|
||||
OP_TESTSET /* A B C if (R(B) <=> C) then R(A) := R(B) else pc++ */
|
||||
|
||||
OP_CALL /* A B C R(A) ... R(A+C-2) := R(A)(R(A+1) ... R(A+B-1)) */
|
||||
OP_TAILCALL /* A B C return R(A)(R(A+1) ... R(A+B-1)) */
|
||||
OP_RETURN /* A B return R(A) ... R(A+B-2) (see note) */
|
||||
|
||||
OP_FORLOOP /* A sBx R(A)+=R(A+2);
|
||||
if R(A) <?= R(A+1) then { pc+=sBx; R(A+3)=R(A) }*/
|
||||
OP_FORPREP /* A sBx R(A)-=R(A+2); pc+=sBx */
|
||||
|
||||
OP_TFORLOOP /* A C R(A+3) ... R(A+3+C) := R(A)(R(A+1) R(A+2));
|
||||
if R(A+3) ~= nil then { pc++; R(A+2)=R(A+3); } */
|
||||
OP_SETLIST /* A B C R(A)[(C-1)*FPF+i] := R(A+i) 1 <= i <= B */
|
||||
|
||||
OP_CLOSE /* A close all variables in the stack up to (>=) R(A)*/
|
||||
OP_CLOSURE /* A Bx R(A) := closure(KPROTO[Bx] R(A) ... R(A+n)) */
|
||||
|
||||
OP_VARARG /* A B R(A) R(A+1) ... R(A+B-1) = vararg */
|
||||
|
||||
OP_NOP /* NOP */
|
||||
)
|
||||
const opCodeMax = OP_NOP
|
||||
|
||||
type opArgMode int
|
||||
|
||||
const (
|
||||
opArgModeN opArgMode = iota
|
||||
opArgModeU
|
||||
opArgModeR
|
||||
opArgModeK
|
||||
)
|
||||
|
||||
type opType int
|
||||
|
||||
const (
|
||||
opTypeABC = iota
|
||||
opTypeABx
|
||||
opTypeASbx
|
||||
)
|
||||
|
||||
type opProp struct {
|
||||
Name string
|
||||
IsTest bool
|
||||
SetRegA bool
|
||||
ModeArgB opArgMode
|
||||
ModeArgC opArgMode
|
||||
Type opType
|
||||
}
|
||||
|
||||
var opProps = []opProp{
|
||||
opProp{"MOVE", false, true, opArgModeR, opArgModeN, opTypeABC},
|
||||
opProp{"MOVEN", false, true, opArgModeR, opArgModeN, opTypeABC},
|
||||
opProp{"LOADK", false, true, opArgModeK, opArgModeN, opTypeABx},
|
||||
opProp{"LOADBOOL", false, true, opArgModeU, opArgModeU, opTypeABC},
|
||||
opProp{"LOADNIL", false, true, opArgModeR, opArgModeN, opTypeABC},
|
||||
opProp{"GETUPVAL", false, true, opArgModeU, opArgModeN, opTypeABC},
|
||||
opProp{"GETGLOBAL", false, true, opArgModeK, opArgModeN, opTypeABx},
|
||||
opProp{"GETTABLE", false, true, opArgModeR, opArgModeK, opTypeABC},
|
||||
opProp{"GETTABLEKS", false, true, opArgModeR, opArgModeK, opTypeABC},
|
||||
opProp{"SETGLOBAL", false, false, opArgModeK, opArgModeN, opTypeABx},
|
||||
opProp{"SETUPVAL", false, false, opArgModeU, opArgModeN, opTypeABC},
|
||||
opProp{"SETTABLE", false, false, opArgModeK, opArgModeK, opTypeABC},
|
||||
opProp{"SETTABLEKS", false, false, opArgModeK, opArgModeK, opTypeABC},
|
||||
opProp{"NEWTABLE", false, true, opArgModeU, opArgModeU, opTypeABC},
|
||||
opProp{"SELF", false, true, opArgModeR, opArgModeK, opTypeABC},
|
||||
opProp{"ADD", false, true, opArgModeK, opArgModeK, opTypeABC},
|
||||
opProp{"SUB", false, true, opArgModeK, opArgModeK, opTypeABC},
|
||||
opProp{"MUL", false, true, opArgModeK, opArgModeK, opTypeABC},
|
||||
opProp{"DIV", false, true, opArgModeK, opArgModeK, opTypeABC},
|
||||
opProp{"MOD", false, true, opArgModeK, opArgModeK, opTypeABC},
|
||||
opProp{"POW", false, true, opArgModeK, opArgModeK, opTypeABC},
|
||||
opProp{"UNM", false, true, opArgModeR, opArgModeN, opTypeABC},
|
||||
opProp{"NOT", false, true, opArgModeR, opArgModeN, opTypeABC},
|
||||
opProp{"LEN", false, true, opArgModeR, opArgModeN, opTypeABC},
|
||||
opProp{"CONCAT", false, true, opArgModeR, opArgModeR, opTypeABC},
|
||||
opProp{"JMP", false, false, opArgModeR, opArgModeN, opTypeASbx},
|
||||
opProp{"EQ", true, false, opArgModeK, opArgModeK, opTypeABC},
|
||||
opProp{"LT", true, false, opArgModeK, opArgModeK, opTypeABC},
|
||||
opProp{"LE", true, false, opArgModeK, opArgModeK, opTypeABC},
|
||||
opProp{"TEST", true, true, opArgModeR, opArgModeU, opTypeABC},
|
||||
opProp{"TESTSET", true, true, opArgModeR, opArgModeU, opTypeABC},
|
||||
opProp{"CALL", false, true, opArgModeU, opArgModeU, opTypeABC},
|
||||
opProp{"TAILCALL", false, true, opArgModeU, opArgModeU, opTypeABC},
|
||||
opProp{"RETURN", false, false, opArgModeU, opArgModeN, opTypeABC},
|
||||
opProp{"FORLOOP", false, true, opArgModeR, opArgModeN, opTypeASbx},
|
||||
opProp{"FORPREP", false, true, opArgModeR, opArgModeN, opTypeASbx},
|
||||
opProp{"TFORLOOP", true, false, opArgModeN, opArgModeU, opTypeABC},
|
||||
opProp{"SETLIST", false, false, opArgModeU, opArgModeU, opTypeABC},
|
||||
opProp{"CLOSE", false, false, opArgModeN, opArgModeN, opTypeABC},
|
||||
opProp{"CLOSURE", false, true, opArgModeU, opArgModeN, opTypeABx},
|
||||
opProp{"VARARG", false, true, opArgModeU, opArgModeN, opTypeABC},
|
||||
opProp{"NOP", false, false, opArgModeR, opArgModeN, opTypeASbx},
|
||||
}
|
||||
|
||||
func opGetOpCode(inst uint32) int {
|
||||
return int(inst >> 26)
|
||||
}
|
||||
|
||||
func opSetOpCode(inst *uint32, opcode int) {
|
||||
*inst = (*inst & 0x3ffffff) | uint32(opcode<<26)
|
||||
}
|
||||
|
||||
func opGetArgA(inst uint32) int {
|
||||
return int(inst>>18) & 0xff
|
||||
}
|
||||
|
||||
func opSetArgA(inst *uint32, arg int) {
|
||||
*inst = (*inst & 0xfc03ffff) | uint32((arg&0xff)<<18)
|
||||
}
|
||||
|
||||
func opGetArgB(inst uint32) int {
|
||||
return int(inst & 0x1ff)
|
||||
}
|
||||
|
||||
func opSetArgB(inst *uint32, arg int) {
|
||||
*inst = (*inst & 0xfffffe00) | uint32(arg&0x1ff)
|
||||
}
|
||||
|
||||
func opGetArgC(inst uint32) int {
|
||||
return int(inst>>9) & 0x1ff
|
||||
}
|
||||
|
||||
func opSetArgC(inst *uint32, arg int) {
|
||||
*inst = (*inst & 0xfffc01ff) | uint32((arg&0x1ff)<<9)
|
||||
}
|
||||
|
||||
func opGetArgBx(inst uint32) int {
|
||||
return int(inst & 0x3ffff)
|
||||
}
|
||||
|
||||
func opSetArgBx(inst *uint32, arg int) {
|
||||
*inst = (*inst & 0xfffc0000) | uint32(arg&0x3ffff)
|
||||
}
|
||||
|
||||
func opGetArgSbx(inst uint32) int {
|
||||
return opGetArgBx(inst) - opMaxArgSbx
|
||||
}
|
||||
|
||||
func opSetArgSbx(inst *uint32, arg int) {
|
||||
opSetArgBx(inst, arg+opMaxArgSbx)
|
||||
}
|
||||
|
||||
func opCreateABC(op int, a int, b int, c int) uint32 {
|
||||
var inst uint32 = 0
|
||||
opSetOpCode(&inst, op)
|
||||
opSetArgA(&inst, a)
|
||||
opSetArgB(&inst, b)
|
||||
opSetArgC(&inst, c)
|
||||
return inst
|
||||
}
|
||||
|
||||
func opCreateABx(op int, a int, bx int) uint32 {
|
||||
var inst uint32 = 0
|
||||
opSetOpCode(&inst, op)
|
||||
opSetArgA(&inst, a)
|
||||
opSetArgBx(&inst, bx)
|
||||
return inst
|
||||
}
|
||||
|
||||
func opCreateASbx(op int, a int, sbx int) uint32 {
|
||||
var inst uint32 = 0
|
||||
opSetOpCode(&inst, op)
|
||||
opSetArgA(&inst, a)
|
||||
opSetArgSbx(&inst, sbx)
|
||||
return inst
|
||||
}
|
||||
|
||||
const opBitRk = 1 << (opSizeB - 1)
|
||||
const opMaxIndexRk = opBitRk - 1
|
||||
|
||||
func opIsK(value int) bool {
|
||||
return bool((value & opBitRk) != 0)
|
||||
}
|
||||
|
||||
func opIndexK(value int) int {
|
||||
return value & ^opBitRk
|
||||
}
|
||||
|
||||
func opRkAsk(value int) int {
|
||||
return value | opBitRk
|
||||
}
|
||||
|
||||
func opToString(inst uint32) string {
|
||||
op := opGetOpCode(inst)
|
||||
if op > opCodeMax {
|
||||
return ""
|
||||
}
|
||||
prop := &(opProps[op])
|
||||
|
||||
arga := opGetArgA(inst)
|
||||
argb := opGetArgB(inst)
|
||||
argc := opGetArgC(inst)
|
||||
argbx := opGetArgBx(inst)
|
||||
argsbx := opGetArgSbx(inst)
|
||||
|
||||
buf := ""
|
||||
switch prop.Type {
|
||||
case opTypeABC:
|
||||
buf = fmt.Sprintf("%s | %d, %d, %d", prop.Name, arga, argb, argc)
|
||||
case opTypeABx:
|
||||
buf = fmt.Sprintf("%s | %d, %d", prop.Name, arga, argbx)
|
||||
case opTypeASbx:
|
||||
buf = fmt.Sprintf("%s | %d, %d", prop.Name, arga, argsbx)
|
||||
}
|
||||
|
||||
switch op {
|
||||
case OP_MOVE:
|
||||
buf += fmt.Sprintf("; R(%v) := R(%v)", arga, argb)
|
||||
case OP_MOVEN:
|
||||
buf += fmt.Sprintf("; R(%v) := R(%v); followed by %v MOVE ops", arga, argb, argc)
|
||||
case OP_LOADK:
|
||||
buf += fmt.Sprintf("; R(%v) := Kst(%v)", arga, argbx)
|
||||
case OP_LOADBOOL:
|
||||
buf += fmt.Sprintf("; R(%v) := (Bool)%v; if (%v) pc++", arga, argb, argc)
|
||||
case OP_LOADNIL:
|
||||
buf += fmt.Sprintf("; R(%v) := ... := R(%v) := nil", arga, argb)
|
||||
case OP_GETUPVAL:
|
||||
buf += fmt.Sprintf("; R(%v) := UpValue[%v]", arga, argb)
|
||||
case OP_GETGLOBAL:
|
||||
buf += fmt.Sprintf("; R(%v) := Gbl[Kst(%v)]", arga, argbx)
|
||||
case OP_GETTABLE:
|
||||
buf += fmt.Sprintf("; R(%v) := R(%v)[RK(%v)]", arga, argb, argc)
|
||||
case OP_GETTABLEKS:
|
||||
buf += fmt.Sprintf("; R(%v) := R(%v)[RK(%v)] ; RK(%v) is constant string", arga, argb, argc, argc)
|
||||
case OP_SETGLOBAL:
|
||||
buf += fmt.Sprintf("; Gbl[Kst(%v)] := R(%v)", argbx, arga)
|
||||
case OP_SETUPVAL:
|
||||
buf += fmt.Sprintf("; UpValue[%v] := R(%v)", argb, arga)
|
||||
case OP_SETTABLE:
|
||||
buf += fmt.Sprintf("; R(%v)[RK(%v)] := RK(%v)", arga, argb, argc)
|
||||
case OP_SETTABLEKS:
|
||||
buf += fmt.Sprintf("; R(%v)[RK(%v)] := RK(%v) ; RK(%v) is constant string", arga, argb, argc, argb)
|
||||
case OP_NEWTABLE:
|
||||
buf += fmt.Sprintf("; R(%v) := {} (size = BC)", arga)
|
||||
case OP_SELF:
|
||||
buf += fmt.Sprintf("; R(%v+1) := R(%v); R(%v) := R(%v)[RK(%v)]", arga, argb, arga, argb, argc)
|
||||
case OP_ADD:
|
||||
buf += fmt.Sprintf("; R(%v) := RK(%v) + RK(%v)", arga, argb, argc)
|
||||
case OP_SUB:
|
||||
buf += fmt.Sprintf("; R(%v) := RK(%v) - RK(%v)", arga, argb, argc)
|
||||
case OP_MUL:
|
||||
buf += fmt.Sprintf("; R(%v) := RK(%v) * RK(%v)", arga, argb, argc)
|
||||
case OP_DIV:
|
||||
buf += fmt.Sprintf("; R(%v) := RK(%v) / RK(%v)", arga, argb, argc)
|
||||
case OP_MOD:
|
||||
buf += fmt.Sprintf("; R(%v) := RK(%v) %% RK(%v)", arga, argb, argc)
|
||||
case OP_POW:
|
||||
buf += fmt.Sprintf("; R(%v) := RK(%v) ^ RK(%v)", arga, argb, argc)
|
||||
case OP_UNM:
|
||||
buf += fmt.Sprintf("; R(%v) := -R(%v)", arga, argb)
|
||||
case OP_NOT:
|
||||
buf += fmt.Sprintf("; R(%v) := not R(%v)", arga, argb)
|
||||
case OP_LEN:
|
||||
buf += fmt.Sprintf("; R(%v) := length of R(%v)", arga, argb)
|
||||
case OP_CONCAT:
|
||||
buf += fmt.Sprintf("; R(%v) := R(%v).. ... ..R(%v)", arga, argb, argc)
|
||||
case OP_JMP:
|
||||
buf += fmt.Sprintf("; pc+=%v", argsbx)
|
||||
case OP_EQ:
|
||||
buf += fmt.Sprintf("; if ((RK(%v) == RK(%v)) ~= %v) then pc++", argb, argc, arga)
|
||||
case OP_LT:
|
||||
buf += fmt.Sprintf("; if ((RK(%v) < RK(%v)) ~= %v) then pc++", argb, argc, arga)
|
||||
case OP_LE:
|
||||
buf += fmt.Sprintf("; if ((RK(%v) <= RK(%v)) ~= %v) then pc++", argb, argc, arga)
|
||||
case OP_TEST:
|
||||
buf += fmt.Sprintf("; if not (R(%v) <=> %v) then pc++", arga, argc)
|
||||
case OP_TESTSET:
|
||||
buf += fmt.Sprintf("; if (R(%v) <=> %v) then R(%v) := R(%v) else pc++", argb, argc, arga, argb)
|
||||
case OP_CALL:
|
||||
buf += fmt.Sprintf("; R(%v) ... R(%v+%v-2) := R(%v)(R(%v+1) ... R(%v+%v-1))", arga, arga, argc, arga, arga, arga, argb)
|
||||
case OP_TAILCALL:
|
||||
buf += fmt.Sprintf("; return R(%v)(R(%v+1) ... R(%v+%v-1))", arga, arga, arga, argb)
|
||||
case OP_RETURN:
|
||||
buf += fmt.Sprintf("; return R(%v) ... R(%v+%v-2)", arga, arga, argb)
|
||||
case OP_FORLOOP:
|
||||
buf += fmt.Sprintf("; R(%v)+=R(%v+2); if R(%v) <?= R(%v+1) then { pc+=%v; R(%v+3)=R(%v) }", arga, arga, arga, arga, argsbx, arga, arga)
|
||||
case OP_FORPREP:
|
||||
buf += fmt.Sprintf("; R(%v)-=R(%v+2); pc+=%v", arga, arga, argsbx)
|
||||
case OP_TFORLOOP:
|
||||
buf += fmt.Sprintf("; R(%v+3) ... R(%v+3+%v) := R(%v)(R(%v+1) R(%v+2)); if R(%v+3) ~= nil then { pc++; R(%v+2)=R(%v+3); }", arga, arga, argc, arga, arga, arga, arga, arga, arga)
|
||||
case OP_SETLIST:
|
||||
buf += fmt.Sprintf("; R(%v)[(%v-1)*FPF+i] := R(%v+i) 1 <= i <= %v", arga, argc, arga, argb)
|
||||
case OP_CLOSE:
|
||||
buf += fmt.Sprintf("; close all variables in the stack up to (>=) R(%v)", arga)
|
||||
case OP_CLOSURE:
|
||||
buf += fmt.Sprintf("; R(%v) := closure(KPROTO[%v] R(%v) ... R(%v+n))", arga, argbx, arga, arga)
|
||||
case OP_VARARG:
|
||||
buf += fmt.Sprintf("; R(%v) R(%v+1) ... R(%v+%v-1) = vararg", arga, arga, arga, argb)
|
||||
case OP_NOP:
|
||||
/* nothing to do */
|
||||
}
|
||||
return buf
|
||||
}
|
|
@ -0,0 +1,232 @@
|
|||
package lua
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var startedAt time.Time
|
||||
|
||||
func init() {
|
||||
startedAt = time.Now()
|
||||
}
|
||||
|
||||
func getIntField(L *LState, tb *LTable, key string, v int) int {
|
||||
ret := tb.RawGetString(key)
|
||||
|
||||
switch lv := ret.(type) {
|
||||
case LNumber:
|
||||
return int(lv)
|
||||
case LString:
|
||||
slv := string(lv)
|
||||
slv = strings.TrimLeft(slv, " ")
|
||||
if strings.HasPrefix(slv, "0") && !strings.HasPrefix(slv, "0x") && !strings.HasPrefix(slv, "0X") {
|
||||
//Standard lua interpreter only support decimal and hexadecimal
|
||||
slv = strings.TrimLeft(slv, "0")
|
||||
if slv == "" {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
if num, err := parseNumber(slv); err == nil {
|
||||
return int(num)
|
||||
}
|
||||
default:
|
||||
return v
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func getBoolField(L *LState, tb *LTable, key string, v bool) bool {
|
||||
ret := tb.RawGetString(key)
|
||||
if lb, ok := ret.(LBool); ok {
|
||||
return bool(lb)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func OpenOs(L *LState) int {
|
||||
osmod := L.RegisterModule(OsLibName, osFuncs)
|
||||
L.Push(osmod)
|
||||
return 1
|
||||
}
|
||||
|
||||
var osFuncs = map[string]LGFunction{
|
||||
"clock": osClock,
|
||||
"difftime": osDiffTime,
|
||||
"execute": osExecute,
|
||||
"exit": osExit,
|
||||
"date": osDate,
|
||||
"getenv": osGetEnv,
|
||||
"remove": osRemove,
|
||||
"rename": osRename,
|
||||
"setenv": osSetEnv,
|
||||
"setlocale": osSetLocale,
|
||||
"time": osTime,
|
||||
"tmpname": osTmpname,
|
||||
}
|
||||
|
||||
func osClock(L *LState) int {
|
||||
L.Push(LNumber(float64(time.Now().Sub(startedAt)) / float64(time.Second)))
|
||||
return 1
|
||||
}
|
||||
|
||||
func osDiffTime(L *LState) int {
|
||||
L.Push(LNumber(L.CheckInt64(1) - L.CheckInt64(2)))
|
||||
return 1
|
||||
}
|
||||
|
||||
func osExecute(L *LState) int {
|
||||
var procAttr os.ProcAttr
|
||||
procAttr.Files = []*os.File{os.Stdin, os.Stdout, os.Stderr}
|
||||
cmd, args := popenArgs(L.CheckString(1))
|
||||
args = append([]string{cmd}, args...)
|
||||
process, err := os.StartProcess(cmd, args, &procAttr)
|
||||
if err != nil {
|
||||
L.Push(LNumber(1))
|
||||
return 1
|
||||
}
|
||||
|
||||
ps, err := process.Wait()
|
||||
if err != nil || !ps.Success() {
|
||||
L.Push(LNumber(1))
|
||||
return 1
|
||||
}
|
||||
L.Push(LNumber(0))
|
||||
return 1
|
||||
}
|
||||
|
||||
func osExit(L *LState) int {
|
||||
L.Close()
|
||||
os.Exit(L.OptInt(1, 0))
|
||||
return 1
|
||||
}
|
||||
|
||||
func osDate(L *LState) int {
|
||||
t := time.Now()
|
||||
cfmt := "%c"
|
||||
if L.GetTop() >= 1 {
|
||||
cfmt = L.CheckString(1)
|
||||
if strings.HasPrefix(cfmt, "!") {
|
||||
t = time.Now().UTC()
|
||||
cfmt = strings.TrimLeft(cfmt, "!")
|
||||
}
|
||||
if L.GetTop() >= 2 {
|
||||
t = time.Unix(L.CheckInt64(2), 0)
|
||||
}
|
||||
if strings.HasPrefix(cfmt, "*t") {
|
||||
ret := L.NewTable()
|
||||
ret.RawSetString("year", LNumber(t.Year()))
|
||||
ret.RawSetString("month", LNumber(t.Month()))
|
||||
ret.RawSetString("day", LNumber(t.Day()))
|
||||
ret.RawSetString("hour", LNumber(t.Hour()))
|
||||
ret.RawSetString("min", LNumber(t.Minute()))
|
||||
ret.RawSetString("sec", LNumber(t.Second()))
|
||||
ret.RawSetString("wday", LNumber(t.Weekday()+1))
|
||||
// TODO yday & dst
|
||||
ret.RawSetString("yday", LNumber(0))
|
||||
ret.RawSetString("isdst", LFalse)
|
||||
L.Push(ret)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
L.Push(LString(strftime(t, cfmt)))
|
||||
return 1
|
||||
}
|
||||
|
||||
func osGetEnv(L *LState) int {
|
||||
v := os.Getenv(L.CheckString(1))
|
||||
if len(v) == 0 {
|
||||
L.Push(LNil)
|
||||
} else {
|
||||
L.Push(LString(v))
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func osRemove(L *LState) int {
|
||||
err := os.Remove(L.CheckString(1))
|
||||
if err != nil {
|
||||
L.Push(LNil)
|
||||
L.Push(LString(err.Error()))
|
||||
return 2
|
||||
} else {
|
||||
L.Push(LTrue)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
func osRename(L *LState) int {
|
||||
err := os.Rename(L.CheckString(1), L.CheckString(2))
|
||||
if err != nil {
|
||||
L.Push(LNil)
|
||||
L.Push(LString(err.Error()))
|
||||
return 2
|
||||
} else {
|
||||
L.Push(LTrue)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
func osSetLocale(L *LState) int {
|
||||
// setlocale is not supported
|
||||
L.Push(LFalse)
|
||||
return 1
|
||||
}
|
||||
|
||||
func osSetEnv(L *LState) int {
|
||||
err := os.Setenv(L.CheckString(1), L.CheckString(2))
|
||||
if err != nil {
|
||||
L.Push(LNil)
|
||||
L.Push(LString(err.Error()))
|
||||
return 2
|
||||
} else {
|
||||
L.Push(LTrue)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
func osTime(L *LState) int {
|
||||
if L.GetTop() == 0 {
|
||||
L.Push(LNumber(time.Now().Unix()))
|
||||
} else {
|
||||
lv := L.CheckAny(1)
|
||||
if lv == LNil {
|
||||
L.Push(LNumber(time.Now().Unix()))
|
||||
} else {
|
||||
tbl, ok := lv.(*LTable)
|
||||
if !ok {
|
||||
L.TypeError(1, LTTable)
|
||||
}
|
||||
sec := getIntField(L, tbl, "sec", 0)
|
||||
min := getIntField(L, tbl, "min", 0)
|
||||
hour := getIntField(L, tbl, "hour", 12)
|
||||
day := getIntField(L, tbl, "day", -1)
|
||||
month := getIntField(L, tbl, "month", -1)
|
||||
year := getIntField(L, tbl, "year", -1)
|
||||
isdst := getBoolField(L, tbl, "isdst", false)
|
||||
t := time.Date(year, time.Month(month), day, hour, min, sec, 0, time.Local)
|
||||
// TODO dst
|
||||
if false {
|
||||
print(isdst)
|
||||
}
|
||||
L.Push(LNumber(t.Unix()))
|
||||
}
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func osTmpname(L *LState) int {
|
||||
file, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
L.RaiseError("unable to generate a unique filename")
|
||||
}
|
||||
file.Close()
|
||||
os.Remove(file.Name()) // ignore errors
|
||||
L.Push(LString(file.Name()))
|
||||
return 1
|
||||
}
|
||||
|
||||
//
|
|
@ -0,0 +1,7 @@
|
|||
// GopherLua: VM and compiler for Lua in Go
|
||||
package lua
|
||||
|
||||
const PackageName = "GopherLua"
|
||||
const PackageVersion = "0.1"
|
||||
const PackageAuthors = "Yusuke Inuzuka"
|
||||
const PackageCopyRight = PackageName + " " + PackageVersion + " Copyright (C) 2015 -2017 " + PackageAuthors
|
|
@ -0,0 +1,7 @@
|
|||
all : parser.go
|
||||
|
||||
parser.go : parser.go.y
|
||||
goyacc -o $@ parser.go.y; [ -f y.output ] && ( rm -f y.output )
|
||||
|
||||
clean:
|
||||
rm -f parser.go
|
|
@ -0,0 +1,539 @@
|
|||
package parse
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/yuin/gopher-lua/ast"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const EOF = -1
|
||||
const whitespace1 = 1<<'\t' | 1<<' '
|
||||
const whitespace2 = 1<<'\t' | 1<<'\n' | 1<<'\r' | 1<<' '
|
||||
|
||||
type Error struct {
|
||||
Pos ast.Position
|
||||
Message string
|
||||
Token string
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
pos := e.Pos
|
||||
if pos.Line == EOF {
|
||||
return fmt.Sprintf("%v at EOF: %s\n", pos.Source, e.Message)
|
||||
} else {
|
||||
return fmt.Sprintf("%v line:%d(column:%d) near '%v': %s\n", pos.Source, pos.Line, pos.Column, e.Token, e.Message)
|
||||
}
|
||||
}
|
||||
|
||||
func writeChar(buf *bytes.Buffer, c int) { buf.WriteByte(byte(c)) }
|
||||
|
||||
func isDecimal(ch int) bool { return '0' <= ch && ch <= '9' }
|
||||
|
||||
func isIdent(ch int, pos int) bool {
|
||||
return ch == '_' || 'A' <= ch && ch <= 'Z' || 'a' <= ch && ch <= 'z' || isDecimal(ch) && pos > 0
|
||||
}
|
||||
|
||||
func isDigit(ch int) bool {
|
||||
return '0' <= ch && ch <= '9' || 'a' <= ch && ch <= 'f' || 'A' <= ch && ch <= 'F'
|
||||
}
|
||||
|
||||
type Scanner struct {
|
||||
Pos ast.Position
|
||||
reader *bufio.Reader
|
||||
}
|
||||
|
||||
func NewScanner(reader io.Reader, source string) *Scanner {
|
||||
return &Scanner{
|
||||
Pos: ast.Position{
|
||||
Source: source,
|
||||
Line: 1,
|
||||
Column: 0,
|
||||
},
|
||||
reader: bufio.NewReaderSize(reader, 4096),
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *Scanner) Error(tok string, msg string) *Error { return &Error{sc.Pos, msg, tok} }
|
||||
|
||||
func (sc *Scanner) TokenError(tok ast.Token, msg string) *Error { return &Error{tok.Pos, msg, tok.Str} }
|
||||
|
||||
func (sc *Scanner) readNext() int {
|
||||
ch, err := sc.reader.ReadByte()
|
||||
if err == io.EOF {
|
||||
return EOF
|
||||
}
|
||||
return int(ch)
|
||||
}
|
||||
|
||||
func (sc *Scanner) Newline(ch int) {
|
||||
if ch < 0 {
|
||||
return
|
||||
}
|
||||
sc.Pos.Line += 1
|
||||
sc.Pos.Column = 0
|
||||
next := sc.Peek()
|
||||
if ch == '\n' && next == '\r' || ch == '\r' && next == '\n' {
|
||||
sc.reader.ReadByte()
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *Scanner) Next() int {
|
||||
ch := sc.readNext()
|
||||
switch ch {
|
||||
case '\n', '\r':
|
||||
sc.Newline(ch)
|
||||
ch = int('\n')
|
||||
case EOF:
|
||||
sc.Pos.Line = EOF
|
||||
sc.Pos.Column = 0
|
||||
default:
|
||||
sc.Pos.Column++
|
||||
}
|
||||
return ch
|
||||
}
|
||||
|
||||
func (sc *Scanner) Peek() int {
|
||||
ch := sc.readNext()
|
||||
if ch != EOF {
|
||||
sc.reader.UnreadByte()
|
||||
}
|
||||
return ch
|
||||
}
|
||||
|
||||
func (sc *Scanner) skipWhiteSpace(whitespace int64) int {
|
||||
ch := sc.Next()
|
||||
for ; whitespace&(1<<uint(ch)) != 0; ch = sc.Next() {
|
||||
}
|
||||
return ch
|
||||
}
|
||||
|
||||
func (sc *Scanner) skipComments(ch int) error {
|
||||
// multiline comment
|
||||
if sc.Peek() == '[' {
|
||||
ch = sc.Next()
|
||||
if sc.Peek() == '[' || sc.Peek() == '=' {
|
||||
var buf bytes.Buffer
|
||||
if err := sc.scanMultilineString(sc.Next(), &buf); err != nil {
|
||||
return sc.Error(buf.String(), "invalid multiline comment")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
for {
|
||||
if ch == '\n' || ch == '\r' || ch < 0 {
|
||||
break
|
||||
}
|
||||
ch = sc.Next()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sc *Scanner) scanIdent(ch int, buf *bytes.Buffer) error {
|
||||
writeChar(buf, ch)
|
||||
for isIdent(sc.Peek(), 1) {
|
||||
writeChar(buf, sc.Next())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sc *Scanner) scanDecimal(ch int, buf *bytes.Buffer) error {
|
||||
writeChar(buf, ch)
|
||||
for isDecimal(sc.Peek()) {
|
||||
writeChar(buf, sc.Next())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sc *Scanner) scanNumber(ch int, buf *bytes.Buffer) error {
|
||||
if ch == '0' { // octal
|
||||
if sc.Peek() == 'x' || sc.Peek() == 'X' {
|
||||
writeChar(buf, ch)
|
||||
writeChar(buf, sc.Next())
|
||||
hasvalue := false
|
||||
for isDigit(sc.Peek()) {
|
||||
writeChar(buf, sc.Next())
|
||||
hasvalue = true
|
||||
}
|
||||
if !hasvalue {
|
||||
return sc.Error(buf.String(), "illegal hexadecimal number")
|
||||
}
|
||||
return nil
|
||||
} else if sc.Peek() != '.' && isDecimal(sc.Peek()) {
|
||||
ch = sc.Next()
|
||||
}
|
||||
}
|
||||
sc.scanDecimal(ch, buf)
|
||||
if sc.Peek() == '.' {
|
||||
sc.scanDecimal(sc.Next(), buf)
|
||||
}
|
||||
if ch = sc.Peek(); ch == 'e' || ch == 'E' {
|
||||
writeChar(buf, sc.Next())
|
||||
if ch = sc.Peek(); ch == '-' || ch == '+' {
|
||||
writeChar(buf, sc.Next())
|
||||
}
|
||||
sc.scanDecimal(sc.Next(), buf)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sc *Scanner) scanString(quote int, buf *bytes.Buffer) error {
|
||||
ch := sc.Next()
|
||||
for ch != quote {
|
||||
if ch == '\n' || ch == '\r' || ch < 0 {
|
||||
return sc.Error(buf.String(), "unterminated string")
|
||||
}
|
||||
if ch == '\\' {
|
||||
if err := sc.scanEscape(ch, buf); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
writeChar(buf, ch)
|
||||
}
|
||||
ch = sc.Next()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sc *Scanner) scanEscape(ch int, buf *bytes.Buffer) error {
|
||||
ch = sc.Next()
|
||||
switch ch {
|
||||
case 'a':
|
||||
buf.WriteByte('\a')
|
||||
case 'b':
|
||||
buf.WriteByte('\b')
|
||||
case 'f':
|
||||
buf.WriteByte('\f')
|
||||
case 'n':
|
||||
buf.WriteByte('\n')
|
||||
case 'r':
|
||||
buf.WriteByte('\r')
|
||||
case 't':
|
||||
buf.WriteByte('\t')
|
||||
case 'v':
|
||||
buf.WriteByte('\v')
|
||||
case '\\':
|
||||
buf.WriteByte('\\')
|
||||
case '"':
|
||||
buf.WriteByte('"')
|
||||
case '\'':
|
||||
buf.WriteByte('\'')
|
||||
case '\n':
|
||||
buf.WriteByte('\n')
|
||||
case '\r':
|
||||
buf.WriteByte('\n')
|
||||
sc.Newline('\r')
|
||||
default:
|
||||
if '0' <= ch && ch <= '9' {
|
||||
bytes := []byte{byte(ch)}
|
||||
for i := 0; i < 2 && isDecimal(sc.Peek()); i++ {
|
||||
bytes = append(bytes, byte(sc.Next()))
|
||||
}
|
||||
val, _ := strconv.ParseInt(string(bytes), 10, 32)
|
||||
writeChar(buf, int(val))
|
||||
} else {
|
||||
writeChar(buf, ch)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sc *Scanner) countSep(ch int) (int, int) {
|
||||
count := 0
|
||||
for ; ch == '='; count = count + 1 {
|
||||
ch = sc.Next()
|
||||
}
|
||||
return count, ch
|
||||
}
|
||||
|
||||
func (sc *Scanner) scanMultilineString(ch int, buf *bytes.Buffer) error {
|
||||
var count1, count2 int
|
||||
count1, ch = sc.countSep(ch)
|
||||
if ch != '[' {
|
||||
return sc.Error(string(rune(ch)), "invalid multiline string")
|
||||
}
|
||||
ch = sc.Next()
|
||||
if ch == '\n' || ch == '\r' {
|
||||
ch = sc.Next()
|
||||
}
|
||||
for {
|
||||
if ch < 0 {
|
||||
return sc.Error(buf.String(), "unterminated multiline string")
|
||||
} else if ch == ']' {
|
||||
count2, ch = sc.countSep(sc.Next())
|
||||
if count1 == count2 && ch == ']' {
|
||||
goto finally
|
||||
}
|
||||
buf.WriteByte(']')
|
||||
buf.WriteString(strings.Repeat("=", count2))
|
||||
continue
|
||||
}
|
||||
writeChar(buf, ch)
|
||||
ch = sc.Next()
|
||||
}
|
||||
|
||||
finally:
|
||||
return nil
|
||||
}
|
||||
|
||||
var reservedWords = map[string]int{
|
||||
"and": TAnd, "break": TBreak, "do": TDo, "else": TElse, "elseif": TElseIf,
|
||||
"end": TEnd, "false": TFalse, "for": TFor, "function": TFunction,
|
||||
"if": TIf, "in": TIn, "local": TLocal, "nil": TNil, "not": TNot, "or": TOr,
|
||||
"return": TReturn, "repeat": TRepeat, "then": TThen, "true": TTrue,
|
||||
"until": TUntil, "while": TWhile}
|
||||
|
||||
func (sc *Scanner) Scan(lexer *Lexer) (ast.Token, error) {
|
||||
redo:
|
||||
var err error
|
||||
tok := ast.Token{}
|
||||
newline := false
|
||||
|
||||
ch := sc.skipWhiteSpace(whitespace1)
|
||||
if ch == '\n' || ch == '\r' {
|
||||
newline = true
|
||||
ch = sc.skipWhiteSpace(whitespace2)
|
||||
}
|
||||
|
||||
if ch == '(' && lexer.PrevTokenType == ')' {
|
||||
lexer.PNewLine = newline
|
||||
} else {
|
||||
lexer.PNewLine = false
|
||||
}
|
||||
|
||||
var _buf bytes.Buffer
|
||||
buf := &_buf
|
||||
tok.Pos = sc.Pos
|
||||
|
||||
switch {
|
||||
case isIdent(ch, 0):
|
||||
tok.Type = TIdent
|
||||
err = sc.scanIdent(ch, buf)
|
||||
tok.Str = buf.String()
|
||||
if err != nil {
|
||||
goto finally
|
||||
}
|
||||
if typ, ok := reservedWords[tok.Str]; ok {
|
||||
tok.Type = typ
|
||||
}
|
||||
case isDecimal(ch):
|
||||
tok.Type = TNumber
|
||||
err = sc.scanNumber(ch, buf)
|
||||
tok.Str = buf.String()
|
||||
default:
|
||||
switch ch {
|
||||
case EOF:
|
||||
tok.Type = EOF
|
||||
case '-':
|
||||
if sc.Peek() == '-' {
|
||||
err = sc.skipComments(sc.Next())
|
||||
if err != nil {
|
||||
goto finally
|
||||
}
|
||||
goto redo
|
||||
} else {
|
||||
tok.Type = ch
|
||||
tok.Str = string(rune(ch))
|
||||
}
|
||||
case '"', '\'':
|
||||
tok.Type = TString
|
||||
err = sc.scanString(ch, buf)
|
||||
tok.Str = buf.String()
|
||||
case '[':
|
||||
if c := sc.Peek(); c == '[' || c == '=' {
|
||||
tok.Type = TString
|
||||
err = sc.scanMultilineString(sc.Next(), buf)
|
||||
tok.Str = buf.String()
|
||||
} else {
|
||||
tok.Type = ch
|
||||
tok.Str = string(rune(ch))
|
||||
}
|
||||
case '=':
|
||||
if sc.Peek() == '=' {
|
||||
tok.Type = TEqeq
|
||||
tok.Str = "=="
|
||||
sc.Next()
|
||||
} else {
|
||||
tok.Type = ch
|
||||
tok.Str = string(rune(ch))
|
||||
}
|
||||
case '~':
|
||||
if sc.Peek() == '=' {
|
||||
tok.Type = TNeq
|
||||
tok.Str = "~="
|
||||
sc.Next()
|
||||
} else {
|
||||
err = sc.Error("~", "Invalid '~' token")
|
||||
}
|
||||
case '<':
|
||||
if sc.Peek() == '=' {
|
||||
tok.Type = TLte
|
||||
tok.Str = "<="
|
||||
sc.Next()
|
||||
} else {
|
||||
tok.Type = ch
|
||||
tok.Str = string(rune(ch))
|
||||
}
|
||||
case '>':
|
||||
if sc.Peek() == '=' {
|
||||
tok.Type = TGte
|
||||
tok.Str = ">="
|
||||
sc.Next()
|
||||
} else {
|
||||
tok.Type = ch
|
||||
tok.Str = string(rune(ch))
|
||||
}
|
||||
case '.':
|
||||
ch2 := sc.Peek()
|
||||
switch {
|
||||
case isDecimal(ch2):
|
||||
tok.Type = TNumber
|
||||
err = sc.scanNumber(ch, buf)
|
||||
tok.Str = buf.String()
|
||||
case ch2 == '.':
|
||||
writeChar(buf, ch)
|
||||
writeChar(buf, sc.Next())
|
||||
if sc.Peek() == '.' {
|
||||
writeChar(buf, sc.Next())
|
||||
tok.Type = T3Comma
|
||||
} else {
|
||||
tok.Type = T2Comma
|
||||
}
|
||||
default:
|
||||
tok.Type = '.'
|
||||
}
|
||||
tok.Str = buf.String()
|
||||
case '+', '*', '/', '%', '^', '#', '(', ')', '{', '}', ']', ';', ':', ',':
|
||||
tok.Type = ch
|
||||
tok.Str = string(rune(ch))
|
||||
default:
|
||||
writeChar(buf, ch)
|
||||
err = sc.Error(buf.String(), "Invalid token")
|
||||
goto finally
|
||||
}
|
||||
}
|
||||
|
||||
finally:
|
||||
tok.Name = TokenName(int(tok.Type))
|
||||
return tok, err
|
||||
}
|
||||
|
||||
// yacc interface {{{
|
||||
|
||||
type Lexer struct {
|
||||
scanner *Scanner
|
||||
Stmts []ast.Stmt
|
||||
PNewLine bool
|
||||
Token ast.Token
|
||||
PrevTokenType int
|
||||
}
|
||||
|
||||
func (lx *Lexer) Lex(lval *yySymType) int {
|
||||
lx.PrevTokenType = lx.Token.Type
|
||||
tok, err := lx.scanner.Scan(lx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if tok.Type < 0 {
|
||||
return 0
|
||||
}
|
||||
lval.token = tok
|
||||
lx.Token = tok
|
||||
return int(tok.Type)
|
||||
}
|
||||
|
||||
func (lx *Lexer) Error(message string) {
|
||||
panic(lx.scanner.Error(lx.Token.Str, message))
|
||||
}
|
||||
|
||||
func (lx *Lexer) TokenError(tok ast.Token, message string) {
|
||||
panic(lx.scanner.TokenError(tok, message))
|
||||
}
|
||||
|
||||
func Parse(reader io.Reader, name string) (chunk []ast.Stmt, err error) {
|
||||
lexer := &Lexer{NewScanner(reader, name), nil, false, ast.Token{Str: ""}, TNil}
|
||||
chunk = nil
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err, _ = e.(error)
|
||||
}
|
||||
}()
|
||||
yyParse(lexer)
|
||||
chunk = lexer.Stmts
|
||||
return
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
// Dump {{{
|
||||
|
||||
func isInlineDumpNode(rv reflect.Value) bool {
|
||||
switch rv.Kind() {
|
||||
case reflect.Struct, reflect.Slice, reflect.Interface, reflect.Ptr:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func dump(node interface{}, level int, s string) string {
|
||||
rt := reflect.TypeOf(node)
|
||||
if fmt.Sprint(rt) == "<nil>" {
|
||||
return strings.Repeat(s, level) + "<nil>"
|
||||
}
|
||||
|
||||
rv := reflect.ValueOf(node)
|
||||
buf := []string{}
|
||||
switch rt.Kind() {
|
||||
case reflect.Slice:
|
||||
if rv.Len() == 0 {
|
||||
return strings.Repeat(s, level) + "<empty>"
|
||||
}
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
buf = append(buf, dump(rv.Index(i).Interface(), level, s))
|
||||
}
|
||||
case reflect.Ptr:
|
||||
vt := rv.Elem()
|
||||
tt := rt.Elem()
|
||||
indicies := []int{}
|
||||
for i := 0; i < tt.NumField(); i++ {
|
||||
if strings.Index(tt.Field(i).Name, "Base") > -1 {
|
||||
continue
|
||||
}
|
||||
indicies = append(indicies, i)
|
||||
}
|
||||
switch {
|
||||
case len(indicies) == 0:
|
||||
return strings.Repeat(s, level) + "<empty>"
|
||||
case len(indicies) == 1 && isInlineDumpNode(vt.Field(indicies[0])):
|
||||
for _, i := range indicies {
|
||||
buf = append(buf, strings.Repeat(s, level)+"- Node$"+tt.Name()+": "+dump(vt.Field(i).Interface(), 0, s))
|
||||
}
|
||||
default:
|
||||
buf = append(buf, strings.Repeat(s, level)+"- Node$"+tt.Name())
|
||||
for _, i := range indicies {
|
||||
if isInlineDumpNode(vt.Field(i)) {
|
||||
inf := dump(vt.Field(i).Interface(), 0, s)
|
||||
buf = append(buf, strings.Repeat(s, level+1)+tt.Field(i).Name+": "+inf)
|
||||
} else {
|
||||
buf = append(buf, strings.Repeat(s, level+1)+tt.Field(i).Name+": ")
|
||||
buf = append(buf, dump(vt.Field(i).Interface(), level+2, s))
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
buf = append(buf, strings.Repeat(s, level)+fmt.Sprint(node))
|
||||
}
|
||||
return strings.Join(buf, "\n")
|
||||
}
|
||||
|
||||
func Dump(chunk []ast.Stmt) string {
|
||||
return dump(chunk, 0, " ")
|
||||
}
|
||||
|
||||
// }}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,527 @@
|
|||
%{
|
||||
package parse
|
||||
|
||||
import (
|
||||
"github.com/yuin/gopher-lua/ast"
|
||||
)
|
||||
%}
|
||||
%type<stmts> chunk
|
||||
%type<stmts> chunk1
|
||||
%type<stmts> block
|
||||
%type<stmt> stat
|
||||
%type<stmts> elseifs
|
||||
%type<stmt> laststat
|
||||
%type<funcname> funcname
|
||||
%type<funcname> funcname1
|
||||
%type<exprlist> varlist
|
||||
%type<expr> var
|
||||
%type<namelist> namelist
|
||||
%type<exprlist> exprlist
|
||||
%type<expr> expr
|
||||
%type<expr> string
|
||||
%type<expr> prefixexp
|
||||
%type<expr> functioncall
|
||||
%type<expr> afunctioncall
|
||||
%type<exprlist> args
|
||||
%type<expr> function
|
||||
%type<funcexpr> funcbody
|
||||
%type<parlist> parlist
|
||||
%type<expr> tableconstructor
|
||||
%type<fieldlist> fieldlist
|
||||
%type<field> field
|
||||
%type<fieldsep> fieldsep
|
||||
|
||||
%union {
|
||||
token ast.Token
|
||||
|
||||
stmts []ast.Stmt
|
||||
stmt ast.Stmt
|
||||
|
||||
funcname *ast.FuncName
|
||||
funcexpr *ast.FunctionExpr
|
||||
|
||||
exprlist []ast.Expr
|
||||
expr ast.Expr
|
||||
|
||||
fieldlist []*ast.Field
|
||||
field *ast.Field
|
||||
fieldsep string
|
||||
|
||||
namelist []string
|
||||
parlist *ast.ParList
|
||||
}
|
||||
|
||||
/* Reserved words */
|
||||
%token<token> TAnd TBreak TDo TElse TElseIf TEnd TFalse TFor TFunction TIf TIn TLocal TNil TNot TOr TReturn TRepeat TThen TTrue TUntil TWhile
|
||||
|
||||
/* Literals */
|
||||
%token<token> TEqeq TNeq TLte TGte T2Comma T3Comma TIdent TNumber TString '{' '('
|
||||
|
||||
/* Operators */
|
||||
%left TOr
|
||||
%left TAnd
|
||||
%left '>' '<' TGte TLte TEqeq TNeq
|
||||
%right T2Comma
|
||||
%left '+' '-'
|
||||
%left '*' '/' '%'
|
||||
%right UNARY /* not # -(unary) */
|
||||
%right '^'
|
||||
|
||||
%%
|
||||
|
||||
chunk:
|
||||
chunk1 {
|
||||
$$ = $1
|
||||
if l, ok := yylex.(*Lexer); ok {
|
||||
l.Stmts = $$
|
||||
}
|
||||
} |
|
||||
chunk1 laststat {
|
||||
$$ = append($1, $2)
|
||||
if l, ok := yylex.(*Lexer); ok {
|
||||
l.Stmts = $$
|
||||
}
|
||||
} |
|
||||
chunk1 laststat ';' {
|
||||
$$ = append($1, $2)
|
||||
if l, ok := yylex.(*Lexer); ok {
|
||||
l.Stmts = $$
|
||||
}
|
||||
}
|
||||
|
||||
chunk1:
|
||||
{
|
||||
$$ = []ast.Stmt{}
|
||||
} |
|
||||
chunk1 stat {
|
||||
$$ = append($1, $2)
|
||||
} |
|
||||
chunk1 ';' {
|
||||
$$ = $1
|
||||
}
|
||||
|
||||
block:
|
||||
chunk {
|
||||
$$ = $1
|
||||
}
|
||||
|
||||
stat:
|
||||
varlist '=' exprlist {
|
||||
$$ = &ast.AssignStmt{Lhs: $1, Rhs: $3}
|
||||
$$.SetLine($1[0].Line())
|
||||
} |
|
||||
/* 'stat = functioncal' causes a reduce/reduce conflict */
|
||||
prefixexp {
|
||||
if _, ok := $1.(*ast.FuncCallExpr); !ok {
|
||||
yylex.(*Lexer).Error("parse error")
|
||||
} else {
|
||||
$$ = &ast.FuncCallStmt{Expr: $1}
|
||||
$$.SetLine($1.Line())
|
||||
}
|
||||
} |
|
||||
TDo block TEnd {
|
||||
$$ = &ast.DoBlockStmt{Stmts: $2}
|
||||
$$.SetLine($1.Pos.Line)
|
||||
$$.SetLastLine($3.Pos.Line)
|
||||
} |
|
||||
TWhile expr TDo block TEnd {
|
||||
$$ = &ast.WhileStmt{Condition: $2, Stmts: $4}
|
||||
$$.SetLine($1.Pos.Line)
|
||||
$$.SetLastLine($5.Pos.Line)
|
||||
} |
|
||||
TRepeat block TUntil expr {
|
||||
$$ = &ast.RepeatStmt{Condition: $4, Stmts: $2}
|
||||
$$.SetLine($1.Pos.Line)
|
||||
$$.SetLastLine($4.Line())
|
||||
} |
|
||||
TIf expr TThen block elseifs TEnd {
|
||||
$$ = &ast.IfStmt{Condition: $2, Then: $4}
|
||||
cur := $$
|
||||
for _, elseif := range $5 {
|
||||
cur.(*ast.IfStmt).Else = []ast.Stmt{elseif}
|
||||
cur = elseif
|
||||
}
|
||||
$$.SetLine($1.Pos.Line)
|
||||
$$.SetLastLine($6.Pos.Line)
|
||||
} |
|
||||
TIf expr TThen block elseifs TElse block TEnd {
|
||||
$$ = &ast.IfStmt{Condition: $2, Then: $4}
|
||||
cur := $$
|
||||
for _, elseif := range $5 {
|
||||
cur.(*ast.IfStmt).Else = []ast.Stmt{elseif}
|
||||
cur = elseif
|
||||
}
|
||||
cur.(*ast.IfStmt).Else = $7
|
||||
$$.SetLine($1.Pos.Line)
|
||||
$$.SetLastLine($8.Pos.Line)
|
||||
} |
|
||||
TFor TIdent '=' expr ',' expr TDo block TEnd {
|
||||
$$ = &ast.NumberForStmt{Name: $2.Str, Init: $4, Limit: $6, Stmts: $8}
|
||||
$$.SetLine($1.Pos.Line)
|
||||
$$.SetLastLine($9.Pos.Line)
|
||||
} |
|
||||
TFor TIdent '=' expr ',' expr ',' expr TDo block TEnd {
|
||||
$$ = &ast.NumberForStmt{Name: $2.Str, Init: $4, Limit: $6, Step:$8, Stmts: $10}
|
||||
$$.SetLine($1.Pos.Line)
|
||||
$$.SetLastLine($11.Pos.Line)
|
||||
} |
|
||||
TFor namelist TIn exprlist TDo block TEnd {
|
||||
$$ = &ast.GenericForStmt{Names:$2, Exprs:$4, Stmts: $6}
|
||||
$$.SetLine($1.Pos.Line)
|
||||
$$.SetLastLine($7.Pos.Line)
|
||||
} |
|
||||
TFunction funcname funcbody {
|
||||
$$ = &ast.FuncDefStmt{Name: $2, Func: $3}
|
||||
$$.SetLine($1.Pos.Line)
|
||||
$$.SetLastLine($3.LastLine())
|
||||
} |
|
||||
TLocal TFunction TIdent funcbody {
|
||||
$$ = &ast.LocalAssignStmt{Names:[]string{$3.Str}, Exprs: []ast.Expr{$4}}
|
||||
$$.SetLine($1.Pos.Line)
|
||||
$$.SetLastLine($4.LastLine())
|
||||
} |
|
||||
TLocal namelist '=' exprlist {
|
||||
$$ = &ast.LocalAssignStmt{Names: $2, Exprs:$4}
|
||||
$$.SetLine($1.Pos.Line)
|
||||
} |
|
||||
TLocal namelist {
|
||||
$$ = &ast.LocalAssignStmt{Names: $2, Exprs:[]ast.Expr{}}
|
||||
$$.SetLine($1.Pos.Line)
|
||||
}
|
||||
|
||||
elseifs:
|
||||
{
|
||||
$$ = []ast.Stmt{}
|
||||
} |
|
||||
elseifs TElseIf expr TThen block {
|
||||
$$ = append($1, &ast.IfStmt{Condition: $3, Then: $5})
|
||||
$$[len($$)-1].SetLine($2.Pos.Line)
|
||||
}
|
||||
|
||||
laststat:
|
||||
TReturn {
|
||||
$$ = &ast.ReturnStmt{Exprs:nil}
|
||||
$$.SetLine($1.Pos.Line)
|
||||
} |
|
||||
TReturn exprlist {
|
||||
$$ = &ast.ReturnStmt{Exprs:$2}
|
||||
$$.SetLine($1.Pos.Line)
|
||||
} |
|
||||
TBreak {
|
||||
$$ = &ast.BreakStmt{}
|
||||
$$.SetLine($1.Pos.Line)
|
||||
}
|
||||
|
||||
funcname:
|
||||
funcname1 {
|
||||
$$ = $1
|
||||
} |
|
||||
funcname1 ':' TIdent {
|
||||
$$ = &ast.FuncName{Func:nil, Receiver:$1.Func, Method: $3.Str}
|
||||
}
|
||||
|
||||
funcname1:
|
||||
TIdent {
|
||||
$$ = &ast.FuncName{Func: &ast.IdentExpr{Value:$1.Str}}
|
||||
$$.Func.SetLine($1.Pos.Line)
|
||||
} |
|
||||
funcname1 '.' TIdent {
|
||||
key:= &ast.StringExpr{Value:$3.Str}
|
||||
key.SetLine($3.Pos.Line)
|
||||
fn := &ast.AttrGetExpr{Object: $1.Func, Key: key}
|
||||
fn.SetLine($3.Pos.Line)
|
||||
$$ = &ast.FuncName{Func: fn}
|
||||
}
|
||||
|
||||
varlist:
|
||||
var {
|
||||
$$ = []ast.Expr{$1}
|
||||
} |
|
||||
varlist ',' var {
|
||||
$$ = append($1, $3)
|
||||
}
|
||||
|
||||
var:
|
||||
TIdent {
|
||||
$$ = &ast.IdentExpr{Value:$1.Str}
|
||||
$$.SetLine($1.Pos.Line)
|
||||
} |
|
||||
prefixexp '[' expr ']' {
|
||||
$$ = &ast.AttrGetExpr{Object: $1, Key: $3}
|
||||
$$.SetLine($1.Line())
|
||||
} |
|
||||
prefixexp '.' TIdent {
|
||||
key := &ast.StringExpr{Value:$3.Str}
|
||||
key.SetLine($3.Pos.Line)
|
||||
$$ = &ast.AttrGetExpr{Object: $1, Key: key}
|
||||
$$.SetLine($1.Line())
|
||||
}
|
||||
|
||||
namelist:
|
||||
TIdent {
|
||||
$$ = []string{$1.Str}
|
||||
} |
|
||||
namelist ',' TIdent {
|
||||
$$ = append($1, $3.Str)
|
||||
}
|
||||
|
||||
exprlist:
|
||||
expr {
|
||||
$$ = []ast.Expr{$1}
|
||||
} |
|
||||
exprlist ',' expr {
|
||||
$$ = append($1, $3)
|
||||
}
|
||||
|
||||
expr:
|
||||
TNil {
|
||||
$$ = &ast.NilExpr{}
|
||||
$$.SetLine($1.Pos.Line)
|
||||
} |
|
||||
TFalse {
|
||||
$$ = &ast.FalseExpr{}
|
||||
$$.SetLine($1.Pos.Line)
|
||||
} |
|
||||
TTrue {
|
||||
$$ = &ast.TrueExpr{}
|
||||
$$.SetLine($1.Pos.Line)
|
||||
} |
|
||||
TNumber {
|
||||
$$ = &ast.NumberExpr{Value: $1.Str}
|
||||
$$.SetLine($1.Pos.Line)
|
||||
} |
|
||||
T3Comma {
|
||||
$$ = &ast.Comma3Expr{}
|
||||
$$.SetLine($1.Pos.Line)
|
||||
} |
|
||||
function {
|
||||
$$ = $1
|
||||
} |
|
||||
prefixexp {
|
||||
$$ = $1
|
||||
} |
|
||||
string {
|
||||
$$ = $1
|
||||
} |
|
||||
tableconstructor {
|
||||
$$ = $1
|
||||
} |
|
||||
expr TOr expr {
|
||||
$$ = &ast.LogicalOpExpr{Lhs: $1, Operator: "or", Rhs: $3}
|
||||
$$.SetLine($1.Line())
|
||||
} |
|
||||
expr TAnd expr {
|
||||
$$ = &ast.LogicalOpExpr{Lhs: $1, Operator: "and", Rhs: $3}
|
||||
$$.SetLine($1.Line())
|
||||
} |
|
||||
expr '>' expr {
|
||||
$$ = &ast.RelationalOpExpr{Lhs: $1, Operator: ">", Rhs: $3}
|
||||
$$.SetLine($1.Line())
|
||||
} |
|
||||
expr '<' expr {
|
||||
$$ = &ast.RelationalOpExpr{Lhs: $1, Operator: "<", Rhs: $3}
|
||||
$$.SetLine($1.Line())
|
||||
} |
|
||||
expr TGte expr {
|
||||
$$ = &ast.RelationalOpExpr{Lhs: $1, Operator: ">=", Rhs: $3}
|
||||
$$.SetLine($1.Line())
|
||||
} |
|
||||
expr TLte expr {
|
||||
$$ = &ast.RelationalOpExpr{Lhs: $1, Operator: "<=", Rhs: $3}
|
||||
$$.SetLine($1.Line())
|
||||
} |
|
||||
expr TEqeq expr {
|
||||
$$ = &ast.RelationalOpExpr{Lhs: $1, Operator: "==", Rhs: $3}
|
||||
$$.SetLine($1.Line())
|
||||
} |
|
||||
expr TNeq expr {
|
||||
$$ = &ast.RelationalOpExpr{Lhs: $1, Operator: "~=", Rhs: $3}
|
||||
$$.SetLine($1.Line())
|
||||
} |
|
||||
expr T2Comma expr {
|
||||
$$ = &ast.StringConcatOpExpr{Lhs: $1, Rhs: $3}
|
||||
$$.SetLine($1.Line())
|
||||
} |
|
||||
expr '+' expr {
|
||||
$$ = &ast.ArithmeticOpExpr{Lhs: $1, Operator: "+", Rhs: $3}
|
||||
$$.SetLine($1.Line())
|
||||
} |
|
||||
expr '-' expr {
|
||||
$$ = &ast.ArithmeticOpExpr{Lhs: $1, Operator: "-", Rhs: $3}
|
||||
$$.SetLine($1.Line())
|
||||
} |
|
||||
expr '*' expr {
|
||||
$$ = &ast.ArithmeticOpExpr{Lhs: $1, Operator: "*", Rhs: $3}
|
||||
$$.SetLine($1.Line())
|
||||
} |
|
||||
expr '/' expr {
|
||||
$$ = &ast.ArithmeticOpExpr{Lhs: $1, Operator: "/", Rhs: $3}
|
||||
$$.SetLine($1.Line())
|
||||
} |
|
||||
expr '%' expr {
|
||||
$$ = &ast.ArithmeticOpExpr{Lhs: $1, Operator: "%", Rhs: $3}
|
||||
$$.SetLine($1.Line())
|
||||
} |
|
||||
expr '^' expr {
|
||||
$$ = &ast.ArithmeticOpExpr{Lhs: $1, Operator: "^", Rhs: $3}
|
||||
$$.SetLine($1.Line())
|
||||
} |
|
||||
'-' expr %prec UNARY {
|
||||
$$ = &ast.UnaryMinusOpExpr{Expr: $2}
|
||||
$$.SetLine($2.Line())
|
||||
} |
|
||||
TNot expr %prec UNARY {
|
||||
$$ = &ast.UnaryNotOpExpr{Expr: $2}
|
||||
$$.SetLine($2.Line())
|
||||
} |
|
||||
'#' expr %prec UNARY {
|
||||
$$ = &ast.UnaryLenOpExpr{Expr: $2}
|
||||
$$.SetLine($2.Line())
|
||||
}
|
||||
|
||||
string:
|
||||
TString {
|
||||
$$ = &ast.StringExpr{Value: $1.Str}
|
||||
$$.SetLine($1.Pos.Line)
|
||||
}
|
||||
|
||||
prefixexp:
|
||||
var {
|
||||
$$ = $1
|
||||
} |
|
||||
afunctioncall {
|
||||
$$ = $1
|
||||
} |
|
||||
functioncall {
|
||||
$$ = $1
|
||||
} |
|
||||
'(' expr ')' {
|
||||
if ex, ok := $2.(*ast.Comma3Expr); ok {
|
||||
ex.AdjustRet = true
|
||||
}
|
||||
$$ = $2
|
||||
$$.SetLine($1.Pos.Line)
|
||||
}
|
||||
|
||||
afunctioncall:
|
||||
'(' functioncall ')' {
|
||||
$2.(*ast.FuncCallExpr).AdjustRet = true
|
||||
$$ = $2
|
||||
}
|
||||
|
||||
functioncall:
|
||||
prefixexp args {
|
||||
$$ = &ast.FuncCallExpr{Func: $1, Args: $2}
|
||||
$$.SetLine($1.Line())
|
||||
} |
|
||||
prefixexp ':' TIdent args {
|
||||
$$ = &ast.FuncCallExpr{Method: $3.Str, Receiver: $1, Args: $4}
|
||||
$$.SetLine($1.Line())
|
||||
}
|
||||
|
||||
args:
|
||||
'(' ')' {
|
||||
if yylex.(*Lexer).PNewLine {
|
||||
yylex.(*Lexer).TokenError($1, "ambiguous syntax (function call x new statement)")
|
||||
}
|
||||
$$ = []ast.Expr{}
|
||||
} |
|
||||
'(' exprlist ')' {
|
||||
if yylex.(*Lexer).PNewLine {
|
||||
yylex.(*Lexer).TokenError($1, "ambiguous syntax (function call x new statement)")
|
||||
}
|
||||
$$ = $2
|
||||
} |
|
||||
tableconstructor {
|
||||
$$ = []ast.Expr{$1}
|
||||
} |
|
||||
string {
|
||||
$$ = []ast.Expr{$1}
|
||||
}
|
||||
|
||||
function:
|
||||
TFunction funcbody {
|
||||
$$ = &ast.FunctionExpr{ParList:$2.ParList, Stmts: $2.Stmts}
|
||||
$$.SetLine($1.Pos.Line)
|
||||
$$.SetLastLine($2.LastLine())
|
||||
}
|
||||
|
||||
funcbody:
|
||||
'(' parlist ')' block TEnd {
|
||||
$$ = &ast.FunctionExpr{ParList: $2, Stmts: $4}
|
||||
$$.SetLine($1.Pos.Line)
|
||||
$$.SetLastLine($5.Pos.Line)
|
||||
} |
|
||||
'(' ')' block TEnd {
|
||||
$$ = &ast.FunctionExpr{ParList: &ast.ParList{HasVargs: false, Names: []string{}}, Stmts: $3}
|
||||
$$.SetLine($1.Pos.Line)
|
||||
$$.SetLastLine($4.Pos.Line)
|
||||
}
|
||||
|
||||
parlist:
|
||||
T3Comma {
|
||||
$$ = &ast.ParList{HasVargs: true, Names: []string{}}
|
||||
} |
|
||||
namelist {
|
||||
$$ = &ast.ParList{HasVargs: false, Names: []string{}}
|
||||
$$.Names = append($$.Names, $1...)
|
||||
} |
|
||||
namelist ',' T3Comma {
|
||||
$$ = &ast.ParList{HasVargs: true, Names: []string{}}
|
||||
$$.Names = append($$.Names, $1...)
|
||||
}
|
||||
|
||||
|
||||
tableconstructor:
|
||||
'{' '}' {
|
||||
$$ = &ast.TableExpr{Fields: []*ast.Field{}}
|
||||
$$.SetLine($1.Pos.Line)
|
||||
} |
|
||||
'{' fieldlist '}' {
|
||||
$$ = &ast.TableExpr{Fields: $2}
|
||||
$$.SetLine($1.Pos.Line)
|
||||
}
|
||||
|
||||
|
||||
fieldlist:
|
||||
field {
|
||||
$$ = []*ast.Field{$1}
|
||||
} |
|
||||
fieldlist fieldsep field {
|
||||
$$ = append($1, $3)
|
||||
} |
|
||||
fieldlist fieldsep {
|
||||
$$ = $1
|
||||
}
|
||||
|
||||
field:
|
||||
TIdent '=' expr {
|
||||
$$ = &ast.Field{Key: &ast.StringExpr{Value:$1.Str}, Value: $3}
|
||||
$$.Key.SetLine($1.Pos.Line)
|
||||
} |
|
||||
'[' expr ']' '=' expr {
|
||||
$$ = &ast.Field{Key: $2, Value: $5}
|
||||
} |
|
||||
expr {
|
||||
$$ = &ast.Field{Value: $1}
|
||||
}
|
||||
|
||||
fieldsep:
|
||||
',' {
|
||||
$$ = ","
|
||||
} |
|
||||
';' {
|
||||
$$ = ";"
|
||||
}
|
||||
|
||||
%%
|
||||
|
||||
func TokenName(c int) string {
|
||||
if c >= TAnd && c-TAnd < len(yyToknames) {
|
||||
if yyToknames[c-TAnd] != "" {
|
||||
return yyToknames[c-TAnd]
|
||||
}
|
||||
}
|
||||
return string([]byte{byte(c)})
|
||||
}
|
||||
|
|
@ -0,0 +1,638 @@
|
|||
// Lua pattern match functions for Go
|
||||
package pm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const EOS = -1
|
||||
const _UNKNOWN = -2
|
||||
|
||||
/* Error {{{ */
|
||||
|
||||
type Error struct {
|
||||
Pos int
|
||||
Message string
|
||||
}
|
||||
|
||||
func newError(pos int, message string, args ...interface{}) *Error {
|
||||
if len(args) == 0 {
|
||||
return &Error{pos, message}
|
||||
}
|
||||
return &Error{pos, fmt.Sprintf(message, args...)}
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
switch e.Pos {
|
||||
case EOS:
|
||||
return fmt.Sprintf("%s at EOS", e.Message)
|
||||
case _UNKNOWN:
|
||||
return fmt.Sprintf("%s", e.Message)
|
||||
default:
|
||||
return fmt.Sprintf("%s at %d", e.Message, e.Pos)
|
||||
}
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
/* MatchData {{{ */
|
||||
|
||||
type MatchData struct {
|
||||
// captured positions
|
||||
// layout
|
||||
// xxxx xxxx xxxx xxx0 : caputured positions
|
||||
// xxxx xxxx xxxx xxx1 : position captured positions
|
||||
captures []uint32
|
||||
}
|
||||
|
||||
func newMatchState() *MatchData { return &MatchData{[]uint32{}} }
|
||||
|
||||
func (st *MatchData) addPosCapture(s, pos int) {
|
||||
for s+1 >= len(st.captures) {
|
||||
st.captures = append(st.captures, 0)
|
||||
}
|
||||
st.captures[s] = (uint32(pos) << 1) | 1
|
||||
st.captures[s+1] = (uint32(pos) << 1) | 1
|
||||
}
|
||||
|
||||
func (st *MatchData) setCapture(s, pos int) uint32 {
|
||||
for s >= len(st.captures) {
|
||||
st.captures = append(st.captures, 0)
|
||||
}
|
||||
v := st.captures[s]
|
||||
st.captures[s] = (uint32(pos) << 1)
|
||||
return v
|
||||
}
|
||||
|
||||
func (st *MatchData) restoreCapture(s int, pos uint32) { st.captures[s] = pos }
|
||||
|
||||
func (st *MatchData) CaptureLength() int { return len(st.captures) }
|
||||
|
||||
func (st *MatchData) IsPosCapture(idx int) bool { return (st.captures[idx] & 1) == 1 }
|
||||
|
||||
func (st *MatchData) Capture(idx int) int { return int(st.captures[idx] >> 1) }
|
||||
|
||||
/* }}} */
|
||||
|
||||
/* scanner {{{ */
|
||||
|
||||
type scannerState struct {
|
||||
Pos int
|
||||
started bool
|
||||
}
|
||||
|
||||
type scanner struct {
|
||||
src []byte
|
||||
State scannerState
|
||||
saved scannerState
|
||||
}
|
||||
|
||||
func newScanner(src []byte) *scanner {
|
||||
return &scanner{
|
||||
src: src,
|
||||
State: scannerState{
|
||||
Pos: 0,
|
||||
started: false,
|
||||
},
|
||||
saved: scannerState{},
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *scanner) Length() int { return len(sc.src) }
|
||||
|
||||
func (sc *scanner) Next() int {
|
||||
if !sc.State.started {
|
||||
sc.State.started = true
|
||||
if len(sc.src) == 0 {
|
||||
sc.State.Pos = EOS
|
||||
}
|
||||
} else {
|
||||
sc.State.Pos = sc.NextPos()
|
||||
}
|
||||
if sc.State.Pos == EOS {
|
||||
return EOS
|
||||
}
|
||||
return int(sc.src[sc.State.Pos])
|
||||
}
|
||||
|
||||
func (sc *scanner) CurrentPos() int {
|
||||
return sc.State.Pos
|
||||
}
|
||||
|
||||
func (sc *scanner) NextPos() int {
|
||||
if sc.State.Pos == EOS || sc.State.Pos >= len(sc.src)-1 {
|
||||
return EOS
|
||||
}
|
||||
if !sc.State.started {
|
||||
return 0
|
||||
} else {
|
||||
return sc.State.Pos + 1
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *scanner) Peek() int {
|
||||
cureof := sc.State.Pos == EOS
|
||||
ch := sc.Next()
|
||||
if !cureof {
|
||||
if sc.State.Pos == EOS {
|
||||
sc.State.Pos = len(sc.src) - 1
|
||||
} else {
|
||||
sc.State.Pos--
|
||||
if sc.State.Pos < 0 {
|
||||
sc.State.Pos = 0
|
||||
sc.State.started = false
|
||||
}
|
||||
}
|
||||
}
|
||||
return ch
|
||||
}
|
||||
|
||||
func (sc *scanner) Save() { sc.saved = sc.State }
|
||||
|
||||
func (sc *scanner) Restore() { sc.State = sc.saved }
|
||||
|
||||
/* }}} */
|
||||
|
||||
/* bytecode {{{ */
|
||||
|
||||
type opCode int
|
||||
|
||||
const (
|
||||
opChar opCode = iota
|
||||
opMatch
|
||||
opTailMatch
|
||||
opJmp
|
||||
opSplit
|
||||
opSave
|
||||
opPSave
|
||||
opBrace
|
||||
opNumber
|
||||
)
|
||||
|
||||
type inst struct {
|
||||
OpCode opCode
|
||||
Class class
|
||||
Operand1 int
|
||||
Operand2 int
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
/* classes {{{ */
|
||||
|
||||
type class interface {
|
||||
Matches(ch int) bool
|
||||
}
|
||||
|
||||
type dotClass struct{}
|
||||
|
||||
func (pn *dotClass) Matches(ch int) bool { return true }
|
||||
|
||||
type charClass struct {
|
||||
Ch int
|
||||
}
|
||||
|
||||
func (pn *charClass) Matches(ch int) bool { return pn.Ch == ch }
|
||||
|
||||
type singleClass struct {
|
||||
Class int
|
||||
}
|
||||
|
||||
func (pn *singleClass) Matches(ch int) bool {
|
||||
ret := false
|
||||
switch pn.Class {
|
||||
case 'a', 'A':
|
||||
ret = 'A' <= ch && ch <= 'Z' || 'a' <= ch && ch <= 'z'
|
||||
case 'c', 'C':
|
||||
ret = (0x00 <= ch && ch <= 0x1F) || ch == 0x7F
|
||||
case 'd', 'D':
|
||||
ret = '0' <= ch && ch <= '9'
|
||||
case 'l', 'L':
|
||||
ret = 'a' <= ch && ch <= 'z'
|
||||
case 'p', 'P':
|
||||
ret = (0x21 <= ch && ch <= 0x2f) || (0x3a <= ch && ch <= 0x40) || (0x5b <= ch && ch <= 0x60) || (0x7b <= ch && ch <= 0x7e)
|
||||
case 's', 'S':
|
||||
switch ch {
|
||||
case ' ', '\f', '\n', '\r', '\t', '\v':
|
||||
ret = true
|
||||
}
|
||||
case 'u', 'U':
|
||||
ret = 'A' <= ch && ch <= 'Z'
|
||||
case 'w', 'W':
|
||||
ret = '0' <= ch && ch <= '9' || 'A' <= ch && ch <= 'Z' || 'a' <= ch && ch <= 'z'
|
||||
case 'x', 'X':
|
||||
ret = '0' <= ch && ch <= '9' || 'a' <= ch && ch <= 'f' || 'A' <= ch && ch <= 'F'
|
||||
case 'z', 'Z':
|
||||
ret = ch == 0
|
||||
default:
|
||||
return ch == pn.Class
|
||||
}
|
||||
if 'A' <= pn.Class && pn.Class <= 'Z' {
|
||||
return !ret
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
type setClass struct {
|
||||
IsNot bool
|
||||
Classes []class
|
||||
}
|
||||
|
||||
func (pn *setClass) Matches(ch int) bool {
|
||||
for _, class := range pn.Classes {
|
||||
if class.Matches(ch) {
|
||||
return !pn.IsNot
|
||||
}
|
||||
}
|
||||
return pn.IsNot
|
||||
}
|
||||
|
||||
type rangeClass struct {
|
||||
Begin class
|
||||
End class
|
||||
}
|
||||
|
||||
func (pn *rangeClass) Matches(ch int) bool {
|
||||
switch begin := pn.Begin.(type) {
|
||||
case *charClass:
|
||||
end, ok := pn.End.(*charClass)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return begin.Ch <= ch && ch <= end.Ch
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
// patterns {{{
|
||||
|
||||
type pattern interface{}
|
||||
|
||||
type singlePattern struct {
|
||||
Class class
|
||||
}
|
||||
|
||||
type seqPattern struct {
|
||||
MustHead bool
|
||||
MustTail bool
|
||||
Patterns []pattern
|
||||
}
|
||||
|
||||
type repeatPattern struct {
|
||||
Type int
|
||||
Class class
|
||||
}
|
||||
|
||||
type posCapPattern struct{}
|
||||
|
||||
type capPattern struct {
|
||||
Pattern pattern
|
||||
}
|
||||
|
||||
type numberPattern struct {
|
||||
N int
|
||||
}
|
||||
|
||||
type bracePattern struct {
|
||||
Begin int
|
||||
End int
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
/* parse {{{ */
|
||||
|
||||
func parseClass(sc *scanner, allowset bool) class {
|
||||
ch := sc.Next()
|
||||
switch ch {
|
||||
case '%':
|
||||
return &singleClass{sc.Next()}
|
||||
case '.':
|
||||
if allowset {
|
||||
return &dotClass{}
|
||||
}
|
||||
return &charClass{ch}
|
||||
case '[':
|
||||
if allowset {
|
||||
return parseClassSet(sc)
|
||||
}
|
||||
return &charClass{ch}
|
||||
//case '^' '$', '(', ')', ']', '*', '+', '-', '?':
|
||||
// panic(newError(sc.CurrentPos(), "invalid %c", ch))
|
||||
case EOS:
|
||||
panic(newError(sc.CurrentPos(), "unexpected EOS"))
|
||||
default:
|
||||
return &charClass{ch}
|
||||
}
|
||||
}
|
||||
|
||||
func parseClassSet(sc *scanner) class {
|
||||
set := &setClass{false, []class{}}
|
||||
if sc.Peek() == '^' {
|
||||
set.IsNot = true
|
||||
sc.Next()
|
||||
}
|
||||
isrange := false
|
||||
for {
|
||||
ch := sc.Peek()
|
||||
switch ch {
|
||||
// case '[':
|
||||
// panic(newError(sc.CurrentPos(), "'[' can not be nested"))
|
||||
case EOS:
|
||||
panic(newError(sc.CurrentPos(), "unexpected EOS"))
|
||||
case ']':
|
||||
if len(set.Classes) > 0 {
|
||||
sc.Next()
|
||||
goto exit
|
||||
}
|
||||
fallthrough
|
||||
case '-':
|
||||
if len(set.Classes) > 0 {
|
||||
sc.Next()
|
||||
isrange = true
|
||||
continue
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
set.Classes = append(set.Classes, parseClass(sc, false))
|
||||
}
|
||||
if isrange {
|
||||
begin := set.Classes[len(set.Classes)-2]
|
||||
end := set.Classes[len(set.Classes)-1]
|
||||
set.Classes = set.Classes[0 : len(set.Classes)-2]
|
||||
set.Classes = append(set.Classes, &rangeClass{begin, end})
|
||||
isrange = false
|
||||
}
|
||||
}
|
||||
exit:
|
||||
if isrange {
|
||||
set.Classes = append(set.Classes, &charClass{'-'})
|
||||
}
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func parsePattern(sc *scanner, toplevel bool) *seqPattern {
|
||||
pat := &seqPattern{}
|
||||
if toplevel {
|
||||
if sc.Peek() == '^' {
|
||||
sc.Next()
|
||||
pat.MustHead = true
|
||||
}
|
||||
}
|
||||
for {
|
||||
ch := sc.Peek()
|
||||
switch ch {
|
||||
case '%':
|
||||
sc.Save()
|
||||
sc.Next()
|
||||
switch sc.Peek() {
|
||||
case '0':
|
||||
panic(newError(sc.CurrentPos(), "invalid capture index"))
|
||||
case '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
pat.Patterns = append(pat.Patterns, &numberPattern{sc.Next() - 48})
|
||||
case 'b':
|
||||
sc.Next()
|
||||
pat.Patterns = append(pat.Patterns, &bracePattern{sc.Next(), sc.Next()})
|
||||
default:
|
||||
sc.Restore()
|
||||
pat.Patterns = append(pat.Patterns, &singlePattern{parseClass(sc, true)})
|
||||
}
|
||||
case '.', '[', ']':
|
||||
pat.Patterns = append(pat.Patterns, &singlePattern{parseClass(sc, true)})
|
||||
//case ']':
|
||||
// panic(newError(sc.CurrentPos(), "invalid ']'"))
|
||||
case ')':
|
||||
if toplevel {
|
||||
panic(newError(sc.CurrentPos(), "invalid ')'"))
|
||||
}
|
||||
return pat
|
||||
case '(':
|
||||
sc.Next()
|
||||
if sc.Peek() == ')' {
|
||||
sc.Next()
|
||||
pat.Patterns = append(pat.Patterns, &posCapPattern{})
|
||||
} else {
|
||||
ret := &capPattern{parsePattern(sc, false)}
|
||||
if sc.Peek() != ')' {
|
||||
panic(newError(sc.CurrentPos(), "unfinished capture"))
|
||||
}
|
||||
sc.Next()
|
||||
pat.Patterns = append(pat.Patterns, ret)
|
||||
}
|
||||
case '*', '+', '-', '?':
|
||||
sc.Next()
|
||||
if len(pat.Patterns) > 0 {
|
||||
spat, ok := pat.Patterns[len(pat.Patterns)-1].(*singlePattern)
|
||||
if ok {
|
||||
pat.Patterns = pat.Patterns[0 : len(pat.Patterns)-1]
|
||||
pat.Patterns = append(pat.Patterns, &repeatPattern{ch, spat.Class})
|
||||
continue
|
||||
}
|
||||
}
|
||||
pat.Patterns = append(pat.Patterns, &singlePattern{&charClass{ch}})
|
||||
case '$':
|
||||
if toplevel && (sc.NextPos() == sc.Length()-1 || sc.NextPos() == EOS) {
|
||||
pat.MustTail = true
|
||||
} else {
|
||||
pat.Patterns = append(pat.Patterns, &singlePattern{&charClass{ch}})
|
||||
}
|
||||
sc.Next()
|
||||
case EOS:
|
||||
sc.Next()
|
||||
goto exit
|
||||
default:
|
||||
sc.Next()
|
||||
pat.Patterns = append(pat.Patterns, &singlePattern{&charClass{ch}})
|
||||
}
|
||||
}
|
||||
exit:
|
||||
return pat
|
||||
}
|
||||
|
||||
type iptr struct {
|
||||
insts []inst
|
||||
capture int
|
||||
}
|
||||
|
||||
func compilePattern(p pattern, ps ...*iptr) []inst {
|
||||
var ptr *iptr
|
||||
toplevel := false
|
||||
if len(ps) == 0 {
|
||||
toplevel = true
|
||||
ptr = &iptr{[]inst{inst{opSave, nil, 0, -1}}, 2}
|
||||
} else {
|
||||
ptr = ps[0]
|
||||
}
|
||||
switch pat := p.(type) {
|
||||
case *singlePattern:
|
||||
ptr.insts = append(ptr.insts, inst{opChar, pat.Class, -1, -1})
|
||||
case *seqPattern:
|
||||
for _, cp := range pat.Patterns {
|
||||
compilePattern(cp, ptr)
|
||||
}
|
||||
case *repeatPattern:
|
||||
idx := len(ptr.insts)
|
||||
switch pat.Type {
|
||||
case '*':
|
||||
ptr.insts = append(ptr.insts,
|
||||
inst{opSplit, nil, idx + 1, idx + 3},
|
||||
inst{opChar, pat.Class, -1, -1},
|
||||
inst{opJmp, nil, idx, -1})
|
||||
case '+':
|
||||
ptr.insts = append(ptr.insts,
|
||||
inst{opChar, pat.Class, -1, -1},
|
||||
inst{opSplit, nil, idx, idx + 2})
|
||||
case '-':
|
||||
ptr.insts = append(ptr.insts,
|
||||
inst{opSplit, nil, idx + 3, idx + 1},
|
||||
inst{opChar, pat.Class, -1, -1},
|
||||
inst{opJmp, nil, idx, -1})
|
||||
case '?':
|
||||
ptr.insts = append(ptr.insts,
|
||||
inst{opSplit, nil, idx + 1, idx + 2},
|
||||
inst{opChar, pat.Class, -1, -1})
|
||||
}
|
||||
case *posCapPattern:
|
||||
ptr.insts = append(ptr.insts, inst{opPSave, nil, ptr.capture, -1})
|
||||
ptr.capture += 2
|
||||
case *capPattern:
|
||||
c0, c1 := ptr.capture, ptr.capture+1
|
||||
ptr.capture += 2
|
||||
ptr.insts = append(ptr.insts, inst{opSave, nil, c0, -1})
|
||||
compilePattern(pat.Pattern, ptr)
|
||||
ptr.insts = append(ptr.insts, inst{opSave, nil, c1, -1})
|
||||
case *bracePattern:
|
||||
ptr.insts = append(ptr.insts, inst{opBrace, nil, pat.Begin, pat.End})
|
||||
case *numberPattern:
|
||||
ptr.insts = append(ptr.insts, inst{opNumber, nil, pat.N, -1})
|
||||
}
|
||||
if toplevel {
|
||||
if p.(*seqPattern).MustTail {
|
||||
ptr.insts = append(ptr.insts, inst{opSave, nil, 1, -1}, inst{opTailMatch, nil, -1, -1})
|
||||
}
|
||||
ptr.insts = append(ptr.insts, inst{opSave, nil, 1, -1}, inst{opMatch, nil, -1, -1})
|
||||
}
|
||||
return ptr.insts
|
||||
}
|
||||
|
||||
/* }}} parse */
|
||||
|
||||
/* VM {{{ */
|
||||
|
||||
// Simple recursive virtual machine based on the
|
||||
// "Regular Expression Matching: the Virtual Machine Approach" (https://swtch.com/~rsc/regexp/regexp2.html)
|
||||
func recursiveVM(src []byte, insts []inst, pc, sp int, ms ...*MatchData) (bool, int, *MatchData) {
|
||||
var m *MatchData
|
||||
if len(ms) == 0 {
|
||||
m = newMatchState()
|
||||
} else {
|
||||
m = ms[0]
|
||||
}
|
||||
redo:
|
||||
inst := insts[pc]
|
||||
switch inst.OpCode {
|
||||
case opChar:
|
||||
if sp >= len(src) || !inst.Class.Matches(int(src[sp])) {
|
||||
return false, sp, m
|
||||
}
|
||||
pc++
|
||||
sp++
|
||||
goto redo
|
||||
case opMatch:
|
||||
return true, sp, m
|
||||
case opTailMatch:
|
||||
return sp >= len(src), sp, m
|
||||
case opJmp:
|
||||
pc = inst.Operand1
|
||||
goto redo
|
||||
case opSplit:
|
||||
if ok, nsp, _ := recursiveVM(src, insts, inst.Operand1, sp, m); ok {
|
||||
return true, nsp, m
|
||||
}
|
||||
pc = inst.Operand2
|
||||
goto redo
|
||||
case opSave:
|
||||
s := m.setCapture(inst.Operand1, sp)
|
||||
if ok, nsp, _ := recursiveVM(src, insts, pc+1, sp, m); ok {
|
||||
return true, nsp, m
|
||||
}
|
||||
m.restoreCapture(inst.Operand1, s)
|
||||
return false, sp, m
|
||||
case opPSave:
|
||||
m.addPosCapture(inst.Operand1, sp+1)
|
||||
pc++
|
||||
goto redo
|
||||
case opBrace:
|
||||
if sp >= len(src) || int(src[sp]) != inst.Operand1 {
|
||||
return false, sp, m
|
||||
}
|
||||
count := 1
|
||||
for sp = sp + 1; sp < len(src); sp++ {
|
||||
if int(src[sp]) == inst.Operand2 {
|
||||
count--
|
||||
}
|
||||
if count == 0 {
|
||||
pc++
|
||||
sp++
|
||||
goto redo
|
||||
}
|
||||
if int(src[sp]) == inst.Operand1 {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return false, sp, m
|
||||
case opNumber:
|
||||
idx := inst.Operand1 * 2
|
||||
if idx >= m.CaptureLength()-1 {
|
||||
panic(newError(_UNKNOWN, "invalid capture index"))
|
||||
}
|
||||
capture := src[m.Capture(idx):m.Capture(idx+1)]
|
||||
for i := 0; i < len(capture); i++ {
|
||||
if i+sp >= len(src) || capture[i] != src[i+sp] {
|
||||
return false, sp, m
|
||||
}
|
||||
}
|
||||
pc++
|
||||
sp += len(capture)
|
||||
goto redo
|
||||
}
|
||||
panic("should not reach here")
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
/* API {{{ */
|
||||
|
||||
func Find(p string, src []byte, offset, limit int) (matches []*MatchData, err error) {
|
||||
defer func() {
|
||||
if v := recover(); v != nil {
|
||||
if perr, ok := v.(*Error); ok {
|
||||
err = perr
|
||||
} else {
|
||||
panic(v)
|
||||
}
|
||||
}
|
||||
}()
|
||||
pat := parsePattern(newScanner([]byte(p)), true)
|
||||
insts := compilePattern(pat)
|
||||
matches = []*MatchData{}
|
||||
for sp := offset; sp <= len(src); {
|
||||
ok, nsp, ms := recursiveVM(src, insts, 0, sp)
|
||||
sp++
|
||||
if ok {
|
||||
if sp < nsp {
|
||||
sp = nsp
|
||||
}
|
||||
matches = append(matches, ms)
|
||||
}
|
||||
if len(matches) == limit || pat.MustHead {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
/* }}} */
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,448 @@
|
|||
package lua
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/yuin/gopher-lua/pm"
|
||||
)
|
||||
|
||||
const emptyLString LString = LString("")
|
||||
|
||||
func OpenString(L *LState) int {
|
||||
var mod *LTable
|
||||
//_, ok := L.G.builtinMts[int(LTString)]
|
||||
//if !ok {
|
||||
mod = L.RegisterModule(StringLibName, strFuncs).(*LTable)
|
||||
gmatch := L.NewClosure(strGmatch, L.NewFunction(strGmatchIter))
|
||||
mod.RawSetString("gmatch", gmatch)
|
||||
mod.RawSetString("gfind", gmatch)
|
||||
mod.RawSetString("__index", mod)
|
||||
L.G.builtinMts[int(LTString)] = mod
|
||||
//}
|
||||
L.Push(mod)
|
||||
return 1
|
||||
}
|
||||
|
||||
var strFuncs = map[string]LGFunction{
|
||||
"byte": strByte,
|
||||
"char": strChar,
|
||||
"dump": strDump,
|
||||
"find": strFind,
|
||||
"format": strFormat,
|
||||
"gsub": strGsub,
|
||||
"len": strLen,
|
||||
"lower": strLower,
|
||||
"match": strMatch,
|
||||
"rep": strRep,
|
||||
"reverse": strReverse,
|
||||
"sub": strSub,
|
||||
"upper": strUpper,
|
||||
}
|
||||
|
||||
func strByte(L *LState) int {
|
||||
str := L.CheckString(1)
|
||||
start := L.OptInt(2, 1) - 1
|
||||
end := L.OptInt(3, -1)
|
||||
l := len(str)
|
||||
if start < 0 {
|
||||
start = l + start + 1
|
||||
}
|
||||
if end < 0 {
|
||||
end = l + end + 1
|
||||
}
|
||||
|
||||
if L.GetTop() == 2 {
|
||||
if start < 0 || start >= l {
|
||||
return 0
|
||||
}
|
||||
L.Push(LNumber(str[start]))
|
||||
return 1
|
||||
}
|
||||
|
||||
start = intMax(start, 0)
|
||||
end = intMin(end, l)
|
||||
if end < 0 || end <= start || start >= l {
|
||||
return 0
|
||||
}
|
||||
|
||||
for i := start; i < end; i++ {
|
||||
L.Push(LNumber(str[i]))
|
||||
}
|
||||
return end - start
|
||||
}
|
||||
|
||||
func strChar(L *LState) int {
|
||||
top := L.GetTop()
|
||||
bytes := make([]byte, L.GetTop())
|
||||
for i := 1; i <= top; i++ {
|
||||
bytes[i-1] = uint8(L.CheckInt(i))
|
||||
}
|
||||
L.Push(LString(string(bytes)))
|
||||
return 1
|
||||
}
|
||||
|
||||
func strDump(L *LState) int {
|
||||
L.RaiseError("GopherLua does not support the string.dump")
|
||||
return 0
|
||||
}
|
||||
|
||||
func strFind(L *LState) int {
|
||||
str := L.CheckString(1)
|
||||
pattern := L.CheckString(2)
|
||||
if len(pattern) == 0 {
|
||||
L.Push(LNumber(1))
|
||||
L.Push(LNumber(0))
|
||||
return 2
|
||||
}
|
||||
init := luaIndex2StringIndex(str, L.OptInt(3, 1), true)
|
||||
plain := false
|
||||
if L.GetTop() == 4 {
|
||||
plain = LVAsBool(L.Get(4))
|
||||
}
|
||||
|
||||
if plain {
|
||||
pos := strings.Index(str[init:], pattern)
|
||||
if pos < 0 {
|
||||
L.Push(LNil)
|
||||
return 1
|
||||
}
|
||||
L.Push(LNumber(init+pos) + 1)
|
||||
L.Push(LNumber(init + pos + len(pattern)))
|
||||
return 2
|
||||
}
|
||||
|
||||
mds, err := pm.Find(pattern, unsafeFastStringToReadOnlyBytes(str), init, 1)
|
||||
if err != nil {
|
||||
L.RaiseError(err.Error())
|
||||
}
|
||||
if len(mds) == 0 {
|
||||
L.Push(LNil)
|
||||
return 1
|
||||
}
|
||||
md := mds[0]
|
||||
L.Push(LNumber(md.Capture(0) + 1))
|
||||
L.Push(LNumber(md.Capture(1)))
|
||||
for i := 2; i < md.CaptureLength(); i += 2 {
|
||||
if md.IsPosCapture(i) {
|
||||
L.Push(LNumber(md.Capture(i)))
|
||||
} else {
|
||||
L.Push(LString(str[md.Capture(i):md.Capture(i+1)]))
|
||||
}
|
||||
}
|
||||
return md.CaptureLength()/2 + 1
|
||||
}
|
||||
|
||||
func strFormat(L *LState) int {
|
||||
str := L.CheckString(1)
|
||||
args := make([]interface{}, L.GetTop()-1)
|
||||
top := L.GetTop()
|
||||
for i := 2; i <= top; i++ {
|
||||
args[i-2] = L.Get(i)
|
||||
}
|
||||
npat := strings.Count(str, "%") - strings.Count(str, "%%")
|
||||
L.Push(LString(fmt.Sprintf(str, args[:intMin(npat, len(args))]...)))
|
||||
return 1
|
||||
}
|
||||
|
||||
func strGsub(L *LState) int {
|
||||
str := L.CheckString(1)
|
||||
pat := L.CheckString(2)
|
||||
L.CheckTypes(3, LTString, LTTable, LTFunction)
|
||||
repl := L.CheckAny(3)
|
||||
limit := L.OptInt(4, -1)
|
||||
|
||||
mds, err := pm.Find(pat, unsafeFastStringToReadOnlyBytes(str), 0, limit)
|
||||
if err != nil {
|
||||
L.RaiseError(err.Error())
|
||||
}
|
||||
if len(mds) == 0 {
|
||||
L.SetTop(1)
|
||||
L.Push(LNumber(0))
|
||||
return 2
|
||||
}
|
||||
switch lv := repl.(type) {
|
||||
case LString:
|
||||
L.Push(LString(strGsubStr(L, str, string(lv), mds)))
|
||||
case *LTable:
|
||||
L.Push(LString(strGsubTable(L, str, lv, mds)))
|
||||
case *LFunction:
|
||||
L.Push(LString(strGsubFunc(L, str, lv, mds)))
|
||||
}
|
||||
L.Push(LNumber(len(mds)))
|
||||
return 2
|
||||
}
|
||||
|
||||
type replaceInfo struct {
|
||||
Indicies []int
|
||||
String string
|
||||
}
|
||||
|
||||
func checkCaptureIndex(L *LState, m *pm.MatchData, idx int) {
|
||||
if idx <= 2 {
|
||||
return
|
||||
}
|
||||
if idx >= m.CaptureLength() {
|
||||
L.RaiseError("invalid capture index")
|
||||
}
|
||||
}
|
||||
|
||||
func capturedString(L *LState, m *pm.MatchData, str string, idx int) string {
|
||||
checkCaptureIndex(L, m, idx)
|
||||
if idx >= m.CaptureLength() && idx == 2 {
|
||||
idx = 0
|
||||
}
|
||||
if m.IsPosCapture(idx) {
|
||||
return fmt.Sprint(m.Capture(idx))
|
||||
} else {
|
||||
return str[m.Capture(idx):m.Capture(idx+1)]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func strGsubDoReplace(str string, info []replaceInfo) string {
|
||||
offset := 0
|
||||
buf := []byte(str)
|
||||
for _, replace := range info {
|
||||
oldlen := len(buf)
|
||||
b1 := append([]byte(""), buf[0:offset+replace.Indicies[0]]...)
|
||||
b2 := []byte("")
|
||||
index2 := offset + replace.Indicies[1]
|
||||
if index2 <= len(buf) {
|
||||
b2 = append(b2, buf[index2:len(buf)]...)
|
||||
}
|
||||
buf = append(b1, replace.String...)
|
||||
buf = append(buf, b2...)
|
||||
offset += len(buf) - oldlen
|
||||
}
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
func strGsubStr(L *LState, str string, repl string, matches []*pm.MatchData) string {
|
||||
infoList := make([]replaceInfo, 0, len(matches))
|
||||
for _, match := range matches {
|
||||
start, end := match.Capture(0), match.Capture(1)
|
||||
sc := newFlagScanner('%', "", "", repl)
|
||||
for c, eos := sc.Next(); !eos; c, eos = sc.Next() {
|
||||
if !sc.ChangeFlag {
|
||||
if sc.HasFlag {
|
||||
if c >= '0' && c <= '9' {
|
||||
sc.AppendString(capturedString(L, match, str, 2*(int(c)-48)))
|
||||
} else {
|
||||
sc.AppendChar('%')
|
||||
sc.AppendChar(c)
|
||||
}
|
||||
sc.HasFlag = false
|
||||
} else {
|
||||
sc.AppendChar(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
infoList = append(infoList, replaceInfo{[]int{start, end}, sc.String()})
|
||||
}
|
||||
|
||||
return strGsubDoReplace(str, infoList)
|
||||
}
|
||||
|
||||
func strGsubTable(L *LState, str string, repl *LTable, matches []*pm.MatchData) string {
|
||||
infoList := make([]replaceInfo, 0, len(matches))
|
||||
for _, match := range matches {
|
||||
idx := 0
|
||||
if match.CaptureLength() > 2 { // has captures
|
||||
idx = 2
|
||||
}
|
||||
var value LValue
|
||||
if match.IsPosCapture(idx) {
|
||||
value = L.GetTable(repl, LNumber(match.Capture(idx)))
|
||||
} else {
|
||||
value = L.GetField(repl, str[match.Capture(idx):match.Capture(idx+1)])
|
||||
}
|
||||
if !LVIsFalse(value) {
|
||||
infoList = append(infoList, replaceInfo{[]int{match.Capture(0), match.Capture(1)}, LVAsString(value)})
|
||||
}
|
||||
}
|
||||
return strGsubDoReplace(str, infoList)
|
||||
}
|
||||
|
||||
func strGsubFunc(L *LState, str string, repl *LFunction, matches []*pm.MatchData) string {
|
||||
infoList := make([]replaceInfo, 0, len(matches))
|
||||
for _, match := range matches {
|
||||
start, end := match.Capture(0), match.Capture(1)
|
||||
L.Push(repl)
|
||||
nargs := 0
|
||||
if match.CaptureLength() > 2 { // has captures
|
||||
for i := 2; i < match.CaptureLength(); i += 2 {
|
||||
if match.IsPosCapture(i) {
|
||||
L.Push(LNumber(match.Capture(i)))
|
||||
} else {
|
||||
L.Push(LString(capturedString(L, match, str, i)))
|
||||
}
|
||||
nargs++
|
||||
}
|
||||
} else {
|
||||
L.Push(LString(capturedString(L, match, str, 0)))
|
||||
nargs++
|
||||
}
|
||||
L.Call(nargs, 1)
|
||||
ret := L.reg.Pop()
|
||||
if !LVIsFalse(ret) {
|
||||
infoList = append(infoList, replaceInfo{[]int{start, end}, LVAsString(ret)})
|
||||
}
|
||||
}
|
||||
return strGsubDoReplace(str, infoList)
|
||||
}
|
||||
|
||||
type strMatchData struct {
|
||||
str string
|
||||
pos int
|
||||
matches []*pm.MatchData
|
||||
}
|
||||
|
||||
func strGmatchIter(L *LState) int {
|
||||
md := L.CheckUserData(1).Value.(*strMatchData)
|
||||
str := md.str
|
||||
matches := md.matches
|
||||
idx := md.pos
|
||||
md.pos += 1
|
||||
if idx == len(matches) {
|
||||
return 0
|
||||
}
|
||||
L.Push(L.Get(1))
|
||||
match := matches[idx]
|
||||
if match.CaptureLength() == 2 {
|
||||
L.Push(LString(str[match.Capture(0):match.Capture(1)]))
|
||||
return 1
|
||||
}
|
||||
|
||||
for i := 2; i < match.CaptureLength(); i += 2 {
|
||||
if match.IsPosCapture(i) {
|
||||
L.Push(LNumber(match.Capture(i)))
|
||||
} else {
|
||||
L.Push(LString(str[match.Capture(i):match.Capture(i+1)]))
|
||||
}
|
||||
}
|
||||
return match.CaptureLength()/2 - 1
|
||||
}
|
||||
|
||||
func strGmatch(L *LState) int {
|
||||
str := L.CheckString(1)
|
||||
pattern := L.CheckString(2)
|
||||
mds, err := pm.Find(pattern, []byte(str), 0, -1)
|
||||
if err != nil {
|
||||
L.RaiseError(err.Error())
|
||||
}
|
||||
L.Push(L.Get(UpvalueIndex(1)))
|
||||
ud := L.NewUserData()
|
||||
ud.Value = &strMatchData{str, 0, mds}
|
||||
L.Push(ud)
|
||||
return 2
|
||||
}
|
||||
|
||||
func strLen(L *LState) int {
|
||||
str := L.CheckString(1)
|
||||
L.Push(LNumber(len(str)))
|
||||
return 1
|
||||
}
|
||||
|
||||
func strLower(L *LState) int {
|
||||
str := L.CheckString(1)
|
||||
L.Push(LString(strings.ToLower(str)))
|
||||
return 1
|
||||
}
|
||||
|
||||
func strMatch(L *LState) int {
|
||||
str := L.CheckString(1)
|
||||
pattern := L.CheckString(2)
|
||||
offset := L.OptInt(3, 1)
|
||||
l := len(str)
|
||||
if offset < 0 {
|
||||
offset = l + offset + 1
|
||||
}
|
||||
offset--
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
|
||||
mds, err := pm.Find(pattern, unsafeFastStringToReadOnlyBytes(str), offset, 1)
|
||||
if err != nil {
|
||||
L.RaiseError(err.Error())
|
||||
}
|
||||
if len(mds) == 0 {
|
||||
L.Push(LNil)
|
||||
return 0
|
||||
}
|
||||
md := mds[0]
|
||||
nsubs := md.CaptureLength() / 2
|
||||
switch nsubs {
|
||||
case 1:
|
||||
L.Push(LString(str[md.Capture(0):md.Capture(1)]))
|
||||
return 1
|
||||
default:
|
||||
for i := 2; i < md.CaptureLength(); i += 2 {
|
||||
if md.IsPosCapture(i) {
|
||||
L.Push(LNumber(md.Capture(i)))
|
||||
} else {
|
||||
L.Push(LString(str[md.Capture(i):md.Capture(i+1)]))
|
||||
}
|
||||
}
|
||||
return nsubs - 1
|
||||
}
|
||||
}
|
||||
|
||||
func strRep(L *LState) int {
|
||||
str := L.CheckString(1)
|
||||
n := L.CheckInt(2)
|
||||
if n < 0 {
|
||||
L.Push(emptyLString)
|
||||
} else {
|
||||
L.Push(LString(strings.Repeat(str, n)))
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func strReverse(L *LState) int {
|
||||
str := L.CheckString(1)
|
||||
bts := []byte(str)
|
||||
out := make([]byte, len(bts))
|
||||
for i, j := 0, len(bts)-1; j >= 0; i, j = i+1, j-1 {
|
||||
out[i] = bts[j]
|
||||
}
|
||||
L.Push(LString(string(out)))
|
||||
return 1
|
||||
}
|
||||
|
||||
func strSub(L *LState) int {
|
||||
str := L.CheckString(1)
|
||||
start := luaIndex2StringIndex(str, L.CheckInt(2), true)
|
||||
end := luaIndex2StringIndex(str, L.OptInt(3, -1), false)
|
||||
l := len(str)
|
||||
if start >= l || end < start {
|
||||
L.Push(emptyLString)
|
||||
} else {
|
||||
L.Push(LString(str[start:end]))
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func strUpper(L *LState) int {
|
||||
str := L.CheckString(1)
|
||||
L.Push(LString(strings.ToUpper(str)))
|
||||
return 1
|
||||
}
|
||||
|
||||
func luaIndex2StringIndex(str string, i int, start bool) int {
|
||||
if start && i != 0 {
|
||||
i -= 1
|
||||
}
|
||||
l := len(str)
|
||||
if i < 0 {
|
||||
i = l + i + 1
|
||||
}
|
||||
i = intMax(0, i)
|
||||
if !start && i > l {
|
||||
i = l
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
//
|
|
@ -0,0 +1,387 @@
|
|||
package lua
|
||||
|
||||
const defaultArrayCap = 32
|
||||
const defaultHashCap = 32
|
||||
|
||||
type lValueArraySorter struct {
|
||||
L *LState
|
||||
Fn *LFunction
|
||||
Values []LValue
|
||||
}
|
||||
|
||||
func (lv lValueArraySorter) Len() int {
|
||||
return len(lv.Values)
|
||||
}
|
||||
|
||||
func (lv lValueArraySorter) Swap(i, j int) {
|
||||
lv.Values[i], lv.Values[j] = lv.Values[j], lv.Values[i]
|
||||
}
|
||||
|
||||
func (lv lValueArraySorter) Less(i, j int) bool {
|
||||
if lv.Fn != nil {
|
||||
lv.L.Push(lv.Fn)
|
||||
lv.L.Push(lv.Values[i])
|
||||
lv.L.Push(lv.Values[j])
|
||||
lv.L.Call(2, 1)
|
||||
return LVAsBool(lv.L.reg.Pop())
|
||||
}
|
||||
return lessThan(lv.L, lv.Values[i], lv.Values[j])
|
||||
}
|
||||
|
||||
func newLTable(acap int, hcap int) *LTable {
|
||||
if acap < 0 {
|
||||
acap = 0
|
||||
}
|
||||
if hcap < 0 {
|
||||
hcap = 0
|
||||
}
|
||||
tb := <able{}
|
||||
tb.Metatable = LNil
|
||||
if acap != 0 {
|
||||
tb.array = make([]LValue, 0, acap)
|
||||
}
|
||||
if hcap != 0 {
|
||||
tb.strdict = make(map[string]LValue, hcap)
|
||||
}
|
||||
return tb
|
||||
}
|
||||
|
||||
// Len returns length of this LTable without using __len.
|
||||
func (tb *LTable) Len() int {
|
||||
if tb.array == nil {
|
||||
return 0
|
||||
}
|
||||
var prev LValue = LNil
|
||||
for i := len(tb.array) - 1; i >= 0; i-- {
|
||||
v := tb.array[i]
|
||||
if prev == LNil && v != LNil {
|
||||
return i + 1
|
||||
}
|
||||
prev = v
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Append appends a given LValue to this LTable.
|
||||
func (tb *LTable) Append(value LValue) {
|
||||
if value == LNil {
|
||||
return
|
||||
}
|
||||
if tb.array == nil {
|
||||
tb.array = make([]LValue, 0, defaultArrayCap)
|
||||
}
|
||||
if len(tb.array) == 0 || tb.array[len(tb.array)-1] != LNil {
|
||||
tb.array = append(tb.array, value)
|
||||
} else {
|
||||
i := len(tb.array) - 2
|
||||
for ; i >= 0; i-- {
|
||||
if tb.array[i] != LNil {
|
||||
break
|
||||
}
|
||||
}
|
||||
tb.array[i+1] = value
|
||||
}
|
||||
}
|
||||
|
||||
// Insert inserts a given LValue at position `i` in this table.
|
||||
func (tb *LTable) Insert(i int, value LValue) {
|
||||
if tb.array == nil {
|
||||
tb.array = make([]LValue, 0, defaultArrayCap)
|
||||
}
|
||||
if i > len(tb.array) {
|
||||
tb.RawSetInt(i, value)
|
||||
return
|
||||
}
|
||||
if i <= 0 {
|
||||
tb.RawSet(LNumber(i), value)
|
||||
return
|
||||
}
|
||||
i -= 1
|
||||
tb.array = append(tb.array, LNil)
|
||||
copy(tb.array[i+1:], tb.array[i:])
|
||||
tb.array[i] = value
|
||||
}
|
||||
|
||||
// MaxN returns a maximum number key that nil value does not exist before it.
|
||||
func (tb *LTable) MaxN() int {
|
||||
if tb.array == nil {
|
||||
return 0
|
||||
}
|
||||
for i := len(tb.array) - 1; i >= 0; i-- {
|
||||
if tb.array[i] != LNil {
|
||||
return i + 1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Remove removes from this table the element at a given position.
|
||||
func (tb *LTable) Remove(pos int) LValue {
|
||||
if tb.array == nil {
|
||||
return LNil
|
||||
}
|
||||
larray := len(tb.array)
|
||||
if larray == 0 {
|
||||
return LNil
|
||||
}
|
||||
i := pos - 1
|
||||
oldval := LNil
|
||||
switch {
|
||||
case i >= larray:
|
||||
// nothing to do
|
||||
case i == larray-1 || i < 0:
|
||||
oldval = tb.array[larray-1]
|
||||
tb.array = tb.array[:larray-1]
|
||||
default:
|
||||
oldval = tb.array[i]
|
||||
copy(tb.array[i:], tb.array[i+1:])
|
||||
tb.array[larray-1] = nil
|
||||
tb.array = tb.array[:larray-1]
|
||||
}
|
||||
return oldval
|
||||
}
|
||||
|
||||
// RawSet sets a given LValue to a given index without the __newindex metamethod.
|
||||
// It is recommended to use `RawSetString` or `RawSetInt` for performance
|
||||
// if you already know the given LValue is a string or number.
|
||||
func (tb *LTable) RawSet(key LValue, value LValue) {
|
||||
switch v := key.(type) {
|
||||
case LNumber:
|
||||
if isArrayKey(v) {
|
||||
if tb.array == nil {
|
||||
tb.array = make([]LValue, 0, defaultArrayCap)
|
||||
}
|
||||
index := int(v) - 1
|
||||
alen := len(tb.array)
|
||||
switch {
|
||||
case index == alen:
|
||||
tb.array = append(tb.array, value)
|
||||
case index > alen:
|
||||
for i := 0; i < (index - alen); i++ {
|
||||
tb.array = append(tb.array, LNil)
|
||||
}
|
||||
tb.array = append(tb.array, value)
|
||||
case index < alen:
|
||||
tb.array[index] = value
|
||||
}
|
||||
return
|
||||
}
|
||||
case LString:
|
||||
tb.RawSetString(string(v), value)
|
||||
return
|
||||
}
|
||||
|
||||
tb.RawSetH(key, value)
|
||||
}
|
||||
|
||||
// RawSetInt sets a given LValue at a position `key` without the __newindex metamethod.
|
||||
func (tb *LTable) RawSetInt(key int, value LValue) {
|
||||
if key < 1 || key >= MaxArrayIndex {
|
||||
tb.RawSetH(LNumber(key), value)
|
||||
return
|
||||
}
|
||||
if tb.array == nil {
|
||||
tb.array = make([]LValue, 0, 32)
|
||||
}
|
||||
index := key - 1
|
||||
alen := len(tb.array)
|
||||
switch {
|
||||
case index == alen:
|
||||
tb.array = append(tb.array, value)
|
||||
case index > alen:
|
||||
for i := 0; i < (index - alen); i++ {
|
||||
tb.array = append(tb.array, LNil)
|
||||
}
|
||||
tb.array = append(tb.array, value)
|
||||
case index < alen:
|
||||
tb.array[index] = value
|
||||
}
|
||||
}
|
||||
|
||||
// RawSetString sets a given LValue to a given string index without the __newindex metamethod.
|
||||
func (tb *LTable) RawSetString(key string, value LValue) {
|
||||
if tb.strdict == nil {
|
||||
tb.strdict = make(map[string]LValue, defaultHashCap)
|
||||
}
|
||||
if tb.keys == nil {
|
||||
tb.keys = []LValue{}
|
||||
tb.k2i = map[LValue]int{}
|
||||
}
|
||||
|
||||
if value == LNil {
|
||||
// TODO tb.keys and tb.k2i should also be removed
|
||||
delete(tb.strdict, key)
|
||||
} else {
|
||||
tb.strdict[key] = value
|
||||
lkey := LString(key)
|
||||
if _, ok := tb.k2i[lkey]; !ok {
|
||||
tb.k2i[lkey] = len(tb.keys)
|
||||
tb.keys = append(tb.keys, lkey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RawSetH sets a given LValue to a given index without the __newindex metamethod.
|
||||
func (tb *LTable) RawSetH(key LValue, value LValue) {
|
||||
if s, ok := key.(LString); ok {
|
||||
tb.RawSetString(string(s), value)
|
||||
return
|
||||
}
|
||||
if tb.dict == nil {
|
||||
tb.dict = make(map[LValue]LValue, len(tb.strdict))
|
||||
}
|
||||
if tb.keys == nil {
|
||||
tb.keys = []LValue{}
|
||||
tb.k2i = map[LValue]int{}
|
||||
}
|
||||
|
||||
if value == LNil {
|
||||
// TODO tb.keys and tb.k2i should also be removed
|
||||
delete(tb.dict, key)
|
||||
} else {
|
||||
tb.dict[key] = value
|
||||
if _, ok := tb.k2i[key]; !ok {
|
||||
tb.k2i[key] = len(tb.keys)
|
||||
tb.keys = append(tb.keys, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RawGet returns an LValue associated with a given key without __index metamethod.
|
||||
func (tb *LTable) RawGet(key LValue) LValue {
|
||||
switch v := key.(type) {
|
||||
case LNumber:
|
||||
if isArrayKey(v) {
|
||||
if tb.array == nil {
|
||||
return LNil
|
||||
}
|
||||
index := int(v) - 1
|
||||
if index >= len(tb.array) {
|
||||
return LNil
|
||||
}
|
||||
return tb.array[index]
|
||||
}
|
||||
case LString:
|
||||
if tb.strdict == nil {
|
||||
return LNil
|
||||
}
|
||||
if ret, ok := tb.strdict[string(v)]; ok {
|
||||
return ret
|
||||
}
|
||||
return LNil
|
||||
}
|
||||
if tb.dict == nil {
|
||||
return LNil
|
||||
}
|
||||
if v, ok := tb.dict[key]; ok {
|
||||
return v
|
||||
}
|
||||
return LNil
|
||||
}
|
||||
|
||||
// RawGetInt returns an LValue at position `key` without __index metamethod.
|
||||
func (tb *LTable) RawGetInt(key int) LValue {
|
||||
if tb.array == nil {
|
||||
return LNil
|
||||
}
|
||||
index := int(key) - 1
|
||||
if index >= len(tb.array) || index < 0 {
|
||||
return LNil
|
||||
}
|
||||
return tb.array[index]
|
||||
}
|
||||
|
||||
// RawGet returns an LValue associated with a given key without __index metamethod.
|
||||
func (tb *LTable) RawGetH(key LValue) LValue {
|
||||
if s, sok := key.(LString); sok {
|
||||
if tb.strdict == nil {
|
||||
return LNil
|
||||
}
|
||||
if v, vok := tb.strdict[string(s)]; vok {
|
||||
return v
|
||||
}
|
||||
return LNil
|
||||
}
|
||||
if tb.dict == nil {
|
||||
return LNil
|
||||
}
|
||||
if v, ok := tb.dict[key]; ok {
|
||||
return v
|
||||
}
|
||||
return LNil
|
||||
}
|
||||
|
||||
// RawGetString returns an LValue associated with a given key without __index metamethod.
|
||||
func (tb *LTable) RawGetString(key string) LValue {
|
||||
if tb.strdict == nil {
|
||||
return LNil
|
||||
}
|
||||
if v, vok := tb.strdict[string(key)]; vok {
|
||||
return v
|
||||
}
|
||||
return LNil
|
||||
}
|
||||
|
||||
// ForEach iterates over this table of elements, yielding each in turn to a given function.
|
||||
func (tb *LTable) ForEach(cb func(LValue, LValue)) {
|
||||
if tb.array != nil {
|
||||
for i, v := range tb.array {
|
||||
if v != LNil {
|
||||
cb(LNumber(i+1), v)
|
||||
}
|
||||
}
|
||||
}
|
||||
if tb.strdict != nil {
|
||||
for k, v := range tb.strdict {
|
||||
if v != LNil {
|
||||
cb(LString(k), v)
|
||||
}
|
||||
}
|
||||
}
|
||||
if tb.dict != nil {
|
||||
for k, v := range tb.dict {
|
||||
if v != LNil {
|
||||
cb(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This function is equivalent to lua_next ( http://www.lua.org/manual/5.1/manual.html#lua_next ).
|
||||
func (tb *LTable) Next(key LValue) (LValue, LValue) {
|
||||
init := false
|
||||
if key == LNil {
|
||||
key = LNumber(0)
|
||||
init = true
|
||||
}
|
||||
|
||||
if init || key != LNumber(0) {
|
||||
if kv, ok := key.(LNumber); ok && isInteger(kv) && int(kv) >= 0 && kv < LNumber(MaxArrayIndex) {
|
||||
index := int(kv)
|
||||
if tb.array != nil {
|
||||
for ; index < len(tb.array); index++ {
|
||||
if v := tb.array[index]; v != LNil {
|
||||
return LNumber(index + 1), v
|
||||
}
|
||||
}
|
||||
}
|
||||
if tb.array == nil || index == len(tb.array) {
|
||||
if (tb.dict == nil || len(tb.dict) == 0) && (tb.strdict == nil || len(tb.strdict) == 0) {
|
||||
return LNil, LNil
|
||||
}
|
||||
key = tb.keys[0]
|
||||
if v := tb.RawGetH(key); v != LNil {
|
||||
return key, v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i := tb.k2i[key] + 1; i < len(tb.keys); i++ {
|
||||
key := tb.keys[i]
|
||||
if v := tb.RawGetH(key); v != LNil {
|
||||
return key, v
|
||||
}
|
||||
}
|
||||
return LNil, LNil
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
package lua
|
||||
|
||||
import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
func OpenTable(L *LState) int {
|
||||
tabmod := L.RegisterModule(TabLibName, tableFuncs)
|
||||
L.Push(tabmod)
|
||||
return 1
|
||||
}
|
||||
|
||||
var tableFuncs = map[string]LGFunction{
|
||||
"getn": tableGetN,
|
||||
"concat": tableConcat,
|
||||
"insert": tableInsert,
|
||||
"maxn": tableMaxN,
|
||||
"remove": tableRemove,
|
||||
"sort": tableSort,
|
||||
}
|
||||
|
||||
func tableSort(L *LState) int {
|
||||
tbl := L.CheckTable(1)
|
||||
sorter := lValueArraySorter{L, nil, tbl.array}
|
||||
if L.GetTop() != 1 {
|
||||
sorter.Fn = L.CheckFunction(2)
|
||||
}
|
||||
sort.Sort(sorter)
|
||||
return 0
|
||||
}
|
||||
|
||||
func tableGetN(L *LState) int {
|
||||
L.Push(LNumber(L.CheckTable(1).Len()))
|
||||
return 1
|
||||
}
|
||||
|
||||
func tableMaxN(L *LState) int {
|
||||
L.Push(LNumber(L.CheckTable(1).MaxN()))
|
||||
return 1
|
||||
}
|
||||
|
||||
func tableRemove(L *LState) int {
|
||||
tbl := L.CheckTable(1)
|
||||
if L.GetTop() == 1 {
|
||||
L.Push(tbl.Remove(-1))
|
||||
} else {
|
||||
L.Push(tbl.Remove(L.CheckInt(2)))
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func tableConcat(L *LState) int {
|
||||
tbl := L.CheckTable(1)
|
||||
sep := LString(L.OptString(2, ""))
|
||||
i := L.OptInt(3, 1)
|
||||
j := L.OptInt(4, tbl.Len())
|
||||
if L.GetTop() == 3 {
|
||||
if i > tbl.Len() || i < 1 {
|
||||
L.Push(emptyLString)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
i = intMax(intMin(i, tbl.Len()), 1)
|
||||
j = intMin(intMin(j, tbl.Len()), tbl.Len())
|
||||
if i > j {
|
||||
L.Push(emptyLString)
|
||||
return 1
|
||||
}
|
||||
//TODO should flushing?
|
||||
retbottom := L.GetTop()
|
||||
for ; i <= j; i++ {
|
||||
v := tbl.RawGetInt(i)
|
||||
if !LVCanConvToString(v) {
|
||||
L.RaiseError("invalid value (%s) at index %d in table for concat", v.Type().String(), i)
|
||||
}
|
||||
L.Push(v)
|
||||
if i != j {
|
||||
L.Push(sep)
|
||||
}
|
||||
}
|
||||
L.Push(stringConcat(L, L.GetTop()-retbottom, L.reg.Top()-1))
|
||||
return 1
|
||||
}
|
||||
|
||||
func tableInsert(L *LState) int {
|
||||
tbl := L.CheckTable(1)
|
||||
nargs := L.GetTop()
|
||||
if nargs == 1 {
|
||||
L.RaiseError("wrong number of arguments")
|
||||
}
|
||||
|
||||
if L.GetTop() == 2 {
|
||||
tbl.Append(L.Get(2))
|
||||
return 0
|
||||
}
|
||||
tbl.Insert(int(L.CheckInt(2)), L.CheckAny(3))
|
||||
return 0
|
||||
}
|
||||
|
||||
//
|
|
@ -0,0 +1,265 @@
|
|||
package lua
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func intMin(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
} else {
|
||||
return b
|
||||
}
|
||||
}
|
||||
|
||||
func intMax(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
} else {
|
||||
return b
|
||||
}
|
||||
}
|
||||
|
||||
func defaultFormat(v interface{}, f fmt.State, c rune) {
|
||||
buf := make([]string, 0, 10)
|
||||
buf = append(buf, "%")
|
||||
for i := 0; i < 128; i++ {
|
||||
if f.Flag(i) {
|
||||
buf = append(buf, string(rune(i)))
|
||||
}
|
||||
}
|
||||
|
||||
if w, ok := f.Width(); ok {
|
||||
buf = append(buf, strconv.Itoa(w))
|
||||
}
|
||||
if p, ok := f.Precision(); ok {
|
||||
buf = append(buf, "."+strconv.Itoa(p))
|
||||
}
|
||||
buf = append(buf, string(c))
|
||||
format := strings.Join(buf, "")
|
||||
fmt.Fprintf(f, format, v)
|
||||
}
|
||||
|
||||
type flagScanner struct {
|
||||
flag byte
|
||||
start string
|
||||
end string
|
||||
buf []byte
|
||||
str string
|
||||
Length int
|
||||
Pos int
|
||||
HasFlag bool
|
||||
ChangeFlag bool
|
||||
}
|
||||
|
||||
func newFlagScanner(flag byte, start, end, str string) *flagScanner {
|
||||
return &flagScanner{flag, start, end, make([]byte, 0, len(str)), str, len(str), 0, false, false}
|
||||
}
|
||||
|
||||
func (fs *flagScanner) AppendString(str string) { fs.buf = append(fs.buf, str...) }
|
||||
|
||||
func (fs *flagScanner) AppendChar(ch byte) { fs.buf = append(fs.buf, ch) }
|
||||
|
||||
func (fs *flagScanner) String() string { return string(fs.buf) }
|
||||
|
||||
func (fs *flagScanner) Next() (byte, bool) {
|
||||
c := byte('\000')
|
||||
fs.ChangeFlag = false
|
||||
if fs.Pos == fs.Length {
|
||||
if fs.HasFlag {
|
||||
fs.AppendString(fs.end)
|
||||
}
|
||||
return c, true
|
||||
} else {
|
||||
c = fs.str[fs.Pos]
|
||||
if c == fs.flag {
|
||||
if fs.Pos < (fs.Length-1) && fs.str[fs.Pos+1] == fs.flag {
|
||||
fs.HasFlag = false
|
||||
fs.AppendChar(fs.flag)
|
||||
fs.Pos += 2
|
||||
return fs.Next()
|
||||
} else if fs.Pos != fs.Length-1 {
|
||||
if fs.HasFlag {
|
||||
fs.AppendString(fs.end)
|
||||
}
|
||||
fs.AppendString(fs.start)
|
||||
fs.ChangeFlag = true
|
||||
fs.HasFlag = true
|
||||
}
|
||||
}
|
||||
}
|
||||
fs.Pos++
|
||||
return c, false
|
||||
}
|
||||
|
||||
var cDateFlagToGo = map[byte]string{
|
||||
'a': "mon", 'A': "Monday", 'b': "Jan", 'B': "January", 'c': "02 Jan 06 15:04 MST", 'd': "02",
|
||||
'F': "2006-01-02", 'H': "15", 'I': "03", 'm': "01", 'M': "04", 'p': "PM", 'P': "pm", 'S': "05",
|
||||
'x': "15/04/05", 'X': "15:04:05", 'y': "06", 'Y': "2006", 'z': "-0700", 'Z': "MST"}
|
||||
|
||||
func strftime(t time.Time, cfmt string) string {
|
||||
sc := newFlagScanner('%', "", "", cfmt)
|
||||
for c, eos := sc.Next(); !eos; c, eos = sc.Next() {
|
||||
if !sc.ChangeFlag {
|
||||
if sc.HasFlag {
|
||||
if v, ok := cDateFlagToGo[c]; ok {
|
||||
sc.AppendString(t.Format(v))
|
||||
} else {
|
||||
switch c {
|
||||
case 'w':
|
||||
sc.AppendString(fmt.Sprint(int(t.Weekday())))
|
||||
default:
|
||||
sc.AppendChar('%')
|
||||
sc.AppendChar(c)
|
||||
}
|
||||
}
|
||||
sc.HasFlag = false
|
||||
} else {
|
||||
sc.AppendChar(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sc.String()
|
||||
}
|
||||
|
||||
func isInteger(v LNumber) bool {
|
||||
return float64(v) == float64(int64(v))
|
||||
//_, frac := math.Modf(float64(v))
|
||||
//return frac == 0.0
|
||||
}
|
||||
|
||||
func isArrayKey(v LNumber) bool {
|
||||
return isInteger(v) && v < LNumber(int((^uint(0))>>1)) && v > LNumber(0) && v < LNumber(MaxArrayIndex)
|
||||
}
|
||||
|
||||
func parseNumber(number string) (LNumber, error) {
|
||||
var value LNumber
|
||||
number = strings.Trim(number, " \t\n")
|
||||
if v, err := strconv.ParseInt(number, 0, LNumberBit); err != nil {
|
||||
if v2, err2 := strconv.ParseFloat(number, LNumberBit); err2 != nil {
|
||||
return LNumber(0), err2
|
||||
} else {
|
||||
value = LNumber(v2)
|
||||
}
|
||||
} else {
|
||||
value = LNumber(v)
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func popenArgs(arg string) (string, []string) {
|
||||
cmd := "/bin/sh"
|
||||
args := []string{"-c"}
|
||||
if LuaOS == "windows" {
|
||||
cmd = "C:\\Windows\\system32\\cmd.exe"
|
||||
args = []string{"/c"}
|
||||
}
|
||||
args = append(args, arg)
|
||||
return cmd, args
|
||||
}
|
||||
|
||||
func isGoroutineSafe(lv LValue) bool {
|
||||
switch v := lv.(type) {
|
||||
case *LFunction, *LUserData, *LState:
|
||||
return false
|
||||
case *LTable:
|
||||
return v.Metatable == LNil
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func readBufioSize(reader *bufio.Reader, size int64) ([]byte, error, bool) {
|
||||
result := []byte{}
|
||||
read := int64(0)
|
||||
var err error
|
||||
var n int
|
||||
for read != size {
|
||||
buf := make([]byte, size-read)
|
||||
n, err = reader.Read(buf)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
read += int64(n)
|
||||
result = append(result, buf[:n]...)
|
||||
}
|
||||
e := err
|
||||
if e != nil && e == io.EOF {
|
||||
e = nil
|
||||
}
|
||||
|
||||
return result, e, len(result) == 0 && err == io.EOF
|
||||
}
|
||||
|
||||
func readBufioLine(reader *bufio.Reader) ([]byte, error, bool) {
|
||||
result := []byte{}
|
||||
var buf []byte
|
||||
var err error
|
||||
var isprefix bool = true
|
||||
for isprefix {
|
||||
buf, isprefix, err = reader.ReadLine()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
result = append(result, buf...)
|
||||
}
|
||||
e := err
|
||||
if e != nil && e == io.EOF {
|
||||
e = nil
|
||||
}
|
||||
|
||||
return result, e, len(result) == 0 && err == io.EOF
|
||||
}
|
||||
|
||||
func int2Fb(val int) int {
|
||||
e := 0
|
||||
x := val
|
||||
for x >= 16 {
|
||||
x = (x + 1) >> 1
|
||||
e++
|
||||
}
|
||||
if x < 8 {
|
||||
return x
|
||||
}
|
||||
return ((e + 1) << 3) | (x - 8)
|
||||
}
|
||||
|
||||
func strCmp(s1, s2 string) int {
|
||||
len1 := len(s1)
|
||||
len2 := len(s2)
|
||||
for i := 0; ; i++ {
|
||||
c1 := -1
|
||||
if i < len1 {
|
||||
c1 = int(s1[i])
|
||||
}
|
||||
c2 := -1
|
||||
if i != len2 {
|
||||
c2 = int(s2[i])
|
||||
}
|
||||
switch {
|
||||
case c1 < c2:
|
||||
return -1
|
||||
case c1 > c2:
|
||||
return +1
|
||||
case c1 < 0:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func unsafeFastStringToReadOnlyBytes(s string) (bs []byte) {
|
||||
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
||||
bh := (*reflect.SliceHeader)(unsafe.Pointer(&bs))
|
||||
bh.Data = sh.Data
|
||||
bh.Cap = sh.Len
|
||||
bh.Len = sh.Len
|
||||
return
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
package lua
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
type LValueType int
|
||||
|
||||
const (
|
||||
LTNil LValueType = iota
|
||||
LTBool
|
||||
LTNumber
|
||||
LTString
|
||||
LTFunction
|
||||
LTUserData
|
||||
LTThread
|
||||
LTTable
|
||||
LTChannel
|
||||
)
|
||||
|
||||
var lValueNames = [9]string{"nil", "boolean", "number", "string", "function", "userdata", "thread", "table", "channel"}
|
||||
|
||||
func (vt LValueType) String() string {
|
||||
return lValueNames[int(vt)]
|
||||
}
|
||||
|
||||
type LValue interface {
|
||||
String() string
|
||||
Type() LValueType
|
||||
// to reduce `runtime.assertI2T2` costs, this method should be used instead of the type assertion in heavy paths(typically inside the VM).
|
||||
assertFloat64() (float64, bool)
|
||||
// to reduce `runtime.assertI2T2` costs, this method should be used instead of the type assertion in heavy paths(typically inside the VM).
|
||||
assertString() (string, bool)
|
||||
// to reduce `runtime.assertI2T2` costs, this method should be used instead of the type assertion in heavy paths(typically inside the VM).
|
||||
assertFunction() (*LFunction, bool)
|
||||
}
|
||||
|
||||
// LVIsFalse returns true if a given LValue is a nil or false otherwise false.
|
||||
func LVIsFalse(v LValue) bool { return v == LNil || v == LFalse }
|
||||
|
||||
// LVIsFalse returns false if a given LValue is a nil or false otherwise true.
|
||||
func LVAsBool(v LValue) bool { return v != LNil && v != LFalse }
|
||||
|
||||
// LVAsString returns string representation of a given LValue
|
||||
// if the LValue is a string or number, otherwise an empty string.
|
||||
func LVAsString(v LValue) string {
|
||||
switch sn := v.(type) {
|
||||
case LString, LNumber:
|
||||
return sn.String()
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// LVCanConvToString returns true if a given LValue is a string or number
|
||||
// otherwise false.
|
||||
func LVCanConvToString(v LValue) bool {
|
||||
switch v.(type) {
|
||||
case LString, LNumber:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// LVAsNumber tries to convert a given LValue to a number.
|
||||
func LVAsNumber(v LValue) LNumber {
|
||||
switch lv := v.(type) {
|
||||
case LNumber:
|
||||
return lv
|
||||
case LString:
|
||||
if num, err := parseNumber(string(lv)); err == nil {
|
||||
return num
|
||||
}
|
||||
}
|
||||
return LNumber(0)
|
||||
}
|
||||
|
||||
type LNilType struct{}
|
||||
|
||||
func (nl *LNilType) String() string { return "nil" }
|
||||
func (nl *LNilType) Type() LValueType { return LTNil }
|
||||
func (nl *LNilType) assertFloat64() (float64, bool) { return 0, false }
|
||||
func (nl *LNilType) assertString() (string, bool) { return "", false }
|
||||
func (nl *LNilType) assertFunction() (*LFunction, bool) { return nil, false }
|
||||
|
||||
var LNil = LValue(&LNilType{})
|
||||
|
||||
type LBool bool
|
||||
|
||||
func (bl LBool) String() string {
|
||||
if bool(bl) {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
}
|
||||
func (bl LBool) Type() LValueType { return LTBool }
|
||||
func (bl LBool) assertFloat64() (float64, bool) { return 0, false }
|
||||
func (bl LBool) assertString() (string, bool) { return "", false }
|
||||
func (bl LBool) assertFunction() (*LFunction, bool) { return nil, false }
|
||||
|
||||
var LTrue = LBool(true)
|
||||
var LFalse = LBool(false)
|
||||
|
||||
type LString string
|
||||
|
||||
func (st LString) String() string { return string(st) }
|
||||
func (st LString) Type() LValueType { return LTString }
|
||||
func (st LString) assertFloat64() (float64, bool) { return 0, false }
|
||||
func (st LString) assertString() (string, bool) { return string(st), true }
|
||||
func (st LString) assertFunction() (*LFunction, bool) { return nil, false }
|
||||
|
||||
// fmt.Formatter interface
|
||||
func (st LString) Format(f fmt.State, c rune) {
|
||||
switch c {
|
||||
case 'd', 'i':
|
||||
if nm, err := parseNumber(string(st)); err != nil {
|
||||
defaultFormat(nm, f, 'd')
|
||||
} else {
|
||||
defaultFormat(string(st), f, 's')
|
||||
}
|
||||
default:
|
||||
defaultFormat(string(st), f, c)
|
||||
}
|
||||
}
|
||||
|
||||
func (nm LNumber) String() string {
|
||||
if isInteger(nm) {
|
||||
return fmt.Sprint(int64(nm))
|
||||
}
|
||||
return fmt.Sprint(float64(nm))
|
||||
}
|
||||
|
||||
func (nm LNumber) Type() LValueType { return LTNumber }
|
||||
func (nm LNumber) assertFloat64() (float64, bool) { return float64(nm), true }
|
||||
func (nm LNumber) assertString() (string, bool) { return "", false }
|
||||
func (nm LNumber) assertFunction() (*LFunction, bool) { return nil, false }
|
||||
|
||||
// fmt.Formatter interface
|
||||
func (nm LNumber) Format(f fmt.State, c rune) {
|
||||
switch c {
|
||||
case 'q', 's':
|
||||
defaultFormat(nm.String(), f, c)
|
||||
case 'b', 'c', 'd', 'o', 'x', 'X', 'U':
|
||||
defaultFormat(int64(nm), f, c)
|
||||
case 'e', 'E', 'f', 'F', 'g', 'G':
|
||||
defaultFormat(float64(nm), f, c)
|
||||
case 'i':
|
||||
defaultFormat(int64(nm), f, 'd')
|
||||
default:
|
||||
if isInteger(nm) {
|
||||
defaultFormat(int64(nm), f, c)
|
||||
} else {
|
||||
defaultFormat(float64(nm), f, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type LTable struct {
|
||||
Metatable LValue
|
||||
|
||||
array []LValue
|
||||
dict map[LValue]LValue
|
||||
strdict map[string]LValue
|
||||
keys []LValue
|
||||
k2i map[LValue]int
|
||||
}
|
||||
|
||||
func (tb *LTable) String() string { return fmt.Sprintf("table: %p", tb) }
|
||||
func (tb *LTable) Type() LValueType { return LTTable }
|
||||
func (tb *LTable) assertFloat64() (float64, bool) { return 0, false }
|
||||
func (tb *LTable) assertString() (string, bool) { return "", false }
|
||||
func (tb *LTable) assertFunction() (*LFunction, bool) { return nil, false }
|
||||
|
||||
type LFunction struct {
|
||||
IsG bool
|
||||
Env *LTable
|
||||
Proto *FunctionProto
|
||||
GFunction LGFunction
|
||||
Upvalues []*Upvalue
|
||||
}
|
||||
type LGFunction func(*LState) int
|
||||
|
||||
func (fn *LFunction) String() string { return fmt.Sprintf("function: %p", fn) }
|
||||
func (fn *LFunction) Type() LValueType { return LTFunction }
|
||||
func (fn *LFunction) assertFloat64() (float64, bool) { return 0, false }
|
||||
func (fn *LFunction) assertString() (string, bool) { return "", false }
|
||||
func (fn *LFunction) assertFunction() (*LFunction, bool) { return fn, true }
|
||||
|
||||
type Global struct {
|
||||
MainThread *LState
|
||||
CurrentThread *LState
|
||||
Registry *LTable
|
||||
Global *LTable
|
||||
|
||||
builtinMts map[int]LValue
|
||||
tempFiles []*os.File
|
||||
gccount int32
|
||||
}
|
||||
|
||||
type LState struct {
|
||||
G *Global
|
||||
Parent *LState
|
||||
Env *LTable
|
||||
Panic func(*LState)
|
||||
Dead bool
|
||||
Options Options
|
||||
|
||||
stop int32
|
||||
reg *registry
|
||||
stack callFrameStack
|
||||
alloc *allocator
|
||||
currentFrame *callFrame
|
||||
wrapped bool
|
||||
uvcache *Upvalue
|
||||
hasErrorFunc bool
|
||||
mainLoop func(*LState, *callFrame)
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (ls *LState) String() string { return fmt.Sprintf("thread: %p", ls) }
|
||||
func (ls *LState) Type() LValueType { return LTThread }
|
||||
func (ls *LState) assertFloat64() (float64, bool) { return 0, false }
|
||||
func (ls *LState) assertString() (string, bool) { return "", false }
|
||||
func (ls *LState) assertFunction() (*LFunction, bool) { return nil, false }
|
||||
|
||||
type LUserData struct {
|
||||
Value interface{}
|
||||
Env *LTable
|
||||
Metatable LValue
|
||||
}
|
||||
|
||||
func (ud *LUserData) String() string { return fmt.Sprintf("userdata: %p", ud) }
|
||||
func (ud *LUserData) Type() LValueType { return LTUserData }
|
||||
func (ud *LUserData) assertFloat64() (float64, bool) { return 0, false }
|
||||
func (ud *LUserData) assertString() (string, bool) { return "", false }
|
||||
func (ud *LUserData) assertFunction() (*LFunction, bool) { return nil, false }
|
||||
|
||||
type LChannel chan LValue
|
||||
|
||||
func (ch LChannel) String() string { return fmt.Sprintf("channel: %p", ch) }
|
||||
func (ch LChannel) Type() LValueType { return LTChannel }
|
||||
func (ch LChannel) assertFloat64() (float64, bool) { return 0, false }
|
||||
func (ch LChannel) assertString() (string, bool) { return "", false }
|
||||
func (ch LChannel) assertFunction() (*LFunction, bool) { return nil, false }
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,24 @@
|
|||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
|
@ -0,0 +1,7 @@
|
|||
# gopher-json [](https://godoc.org/layeh.com/gopher-json)
|
||||
|
||||
Package json is a simple JSON encoder/decoder for [gopher-lua](https://github.com/yuin/gopher-lua).
|
||||
|
||||
## License
|
||||
|
||||
Public domain
|
|
@ -0,0 +1,33 @@
|
|||
// Package json is a simple JSON encoder/decoder for gopher-lua.
|
||||
//
|
||||
// Documentation
|
||||
//
|
||||
// The following functions are exposed by the library:
|
||||
// decode(string): Decodes a JSON string. Returns nil and an error string if
|
||||
// the string could not be decoded.
|
||||
// encode(value): Encodes a value into a JSON string. Returns nil and an error
|
||||
// string if the value could not be encoded.
|
||||
//
|
||||
// The following types are supported:
|
||||
//
|
||||
// Lua | JSON
|
||||
// ---------+-----
|
||||
// nil | null
|
||||
// number | number
|
||||
// string | string
|
||||
// table | object: when table is non-empty and has only string keys
|
||||
// | array: when table is empty, or has only sequential numeric keys
|
||||
// | starting from 1
|
||||
//
|
||||
// Attempting to encode any other Lua type will result in an error.
|
||||
//
|
||||
// Example
|
||||
//
|
||||
// Below is an example usage of the library:
|
||||
// import (
|
||||
// luajson "layeh.com/gopher-json"
|
||||
// )
|
||||
//
|
||||
// L := lua.NewState()
|
||||
// luajson.Preload(L)
|
||||
package json // import "layeh.com/gopher-json"
|
|
@ -0,0 +1,181 @@
|
|||
package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/yuin/gopher-lua"
|
||||
)
|
||||
|
||||
// Preload adds json to the given Lua state's package.preload table. After it
|
||||
// has been preloaded, it can be loaded using require:
|
||||
//
|
||||
// local json = require("json")
|
||||
func Preload(L *lua.LState) {
|
||||
L.PreloadModule("json", Loader)
|
||||
}
|
||||
|
||||
// Loader is the module loader function.
|
||||
func Loader(L *lua.LState) int {
|
||||
t := L.NewTable()
|
||||
L.SetFuncs(t, api)
|
||||
L.Push(t)
|
||||
return 1
|
||||
}
|
||||
|
||||
var api = map[string]lua.LGFunction{
|
||||
"decode": apiDecode,
|
||||
"encode": apiEncode,
|
||||
}
|
||||
|
||||
func apiDecode(L *lua.LState) int {
|
||||
str := L.CheckString(1)
|
||||
|
||||
value, err := Decode(L, []byte(str))
|
||||
if err != nil {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
L.Push(value)
|
||||
return 1
|
||||
}
|
||||
|
||||
func apiEncode(L *lua.LState) int {
|
||||
value := L.CheckAny(1)
|
||||
|
||||
data, err := Encode(value)
|
||||
if err != nil {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
L.Push(lua.LString(string(data)))
|
||||
return 1
|
||||
}
|
||||
|
||||
var (
|
||||
errNested = errors.New("cannot encode recursively nested tables to JSON")
|
||||
errSparseArray = errors.New("cannot encode sparse array")
|
||||
errInvalidKeys = errors.New("cannot encode mixed or invalid key types")
|
||||
)
|
||||
|
||||
type invalidTypeError lua.LValueType
|
||||
|
||||
func (i invalidTypeError) Error() string {
|
||||
return `cannot encode ` + lua.LValueType(i).String() + ` to JSON`
|
||||
}
|
||||
|
||||
// Encode returns the JSON encoding of value.
|
||||
func Encode(value lua.LValue) ([]byte, error) {
|
||||
return json.Marshal(jsonValue{
|
||||
LValue: value,
|
||||
visited: make(map[*lua.LTable]bool),
|
||||
})
|
||||
}
|
||||
|
||||
type jsonValue struct {
|
||||
lua.LValue
|
||||
visited map[*lua.LTable]bool
|
||||
}
|
||||
|
||||
func (j jsonValue) MarshalJSON() (data []byte, err error) {
|
||||
switch converted := j.LValue.(type) {
|
||||
case lua.LBool:
|
||||
data, err = json.Marshal(bool(converted))
|
||||
case lua.LNumber:
|
||||
data, err = json.Marshal(float64(converted))
|
||||
case *lua.LNilType:
|
||||
data = []byte(`null`)
|
||||
case lua.LString:
|
||||
data, err = json.Marshal(string(converted))
|
||||
case *lua.LTable:
|
||||
if j.visited[converted] {
|
||||
return nil, errNested
|
||||
}
|
||||
j.visited[converted] = true
|
||||
|
||||
key, value := converted.Next(lua.LNil)
|
||||
|
||||
switch key.Type() {
|
||||
case lua.LTNil: // empty table
|
||||
data = []byte(`[]`)
|
||||
case lua.LTNumber:
|
||||
arr := make([]jsonValue, 0, converted.Len())
|
||||
expectedKey := lua.LNumber(1)
|
||||
for key != lua.LNil {
|
||||
if key.Type() != lua.LTNumber {
|
||||
err = errInvalidKeys
|
||||
return
|
||||
}
|
||||
if expectedKey != key {
|
||||
err = errSparseArray
|
||||
return
|
||||
}
|
||||
arr = append(arr, jsonValue{value, j.visited})
|
||||
expectedKey++
|
||||
key, value = converted.Next(key)
|
||||
}
|
||||
data, err = json.Marshal(arr)
|
||||
case lua.LTString:
|
||||
obj := make(map[string]jsonValue)
|
||||
for key != lua.LNil {
|
||||
if key.Type() != lua.LTString {
|
||||
err = errInvalidKeys
|
||||
return
|
||||
}
|
||||
obj[key.String()] = jsonValue{value, j.visited}
|
||||
key, value = converted.Next(key)
|
||||
}
|
||||
data, err = json.Marshal(obj)
|
||||
default:
|
||||
err = errInvalidKeys
|
||||
}
|
||||
default:
|
||||
err = invalidTypeError(j.LValue.Type())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Decode converts the JSON encoded data to Lua values.
|
||||
func Decode(L *lua.LState, data []byte) (lua.LValue, error) {
|
||||
var value interface{}
|
||||
err := json.Unmarshal(data, &value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return DecodeValue(L, value), nil
|
||||
}
|
||||
|
||||
// DecodeValue converts the value to a Lua value.
|
||||
//
|
||||
// This function only converts values that the encoding/json package decodes to.
|
||||
// All other values will return lua.LNil.
|
||||
func DecodeValue(L *lua.LState, value interface{}) lua.LValue {
|
||||
switch converted := value.(type) {
|
||||
case bool:
|
||||
return lua.LBool(converted)
|
||||
case float64:
|
||||
return lua.LNumber(converted)
|
||||
case string:
|
||||
return lua.LString(converted)
|
||||
case json.Number:
|
||||
return lua.LString(converted)
|
||||
case []interface{}:
|
||||
arr := L.CreateTable(len(converted), 0)
|
||||
for _, item := range converted {
|
||||
arr.Append(DecodeValue(L, item))
|
||||
}
|
||||
return arr
|
||||
case map[string]interface{}:
|
||||
tbl := L.CreateTable(0, len(converted))
|
||||
for key, item := range converted {
|
||||
tbl.RawSetH(lua.LString(key), DecodeValue(L, item))
|
||||
}
|
||||
return tbl
|
||||
case nil:
|
||||
return lua.LNil
|
||||
}
|
||||
|
||||
return lua.LNil
|
||||
}
|
|
@ -428,6 +428,12 @@ github.com/vektra/mockery/v2/pkg/logging
|
|||
# github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca
|
||||
## explicit
|
||||
github.com/xlab/treeprint
|
||||
# github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64
|
||||
## explicit; go 1.17
|
||||
github.com/yuin/gopher-lua
|
||||
github.com/yuin/gopher-lua/ast
|
||||
github.com/yuin/gopher-lua/parse
|
||||
github.com/yuin/gopher-lua/pm
|
||||
# go.etcd.io/etcd/api/v3 v3.5.1
|
||||
## explicit; go 1.16
|
||||
go.etcd.io/etcd/api/v3/authpb
|
||||
|
@ -1491,6 +1497,9 @@ k8s.io/utils/path
|
|||
k8s.io/utils/pointer
|
||||
k8s.io/utils/strings/slices
|
||||
k8s.io/utils/trace
|
||||
# layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf
|
||||
## explicit
|
||||
layeh.com/gopher-json
|
||||
# sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30
|
||||
## explicit; go 1.17
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client/pkg/client
|
||||
|
|
Loading…
Reference in New Issue