Compare commits

..

No commits in common. "728977d241466dc607f7a7a057375d3dca7ed556" and "f751975cfd967c717473fea400d926a4c9f8beb1" have entirely different histories.

6 changed files with 764 additions and 706 deletions

View File

@ -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
------------------------ ------------------------

View File

@ -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)
} }
} }

View File

@ -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
View File

@ -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 *

File diff suppressed because it is too large Load Diff

View File

@ -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
}