From 3f46ae9c94055175457824d29fbe23a9641b28bf Mon Sep 17 00:00:00 2001 From: VaclavT Date: Tue, 9 Mar 2021 22:57:16 +0100 Subject: [PATCH] sprintf added, terminal colors small code fixes and rearangements --- .vscode/settings.json | 69 +++++++++++++- CMakeLists.txt | 3 +- clib/csvparser.h | 2 + clib/printf.cpp | 216 ++++++++++++++++++++++++++++++++++++++++++ clib/printf.h | 12 +++ clib/sslclient.h | 1 + debug.lsp | 38 +++++++- ml.cpp | 26 +++-- stdlib/terminal.lsp | 22 +++++ tests/test.lsp | 16 ++++ 10 files changed, 392 insertions(+), 13 deletions(-) create mode 100644 clib/printf.cpp create mode 100644 clib/printf.h create mode 100644 stdlib/terminal.lsp diff --git a/.vscode/settings.json b/.vscode/settings.json index b4d8c35..b04b0af 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,70 @@ { - "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools" + "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", + "files.associations": { + "__bit_reference": "cpp", + "__config": "cpp", + "__debug": "cpp", + "__errc": "cpp", + "__functional_base": "cpp", + "__hash_table": "cpp", + "__locale": "cpp", + "__mutex_base": "cpp", + "__node_handle": "cpp", + "__nullptr": "cpp", + "__split_buffer": "cpp", + "__string": "cpp", + "__threading_support": "cpp", + "__tree": "cpp", + "__tuple": "cpp", + "algorithm": "cpp", + "array": "cpp", + "atomic": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "chrono": "cpp", + "cmath": "cpp", + "complex": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "exception": "cpp", + "optional": "cpp", + "fstream": "cpp", + "functional": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "ios": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "iterator": "cpp", + "limits": "cpp", + "locale": "cpp", + "map": "cpp", + "memory": "cpp", + "mutex": "cpp", + "new": "cpp", + "ostream": "cpp", + "ratio": "cpp", + "regex": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "string": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "typeinfo": "cpp", + "unordered_map": "cpp", + "utility": "cpp", + "vector": "cpp" + } } \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index f2cf375..1123fc8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,7 +28,8 @@ set(SOURCE ml_string.cpp clib/csvparser.cpp clib/sslclient.cpp - clib/json11.cpp ml_date.h ml_date.cpp) + clib/json11.cpp + clib/printf.cpp) add_executable(${PROJECT_NAME} ${SOURCE}) diff --git a/clib/csvparser.h b/clib/csvparser.h index 2507ebf..2559920 100644 --- a/clib/csvparser.h +++ b/clib/csvparser.h @@ -1,4 +1,6 @@ +#pragma once + #include "../ml.h" #include diff --git a/clib/printf.cpp b/clib/printf.cpp new file mode 100644 index 0000000..3cfe244 --- /dev/null +++ b/clib/printf.cpp @@ -0,0 +1,216 @@ +#include "printf.h" + +#include +#include +#include +#include +#include + + +std::string mini_sprintf_format(bool left_align, bool sign, bool space_on_left, bool padding_by_zero, int width, int precision, int length, char specifier, const MlValue &value) { + std::string s; + bool is_positive = false; + + if (specifier == 's') { + return value.as_string(); + } + if (specifier == 'i' || specifier == 'd') { + int ival = value.as_int(); + is_positive = ival >= 0; + + s = std::to_string(ival); + } else if (specifier == 'f' || specifier == 'e') { + double dval = value.as_float(); + is_positive = dval >= 0; + + std::ostringstream stream_str; + if (specifier == 'f') + stream_str << std::fixed; + else + stream_str << std::scientific; + + if (precision >= 0) + stream_str << std::setprecision(precision); + + stream_str << dval; + s = stream_str.str(); // TODO ?? + } + + if (width > -1 && s.size() < width) { + int new_width = width - (sign && is_positive ? 1 : 0); // spce for + sign + if (s.size() < new_width) { + char padd = padding_by_zero ? '0' : ' '; + if (left_align) { + s.insert(0, new_width - s.size(), padd); + } else { + s.append(new_width - s.size(), ' '); // cannot append 0 because its different number then + } + } + } + if (sign && is_positive) { + s.insert(0, 1, '+'); + } + if (space_on_left) { + s.insert(0, 1, ' '); + } + + return s; +} + +std::string mini_sprintf(const std::string &format_str, const std::vector ¶meters) { + int arg_position = 0; + char c, c1; + std::string buf; + std::string output_str; + + std::string::const_iterator si; + for (si = format_str.begin(); si != format_str.end(); ++si) { + c = *si; + + // formating directives + if (c == '%') { + if (++si >= format_str.end()) { + return output_str; // end of string, invalid % on last pos + } + + c1 = *si; + switch (c1) { + case '%': + output_str += c1; + break; + case '-': + case '+': + case ' ': + case '#': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '.': + case 'i': + case 'd': + case 'f': + case 's': + bool left_align = false; + bool sign = false; + bool space_on_left = false; + bool padding_by_zero = false; + int width = -1; + int precision = -1; + int length = -1; + // http://www.cplusplus.com/reference/cstdio/printf/ + // https://www.menie.org/georges/embedded/small_printf_source_code.html + // + // %[flags][width][.precision][length]specifier + + // flags: - + \_ # 0 + while ((*si == '-' || *si == '+' || *si == ' ' || *si == '#' || *si == '0') && si < format_str.end()) { + switch (*si) { + case '-': + left_align = true; + break; + case '+': + sign = true; + break; + case ' ': + space_on_left = true; + break; + case '#': + break; + case '0': + padding_by_zero = true; + left_align = true; + break; + }; + si++; + } + // width + if (si >= format_str.end()) + return output_str; // invalid end of string + while (*si >= '0' && *si <= '9' && si < format_str.end()) { + if (width == -1) { + width = 0; + } + width = width * 10 + (*si - '0'); + si++; + } + // precision + if (si >= format_str.end()) + return output_str; // invalid end of string + if (*si == '.') { + precision = 0; + if (++si >= format_str.end()) + return output_str; // invalid end of string + while (*si >= '0' && *si <= '9' && si < format_str.end()) { + precision = precision * 10 + (*si - '0'); + si++; + } + } + // length + // specifier + if (si >= format_str.end()) + return output_str; // invalid end of string + if (*si == 'i' || *si == 'd' || *si == 'f' || *si == 's') { // TODO more specifiers + std::string s = mini_sprintf_format(left_align, sign, space_on_left, padding_by_zero, width, precision, length, *si, parameters[arg_position]); + arg_position++; + output_str += s; + } else { + output_str += "UNKNOWN FORMAT SPECIFIER"; + } + }; + + // escaping sequences + } else if (c == '\\') { + if (++si >= format_str.end()) { + return output_str; // end of string, invalid % on last pos + } + + c1 = *si; + switch (c1) { + case 't': + output_str += '\t'; + break; + case 'r': + output_str += '\r'; + break; + case 'n': + output_str += '\n'; + break; + case 'x': // hex ie \x1b + if (si + 2 >= format_str.end()) return output_str; // end of string, invalid hex constant + buf.clear(); + buf.push_back(*(si + 1)); + buf.push_back(*(si + 2)); + output_str.push_back((char)std::strtol( &buf[0], 0, 16)); + si += 2; + break; + case '0': // octal ie "\033" + if (si + 2 >= format_str.end()) return output_str; // end of string, invalid octal constant + buf.clear(); + buf.push_back(*(si + 1)); + buf.push_back(*(si + 2)); + output_str.push_back((char)std::strtol( &buf[0], 0, 8)); + // TODO maybe octal constant has 3 bytes + // if (si + 2 >= format_str.end()) return output_str; // end of string, invalid octal or hex constant + // buf.push_back(*(si + 3)); + // si += 3; + si += 2; + break; + default: + output_str += c1; + }; + + // normal characters + } else { + output_str += c; + } + } + + return output_str; +} \ No newline at end of file diff --git a/clib/printf.h b/clib/printf.h new file mode 100644 index 0000000..b02e15e --- /dev/null +++ b/clib/printf.h @@ -0,0 +1,12 @@ +#pragma once + +#include "../ml.h" + +#include +#include +#include +#include +#include + + +std::string mini_sprintf(const std::string &format_str, const std::vector ¶meters); diff --git a/clib/sslclient.h b/clib/sslclient.h index 36a7d48..8630d37 100644 --- a/clib/sslclient.h +++ b/clib/sslclient.h @@ -1,3 +1,4 @@ +#pragma once #include #include diff --git a/debug.lsp b/debug.lsp index 5a05629..4454ad6 100644 --- a/debug.lsp +++ b/debug.lsp @@ -1,3 +1,35 @@ -(define json_list (parse-json "{\"k1\":\"v1\", \"k2\":42, \"k3\":[\"a\",123,true,false,null]}")) -(print json_list) -(for x json_list (print x)) \ No newline at end of file + +; (print (sprintf "\033[31mred text")) +; (print (sprintf "\x1B[31mred text")) + +(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))) + +(print (term-red "red text")) +(print (term-green "green text")) +(print (term-yellow "yellow text")) +(print (term-blue "blue text")) +(print (term-magenta "magenta text")) +(print (term-cyan "cyan text")) +(print (term-white "white text")) +(print (term-bold "bold text")) +(print (term-underline "underline text")) +(print "normal text") diff --git a/ml.cpp b/ml.cpp index c3f6a0b..0b10968 100644 --- a/ml.cpp +++ b/ml.cpp @@ -7,6 +7,7 @@ #include "clib/csvparser.h" #include "clib/sslclient.h" #include "clib/json11.h" +#include "clib/printf.h" #include @@ -176,7 +177,7 @@ int MlValue::as_int() const { // Get this item's floating point value double MlValue::as_float() const { - return cast_to_int().stack_data.f; + return cast_to_float().stack_data.f; } // Get this item's string value @@ -728,7 +729,7 @@ MlValue parse(std::string &s, int &ptr) { skip_whitespace(s, ptr); if (s[ptr] == ';') - throw std::runtime_error("this should never happen!"); + throw std::runtime_error(INTERNAL_ERROR); if (s == "") { @@ -1084,8 +1085,7 @@ namespace builtin { // TODO add support for more params specifying options if (args.size() != 1) - throw MlError(MlValue("parse-json", parse_json), env, - args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); + throw MlError(MlValue("parse-json", parse_json), env, args.size() > 1 ? TOO_MANY_ARGS : TOO_FEW_ARGS); std::string str = args[0].as_string(); std::string err; @@ -1099,7 +1099,7 @@ namespace builtin { return json.ivalualize(); } - // get current time as secs from epoch + // Get current time as secs from epoch MlValue get_universal_time(std::vector args, MlEnvironment &env) { eval_args(args, env); @@ -1113,8 +1113,7 @@ namespace builtin { eval_args(args, env); if (args.size() != 2) - throw MlError(MlValue("date_to_str", date_to_str), env, - args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); + throw MlError(MlValue("date_to_str", date_to_str), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); return MlValue::string(date_to_string(args[0].as_int(), args[1].as_string())); } @@ -1498,7 +1497,7 @@ namespace builtin { eval_args(args, env); if (args.size() != 4) - throw MlError(MlValue("regex_search", regex_search), env, args.size() > 4 ? TOO_MANY_ARGS : TOO_FEW_ARGS); + throw MlError(MlValue("string_pad", string_pad), env, args.size() > 4 ? TOO_MANY_ARGS : TOO_FEW_ARGS); // TODO validate len > 0 etc return MlValue::string(string_padd(args[0].as_string(), args[1].as_int(), args[2].as_string()[0], (args[3].as_string()=="rpad"))); @@ -1522,6 +1521,16 @@ namespace builtin { return MlValue::string(args[0].debug()); } + MlValue sprintf(std::vector args, MlEnvironment &env) { + eval_args(args, env); + + std::string result; + if (args.size() < 1 || args.size() > 2) + throw MlError(MlValue("sprintf", sprintf), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS); + + return MlValue::string(mini_sprintf(args[0].as_string(), args.size()==2 ? args[1].as_list() : std::vector {} )); + } + // >>> (map (lambda (x) (+ x 10)) '(1 2 3 4 5 6)) // => (11 12 13 14 15 16) MlValue map_list(std::vector args, MlEnvironment &env) { @@ -1741,6 +1750,7 @@ MlValue MlEnvironment::get(const std::string &name) const { // String operations if (name == "debug") return MlValue("debug", builtin::debug); + if (name == "sprintf") return MlValue("sprintf", builtin::sprintf); if (name == "display") return MlValue("display", builtin::display); if (name == "replace") return MlValue("replace", builtin::replace); if (name == "regex-search?") return MlValue("regex-search?", builtin::regex_search); diff --git a/stdlib/terminal.lsp b/stdlib/terminal.lsp new file mode 100644 index 0000000..432679a --- /dev/null +++ b/stdlib/terminal.lsp @@ -0,0 +1,22 @@ + +(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))) + diff --git a/tests/test.lsp b/tests/test.lsp index 73329cd..d8c4d37 100644 --- a/tests/test.lsp +++ b/tests/test.lsp @@ -158,4 +158,20 @@ (print "list:" l) (print "flatten-ed list:" (flatten l)) +(print (sprintf "%.2f" '(1.23456))) +(print (sprintf "%d" '(10000000))) + + +(print (term-red "red text")) +(print (term-green "green text")) +(print (term-yellow "yellow text")) +(print (term-blue "blue text")) +(print (term-magenta "magenta text")) +(print (term-cyan "cyan text")) +(print (term-white "white text")) +(print (term-bold "bold text")) +(print (term-underline "underline text")) +(print "normal text") + + (print "Test ends") \ No newline at end of file