Compare commits
24 Commits
4c844e2789
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
b785eacf20
|
|||
|
71056896ac
|
|||
|
d6b495a7c9
|
|||
|
4ed2f5fe7c
|
|||
|
13b4b8a5f5
|
|||
|
cd0f978b09
|
|||
|
728977d241
|
|||
|
c81f986ec2
|
|||
|
64f892f40e
|
|||
|
2fe3c4753f
|
|||
|
2717cd569b
|
|||
|
563e8ba069
|
|||
|
b210216c71
|
|||
|
fb143f4d27
|
|||
|
f4f03d1737
|
|||
|
1ae1b9bb98
|
|||
|
b3e27a5df3
|
|||
|
1e03aeacdd
|
|||
|
f7bb33cc3d
|
|||
|
f751975cfd
|
|||
|
55a1076367
|
|||
|
7c9fb564af
|
|||
|
a004e91c80
|
|||
|
f452191e62
|
15
.clang-format
Normal file
15
.clang-format
Normal 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
2
.gitignore
vendored
@@ -9,3 +9,5 @@
|
|||||||
/ell.files
|
/ell.files
|
||||||
/ell.creator*
|
/ell.creator*
|
||||||
/ell.includes
|
/ell.includes
|
||||||
|
/ell.cflags
|
||||||
|
/ell.cxxflags
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -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.
|
||||||
|
|||||||
14
README.adoc
14
README.adoc
@@ -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
59
cmd/interpreter/main.go
Normal 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
95
cmd/repl/main.go
Normal 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
158
ell.c
@@ -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
714
ell/ell.go
Normal 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
513
ell/stdlib.go
Normal 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
7
go.mod
Normal 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
4
go.sum
Normal 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=
|
||||||
@@ -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
4
repl.c
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user