Compare commits

..

19 Commits

Author SHA1 Message Date
b785eacf20 Go: fix string formatting in error messages
All checks were successful
Alpine 3.19 Success
2022-03-16 12:38:51 +01:00
71056896ac Update .gitignore 2021-11-06 13:00:52 +01:00
d6b495a7c9 Add clang-format configuration, clean up 2021-11-06 12:58:33 +01:00
4ed2f5fe7c Bump Go modules to 1.17 2021-08-19 05:37:23 +02:00
13b4b8a5f5 Name change 2020-09-28 05:11:26 +02:00
cd0f978b09 Use Go modules 2018-12-01 22:45:26 +01:00
728977d241 C: add const specifiers where appropriate
As a hint whether values are eaten.
2018-10-10 21:39:29 +02:00
c81f986ec2 Go: move the stdlib to a different file 2018-10-10 21:23:25 +02:00
64f892f40e Update README 2018-10-10 21:21:39 +02:00
2fe3c4753f Go: make use of multiple return values 2018-10-10 21:12:35 +02:00
2717cd569b Go: store scopes in reverse order for efficiency 2018-10-10 19:56:05 +02:00
563e8ba069 Go: store scopes and globals as maps 2018-10-10 19:56:05 +02:00
b210216c71 Go: use slices for list values 2018-10-10 19:39:29 +02:00
fb143f4d27 Go: use slices for Handler results 2018-10-10 17:13:05 +02:00
f4f03d1737 Go: use slices for Handler arguments
First step to replacing linked lists with something more Go-like.
2018-10-10 16:37:56 +02:00
1ae1b9bb98 Go/repl: improve completion 2018-10-10 15:40:18 +02:00
b3e27a5df3 Go: make the system command more useful
Connect standard streams.
2018-10-09 18:31:17 +02:00
1e03aeacdd Go: use string for strings instead of []byte
A few conversions more, a few conversions less.
2018-10-09 18:25:41 +02:00
f7bb33cc3d Go: remove useless accessors to Ell.Handlers 2018-10-09 18:16:19 +02:00
13 changed files with 795 additions and 819 deletions

15
.clang-format Normal file
View File

@@ -0,0 +1,15 @@
BasedOnStyle: GNU
ColumnLimit: 80
IndentWidth: 4
TabWidth: 4
UseTab: ForContinuationAndIndentation
BreakBeforeBraces: Attach
BreakBeforeBinaryOperators: None
SpaceAfterCStyleCast: true
AlignAfterOpenBracket: DontAlign
AlignEscapedNewlines: DontAlign
AlignOperands: DontAlign
AlignConsecutiveMacros: Consecutive
AllowAllArgumentsOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
IndentGotoLabels: false

2
.gitignore vendored
View File

@@ -9,3 +9,5 @@
/ell.files
/ell.creator*
/ell.includes
/ell.cflags
/ell.cxxflags

View File

@@ -1,4 +1,4 @@
Copyright (c) 2017, Přemysl Janouch <p@janouch.name>
Copyright (c) 2017, Přemysl Eric Janouch <p@janouch.name>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.

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
execution as they are.
The last expression in a block is the return value.
The last expression in a block is the block's return value.
Special Forms
-------------
@@ -172,13 +172,16 @@ Install development packages for GNU Readline to get a REPL for toying around:
$ make repl
$ ./repl
The Go port can be built using standard Go tools and behaves the same.
Possible Ways of Complicating
-----------------------------
* `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
when there are more arguments than names
* reference counting: currently all values are always copied as needed, which
is good enough for all imaginable use cases, simpler and less error-prone
* reference counting: in the C version, currently all values are always copied
as needed, which is good enough for all imaginable use cases, simpler and
less error-prone
Contributing and Support
------------------------

View File

@@ -1,5 +1,5 @@
//
// Copyright (c) 2018, Přemysl Janouch <p@janouch.name>
// Copyright (c) 2018, Přemysl Eric Janouch <p@janouch.name>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted.
@@ -49,15 +49,11 @@ func main() {
os.Exit(1)
}
var args *ell.V
tail := &args
var args []ell.V
for i := 2; i < len(os.Args); i++ {
*tail = ell.NewString([]byte(os.Args[i]))
tail = &(*tail).Next
args = append(args, *ell.NewString(os.Args[i]))
}
var result *ell.V
if !L.EvalBlock(program, args, &result) {
if _, ok := L.EvalBlock(program, args); !ok {
fmt.Printf("%s: %s\n", "runtime error", L.Error)
}
}

View File

@@ -1,5 +1,5 @@
//
// Copyright (c) 2018, Přemysl Janouch <p@janouch.name>
// Copyright (c) 2018, Přemysl Eric Janouch <p@janouch.name>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted.
@@ -28,9 +28,8 @@ import (
"janouch.name/ell/ell"
)
func run(L *ell.Ell, program *ell.V) {
var result *ell.V
if !L.EvalBlock(program, nil, &result) {
func run(L *ell.Ell, program []ell.V) {
if result, ok := L.EvalBlock(program, nil); !ok {
fmt.Printf("\x1b[31m%s: %s\x1b[0m\n", "runtime error", L.Error)
L.Error = ""
} else {
@@ -39,21 +38,23 @@ func run(L *ell.Ell, program *ell.V) {
}
}
func complete(L *ell.Ell, line string) (res []string) {
// This never actually completes anything, just shows the options,
// we'd have to figure out the longest common prefix.
res = append(res, line)
func complete(L *ell.Ell, line string, pos int) (
head string, completions []string, tail string) {
tail = string([]rune(line)[pos:])
line = strings.ToLower(line)
for v := L.Globals; v != nil; v = v.Next {
name := string(v.Head.String)
lastSpace := strings.LastIndexAny(string([]rune(line)[:pos]), " ()[]{};\n")
if lastSpace > -1 {
head, line = line[:lastSpace+1], line[lastSpace+1:]
}
for name := range L.Globals {
if strings.HasPrefix(strings.ToLower(name), line) {
res = append(res, name)
completions = append(completions, name)
}
}
for name := range L.Native {
if strings.HasPrefix(strings.ToLower(name), line) {
res = append(res, name)
completions = append(completions, name)
}
}
return
@@ -66,7 +67,10 @@ func main() {
}
line := liner.NewLiner()
line.SetCompleter(func(line string) []string { return complete(L, line) })
line.SetWordCompleter(func(line string, pos int) (
string, []string, string) {
return complete(L, line, pos)
})
line.SetMultiLineMode(true)
line.SetTabCompletionStyle(liner.TabPrints)

139
ell.c
View File

@@ -1,7 +1,7 @@
/*
* ell.c: an experimental little language
*
* Copyright (c) 2017, Přemysl Janouch <p@janouch.name>
* Copyright (c) 2017, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
@@ -16,14 +16,14 @@
*
*/
#include <ctype.h>
#include <errno.h>
#include <setjmp.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <stdarg.h>
#include <stdbool.h>
#include <setjmp.h>
#if defined __GNUC__
#define ELL_ATTRIBUTE_PRINTF(x, y) __attribute__ ((format (printf, x, y)))
@@ -188,9 +188,18 @@ ell_list (struct ell_v *head) {
// --- Lexer -------------------------------------------------------------------
enum ell_token { ELLT_ABORT, ELLT_LPAREN, ELLT_RPAREN,
ELLT_LBRACKET, ELLT_RBRACKET, ELLT_LBRACE, ELLT_RBRACE,
ELLT_STRING, ELLT_NEWLINE, ELLT_AT };
enum ell_token {
ELLT_ABORT,
ELLT_LPAREN,
ELLT_RPAREN,
ELLT_LBRACKET,
ELLT_RBRACKET,
ELLT_LBRACE,
ELLT_RBRACE,
ELLT_STRING,
ELLT_NEWLINE,
ELLT_AT
};
static const char *ell_token_names[] = {
[ELLT_ABORT] = "end of input",
@@ -240,8 +249,8 @@ ell_lexer_advance (struct ell_lexer *self) {
static bool
ell_lexer_hexa_escape (struct ell_lexer *self, struct ell_buffer *output) {
const char *abc = "0123456789abcdef", *h, *l;
if (!self->len || !(h = strchr (abc, tolower (ell_lexer_advance (self))))
|| !self->len || !(l = strchr (abc, tolower (ell_lexer_advance (self)))))
if (!self->len || !(h = strchr (abc, tolower (ell_lexer_advance (self)))) ||
!self->len || !(l = strchr (abc, tolower (ell_lexer_advance (self)))))
return false;
ell_buffer_append_c (output, (h - abc) << 4 | (l - abc));
@@ -254,7 +263,8 @@ enum {
ELL_LEXER_COMMENT = '#'
};
static bool ell_lexer_is_whitespace (int c) {
static bool
ell_lexer_is_whitespace (int c) {
return !c || c == ' ' || c == '\t' || c == '\r';
}
@@ -326,13 +336,12 @@ ell_lexer_next (struct ell_lexer *self, const char **e) {
enum ell_token token = ell_lexer_tokens[c];
if (!token) {
ell_buffer_append_c (&self->string, c);
while (self->len && !ell_lexer_is_whitespace (*self->p)
&& !ell_lexer_tokens[*self->p])
while (self->len && !ell_lexer_is_whitespace (*self->p) &&
!ell_lexer_tokens[*self->p])
ell_buffer_append_c (&self->string, ell_lexer_advance (self));
return ELLT_STRING;
}
if (token == ELLT_STRING
&& (*e = ell_lexer_string (self, &self->string)))
if (token == ELLT_STRING && (*e = ell_lexer_string (self, &self->string)))
return ELLT_ABORT;
return token;
}
@@ -369,8 +378,8 @@ static bool
ell_print_string_needs_quoting (struct ell_v *s) {
for (size_t i = 0; i < s->len; i++) {
unsigned char c = s->string[i];
if (ell_lexer_is_whitespace (c) || ell_lexer_tokens[c]
|| c == ELL_LEXER_ESCAPE || c < 32)
if (ell_lexer_is_whitespace (c) || ell_lexer_tokens[c] ||
c == ELL_LEXER_ESCAPE || c < 32)
return true;
}
return s->len == 0;
@@ -426,8 +435,8 @@ ell_print_block (struct ell_printer *printer, struct ell_v *list) {
static bool
ell_print_set (struct ell_printer *printer, struct ell_v *list) {
if (!list->head || strcmp (list->head->string, "set")
|| !list->head->next || list->head->next->next)
if (!list->head || strcmp (list->head->string, "set") ||
!list->head->next || list->head->next->next)
return false;
printer->putchar (printer, '@');
@@ -448,10 +457,10 @@ ell_print_list (struct ell_printer *printer, struct ell_v *list) {
static void
ell_print_v (struct ell_printer *printer, struct ell_v *v) {
if (ell_print_string (printer, v)
|| ell_print_block (printer, v)
|| ell_print_set (printer, v)
|| ell_print_list (printer, v))
if (ell_print_string (printer, v) ||
ell_print_block (printer, v) ||
ell_print_set (printer, v) ||
ell_print_list (printer, v))
return;
printer->putchar (printer, '(');
@@ -576,8 +585,7 @@ ell_parse_v (struct ell_parser *p, jmp_buf out) {
SKIP_NL ();
if (ACCEPT (ELLT_STRING))
return CHECK (ell_string
(p->lexer.string.s, p->lexer.string.len));
return CHECK (ell_string (p->lexer.string.s, p->lexer.string.len));
if (ACCEPT (ELLT_AT)) {
result = ell_parse_v (p, out);
return CHECK (ell_parse_prefix_list (result, "set"));
@@ -706,8 +714,8 @@ static bool
ell_scope_prepend (struct ell *ell, struct ell_v **scope, const char *name,
struct ell_v *v) {
struct ell_v *key, *pair;
if (!ell_check (ell, (key = ell_string (name, strlen (name))))
|| !ell_check (ell, (pair = ell_list (key)))) {
if (!ell_check (ell, (key = ell_string (name, strlen (name)))) ||
!ell_check (ell, (pair = ell_list (key)))) {
ell_free_seq (v);
return false;
}
@@ -793,19 +801,21 @@ ell_can_modify_error (struct ell *ell) {
return !ell->memory_failure && ell->error[0] != '_';
}
static bool ell_eval_statement (struct ell *, struct ell_v *, struct ell_v **);
static bool ell_eval_statement
(struct ell *, const struct ell_v *, struct ell_v **);
static bool ell_eval_block
(struct ell *, struct ell_v *, struct ell_v *, struct ell_v **);
(struct ell *, const struct ell_v *, struct ell_v *, struct ell_v **);
static bool
ell_eval_args (struct ell *ell, struct ell_v *args, struct ell_v **result) {
ell_eval_args (struct ell *ell,
const struct ell_v *args, struct ell_v **result) {
size_t i = 0;
struct ell_v *res = NULL, **out = &res;
for (; args; args = args->next) {
struct ell_v *evaluated = NULL;
// Arguments should not evaporate, default to a nil value
if (!ell_eval_statement (ell, args, &evaluated)
|| (!evaluated && !ell_check (ell, (evaluated = ell_list (NULL)))))
if (!ell_eval_statement (ell, args, &evaluated) ||
(!evaluated && !ell_check (ell, (evaluated = ell_list (NULL)))))
goto error;
ell_free_seq (evaluated->next);
evaluated->next = NULL;
@@ -828,7 +838,7 @@ error:
}
static bool
ell_eval_native (struct ell *ell, const char *name, struct ell_v *args,
ell_eval_native (struct ell *ell, const char *name, const struct ell_v *args,
struct ell_v **result) {
struct ell_native_fn *fn = ell_native_find (ell, name);
if (!fn)
@@ -844,8 +854,8 @@ ell_eval_native (struct ell *ell, const char *name, struct ell_v *args,
}
static bool
ell_eval_resolved (struct ell *ell, struct ell_v *body, struct ell_v *args,
struct ell_v **result) {
ell_eval_resolved (struct ell *ell,
const struct ell_v *body, const struct ell_v *args, struct ell_v **result) {
// Resolving names recursively could be pretty fatal, let's not do that
if (body->type == ELL_STRING)
return ell_check (ell, (*result = ell_clone (body)));
@@ -855,13 +865,16 @@ ell_eval_resolved (struct ell *ell, struct ell_v *body, struct ell_v *args,
}
static bool
ell_eval_value (struct ell *ell, struct ell_v *body, struct ell_v **result) {
struct ell_v *args = body->next;
ell_eval_value (struct ell *ell, const struct ell_v *body,
struct ell_v **result) {
const struct ell_v *args = body->next;
if (body->type == ELL_STRING) {
const char *name = body->string;
if (!strcmp (name, "block"))
return (!args || ell_check (ell, (args = ell_clone_seq (args))))
&& ell_check (ell, (*result = ell_list (args)));
if (!strcmp (name, "block")) {
struct ell_v *cloned = NULL;
return (!args || ell_check (ell, (cloned = ell_clone_seq (args))))
&& ell_check (ell, (*result = ell_list (cloned)));
}
if ((body = ell_get (ell, name)))
return ell_eval_resolved (ell, body, args, result);
return ell_eval_native (ell, name, args, result);
@@ -884,15 +897,14 @@ ell_eval_value (struct ell *ell, struct ell_v *body, struct ell_v **result) {
}
static bool
ell_eval_statement
(struct ell *ell, struct ell_v *statement, struct ell_v **result) {
ell_eval_statement (struct ell *ell, const struct ell_v *statement,
struct ell_v **result) {
if (statement->type == ELL_STRING)
return ell_check (ell, (*result = ell_clone (statement)));
// Executing a nil value results in no value. It's not very different from
// calling a block that returns no value--it's for our callers to resolve.
if (!statement->head
|| ell_eval_value (ell, statement->head, result))
if (!statement->head || ell_eval_value (ell, statement->head, result))
return true;
ell_free_seq (*result);
@@ -913,8 +925,8 @@ ell_eval_statement
static bool
args_to_scope (struct ell *ell, struct ell_v *args, struct ell_v **scope) {
if (!ell_check (ell, (args = ell_list (args)))
|| !ell_scope_prepend (ell, scope, "args", args))
if (!ell_check (ell, (args = ell_list (args))) ||
!ell_scope_prepend (ell, scope, "args", args))
return false;
size_t i = 0;
@@ -922,8 +934,8 @@ args_to_scope (struct ell *ell, struct ell_v *args, struct ell_v **scope) {
char buf[16] = "";
(void) snprintf (buf, sizeof buf, "%zu", ++i);
struct ell_v *copy = NULL;
if ((args && !ell_check (ell, (copy = ell_clone (args))))
|| !ell_scope_prepend (ell, scope, buf, copy))
if ((args && !ell_check (ell, (copy = ell_clone (args)))) ||
!ell_scope_prepend (ell, scope, buf, copy))
return false;
}
return ell_check (ell, (*scope = ell_list (*scope)));
@@ -931,7 +943,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
static bool
ell_eval_block (struct ell *ell, struct ell_v *body, struct ell_v *args,
ell_eval_block (struct ell *ell, const struct ell_v *body, struct ell_v *args,
struct ell_v **result) {
struct ell_v *scope = NULL;
if (!args_to_scope (ell, args, &scope)) {
@@ -961,13 +973,14 @@ ell_eval_block (struct ell *ell, struct ell_v *body, struct ell_v *args,
(struct ell *ell, struct ell_v *args, struct ell_v **result)
static bool
ell_eval_any (struct ell *ell, struct ell_v *body, struct ell_v *arg,
struct ell_v **result) {
ell_eval_any (struct ell *ell,
const struct ell_v *body, const struct ell_v *arg, struct ell_v **result) {
if (body->type == ELL_STRING)
return ell_check (ell, (*result = ell_clone (body)));
if (arg && !ell_check (ell, (arg = ell_clone (arg))))
struct ell_v *cloned_arg = NULL;
if (arg && !ell_check (ell, (cloned_arg = ell_clone (arg))))
return false;
return ell_eval_block (ell, body->head, arg, result);
return ell_eval_block (ell, body->head, cloned_arg, result);
}
static struct ell_v *
@@ -1025,8 +1038,8 @@ ell_defn (ell_fn_local) {
struct ell_v *values = names->next;
for (names = names->head; names; names = names->next) {
struct ell_v *value = NULL;
if ((values && !ell_check (ell, (value = ell_clone (values))))
|| !ell_scope_prepend (ell, scope, names->string, value))
if ((values && !ell_check (ell, (value = ell_clone (values)))) ||
!ell_scope_prepend (ell, scope, names->string, value))
return false;
if (values)
values = values->next;
@@ -1041,9 +1054,9 @@ ell_defn (ell_fn_set) {
struct ell_v *v;
if ((v = name->next))
return ell_check (ell, (v = ell_clone (v)))
&& ell_check (ell, (*result = ell_clone (v)))
&& ell_set (ell, name->string, v);
return ell_check (ell, (v = ell_clone (v))) &&
ell_check (ell, (*result = ell_clone (v))) &&
ell_set (ell, name->string, v);
// We return an empty list for a nil value
if (!(v = ell_get (ell, name->string)))
@@ -1133,8 +1146,8 @@ ell_defn (ell_fn_cat) {
else
ell_buffer_append (&buf, args->string, args->len);
}
bool ok = !(ell->memory_failure |= buf.memory_failure)
&& ell_check (ell, (*result = ell_string (buf.s, buf.len)));
bool ok = !(ell->memory_failure |= buf.memory_failure) &&
ell_check (ell, (*result = ell_string (buf.s, buf.len)));
free (buf.s);
return ok;
}
@@ -1172,8 +1185,8 @@ ell_defn (ell_fn_try) {
return true;
struct ell_v *msg;
if (ell->memory_failure
|| !ell_check (ell, (msg = ell_string (ell->error, strlen (ell->error)))))
if (ell->memory_failure ||
!ell_check (ell, (msg = ell_string (ell->error, strlen (ell->error)))))
return false;
free (ell->error); ell->error = NULL;

File diff suppressed because it is too large Load Diff

513
ell/stdlib.go Normal file
View File

@@ -0,0 +1,513 @@
//
// Copyright (c) 2018, Přemysl Eric 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: %s", 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: %s", 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: %s", 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: %s", 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: %s", 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: %s", 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: %s", 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: %s", 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
}

7
go.mod Normal file
View File

@@ -0,0 +1,7 @@
module janouch.name/ell
go 1.17
require github.com/peterh/liner v1.1.0
require github.com/mattn/go-runewidth v0.0.3 // indirect

4
go.sum Normal file
View File

@@ -0,0 +1,4 @@
github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/peterh/liner v1.1.0 h1:f+aAedNJA6uk7+6rXsYBnhdo4Xux7ESLe+kcuVUF5os=
github.com/peterh/liner v1.1.0/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=

View File

@@ -1,7 +1,7 @@
/*
* interpreter.c: test interpreter
*
* Copyright (c) 2017, Přemysl Janouch <p@janouch.name>
* Copyright (c) 2017, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
@@ -65,4 +65,3 @@ main (int argc, char *argv[]) {
ell_free (&ell);
return 0;
}

4
repl.c
View File

@@ -1,7 +1,7 @@
/*
* repl.c: test REPL
*
* Copyright (c) 2017, Přemysl Janouch <p@janouch.name>
* Copyright (c) 2017, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
@@ -17,8 +17,8 @@
*/
#include "ell.c"
#include <readline/readline.h>
#include <readline/history.h>
#include <readline/readline.h>
static void
run (struct ell *ell, struct ell_v *program) {