Add lexer and input classes

This commit is contained in:
Louis Rubet 2022-02-21 15:22:37 +01:00
parent 8542890b61
commit 1faf101dd8
21 changed files with 524 additions and 613 deletions

View file

@ -55,7 +55,8 @@ add_executable(
${PROJECT_SOURCE_DIR}/src/object.cpp ${PROJECT_SOURCE_DIR}/src/object.cpp
${PROJECT_SOURCE_DIR}/src/mpreal-out.cpp ${PROJECT_SOURCE_DIR}/src/mpreal-out.cpp
${PROJECT_SOURCE_DIR}/src/program.cpp ${PROJECT_SOURCE_DIR}/src/program.cpp
${PROJECT_SOURCE_DIR}/src/parse.cpp ${PROJECT_SOURCE_DIR}/src/lexer.cpp
${PROJECT_SOURCE_DIR}/src/input.cpp
${PROJECT_SOURCE_DIR}/src/rpn-branch.cpp ${PROJECT_SOURCE_DIR}/src/rpn-branch.cpp
${PROJECT_SOURCE_DIR}/src/rpn-complex.cpp ${PROJECT_SOURCE_DIR}/src/rpn-complex.cpp
${PROJECT_SOURCE_DIR}/src/rpn-general.cpp ${PROJECT_SOURCE_DIR}/src/rpn-general.cpp

View file

@ -10,7 +10,6 @@
// default mode and number of printed digits // default mode and number of printed digits
// //
#define DEFAULT_MODE number::std #define DEFAULT_MODE number::std
#define MPFR_DEFAULT_FORMAT "%.xxRg"
/* directly calculated from 128 bits precision /* directly calculated from 128 bits precision
ceil(128 * log10(2)) - 1 = 38 */ ceil(128 * log10(2)) - 1 = 38 */
@ -19,34 +18,9 @@
// MPFR related defaults // MPFR related defaults
// //
// rounding method
#define MPFR_DEFAULT_RND MPFR_RNDN
#define MPFR_ROUND_STRINGS \
{"nearest (even)", MPFR_RNDN}, {"toward zero", MPFR_RNDZ}, {"toward +inf", MPFR_RNDU}, {"toward -inf", MPFR_RNDD}, \
{"away from zero", MPFR_RNDA}, {"faithful rounding", MPFR_RNDF}, { \
"nearest (away from zero)", MPFR_RNDNA \
}
// 128 bits significand precision // 128 bits significand precision
#define MPFR_DEFAULT_PREC_BITS 128 #define MPFR_DEFAULT_PREC_BITS 128
// constants
//
// commands and entry related constants
#define MAX_COMMAND_LENGTH 24
#define AUTOCOMPLETE_KEY '\t'
#define SHOW_STACK_SEPARATOR "> "
#define PROMPT "rpn> "
#define MULTILINE_PROMPT "> "
// show formats
#define MPFR_FORMAT_BEG "%."
#define MPFR_FORMAT_STD "Rg"
#define MPFR_FORMAT_FIX "Rf"
#define MPFR_FORMAT_SCI "Re"
#define MPFR_FORMAT_HEX "%Ra"
// return values, used by all classes // return values, used by all classes
// //
typedef enum { typedef enum {
@ -70,20 +44,6 @@ typedef enum {
ret_max ret_max
} ret_value; } ret_value;
#define RET_VALUE_STRINGS \
{ \
"ok", "unknown command", "missing operand", "bad operand type", "out of range", "unknown variable", \
"internal error, aborting", "deadly", "goodbye", "not implemented", "no operation", "syntax error", \
"division by zero", "runtime error", "aborted current entry", "out of memory", "bad value", \
}
// command types
//
// history
#define HISTORY_FILE ".rpn_history"
#define HISTORY_FILE_MAX_LINES (100)
// base min and max // base min and max
#define BASE_MIN 2 #define BASE_MIN 2
#define BASE_MAX 62 #define BASE_MAX 62

View file

@ -1,299 +1,10 @@
#include <regex> #include <regex>
using namespace std;
#include "program.hpp" #include "lexer.hpp"
/// @brief completion callback as asked by linenoise-ng bool Lexer::lexer(string& entry, map<string, ReservedWord>& keywords, vector<SynElement>& elements,
/// this is called by linenoise-ng whenever the user enters TAB vector<SynError>& errors) {
///
/// @param text the text after wich the user entered TAB
/// @param lc the completion object to add strings with linenoiseAddCompletion()
///
void program::entry_completion_generator(const char* text, linenoiseCompletions* lc) {
int i = 0;
int text_len = strnlen(text, 6);
// propose all keywords
if (text_len == 0) {
while (program::s_keywords[i].type != cmd_max) {
if (program::s_keywords[i].fn != NULL)
// add all keywords
linenoiseAddCompletion(lc, program::s_keywords[i].name);
i++;
}
}
// propose keywords matching to text begining
else {
while (program::s_keywords[i].type != cmd_max) {
if (program::s_keywords[i].fn != NULL) {
// compare list entry with text, return if match
if (strncmp(program::s_keywords[i].name, text, text_len) == 0)
linenoiseAddCompletion(lc, program::s_keywords[i].name);
}
i++;
}
}
}
/// @brief interactive entry and decoding
///
/// @param prog the program to add entered objects
/// @return ret_value see this type
///
ret_value program::entry(program& prog) {
string entry_str;
char* entry;
int entry_len;
int total_entry_len;
ret_value ret = ret_max;
bool multiline = false;
// linenoise for entry
linenoiseSetCompletionCallback(entry_completion_generator);
while (ret == ret_max) {
// get user entry
if (multiline)
entry = linenoise(MULTILINE_PROMPT, &entry_len);
else
entry = linenoise(PROMPT, &entry_len);
// Ctrl-C
if (linenoiseKeyType() == 1) {
if (entry_len > 0 || multiline)
ret = ret_abort_current_entry;
else
ret = ret_good_bye;
} else if (linenoiseKeyType() == 3) {
multiline = true;
if (entry != NULL) entry_str += entry;
entry_str += " ";
} else {
if (entry != NULL) {
entry_str += entry;
// parse it
ret = parse(entry_str, prog);
// keep history
if (entry[0] != 0) (void)linenoiseHistoryAdd(entry_str.c_str());
} else
ret = ret_internal;
}
free(entry);
}
return ret;
}
struct SynElement {
cmd_type_t type;
string value;
mpreal re;
mpreal im;
int reBase;
int imBase;
program_fn_t fn;
bool autoEval;
};
struct SynError {
size_t indx;
string err;
};
struct ReservedWord {
cmd_type_t type;
program_fn_t fn;
};
static map<string, ReservedWord> _keywordsMap;
static void trim(string& s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { return !std::isspace(ch); }));
s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), s.end());
}
static bool parseString(string& entry, size_t idx, size_t& nextIdx, vector<SynError>& errors,
vector<SynElement>& elements) {
// here we are sure that entry[0] is at least '"'
for (size_t i = idx + 1; i < entry.size(); i++) {
if (entry[i] == '"') {
if (entry[i] - 1 != '\\') {
elements.push_back({cmd_string, .value = entry.substr(idx + 1, i - idx - 1)});
nextIdx = i + 1;
return true;
}
}
}
elements.push_back({cmd_string, .value = entry.substr(idx + 1, entry.size() - idx - 1)});
nextIdx = entry.size();
return true;
}
static bool parseSymbol(string& entry, size_t idx, size_t& nextIdx, vector<SynError>& errors,
vector<SynElement>& elements) {
// here we are sure that entry[0] is at least '\''
for (size_t i = idx + 1; i < entry.size(); i++) {
if (entry[i] == '\'') {
elements.push_back({cmd_symbol, .value = entry.substr(idx + 1, i - idx - 1), .autoEval = false});
nextIdx = i + 1;
return true;
}
}
elements.push_back({cmd_symbol, .value = entry.substr(idx + 1, entry.size() - idx - 1)});
nextIdx = entry.size();
return true;
}
static bool parseProgram(string& entry, size_t idx, size_t& nextIdx, vector<SynError>& errors,
vector<SynElement>& elements) {
// here we are sure that entry is at least "<<"
// find last ">>" or "»"
int countNested = 0;
for (size_t i = idx + 2; i < entry.size() - 1; i++) {
if ((entry[i] == '<' && entry[i + 1] == '<') || (entry.substr(i, 2) == "«"))
countNested++;
else if ((entry[i] == '>' && entry[i + 1] == '>') || (entry.substr(i, 2) == "»")) {
if (countNested == 0) {
string prg = entry.substr(idx + 2, i - idx - 2);
trim(prg);
elements.push_back({cmd_program, .value = prg});
nextIdx = i + 2;
return true;
} else
countNested--;
}
}
string prg = entry.substr(idx + 2, entry.size() - idx - 2);
trim(prg);
elements.push_back({cmd_program, .value = prg});
nextIdx = entry.size();
return true;
}
static int getBase(string& entry, int idxStart, bool& positive) {
regex baseRegex("([+-])?((0[xX])|([0-9][0-9]?[bB]))");
smatch match;
if (regex_search(entry, match, baseRegex) && match.size() >= 5) {
string sign = match[1].str();
string base = match[2].str();
// sign out, permits expressions like -0xAB3F
positive = sign.size() > 0 && sign[0] == '-' ? false : true;
// base
entry = entry.substr(base.size() + sign.size());
if (base[1] == 'X' || base[1] == 'x') return 16;
if (base.size() > 0) {
int b = stoi(base.substr(0, base.size() - 1));
if (b == 0) b = 2; // admit "0b" as binary suffix
return b;
}
}
return 10;
}
static bool getNumberAt(string& entry, size_t idx, size_t& nextIdx, int& base, mpreal& r, char delim = ' ') {
stringstream ss;
int idxNumber = 0;
string token;
bool positive = true;
ss.str(entry.substr(idx));
if (getline(ss, token, delim)) {
nextIdx = token.size() + idx + 1;
base = getBase(token, idx, positive);
if (base < BASE_MIN || base > BASE_MAX) return false;
trim(token);
if (mpfr_set_str(r.mpfr_ptr(), token.c_str(), base, mpreal::get_default_rnd()) == 0) {
if (!positive) r = -r;
return true;
} else
return false;
}
nextIdx = token.size() + idx + 1;
return false;
}
static bool parseNumber(string& entry, size_t idx, size_t& nextIdx, vector<SynError>& errors,
vector<SynElement>& elements) {
mpreal r;
int base = 10;
if (getNumberAt(entry, idx, nextIdx, base, r)) {
elements.push_back({cmd_number, .re = r, .reBase = base});
return true;
} else {
errors.push_back({entry.size(), "unterminated number"});
return false;
}
}
static bool parseComplex(string& entry, size_t idx, size_t& nextIdx, vector<SynError>& errors,
vector<SynElement>& elements) {
mpreal re, im;
int reBase, imBase = 10;
if (idx + 1 == entry.size()) {
elements.push_back({cmd_symbol, .value = entry.substr(idx, entry.size() - idx)});
nextIdx = entry.size();
return true; // complex format error, return a symbol
}
if (!getNumberAt(entry, idx + 1, nextIdx, reBase, re, ',')) {
elements.push_back({cmd_symbol, .value = entry.substr(idx, entry.size() - idx)});
nextIdx = entry.size();
return true; // complex format error, return a symbol
}
size_t i = nextIdx;
//while (i < entry.size() && entry[i] != ',') i++;
if (i >= entry.size()) {
elements.push_back({cmd_symbol, .value = entry.substr(idx, entry.size() - idx)});
nextIdx = entry.size();
return true; // complex format error, return a symbol
}
if (!getNumberAt(entry, i, nextIdx, imBase, im, ')')) {
elements.push_back({cmd_symbol, .value = entry.substr(idx, entry.size() - idx)});
nextIdx = entry.size();
return true; // complex format error, return a symbol
}
elements.push_back({cmd_complex, .re = re, .im = im, .reBase = reBase, .imBase = imBase});
nextIdx++;
return true;
}
static bool parseReserved(string& entry, size_t idx, size_t& nextIdx, vector<SynElement>& elements,
map<string, ReservedWord>& keywords) {
stringstream ss(entry.substr(idx));
string token;
ss >> token;
auto resa = keywords.find(token);
if (resa != keywords.end()) {
elements.push_back({resa->second.type, .value = token, .fn = resa->second.fn});
nextIdx = token.size() + idx;
return true;
}
return false;
}
static bool parseUnknown(string& entry, size_t idx, size_t& nextIdx, vector<SynElement>& elements) {
stringstream ss(entry.substr(idx));
string token;
ss >> token;
elements.push_back({cmd_symbol, .value = token, .autoEval = true});
nextIdx = token.size() + idx;
return true;
}
/// @brief lexical analysis of an entry string
///
/// @param entry the entry string
/// @param elements syntax elements vector
/// @param errors errors vector
/// @param keywords reserved keywords
/// @return false=errors, the lexer must stop
///
static bool lexer(string& entry, vector<SynElement>& elements, vector<SynError>& errors,
map<string, ReservedWord>& keywords) {
size_t jump; size_t jump;
for (size_t i = 0; i < entry.size(); i++) { for (size_t i = 0; i < entry.size(); i++) {
if (isspace(entry[i])) continue; if (isspace(entry[i])) continue;
@ -332,60 +43,177 @@ static bool lexer(string& entry, vector<SynElement>& elements, vector<SynError>&
return true; return true;
} }
static bool progFromElements(vector<SynElement>& elements, program& prog) { void Lexer::trim(string& s) {
// TODO control elements creation s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { return !std::isspace(ch); }));
for (SynElement& element : elements) { s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), s.end());
switch (element.type) { }
case cmd_number:
prog.push_back(new number(element.re, element.reBase)); bool Lexer::parseString(string& entry, size_t idx, size_t& nextIdx, vector<SynError>& errors,
break; vector<SynElement>& elements) {
case cmd_complex: // here we are sure that entry[0] is at least '"'
prog.push_back(new ocomplex(element.re, element.im, element.reBase, element.imBase)); for (size_t i = idx + 1; i < entry.size(); i++) {
break; if (entry[i] == '"') {
case cmd_string: if (entry[i] - 1 != '\\') {
prog.push_back(new ostring(element.value)); elements.push_back({cmd_string, .value = entry.substr(idx + 1, i - idx - 1)});
break; nextIdx = i + 1;
case cmd_symbol: return true;
prog.push_back(new symbol(element.value, element.autoEval)); }
break;
case cmd_program:
prog.push_back(new oprogram(element.value));
break;
case cmd_keyword:
prog.push_back(new keyword(element.fn, element.value));
break;
case cmd_branch:
prog.push_back(new branch((branch_fn_t)element.fn, element.value));
break;
default:
return false;
} }
} }
elements.push_back({cmd_string, .value = entry.substr(idx + 1, entry.size() - idx - 1)});
nextIdx = entry.size();
return true; return true;
} }
/// @brief parse an entry string: cut it into objects chunks and add them to a program bool Lexer::parseSymbol(string& entry, size_t idx, size_t& nextIdx, vector<SynError>& errors,
/// vector<SynElement>& elements) {
/// @param entry the entry string // here we are sure that entry[0] is at least '\''
/// @param prog the program to be filled for (size_t i = idx + 1; i < entry.size(); i++) {
/// @return ret_value see this type if (entry[i] == '\'') {
/// elements.push_back({cmd_symbol, .value = entry.substr(idx + 1, i - idx - 1), .autoEval = false});
ret_value program::parse(string& entry, program& prog) { nextIdx = i + 1;
vector<SynElement> elements; return true;
vector<SynError> errors; }
ret_value ret = ret_ok; }
elements.push_back({cmd_symbol, .value = entry.substr(idx + 1, entry.size() - idx - 1)});
// prepare map for finding reserved keywords nextIdx = entry.size();
if (_keywordsMap.size() == 0) return true;
for (int i = 0; s_keywords[i].type != cmd_max; i++) }
_keywordsMap[s_keywords[i].name] = {s_keywords[i].type, s_keywords[i].fn};
bool Lexer::parseProgram(string& entry, size_t idx, size_t& nextIdx, vector<SynError>& errors,
// separate the entry string vector<SynElement>& elements) {
if (lexer(entry, elements, errors, _keywordsMap)) { // here we are sure that entry is at least "<<"
// make objects from parsed elements // find last ">>" or "»"
if (!progFromElements(elements, prog)) prog.show_error(ret_unknown_err, "error creating program from entry"); int countNested = 0;
} else for (size_t i = idx + 2; i < entry.size() - 1; i++) {
for (SynError& err : errors) prog.show_syntax_error(err.err.c_str()); if ((entry[i] == '<' && entry[i + 1] == '<') || (entry.substr(i, 2) == "«"))
countNested++;
return ret; else if ((entry[i] == '>' && entry[i + 1] == '>') || (entry.substr(i, 2) == "»")) {
if (countNested == 0) {
string prg = entry.substr(idx + 2, i - idx - 2);
trim(prg);
elements.push_back({cmd_program, .value = prg});
nextIdx = i + 2;
return true;
} else
countNested--;
}
}
string prg = entry.substr(idx + 2, entry.size() - idx - 2);
trim(prg);
elements.push_back({cmd_program, .value = prg});
nextIdx = entry.size();
return true;
}
int Lexer::getBaseAt(string& entry, int idxStart, bool& positive) {
regex baseRegex("([+-])?((0[xX])|([0-9][0-9]?[bB]))");
smatch match;
if (regex_search(entry, match, baseRegex) && match.size() >= 5) {
string sign = match[1].str();
string base = match[2].str();
// sign out, permits expressions like -0xAB3F
positive = sign.size() > 0 && sign[0] == '-' ? false : true;
// base
entry = entry.substr(base.size() + sign.size());
if (base[1] == 'X' || base[1] == 'x') return 16;
if (base.size() > 0) {
int b = stoi(base.substr(0, base.size() - 1));
if (b == 0) b = 2; // admit "0b" as binary suffix
return b;
}
}
return 10;
}
bool Lexer::getNumberAt(string& entry, size_t idx, size_t& nextIdx, int& base, mpreal& r, char delim) {
stringstream ss;
int idxNumber = 0;
string token;
bool positive = true;
ss.str(entry.substr(idx));
if (getline(ss, token, delim)) {
nextIdx = token.size() + idx + 1;
base = getBaseAt(token, idx, positive);
if (base < BASE_MIN || base > BASE_MAX) return false;
trim(token);
if (mpfr_set_str(r.mpfr_ptr(), token.c_str(), base, mpreal::get_default_rnd()) == 0) {
if (!positive) r = -r;
return true;
} else
return false;
}
nextIdx = token.size() + idx + 1;
return false;
}
bool Lexer::parseNumber(string& entry, size_t idx, size_t& nextIdx, vector<SynError>& errors,
vector<SynElement>& elements) {
mpreal r;
int base = 10;
if (getNumberAt(entry, idx, nextIdx, base, r)) {
elements.push_back({cmd_number, .re = r, .reBase = base});
return true;
} else {
errors.push_back({entry.size(), "unterminated number"});
return false;
}
}
bool Lexer::parseComplex(string& entry, size_t idx, size_t& nextIdx, vector<SynError>& errors,
vector<SynElement>& elements) {
mpreal re, im;
int reBase, imBase = 10;
if (idx + 1 == entry.size()) {
elements.push_back({cmd_symbol, .value = entry.substr(idx, entry.size() - idx)});
nextIdx = entry.size();
return true; // complex format error, return a symbol
}
if (!getNumberAt(entry, idx + 1, nextIdx, reBase, re, ',')) {
elements.push_back({cmd_symbol, .value = entry.substr(idx, entry.size() - idx)});
nextIdx = entry.size();
return true; // complex format error, return a symbol
}
size_t i = nextIdx;
// while (i < entry.size() && entry[i] != ',') i++;
if (i >= entry.size()) {
elements.push_back({cmd_symbol, .value = entry.substr(idx, entry.size() - idx)});
nextIdx = entry.size();
return true; // complex format error, return a symbol
}
if (!getNumberAt(entry, i, nextIdx, imBase, im, ')')) {
elements.push_back({cmd_symbol, .value = entry.substr(idx, entry.size() - idx)});
nextIdx = entry.size();
return true; // complex format error, return a symbol
}
elements.push_back({cmd_complex, .re = re, .im = im, .reBase = reBase, .imBase = imBase});
nextIdx++;
return true;
}
bool Lexer::parseReserved(string& entry, size_t idx, size_t& nextIdx, vector<SynElement>& elements,
map<string, ReservedWord>& keywords) {
stringstream ss(entry.substr(idx));
string token;
ss >> token;
auto resa = keywords.find(token);
if (resa != keywords.end()) {
elements.push_back({resa->second.type, .value = token, .fn = resa->second.fn});
nextIdx = token.size() + idx;
return true;
}
return false;
}
bool Lexer::parseUnknown(string& entry, size_t idx, size_t& nextIdx, vector<SynElement>& elements) {
stringstream ss(entry.substr(idx));
string token;
ss >> token;
elements.push_back({cmd_symbol, .value = token, .autoEval = true});
nextIdx = token.size() + idx;
return true;
} }

73
src/lexer.hpp Normal file
View file

@ -0,0 +1,73 @@
#ifndef _PARSER_HPP_
#define _PARSER_HPP_
#include <map>
#include <vector>
using namespace std;
#define MPFR_USE_NO_MACRO
#include <mpfr.h>
#include <mpreal.h>
using namespace mpfr;
#include "constant.h"
#include "object.hpp"
class Lexer {
public:
// a structure to describe a syntaxical element and its value
struct SynElement {
cmd_type_t type;
string value;
mpreal re;
mpreal im;
int reBase;
int imBase;
program_fn_t fn;
bool autoEval;
};
struct SynError {
size_t indx;
string err;
};
struct ReservedWord {
cmd_type_t type;
program_fn_t fn;
};
Lexer() {}
/// @brief lexical analysis of an entry string
///
/// @param[in] entry the entry to lex
/// @param[out] elements syntax elements vector
/// @param[out] errors errors vector
/// @param[in] keywords reserved keywords
/// @return false=errors, the lexer must stop
///
bool lexer(string& entry, map<string, ReservedWord>& keywords, vector<SynElement>& elements,
vector<SynError>& errors);
private:
bool parseString(string& entry, size_t idx, size_t& nextIdx, vector<SynError>& errors,
vector<SynElement>& elements);
bool parseSymbol(string& entry, size_t idx, size_t& nextIdx, vector<SynError>& errors,
vector<SynElement>& elements);
bool parseProgram(string& entry, size_t idx, size_t& nextIdx, vector<SynError>& errors,
vector<SynElement>& elements);
bool parseNumber(string& entry, size_t idx, size_t& nextIdx, vector<SynError>& errors,
vector<SynElement>& elements);
bool parseComplex(string& entry, size_t idx, size_t& nextIdx, vector<SynError>& errors,
vector<SynElement>& elements);
bool parseReserved(string& entry, size_t idx, size_t& nextIdx, vector<SynElement>& elements,
map<string, ReservedWord>& keywords);
bool parseUnknown(string& entry, size_t idx, size_t& nextIdx, vector<SynElement>& elements);
void trim(string& s);
int getBaseAt(string& entry, int idxStart, bool& positive);
bool getNumberAt(string& entry, size_t idx, size_t& nextIdx, int& base, mpreal& r, char delim = ' ');
};
#endif

View file

@ -1,4 +1,3 @@
#include <linux/limits.h>
#include <pwd.h> #include <pwd.h>
#include <signal.h> #include <signal.h>
@ -7,19 +6,20 @@
using namespace std; using namespace std;
// internal includes // internal includes
#include "input.hpp"
#include "program.hpp" #include "program.hpp"
static heap _global_heap; static heap _global_heap;
static rpnstack _global_stack; static rpnstack _global_stack;
static program* _prog_to_interrupt = NULL; static program* _prog_to_interrupt = nullptr;
/// @brief actions to be done at rpn exit /// @brief actions to be done at rpn exit
/// ///
static void exit_interactive_rpn() { static void exit_interactive_rpn() {
struct passwd* pw = getpwuid(getuid()); struct passwd* pw = getpwuid(getuid());
if (pw != NULL) { if (pw != nullptr) {
stringstream history_path; stringstream history_path;
history_path << pw->pw_dir << '/' << HISTORY_FILE; history_path << pw->pw_dir << "/.rpn_history";
// trunc current history // trunc current history
ofstream history(history_path.str(), ios_base::out | ios_base::trunc); ofstream history(history_path.str(), ios_base::out | ios_base::trunc);
@ -37,12 +37,12 @@ static void exit_interactive_rpn() {
/// ///
static void init_interactive_rpn() { static void init_interactive_rpn() {
struct passwd* pw = getpwuid(getuid()); struct passwd* pw = getpwuid(getuid());
if (pw != NULL) { if (pw != nullptr) {
stringstream history_path; stringstream history_path;
history_path << pw->pw_dir << '/' << HISTORY_FILE; history_path << pw->pw_dir << "/.rpn_history";
// don't care about errors // don't care about errors
linenoiseHistorySetMaxLen(HISTORY_FILE_MAX_LINES); linenoiseHistorySetMaxLen(100);
linenoiseHistoryLoad(history_path.str().c_str()); linenoiseHistoryLoad(history_path.str().c_str());
} }
} }
@ -54,9 +54,9 @@ static void init_interactive_rpn() {
/// @param context see POSIX sigaction /// @param context see POSIX sigaction
/// ///
static void ctrlc_handler(int sig, siginfo_t* siginfo, void* context) { static void ctrlc_handler(int sig, siginfo_t* siginfo, void* context) {
if (_prog_to_interrupt != NULL) { if (_prog_to_interrupt != nullptr) {
_prog_to_interrupt->stop(); _prog_to_interrupt->stop();
_prog_to_interrupt = NULL; _prog_to_interrupt = nullptr;
} }
exit_interactive_rpn(); exit_interactive_rpn();
@ -71,12 +71,12 @@ static void ctrlc_handler(int sig, siginfo_t* siginfo, void* context) {
static void segv_handler(int sig, siginfo_t* siginfo, void* context) { static void segv_handler(int sig, siginfo_t* siginfo, void* context) {
cerr << "Internal error" << endl; cerr << "Internal error" << endl;
_prog_to_interrupt->stop(); _prog_to_interrupt->stop();
_prog_to_interrupt = NULL; _prog_to_interrupt = nullptr;
} }
/// @brief setup signals handlers to stop with honours /// @brief setup signals handlers to stop with honours
/// ///
/// @param prog the prog to catch the signals to, must be checked not NULL by user /// @param prog the prog to catch the signals to, must be checked not nullptr by user
/// ///
static void catch_signals(program* prog) { static void catch_signals(program* prog) {
struct sigaction act = {0}; struct sigaction act = {0};
@ -85,12 +85,12 @@ static void catch_signals(program* prog) {
act.sa_sigaction = &ctrlc_handler; act.sa_sigaction = &ctrlc_handler;
act.sa_flags = SA_SIGINFO; act.sa_flags = SA_SIGINFO;
if (sigaction(SIGINT, &act, NULL) < 0) if (sigaction(SIGINT, &act, nullptr) < 0)
cerr << "Warning, Ctrl-C cannot be caught [errno=" << errno << ' ' << strerror(errno) << "']" << endl; cerr << "Warning, Ctrl-C cannot be caught [errno=" << errno << ' ' << strerror(errno) << "']" << endl;
act.sa_sigaction = &segv_handler; act.sa_sigaction = &segv_handler;
act.sa_flags = SA_SIGINFO; act.sa_flags = SA_SIGINFO;
if (sigaction(SIGSEGV, &act, NULL) < 0) if (sigaction(SIGSEGV, &act, nullptr) < 0)
cerr << "Warning, SIGSEGV cannot be caught [errno=" << errno << ' ' << strerror(errno) << "']" << endl; cerr << "Warning, SIGSEGV cannot be caught [errno=" << errno << ' ' << strerror(errno) << "']" << endl;
} }
@ -116,30 +116,28 @@ int main(int argc, char* argv[]) {
while (go_on) { while (go_on) {
// make program from interactive entry // make program from interactive entry
program prog(_global_stack, _global_heap); program prog(_global_stack, _global_heap);
switch (program::entry(prog)) { string entry;
case ret_good_bye: switch (Input(entry, program::getAutocompletionWords()).status) {
go_on = false; case Input::ok:
break;
case ret_abort_current_entry:
break;
default:
// user could stop prog with CtrlC // user could stop prog with CtrlC
catch_signals(&prog); catch_signals(&prog);
// run it // run it
if (prog.run() == ret_good_bye) if (prog.parse(entry) == ret_ok && prog.run() == ret_good_bye)
go_on = false; go_on = false;
else else
program::show_stack(_global_stack); program::show_stack(_global_stack);
break; break;
case Input::ctrlc:
go_on = false;
break;
default:
break;
} }
} }
// manage history and exit // manage history and exit
exit_interactive_rpn(); exit_interactive_rpn();
} } else { // run with cmd line arguments
// run with cmd line arguments
else {
program prog(_global_stack, _global_heap); program prog(_global_stack, _global_heap);
string entry; string entry;
int i; int i;
@ -151,7 +149,7 @@ int main(int argc, char* argv[]) {
} }
// make program // make program
ret = program::parse(entry, prog); ret = prog.parse(entry);
if (ret == ret_ok) { if (ret == ret_ok) {
// user could stop prog with CtrlC // user could stop prog with CtrlC
catch_signals(&prog); catch_signals(&prog);

View file

@ -1,10 +1,4 @@
#include <math.h>
#include <string>
using namespace std;
#include "constant.h" #include "constant.h"
#define MPFR_USE_NO_MACRO
#include <mpfr.h>
#include "object.hpp" #include "object.hpp"
// number statics // number statics

View file

@ -1,13 +1,14 @@
#ifndef OBJECT_HPP #ifndef OBJECT_HPP
#define OBJECT_HPP #define OBJECT_HPP
#define MPFR_USE_NO_MACRO
#include <mpfr.h>
#include <mpreal.h> #include <mpreal.h>
using namespace mpfr; using namespace mpfr;
#include <bitset>
#include <complex>
#include <iomanip>
#include <ostream> #include <ostream>
#include <string>
#include <sstream>
using namespace std; using namespace std;
#include "mpreal-out.hpp" #include "mpreal-out.hpp"
@ -127,7 +128,6 @@ struct ocomplex : object {
struct ostring : object { struct ostring : object {
ostring() : object(cmd_string) {} ostring() : object(cmd_string) {}
ostring(const string& value_) : object(cmd_string), value(value_) {} ostring(const string& value_) : object(cmd_string), value(value_) {}
ostring(const char* value_) : object(cmd_string) { value = string(value_); }
virtual object* clone() { return new ostring(value); } virtual object* clone() { return new ostring(value); }
virtual string name() { return string("string"); } virtual string name() { return string("string"); }
virtual ostream& show(ostream& out) { return out << "\"" << value << "\""; } virtual ostream& show(ostream& out) { return out << "\"" << value << "\""; }

View file

@ -1,12 +1,9 @@
#include "program.hpp" #include "program.hpp"
//< return type strings
const char* program::s_ret_value_string[ret_max] = RET_VALUE_STRINGS;
//< language reserved keywords (allowed types are cmd_keyword, cmd_branch or cmd_undef) //< language reserved keywords (allowed types are cmd_keyword, cmd_branch or cmd_undef)
program::keyword_t program::s_keywords[] = { vector<program::keyword_t> program::_keywords{
// GENERAL // GENERAL
{cmd_undef, "", NULL, "\nGENERAL"}, {cmd_undef, "", nullptr, "\nGENERAL"},
{cmd_keyword, "nop", &program::rpn_nop, "no operation"}, {cmd_keyword, "nop", &program::rpn_nop, "no operation"},
{cmd_keyword, "help", &program::rpn_help, "this help message"}, {cmd_keyword, "help", &program::rpn_help, "this help message"},
{cmd_keyword, "h", &program::rpn_help, ""}, {cmd_keyword, "h", &program::rpn_help, ""},
@ -20,7 +17,7 @@ program::keyword_t program::s_keywords[] = {
{cmd_keyword, "history", &program::rpn_history, "see commands history"}, {cmd_keyword, "history", &program::rpn_history, "see commands history"},
// USUAL OPERATIONS ON REALS AND COMPLEXES // USUAL OPERATIONS ON REALS AND COMPLEXES
{cmd_undef, "", NULL, "\nUSUAL OPERATIONS ON REALS AND COMPLEXES"}, {cmd_undef, "", nullptr, "\nUSUAL OPERATIONS ON REALS AND COMPLEXES"},
{cmd_keyword, "+", &program::rpn_plus, "addition"}, {cmd_keyword, "+", &program::rpn_plus, "addition"},
{cmd_keyword, "-", &program::rpn_minus, "substraction"}, {cmd_keyword, "-", &program::rpn_minus, "substraction"},
{cmd_keyword, "*", &program::rpn_mul, "multiplication"}, {cmd_keyword, "*", &program::rpn_mul, "multiplication"},
@ -36,7 +33,7 @@ program::keyword_t program::s_keywords[] = {
{cmd_keyword, "sign", &program::rpn_sign, "sign of a number or z/|z| for a complex"}, {cmd_keyword, "sign", &program::rpn_sign, "sign of a number or z/|z| for a complex"},
// OPERATIONS ON REALS // OPERATIONS ON REALS
{cmd_undef, "", NULL, "\nOPERATIONS ON REALS"}, {cmd_undef, "", nullptr, "\nOPERATIONS ON REALS"},
{cmd_keyword, "%", &program::rpn_purcent, "purcent"}, {cmd_keyword, "%", &program::rpn_purcent, "purcent"},
{cmd_keyword, "%CH", &program::rpn_purcentCH, "inverse purcent"}, {cmd_keyword, "%CH", &program::rpn_purcentCH, "inverse purcent"},
{cmd_keyword, "mod", &program::rpn_modulo, "modulo"}, {cmd_keyword, "mod", &program::rpn_modulo, "modulo"},
@ -51,7 +48,7 @@ program::keyword_t program::s_keywords[] = {
{cmd_keyword, "max", &program::rpn_max, "max of 2 real numbers"}, {cmd_keyword, "max", &program::rpn_max, "max of 2 real numbers"},
// OPERATIONS ON COMPLEXES // OPERATIONS ON COMPLEXES
{cmd_undef, "", NULL, "\nOPERATIONS ON COMPLEXES"}, {cmd_undef, "", nullptr, "\nOPERATIONS ON COMPLEXES"},
{cmd_keyword, "re", &program::rpn_re, "complex real part"}, {cmd_keyword, "re", &program::rpn_re, "complex real part"},
{cmd_keyword, "im", &program::rpn_im, "complex imaginary part"}, {cmd_keyword, "im", &program::rpn_im, "complex imaginary part"},
{cmd_keyword, "conj", &program::rpn_conj, "complex conjugate"}, {cmd_keyword, "conj", &program::rpn_conj, "complex conjugate"},
@ -62,7 +59,7 @@ program::keyword_t program::s_keywords[] = {
{cmd_keyword, "r->p", &program::rpn_r2p, "polar to cartesian"}, {cmd_keyword, "r->p", &program::rpn_r2p, "polar to cartesian"},
// MODE // MODE
{cmd_undef, "", NULL, "\nMODE"}, {cmd_undef, "", nullptr, "\nMODE"},
{cmd_keyword, "std", &program::rpn_std, "standard floating numbers representation. ex: std"}, {cmd_keyword, "std", &program::rpn_std, "standard floating numbers representation. ex: std"},
{cmd_keyword, "fix", &program::rpn_fix, "fixed point representation. ex: 6 fix"}, {cmd_keyword, "fix", &program::rpn_fix, "fixed point representation. ex: 6 fix"},
{cmd_keyword, "sci", &program::rpn_sci, "scientific floating point representation. ex: 20 sci"}, {cmd_keyword, "sci", &program::rpn_sci, "scientific floating point representation. ex: 20 sci"},
@ -79,7 +76,7 @@ program::keyword_t program::s_keywords[] = {
{cmd_keyword, "base", &program::rpn_base, "arbitrary base representation, applies on stack level 0 only"}, {cmd_keyword, "base", &program::rpn_base, "arbitrary base representation, applies on stack level 0 only"},
// TESTS // TESTS
{cmd_undef, "", NULL, "\nTEST"}, {cmd_undef, "", nullptr, "\nTEST"},
{cmd_keyword, ">", &program::rpn_sup, "binary operator >"}, {cmd_keyword, ">", &program::rpn_sup, "binary operator >"},
{cmd_keyword, ">=", &program::rpn_sup_eq, "binary operator >="}, {cmd_keyword, ">=", &program::rpn_sup_eq, "binary operator >="},
{cmd_keyword, "<", &program::rpn_inf, "binary operator <"}, {cmd_keyword, "<", &program::rpn_inf, "binary operator <"},
@ -93,7 +90,7 @@ program::keyword_t program::s_keywords[] = {
{cmd_keyword, "same", &program::rpn_same, "boolean operator same (equal)"}, {cmd_keyword, "same", &program::rpn_same, "boolean operator same (equal)"},
// STACK // STACK
{cmd_undef, "", NULL, "\nSTACK"}, {cmd_undef, "", nullptr, "\nSTACK"},
{cmd_keyword, "swap", &program::rpn_swap, "swap 2 first stack entries"}, {cmd_keyword, "swap", &program::rpn_swap, "swap 2 first stack entries"},
{cmd_keyword, "drop", &program::rpn_drop, "drop first stack entry"}, {cmd_keyword, "drop", &program::rpn_drop, "drop first stack entry"},
{cmd_keyword, "drop2", &program::rpn_drop2, "drop 2 first stack entries"}, {cmd_keyword, "drop2", &program::rpn_drop2, "drop 2 first stack entries"},
@ -111,7 +108,7 @@ program::keyword_t program::s_keywords[] = {
{cmd_keyword, "over", &program::rpn_over, "push a copy of the element in stack level 2 onto the stack"}, {cmd_keyword, "over", &program::rpn_over, "push a copy of the element in stack level 2 onto the stack"},
// STRING // STRING
{cmd_undef, "", NULL, "\nSTRING"}, {cmd_undef, "", nullptr, "\nSTRING"},
{cmd_keyword, "->str", &program::rpn_instr, "convert an object into a string"}, {cmd_keyword, "->str", &program::rpn_instr, "convert an object into a string"},
{cmd_keyword, "str->", &program::rpn_strout, "convert a string into an object"}, {cmd_keyword, "str->", &program::rpn_strout, "convert a string into an object"},
{cmd_keyword, "chr", &program::rpn_chr, "convert ASCII character code in stack level 1 into a string"}, {cmd_keyword, "chr", &program::rpn_chr, "convert ASCII character code in stack level 1 into a string"},
@ -122,7 +119,7 @@ program::keyword_t program::s_keywords[] = {
{cmd_keyword, "sub", &program::rpn_strsub, "return a substring of the string in level 3"}, {cmd_keyword, "sub", &program::rpn_strsub, "return a substring of the string in level 3"},
// BRANCH // BRANCH
{cmd_undef, "", NULL, "\nBRANCH"}, {cmd_undef, "", nullptr, "\nBRANCH"},
{cmd_branch, "if", (program_fn_t)&program::rpn_if, {cmd_branch, "if", (program_fn_t)&program::rpn_if,
"if <test-instruction> then <true-instructions> else <false-instructions> " "if <test-instruction> then <true-instructions> else <false-instructions> "
"end"}, "end"},
@ -144,7 +141,7 @@ program::keyword_t program::s_keywords[] = {
{cmd_branch, "repeat", (program_fn_t)&program::rpn_repeat, "used with while"}, {cmd_branch, "repeat", (program_fn_t)&program::rpn_repeat, "used with while"},
// STORE // STORE
{cmd_undef, "", NULL, "\nSTORE"}, {cmd_undef, "", nullptr, "\nSTORE"},
{cmd_keyword, "sto", &program::rpn_sto, "store a variable. ex: 1 'name' sto"}, {cmd_keyword, "sto", &program::rpn_sto, "store a variable. ex: 1 'name' sto"},
{cmd_keyword, "rcl", &program::rpn_rcl, "recall a variable. ex: 'name' rcl"}, {cmd_keyword, "rcl", &program::rpn_rcl, "recall a variable. ex: 'name' rcl"},
{cmd_keyword, "purge", &program::rpn_purge, "delete a variable. ex: 'name' purge"}, {cmd_keyword, "purge", &program::rpn_purge, "delete a variable. ex: 'name' purge"},
@ -158,14 +155,14 @@ program::keyword_t program::s_keywords[] = {
{cmd_keyword, "sneg", &program::rpn_stoneg, "negate a variable. ex: 'name' sneg"}, {cmd_keyword, "sneg", &program::rpn_stoneg, "negate a variable. ex: 'name' sneg"},
{cmd_keyword, "sinv", &program::rpn_stoinv, "inverse a variable. ex: 1 'name' sinv"}, {cmd_keyword, "sinv", &program::rpn_stoinv, "inverse a variable. ex: 1 'name' sinv"},
// PROGRAM // PROGRAM
{cmd_undef, "", NULL, "\nPROGRAM"}, {cmd_undef, "", nullptr, "\nPROGRAM"},
{cmd_keyword, "eval", &program::rpn_eval, "evaluate (run) a program, or recall a variable. ex: 'my_prog' eval"}, {cmd_keyword, "eval", &program::rpn_eval, "evaluate (run) a program, or recall a variable. ex: 'my_prog' eval"},
{cmd_branch, "->", (program_fn_t)&program::rpn_inprog, {cmd_branch, "->", (program_fn_t)&program::rpn_inprog,
"load program local variables. ex: << -> n m << 0 n m for i i + next >> " "load program local variables. ex: << -> n m << 0 n m for i i + next >> "
">>"}, ">>"},
// TRIG ON REALS AND COMPLEXES // TRIG ON REALS AND COMPLEXES
{cmd_undef, "", NULL, "\nTRIG ON REALS AND COMPLEXES"}, {cmd_undef, "", nullptr, "\nTRIG ON REALS AND COMPLEXES"},
{cmd_keyword, "pi", &program::rpn_pi, "pi constant"}, {cmd_keyword, "pi", &program::rpn_pi, "pi constant"},
{cmd_keyword, "sin", &program::rpn_sin, "sinus"}, {cmd_keyword, "sin", &program::rpn_sin, "sinus"},
{cmd_keyword, "asin", &program::rpn_asin, "arg sinus"}, {cmd_keyword, "asin", &program::rpn_asin, "arg sinus"},
@ -177,7 +174,7 @@ program::keyword_t program::s_keywords[] = {
{cmd_keyword, "r->d", &program::rpn_r2d, "convert radians to degrees"}, {cmd_keyword, "r->d", &program::rpn_r2d, "convert radians to degrees"},
// LOGS ON REALS AND COMPLEXES // LOGS ON REALS AND COMPLEXES
{cmd_undef, "", NULL, "\nLOGS ON REALS AND COMPLEXES"}, {cmd_undef, "", nullptr, "\nLOGS ON REALS AND COMPLEXES"},
{cmd_keyword, "e", &program::rpn_e, "Euler constant"}, {cmd_keyword, "e", &program::rpn_e, "Euler constant"},
{cmd_keyword, "ln", &program::rpn_ln, "logarithm base e"}, {cmd_keyword, "ln", &program::rpn_ln, "logarithm base e"},
{cmd_keyword, "log", &program::rpn_ln, ""}, {cmd_keyword, "log", &program::rpn_ln, ""},
@ -198,15 +195,25 @@ program::keyword_t program::s_keywords[] = {
{cmd_keyword, "atanh", &program::rpn_atanh, "inverse hyperbolic tangent"}, {cmd_keyword, "atanh", &program::rpn_atanh, "inverse hyperbolic tangent"},
// TIME AND DATE // TIME AND DATE
{cmd_undef, "", NULL, "\nTIME AND DATE"}, {cmd_undef, "", nullptr, "\nTIME AND DATE"},
{cmd_keyword, "time", &program::rpn_time, "time in local format"}, {cmd_keyword, "time", &program::rpn_time, "time in local format"},
{cmd_keyword, "date", &program::rpn_date, "date in local format"}, {cmd_keyword, "date", &program::rpn_date, "date in local format"},
{cmd_keyword, "ticks", &program::rpn_ticks, "system tick in µs"}, {cmd_keyword, "ticks", &program::rpn_ticks, "system tick in µs"},
// end
{cmd_max, "", NULL, ""},
}; };
/// keywords map for lexer
map<string, Lexer::ReservedWord> program::_keywordsMap;
/// autocompletion vector for linenoise autocompletion
vector<string> program::_autocompletionWords;
vector<string>& program::getAutocompletionWords() {
if (_autocompletionWords.empty())
for (auto& kw : _keywords)
if (!kw.name.empty()) _autocompletionWords.push_back(kw.name);
return _autocompletionWords;
}
/// @brief run a program on a stack and a heap /// @brief run a program on a stack and a heap
/// ///
/// @return ret_value see this type /// @return ret_value see this type
@ -302,7 +309,7 @@ ret_value program::run() {
_local_heap.clear(); _local_heap.clear();
if (interrupt_now) { if (interrupt_now) {
fprintf(stderr, "\nInterrupted\n"); cerr << endl << "Interrupted" << endl;
interrupt_now = false; interrupt_now = false;
} }
@ -561,15 +568,75 @@ ret_value program::preprocess(void) {
return ret_ok; return ret_ok;
} }
/// @brief parse an entry string: cut it into objects chunks and add them to a program
///
/// @param entry the entry string
/// @param prog the program to be filled
/// @return ret_value see this type
///
ret_value program::parse(string& entry) {
vector<Lexer::SynElement> elements;
vector<Lexer::SynError> errors;
ret_value ret = ret_ok;
// prepare map for finding reserved keywords
if (_keywordsMap.empty())
for (auto& kw : _keywords)
if (!kw.name.empty()) _keywordsMap[kw.name] = {kw.type, kw.fn};
// separate the entry string
if (lexer(entry, _keywordsMap, elements, errors)) {
// make objects from parsed elements
for (Lexer::SynElement& element : elements) {
switch (element.type) {
case cmd_number:
push_back(new number(element.re, element.reBase));
break;
case cmd_complex:
push_back(new ocomplex(element.re, element.im, element.reBase, element.imBase));
break;
case cmd_string:
push_back(new ostring(element.value));
break;
case cmd_symbol:
push_back(new symbol(element.value, element.autoEval));
break;
case cmd_program:
push_back(new oprogram(element.value));
break;
case cmd_keyword:
push_back(new keyword(element.fn, element.value));
break;
case cmd_branch:
push_back(new branch((branch_fn_t)element.fn, element.value));
break;
default:
show_error(ret_unknown_err, "error creating program from entry");
}
}
} else
for (SynError& err : errors) show_syntax_error(err.err.c_str());
return ret;
}
/// @brief show the last error set /// @brief show the last error set
/// ///
/// @return ret_value see this type /// @return ret_value see this type
/// ///
ret_value program::show_error() { ret_value program::show_error() {
ret_value ret; ret_value ret;
// clang-format off
vector<string> errorStrings {"ok", "unknown command", "missing operand", "bad operand type",
"out of range", "unknown variable", "internal error, aborting", "deadly", "goodbye", "not implemented",
"no operation", "syntax error", "division by zero", "runtime error", "aborted current entry", "out of memory",
"bad value"};
// clang-format on
// show last recorded error // show last recorded error
cerr << _err_context << ": error " << _err << ": " << s_ret_value_string[_err] << endl; if ((size_t)_err < errorStrings.size())
cerr << _err_context << ": error " << _err << ": " << errorStrings[_err] << endl;
else
cerr << _err_context << " (unknown error code)" << endl;
switch (_err) { switch (_err) {
case ret_internal: case ret_internal:
case ret_deadly: case ret_deadly:
@ -637,7 +704,7 @@ void program::show_stack(rpnstack& st, bool show_separator) {
cout << st[0] << endl; cout << st[0] << endl;
else else
for (int i = st.size() - 1; i >= 0; i--) { for (int i = st.size() - 1; i >= 0; i--) {
if (show_separator) cout << i + 1 << SHOW_STACK_SEPARATOR; if (show_separator) cout << i + 1 << "> ";
cout << st[i] << endl; cout << st[i] << endl;
} }
} }

View file

@ -1,33 +1,21 @@
#ifndef PROGRAM_HPP #ifndef PROGRAM_HPP
#define PROGRAM_HPP #define PROGRAM_HPP
// std c
#include <math.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/mman.h>
// std c++ // std c++
#include <fstream> #include <fstream>
#include <iomanip>
#include <iostream>
#include <map>
#include <sstream>
#include <vector> #include <vector>
using namespace std; using namespace std;
// external libs
#define MPFR_USE_NO_MACRO #define MPFR_USE_NO_MACRO
#include <mpfr.h> #include <mpfr.h>
#include <mpreal.h>
#include "linenoise.h" using namespace mpfr;
// internal includes // internal includes
#include "constant.h" #include "constant.h"
#include "escape.h"
#include "object.hpp" #include "object.hpp"
#include "stack.hpp" #include "stack.hpp"
#include "version.h" #include "lexer.hpp"
//< convinient structure to preprocess a program //< convinient structure to preprocess a program
struct if_layout_t { struct if_layout_t {
@ -42,7 +30,7 @@ struct if_layout_t {
}; };
//< program class: the class containing a string parser, all the programs keywords, a stack for running the program //< program class: the class containing a string parser, all the programs keywords, a stack for running the program
class program : public deque<object*> { class program : public deque<object*>, public Lexer {
public: public:
program(rpnstack& stk, heap& hp, program* parent = nullptr):_stack(stk),_heap(hp),_parent(parent) { program(rpnstack& stk, heap& hp, program* parent = nullptr):_stack(stk),_heap(hp),_parent(parent) {
interrupt_now = false; interrupt_now = false;
@ -53,9 +41,7 @@ class program : public deque<object*> {
} }
// parser // parser
static ret_value parse(string& entry, program& prog); ret_value parse(string& entry);
static ret_value entry(program& prog);
static void entry_completion_generator(const char* text, linenoiseCompletions* lc);
static ret_value get_fn(const char* fn_name, program_fn_t& fn, cmd_type_t& type); static ret_value get_fn(const char* fn_name, program_fn_t& fn, cmd_type_t& type);
// running // running
@ -74,6 +60,8 @@ class program : public deque<object*> {
static void apply_default(); static void apply_default();
static vector<string>& getAutocompletionWords();
private: private:
bool interrupt_now; bool interrupt_now;
@ -96,16 +84,16 @@ class program : public deque<object*> {
int stack_size() { return _stack.size(); } int stack_size() { return _stack.size(); }
private: private:
static const char* s_ret_value_string[ret_max];
// keywords // keywords
struct keyword_t { struct keyword_t {
cmd_type_t type; cmd_type_t type;
char name[MAX_COMMAND_LENGTH]; string name;
program_fn_t fn; program_fn_t fn;
string comment; string comment;
}; };
static keyword_t s_keywords[]; static vector<keyword_t> _keywords;
static map<string, Lexer::ReservedWord> _keywordsMap;
static vector<string> _autocompletionWords;
// keywords implementation // keywords implementation
//// ////
@ -285,26 +273,22 @@ class program : public deque<object*> {
// convenience macros for rpn_xx function // convenience macros for rpn_xx function
// carefull : some of these macros modify program flow // carefull : some of these macros modify program flow
#define ERR_CONTEXT(err) do { _err = (err); _err_context = __FUNCTION__; } while (0) #define setErrorContext(err) do { _err = (err); _err_context = __FUNCTION__; } while (0)
#define MIN_ARGUMENTS(num) do { \ #define MIN_ARGUMENTS(num) do { \
if (stack_size() < (num)) { ERR_CONTEXT(ret_missing_operand); return; } \ if (stack_size() < (num)) { setErrorContext(ret_missing_operand); return; } \
} while (0) } while (0)
#define MIN_ARGUMENTS_RET(num, ret) do { \ #define MIN_ARGUMENTS_RET(num, ret) do { \
if (stack_size() < (num)) { ERR_CONTEXT(ret_missing_operand); return (ret); } \ if (stack_size() < (num)) { setErrorContext(ret_missing_operand); return (ret); } \
} while (0) } while (0)
#define ARG_MUST_BE_OF_TYPE(num, type) do { \ #define ARG_MUST_BE_OF_TYPE(num, type) do { \
if (_stack.at(num)->_type != (type)) { ERR_CONTEXT(ret_bad_operand_type); return; } \ if (_stack.at(num)->_type != (type)) { setErrorContext(ret_bad_operand_type); return; } \
} while (0) } while (0)
#define ARG_MUST_BE_OF_TYPE_RET(num, type, ret) do { \ #define ARG_MUST_BE_OF_TYPE_RET(num, type, ret) do { \
if (_stack.at(num)->_type != (type)) { ERR_CONTEXT(ret_bad_operand_type); return (ret); } \ if (_stack.at(num)->_type != (type)) { setErrorContext(ret_bad_operand_type); return (ret); } \
} while (0) } while (0)
#define IS_ARG_TYPE(num, type) (_stack.at(num)->_type == (type))
#define CHECK_MPFR(op) do { (void)(op); } while (0)
#endif #endif

View file

@ -1,4 +1,34 @@
#include <stdio.h>
#include "escape.h"
#include "linenoise.h"
#include "program.hpp" #include "program.hpp"
#include "version.h"
// description
#define XSTR(a) STR(a)
#define STR(a) #a
static const string _description{
ATTR_BOLD "R" ATTR_OFF "everse " ATTR_BOLD "P" ATTR_OFF "olish " ATTR_BOLD "N" ATTR_OFF
"otation language\n\n"
"using " ATTR_BOLD "GMP" ATTR_OFF " v" XSTR(__GNU_MP_VERSION) "." XSTR(__GNU_MP_VERSION_MINOR) "." XSTR(
__GNU_MP_VERSION_PATCHLEVEL) " under GNU LGPL\n" ATTR_BOLD "MPFR" ATTR_OFF " v" MPFR_VERSION_STRING
" under GNU LGPL\n"
"and " ATTR_BOLD "linenoise-ng" ATTR_OFF " v" LINENOISE_VERSION
" under BSD\n"};
// syntax
static const string _syntax{ATTR_BOLD "Syntax" ATTR_OFF
": rpn [command]\n"
"with optional command = list of commands"};
static const map<string, mpfr_rnd_t> _mpfr_round{{"nearest (even)", MPFR_RNDN},
{"toward zero", MPFR_RNDZ},
{"toward +inf", MPFR_RNDU},
{"toward -inf", MPFR_RNDD},
{"away from zero", MPFR_RNDA},
{"faithful rounding", MPFR_RNDF},
{"nearest (away from zero)", MPFR_RNDNA}};
/// @brief nop keyword implementation /// @brief nop keyword implementation
/// ///
@ -8,33 +38,31 @@ void program::rpn_nop() {
/// @brief quit keyword implementation /// @brief quit keyword implementation
/// ///
void program::rpn_good_bye() { ERR_CONTEXT(ret_good_bye); } void program::rpn_good_bye() { setErrorContext(ret_good_bye); }
/// @brief nop keyword implementation /// @brief nop keyword implementation
/// the result is written on stdout /// the result is written on stdout
/// ///
void program::rpn_help() { void program::rpn_help() {
// software name // software name
cout << endl << ATTR_BOLD << uname << ATTR_OFF << endl; cout << endl << ATTR_BOLD << RPN_UNAME << ATTR_OFF << endl;
// description // _description
cout << description << endl << endl; cout << _description << endl << endl;
// syntax // _syntax
cout << syntax << endl; cout << _syntax << endl;
// keywords // keywords
unsigned int i = 0; unsigned int i = 0;
while (s_keywords[i].type != cmd_max) { for (auto& kw : _keywords)
if (s_keywords[i].comment.size() != 0) { if (!kw.comment.empty()) {
// titles in bold // titles in bold
if (s_keywords[i].type == cmd_undef) cout << ATTR_BOLD; if (kw.type == cmd_undef) cout << ATTR_BOLD;
// show title or keyword + comment // show title or keyword + comment
cout << s_keywords[i].name << '\t' << s_keywords[i].comment << endl; cout << kw.name << '\t' << kw.comment << endl;
if (s_keywords[i].type == cmd_undef) cout << ATTR_OFF; if (kw.type == cmd_undef) cout << ATTR_OFF;
} }
i++;
}
cout << endl; cout << endl;
// show mode // show mode
@ -57,14 +85,9 @@ void program::rpn_help() {
// bits precision, decimal digits and rounding mode // bits precision, decimal digits and rounding mode
cout << " with " << number::s_digits << " digits after the decimal point" << endl; cout << " with " << number::s_digits << " digits after the decimal point" << endl;
cout << "Current floating point precision is " << (int)mpreal::get_default_prec() << " bits" << endl; cout << "Current floating point precision is " << (int)mpreal::get_default_prec() << " bits" << endl;
struct RndStrings { for (auto& rn : _mpfr_round)
string name; if (rn.second == mpreal::get_default_rnd()) {
mp_rnd_t rnd; cout << "Current rounding mode is '" << rn.first << '\'' << endl;
};
vector<RndStrings> rndStrings{MPFR_ROUND_STRINGS};
for (RndStrings r : rndStrings)
if (r.rnd == mpreal::get_default_rnd()) {
cout << "Current rounding mode is " << r.name << endl;
break; break;
} }
cout << endl << endl; cout << endl << endl;
@ -103,7 +126,7 @@ void program::rpn_std() {
number::s_digits = (int)digits; number::s_digits = (int)digits;
_stack.pop(); _stack.pop();
} else } else
ERR_CONTEXT(ret_out_of_range); setErrorContext(ret_out_of_range);
} }
/// @brief fix keyword implementation /// @brief fix keyword implementation
@ -120,7 +143,7 @@ void program::rpn_fix() {
number::s_digits = (int)digits; number::s_digits = (int)digits;
_stack.pop(); _stack.pop();
} else } else
ERR_CONTEXT(ret_out_of_range); setErrorContext(ret_out_of_range);
} }
/// @brief sci keyword implementation /// @brief sci keyword implementation
@ -137,16 +160,16 @@ void program::rpn_sci() {
number::s_digits = (int)digits; number::s_digits = (int)digits;
_stack.pop(); _stack.pop();
} else } else
ERR_CONTEXT(ret_out_of_range); setErrorContext(ret_out_of_range);
} }
/// @brief version keyword implementation /// @brief _version keyword implementation
/// ///
void program::rpn_version() { _stack.push_front(new ostring(version)); } void program::rpn_version() { _stack.push_front(new ostring(RPN_VERSION)); }
/// @brief uname keyword implementation /// @brief _uname keyword implementation
/// ///
void program::rpn_uname() { _stack.push_front(new ostring(uname)); } void program::rpn_uname() { _stack.push_front(new ostring(RPN_UNAME)); }
/// @brief history keyword implementation /// @brief history keyword implementation
/// ///
@ -154,7 +177,7 @@ void program::rpn_history() {
// see command history on stdout // see command history on stdout
int index = 0; int index = 0;
char* line = linenoiseHistoryLine(index); char* line = linenoiseHistoryLine(index);
while (line != NULL) { while (line != nullptr) {
cout << line << endl; cout << line << endl;
free(line); free(line);
line = linenoiseHistoryLine(++index); line = linenoiseHistoryLine(++index);
@ -191,7 +214,7 @@ void program::rpn_precision() {
} }
_stack.pop(); _stack.pop();
} else } else
ERR_CONTEXT(ret_out_of_range); setErrorContext(ret_out_of_range);
} }
/// @brief round keyword implementation /// @brief round keyword implementation
@ -200,12 +223,12 @@ void program::rpn_round() {
MIN_ARGUMENTS(1); MIN_ARGUMENTS(1);
ARG_MUST_BE_OF_TYPE(0, cmd_string); ARG_MUST_BE_OF_TYPE(0, cmd_string);
map<string, mpfr_rnd_t> matchRound{MPFR_ROUND_STRINGS}; map<string, mpfr_rnd_t> matchRound{_mpfr_round};
auto found = matchRound.find(_stack.value<ostring>(0)); auto found = matchRound.find(_stack.value<ostring>(0));
if (found != matchRound.end()) if (found != matchRound.end())
mpreal::set_default_rnd(found->second); mpreal::set_default_rnd(found->second);
else else
ERR_CONTEXT(ret_out_of_range); setErrorContext(ret_out_of_range);
_stack.pop(); _stack.pop();
} }

View file

@ -13,7 +13,7 @@ void program::rpn_log10() {
else if (_stack.type(0) == cmd_complex) else if (_stack.type(0) == cmd_complex)
_stack.value<ocomplex>(0) = log10(_stack.value<ocomplex>(0)); _stack.value<ocomplex>(0) = log10(_stack.value<ocomplex>(0));
else else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief alog10 keyword implementation /// @brief alog10 keyword implementation
@ -25,7 +25,7 @@ void program::rpn_alog10() {
else if (_stack.type(0) == cmd_complex) else if (_stack.type(0) == cmd_complex)
_stack.value<ocomplex>(0) = exp(log(mpreal(10)) * _stack.value<ocomplex>(0)); _stack.value<ocomplex>(0) = exp(log(mpreal(10)) * _stack.value<ocomplex>(0));
else else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief log2 keyword implementation /// @brief log2 keyword implementation
@ -37,7 +37,7 @@ void program::rpn_log2() {
else if (_stack.type(0) == cmd_complex) else if (_stack.type(0) == cmd_complex)
_stack.value<ocomplex>(0) = log(_stack.value<ocomplex>(0)) / const_log2(); _stack.value<ocomplex>(0) = log(_stack.value<ocomplex>(0)) / const_log2();
else else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief alog2 keyword implementation /// @brief alog2 keyword implementation
@ -49,7 +49,7 @@ void program::rpn_alog2() {
else if (_stack.type(0) == cmd_complex) else if (_stack.type(0) == cmd_complex)
_stack.value<ocomplex>(0) = exp(const_log2() * _stack.value<ocomplex>(0)); _stack.value<ocomplex>(0) = exp(const_log2() * _stack.value<ocomplex>(0));
else else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief ln keyword implementation /// @brief ln keyword implementation
@ -61,7 +61,7 @@ void program::rpn_ln() {
else if (_stack.type(0) == cmd_complex) else if (_stack.type(0) == cmd_complex)
_stack.value<ocomplex>(0) = log(_stack.value<ocomplex>(0)); _stack.value<ocomplex>(0) = log(_stack.value<ocomplex>(0));
else else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief exp keyword implementation /// @brief exp keyword implementation
@ -73,7 +73,7 @@ void program::rpn_exp() {
else if (_stack.type(0) == cmd_complex) else if (_stack.type(0) == cmd_complex)
_stack.value<ocomplex>(0) = exp(_stack.value<ocomplex>(0)); _stack.value<ocomplex>(0) = exp(_stack.value<ocomplex>(0));
else else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief expm keyword implementation /// @brief expm keyword implementation
@ -85,7 +85,7 @@ void program::rpn_expm() {
else if (_stack.type(0) == cmd_complex) else if (_stack.type(0) == cmd_complex)
_stack.value<ocomplex>(0) = exp(_stack.value<ocomplex>(0)) - mpreal(1); _stack.value<ocomplex>(0) = exp(_stack.value<ocomplex>(0)) - mpreal(1);
else else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief lnp1 keyword implementation /// @brief lnp1 keyword implementation
@ -97,7 +97,7 @@ void program::rpn_lnp1() {
else if (_stack.type(0) == cmd_complex) else if (_stack.type(0) == cmd_complex)
_stack.value<ocomplex>(0) = log(_stack.value<ocomplex>(0) + mpreal(1)); _stack.value<ocomplex>(0) = log(_stack.value<ocomplex>(0) + mpreal(1));
else else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief sinh keyword implementation /// @brief sinh keyword implementation
@ -109,7 +109,7 @@ void program::rpn_sinh() {
else if (_stack.type(0) == cmd_complex) else if (_stack.type(0) == cmd_complex)
_stack.value<ocomplex>(0) = sinh(_stack.value<ocomplex>(0)); _stack.value<ocomplex>(0) = sinh(_stack.value<ocomplex>(0));
else else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief asinh keyword implementation /// @brief asinh keyword implementation
@ -121,7 +121,7 @@ void program::rpn_asinh() {
else if (_stack.type(0) == cmd_complex) else if (_stack.type(0) == cmd_complex)
_stack.value<ocomplex>(0) = asinh(_stack.value<ocomplex>(0)); _stack.value<ocomplex>(0) = asinh(_stack.value<ocomplex>(0));
else else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief cosh keyword implementation /// @brief cosh keyword implementation
@ -133,7 +133,7 @@ void program::rpn_cosh() {
else if (_stack.type(0) == cmd_complex) else if (_stack.type(0) == cmd_complex)
_stack.value<ocomplex>(0) = cosh(_stack.value<ocomplex>(0)); _stack.value<ocomplex>(0) = cosh(_stack.value<ocomplex>(0));
else else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief acosh keyword implementation /// @brief acosh keyword implementation
@ -145,7 +145,7 @@ void program::rpn_acosh() {
else if (_stack.type(0) == cmd_complex) else if (_stack.type(0) == cmd_complex)
_stack.value<ocomplex>(0) = acosh(_stack.value<ocomplex>(0)); _stack.value<ocomplex>(0) = acosh(_stack.value<ocomplex>(0));
else else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief tanh keyword implementation /// @brief tanh keyword implementation
@ -157,7 +157,7 @@ void program::rpn_tanh() {
else if (_stack.type(0) == cmd_complex) else if (_stack.type(0) == cmd_complex)
_stack.value<ocomplex>(0) = tanh(_stack.value<ocomplex>(0)); _stack.value<ocomplex>(0) = tanh(_stack.value<ocomplex>(0));
else else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief atanh keyword implementation /// @brief atanh keyword implementation
@ -169,5 +169,5 @@ void program::rpn_atanh() {
else if (_stack.type(0) == cmd_complex) else if (_stack.type(0) == cmd_complex)
_stack.value<ocomplex>(0) = atanh(_stack.value<ocomplex>(0)); _stack.value<ocomplex>(0) = atanh(_stack.value<ocomplex>(0));
else else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }

View file

@ -14,7 +14,7 @@ bool program::find_variable(string& variable, object*& obj) {
if (_local_heap.get(variable, obj)) if (_local_heap.get(variable, obj))
found = true; found = true;
else { else {
while (parent != NULL) { while (parent != nullptr) {
if (parent->_local_heap.get(variable, obj)) { if (parent->_local_heap.get(variable, obj)) {
found = true; found = true;
break; break;
@ -36,7 +36,7 @@ void program::rpn_eval(void) {
string prog_text; string prog_text;
MIN_ARGUMENTS(1); MIN_ARGUMENTS(1);
if (IS_ARG_TYPE(0, cmd_symbol)) { if (_stack.type(0) == cmd_symbol) {
// recall a variable // recall a variable
object* obj; object* obj;
string variable(_stack.value<symbol>(0)); string variable(_stack.value<symbol>(0));
@ -53,21 +53,21 @@ void program::rpn_eval(void) {
_stack.push_front(obj); _stack.push_front(obj);
} }
} else } else
ERR_CONTEXT(ret_unknown_variable); setErrorContext(ret_unknown_variable);
} else if (IS_ARG_TYPE(0, cmd_program)) { } else if (_stack.type(0) == cmd_program) {
// eval a program // eval a program
prog_text = _stack.value<oprogram>(0); prog_text = _stack.value<oprogram>(0);
_stack.pop(); _stack.pop();
run_prog = true; run_prog = true;
} else } else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
// run prog if any // run prog if any
if (run_prog) { if (run_prog) {
program prog(_stack, _heap, this); program prog(_stack, _heap, this);
// make program from entry // make program from entry
if (program::parse(prog_text, prog) == ret_ok) { if (prog.parse(prog_text) == ret_ok) {
// run it // run it
prog.run(); prog.run();
} }
@ -82,7 +82,7 @@ int program::rpn_inprog(branch& myobj) {
bool prog_found = false; bool prog_found = false;
if (myobj.arg1 == -1) { if (myobj.arg1 == -1) {
ERR_CONTEXT(ret_unknown_err); setErrorContext(ret_unknown_err);
return -1; return -1;
} }
@ -92,15 +92,15 @@ int program::rpn_inprog(branch& myobj) {
// find next oprogram object // find next oprogram object
for (unsigned int i = myobj.arg1 + 1; i < size(); i++) { for (unsigned int i = myobj.arg1 + 1; i < size(); i++) {
// count symbol // count symbol
if ((*this)[i]->_type == cmd_symbol) count_symbols++; if (at(i)->_type == cmd_symbol) count_symbols++;
// stop if prog // stop if prog
else if ((*this)[i]->_type == cmd_program) { else if (at(i)->_type == cmd_program) {
prog_found = true; prog_found = true;
break; break;
} }
// found something other than symbol // found something other than symbol
else { else {
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
show_error(_err, context); show_error(_err, context);
return -1; return -1;
} }
@ -108,37 +108,37 @@ int program::rpn_inprog(branch& myobj) {
// found 0 symbols // found 0 symbols
if (count_symbols == 0) { if (count_symbols == 0) {
ERR_CONTEXT(ret_syntax); setErrorContext(ret_syntax);
show_error(_err, context); show_error(_err, context);
return -1; return -1;
} }
// <oprogram> is missing // <oprogram> is missing
if (!prog_found) { if (!prog_found) {
ERR_CONTEXT(ret_syntax); setErrorContext(ret_syntax);
show_error(_err, context); show_error(_err, context);
return -1; return -1;
} }
// check symbols number vs stack size // check symbols number vs stack size
if (stack_size() < count_symbols) { if (stack_size() < count_symbols) {
ERR_CONTEXT(ret_missing_operand); setErrorContext(ret_missing_operand);
show_error(_err, context); show_error(_err, context);
return -1; return -1;
} }
// load variables // load variables
for (unsigned int i = myobj.arg1 + count_symbols; i > myobj.arg1; i--) { for (unsigned int i = myobj.arg1 + count_symbols; i > myobj.arg1; i--) {
_local_heap[string(((symbol*)(*this)[i])->value)] = _stack.at(0)->clone(); _local_heap[string(((symbol*)at(i))->value)] = _stack.at(0)->clone();
_stack.pop_front(); _stack.pop_front();
} }
// run the program // run the program
string entry(((oprogram*)(*this)[myobj.arg1 + count_symbols + 1])->value); string entry(((oprogram*)at(myobj.arg1 + count_symbols + 1))->value);
program prog(_stack, _heap, this); program prog(_stack, _heap, this);
// make the program from entry // make the program from entry
if (program::parse(entry, prog) == ret_ok) { if (prog.parse(entry) == ret_ok) {
// run it // run it
prog.run(); prog.run();
} }

View file

@ -31,7 +31,7 @@ void program::rpn_plus() {
_stack.value<ocomplex>(1) += _stack.value<number>(0); _stack.value<ocomplex>(1) += _stack.value<number>(0);
_stack.pop(); _stack.pop();
} else } else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief - keyword implementation /// @brief - keyword implementation
@ -60,7 +60,7 @@ void program::rpn_minus() {
_stack.value<ocomplex>(1) = _stack.value<number>(0) - _stack.value<ocomplex>(1); _stack.value<ocomplex>(1) = _stack.value<number>(0) - _stack.value<ocomplex>(1);
_stack.pop(); _stack.pop();
} else } else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief * keyword implementation /// @brief * keyword implementation
@ -89,7 +89,7 @@ void program::rpn_mul() {
_stack.value<ocomplex>(1) *= _stack.value<number>(0); _stack.value<ocomplex>(1) *= _stack.value<number>(0);
_stack.pop(); _stack.pop();
} else } else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief / keyword implementation /// @brief / keyword implementation
@ -118,7 +118,7 @@ void program::rpn_div() {
_stack.value<ocomplex>(1) = _stack.value<number>(0) / _stack.value<ocomplex>(1); _stack.value<ocomplex>(1) = _stack.value<number>(0) / _stack.value<ocomplex>(1);
_stack.pop(); _stack.pop();
} else } else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief neg keyword implementation /// @brief neg keyword implementation
@ -131,7 +131,7 @@ void program::rpn_neg() {
else if (_stack.type(0) == cmd_complex) else if (_stack.type(0) == cmd_complex)
_stack.value<ocomplex>(0) = -_stack.value<ocomplex>(0); _stack.value<ocomplex>(0) = -_stack.value<ocomplex>(0);
else else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief inv keyword implementation /// @brief inv keyword implementation
@ -144,7 +144,7 @@ void program::rpn_inv() {
else if (_stack.type(0) == cmd_complex) else if (_stack.type(0) == cmd_complex)
_stack.value<ocomplex>(0) = mpreal{1} / _stack.value<ocomplex>(0); _stack.value<ocomplex>(0) = mpreal{1} / _stack.value<ocomplex>(0);
else else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief power keyword implementation /// @brief power keyword implementation
@ -172,7 +172,7 @@ void program::rpn_power() {
_stack.value<ocomplex>(1) = pow(_stack.value<number>(0), _stack.value<ocomplex>(1)); _stack.value<ocomplex>(1) = pow(_stack.value<number>(0), _stack.value<ocomplex>(1));
_stack.pop(); _stack.pop();
} else } else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief sqrt keyword implementation /// @brief sqrt keyword implementation
@ -192,7 +192,7 @@ void program::rpn_squareroot() {
} else if (_stack.type(0) == cmd_complex) } else if (_stack.type(0) == cmd_complex)
_stack.value<ocomplex>(0) = sqrt(_stack.value<ocomplex>(0)); _stack.value<ocomplex>(0) = sqrt(_stack.value<ocomplex>(0));
else else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief hex keyword implementation /// @brief hex keyword implementation
@ -205,7 +205,7 @@ void program::rpn_hex() {
_stack.obj<ocomplex>(0).reBase = 16; _stack.obj<ocomplex>(0).reBase = 16;
_stack.obj<ocomplex>(0).imBase = 16; _stack.obj<ocomplex>(0).imBase = 16;
} else } else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief bin keyword implementation /// @brief bin keyword implementation
@ -218,7 +218,7 @@ void program::rpn_bin() {
_stack.obj<ocomplex>(0).reBase = 2; _stack.obj<ocomplex>(0).reBase = 2;
_stack.obj<ocomplex>(0).imBase = 2; _stack.obj<ocomplex>(0).imBase = 2;
} else } else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief dec keyword implementation /// @brief dec keyword implementation
@ -231,7 +231,7 @@ void program::rpn_dec() {
_stack.obj<ocomplex>(0).reBase = 10; _stack.obj<ocomplex>(0).reBase = 10;
_stack.obj<ocomplex>(0).imBase = 10; _stack.obj<ocomplex>(0).imBase = 10;
} else } else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief base keyword implementation /// @brief base keyword implementation
@ -249,9 +249,9 @@ void program::rpn_base() {
_stack.obj<ocomplex>(0).imBase = base; _stack.obj<ocomplex>(0).imBase = base;
} }
} else } else
ERR_CONTEXT(ret_out_of_range); setErrorContext(ret_out_of_range);
} else } else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief % (purcent) keyword implementation /// @brief % (purcent) keyword implementation
@ -283,7 +283,7 @@ void program::rpn_square() {
else if (_stack.at(0)->_type == cmd_complex) else if (_stack.at(0)->_type == cmd_complex)
_stack.value<ocomplex>(0) *= _stack.value<ocomplex>(0); _stack.value<ocomplex>(0) *= _stack.value<ocomplex>(0);
else else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief mod keyword implementation /// @brief mod keyword implementation
@ -306,7 +306,7 @@ void program::rpn_abs() {
_stack.push(new number(abs(_stack.value<ocomplex>(0)))); _stack.push(new number(abs(_stack.value<ocomplex>(0))));
_stack.erase(1); _stack.erase(1);
} else } else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief fact (factorial) keyword implementation /// @brief fact (factorial) keyword implementation
@ -327,7 +327,7 @@ void program::rpn_sign() {
else if (_stack.at(0)->_type == cmd_complex) else if (_stack.at(0)->_type == cmd_complex)
_stack.value<ocomplex>(0) = _stack.value<ocomplex>(0) / abs(_stack.value<ocomplex>(0)); _stack.value<ocomplex>(0) = _stack.value<ocomplex>(0) / abs(_stack.value<ocomplex>(0));
else else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief mant keyword implementation /// @brief mant keyword implementation
@ -336,7 +336,7 @@ void program::rpn_mant() {
MIN_ARGUMENTS(1); MIN_ARGUMENTS(1);
ARG_MUST_BE_OF_TYPE(0, cmd_number); ARG_MUST_BE_OF_TYPE(0, cmd_number);
if (!isfinite(_stack.value<number>(0))) { if (!isfinite(_stack.value<number>(0))) {
ERR_CONTEXT(ret_out_of_range); setErrorContext(ret_out_of_range);
return; return;
} }
mp_exp_t exp; mp_exp_t exp;
@ -349,7 +349,7 @@ void program::rpn_xpon() {
MIN_ARGUMENTS(1); MIN_ARGUMENTS(1);
ARG_MUST_BE_OF_TYPE(0, cmd_number); ARG_MUST_BE_OF_TYPE(0, cmd_number);
if (!isfinite(_stack.value<number>(0))) { if (!isfinite(_stack.value<number>(0))) {
ERR_CONTEXT(ret_out_of_range); setErrorContext(ret_out_of_range);
return; return;
} }
mp_exp_t exp; mp_exp_t exp;

View file

@ -77,7 +77,7 @@ void program::rpn_pick(void) {
// treat stack depth errors // treat stack depth errors
if ((to_pick == 0) || (to_pick > _stack.size())) { if ((to_pick == 0) || (to_pick > _stack.size())) {
ERR_CONTEXT(ret_out_of_range); setErrorContext(ret_out_of_range);
return; return;
} }

View file

@ -1,4 +1,5 @@
#include "program.hpp" #include "program.hpp"
#include "input.hpp"
/// @brief sto keyword implementation /// @brief sto keyword implementation
/// ///
@ -22,7 +23,7 @@ void program::rpn_stoadd(void) {
MIN_ARGUMENTS(2); MIN_ARGUMENTS(2);
ARG_MUST_BE_OF_TYPE(0, cmd_symbol); ARG_MUST_BE_OF_TYPE(0, cmd_symbol);
if (_heap.find(_stack.value<ostring>(0)) == _heap.end()) { if (_heap.find(_stack.value<ostring>(0)) == _heap.end()) {
ERR_CONTEXT(ret_unknown_variable); setErrorContext(ret_unknown_variable);
return; return;
} }
rpn_dup(); rpn_dup();
@ -39,7 +40,7 @@ void program::rpn_stosub(void) {
MIN_ARGUMENTS(2); MIN_ARGUMENTS(2);
ARG_MUST_BE_OF_TYPE(0, cmd_symbol); ARG_MUST_BE_OF_TYPE(0, cmd_symbol);
if (_heap.find(_stack.value<ostring>(0)) == _heap.end()) { if (_heap.find(_stack.value<ostring>(0)) == _heap.end()) {
ERR_CONTEXT(ret_unknown_variable); setErrorContext(ret_unknown_variable);
return; return;
} }
rpn_dup(); rpn_dup();
@ -56,7 +57,7 @@ void program::rpn_stomul(void) {
MIN_ARGUMENTS(2); MIN_ARGUMENTS(2);
ARG_MUST_BE_OF_TYPE(0, cmd_symbol); ARG_MUST_BE_OF_TYPE(0, cmd_symbol);
if (_heap.find(_stack.value<ostring>(0)) == _heap.end()) { if (_heap.find(_stack.value<ostring>(0)) == _heap.end()) {
ERR_CONTEXT(ret_unknown_variable); setErrorContext(ret_unknown_variable);
return; return;
} }
rpn_dup(); rpn_dup();
@ -73,7 +74,7 @@ void program::rpn_stodiv(void) {
MIN_ARGUMENTS(2); MIN_ARGUMENTS(2);
ARG_MUST_BE_OF_TYPE(0, cmd_symbol); ARG_MUST_BE_OF_TYPE(0, cmd_symbol);
if (_heap.find(_stack.value<ostring>(0)) == _heap.end()) { if (_heap.find(_stack.value<ostring>(0)) == _heap.end()) {
ERR_CONTEXT(ret_unknown_variable); setErrorContext(ret_unknown_variable);
return; return;
} }
rpn_dup(); rpn_dup();
@ -90,7 +91,7 @@ void program::rpn_stoneg(void) {
MIN_ARGUMENTS(1); MIN_ARGUMENTS(1);
ARG_MUST_BE_OF_TYPE(0, cmd_symbol); ARG_MUST_BE_OF_TYPE(0, cmd_symbol);
if (_heap.find(_stack.value<ostring>(0)) == _heap.end()) { if (_heap.find(_stack.value<ostring>(0)) == _heap.end()) {
ERR_CONTEXT(ret_unknown_variable); setErrorContext(ret_unknown_variable);
return; return;
} }
rpn_dup(); rpn_dup();
@ -106,7 +107,7 @@ void program::rpn_stoinv(void) {
MIN_ARGUMENTS(1); MIN_ARGUMENTS(1);
ARG_MUST_BE_OF_TYPE(0, cmd_symbol); ARG_MUST_BE_OF_TYPE(0, cmd_symbol);
if (_heap.find(_stack.value<ostring>(0)) == _heap.end()) { if (_heap.find(_stack.value<ostring>(0)) == _heap.end()) {
ERR_CONTEXT(ret_unknown_variable); setErrorContext(ret_unknown_variable);
return; return;
} }
rpn_dup(); rpn_dup();
@ -131,7 +132,7 @@ void program::rpn_rcl(void) {
(void)_stack.pop_front(); (void)_stack.pop_front();
_stack.push_front(obj->clone()); _stack.push_front(obj->clone());
} else } else
ERR_CONTEXT(ret_unknown_variable); setErrorContext(ret_unknown_variable);
} }
/// @brief edit keyword implementation /// @brief edit keyword implementation
@ -146,7 +147,7 @@ void program::rpn_edit(void) {
_stack.pop(); _stack.pop();
// set it as the linenoise line entry // set it as the linenoise line entry
linenoisePreloadBuffer((const char*)st.str().c_str()); Input::preload(st.str().c_str());
} }
/// @brief recall then eval a symbol variable if it is auto-evaluable /// @brief recall then eval a symbol variable if it is auto-evaluable
@ -179,7 +180,7 @@ void program::rpn_purge(void) {
delete i->second; delete i->second;
_heap.erase(i); _heap.erase(i);
} else } else
ERR_CONTEXT(ret_unknown_variable); setErrorContext(ret_unknown_variable);
_stack.pop(); _stack.pop();
} }
@ -199,7 +200,7 @@ void program::rpn_vars(void) {
} }
// parents local variables // parents local variables
while (parent != NULL) { while (parent != nullptr) {
for (int i = 0; i < (int)parent->_local_heap.size(); i++) { for (int i = 0; i < (int)parent->_local_heap.size(); i++) {
(void)parent->_local_heap.get_by_index(i, name, obj); (void)parent->_local_heap.get_by_index(i, name, obj);
cout<<"var "<<i+1<<": name '"<<name<<"', type "<<obj->name()<<", value "; cout<<"var "<<i+1<<": name '"<<name<<"', type "<<obj->name()<<", value ";

View file

@ -27,7 +27,7 @@ void program::rpn_strout() {
_stack.pop(); _stack.pop();
// make program from string in stack level 1 // make program from string in stack level 1
if (program::parse(entry, prog) == ret_ok) if (prog.parse(entry) == ret_ok)
// run it // run it
prog.run(); prog.run();
} }

View file

@ -1,5 +1,8 @@
#include <regex> #include <regex>
using namespace std;
#include "version.h"
#include "escape.h"
#include "program.hpp" #include "program.hpp"
/// @brief write stack in a string, each entry separated between commas /// @brief write stack in a string, each entry separated between commas
@ -59,9 +62,9 @@ void program::rpn_test() {
string test_filename = _stack.value<ostring>(0); string test_filename = _stack.value<ostring>(0);
_stack.pop(); _stack.pop();
cout << endl << "rpn version is " << version << endl; cout << endl << "rpn version is " << RPN_VERSION << endl;
test(test_filename, total_tests, total_tests_failed, total_steps, total_steps_failed); test(test_filename, total_tests, total_tests_failed, total_steps, total_steps_failed);
test_show_result("Total", total_tests, total_tests_failed, total_steps, total_steps_failed); test_show_result("\nTotal", total_tests, total_tests_failed, total_steps, total_steps_failed);
} }
/// @brief load a test file and run its tests /// @brief load a test file and run its tests
@ -228,7 +231,7 @@ void program::test(string test_filename, int& total_tests, int& total_tests_fail
entry = regex_replace(entry, regex("`"), ""); entry = regex_replace(entry, regex("`"), "");
if (!entry.empty()) { if (!entry.empty()) {
program prog(stk, hp); program prog(stk, hp);
ret = program::parse(entry, prog); ret = prog.parse(entry);
if (ret == ret_ok) { if (ret == ret_ok) {
// run it // run it
(void)prog.run(); (void)prog.run();

View file

@ -28,7 +28,7 @@ void program::rpn_sup(void) {
_stack.push_front(new number(cmp_strings_on_stack_top() == 1)); _stack.push_front(new number(cmp_strings_on_stack_top() == 1));
_stack.erase(1, 2); _stack.erase(1, 2);
} else } else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief >= keyword implementation /// @brief >= keyword implementation
@ -46,7 +46,7 @@ void program::rpn_sup_eq(void) {
_stack.push_front(new number(cmp_strings_on_stack_top() != -1)); _stack.push_front(new number(cmp_strings_on_stack_top() != -1));
_stack.erase(1, 2); _stack.erase(1, 2);
} else } else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief < keyword implementation /// @brief < keyword implementation
@ -64,7 +64,7 @@ void program::rpn_inf(void) {
_stack.push_front(new number(cmp_strings_on_stack_top() == -1)); _stack.push_front(new number(cmp_strings_on_stack_top() == -1));
_stack.erase(1, 2); _stack.erase(1, 2);
} else } else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief <= keyword implementation /// @brief <= keyword implementation
@ -80,7 +80,7 @@ void program::rpn_inf_eq(void) {
_stack.push_front(new number(cmp_strings_on_stack_top() != 1)); _stack.push_front(new number(cmp_strings_on_stack_top() != 1));
_stack.erase(1, 2); _stack.erase(1, 2);
} else } else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief != keyword implementation /// @brief != keyword implementation
@ -103,7 +103,7 @@ void program::rpn_diff(void) {
_stack.push_front(new number(cmp_strings_on_stack_top() != 0)); _stack.push_front(new number(cmp_strings_on_stack_top() != 0));
_stack.erase(1, 2); _stack.erase(1, 2);
} else } else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief == keyword implementation /// @brief == keyword implementation
@ -126,7 +126,7 @@ void program::rpn_eq(void) {
_stack.push_front(new number(cmp_strings_on_stack_top() == 0)); _stack.push_front(new number(cmp_strings_on_stack_top() == 0));
_stack.erase(1, 2); _stack.erase(1, 2);
} else } else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief and keyword implementation /// @brief and keyword implementation

View file

@ -13,7 +13,7 @@ void program::rpn_time() {
clock_gettime(CLOCK_REALTIME, &ts); clock_gettime(CLOCK_REALTIME, &ts);
time_t time = (time_t)ts.tv_sec; time_t time = (time_t)ts.tv_sec;
tm = localtime(&time); tm = localtime(&time);
if (tm != NULL) { if (tm != nullptr) {
// date format = HH.MMSSssssss // date format = HH.MMSSssssss
date = ((double)tm->tm_hour) * 10000000000.0 + ((double)tm->tm_min) * 100000000.0 + date = ((double)tm->tm_hour) * 10000000000.0 + ((double)tm->tm_min) * 100000000.0 +
((double)tm->tm_sec) * 1000000.0 + (double)(ts.tv_nsec / 1000); ((double)tm->tm_sec) * 1000000.0 + (double)(ts.tv_nsec / 1000);
@ -23,7 +23,7 @@ void program::rpn_time() {
_stack.push(new number(date)); _stack.push(new number(date));
_stack.value<number>(0) /= 10000000000.0; _stack.value<number>(0) /= 10000000000.0;
} else } else
ERR_CONTEXT(ret_internal); setErrorContext(ret_internal);
} }
/// @brief date keyword implementation /// @brief date keyword implementation
@ -37,7 +37,7 @@ void program::rpn_date() {
clock_gettime(CLOCK_REALTIME, &ts); clock_gettime(CLOCK_REALTIME, &ts);
time_t time = (time_t)ts.tv_sec; time_t time = (time_t)ts.tv_sec;
tm = localtime(&time); tm = localtime(&time);
if (tm != NULL) { if (tm != nullptr) {
// date format = (M)M.DDYYYY // date format = (M)M.DDYYYY
date = (double)(tm->tm_mon + 1) * 1000000.0 + (double)(tm->tm_mday) * 10000.0 + (double)(tm->tm_year + 1900); date = (double)(tm->tm_mon + 1) * 1000000.0 + (double)(tm->tm_mday) * 10000.0 + (double)(tm->tm_year + 1900);
@ -47,7 +47,7 @@ void program::rpn_date() {
_stack.push(new number(date)); _stack.push(new number(date));
_stack.value<number>(0) /= 1000000.0; _stack.value<number>(0) /= 1000000.0;
} else } else
ERR_CONTEXT(ret_internal); setErrorContext(ret_internal);
} }
/// @brief ticks keyword implementation /// @brief ticks keyword implementation
@ -61,10 +61,10 @@ void program::rpn_ticks() {
clock_gettime(CLOCK_REALTIME, &ts); clock_gettime(CLOCK_REALTIME, &ts);
time_t time = (time_t)ts.tv_sec; time_t time = (time_t)ts.tv_sec;
tm = localtime(&time); tm = localtime(&time);
if (tm != NULL) { if (tm != nullptr) {
// date in µs // date in µs
date = 1000000.0 * (double)ts.tv_sec + (double)(ts.tv_nsec / 1000); date = 1000000.0 * (double)ts.tv_sec + (double)(ts.tv_nsec / 1000);
_stack.push(new number(date)); _stack.push(new number(date));
} else } else
ERR_CONTEXT(ret_internal); setErrorContext(ret_internal);
} }

View file

@ -34,7 +34,7 @@ void program::rpn_sin(void) {
else if (_stack.type(0) == cmd_complex) else if (_stack.type(0) == cmd_complex)
_stack.value<ocomplex>(0) = sin(_stack.value<ocomplex>(0)); _stack.value<ocomplex>(0) = sin(_stack.value<ocomplex>(0));
else else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief asin keyword implementation /// @brief asin keyword implementation
@ -47,7 +47,7 @@ void program::rpn_asin(void) {
else if (_stack.type(0) == cmd_complex) else if (_stack.type(0) == cmd_complex)
_stack.value<ocomplex>(0) = asin(_stack.value<ocomplex>(0)); _stack.value<ocomplex>(0) = asin(_stack.value<ocomplex>(0));
else else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief cos keyword implementation /// @brief cos keyword implementation
@ -60,7 +60,7 @@ void program::rpn_cos(void) {
else if (_stack.type(0) == cmd_complex) else if (_stack.type(0) == cmd_complex)
_stack.value<ocomplex>(0) = cos(_stack.value<ocomplex>(0)); _stack.value<ocomplex>(0) = cos(_stack.value<ocomplex>(0));
else else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief acos keyword implementation /// @brief acos keyword implementation
@ -73,7 +73,7 @@ void program::rpn_acos(void) {
else if (_stack.type(0) == cmd_complex) else if (_stack.type(0) == cmd_complex)
_stack.value<ocomplex>(0) = acos(_stack.value<ocomplex>(0)); _stack.value<ocomplex>(0) = acos(_stack.value<ocomplex>(0));
else else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief tan keyword implementation /// @brief tan keyword implementation
@ -86,7 +86,7 @@ void program::rpn_tan(void) {
else if (_stack.type(0) == cmd_complex) else if (_stack.type(0) == cmd_complex)
_stack.value<ocomplex>(0) = tan(_stack.value<ocomplex>(0)); _stack.value<ocomplex>(0) = tan(_stack.value<ocomplex>(0));
else else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }
/// @brief atan keyword implementation /// @brief atan keyword implementation
@ -99,5 +99,5 @@ void program::rpn_atan(void) {
else if (_stack.type(0) == cmd_complex) else if (_stack.type(0) == cmd_complex)
_stack.value<ocomplex>(0) = atan(_stack.value<ocomplex>(0)); _stack.value<ocomplex>(0) = atan(_stack.value<ocomplex>(0));
else else
ERR_CONTEXT(ret_bad_operand_type); setErrorContext(ret_bad_operand_type);
} }

View file

@ -1,23 +1,2 @@
// version and soft name
#define RPN_VERSION "2.4" #define RPN_VERSION "2.4"
static const string version{RPN_VERSION}; #define RPN_UNAME "rpn v" RPN_VERSION ", (c) 2017 <louis@rubet.fr>, GNU LGPL v3"
static const string uname{"rpn v" RPN_VERSION ", (c) 2017 <louis@rubet.fr>, GNU LGPL v3"};
#define XSTR(a) STR(a)
#define STR(a) #a
// description
static const string description{
ATTR_BOLD "R" ATTR_OFF "everse " ATTR_BOLD "P" ATTR_OFF "olish " ATTR_BOLD "N" ATTR_OFF
"otation language\n\n"
"using " ATTR_BOLD "GMP" ATTR_OFF
" v" XSTR(__GNU_MP_VERSION) "." XSTR(__GNU_MP_VERSION_MINOR) "." XSTR(
__GNU_MP_VERSION_PATCHLEVEL) " under GNU LGPL\n" ATTR_BOLD "MPFR" ATTR_OFF " v" MPFR_VERSION_STRING
" under GNU LGPL\n"
"and " ATTR_BOLD "linenoise-ng" ATTR_OFF " v" LINENOISE_VERSION
" under BSD\n"};
// syntax
static const string syntax{ATTR_BOLD "Syntax" ATTR_OFF
": rpn [command]\n"
"with optional command = list of commands"};