Added 'load from file' option(' operator)
dc / build (push) Successful in 13s Details

This commit is contained in:
Marco Cetica 2024-03-13 11:04:39 +01:00
parent dbecebce07
commit 4a71a91ee8
Signed by: marco
GPG Key ID: 45060A949E90D0FD
7 changed files with 119 additions and 4 deletions

View File

@ -46,7 +46,8 @@ Some of the supported features are:
- Macros:
- Define a new macro inside square brackets(`[ ]`);
- Executing a macro from the stack(`x`);
- Evaluate a macro by comparing top-of-head and second-of-head elements(`>X`, `<X`, `>=X`, `<=X`, `!=` where `X` is a register).
- Evaluate a macro by comparing top-of-head and second-of-head elements(`>X`, `<X`, `>=X`, `<=X`, `!=` where `X` is a register);
- Load external file(`'`).
And much more. You can find the complete manual [here](https://github.com/ice-bit/dc/blob/master/man.md).

16
man.md
View File

@ -3,7 +3,7 @@ title: dc
section: 1
header: General Commands Manual
footer: Marco Cetica
date: March 12, 2024
date: March 13, 2024
---
@ -385,6 +385,10 @@ Exit with return code `0`.
Reads a line from the terminal and executes it. This command allows a macro to request input from the user.
**'**
Pops a string off the stack, use it as a filepath, read its content and execute it.
# EXAMPLES
Below, there are some practical problems solved using **dc**.
@ -469,6 +473,16 @@ lB -1 * lD + lA # POSITIVE DELTA
[ Result in base 2: ] P R pb
```
11. Load an external file:
```sh
$> echo "[ p 1 + d lN >=L ] sL" > loop.dc
$> cat prg.dc
[ loop.dc ] ' # Load loop macro
[ Enter limit: ] P # Ask user for limit 'N'
? 1 + sN # Read from stdin
c 1 lL x # Clear the stack, add lower bound, load and execute macro
```
# AUTHORS
The original version of the **dc** command was written by Robert Morris and Lorinda Cherry.
This version of **dc** is developed by Marco Cetica.

View File

@ -65,6 +65,7 @@ void Evaluate::init_environment() {
// Macro operations
this->op_factory.emplace("x", MAKE_UNIQUE_PTR(Macro, OPType::EX));
this->op_factory.emplace("?", MAKE_UNIQUE_PTR(Macro, OPType::RI));
this->op_factory.emplace("'", MAKE_UNIQUE_PTR(Macro, OPType::LF));
}
std::optional<std::string> Evaluate::eval() {

View File

@ -2,6 +2,7 @@
#include <sstream>
#include <iterator>
#include <limits>
#include <fstream>
#include "adt.cpp"
#include "eval.h"
@ -15,6 +16,7 @@ std::optional<std::string> Macro::exec(dc::Stack<std::string> &stack, dc::Parame
case OPType::EX: err = fn_execute(stack, parameters, regs); break;
case OPType::CMP: err = fn_evaluate_macro(stack, parameters, regs); break;
case OPType::RI: err = fn_read_input(stack, parameters, regs); break;
case OPType::LF: err = fn_evaluate_file(stack, parameters, regs); break;
default: break;
}
@ -150,7 +152,7 @@ std::optional<std::string> Macro::fn_read_input(dc::Stack<std::string> &stack, d
// Read user input from stdin
std::string user_input;
std::cin >> user_input;
std::getline(std::cin, user_input);
if(std::cin.fail()) {
return "Error while reading from stdin";
}
@ -167,6 +169,63 @@ std::optional<std::string> Macro::fn_read_input(dc::Stack<std::string> &stack, d
return std::nullopt;
}
std::optional<std::string> Macro::fn_evaluate_file(dc::Stack<std::string> &stack, dc::Parameters &parameters, std::unordered_map<char, dc::Register> &regs) {
// Check whether the main stack has enough elements
if(stack.empty()) {
return "This operation does not work on empty stack";
}
// If the head of the stack is a string,
auto file_name = stack.pop(false);
if(!is_num<double>(file_name)) {
// Pop it from the stack
stack.copy_xyz();
stack.pop(true);
// And use it as a filename
std::fstream source_file(file_name, std::ios::in | std::ios::binary);
if(source_file.fail()) {
return std::string("Cannot open source file \"") + file_name + std::string("\"");
}
// Read whole file into a buffer
std::stringstream buf;
buf << source_file.rdbuf();
// Execute file line by line
std::string line;
while(std::getline(buf, line, '\n')) {
// Ignore comments or empty lines
if(line.empty() || line.starts_with('#')) {
continue;
}
// Remove inline comments
auto comment_pos = line.find('#');
std::vector<std::string> tokens;
if(comment_pos != std::string::npos) {
// Convert only the first part of the line
tokens = split(line.substr(0, comment_pos));
} else {
// Otherwise split the whole string
tokens = split(line);
}
// Evaluate expression
Evaluate evaluator(tokens, regs, stack, parameters);
auto err = evaluator.eval();
// Handle errors
if(err != std::nullopt) {
return err;
}
}
} else {
return "This operation requires string values";
}
return std::nullopt;
}
std::vector<std::string> Macro::split(const std::string& str) {
std::stringstream ss(str);
std::istream_iterator<std::string> begin(ss);

View File

@ -17,6 +17,7 @@ private:
static std::optional<std::string> fn_execute(dc::Stack<std::string> &stack, dc::Parameters &parameters, std::unordered_map<char, dc::Register> &regs);
std::optional<std::string> fn_evaluate_macro(dc::Stack<std::string> &stack, dc::Parameters &parameters, std::unordered_map<char, dc::Register> &regs);
static std::optional<std::string> fn_read_input(dc::Stack<std::string> &stack, dc::Parameters &parameters, std::unordered_map<char, dc::Register> &regs);
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{};

View File

@ -17,5 +17,5 @@ enum class OPType {
PCG, 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
EX, CMP, RI, LF
};

39
tests/test_lfile Normal file
View File

@ -0,0 +1,39 @@
#!/bin/sh
tearup() {
cat <<EOF > test_lfile.dc
[ 5 d ! + ] sX # Computes 5! + 5
EOF
}
teardown() {
rm test_lfile.dc
}
utest() {
PROGRAM="$PWD/build/dc"
tearup
# Test empty stack
EXPECTED="This operation does not work on empty stack"
ACTUAL=$("$PROGRAM" -e "'" 2>&1) || true
assert_eq "$EXPECTED" "$ACTUAL"
# Test non string values
EXPECTED="This operation requires string values"
ACTUAL=$("$PROGRAM" -e "[ 5 ] '" 2>&1) || true
assert_eq "$EXPECTED" "$ACTUAL"
# Test file not found
EXPECTED="Cannot open source file \"foo.dc\""
ACTUAL=$("$PROGRAM" -e "[ foo.dc ] '" 2>&1) || true
assert_eq "$EXPECTED" "$ACTUAL"
# Test normal behaviour
EXPECTED="125"
ACTUAL=$("$PROGRAM" -e "[ test_lfile.dc ] ' lX x p")
assert_eq "$EXPECTED" "$ACTUAL"
teardown
}
# vim: ts=4 sw=4 softtabstop=4 expandtab: