Compare commits
No commits in common. "728977d241466dc607f7a7a057375d3dca7ed556" and "f751975cfd967c717473fea400d926a4c9f8beb1" have entirely different histories.
728977d241
...
f751975cfd
@ -59,7 +59,7 @@ When evaluating a command, the first argument is typically a string with its
|
|||||||
name and it is resolved as if `set` was called on it. Lists are left for
|
name and it is resolved as if `set` was called on it. Lists are left for
|
||||||
execution as they are.
|
execution as they are.
|
||||||
|
|
||||||
The last expression in a block is the block's return value.
|
The last expression in a block is the return value.
|
||||||
|
|
||||||
Special Forms
|
Special Forms
|
||||||
-------------
|
-------------
|
||||||
@ -172,16 +172,13 @@ Install development packages for GNU Readline to get a REPL for toying around:
|
|||||||
$ make repl
|
$ make repl
|
||||||
$ ./repl
|
$ ./repl
|
||||||
|
|
||||||
The Go port can be built using standard Go tools and behaves the same.
|
|
||||||
|
|
||||||
Possible Ways of Complicating
|
Possible Ways of Complicating
|
||||||
-----------------------------
|
-----------------------------
|
||||||
* `local [_a _b _rest] @args` would elegantly solve the problem of varargs,
|
* `local [_a _b _rest] @args` would elegantly solve the problem of varargs,
|
||||||
that is, unpack a list when names are list, and make the last element a list
|
that is, unpack a list when names are list, and make the last element a list
|
||||||
when there are more arguments than names
|
when there are more arguments than names
|
||||||
* reference counting: in the C version, currently all values are always copied
|
* reference counting: currently all values are always copied as needed, which
|
||||||
as needed, which is good enough for all imaginable use cases, simpler and
|
is good enough for all imaginable use cases, simpler and less error-prone
|
||||||
less error-prone
|
|
||||||
|
|
||||||
Contributing and Support
|
Contributing and Support
|
||||||
------------------------
|
------------------------
|
||||||
|
@ -49,11 +49,15 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
var args []ell.V
|
var args *ell.V
|
||||||
|
tail := &args
|
||||||
for i := 2; i < len(os.Args); i++ {
|
for i := 2; i < len(os.Args); i++ {
|
||||||
args = append(args, *ell.NewString(os.Args[i]))
|
*tail = ell.NewString([]byte(os.Args[i]))
|
||||||
|
tail = &(*tail).Next
|
||||||
}
|
}
|
||||||
if _, ok := L.EvalBlock(program, args); !ok {
|
|
||||||
|
var result *ell.V
|
||||||
|
if !L.EvalBlock(program, args, &result) {
|
||||||
fmt.Printf("%s: %s\n", "runtime error", L.Error)
|
fmt.Printf("%s: %s\n", "runtime error", L.Error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,8 +28,9 @@ import (
|
|||||||
"janouch.name/ell/ell"
|
"janouch.name/ell/ell"
|
||||||
)
|
)
|
||||||
|
|
||||||
func run(L *ell.Ell, program []ell.V) {
|
func run(L *ell.Ell, program *ell.V) {
|
||||||
if result, ok := L.EvalBlock(program, nil); !ok {
|
var result *ell.V
|
||||||
|
if !L.EvalBlock(program, nil, &result) {
|
||||||
fmt.Printf("\x1b[31m%s: %s\x1b[0m\n", "runtime error", L.Error)
|
fmt.Printf("\x1b[31m%s: %s\x1b[0m\n", "runtime error", L.Error)
|
||||||
L.Error = ""
|
L.Error = ""
|
||||||
} else {
|
} else {
|
||||||
@ -38,23 +39,21 @@ func run(L *ell.Ell, program []ell.V) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func complete(L *ell.Ell, line string, pos int) (
|
func complete(L *ell.Ell, line string) (res []string) {
|
||||||
head string, completions []string, tail string) {
|
// This never actually completes anything, just shows the options,
|
||||||
tail = string([]rune(line)[pos:])
|
// we'd have to figure out the longest common prefix.
|
||||||
|
res = append(res, line)
|
||||||
|
|
||||||
lastSpace := strings.LastIndexAny(string([]rune(line)[:pos]), " ()[]{};\n")
|
line = strings.ToLower(line)
|
||||||
if lastSpace > -1 {
|
for v := L.Globals; v != nil; v = v.Next {
|
||||||
head, line = line[:lastSpace+1], line[lastSpace+1:]
|
name := string(v.Head.String)
|
||||||
}
|
|
||||||
|
|
||||||
for name := range L.Globals {
|
|
||||||
if strings.HasPrefix(strings.ToLower(name), line) {
|
if strings.HasPrefix(strings.ToLower(name), line) {
|
||||||
completions = append(completions, name)
|
res = append(res, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for name := range L.Native {
|
for name := range L.Native {
|
||||||
if strings.HasPrefix(strings.ToLower(name), line) {
|
if strings.HasPrefix(strings.ToLower(name), line) {
|
||||||
completions = append(completions, name)
|
res = append(res, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -67,10 +66,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
line := liner.NewLiner()
|
line := liner.NewLiner()
|
||||||
line.SetWordCompleter(func(line string, pos int) (
|
line.SetCompleter(func(line string) []string { return complete(L, line) })
|
||||||
string, []string, string) {
|
|
||||||
return complete(L, line, pos)
|
|
||||||
})
|
|
||||||
line.SetMultiLineMode(true)
|
line.SetMultiLineMode(true)
|
||||||
line.SetTabCompletionStyle(liner.TabPrints)
|
line.SetTabCompletionStyle(liner.TabPrints)
|
||||||
|
|
||||||
|
40
ell.c
40
ell.c
@ -793,14 +793,12 @@ ell_can_modify_error (struct ell *ell) {
|
|||||||
return !ell->memory_failure && ell->error[0] != '_';
|
return !ell->memory_failure && ell->error[0] != '_';
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool ell_eval_statement
|
static bool ell_eval_statement (struct ell *, struct ell_v *, struct ell_v **);
|
||||||
(struct ell *, const struct ell_v *, struct ell_v **);
|
|
||||||
static bool ell_eval_block
|
static bool ell_eval_block
|
||||||
(struct ell *, const struct ell_v *, struct ell_v *, struct ell_v **);
|
(struct ell *, struct ell_v *, struct ell_v *, struct ell_v **);
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
ell_eval_args (struct ell *ell,
|
ell_eval_args (struct ell *ell, struct ell_v *args, struct ell_v **result) {
|
||||||
const struct ell_v *args, struct ell_v **result) {
|
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
struct ell_v *res = NULL, **out = &res;
|
struct ell_v *res = NULL, **out = &res;
|
||||||
for (; args; args = args->next) {
|
for (; args; args = args->next) {
|
||||||
@ -830,7 +828,7 @@ error:
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
ell_eval_native (struct ell *ell, const char *name, const struct ell_v *args,
|
ell_eval_native (struct ell *ell, const char *name, struct ell_v *args,
|
||||||
struct ell_v **result) {
|
struct ell_v **result) {
|
||||||
struct ell_native_fn *fn = ell_native_find (ell, name);
|
struct ell_native_fn *fn = ell_native_find (ell, name);
|
||||||
if (!fn)
|
if (!fn)
|
||||||
@ -846,8 +844,8 @@ ell_eval_native (struct ell *ell, const char *name, const struct ell_v *args,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
ell_eval_resolved (struct ell *ell,
|
ell_eval_resolved (struct ell *ell, struct ell_v *body, struct ell_v *args,
|
||||||
const struct ell_v *body, const struct ell_v *args, struct ell_v **result) {
|
struct ell_v **result) {
|
||||||
// Resolving names recursively could be pretty fatal, let's not do that
|
// Resolving names recursively could be pretty fatal, let's not do that
|
||||||
if (body->type == ELL_STRING)
|
if (body->type == ELL_STRING)
|
||||||
return ell_check (ell, (*result = ell_clone (body)));
|
return ell_check (ell, (*result = ell_clone (body)));
|
||||||
@ -857,16 +855,13 @@ ell_eval_resolved (struct ell *ell,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
ell_eval_value (struct ell *ell, const struct ell_v *body,
|
ell_eval_value (struct ell *ell, struct ell_v *body, struct ell_v **result) {
|
||||||
struct ell_v **result) {
|
struct ell_v *args = body->next;
|
||||||
const struct ell_v *args = body->next;
|
|
||||||
if (body->type == ELL_STRING) {
|
if (body->type == ELL_STRING) {
|
||||||
const char *name = body->string;
|
const char *name = body->string;
|
||||||
if (!strcmp (name, "block")) {
|
if (!strcmp (name, "block"))
|
||||||
struct ell_v *cloned = NULL;
|
return (!args || ell_check (ell, (args = ell_clone_seq (args))))
|
||||||
return (!args || ell_check (ell, (cloned = ell_clone_seq (args))))
|
&& ell_check (ell, (*result = ell_list (args)));
|
||||||
&& ell_check (ell, (*result = ell_list (cloned)));
|
|
||||||
}
|
|
||||||
if ((body = ell_get (ell, name)))
|
if ((body = ell_get (ell, name)))
|
||||||
return ell_eval_resolved (ell, body, args, result);
|
return ell_eval_resolved (ell, body, args, result);
|
||||||
return ell_eval_native (ell, name, args, result);
|
return ell_eval_native (ell, name, args, result);
|
||||||
@ -890,7 +885,7 @@ ell_eval_value (struct ell *ell, const struct ell_v *body,
|
|||||||
|
|
||||||
static bool
|
static bool
|
||||||
ell_eval_statement
|
ell_eval_statement
|
||||||
(struct ell *ell, const struct ell_v *statement, struct ell_v **result) {
|
(struct ell *ell, struct ell_v *statement, struct ell_v **result) {
|
||||||
if (statement->type == ELL_STRING)
|
if (statement->type == ELL_STRING)
|
||||||
return ell_check (ell, (*result = ell_clone (statement)));
|
return ell_check (ell, (*result = ell_clone (statement)));
|
||||||
|
|
||||||
@ -936,7 +931,7 @@ args_to_scope (struct ell *ell, struct ell_v *args, struct ell_v **scope) {
|
|||||||
|
|
||||||
/// Execute a block and return whatever the last statement returned, eats args
|
/// Execute a block and return whatever the last statement returned, eats args
|
||||||
static bool
|
static bool
|
||||||
ell_eval_block (struct ell *ell, const struct ell_v *body, struct ell_v *args,
|
ell_eval_block (struct ell *ell, struct ell_v *body, struct ell_v *args,
|
||||||
struct ell_v **result) {
|
struct ell_v **result) {
|
||||||
struct ell_v *scope = NULL;
|
struct ell_v *scope = NULL;
|
||||||
if (!args_to_scope (ell, args, &scope)) {
|
if (!args_to_scope (ell, args, &scope)) {
|
||||||
@ -966,14 +961,13 @@ ell_eval_block (struct ell *ell, const struct ell_v *body, struct ell_v *args,
|
|||||||
(struct ell *ell, struct ell_v *args, struct ell_v **result)
|
(struct ell *ell, struct ell_v *args, struct ell_v **result)
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
ell_eval_any (struct ell *ell,
|
ell_eval_any (struct ell *ell, struct ell_v *body, struct ell_v *arg,
|
||||||
const struct ell_v *body, const struct ell_v *arg, struct ell_v **result) {
|
struct ell_v **result) {
|
||||||
if (body->type == ELL_STRING)
|
if (body->type == ELL_STRING)
|
||||||
return ell_check (ell, (*result = ell_clone (body)));
|
return ell_check (ell, (*result = ell_clone (body)));
|
||||||
struct ell_v *cloned_arg = NULL;
|
if (arg && !ell_check (ell, (arg = ell_clone (arg))))
|
||||||
if (arg && !ell_check (ell, (cloned_arg = ell_clone (arg))))
|
|
||||||
return false;
|
return false;
|
||||||
return ell_eval_block (ell, body->head, cloned_arg, result);
|
return ell_eval_block (ell, body->head, arg, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct ell_v *
|
static struct ell_v *
|
||||||
|
868
ell/ell.go
868
ell/ell.go
File diff suppressed because it is too large
Load Diff
513
ell/stdlib.go
513
ell/stdlib.go
@ -1,513 +0,0 @@
|
|||||||
//
|
|
||||||
// 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
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user