Added support to nested macros

This commit is contained in:
Marco Cetica 2024-03-25 10:53:23 +01:00
parent 616de523cf
commit 5997f06bd0
Signed by: marco
GPG Key ID: 45060A949E90D0FD
9 changed files with 80 additions and 43 deletions

View File

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

25
man.md
View File

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

View File

@ -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<std::string> Evaluate::parse_base_n(const std::string& token) {
std::optional<std::string> 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<std::string> 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<std::string> Evaluate::parse_macro_command(std::string token) {
// execute register's content as a macro
std::optional<std::string> err = std::nullopt;
if(operation == ">") {
auto macro = std::make_unique<Macro>(OPType::CMP, Operator::GT, dc_register);
auto macro = std::make_unique<Macro>(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<Macro>(OPType::CMP, Operator::LT, dc_register);
auto macro = std::make_unique<Macro>(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<Macro>(OPType::CMP, Operator::EQ, dc_register);
auto macro = std::make_unique<Macro>(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<Macro>(OPType::CMP, Operator::GEQ, dc_register);
auto macro = std::make_unique<Macro>(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<Macro>(OPType::CMP, Operator::LEQ, dc_register);
auto macro = std::make_unique<Macro>(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<Macro>(OPType::CMP, Operator::NEQ, dc_register);
auto macro = std::make_unique<Macro>(OPType::CMP, MacroOP::NEQ, dc_register);
err = macro->exec(this->stack, this->parameters, this->regs);
if(err != std::nullopt) {
return err;

View File

@ -70,7 +70,7 @@ std::optional<std::string> Macro::fn_evaluate_macro(dc::Stack<std::string> &stac
auto second = std::stod(second_str);
switch(this->op) {
case Operator::GT: {
case MacroOP::GT: {
if(head > second) {
std::vector<std::string> tokens = split(dc_macro);
Evaluate evaluator(tokens, regs, stack, parameters);
@ -82,7 +82,7 @@ std::optional<std::string> Macro::fn_evaluate_macro(dc::Stack<std::string> &stac
}
break;
}
case Operator::LT: {
case MacroOP::LT: {
if(head < second) {
std::vector<std::string> tokens = split(dc_macro);
Evaluate evaluator(tokens, regs, stack, parameters);
@ -94,7 +94,7 @@ std::optional<std::string> Macro::fn_evaluate_macro(dc::Stack<std::string> &stac
}
break;
}
case Operator::EQ: {
case MacroOP::EQ: {
if(head == second) {
std::vector<std::string> tokens = split(dc_macro);
Evaluate evaluator(tokens, regs, stack, parameters);
@ -106,7 +106,7 @@ std::optional<std::string> Macro::fn_evaluate_macro(dc::Stack<std::string> &stac
}
break;
}
case Operator::GEQ: {
case MacroOP::GEQ: {
if(head >= second) {
std::vector<std::string> tokens = split(dc_macro);
Evaluate evaluator(tokens, regs, stack, parameters);
@ -118,7 +118,7 @@ std::optional<std::string> Macro::fn_evaluate_macro(dc::Stack<std::string> &stac
}
break;
}
case Operator::LEQ: {
case MacroOP::LEQ: {
if(head <= second) {
std::vector<std::string> tokens = split(dc_macro);
Evaluate evaluator(tokens, regs, stack, parameters);
@ -130,7 +130,7 @@ std::optional<std::string> Macro::fn_evaluate_macro(dc::Stack<std::string> &stac
}
break;
}
case Operator::NEQ: {
case MacroOP::NEQ: {
if(head != second) {
std::vector<std::string> tokens = split(dc_macro);
Evaluate evaluator(tokens, regs, stack, parameters);

View File

@ -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<std::string> exec(dc::Stack<std::string> &stack, dc::Parameters &parameters, std::unordered_map<char, dc::Register> &regs) override;
static std::vector<std::string> split(const std::string& str);
@ -20,6 +20,6 @@ private:
static std::optional<std::string> fn_evaluate_file(dc::Stack<std::string> &stack, dc::Parameters &parameters, std::unordered_map<char, dc::Register> &regs);
OPType op_type;
Operator op{};
MacroOP op{};
char dc_register{};
};

View File

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

View File

@ -13,15 +13,16 @@ std::optional<std::string> Stack::exec(dc::Stack<std::string> &stack, dc::Parame
auto print_oradix = [&stack, &parameters, 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<std::string> Stack::exec(dc::Stack<std::string> &stack, dc::Parame
return err;
}
std::optional<std::string> Stack::fn_print(dc::Stack<std::string> &stack, dc::Parameters &parameters, bool new_line) {
std::optional<std::string> Stack::fn_print(dc::Stack<std::string> &stack, dc::Parameters &parameters, const StackOP op) {
// Check if the stack is empty
if(stack.empty()) {
return "Cannot print empty stack";
@ -60,10 +61,10 @@ std::optional<std::string> Stack::fn_print(dc::Stack<std::string> &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;
}

View File

@ -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<std::string> exec(dc::Stack<std::string> &stack, dc::Parameters &parameters, std::unordered_map<char, dc::Register> &regs) override;
private:
std::optional<std::string> fn_print(dc::Stack<std::string> &stack, dc::Parameters &parameters, bool new_line);
std::optional<std::string> fn_print(dc::Stack<std::string> &stack, dc::Parameters &parameters, const StackOP op);
static std::optional<std::string> fn_pop_head(dc::Stack<std::string> &stack);
static std::optional<std::string> fn_swap_xy(dc::Stack<std::string> &stack);
static std::optional<std::string> fn_dup_head(dc::Stack<std::string> &stack);

View File

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