diff --git a/Readme.md b/Readme.md index c27df5d..291abaa 100644 --- a/Readme.md +++ b/Readme.md @@ -1,15 +1,12 @@ ### BUGS - (read-file "nonexisting/file.csv") shows only "could not open file" - should print filename -- error: the expression `("/usr/local/var/mlisp")` with message "cannot cast" - should add to what is casting - better error reporting..for example ls_dir on non existing dir should prind `pwd` dir ### TODO - add debug support, at least call stack -- add stdtest - to test every functionality -- add parenthesis coloring into linenoise - multiline editing (kilo editor) - execute system command should capture stderr - add some mem stats to benchmark diff --git a/doc/Doc.md b/doc/Doc.md index a407c7b..d858dd3 100644 --- a/doc/Doc.md +++ b/doc/Doc.md @@ -57,7 +57,7 @@ |`(include file)`|Read a file and execute its code|| |`(read-file filename)`|Get the contents of a file|| |`(write-file filename)`|Write a string to a file|| -|`(read-url url [headers])`|Reads URL|Returnd list (status_code content)| +|`(read-url url [headers])`|Reads URL|Returns list (status_code content)| |`(system-cmd command_str)`|Execute system command|| |`(ls-dir ..)`|List a dir|List of directory entries| |`(is-file? ..)`|Returns true if passed filename is a file|| @@ -77,7 +77,7 @@ |`(string-pad str len char rpad_lpad)`||| |`(string-lpad str len char)`|Pad string from start with char to length len|`>>> (string-lpad "0" 10 "x") => "xxxxxxxxx0"`| |`(string-rpad str len char)`|Pad string from righ with char to length len|`>>> (string-rpad "0" 10 "x") => "0xxxxxxxxx"`| -|`(string-split str separator)`||| +|`(string-split str separator)`|Splits string into list by regexp|`>>> (string-split "split me by space" "\s+") => ("split" "me" "by" "space")`| `(string-rltrim str len RKRRKR)`| || `(string-ltrim str)`|Removes " \n\r\t" from the begininfg of str|| `(string-rtrim str)`|Removes " \n\r\t" from the end of str|| @@ -85,24 +85,26 @@ `(string-case str RKRRKR)`||| `(string-upcase str)`|Returns up cased string|`>>> (string-upcase "abcdefghchijklmn") => "ABCDEFGHCHIJKLMN"`| `(string-downcase str)`|Returns down cased string |`>>> (string-downcase "ABCDefghchijklmn") => "abcdefghchijklmn"`| -`(string-join lst sep)`|Returns string created as elements of concatenation of lst elements separated by sep|| -`(endl)`||| +`(string-join lst sep)`|Returns string created as elements of concatenation of lst elements separated by sep|`>>> (string-join ("A" "B" "C" "D") ",") => "A,B,C,D"`| +`(string-len str)`|Returns string length|`>>> (string-len "abcdef") => 6`| +`(string-substr str pos len`|Returns substring from str starting at pos with len. If pos is negative returns substring from the end of string|`>>> (string-substr "ABCD" -2 2) => "CD"`| +`(string-find str lookup pos`|Returns position of lookup in str starting on position. First char index is 0. If not found returns nil|`>>> (string-find " long long int;" "long" 2) => 6`| |`(int value)`|Cast an item to an int|`>>> (int 3.41) => 3`| |`(float value)`|Cast item to a float|`>>> (int 3.41) => 3.14`| |`(string value)`|Cast int or float item to a string|`>>> (string 3.14) => "3.14"`| |`(eval )`|Eval returns the value of the second evaluation|`>>> (eval '(+ 1 2)) => 3`| |`(type e)`|Returns data type of e|`>>> (type (+ 1 2)) => "int"`| -|`(parse ..)`||| -|`(make-list-of size value)`|Makes list with size elements of values|| -|`(make-list size)`|Makes list of nil values with size length|| -|`(empty-list? lst)`|Return true is lst is list with zero elements|| +|`(parse ..)`||`>>> (eval (first (parse "(+ 1 2)"))) => 3`| +|`(make-list-of size value)`|Makes list with size elements of values|`>>> (make-list-of 5 0) => (0 0 0 0 0)`| +|`(make-list size)`|Makes list of nil values with size length|`>>> (make-list 5) => (nil nil nil nil nil)`| +|`(empty-list? lst)`|Return true is lst is list with zero elements|`>>> (empty-list? '()) => 1`| |`(memeber lst item)`|Returns 1 when item is inluded in lst otherwise 0|| |`(uniq list)`|Filter out any duplicates from list|`>>> (uniq '(1 2 2 3 4 5 2 2)) => (1 2 3 4 5)`| |`(flatten list)`||`>>> (flatten '(1 (2 2) 3)) => (1 2 2 3)`| |`(quick-sort-by list cmp)`||| |`(quick-sort list)`|return sorted list|`>>> (quick-sort '(2 4 6 1 7 3 3 9 5)) => (1 2 3 3 4 5 6 7 9)`| -|`(not c)`|Logical NOT of c|| -|`(new n)`|Negates number|| +|`(not c)`|Logical NOT of c|`>>> (not 1) => nil`| +|`(neg n)`|Negates number|`>>> (neg -5) => 5`| |`(is-pos? n)`|Returns true if n is positive number|| |`(is-neg? n)`|Returns true if n is negative number|| |`(dec n)`|Return n decremented by 1|`>>> (dec 5) => 4`| @@ -115,29 +117,6 @@ |`(fifth l)`|Returns fifth element of list|| |`(nth i l)`|Return i-th elemenet of list. First element has index 1|| |`( make-csv list)`|creates csv string from list of lists|(print (make-csv '(("r1c1" "r1c2") ("r2c1" "r2c2")))) -|`(sprintf ..)`||| +|`(sprintf ..)`||`>>> (sprintf "%s, %d, %.2f" (list "string" 1000 3.14)) => "string, 1000, 3.14"`| |`(xx ..)`||| - -``` -(define term-rst-esc "\x1B[0m") -(define term-red-esc '"\x1B[31m") -(define term-green-esc "\x1B[32m") -(define term-yellow-esc "\x1B[33m") -(define term-blue-esc "\x1B[34m") -(define term-magenta-esc "\x1B[35m") -(define term-cyan-esc "\x1B[36m") -(define term-white-esc "\x1B[37m") -(define term-bold-esc "\x1B[1m") -(define term-underline-esc "\x1B[4m") - -(defun term-red (str) (sprintf (+ term-red-esc str term-rst-esc))) -(defun term-green (str) (sprintf (+ term-green-esc str term-rst-esc))) -(defun term-yellow (str) (sprintf (+ term-yellow-esc str term-rst-esc))) -(defun term-blue (str) (sprintf (+ term-blue-esc str term-rst-esc))) -(defun term-magenta (str) (sprintf (+ term-magenta-esc str term-rst-esc))) -(defun term-cyan (str) (sprintf (+ term-cyan-esc str term-rst-esc))) -(defun term-white (str) (sprintf (+ term-white-esc str term-rst-esc))) -(defun term-bold (str) (sprintf (+ term-bold-esc str term-rst-esc))) -(defun term-underline (str) (sprintf (+ term-underline-esc str term-rst-esc))) -``` \ No newline at end of file diff --git a/ml.cpp b/ml.cpp index b479735..87c109f 100644 --- a/ml.cpp +++ b/ml.cpp @@ -40,6 +40,7 @@ #define INTERNAL_ERROR "internal virtual machine error" #define INDEX_OUT_OF_RANGE "index out of range" #define MALFORMED_PROGRAM "malformed program" +#define NOT_IMPLEMENTED_YET_ERROR "not implemented yet" #define STRING_TYPE "string" @@ -1552,9 +1553,6 @@ namespace builtin { throw MlError(args[0], env, INVALID_ARGUMENT); std::vector parsed = ::parse(args[0].as_string()); - // if (parsed.size() == 1) - // return parsed[0]; - // else return MlValue(parsed); return MlValue(parsed); } @@ -1602,11 +1600,46 @@ namespace builtin { eval_args(args, env); if (args.size() != 2) - throw MlError(MlValue("string_case", string_case), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); + throw MlError(MlValue("string-case", string_case), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return MlValue::string(string_lucase(args[0].as_string(), args[1].as_string())); } + MlValue string_len(std::vector args, MlEnvironment &env) { + eval_args(args, env); + + if (args.size() != 1) + throw MlError(MlValue("string-len", string_len), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); + + return MlValue{(long)args[0].as_string().size()}; + } + + MlValue string_substr(std::vector args, MlEnvironment &env) { + eval_args(args, env); + + if (args.size() < 1 || args.size() > 3) + throw MlError(MlValue("string-substr", string_substr), env, args.size() > 3 ? TOO_MANY_ARGS : TOO_FEW_ARGS); + + const std::string& str = args[0].as_string(); + long pos = args.size() > 1 ? args[1].as_int() : 0; + long count = args.size() > 2 ? args[2].as_int() : str.size(); + + return MlValue::string(string_substring(str, pos, count)); + } + + MlValue string_find(std::vector args, MlEnvironment &env) { + eval_args(args, env); + + if (args.size() < 2 || args.size() > 3) + throw MlError(MlValue("string-find", string_find), env, args.size() > 3 ? TOO_MANY_ARGS : TOO_FEW_ARGS); + + size_t start_pos = args.size() > 2 ? args[2].as_int() : 0; + size_t pos = string_find_substr(args[0].as_string(), args[1].as_string(), start_pos); + + return pos == -1 ? MlValue::nil() : MlValue((long)pos); + } + + // trims characters " \n\r\t" from left or right or both ends of a string MlValue string_rltrim(std::vector args, MlEnvironment &env) { eval_args(args, env); @@ -1966,6 +1999,9 @@ MlValue MlEnvironment::get(const std::string &name) const { if (name == "string-pad") return MlValue("string-pad", builtin::string_pad); if (name == "string-rltrim") return MlValue("string-rltrim", builtin::string_rltrim); if (name == "string-case") return MlValue("string-case", builtin::string_case); + if (name == "string-len") return MlValue("string-len", builtin::string_len); + if (name == "string-substr") return MlValue("string-substr", builtin::string_substr); + if (name == "string-find") return MlValue("string-find", builtin::string_find); // Casting operations if (name == "int") return MlValue("int", builtin::cast_to_int); diff --git a/ml_string.cpp b/ml_string.cpp index ac7e9d6..08cf4c0 100644 --- a/ml_string.cpp +++ b/ml_string.cpp @@ -77,3 +77,28 @@ std::string string_padd(const std::string &str, int pad_len, char fill_char, boo else return std::string(pad_len - str.size(), fill_char) + str; } + + +std::string string_substring(const std::string & str, long pos, long count) { + size_t start_pos = pos; + + if (pos < 0) { + start_pos = str.size() - abs(pos); + } + + if ( (start_pos >= str.size()) || (count < 1) || (start_pos < 0) ) { + throw std::invalid_argument("Invalid parameter(s) for string-substr."); + } + + return str.substr(start_pos, count); +} + +size_t string_find_substr(const std::string & str, const std::string & pattern, long pos) { + if (pos >= str.size()) { + throw std::invalid_argument("Invalid parameter(s) for string-find."); + } + + size_t p = str.find(pattern, pos); + + return p != str.npos ? p : -1; +} \ No newline at end of file diff --git a/ml_string.h b/ml_string.h index 7710a7b..64bb8ae 100644 --- a/ml_string.h +++ b/ml_string.h @@ -18,4 +18,8 @@ std::string string_lucase(std::string s, const std::string &strcase); std::string string_trim(std::string s, const std::string &chars_to_trim, const std::string &rltrim); -std::string string_padd(const std::string & str, int pad_len, char fill_char, bool from_right); \ No newline at end of file +std::string string_padd(const std::string & str, int pad_len, char fill_char, bool from_right); + +std::string string_substring(const std::string & str, long pos, long count); + +size_t string_find_substr(const std::string & str, const std::string & pattern, long pos); \ No newline at end of file diff --git a/stdlib/ut.lsp b/stdlib/ut.lsp index fa8359e..f52cf26 100644 --- a/stdlib/ut.lsp +++ b/stdlib/ut.lsp @@ -11,6 +11,8 @@ (defun ut::assert-false (test) (ut::assert-equal nil test)) +(defun ut::assert-nil (test) (ut::assert-equal nil test)) + (defun ut::define-test (name exp_list) (set! ut::tests_list (push ut::tests_list (list name exp_list)))) diff --git a/tests/test.lsp b/tests/test.lsp index 1ea1752..5714065 100644 --- a/tests/test.lsp +++ b/tests/test.lsp @@ -54,6 +54,19 @@ (ut::define-test "result of (string-split \"split me by space\" \"\\s+\")" '(ut::assert-equal '("split" "me" "by" "space") (string-split "split me by space" "\\s+"))) (ut::define-test "result of (string-upcase \"abcABCD\")" '(ut::assert-equal "ABCABCD" (string-upcase "abcABCD"))) (ut::define-test "result of (string-downcase \"abcABCD\")" '(ut::assert-equal "abcabcd" (string-downcase "abcABCD"))) +(ut::define-test "result of (string-len \"abcdef\")" '(ut::assert-equal 6 (string-len "abcdef"))) + +(ut::define-test "result of (string-substr \"ABCDEF\")" '(ut::assert-equal "ABCDEF" (string-substr "ABCDEF"))) +(ut::define-test "result of (string-substr \"ABCDEF\" 1)" '(ut::assert-equal "BCDEF" (string-substr "ABCDEF" 1))) +(ut::define-test "result of (string-substr \"ABCDEF\" 2 3)" '(ut::assert-equal "CDE" (string-substr "ABCDEF" 2 3))) +(ut::define-test "result of (string-substr \"ABCDEF\"4 42)" '(ut::assert-equal "EF" (string-substr "ABCDEF" 4 42))) +(ut::define-test "result of (string-substr \"ABCDEF\" -2 2)" '(ut::assert-equal "EF" (string-substr "ABCDEF" -2 2))) + +(ut::define-test "result of (string-find \" long long int;\" \"long\")" '(ut::assert-equal 1 (string-find " long long int;" "long"))) +(ut::define-test "result of (string-find \" long long int;\" \"long\" 2)" '(ut::assert-equal 6 (string-find " long long int;" "long" 2))) +(ut::define-test "result of (string-find \" long long int;\" \" \")" '(ut::assert-equal 0 (string-find " long long int;" " "))) +(ut::define-test "result of (string-find \" long long int;\" \"o\")" '(ut::assert-equal 2 (string-find " long long int;" "o"))) +(ut::define-test "result of (string-find \" long long int;\" \"float\")" '(ut::assert-nil (string-find " long long int;" "float"))) (ut::define-test "result of (write-file \"/tmp/file\" \"write-file test\")" '(ut::assert-equal 1 (write-file "/tmp/file" "write-file test\n"))) (ut::define-test "result of (is-file? \"/tmp/file\")" '(ut::assert-true (is-file? "/tmp/file")))