aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ell/ell.go495
-rw-r--r--ell/stdlib.go513
2 files changed, 513 insertions, 495 deletions
diff --git a/ell/ell.go b/ell/ell.go
index af4c407..7995a86 100644
--- a/ell/ell.go
+++ b/ell/ell.go
@@ -20,11 +20,6 @@ import (
"errors"
"fmt"
"io"
-
- // standard library
- "bytes"
- "os"
- "os/exec"
)
// --- 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]
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
-}
diff --git a/ell/stdlib.go b/ell/stdlib.go
new file mode 100644
index 0000000..f91e721
--- /dev/null
+++ b/ell/stdlib.go
@@ -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
+}