Go: move the stdlib to a different file
This commit is contained in:
		
							
								
								
									
										495
									
								
								ell/ell.go
									
									
									
									
									
								
							
							
						
						
									
										495
									
								
								ell/ell.go
									
									
									
									
									
								
							@@ -20,11 +20,6 @@ import (
 | 
				
			|||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
 | 
					 | 
				
			||||||
	// standard library
 | 
					 | 
				
			||||||
	"bytes"
 | 
					 | 
				
			||||||
	"os"
 | 
					 | 
				
			||||||
	"os/exec"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// --- Values ------------------------------------------------------------------
 | 
					// --- Values ------------------------------------------------------------------
 | 
				
			||||||
@@ -717,493 +712,3 @@ func (ell *Ell) EvalBlock(body []V, args []V) (result []V, ok bool) {
 | 
				
			|||||||
	ell.scopes = ell.scopes[:len(ell.scopes)-1]
 | 
						ell.scopes = ell.scopes[:len(ell.scopes)-1]
 | 
				
			||||||
	return result, ok
 | 
						return result, ok
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
// --- Standard library --------------------------------------------------------
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// EvalAny evaluates any value and appends to the result.
 | 
					 | 
				
			||||||
func EvalAny(ell *Ell, body *V, arg *V) (result []V, ok bool) {
 | 
					 | 
				
			||||||
	if body.Type == VTypeString {
 | 
					 | 
				
			||||||
		return []V{*body}, true
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	var args []V
 | 
					 | 
				
			||||||
	if arg != nil {
 | 
					 | 
				
			||||||
		args = append(args, *arg.Clone())
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if res, ok := ell.EvalBlock(body.List, args); ok {
 | 
					 | 
				
			||||||
		return res, true
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil, false
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// NewNumber creates a new string value containing a number.
 | 
					 | 
				
			||||||
func NewNumber(n float64) *V {
 | 
					 | 
				
			||||||
	s := fmt.Sprintf("%f", n)
 | 
					 | 
				
			||||||
	i := len(s)
 | 
					 | 
				
			||||||
	for i > 0 && s[i-1] == '0' {
 | 
					 | 
				
			||||||
		i--
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if s[i-1] == '.' {
 | 
					 | 
				
			||||||
		i--
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return NewString(s[:i])
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Truthy decides whether any value is logically true.
 | 
					 | 
				
			||||||
func Truthy(v *V) bool {
 | 
					 | 
				
			||||||
	return v != nil && (len(v.List) > 0 || len(v.String) > 0)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// NewBoolean creates a new string value copying the boolean's truthiness.
 | 
					 | 
				
			||||||
func NewBoolean(b bool) *V {
 | 
					 | 
				
			||||||
	if b {
 | 
					 | 
				
			||||||
		return NewString("1")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return NewString("")
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func fnLocal(ell *Ell, args []V) (result []V, ok bool) {
 | 
					 | 
				
			||||||
	if len(args) == 0 || args[0].Type != VTypeList {
 | 
					 | 
				
			||||||
		return ell.Errorf("first argument must be a list")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Duplicates or non-strings don't really matter to us, user's problem.
 | 
					 | 
				
			||||||
	scope := ell.scopes[len(ell.scopes)-1]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	values := args[1:]
 | 
					 | 
				
			||||||
	for _, name := range args[0].List {
 | 
					 | 
				
			||||||
		if len(values) > 0 {
 | 
					 | 
				
			||||||
			scope[name.String] = *values[0].Clone()
 | 
					 | 
				
			||||||
			values = values[1:]
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil, true
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func fnSet(ell *Ell, args []V) (result []V, ok bool) {
 | 
					 | 
				
			||||||
	if len(args) == 0 || args[0].Type != VTypeString {
 | 
					 | 
				
			||||||
		return ell.Errorf("first argument must be string")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if len(args) > 1 {
 | 
					 | 
				
			||||||
		result = []V{*args[1].Clone()}
 | 
					 | 
				
			||||||
		ell.Set(args[0].String, &result[0])
 | 
					 | 
				
			||||||
		return result, true
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// We return an empty list for a nil value.
 | 
					 | 
				
			||||||
	if v := ell.Get(args[0].String); v != nil {
 | 
					 | 
				
			||||||
		result = []V{*v.Clone()}
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		result = []V{*NewList(nil)}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return result, true
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func fnList(ell *Ell, args []V) (result []V, ok bool) {
 | 
					 | 
				
			||||||
	return []V{*NewList(args)}, true
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func fnValues(ell *Ell, args []V) (result []V, ok bool) {
 | 
					 | 
				
			||||||
	return args, true
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func fnIf(ell *Ell, args []V) (result []V, ok bool) {
 | 
					 | 
				
			||||||
	var cond, body, keyword int
 | 
					 | 
				
			||||||
	for cond = 0; ; cond = keyword + 1 {
 | 
					 | 
				
			||||||
		if cond >= len(args) {
 | 
					 | 
				
			||||||
			return ell.Errorf("missing condition")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if body = cond + 1; body >= len(args) {
 | 
					 | 
				
			||||||
			return ell.Errorf("missing body")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		var res []V
 | 
					 | 
				
			||||||
		if res, ok = EvalAny(ell, &args[cond], nil); !ok {
 | 
					 | 
				
			||||||
			return nil, false
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if len(res) > 0 && Truthy(&res[0]) {
 | 
					 | 
				
			||||||
			return EvalAny(ell, &args[body], nil)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if keyword = body + 1; keyword >= len(args) {
 | 
					 | 
				
			||||||
			break
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if args[keyword].Type != VTypeString {
 | 
					 | 
				
			||||||
			return ell.Errorf("expected keyword, got list")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		switch kw := args[keyword].String; kw {
 | 
					 | 
				
			||||||
		case "else":
 | 
					 | 
				
			||||||
			if body = keyword + 1; body >= len(args) {
 | 
					 | 
				
			||||||
				return ell.Errorf("missing body")
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			return EvalAny(ell, &args[body], nil)
 | 
					 | 
				
			||||||
		case "elif":
 | 
					 | 
				
			||||||
		default:
 | 
					 | 
				
			||||||
			return ell.Errorf("invalid keyword: %s", kw)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil, true
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func fnMap(ell *Ell, args []V) (result []V, ok bool) {
 | 
					 | 
				
			||||||
	if len(args) < 1 {
 | 
					 | 
				
			||||||
		return ell.Errorf("first argument must be a function")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if len(args) < 2 || args[0].Type != VTypeList {
 | 
					 | 
				
			||||||
		return ell.Errorf("second argument must be a list")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	body, values := &args[0], &args[1]
 | 
					 | 
				
			||||||
	for _, v := range values.List {
 | 
					 | 
				
			||||||
		res, ok := EvalAny(ell, body, &v)
 | 
					 | 
				
			||||||
		if !ok {
 | 
					 | 
				
			||||||
			return nil, false
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		result = append(result, res...)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return []V{*NewList(result)}, true
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func fnPrint(ell *Ell, args []V) (result []V, ok bool) {
 | 
					 | 
				
			||||||
	for _, arg := range args {
 | 
					 | 
				
			||||||
		if arg.Type != VTypeString {
 | 
					 | 
				
			||||||
			PrintV(os.Stdout, &arg)
 | 
					 | 
				
			||||||
		} else if _, err := os.Stdout.WriteString(arg.String); err != nil {
 | 
					 | 
				
			||||||
			return ell.Errorf("write failed: %s", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil, true
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func fnCat(ell *Ell, args []V) (result []V, ok bool) {
 | 
					 | 
				
			||||||
	buf := bytes.NewBuffer(nil)
 | 
					 | 
				
			||||||
	for _, arg := range args {
 | 
					 | 
				
			||||||
		if arg.Type != VTypeString {
 | 
					 | 
				
			||||||
			PrintV(buf, &arg)
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			buf.WriteString(arg.String)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return []V{*NewString(buf.String())}, true
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func fnSystem(ell *Ell, args []V) (result []V, ok bool) {
 | 
					 | 
				
			||||||
	var argv []string
 | 
					 | 
				
			||||||
	for _, arg := range args {
 | 
					 | 
				
			||||||
		if arg.Type != VTypeString {
 | 
					 | 
				
			||||||
			return ell.Errorf("arguments must be strings")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		argv = append(argv, arg.String)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if len(argv) == 0 {
 | 
					 | 
				
			||||||
		return ell.Errorf("command name required")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	cmd := exec.Command(argv[0], argv[1:]...)
 | 
					 | 
				
			||||||
	cmd.Stdin = os.Stdin
 | 
					 | 
				
			||||||
	cmd.Stdout = os.Stdout
 | 
					 | 
				
			||||||
	cmd.Stderr = os.Stderr
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Approximation of system(3) return value to match C ell at least a bit.
 | 
					 | 
				
			||||||
	if err := cmd.Run(); err == nil {
 | 
					 | 
				
			||||||
		return []V{*NewNumber(0)}, true
 | 
					 | 
				
			||||||
	} else if _, ok := err.(*exec.Error); ok {
 | 
					 | 
				
			||||||
		return ell.Errorf("%s", err)
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		return []V{*NewNumber(1)}, true
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func fnParse(ell *Ell, args []V) (result []V, ok bool) {
 | 
					 | 
				
			||||||
	if len(args) < 1 || args[0].Type != VTypeString {
 | 
					 | 
				
			||||||
		return ell.Errorf("first argument must be string")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	res, err := NewParser([]byte(args[0].String)).Run()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return ell.Errorf("%s", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return []V{*NewList(res)}, true
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func fnTry(ell *Ell, args []V) (result []V, ok bool) {
 | 
					 | 
				
			||||||
	var body, handler *V
 | 
					 | 
				
			||||||
	if len(args) < 1 {
 | 
					 | 
				
			||||||
		return ell.Errorf("first argument must be a function")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if len(args) < 2 {
 | 
					 | 
				
			||||||
		return ell.Errorf("second argument must be a function")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	body, handler = &args[0], &args[1]
 | 
					 | 
				
			||||||
	if result, ok = EvalAny(ell, body, nil); ok {
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	msg := NewString(ell.Error)
 | 
					 | 
				
			||||||
	ell.Error = ""
 | 
					 | 
				
			||||||
	return EvalAny(ell, handler, msg)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func fnThrow(ell *Ell, args []V) (result []V, ok bool) {
 | 
					 | 
				
			||||||
	if len(args) < 1 || args[0].Type != VTypeString {
 | 
					 | 
				
			||||||
		return ell.Errorf("first argument must be string")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return ell.Errorf("%s", args[0].String)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func fnPlus(ell *Ell, args []V) (result []V, ok bool) {
 | 
					 | 
				
			||||||
	res := 0.
 | 
					 | 
				
			||||||
	for _, arg := range args {
 | 
					 | 
				
			||||||
		if arg.Type != VTypeString {
 | 
					 | 
				
			||||||
			return ell.Errorf("arguments must be strings")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		var value float64
 | 
					 | 
				
			||||||
		if n, _ := fmt.Sscan(arg.String, &value); n < 1 {
 | 
					 | 
				
			||||||
			return ell.Errorf("invalid number: %s", arg.String)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		res += value
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return []V{*NewNumber(res)}, true
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func fnMinus(ell *Ell, args []V) (result []V, ok bool) {
 | 
					 | 
				
			||||||
	if len(args) < 1 || args[0].Type != VTypeString {
 | 
					 | 
				
			||||||
		return ell.Errorf("first argument must be string")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var res float64
 | 
					 | 
				
			||||||
	if n, _ := fmt.Sscan(args[0].String, &res); n < 1 {
 | 
					 | 
				
			||||||
		return ell.Errorf("invalid number: %f", args[0].String)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if len(args) == 1 {
 | 
					 | 
				
			||||||
		res = -res
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for _, arg := range args[1:] {
 | 
					 | 
				
			||||||
		if arg.Type != VTypeString {
 | 
					 | 
				
			||||||
			return ell.Errorf("arguments must be strings")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		var value float64
 | 
					 | 
				
			||||||
		if n, _ := fmt.Sscan(arg.String, &value); n < 1 {
 | 
					 | 
				
			||||||
			return ell.Errorf("invalid number: %f", arg.String)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		res -= value
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return []V{*NewNumber(res)}, true
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func fnMultiply(ell *Ell, args []V) (result []V, ok bool) {
 | 
					 | 
				
			||||||
	res := 1.
 | 
					 | 
				
			||||||
	for _, arg := range args {
 | 
					 | 
				
			||||||
		if arg.Type != VTypeString {
 | 
					 | 
				
			||||||
			return ell.Errorf("arguments must be strings")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		var value float64
 | 
					 | 
				
			||||||
		if n, _ := fmt.Sscan(arg.String, &value); n < 1 {
 | 
					 | 
				
			||||||
			return ell.Errorf("invalid number: %s", arg.String)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		res *= value
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return []V{*NewNumber(res)}, true
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func fnDivide(ell *Ell, args []V) (result []V, ok bool) {
 | 
					 | 
				
			||||||
	if len(args) < 1 || args[0].Type != VTypeString {
 | 
					 | 
				
			||||||
		return ell.Errorf("first argument must be string")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var res float64
 | 
					 | 
				
			||||||
	if n, _ := fmt.Sscan(args[0].String, &res); n < 1 {
 | 
					 | 
				
			||||||
		return ell.Errorf("invalid number: %f", args[0].String)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	for _, arg := range args[1:] {
 | 
					 | 
				
			||||||
		if arg.Type != VTypeString {
 | 
					 | 
				
			||||||
			return ell.Errorf("arguments must be strings")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		var value float64
 | 
					 | 
				
			||||||
		if n, _ := fmt.Sscan(arg.String, &value); n < 1 {
 | 
					 | 
				
			||||||
			return ell.Errorf("invalid number: %f", arg.String)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		res /= value
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return []V{*NewNumber(res)}, true
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func fnNot(ell *Ell, args []V) (result []V, ok bool) {
 | 
					 | 
				
			||||||
	if len(args) < 1 {
 | 
					 | 
				
			||||||
		return ell.Errorf("missing argument")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return []V{*NewBoolean(!Truthy(&args[0]))}, true
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func fnAnd(ell *Ell, args []V) (result []V, ok bool) {
 | 
					 | 
				
			||||||
	if args == nil {
 | 
					 | 
				
			||||||
		return []V{*NewBoolean(true)}, true
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	for _, arg := range args {
 | 
					 | 
				
			||||||
		result, ok = EvalAny(ell, &arg, nil)
 | 
					 | 
				
			||||||
		if !ok {
 | 
					 | 
				
			||||||
			return nil, false
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if len(result) < 1 || !Truthy(&result[0]) {
 | 
					 | 
				
			||||||
			return []V{*NewBoolean(false)}, true
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return result, true
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func fnOr(ell *Ell, args []V) (result []V, ok bool) {
 | 
					 | 
				
			||||||
	for _, arg := range args {
 | 
					 | 
				
			||||||
		result, ok = EvalAny(ell, &arg, nil)
 | 
					 | 
				
			||||||
		if !ok {
 | 
					 | 
				
			||||||
			return nil, false
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if len(result) > 0 && Truthy(&result[0]) {
 | 
					 | 
				
			||||||
			return result, true
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return []V{*NewBoolean(false)}, true
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func fnEq(ell *Ell, args []V) (result []V, ok bool) {
 | 
					 | 
				
			||||||
	if len(args) < 1 || args[0].Type != VTypeString {
 | 
					 | 
				
			||||||
		return ell.Errorf("first argument must be string")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	etalon, res := args[0].String, true
 | 
					 | 
				
			||||||
	for _, arg := range args[1:] {
 | 
					 | 
				
			||||||
		if arg.Type != VTypeString {
 | 
					 | 
				
			||||||
			return ell.Errorf("arguments must be strings")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if res = etalon == arg.String; !res {
 | 
					 | 
				
			||||||
			break
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return []V{*NewBoolean(res)}, true
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func fnLt(ell *Ell, args []V) (result []V, ok bool) {
 | 
					 | 
				
			||||||
	if len(args) < 1 || args[0].Type != VTypeString {
 | 
					 | 
				
			||||||
		return ell.Errorf("first argument must be string")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	etalon, res := args[0].String, true
 | 
					 | 
				
			||||||
	for _, arg := range args[1:] {
 | 
					 | 
				
			||||||
		if arg.Type != VTypeString {
 | 
					 | 
				
			||||||
			return ell.Errorf("arguments must be strings")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if res = etalon < arg.String; !res {
 | 
					 | 
				
			||||||
			break
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		etalon = arg.String
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return []V{*NewBoolean(res)}, true
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func fnEquals(ell *Ell, args []V) (result []V, ok bool) {
 | 
					 | 
				
			||||||
	if len(args) < 1 || args[0].Type != VTypeString {
 | 
					 | 
				
			||||||
		return ell.Errorf("first argument must be string")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	var first, second float64
 | 
					 | 
				
			||||||
	if n, _ := fmt.Sscan(args[0].String, &first); n < 1 {
 | 
					 | 
				
			||||||
		return ell.Errorf("invalid number: %f", args[0].String)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	res := true
 | 
					 | 
				
			||||||
	for _, arg := range args[1:] {
 | 
					 | 
				
			||||||
		if arg.Type != VTypeString {
 | 
					 | 
				
			||||||
			return ell.Errorf("arguments must be strings")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if n, _ := fmt.Sscan(arg.String, &second); n < 1 {
 | 
					 | 
				
			||||||
			return ell.Errorf("invalid number: %f", arg.String)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if res = first == second; !res {
 | 
					 | 
				
			||||||
			break
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		first = second
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return []V{*NewBoolean(res)}, true
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func fnLess(ell *Ell, args []V) (result []V, ok bool) {
 | 
					 | 
				
			||||||
	if len(args) < 1 || args[0].Type != VTypeString {
 | 
					 | 
				
			||||||
		return ell.Errorf("first argument must be string")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	var first, second float64
 | 
					 | 
				
			||||||
	if n, _ := fmt.Sscan(args[0].String, &first); n < 1 {
 | 
					 | 
				
			||||||
		return ell.Errorf("invalid number: %f", args[0].String)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	res := true
 | 
					 | 
				
			||||||
	for _, arg := range args[1:] {
 | 
					 | 
				
			||||||
		if arg.Type != VTypeString {
 | 
					 | 
				
			||||||
			return ell.Errorf("arguments must be strings")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if n, _ := fmt.Sscan(arg.String, &second); n < 1 {
 | 
					 | 
				
			||||||
			return ell.Errorf("invalid number: %f", arg.String)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if res = first < second; !res {
 | 
					 | 
				
			||||||
			break
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		first = second
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return []V{*NewBoolean(res)}, true
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var stdNative = map[string]Handler{
 | 
					 | 
				
			||||||
	"local":  fnLocal,
 | 
					 | 
				
			||||||
	"set":    fnSet,
 | 
					 | 
				
			||||||
	"list":   fnList,
 | 
					 | 
				
			||||||
	"values": fnValues,
 | 
					 | 
				
			||||||
	"if":     fnIf,
 | 
					 | 
				
			||||||
	"map":    fnMap,
 | 
					 | 
				
			||||||
	"print":  fnPrint,
 | 
					 | 
				
			||||||
	"..":     fnCat,
 | 
					 | 
				
			||||||
	"system": fnSystem,
 | 
					 | 
				
			||||||
	"parse":  fnParse,
 | 
					 | 
				
			||||||
	"try":    fnTry,
 | 
					 | 
				
			||||||
	"throw":  fnThrow,
 | 
					 | 
				
			||||||
	"+":      fnPlus,
 | 
					 | 
				
			||||||
	"-":      fnMinus,
 | 
					 | 
				
			||||||
	"*":      fnMultiply,
 | 
					 | 
				
			||||||
	"/":      fnDivide,
 | 
					 | 
				
			||||||
	"not":    fnNot,
 | 
					 | 
				
			||||||
	"and":    fnAnd,
 | 
					 | 
				
			||||||
	"or":     fnOr,
 | 
					 | 
				
			||||||
	"eq?":    fnEq,
 | 
					 | 
				
			||||||
	"lt?":    fnLt,
 | 
					 | 
				
			||||||
	"=":      fnEquals,
 | 
					 | 
				
			||||||
	"<":      fnLess,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var stdComposed = `
 | 
					 | 
				
			||||||
set unless { if (not (@1)) @2 }
 | 
					 | 
				
			||||||
set filter { local [_body _list] @1 @2;
 | 
					 | 
				
			||||||
 map { if (@_body @1) { @1 } } @_list }
 | 
					 | 
				
			||||||
set for { local [_list _body] @1 @2;
 | 
					 | 
				
			||||||
 try { map { @_body @1 } @_list } { if (ne? @1 _break) { throw @1 } } }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
set break { throw _break }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# TODO: we should be able to apply them to all arguments
 | 
					 | 
				
			||||||
set ne? { not (eq? @1 @2) }; set le? { ge? @2 @1 }
 | 
					 | 
				
			||||||
set ge? { not (lt? @1 @2) }; set gt? { lt? @2 @1 }
 | 
					 | 
				
			||||||
set <>  { not (= @1 @2)   }; set <=  { >= @2 @1  }
 | 
					 | 
				
			||||||
set >=  { not (< @1 @2)   }; set >   { <  @2 @1  }`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// StdInitialize initializes the ell standard library.
 | 
					 | 
				
			||||||
func StdInitialize(ell *Ell) bool {
 | 
					 | 
				
			||||||
	for name, handler := range stdNative {
 | 
					 | 
				
			||||||
		ell.Native[name] = handler
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	p := NewParser([]byte(stdComposed))
 | 
					 | 
				
			||||||
	program, err := p.Run()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return false
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	_, ok := ell.EvalBlock(program, nil)
 | 
					 | 
				
			||||||
	return ok
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										513
									
								
								ell/stdlib.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										513
									
								
								ell/stdlib.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,513 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					// Copyright (c) 2018, Přemysl Janouch <p@janouch.name>
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Permission to use, copy, modify, and/or distribute this software for any
 | 
				
			||||||
 | 
					// purpose with or without fee is hereby granted.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
				
			||||||
 | 
					// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 | 
				
			||||||
 | 
					// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 | 
				
			||||||
 | 
					// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
				
			||||||
 | 
					// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 | 
				
			||||||
 | 
					// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 | 
				
			||||||
 | 
					// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package ell
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"os/exec"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// --- Standard library --------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// EvalAny evaluates any value and appends to the result.
 | 
				
			||||||
 | 
					func EvalAny(ell *Ell, body *V, arg *V) (result []V, ok bool) {
 | 
				
			||||||
 | 
						if body.Type == VTypeString {
 | 
				
			||||||
 | 
							return []V{*body}, true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var args []V
 | 
				
			||||||
 | 
						if arg != nil {
 | 
				
			||||||
 | 
							args = append(args, *arg.Clone())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if res, ok := ell.EvalBlock(body.List, args); ok {
 | 
				
			||||||
 | 
							return res, true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil, false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewNumber creates a new string value containing a number.
 | 
				
			||||||
 | 
					func NewNumber(n float64) *V {
 | 
				
			||||||
 | 
						s := fmt.Sprintf("%f", n)
 | 
				
			||||||
 | 
						i := len(s)
 | 
				
			||||||
 | 
						for i > 0 && s[i-1] == '0' {
 | 
				
			||||||
 | 
							i--
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if s[i-1] == '.' {
 | 
				
			||||||
 | 
							i--
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return NewString(s[:i])
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Truthy decides whether any value is logically true.
 | 
				
			||||||
 | 
					func Truthy(v *V) bool {
 | 
				
			||||||
 | 
						return v != nil && (len(v.List) > 0 || len(v.String) > 0)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewBoolean creates a new string value copying the boolean's truthiness.
 | 
				
			||||||
 | 
					func NewBoolean(b bool) *V {
 | 
				
			||||||
 | 
						if b {
 | 
				
			||||||
 | 
							return NewString("1")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return NewString("")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fnLocal(ell *Ell, args []V) (result []V, ok bool) {
 | 
				
			||||||
 | 
						if len(args) == 0 || args[0].Type != VTypeList {
 | 
				
			||||||
 | 
							return ell.Errorf("first argument must be a list")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Duplicates or non-strings don't really matter to us, user's problem.
 | 
				
			||||||
 | 
						scope := ell.scopes[len(ell.scopes)-1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						values := args[1:]
 | 
				
			||||||
 | 
						for _, name := range args[0].List {
 | 
				
			||||||
 | 
							if len(values) > 0 {
 | 
				
			||||||
 | 
								scope[name.String] = *values[0].Clone()
 | 
				
			||||||
 | 
								values = values[1:]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil, true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fnSet(ell *Ell, args []V) (result []V, ok bool) {
 | 
				
			||||||
 | 
						if len(args) == 0 || args[0].Type != VTypeString {
 | 
				
			||||||
 | 
							return ell.Errorf("first argument must be string")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(args) > 1 {
 | 
				
			||||||
 | 
							result = []V{*args[1].Clone()}
 | 
				
			||||||
 | 
							ell.Set(args[0].String, &result[0])
 | 
				
			||||||
 | 
							return result, true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// We return an empty list for a nil value.
 | 
				
			||||||
 | 
						if v := ell.Get(args[0].String); v != nil {
 | 
				
			||||||
 | 
							result = []V{*v.Clone()}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							result = []V{*NewList(nil)}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return result, true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fnList(ell *Ell, args []V) (result []V, ok bool) {
 | 
				
			||||||
 | 
						return []V{*NewList(args)}, true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fnValues(ell *Ell, args []V) (result []V, ok bool) {
 | 
				
			||||||
 | 
						return args, true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fnIf(ell *Ell, args []V) (result []V, ok bool) {
 | 
				
			||||||
 | 
						var cond, body, keyword int
 | 
				
			||||||
 | 
						for cond = 0; ; cond = keyword + 1 {
 | 
				
			||||||
 | 
							if cond >= len(args) {
 | 
				
			||||||
 | 
								return ell.Errorf("missing condition")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if body = cond + 1; body >= len(args) {
 | 
				
			||||||
 | 
								return ell.Errorf("missing body")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var res []V
 | 
				
			||||||
 | 
							if res, ok = EvalAny(ell, &args[cond], nil); !ok {
 | 
				
			||||||
 | 
								return nil, false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if len(res) > 0 && Truthy(&res[0]) {
 | 
				
			||||||
 | 
								return EvalAny(ell, &args[body], nil)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if keyword = body + 1; keyword >= len(args) {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if args[keyword].Type != VTypeString {
 | 
				
			||||||
 | 
								return ell.Errorf("expected keyword, got list")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							switch kw := args[keyword].String; kw {
 | 
				
			||||||
 | 
							case "else":
 | 
				
			||||||
 | 
								if body = keyword + 1; body >= len(args) {
 | 
				
			||||||
 | 
									return ell.Errorf("missing body")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return EvalAny(ell, &args[body], nil)
 | 
				
			||||||
 | 
							case "elif":
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								return ell.Errorf("invalid keyword: %s", kw)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil, true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fnMap(ell *Ell, args []V) (result []V, ok bool) {
 | 
				
			||||||
 | 
						if len(args) < 1 {
 | 
				
			||||||
 | 
							return ell.Errorf("first argument must be a function")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(args) < 2 || args[0].Type != VTypeList {
 | 
				
			||||||
 | 
							return ell.Errorf("second argument must be a list")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						body, values := &args[0], &args[1]
 | 
				
			||||||
 | 
						for _, v := range values.List {
 | 
				
			||||||
 | 
							res, ok := EvalAny(ell, body, &v)
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								return nil, false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							result = append(result, res...)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return []V{*NewList(result)}, true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fnPrint(ell *Ell, args []V) (result []V, ok bool) {
 | 
				
			||||||
 | 
						for _, arg := range args {
 | 
				
			||||||
 | 
							if arg.Type != VTypeString {
 | 
				
			||||||
 | 
								PrintV(os.Stdout, &arg)
 | 
				
			||||||
 | 
							} else if _, err := os.Stdout.WriteString(arg.String); err != nil {
 | 
				
			||||||
 | 
								return ell.Errorf("write failed: %s", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil, true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fnCat(ell *Ell, args []V) (result []V, ok bool) {
 | 
				
			||||||
 | 
						buf := bytes.NewBuffer(nil)
 | 
				
			||||||
 | 
						for _, arg := range args {
 | 
				
			||||||
 | 
							if arg.Type != VTypeString {
 | 
				
			||||||
 | 
								PrintV(buf, &arg)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								buf.WriteString(arg.String)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return []V{*NewString(buf.String())}, true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fnSystem(ell *Ell, args []V) (result []V, ok bool) {
 | 
				
			||||||
 | 
						var argv []string
 | 
				
			||||||
 | 
						for _, arg := range args {
 | 
				
			||||||
 | 
							if arg.Type != VTypeString {
 | 
				
			||||||
 | 
								return ell.Errorf("arguments must be strings")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							argv = append(argv, arg.String)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(argv) == 0 {
 | 
				
			||||||
 | 
							return ell.Errorf("command name required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cmd := exec.Command(argv[0], argv[1:]...)
 | 
				
			||||||
 | 
						cmd.Stdin = os.Stdin
 | 
				
			||||||
 | 
						cmd.Stdout = os.Stdout
 | 
				
			||||||
 | 
						cmd.Stderr = os.Stderr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Approximation of system(3) return value to match C ell at least a bit.
 | 
				
			||||||
 | 
						if err := cmd.Run(); err == nil {
 | 
				
			||||||
 | 
							return []V{*NewNumber(0)}, true
 | 
				
			||||||
 | 
						} else if _, ok := err.(*exec.Error); ok {
 | 
				
			||||||
 | 
							return ell.Errorf("%s", err)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return []V{*NewNumber(1)}, true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fnParse(ell *Ell, args []V) (result []V, ok bool) {
 | 
				
			||||||
 | 
						if len(args) < 1 || args[0].Type != VTypeString {
 | 
				
			||||||
 | 
							return ell.Errorf("first argument must be string")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res, err := NewParser([]byte(args[0].String)).Run()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return ell.Errorf("%s", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return []V{*NewList(res)}, true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fnTry(ell *Ell, args []V) (result []V, ok bool) {
 | 
				
			||||||
 | 
						var body, handler *V
 | 
				
			||||||
 | 
						if len(args) < 1 {
 | 
				
			||||||
 | 
							return ell.Errorf("first argument must be a function")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(args) < 2 {
 | 
				
			||||||
 | 
							return ell.Errorf("second argument must be a function")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						body, handler = &args[0], &args[1]
 | 
				
			||||||
 | 
						if result, ok = EvalAny(ell, body, nil); ok {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						msg := NewString(ell.Error)
 | 
				
			||||||
 | 
						ell.Error = ""
 | 
				
			||||||
 | 
						return EvalAny(ell, handler, msg)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fnThrow(ell *Ell, args []V) (result []V, ok bool) {
 | 
				
			||||||
 | 
						if len(args) < 1 || args[0].Type != VTypeString {
 | 
				
			||||||
 | 
							return ell.Errorf("first argument must be string")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ell.Errorf("%s", args[0].String)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fnPlus(ell *Ell, args []V) (result []V, ok bool) {
 | 
				
			||||||
 | 
						res := 0.
 | 
				
			||||||
 | 
						for _, arg := range args {
 | 
				
			||||||
 | 
							if arg.Type != VTypeString {
 | 
				
			||||||
 | 
								return ell.Errorf("arguments must be strings")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							var value float64
 | 
				
			||||||
 | 
							if n, _ := fmt.Sscan(arg.String, &value); n < 1 {
 | 
				
			||||||
 | 
								return ell.Errorf("invalid number: %s", arg.String)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							res += value
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return []V{*NewNumber(res)}, true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fnMinus(ell *Ell, args []V) (result []V, ok bool) {
 | 
				
			||||||
 | 
						if len(args) < 1 || args[0].Type != VTypeString {
 | 
				
			||||||
 | 
							return ell.Errorf("first argument must be string")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var res float64
 | 
				
			||||||
 | 
						if n, _ := fmt.Sscan(args[0].String, &res); n < 1 {
 | 
				
			||||||
 | 
							return ell.Errorf("invalid number: %f", args[0].String)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(args) == 1 {
 | 
				
			||||||
 | 
							res = -res
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, arg := range args[1:] {
 | 
				
			||||||
 | 
							if arg.Type != VTypeString {
 | 
				
			||||||
 | 
								return ell.Errorf("arguments must be strings")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							var value float64
 | 
				
			||||||
 | 
							if n, _ := fmt.Sscan(arg.String, &value); n < 1 {
 | 
				
			||||||
 | 
								return ell.Errorf("invalid number: %f", arg.String)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							res -= value
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return []V{*NewNumber(res)}, true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fnMultiply(ell *Ell, args []V) (result []V, ok bool) {
 | 
				
			||||||
 | 
						res := 1.
 | 
				
			||||||
 | 
						for _, arg := range args {
 | 
				
			||||||
 | 
							if arg.Type != VTypeString {
 | 
				
			||||||
 | 
								return ell.Errorf("arguments must be strings")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							var value float64
 | 
				
			||||||
 | 
							if n, _ := fmt.Sscan(arg.String, &value); n < 1 {
 | 
				
			||||||
 | 
								return ell.Errorf("invalid number: %s", arg.String)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							res *= value
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return []V{*NewNumber(res)}, true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fnDivide(ell *Ell, args []V) (result []V, ok bool) {
 | 
				
			||||||
 | 
						if len(args) < 1 || args[0].Type != VTypeString {
 | 
				
			||||||
 | 
							return ell.Errorf("first argument must be string")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var res float64
 | 
				
			||||||
 | 
						if n, _ := fmt.Sscan(args[0].String, &res); n < 1 {
 | 
				
			||||||
 | 
							return ell.Errorf("invalid number: %f", args[0].String)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, arg := range args[1:] {
 | 
				
			||||||
 | 
							if arg.Type != VTypeString {
 | 
				
			||||||
 | 
								return ell.Errorf("arguments must be strings")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							var value float64
 | 
				
			||||||
 | 
							if n, _ := fmt.Sscan(arg.String, &value); n < 1 {
 | 
				
			||||||
 | 
								return ell.Errorf("invalid number: %f", arg.String)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							res /= value
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return []V{*NewNumber(res)}, true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fnNot(ell *Ell, args []V) (result []V, ok bool) {
 | 
				
			||||||
 | 
						if len(args) < 1 {
 | 
				
			||||||
 | 
							return ell.Errorf("missing argument")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return []V{*NewBoolean(!Truthy(&args[0]))}, true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fnAnd(ell *Ell, args []V) (result []V, ok bool) {
 | 
				
			||||||
 | 
						if args == nil {
 | 
				
			||||||
 | 
							return []V{*NewBoolean(true)}, true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, arg := range args {
 | 
				
			||||||
 | 
							result, ok = EvalAny(ell, &arg, nil)
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								return nil, false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if len(result) < 1 || !Truthy(&result[0]) {
 | 
				
			||||||
 | 
								return []V{*NewBoolean(false)}, true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return result, true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fnOr(ell *Ell, args []V) (result []V, ok bool) {
 | 
				
			||||||
 | 
						for _, arg := range args {
 | 
				
			||||||
 | 
							result, ok = EvalAny(ell, &arg, nil)
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								return nil, false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if len(result) > 0 && Truthy(&result[0]) {
 | 
				
			||||||
 | 
								return result, true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return []V{*NewBoolean(false)}, true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fnEq(ell *Ell, args []V) (result []V, ok bool) {
 | 
				
			||||||
 | 
						if len(args) < 1 || args[0].Type != VTypeString {
 | 
				
			||||||
 | 
							return ell.Errorf("first argument must be string")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						etalon, res := args[0].String, true
 | 
				
			||||||
 | 
						for _, arg := range args[1:] {
 | 
				
			||||||
 | 
							if arg.Type != VTypeString {
 | 
				
			||||||
 | 
								return ell.Errorf("arguments must be strings")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if res = etalon == arg.String; !res {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return []V{*NewBoolean(res)}, true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fnLt(ell *Ell, args []V) (result []V, ok bool) {
 | 
				
			||||||
 | 
						if len(args) < 1 || args[0].Type != VTypeString {
 | 
				
			||||||
 | 
							return ell.Errorf("first argument must be string")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						etalon, res := args[0].String, true
 | 
				
			||||||
 | 
						for _, arg := range args[1:] {
 | 
				
			||||||
 | 
							if arg.Type != VTypeString {
 | 
				
			||||||
 | 
								return ell.Errorf("arguments must be strings")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if res = etalon < arg.String; !res {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							etalon = arg.String
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return []V{*NewBoolean(res)}, true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fnEquals(ell *Ell, args []V) (result []V, ok bool) {
 | 
				
			||||||
 | 
						if len(args) < 1 || args[0].Type != VTypeString {
 | 
				
			||||||
 | 
							return ell.Errorf("first argument must be string")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var first, second float64
 | 
				
			||||||
 | 
						if n, _ := fmt.Sscan(args[0].String, &first); n < 1 {
 | 
				
			||||||
 | 
							return ell.Errorf("invalid number: %f", args[0].String)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						res := true
 | 
				
			||||||
 | 
						for _, arg := range args[1:] {
 | 
				
			||||||
 | 
							if arg.Type != VTypeString {
 | 
				
			||||||
 | 
								return ell.Errorf("arguments must be strings")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if n, _ := fmt.Sscan(arg.String, &second); n < 1 {
 | 
				
			||||||
 | 
								return ell.Errorf("invalid number: %f", arg.String)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if res = first == second; !res {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							first = second
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return []V{*NewBoolean(res)}, true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fnLess(ell *Ell, args []V) (result []V, ok bool) {
 | 
				
			||||||
 | 
						if len(args) < 1 || args[0].Type != VTypeString {
 | 
				
			||||||
 | 
							return ell.Errorf("first argument must be string")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var first, second float64
 | 
				
			||||||
 | 
						if n, _ := fmt.Sscan(args[0].String, &first); n < 1 {
 | 
				
			||||||
 | 
							return ell.Errorf("invalid number: %f", args[0].String)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						res := true
 | 
				
			||||||
 | 
						for _, arg := range args[1:] {
 | 
				
			||||||
 | 
							if arg.Type != VTypeString {
 | 
				
			||||||
 | 
								return ell.Errorf("arguments must be strings")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if n, _ := fmt.Sscan(arg.String, &second); n < 1 {
 | 
				
			||||||
 | 
								return ell.Errorf("invalid number: %f", arg.String)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if res = first < second; !res {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							first = second
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return []V{*NewBoolean(res)}, true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var stdNative = map[string]Handler{
 | 
				
			||||||
 | 
						"local":  fnLocal,
 | 
				
			||||||
 | 
						"set":    fnSet,
 | 
				
			||||||
 | 
						"list":   fnList,
 | 
				
			||||||
 | 
						"values": fnValues,
 | 
				
			||||||
 | 
						"if":     fnIf,
 | 
				
			||||||
 | 
						"map":    fnMap,
 | 
				
			||||||
 | 
						"print":  fnPrint,
 | 
				
			||||||
 | 
						"..":     fnCat,
 | 
				
			||||||
 | 
						"system": fnSystem,
 | 
				
			||||||
 | 
						"parse":  fnParse,
 | 
				
			||||||
 | 
						"try":    fnTry,
 | 
				
			||||||
 | 
						"throw":  fnThrow,
 | 
				
			||||||
 | 
						"+":      fnPlus,
 | 
				
			||||||
 | 
						"-":      fnMinus,
 | 
				
			||||||
 | 
						"*":      fnMultiply,
 | 
				
			||||||
 | 
						"/":      fnDivide,
 | 
				
			||||||
 | 
						"not":    fnNot,
 | 
				
			||||||
 | 
						"and":    fnAnd,
 | 
				
			||||||
 | 
						"or":     fnOr,
 | 
				
			||||||
 | 
						"eq?":    fnEq,
 | 
				
			||||||
 | 
						"lt?":    fnLt,
 | 
				
			||||||
 | 
						"=":      fnEquals,
 | 
				
			||||||
 | 
						"<":      fnLess,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var stdComposed = `
 | 
				
			||||||
 | 
					set unless { if (not (@1)) @2 }
 | 
				
			||||||
 | 
					set filter { local [_body _list] @1 @2;
 | 
				
			||||||
 | 
					 map { if (@_body @1) { @1 } } @_list }
 | 
				
			||||||
 | 
					set for { local [_list _body] @1 @2;
 | 
				
			||||||
 | 
					 try { map { @_body @1 } @_list } { if (ne? @1 _break) { throw @1 } } }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					set break { throw _break }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# TODO: we should be able to apply them to all arguments
 | 
				
			||||||
 | 
					set ne? { not (eq? @1 @2) }; set le? { ge? @2 @1 }
 | 
				
			||||||
 | 
					set ge? { not (lt? @1 @2) }; set gt? { lt? @2 @1 }
 | 
				
			||||||
 | 
					set <>  { not (= @1 @2)   }; set <=  { >= @2 @1  }
 | 
				
			||||||
 | 
					set >=  { not (< @1 @2)   }; set >   { <  @2 @1  }`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// StdInitialize initializes the ell standard library.
 | 
				
			||||||
 | 
					func StdInitialize(ell *Ell) bool {
 | 
				
			||||||
 | 
						for name, handler := range stdNative {
 | 
				
			||||||
 | 
							ell.Native[name] = handler
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						p := NewParser([]byte(stdComposed))
 | 
				
			||||||
 | 
						program, err := p.Run()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, ok := ell.EvalBlock(program, nil)
 | 
				
			||||||
 | 
						return ok
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user