Added support to nested macros
This commit is contained in:
parent
616de523cf
commit
5997f06bd0
@ -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
25
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
|
||||
|
29
src/eval.cpp
29
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<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;
|
||||
|
@ -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);
|
||||
|
@ -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 ¶meters, std::unordered_map<char, dc::Register> ®s) 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 ¶meters, std::unordered_map<char, dc::Register> ®s);
|
||||
|
||||
OPType op_type;
|
||||
Operator op{};
|
||||
MacroOP op{};
|
||||
char dc_register{};
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -13,15 +13,16 @@ std::optional<std::string> Stack::exec(dc::Stack<std::string> &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<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 ¶meters, bool new_line) {
|
||||
std::optional<std::string> Stack::fn_print(dc::Stack<std::string> &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<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;
|
||||
}
|
||||
|
@ -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 ¶meters, std::unordered_map<char, dc::Register> ®s) override;
|
||||
|
||||
private:
|
||||
std::optional<std::string> fn_print(dc::Stack<std::string> &stack, dc::Parameters ¶meters, bool new_line);
|
||||
std::optional<std::string> fn_print(dc::Stack<std::string> &stack, dc::Parameters ¶meters, 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);
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user