version 0.2

try, throw implemented
help screen updates
some code reorganisation
readme and doc updates
This commit is contained in:
2021-06-10 23:26:14 +02:00
parent b50e9ba128
commit f594437b61
7 changed files with 227 additions and 105 deletions

105
Readme.md
View File

@@ -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

View File

@@ -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)

View File

@@ -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 ..)`|||

129
ml.cpp
View File

@@ -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;
return MlValue::nil();
}
linenoise_line_read(line);
input = std::string(line);
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);
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);
MlValue value;
size_t cs_posisition; // unroll stack position to state when try begun to prevent "forgotten" entries in case of exception
write_file_contents(input, code);
} else if (input != "") {
// try block
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();
cs_posisition = MlPerfMon::instance().get_callstack_position();
value = args[0].eval(env);
// catch block
} catch (std::exception &e) {
std::cerr << e.what() << std::endl;
}
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
View File

@@ -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"(

View File

@@ -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();
}

View File

@@ -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: