-
Notifications
You must be signed in to change notification settings - Fork 107
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
886 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package starlark | ||
|
||
import ( | ||
"fmt" | ||
|
||
"go.starlark.net/starlark" | ||
) | ||
|
||
func (c *ctx) Call(functionName string, args ...starlark.Value) (starlark.Value, error) { | ||
fn, ok := c.globals[functionName].(*starlark.Function) | ||
if !ok { | ||
return nil, fmt.Errorf("function %s not found", functionName) | ||
} | ||
result, err := starlark.Call(c.thread, fn, starlark.Tuple(args), nil) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to call function %s: %w", functionName, err) | ||
} | ||
return result, nil | ||
} | ||
|
||
func (c *ctx) CallWithNative(functionName string, args ...any) (any, error) { | ||
fn, ok := c.globals[functionName].(*starlark.Function) | ||
if !ok { | ||
return nil, fmt.Errorf("function %s not found", functionName) | ||
} | ||
|
||
// Convert native Go arguments to Starlark values | ||
starlarkArgs := make([]starlark.Value, len(args)) | ||
for i, arg := range args { | ||
starlarkArgs[i] = convertToStarlark(arg) | ||
} | ||
|
||
// Call the Starlark function | ||
result, err := starlark.Call(c.thread, fn, starlark.Tuple(starlarkArgs), nil) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to call function %s: %w", functionName, err) | ||
} | ||
|
||
// Convert the result back to a Go value | ||
return convertFromStarlarkBasedOnValue(result), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package starlark | ||
|
||
import ( | ||
"reflect" | ||
|
||
"go.starlark.net/starlark" | ||
) | ||
|
||
func convertFromStarlarkBasedOnValue(val starlark.Value) any { | ||
switch v := val.(type) { | ||
case starlark.Int: | ||
intVal, _ := v.Int64() | ||
return intVal | ||
case starlark.Float: | ||
return float64(v) | ||
case starlark.String: | ||
return string(v) | ||
case starlark.Bool: | ||
return bool(v) | ||
case *starlark.List: | ||
result := make([]any, v.Len()) | ||
for i := 0; i < v.Len(); i++ { | ||
result[i] = convertFromStarlarkBasedOnValue(v.Index(i)) | ||
} | ||
return result | ||
case *starlark.Dict: | ||
result := make(map[any]any) | ||
for _, item := range v.Items() { | ||
key := convertFromStarlarkBasedOnValue(item[0]) | ||
value := convertFromStarlarkBasedOnValue(item[1]) | ||
result[key] = value | ||
} | ||
return result | ||
case starlark.NoneType: | ||
return nil | ||
default: | ||
return val | ||
} | ||
} | ||
|
||
func convertFromStarlark(val starlark.Value, targetType reflect.Type) interface{} { | ||
switch targetType.Kind() { | ||
case reflect.Int: | ||
intVal, _ := starlark.AsInt32(val) | ||
return int(intVal) | ||
case reflect.Float64: | ||
floatVal, _ := starlark.AsFloat(val) | ||
return floatVal | ||
case reflect.String: | ||
return string(val.(starlark.String)) | ||
case reflect.Bool: | ||
return bool(val.(starlark.Bool)) | ||
case reflect.Slice: | ||
list := val.(*starlark.List) | ||
slice := reflect.MakeSlice(targetType, list.Len(), list.Len()) | ||
for i := 0; i < list.Len(); i++ { | ||
slice.Index(i).Set(reflect.ValueOf(convertFromStarlark(list.Index(i), targetType.Elem()))) | ||
} | ||
return slice.Interface() | ||
case reflect.Map: | ||
dict := val.(*starlark.Dict) | ||
mapType := reflect.MapOf(targetType.Key(), targetType.Elem()) | ||
mapVal := reflect.MakeMap(mapType) | ||
for _, item := range dict.Items() { | ||
key := convertFromStarlark(item[0], targetType.Key()) | ||
value := convertFromStarlark(item[1], targetType.Elem()) | ||
mapVal.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(value)) | ||
} | ||
return mapVal.Interface() | ||
default: | ||
return val | ||
} | ||
} | ||
|
||
func convertToStarlark(val interface{}) starlark.Value { | ||
switch v := val.(type) { | ||
case int: | ||
return starlark.MakeInt(v) | ||
case float64: | ||
return starlark.Float(v) | ||
case string: | ||
return starlark.String(v) | ||
case bool: | ||
return starlark.Bool(v) | ||
case []interface{}: | ||
list := &starlark.List{} | ||
for _, item := range v { | ||
list.Append(convertToStarlark(item)) | ||
} | ||
return list | ||
case map[interface{}]interface{}: | ||
dict := starlark.NewDict(len(v)) | ||
for key, value := range v { | ||
dict.SetKey(convertToStarlark(key), convertToStarlark(value)) | ||
} | ||
return dict | ||
case nil: | ||
return starlark.None | ||
default: | ||
return nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package starlark | ||
|
||
import "go.starlark.net/starlark" | ||
|
||
type Module interface { | ||
Name() string | ||
} | ||
|
||
type VM interface { | ||
Module(mod Module) | ||
Modules(mod ...Module) | ||
File(module string) (Context, error) | ||
} | ||
|
||
type Context interface { | ||
Call(functionName string, args ...starlark.Value) (starlark.Value, error) | ||
CallWithNative(functionName string, args ...any) (any, error) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package starlark | ||
|
||
import ( | ||
"fmt" | ||
"io/fs" | ||
|
||
"go.starlark.net/starlark" | ||
"go.starlark.net/starlarkstruct" | ||
"go.starlark.net/syntax" | ||
) | ||
|
||
func makeLoadFunc(v *vm) func(thread *starlark.Thread, module string) (starlark.StringDict, error) { | ||
return func(thread *starlark.Thread, module string) (starlark.StringDict, error) { | ||
for name, dict := range v.builtins { | ||
if module == name+".star" { | ||
return dict, nil | ||
} | ||
} | ||
|
||
// nothing builtin, check file system | ||
var lastErr error | ||
for _, fsys := range v.filesystems { | ||
script, err := fs.ReadFile(fsys, module) | ||
if err != nil { | ||
lastErr = err | ||
continue // Try next file system | ||
} | ||
predeclared := starlark.StringDict{ | ||
"struct": starlark.NewBuiltin("struct", starlarkstruct.Make), | ||
} | ||
opts := syntax.FileOptions{} | ||
return starlark.ExecFileOptions(&opts, thread, module, script, predeclared) | ||
} | ||
|
||
return nil, fmt.Errorf("failed to load module %s: %w", module, lastErr) // No file system had the module | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
package starlark | ||
|
||
import ( | ||
"fmt" | ||
"reflect" | ||
"strings" | ||
|
||
"go.starlark.net/starlark" | ||
"go.starlark.net/starlarkstruct" | ||
) | ||
|
||
// Builtin registers methods from a given struct that have names starting with 'E_' | ||
// will override an existing module with the same name | ||
// E_ followed by a capital letter export a function with native go types | ||
func (v *vm) Module(mod Module) { | ||
v.builtins[mod.Name()] = starlark.StringDict{ | ||
mod.Name(): starlarkstruct.FromStringDict(starlarkstruct.Default, registerMethods(mod)), | ||
} | ||
} | ||
|
||
func (v *vm) Modules(mods ...Module) { | ||
for _, mod := range mods { | ||
v.builtins[mod.Name()] = starlark.StringDict{ | ||
mod.Name(): starlarkstruct.FromStringDict(starlarkstruct.Default, registerMethods(mod)), | ||
} | ||
} | ||
} | ||
|
||
func registerMethods(obj interface{}) starlark.StringDict { | ||
methods := make(starlark.StringDict) | ||
val := reflect.ValueOf(obj) | ||
typ := val.Type() | ||
|
||
for i := 0; i < typ.NumMethod(); i++ { | ||
method := val.Method(i) | ||
methodName := typ.Method(i).Name | ||
|
||
if strings.HasPrefix(methodName, "E_") && len(methodName) > 2 { | ||
starlarkName := strings.TrimPrefix(methodName, "E_") | ||
|
||
if strings.ToUpper(string(methodName[2])) == string(methodName[2]) { | ||
starlarkName = strings.ToLower(string(starlarkName[0])) + starlarkName[1:] | ||
methods[starlarkName] = makeGoFunc(method) | ||
} else if validateMethodSignature(method.Type()) { | ||
methods[starlarkName] = makeStarlarkFunc(method) | ||
} | ||
} | ||
} | ||
|
||
return methods | ||
} | ||
|
||
func validateMethodSignature(t reflect.Type) bool { | ||
return t.NumIn() == 4 && | ||
t.In(0).AssignableTo(reflect.TypeOf((*starlark.Thread)(nil))) && | ||
t.In(1).AssignableTo(reflect.TypeOf((*starlark.Builtin)(nil))) && | ||
t.In(2).AssignableTo(reflect.TypeOf(starlark.Tuple(nil))) && | ||
t.In(3).AssignableTo(reflect.TypeOf([]starlark.Tuple(nil))) && | ||
t.NumOut() == 2 && | ||
t.Out(0).AssignableTo(reflect.TypeOf((*starlark.Value)(nil)).Elem()) && | ||
t.Out(1).Implements(reflect.TypeOf((*error)(nil)).Elem()) | ||
} | ||
|
||
func makeStarlarkFunc(method reflect.Value) *starlark.Builtin { | ||
return starlark.NewBuiltin(method.Type().Name(), func(thread *starlark.Thread, builtin *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { | ||
retValues := method.Call([]reflect.Value{ | ||
reflect.ValueOf(thread), | ||
reflect.ValueOf(builtin), | ||
reflect.ValueOf(args), | ||
reflect.ValueOf(kwargs), | ||
}) | ||
result := retValues[0].Interface() | ||
err := retValues[1].Interface() | ||
if err != nil { | ||
return nil, err.(error) | ||
} | ||
return result.(starlark.Value), nil | ||
}) | ||
} | ||
|
||
func makeGoFunc(method reflect.Value) *starlark.Builtin { | ||
return starlark.NewBuiltin(method.Type().Name(), func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, _ []starlark.Tuple) (starlark.Value, error) { | ||
methodType := method.Type() | ||
if args.Len() != methodType.NumIn() { | ||
return nil, fmt.Errorf("expected %d arguments, got %d", methodType.NumIn(), args.Len()) | ||
} | ||
|
||
in := make([]reflect.Value, args.Len()) | ||
for i := 0; i < args.Len(); i++ { | ||
arg := args.Index(i) | ||
in[i] = reflect.ValueOf(convertFromStarlark(arg, methodType.In(i))) | ||
} | ||
|
||
retValues := method.Call(in) | ||
starlarkRet := make(starlark.Tuple, 0, len(retValues)) | ||
|
||
for _, ret := range retValues { | ||
starlarkRet = append(starlarkRet, convertToStarlark(ret.Interface())) | ||
} | ||
|
||
if len(starlarkRet) == 0 { | ||
return starlark.None, nil | ||
} else if len(starlarkRet) == 1 { | ||
return starlarkRet[0], nil | ||
} | ||
|
||
return starlarkRet, nil | ||
}) | ||
} |
Oops, something went wrong.