diff --git a/CMakeLists.txt b/CMakeLists.txt index 235133e6..337c16d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,7 +39,8 @@ src/assembler/asm.c set(COMPILER_SOURCES src/compiler/compile.c -src/compiler/compile_specials.c +src/compiler/specials.c +src/compiler/cfuns.c src/compiler/context.c src/compiler/compile.h diff --git a/README.md b/README.md index 4edc8b17..a8b9d46d 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,7 @@ Lisp with several native useful datatypes. Some of the more interesting and useful features are first class functions and closures, immutable and mutable hashtables, arrays, and bytebuffers, macros (NYI), tail-call optimization, and continuations (coroutines, error handling). The runtime and -compiler are written in C99, but should eventually be completely compatible -with C89 compilers. +compiler are written in C99. There is a repl for trying out the language, as well as the ability to run script files. This client program is separate from the core runtime, so @@ -16,16 +15,20 @@ dst could be embedded into other programs. ## Features -First class closures -Garbage collection -lexical scoping -First class green threads (continuations) -Mutable and immutable arrays (array/tuple) -Mutable and immutable hashtables (table/struct) -Mutable and immutable strings (buffer/string) -Byte code interpreter with an assembly interface -Proper tail calls for functional code -Direct interop with C +* First class closures +* Garbage collection +* Lexical scoping +* First class green threads (continuations) +* Mutable and immutable arrays (array/tuple) +* Mutable and immutable hashtables (table/struct) +* Mutable and immutable strings (buffer/string) +* Byte code interpreter with an assembly interface, as well as bytecode verification +* Proper tail calls for functional code +* Direct interop with C +* REPL (read eval print loop) + +The code can be compiled to be either a bytecode interpreter and runtime, or +a full language. ## Compiling and Running diff --git a/src/compiler/cfuns.c b/src/compiler/cfuns.c new file mode 100644 index 00000000..66674a06 --- /dev/null +++ b/src/compiler/cfuns.c @@ -0,0 +1,96 @@ +/* +* Copyright (c) 2017 Calvin Rose +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to +* deal in the Software without restriction, including without limitation the +* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +* sell copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +* IN THE SOFTWARE. +*/ + +#include +#include +#include "compile.h" +#define DST_V_NODEF_GROW +#include +#undef DST_V_NODEF_GROW + +/* This logic needs to be expanded for more types */ + +/* Check if a function recieved only numbers */ +static int numbers(DstFopts opts, DstSM *args) { + int32_t i; + int32_t len = dst_v_count(args); + (void) opts; + for (i = 0; i < len; i++) { + DstSlot s = args[i].slot; + if (s.flags & DST_SLOT_CONSTANT) { + Dst c = s.constant; + if (!dst_checktype(c, DST_INTEGER) && + !dst_checktype(c, DST_REAL)) { + /*dstc_cerror(opts.compiler, args[i].map, "expected number");*/ + return 0; + } + } + } + return 1; +} + +static int can_add(DstFopts opts, DstAst *ast, DstSM *args) { + (void) ast; + return numbers(opts, args); +} + +static DstSlot add(DstFopts opts, DstAst *ast, DstSM *args) { + DstCompiler *c = opts.compiler; + int32_t i, len; + int32_t op1, op2; + len = dst_v_count(args); + DstSlot t; + if (len == 0) { + return dstc_cslot(dst_wrap_integer(0)); + } else if (len == 1) { + return args[0].slot; + } + t = dstc_gettarget(opts); + /* Compile initial two arguments */ + op1 = dstc_preread(c, args[0].map, 0xFF, 1, args[0].slot); + op2 = dstc_preread(c, args[1].map, 0xFF, 2, args[1].slot); + dstc_emit(c, ast, (t.index << 8) | (op1 << 16) | (op2 << 24) | DOP_ADD); + dstc_postread(c, args[0].slot, op1); + dstc_postread(c, args[1].slot, op2); + for (i = 2; i < len; i++) { + op1 = dstc_preread(c, args[i].map, 0xFF, 1, args[i].slot); + dstc_emit(c, ast, (t.index << 8) | (t.index << 16) | (op1 << 24) | DOP_ADD); + dstc_postread(c, args[i].slot, op1); + } + return t; +} + +/* Keep in lexographic order */ +static const DstCFunOptimizer optimizers[] = { + {dst_add, can_add, add} +}; + +/* Get a cfunction optimizer. Return NULL if none exists. */ +const DstCFunOptimizer *dstc_cfunopt(DstCFunction cfun) { + size_t i; + size_t n = sizeof(optimizers)/sizeof(DstCFunOptimizer); + for (i = 0; i < n; i++) + if (optimizers[i].cfun == cfun) + return optimizers + i; + return NULL; +} + diff --git a/src/compiler/compile.c b/src/compiler/compile.c index 3260bb0f..e1d2dcb0 100644 --- a/src/compiler/compile.c +++ b/src/compiler/compile.c @@ -703,18 +703,31 @@ static DstSlot dstc_call(DstFopts opts, DstAst *ast, DstSM *sms, DstSlot fun) { DstSlot retslot; int32_t localindex; DstCompiler *c = opts.compiler; - dstc_pushslots(c, ast, sms); - dstc_freeslots(c, sms); - localindex = dstc_preread(c, ast, 0xFF, 1, fun); - if (opts.flags & DST_FOPTS_TAIL) { - dstc_emit(c, ast, (localindex << 8) | DOP_TAILCALL); - retslot = dstc_cslot(dst_wrap_nil()); - retslot.flags = DST_SLOT_RETURNED; - } else { - retslot = dstc_gettarget(opts); - dstc_emit(c, ast, (localindex << 16) | (retslot.index << 8) | DOP_CALL); + int specialized = 0; + if (fun.flags & DST_SLOT_CONSTANT) { + if (dst_checktype(fun.constant, DST_CFUNCTION)) { + const DstCFunOptimizer *o = dstc_cfunopt(dst_unwrap_cfunction(fun.constant)); + if (o && o->can_optimize(opts, ast, sms)) { + specialized = 1; + retslot = o->optimize(opts, ast, sms); + } + } + /* TODO dst function inlining (no c functions)*/ } - dstc_postread(c, fun, localindex); + if (!specialized) { + dstc_pushslots(c, ast, sms); + localindex = dstc_preread(c, ast, 0xFF, 1, fun); + if (opts.flags & DST_FOPTS_TAIL) { + dstc_emit(c, ast, (localindex << 8) | DOP_TAILCALL); + retslot = dstc_cslot(dst_wrap_nil()); + retslot.flags = DST_SLOT_RETURNED; + } else { + retslot = dstc_gettarget(opts); + dstc_emit(c, ast, (localindex << 16) | (retslot.index << 8) | DOP_CALL); + } + dstc_postread(c, fun, localindex); + } + dstc_freeslots(c, sms); return retslot; } diff --git a/src/compiler/compile.h b/src/compiler/compile.h index 092e4c6c..cc3d2e5a 100644 --- a/src/compiler/compile.h +++ b/src/compiler/compile.h @@ -34,7 +34,8 @@ typedef struct SlotTracker SlotTracker; typedef struct DstScope DstScope; typedef struct DstSlot DstSlot; typedef struct DstFopts DstFopts; -typedef struct DstCFunctionOptimizer DstCFunctionOptimizer; +typedef struct DstCFunOptimizer DstCFunOptimizer; +typedef struct DstSpecial DstSpecial; #define DST_SLOT_CONSTANT 0x10000 #define DST_SLOT_NAMED 0x20000 @@ -130,16 +131,17 @@ DstFopts dstc_fopts_default(DstCompiler *c); /* A grouping of optimizations on a cfunction given certain conditions * on the arguments (such as all constants, or some known types). The appropriate * optimizations should be tried before compiling a normal function call. */ -typedef struct DstCFunOptimizer { +struct DstCFunOptimizer { DstCFunction cfun; - DstSlot (*optimize)(DstFopts opts, DstAst *ast, int32_t argn, const Dst *argv); -} DstCFunOptimizer; + int (*can_optimize)(DstFopts opts, DstAst *ast, DstSM *args); + DstSlot (*optimize)(DstFopts opts, DstAst *ast, DstSM *args); +}; /* A grouping of a named special and the corresponding compiler fragment */ -typedef struct DstSpecial { +struct DstSpecial { const char *name; DstSlot (*compile)(DstFopts opts, DstAst *ast, int32_t argn, const Dst *argv); -} DstSpecial; +}; /****************************************************/ diff --git a/src/compiler/context.c b/src/compiler/context.c index ba29ca21..d8763434 100644 --- a/src/compiler/context.c +++ b/src/compiler/context.c @@ -27,11 +27,11 @@ #define CHUNKSIZE 1024 /* Read input for a repl */ -static int replread(DstContext *c) { - if (c->buffer.count == 0) - printf("> "); - else +static int replread(DstContext *c, DstParserStatus status) { + if (status == DST_PARSE_PENDING) printf(">> "); + else + printf("> "); for (;;) { int x = fgetc(stdin); if (x == EOF) { @@ -76,9 +76,10 @@ static void filedeinit(DstContext *c) { fclose((FILE *) (c->user)); } -static int fileread(DstContext *c) { +static int fileread(DstContext *c, DstParserStatus status) { size_t nread; FILE *f = (FILE *) c->user; + (void) status; dst_buffer_ensure(&c->buffer, CHUNKSIZE); nread = fread(c->buffer.data, 1, CHUNKSIZE, f); if (nread != CHUNKSIZE && ferror(f)) { @@ -149,11 +150,12 @@ int dst_context_run(DstContext *c, int flags) { int done = 0; int errflags = 0; DstParser parser; + DstParserStatus status; dst_parser_init(&parser, flags); while (!done) { int bufferdone = 0; while (!bufferdone) { - DstParserStatus status = dst_parser_status(&parser); + status = dst_parser_status(&parser); switch (status) { case DST_PARSE_FULL: { @@ -196,7 +198,7 @@ int dst_context_run(DstContext *c, int flags) { /* Refill the buffer */ c->buffer.count = 0; c->index = 0; - if (c->read_chunk(c) || c->buffer.count == 0) { + if (c->read_chunk(c, status) || c->buffer.count == 0) { done = 1; } } diff --git a/src/compiler/compile_specials.c b/src/compiler/specials.c similarity index 100% rename from src/compiler/compile_specials.c rename to src/compiler/specials.c diff --git a/src/core/ast.c b/src/core/ast.c index 2dc2eb41..facf823d 100644 --- a/src/core/ast.c +++ b/src/core/ast.c @@ -184,3 +184,5 @@ Dst dst_ast_unwrap(Dst x) { } } + + diff --git a/src/core/math.c b/src/core/math.c index ead2e44f..d908ef03 100644 --- a/src/core/math.c +++ b/src/core/math.c @@ -388,5 +388,6 @@ int dst_lib_math(DstArgs args) { dst_env_def(env, "pi", dst_wrap_real(3.1415926535897931)); dst_env_def(env, "e", dst_wrap_real(2.7182818284590451)); + dst_env_def(env, "inf", dst_wrap_real(1.0 / 0.0)); return 0; } diff --git a/src/include/dst/dstcompile.h b/src/include/dst/dstcompile.h index a64b609f..dacf3c59 100644 --- a/src/include/dst/dstcompile.h +++ b/src/include/dst/dstcompile.h @@ -67,7 +67,7 @@ struct DstContext { void *user; int32_t index; - int (*read_chunk)(DstContext *self); + int (*read_chunk)(DstContext *self, DstParserStatus status); void (*on_error)(DstContext *self, DstContextErrorType type, Dst err, size_t start, size_t end); void (*on_value)(DstContext *self, Dst value); void (*deinit)(DstContext *self); diff --git a/src/include/headerlibs/vector.h b/src/include/headerlibs/vector.h index 9389f4ff..34e1a13d 100644 --- a/src/include/headerlibs/vector.h +++ b/src/include/headerlibs/vector.h @@ -54,6 +54,7 @@ /* Vector code */ /* Grow the buffer dynamically. Used for push operations. */ +#ifndef DST_V_NODEF_GROW static void *dst_v_grow(void *v, int32_t increment, int32_t itemsize) { int32_t dbl_cur = (NULL != v) ? 2 * dst_v__cap(v) : 0; int32_t min_needed = dst_v_count(v) + increment; @@ -70,6 +71,7 @@ static void *dst_v_grow(void *v, int32_t increment, int32_t itemsize) { return (void *) (2 * sizeof(int32_t)); // try to force a NULL pointer exception later } } +#endif /* Clone a buffer. */ #ifdef DST_V_DEF_COPYMEM diff --git a/src/parser/parse.c b/src/parser/parse.c index 2f117143..f1a11b8a 100644 --- a/src/parser/parse.c +++ b/src/parser/parse.c @@ -155,6 +155,7 @@ struct DstParseState { }; #define PFLAG_CONTAINER 1 +#define PFLAG_WASTOKEN 2 static void pushstate(DstParser *p, Consumer consumer, int flags) { DstParseState s; @@ -475,10 +476,14 @@ const char *dst_parser_error(DstParser *parser) { Dst dst_parser_produce(DstParser *parser) { Dst ret; + int32_t i; DstParserStatus status = dst_parser_status(parser); if (status != DST_PARSE_FULL) return dst_wrap_nil(); - ret = dst_v_last(parser->argstack); - dst_v_pop(parser->argstack); + ret = parser->argstack[0]; + for (i = 1; i < dst_v_count(parser->argstack); i++) { + parser->argstack[i - 1] = parser->argstack[i]; + } + dst_v__cnt(parser->argstack)--; return ret; } diff --git a/test/hello.dst b/test/hello.dst new file mode 100644 index 00000000..e83d7385 --- /dev/null +++ b/test/hello.dst @@ -0,0 +1 @@ +(print "Hello, World!") diff --git a/test/scratch.dst b/test/scratch.dst new file mode 100644 index 00000000..48ce2551 --- /dev/null +++ b/test/scratch.dst @@ -0,0 +1,18 @@ +(def fib (asm '{ + bytecode [ + (load-integer 2 2) + (less-than 2 0 2) + (jump-if-not 2 2) + (return 0) + (load-self 1) + (add-immediate 0 0 -1) + (push 0) + (call 2 1) + (add-immediate 0 0 -1) + (push 0) + (call 3 1) + (add-integer 0 2 3) + (return 0) + ] + arity 1 +})) diff --git a/test/suite0.dst b/test/suite0.dst index 2459bba2..f7c87797 100644 --- a/test/suite0.dst +++ b/test/suite0.dst @@ -151,6 +151,10 @@ '(1 2 3) 5 :apple 1)) "struct order does not matter 2") +# Symbol function + +(assert (= (symbol "abc" 1 2 3) 'abc123) "symbol function") + # Fiber tests (def afiber (fiber (fn [x] @@ -163,7 +167,7 @@ # yield tests -(def t (fiber (fn [] (transfer nil 1) (transfer nil 2) 3))) +(def t (fiber (fn [] (transfer nil 1) (yield 2) 3))) (assert (= 1 (transfer t)) "initial transfer to new fiber") (assert (= 2 (transfer t)) "second transfer to fiber")