diff --git a/Readme.md b/Readme.md index c256523..a5d5aa2 100644 --- a/Readme.md +++ b/Readme.md @@ -1,41 +1,35 @@ -### BUGS -- (read-file "nonexisting/file.csv") shows only "could not open file" - should print filename -- better error reporting..for example ls_dir on non existing dir should prind `pwd` dir +## ml +is a small lisp interpreter based on Adam McDaniel's wisp interpreter (https://github.com/adam-mcdaniel/wisp). It's main purpose is to learn something about more recent c++ -### TODO -- add debug support, at least call stack -- multiline editing (kilo editor) -- execute system command should capture stderr -- add some mem stats to benchmark -- create pastebin like web using ml -#### Code -- documentation -- rename constants in ml_profiler.h -- replace to_string macro in ml.cpp -- add url of source/inspiration to clib/*.cpp -- add instrumentation (time, nr of evals, num of atoms, debug info, debug environment etc) -#### Language -- support for "t" symbol as a true -- support for exceptions -- string functions - - compare - needed for sorting, cmp ignore case - - regexp functions -- date support - - decode-universal-time -- env functions - - get-env, set-env; set-env cannot be implemented in stdlib.lsp, because popen is in fact subshell -- add include-stdlib function for other libs in stdlib dir (during startup stdlib.lsp is loaded only) -- syntax highlighting do VS Code +### Example of use +``` +cat < /tmp/qs.lsp +(defun qs (l) + (if (<= (len l) 1) + l + (do + (define pivot (first l)) + (+ + (qs (filter (lambda (n) (> pivot n)) l)) + (list pivot) + (qs (tail (filter (lambda (n) (<= pivot n)) l))) + )) + )) -#### Performance -- define is one of most frequent callee, when in scope with very few vars, lookup sequentially -- first, second are often called -> implement in c++ -- push_back - repeatedly without reserving size -- range - with for(int i...) and reserving result size can be 3times faster on (range 1 10000) -- mini_sprintf - unnecesary copying between vector and list -- (do, scope ..) repeatedly assign to acc +(print (qs '(10 9 8 7 6 5 4 3 2 10))) +EOT + +ml -f /tmp/qs.lsp +``` +#### Interactive mode +``` +ml +``` + +#### Documentation +see /doc/Doc.md #### Compile ``` @@ -58,17 +52,48 @@ utils/local_install.sh ``` -### Example of use -``` -ml -f tests/test.lsp -``` +### KNOWNN BUGS + + +### TODO +- add debug support, at least call stack +- multiline editing (see kilocpp editor) +- execute system command should capture stderr +- add some mem stats to benchmark + +#### Code +- add documentation +- rename constants in ml_profiler.h +- replace to_string macro in ml.cpp +- add instrumentation (time, nr of evals, num of atoms, debug info, debug environment etc) + +#### Language +- support for "t" symbol as a true +- support for exceptions +- string functions + - compare - needed for sorting, cmp ignore case + - regexp match, regexp tokens +- date support + - decode-universal-time (http://www.lispworks.com/documentation/HyperSpec/Body/f_dec_un.htm) +- env functions + - get-env, set-env; set-env cannot be implemented in stdlib.lsp, because popen is in fact subshell +- add include-stdlib function for other libs in stdlib dir (during startup stdlib.lsp is loaded only) +- syntax highlighting do VS Code + +#### Performance +- define is one of most frequent callee, when in scope with very few vars, lookup sequentially +- first, second are often called -> implement in c++ +- push_back - repeatedly without reserving size +- range - with for(int i...) and reserving result size can be 3times faster on (range 1 10000) +- mini_sprintf - unnecesary copying between vector and list +- (do, scope ..) repeatedly assign to acc + ### Links -https://github.com/adam-mcdaniel/wisp -https://github.com/dropbox/json11 +https://github.com/adam-mcdaniel/wisp +https://github.com/dropbox/json11 https://github.com/antirez/linenoise - #### read stdout and stderr from popen -https://stackoverflow.com/questions/478898/how-do-i-execute-a-command-and-get-the-output-of-the-command-within-c-using-po +https://stackoverflow.com/questions/478898how-do-i-execute-a-command-and-get-the-output-of-the-command-within-c-using-po diff --git a/debug.lsp b/debug.lsp index ab62f3e..4a50270 100644 --- a/debug.lsp +++ b/debug.lsp @@ -1,6 +1,20 @@ -(write-file "/tmp/f.txt" "line 1\nline 2\nline3") -(read-file-lines "/tmp/f.txt" (lambda (ln) (print ln))) +(define fc 5) +(define sc 5) -(define counter (lambda (ln) (do (set! ii (+ ii 1)) t ii)))) -(define ii 0) -(print (read-file-lines "/tmp/f.txt" counter)) +(define first_tid (thread-create (while (> fc 0) (do (thread-under-lock "ilock" (do (set! fc (dec fc))(print 1))) (thread-sleep 500))))) +(define second_tid (thread-create (while (> sc 0) (do (thread-under-lock "ilock" (do (set! sc (dec sc))(print 2))) (thread-sleep 750))))) + +(print "first thread id:" first_tid) +(print "second thread id:" second_tid) +(threads-join) +(print "ok") + +(try + (ls-dir "nonexisting/dir") + (print "dir not exists")) + +(try + (throw "my-exception") + (print "exception caught:" ml-exception)) + +(/ 1 0) \ No newline at end of file diff --git a/doc/Doc.md b/doc/Doc.md index 3435220..f5355b4 100644 --- a/doc/Doc.md +++ b/doc/Doc.md @@ -1,6 +1,24 @@ +## Command line options +|option|Description| +|:-|-| +|-h|Prints help| +|-b|Skips loadin of std lib| +|-c code|Runs given code| +|-f source_file ..|Executes code in files| +|-i|Runs REPL| +|-p|Prints profile info at the end| +|-v|Prints version string| + + +## REPL commands +|Command|Description| +|:-|-| +|!q, !quit|Quit REPL| +|!e, !env|Display environment| +|!x, !export|Exports command from this session. Asks for filename to save| + ## Syntax and Special Forms - |Special Form|Argument Evaluations|Purpose| |:-|-|-| |`(if cond a b)`|`if` only evaluates its `cond` argument. If `cond` is truthy (non-zero), then `a` is evaluated. Otherwise, `b` is evaluated.|This special form is the main method of control flow.| @@ -123,12 +141,17 @@ |`(inc n)`|Return n incremented by 1|`>>> (inc 5) => 6`| |`(sleep time)`|Pauses execution for time interval of seconds|| |`(get-env var)`|Return environment variable var|`>>> (get-env "HOME") => "/Users/vaclavt"`| -|`(second l)`|Returns second element of list|| -|`(third l)`|Returns third element of list|| -|`(fourth l)`|Returns fourth element of list|| -|`(fifth l)`|Returns fifth element of list|| -|`(nth i l)`|Return i-th elemenet of list. First element has index 1|| +|`(second list)`|Returns second element of list|`>>> (second '(1 2 3 4 5 6 7)) => 2`| +|`(third list)`|Returns third element of list|`>>> (third '(1 2 3 4 5 6 7)) => 3`| +|`(fourth list)`|Returns fourth element of list|`>>> (fourth '(1 2 3 4 5 6 7)) => 4`| +|`(fifth list)`|Returns fifth element of list|`>>> (fifth '(1 2 3 4 5 6 7)) => 5`| +|`(nth i list)`|Return i-th elemenet of list. First element has index 1|`>>> (nth 7 '(1 2 3 4 5 6 7)) => 7`| |`( make-csv list)`|creates csv string from list of lists|(print (make-csv '(("r1c1" "r1c2") ("r2c1" "r2c2")))) |`(sprintf ..)`||`>>> (sprintf "%s, %d, %.2f" (list "string" 1000 3.14)) => "string, 1000, 3.14"`| - +|`(thread-create code..)`|Creates new thread, starts evalueating code and returns immediatelly thread id|| +|`(thread-under-lock lockname code)`|Acquire lock with lockname and eval code. ilock currently is only allowd lockname|| +|`(thread-sleep milisecons)`|Sleeps thread for given amount of miliseconds|| +|`(threads-join)`|Wait for all running threads to finish|| +|`(try block catch_block [finally_block])`|Evaluates block and if an exception is thrown, evaluates catch_block.Eventually in both cases evals finally_block. Return evaluated last expression from block if no exception or last expression from catch_block, if exception is thrown from block. Variable ml-exception in catch block is available with astring describing exception|| +|`(throw-exception exp_desc)`|Throws an exception with exp_desc describing what happened || |`(xx ..)`||| diff --git a/ml.cpp b/ml.cpp index a962859..005d198 100644 --- a/ml.cpp +++ b/ml.cpp @@ -1351,6 +1351,11 @@ namespace builtin { if (args.size() != 2) throw MlError(MlValue("/", divide), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); + + if ((args[1].get_type_name() == "int" && args[1] == 0l) || + (args[1].get_type_name() == "float" && args[1] == 0.0)) + throw std::invalid_argument("divide by zero."); + return args[0] / args[1]; } @@ -1846,17 +1851,6 @@ namespace builtin { return acc; } - MlValue threads_join(std::vector args, MlEnvironment &env) { - if (args.size() != 0) - throw MlError(MlValue("threads_join", threads_join), env, TOO_MANY_ARGS); - - std::lock_guard lockGuard(register_mutex); - for (auto &th : threads_register) - if (th.joinable()) th.join(); - - return MlValue::nil(); - } - MlValue thread_sleep(std::vector args, MlEnvironment &env) { eval_args(args, env); @@ -1866,48 +1860,51 @@ namespace builtin { std::this_thread::sleep_for(std::chrono::milliseconds(args[0].as_int())); return args[0]; } -} -void repl(MlEnvironment &env) { - std::string code; - std::string input; - MlValue tmp; - std::vector parsed; + MlValue threads_join(std::vector args, MlEnvironment &env) { + if (args.size() != 0) + throw MlError(MlValue("threads-join", threads_join), env, TOO_MANY_ARGS); - setup_linenoise(env); - - while (true) { - char *line = linenoise(">>> "); - if (line == nullptr) break; - - linenoise_line_read(line); - - input = std::string(line); - - if (input == "!quit" || input == "!q") - break; - else if (input == "!env" || input == "!e") - std::cout << env << std::endl; - else if (input == "!export" || input == "!x") { - std::cout << "File to export to: "; - std::getline(std::cin, input); - - write_file_contents(input, code); - } else if (input != "") { - try { - tmp = run(input, env); - std::cout << " => " << tmp.debug() << std::endl; - code += input + "\n"; - } catch (MlError &e) { - std::cerr << e.description() << std::endl; - MlPerfMon::instance().clear_callstack(); - } catch (std::exception &e) { - std::cerr << e.what() << std::endl; - } - } + std::lock_guard lockGuard(register_mutex); + for (auto &th : threads_register) + if (th.joinable()) th.join(); + + return MlValue::nil(); + } + + + MlValue try_block(std::vector args, MlEnvironment &env) { + if (args.size() < 2 || args.size() > 3) + throw MlError(MlValue("try", try_block), env, args.size() > 3 ? TOO_MANY_ARGS : TOO_FEW_ARGS); + + MlValue value; + size_t cs_posisition; // unroll stack position to state when try begun to prevent "forgotten" entries in case of exception + + // try block + try { + cs_posisition = MlPerfMon::instance().get_callstack_position(); + value = args[0].eval(env); + // catch block + } catch (std::exception &e) { + MlPerfMon::instance().restore_callstack_position(cs_posisition); + env.set("ml-exception", MlValue::string(e.what())); + value = args[1].eval(env); + } + // finally block + if (args.size() == 3) { + args[2].eval(env); + } + + return value; + } + + MlValue throw_exception(std::vector args, MlEnvironment &env) { + if (args.size() != 1) + throw MlError(MlValue("throw", throw_exception), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); + + throw std::runtime_error(args[0].as_string()); } - close_linenoise(); } void load_std_lib(MlEnvironment &env) { @@ -2037,6 +2034,10 @@ MlValue MlEnvironment::get(const std::string &name) const { if (name == "thread-sleep") return MlValue("thread-sleep", builtin::thread_sleep); if (name == "threads-join") return MlValue("threads-join", builtin::threads_join); + // Exceptions + if (name == "try") return MlValue("try", builtin::try_block); + if (name == "throw") return MlValue("throw", builtin::throw_exception); + std::map::const_iterator itr = defs.find(name); if (itr != defs.end()) return itr->second; else if (parent_scope != nullptr) { @@ -2061,6 +2062,50 @@ std::vector MlEnvironment::get_lambdas_list() const { return lambdas; } + +void repl(MlEnvironment &env) { + std::string code; + std::string input; + MlValue tmp; + std::vector parsed; + + setup_linenoise(env); + + while (true) { + char *line = linenoise(">>> "); + if (line == nullptr) break; + + linenoise_line_read(line); + + input = std::string(line); + + if (input == "!quit" || input == "!q") + break; + else if (input == "!env" || input == "!e") + std::cout << env << std::endl; + else if (input == "!export" || input == "!x") { + std::cout << "File to export to: "; + std::getline(std::cin, input); + + write_file_contents(input, code); + } else if (input != "") { + try { + tmp = run(input, env); + std::cout << " => " << tmp.debug() << std::endl; + code += input + "\n"; + } catch (MlError &e) { + std::cerr << e.description() << std::endl; + MlPerfMon::instance().clear_callstack(); + } catch (std::exception &e) { + std::cerr << e.what() << std::endl; + } + } + } + + close_linenoise(); +} + + bool cmdOptionExists(char **begin, char **end, const std::string &option) { return std::find(begin, end, option) != end; } std::vector getCmdOption(char *argv[], int argc, const std::string &option) { @@ -2092,7 +2137,7 @@ int main(int argc, char *argv[]) { } // help if (cmdOptionExists(argv, argv + argc, "-h")) { - std::cout << "Usage:\n\t-h print this help\n\t-f source_file - executes code in file\n\t-c code - runs passed code\n\t-i runs repl\n\t-b skip stdlib loading\n\t-p prints profile info at the end\n\t-v prints version string\n\n"; + std::cout << "Usage:\n\t-h print this help\n\t-b skip stdlib loading\n\t-c code - runs code passed on command line\n\t-f source_file - executes code in file\n\t-i runs repl\n\t-p prints profile info at the end\n\t-v prints version string\n\n"; return 0; } // version diff --git a/ml.h b/ml.h index 8d81575..0aa137a 100644 --- a/ml.h +++ b/ml.h @@ -7,7 +7,7 @@ #include #include -const std::string VERSION = "ml 0.1 (" __DATE__ " " __TIME__ ")"; +const std::string VERSION = "ml 0.2 (" __DATE__ " " __TIME__ ")"; const std::string STDLIB_LOADER = R"( diff --git a/ml_profiler.cpp b/ml_profiler.cpp index bb88149..43f2ef7 100644 --- a/ml_profiler.cpp +++ b/ml_profiler.cpp @@ -58,6 +58,16 @@ std::string MlPerfMon::callstack() const { return cs.append("\n"); } +size_t MlPerfMon::get_callstack_position() { + return call_stack.size(); +} + +void MlPerfMon::restore_callstack_position(size_t to_position) { + if (to_position < call_stack.size()) { + call_stack.resize(to_position); + } +} + void MlPerfMon::clear_callstack() { call_stack.empty(); } diff --git a/ml_profiler.h b/ml_profiler.h index a4e45de..d374458 100644 --- a/ml_profiler.h +++ b/ml_profiler.h @@ -32,8 +32,13 @@ public: void add_method_call(const std::string &method); void end_method_call(); + std::string callstack() const; + + size_t get_callstack_position(); + void restore_callstack_position(size_t to_position); void clear_callstack(); + void print_results(); private: