diff --git a/README.md b/README.md index 8d6a85d..4731a09 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Some of the supported features are: - Integer conversion(`$`); - Bitwise operations(`{`, `}`, `l`, `L`, `m`, `M`); - Stack operations: - - Print top element(`p`, `P`); + - Print top element(`p`, `P`, `p.`); - Clear the stack(`c`); - Remove top element(`R`); - Swap order of top two elements(`r`); @@ -281,9 +281,8 @@ lL x 0 ;A lN / lV FF { 0 :A # Blue lV 8 M FF { 1 :A # Green lV 10 M FF { 2 :A # Red -[ RED: ] P R 2 ;A p -[ GREEN: ] P R 1 ;A p -[ BLUE: ] P R 0 ;A p +[ , ] sc +[ [ RGB( ] P 2 ;A P lc p. 1 ;A P lc p. 0 ;A P [ ) ] p. [ = ] p. lV ph ] x ``` ## License diff --git a/man.md b/man.md index 02ab558..c7b9997 100644 --- a/man.md +++ b/man.md @@ -3,7 +3,7 @@ title: dc section: 1 header: General Commands Manual footer: Marco Cetica -date: March 20, 2024 +date: March 25, 2024 --- @@ -378,11 +378,21 @@ the array `r`, indexed by the top-of-stack value. Pops the top-of-stack and uses it as an index into array `r`. The selected value is then pushed onto the stack. -## Strings +## Strings/Macros -_dc_ has a limited ability to operate on strings as well as on numbers; the only things you can do with strings are print them and execute them as macros (which means that the content of a string can execute as a _dc_ program). Any kind of stack can hold strings, and _dc_ always knows whether any given object is a string or a number. -Some commands such as arithmetic operations demand numbers as arguments and print errors if given strings. -Other commands can accept either a number or a string; for example, the **p** command can accept either and prints the object according to its type. +_dc_ has a limited ability to operate on strings as well as on numbers; strings can be printed or executed as a macro, that is as a dc program. +Strings can be nested, i.e. a string can contain another string. For example: + +- **\[ 1 \] p**: Prints the string(note: `1` is a string and not a number); +- **\[ \[ Hello World \] p \] x**: pushes a string containing another string and a command and execute it; +- **\[ Executing: \] p \[ 2 p 10 p \[ r / p \] p x \] x**: Prints the ongoing operation and execute it using homoiconicity property. + +When a string is used as a macro, dc *lazily evaluate* it; that is, the evaluation of subprograms is delayed until the evaluator requires their values. +Avoiding eagerly evaluation allows the programmer to take advantage of DC's homoiconicity and to make macro evaluation more lightweight. + +Any kind of stack can hold strings, and _dc_ always knows whether any given object is a string or a number. Some commands such as arithmetic operations demand +numbers as arguments and print errors if given strings. Other commands can accept either a number or a string; for example, the **p** +command can accept either and prints the object according to its type. **[ characters ]** @@ -568,9 +578,8 @@ lL x 0 ;A lN / lV FF { 0 :A # Blue lV 8 M FF { 1 :A # Green lV 10 M FF { 2 :A # Red -[ RED: ] P R 2 ;A p -[ GREEN: ] P R 1 ;A p -[ BLUE: ] P R 0 ;A p +[ , ] sc +[ [ RGB( ] P 2 ;A P lc p. 1 ;A P lc p. 0 ;A P [ ) ] p. [ = ] p. lV ph ] x ``` # AUTHORS diff --git a/src/eval.cpp b/src/eval.cpp index 1186b06..2034ef9 100644 --- a/src/eval.cpp +++ b/src/eval.cpp @@ -52,6 +52,7 @@ void Evaluate::init_environment() { this->op_factory.emplace("M", MAKE_UNIQUE_PTR(Bitwise, OPType::BSR)); // Stack operations this->op_factory.emplace("p", MAKE_UNIQUE_PTR(Stack, OPType::PCG)); + this->op_factory.emplace("p.", MAKE_UNIQUE_PTR(Stack, OPType::PWS)); this->op_factory.emplace("pb", MAKE_UNIQUE_PTR(Stack, OPType::PBB)); this->op_factory.emplace("ph", MAKE_UNIQUE_PTR(Stack, OPType::PBH)); this->op_factory.emplace("po", MAKE_UNIQUE_PTR(Stack, OPType::PBO)); @@ -137,17 +138,21 @@ std::optional Evaluate::parse_base_n(const std::string& token) { std::optional Evaluate::parse_macro(std::size_t &idx) { // A macro is any string surrounded by square brackets std::string dc_macro; - bool closing_bracket = false; + std::int8_t brackets_count = 1; // Scan next token idx++; // Parse the macro while(idx < this->expr.size()) { - // Continue to parse until the clsoing square brackets - if(this->expr.at(idx) == "]") { - closing_bracket = true; - break; + // Parse nested macros as well + if(this->expr.at(idx) == "[") { + brackets_count++; + } else if(this->expr.at(idx) == "]") { + brackets_count--; + if(brackets_count == 0) { + break; + } } // Otherwise append the token to the macro. @@ -164,7 +169,7 @@ std::optional Evaluate::parse_macro(std::size_t &idx) { } // Check if macro is properly formatted - if(!closing_bracket) { + if(brackets_count != 0) { return "Unbalanced parenthesis"; } @@ -201,37 +206,37 @@ std::optional Evaluate::parse_macro_command(std::string token) { // execute register's content as a macro std::optional err = std::nullopt; if(operation == ">") { - auto macro = std::make_unique(OPType::CMP, Operator::GT, dc_register); + auto macro = std::make_unique(OPType::CMP, MacroOP::GT, dc_register); err = macro->exec(this->stack, this->parameters, this->regs); if(err != std::nullopt) { return err; } } else if(operation == "<") { - auto macro = std::make_unique(OPType::CMP, Operator::LT, dc_register); + auto macro = std::make_unique(OPType::CMP, MacroOP::LT, dc_register); err = macro->exec(this->stack, this->parameters, this->regs); if(err != std::nullopt) { return err; } } else if(operation == "=") { - auto macro = std::make_unique(OPType::CMP, Operator::EQ, dc_register); + auto macro = std::make_unique(OPType::CMP, MacroOP::EQ, dc_register); err = macro->exec(this->stack, this->parameters, this->regs); if(err != std::nullopt) { return err; } } else if(operation == ">=") { - auto macro = std::make_unique(OPType::CMP, Operator::GEQ, dc_register); + auto macro = std::make_unique(OPType::CMP, MacroOP::GEQ, dc_register); err = macro->exec(this->stack, this->parameters, this->regs); if(err != std::nullopt) { return err; } } else if(operation == "<=") { - auto macro = std::make_unique(OPType::CMP, Operator::LEQ, dc_register); + auto macro = std::make_unique(OPType::CMP, MacroOP::LEQ, dc_register); err = macro->exec(this->stack, this->parameters, this->regs); if(err != std::nullopt) { return err; } } else if(operation == "!=") { - auto macro = std::make_unique(OPType::CMP, Operator::NEQ, dc_register); + auto macro = std::make_unique(OPType::CMP, MacroOP::NEQ, dc_register); err = macro->exec(this->stack, this->parameters, this->regs); if(err != std::nullopt) { return err; diff --git a/src/macro.cpp b/src/macro.cpp index 62ba1f5..21b12ce 100644 --- a/src/macro.cpp +++ b/src/macro.cpp @@ -70,7 +70,7 @@ std::optional Macro::fn_evaluate_macro(dc::Stack &stac auto second = std::stod(second_str); switch(this->op) { - case Operator::GT: { + case MacroOP::GT: { if(head > second) { std::vector tokens = split(dc_macro); Evaluate evaluator(tokens, regs, stack, parameters); @@ -82,7 +82,7 @@ std::optional Macro::fn_evaluate_macro(dc::Stack &stac } break; } - case Operator::LT: { + case MacroOP::LT: { if(head < second) { std::vector tokens = split(dc_macro); Evaluate evaluator(tokens, regs, stack, parameters); @@ -94,7 +94,7 @@ std::optional Macro::fn_evaluate_macro(dc::Stack &stac } break; } - case Operator::EQ: { + case MacroOP::EQ: { if(head == second) { std::vector tokens = split(dc_macro); Evaluate evaluator(tokens, regs, stack, parameters); @@ -106,7 +106,7 @@ std::optional Macro::fn_evaluate_macro(dc::Stack &stac } break; } - case Operator::GEQ: { + case MacroOP::GEQ: { if(head >= second) { std::vector tokens = split(dc_macro); Evaluate evaluator(tokens, regs, stack, parameters); @@ -118,7 +118,7 @@ std::optional Macro::fn_evaluate_macro(dc::Stack &stac } break; } - case Operator::LEQ: { + case MacroOP::LEQ: { if(head <= second) { std::vector tokens = split(dc_macro); Evaluate evaluator(tokens, regs, stack, parameters); @@ -130,7 +130,7 @@ std::optional Macro::fn_evaluate_macro(dc::Stack &stac } break; } - case Operator::NEQ: { + case MacroOP::NEQ: { if(head != second) { std::vector tokens = split(dc_macro); Evaluate evaluator(tokens, regs, stack, parameters); diff --git a/src/macro.h b/src/macro.h index 7fed7c6..3e73aec 100644 --- a/src/macro.h +++ b/src/macro.h @@ -2,13 +2,13 @@ #include "operation.h" -enum class Operator { +enum class MacroOP { GT, LT, EQ, GEQ, LEQ, NEQ }; class Macro : public IOperation { public: - Macro(const OPType op_t, const Operator o, const char dc_r) : op_type(op_t), op(o), dc_register(dc_r) {} + Macro(const OPType op_t, const MacroOP o, const char dc_r) : op_type(op_t), op(o), dc_register(dc_r) {} explicit Macro(const OPType op_t) : op_type(op_t) {} std::optional exec(dc::Stack &stack, dc::Parameters ¶meters, std::unordered_map ®s) override; static std::vector split(const std::string& str); @@ -20,6 +20,6 @@ private: static std::optional fn_evaluate_file(dc::Stack &stack, dc::Parameters ¶meters, std::unordered_map ®s); OPType op_type; - Operator op{}; + MacroOP op{}; char dc_register{}; }; diff --git a/src/operation.h b/src/operation.h index 3ef455a..d486ece 100644 --- a/src/operation.h +++ b/src/operation.h @@ -17,7 +17,7 @@ enum class OPType { // Bitwise operations BAND, BOR, BNOT, BXOR, BSL, BSR, // Stack operations - PCG, P, PBB, PBH, PBO, CLR, PH, SO, DP, PS, CH, CS, + PCG, PWS, P, PBB, PBH, PBO, CLR, PH, SO, DP, PS, CH, CS, SP, GP, SOR, GOR, SIR, GIR, LX, LY, LZ, // Macro operations EX, CMP, RI, LF diff --git a/src/stack.cpp b/src/stack.cpp index 8f75d81..fd6a76c 100644 --- a/src/stack.cpp +++ b/src/stack.cpp @@ -13,15 +13,16 @@ std::optional Stack::exec(dc::Stack &stack, dc::Parame auto print_oradix = [&stack, ¶meters, this](dc::radix_base base) { auto old_rdx = parameters.oradix; parameters.oradix = base; - auto res = fn_print(stack, parameters, false); + auto res = fn_print(stack, parameters, StackOP::P); parameters.oradix = old_rdx; return res; }; switch(this->op_type) { - case OPType::PCG: err = fn_print(stack, parameters, true); break; - case OPType::P: err = fn_print(stack, parameters, false); break; + case OPType::PCG: err = fn_print(stack, parameters, StackOP::PNL); break; + case OPType::P: err = fn_print(stack, parameters, StackOP::P); break; + case OPType::PWS: err = fn_print(stack, parameters, StackOP::PS); break; case OPType::PBB: err = print_oradix(dc::radix_base::BIN); break; case OPType::PBO: err = print_oradix(dc::radix_base::OCT); break; case OPType::PBH: err = print_oradix(dc::radix_base::HEX); break; @@ -47,7 +48,7 @@ std::optional Stack::exec(dc::Stack &stack, dc::Parame return err; } -std::optional Stack::fn_print(dc::Stack &stack, dc::Parameters ¶meters, bool new_line) { +std::optional Stack::fn_print(dc::Stack &stack, dc::Parameters ¶meters, const StackOP op) { // Check if the stack is empty if(stack.empty()) { return "Cannot print empty stack"; @@ -60,10 +61,10 @@ std::optional Stack::fn_print(dc::Stack &stack, dc::Pa switch(parameters.oradix) { case dc::radix_base::DEC: { - if(new_line) { - std::cout << stack.pop(false) << std::endl; - } else { - std::cout << stack.pop(false); + switch(op) { + case StackOP::PNL: std::cout << stack.pop(false) << std::endl; break; + case StackOP::P: std::cout << stack.pop(false); break; + case StackOP::PS: std::cout << stack.pop(false) << ' '; break; } break; } diff --git a/src/stack.h b/src/stack.h index 465ec78..149e10b 100644 --- a/src/stack.h +++ b/src/stack.h @@ -2,13 +2,17 @@ #include "operation.h" +enum class StackOP { + P, PNL, PS +}; + class Stack : public IOperation { public: explicit Stack(const OPType op_t) : op_type(op_t) {} std::optional exec(dc::Stack &stack, dc::Parameters ¶meters, std::unordered_map ®s) override; private: - std::optional fn_print(dc::Stack &stack, dc::Parameters ¶meters, bool new_line); + std::optional fn_print(dc::Stack &stack, dc::Parameters ¶meters, const StackOP op); static std::optional fn_pop_head(dc::Stack &stack); static std::optional fn_swap_xy(dc::Stack &stack); static std::optional fn_dup_head(dc::Stack &stack); diff --git a/tests/test_prt b/tests/test_prt index af2cb52..0ed68f9 100644 --- a/tests/test_prt +++ b/tests/test_prt @@ -90,9 +90,28 @@ test_print_no_newline() { assert_eq "$EXPECTED" "$ACTUAL" } +test_print_space() { + PROGRAM="$PWD/build/dc" + EXPECTED="0 1" + ACTUAL=$("$PROGRAM" -e '1 0 p. R P') + assert_eq "$EXPECTED" "$ACTUAL" + + # Test empty stack + EXPECTED="Cannot print empty stack" + ACTUAL=$("$PROGRAM" -e 'p.' 2>&1) || true + assert_eq "$EXPECTED" "$ACTUAL" + + # Test non numerical values + EXPECTED="foo " + ACTUAL=$("$PROGRAM" -e '[ foo ] p.' 2>&1) || true + assert_eq "$EXPECTED" "$ACTUAL" +} + + utest() { test_print test_print_no_newline + test_print_space test_print_bin test_print_hex test_print_oct