├── .gitattributes ├── vs2015.bat ├── src ├── tests │ ├── test_fs.lip │ ├── mod7.lip │ ├── mod9.lip │ ├── test.mod.lip │ ├── test.mod3.lip │ ├── mod6.lip │ ├── test_helpers.c │ ├── test │ │ └── mod2.lip │ ├── mod5.lip │ ├── mod4.lip │ ├── temp_allocator.h │ ├── cpp.c │ ├── repl.sh │ ├── main.c │ ├── io.c │ ├── cpp-compat.cpp │ ├── temp_allocator.c │ ├── array.c │ ├── runtime_helper.h │ ├── arena_allocator.c │ ├── test_helpers.h │ ├── bind.c │ ├── module.c │ └── lexer.c ├── dbg-ui │ ├── .gitattributes │ ├── .gitignore │ ├── .babelrc │ ├── src │ │ ├── CodeView.scss │ │ ├── index.js │ │ ├── Collapsible.scss │ │ ├── splitPane.js │ │ ├── Collapsible.js │ │ ├── splitPane.scss │ │ ├── CallStackView.js │ │ ├── krueger.js │ │ ├── CodeView.js │ │ ├── Toolbar.js │ │ ├── ValueListView.js │ │ ├── hal.js │ │ └── App.js │ ├── .tern-project │ ├── main.js │ ├── index.html │ ├── Makefile.nu │ ├── package.json │ ├── styles │ │ └── main.scss │ ├── .jshintrc │ ├── webpack.config.js │ └── incbin.bat ├── core │ ├── vm_dispatch.h │ ├── arena_allocator.h │ ├── memory.c │ ├── platform.h │ ├── khash_impl.c │ ├── vendor │ │ └── format │ │ │ ├── LICENSE │ │ │ ├── format.h │ │ │ └── format_config.h │ ├── utils.h │ ├── io.c │ ├── platform.c │ ├── array.c │ ├── prim_ops.c │ ├── repl.c │ ├── lip_internal.h │ ├── vm.c │ ├── vm_ops │ └── vm_dispatch.c ├── std │ ├── memory.c │ ├── runtime.c │ ├── io.c │ └── vendor │ │ └── sort_r.h ├── cli.h └── compiler │ └── main.c ├── tools └── genie.exe ├── with-musl-gcc ├── with-clang ├── with-ccache ├── benchmark ├── fib.py ├── fib.scm ├── fib.lua ├── fib.lip └── benchmark ├── with-musl-clang ├── .gitignore ├── include └── lip │ ├── std │ ├── lib.h │ ├── memory.h │ ├── common.h │ ├── runtime.h │ └── io.h │ ├── core │ ├── pp.h │ ├── sexp.h │ ├── module.h │ ├── prim_ops.h │ ├── extra.h │ ├── compiler.h │ ├── opcode.h │ ├── parser.h │ ├── print.h │ ├── lexer.h │ ├── ast.h │ ├── memory.h │ ├── array.h │ ├── asm.h │ ├── io.h │ └── vm.h │ ├── config.h │ └── dbg.h ├── watch ├── .gitmodules ├── appveyor.yml ├── LICENSE ├── .travis.yml ├── README.md ├── genie.lua └── Makefile.nu /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /vs2015.bat: -------------------------------------------------------------------------------- 1 | tools\genie.exe vs2015 -------------------------------------------------------------------------------- /src/tests/test_fs.lip: -------------------------------------------------------------------------------- 1 | (+ 0.5 1.5) 2 | -------------------------------------------------------------------------------- /src/dbg-ui/.gitattributes: -------------------------------------------------------------------------------- 1 | /incbin.bat binary 2 | -------------------------------------------------------------------------------- /src/tests/mod7.lip: -------------------------------------------------------------------------------- 1 | (declare 'b true (fn (x) (declare 'a true identity))) 2 | -------------------------------------------------------------------------------- /src/tests/mod9.lip: -------------------------------------------------------------------------------- 1 | (declare 'b true print) 2 | 3 | (identity 3 4 5 6) 4 | -------------------------------------------------------------------------------- /tools/genie.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bullno1/lip/HEAD/tools/genie.exe -------------------------------------------------------------------------------- /src/dbg-ui/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /bundle.js 3 | /bundle.js.map 4 | /gen 5 | -------------------------------------------------------------------------------- /src/tests/test.mod.lip: -------------------------------------------------------------------------------- 1 | (declare 'test-fun true (fn (x) (test.mod2/test-fun x))) 2 | -------------------------------------------------------------------------------- /src/tests/test.mod3.lip: -------------------------------------------------------------------------------- 1 | (declare 'test-fun2 true (fn (x) (- (test.mod2/test-fun x) 2))) 2 | -------------------------------------------------------------------------------- /with-musl-gcc: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | export CC='musl-gcc -static -D__MUSL__' 4 | exec $@ 5 | -------------------------------------------------------------------------------- /src/dbg-ui/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["es2015", { loose: true, modules: false }]] 3 | } 4 | -------------------------------------------------------------------------------- /src/tests/mod6.lip: -------------------------------------------------------------------------------- 1 | (declare 'a true (fn (x) (b x))) 2 | 3 | (declare 'b false (fn (x) x)) 4 | -------------------------------------------------------------------------------- /with-clang: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | export CC=clang 4 | export CXX='clang++ -std=c++11' 5 | exec $@ 6 | -------------------------------------------------------------------------------- /with-ccache: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | export CC="ccache ${CC:-cc}" 4 | export CXX="ccache ${CXX:-c++}" 5 | exec $* 6 | -------------------------------------------------------------------------------- /src/tests/test_helpers.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #if LIP_DYNAMIC==1 4 | #include 5 | #endif 6 | -------------------------------------------------------------------------------- /src/dbg-ui/src/CodeView.scss: -------------------------------------------------------------------------------- 1 | .active-range { 2 | background-color: #d9d9d9; 3 | } 4 | 5 | .CodeMirror { 6 | height: 100%; 7 | } 8 | -------------------------------------------------------------------------------- /src/tests/test/mod2.lip: -------------------------------------------------------------------------------- 1 | (test/count-load) ; Count number of times this script was loaded 2 | 3 | (declare 'test-fun true (fn (x) (+ x 1))) 4 | -------------------------------------------------------------------------------- /benchmark/fib.py: -------------------------------------------------------------------------------- 1 | def fib(n): 2 | if n < 2: 3 | return n 4 | else: 5 | return fib(n - 1) + fib(n - 2) 6 | 7 | print(fib(41)) 8 | -------------------------------------------------------------------------------- /benchmark/fib.scm: -------------------------------------------------------------------------------- 1 | (define (fib n) 2 | (if (< n 2) 3 | n 4 | (+ (fib (- n 1)) (fib (- n 2))))) 5 | 6 | (display (fib 41)) 7 | (newline) 8 | -------------------------------------------------------------------------------- /src/tests/mod5.lip: -------------------------------------------------------------------------------- 1 | (declare 'a true (fn (y) (b y c))) ; Should fail to link 2 | 3 | ; b is defined after a 4 | (declare 'b true (fn (x) (* 2 x))) 5 | -------------------------------------------------------------------------------- /src/dbg-ui/src/index.js: -------------------------------------------------------------------------------- 1 | import { mount } from './krueger'; 2 | import * as App from './App'; 3 | 4 | mount(App, document.getElementById('container')); 5 | -------------------------------------------------------------------------------- /src/dbg-ui/.tern-project: -------------------------------------------------------------------------------- 1 | { 2 | "ecmaVersion": 6, 3 | "libs": [ 4 | "browser" 5 | ], 6 | "plugins": { 7 | "node": {} 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/dbg-ui/main.js: -------------------------------------------------------------------------------- 1 | //All scripts start from src/index.js 2 | //Please do not add anything here 3 | import './src/index'; 4 | 5 | import './styles/main.scss'; 6 | -------------------------------------------------------------------------------- /with-musl-clang: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | export CC='musl-clang -Wno-unused-command-line-argument -static -D__MUSL__' 4 | export CXX='clang++ -std=c++11' 5 | exec $@ 6 | -------------------------------------------------------------------------------- /benchmark/fib.lua: -------------------------------------------------------------------------------- 1 | local function fib(n) 2 | if n < 2 then 3 | return n 4 | else 5 | return fib(n - 1) + fib(n - 2) 6 | end 7 | end 8 | 9 | print(fib(41)) 10 | -------------------------------------------------------------------------------- /benchmark/fib.lip: -------------------------------------------------------------------------------- 1 | (letrec ((fib (fn (n) 2 | (if (< n 2) 3 | n 4 | (+ (fib (- n 1)) 5 | (fib (- n 2))))))) 6 | (print (fib 41))) 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.numake 2 | /bin 3 | /doc 4 | /include/lip/gen 5 | /.build 6 | /vs2015 7 | /.ycm_extra_conf.py 8 | /.ycm_extra_conf.pyc 9 | /cover 10 | /__pycache__ 11 | /.ninja_deps 12 | /.ninja_log 13 | *.gcno 14 | -------------------------------------------------------------------------------- /benchmark/benchmark: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | DIR=$(dirname $(readlink -f $0)) 4 | 5 | set -x 6 | 7 | time python $DIR/fib.py 8 | time lua $DIR/fib.lua 9 | time bin/lip $DIR/fib.lip 10 | time guile $DIR/fib.scm 11 | -------------------------------------------------------------------------------- /include/lip/std/lib.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_STD_LIB_H 2 | #define LIP_STD_LIB_H 3 | 4 | #include 5 | #include "common.h" 6 | 7 | LIP_STD_API void 8 | lip_load_stdlib(lip_context_t* ctx); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/dbg-ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | lip-dbg 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /watch: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | NUMAKE=$(readlink -f numake) 4 | 5 | ${NUMAKE} $@ 6 | ${NUMAKE} --gc 7 | while : 8 | do 9 | inotifywait -e close_write -r src include `find . -name '*.nu'` || exit 1 10 | ${NUMAKE} $@ 11 | done 12 | -------------------------------------------------------------------------------- /include/lip/std/memory.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_STD_MEMORY_H 2 | #define LIP_STD_MEMORY_H 3 | 4 | #include "common.h" 5 | 6 | /// An allocator that uses `realloc` and `free` 7 | LIP_STD_API lip_allocator_t* const lip_std_allocator; 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /src/tests/mod4.lip: -------------------------------------------------------------------------------- 1 | (test/count-load) 2 | 3 | (declare 'a true (fn (y) (b y))) 4 | 5 | ; b is defined after a 6 | (declare 'b true (fn (x) (* 2 x))) 7 | 8 | ; should not fail to link 9 | (declare 'c true (fn (x) (print x))) 10 | 11 | (b 2) ; Should not crash 12 | -------------------------------------------------------------------------------- /src/core/vm_dispatch.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_VM_DISPATCH_H 2 | #define LIP_VM_DISPATCH_H 3 | 4 | #include 5 | 6 | lip_exec_status_t 7 | lip_vm_loop(lip_vm_t* vm); 8 | 9 | lip_exec_status_t 10 | lip_vm_do_call(lip_vm_t* vm, lip_value_t* fn, uint8_t num_args); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /src/tests/temp_allocator.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_TEMP_ALLOCATOR_H 2 | #define LIP_TEMP_ALLOCATOR_H 3 | 4 | #include 5 | 6 | lip_allocator_t* 7 | lip_temp_allocator_create(lip_allocator_t* allocator); 8 | 9 | void 10 | lip_temp_allocator_destroy(lip_allocator_t* allocator); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /src/dbg-ui/Makefile.nu: -------------------------------------------------------------------------------- 1 | gen: bundle.js 2 | mkdir -p $@ 3 | ./incbin.bat bundle.js gen/bundle.js.h bundle_js 4 | ./incbin.bat bundle.js.map gen/bundle.js.map.h bundle_js_map 5 | ./incbin.bat index.html gen/index.html.h index_html 6 | 7 | bundle.js: package.json ! live 8 | npm install 9 | npm run build 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/linenoise"] 2 | path = deps/linenoise 3 | url = https://github.com/yhirose/linenoise 4 | branch = utf8-support 5 | [submodule "deps/cmp"] 6 | path = deps/cmp 7 | url = https://github.com/camgunz/cmp 8 | [submodule "deps/optparse"] 9 | path = deps/optparse 10 | url = https://github.com/skeeto/optparse 11 | -------------------------------------------------------------------------------- /include/lip/core/pp.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_CORE_PP_H 2 | #define LIP_CORE_PP_H 3 | 4 | #include "extra.h" 5 | #include "sexp.h" 6 | 7 | typedef struct lip_pp_s lip_pp_t; 8 | typedef lip_error_m(lip_sexp_t*) lip_pp_result_t; 9 | 10 | struct lip_pp_s 11 | { 12 | lip_allocator_t* allocator; 13 | }; 14 | 15 | LIP_CORE_API lip_pp_result_t 16 | lip_preprocess(lip_pp_t* pp, lip_sexp_t* sexp); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /src/core/arena_allocator.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_ARENA_ALLOCATOR_H 2 | #define LIP_ARENA_ALLOCATOR_H 3 | 4 | #include 5 | 6 | lip_allocator_t* 7 | lip_arena_allocator_create( 8 | lip_allocator_t* allocator, size_t chunk_size, bool relocatable 9 | ); 10 | 11 | void 12 | lip_arena_allocator_destroy(lip_allocator_t* arena_allocator); 13 | 14 | void 15 | lip_arena_allocator_reset(lip_allocator_t* arena_allocator); 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /src/tests/cpp.c: -------------------------------------------------------------------------------- 1 | #include "munit.h" 2 | 3 | void test_cpp(void); 4 | 5 | static MunitResult 6 | test(const MunitParameter params[], void* fixture) 7 | { 8 | (void)params; 9 | (void)fixture; 10 | 11 | test_cpp(); 12 | 13 | return MUNIT_OK; 14 | } 15 | 16 | static MunitTest tests[] = { 17 | { 18 | .name = "/cpp", 19 | .test = test, 20 | }, 21 | { .test = NULL } 22 | }; 23 | 24 | MunitSuite cpp = { 25 | .prefix = "/cpp", 26 | .tests = tests 27 | }; 28 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '0.2.{build}' 2 | image: Visual Studio 2015 3 | 4 | configuration: 5 | - ReleaseLib 6 | - DebugLib 7 | - ReleaseDLL 8 | - DebugDLL 9 | 10 | install: 11 | - git submodule update --init --recursive 12 | 13 | before_build: 14 | - tools\genie.exe vs2015 15 | 16 | build: 17 | parallel: true 18 | project: vs2015/lip.sln 19 | verbosity: normal 20 | 21 | test_script: 22 | - bin\%CONFIGURATION%\tests.exe 23 | - bin\%CONFIGURATION%\repl.exe -v 24 | -------------------------------------------------------------------------------- /src/dbg-ui/src/Collapsible.scss: -------------------------------------------------------------------------------- 1 | .collapsible { 2 | .collapsible-heading { 3 | user-select: none; 4 | cursor: pointer; 5 | 6 | &:before { 7 | content: "\25bc"; 8 | display: inline-block; 9 | width: 25px; 10 | text-align: left; 11 | } 12 | } 13 | 14 | &.collapsed { 15 | .collapsible-heading:before { 16 | position: relative; 17 | content: "\25b8"; 18 | top: -1px; 19 | left: 4px; 20 | } 21 | 22 | .collapsible-body { 23 | display: none; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/std/memory.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | static void* 5 | lip_crt_realloc(lip_allocator_t* self, void* old, size_t size) 6 | { 7 | (void)self; 8 | return realloc(old, size); 9 | } 10 | 11 | static void 12 | lip_crt_free(lip_allocator_t* self, void* mem) 13 | { 14 | (void)self; 15 | free(mem); 16 | } 17 | static lip_allocator_t lip_crt_allocator = { 18 | .realloc = lip_crt_realloc, 19 | .free = lip_crt_free 20 | }; 21 | 22 | lip_allocator_t* const lip_std_allocator = &lip_crt_allocator; 23 | -------------------------------------------------------------------------------- /src/tests/repl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ex 2 | 3 | LIP=$1 4 | 5 | test "$(echo '(print (identity true))' | $LIP)" = "true" 6 | test "$($LIP -i -e '(print (identity true))')" = "true" 7 | test "$(echo '(print (identity true))' | $LIP -i)" = "true" 8 | 9 | echo '(print "Hello world")' | $LIP --compile bin/hello_world.lipc 10 | test "$($LIP bin/hello_world.lipc)" = '"Hello world"' 11 | 12 | echo '(print "Hello world again")' > bin/temp.lip 13 | $LIP --compile bin/temp.lipc bin/temp.lip 14 | test "$($LIP bin/temp.lipc)" = '"Hello world again"' 15 | -------------------------------------------------------------------------------- /include/lip/core/sexp.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_CORE_SEXP_H 2 | #define LIP_CORE_SEXP_H 3 | 4 | #include "common.h" 5 | 6 | #define LIP_SEXP(F) \ 7 | F(LIP_SEXP_LIST) \ 8 | F(LIP_SEXP_SYMBOL) \ 9 | F(LIP_SEXP_STRING) \ 10 | F(LIP_SEXP_NUMBER) 11 | 12 | LIP_ENUM(lip_sexp_type_t, LIP_SEXP) 13 | 14 | typedef struct lip_sexp_s lip_sexp_t; 15 | 16 | struct lip_sexp_s 17 | { 18 | lip_sexp_type_t type; 19 | lip_loc_range_t location; 20 | union 21 | { 22 | lip_array(lip_sexp_t) list; 23 | lip_string_ref_t string; 24 | } data; 25 | }; 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /include/lip/std/common.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_STD_COMMON_H 2 | #define LIP_STD_COMMON_H 3 | 4 | #include 5 | 6 | #if LIP_DYNAMIC == 1 7 | # ifdef _WIN32 8 | # ifdef LIP_STD_BUILDING 9 | # define LIP_STD_DECL __declspec(dllexport) 10 | # else 11 | # define LIP_STD_DECL __declspec(dllimport) 12 | # endif 13 | # else 14 | # ifdef LIP_STD_BUILDING 15 | # define LIP_STD_DECL __attribute__((visibility("default"))) 16 | # else 17 | # define LIP_STD_DECL 18 | # endif 19 | # endif 20 | #else 21 | # define LIP_STD_DECL 22 | #endif 23 | 24 | #define LIP_STD_API LIP_LINKAGE LIP_STD_DECL 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /src/dbg-ui/src/splitPane.js: -------------------------------------------------------------------------------- 1 | import h from 'snabbdom/h'; 2 | import Split from 'split.js'; 3 | import './splitPane.scss'; 4 | 5 | export const splitPane = (opts, children) => { 6 | const childElms = new Array(children.length); 7 | let count = 0; 8 | 9 | return h("div.full-height", 10 | children.map((child, index) => h("div.split", { 11 | class: { ["split-" + opts.direction]: true }, 12 | hook: { 13 | insert: (vnode) => { 14 | childElms[index] = vnode.elm; 15 | if(++count == childElms.length) { 16 | Split(childElms, opts); 17 | } 18 | } 19 | } 20 | }, [child])) 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/tests/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "munit.h" 4 | 5 | #define FOREACH_SUITE(F) \ 6 | F(io) \ 7 | F(array) \ 8 | F(lexer) \ 9 | F(parser) \ 10 | F(assembler) \ 11 | F(arena_allocator) \ 12 | F(vm) \ 13 | F(runtime()) \ 14 | F(bind) \ 15 | F(cpp) 16 | 17 | #define DECLARE_SUITE(S) extern MunitSuite S; 18 | 19 | #define IMPORT_SUITE(S) S, 20 | 21 | FOREACH_SUITE(DECLARE_SUITE) 22 | 23 | int 24 | main(int argc, char* argv[]) 25 | { 26 | MunitSuite all_suites[] = { 27 | FOREACH_SUITE(IMPORT_SUITE) 28 | { .tests = NULL, .suites = NULL } 29 | }; 30 | 31 | MunitSuite main_suite = { 32 | .prefix = "", 33 | .suites = all_suites 34 | }; 35 | 36 | return munit_suite_main(&main_suite, NULL, argc, argv); 37 | } 38 | -------------------------------------------------------------------------------- /include/lip/core/module.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_CORE_MODULE_H 2 | #define LIP_CORE_MODULE_H 3 | 4 | #include "common.h" 5 | #include "extra.h" 6 | 7 | typedef struct lip_module_info_s lip_module_info_t; 8 | typedef struct lip_module_runtime_s lip_module_runtime_t; 9 | 10 | struct kk 11 | 12 | struct lip_module_info_s 13 | { 14 | size_t num_dependencies; 15 | lip_string_ref_t* dependencies; 16 | }; 17 | 18 | typedef lip_error_m(lip_module_info_t) lip_module_preload_t; 19 | 20 | struct lip_module_loader_s 21 | { 22 | lip_module_preload_t(*begin_load)( 23 | lip_module_loader_t* loader, lip_string_ref_t name 24 | ); 25 | bool(*initialize)(lip_module_loader_t* loader); 26 | bool(*load)(lip_module_loader_t* loader); 27 | void(*end_load)(lip_module_loader_t* loader); 28 | }; 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /include/lip/config.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_CONFIG_H 2 | #define LIP_CONFIG_H 3 | 4 | #ifdef LIP_HAVE_CONFIG_H 5 | # include "gen/config.h" 6 | #else 7 | # define LIP_VERSION "0.1" 8 | #endif 9 | 10 | #ifdef __cplusplus 11 | # define LIP_LINKAGE extern "C" 12 | #else 13 | # define LIP_LINKAGE extern 14 | #endif 15 | 16 | #if defined(LIP_SINGLE_THREADED) 17 | # define LIP_THREADING_DUMMY "dummy" 18 | # define LIP_THREADING_API LIP_THREADING_DUMMY 19 | #elif defined(__linux__) 20 | # define LIP_THREADING_PTHREAD "pthread" 21 | # define LIP_THREADING_API LIP_THREADING_PTHREAD 22 | #elif defined(_WIN32) || defined(_WIN64) 23 | # define LIP_THREADING_WINAPI "winapi" 24 | # define LIP_THREADING_API LIP_THREADING_WINAPI 25 | #else 26 | # error "Unspported platform. Please recompile with LIP_SINGLE_THREADED" 27 | #endif 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /include/lip/core/prim_ops.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_CORE_PRIM_OPS_H 2 | #define LIP_CORE_PRIM_OPS_H 3 | 4 | #include "common.h" 5 | #include 6 | 7 | #define LIP_PRIM_OP(F) \ 8 | F(+, ADD) \ 9 | F(-, SUB) \ 10 | F(*, MUL) \ 11 | F(/, FDIV) \ 12 | F(!, NOT) \ 13 | F(cmp, CMP) \ 14 | LIP_CMP_OP(F) 15 | 16 | #define LIP_CMP_OP(F) \ 17 | F(==, EQ) \ 18 | F(!=, NEQ) \ 19 | F(>, GT) \ 20 | F(<, LT) \ 21 | F(>=, GTE) \ 22 | F(<=, LTE) \ 23 | 24 | #define LIP_PRIM_OP_FN_NAME(name) lip_pp_concat(lip_, name) 25 | #define LIP_PRIM_OP_FN(name) \ 26 | LIP_CORE_API lip_exec_status_t LIP_PRIM_OP_FN_NAME(name)( \ 27 | lip_vm_t* vm, lip_value_t* result, \ 28 | unsigned int argc, const lip_value_t* argv \ 29 | ) 30 | 31 | #define LIP_DECLARE_PRIM_OP(op, name) \ 32 | LIP_PRIM_OP_FN(name); 33 | 34 | LIP_PRIM_OP(LIP_DECLARE_PRIM_OP) 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /src/dbg-ui/src/Collapsible.js: -------------------------------------------------------------------------------- 1 | import h from 'snabbdom/h'; 2 | import Union from 'union-type'; 3 | import curry from 'ramda/src/curry'; 4 | import over from 'ramda/src/over'; 5 | import './Collapsible.scss'; 6 | 7 | export const init = () => false; 8 | 9 | export const Action = Union({ 10 | SetCollapsed: [Boolean] 11 | }); 12 | 13 | export const update = curry(Action.caseOn({ 14 | SetCollapsed: (collapsed) => collapsed 15 | })); 16 | 17 | export const updateNested = curry((lens, action, model) => 18 | over(lens, update(action), model) 19 | ); 20 | 21 | export const render = (collapsed, actions$, heading, body) => 22 | h("div.collapsible", { class: { "collapsed": collapsed } }, [ 23 | h("div.collapsible-heading.pure-menu-heading", { 24 | on: { click: [ actions$, Action.SetCollapsed(!collapsed) ] } 25 | }, heading), 26 | h("div.collapsible-body", body) 27 | ]); 28 | -------------------------------------------------------------------------------- /src/tests/io.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "munit.h" 3 | 4 | static MunitResult 5 | sstream(const MunitParameter params[], void* fixture) 6 | { 7 | (void)params; 8 | (void)fixture; 9 | 10 | const char* text = 11 | "first line\n" 12 | "second line\n" 13 | ; 14 | lip_string_ref_t ref = lip_string_ref(text); 15 | struct lip_isstream_s sstream; 16 | char ch; 17 | lip_in_t* input = lip_make_isstream(ref, &sstream); 18 | for(unsigned int i = 0; i < ref.length; ++i) 19 | { 20 | munit_assert_size(1, ==, lip_read(&ch, sizeof(ch), input)); 21 | munit_assert_char(text[i], ==, ch); 22 | } 23 | 24 | munit_assert_size(0, ==, lip_read(&ch, sizeof(ch), input)); 25 | 26 | return MUNIT_OK; 27 | } 28 | 29 | static MunitTest tests[] = { 30 | { 31 | .name = "/sstream", 32 | .test = sstream 33 | }, 34 | { .test = NULL } 35 | }; 36 | 37 | MunitSuite io = { 38 | .prefix = "/io", 39 | .tests = tests 40 | }; 41 | -------------------------------------------------------------------------------- /src/dbg-ui/src/splitPane.scss: -------------------------------------------------------------------------------- 1 | .split { 2 | -webkit-box-sizing: border-box; 3 | -moz-box-sizing: border-box; 4 | box-sizing: border-box; 5 | 6 | overflow-y: auto; 7 | overflow-x: hidden; 8 | } 9 | 10 | .gutter { 11 | background-color: #F6F6F6; 12 | background-repeat: no-repeat; 13 | background-position: 50%; 14 | } 15 | 16 | .gutter.gutter-horizontal { 17 | cursor: col-resize; 18 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==') 19 | } 20 | 21 | .gutter.gutter-vertical { 22 | cursor: row-resize; 23 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFAQMAAABo7865AAAABlBMVEVHcEzMzMzyAv2sAAAAAXRSTlMAQObYZgAAABBJREFUeF5jOAMEEAIEEFwAn3kMwcB6I2AAAAAASUVORK5CYII=') 24 | } 25 | 26 | .split.split-horizontal, .gutter.gutter-horizontal { 27 | height: 100%; 28 | float: left; 29 | } 30 | -------------------------------------------------------------------------------- /src/core/memory.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | lip_memblock_info_t 4 | lip_align_memblocks(unsigned int num_blocks, lip_memblock_info_t** blocks) 5 | { 6 | lip_memblock_info_t result = { 7 | .element_size = sizeof(char), 8 | .alignment = 0, 9 | .num_elements = 0, 10 | .offset = 0 11 | }; 12 | 13 | // Find max alignment 14 | for(unsigned int i = 0; i < num_blocks; ++i) 15 | { 16 | result.alignment = LIP_MAX(result.alignment, blocks[i]->alignment); 17 | } 18 | 19 | // Assume base addressed is aligned to the max alignment, place blocks 20 | for(unsigned int i = 0; i < num_blocks; ++i) 21 | { 22 | lip_memblock_info_t* block = blocks[i]; 23 | 24 | size_t rem = result.num_elements % block->alignment; 25 | size_t shift = result.alignment - rem; 26 | result.num_elements += rem == 0 ? 0 : shift; 27 | block->offset = result.num_elements; 28 | 29 | result.num_elements += block->num_elements * block->element_size; 30 | } 31 | 32 | return result; 33 | } 34 | -------------------------------------------------------------------------------- /src/core/platform.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_PLATFORM_H 2 | #define LIP_PLATFORM_H 3 | 4 | #if defined(__GNUC__) || defined(__clang__) 5 | # define _XOPEN_SOURCE 700 6 | #endif 7 | 8 | #include 9 | #include 10 | 11 | #if defined(LIP_THREADING_DUMMY) 12 | 13 | typedef char lip_rwlock_t; 14 | 15 | #elif defined(LIP_THREADING_PTHREAD) 16 | 17 | #include 18 | 19 | typedef pthread_rwlock_t lip_rwlock_t; 20 | 21 | #elif defined(LIP_THREADING_WINAPI) 22 | 23 | #define WIN32_LEAN_AND_MEAN 24 | #include 25 | 26 | typedef SRWLOCK lip_rwlock_t; 27 | 28 | #endif 29 | 30 | bool 31 | lip_rwlock_init(lip_rwlock_t* rwlock); 32 | 33 | void 34 | lip_rwlock_destroy(lip_rwlock_t* rwlock); 35 | 36 | bool 37 | lip_rwlock_begin_read(lip_rwlock_t* rwlock); 38 | 39 | void 40 | lip_rwlock_end_read(lip_rwlock_t* rwlock); 41 | 42 | bool 43 | lip_rwlock_begin_write(lip_rwlock_t* rwlock); 44 | 45 | void 46 | lip_rwlock_end_write(lip_rwlock_t* rwlock); 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /include/lip/core/extra.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_CORE_EXTRA_H 2 | #define LIP_CORE_EXTRA_H 3 | 4 | #include "common.h" 5 | #include "vendor/khash.h" 6 | 7 | KHASH_DECLARE(lip_string_ref_set, lip_string_ref_t, char) 8 | 9 | #define LIP_STREAM(F) \ 10 | F(LIP_STREAM_OK) \ 11 | F(LIP_STREAM_ERROR) \ 12 | F(LIP_STREAM_END) 13 | 14 | LIP_ENUM(lip_stream_status_t, LIP_STREAM) 15 | 16 | #define lip_error_m(T) \ 17 | struct { \ 18 | bool success; \ 19 | union { \ 20 | T result; \ 21 | lip_error_t error; \ 22 | } value; \ 23 | } 24 | 25 | typedef struct lip_function_s lip_function_t; 26 | typedef struct lip_closure_s lip_closure_t; 27 | typedef struct lip_error_s lip_error_t; 28 | typedef struct lip_last_error_s lip_last_error_t; 29 | typedef struct lip_error_m_s lip_error_m; 30 | 31 | struct lip_error_s 32 | { 33 | unsigned int code; 34 | lip_loc_range_t location; 35 | const void* extra; 36 | }; 37 | 38 | struct lip_last_error_s 39 | { 40 | lip_error_t error; 41 | lip_error_t* errorp; 42 | }; 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /include/lip/core/compiler.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_CORE_COMPILER_H 2 | #define LIP_CORE_COMPILER_H 3 | 4 | #include "extra.h" 5 | #include "ast.h" 6 | 7 | typedef struct lip_compiler_s lip_compiler_t; 8 | typedef struct lip_scope_s lip_scope_t; 9 | 10 | struct lip_compiler_s 11 | { 12 | lip_allocator_t* allocator; 13 | lip_allocator_t* arena_allocator; 14 | lip_string_ref_t source_name; 15 | lip_scope_t* current_scope; 16 | lip_scope_t* free_scopes; 17 | khash_t(lip_string_ref_set)* free_var_names; 18 | }; 19 | 20 | LIP_CORE_API void 21 | lip_compiler_init(lip_compiler_t* compiler, lip_allocator_t* allocator); 22 | 23 | LIP_CORE_API void 24 | lip_compiler_cleanup(lip_compiler_t* compiler); 25 | 26 | LIP_CORE_API void 27 | lip_compiler_begin(lip_compiler_t* compiler, lip_string_ref_t source_name); 28 | 29 | LIP_CORE_API void 30 | lip_compiler_add_ast(lip_compiler_t* compiler, const lip_ast_t* ast); 31 | 32 | LIP_CORE_API lip_function_t* 33 | lip_compiler_end(lip_compiler_t* compiler, lip_allocator_t* allocator); 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /include/lip/core/opcode.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_CORE_OPCODE_H 2 | #define LIP_CORE_OPCODE_H 3 | 4 | #include "common.h" 5 | 6 | #define LIP_LDI_MIN -8388608 7 | #define LIP_LDI_MAX 8388607 8 | 9 | #define LIP_OP(F) \ 10 | F(LIP_OP_NOP) \ 11 | F(LIP_OP_POP) \ 12 | F(LIP_OP_NIL) \ 13 | F(LIP_OP_LDK) \ 14 | F(LIP_OP_LDI) \ 15 | F(LIP_OP_LDB) \ 16 | F(LIP_OP_PLHR) \ 17 | F(LIP_OP_LARG) \ 18 | F(LIP_OP_LDLV) \ 19 | F(LIP_OP_LDCV) \ 20 | F(LIP_OP_IMP) \ 21 | F(LIP_OP_IMPS) \ 22 | F(LIP_OP_SET) \ 23 | F(LIP_OP_JMP) \ 24 | F(LIP_OP_JOF) \ 25 | F(LIP_OP_CALL) \ 26 | F(LIP_OP_TAIL) \ 27 | F(LIP_OP_RET) \ 28 | F(LIP_OP_CLS) \ 29 | F(LIP_OP_RCLS) \ 30 | F(LIP_OP_ADD) \ 31 | F(LIP_OP_SUB) \ 32 | F(LIP_OP_MUL) \ 33 | F(LIP_OP_FDIV) \ 34 | F(LIP_OP_NOT) \ 35 | F(LIP_OP_CMP) \ 36 | F(LIP_OP_EQ) \ 37 | F(LIP_OP_NEQ) \ 38 | F(LIP_OP_GT) \ 39 | F(LIP_OP_LT) \ 40 | F(LIP_OP_GTE) \ 41 | F(LIP_OP_LTE) 42 | 43 | LIP_ENUM(lip_opcode_t, LIP_OP) 44 | 45 | typedef int32_t lip_instruction_t; 46 | typedef int32_t lip_operand_t; 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /include/lip/dbg.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_DBG_H 2 | #define LIP_DBG_H 3 | 4 | #include 5 | 6 | #if LIP_DYNAMIC == 1 7 | # ifdef _WIN32 8 | # ifdef LIP_DBG_BUILDING 9 | # define LIP_DBG_DECL __declspec(dllexport) 10 | # else 11 | # define LIP_DBG_DECL __declspec(dllimport) 12 | # endif 13 | # else 14 | # ifdef LIP_DBG_BUILDING 15 | # define LIP_DBG_DECL __attribute__((visibility("default"))) 16 | # else 17 | # define LIP_DBG_DECL 18 | # endif 19 | # endif 20 | #else 21 | # define LIP_DBG_DECL 22 | #endif 23 | 24 | #define LIP_DBG_API LIP_LINKAGE LIP_DBG_DECL 25 | 26 | typedef struct lip_dbg_s lip_dbg_t; 27 | typedef struct lip_dbg_config_s lip_dbg_config_t; 28 | 29 | struct lip_dbg_config_s 30 | { 31 | lip_allocator_t* allocator; 32 | lip_fs_t* fs; 33 | uint16_t port; 34 | bool hook_step; 35 | }; 36 | 37 | LIP_DBG_API lip_dbg_t* 38 | lip_create_debugger(lip_dbg_config_t* cfg); 39 | 40 | LIP_DBG_API void 41 | lip_destroy_debugger(lip_dbg_t* dbg); 42 | 43 | LIP_DBG_API void 44 | lip_attach_debugger(lip_dbg_t* dbg, lip_vm_t* vm); 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /include/lip/std/runtime.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_STD_RUNTIME_H 2 | #define LIP_STD_RUNTIME_H 3 | 4 | #include 5 | #include "common.h" 6 | 7 | /** 8 | * @brief Create a standard lip_runtime_config_s. 9 | * 10 | * @param allocator The allocator for the runtime config. 11 | * Pass NULL to use ::lip_std_allocator 12 | * 13 | * Details: 14 | * 15 | * - Use ::lip_std_allocator for lip_runtime_config_s::allocator. 16 | * - Initialize lip_runtime_config_s::default_vm_config with the following values: 17 | * - lip_vm_config_t::os_len: 256 18 | * - lip_vm_config_t::cs_len: 256 19 | * - lip_vm_config_t::env_len: 256 20 | * - Initialize lip_runtime_config_s::module_search_patterns with the following: 21 | * `?.lip`, `?.lipc`, `?/index.lip`, `?/index.lipc`, `!.lip`, `!.lipc`, 22 | * '!/index.lip`, `!/index.lipc` 23 | * 24 | */ 25 | LIP_STD_API lip_runtime_config_t* 26 | lip_create_std_runtime_config(lip_allocator_t* allocator); 27 | 28 | /// Destroy a previously created configuration 29 | LIP_STD_API void 30 | lip_destroy_std_runtime_config(lip_runtime_config_t* cfg); 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /include/lip/core/parser.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_CORE_PARSER_H 2 | #define LIP_CORE_PARSER_H 3 | 4 | #include "extra.h" 5 | #include "sexp.h" 6 | #include "lexer.h" 7 | 8 | #define LIP_PARSE_ERROR(F) \ 9 | F(LIP_PARSE_LEX_ERROR) \ 10 | F(LIP_PARSE_UNEXPECTED_TOKEN) \ 11 | F(LIP_PARSE_UNTERMINATED_LIST) 12 | 13 | LIP_ENUM(lip_parse_error_t, LIP_PARSE_ERROR) 14 | 15 | typedef struct lip_parser_s lip_parser_t; 16 | 17 | struct lip_parser_s 18 | { 19 | lip_allocator_t* allocator; 20 | lip_allocator_t* arena_allocator; 21 | lip_last_error_t last_error; 22 | lip_token_t token; 23 | lip_stream_status_t lexer_status; 24 | lip_lexer_t lexer; 25 | bool buffered; 26 | }; 27 | 28 | LIP_CORE_API void 29 | lip_parser_init(lip_parser_t* parser, lip_allocator_t* allocator); 30 | 31 | LIP_CORE_API void 32 | lip_parser_cleanup(lip_parser_t* parser); 33 | 34 | LIP_CORE_API void 35 | lip_parser_reset(lip_parser_t* parser, lip_in_t* input); 36 | 37 | LIP_CORE_API lip_stream_status_t 38 | lip_parser_next_sexp(lip_parser_t* parser, lip_sexp_t* sexp); 39 | 40 | LIP_CORE_API const lip_error_t* 41 | lip_parser_last_error(lip_parser_t* parser); 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /src/dbg-ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dbg-client", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack-dev-server --env.prod=false", 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "build": "webpack --env.prod=true" 10 | }, 11 | "author": "Bach Le ", 12 | "license": "BSD-2-Clause", 13 | "devDependencies": { 14 | "babel-core": "^6.3.15", 15 | "babel-loader": "^6.4.0", 16 | "babel-preset-es2015": "^6.22.0", 17 | "babel-preset-latest": "^6.22.0", 18 | "css-loader": "^0.27.1", 19 | "sass-loader": "^6.0.3", 20 | "style-loader": "^0.13.2", 21 | "webpack": "^2.2.1", 22 | "webpack-dev-server": "^2.4.1" 23 | }, 24 | "dependencies": { 25 | "codemirror": "^5.24.2", 26 | "flyd": "^0.2.4", 27 | "msgpack-lite": "^0.1.26", 28 | "node-sass": "^4.5.0", 29 | "normalize-scss": "^6.0.0", 30 | "purecss": "^0.6.2", 31 | "ramda": "^0.23.0", 32 | "snabbdom": "^0.6.6", 33 | "split.js": "^1.2.0", 34 | "union-type": "^0.3.3", 35 | "uri-template": "^1.0.1" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/core/khash_impl.c: -------------------------------------------------------------------------------- 1 | #include "lip_internal.h" 2 | #include 3 | #include 4 | #include 5 | #include "vendor/xxhash.h" 6 | 7 | #define lip_string_ref_hash(str) XXH32(str.ptr, str.length, __LINE__) 8 | #define lip_value_hash(ptr) XXH32(&ptr, sizeof(ptr), __LINE__) 9 | #define lip_value_equal(lhs, rhs) ((lhs) == (rhs)) 10 | 11 | __KHASH_IMPL( 12 | lip_string_ref_set, 13 | , 14 | lip_string_ref_t, 15 | char, 16 | 0, 17 | lip_string_ref_hash, 18 | lip_string_ref_equal 19 | ) 20 | 21 | __KHASH_IMPL( 22 | lip_module, 23 | , 24 | lip_string_ref_t, 25 | lip_symbol_t, 26 | 1, 27 | lip_string_ref_hash, 28 | lip_string_ref_equal 29 | ) 30 | 31 | __KHASH_IMPL( 32 | lip_symtab, 33 | , 34 | lip_string_ref_t, 35 | khash_t(lip_module)*, 36 | 1, 37 | lip_string_ref_hash, 38 | lip_string_ref_equal 39 | ) 40 | 41 | __KHASH_IMPL( 42 | lip_ptr_set, 43 | , 44 | void*, 45 | char, 46 | 0, 47 | lip_value_hash, 48 | lip_value_equal 49 | ) 50 | 51 | __KHASH_IMPL( 52 | lip_ptr_map, 53 | , 54 | const void*, 55 | void*, 56 | 1, 57 | lip_value_hash, 58 | lip_value_equal 59 | ) 60 | -------------------------------------------------------------------------------- /include/lip/core/print.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_CORE_PRINT_H 2 | #define LIP_CORE_PRINT_H 3 | 4 | #include 5 | #include "opcode.h" 6 | #include "ast.h" 7 | 8 | LIP_CORE_API void 9 | lip_print_instruction( 10 | lip_out_t* output, 11 | lip_instruction_t instr 12 | ); 13 | 14 | LIP_CORE_API void 15 | lip_print_value( 16 | unsigned int depth, 17 | unsigned int indent, 18 | lip_out_t* output, 19 | lip_value_t value 20 | ); 21 | 22 | LIP_CORE_API void 23 | lip_print_list( 24 | unsigned int depth, 25 | unsigned int indent, 26 | lip_out_t* output, 27 | const lip_list_t* list 28 | ); 29 | 30 | LIP_CORE_API void 31 | lip_print_closure( 32 | unsigned int depth, 33 | unsigned int indent, 34 | lip_out_t* output, 35 | const lip_closure_t* closure 36 | ); 37 | 38 | LIP_CORE_API void 39 | lip_print_script( 40 | unsigned int depth, 41 | unsigned int indent, 42 | lip_out_t* output, 43 | const lip_script_t* script 44 | ); 45 | 46 | LIP_CORE_API void 47 | lip_print_function( 48 | unsigned int depth, 49 | unsigned int indent, 50 | lip_out_t* output, 51 | const lip_function_t* function 52 | ); 53 | 54 | LIP_CORE_API void 55 | lip_print_ast( 56 | unsigned int depth, 57 | unsigned int indent, 58 | lip_out_t* output, 59 | const lip_ast_t* ast 60 | ); 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Bach Le 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11 | -------------------------------------------------------------------------------- /src/dbg-ui/src/CallStackView.js: -------------------------------------------------------------------------------- 1 | import h from 'snabbdom/h'; 2 | import assoc from 'ramda/src/assoc'; 3 | import lensProp from 'ramda/src/lensProp'; 4 | import forwardTo from 'flyd/module/forwardto'; 5 | import Union from 'union-type'; 6 | import * as Collapsible from 'Collapsible'; 7 | 8 | export const init = () => ({ 9 | callStack: [], 10 | activeStackLevel: 0, 11 | collapsible: Collapsible.init() 12 | }); 13 | 14 | export const Action = Union({ 15 | SetCallStack: [Array], 16 | SetActiveStackLevel: [Number], 17 | Collapsible: [Collapsible.Action] 18 | }); 19 | 20 | export const update = Action.caseOn({ 21 | SetCallStack: assoc('callStack'), 22 | SetActiveStackLevel: assoc('activeStackLevel'), 23 | Collapsible: Collapsible.updateNested(lensProp('collapsible')) 24 | }); 25 | 26 | export const render = (model, actions$) => 27 | Collapsible.render( 28 | model.collapsible, forwardTo(actions$, Action.Collapsible), 29 | "Call Stack", 30 | h("div.pure-menu", 31 | h("ul.pure-menu-list", model.callStack.map((entry, index) => 32 | h("li.pure-menu-item", 33 | { class: { "pure-menu-selected": index === model.activeStackLevel } }, 34 | h("a.pure-menu-link", { 35 | props: { href: '#' }, 36 | on: { click: [actions$, Action.SetActiveStackLevel(index)] } 37 | }, entry.filename) 38 | ) 39 | )) 40 | ) 41 | ); 42 | -------------------------------------------------------------------------------- /include/lip/std/io.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_STD_IO_H 2 | #define LIP_STD_IO_H 3 | 4 | #include "common.h" 5 | #include 6 | #include 7 | 8 | /** 9 | * @brief Output file stream. 10 | * @see lip_make_ofstream 11 | */ 12 | struct lip_ofstream_s 13 | { 14 | lip_out_t vtable; 15 | FILE* file; 16 | }; 17 | 18 | /** 19 | * @brief Input file stream. 20 | * @see lip_make_ifstream 21 | */ 22 | struct lip_ifstream_s 23 | { 24 | lip_in_t vtable; 25 | FILE* file; 26 | }; 27 | 28 | /** 29 | * @brief Create an instance of a native filesystem (fopen, fread, fclose...). 30 | * @param allocator Allocator that the filesystem will use. 31 | */ 32 | LIP_STD_API lip_fs_t* 33 | lip_create_std_fs(lip_allocator_t* allocator); 34 | 35 | /// Destroy the filesystem implementation previously created with ::lip_create_std_fs. 36 | LIP_STD_API void 37 | lip_destroy_std_fs(lip_fs_t* fs); 38 | 39 | /// Returns stdin. 40 | LIP_STD_API lip_in_t* 41 | lip_stdin(void); 42 | 43 | /// Returns stdout. 44 | LIP_STD_API lip_out_t* 45 | lip_stdout(void); 46 | 47 | /// Returns stderr. 48 | LIP_STD_API lip_out_t* 49 | lip_stderr(void); 50 | 51 | /// Create an output file stream. 52 | LIP_STD_API lip_out_t* 53 | lip_make_ofstream(FILE* file, struct lip_ofstream_s* ofstream); 54 | 55 | /// Create an input file stream. 56 | LIP_STD_API lip_in_t* 57 | lip_make_ifstream(FILE* file, struct lip_ifstream_s* ifstream); 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /src/dbg-ui/styles/main.scss: -------------------------------------------------------------------------------- 1 | @import '~normalize-scss/sass/normalize'; 2 | @include normalize(); 3 | @import '~purecss'; 4 | 5 | .full-height { 6 | height: 100%; 7 | } 8 | 9 | html, body { 10 | height: 100%; 11 | } 12 | 13 | .content { 14 | box-shadow: inset 0 1px 2px #e4e4e4; 15 | background-color: #fff; 16 | height: 100%; 17 | } 18 | 19 | i.icon { 20 | font-style: normal; 21 | position: relative; 22 | white-space: pre; 23 | width: 20px; 24 | display: inline-block; 25 | 26 | &.icon-continue { 27 | &:after { 28 | content: "\25ba"; 29 | } 30 | } 31 | 32 | &.icon-break { 33 | &:after { 34 | content: "\2590\A0\258C"; 35 | } 36 | } 37 | 38 | &.icon-step { 39 | &:after { 40 | content: "\2911"; 41 | font-size: 150%; 42 | position: absolute; 43 | top: -5px; 44 | left: 0px; 45 | } 46 | } 47 | 48 | &.icon-step-over { 49 | &:after { 50 | font-size: 185%; 51 | position: absolute; 52 | top: -5.5px; 53 | left: 17px; 54 | content: "\20d5"; 55 | } 56 | } 57 | 58 | &.icon-step-into { 59 | &:after { 60 | position: absolute; 61 | top: -7px; 62 | left: 2.5px; 63 | content: "\21b4"; 64 | } 65 | } 66 | 67 | &.icon-step-out { 68 | &:after { 69 | position: absolute; 70 | top: -7px; 71 | left: 6px; 72 | content: "\21b1"; 73 | } 74 | } 75 | } 76 | 77 | .pure-button { 78 | &.button-small { 79 | font-size: 85%; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/std/runtime.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define LIP_STRING_REF_LITERAL(str) \ 7 | { .length = LIP_STATIC_ARRAY_LEN(str), .ptr = str} 8 | 9 | static const lip_string_ref_t LIP_DEFAULT_MODULE_SEARCH_PATTERNS[] = { 10 | LIP_STRING_REF_LITERAL("?.lip"), 11 | LIP_STRING_REF_LITERAL("?.lipc"), 12 | LIP_STRING_REF_LITERAL("!.lip"), 13 | LIP_STRING_REF_LITERAL("!.lipc"), 14 | LIP_STRING_REF_LITERAL("?/init.lip"), 15 | LIP_STRING_REF_LITERAL("?/init.lipc"), 16 | LIP_STRING_REF_LITERAL("!/init.lip"), 17 | LIP_STRING_REF_LITERAL("!/init.lipc") 18 | }; 19 | 20 | 21 | lip_runtime_config_t* 22 | lip_create_std_runtime_config(lip_allocator_t* allocator) 23 | { 24 | if(allocator == NULL) { allocator = lip_std_allocator; } 25 | 26 | lip_runtime_config_t* cfg = lip_new(allocator, lip_runtime_config_t); 27 | *cfg = (lip_runtime_config_t){ 28 | .allocator = lip_std_allocator, 29 | .default_vm_config = { 30 | .os_len = 256, 31 | .cs_len = 256, 32 | .env_len = 256 33 | }, 34 | .module_search_patterns = LIP_DEFAULT_MODULE_SEARCH_PATTERNS, 35 | .num_module_search_patterns = LIP_STATIC_ARRAY_LEN(LIP_DEFAULT_MODULE_SEARCH_PATTERNS), 36 | .fs = lip_create_std_fs(allocator) 37 | }; 38 | 39 | return cfg; 40 | } 41 | 42 | void 43 | lip_destroy_std_runtime_config(lip_runtime_config_t* cfg) 44 | { 45 | lip_destroy_std_fs(cfg->fs); 46 | lip_free(cfg->allocator, cfg); 47 | } 48 | -------------------------------------------------------------------------------- /src/cli.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_CLI_H 2 | #define LIP_CLI_H 3 | 4 | static inline unsigned int 5 | get_opt_width(const struct optparse_long opt, const char* param) 6 | { 7 | return 0 8 | + 2 // "-o" 9 | + (opt.longname ? 3 + strlen(opt.longname) : 0) // ",--option", 10 | + (param ? 3 + strlen(param) : 0); //" [param]" 11 | } 12 | 13 | static inline void 14 | show_options(const struct optparse_long* opts, const char* help[]) 15 | { 16 | unsigned int max_width = 0; 17 | for(unsigned int i = 0;; ++i) 18 | { 19 | struct optparse_long opt = opts[i]; 20 | if(!opt.shortname) { break; } 21 | 22 | unsigned int opt_width = get_opt_width(opt, help[i * 2]); 23 | max_width = LIP_MAX(opt_width, max_width); 24 | } 25 | 26 | for(unsigned int i = 0;; ++i) 27 | { 28 | struct optparse_long opt = opts[i]; 29 | if(!opt.shortname) { break; } 30 | 31 | fprintf(stderr, " -%c", opt.shortname); 32 | if(opt.longname) 33 | { 34 | fprintf(stderr, ",--%s", opt.longname); 35 | } 36 | 37 | const char* param = help[i * 2]; 38 | const char* description = help[i * 2 + 1]; 39 | 40 | switch(opt.argtype) 41 | { 42 | case OPTPARSE_NONE: 43 | break; 44 | case OPTPARSE_OPTIONAL: 45 | fprintf(stderr, "[=%s]", param); 46 | break; 47 | case OPTPARSE_REQUIRED: 48 | fprintf(stderr, " <%s>", param); 49 | break; 50 | } 51 | fprintf(stderr, "%*s", max_width + 4 - get_opt_width(opt, param), ""); 52 | fprintf(stderr, "%s\n", description); 53 | } 54 | } 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /src/tests/cpp-compat.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #define _Bool bool 22 | #include "munit.h" 23 | 24 | namespace cpp { 25 | 26 | lip_exec_status_t cpp_identity(lip_vm_t* vm, lip_value_t* result) 27 | { 28 | lip_bind_args((any, x)); 29 | lip_return(x); 30 | } 31 | 32 | } 33 | 34 | extern "C" void test_cpp() 35 | { 36 | lip_runtime_config_t cfg; 37 | lip_reset_runtime_config(&cfg); 38 | lip_runtime_t* runtime = lip_create_runtime(&cfg); 39 | lip_context_t* ctx = lip_create_context(runtime, NULL); 40 | lip_vm_t* vm = lip_create_vm(ctx, NULL); 41 | lip_value_t fn = lip_make_function(vm, cpp::cpp_identity, 0, NULL); 42 | lip_value_t result; 43 | lip_exec_status_t status = lip_call(vm, &result, fn, 1, lip_make_number(vm, 42.5)); 44 | munit_assert_uint(LIP_EXEC_OK, ==, status); 45 | munit_assert_uint(LIP_VAL_NUMBER, ==, result.type); 46 | munit_assert_double_equal(42.5, result.data.number, 2); 47 | 48 | lip_destroy_vm(ctx, vm); 49 | lip_destroy_context(ctx); 50 | lip_destroy_runtime(runtime); 51 | } 52 | -------------------------------------------------------------------------------- /src/dbg-ui/src/krueger.js: -------------------------------------------------------------------------------- 1 | import flyd from 'flyd'; 2 | import flip from 'ramda/src/flip'; 3 | import over from 'ramda/src/over'; 4 | import curry from 'ramda/src/curry'; 5 | import forwardTo from 'flyd/module/forwardto'; 6 | const snabbdom = require('snabbdom'); 7 | const patch = snabbdom.init([ 8 | require('snabbdom/modules/class').default, 9 | require('snabbdom/modules/props').default, 10 | require('snabbdom/modules/style').default, 11 | require('snabbdom/modules/eventlisteners').default 12 | ]); 13 | 14 | export const mount = (module, root) => { 15 | const { init, update, render, subscribe } = module; 16 | 17 | const model = init(); 18 | const actions$ = flyd.stream(); 19 | 20 | if(subscribe) { 21 | const subscription$ = subscribe(model); 22 | flyd.on(actions$, subscription$); 23 | } 24 | 25 | const model$ = flyd.scan(flip(update), model, actions$); 26 | const vnode$ = flyd.map((model) => render(model, actions$), model$); 27 | 28 | flyd.scan(patch, root, vnode$); 29 | } 30 | 31 | const makeNested = (module, actionWrapFn, model) => ({ 32 | model, 33 | update: (action) => makeNested(module, actionWrapFn, module.update(action, model)), 34 | render: (actions$) => module.render(model, forwardTo(actions$, actionWrapFn)) 35 | }); 36 | 37 | export const nested = (module, actionWrapFn, ...args) => 38 | makeNested(module, actionWrapFn, module.init(...args)); 39 | 40 | export const updateNested = curry((lens, action, model) => 41 | over(lens, (nested) => nested.update(action), model)); 42 | -------------------------------------------------------------------------------- /src/dbg-ui/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | /* globals Mailer */ 3 | /* 4 | * ENVIRONMENTS 5 | * ================= 6 | */ 7 | 8 | // Define globals exposed by Node.js. 9 | "node": true, 10 | //Allow browser API 11 | "browser": true, 12 | 13 | // Allow ES6. 14 | "esnext": true, 15 | 16 | /* 17 | * ENFORCING OPTIONS 18 | * ================= 19 | */ 20 | 21 | // Force all variable names to use either camelCase style or UPPER_CASE 22 | // with underscores. 23 | "camelcase": true, 24 | 25 | // Prohibit use of == and != in favor of === and !==. 26 | "eqeqeq": true, 27 | 28 | // Enforce tab width of 2 spaces. 29 | "indent": 2, 30 | 31 | // Prohibit use of a variable before it is defined. 32 | "latedef": true, 33 | 34 | // Enforce line length to 80 characters 35 | /*"maxlen": 80,*/ 36 | 37 | // Require capitalized names for constructor functions. 38 | "newcap": false, 39 | 40 | // Enforce use of single quotation marks for strings. 41 | "quotmark": "single", 42 | 43 | // Enforce placing 'use strict' at the top function scope 44 | "strict": true, 45 | 46 | // Prohibit use of explicitly undeclared variables. 47 | "undef": true, 48 | 49 | // Warn when variables are defined but never used. 50 | "unused": true, 51 | 52 | /* 53 | * RELAXING OPTIONS 54 | * ================= 55 | */ 56 | 57 | // Suppress warnings about == null comparisons. 58 | "eqnull": true, 59 | 60 | "globals": { 61 | "describe": true, 62 | "it":true, 63 | "assert" : true, 64 | "expect" : true 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /include/lip/core/lexer.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_CORE_LEXER_H 2 | #define LIP_CORE_LEXER_H 3 | 4 | #include "extra.h" 5 | 6 | #define LIP_TOKEN(F) \ 7 | F(LIP_TOKEN_LPAREN) \ 8 | F(LIP_TOKEN_RPAREN) \ 9 | F(LIP_TOKEN_SYMBOL) \ 10 | F(LIP_TOKEN_STRING) \ 11 | F(LIP_TOKEN_QUOTE) \ 12 | F(LIP_TOKEN_QUASIQUOTE) \ 13 | F(LIP_TOKEN_UNQUOTE) \ 14 | F(LIP_TOKEN_UNQUOTE_SPLICING) \ 15 | F(LIP_TOKEN_NUMBER) 16 | 17 | LIP_ENUM(lip_token_type_t, LIP_TOKEN) 18 | 19 | #define LIP_LEX_ERROR(F) \ 20 | F(LIP_LEX_BAD_STRING) \ 21 | F(LIP_LEX_BAD_NUMBER) 22 | 23 | LIP_ENUM(lip_lex_error_t, LIP_LEX_ERROR) 24 | 25 | typedef struct lip_token_s lip_token_t; 26 | typedef struct lip_lexer_s lip_lexer_t; 27 | 28 | struct lip_token_s 29 | { 30 | lip_token_type_t type; 31 | lip_string_ref_t lexeme; 32 | lip_loc_range_t location; 33 | }; 34 | 35 | struct lip_lexer_s 36 | { 37 | lip_allocator_t* allocator; 38 | lip_allocator_t* arena_allocator; 39 | lip_last_error_t last_error; 40 | lip_in_t* input; 41 | lip_loc_t location; 42 | lip_array(char) capture_buff; 43 | char read_buff; 44 | bool capturing; 45 | bool buffered; 46 | bool eos; 47 | }; 48 | 49 | LIP_CORE_API void 50 | lip_lexer_init(lip_lexer_t* lexer, lip_allocator_t* allocator); 51 | 52 | LIP_CORE_API void 53 | lip_lexer_cleanup(lip_lexer_t* lexer); 54 | 55 | LIP_CORE_API void 56 | lip_lexer_reset(lip_lexer_t* lexer, lip_in_t* input); 57 | 58 | LIP_CORE_API lip_stream_status_t 59 | lip_lexer_next_token(lip_lexer_t* lexer, lip_token_t* token); 60 | 61 | LIP_CORE_API const lip_error_t* 62 | lip_lexer_last_error(lip_lexer_t* lexer); 63 | 64 | #endif 65 | -------------------------------------------------------------------------------- /include/lip/core/ast.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_CORE_AST_H 2 | #define LIP_CORE_AST_H 3 | 4 | #include "extra.h" 5 | #include "sexp.h" 6 | 7 | #define LIP_AST(F) \ 8 | F(LIP_AST_NUMBER) \ 9 | F(LIP_AST_STRING) \ 10 | F(LIP_AST_SYMBOL) \ 11 | F(LIP_AST_IDENTIFIER) \ 12 | F(LIP_AST_APPLICATION) \ 13 | F(LIP_AST_IF) \ 14 | F(LIP_AST_LET) \ 15 | F(LIP_AST_LETREC) \ 16 | F(LIP_AST_LAMBDA) \ 17 | F(LIP_AST_DO) 18 | 19 | LIP_ENUM(lip_ast_type_t, LIP_AST) 20 | 21 | typedef struct lip_ast_s lip_ast_t; 22 | typedef struct lip_let_binding_s lip_let_binding_t; 23 | typedef lip_error_m(lip_ast_t*) lip_ast_result_t; 24 | 25 | struct lip_let_binding_s 26 | { 27 | lip_string_ref_t name; 28 | lip_ast_t* value; 29 | lip_loc_range_t location; 30 | }; 31 | 32 | struct lip_ast_s 33 | { 34 | lip_ast_type_t type; 35 | lip_loc_range_t location; 36 | union 37 | { 38 | double number; 39 | lip_string_ref_t string; 40 | 41 | struct 42 | { 43 | lip_ast_t* function; 44 | lip_array(lip_ast_t*) arguments; 45 | } application; 46 | 47 | struct 48 | { 49 | lip_ast_t* condition; 50 | lip_ast_t* then; 51 | lip_ast_t* else_; 52 | } if_; 53 | 54 | struct 55 | { 56 | lip_array(lip_let_binding_t) bindings; 57 | lip_array(lip_ast_t*) body; 58 | } let; 59 | 60 | struct 61 | { 62 | lip_array(lip_string_ref_t) arguments; 63 | bool is_vararg; 64 | lip_array(lip_ast_t*) body; 65 | } lambda; 66 | 67 | lip_array(lip_ast_t*) do_; 68 | } data; 69 | }; 70 | 71 | LIP_CORE_API lip_ast_result_t 72 | lip_translate_sexp(lip_allocator_t* allocator, const lip_sexp_t* sexp); 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /src/core/vendor/format/LICENSE: -------------------------------------------------------------------------------- 1 | Format - lightweight string formatting library. 2 | Copyright (C) 2010-2015, Neil Johnson 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, 6 | with or without modification, 7 | are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | * Neither the name of nor the names of its contributors 15 | may be used to endorse or promote products derived from this software 16 | without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 22 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 23 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 24 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 25 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 27 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | -------------------------------------------------------------------------------- /src/core/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_UTILS_H 2 | #define LIP_UTILS_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #if defined(__GNUC__) || defined(__GNUG__) || defined(__clang__) 9 | # define LIP_LIKELY(cond) __builtin_expect(cond, 1) 10 | # define LIP_UNLIKELY(cond) __builtin_expect(cond, 0) 11 | #else 12 | # define LIP_LIKELY(cond) cond 13 | # define LIP_UNLIKELY(cond) cond 14 | #endif 15 | 16 | LIP_MAYBE_UNUSED static inline lip_string_ref_t 17 | lip_string_ref_from_string(lip_string_t* str) 18 | { 19 | return (lip_string_ref_t){ 20 | .length = str->length, 21 | .ptr = str->ptr 22 | }; 23 | } 24 | 25 | LIP_MAYBE_UNUSED static inline size_t 26 | lip_sprintf(lip_array(char)* buf, const char* fmt, ...) 27 | { 28 | va_list args; 29 | struct lip_osstream_s osstream; 30 | lip_out_t* out = lip_make_osstream(buf, &osstream); 31 | va_start(args, fmt); 32 | size_t result = lip_vprintf(out, fmt, args); 33 | va_end(args); 34 | return result; 35 | } 36 | 37 | LIP_MAYBE_UNUSED static inline void 38 | lip_set_last_error( 39 | lip_last_error_t* last_error, 40 | unsigned int code, lip_loc_range_t location, const void* extra 41 | ) 42 | { 43 | last_error->errorp = &last_error->error; 44 | last_error->error.code = code; 45 | last_error->error.location = location; 46 | last_error->error.extra = extra; 47 | } 48 | 49 | LIP_MAYBE_UNUSED static inline void 50 | lip_clear_last_error(lip_last_error_t* last_error) 51 | { 52 | last_error->errorp = NULL; 53 | } 54 | 55 | LIP_MAYBE_UNUSED static inline const lip_error_t* 56 | lip_last_error(lip_last_error_t* last_error) 57 | { 58 | return last_error->errorp; 59 | } 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /src/dbg-ui/src/CodeView.js: -------------------------------------------------------------------------------- 1 | import h from 'snabbdom/h'; 2 | import Union from 'union-type'; 3 | import assoc from 'ramda/src/assoc'; 4 | import CodeMirror from 'codemirror'; 5 | import 'codemirror/lib/codemirror.css'; 6 | import 'codemirror/addon/scroll/simplescrollbars.css'; 7 | import './CodeView.scss'; 8 | 9 | require('codemirror/mode/clojure/clojure'); 10 | require('codemirror/mode/clike/clike'); 11 | require('codemirror/addon/scroll/simplescrollbars'); 12 | 13 | export const init = () => null; 14 | 15 | export const Action = Union({ 16 | InitCodeMirror: [Object], 17 | ViewCode: [String, String, Object] 18 | }); 19 | 20 | export const update = Action.caseOn({ 21 | InitCodeMirror: (codemirror, _) => codemirror, 22 | ViewCode: (code, mode, location, codemirror) => { 23 | codemirror.setOption("mode", mode); 24 | codemirror.setValue(code); 25 | codemirror.markText( 26 | { 27 | line: Math.max(0, location.start.line - 1), 28 | ch: Math.max(0, location.start.column - 1) 29 | }, 30 | { 31 | line: Math.max(0, location.end.line - 1), 32 | ch: Math.max(0, location.end.column || 80) 33 | }, 34 | { className: "active-range" } 35 | ); 36 | const t = codemirror.charCoords({line: location.start.line, ch: 0}, "local").top; 37 | const middleHeight = codemirror.getScrollerElement().offsetHeight / 2; 38 | codemirror.scrollTo(null, t - middleHeight - 5); 39 | return codemirror; 40 | } 41 | }); 42 | 43 | export const render = (model, actions$) => 44 | h("textarea", { 45 | hook: { 46 | insert: (vnode) => { 47 | const codemirror = CodeMirror.fromTextArea(vnode.elm, { 48 | theme: "default", 49 | mode: "clike", 50 | scrollbarStyle: "simple", 51 | lineNumbers: true, 52 | readOnly: true 53 | }); 54 | actions$(Action.InitCodeMirror(codemirror)); 55 | } 56 | } 57 | }); 58 | -------------------------------------------------------------------------------- /src/core/io.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "vendor/format/format.h" 5 | 6 | static size_t 7 | lip_isstream_read(void* buff, size_t size, lip_in_t* vtable) 8 | { 9 | struct lip_isstream_s* sstream = 10 | LIP_CONTAINER_OF(vtable, struct lip_isstream_s, vtable); 11 | 12 | size_t bytes_read = LIP_MIN(sstream->str.length - sstream->pos, size); 13 | memcpy(buff, sstream->str.ptr + sstream->pos, bytes_read); 14 | sstream->pos += bytes_read; 15 | return bytes_read; 16 | } 17 | 18 | static size_t 19 | lip_osstream_write(const void* buff, size_t size, lip_out_t* vtable) 20 | { 21 | struct lip_osstream_s* osstream = 22 | LIP_CONTAINER_OF(vtable, struct lip_osstream_s, vtable); 23 | 24 | lip_array(char)* buffer = osstream->buffer; 25 | size_t len = lip_array_len(*buffer); 26 | *buffer = lip_array__prepare_push(*buffer); 27 | lip_array_resize(*buffer, len + size); 28 | memcpy(*buffer + len, buff, size); 29 | return size; 30 | } 31 | 32 | lip_in_t* 33 | lip_make_isstream(lip_string_ref_t str, struct lip_isstream_s* sstream) 34 | { 35 | sstream->str = str; 36 | sstream->pos = 0; 37 | sstream->vtable.read = lip_isstream_read; 38 | 39 | return &sstream->vtable; 40 | } 41 | 42 | lip_out_t* 43 | lip_make_osstream(lip_array(char)* buffer, struct lip_osstream_s* sstream) 44 | { 45 | sstream->buffer = buffer; 46 | sstream->vtable.write = lip_osstream_write; 47 | 48 | return &sstream->vtable; 49 | } 50 | 51 | size_t 52 | lip_printf(lip_out_t* output, const char* format, ...) 53 | { 54 | va_list args; 55 | va_start(args, format); 56 | size_t ret = lip_vprintf(output, format, args); 57 | va_end(args); 58 | return ret; 59 | } 60 | 61 | static void* 62 | lip_print_cons(void* out, const char* str, size_t size) 63 | { 64 | return lip_write(str, size, out) > 0 ? out : NULL; 65 | } 66 | 67 | size_t 68 | lip_vprintf(lip_out_t* output, const char* format, va_list va) 69 | { 70 | int ret = lip_format(lip_print_cons, output, format, va); 71 | return ret >= 0 ? ret : 0; 72 | } 73 | -------------------------------------------------------------------------------- /src/dbg-ui/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const webpack = require("webpack"); 3 | 4 | module.exports = function(env) { 5 | const plugins = []; 6 | const isProd = env.prod === 'true'; 7 | if(isProd) { 8 | plugins.push(new webpack.LoaderOptionsPlugin({ 9 | minimize: true, 10 | debug: false 11 | })); 12 | plugins.push(new webpack.optimize.UglifyJsPlugin({ 13 | sourceMap: true, 14 | compress: { 15 | warnings: false, 16 | screw_ie8: true, 17 | conditionals: true, 18 | unused: true, 19 | comparisons: true, 20 | sequences: true, 21 | dead_code: true, 22 | evaluate: true, 23 | if_return: true, 24 | join_vars: true, 25 | }, 26 | output: { 27 | comments: false, 28 | }, 29 | })); 30 | } 31 | 32 | return { 33 | devtool: env.prod ? 'source-map' : 'eval', 34 | entry: './main.js', 35 | output: { 36 | filename: 'bundle.js', 37 | sourceMapFilename: 'bundle.js.map' 38 | }, 39 | plugins, 40 | module: { 41 | rules: [{ 42 | test: /\.jsx?$/, 43 | exclude: /node_modules/, 44 | use: ['babel-loader'], 45 | }, { 46 | test: /\.scss$/, 47 | use: ['style-loader', 'css-loader', 'sass-loader'] 48 | }, { 49 | test: /\.css$/, 50 | loader: ['style-loader', 'css-loader'] 51 | }] 52 | }, 53 | devServer: { 54 | historyApiFallback: true, 55 | port: 8080, 56 | compress: isProd, 57 | stats: { 58 | assets: true, 59 | children: false, 60 | chunks: false, 61 | hash: false, 62 | modules: false, 63 | publicPath: false, 64 | timings: true, 65 | version: false, 66 | warnings: true, 67 | colors: { 68 | green: '\u001b[32m', 69 | } 70 | }, 71 | }, 72 | resolve: { 73 | extensions: ['.js', '.jsx'], 74 | modules: [ path.join(__dirname, 'src'), 'node_modules' ] 75 | } 76 | }; 77 | }; 78 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: generic 3 | cache: ccache 4 | addons: 5 | apt: 6 | sources: 7 | - ubuntu-toolchain-r-test 8 | - llvm-toolchain-precise-3.8 9 | packages: 10 | - gcc-5 11 | - g++-5 12 | - clang-3.8 13 | - ninja-build 14 | - doxygen 15 | - nodejs 16 | env: 17 | global: 18 | - WITH_COVERAGE=1 19 | - WITH_DBG_EMBED_RESOURCES=1 20 | - WITH_LTO=0 21 | - WITH_UBSAN=1 22 | - WITH_ASAN=1 23 | matrix: 24 | - COMPILER=gcc CC='gcc-5 -fuse-ld=gold' CXX='g++-5 --std=c++11 -fuse-ld=gold' WITH_THREADING=0 WITH_COMPUTED_GOTO=1 BUILD_DYNAMIC_LIB=0 25 | - COMPILER=gcc CC='gcc-5 -fuse-ld=gold' CXX='g++-5 --std=c++11 -fuse-ld=gold' WITH_THREADING=0 WITH_COMPUTED_GOTO=0 BUILD_DYNAMIC_LIB=0 26 | - COMPILER=gcc CC='gcc-5 -fuse-ld=gold' CXX='g++-5 --std=c++11 -fuse-ld=gold' WITH_THREADING=1 WITH_COMPUTED_GOTO=0 BUILD_DYNAMIC_LIB=1 27 | - COMPILER=gcc CC='gcc-5 -fuse-ld=gold' CXX='g++-5 --std=c++11 -fuse-ld=gold' WITH_THREADING=1 WITH_COMPUTED_GOTO=1 BUILD_DYNAMIC_LIB=1 28 | - COMPILER=clang CC='clang-3.8 -Wno-error=unused-command-line-argument' CXX='clang++-3.8 --std=c++11 -Wno-error=unused-command-line-argument' WITH_COMPUTED_GOTO=1 BUILD_DYNAMIC_LIB=0 29 | - COMPILER=clang CC='clang-3.8 -Wno-error=unused-command-line-argument' CXX='clang++-3.8 --std=c++11 -Wno-error=unused-command-line-argument' WITH_COMPUTED_GOTO=1 BUILD_DYNAMIC_LIB=1 30 | - COMPILER=clang CC='clang-3.8 -Wno-error=unused-command-line-argument' CXX='clang++-3.8 --std=c++11 -Wno-error=unused-command-line-argument' WITH_COMPUTED_GOTO=0 BUILD_DYNAMIC_LIB=0 31 | - COMPILER=clang CC='clang-3.8 -Wno-error=unused-command-line-argument' CXX='clang++-3.8 --std=c++11 -Wno-error=unused-command-line-argument' WITH_COMPUTED_GOTO=0 BUILD_DYNAMIC_LIB=1 32 | install: 33 | - pip install --user cpp-coveralls 34 | script: 35 | - ./numake 36 | after_success: 37 | - if [ "$COMPILER" = "gcc" ]; then coveralls --root . --build-root . --exclude .build/*/src/tests --exclude .build/*/src/lip/vendor --exclude .build/*/src/lip/khash_impl.c ; fi 38 | sudo: required 39 | dist: trusty 40 | -------------------------------------------------------------------------------- /src/tests/temp_allocator.c: -------------------------------------------------------------------------------- 1 | #include "temp_allocator.h" 2 | 3 | typedef struct lip_temp_allocator_s lip_temp_allocator_t; 4 | 5 | struct lip_temp_allocator_s 6 | { 7 | lip_allocator_t vtable; 8 | lip_allocator_t* backing_allocator; 9 | size_t size; 10 | void* mem; 11 | bool freed; 12 | }; 13 | 14 | static void* 15 | lip_temp_allocator_realloc(lip_allocator_t* vtable, void* old, size_t size) 16 | { 17 | lip_temp_allocator_t* temp_allocator = 18 | LIP_CONTAINER_OF(vtable, lip_temp_allocator_t, vtable); 19 | 20 | if(!(false 21 | || (old != NULL && old == temp_allocator->mem) // realloc 22 | || (old == NULL && temp_allocator->freed))) // malloc 23 | { 24 | return NULL; 25 | } 26 | 27 | temp_allocator->freed = false; 28 | void* mem = NULL; 29 | if(temp_allocator->size < size) 30 | { 31 | mem = lip_realloc(temp_allocator->backing_allocator, temp_allocator->mem, size); 32 | temp_allocator->mem = mem; 33 | temp_allocator->size = size; 34 | } 35 | else 36 | { 37 | mem = temp_allocator->mem; 38 | } 39 | 40 | return mem; 41 | } 42 | 43 | static void 44 | lip_temp_allocator_free(lip_allocator_t* vtable, void* mem) 45 | { 46 | lip_temp_allocator_t* temp_allocator = 47 | LIP_CONTAINER_OF(vtable, lip_temp_allocator_t, vtable); 48 | if(mem == temp_allocator->mem) 49 | { 50 | temp_allocator->freed = true; 51 | } 52 | } 53 | 54 | lip_allocator_t* 55 | lip_temp_allocator_create(lip_allocator_t* allocator) 56 | { 57 | lip_temp_allocator_t* temp_allocator = lip_new(allocator, lip_temp_allocator_t); 58 | *temp_allocator = (lip_temp_allocator_t){ 59 | .backing_allocator = allocator, 60 | .size = 0, 61 | .mem = NULL, 62 | .freed = true, 63 | .vtable = { 64 | .realloc = lip_temp_allocator_realloc, 65 | .free = lip_temp_allocator_free 66 | } 67 | }; 68 | return &temp_allocator->vtable; 69 | } 70 | 71 | void 72 | lip_temp_allocator_destroy(lip_allocator_t* allocator) 73 | { 74 | lip_temp_allocator_t* temp_allocator = 75 | LIP_CONTAINER_OF(allocator, lip_temp_allocator_t, vtable); 76 | if(temp_allocator->mem) 77 | { 78 | lip_free(temp_allocator->backing_allocator, temp_allocator->mem); 79 | } 80 | lip_free(temp_allocator->backing_allocator, temp_allocator); 81 | } 82 | -------------------------------------------------------------------------------- /src/core/platform.c: -------------------------------------------------------------------------------- 1 | #include "platform.h" 2 | 3 | #if defined(LIP_THREADING_DUMMY) 4 | 5 | bool 6 | lip_rwlock_init(lip_rwlock_t* rwlock) 7 | { 8 | (void)rwlock; 9 | return true; 10 | } 11 | 12 | void 13 | lip_rwlock_destroy(lip_rwlock_t* rwlock) 14 | { 15 | (void)rwlock; 16 | } 17 | 18 | bool 19 | lip_rwlock_begin_read(lip_rwlock_t* rwlock) 20 | { 21 | (void)rwlock; 22 | return true; 23 | } 24 | 25 | void 26 | lip_rwlock_end_read(lip_rwlock_t* rwlock) 27 | { 28 | (void)rwlock; 29 | } 30 | 31 | bool 32 | lip_rwlock_begin_write(lip_rwlock_t* rwlock) 33 | { 34 | (void)rwlock; 35 | return true; 36 | } 37 | 38 | void 39 | lip_rwlock_end_write(lip_rwlock_t* rwlock) 40 | { 41 | (void)rwlock; 42 | } 43 | 44 | #elif defined(LIP_THREADING_PTHREAD) 45 | 46 | bool 47 | lip_rwlock_init(lip_rwlock_t* rwlock) 48 | { 49 | return pthread_rwlock_init(rwlock, NULL) == 0; 50 | } 51 | 52 | void 53 | lip_rwlock_destroy(lip_rwlock_t* rwlock) 54 | { 55 | pthread_rwlock_destroy(rwlock); 56 | } 57 | 58 | bool 59 | lip_rwlock_begin_read(lip_rwlock_t* rwlock) 60 | { 61 | return pthread_rwlock_rdlock(rwlock) == 0; 62 | } 63 | 64 | void 65 | lip_rwlock_end_read(lip_rwlock_t* rwlock) 66 | { 67 | pthread_rwlock_unlock(rwlock); 68 | } 69 | 70 | bool 71 | lip_rwlock_begin_write(lip_rwlock_t* rwlock) 72 | { 73 | return pthread_rwlock_wrlock(rwlock) == 0; 74 | } 75 | 76 | void 77 | lip_rwlock_end_write(lip_rwlock_t* rwlock) 78 | { 79 | pthread_rwlock_unlock(rwlock); 80 | } 81 | 82 | #elif defined(LIP_THREADING_WINAPI) 83 | 84 | bool 85 | lip_rwlock_init(lip_rwlock_t* rwlock) 86 | { 87 | InitializeSRWLock(rwlock); 88 | return true; 89 | } 90 | 91 | void 92 | lip_rwlock_destroy(lip_rwlock_t* rwlock) 93 | { 94 | (void)rwlock; 95 | } 96 | 97 | bool 98 | lip_rwlock_begin_read(lip_rwlock_t* rwlock) 99 | { 100 | AcquireSRWLockShared(rwlock); 101 | return true; 102 | } 103 | 104 | void 105 | lip_rwlock_end_read(lip_rwlock_t* rwlock) 106 | { 107 | ReleaseSRWLockShared(rwlock); 108 | } 109 | 110 | bool 111 | lip_rwlock_begin_write(lip_rwlock_t* rwlock) 112 | { 113 | AcquireSRWLockExclusive(rwlock); 114 | return true; 115 | } 116 | 117 | void 118 | lip_rwlock_end_write(lip_rwlock_t* rwlock) 119 | { 120 | ReleaseSRWLockExclusive(rwlock); 121 | } 122 | 123 | #endif 124 | -------------------------------------------------------------------------------- /include/lip/core/memory.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_CORE_MEMORY_H 2 | #define LIP_CORE_MEMORY_H 3 | 4 | /** 5 | * @defgroup memory Memory 6 | * @brief Memory managment functions 7 | * 8 | * @{ 9 | */ 10 | 11 | #include "common.h" 12 | 13 | #define lip_new(ALLOCATOR, TYPE) (TYPE*)(lip_malloc(ALLOCATOR, sizeof(TYPE))) 14 | #define LIP_ARRAY_BLOCK(TYPE, LENGTH) \ 15 | { \ 16 | .element_size = sizeof(TYPE), \ 17 | .num_elements = LENGTH, \ 18 | .alignment = LIP_ALIGN_OF(TYPE) \ 19 | } 20 | #define LIP_STATIC_ARRAY_LEN(ARRAY) (sizeof((ARRAY)) / sizeof((ARRAY)[0])) 21 | 22 | typedef struct lip_memblock_info_s lip_memblock_info_t; 23 | 24 | /// Allocator interface. 25 | struct lip_allocator_s 26 | { 27 | /// Allocation callback, should be similar to realloc. 28 | void*(*realloc)(lip_allocator_t* self, void* old, size_t size); 29 | /// Free callback, should be similar to free. 30 | void(*free)(lip_allocator_t* self, void* mem); 31 | }; 32 | 33 | struct lip_memblock_info_s 34 | { 35 | size_t element_size; 36 | size_t num_elements; 37 | uint8_t alignment; 38 | ptrdiff_t offset; 39 | }; 40 | 41 | struct lip_max_align_helper 42 | { 43 | long long d; 44 | void* e; 45 | float f; 46 | double g; 47 | void(*h)(); 48 | }; 49 | 50 | static const size_t LIP_MAX_ALIGNMENT = LIP_ALIGN_OF(struct lip_max_align_helper); 51 | 52 | LIP_CORE_API lip_memblock_info_t 53 | lip_align_memblocks(unsigned int num_blocks, lip_memblock_info_t** blocks); 54 | 55 | LIP_MAYBE_UNUSED static inline void* 56 | lip_locate_memblock(void* base, const lip_memblock_info_t* block) 57 | { 58 | return (char*)base + block->offset; 59 | } 60 | 61 | /// Realloc using an allocator 62 | LIP_MAYBE_UNUSED static inline void* 63 | lip_realloc(lip_allocator_t* allocator, void* ptr, size_t size) 64 | { 65 | return allocator->realloc(allocator, ptr, size); 66 | } 67 | 68 | /// Malloc using an allocator 69 | LIP_MAYBE_UNUSED static inline void* 70 | lip_malloc(lip_allocator_t* allocator, size_t size) 71 | { 72 | return lip_realloc(allocator, 0, size); 73 | } 74 | 75 | /// Free using an allocator 76 | LIP_MAYBE_UNUSED static inline void 77 | lip_free(lip_allocator_t* allocator, void* ptr) 78 | { 79 | allocator->free(allocator, ptr); 80 | } 81 | 82 | LIP_MAYBE_UNUSED static inline void* 83 | lip_align_ptr(const void* ptr, size_t alignment) 84 | { 85 | return (void*)(((uintptr_t)ptr + alignment - 1) / alignment * alignment); 86 | } 87 | 88 | /** 89 | * @} 90 | */ 91 | 92 | #endif 93 | -------------------------------------------------------------------------------- /include/lip/core/array.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_CORE_ARRAY_H 2 | #define LIP_CORE_ARRAY_H 3 | 4 | /** 5 | * @defgroup array Array 6 | * @brief Flexible array. 7 | * 8 | * @{ 9 | */ 10 | 11 | #include "common.h" 12 | 13 | /** 14 | * @brief Create a new array. 15 | * @param ALLOCATOR The allocator to use. 16 | * @param TYPE Type of elements. 17 | * @param CAPACITY Initial capacity. 18 | */ 19 | #define lip_array_create(ALLOCATOR, TYPE, CAPACITY) \ 20 | ((TYPE*)(lip_array__create(\ 21 | (ALLOCATOR), \ 22 | sizeof(TYPE), \ 23 | LIP_ALIGN_OF(TYPE), \ 24 | (CAPACITY)))) 25 | 26 | /// Push an element to the end of the array. 27 | #define lip_array_push(ARRAY, ITEM) \ 28 | ((ARRAY) = lip_array__prepare_push((ARRAY)), \ 29 | ((ARRAY))[lip_array_len((ARRAY)) - 1] = (ITEM)) 30 | 31 | /// Resize an array. 32 | #define lip_array_resize(ARRAY, NEW_LENGTH) \ 33 | ((ARRAY) = lip_array__resize((ARRAY), (NEW_LENGTH))) 34 | 35 | /// Get the begin iterator the array. 36 | #define lip_array_begin(ARRAY) (ARRAY) 37 | 38 | /// Get the end iterator of an array. 39 | #define lip_array_end(ARRAY) ((ARRAY) + lip_array_len((ARRAY))) 40 | 41 | /** 42 | * @brief Loop through an array. 43 | * @param TYPE element type. 44 | * @param VAR iterator variable. 45 | * @param ARRAY array to loop through. 46 | */ 47 | #define lip_array_foreach(TYPE, VAR, ARRAY) \ 48 | for(TYPE* VAR = lip_array_begin((ARRAY)); VAR != lip_array_end((ARRAY)); ++(VAR)) 49 | 50 | /// Remove an item from an array by overwriting it with the last item. 51 | #define lip_array_quick_remove(ARRAY, INDEX) \ 52 | ((ARRAY)[(INDEX)] = (ARRAY)[lip_array_len((ARRAY)) - 1], \ 53 | lip_array_resize((ARRAY), lip_array_len((ARRAY)) - 1)) 54 | 55 | /// Reserve space at the end of the array and return pointer to that slot. 56 | #define lip_array_alloc(ARRAY) \ 57 | ((ARRAY) = lip_array__prepare_push((ARRAY)), \ 58 | &(ARRAY)[lip_array_len((ARRAY)) - 1]) 59 | 60 | /// Destroy an array. 61 | LIP_CORE_API void 62 | lip_array_destroy(void* array); 63 | 64 | /// Remove all items from an array. 65 | LIP_CORE_API void 66 | lip_array_clear(void* array); 67 | 68 | /// Get the length of an array. 69 | LIP_CORE_API size_t 70 | lip_array_len(const void* array); 71 | 72 | // private 73 | LIP_CORE_API void* 74 | lip_array__create( 75 | lip_allocator_t* allocator, 76 | size_t elem_size, 77 | uint8_t alignment, 78 | size_t capacity 79 | ); 80 | 81 | LIP_CORE_API void* 82 | lip_array__prepare_push(void* array); 83 | 84 | LIP_CORE_API void* 85 | lip_array__resize(void* array, size_t new_length); 86 | 87 | /** 88 | * @} 89 | */ 90 | 91 | #endif 92 | -------------------------------------------------------------------------------- /src/dbg-ui/src/Toolbar.js: -------------------------------------------------------------------------------- 1 | import h from 'snabbdom/h'; 2 | import Union from 'union-type'; 3 | import assoc from 'ramda/src/assoc'; 4 | import pipe from 'ramda/src/pipe'; 5 | import Msgpack from 'msgpack-lite'; 6 | 7 | export const init = () => ({ 8 | commandURL: null, 9 | paused: false 10 | }); 11 | 12 | export const Command = Union({ 13 | Step: [], 14 | StepInto: [], 15 | StepOut: [], 16 | StepOver: [], 17 | Continue: [], 18 | Break: [] 19 | }); 20 | 21 | export const Action = Union({ 22 | UpdateDbg: [Object], 23 | SendCommand: [Command] 24 | }); 25 | 26 | export const update = Action.caseOn({ 27 | UpdateDbg: (dbg, model) => 28 | pipe( 29 | assoc('paused', dbg.command === 'LIP_DBG_BREAK'), 30 | assoc('commandURL', dbg.getLink('command').href) 31 | )(model), 32 | SendCommand: (cmd, model) => { 33 | const command = Msgpack.encode(Command.case({ 34 | StepInto: () => "step-into", 35 | StepOut: () => "step-out", 36 | StepOver: () => "step-over", 37 | Step: () => "step", 38 | Continue: () => "continue", 39 | Break: () => "break", 40 | }, cmd)); 41 | fetch(model.commandURL, {method: "POST", body: command}); 42 | return assoc('paused', false, model); 43 | } 44 | }); 45 | 46 | export const render = (model, actions$) => { 47 | const stepDisabled = !model.commandURL || !model.paused; 48 | 49 | return h("div.pure-button-group", [ 50 | h("button.pure-button.button-small", 51 | { 52 | props: { disabled: !model.commandURL }, 53 | on: {click: [ 54 | actions$, 55 | Action.SendCommand(model.paused ? Command.Continue() : Command.Break()) 56 | ]} 57 | }, 58 | [model.paused ? h("i.icon.icon-continue") : h("i.icon.icon-break")]), 59 | h("button.pure-button.button-small", 60 | { 61 | props: { disabled: stepDisabled }, 62 | on: { click: [ actions$, Action.SendCommand(Command.StepOver()) ] } 63 | }, 64 | [h("i.icon.icon-step-over", "( )")]), 65 | h("button.pure-button.button-small", 66 | { 67 | props: { disabled: stepDisabled }, 68 | on: { click: [ actions$, Action.SendCommand(Command.StepInto()) ] } 69 | }, 70 | [h("i.icon.icon-step-into", "( )")]), 71 | h("button.pure-button.button-small", 72 | { 73 | props: { disabled: stepDisabled }, 74 | on: { click: [ actions$, Action.SendCommand(Command.StepOut()) ] } 75 | }, 76 | [h("i.icon.icon-step-out", "( )")] 77 | ), 78 | h("button.pure-button.button-small", 79 | { 80 | props: { disabled: stepDisabled }, 81 | on: { click: [ actions$, Action.SendCommand(Command.Step()) ] } 82 | }, 83 | [h("i.icon.icon-step", " ")] 84 | ) 85 | ]); 86 | } 87 | -------------------------------------------------------------------------------- /include/lip/core/asm.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_CORE_ASM_H 2 | #define LIP_CORE_ASM_H 3 | 4 | #include 5 | #include "opcode.h" 6 | #include "memory.h" 7 | 8 | #define LIP_OP_LABEL 0xFF 9 | 10 | typedef uint32_t lip_asm_index_t; 11 | typedef struct lip_asm_s lip_asm_t; 12 | typedef struct lip_tagged_instruction_s lip_tagged_instruction_t; 13 | 14 | struct lip_tagged_instruction_s 15 | { 16 | lip_instruction_t instruction; 17 | lip_loc_range_t location; 18 | }; 19 | 20 | struct lip_asm_s 21 | { 22 | lip_allocator_t* allocator; 23 | lip_string_ref_t source_name; 24 | lip_loc_range_t location; 25 | lip_array(lip_asm_index_t) labels; 26 | lip_array(lip_asm_index_t) jumps; 27 | lip_array(lip_tagged_instruction_t) instructions; 28 | lip_array(lip_function_t*) functions; 29 | lip_array(uint32_t) imports; 30 | lip_array(lip_value_t) constants; 31 | lip_array(lip_string_ref_t) string_pool; 32 | lip_array(lip_memblock_info_t) string_layout; 33 | lip_array(lip_memblock_info_t) nested_layout; 34 | lip_array(lip_memblock_info_t*) function_layout; 35 | }; 36 | 37 | LIP_CORE_API void 38 | lip_asm_init(lip_asm_t* lasm, lip_allocator_t* allocator); 39 | 40 | LIP_CORE_API void 41 | lip_asm_cleanup(lip_asm_t* lasm); 42 | 43 | LIP_CORE_API void 44 | lip_asm_begin(lip_asm_t* lasm, lip_string_ref_t source_name, lip_loc_range_t location); 45 | 46 | LIP_CORE_API void 47 | lip_asm_add( 48 | lip_asm_t* lasm, 49 | lip_opcode_t opcode, 50 | lip_operand_t operand, 51 | lip_loc_range_t location 52 | ); 53 | 54 | LIP_CORE_API lip_asm_index_t 55 | lip_asm_new_label(lip_asm_t* lasm); 56 | 57 | LIP_CORE_API lip_asm_index_t 58 | lip_asm_new_function(lip_asm_t* lasm, lip_function_t* function); 59 | 60 | LIP_CORE_API lip_asm_index_t 61 | lip_asm_alloc_import(lip_asm_t* lasm, lip_string_ref_t import); 62 | 63 | LIP_CORE_API lip_asm_index_t 64 | lip_asm_alloc_numeric_constant(lip_asm_t* lasm, double number); 65 | 66 | LIP_CORE_API lip_asm_index_t 67 | lip_asm_alloc_string_constant(lip_asm_t* lasm, lip_string_ref_t string); 68 | 69 | LIP_CORE_API lip_asm_index_t 70 | lip_asm_alloc_symbol(lip_asm_t* lasm, lip_string_ref_t string); 71 | 72 | LIP_CORE_API lip_function_t* 73 | lip_asm_end(lip_asm_t* lasm, lip_allocator_t* allocator); 74 | 75 | LIP_MAYBE_UNUSED static inline lip_instruction_t 76 | lip_asm(lip_opcode_t opcode, lip_operand_t operand) 77 | { 78 | return (((uint32_t)opcode & 0xFF) << 24) | (operand & 0x00FFFFFF); 79 | } 80 | 81 | LIP_MAYBE_UNUSED static inline void 82 | lip_disasm(lip_instruction_t instr, lip_opcode_t* opcode, lip_operand_t* operand) 83 | { 84 | *opcode = (lip_opcode_t)((instr >> 24) & 0xFF); 85 | *operand = (lip_operand_t)((int32_t)((uint32_t)instr << 8) >> 8); 86 | } 87 | 88 | #endif 89 | -------------------------------------------------------------------------------- /src/dbg-ui/src/ValueListView.js: -------------------------------------------------------------------------------- 1 | import h from 'snabbdom/h'; 2 | import assoc from 'ramda/src/assoc'; 3 | import lensProp from 'ramda/src/lensProp'; 4 | import forwardTo from 'flyd/module/forwardto'; 5 | import Union from 'union-type'; 6 | import * as Collapsible from 'Collapsible'; 7 | 8 | export const init = (heading) => ({ 9 | valueList: [], 10 | collapsible: Collapsible.init(), 11 | heading 12 | }); 13 | 14 | export const Action = Union({ 15 | SetValueList: [Array], 16 | Collapsible: [Collapsible.Action] 17 | }); 18 | 19 | export const update = Action.caseOn({ 20 | SetValueList: assoc('valueList'), 21 | Collapsible: Collapsible.updateNested(lensProp('collapsible')) 22 | }); 23 | 24 | export const render = (model, actions$) => 25 | Collapsible.render( 26 | model.collapsible, forwardTo(actions$, Action.Collapsible), 27 | model.heading, 28 | renderValueList(model.valueList) 29 | ); 30 | 31 | const renderValueList = (valueList) => 32 | h("table.pure-table.pure-table-bordered", [ 33 | h("thead", h("tr", [h("th", "#"), h("th", "Type"), h("th", "Value")])), 34 | h("tbody", valueList.map((value, index) => { 35 | const parsedValue = parseValue(value); 36 | return h("tr", [ 37 | h("td", index.toString()), 38 | h("td", parsedValue._name), 39 | h("td", renderValue(parsedValue)) 40 | ]) 41 | })) 42 | ]); 43 | 44 | const Value = Union({ 45 | Nil: [], 46 | Number: [Number], 47 | Boolean: [Boolean], 48 | String: [String], 49 | List: [Array], 50 | Symbol: [String], 51 | Native: [Number], 52 | Function: [Number], 53 | Placeholder: [Number], 54 | Corrupted: [] 55 | }); 56 | 57 | const parseValue = (rawValue) => { 58 | if(rawValue === null) { 59 | return Value.Nil(); 60 | } else if(typeof rawValue == 'number') { 61 | return Value.Number(rawValue); 62 | } else if(typeof rawValue == 'boolean') { 63 | return Value.Boolean(rawValue); 64 | } else if(typeof rawValue == 'string') { 65 | return Value.String(rawValue); 66 | } else if(rawValue instanceof Array) { 67 | return Value.List(rawValue); 68 | } else if(rawValue["symbol"]) { 69 | return Value.Symbol(rawValue["symbol"]); 70 | } else if(rawValue["native"]) { 71 | return Value.Native(rawValue["native"]); 72 | } else if(rawValue["function"]) { 73 | return Value.Function(rawValue["function"]); 74 | } else if(rawValue["placeholder"]) { 75 | return Value.Placeholder(rawValue["placeholder"]); 76 | } else { 77 | return Value.Corrupted(); 78 | } 79 | }; 80 | 81 | const renderValue = Value.case({ 82 | Nil: () => "nil", 83 | Number: (number) => number.toString(), 84 | Boolean: (boolean) => boolean.toString(), 85 | String: (string) => string, 86 | List: (list) => renderValueList(list), 87 | Symbol: (string) => string, 88 | Native: (number) => "0x" + number.toString(16), 89 | Function: (number) => "0x" + number.toString(16), 90 | Placeholder: (number) => number.toString(), 91 | Corrupted: () => "" 92 | }); 93 | -------------------------------------------------------------------------------- /src/core/array.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | typedef struct lip_array_s 5 | { 6 | lip_allocator_t* allocator; 7 | size_t elem_size; 8 | size_t length; 9 | size_t capacity; 10 | uint8_t alignment; 11 | uint8_t padding; 12 | } lip_array_t; 13 | 14 | static inline lip_array_t* 15 | lip_array_head(const void* ptr) 16 | { 17 | const char* body = ptr; 18 | return (lip_array_t*)(body - (*(body - 1))); 19 | } 20 | 21 | void* 22 | lip_array__create( 23 | lip_allocator_t* allocator, 24 | size_t elem_size, 25 | uint8_t alignment, 26 | size_t capacity 27 | ) 28 | { 29 | size_t mem_required = sizeof(lip_array_t) + elem_size * capacity + alignment - 1; 30 | lip_array_t* array = lip_malloc(allocator, mem_required); 31 | array->allocator = allocator; 32 | array->elem_size = elem_size; 33 | array->length = 0; 34 | array->capacity = capacity; 35 | array->alignment = alignment; 36 | 37 | char* body = lip_align_ptr((char*)array + sizeof(lip_array_t), alignment); 38 | char offset = body - (char*)array; 39 | *(body - 1) = offset; 40 | return body; 41 | } 42 | 43 | void 44 | lip_array_destroy(void* array) 45 | { 46 | lip_array_t* head = lip_array_head(array); 47 | lip_free(head->allocator, head); 48 | } 49 | 50 | static 51 | void* lip_array__realloc(void* array, size_t new_capacity) 52 | { 53 | lip_array_t* head = lip_array_head(array); 54 | char old_offset = (char*)array - (char*)head; 55 | 56 | size_t mem_required = 57 | sizeof(lip_array_t) + head->elem_size * new_capacity + head->alignment - 1; 58 | head = lip_realloc(head->allocator, head, mem_required); 59 | head->capacity = new_capacity; 60 | 61 | char* new_body = lip_align_ptr((char*)head + sizeof(lip_array_t), head->alignment); 62 | memmove(new_body, (char*)head + old_offset, head->elem_size * head->length); 63 | *(new_body - 1) = new_body - (char*)head; 64 | 65 | return new_body; 66 | } 67 | 68 | void* 69 | lip_array__resize(void* array, size_t new_length) 70 | { 71 | lip_array_t* head = lip_array_head(array); 72 | if(new_length <= head->capacity) 73 | { 74 | head->length = new_length; 75 | return array; 76 | } 77 | else 78 | { 79 | void* new_array = lip_array__realloc(array, new_length); 80 | lip_array_head(new_array)->length = new_length; 81 | return new_array; 82 | } 83 | } 84 | 85 | void* 86 | lip_array__prepare_push(void* array) 87 | { 88 | lip_array_t* head = lip_array_head(array); 89 | size_t new_length = head->length + 1; 90 | if(new_length <= head->capacity) 91 | { 92 | head->length = new_length; 93 | return array; 94 | } 95 | else 96 | { 97 | void* new_array = lip_array__realloc(array, LIP_MAX(head->capacity * 2, new_length)); 98 | lip_array_head(new_array)->length = new_length; 99 | return new_array; 100 | } 101 | } 102 | 103 | size_t 104 | lip_array_len(const void* array) 105 | { 106 | return lip_array_head(array)->length; 107 | } 108 | 109 | void 110 | lip_array_clear(void* array) 111 | { 112 | lip_array_head(array)->length = 0; 113 | } 114 | -------------------------------------------------------------------------------- /src/dbg-ui/src/hal.js: -------------------------------------------------------------------------------- 1 | import { parse as parseURITemplate } from 'uri-template'; 2 | 3 | export class HALApp { 4 | constructor(opts) { 5 | this.custom_rels = opts.custom_rels; 6 | this.codecs = opts.codecs; 7 | 8 | const a = document.createElement('a'); 9 | a.href = opts.entrypoint; 10 | this.baseURL = a.protocol + '//' + a.hostname + ':' + a.port; 11 | 12 | this.entrypoint = new HALLink({href: opts.entrypoint}, this); 13 | } 14 | 15 | fetch() { 16 | return this.entrypoint.fetch(); 17 | } 18 | 19 | parse(doc) { 20 | return new HALResource(doc, this); 21 | } 22 | } 23 | 24 | class HALResource { 25 | constructor(doc, app, curies_) { 26 | Object.assign(this, doc); 27 | 28 | const links = doc._links || {} 29 | const embedded = doc._embedded || {} 30 | 31 | const curies = curies_ || {}; 32 | (links.curies || []).forEach(curie => { 33 | curies[curie.name] = parseURITemplate(curie.href); 34 | }); 35 | 36 | mapToObj(links, doc => new HALLink(doc, app)); 37 | mapToObj(embedded, doc => new HALResource(doc, app, curies)); 38 | 39 | expandCuries(links, curies); 40 | expandCuries(embedded, curies); 41 | 42 | this._links = links; 43 | this._embedded = embedded; 44 | this._app = app; 45 | } 46 | 47 | getLink(rel) { 48 | if(rel in this._app.custom_rels) { 49 | return this.getLink(this._app.custom_rels[rel]); 50 | } else { 51 | return this._links[rel]; 52 | } 53 | } 54 | 55 | getEmbedded(rel) { 56 | if(rel in this._app.custom_rels) { 57 | return this.getEmbedded(this._app.custom_rels[rel]); 58 | } else { 59 | return this._embedded[rel]; 60 | } 61 | } 62 | } 63 | 64 | class HALLink { 65 | constructor(link, app) { 66 | Object.assign(this, link); 67 | const href = link.href; 68 | if(href.startsWith('/') && !href.startsWith('//')) { 69 | this.href = app.baseURL + link.href; 70 | } 71 | 72 | this._app = app; 73 | } 74 | 75 | fetch(opts) { 76 | return fetch(this.href, opts) 77 | .then(resp => { 78 | if(!resp.ok) { return Promise.reject(resp); } 79 | if(!resp.headers.has("Content-Type")) { return Promise.reject(resp); } 80 | 81 | const codecs = this._app.codecs; 82 | const contentType = resp.headers.get("Content-Type"); 83 | if(contentType in codecs) { 84 | const codec = codecs[contentType]; 85 | return codec 86 | .decode(resp) 87 | .then(doc => codec.isHAL ? new HALResource(doc, this._app) : doc); 88 | } else { 89 | return resp; 90 | } 91 | }); 92 | } 93 | } 94 | 95 | function mapToObj(collection, fn) { 96 | for(let rel in collection) { 97 | const item = collection[rel]; 98 | if(Array.isArray(item)) { 99 | for(let index in item) { 100 | item[index] = fn(item[index]); 101 | } 102 | } else { 103 | collection[rel] = fn(item); 104 | } 105 | } 106 | } 107 | 108 | function expandCuries(links, curies) { 109 | for(let curie in curies) { 110 | for(let rel in links) { 111 | if(rel.startsWith(curie + ":")) { 112 | let shortRelName = rel.substring(curie.length + 1); 113 | let fullLinkRel = curies[curie].expand({rel: shortRelName}); 114 | links[fullLinkRel] = links[rel]; 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/dbg-ui/incbin.bat: -------------------------------------------------------------------------------- 1 | : incbin - A utility for including binary files in C source. 2 | : Given a binary file and a C symbol name, generates 3 | : a .h or .c file with that binary data stored as an 4 | : array of unsigned chars. 5 | : 6 | : This script has no non-standard dependencies and runs 7 | : on both Windows and UNIX. 8 | : 9 | : http://github.com/rmitton/incbin 10 | : 11 | : Usage: 12 | : Windows: incbin.bat myfile.bin output.h symbolname 13 | : UNIX: sh incbin.bat myfile.bin output.h symbolname 14 | : 15 | : ; if false ; then #lolwut 16 | 17 | 25 | 26 | 130 | -------------------------------------------------------------------------------- /src/compiler/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #define OPTPARSE_IMPLEMENTATION 8 | #define OPTPARSE_API static 9 | #include 10 | #include "../cli.h" 11 | 12 | #define quit(code) exit_code = code; goto quit; 13 | 14 | const struct optparse_long opts[] = { 15 | { "help", 'h', OPTPARSE_NONE }, 16 | { "version", 'v', OPTPARSE_NONE }, 17 | { "output", 'o', OPTPARSE_REQUIRED }, 18 | { "inspect", 'i', OPTPARSE_OPTIONAL }, 19 | { 0 } 20 | }; 21 | 22 | const char* help[] = { 23 | NULL, "Print this message", 24 | NULL, "Show version information", 25 | "name", "Output bytecode to file `name`", 26 | "depth", "Inspect script up to depth `depth` (default: 1)", 27 | }; 28 | 29 | static void 30 | show_usage() 31 | { 32 | fprintf(stderr, "Usage: lipc [options] [--] \n"); 33 | fprintf(stderr, "Available options:\n"); 34 | show_options(opts, help); 35 | fprintf(stderr, "\nUse '-' as `input` to read from stdin\n"); 36 | } 37 | 38 | int 39 | main(int argc, char* argv[]) 40 | { 41 | (void)argc; 42 | 43 | int exit_code = EXIT_SUCCESS; 44 | 45 | const char* input_file = NULL; 46 | const char* output_file = NULL; 47 | int print_depth = -1; 48 | 49 | lip_runtime_config_t* config = NULL; 50 | lip_runtime_t* runtime = NULL; 51 | lip_context_t* ctx = NULL; 52 | lip_script_t* script = NULL; 53 | 54 | int option; 55 | struct optparse options; 56 | optparse_init(&options, argv); 57 | 58 | while((option = optparse_long(&options, opts, NULL)) != -1) 59 | { 60 | switch(option) 61 | { 62 | case 'h': 63 | show_usage(); 64 | quit(EXIT_SUCCESS); 65 | break; 66 | case 'v': 67 | printf("lip %s\n", LIP_VERSION); 68 | quit(EXIT_SUCCESS); 69 | break; 70 | case '?': 71 | fprintf(stderr, "lipc: %s\n", options.errmsg); 72 | quit(EXIT_FAILURE); 73 | break; 74 | case 'o': 75 | output_file = options.optarg; 76 | break; 77 | case 'i': 78 | print_depth = options.optarg ? atoi(options.optarg) : 1; 79 | break; 80 | } 81 | } 82 | 83 | input_file = optparse_arg(&options); 84 | 85 | if(!input_file) 86 | { 87 | fprintf(stderr, "lipc: No input file given\n"); 88 | show_usage(); 89 | quit(EXIT_FAILURE); 90 | } 91 | 92 | config = lip_create_std_runtime_config(NULL); 93 | runtime = lip_create_runtime(config); 94 | ctx = lip_create_context(runtime, NULL); 95 | 96 | lip_in_t* input; 97 | 98 | if(strcmp(input_file, "-") == 0) 99 | { 100 | input_file = ""; 101 | input = lip_stdin(); 102 | } 103 | else 104 | { 105 | input = NULL; 106 | } 107 | 108 | script = lip_load_script(ctx, lip_string_ref(input_file), input); 109 | if(!script) 110 | { 111 | lip_print_error(lip_stderr(), ctx); 112 | quit(EXIT_FAILURE); 113 | } 114 | 115 | if(output_file) 116 | { 117 | bool result = lip_dump_script( 118 | ctx, script, lip_string_ref(output_file), NULL 119 | ); 120 | if(!result) 121 | { 122 | lip_print_error(lip_stderr(), ctx); 123 | quit(EXIT_FAILURE); 124 | } 125 | } 126 | 127 | if(print_depth > 0) 128 | { 129 | lip_print_script(print_depth, 0, lip_stdout(), script); 130 | } 131 | 132 | quit: 133 | if(script) { lip_unload_script(ctx, script); } 134 | if(ctx) { lip_destroy_context(ctx); } 135 | if(runtime) { lip_destroy_runtime(runtime); } 136 | if(config) { lip_destroy_std_runtime_config(config); } 137 | return exit_code; 138 | } 139 | -------------------------------------------------------------------------------- /src/core/prim_ops.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "utils.h" 6 | 7 | #define lip_prim_op_bind_args(...) \ 8 | const unsigned int arity_min = 0 + lip_pp_map(lip_bind_count_arity, __VA_ARGS__); \ 9 | const unsigned int arity_max = lip_pp_len(__VA_ARGS__); \ 10 | if(arity_min != arity_max) { \ 11 | lip_bind_assert_argc_at_least(arity_min); \ 12 | lip_bind_assert_argc_at_most(arity_max); \ 13 | } else { \ 14 | lip_bind_assert_argc(arity_min); \ 15 | } \ 16 | lip_pp_map(lip_bind_arg, __VA_ARGS__) 17 | 18 | #define LIP_DECLARE_CMP_OP_FN(op, name) \ 19 | LIP_PRIM_OP_FN(name) \ 20 | { \ 21 | lip_prim_op_bind_args((any, lhs), (any, rhs)); \ 22 | lip_return(lip_make_boolean(vm, lip_gen_cmp(lhs, rhs) op 0)); \ 23 | } 24 | 25 | LIP_PRIM_OP_FN(ADD) 26 | { 27 | double sum = 0; 28 | for(unsigned int i = 1; i <= argc; ++i) 29 | { 30 | lip_bind_arg(i, (number, x)); 31 | sum += x; 32 | } 33 | lip_return(lip_make_number(vm, sum)); 34 | } 35 | 36 | LIP_PRIM_OP_FN(SUB) 37 | { 38 | lip_prim_op_bind_args((number, lhs), (number, rhs, (optional, 0))); 39 | lip_return(lip_make_number(vm, argc == 1 ? -lhs : lhs - rhs)); 40 | } 41 | 42 | LIP_PRIM_OP_FN(MUL) 43 | { 44 | double product = 1.0; 45 | for(unsigned int i = 1; i <= argc; ++i) 46 | { 47 | lip_bind_arg(i, (number, x)); 48 | product *= x; 49 | } 50 | lip_return(lip_make_number(vm, product)); 51 | } 52 | 53 | LIP_PRIM_OP_FN(FDIV) 54 | { 55 | lip_prim_op_bind_args((number, lhs), (number, rhs, (optional, 0))); 56 | lip_return(lip_make_number(vm, argc == 1 ? 1.0 / lhs : lhs / rhs)); 57 | } 58 | 59 | LIP_PRIM_OP_FN(NOT) 60 | { 61 | lip_bind_assert_argc(1); 62 | bool is_false = 63 | (argv[0].type == LIP_VAL_NIL) 64 | || (argv[0].type == LIP_VAL_BOOLEAN && !argv[0].data.boolean); 65 | lip_return(lip_make_boolean(vm, is_false)); 66 | } 67 | 68 | static int 69 | lip_gen_cmp(lip_value_t lhs, lip_value_t rhs) 70 | { 71 | int type_cmp = lhs.type - rhs.type; 72 | if(LIP_UNLIKELY(type_cmp != 0)) { return type_cmp; } 73 | 74 | switch(lhs.type) 75 | { 76 | case LIP_VAL_NIL: 77 | return 0; 78 | case LIP_VAL_NUMBER: 79 | return lhs.data.number - rhs.data.number; 80 | case LIP_VAL_BOOLEAN: 81 | return lhs.data.boolean - rhs.data.boolean; 82 | case LIP_VAL_STRING: 83 | { 84 | lip_string_t* lstr = lip_as_string(lhs); 85 | lip_string_t* rstr = lip_as_string(rhs); 86 | size_t min_len = LIP_MIN(lstr->length, rstr->length); 87 | int cmp = memcmp(lstr->ptr, rstr->ptr, min_len); 88 | return cmp != 0 ? cmp : (int)(lstr->length - rstr->length); 89 | } 90 | break; 91 | case LIP_VAL_PLACEHOLDER: 92 | return lhs.data.index - rhs.data.index; 93 | case LIP_VAL_LIST: 94 | { 95 | const lip_list_t* llist = lip_as_list(lhs); 96 | const lip_list_t* rlist = lip_as_list(rhs); 97 | 98 | size_t cmp_len = LIP_MIN(llist->length, rlist->length); 99 | for(size_t i = 0; i < cmp_len; ++i) 100 | { 101 | int cmp = lip_gen_cmp(llist->elements[i], rlist->elements[i]); 102 | if(cmp != 0) { return cmp; } 103 | } 104 | 105 | return (int)(llist->length - rlist->length); 106 | } 107 | default: 108 | return (ptrdiff_t)((char*)lhs.data.reference - (char*)rhs.data.reference); 109 | } 110 | } 111 | 112 | LIP_PRIM_OP_FN(CMP) 113 | { 114 | lip_prim_op_bind_args((any, lhs), (any, rhs)); \ 115 | lip_return(lip_make_number(vm, lip_gen_cmp(lhs, rhs))); \ 116 | } 117 | 118 | LIP_CMP_OP(LIP_DECLARE_CMP_OP_FN) 119 | -------------------------------------------------------------------------------- /src/core/vendor/format/format.h: -------------------------------------------------------------------------------- 1 | /* **************************************************************************** 2 | * Format - lightweight string formatting library. 3 | * Copyright (C) 2010-2015, Neil Johnson 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, 7 | * with or without modification, 8 | * are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, 11 | * this list of conditions and the following disclaimer. 12 | * * Redistributions in binary form must reproduce the above copyright notice, 13 | * this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * * Neither the name of nor the names of its contributors 16 | * may be used to endorse or promote products derived from this software 17 | * without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 23 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 24 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 25 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | * ************************************************************************* */ 31 | 32 | #ifndef FORMAT_H 33 | #define FORMAT_H 34 | 35 | #include 36 | 37 | /* Error code returned when problem with format specification */ 38 | 39 | #define EXBADFORMAT (-1) 40 | 41 | /** 42 | Interpret format specification passing formatted text to consumer function. 43 | 44 | Executes the printf-compatible format specification @a fmt, referring to 45 | optional arguments @a ap. Any output text is passed to caller-provided 46 | consumer function @a cons, which also takes caller-provided opaque pointer 47 | @a arg. 48 | 49 | @param cons Pointer to caller-provided consumer function. 50 | @param arg Opaque pointer passed through to @a cons. 51 | @param fmt printf-compatible format specifier. 52 | @param ap List of optional format string arguments 53 | 54 | @returns Number of characters sent to @a cons, or EXBADFORMAT. 55 | **/ 56 | int lip_format( void * (* /* cons */) (void *, const char *, size_t), 57 | void * /* arg */, 58 | const char * /* fmt */, 59 | va_list /* ap */ 60 | ); 61 | 62 | /* The Consumer Function 63 | * 64 | * The consumer function 'cons' must have the following type: 65 | * 66 | * void * cons ( void * arg, const char * s, size_t n ) 67 | * 68 | * You may give your function any valid C name. 69 | * 70 | * It takes an opaque pointer argument, which may be modified by the call. 71 | * The second and third arguments specify the source string and the number of 72 | * characters to take from the string. 73 | * If the call is successful then a new value of the opaque pointer is returned, 74 | * which will be passed on the next time the function is called. 75 | * In case of an error, the function returns NULL. 76 | */ 77 | 78 | #endif 79 | -------------------------------------------------------------------------------- /src/tests/array.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "munit.h" 5 | 6 | static MunitResult 7 | retention(const MunitParameter params[], void* fixture) 8 | { 9 | (void)params; 10 | (void)fixture; 11 | 12 | lip_array(int) array = lip_array_create(lip_default_allocator, int, 2); 13 | munit_assert_size(0, ==, lip_array_len(array)); 14 | 15 | for(int i = 0; i < 10000; ++i) 16 | { 17 | munit_assert_size(i, ==, lip_array_len(array)); 18 | lip_array_push(array, i); 19 | } 20 | 21 | munit_assert_size(10000, ==, lip_array_len(array)); 22 | 23 | for(int i = 0; i < 10000; ++i) 24 | { 25 | munit_assert_int(array[i], ==, i); 26 | } 27 | 28 | lip_array_destroy(array); 29 | 30 | return MUNIT_OK; 31 | } 32 | 33 | static MunitResult 34 | alignment_sse(const MunitParameter params[], void* fixture) 35 | { 36 | (void)params; 37 | (void)fixture; 38 | 39 | lip_array(__m128i) array = lip_array_create(lip_default_allocator, __m128i, 1); 40 | munit_assert_true((uintptr_t)array % LIP_ALIGN_OF(__m128i) == 0); 41 | 42 | for(int i = 0; i < 10000; ++i) 43 | { 44 | lip_array_push(array, _mm_set_epi32(i, i + 1, i + 2, i + 3)); 45 | munit_assert_true((uintptr_t)array % LIP_ALIGN_OF(__m128i) == 0); 46 | } 47 | 48 | for(int i = 0; i < 10000; ++i) 49 | { 50 | __m128i expected = _mm_set_epi32(i, i + 1, i + 2, i + 3); 51 | munit_assert_memory_equal(sizeof(expected), &expected, &array[i]); 52 | } 53 | 54 | lip_array_destroy(array); 55 | 56 | return MUNIT_OK; 57 | } 58 | 59 | static MunitResult 60 | alignment_long_double(const MunitParameter params[], void* fixture) 61 | { 62 | (void)params; 63 | (void)fixture; 64 | 65 | lip_array(long double) array = lip_array_create(lip_default_allocator, long double, 1); 66 | munit_assert_true((uintptr_t)array % LIP_ALIGN_OF(long double) == 0); 67 | 68 | for(int i = 0; i < 10000; ++i) 69 | { 70 | lip_array_push(array, i); 71 | munit_assert_true((uintptr_t)array % LIP_ALIGN_OF(long double) == 0); 72 | } 73 | 74 | lip_array_destroy(array); 75 | 76 | return MUNIT_OK; 77 | } 78 | 79 | static MunitResult 80 | quick_remove(const MunitParameter params[], void* fixture) 81 | { 82 | (void)params; 83 | (void)fixture; 84 | 85 | uint32_t num_elements = munit_rand_uint32() % 1000 + 1; 86 | uint32_t deleted_element = munit_rand_uint32() % num_elements; 87 | 88 | lip_array(uint32_t) array = lip_array_create(lip_default_allocator, uint32_t, 0); 89 | 90 | for(uint32_t i = 0; i < num_elements; ++i) 91 | { 92 | lip_array_push(array, i); 93 | } 94 | 95 | munit_assert_size(num_elements, ==, lip_array_len(array)); 96 | lip_array_quick_remove(array, deleted_element); 97 | munit_assert_size(num_elements - 1, ==, lip_array_len(array)); 98 | 99 | for(uint32_t i = 0; i < num_elements; ++i) 100 | { 101 | bool present = false; 102 | 103 | for(uint32_t j = 0; j < lip_array_len(array); ++j) 104 | { 105 | if(array[j] == i) 106 | { 107 | present = true; 108 | break; 109 | } 110 | } 111 | 112 | if(i == deleted_element) 113 | { 114 | munit_assert_false(present); 115 | } 116 | else 117 | { 118 | munit_assert_true(present); 119 | } 120 | } 121 | 122 | lip_array_destroy(array); 123 | 124 | return MUNIT_OK; 125 | } 126 | 127 | static MunitTest tests[] = { 128 | { 129 | .name = "/retention", 130 | .test = retention 131 | }, 132 | { 133 | .name = "/alignment/sse", 134 | .test = alignment_sse 135 | }, 136 | { 137 | .name = "/alignment/long_double", 138 | .test = alignment_long_double 139 | }, 140 | { 141 | .name = "/quick_remove", 142 | .test = quick_remove, 143 | }, 144 | { .test = NULL } 145 | }; 146 | 147 | MunitSuite array = { 148 | .prefix = "/array", 149 | .tests = tests 150 | }; 151 | -------------------------------------------------------------------------------- /src/core/vendor/format/format_config.h: -------------------------------------------------------------------------------- 1 | /* **************************************************************************** 2 | * Format - lightweight string formatting library. 3 | * Copyright (C) 2010-2015, Neil Johnson 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, 7 | * with or without modification, 8 | * are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, 11 | * this list of conditions and the following disclaimer. 12 | * * Redistributions in binary form must reproduce the above copyright notice, 13 | * this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * * Neither the name of nor the names of its contributors 16 | * may be used to endorse or promote products derived from this software 17 | * without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 23 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 24 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 25 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | * ************************************************************************* */ 31 | 32 | #ifndef FORMAT_CONFIG_H 33 | #define FORMAT_CONFIG_H 34 | 35 | /*****************************************************************************/ 36 | /* Query the environment about what capabilities are available. */ 37 | /*****************************************************************************/ 38 | 39 | /** Check if we are in the C hosted configuration **/ 40 | 41 | #if (__STDC_HOSTED__ == 1) 42 | #define CONFIG_HAVE_LIBC 43 | #endif 44 | 45 | /*****************************************************************************/ 46 | /** On some machines it is better (smaller/faster) to do the long div-10 math 47 | inline rather than calling out to compiler support library. If this switch 48 | is defined then format will use an inline divide-by-ten. 49 | Only recommended for 32-bit machines (eg. ARM) as the necessary math 50 | produces a 64-bit (or larger) result. 51 | **/ 52 | #if defined(__arm__) || defined(__i386__) 53 | #define CONFIG_USE_INLINE_DIV10 54 | #endif 55 | 56 | /*****************************************************************************/ 57 | /** Some machines have a separate memory space for constants. This macro and 58 | type support addressing strings in this alternate data space. 59 | The default is treated as normal data accesses. 60 | **/ 61 | #if defined(__GNUC__) && defined(__AVR__) 62 | #include 63 | #define ROM_CHAR(p) (pgm_read_byte((PGM_P)(p))) 64 | #define ROM_PTR_T PGM_P 65 | #define ROM_DECL(x) x PROGMEM 66 | #define CONFIG_HAVE_ALT_PTR 67 | #else /* Default */ 68 | #define ROM_CHAR(p) ( *(const char *)(p) ) 69 | #define ROM_PTR_T const char * 70 | #define ROM_DECL(x) x 71 | #endif 72 | 73 | /*****************************************************************************/ 74 | /** Provide support for floating point output. Many smaller embedded systems 75 | simply do not need this functionality so make it possible to remove it at 76 | build time. If used at runtime the call to format will return EXBADFORMAT. 77 | **/ 78 | #define CONFIG_WITH_FP_SUPPORT 79 | 80 | 81 | #endif /* FORMAT_CONFIG_H */ 82 | -------------------------------------------------------------------------------- /src/core/repl.c: -------------------------------------------------------------------------------- 1 | #include "lip_internal.h" 2 | #include 3 | #include 4 | 5 | typedef struct lip_repl_stream_s lip_repl_stream_t; 6 | 7 | struct lip_repl_stream_s 8 | { 9 | lip_in_t vtable; 10 | lip_repl_handler_t* repl_handler; 11 | }; 12 | 13 | static size_t 14 | lip_repl_read(void* buff, size_t size, lip_in_t* vtable) 15 | { 16 | lip_repl_stream_t* stream = LIP_CONTAINER_OF(vtable, lip_repl_stream_t, vtable); 17 | return stream->repl_handler->read(stream->repl_handler, buff, size); 18 | } 19 | 20 | void 21 | lip_repl( 22 | lip_vm_t* vm, lip_string_ref_t source_name, lip_repl_handler_t* repl_handler 23 | ) 24 | { 25 | lip_runtime_link_t* rt = LIP_CONTAINER_OF(vm->rt, lip_runtime_link_t, vtable); 26 | lip_context_t* ctx = rt->ctx; 27 | 28 | struct lip_repl_stream_s input = { 29 | .repl_handler = repl_handler, 30 | .vtable = { .read = lip_repl_read } 31 | }; 32 | 33 | lip_pp_t pp = { 34 | .allocator = ctx->temp_pool 35 | }; 36 | 37 | for(;;) 38 | { 39 | lip_arena_allocator_reset(ctx->temp_pool); 40 | lip_parser_reset(&ctx->parser, &input.vtable); 41 | 42 | lip_sexp_t sexp; 43 | switch(lip_parser_next_sexp(&ctx->parser, &sexp)) 44 | { 45 | case LIP_STREAM_OK: 46 | { 47 | lip_pp_result_t pp_result = lip_preprocess(&pp, &sexp); 48 | if(!pp_result.success) 49 | { 50 | lip_set_compile_error( 51 | ctx, 52 | lip_string_ref(pp_result.value.error.extra), 53 | source_name, 54 | pp_result.value.error.location 55 | ); 56 | repl_handler->print( 57 | repl_handler, 58 | LIP_EXEC_ERROR, 59 | (lip_value_t) { .type = LIP_VAL_NIL } 60 | ); 61 | continue; 62 | } 63 | 64 | lip_ast_result_t ast_result = lip_translate_sexp( 65 | ctx->temp_pool, pp_result.value.result 66 | ); 67 | 68 | if(!ast_result.success) 69 | { 70 | lip_set_compile_error( 71 | ctx, 72 | lip_string_ref(ast_result.value.error.extra), 73 | source_name, 74 | ast_result.value.error.location 75 | ); 76 | repl_handler->print( 77 | repl_handler, 78 | LIP_EXEC_ERROR, 79 | (lip_value_t) { .type = LIP_VAL_NIL } 80 | ); 81 | continue; 82 | } 83 | 84 | lip_compiler_begin(&ctx->compiler, source_name); 85 | lip_compiler_add_ast(&ctx->compiler, ast_result.value.result); 86 | lip_function_t* fn = lip_compiler_end(&ctx->compiler, ctx->temp_pool); 87 | lip_ctx_begin_load(ctx); 88 | bool linked = lip_link_function(ctx, fn); 89 | lip_ctx_end_load(ctx); 90 | 91 | if(!linked) 92 | { 93 | repl_handler->print( 94 | repl_handler, 95 | LIP_EXEC_ERROR, 96 | (lip_value_t) { .type = LIP_VAL_NIL } 97 | ); 98 | continue; 99 | } 100 | 101 | lip_closure_t* closure = lip_new(ctx->temp_pool, lip_closure_t); 102 | *closure = (lip_closure_t){ 103 | .function = { .lip = fn }, 104 | .is_native = false, 105 | .env_len = 0, 106 | }; 107 | lip_reset_vm(vm); 108 | lip_value_t result; 109 | lip_exec_status_t status = lip_call( 110 | vm, 111 | &result, 112 | (lip_value_t){ 113 | .type = LIP_VAL_FUNCTION, 114 | .data = { .reference = closure } 115 | }, 116 | 0 117 | ); 118 | ctx->last_result = result; 119 | ctx->last_vm = vm; 120 | repl_handler->print(repl_handler, status, result); 121 | } 122 | break; 123 | case LIP_STREAM_ERROR: 124 | { 125 | const lip_error_t* error = lip_parser_last_error(&ctx->parser); 126 | lip_set_compile_error( 127 | ctx, 128 | lip_format_parse_error(error), 129 | source_name, 130 | error->location 131 | ); 132 | repl_handler->print( 133 | repl_handler, 134 | LIP_EXEC_ERROR, 135 | (lip_value_t) { .type = LIP_VAL_NIL } 136 | ); 137 | } 138 | break; 139 | case LIP_STREAM_END: 140 | return; 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /include/lip/core/io.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_CORE_IO_H 2 | #define LIP_CORE_IO_H 3 | 4 | /** 5 | * @defgroup io IO 6 | * @brief IO functions and structures 7 | * 8 | * @{ 9 | */ 10 | 11 | #include 12 | #include 13 | #include "common.h" 14 | 15 | /** 16 | * @brief Filesystem interface. 17 | * 18 | * Callbacks might be invoked from different threads at the same time. 19 | */ 20 | struct lip_fs_s 21 | { 22 | /** 23 | * @brief Callback to open a file for reading. 24 | * 25 | * Should have similar behaviour to `fopen(path, "rb")`. 26 | * 27 | * @param self Filesystem. 28 | * @param path Path to file. 29 | * @return an input stream or `NULL` if file cannot be openned. 30 | * 31 | * @see lip_fs_s::last_error 32 | */ 33 | lip_in_t*(*begin_read)(lip_fs_t* self, lip_string_ref_t path); 34 | 35 | /** 36 | * @brief Callback to close a file previously openned for reading. 37 | * 38 | * Should have similar behaviour to `fclose(file)`. 39 | */ 40 | void(*end_read)(lip_fs_t* self, lip_in_t* input); 41 | 42 | /** 43 | * @brief Callback to open a file for writing. 44 | * 45 | * Should have similar behaviour to `fopen(path, "wb")`. 46 | * 47 | * @param self Filesystem. 48 | * @param path Path to file. 49 | * @return an output stream or `NULL` if file cannot be openned. 50 | * 51 | * @see lip_fs_s::last_error 52 | */ 53 | lip_out_t*(*begin_write)(lip_fs_t* self, lip_string_ref_t path); 54 | 55 | /** 56 | * @brief Callback to close a file previously openned for writing. 57 | * 58 | * Should have similar behaviour to `fclose(file)`. 59 | */ 60 | void(*end_write)(lip_fs_t* self, lip_out_t* output); 61 | 62 | /** 63 | * @brief Callback to get the last error of the filesystem. 64 | * 65 | * Should have similar behaviour to `strerror(errno)`. 66 | */ 67 | lip_string_ref_t(*last_error)(lip_fs_t* self); 68 | }; 69 | 70 | /// Input stream interface. 71 | struct lip_in_s 72 | { 73 | /// This callback should behave like fread. 74 | size_t (*read)(void* buff, size_t size, lip_in_t* input); 75 | }; 76 | 77 | /// Output stream interface. 78 | struct lip_out_s 79 | { 80 | /// This callback should behave like fwrite. 81 | size_t (*write)(const void* buff, size_t size, lip_out_t* output); 82 | }; 83 | 84 | /** 85 | * @brief Input string stream. 86 | * @see lip_make_isstream 87 | */ 88 | struct lip_isstream_s 89 | { 90 | lip_in_t vtable; 91 | lip_string_ref_t str; 92 | size_t pos; 93 | }; 94 | 95 | /** 96 | * @brief Output string stream. 97 | * @see lip_make_osstream 98 | */ 99 | struct lip_osstream_s 100 | { 101 | lip_out_t vtable; 102 | lip_array(char)* buffer; 103 | }; 104 | 105 | /// Read from a ::lip_in_s, similar behaviour to fread. 106 | LIP_MAYBE_UNUSED static inline size_t 107 | lip_read(void* buff, size_t size, lip_in_t* input) 108 | { 109 | return input->read(buff, size, input); 110 | } 111 | 112 | /// Write to a ::lip_in_s, similar behaviour to fwrite. 113 | LIP_MAYBE_UNUSED static inline size_t 114 | lip_write(const void* buff, size_t size, lip_out_t* output) 115 | { 116 | return output->write(buff, size, output); 117 | } 118 | 119 | /// Print formatted text to a ::lip_out_s, similar behaviour to fprintf. 120 | LIP_CORE_API LIP_PRINTF_LIKE(2, 3) size_t 121 | lip_printf(lip_out_t* output, const char* format, ...); 122 | 123 | /// Print formatted text to a ::lip_out_s, similar behaviour to fvprintf. 124 | LIP_CORE_API size_t 125 | lip_vprintf(lip_out_t* output, const char* format, va_list args); 126 | 127 | /// Create an input string stream. 128 | LIP_CORE_API lip_in_t* 129 | lip_make_isstream(lip_string_ref_t str, struct lip_isstream_s* sstream); 130 | 131 | /** 132 | * @brief Create an output string stream. 133 | * 134 | * @param buffer An array to hold the output. 135 | * @param sstream An instance of ::lip_osstream_s (usually stack-allocated). 136 | * 137 | * @see array 138 | */ 139 | LIP_CORE_API lip_out_t* 140 | lip_make_osstream(lip_array(char)* buffer, struct lip_osstream_s* sstream); 141 | 142 | /** 143 | * @} 144 | */ 145 | 146 | #endif 147 | -------------------------------------------------------------------------------- /src/std/io.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | static struct lip_ofstream_s lip_stdout_ofstream; 6 | static struct lip_ofstream_s lip_stderr_ofstream; 7 | static struct lip_ifstream_s lip_stdin_ifstream; 8 | 9 | struct lip_std_fs_s 10 | { 11 | lip_fs_t vtable; 12 | lip_allocator_t* allocator; 13 | }; 14 | 15 | static size_t 16 | lip_ifstream_read(void* buff, size_t size, lip_in_t* vtable) 17 | { 18 | struct lip_ifstream_s* ifstream = 19 | LIP_CONTAINER_OF(vtable, struct lip_ifstream_s, vtable); 20 | return fread(buff, 1, size, ifstream->file); 21 | } 22 | 23 | static size_t 24 | lip_ofstream_write(const void* buff, size_t size, lip_out_t* vtable) 25 | { 26 | struct lip_ofstream_s* ofstream = 27 | LIP_CONTAINER_OF(vtable, struct lip_ofstream_s, vtable); 28 | return fwrite(buff, 1, size, ofstream->file); 29 | } 30 | 31 | static lip_in_t* 32 | lip_std_fs_begin_read(lip_fs_t* vtable, lip_string_ref_t path) 33 | { 34 | FILE* file = fopen(path.ptr, "rb"); 35 | if(file == NULL) { return NULL; } 36 | 37 | struct lip_std_fs_s* fs = 38 | LIP_CONTAINER_OF(vtable, struct lip_std_fs_s, vtable); 39 | struct lip_ifstream_s* ifstream = lip_new(fs->allocator, struct lip_ifstream_s); 40 | return lip_make_ifstream(file, ifstream); 41 | } 42 | 43 | static lip_out_t* 44 | lip_std_fs_begin_write(lip_fs_t* vtable, lip_string_ref_t path) 45 | { 46 | FILE* file = fopen(path.ptr, "wb"); 47 | if(file == NULL) { return NULL; } 48 | 49 | struct lip_std_fs_s* fs = 50 | LIP_CONTAINER_OF(vtable, struct lip_std_fs_s, vtable); 51 | struct lip_ofstream_s* ofstream = lip_new(fs->allocator, struct lip_ofstream_s); 52 | return lip_make_ofstream(file, ofstream); 53 | } 54 | 55 | static void 56 | lip_std_fs_end_read(lip_fs_t* vtable, lip_in_t* file) 57 | { 58 | struct lip_std_fs_s* fs = 59 | LIP_CONTAINER_OF(vtable, struct lip_std_fs_s, vtable); 60 | struct lip_ifstream_s* ifstream = 61 | LIP_CONTAINER_OF(file, struct lip_ifstream_s, vtable); 62 | fclose(ifstream->file); 63 | lip_free(fs->allocator, ifstream); 64 | } 65 | 66 | static void 67 | lip_std_fs_end_write(lip_fs_t* vtable, lip_out_t* file) 68 | { 69 | struct lip_std_fs_s* fs = 70 | LIP_CONTAINER_OF(vtable, struct lip_std_fs_s, vtable); 71 | struct lip_ofstream_s* ofstream = 72 | LIP_CONTAINER_OF(file, struct lip_ofstream_s, vtable); 73 | fclose(ofstream->file); 74 | lip_free(fs->allocator, ofstream); 75 | } 76 | 77 | static lip_string_ref_t 78 | lip_std_fs_last_error(lip_fs_t* vtable) 79 | { 80 | (void)vtable; 81 | return lip_string_ref(strerror(errno)); 82 | } 83 | 84 | lip_fs_t* 85 | lip_create_std_fs(lip_allocator_t* allocator) 86 | { 87 | struct lip_std_fs_s* fs = lip_new(allocator, struct lip_std_fs_s); 88 | *fs = (struct lip_std_fs_s){ 89 | .allocator = allocator, 90 | .vtable = { 91 | .begin_read = lip_std_fs_begin_read, 92 | .end_read = lip_std_fs_end_read, 93 | .begin_write = lip_std_fs_begin_write, 94 | .end_write = lip_std_fs_end_write, 95 | .last_error = lip_std_fs_last_error 96 | } 97 | }; 98 | return &fs->vtable; 99 | } 100 | 101 | void 102 | lip_destroy_std_fs(lip_fs_t* vtable) 103 | { 104 | struct lip_std_fs_s* fs = 105 | LIP_CONTAINER_OF(vtable, struct lip_std_fs_s, vtable); 106 | lip_free(fs->allocator, fs); 107 | } 108 | 109 | 110 | lip_in_t* 111 | lip_stdin(void) 112 | { 113 | static lip_in_t* in = NULL; 114 | if(!in) 115 | { 116 | in = lip_make_ifstream(stdin, &lip_stdin_ifstream); 117 | } 118 | 119 | return in; 120 | } 121 | 122 | lip_out_t* 123 | lip_stdout(void) 124 | { 125 | static lip_out_t* out = NULL; 126 | if(!out) 127 | { 128 | out = lip_make_ofstream(stdout, &lip_stdout_ofstream); 129 | } 130 | 131 | return out; 132 | } 133 | 134 | lip_out_t* 135 | lip_stderr(void) 136 | { 137 | static lip_out_t* out = NULL; 138 | if(!out) 139 | { 140 | out = lip_make_ofstream(stderr, &lip_stderr_ofstream); 141 | } 142 | 143 | return out; 144 | } 145 | 146 | lip_out_t* 147 | lip_make_ofstream(FILE* file, struct lip_ofstream_s* ofstream) 148 | { 149 | ofstream->file = file; 150 | ofstream->vtable.write = lip_ofstream_write; 151 | 152 | return &ofstream->vtable; 153 | } 154 | 155 | lip_in_t* 156 | lip_make_ifstream(FILE* file, struct lip_ifstream_s* ifstream) 157 | { 158 | ifstream->file = file; 159 | ifstream->vtable.read = lip_ifstream_read; 160 | 161 | return &ifstream->vtable; 162 | } 163 | -------------------------------------------------------------------------------- /src/core/lip_internal.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_LIP_INTERNAL_H 2 | #define LIP_LIP_INTERNAL_H 3 | 4 | #include "platform.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "arena_allocator.h" 12 | 13 | #define lip_assert(ctx, cond) \ 14 | do { \ 15 | if(!(cond)) { \ 16 | lip_panic( \ 17 | ctx, \ 18 | __FILE__ ":" lip_stringify(__LINE__) ": Assertion '" #cond "' failed." \ 19 | ); \ 20 | } \ 21 | } while(0) 22 | #define lip_stringify(x) lip_stringify2(x) 23 | #define lip_stringify2(x) #x 24 | 25 | typedef struct lip_runtime_link_s lip_runtime_link_t; 26 | typedef struct lip_symbol_s lip_symbol_t; 27 | 28 | KHASH_DECLARE(lip_module, lip_string_ref_t, lip_symbol_t) 29 | KHASH_DECLARE(lip_symtab, lip_string_ref_t, khash_t(lip_module)*) 30 | KHASH_DECLARE(lip_ptr_set, void*, char) 31 | KHASH_DECLARE(lip_ptr_map, const void*, void*) 32 | 33 | struct lip_symbol_s 34 | { 35 | bool is_public; 36 | lip_closure_t* value; 37 | }; 38 | 39 | struct lip_module_context_s 40 | { 41 | lip_allocator_t* allocator; 42 | lip_string_ref_t name; 43 | khash_t(lip_module)* content; 44 | }; 45 | 46 | struct lip_runtime_s 47 | { 48 | lip_runtime_config_t cfg; 49 | khash_t(lip_symtab)* symtab; 50 | lip_rwlock_t rt_lock; 51 | }; 52 | 53 | struct lip_runtime_link_s 54 | { 55 | lip_runtime_interface_t vtable; 56 | lip_allocator_t* allocator; 57 | lip_context_t* ctx; 58 | }; 59 | 60 | struct lip_context_s 61 | { 62 | lip_runtime_t* runtime; 63 | lip_allocator_t* allocator; 64 | lip_allocator_t* temp_pool; 65 | lip_allocator_t* module_pool; 66 | lip_panic_fn_t panic_handler; 67 | lip_array(lip_error_record_t) error_records; 68 | lip_context_error_t error; 69 | lip_parser_t parser; 70 | lip_compiler_t compiler; 71 | lip_array(char) string_buff; 72 | lip_vm_t* default_vm; 73 | khash_t(lip_symtab)* loading_symtab; 74 | khash_t(lip_string_ref_set)* loading_modules; 75 | khash_t(lip_ptr_map)* new_exported_functions; 76 | khash_t(lip_ptr_set)* new_script_functions; 77 | khash_t(lip_module)* current_module; 78 | lip_vm_t* last_vm; 79 | lip_value_t last_result; 80 | bool load_aborted; 81 | unsigned int load_depth; 82 | unsigned int rt_read_lock_depth; 83 | unsigned int rt_write_lock_depth; 84 | }; 85 | 86 | struct lip_script_s 87 | { 88 | lip_closure_t* closure; 89 | bool linked; 90 | }; 91 | 92 | void 93 | lip_ctx_begin_load(lip_context_t* ctx); 94 | 95 | void 96 | lip_ctx_end_load(lip_context_t* ctx); 97 | 98 | bool 99 | lip_link_function(lip_context_t* ctx, lip_function_t* fn); 100 | 101 | void 102 | lip_ctx_begin_rt_read(lip_context_t* ctx); 103 | 104 | void 105 | lip_ctx_end_rt_read(lip_context_t* ctx); 106 | 107 | void 108 | lip_ctx_begin_rt_write(lip_context_t* ctx); 109 | 110 | void 111 | lip_ctx_end_rt_write(lip_context_t* ctx); 112 | 113 | lip_string_ref_t 114 | lip_format_parse_error(const lip_error_t* error); 115 | 116 | void 117 | lip_unload_all_scripts(lip_context_t* ctx); 118 | 119 | void 120 | lip_destroy_all_modules(lip_runtime_t* runtime); 121 | 122 | LIP_MAYBE_UNUSED static inline void 123 | lip_set_context_error( 124 | lip_context_t* ctx, 125 | const char* error_type, 126 | lip_string_ref_t message, 127 | lip_string_ref_t filename, 128 | lip_loc_range_t location 129 | ) 130 | { 131 | lip_array_clear(ctx->error_records); 132 | *lip_array_alloc(ctx->error_records) = (lip_error_record_t) { 133 | .filename = filename, 134 | .location = location, 135 | .message = message 136 | }; 137 | ctx->error = (lip_context_error_t) { 138 | .message = lip_string_ref(error_type), 139 | .num_records = 1, 140 | .records = ctx->error_records 141 | }; 142 | } 143 | 144 | LIP_MAYBE_UNUSED static inline void 145 | lip_set_compile_error( 146 | lip_context_t* ctx, 147 | lip_string_ref_t message, 148 | lip_string_ref_t filename, 149 | lip_loc_range_t location 150 | ) 151 | { 152 | lip_set_context_error(ctx, "Syntax error", message, filename, location); 153 | } 154 | 155 | LIP_MAYBE_UNUSED static inline void 156 | lip_panic(lip_context_t* ctx, const char* msg) 157 | { 158 | ctx->panic_handler(ctx, msg); 159 | } 160 | 161 | LIP_MAYBE_UNUSED static lip_closure_t* 162 | lip_copy_closure(lip_allocator_t* allocator, lip_closure_t* closure) 163 | { 164 | size_t closure_size = 165 | sizeof(lip_closure_t) + 166 | sizeof(lip_value_t) * closure->env_len; 167 | lip_closure_t* closure_copy = lip_malloc(allocator, closure_size); 168 | memcpy(closure_copy, closure, closure_size); 169 | 170 | if(!closure->is_native) 171 | { 172 | lip_function_t* function = closure->function.lip; 173 | lip_function_t* function_copy = lip_malloc(allocator, function->size); 174 | memcpy(function_copy, function, function->size); 175 | closure_copy->function.lip = function_copy; 176 | } 177 | 178 | return closure_copy; 179 | } 180 | 181 | #endif 182 | -------------------------------------------------------------------------------- /src/core/vm.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "vm_dispatch.h" 5 | #include "utils.h" 6 | 7 | size_t 8 | lip_vm_memory_required(const lip_vm_config_t* config) 9 | { 10 | lip_memblock_info_t os_block, env_block, cs_block; 11 | lip_memblock_info_t mem_layout = 12 | lip_vm_memory_layout(config, &os_block, &env_block, &cs_block); 13 | 14 | return mem_layout.num_elements; 15 | } 16 | 17 | void 18 | lip_vm_init( 19 | lip_vm_t* vm, 20 | const lip_vm_config_t* config, 21 | lip_runtime_interface_t* rt, 22 | void* mem 23 | ) 24 | { 25 | lip_memblock_info_t os_block, env_block, cs_block; 26 | lip_vm_memory_layout(config, &os_block, &env_block, &cs_block); 27 | 28 | *vm = (lip_vm_t){ 29 | .config = *config, 30 | .rt = rt, 31 | .status = LIP_EXEC_OK, 32 | .mem = mem, 33 | .sp = (lip_value_t*)lip_locate_memblock(mem, &os_block) + config->os_len, 34 | .fp = lip_locate_memblock(mem, &cs_block) 35 | }; 36 | 37 | // Clear out debug info 38 | memset(vm->fp, 0, sizeof(lip_stack_frame_t) * config->cs_len); 39 | 40 | *(vm->fp) = (lip_stack_frame_t){ 41 | .ep = (lip_value_t*)lip_locate_memblock(mem, &env_block) + config->env_len, 42 | .bp = vm->sp 43 | }; 44 | } 45 | 46 | lip_exec_status_t 47 | (lip_call)( 48 | lip_vm_t* vm, 49 | lip_value_t* result, 50 | lip_value_t fn, 51 | unsigned int num_args, 52 | ... 53 | ) 54 | { 55 | if(vm->status != LIP_EXEC_OK) 56 | { 57 | *result = lip_make_string_copy(vm, lip_string_ref("VM is in error state")); 58 | return vm->status; 59 | } 60 | 61 | vm->sp -= num_args; 62 | va_list args; 63 | va_start(args, num_args); 64 | for(unsigned int i = 0; i < LIP_MIN(UINT8_MAX, num_args); ++i) 65 | { 66 | vm->sp[i] = va_arg(args, lip_value_t); 67 | } 68 | va_end(args); 69 | 70 | lip_stack_frame_t* old_fp = vm->fp++; 71 | vm->fp->ep = old_fp->ep; 72 | 73 | lip_exec_status_t status = lip_vm_do_call(vm, &fn, num_args); 74 | if(status != LIP_EXEC_OK) { goto end; } 75 | 76 | if(vm->fp == old_fp) 77 | { 78 | status = LIP_EXEC_OK; 79 | } 80 | else 81 | { 82 | status = lip_vm_loop(vm); 83 | } 84 | 85 | end: 86 | *result = *vm->sp; 87 | // TODO: hold this so that result is not GC'd 88 | ++vm->sp; 89 | vm->status = status; 90 | 91 | if(LIP_UNLIKELY(status == LIP_EXEC_ERROR && vm->hook && vm->hook->error)) 92 | { 93 | vm->hook->error(vm->hook, vm); 94 | } 95 | 96 | return status; 97 | } 98 | 99 | void 100 | lip_set_native_location( 101 | lip_vm_t* vm, const char* function, const char* file, int line 102 | ) 103 | { 104 | vm->fp->native_function = function; 105 | vm->fp->native_filename = file; 106 | vm->fp->native_line = line; 107 | } 108 | 109 | lip_vm_hook_t* 110 | lip_set_vm_hook(lip_vm_t* vm, lip_vm_hook_t* hook) 111 | { 112 | lip_vm_hook_t* old_hook = vm->hook; 113 | vm->hook = hook; 114 | return old_hook; 115 | } 116 | 117 | const lip_value_t* 118 | lip_get_args(const lip_vm_t* vm, uint8_t* num_args) 119 | { 120 | if(num_args) { *num_args = vm->fp->num_args; } 121 | return vm->fp->bp; 122 | } 123 | 124 | const lip_value_t* 125 | lip_get_env(const lip_vm_t* vm, uint8_t* env_len) 126 | { 127 | if(env_len) { *env_len = vm->fp->closure->env_len; } 128 | return vm->fp->closure->environment; 129 | } 130 | 131 | lip_value_t 132 | lip_make_string_copy(lip_vm_t* vm, lip_string_ref_t str) 133 | { 134 | size_t size = sizeof(lip_string_t) + str.length + 1; // null-terminator 135 | lip_string_t* string = vm->rt->malloc(vm->rt, LIP_VAL_STRING, size); 136 | string->length = str.length; 137 | memcpy(string->ptr, str.ptr, str.length); 138 | string->ptr[str.length] = '\0'; 139 | return (lip_value_t){ 140 | .type = LIP_VAL_STRING, 141 | .data = { .reference = string } 142 | }; 143 | } 144 | 145 | lip_value_t 146 | lip_make_string(lip_vm_t* vm, const char* fmt, ...) 147 | { 148 | va_list args; 149 | va_start(args, fmt); 150 | lip_value_t string = lip_make_stringv(vm, fmt, args); 151 | va_end(args); 152 | 153 | return string; 154 | } 155 | 156 | lip_value_t 157 | lip_make_stringv(lip_vm_t* vm, const char* fmt, va_list args) 158 | { 159 | const char* str = vm->rt->format(vm->rt, fmt, args); 160 | size_t len = strlen(str); 161 | 162 | return lip_make_string_copy(vm, (lip_string_ref_t){ 163 | .ptr = str, 164 | .length = len 165 | }); 166 | } 167 | 168 | lip_value_t 169 | lip_make_function( 170 | lip_vm_t* vm, 171 | lip_native_fn_t native_fn, 172 | uint8_t env_len, 173 | lip_value_t env[] 174 | ) 175 | { 176 | size_t size = sizeof(lip_closure_t) + sizeof(lip_value_t) * env_len; 177 | lip_closure_t* closure = vm->rt->malloc(vm->rt, LIP_VAL_FUNCTION, size); 178 | *closure = (lip_closure_t){ 179 | .is_native = true, 180 | .env_len = env_len, 181 | .function = { .native = native_fn } 182 | }; 183 | 184 | if(env_len > 0) 185 | { 186 | memcpy(closure->environment, env, sizeof(lip_value_t) * env_len); 187 | } 188 | 189 | return (lip_value_t){ 190 | .type = LIP_VAL_FUNCTION, 191 | .data = { .reference = closure } 192 | }; 193 | } 194 | -------------------------------------------------------------------------------- /src/tests/runtime_helper.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_TEST_RUNTIME_HELPERS_H 2 | #define LIP_TEST_RUNTIME_HELPERS_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "test_helpers.h" 10 | 11 | #define SOURCE_NAME __FILE__ ":" STRINGIFY(__LINE__) 12 | #define STRINGIFY(X) STRINGIFY2(X) 13 | #define STRINGIFY2(X) #X 14 | 15 | #define lip_assert_result(code, expected_status, assert_fn, ...) \ 16 | do { \ 17 | test_hook_t hook = { .printed = false, .vtable = { .step = step } }; \ 18 | lip_set_vm_hook(vm, &hook.vtable); \ 19 | struct lip_isstream_s sstream; \ 20 | lip_in_t* input = lip_make_isstream(lip_string_ref(code), &sstream); \ 21 | lip_script_t* script = lip_load_script(ctx, lip_string_ref(SOURCE_NAME), input); \ 22 | if(script == NULL) { lip_print_error(lip_stderr(), ctx); } \ 23 | munit_assert_not_null(script); \ 24 | lip_print_script(100, 0, lip_null, script); \ 25 | lip_value_t result; \ 26 | lip_reset_vm(vm); \ 27 | lip_exec_status_t status = lip_exec_script(vm, script, &result); \ 28 | if(status != LIP_EXEC_OK) { lip_print_error(lip_stderr(), ctx); } \ 29 | lip_assert_enum(lip_exec_status_t, expected_status, ==, status); \ 30 | assert_fn(lip_pp_nth(1, (__VA_ARGS__), 0), result); \ 31 | lip_unload_script(ctx, script); \ 32 | } while(0) 33 | 34 | #define lip_assert_num_result(code, ...) \ 35 | lip_assert_result(code, LIP_EXEC_OK, lip_assert_num, __VA_ARGS__) 36 | 37 | #define lip_assert_str_result(code, result_value) \ 38 | lip_assert_result(code, LIP_EXEC_OK, lip_assert_str, result_value) 39 | 40 | #define lip_assert_symbol_result(code, result_value) \ 41 | lip_assert_result(code, LIP_EXEC_OK, lip_assert_symbol, result_value) 42 | 43 | #define lip_assert_nil_result(code) \ 44 | lip_assert_result(code, LIP_EXEC_OK, lip_assert_nil, placeholder) 45 | 46 | #define lip_assert_boolean_result(code, result_value) \ 47 | lip_assert_result(code, LIP_EXEC_OK, lip_assert_boolean, result_value) 48 | 49 | #define lip_assert_pass(expected, actual) 50 | 51 | #define lip_assert_error_msg(code, msg) \ 52 | do { \ 53 | lip_assert_result(code, LIP_EXEC_ERROR, lip_assert_pass, msg); \ 54 | const lip_context_error_t* error = lip_get_error(ctx); \ 55 | lip_assert_string_ref_equal(lip_string_ref(msg), error->message); \ 56 | } while(0) 57 | 58 | #define lip_assert_syntax_error(code, error_msg, start_line, start_col, end_line, end_col) \ 59 | do { \ 60 | struct lip_isstream_s sstream; \ 61 | lip_in_t* input = lip_make_isstream(lip_string_ref(code), &sstream); \ 62 | lip_script_t* script = lip_load_script(ctx, lip_string_ref(SOURCE_NAME), input); \ 63 | munit_assert_null(script); \ 64 | const lip_context_error_t* error = lip_get_error(ctx); \ 65 | lip_assert_string_ref_equal(lip_string_ref("Syntax error"), error->message); \ 66 | lip_assert_string_ref_equal(lip_string_ref(error_msg), error->records[0].message); \ 67 | munit_assert_uint(1, ==, error->num_records); \ 68 | lip_loc_range_t expected_location = { \ 69 | .start = { .line = start_line, .column = start_col }, \ 70 | .end = { .line = end_line, .column = end_col } \ 71 | }; \ 72 | lip_assert_loc_range_equal(expected_location, error->records[0].location); \ 73 | } while(0) 74 | 75 | #define LIP_STRING_REF_LITERAL(str) \ 76 | { .length = LIP_STATIC_ARRAY_LEN(str), .ptr = str} 77 | 78 | typedef struct lip_fixture_s lip_fixture_t; 79 | typedef struct test_hook_s test_hook_t; 80 | 81 | static const lip_string_ref_t module_search_patterns[] = { 82 | LIP_STRING_REF_LITERAL("src/tests/?.lip"), 83 | LIP_STRING_REF_LITERAL("src/tests/!.lip") 84 | }; 85 | 86 | struct test_hook_s 87 | { 88 | lip_vm_hook_t vtable; 89 | bool printed; 90 | }; 91 | 92 | struct lip_fixture_s 93 | { 94 | lip_runtime_t* runtime; 95 | lip_context_t* context; 96 | lip_vm_t* vm; 97 | }; 98 | 99 | LIP_MAYBE_UNUSED static void 100 | step(lip_vm_hook_t* vtable, const lip_vm_t* vm) 101 | { 102 | test_hook_t* hook = LIP_CONTAINER_OF(vtable, test_hook_t, vtable); 103 | if(!hook->printed) 104 | { 105 | hook->printed = true; 106 | lip_print_closure(10, 0, lip_stderr(), vm->fp->closure); 107 | } 108 | } 109 | 110 | LIP_MAYBE_UNUSED static void 111 | teardown(void* fixture_) 112 | { 113 | lip_fixture_t* fixture = fixture_; 114 | lip_destroy_vm(fixture->context, fixture->vm); 115 | lip_destroy_context(fixture->context); 116 | lip_destroy_runtime(fixture->runtime); 117 | lip_free(lip_default_allocator, fixture); 118 | } 119 | 120 | LIP_MAYBE_UNUSED static void* 121 | setup(const MunitParameter params[], void* data) 122 | { 123 | (void)params; 124 | (void)data; 125 | 126 | lip_runtime_config_t cfg; 127 | lip_reset_runtime_config(&cfg); 128 | cfg.module_search_patterns = module_search_patterns; 129 | cfg.num_module_search_patterns = LIP_STATIC_ARRAY_LEN(module_search_patterns); 130 | lip_fixture_t* fixture = lip_new(lip_default_allocator, lip_fixture_t); 131 | fixture->runtime = lip_create_runtime(&cfg); 132 | fixture->context = lip_create_context(fixture->runtime, NULL); 133 | fixture->vm = lip_create_vm(fixture->context, NULL); 134 | return fixture; 135 | } 136 | 137 | #endif 138 | -------------------------------------------------------------------------------- /src/tests/arena_allocator.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "munit.h" 5 | #include "test_helpers.h" 6 | 7 | typedef struct tracking_allocator_s tracking_allocator_t; 8 | typedef struct fixture_s fixture_t; 9 | 10 | struct tracking_allocator_s 11 | { 12 | lip_allocator_t vtable; 13 | int count; 14 | }; 15 | 16 | struct fixture_s 17 | { 18 | tracking_allocator_t base_allocator; 19 | }; 20 | 21 | static void* 22 | tracked_realloc(lip_allocator_t* self, void* old, size_t size) 23 | { 24 | tracking_allocator_t* allocator = (tracking_allocator_t*)self; 25 | if(old == NULL) { ++(allocator->count); } 26 | 27 | void* mem = realloc(old, size); 28 | 29 | munit_logf( 30 | MUNIT_LOG_INFO, 31 | "tracked_realloc: Allocated %zu bytes at %p", size, mem 32 | ); 33 | 34 | return mem; 35 | } 36 | 37 | static void 38 | tracked_free(lip_allocator_t* self, void* mem) 39 | { 40 | tracking_allocator_t* allocator = (tracking_allocator_t*)self; 41 | if(mem) { --(allocator->count); } 42 | 43 | free(mem); 44 | 45 | munit_logf( 46 | MUNIT_LOG_INFO, 47 | "tracked_free: Freed %p", mem 48 | ); 49 | } 50 | 51 | static void* 52 | setup(const MunitParameter params[], void* data) 53 | { 54 | (void)params; 55 | (void)data; 56 | 57 | fixture_t* fixture = malloc(sizeof(fixture_t)); 58 | fixture->base_allocator.vtable.realloc = tracked_realloc; 59 | fixture->base_allocator.vtable.free = tracked_free; 60 | fixture->base_allocator.count = 0; 61 | 62 | return fixture; 63 | } 64 | 65 | static void 66 | teardown(void* data) 67 | { 68 | fixture_t* fixture = data; 69 | munit_assert_int(0, ==, fixture->base_allocator.count); 70 | free(fixture); 71 | } 72 | 73 | static MunitResult 74 | no_leak(const MunitParameter params[], void* fixture_) 75 | { 76 | (void)params; 77 | fixture_t* fixture = fixture_; 78 | lip_allocator_t* allocator = lip_arena_allocator_create( 79 | &fixture->base_allocator.vtable, 80 | 128, 81 | false 82 | ); 83 | 84 | lip_array(void*) pointers = lip_array_create( 85 | lip_default_allocator, 86 | void*, 87 | 0 88 | ); 89 | int num_rounds = munit_rand_int_range(0, 50); 90 | for(int i = 0; i < num_rounds; ++i) 91 | { 92 | lip_arena_allocator_reset(allocator); 93 | lip_array_clear(pointers); 94 | 95 | int num_small_allocs = munit_rand_int_range(0, 30); 96 | int num_large_allocs = munit_rand_int_range(0, 20); 97 | munit_logf( 98 | MUNIT_LOG_INFO, 99 | "Allocating %d small blocks and %d large blocks", 100 | num_small_allocs, num_large_allocs 101 | ); 102 | 103 | for(int j = 0; j < num_small_allocs; ++j) 104 | { 105 | size_t small_alloc_size = munit_rand_int_range(1, 64); 106 | void* mem = lip_malloc(allocator, small_alloc_size); 107 | munit_assert_ptr_not_null(mem); 108 | 109 | lip_array_foreach(void*, ptr, pointers) 110 | { 111 | munit_assert_ptr_not_equal(*ptr, mem); 112 | } 113 | lip_array_push(pointers, mem); 114 | 115 | memset(mem, i, small_alloc_size); 116 | } 117 | 118 | for(int j = 0; j < num_large_allocs; ++j) 119 | { 120 | size_t large_alloc_size = munit_rand_int_range(128 + 1, 128 * 2); 121 | void* mem = lip_malloc(allocator, large_alloc_size); 122 | munit_assert_ptr_not_null(mem); 123 | 124 | lip_array_foreach(void*, ptr, pointers) 125 | { 126 | munit_assert_ptr_not_equal(*ptr, mem); 127 | } 128 | lip_array_push(pointers, mem); 129 | 130 | memset(mem, i, large_alloc_size); 131 | } 132 | } 133 | 134 | lip_array_destroy(pointers); 135 | lip_arena_allocator_destroy(allocator); 136 | 137 | return MUNIT_OK; 138 | } 139 | 140 | static MunitResult 141 | reallocate(const MunitParameter params[], void* fixture_) 142 | { 143 | (void)params; 144 | fixture_t* fixture = fixture_; 145 | lip_allocator_t* allocator = lip_arena_allocator_create( 146 | &fixture->base_allocator.vtable, 147 | 128, 148 | true 149 | ); 150 | lip_array(int) array = lip_array_create(allocator, int, 2); 151 | for(int i = 0; i < 10000; ++i) 152 | { 153 | munit_assert_size(i, ==, lip_array_len(array)); 154 | lip_array_push(array, i); 155 | } 156 | 157 | munit_assert_size(10000, ==, lip_array_len(array)); 158 | 159 | for(int i = 0; i < 10000; ++i) 160 | { 161 | munit_assert_int(array[i], ==, i); 162 | } 163 | 164 | lip_arena_allocator_destroy(allocator); 165 | 166 | return MUNIT_OK; 167 | } 168 | 169 | static MunitResult 170 | min_chunk_size(const MunitParameter params[], void* fixture_) 171 | { 172 | (void)params; 173 | (void)fixture_; 174 | 175 | fixture_t* fixture = fixture_; 176 | lip_allocator_t* allocator = lip_arena_allocator_create( 177 | &fixture->base_allocator.vtable, 178 | 4, 179 | false 180 | ); 181 | 182 | munit_assert_ptr_not_null(lip_malloc(allocator, 2)); 183 | munit_assert_ptr_not_null(lip_malloc(allocator, 16)); 184 | 185 | lip_arena_allocator_destroy(allocator); 186 | 187 | return MUNIT_OK; 188 | } 189 | 190 | static MunitTest tests[] = { 191 | { 192 | .name = "/no_leak", 193 | .test = no_leak, 194 | .setup = setup, 195 | .tear_down = teardown 196 | }, 197 | { 198 | .name = "/reallocate", 199 | .test = reallocate, 200 | .setup = setup, 201 | .tear_down = teardown 202 | }, 203 | { 204 | .name = "/min_chunk_size", 205 | .test = min_chunk_size, 206 | .setup = setup, 207 | .tear_down = teardown 208 | }, 209 | { .test = NULL } 210 | }; 211 | 212 | MunitSuite arena_allocator = { 213 | .prefix = "/arena_allocator", 214 | .tests = tests 215 | }; 216 | -------------------------------------------------------------------------------- /src/core/vm_ops: -------------------------------------------------------------------------------- 1 | BEGIN_OP(NOP) 2 | END_OP(NOP) 3 | 4 | BEGIN_OP(POP) 5 | ++sp; 6 | END_OP(POP) 7 | 8 | BEGIN_OP(LDK) 9 | lip_value_t constant = fn.constants[operand]; 10 | switch(constant.type) 11 | { 12 | case LIP_VAL_NUMBER: 13 | *(--sp) = constant; 14 | break; 15 | case LIP_VAL_STRING: 16 | case LIP_VAL_SYMBOL: 17 | { 18 | lip_string_t* string = lip_function_resource( 19 | fp->closure->function.lip, 20 | constant.data.index 21 | ); 22 | lip_value_t copy = lip_make_string_copy( 23 | vm, lip_string_ref_from_string(string) 24 | ); 25 | copy.type = constant.type; 26 | *(--sp) = copy; 27 | } 28 | break; 29 | default: 30 | THROW("Illegal instruction"); 31 | } 32 | END_OP(LDK) 33 | 34 | BEGIN_OP(LARG) 35 | *(--sp) = bp[operand]; 36 | END_OP(LARG) 37 | 38 | BEGIN_OP(LDLV) 39 | *(--sp) = ep[operand]; 40 | END_OP(LDLV) 41 | 42 | BEGIN_OP(LDCV) 43 | *(--sp) = fp->closure->environment[operand]; 44 | END_OP(LDCV) 45 | 46 | BEGIN_OP(IMP) 47 | lip_value_t result; 48 | lip_string_t* symbol_name = lip_function_resource( 49 | fp->closure->function.lip, fn.imports[operand].name 50 | ); 51 | 52 | SAVE_CONTEXT(); 53 | bool resolved = vm->rt->resolve_import(vm->rt, symbol_name, &result); 54 | if(LIP_UNLIKELY(!resolved)) { 55 | THROW_FMT( 56 | "Undefined symbol: %.*s", (int)symbol_name->length, symbol_name->ptr 57 | ); 58 | } 59 | LOAD_CONTEXT(); 60 | 61 | *(--sp) = result; 62 | END_OP(IMP) 63 | 64 | BEGIN_OP(IMPS) 65 | lip_value_t val = fn.imports[operand].value; 66 | *(--sp) = val; 67 | END_OP(IMPS) 68 | 69 | BEGIN_OP(LDI) 70 | lip_value_t* value = --sp; 71 | value->type = LIP_VAL_NUMBER; 72 | value->data.number = operand; 73 | END_OP(LDI) 74 | 75 | BEGIN_OP(LDB) 76 | lip_value_t* value = --sp; 77 | value->type = LIP_VAL_BOOLEAN; 78 | value->data.boolean = operand; 79 | END_OP(LDB) 80 | 81 | BEGIN_OP(PLHR) 82 | lip_value_t* value = ep + operand; 83 | value->type = LIP_VAL_PLACEHOLDER; 84 | value->data.index = operand; 85 | END_OP(PLHR) 86 | 87 | BEGIN_OP(NIL) 88 | (--sp)->type = LIP_VAL_NIL; 89 | END_OP(NIL) 90 | 91 | BEGIN_OP(JMP) 92 | pc = fn.instructions + operand; 93 | END_OP(JMP) 94 | 95 | BEGIN_OP(JOF) 96 | lip_value_t* top = (sp++); 97 | bool is_false = 98 | (top->type == LIP_VAL_NIL) 99 | || (top->type == LIP_VAL_BOOLEAN && !top->data.boolean); 100 | lip_instruction_t* false_target = fn.instructions + operand; 101 | pc = is_false ? false_target : pc; 102 | END_OP(JOF) 103 | 104 | BEGIN_OP(CALL) 105 | lip_value_t* next_fn = sp++; 106 | SAVE_CONTEXT(); 107 | ++vm->fp; 108 | vm->fp->ep = ep; 109 | lip_exec_status_t status = lip_vm_do_call(vm, next_fn, operand); 110 | if(status != LIP_EXEC_OK) { return status; } 111 | LOAD_CONTEXT(); 112 | END_OP(CALL) 113 | 114 | BEGIN_OP(TAIL) 115 | lip_value_t* next_fn = sp++; 116 | lip_value_t* next_sp = bp + fp->num_args - operand; 117 | memmove(next_sp, sp, sizeof(lip_value_t) * operand); 118 | sp = next_sp; 119 | SAVE_CONTEXT(); 120 | vm->fp->ep = (vm->fp - 1)->ep; 121 | lip_exec_status_t status = lip_vm_do_call(vm, next_fn, operand); 122 | if(status != LIP_EXEC_OK) { return status; } 123 | if(lip_stack_frame_is_native(vm->fp)) { return LIP_EXEC_OK; } 124 | LOAD_CONTEXT(); 125 | END_OP(TAIL) 126 | 127 | BEGIN_OP(RET) 128 | lip_value_t* next_sp = bp + fp->num_args - 1; 129 | *next_sp = *sp; 130 | sp = next_sp; 131 | SAVE_CONTEXT(); 132 | --vm->fp; 133 | if(lip_stack_frame_is_native(vm->fp)) { return LIP_EXEC_OK; } 134 | LOAD_CONTEXT(); 135 | END_OP(RET) 136 | 137 | BEGIN_OP(CLS) 138 | unsigned int function_index = operand & 0xFFF; 139 | unsigned int num_captures = (operand >> 12) & 0xFFF; 140 | size_t closure_size = 141 | sizeof(lip_closure_t) + sizeof(lip_value_t) * num_captures; 142 | lip_closure_t* closure = vm->rt->malloc( 143 | vm->rt, LIP_VAL_FUNCTION, closure_size 144 | ); 145 | *closure = (lip_closure_t){ 146 | .is_native = false, 147 | .function = { 148 | .lip = lip_function_resource( 149 | fp->closure->function.lip, fn.function_offsets[function_index] 150 | ) 151 | }, 152 | .env_len = num_captures 153 | }; 154 | for(unsigned int i = 0; i < num_captures; ++i) 155 | { 156 | lip_opcode_t opcode; 157 | int32_t var_index; 158 | lip_disasm(pc[i], &opcode, &var_index); 159 | lip_value_t* base; 160 | switch(opcode) 161 | { 162 | case LIP_OP_LARG: 163 | base = bp; 164 | break; 165 | case LIP_OP_LDLV: 166 | base = ep; 167 | break; 168 | case LIP_OP_LDCV: 169 | base = fp->closure->environment; 170 | break; 171 | default: 172 | THROW("Illegal instruction"); 173 | } 174 | closure->environment[i] = base[var_index]; 175 | } 176 | pc += num_captures; 177 | lip_value_t value = { 178 | .type = LIP_VAL_FUNCTION, 179 | .data = { .reference = closure } 180 | }; 181 | *(--sp) = value; 182 | END_OP(CLS) 183 | 184 | BEGIN_OP(RCLS) 185 | lip_value_t* target = ep + operand; 186 | if(target->type == LIP_VAL_FUNCTION) 187 | { 188 | lip_closure_t* closure = target->data.reference; 189 | for(unsigned int i = 0; i < closure->env_len; ++i) 190 | { 191 | lip_value_t* captured_val = &closure->environment[i]; 192 | if(captured_val->type == LIP_VAL_PLACEHOLDER) 193 | { 194 | *captured_val = *(ep + captured_val->data.index); 195 | } 196 | } 197 | } 198 | else if(target->type == LIP_VAL_PLACEHOLDER) 199 | { 200 | *target = *(ep + target->data.index); 201 | } 202 | END_OP(RCLS) 203 | 204 | BEGIN_OP(SET) 205 | ep[operand] = *(sp++); 206 | END_OP(SET) 207 | 208 | LIP_PRIM_OP(DO_PRIM_OP) 209 | -------------------------------------------------------------------------------- /src/tests/test_helpers.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_TEST_HELPERS_H 2 | #define LIP_TEST_HELPERS_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "munit.h" 10 | 11 | #define lip_assert_enum(ENUM_TYPE, EXPECTED, OP, ACTUAL) \ 12 | do { \ 13 | unsigned int expected = (EXPECTED); \ 14 | unsigned int actual = (ACTUAL); \ 15 | if(!(expected OP actual)) { \ 16 | munit_errorf( \ 17 | "assert failed: " #EXPECTED " " #OP " " #ACTUAL " (%s " #OP " %s)", \ 18 | ENUM_TYPE##_to_str(expected), ENUM_TYPE##_to_str(actual) \ 19 | ); \ 20 | } \ 21 | } while (0) 22 | 23 | #define lip_assert_string_ref_equal(EXPECTED, ACTUAL) \ 24 | do { \ 25 | lip_string_ref_t expected = (EXPECTED); \ 26 | lip_string_ref_t actual = (ACTUAL); \ 27 | if(!lip_string_ref_equal(expected, actual)) { \ 28 | munit_errorf( \ 29 | "assert failed: " #EXPECTED " == " #ACTUAL " (\"%.*s\" == \"%.*s\")", \ 30 | (int)expected.length, expected.ptr, \ 31 | (int)actual.length, actual.ptr \ 32 | ); \ 33 | } \ 34 | } while (0) 35 | 36 | #define lip_assert_string_equal(EXPECTED, ACTUAL) \ 37 | do { \ 38 | lip_string_ref_t expected = (EXPECTED); \ 39 | lip_string_t* actual = (ACTUAL); \ 40 | lip_string_ref_t actual_ref = { \ 41 | .length = actual->length, .ptr = actual->ptr \ 42 | }; \ 43 | if(!lip_string_ref_equal(expected, actual_ref)) { \ 44 | munit_errorf( \ 45 | "assert failed: " #EXPECTED " == " #ACTUAL " (\"%.*s\" == \"%.*s\")", \ 46 | (int)expected.length, expected.ptr, \ 47 | (int)actual_ref.length, actual_ref.ptr \ 48 | ); \ 49 | } \ 50 | } while (0) 51 | 52 | #define lip_assert_loc_equal(EXPECTED, ACTUAL) \ 53 | do { \ 54 | lip_loc_t loc_expected = (EXPECTED); \ 55 | lip_loc_t loc_actual = (ACTUAL); \ 56 | munit_assert_uint(loc_expected.column, ==, loc_actual.column); \ 57 | munit_assert_uint(loc_expected.line, ==, loc_actual.line); \ 58 | } while (0) 59 | 60 | #define lip_assert_loc_range_equal(EXPECTED, ACTUAL) \ 61 | do { \ 62 | lip_loc_range_t loc_range_expected = (EXPECTED); \ 63 | lip_loc_range_t loc_range_actual = (ACTUAL); \ 64 | lip_assert_loc_equal(loc_range_expected.start, loc_range_actual.start); \ 65 | lip_assert_loc_equal(loc_range_expected.end, loc_range_actual.end); \ 66 | } while(0) 67 | 68 | #define lip_assert_error_equal(EXPECTED, ACTUAL) \ 69 | do { \ 70 | lip_error_t error_expected = (EXPECTED); \ 71 | lip_error_t error_actual = (ACTUAL); \ 72 | munit_assert_uint(error_expected.code, ==, error_actual.code); \ 73 | lip_assert_loc_range_equal(error_expected.location, error_actual.location); \ 74 | } while(0) 75 | 76 | #define lip_assert_typed_alignment(POINTER, TYPE) \ 77 | lip_assert_alignment(POINTER, LIP_ALIGN_OF(TYPE)) 78 | 79 | #define lip_assert_alignment(POINTER, ALIGNMENT) \ 80 | do { \ 81 | void* ptr = (POINTER); \ 82 | uint32_t alignment = (ALIGNMENT); \ 83 | uint32_t rem = (uintptr_t)ptr % alignment; \ 84 | if(!(rem == 0)) { \ 85 | munit_errorf( \ 86 | "assert failed: " #POINTER " (%p) is not aligned to " #ALIGNMENT " (%d bytes), remainder: %d", \ 87 | ptr, alignment, rem \ 88 | ); \ 89 | } \ 90 | } while(0) 91 | 92 | #define lip_assert_mem_equal(a, a_len, b, b_len) \ 93 | do { \ 94 | const char* a_ = a; \ 95 | size_t a_len_ = a_len; \ 96 | const char* b_ = b; \ 97 | size_t b_len_ = b_len; \ 98 | munit_assert_size(a_len_, ==, b_len_); \ 99 | munit_assert_memory_equal(a_len_, a_, b_); \ 100 | } while (0) 101 | 102 | #define LIP_CONSTRUCTOR_AND_DESTRUCTOR(T) \ 103 | LIP_MAYBE_UNUSED static inline T##_t* \ 104 | T##_create(lip_allocator_t* allocator) { \ 105 | T##_t* instance = lip_new(allocator, T##_t); \ 106 | T##_init(instance, allocator); \ 107 | return instance; \ 108 | } \ 109 | LIP_MAYBE_UNUSED static inline void \ 110 | T##_destroy(T##_t* instance) { \ 111 | T##_cleanup(instance); \ 112 | lip_free(instance->allocator, instance); \ 113 | } 114 | 115 | #define lip_assert_num(expected, actual) \ 116 | do { \ 117 | lip_assert_enum(lip_value_type_t, LIP_VAL_NUMBER, ==, actual.type); \ 118 | munit_assert_double_equal(expected, actual.data.number, 4); \ 119 | } while(0) 120 | 121 | #define lip_assert_str(expected, actual) \ 122 | do { \ 123 | lip_assert_enum(lip_value_type_t, LIP_VAL_STRING, ==, actual.type); \ 124 | lip_string_t* returned_str = actual.data.reference; \ 125 | munit_assert_string_equal(expected, returned_str->ptr); \ 126 | } while(0) 127 | 128 | #define lip_assert_symbol(expected, actual) \ 129 | do { \ 130 | lip_assert_enum(lip_value_type_t, LIP_VAL_SYMBOL, ==, actual.type); \ 131 | lip_string_t* returned_str = actual.data.reference; \ 132 | munit_assert_string_equal(expected, returned_str->ptr); \ 133 | } while(0) 134 | 135 | #define lip_assert_boolean(expected, actual) \ 136 | do { \ 137 | lip_assert_enum(lip_value_type_t, LIP_VAL_BOOLEAN, ==, actual.type); \ 138 | munit_assert_int(expected, ==, actual.data.boolean); \ 139 | } while(0) 140 | 141 | #define lip_assert_nil(placeholder, actual) \ 142 | lip_assert_enum(lip_value_type_t, LIP_VAL_NIL, ==, actual.type); \ 143 | 144 | static size_t 145 | lip_null_write(const void* buff, size_t size, lip_out_t* vtable) 146 | { 147 | (void)buff; 148 | (void)vtable; 149 | 150 | return size; 151 | } 152 | 153 | static lip_out_t null_ = { .write = lip_null_write }; 154 | 155 | static lip_out_t* const lip_null = &null_; 156 | 157 | LIP_CONSTRUCTOR_AND_DESTRUCTOR(lip_lexer) 158 | LIP_CONSTRUCTOR_AND_DESTRUCTOR(lip_parser) 159 | LIP_CONSTRUCTOR_AND_DESTRUCTOR(lip_asm) 160 | 161 | #endif 162 | -------------------------------------------------------------------------------- /src/tests/bind.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "munit.h" 5 | #include "test_helpers.h" 6 | 7 | typedef struct lip_fixture_s lip_fixture_t; 8 | 9 | struct lip_fixture_s 10 | { 11 | lip_runtime_t* runtime; 12 | lip_context_t* context; 13 | lip_vm_t* vm; 14 | }; 15 | 16 | static void* 17 | setup(const MunitParameter params[], void* data) 18 | { 19 | (void)params; 20 | (void)data; 21 | lip_runtime_config_t cfg; 22 | lip_reset_runtime_config(&cfg); 23 | lip_fixture_t* fixture = lip_new(lip_default_allocator, lip_fixture_t); 24 | fixture->runtime = lip_create_runtime(&cfg); 25 | fixture->context = lip_create_context(fixture->runtime, NULL); 26 | fixture->vm = lip_create_vm(fixture->context, NULL); 27 | return fixture; 28 | } 29 | 30 | static void 31 | teardown(void* fixture_) 32 | { 33 | lip_fixture_t* fixture = fixture_; 34 | lip_destroy_vm(fixture->context, fixture->vm); 35 | lip_destroy_context(fixture->context); 36 | lip_destroy_runtime(fixture->runtime); 37 | lip_free(lip_default_allocator, fixture); 38 | } 39 | 40 | static 41 | lip_function(lip_pow) 42 | { 43 | lip_bind_args((number, x), (number, y)); 44 | lip_return((lip_make_number(vm, pow(x, y)))); 45 | } 46 | 47 | static 48 | lip_function(lip_atan) 49 | { 50 | lip_bind_args((number, x), (number, y, (optional, 0))); 51 | lip_return(lip_make_number(vm, argc == 1 ? atan(x) : atan2(x, y))); 52 | } 53 | 54 | static MunitResult 55 | direct(const MunitParameter params[], void* fixture_) 56 | { 57 | (void)params; 58 | lip_fixture_t* fixture = fixture_; 59 | lip_vm_t* vm = fixture->vm; 60 | 61 | lip_reset_vm(vm); 62 | lip_value_t fn = lip_make_function(vm, lip_pow, 0, NULL); 63 | lip_value_t result; 64 | lip_exec_status_t status = 65 | lip_call(vm, &result, fn, 2, lip_make_number(vm, 3.5), lip_make_number(vm, 3.6)); 66 | 67 | lip_assert_enum(lip_exec_status_t, LIP_EXEC_OK, ==, status); 68 | lip_assert_enum(lip_value_type_t, LIP_VAL_NUMBER, ==, result.type); 69 | munit_assert_double_equal(pow(3.5, 3.6), result.data.number, 3); 70 | 71 | lip_reset_vm(vm); 72 | lip_value_t latan = lip_make_function(vm, lip_atan, 0, NULL); 73 | status = 74 | lip_call(vm, &result, latan, 2, lip_make_number(vm, 3.5), lip_make_number(vm, 3.6)); 75 | lip_assert_enum(lip_exec_status_t, LIP_EXEC_OK, ==, status); 76 | lip_assert_enum(lip_value_type_t, LIP_VAL_NUMBER, ==, result.type); 77 | munit_assert_double_equal(atan2(3.5, 3.6), result.data.number, 3); 78 | 79 | lip_reset_vm(vm); 80 | latan = lip_make_function(vm, lip_atan, 0, NULL); 81 | status = 82 | lip_call(vm, &result, latan, 1, lip_make_number(vm, 3.5)); 83 | lip_assert_enum(lip_exec_status_t, LIP_EXEC_OK, ==, status); 84 | lip_assert_enum(lip_value_type_t, LIP_VAL_NUMBER, ==, result.type); 85 | munit_assert_double_equal(atan(3.5), result.data.number, 3); 86 | 87 | lip_reset_vm(vm); 88 | latan = lip_make_function(vm, lip_atan, 0, NULL); 89 | status = lip_call(vm, &result, latan, 0); 90 | lip_assert_enum(lip_exec_status_t, LIP_EXEC_ERROR, ==, status); 91 | lip_assert_str("Bad number of arguments (at least 1 expected, got 0)", result); 92 | 93 | lip_reset_vm(vm); 94 | latan = lip_make_function(vm, lip_atan, 0, NULL); 95 | status = lip_call(vm, &result, latan, 3, lip_make_number(vm, 3.5), lip_make_number(vm, 3.5), lip_make_number(vm, 3.5)); 96 | lip_assert_enum(lip_exec_status_t, LIP_EXEC_ERROR, ==, status); 97 | lip_assert_str("Bad number of arguments (at most 2 expected, got 3)", result); 98 | 99 | return MUNIT_OK; 100 | } 101 | 102 | static lip_bind_wrap_function(pow, number, number, number) 103 | 104 | static MunitResult 105 | wrapper(const MunitParameter params[], void* fixture_) 106 | { 107 | (void)params; 108 | lip_fixture_t* fixture = fixture_; 109 | lip_context_t* ctx = fixture->context; 110 | lip_vm_t* vm = fixture->vm; 111 | 112 | lip_value_t fn = lip_make_function(vm, lip_bind_wrapper(pow), 0, NULL); 113 | lip_value_t result; 114 | lip_exec_status_t status = 115 | lip_call(vm, &result, fn, 2, lip_make_number(vm, 3.5), lip_make_number(vm, 3.6)); 116 | 117 | lip_assert_enum(lip_exec_status_t, LIP_EXEC_OK, ==, status); 118 | lip_assert_enum(lip_value_type_t, LIP_VAL_NUMBER, ==, result.type); 119 | munit_assert_double_equal(pow(3.5, 3.6), result.data.number, 3); 120 | 121 | lip_reset_vm(vm); 122 | fn = lip_make_function(vm, lip_bind_wrapper(pow), 0, NULL); 123 | status = lip_call(vm, &result, fn, 1, lip_make_number(vm, 3.5)); 124 | lip_assert_enum(lip_exec_status_t, LIP_EXEC_ERROR, ==, status); 125 | lip_assert_str("Bad number of arguments (exactly 2 expected, got 1)", result); 126 | 127 | lip_reset_vm(vm); 128 | fn = lip_make_function(vm, lip_bind_wrapper(pow), 0, NULL); 129 | status = lip_call(vm, &result, fn, 2, lip_make_number(vm, 3.5), lip_make_string(vm, "wat")); 130 | lip_assert_enum(lip_exec_status_t, LIP_EXEC_ERROR, ==, status); 131 | lip_assert_str("Bad argument #2 (LIP_VAL_NUMBER expected, got LIP_VAL_STRING)", result); 132 | 133 | const lip_context_error_t* error = lip_traceback(ctx, vm, lip_make_nil(vm)); 134 | unsigned int bottom = error->num_records - 1; 135 | lip_assert_string_ref_equal(lip_string_ref(__FILE__), error->records[bottom].filename); 136 | lip_assert_string_ref_equal(lip_string_ref(__func__), error->records[bottom].message); 137 | lip_assert_string_ref_equal(lip_string_ref(__FILE__), error->records[0].filename); 138 | #define lip_stringify(x) lip_stringify1(x) 139 | #define lip_stringify1(x) #x 140 | lip_assert_string_ref_equal( 141 | lip_string_ref(lip_stringify(lip_bind_wrapper(pow))), 142 | error->records[0].message 143 | ); 144 | 145 | return MUNIT_OK; 146 | } 147 | 148 | static MunitTest tests[] = { 149 | { 150 | .name = "/direct", 151 | .test = direct, 152 | .setup = setup, 153 | .tear_down = teardown 154 | }, 155 | { 156 | .name = "/wrapper", 157 | .test = wrapper, 158 | .setup = setup, 159 | .tear_down = teardown 160 | }, 161 | { .test = NULL } 162 | }; 163 | 164 | MunitSuite bind = { 165 | .prefix = "/bind", 166 | .tests = tests 167 | }; 168 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lip - An embeddable LISP 2 | 3 | [![License](https://img.shields.io/badge/license-BSD-blue.svg)](LICENSE) 4 | [![Travis CI Status](https://travis-ci.org/bullno1/lip.svg?branch=master)](https://travis-ci.org/bullno1/lip) 5 | [![AppVeyor CI Status](https://ci.appveyor.com/api/projects/status/n02fiiw6xwfhwula/branch/master?svg=true)](https://ci.appveyor.com/project/bullno1/lip/branch/master) 6 | [![Coverage Status](https://coveralls.io/repos/github/bullno1/lip/badge.svg?branch=master)](https://coveralls.io/github/bullno1/lip?branch=master) 7 | 8 | `lip` is my own implementation of a LISP language, designed to be embedded in a host program (similar to Lua). 9 | It is written using C99. 10 | 11 | Currently, `lip` is still under heavy development. 12 | 13 | ## How to build 14 | 15 | ### Using the provided scripts 16 | 17 | An Unix environment, Python and a modern C99 compiler is required. 18 | From the project folder, run: `./numake bin/lip` and an interpreter will be created at `bin/lip`. 19 | 20 | `numake` is my own custom build tool: https://github.com/bullno1/numake 21 | 22 | `./watch bin/lip` will watch all dependencies of `bin/lip` and rebuild if needed. 23 | 24 | ### Using Visual Studio 2015 25 | 26 | Visual Studio 2015 is the only Visual Studio version that has proper C99 supports, anything earlier probably will not work. 27 | Run `vs2015.bat` and a solution will be created in the `vs2015` folder. 28 | 29 | ### Using GENie 30 | 31 | [GENie](https://github.com/bkaradzic/genie) is a project generator for XCode, Visual Studio and Makefile. 32 | Run it on `genie.lua` to create a project for your tool. 33 | 34 | ## What works currently 35 | 36 | - The interpreter can execute simple scripts (see [benchmark](benchmarkj)). 37 | To run a script: `bin/lip benchmark/fib.lip`. 38 | Run `bin/lip --help` for some options. 39 | - The current speed is slightly slower than that of interpreted Lua. 40 | - A simple interface to bind C functions to the runtime: [include/lip/bind.h](include/lip/bind.h) (see [src/lip/builtins.c](src/lip/builtins.c), for example). 41 | 42 | ## What is planned? 43 | 44 | - A simple error handling system similar to Lua's pcall/error. 45 | This means no polymorphic exception nonsense. 46 | Exception should only be used for panic cases. 47 | - A module/package system similar to that of Common Lisp or Clojure. 48 | - A minimal standard library. 49 | 50 | ## Why another language? 51 | 52 | Lua is one of the best embeddable scripting language available, however there are a few parts I am not happy with: 53 | 54 | - *Treatment of globals*: They are silently treated as `nil` and cause problems much later when used. 55 | It is possible to setup metatables such that they throw errors but the erroneous code still have to be executed. 56 | In `lip`, undefined globals would be an error at load-time or compile-time. 57 | - *Lack of typing*: Even in a dynamic environment, type checking techniques can still be used. 58 | - *Macros*: I am not a big fan of macros but macros are good for building DSLs. 59 | Having used Lua for data declaration, I like its syntactic sugar for unary function with strings and table (i.e: the parentheses can be omitted: `func "str arg"` or `func {key = value}`). 60 | However, its anonymous function syntax is too verbose: `function(arg1, arg2) boyd end`. 61 | Good macro support will make DSLs much nicer. 62 | - *Garbage collector*: It has become a problem in multiple projects. 63 | Sometimes, it's because GC pauses for too long. 64 | Sometimes, it's because developer forgot to mark objects (and it's not easy with Lua). 65 | This is my chance to experiment with two ideas: 66 | 1. A scripting language and runtime for game development that does not use garbage collection. 67 | Details will be revealed later. 68 | 2. Pluggable garbage collection: There can be several type of GC: incremental, stop-the-world or just reference counting. 69 | An developer can choose which fits a project the most. 70 | Of course, programs written will have to take the plugged GC into account (e.g: you cannot create cycle in a reference counting collector). 71 | 72 | There are also areas I want to experiment with: 73 | 74 | - *Hackable runtime for scripting languages*: Many scripting languages such as Lua does not expose its underlying infrastructure enough for language hackers. 75 | To target the Lua VM, one would have to read [a document written by a third-party](http://luaforge.net/docman/83/98/ANoFrillsIntroToLua51VMInstructions.pdf). 76 | Making languages which compile to Lua but have line information and source name map nicely to the source language for ease of debugging is a challenging task 77 | There is no simple way to access Lua's compiler or AST. 78 | lip is designed to be modular and hackable from the start. 79 | There is API (albeit undocumented for now) for [codegen](src/lip/asm.h), [ast](src/lip/ast.h), [parser](src/lip/parser.h) and [compiler](src/lip/compiler.h). 80 | In fact, every part of it can be used independently (e.g: one can use lip's sexp parser to build their own language in their own runtime or parse their own language and generate corresponding s-expressions to feed into lip's compiler). 81 | - *Type system*: Even in a dynamic environment, there are several type inference and checking techniques that can improve performances (by removing type checks) and improve correctness (by catching error at compile-time instead of runtime). 82 | lip is an attempt to explore type system in an embedded and dynamic environment. 83 | - *Hot code reloading*: Among one of the many things which [GOAL](https://en.wikipedia.org/wiki/Game_Oriented_Assembly_Lisp) is famous for is its ability to reload code on-the-fly. 84 | This feature can already be simulated in many current scripting languages. 85 | However, I want to experiment with a runtime where this is designed as as a first-class feature. 86 | [Erlang](http://www.erlang.org/) is a language where this is a central design. 87 | 88 | Building `lip` is also an exercise in language design and implementation as well as type system. 89 | Choosing C99 instead of other languages means I could immediately use it in many of my own projects as find about the language's strength and weaknesses. 90 | -------------------------------------------------------------------------------- /src/core/vm_dispatch.c: -------------------------------------------------------------------------------- 1 | #include "vm_dispatch.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "utils.h" 7 | 8 | #if !defined(LIP_NO_COMPUTED_GOTO) && (defined(__GNUC__) || defined(__GNUG__) || defined(__clang__)) 9 | # define GENERATE_LABEL(ENUM) &&do_##ENUM, 10 | # define BEGIN_LOOP() \ 11 | void* dispatch_table[] = { LIP_OP(GENERATE_LABEL) &&do_LIP_OP_ILLEGAL }; \ 12 | lip_opcode_t opcode; \ 13 | lip_operand_t operand; \ 14 | DISPATCH() 15 | # define END_LOOP() do_LIP_OP_ILLEGAL: THROW("Illegal instruction"); 16 | # define BEGIN_OP(OP) do_LIP_OP_##OP: { 17 | # define END_OP(OP) } DISPATCH(); 18 | # define DISPATCH() \ 19 | CALL_HOOK(); \ 20 | lip_disasm(*(pc++), &opcode, &operand); \ 21 | goto *dispatch_table[LIP_MIN((unsigned int)opcode, LIP_STATIC_ARRAY_LEN(dispatch_table) - 1)]; 22 | #else 23 | # define BEGIN_LOOP() \ 24 | lip_opcode_t opcode; \ 25 | lip_operand_t operand; \ 26 | DISPATCH() 27 | # define END_LOOP() do_LIP_OP_ILLEGAL: THROW("Illegal instruction"); 28 | # define BEGIN_OP(OP) do_LIP_OP_##OP: { 29 | # define END_OP(OP) } DISPATCH() 30 | # define DISPATCH() \ 31 | CALL_HOOK(); \ 32 | lip_disasm(*(pc++), &opcode, &operand); \ 33 | switch(opcode) { \ 34 | LIP_OP(GENERATE_CASE) \ 35 | default: goto do_LIP_OP_ILLEGAL; \ 36 | } 37 | # define GENERATE_CASE(ENUM) case ENUM: goto do_##ENUM; 38 | #endif 39 | 40 | #define LOAD_CONTEXT() \ 41 | fp = vm->fp; \ 42 | lip_function_layout(fp->closure->function.lip, &fn); \ 43 | pc = vm->fp->pc; \ 44 | ep = vm->fp->ep; \ 45 | bp = vm->fp->bp; \ 46 | sp = vm->sp; 47 | 48 | #define SAVE_CONTEXT() \ 49 | vm->sp = sp; \ 50 | vm->fp->pc = pc; 51 | 52 | #define PREAMBLE() \ 53 | lip_function_layout_t fn; \ 54 | lip_stack_frame_t* fp; \ 55 | lip_instruction_t* pc; \ 56 | lip_value_t* bp; \ 57 | lip_value_t* ep; \ 58 | lip_value_t* sp; \ 59 | LOAD_CONTEXT() \ 60 | BEGIN_LOOP() 61 | 62 | #define POSTAMBLE() \ 63 | END_LOOP() 64 | 65 | #define THROW(MSG) \ 66 | do { \ 67 | *(--sp) = lip_make_string_copy(vm, lip_string_ref(MSG)); \ 68 | SAVE_CONTEXT(); \ 69 | return LIP_EXEC_ERROR; \ 70 | } while(0) 71 | 72 | #define THROW_FMT(MSG, ...) \ 73 | do { \ 74 | *(--sp) = lip_make_string(vm, MSG, __VA_ARGS__); \ 75 | SAVE_CONTEXT(); \ 76 | return LIP_EXEC_ERROR; \ 77 | } while(0) 78 | 79 | #define DO_PRIM_OP(op, name) \ 80 | BEGIN_OP(name) \ 81 | lip_exec_status_t status = lip_ ## name (vm, sp + operand - 1, operand, sp); \ 82 | sp += operand - 1; \ 83 | if(status != LIP_EXEC_OK) { SAVE_CONTEXT(); return status; } \ 84 | END_OP(name) 85 | 86 | #if defined(__GNUC__) || defined(__GNUG__) || defined(__clang__) 87 | # pragma GCC diagnostic push 88 | # pragma GCC diagnostic ignored "-Wpedantic" 89 | #endif 90 | 91 | static lip_exec_status_t 92 | lip_vm_loop_with_hook(lip_vm_t* vm) 93 | { 94 | #define CALL_HOOK() SAVE_CONTEXT(); vm->hook->step(vm->hook, vm); 95 | PREAMBLE() 96 | #include "vm_ops" 97 | POSTAMBLE() 98 | #undef CALL_HOOK 99 | } 100 | 101 | static lip_exec_status_t 102 | lip_vm_loop_without_hook(lip_vm_t* vm) 103 | { 104 | #define CALL_HOOK() 105 | PREAMBLE() 106 | #include "vm_ops" 107 | POSTAMBLE() 108 | #undef CALL_HOOK 109 | } 110 | 111 | #if defined(__GNUC__) || defined(__GNUG__) || defined(__clang__) 112 | # pragma GCC diagnostic pop 113 | #endif 114 | 115 | lip_exec_status_t 116 | lip_vm_loop(lip_vm_t* vm) 117 | { 118 | return vm->hook && vm->hook->step 119 | ? lip_vm_loop_with_hook(vm) 120 | : lip_vm_loop_without_hook(vm); 121 | } 122 | 123 | lip_exec_status_t 124 | lip_vm_do_call(lip_vm_t* vm, lip_value_t* fn, uint8_t num_args) 125 | { 126 | if(LIP_UNLIKELY(fn->type != LIP_VAL_FUNCTION)) 127 | { 128 | lip_value_t* next_sp = vm->sp + num_args - 1; 129 | *next_sp = lip_make_string_copy( 130 | vm, lip_string_ref("Trying to call a non-function") 131 | ); 132 | vm->sp = next_sp; 133 | return LIP_EXEC_ERROR; 134 | } 135 | 136 | vm->fp->num_args = num_args; 137 | vm->fp->bp = vm->sp; 138 | lip_closure_t* closure = (lip_closure_t*)fn->data.reference; 139 | vm->fp->closure = closure; 140 | 141 | bool is_native = closure->is_native; 142 | unsigned int num_locals = is_native ? 0 : closure->function.lip->num_locals; 143 | vm->fp->ep -= num_locals; 144 | 145 | if(is_native) 146 | { 147 | // Ensure that a value is always returned 148 | lip_value_t* next_sp = vm->sp + num_args - 1; 149 | lip_exec_status_t status = closure->function.native(vm, next_sp); 150 | vm->sp = next_sp; 151 | if(status == LIP_EXEC_OK) { --vm->fp; } 152 | 153 | return status; 154 | } 155 | else 156 | { 157 | lip_function_layout_t layout; 158 | lip_function_layout(closure->function.lip, &layout); 159 | vm->fp->pc = layout.instructions; 160 | 161 | bool is_vararg = closure->function.lip->is_vararg; 162 | const uint8_t arity = is_vararg 163 | ? closure->function.lip->num_args - 1 164 | : closure->function.lip->num_args; 165 | bool wrong_arity = is_vararg ? num_args < arity : num_args != arity; 166 | if(LIP_UNLIKELY(wrong_arity)) 167 | { 168 | lip_value_t* next_sp = vm->sp + num_args - 1; 169 | *next_sp = lip_make_string( 170 | vm, 171 | "Bad number of arguments (%s %u expected, got %u)", 172 | is_vararg ? "at least" : "exactly", arity, num_args 173 | ); 174 | vm->sp = next_sp; 175 | return LIP_EXEC_ERROR; 176 | } 177 | 178 | if(is_vararg) 179 | { 180 | size_t num_varargs = num_args - arity; 181 | lip_list_t* list = vm->rt->malloc(vm->rt, LIP_VAL_LIST, sizeof(lip_list_t)); 182 | list->root = list->elements = 183 | vm->rt->malloc(vm->rt, LIP_VAL_NATIVE, sizeof(lip_value_t) * num_varargs); 184 | list->length = num_varargs; 185 | memcpy(list->elements, vm->sp + arity, sizeof(lip_value_t) * num_varargs); 186 | 187 | // Ensure that there is enough space to place the vararg list 188 | if(num_varargs == 0) 189 | { 190 | memmove(vm->sp - 1, vm->sp, sizeof(*vm->sp) * num_args); 191 | --vm->sp; 192 | --vm->fp->bp; 193 | ++vm->fp->num_args; 194 | } 195 | 196 | vm->sp[arity] = (lip_value_t){ 197 | .type = LIP_VAL_LIST, 198 | .data = { .reference = list } 199 | }; 200 | } 201 | 202 | return LIP_EXEC_OK; 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/tests/module.c: -------------------------------------------------------------------------------- 1 | #include "munit.h" 2 | #include "runtime_helper.h" 3 | #include 4 | 5 | typedef struct lip_test_context_s 6 | { 7 | int count; 8 | } lip_test_context_t; 9 | 10 | static lip_test_context_t test_ctx = { 0 }; 11 | 12 | static lip_function(count_load) 13 | { 14 | ++test_ctx.count; 15 | lip_return(lip_make_nil(vm)); 16 | } 17 | 18 | static MunitResult 19 | basic(const MunitParameter params[], void* fixture_) 20 | { 21 | (void)params; 22 | 23 | lip_fixture_t* fixture = fixture_; 24 | lip_context_t* ctx2 = fixture->context; 25 | lip_load_builtins(ctx2); 26 | 27 | lip_module_context_t* module = lip_begin_module(ctx2, lip_string_ref("test")); 28 | lip_declare_function(module, lip_string_ref("count-load"), count_load); 29 | lip_end_module(ctx2, module); 30 | 31 | test_ctx = (lip_test_context_t){ 0 }; 32 | 33 | { 34 | lip_context_t* ctx = lip_create_context(fixture->runtime, NULL); 35 | lip_vm_t* vm = lip_create_vm(ctx, NULL); 36 | lip_assert_num_result("(test.mod/test-fun 1)", 2, true); 37 | lip_assert_num_result("(test.mod/test-fun 2)", 3, true); 38 | lip_destroy_vm(ctx, vm); 39 | lip_destroy_context(ctx); 40 | } 41 | 42 | { 43 | lip_context_t* ctx = lip_create_context(fixture->runtime, NULL); 44 | lip_vm_t* vm = lip_create_vm(ctx, NULL); 45 | lip_assert_num_result("(test.mod3/test-fun2 3)", 2, true); 46 | lip_destroy_vm(ctx, vm); 47 | lip_destroy_context(ctx); 48 | } 49 | 50 | { 51 | lip_context_t* ctx = lip_create_context(fixture->runtime, NULL); 52 | lip_vm_t* vm = lip_create_vm(ctx, NULL); 53 | lip_assert_num_result("(test.mod/test-fun 3)", 4, true); 54 | lip_assert_num_result("(test.mod3/test-fun2 4)", 3, true); 55 | lip_destroy_vm(ctx, vm); 56 | lip_destroy_context(ctx); 57 | } 58 | munit_assert_int(1, ==, test_ctx.count); 59 | 60 | return MUNIT_OK; 61 | } 62 | 63 | static MunitResult 64 | local_function(const MunitParameter params[], void* fixture_) 65 | { 66 | (void)params; 67 | 68 | lip_fixture_t* fixture = fixture_; 69 | lip_context_t* ctx2 = fixture->context; 70 | lip_load_builtins(ctx2); 71 | 72 | lip_module_context_t* module = lip_begin_module(ctx2, lip_string_ref("test")); 73 | lip_declare_function(module, lip_string_ref("count-load"), count_load); 74 | lip_end_module(ctx2, module); 75 | 76 | test_ctx = (lip_test_context_t){ 0 }; 77 | 78 | { 79 | lip_context_t* ctx = lip_create_context(fixture->runtime, NULL); 80 | lip_vm_t* vm = lip_create_vm(ctx, NULL); 81 | lip_assert_num_result("(mod4/a 3)", 6, true); 82 | lip_destroy_vm(ctx, vm); 83 | lip_destroy_context(ctx); 84 | } 85 | 86 | { 87 | lip_context_t* ctx = lip_create_context(fixture->runtime, NULL); 88 | lip_vm_t* vm = lip_create_vm(ctx, NULL); 89 | lip_assert_num_result("(mod4/a -2)", -4, true); 90 | lip_destroy_vm(ctx, vm); 91 | lip_destroy_context(ctx); 92 | 93 | munit_assert_int(1, ==, test_ctx.count); 94 | } 95 | 96 | { 97 | lip_context_t* ctx = lip_create_context(fixture->runtime, NULL); 98 | lip_vm_t* vm = lip_create_vm(ctx, NULL); 99 | lip_assert_error_msg("(mod5/a 3)", "Undefined symbol: mod5/a"); 100 | lip_destroy_vm(ctx, vm); 101 | lip_destroy_context(ctx); 102 | } 103 | 104 | return MUNIT_OK; 105 | } 106 | 107 | static MunitResult 108 | no_declare_in_body(const MunitParameter params[], void* fixture_) 109 | { 110 | (void)params; 111 | 112 | lip_fixture_t* fixture = fixture_; 113 | lip_context_t* ctx2 = fixture->context; 114 | lip_load_builtins(ctx2); 115 | 116 | { 117 | lip_context_t* ctx = lip_create_context(fixture->runtime, NULL); 118 | lip_vm_t* vm = lip_create_vm(ctx, NULL); 119 | lip_assert_error_msg("(mod7/b 3)", "Undefined symbol: mod7/b"); 120 | const lip_context_error_t* error = lip_get_error(ctx); 121 | while(error->parent) { error = error->parent; } 122 | lip_assert_string_ref_equal( 123 | lip_string_ref("Cannot use `declare` inside a `declare`-d function"), 124 | error->message 125 | ); 126 | lip_destroy_vm(ctx, vm); 127 | lip_destroy_context(ctx); 128 | } 129 | 130 | return MUNIT_OK; 131 | } 132 | 133 | static MunitResult 134 | private_function(const MunitParameter params[], void* fixture_) 135 | { 136 | (void)params; 137 | 138 | lip_fixture_t* fixture = fixture_; 139 | lip_context_t* ctx2 = fixture->context; 140 | lip_load_builtins(ctx2); 141 | 142 | { 143 | lip_context_t* ctx = lip_create_context(fixture->runtime, NULL); 144 | lip_vm_t* vm = lip_create_vm(ctx, NULL); 145 | lip_assert_num_result("(mod6/a -42)", -42); 146 | lip_destroy_vm(ctx, vm); 147 | lip_destroy_context(ctx); 148 | } 149 | 150 | { 151 | lip_context_t* ctx = lip_create_context(fixture->runtime, NULL); 152 | lip_vm_t* vm = lip_create_vm(ctx, NULL); 153 | lip_assert_error_msg("(mod6/b 3)", "Undefined symbol: mod6/b"); 154 | lip_destroy_vm(ctx, vm); 155 | lip_destroy_context(ctx); 156 | } 157 | 158 | return MUNIT_OK; 159 | } 160 | 161 | static MunitResult 162 | error(const MunitParameter params[], void* fixture_) 163 | { 164 | (void)params; 165 | 166 | lip_fixture_t* fixture = fixture_; 167 | lip_context_t* ctx2 = fixture->context; 168 | lip_load_builtins(ctx2); 169 | 170 | { 171 | lip_context_t* ctx = lip_create_context(fixture->runtime, NULL); 172 | lip_vm_t* vm = lip_create_vm(ctx, NULL); 173 | for(int i = 0; i < 500; ++i) 174 | { 175 | lip_assert_error_msg("(mod9/b 3)", "Undefined symbol: mod9/b"); 176 | } 177 | lip_assert_num_result("(mod6/a -42)", -42); 178 | lip_destroy_vm(ctx, vm); 179 | lip_destroy_context(ctx); 180 | } 181 | 182 | return MUNIT_OK; 183 | } 184 | 185 | static MunitTest tests[] = { 186 | { 187 | .name = "/basic", 188 | .test = basic, 189 | .setup = setup, 190 | .tear_down = teardown 191 | }, 192 | { 193 | .name = "/local_function", 194 | .test = local_function, 195 | .setup = setup, 196 | .tear_down = teardown 197 | }, 198 | { 199 | .name = "/private_function", 200 | .test = private_function, 201 | .setup = setup, 202 | .tear_down = teardown 203 | }, 204 | { 205 | .name = "/no_declare_in_body", 206 | .test = no_declare_in_body, 207 | .setup = setup, 208 | .tear_down = teardown 209 | }, 210 | { 211 | .name = "/error", 212 | .test = error, 213 | .setup = setup, 214 | .tear_down = teardown 215 | }, 216 | { .test = NULL } 217 | }; 218 | 219 | MunitSuite module = { 220 | .prefix = "/module", 221 | .tests = tests 222 | }; 223 | -------------------------------------------------------------------------------- /include/lip/core/vm.h: -------------------------------------------------------------------------------- 1 | #ifndef LIP_CORE_VM_H 2 | #define LIP_CORE_VM_H 3 | 4 | #ifdef _MSC_VER 5 | # pragma warning(push) 6 | # pragma warning(disable: 4116) 7 | #endif 8 | 9 | #include 10 | #include "extra.h" 11 | #include "opcode.h" 12 | #include "memory.h" 13 | 14 | typedef struct lip_stack_frame_s lip_stack_frame_t; 15 | typedef struct lip_function_layout_s lip_function_layout_t; 16 | typedef struct lip_import_s lip_import_t; 17 | typedef struct lip_runtime_interface_s lip_runtime_interface_t; 18 | 19 | struct lip_runtime_interface_s 20 | { 21 | bool(*resolve_import)( 22 | lip_runtime_interface_t* rt, 23 | lip_string_t* symbol_name, 24 | lip_value_t* result 25 | ); 26 | void*(*malloc)(lip_runtime_interface_t* rt, lip_value_type_t type, size_t size); 27 | const char*(*format)(lip_runtime_interface_t* rt, const char* fmt, va_list args); 28 | }; 29 | 30 | struct lip_import_s 31 | { 32 | uint32_t name; 33 | lip_value_t value; 34 | }; 35 | 36 | /** 37 | * Layout: 38 | * 39 | * [lip_function_t]: header 40 | * [lip_string_t]: source name 41 | * [lip_import_t...]: imports 42 | * [lip_value_t...]: constant pool, with each string as offset to lip_string_t 43 | * [uint32_t...]: nested function offsets 44 | * [lip_instruction_t...]: instructions 45 | * [lip_loc_range_t...]: source locations 46 | * [lip_string_t...]: string pool, including source name 47 | * [lip_function_t...]: nested functions 48 | */ 49 | struct lip_function_s 50 | { 51 | /// Total size, including body 52 | uint32_t size; 53 | 54 | uint8_t num_args; 55 | uint8_t is_vararg; 56 | uint16_t num_locals; 57 | uint16_t num_imports; 58 | uint16_t num_constants; 59 | uint16_t num_instructions; 60 | uint16_t num_functions; 61 | }; 62 | 63 | struct lip_function_layout_s 64 | { 65 | lip_string_t* source_name; 66 | lip_import_t* imports; 67 | lip_value_t* constants; 68 | uint32_t* function_offsets; 69 | lip_instruction_t* instructions; 70 | lip_loc_range_t* locations; 71 | }; 72 | 73 | struct lip_closure_s 74 | { 75 | union 76 | { 77 | lip_function_t* lip; 78 | lip_native_fn_t native; 79 | } function; 80 | 81 | lip_string_t* debug_name; 82 | 83 | unsigned env_len:8; 84 | unsigned is_native:1; 85 | 86 | LIP_FLEXIBLE_ARRAY_MEMBER(lip_value_t, environment); 87 | }; 88 | 89 | struct lip_stack_frame_s 90 | { 91 | lip_closure_t* closure; 92 | lip_instruction_t* pc; 93 | lip_value_t* ep; 94 | lip_value_t* bp; 95 | 96 | const char* native_function; 97 | const char* native_filename; 98 | int native_line; 99 | 100 | uint8_t num_args; 101 | }; 102 | 103 | struct lip_vm_s 104 | { 105 | lip_vm_config_t config; 106 | lip_exec_status_t status; 107 | lip_runtime_interface_t* rt; 108 | 109 | void* mem; 110 | lip_value_t* sp; 111 | lip_stack_frame_t* fp; 112 | lip_vm_hook_t* hook; 113 | }; 114 | 115 | struct lip_string_t_alignment_helper 116 | { 117 | size_t size; char ptr[1]; 118 | }; 119 | 120 | static const size_t lip_string_t_alignment = 121 | LIP_ALIGN_OF(struct lip_string_t_alignment_helper); 122 | 123 | struct lip_function_t_alignment_helper 124 | { 125 | lip_function_t function; 126 | lip_import_t import; 127 | uint32_t index; 128 | lip_instruction_t instruction; 129 | lip_loc_range_t location; 130 | }; 131 | 132 | // I don't know a better way too do this at compile-time 133 | static const size_t lip_function_t_alignment = 134 | LIP_MAX( 135 | LIP_ALIGN_OF(struct lip_string_t_alignment_helper), 136 | LIP_ALIGN_OF(struct lip_function_t_alignment_helper) 137 | ); 138 | 139 | LIP_CORE_API size_t 140 | lip_vm_memory_required(const lip_vm_config_t* config); 141 | 142 | LIP_CORE_API void 143 | lip_vm_init( 144 | lip_vm_t* vm, 145 | const lip_vm_config_t* config, 146 | lip_runtime_interface_t* rt, 147 | void* mem 148 | ); 149 | 150 | LIP_MAYBE_UNUSED static inline void 151 | lip_vm_reset(lip_vm_t* vm) 152 | { 153 | lip_vm_init(vm, &vm->config, vm->rt, vm->mem); 154 | } 155 | 156 | LIP_MAYBE_UNUSED static inline void 157 | lip_function_layout(const lip_function_t* function, lip_function_layout_t* layout) 158 | { 159 | char* function_end = (char*)function + sizeof(lip_function_t); 160 | layout->source_name = (lip_string_t*)lip_align_ptr(function_end, lip_string_t_alignment); 161 | 162 | char* source_name_end = 163 | (char*)layout->source_name 164 | + sizeof(lip_string_t) 165 | + layout->source_name->length; 166 | layout->imports = (lip_import_t*)lip_align_ptr(source_name_end, LIP_ALIGN_OF(lip_import_t)); 167 | layout->constants = (lip_value_t*)lip_align_ptr( 168 | layout->imports + function->num_imports, LIP_ALIGN_OF(lip_value_t) 169 | ); 170 | layout->function_offsets = (uint32_t*)lip_align_ptr( 171 | layout->constants + function->num_constants, LIP_ALIGN_OF(uint32_t) 172 | ); 173 | layout->instructions = (lip_instruction_t*)lip_align_ptr( 174 | layout->function_offsets + function->num_functions, LIP_ALIGN_OF(lip_instruction_t) 175 | ); 176 | layout->locations = (lip_loc_range_t*)lip_align_ptr( 177 | layout->instructions + function->num_instructions, LIP_ALIGN_OF(lip_loc_range_t) 178 | ); 179 | } 180 | 181 | LIP_MAYBE_UNUSED static inline void* 182 | lip_function_resource(const lip_function_t* function, uint32_t offset) 183 | { 184 | return ((char*)function + offset); 185 | } 186 | 187 | LIP_MAYBE_UNUSED static inline bool 188 | lip_stack_frame_is_native(const lip_stack_frame_t* frame) 189 | { 190 | return frame->closure == NULL || frame->closure->is_native; 191 | } 192 | 193 | LIP_MAYBE_UNUSED static inline lip_memblock_info_t 194 | lip_vm_memory_layout( 195 | const lip_vm_config_t* config, 196 | lip_memblock_info_t* os_block, 197 | lip_memblock_info_t* env_block, 198 | lip_memblock_info_t* cs_block 199 | ) 200 | { 201 | os_block->element_size = sizeof(lip_value_t); 202 | os_block->num_elements = config->os_len; 203 | os_block->alignment = LIP_ALIGN_OF(lip_value_t); 204 | 205 | env_block->element_size = sizeof(lip_value_t); 206 | env_block->num_elements = config->env_len; 207 | env_block->alignment = LIP_ALIGN_OF(lip_value_t); 208 | 209 | cs_block->element_size = sizeof(lip_stack_frame_t); 210 | cs_block->num_elements = config->cs_len; 211 | cs_block->alignment = LIP_ALIGN_OF(lip_stack_frame_t); 212 | 213 | lip_memblock_info_t* mem_layout[] = { os_block, env_block, cs_block }; 214 | return lip_align_memblocks(LIP_STATIC_ARRAY_LEN(mem_layout), mem_layout); 215 | } 216 | 217 | #ifdef _MSC_VER 218 | # pragma warning(pop) 219 | #endif 220 | 221 | #endif 222 | -------------------------------------------------------------------------------- /genie.lua: -------------------------------------------------------------------------------- 1 | local function add_flags(flags) 2 | buildoptions(flags) 3 | linkoptions(flags) 4 | end 5 | 6 | local function declare_project(name) 7 | project(name) 8 | language "C" 9 | includedirs { "include", "deps" } 10 | flags { 11 | "ExtraWarnings", 12 | "FatalWarnings" 13 | } 14 | configuration "linux" 15 | buildoptions { 16 | "-Wno-missing-field-initializers" 17 | } 18 | 19 | files { "src/"..name.."/**" } 20 | end 21 | 22 | local function declare_library(name) 23 | declare_project(name) 24 | language "C" 25 | targetname("lip-"..name) 26 | 27 | configuration "*Dynamic" 28 | kind "SharedLib" 29 | 30 | configuration "*Static" 31 | kind "StaticLib" 32 | 33 | configuration "linux" 34 | buildoptions { "-pedantic" } 35 | 36 | configuration {"linux", "*Dynamic"} 37 | buildoptions { "-fvisibility=hidden" } 38 | 39 | configuration "Debug*" 40 | if _OPTIONS['with-coverage'] then 41 | add_flags { "--coverage" } 42 | end 43 | 44 | configuration {} 45 | 46 | defines { "LIP_" .. name:upper() .. "_BUILDING" } 47 | 48 | files { "include/lip/"..name..".h", } 49 | end 50 | 51 | local function declare_app(name) 52 | declare_project(name) 53 | kind "ConsoleApp" 54 | end 55 | 56 | -- Override generator to use $ORIGIN as rpath 57 | local ninja = premake.ninja 58 | function ninja.cpp.linker(prj, cfg, objfiles, tool) 59 | local all_ldflags = ninja.list(table.join(tool.getlibdirflags(cfg), tool.getldflags(cfg), cfg.linkoptions)) 60 | local lddeps = ninja.list(premake.getlinks(cfg, "siblings", "fullpath")) 61 | local libs 62 | 63 | local flags = {} 64 | local has_so = false 65 | for _, value in ipairs(premake.getlinks(cfg, "siblings", "fullpath")) do 66 | if path.getextension(value) == ".so" then 67 | table.insert(flags, "-l:" .. path.getbasename(value) .. ".so") 68 | has_so = true 69 | else 70 | table.insert(flags, value) 71 | end 72 | end 73 | 74 | libs = ninja.list(flags) .. " " .. ninja.list(tool.getlinkflags(cfg)) 75 | 76 | if has_so then 77 | all_ldflags = all_ldflags .. " -Wl,-rpath='$$ORIGIN'" 78 | end 79 | 80 | local function writevars() 81 | _p(1, "all_ldflags = " .. all_ldflags) 82 | _p(1, "libs = " .. libs) 83 | _p(1, "all_outputfiles = " .. table.concat(objfiles, " ")) 84 | end 85 | 86 | if cfg.kind == "StaticLib" then 87 | local ar_flags = ninja.list(tool.getarchiveflags(cfg, cfg, false)) 88 | _p("# link static lib") 89 | _p("build " .. cfg:getoutputfilename() .. ": ar " .. table.concat(objfiles, " ") .. " | " .. lddeps) 90 | _p(1, "flags = " .. ninja.list(tool.getarchiveflags(cfg, cfg, false))) 91 | _p(1, "all_outputfiles = " .. table.concat(objfiles, " ")) 92 | elseif cfg.kind == "SharedLib" then 93 | local output = cfg:getoutputfilename() 94 | _p("# link shared lib") 95 | _p("build " .. output .. ": link " .. table.concat(objfiles, " ") .. " | " .. lddeps) 96 | writevars() 97 | elseif (cfg.kind == "ConsoleApp") or (cfg.kind == "WindowedApp") then 98 | _p("# link executable") 99 | _p("build " .. cfg:getoutputfilename() .. ": link " .. table.concat(objfiles, " ") .. " | " .. lddeps) 100 | writevars() 101 | else 102 | p.error("ninja action doesn't support this kind of target " .. cfg.kind) 103 | end 104 | end 105 | 106 | newaction { 107 | trigger = "gen-config.h", 108 | description = "Generate config.h", 109 | execute = function() 110 | local version = os.outputof("git describe --tags"):gsub("%s+", "") 111 | os.mkdir("include/lip/gen") 112 | local file = io.open("include/lip/gen/config.h", "w") 113 | file:write("#define LIP_VERSION \""..version.."\"\n") 114 | file:close() 115 | end 116 | } 117 | 118 | solution "lip" 119 | location "build" 120 | configurations { "ReleaseStatic", "DebugStatic", "ReleaseDynamic", "DebugDynamic" } 121 | platforms { "native", "universal", "arm" } 122 | debugdir "." 123 | targetdir "bin" 124 | startproject "repl" 125 | 126 | newoption { 127 | trigger = "with-config.h", 128 | description = "Generate config.h" 129 | } 130 | 131 | if _OPTIONS["with-config.h"] then 132 | prebuildcommands { 133 | _PREMAKE_COMMAND .. " gen-config.h" 134 | } 135 | 136 | defines { "LIP_HAVE_CONFIG_H" } 137 | end 138 | 139 | flags { 140 | "StaticRuntime", 141 | "Symbols", 142 | "NoEditAndContinue", 143 | "NoNativeWChar", 144 | "NoManifest" 145 | } 146 | 147 | defines { "_CRT_SECURE_NO_WARNINGS" } 148 | 149 | configuration "linux" 150 | newoption { 151 | trigger = "with-coverage", 152 | description = "Compile with coverage" 153 | } 154 | 155 | newoption { 156 | trigger = "with-asan", 157 | description = "Compile with AddressSanitizer" 158 | } 159 | 160 | newoption { 161 | trigger = "with-ubsan", 162 | description = "Compile with UndefinedBehaviorSanitizer" 163 | } 164 | 165 | newoption { 166 | trigger = "with-lto", 167 | description = "Compile with Link-time Optimization" 168 | } 169 | 170 | newoption { 171 | trigger = "with-clang", 172 | description = "Compile with Clang" 173 | } 174 | 175 | newoption { 176 | trigger = "with-gprof", 177 | description = "Compile for gprof" 178 | } 179 | 180 | add_flags { "-pthread" } 181 | 182 | configuration "Release*" 183 | flags { "OptimizeSpeed" } 184 | 185 | configuration "*Dynamic" 186 | defines { "LIP_DYNAMIC=1" } 187 | 188 | configuration { "Release*", "linux" } 189 | if _OPTIONS["with-lto"] then 190 | add_flags { add_flags("-flto") } 191 | end 192 | 193 | configuration "linux" 194 | if _OPTIONS["with-asan"] then 195 | add_flags { "-fsanitize=address", "-fPIC" } 196 | end 197 | 198 | if _OPTIONS["with-ubsan"] then 199 | add_flags { 200 | "-fsanitize=undefined", 201 | "-fno-sanitize-recover=undefined" 202 | } 203 | end 204 | 205 | if _OPTIONS["with-gprof"] then 206 | add_flags { "-pg" } 207 | end 208 | 209 | if _OPTIONS["with-clang"] then 210 | premake.gcc.cc = "clang" 211 | premake.gcc.cxx = "clang++" 212 | premake.gcc.llvm = true 213 | end 214 | 215 | declare_library "core" 216 | files { "include/lip/core/*.h" } 217 | 218 | declare_library "std" 219 | links { "core" } 220 | 221 | declare_library "dbg" 222 | links { "core", "cmp" } 223 | 224 | configuration "linux" 225 | buildoptions { "-Wno-unused-function" } 226 | linkoptions { "-Wl,--exclude-libs=ALL" } 227 | 228 | declare_app "repl" 229 | targetname "lip" 230 | links { "core", "std", "dbg", "linenoise" } 231 | 232 | configuration { "linux", "*Static" } 233 | links { "cmp" } 234 | 235 | declare_app "compiler" 236 | targetname "lipc" 237 | links { "core", "std" } 238 | 239 | project "cmp" 240 | language "C" 241 | kind "StaticLib" 242 | files { 243 | "deps/cmp/cmp.h", 244 | "deps/cmp/cmp.c", 245 | } 246 | 247 | project "linenoise" 248 | language "C" 249 | kind "StaticLib" 250 | files { 251 | "deps/linenoise/**.h", 252 | "deps/linenoise/**.c" 253 | } 254 | excludes { 255 | "deps/linenoise/exaple.c" 256 | } 257 | -------------------------------------------------------------------------------- /src/dbg-ui/src/App.js: -------------------------------------------------------------------------------- 1 | import h from 'snabbdom/h'; 2 | import flyd from 'flyd'; 3 | import lensProp from 'ramda/src/lensProp'; 4 | import evolve from 'ramda/src/evolve'; 5 | import pipe from 'ramda/src/pipe'; 6 | import assoc from 'ramda/src/assoc'; 7 | import Union from 'union-type'; 8 | import Msgpack from 'msgpack-lite'; 9 | import { HALApp } from './hal'; 10 | import { nested, updateNested } from './krueger'; 11 | import { splitPane } from './splitPane'; 12 | import * as CodeView from './CodeView'; 13 | import * as Toolbar from './Toolbar'; 14 | import * as CallStackView from './CallStackView'; 15 | import * as ValueListView from './ValueListView'; 16 | 17 | const halApp = new HALApp({ 18 | entrypoint: 'http://localhost:8081/dbg', 19 | custom_rels: { 20 | call_stack: "http://lip.bullno1.com/hal/relations/call_stack", 21 | command: "http://lip.bullno1.com/hal/relations/command", 22 | src: "http://lip.bullno1.com/hal/relations/src", 23 | status: "http://lip.bullno1.com/hal/relations/status", 24 | vm: "http://lip.bullno1.com/hal/relations/vm" 25 | }, 26 | codecs: { 27 | 'application/hal+msgpack': { 28 | isHAL: true, 29 | decode: (resp) => 30 | resp 31 | .arrayBuffer() 32 | .then(buffer => Msgpack.decode(new Uint8Array(buffer))) 33 | }, 34 | 'text/plain': { 35 | isHAL: false, 36 | decode: (resp) => resp.text() 37 | } 38 | } 39 | }); 40 | 41 | export const init = () => ({ 42 | dbg: null, 43 | ws: null, 44 | wsURL: null, 45 | notify$: flyd.stream(), 46 | activeStackLevel: 0, 47 | sourceView: nested(CodeView, Action.SourceView), 48 | bytecodeView: nested(CodeView, Action.BytecodeView), 49 | toolbar: nested(Toolbar, Action.Toolbar), 50 | callStackView: nested(CallStackView, Action.CallStackView), 51 | operandStackView: nested(ValueListView, Action.OperandStackView, "Operand Stack"), 52 | localView: nested(ValueListView, Action.LocalView, "Locals"), 53 | closureView: nested(ValueListView, Action.ClosureView, "Closure") 54 | }); 55 | 56 | export const Action = Union({ 57 | UpdateDbg: [Object], 58 | ConnectWS: [String], 59 | SourceView: [CodeView.Action], 60 | BytecodeView: [CodeView.Action], 61 | Toolbar: [Toolbar.Action], 62 | CallStackView: [CallStackView.Action], 63 | OperandStackView: [ValueListView.Action], 64 | LocalView: [ValueListView.Action], 65 | ClosureView: [ValueListView.Action], 66 | DisplayStackFrame: [Object] 67 | }); 68 | 69 | const refreshSource = (model) => { 70 | const vm = model.dbg.getEmbedded('vm'); 71 | const callStack = vm.getEmbedded('call_stack').getEmbedded('item'); 72 | const activeStackEntry = callStack[model.activeStackLevel]; 73 | activeStackEntry.getLink("src").fetch() 74 | .then((source) => { 75 | model.notify$(Action.SourceView( 76 | CodeView.Action.ViewCode( 77 | source, 78 | activeStackEntry.filename.endsWith('.c') ? "clike" : "clojure", 79 | activeStackEntry.location 80 | ) 81 | )) 82 | }); 83 | 84 | if(!activeStackEntry.is_native) { 85 | activeStackEntry.getLink("self").fetch() 86 | .then((stackFrame) => 87 | model.notify$(Action.DisplayStackFrame(stackFrame)) 88 | ); 89 | 90 | return model; 91 | } else { 92 | return updateNested( 93 | lensProp('bytecodeView'), 94 | CodeView.Action.ViewCode( 95 | '', 96 | '', 97 | { 98 | start: { line: 0, column: 0 }, 99 | end: { line: 0, column: 0 } 100 | } 101 | ), 102 | model 103 | ); 104 | } 105 | }; 106 | 107 | const refreshDbg = (model) => 108 | halApp.fetch().then((dbg) => model.notify$(Action.UpdateDbg(dbg))); 109 | 110 | const formatBytecode = (bytecode) => 111 | bytecode.map((instr) => instr.join(" ")).join("\n"); 112 | 113 | const connectWS = (wsURL, model) => { 114 | const hasWS = !!model.ws && (model.ws.readyState === WebSocket.CONNECTING || model.ws.readyState === WebSocket.OPEN); 115 | const wsURLChanged = wsURL !== model.wsURL; 116 | 117 | if(hasWS && !wsURLChanged) { return model; } 118 | 119 | if(hasWS) { model.ws.close(); } 120 | 121 | const ws = new WebSocket(wsURL); 122 | ws.binaryType = 'arraybuffer'; 123 | model = pipe(assoc('ws', ws), assoc('wsURL', wsURL))(model); 124 | 125 | ws.onopen = () => refreshDbg(model); 126 | ws.onmessage = (event) => { 127 | const dbg = halApp.parse(Msgpack.decode(new Uint8Array(event.data))); 128 | model.notify$(Action.UpdateDbg(dbg)); 129 | } 130 | ws.onerror = ws.onclose = (error) => { 131 | setTimeout(() => model.notify$(Action.ConnectWS(wsURL)), 3000); 132 | } 133 | 134 | return model; 135 | }; 136 | 137 | export const update = Action.caseOn({ 138 | UpdateDbg: (dbg, model) => { 139 | const vm = dbg.getEmbedded('vm'); 140 | const callStack = vm.getEmbedded('call_stack').getEmbedded('item'); 141 | 142 | const wsURL = dbg.getLink('status').href; 143 | model = connectWS(wsURL, model); 144 | 145 | return refreshSource(evolve({ 146 | wsURL: (_) => wsURL, 147 | dbg: (_) => dbg, 148 | callStackView: (callStackView) => 149 | callStackView.update(CallStackView.Action.SetCallStack(callStack)), 150 | toolbar: (toolbar) => 151 | toolbar.update(Toolbar.Action.UpdateDbg(dbg)) 152 | }, model)); 153 | }, 154 | SourceView: updateNested(lensProp('sourceView')), 155 | BytecodeView: updateNested(lensProp('bytecodeView')), 156 | Toolbar: updateNested(lensProp('toolbar')), 157 | CallStackView: (action, model) => CallStackView.Action.case({ 158 | SetActiveStackLevel: (level) => 159 | refreshSource(pipe( 160 | assoc('activeStackLevel', level), 161 | assoc('callStackView', model.callStackView.update(action)), 162 | )(model)), 163 | _: () => updateNested(lensProp('callStackView'), action, model) 164 | }, action), 165 | OperandStackView: updateNested(lensProp('operandStackView')), 166 | LocalView: updateNested(lensProp('localView')), 167 | ClosureView: updateNested(lensProp('closureView')), 168 | DisplayStackFrame: (stackFrame, model) => { 169 | const loc = { line: stackFrame.pc, column: 0 }; 170 | return pipe( 171 | updateNested( 172 | lensProp('bytecodeView'), 173 | CodeView.Action.ViewCode( 174 | formatBytecode(stackFrame.function.bytecode), 175 | '', 176 | { start: loc, end: loc } 177 | ) 178 | ), 179 | updateNested( 180 | lensProp('operandStackView'), 181 | ValueListView.Action.SetValueList(stackFrame.function.stack) 182 | ), 183 | updateNested( 184 | lensProp('localView'), 185 | ValueListView.Action.SetValueList(stackFrame.function.locals) 186 | ), 187 | updateNested( 188 | lensProp('closureView'), 189 | ValueListView.Action.SetValueList(stackFrame.function.env) 190 | ) 191 | )(model); 192 | }, 193 | ConnectWS: connectWS 194 | }); 195 | 196 | export const render = (model, actions) => 197 | splitPane({ direction: 'horizontal', sizes: [70, 30] }, [ 198 | splitPane({ direction: 'vertical', sizes: [50, 50] }, [ 199 | model.sourceView.render(actions), 200 | model.bytecodeView.render(actions), 201 | ]), 202 | h("div", [ 203 | model.toolbar.render(actions), 204 | model.callStackView.render(actions), 205 | model.operandStackView.render(actions), 206 | model.localView.render(actions), 207 | model.closureView.render(actions) 208 | ]) 209 | ]); 210 | 211 | export const subscribe = (model) => { 212 | refreshDbg(model); 213 | return model.notify$; 214 | }; 215 | -------------------------------------------------------------------------------- /src/std/vendor/sort_r.h: -------------------------------------------------------------------------------- 1 | /* Isaac Turner 29 April 2014 Public Domain */ 2 | #ifndef SORT_R_H_ 3 | #define SORT_R_H_ 4 | 5 | #include 6 | #include 7 | 8 | /* 9 | sort_r function to be exported. 10 | Parameters: 11 | base is the array to be sorted 12 | nel is the number of elements in the array 13 | width is the size in bytes of each element of the array 14 | compar is the comparison function 15 | arg is a pointer to be passed to the comparison function 16 | void sort_r(void *base, size_t nel, size_t width, 17 | int (*compar)(const void *_a, const void *_b, void *_arg), 18 | void *arg); 19 | */ 20 | 21 | #if (defined __APPLE__ || defined __MACH__ || defined __DARWIN__ || \ 22 | defined __FreeBSD__ || defined __DragonFly__) 23 | # define _SORT_R_BSD 24 | # define _SORT_R_INLINE inline 25 | #elif (defined _GNU_SOURCE || defined __gnu_hurd__ || defined __GNU__ || \ 26 | defined __linux__ || defined __MINGW32__ || defined __GLIBC__) 27 | # if !defined(__MUSL__) 28 | # define _SORT_R_LINUX 29 | # endif 30 | # define _SORT_R_INLINE inline 31 | #elif (defined _WIN32 || defined _WIN64 || defined __WINDOWS__) 32 | # define _SORT_R_WINDOWS 33 | # define _SORT_R_INLINE __inline 34 | #else 35 | /* Using our own recursive quicksort sort_r_simple() */ 36 | #endif 37 | 38 | #if (defined NESTED_QSORT && NESTED_QSORT == 0) 39 | # undef NESTED_QSORT 40 | #endif 41 | 42 | /* swap a, b iff a>b */ 43 | /* __restrict is same as restrict but better support on old machines */ 44 | static _SORT_R_INLINE int sort_r_cmpswap(char *__restrict a, char *__restrict b, size_t w, 45 | int (*compar)(const void *_a, const void *_b, 46 | void *_arg), 47 | void *arg) 48 | { 49 | char tmp, *end = a+w; 50 | if(compar(a, b, arg) > 0) { 51 | for(; a < end; a++, b++) { tmp = *a; *a = *b; *b = tmp; } 52 | return 1; 53 | } 54 | return 0; 55 | } 56 | 57 | /* Implement recursive quicksort ourselves */ 58 | /* Note: quicksort is not stable, equivalent values may be swapped */ 59 | static _SORT_R_INLINE void sort_r_simple(void *base, size_t nel, size_t w, 60 | int (*compar)(const void *_a, const void *_b, 61 | void *_arg), 62 | void *arg) 63 | { 64 | char *b = (char *)base, *end = b + nel*w; 65 | if(nel < 7) { 66 | /* Insertion sort for arbitrarily small inputs */ 67 | char *pi, *pj; 68 | for(pi = b+w; pi < end; pi += w) { 69 | for(pj = pi; pj > b && sort_r_cmpswap(pj-w,pj,w,compar,arg); pj -= w) {} 70 | } 71 | } 72 | else 73 | { 74 | /* nel > 6; Quicksort */ 75 | 76 | /* Use median of first, middle and last items as pivot */ 77 | char *x, *y, *xend, ch; 78 | char *pl, *pr; 79 | char *last = b+w*(nel-1), *tmp; 80 | char *l[3]; 81 | l[0] = b; 82 | l[1] = b+w*(nel/2); 83 | l[2] = last; 84 | 85 | if(compar(l[0],l[1],arg) > 0) { tmp=l[0]; l[0]=l[1]; l[1]=tmp; } 86 | if(compar(l[1],l[2],arg) > 0) { 87 | tmp=l[1]; l[1]=l[2]; l[2]=tmp; /* swap(l[1],l[2]) */ 88 | if(compar(l[0],l[1],arg) > 0) { tmp=l[0]; l[0]=l[1]; l[1]=tmp; } 89 | } 90 | 91 | /* swap l[id], l[2] to put pivot as last element */ 92 | for(x = l[1], y = last, xend = x+w; xcompar)(a, b, ss->arg); 161 | } 162 | 163 | #endif 164 | 165 | #if defined _SORT_R_LINUX 166 | 167 | typedef int(* __compar_d_fn_t)(const void *, const void *, void *); 168 | extern void qsort_r(void *base, size_t nel, size_t width, 169 | __compar_d_fn_t __compar, void *arg) 170 | __attribute__((nonnull (1, 4))); 171 | 172 | #endif 173 | 174 | /* implementation */ 175 | 176 | static _SORT_R_INLINE void sort_r(void *base, size_t nel, size_t width, 177 | int (*compar)(const void *_a, const void *_b, void *_arg), 178 | void *arg) 179 | { 180 | #if defined _SORT_R_LINUX 181 | 182 | #if defined __GLIBC__ && ((__GLIBC__ < 2) || (__GLIBC__ == 2 && __GLIBC_MINOR__ < 8)) 183 | 184 | /* no qsort_r in glibc before 2.8, need to use nested qsort */ 185 | sort_r_simple(base, nel, width, compar, arg); 186 | 187 | #else 188 | 189 | qsort_r(base, nel, width, compar, arg); 190 | 191 | #endif 192 | 193 | #elif defined _SORT_R_BSD 194 | 195 | struct sort_r_data tmp; 196 | tmp.arg = arg; 197 | tmp.compar = compar; 198 | qsort_r(base, nel, width, &tmp, sort_r_arg_swap); 199 | 200 | #elif defined _SORT_R_WINDOWS 201 | 202 | struct sort_r_data tmp; 203 | tmp.arg = arg; 204 | tmp.compar = compar; 205 | qsort_s(base, nel, width, sort_r_arg_swap, &tmp); 206 | 207 | #else 208 | 209 | /* Fall back to our own quicksort implementation */ 210 | sort_r_simple(base, nel, width, compar, arg); 211 | 212 | #endif 213 | } 214 | 215 | #endif /* !NESTED_QSORT */ 216 | 217 | #undef _SORT_R_INLINE 218 | #undef _SORT_R_WINDOWS 219 | #undef _SORT_R_LINUX 220 | #undef _SORT_R_BSD 221 | 222 | #endif /* SORT_R_H_ */ 223 | -------------------------------------------------------------------------------- /src/tests/lexer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "munit.h" 5 | #include "test_helpers.h" 6 | 7 | static void* 8 | setup(const MunitParameter params[], void* data) 9 | { 10 | (void)params; 11 | (void)data; 12 | 13 | return lip_lexer_create(lip_default_allocator); 14 | } 15 | 16 | static void 17 | teardown(void* data) 18 | { 19 | lip_lexer_destroy(data); 20 | } 21 | 22 | static void 23 | lip_assert_token_equal(lip_token_t lhs, lip_token_t rhs) 24 | { 25 | lip_assert_enum(lip_token_type_t, lhs.type, ==, rhs.type); 26 | lip_assert_loc_range_equal(lhs.location, rhs.location); 27 | lip_assert_string_ref_equal(lhs.lexeme, rhs.lexeme); 28 | } 29 | 30 | static MunitResult 31 | normal(const MunitParameter params[], void* fixture) 32 | { 33 | (void)params; 34 | 35 | lip_string_ref_t text = lip_string_ref( 36 | "(\n" 37 | " )\n" 38 | "50.6 \"hi hi \" \r\n" 39 | "; comment 12 \"hi\" \r\n" 40 | "\n" 41 | "test-23 -3 -ve -\r\n" 42 | "\n" 43 | " \"f \\\"\"\n" 44 | " \" \\\\\"" 45 | ); 46 | 47 | struct lip_isstream_s sstream; 48 | lip_in_t* input = lip_make_isstream(text, &sstream); 49 | 50 | lip_lexer_t* lexer = fixture; 51 | lip_lexer_reset(lexer, input); 52 | 53 | lip_token_t expected_tokens[] = { 54 | { 55 | .type = LIP_TOKEN_LPAREN, 56 | .lexeme = lip_string_ref("("), 57 | .location = { 58 | .start = {.line = 1, .column = 1}, 59 | .end = {.line = 1, .column = 1} 60 | } 61 | }, 62 | { 63 | .type = LIP_TOKEN_RPAREN, 64 | .lexeme = lip_string_ref(")"), 65 | .location = { 66 | .start = {.line = 2, .column = 3}, 67 | .end = {.line = 2, .column = 3}, 68 | } 69 | }, 70 | { 71 | .type = LIP_TOKEN_NUMBER, 72 | .lexeme = lip_string_ref("50.6"), 73 | .location = { 74 | .start = {.line = 3, .column = 1}, 75 | .end = {.line = 3, .column = 4} 76 | } 77 | }, 78 | { 79 | .type = LIP_TOKEN_STRING, 80 | .lexeme = lip_string_ref("hi hi "), 81 | .location = { 82 | .start = {.line = 3, .column = 6}, 83 | .end = {.line = 3, .column = 13} 84 | } 85 | }, 86 | { 87 | .type = LIP_TOKEN_SYMBOL, 88 | .lexeme = lip_string_ref("test-23"), 89 | .location = { 90 | .start = {.line = 6, .column = 1}, 91 | .end = {.line = 6, .column = 7} 92 | } 93 | }, 94 | { 95 | .type = LIP_TOKEN_NUMBER, 96 | .lexeme = lip_string_ref("-3"), 97 | .location = { 98 | .start = {.line = 6, .column = 9}, 99 | .end = {.line = 6, .column = 10} 100 | } 101 | }, 102 | { 103 | .type = LIP_TOKEN_SYMBOL, 104 | .lexeme = lip_string_ref("-ve"), 105 | .location = { 106 | .start = {.line = 6, .column = 12}, 107 | .end = {.line = 6, .column = 14} 108 | } 109 | }, 110 | { 111 | .type = LIP_TOKEN_SYMBOL, 112 | .lexeme = lip_string_ref("-"), 113 | .location = { 114 | .start = {.line = 6, .column = 16}, 115 | .end = {.line = 6, .column = 16} 116 | } 117 | }, 118 | { 119 | .type = LIP_TOKEN_STRING, 120 | .lexeme = lip_string_ref("f \\\""), 121 | .location = { 122 | .start = {.line = 8, .column = 2}, 123 | .end = {.line = 8, .column = 7} 124 | } 125 | }, 126 | { 127 | .type = LIP_TOKEN_STRING, 128 | .lexeme = lip_string_ref(" \\\\"), 129 | .location = { 130 | .start = {.line = 9, .column = 3}, 131 | .end = {.line = 9, .column = 7} 132 | } 133 | }, 134 | }; 135 | 136 | lip_token_t token; 137 | unsigned int num_tokens = LIP_STATIC_ARRAY_LEN(expected_tokens); 138 | for(unsigned int i = 0; i < num_tokens; ++i) 139 | { 140 | lip_token_t* expected_token = &expected_tokens[i]; 141 | lip_stream_status_t status = lip_lexer_next_token(lexer, &token); 142 | 143 | munit_logf( 144 | MUNIT_LOG_INFO, "%s %s '%.*s' (%u:%u - %u:%u)", 145 | lip_stream_status_t_to_str(status) + sizeof("LIP"), 146 | lip_token_type_t_to_str(token.type) + sizeof("LIP"), 147 | (int)token.lexeme.length, 148 | token.lexeme.ptr, 149 | token.location.start.line, 150 | token.location.start.column, 151 | token.location.end.line, 152 | token.location.end.column 153 | ); 154 | 155 | lip_assert_enum(lip_stream_status_t, LIP_STREAM_OK, ==, status); 156 | lip_assert_token_equal(*expected_token, token); 157 | } 158 | 159 | lip_assert_enum(lip_stream_status_t, LIP_STREAM_END, ==, lip_lexer_next_token(lexer, &token)); 160 | 161 | return MUNIT_OK; 162 | } 163 | 164 | static MunitResult 165 | bad_string(const MunitParameter params[], void* fixture) 166 | { 167 | (void)params; 168 | lip_lexer_t* lexer = fixture; 169 | 170 | #define lip_assert_bad_string(str, start_line, start_col, end_line, end_col) \ 171 | do { \ 172 | lip_string_ref_t text = lip_string_ref(str); \ 173 | struct lip_isstream_s sstream; \ 174 | lip_in_t* input = lip_make_isstream(text, &sstream); \ 175 | lip_lexer_reset(lexer, input); \ 176 | lip_token_t token; \ 177 | lip_assert_enum(lip_stream_status_t, LIP_STREAM_ERROR, ==, lip_lexer_next_token(lexer, &token)); \ 178 | lip_error_t error = { \ 179 | .code = LIP_LEX_BAD_STRING, \ 180 | .location = { \ 181 | .start = { .line = start_line, .column = start_col}, \ 182 | .end = { .line = end_line, .column = end_col} \ 183 | } \ 184 | }; \ 185 | lip_assert_error_equal(error, *lip_lexer_last_error(lexer)); \ 186 | } while(0) 187 | 188 | lip_assert_bad_string(" \"ha", 1, 2, 1, 4); 189 | lip_assert_bad_string(" \" \n\"", 1, 2, 1, 3); 190 | lip_assert_bad_string(" \" \r\"", 1, 2, 1, 4); 191 | 192 | return MUNIT_OK; 193 | } 194 | 195 | static MunitResult 196 | bad_number(const MunitParameter params[], void* fixture) 197 | { 198 | (void)params; 199 | 200 | lip_string_ref_t text = lip_string_ref(" 5a"); 201 | 202 | struct lip_isstream_s sstream; 203 | lip_in_t* input = lip_make_isstream(text, &sstream); 204 | 205 | lip_lexer_t* lexer = fixture; 206 | lip_lexer_reset(lexer, input); 207 | 208 | lip_token_t token; 209 | lip_assert_enum(lip_stream_status_t, LIP_STREAM_ERROR, ==, lip_lexer_next_token(lexer, &token)); 210 | 211 | lip_error_t error = { 212 | .code = LIP_LEX_BAD_NUMBER, 213 | .location = { 214 | .start = { .line = 1, .column = 2}, 215 | .end = { .line = 1, .column = 3} 216 | } 217 | }; 218 | 219 | lip_assert_error_equal(error, *lip_lexer_last_error(lexer)); 220 | 221 | text = lip_string_ref(" 5..4"); 222 | input = lip_make_isstream(text, &sstream); 223 | lip_lexer_reset(lexer, input); 224 | 225 | lip_assert_enum(lip_stream_status_t, LIP_STREAM_ERROR, ==, lip_lexer_next_token(lexer, &token)); 226 | error = (lip_error_t){ 227 | .code = LIP_LEX_BAD_NUMBER, 228 | .location = { 229 | .start = { .line = 1, .column = 2}, 230 | .end = { .line = 1, .column = 4} 231 | } 232 | }; 233 | lip_assert_error_equal(error, *lip_lexer_last_error(lexer)); 234 | 235 | return MUNIT_OK; 236 | } 237 | 238 | static MunitTest tests[] = { 239 | { 240 | .name = "/normal", 241 | .test = normal, 242 | .setup = setup, 243 | .tear_down = teardown 244 | }, 245 | { 246 | .name = "/bad_string", 247 | .test = bad_string, 248 | .setup = setup, 249 | .tear_down = teardown 250 | }, 251 | { 252 | .name = "/bad_number", 253 | .test = bad_number, 254 | .setup = setup, 255 | .tear_down = teardown 256 | }, 257 | { .test = NULL } 258 | }; 259 | 260 | MunitSuite lexer = { 261 | .prefix = "/lexer", 262 | .tests = tests 263 | }; 264 | -------------------------------------------------------------------------------- /Makefile.nu: -------------------------------------------------------------------------------- 1 | GENERATE_CONFIG_H ?= 1 2 | LIP_CONFIG_H_0 = 3 | LIP_CONFIG_H_1 = include/lip/gen/config.h 4 | LIP_CONFIG_H = $(eval echo \${LIP_CONFIG_H_$GENERATE_CONFIG_H}) 5 | LIP_CONFIG_EXTRA_FLAGS_0 = 6 | LIP_CONFIG_EXTRA_FLAGS_1 = -DLIP_HAVE_CONFIG_H 7 | LIP_CONFIG_EXTRA_FLAGS = $(eval echo \${LIP_CONFIG_EXTRA_FLAGS_$GENERATE_CONFIG_H}) 8 | 9 | BUILD_DYNAMIC_LIB ?= 1 10 | LIBLIP_0 = bin/liblip.a 11 | LIBLIP_1 = bin/liblip.so 12 | LIBLIP = $(eval echo \${LIBLIP_$BUILD_DYNAMIC_LIB}) 13 | LIB_EXTRA_FLAGS_0 = -DLIP_DYNAMIC=0 14 | LIB_EXTRA_FLAGS_1 = -DLIP_DYNAMIC=1 15 | LIBLIP_EXTRA_FLAGS = $(eval echo \${LIB_EXTRA_FLAGS_$BUILD_DYNAMIC_LIB}) 16 | 17 | WITH_COMPUTED_GOTO ?= 1 18 | COMPUTED_GOTO_0 = -DLIP_NO_COMPUTED_GOTO 19 | COMPUTED_GOTO_1 = 20 | COMPUTED_GOTO_FLAGS = $(eval echo \${COMPUTED_GOTO_$WITH_COMPUTED_GOTO}) 21 | 22 | WITH_THREADING ?= 1 23 | THREADING_0 = -DLIP_SINGLE_THREADED 24 | THREADING_1 = -pthread 25 | THREADING_FLAGS = $(eval echo \${THREADING_$WITH_THREADING}) 26 | 27 | WITH_COVERAGE ?= 0 28 | COVERAGE_0 = 29 | COVERAGE_1 = --coverage 30 | COVERAGE_FLAGS = $(eval echo \${COVERAGE_$WITH_COVERAGE}) 31 | 32 | WITH_DBG_EMBED_RESOURCES ?= 0 33 | DBG_EMBED_RESOURCES_0 = -DLIP_DBG_EMBED_RESOURCES=0 34 | DBG_EMBED_RESOURCES_1 = -DLIP_DBG_EMBED_RESOURCES=1 35 | DBG_FLAGS = $(eval echo \${DBG_EMBED_RESOURCES_$WITH_DBG_EMBED_RESOURCES}) 36 | 37 | OPTIMIZATION_0 = -O3 38 | OPTIMIZATION_1 = -O0 39 | OPTIMIZATION_FLAGS = $(eval echo \${OPTIMIZATION_$WITH_COVERAGE}) 40 | 41 | WITH_LTO ?= 1 42 | LTO_0 = 43 | LTO_1 = -flto 44 | LTO_FLAGS = $(eval echo \${LTO_$WITH_LTO}) 45 | 46 | WITH_UBSAN ?= 0 47 | UBSAN_COMPILE_FLAGS_0 = 48 | UBSAN_COMPILE_FLAGS_1 = -fsanitize=undefined -fno-sanitize-recover=undefined 49 | UBSAN_COMPILE_FLAGS = $(eval echo \${UBSAN_COMPILE_FLAGS_$WITH_UBSAN}) 50 | UBSAN_LINK_FLAGS_0 = 51 | UBSAN_LINK_FLAGS_1 = -fsanitize=undefined -fno-sanitize-recover=undefined 52 | UBSAN_LINK_FLAGS = $(eval echo \${UBSAN_LINK_FLAGS_$WITH_UBSAN}) 53 | 54 | WITH_ASAN ?= 0 55 | ASAN_COMPILE_FLAGS_0 = 56 | ASAN_COMPILE_FLAGS_1 = -fsanitize=address 57 | ASAN_COMPILE_FLAGS = $(eval echo \${ASAN_COMPILE_FLAGS_$WITH_ASAN}) 58 | ASAN_LINK_FLAGS_0 = 59 | ASAN_LINK_FLAGS_1 = -fsanitize=address 60 | ASAN_LINK_FLAGS = $(eval echo \${ASAN_LINK_FLAGS_$WITH_ASAN}) 61 | 62 | COMMON_FLAGS = ${THREADING_FLAGS} ${COVERAGE_FLAGS} ${OPTIMIZATION_FLAGS} ${LTO_FLAGS} 63 | COMPILE_FLAGS = -g -Wall -Wextra -Werror -pedantic -Iinclude ${LIP_CONFIG_EXTRA_FLAGS} ${COMPUTED_GOTO_FLAGS} ${UBSAN_COMPILE_FLAGS} ${ASAN_COMPILE_FLAGS} 64 | C_FLAGS ?= -std=c99 ${COMPILE_FLAGS} ${COMMON_FLAGS} 65 | CPP_FLAGS ?= -std=c++11 ${COMPILE_FLAGS} ${COMMON_FLAGS} 66 | LINK_FLAGS ?= -g ${COMMON_FLAGS} ${UBSAN_LINK_FLAGS} ${ASAN_LINK_FLAGS} 67 | 68 | -import cpp.nu 69 | -include src/dbg-client 70 | 71 | all: tests doc ! live 72 | 73 | doc: ! live 74 | doxygen 75 | 76 | tests: repl-test unit-test ! live 77 | 78 | unit-test: bin/tests ! live 79 | echo "-------------------------------------" 80 | bin/tests --color always 81 | 82 | repl-test: src/tests/repl.sh bin/lip ! live 83 | ${deps} 84 | 85 | test:%: bin/tests ! live << SEED 86 | echo "-------------------------------------" 87 | bin/lip -v 88 | echo "-------------------------------------" 89 | if [ -z "${SEED}" ]; then 90 | bin/tests --color always ${m} 91 | else 92 | bin/tests --color always --seed ${SEED} ${m} 93 | fi 94 | 95 | cover: tests 96 | mkdir -p $@ 97 | gcovr \ 98 | --root . \ 99 | --branches \ 100 | --exclude-unreachable-branches \ 101 | --sort-percentage \ 102 | --delete \ 103 | --filter '.*src/lip/.*' \ 104 | --filter '.*src/repl/.*' \ 105 | --exclude '.*src/lip/vendor/.*' \ 106 | --exclude '.*src/lip/khash_impl.c' \ 107 | --html --html-details \ 108 | --output $@/index.html 109 | 110 | bin/tests: << C_FLAGS CPP_FLAGS CC LIBLIP LIBLIP_EXTRA_FLAGS CLEAR_ENV LINK_FLAGS 111 | ${CLEAR_ENV} 112 | ${NUMAKE} exe:$@ \ 113 | sources="`find src/tests -name '*.cpp' -or -name '*.c'`" \ 114 | c_flags="${C_FLAGS} -g ${LIBLIP_EXTRA_FLAGS} -Isrc" \ 115 | link_flags="${LINK_FLAGS} -lm" \ 116 | libs="${LIBLIP}" 117 | 118 | bin/lip: << C_FLAGS CPP_FLAGS CC LIBLIP LIBLIP_EXTRA_FLAGS CLEAR_ENV 119 | ${CLEAR_ENV} 120 | ${NUMAKE} exe:$@ \ 121 | sources="`find src/repl -name '*.cpp' -or -name '*.c'`" \ 122 | c_flags="${C_FLAGS} ${LIBLIP_EXTRA_FLAGS} -Isrc/dbg -Ideps/linenoise -Ideps/cargo" \ 123 | linker="${CC}" \ 124 | libs="bin/liblinenoise.a bin/libcargo.a bin/libdbg.a bin/libcmp.a ${LIBLIP}" 125 | 126 | bin/liblip.so: << CC C_FLAGS CLEAR_ENV LIP_CONFIG_H 127 | ${CLEAR_ENV} 128 | c_flags="${C_FLAGS} -DLIP_DYNAMIC=1 -DLIP_BUILDING" 129 | ${NUMAKE} --depend ${LIP_CONFIG_H} 130 | ${NUMAKE} dynamic-lib:$@ \ 131 | c_flags="${c_flags}" \ 132 | linker="${CC}" \ 133 | sources="$(find src/lip -name '*.cpp' -or -name '*.c')" 134 | 135 | bin/liblip.a: << C_FLAGS CLEAR_ENV LIP_CONFIG_H 136 | ${CLEAR_ENV} 137 | c_flags="${C_FLAGS} -DLIP_DYNAMIC=0" 138 | ${NUMAKE} --depend ${LIP_CONFIG_H} 139 | ${NUMAKE} static-lib:$@ \ 140 | c_flags="${c_flags}" \ 141 | sources="$(find src/lip -name '*.cpp' -or -name '*.c')" 142 | 143 | include/lip/gen/%.h: src/lip/%.h.in << c_flags C_FLAGS link_flags LINK_FLAGS linker LINKER cc CC ar AR TRAVIS TRAVIS_COMMIT 144 | if test "${TRAVIS}" = "true"; then 145 | export LIP_VERSION="ci-${TRAVIS_COMMIT}" 146 | else 147 | export LIP_VERSION="$(git describe --tags)" 148 | fi 149 | export C_FLAGS="${c_flags:-${C_FLAGS}}" 150 | export LINK_FLAGS="${link_flags:-${LINK_FLAGS}}" 151 | export CC="${cc:-${CC}}" 152 | export AR="${ar:-${AR}}" 153 | export LINKER="${linker:-${LINKER}}" 154 | mkdir -p $(dirname $@) 155 | envsubst < ${deps} > $@ 156 | 157 | bin/libdbg.a: << CLEAR_ENV C_FLAGS DBG_FLAGS WITH_DBG_EMBED_RESOURCES 158 | ${CLEAR_ENV} 159 | if [ "${WITH_DBG_EMBED_RESOURCES}" = 1 ]; then 160 | ${NUMAKE} --depend src/dbg-client/gen 161 | fi 162 | ${NUMAKE} static-lib:$@ \ 163 | c_flags="${C_FLAGS} ${DBG_FLAGS} -Iinclude -Isrc/dbg-client/gen -Ideps/cmp -Wno-unused-function" \ 164 | sources="$(find src/dbg -name '*.cpp' -or -name '*.c')" 165 | 166 | bin/liblinenoise.a: << C_FLAGS CPP_FLAGS CLEAR_ENV ASAN_COMPILE_FLAGS UBSAN_COMPILE_FLAGS LTO_FLAGS 167 | ${CLEAR_ENV} 168 | ${NUMAKE} static-lib:$@ \ 169 | c_flags="-Ideps/linenoise ${ASAN_COMPILE_FLAGS} ${UBSAN_COMPILE_FLAGS} ${LTO_FLAGS}" \ 170 | sources="`find deps/linenoise -name '*.cpp' -or -name '*.c'`" 171 | 172 | bin/libcargo.a: << CLEAR_ENV ASAN_COMPILE_FLAGS UBSAN_COMPILE_FLAGS LTO_FLAGS 173 | ${CLEAR_ENV} 174 | ${NUMAKE} static-lib:$@ \ 175 | c_flags="${LTO_FLAGS} ${ASAN_COMPILE_FLAGS} ${UBSAN_COMPILE_FLAGS}" \ 176 | sources="deps/cargo/cargo.c" 177 | 178 | bin/libcmp.a: << CLEAR_ENV ASAN_COMPILE_FLAGS UBSAN_COMPILE_FLAGS LTO_FLAGS 179 | ${CLEAR_ENV} 180 | ${NUMAKE} static-lib:$@ \ 181 | c_flags="-std=c99 ${LTO_FLAGS} ${ASAN_COMPILE_FLAGS} ${UBSAN_COMPILE_FLAGS}" \ 182 | sources="deps/cmp/cmp.c" 183 | 184 | # Only for vm_dispatch.c, remove -pedantic because we will be using a 185 | # non-standard extension (computed goto) if it is available 186 | $BUILD_DIR/%/src/lip/vm_dispatch.c.ninja: << BUILD_DIR CPPNU_DIR 187 | BUILD_SUBDIR=${m} 188 | build_cfg="${BUILD_DIR}/${BUILD_SUBDIR}/.cfg" 189 | . ${build_cfg} 190 | c_flags=$(echo ${c_flags:-${C_FLAGS}} | sed 's/-pedantic//g') 191 | echo ${c_flags} 192 | ${CPPNU_DIR}/compile c $@ 193 | echo " c_flags = ${c_flags}" >> $@ 194 | --------------------------------------------------------------------------------