394 lines
7.8 KiB
Go
394 lines
7.8 KiB
Go
package cmdlang
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
func echoBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|
if len(args.args) == 0 {
|
|
if _, err := fmt.Fprintln(args.inst.Out()); err != nil {
|
|
return nil, err
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
var line strings.Builder
|
|
for _, arg := range args.args {
|
|
if s, ok := arg.(fmt.Stringer); ok {
|
|
line.WriteString(s.String())
|
|
}
|
|
}
|
|
|
|
if _, err := fmt.Fprintln(args.inst.Out(), line.String()); err != nil {
|
|
return nil, err
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func setBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|
if err := args.expectArgn(2); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
name, err := args.stringArg(0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
newVal := args.args[1]
|
|
|
|
// TODO: if the value is a stream, consume the stream and save it as a list
|
|
args.ec.setVar(name, newVal)
|
|
return newVal, nil
|
|
}
|
|
|
|
func toUpperBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|
if err := args.expectArgn(1); err != nil {
|
|
return nil, err
|
|
}
|
|
sarg, err := args.stringArg(0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return strObject(strings.ToUpper(sarg)), nil
|
|
}
|
|
|
|
func eqBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|
if err := args.expectArgn(2); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
l := args.args[0]
|
|
r := args.args[1]
|
|
|
|
switch lv := l.(type) {
|
|
case strObject:
|
|
if rv, ok := r.(strObject); ok {
|
|
return boolObject(lv == rv), nil
|
|
}
|
|
}
|
|
return boolObject(false), nil
|
|
}
|
|
|
|
func concatBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|
var sb strings.Builder
|
|
|
|
for _, a := range args.args {
|
|
if a == nil {
|
|
continue
|
|
}
|
|
sb.WriteString(a.String())
|
|
}
|
|
|
|
return strObject(sb.String()), nil
|
|
}
|
|
|
|
//
|
|
//func catBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|
// if err := args.expectArgn(1); err != nil {
|
|
// return nil, err
|
|
// }
|
|
//
|
|
// filename, err := args.stringArg(0)
|
|
// if err != nil {
|
|
// return nil, err
|
|
// }
|
|
//
|
|
// return &fileLinesStream{filename: filename}, nil
|
|
//}
|
|
|
|
func callBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|
if err := args.expectArgn(1); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
inv, ok := args.args[0].(invokable)
|
|
if !ok {
|
|
return nil, errors.New("expected invokable")
|
|
}
|
|
|
|
return inv.invoke(ctx, args.shift(1))
|
|
}
|
|
|
|
func lenBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|
if err := args.expectArgn(1); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch v := args.args[0].(type) {
|
|
case strObject:
|
|
return intObject(len(string(v))), nil
|
|
case listable:
|
|
return intObject(v.Len()), nil
|
|
case hashable:
|
|
return intObject(v.Len()), nil
|
|
}
|
|
|
|
return intObject(0), nil
|
|
}
|
|
|
|
func indexBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|
if err := args.expectArgn(1); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
val := args.args[0]
|
|
for _, idx := range args.args[1:] {
|
|
switch v := val.(type) {
|
|
case listable:
|
|
intIdx, ok := idx.(intObject)
|
|
if !ok {
|
|
return nil, errors.New("expected int for listable")
|
|
}
|
|
if int(intIdx) >= 0 && int(intIdx) < v.Len() {
|
|
val = v.Index(int(intIdx))
|
|
} else {
|
|
val = nil
|
|
}
|
|
case hashable:
|
|
strIdx, ok := idx.(strObject)
|
|
if !ok {
|
|
return nil, errors.New("expected string for hashable")
|
|
}
|
|
val = v.Value(string(strIdx))
|
|
default:
|
|
return val, nil
|
|
}
|
|
}
|
|
|
|
return val, nil
|
|
}
|
|
|
|
func mapBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|
if err := args.expectArgn(2); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
inv, err := args.invokableArg(1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch t := args.args[0].(type) {
|
|
case listable:
|
|
l := t.Len()
|
|
newList := listObject{}
|
|
for i := 0; i < l; i++ {
|
|
v := t.Index(i)
|
|
m, err := inv.invoke(ctx, args.fork([]object{v}))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
newList = append(newList, m)
|
|
}
|
|
return newList, nil
|
|
}
|
|
return nil, errors.New("expected listable")
|
|
}
|
|
|
|
func firstBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|
if err := args.expectArgn(1); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch t := args.args[0].(type) {
|
|
case listable:
|
|
if t.Len() == 0 {
|
|
return nil, nil
|
|
}
|
|
return t.Index(0), nil
|
|
}
|
|
return nil, errors.New("expected listable")
|
|
}
|
|
|
|
/*
|
|
type fileLinesStream struct {
|
|
filename string
|
|
f *os.File
|
|
scnr *bufio.Scanner
|
|
}
|
|
|
|
func (f *fileLinesStream) String() string {
|
|
return fmt.Sprintf("fileLinesStream{file: %v}", f.filename)
|
|
}
|
|
|
|
func (f *fileLinesStream) Truthy() bool {
|
|
return true // ??
|
|
}
|
|
|
|
func (f *fileLinesStream) next() (object, error) {
|
|
var err error
|
|
|
|
// We open the file on the first pull. That way, an unconsumed stream won't result in a FD leak
|
|
if f.f == nil {
|
|
f.f, err = os.Open(f.filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
f.scnr = bufio.NewScanner(f.f)
|
|
}
|
|
|
|
if f.scnr.Scan() {
|
|
return strObject(f.scnr.Text()), nil
|
|
}
|
|
if f.scnr.Err() == nil {
|
|
return nil, io.EOF
|
|
}
|
|
return nil, f.scnr.Err()
|
|
}
|
|
|
|
func (f *fileLinesStream) close() error {
|
|
if f.f != nil {
|
|
return f.f.Close()
|
|
}
|
|
return nil
|
|
}
|
|
*/
|
|
|
|
func ifBuiltin(ctx context.Context, args macroArgs) (object, error) {
|
|
if args.nargs() < 2 {
|
|
return nil, errors.New("need at least 2 arguments")
|
|
}
|
|
|
|
if guard, err := args.evalArg(ctx, 0); err == nil && isTruthy(guard) {
|
|
return args.evalBlock(ctx, 1, nil, false)
|
|
} else if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
args.shift(2)
|
|
for args.identIs(ctx, 0, "elif") {
|
|
args.shift(1)
|
|
|
|
if args.nargs() < 2 {
|
|
return nil, errors.New("need at least 2 arguments")
|
|
}
|
|
|
|
if guard, err := args.evalArg(ctx, 0); err == nil && isTruthy(guard) {
|
|
return args.evalBlock(ctx, 1, nil, false)
|
|
} else if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
args.shift(2)
|
|
}
|
|
|
|
if args.identIs(ctx, 0, "else") && args.nargs() > 1 {
|
|
return args.evalBlock(ctx, 1, nil, false)
|
|
} else if args.nargs() == 0 {
|
|
// no elif or else
|
|
return nil, nil
|
|
}
|
|
|
|
return nil, errors.New("malformed if-elif-else")
|
|
}
|
|
|
|
func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) {
|
|
var (
|
|
items object
|
|
blockIdx int
|
|
err error
|
|
)
|
|
if !args.hasPipe {
|
|
if args.nargs() < 2 {
|
|
return nil, errors.New("need at least 2 arguments")
|
|
}
|
|
|
|
items, err = args.evalArg(ctx, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
blockIdx = 1
|
|
} else {
|
|
if args.nargs() < 1 {
|
|
return nil, errors.New("need at least 1 argument")
|
|
}
|
|
items = args.pipeArg
|
|
blockIdx = 0
|
|
}
|
|
|
|
var last object
|
|
|
|
switch t := items.(type) {
|
|
case listable:
|
|
l := t.Len()
|
|
for i := 0; i < l; i++ {
|
|
v := t.Index(i)
|
|
last, err = args.evalBlock(ctx, blockIdx, []object{v}, true) // TO INCLUDE: the index
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
case hashObject:
|
|
for k, v := range t {
|
|
last, err = args.evalBlock(ctx, blockIdx, []object{strObject(k), v}, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
|
|
return last, nil
|
|
}
|
|
|
|
func procBuiltin(ctx context.Context, args macroArgs) (object, error) {
|
|
if args.nargs() < 1 {
|
|
return nil, errors.New("need at least one arguments")
|
|
}
|
|
|
|
var procName string
|
|
if args.nargs() == 2 {
|
|
name, ok := args.shiftIdent(ctx)
|
|
if !ok {
|
|
return nil, errors.New("malformed procedure: expected identifier as first argument")
|
|
}
|
|
procName = name
|
|
}
|
|
|
|
block, err := args.evalArg(ctx, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
blockObj, ok := block.(blockObject)
|
|
if !ok {
|
|
return nil, fmt.Errorf("malformed procedure: expected block object, was %v", block.String())
|
|
}
|
|
|
|
obj := procObject{args.eval, args.ec, blockObj.block}
|
|
if procName != "" {
|
|
args.ec.addCmd(procName, obj)
|
|
}
|
|
return obj, nil
|
|
}
|
|
|
|
type procObject struct {
|
|
eval evaluator
|
|
ec *evalCtx
|
|
block *astBlock
|
|
}
|
|
|
|
func (b procObject) String() string {
|
|
return "(proc)"
|
|
}
|
|
|
|
func (b procObject) Truthy() bool {
|
|
return true
|
|
}
|
|
|
|
func (b procObject) invoke(ctx context.Context, args invocationArgs) (object, error) {
|
|
newEc := b.ec.fork()
|
|
|
|
for i, name := range b.block.Names {
|
|
if i < len(args.args) {
|
|
newEc.setVar(name, args.args[i])
|
|
} else {
|
|
newEc.setVar(name, nil)
|
|
}
|
|
}
|
|
|
|
return b.eval.evalBlock(ctx, newEc, b.block)
|
|
}
|