2024-04-10 10:45:58 +00:00
|
|
|
package cmdlang
|
|
|
|
|
2024-04-10 11:58:06 +00:00
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
2024-04-10 12:19:11 +00:00
|
|
|
"fmt"
|
2024-04-24 11:29:26 +00:00
|
|
|
"github.com/lmika/gopkgs/fp/slices"
|
2024-04-23 00:41:36 +00:00
|
|
|
"reflect"
|
2024-04-10 11:58:06 +00:00
|
|
|
"strconv"
|
|
|
|
)
|
|
|
|
|
2024-04-10 12:19:11 +00:00
|
|
|
type object interface {
|
2024-04-11 12:05:05 +00:00
|
|
|
String() string
|
2024-04-13 11:46:50 +00:00
|
|
|
Truthy() bool
|
2024-04-10 12:19:11 +00:00
|
|
|
}
|
|
|
|
|
2024-04-23 00:41:36 +00:00
|
|
|
type listable interface {
|
|
|
|
Len() int
|
|
|
|
Index(i int) object
|
|
|
|
}
|
|
|
|
|
2024-04-23 12:02:06 +00:00
|
|
|
type hashable interface {
|
|
|
|
Len() int
|
2024-04-24 11:09:52 +00:00
|
|
|
Value(k string) object
|
2024-04-23 12:02:06 +00:00
|
|
|
Each(func(k string, v object) error) error
|
|
|
|
}
|
|
|
|
|
2024-04-16 12:05:21 +00:00
|
|
|
type listObject []object
|
|
|
|
|
2024-04-17 10:43:25 +00:00
|
|
|
func (lo *listObject) Append(o object) {
|
|
|
|
*lo = append(*lo, o)
|
|
|
|
}
|
|
|
|
|
2024-04-16 12:05:21 +00:00
|
|
|
func (s listObject) String() string {
|
|
|
|
return fmt.Sprintf("%v", []object(s))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s listObject) Truthy() bool {
|
|
|
|
return len(s) > 0
|
|
|
|
}
|
|
|
|
|
2024-04-23 00:41:36 +00:00
|
|
|
func (s listObject) Len() int {
|
|
|
|
return len(s)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s listObject) Index(i int) object {
|
|
|
|
return s[i]
|
|
|
|
}
|
|
|
|
|
2024-04-16 12:05:21 +00:00
|
|
|
type hashObject map[string]object
|
|
|
|
|
|
|
|
func (s hashObject) String() string {
|
|
|
|
return fmt.Sprintf("%v", map[string]object(s))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s hashObject) Truthy() bool {
|
|
|
|
return len(s) > 0
|
|
|
|
}
|
|
|
|
|
2024-04-23 12:02:06 +00:00
|
|
|
func (s hashObject) Len() int {
|
|
|
|
return len(s)
|
|
|
|
}
|
|
|
|
|
2024-04-24 11:09:52 +00:00
|
|
|
func (s hashObject) Value(k string) object {
|
|
|
|
return s[k]
|
|
|
|
}
|
|
|
|
|
2024-04-23 12:02:06 +00:00
|
|
|
func (s hashObject) Each(fn func(k string, v object) error) error {
|
|
|
|
for k, v := range s {
|
|
|
|
if err := fn(k, v); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-04-10 12:19:11 +00:00
|
|
|
type strObject string
|
|
|
|
|
|
|
|
func (s strObject) String() string {
|
|
|
|
return string(s)
|
|
|
|
}
|
2024-04-10 10:45:58 +00:00
|
|
|
|
2024-04-13 11:46:50 +00:00
|
|
|
func (s strObject) Truthy() bool {
|
|
|
|
return string(s) != ""
|
|
|
|
}
|
|
|
|
|
2024-04-24 10:12:39 +00:00
|
|
|
type intObject int
|
|
|
|
|
|
|
|
func (i intObject) String() string {
|
|
|
|
return strconv.Itoa(int(i))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i intObject) Truthy() bool {
|
|
|
|
return i != 0
|
|
|
|
}
|
|
|
|
|
2024-04-18 12:24:19 +00:00
|
|
|
type boolObject bool
|
|
|
|
|
|
|
|
func (b boolObject) String() string {
|
|
|
|
if b {
|
|
|
|
return "(true)"
|
|
|
|
}
|
|
|
|
return "(false))"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b boolObject) Truthy() bool {
|
|
|
|
return bool(b)
|
|
|
|
}
|
|
|
|
|
2024-04-11 12:05:05 +00:00
|
|
|
func toGoValue(obj object) (interface{}, bool) {
|
|
|
|
switch v := obj.(type) {
|
|
|
|
case nil:
|
|
|
|
return nil, true
|
|
|
|
case strObject:
|
|
|
|
return string(v), true
|
2024-04-24 10:12:39 +00:00
|
|
|
case intObject:
|
|
|
|
return int(v), true
|
2024-04-16 12:05:21 +00:00
|
|
|
case listObject:
|
|
|
|
xs := make([]interface{}, 0, len(v))
|
|
|
|
for _, va := range v {
|
|
|
|
x, ok := toGoValue(va)
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
xs = append(xs, x)
|
|
|
|
}
|
|
|
|
return xs, true
|
|
|
|
case hashObject:
|
|
|
|
xs := make(map[string]interface{})
|
|
|
|
for k, va := range v {
|
|
|
|
x, ok := toGoValue(va)
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
xs[k] = x
|
|
|
|
}
|
|
|
|
return xs, true
|
2024-04-14 12:09:13 +00:00
|
|
|
case proxyObject:
|
|
|
|
return v.p, true
|
2024-04-23 00:41:36 +00:00
|
|
|
case listableProxyObject:
|
|
|
|
return v.v.Interface(), true
|
2024-04-24 11:09:52 +00:00
|
|
|
case structProxyObject:
|
|
|
|
return v.v.Interface(), true
|
2024-04-11 12:05:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
2024-04-14 12:09:13 +00:00
|
|
|
func fromGoValue(v any) (object, error) {
|
|
|
|
switch t := v.(type) {
|
|
|
|
case nil:
|
|
|
|
return nil, nil
|
|
|
|
case string:
|
|
|
|
return strObject(t), nil
|
2024-04-24 11:09:52 +00:00
|
|
|
case int:
|
|
|
|
return intObject(t), nil
|
2024-04-14 12:09:13 +00:00
|
|
|
}
|
2024-04-23 00:41:36 +00:00
|
|
|
|
|
|
|
resVal := reflect.ValueOf(v)
|
2024-04-24 11:09:52 +00:00
|
|
|
switch resVal.Kind() {
|
|
|
|
case reflect.Slice:
|
2024-04-23 00:41:36 +00:00
|
|
|
return listableProxyObject{resVal}, nil
|
2024-04-24 11:09:52 +00:00
|
|
|
case reflect.Struct:
|
2024-04-24 11:29:26 +00:00
|
|
|
return newStructProxyObject(resVal), nil
|
2024-04-23 00:41:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return proxyObject{v}, nil
|
2024-04-14 12:09:13 +00:00
|
|
|
}
|
|
|
|
|
2024-04-13 11:46:50 +00:00
|
|
|
type macroArgs struct {
|
2024-04-23 12:02:06 +00:00
|
|
|
eval evaluator
|
|
|
|
ec *evalCtx
|
2024-04-24 10:12:39 +00:00
|
|
|
hasPipe bool
|
2024-04-23 12:02:06 +00:00
|
|
|
pipeArg object
|
|
|
|
ast *astCmd
|
|
|
|
argShift int
|
2024-04-13 11:46:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (ma macroArgs) nargs() int {
|
|
|
|
return len(ma.ast.Args[ma.argShift:])
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ma *macroArgs) shift(n int) {
|
|
|
|
ma.argShift += n
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ma macroArgs) identIs(ctx context.Context, n int, expectedIdent string) bool {
|
|
|
|
if n >= len(ma.ast.Args[ma.argShift:]) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
lit := ma.ast.Args[ma.argShift+n].Ident
|
|
|
|
if lit == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return *lit == expectedIdent
|
|
|
|
}
|
|
|
|
|
2024-04-18 10:26:02 +00:00
|
|
|
func (ma *macroArgs) shiftIdent(ctx context.Context) (string, bool) {
|
|
|
|
if ma.argShift >= len(ma.ast.Args) {
|
|
|
|
return "", false
|
|
|
|
}
|
|
|
|
|
|
|
|
lit := ma.ast.Args[ma.argShift].Ident
|
|
|
|
if lit != nil {
|
|
|
|
ma.argShift += 1
|
|
|
|
return *lit, true
|
|
|
|
}
|
|
|
|
return "", false
|
|
|
|
}
|
|
|
|
|
2024-04-13 11:46:50 +00:00
|
|
|
func (ma macroArgs) evalArg(ctx context.Context, n int) (object, error) {
|
|
|
|
if n >= len(ma.ast.Args[ma.argShift:]) {
|
|
|
|
return nil, errors.New("not enough arguments") // FIX
|
|
|
|
}
|
|
|
|
|
|
|
|
return ma.eval.evalArg(ctx, ma.ec, ma.ast.Args[ma.argShift+n])
|
|
|
|
}
|
|
|
|
|
2024-04-16 12:28:12 +00:00
|
|
|
func (ma macroArgs) evalBlock(ctx context.Context, n int, args []object, pushScope bool) (object, error) {
|
2024-04-13 11:46:50 +00:00
|
|
|
obj, err := ma.evalArg(ctx, n)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
block, ok := obj.(blockObject)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("not a block object")
|
|
|
|
}
|
|
|
|
|
2024-04-16 12:28:12 +00:00
|
|
|
ec := ma.ec
|
|
|
|
if pushScope {
|
|
|
|
ec = ec.fork()
|
|
|
|
}
|
|
|
|
for i, n := range block.block.Names {
|
|
|
|
if i < len(args) {
|
|
|
|
ec.setVar(n, args[i])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ma.eval.evalBlock(ctx, ec, block.block)
|
2024-04-13 11:46:50 +00:00
|
|
|
}
|
|
|
|
|
2024-04-10 10:45:58 +00:00
|
|
|
type invocationArgs struct {
|
2024-04-23 12:02:06 +00:00
|
|
|
eval evaluator
|
|
|
|
inst *Inst
|
|
|
|
ec *evalCtx
|
|
|
|
args []object
|
|
|
|
kwargs map[string]*listObject
|
2024-04-18 10:53:25 +00:00
|
|
|
}
|
|
|
|
|
2024-04-10 11:58:06 +00:00
|
|
|
func (ia invocationArgs) expectArgn(x int) error {
|
|
|
|
if len(ia.args) < x {
|
|
|
|
return errors.New("expected at least " + strconv.Itoa(x) + " args")
|
|
|
|
}
|
|
|
|
return nil
|
2024-04-10 10:45:58 +00:00
|
|
|
}
|
|
|
|
|
2024-04-10 12:19:11 +00:00
|
|
|
func (ia invocationArgs) stringArg(i int) (string, error) {
|
|
|
|
if len(ia.args) < i {
|
|
|
|
return "", errors.New("expected at least " + strconv.Itoa(i) + " args")
|
|
|
|
}
|
|
|
|
s, ok := ia.args[i].(fmt.Stringer)
|
|
|
|
if !ok {
|
|
|
|
return "", errors.New("expected a string arg")
|
|
|
|
}
|
|
|
|
return s.String(), nil
|
|
|
|
}
|
|
|
|
|
2024-04-23 12:02:06 +00:00
|
|
|
func (ia invocationArgs) invokableArg(i int) (invokable, error) {
|
|
|
|
if len(ia.args) < i {
|
|
|
|
return nil, errors.New("expected at least " + strconv.Itoa(i) + " args")
|
|
|
|
}
|
|
|
|
|
|
|
|
switch v := ia.args[i].(type) {
|
|
|
|
case invokable:
|
|
|
|
return v, nil
|
|
|
|
}
|
|
|
|
return nil, errors.New("expected an invokable arg")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ia invocationArgs) fork(args []object) invocationArgs {
|
2024-04-18 10:53:25 +00:00
|
|
|
return invocationArgs{
|
2024-04-23 12:02:06 +00:00
|
|
|
eval: ia.eval,
|
|
|
|
inst: ia.inst,
|
|
|
|
ec: ia.ec,
|
|
|
|
args: args,
|
|
|
|
kwargs: make(map[string]*listObject),
|
2024-04-18 10:53:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-18 10:26:02 +00:00
|
|
|
func (ia invocationArgs) shift(i int) invocationArgs {
|
|
|
|
return invocationArgs{
|
2024-04-23 12:02:06 +00:00
|
|
|
eval: ia.eval,
|
|
|
|
inst: ia.inst,
|
|
|
|
ec: ia.ec,
|
|
|
|
args: ia.args[i:],
|
|
|
|
kwargs: ia.kwargs,
|
2024-04-18 10:26:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-10 11:58:06 +00:00
|
|
|
// invokable is an object that can be executed as a command
|
2024-04-10 10:45:58 +00:00
|
|
|
type invokable interface {
|
2024-04-10 11:58:06 +00:00
|
|
|
invoke(ctx context.Context, args invocationArgs) (object, error)
|
2024-04-10 10:45:58 +00:00
|
|
|
}
|
|
|
|
|
2024-04-13 11:46:50 +00:00
|
|
|
type macroable interface {
|
|
|
|
invokeMacro(ctx context.Context, args macroArgs) (object, error)
|
|
|
|
}
|
|
|
|
|
2024-04-23 12:02:06 +00:00
|
|
|
type pipeInvokable interface {
|
2024-04-11 10:47:59 +00:00
|
|
|
invokable
|
|
|
|
}
|
|
|
|
|
2024-04-10 11:58:06 +00:00
|
|
|
type invokableFunc func(ctx context.Context, args invocationArgs) (object, error)
|
2024-04-10 10:45:58 +00:00
|
|
|
|
2024-04-10 11:58:06 +00:00
|
|
|
func (i invokableFunc) invoke(ctx context.Context, args invocationArgs) (object, error) {
|
2024-04-10 10:45:58 +00:00
|
|
|
return i(ctx, args)
|
|
|
|
}
|
2024-04-11 10:47:59 +00:00
|
|
|
|
2024-04-13 11:46:50 +00:00
|
|
|
type blockObject struct {
|
|
|
|
block *astBlock
|
|
|
|
}
|
|
|
|
|
|
|
|
func (bo blockObject) String() string {
|
|
|
|
return "block"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (bo blockObject) Truthy() bool {
|
|
|
|
return len(bo.block.Statements) > 0
|
|
|
|
}
|
|
|
|
|
2024-04-23 12:02:06 +00:00
|
|
|
func (bo blockObject) invoke(ctx context.Context, args invocationArgs) (object, error) {
|
|
|
|
ec := args.ec.fork()
|
|
|
|
for i, n := range bo.block.Names {
|
|
|
|
if i < len(args.args) {
|
|
|
|
ec.setVar(n, args.args[i])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return args.eval.evalBlock(ctx, ec, bo.block)
|
|
|
|
}
|
|
|
|
|
2024-04-13 11:46:50 +00:00
|
|
|
type macroFunc func(ctx context.Context, args macroArgs) (object, error)
|
|
|
|
|
|
|
|
func (i macroFunc) invokeMacro(ctx context.Context, args macroArgs) (object, error) {
|
|
|
|
return i(ctx, args)
|
|
|
|
}
|
|
|
|
|
|
|
|
func isTruthy(obj object) bool {
|
|
|
|
if obj == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return obj.Truthy()
|
|
|
|
}
|
2024-04-14 12:09:13 +00:00
|
|
|
|
|
|
|
type proxyObject struct {
|
|
|
|
p interface{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p proxyObject) String() string {
|
|
|
|
return fmt.Sprintf("proxyObject{%T}", p.p)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p proxyObject) Truthy() bool {
|
|
|
|
//TODO implement me
|
|
|
|
panic("implement me")
|
|
|
|
}
|
2024-04-23 00:41:36 +00:00
|
|
|
|
|
|
|
type listableProxyObject struct {
|
|
|
|
v reflect.Value
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p listableProxyObject) String() string {
|
|
|
|
return fmt.Sprintf("listableProxyObject{%v}", p.v.Type())
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p listableProxyObject) Truthy() bool {
|
|
|
|
panic("implement me")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p listableProxyObject) Len() int {
|
|
|
|
return p.v.Len()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p listableProxyObject) Index(i int) object {
|
|
|
|
e, err := fromGoValue(p.v.Index(i).Interface())
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return e
|
|
|
|
}
|
2024-04-24 11:09:52 +00:00
|
|
|
|
|
|
|
type structProxyObject struct {
|
2024-04-24 11:29:26 +00:00
|
|
|
v reflect.Value
|
|
|
|
vf []reflect.StructField
|
|
|
|
}
|
|
|
|
|
|
|
|
func newStructProxyObject(v reflect.Value) structProxyObject {
|
|
|
|
return structProxyObject{
|
|
|
|
v: v,
|
|
|
|
vf: slices.Filter(reflect.VisibleFields(v.Type()), func(t reflect.StructField) bool { return t.IsExported() }),
|
|
|
|
}
|
2024-04-24 11:09:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s structProxyObject) String() string {
|
|
|
|
return fmt.Sprintf("structProxyObject{%v}", s.v.Type())
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s structProxyObject) Truthy() bool {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s structProxyObject) Len() int {
|
2024-04-24 11:29:26 +00:00
|
|
|
return len(s.vf)
|
2024-04-24 11:09:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s structProxyObject) Value(k string) object {
|
|
|
|
e, err := fromGoValue(s.v.FieldByName(k).Interface())
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return e
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s structProxyObject) Each(fn func(k string, v object) error) error {
|
2024-04-24 11:29:26 +00:00
|
|
|
for _, f := range s.vf {
|
|
|
|
v, err := fromGoValue(s.v.FieldByName(f.Name).Interface())
|
2024-04-24 11:09:52 +00:00
|
|
|
if err != nil {
|
|
|
|
v = nil
|
|
|
|
}
|
|
|
|
|
2024-04-24 11:29:26 +00:00
|
|
|
if err := fn(f.Name, v); err != nil {
|
2024-04-24 11:09:52 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|