Compare commits

...

24 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
f751975cfd Add a port to Go 2018-10-09 10:42:20 +02:00
55a1076367 Fix an apparent memory leak 2018-10-09 08:55:03 +02:00
7c9fb564af Cleanup
Use inline semicolons rather then line feeds.
2018-10-09 08:54:14 +02:00
a004e91c80 Sanitize error message in "throw" 2018-10-09 08:53:39 +02:00
f452191e62 Fix typo 2018-10-09 08:53:17 +02:00
13 changed files with 1507 additions and 83 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.files
/ell.creator* /ell.creator*
/ell.includes /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 Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted. purpose with or without fee is hereby granted.

View File

@@ -6,8 +6,9 @@ ell
a programming language implementable with as little code as possible while a programming language implementable with as little code as possible while
still being reasonably comfortable to use. still being reasonably comfortable to use.
This package is an implementation of said language, meant to be self-contained, This package contains two implementations of said language--one in C and
portable and reusable. Performance is specifically not an intent. another in Go--which are meant to be self-contained, portable and reusable.
Performance is specifically not an intent.
The project is currently in a "proof of concept" stage with many useful data The project is currently in a "proof of concept" stage with many useful data
operations missing but I believe it won't be a problem to implement them as operations missing but I believe it won't be a problem to implement them as
@@ -58,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 return value. The last expression in a block is the block's return value.
Special Forms Special Forms
------------- -------------
@@ -171,13 +172,16 @@ 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: currently all values are always copied as needed, which * reference counting: in the C version, currently all values are always copied
is good enough for all imaginable use cases, simpler and less error-prone as needed, which is good enough for all imaginable use cases, simpler and
less error-prone
Contributing and Support Contributing and Support
------------------------ ------------------------

59
cmd/interpreter/main.go Normal file
View File

@@ -0,0 +1,59 @@
//
// 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.
//
// Program interpreter is a basic ell interpreter.
package main
import (
"fmt"
"io/ioutil"
"os"
"janouch.name/ell/ell"
)
func main() {
var script []byte
var err error
if len(os.Args) < 2 {
script, err = ioutil.ReadAll(os.Stdin)
} else {
script, err = ioutil.ReadFile(os.Args[1])
}
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
L := ell.New()
if !ell.StdInitialize(L) {
fmt.Printf("runtime library initialization failed: %s\n", L.Error)
}
program, err := ell.NewParser(script).Run()
if err != nil {
fmt.Printf("%s: %s\n", "parse error", err)
os.Exit(1)
}
var args []ell.V
for i := 2; i < len(os.Args); i++ {
args = append(args, *ell.NewString(os.Args[i]))
}
if _, ok := L.EvalBlock(program, args); !ok {
fmt.Printf("%s: %s\n", "runtime error", L.Error)
}
}

95
cmd/repl/main.go Normal file
View File

@@ -0,0 +1,95 @@
//
// 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.
//
// Program repl is an interactive ell interpreter.
package main
import (
"fmt"
"io"
"os"
"strings"
// This library is rather simplistic but it's going to serve us fine.
"github.com/peterh/liner"
"janouch.name/ell/ell"
)
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 {
ell.PrintSeq(os.Stdout, result)
os.Stdout.WriteString("\n")
}
}
func complete(L *ell.Ell, line string, pos int) (
head string, completions []string, tail string) {
tail = string([]rune(line)[pos:])
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) {
completions = append(completions, name)
}
}
for name := range L.Native {
if strings.HasPrefix(strings.ToLower(name), line) {
completions = append(completions, name)
}
}
return
}
func main() {
L := ell.New()
if !ell.StdInitialize(L) {
fmt.Printf("runtime library initialization failed: %s\n", L.Error)
}
line := liner.NewLiner()
line.SetWordCompleter(func(line string, pos int) (
string, []string, string) {
return complete(L, line, pos)
})
line.SetMultiLineMode(true)
line.SetTabCompletionStyle(liner.TabPrints)
for {
script, err := line.Prompt("> ")
if err == nil {
line.AppendHistory(script)
p := ell.NewParser([]byte(script))
if program, err := p.Run(); err != nil {
fmt.Printf("\x1b[31m%s: %s\x1b[0m\n", "parse error", err)
} else {
run(L, program)
}
} else if err == liner.ErrPromptAborted || err == io.EOF {
break
} else {
fmt.Printf("\x1b[31m%s: %s\x1b[0m\n", "error", err)
}
}
os.Stdout.WriteString("\n")
}

158
ell.c
View File

@@ -1,7 +1,7 @@
/* /*
* ell.c: an experimental little language * 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 * Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted. * 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 <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <ctype.h>
#include <errno.h>
#include <stdarg.h>
#include <stdbool.h>
#include <setjmp.h>
#if defined __GNUC__ #if defined __GNUC__
#define ELL_ATTRIBUTE_PRINTF(x, y) __attribute__ ((format (printf, x, y))) #define ELL_ATTRIBUTE_PRINTF(x, y) __attribute__ ((format (printf, x, y)))
@@ -188,9 +188,18 @@ ell_list (struct ell_v *head) {
// --- Lexer ------------------------------------------------------------------- // --- Lexer -------------------------------------------------------------------
enum ell_token { ELLT_ABORT, ELLT_LPAREN, ELLT_RPAREN, enum ell_token {
ELLT_LBRACKET, ELLT_RBRACKET, ELLT_LBRACE, ELLT_RBRACE, ELLT_ABORT,
ELLT_STRING, ELLT_NEWLINE, ELLT_AT }; ELLT_LPAREN,
ELLT_RPAREN,
ELLT_LBRACKET,
ELLT_RBRACKET,
ELLT_LBRACE,
ELLT_RBRACE,
ELLT_STRING,
ELLT_NEWLINE,
ELLT_AT
};
static const char *ell_token_names[] = { static const char *ell_token_names[] = {
[ELLT_ABORT] = "end of input", [ELLT_ABORT] = "end of input",
@@ -240,8 +249,8 @@ ell_lexer_advance (struct ell_lexer *self) {
static bool static bool
ell_lexer_hexa_escape (struct ell_lexer *self, struct ell_buffer *output) { ell_lexer_hexa_escape (struct ell_lexer *self, struct ell_buffer *output) {
const char *abc = "0123456789abcdef", *h, *l; const char *abc = "0123456789abcdef", *h, *l;
if (!self->len || !(h = 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))))) !self->len || !(l = strchr (abc, tolower (ell_lexer_advance (self)))))
return false; return false;
ell_buffer_append_c (output, (h - abc) << 4 | (l - abc)); ell_buffer_append_c (output, (h - abc) << 4 | (l - abc));
@@ -254,7 +263,8 @@ enum {
ELL_LEXER_COMMENT = '#' 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'; 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]; enum ell_token token = ell_lexer_tokens[c];
if (!token) { if (!token) {
ell_buffer_append_c (&self->string, c); ell_buffer_append_c (&self->string, c);
while (self->len && !ell_lexer_is_whitespace (*self->p) while (self->len && !ell_lexer_is_whitespace (*self->p) &&
&& !ell_lexer_tokens[*self->p]) !ell_lexer_tokens[*self->p])
ell_buffer_append_c (&self->string, ell_lexer_advance (self)); ell_buffer_append_c (&self->string, ell_lexer_advance (self));
return ELLT_STRING; return ELLT_STRING;
} }
if (token == ELLT_STRING if (token == ELLT_STRING && (*e = ell_lexer_string (self, &self->string)))
&& (*e = ell_lexer_string (self, &self->string)))
return ELLT_ABORT; return ELLT_ABORT;
return token; return token;
} }
@@ -369,8 +378,8 @@ static bool
ell_print_string_needs_quoting (struct ell_v *s) { ell_print_string_needs_quoting (struct ell_v *s) {
for (size_t i = 0; i < s->len; i++) { for (size_t i = 0; i < s->len; i++) {
unsigned char c = s->string[i]; unsigned char c = s->string[i];
if (ell_lexer_is_whitespace (c) || ell_lexer_tokens[c] if (ell_lexer_is_whitespace (c) || ell_lexer_tokens[c] ||
|| c == ELL_LEXER_ESCAPE || c < 32) c == ELL_LEXER_ESCAPE || c < 32)
return true; return true;
} }
return s->len == 0; return s->len == 0;
@@ -426,8 +435,8 @@ ell_print_block (struct ell_printer *printer, struct ell_v *list) {
static bool static bool
ell_print_set (struct ell_printer *printer, struct ell_v *list) { ell_print_set (struct ell_printer *printer, struct ell_v *list) {
if (!list->head || strcmp (list->head->string, "set") if (!list->head || strcmp (list->head->string, "set") ||
|| !list->head->next || list->head->next->next) !list->head->next || list->head->next->next)
return false; return false;
printer->putchar (printer, '@'); printer->putchar (printer, '@');
@@ -448,10 +457,10 @@ ell_print_list (struct ell_printer *printer, struct ell_v *list) {
static void static void
ell_print_v (struct ell_printer *printer, struct ell_v *v) { ell_print_v (struct ell_printer *printer, struct ell_v *v) {
if (ell_print_string (printer, v) if (ell_print_string (printer, v) ||
|| ell_print_block (printer, v) ell_print_block (printer, v) ||
|| ell_print_set (printer, v) ell_print_set (printer, v) ||
|| ell_print_list (printer, v)) ell_print_list (printer, v))
return; return;
printer->putchar (printer, '('); printer->putchar (printer, '(');
@@ -563,7 +572,7 @@ ell_parse_prefix_list (struct ell_v *seq, const char *name) {
return ell_list (prefix); return ell_list (prefix);
} }
static struct ell_v * ell_parse_line (struct ell_parser *p, jmp_buf out); static struct ell_v *ell_parse_line (struct ell_parser *p, jmp_buf out);
static struct ell_v * static struct ell_v *
ell_parse_v (struct ell_parser *p, jmp_buf out) { ell_parse_v (struct ell_parser *p, jmp_buf out) {
@@ -576,8 +585,7 @@ ell_parse_v (struct ell_parser *p, jmp_buf out) {
SKIP_NL (); SKIP_NL ();
if (ACCEPT (ELLT_STRING)) if (ACCEPT (ELLT_STRING))
return CHECK (ell_string return CHECK (ell_string (p->lexer.string.s, p->lexer.string.len));
(p->lexer.string.s, p->lexer.string.len));
if (ACCEPT (ELLT_AT)) { if (ACCEPT (ELLT_AT)) {
result = ell_parse_v (p, out); result = ell_parse_v (p, out);
return CHECK (ell_parse_prefix_list (result, "set")); 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, ell_scope_prepend (struct ell *ell, struct ell_v **scope, const char *name,
struct ell_v *v) { struct ell_v *v) {
struct ell_v *key, *pair; struct ell_v *key, *pair;
if (!ell_check (ell, (key = ell_string (name, strlen (name)))) if (!ell_check (ell, (key = ell_string (name, strlen (name)))) ||
|| !ell_check (ell, (pair = ell_list (key)))) { !ell_check (ell, (pair = ell_list (key)))) {
ell_free_seq (v); ell_free_seq (v);
return false; return false;
} }
@@ -734,9 +742,8 @@ ell_set (struct ell *ell, const char *name, struct ell_v *v) {
for (struct ell_v *scope = ell->scopes; scope; scope = scope->next) { for (struct ell_v *scope = ell->scopes; scope; scope = scope->next) {
if ((place = ell_scope_find (&scope->head, name))) { if ((place = ell_scope_find (&scope->head, name))) {
ell_free_seq ((*place)->head->next); ell_free_seq ((*place)->head->next);
(*place)->head->next = NULL; (*place)->head->next = v;
return !v return true;
|| ell_check (ell, ((*place)->head->next = ell_clone (v)));
} }
} }
@@ -794,19 +801,21 @@ 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 (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 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 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; 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) {
struct ell_v *evaluated = NULL; struct ell_v *evaluated = NULL;
// Arguments should not evaporate, default to a nil value // Arguments should not evaporate, default to a nil value
if (!ell_eval_statement (ell, args, &evaluated) if (!ell_eval_statement (ell, args, &evaluated) ||
|| (!evaluated && !ell_check (ell, (evaluated = ell_list (NULL))))) (!evaluated && !ell_check (ell, (evaluated = ell_list (NULL)))))
goto error; goto error;
ell_free_seq (evaluated->next); ell_free_seq (evaluated->next);
evaluated->next = NULL; evaluated->next = NULL;
@@ -829,7 +838,7 @@ error:
} }
static bool 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_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)
@@ -845,9 +854,9 @@ ell_eval_native (struct ell *ell, const char *name, struct ell_v *args,
} }
static bool static bool
ell_eval_resolved (struct ell *ell, struct ell_v *body, struct ell_v *args, ell_eval_resolved (struct ell *ell,
struct ell_v **result) { const struct ell_v *body, const struct ell_v *args, struct ell_v **result) {
// Resolving names ecursively 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)));
struct ell_v *arguments = NULL; struct ell_v *arguments = NULL;
@@ -856,13 +865,16 @@ ell_eval_resolved (struct ell *ell, struct ell_v *body, struct ell_v *args,
} }
static bool static bool
ell_eval_value (struct ell *ell, struct ell_v *body, struct ell_v **result) { ell_eval_value (struct ell *ell, const struct ell_v *body,
struct ell_v *args = body->next; struct ell_v **result) {
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")) {
return (!args || ell_check (ell, (args = ell_clone_seq (args)))) struct ell_v *cloned = NULL;
&& ell_check (ell, (*result = ell_list (args))); return (!args || ell_check (ell, (cloned = ell_clone_seq (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);
@@ -885,15 +897,14 @@ ell_eval_value (struct ell *ell, struct ell_v *body, struct ell_v **result) {
} }
static bool static bool
ell_eval_statement ell_eval_statement (struct ell *ell, const struct ell_v *statement,
(struct ell *ell, struct ell_v *statement, struct ell_v **result) { 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)));
// Executing a nil value results in no value. It's not very different from // 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. // calling a block that returns no value--it's for our callers to resolve.
if (!statement->head if (!statement->head || ell_eval_value (ell, statement->head, result))
|| ell_eval_value (ell, statement->head, result))
return true; return true;
ell_free_seq (*result); ell_free_seq (*result);
@@ -914,8 +925,8 @@ ell_eval_statement
static bool static bool
args_to_scope (struct ell *ell, struct ell_v *args, struct ell_v **scope) { args_to_scope (struct ell *ell, struct ell_v *args, struct ell_v **scope) {
if (!ell_check (ell, (args = ell_list (args))) if (!ell_check (ell, (args = ell_list (args))) ||
|| !ell_scope_prepend (ell, scope, "args", args)) !ell_scope_prepend (ell, scope, "args", args))
return false; return false;
size_t i = 0; size_t i = 0;
@@ -923,8 +934,8 @@ args_to_scope (struct ell *ell, struct ell_v *args, struct ell_v **scope) {
char buf[16] = ""; char buf[16] = "";
(void) snprintf (buf, sizeof buf, "%zu", ++i); (void) snprintf (buf, sizeof buf, "%zu", ++i);
struct ell_v *copy = NULL; struct ell_v *copy = NULL;
if ((args && !ell_check (ell, (copy = ell_clone (args)))) if ((args && !ell_check (ell, (copy = ell_clone (args)))) ||
|| !ell_scope_prepend (ell, scope, buf, copy)) !ell_scope_prepend (ell, scope, buf, copy))
return false; return false;
} }
return ell_check (ell, (*scope = ell_list (*scope))); return ell_check (ell, (*scope = ell_list (*scope)));
@@ -932,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 /// Execute a block and return whatever the last statement returned, eats args
static bool 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 **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)) {
@@ -962,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) (struct ell *ell, struct ell_v *args, struct ell_v **result)
static bool static bool
ell_eval_any (struct ell *ell, struct ell_v *body, struct ell_v *arg, ell_eval_any (struct ell *ell,
struct ell_v **result) { const struct ell_v *body, const struct ell_v *arg, 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)));
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 false;
return ell_eval_block (ell, body->head, arg, result); return ell_eval_block (ell, body->head, cloned_arg, result);
} }
static struct ell_v * static struct ell_v *
@@ -1026,8 +1038,8 @@ ell_defn (ell_fn_local) {
struct ell_v *values = names->next; struct ell_v *values = names->next;
for (names = names->head; names; names = names->next) { for (names = names->head; names; names = names->next) {
struct ell_v *value = NULL; struct ell_v *value = NULL;
if ((values && !ell_check (ell, (value = ell_clone (values)))) if ((values && !ell_check (ell, (value = ell_clone (values)))) ||
|| !ell_scope_prepend (ell, scope, names->string, value)) !ell_scope_prepend (ell, scope, names->string, value))
return false; return false;
if (values) if (values)
values = values->next; values = values->next;
@@ -1042,9 +1054,9 @@ ell_defn (ell_fn_set) {
struct ell_v *v; struct ell_v *v;
if ((v = name->next)) if ((v = name->next))
return ell_check (ell, (v = ell_clone (v))) return ell_check (ell, (v = ell_clone (v))) &&
&& ell_check (ell, (*result = ell_clone (v))) ell_check (ell, (*result = ell_clone (v))) &&
&& ell_set (ell, name->string, v); ell_set (ell, name->string, v);
// We return an empty list for a nil value // We return an empty list for a nil value
if (!(v = ell_get (ell, name->string))) if (!(v = ell_get (ell, name->string)))
@@ -1134,8 +1146,8 @@ ell_defn (ell_fn_cat) {
else else
ell_buffer_append (&buf, args->string, args->len); ell_buffer_append (&buf, args->string, args->len);
} }
bool ok = !(ell->memory_failure |= buf.memory_failure) bool ok = !(ell->memory_failure |= buf.memory_failure) &&
&& ell_check (ell, (*result = ell_string (buf.s, buf.len))); ell_check (ell, (*result = ell_string (buf.s, buf.len)));
free (buf.s); free (buf.s);
return ok; return ok;
} }
@@ -1173,8 +1185,8 @@ ell_defn (ell_fn_try) {
return true; return true;
struct ell_v *msg; struct ell_v *msg;
if (ell->memory_failure if (ell->memory_failure ||
|| !ell_check (ell, (msg = ell_string (ell->error, strlen (ell->error))))) !ell_check (ell, (msg = ell_string (ell->error, strlen (ell->error)))))
return false; return false;
free (ell->error); ell->error = NULL; free (ell->error); ell->error = NULL;
@@ -1191,7 +1203,7 @@ ell_defn (ell_fn_throw) {
struct ell_v *message = args; struct ell_v *message = args;
if (!message || message->type != ELL_STRING) if (!message || message->type != ELL_STRING)
return ell_error (ell, "first argument must be string"); return ell_error (ell, "first argument must be string");
return ell_error (ell, message->string); return ell_error (ell, "%s", message->string);
} }
ell_defn (ell_fn_plus) { ell_defn (ell_fn_plus) {
@@ -1377,10 +1389,10 @@ const char ell_std_composed[] =
"set break { throw _break }\n" "set break { throw _break }\n"
// TODO: we should be able to apply them to all arguments // TODO: we should be able to apply them to all arguments
"set ne? { not (eq? @1 @2) }\n" "set le? { ge? @2 @1 }\n" "set ne? { not (eq? @1 @2) }; set le? { ge? @2 @1 }\n"
"set ge? { not (lt? @1 @2) }\n" "set gt? { lt? @2 @1 }\n" "set ge? { not (lt? @1 @2) }; set gt? { lt? @2 @1 }\n"
"set <> { not (= @1 @2) }\n" "set <= { >= @2 @1 }\n" "set <> { not (= @1 @2) }; set <= { >= @2 @1 }\n"
"set >= { not (< @1 @2) }\n" "set > { < @2 @1 }\n"; "set >= { not (< @1 @2) }; set > { < @2 @1 }\n";
static bool static bool
ell_std_initialize (struct ell *ell) { ell_std_initialize (struct ell *ell) {

714
ell/ell.go Normal file
View File

@@ -0,0 +1,714 @@
//
// 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 implements a simple scripting language.
package ell
import (
"errors"
"fmt"
"io"
)
// --- Values ------------------------------------------------------------------
// VType denotes the type of a value.
type VType int
const (
// VTypeString denotes a string value.
VTypeString VType = iota
// VTypeList denotes a list value.
VTypeList
)
// V is a value in the ell language.
type V struct {
Type VType // the type of this value
List []V // the contents of a VTypeList
String string // the immutable contents of a VTypeString
}
// Clone clones a value including its sublists.
func (v *V) Clone() *V {
if v == nil {
return nil
}
return &V{
Type: v.Type,
List: CloneSeq(v.List),
String: v.String,
}
}
// CloneSeq clones a value including the rest of its chain.
func CloneSeq(v []V) (result []V) {
for _, v := range v {
result = append(result, *v.Clone())
}
return
}
// NewString creates a new value containing a string.
func NewString(string string) *V {
return &V{
Type: VTypeString,
String: string,
}
}
// NewList creates a new list value containing the given sequence.
func NewList(list []V) *V {
return &V{
Type: VTypeList,
List: list,
}
}
// --- Lexer -------------------------------------------------------------------
type token int
const (
tAbort token = iota
tLParen
tRParen
tLBracket
tRBracket
tLBrace
tRBrace
tString
tNewline
tAt
)
func (t token) String() string {
switch t {
case tAbort:
return "end of input"
case tLParen:
return "left parenthesis"
case tRParen:
return "right parenthesis"
case tLBracket:
return "left bracket"
case tRBracket:
return "right bracket"
case tLBrace:
return "left brace"
case tRBrace:
return "right brace"
case tString:
return "string"
case tNewline:
return "newline"
case tAt:
return "at symbol"
}
panic("unknown token")
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
type lexer struct {
p []byte // unread input
line, column int // current line and column
buf []byte // parsed string value
}
func newLexer(p []byte) *lexer {
return &lexer{p: p}
}
func (lex *lexer) advance() byte {
ch := lex.p[0]
lex.p = lex.p[1:]
if ch == '\n' {
lex.column = 0
lex.line++
} else {
lex.column++
}
return ch
}
var lexerHexAlphabet = "0123456789abcdef"
// fromHex converts a nibble from hexadecimal. Avoiding dependencies.
func lexerFromHex(ch byte) int {
if ch >= 'A' && ch <= 'Z' {
ch += 32
}
for i := 0; i < len(lexerHexAlphabet); i++ {
if lexerHexAlphabet[i] == ch {
return i
}
}
return -1
}
func (lex *lexer) hexaEscape() bool {
if len(lex.p) < 2 {
return false
}
h := lexerFromHex(lex.advance())
if h < 0 {
return false
}
l := lexerFromHex(lex.advance())
if l < 0 {
return false
}
lex.buf = append(lex.buf, byte(h<<4|l))
return true
}
const (
lexerStringQuote = '\''
lexerEscape = '\\'
lexerComment = '#'
)
func lexerIsWhitespace(ch byte) bool {
return ch == 0 || ch == ' ' || ch == '\t' || ch == '\r'
}
var lexerEscapes = map[byte]byte{
lexerStringQuote: lexerStringQuote,
lexerEscape: lexerEscape,
'a': '\a',
'b': '\b',
'n': '\n',
'r': '\r',
't': '\t',
}
func (lex *lexer) escapeSequence() error {
if len(lex.p) == 0 {
return errors.New("premature end of escape sequence")
}
ch := lex.advance()
if ch == 'x' {
if lex.hexaEscape() {
return nil
}
return errors.New("invalid hexadecimal escape")
}
ch, ok := lexerEscapes[ch]
if !ok {
return errors.New("unknown escape sequence")
}
lex.buf = append(lex.buf, ch)
return nil
}
func (lex *lexer) string() error {
for len(lex.p) > 0 {
ch := lex.advance()
if ch == lexerStringQuote {
return nil
}
if ch != lexerEscape {
lex.buf = append(lex.buf, ch)
} else if err := lex.escapeSequence(); err != nil {
return err
}
}
return errors.New("premature end of string")
}
var lexerTokens = map[byte]token{
'(': tLParen,
')': tRParen,
'[': tLBracket,
']': tRBracket,
'{': tLBrace,
'}': tRBrace,
';': tNewline,
'\n': tNewline,
'@': tAt,
lexerStringQuote: tString,
}
func (lex *lexer) next() (token, error) {
for len(lex.p) > 0 && lexerIsWhitespace(lex.p[0]) {
lex.advance()
}
if len(lex.p) == 0 {
return tAbort, nil
}
lex.buf = nil
ch := lex.advance()
if ch == lexerComment {
for len(lex.p) > 0 {
if ch := lex.advance(); ch == '\n' {
return tNewline, nil
}
}
return tAbort, nil
}
token, ok := lexerTokens[ch]
if !ok {
lex.buf = append(lex.buf, ch)
for len(lex.p) > 0 && !lexerIsWhitespace(lex.p[0]) &&
lexerTokens[lex.p[0]] == 0 /* ugly but short */ {
lex.buf = append(lex.buf, lex.advance())
}
return tString, nil
}
if token == tString {
if err := lex.string(); err != nil {
return tAbort, err
}
}
return token, nil
}
func (lex *lexer) errorf(format string, a ...interface{}) error {
return fmt.Errorf("at or before line %d, column %d: %s",
lex.line+1, lex.column+1, fmt.Sprintf(format, a...))
}
// --- Printing ----------------------------------------------------------------
func printStringNeedsQuoting(s *V) bool {
for i := 0; i < len(s.String); i++ {
ch := s.String[i]
if lexerIsWhitespace(ch) || lexerTokens[ch] != 0 ||
ch == lexerEscape || ch < 32 {
return true
}
}
return len(s.String) == 0
}
func printString(w io.Writer, s *V) bool {
if s.Type != VTypeString {
return false
}
if !printStringNeedsQuoting(s) {
_, _ = w.Write([]byte(s.String))
return true
}
_, _ = w.Write([]byte{lexerStringQuote})
for i := 0; i < len(s.String); i++ {
ch := s.String[i]
if ch < 32 {
_, _ = fmt.Fprintf(w, "\\x%02x", ch)
} else if ch == lexerEscape || ch == lexerStringQuote {
_, _ = fmt.Fprintf(w, "\\%c", ch)
} else {
_, _ = w.Write([]byte{ch})
}
}
_, _ = w.Write([]byte{lexerStringQuote})
return true
}
func printBlock(w io.Writer, list *V) bool {
if len(list.List) < 1 || list.List[0].String != "block" {
return false
}
sublist := list.List[1:]
for _, subsub := range sublist {
if subsub.Type != VTypeList {
return false
}
}
_, _ = w.Write([]byte{'{'})
if len(sublist) > 0 {
_, _ = w.Write([]byte{' '})
PrintSeq(w, sublist[0].List)
for _, subsub := range sublist[1:] {
_, _ = w.Write([]byte("; "))
PrintSeq(w, subsub.List)
}
_, _ = w.Write([]byte{' '})
}
_, _ = w.Write([]byte{'}'})
return true
}
func printSet(w io.Writer, list *V) bool {
if len(list.List) != 2 || list.List[0].String != "set" {
return false
}
_, _ = w.Write([]byte{'@'})
PrintSeq(w, list.List[1:])
return true
}
func printList(w io.Writer, list *V) bool {
if len(list.List) < 1 || list.List[0].String != "list" {
return false
}
_, _ = w.Write([]byte{'['})
PrintSeq(w, list.List[1:])
_, _ = w.Write([]byte{']'})
return true
}
// PrintV serializes a value to the given writer, ignoring I/O errors.
func PrintV(w io.Writer, v *V) {
if printString(w, v) ||
printBlock(w, v) ||
printSet(w, v) ||
printList(w, v) {
return
}
_, _ = w.Write([]byte{'('})
PrintSeq(w, v.List)
_, _ = w.Write([]byte{')'})
}
// PrintSeq serializes a sequence of values to the given writer.
func PrintSeq(w io.Writer, seq []V) {
if len(seq) > 0 {
PrintV(w, &seq[0])
for _, v := range seq[1:] {
_, _ = w.Write([]byte{' '})
PrintV(w, &v)
}
}
}
// --- Parsing -----------------------------------------------------------------
// Parser is a context for parsing.
type Parser struct {
lexer *lexer // tokenizer
token token // current token in the lexer
replaceToken bool // replace the token
}
// NewParser returns a new parser for the give byte slice.
func NewParser(script []byte) *Parser {
// As reading in tokens may cause exceptions, we wait for the first peek
// to replace the initial ELLT_ABORT.
return &Parser{
lexer: newLexer(script),
replaceToken: true,
}
}
func (p *Parser) peek() token {
if p.replaceToken {
token, err := p.lexer.next()
if err != nil {
panic(p.lexer.errorf("%s", err))
}
p.token = token
p.replaceToken = false
}
return p.token
}
func (p *Parser) accept(token token) bool {
p.replaceToken = p.peek() == token
return p.replaceToken
}
func (p *Parser) expect(token token) {
if !p.accept(token) {
panic(p.lexer.errorf("unexpected `%s', expected `%s'", p.token, token))
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
func (p *Parser) skipNL() {
for p.accept(tNewline) {
}
}
func parsePrefixList(seq []V, name string) *V {
return NewList(append([]V{*NewString(name)}, seq...))
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
func (p *Parser) parseV() *V {
p.skipNL()
var seq []V
switch {
case p.accept(tString):
return NewString(string(p.lexer.buf))
case p.accept(tAt):
seq = []V{*p.parseV()}
return parsePrefixList(seq, "set")
case p.accept(tLParen):
for !p.accept(tRParen) {
seq = append(seq, *p.parseV())
p.skipNL()
}
return NewList(seq)
case p.accept(tLBracket):
for !p.accept(tRBracket) {
seq = append(seq, *p.parseV())
p.skipNL()
}
return parsePrefixList(seq, "list")
case p.accept(tLBrace):
for {
result := p.parseLine()
if result == nil {
break
}
seq = append(seq, *result)
}
p.expect(tRBrace)
return parsePrefixList(seq, "block")
}
panic(p.lexer.errorf("unexpected `%s', expected a value", p.token))
}
func (p *Parser) parseLine() *V {
var seq []V
for p.peek() != tRBrace && p.peek() != tAbort {
if !p.accept(tNewline) {
seq = append(seq, *p.parseV())
} else if len(seq) > 0 {
return NewList(seq)
}
}
if len(seq) > 0 {
return NewList(seq)
}
return nil
}
// Run runs the parser and returns a value to be interpreted or an error.
func (p *Parser) Run() (seq []V, err error) {
// "The convention in the Go libraries is that even when a package
// uses panic internally, its external API still presents explicit
// error return values." We're good.
defer func() {
if r := recover(); r != nil {
seq, err = nil, r.(error)
}
}()
for {
result := p.parseLine()
if result == nil {
break
}
seq = append(seq, *result)
}
p.expect(tAbort)
return seq, nil
}
// --- Runtime -----------------------------------------------------------------
// Handler is a Go handler for an Ell function.
type Handler func(ell *Ell, args []V) (result []V, ok bool)
// Ell is an interpreter context.
type Ell struct {
Globals map[string]V // list of global variables
scopes []map[string]V // dynamic scopes from the oldest
Native map[string]Handler // maps strings to Go functions
Error string // error information
}
// New returns a new interpreter context ready for program execution.
func New() *Ell {
return &Ell{
Globals: make(map[string]V),
Native: make(map[string]Handler),
}
}
func scopeFind(scope []*V, name string) int {
for i, scope := range scope {
if scope.List[0].String == name {
return i
}
}
return -1
}
// Get retrieves a value by name from the scope or from global variables.
func (ell *Ell) Get(name string) *V {
for i := len(ell.scopes) - 1; i >= 0; i-- {
if v, ok := ell.scopes[i][name]; ok {
return &v
}
}
if v, ok := ell.Globals[name]; ok {
return &v
}
return nil
}
// Set sets a value by name in the scope or in global variables.
func (ell *Ell) Set(name string, v *V) {
for i := len(ell.scopes) - 1; i >= 0; i-- {
if _, ok := ell.scopes[i][name]; ok {
ell.scopes[i][name] = *v
return
}
}
// Variables only get deleted by "arg" or from the global scope.
ell.Globals[name] = *v
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Errorf sets an error message in the interpreter context and returns an empty
// sequence and false.
func (ell *Ell) Errorf(format string, args ...interface{}) ([]V, bool) {
ell.Error = fmt.Sprintf(format, args...)
return nil, false
}
func (ell *Ell) canModifyError() bool {
// Errors starting with an underscore are exceptions and would not work
// with stack traces generated this way.
return ell.Error == "" || ell.Error[0] != '_'
}
func (ell *Ell) evalArgs(args []V) (result []V, ok bool) {
for i, arg := range args {
evaluated, ok := ell.evalStatement(&arg)
if !ok {
// Once the code flows like this, at least make some use of it.
if ell.canModifyError() {
ell.Errorf("(argument %d) -> %s", i, ell.Error)
}
return nil, false
}
// Arguments should not evaporate, default to a nil value.
if len(evaluated) < 1 {
evaluated = []V{*NewList(nil)}
}
result = append(result, evaluated[0])
}
return result, true
}
func (ell *Ell) evalNative(name string, args []V) (result []V, ok bool) {
fn := ell.Native[name]
if fn == nil {
return ell.Errorf("unknown function")
}
if arguments, ok := ell.evalArgs(args); ok {
return fn(ell, arguments)
}
return nil, false
}
func (ell *Ell) evalResolved(body *V, args []V) (result []V, ok bool) {
// Resolving names recursively could be pretty fatal, let's not do that.
if body.Type == VTypeString {
return []V{*body}, true
}
if arguments, ok := ell.evalArgs(args); ok {
return ell.EvalBlock(body.List, arguments)
}
return nil, false
}
func (ell *Ell) evalValue(body []V) (result []V, ok bool) {
args := body[1:]
if body[0].Type == VTypeString {
name := body[0].String
if name == "block" {
if len(args) > 0 {
result = []V{*NewList(CloneSeq(args))}
}
return result, true
}
if body := ell.Get(name); body != nil {
return ell.evalResolved(body, args)
}
return ell.evalNative(name, args)
}
// When someone tries to call a block directly, we must evaluate it;
// e.g. something like `{ choose [@f1 @f2 @f3] } arg1 arg2 arg3`.
if evaluated, ok := ell.evalStatement(&body[0]); !ok {
return nil, false
} else if len(evaluated) > 0 {
return ell.evalResolved(&evaluated[0], args)
}
// It might a bit confusing that this doesn't evaluate arguments
// but neither does "block" and there's nothing to do here.
return nil, true
}
func (ell *Ell) evalStatement(statement *V) (result []V, ok bool) {
if statement.Type == VTypeString {
return []V{*statement}, true
}
// 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 len(statement.List) < 1 {
return nil, true
}
if result, ok = ell.evalValue(statement.List); ok {
return
}
name := "(block)"
if statement.List[0].Type == VTypeString {
name = statement.List[0].String
}
if ell.canModifyError() {
ell.Errorf("%s -> %s", name, ell.Error)
}
return nil, false
}
func argsToScope(args []V) map[string]V {
scope := map[string]V{"args": *NewList(args)}
for i, arg := range args {
scope[fmt.Sprintf("%d", i+1)] = *arg.Clone()
}
return scope
}
// EvalBlock executes a block and returns whatever the last statement returned,
// eats args.
func (ell *Ell) EvalBlock(body []V, args []V) (result []V, ok bool) {
ell.scopes = append(ell.scopes, argsToScope(args))
ok = true
for _, stmt := range body {
if result, ok = ell.evalStatement(&stmt); !ok {
break
}
}
ell.scopes = ell.scopes[:len(ell.scopes)-1]
return result, ok
}

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 * 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 * Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted. * purpose with or without fee is hereby granted.
@@ -65,4 +65,3 @@ main (int argc, char *argv[]) {
ell_free (&ell); ell_free (&ell);
return 0; return 0;
} }

4
repl.c
View File

@@ -1,7 +1,7 @@
/* /*
* repl.c: test REPL * 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 * Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted. * purpose with or without fee is hereby granted.
@@ -17,8 +17,8 @@
*/ */
#include "ell.c" #include "ell.c"
#include <readline/readline.h>
#include <readline/history.h> #include <readline/history.h>
#include <readline/readline.h>
static void static void
run (struct ell *ell, struct ell_v *program) { run (struct ell *ell, struct ell_v *program) {