version 0.2
try, throw implemented help screen updates some code reorganisation readme and doc updates
This commit is contained in:
105
Readme.md
105
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 <<EOT > /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,10 +52,42 @@ 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
|
||||
@@ -69,6 +95,5 @@ 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
|
||||
|
||||
24
debug.lsp
24
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)
|
||||
37
doc/Doc.md
37
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 ..)`|||
|
||||
|
||||
143
ml.cpp
143
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<MlValue> args, MlEnvironment &env) {
|
||||
if (args.size() != 0)
|
||||
throw MlError(MlValue("threads_join", threads_join), env, TOO_MANY_ARGS);
|
||||
|
||||
std::lock_guard<std::mutex> lockGuard(register_mutex);
|
||||
for (auto &th : threads_register)
|
||||
if (th.joinable()) th.join();
|
||||
|
||||
return MlValue::nil();
|
||||
}
|
||||
|
||||
MlValue thread_sleep(std::vector<MlValue> 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<MlValue> parsed;
|
||||
MlValue threads_join(std::vector<MlValue> args, MlEnvironment &env) {
|
||||
if (args.size() != 0)
|
||||
throw MlError(MlValue("threads-join", threads_join), env, TOO_MANY_ARGS);
|
||||
|
||||
setup_linenoise(env);
|
||||
std::lock_guard<std::mutex> lockGuard(register_mutex);
|
||||
for (auto &th : threads_register)
|
||||
if (th.joinable()) th.join();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
return MlValue::nil();
|
||||
}
|
||||
|
||||
|
||||
MlValue try_block(std::vector<MlValue> 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<MlValue> 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<std::string, MlValue>::const_iterator itr = defs.find(name);
|
||||
if (itr != defs.end()) return itr->second;
|
||||
else if (parent_scope != nullptr) {
|
||||
@@ -2061,6 +2062,50 @@ std::vector<std::string> MlEnvironment::get_lambdas_list() const {
|
||||
return lambdas;
|
||||
}
|
||||
|
||||
|
||||
void repl(MlEnvironment &env) {
|
||||
std::string code;
|
||||
std::string input;
|
||||
MlValue tmp;
|
||||
std::vector<MlValue> 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<std::string> 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
|
||||
|
||||
2
ml.h
2
ml.h
@@ -7,7 +7,7 @@
|
||||
#include <sstream>
|
||||
#include <exception>
|
||||
|
||||
const std::string VERSION = "ml 0.1 (" __DATE__ " " __TIME__ ")";
|
||||
const std::string VERSION = "ml 0.2 (" __DATE__ " " __TIME__ ")";
|
||||
|
||||
const std::string STDLIB_LOADER =
|
||||
R"(
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user