Skip to content

Commit

Permalink
Added bitwise operators and fixed various bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
ceticamarco committed Mar 19, 2024
1 parent b247dad commit 62ad335
Show file tree
Hide file tree
Showing 21 changed files with 467 additions and 33 deletions.
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ cmake_minimum_required(VERSION 3.12)
# Project name and version
project(dc VERSION 1.0.4)

# Retrieve build date
string(TIMESTAMP BUILD_DATE "%d-%b-%Y %H:%M:%S")

# Retrieve git hash
execute_process(
COMMAND git rev-parse --short HEAD
Expand Down Expand Up @@ -52,3 +55,4 @@ add_compile_definitions(DC_VER="${PROJECT_VERSION}")
add_compile_definitions(DC_HASH="${DC_GIT_HASH}")
add_compile_definitions(DC_BUILD="${CMAKE_BUILD_TYPE}")
add_compile_definitions(DC_FLAGS="${DC_FLAGS}")
add_compile_definitions(DC_BUILD_DATE="${BUILD_DATE}")
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Some of the supported features are:
- Factorial and constants(`!`, `pi`, `e`);
- Random number generator(`@`);
- Integer conversion(`$`);
- Bitwise operations(`{`, `}`, `l`, `L`, `m`, `M`);
- Stack operations:
- Print top element(`p`, `P`);
- Clear the stack(`c`);
Expand Down Expand Up @@ -259,7 +260,7 @@ lB -1 * lD + lA # POSITIVE DELTA
0 lL x
```

17. Estimate $\pi$ using Monte Carlo simulation
17. Estimate $\pi$ using Monte Carlo simulation:
```
10 k
[ 0 1 @ sX 0 1 @ sY ] sR
Expand All @@ -273,6 +274,18 @@ lL x 0 ;A lN /
4 * p
```

18. Convert an hex color to RGB:
```
16 i
[ Enter hex value: ] P R ? sV
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
[ RED: ] P R 0 ;A p
```

## License

[GPLv3](https://choosealicense.com/licenses/gpl-3.0/)
2 changes: 2 additions & 0 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ void helper() {

void version() {
std::cout << "dc (v" << DC_VER << ", " << DC_HASH << ", " << DC_BUILD ")" << std::endl;
std::cout << "Build date: " << DC_BUILD_DATE << std::endl;
std::cout << "Compile flags: " << DC_FLAGS << std::endl;
std::cout << "Copyright (c) 2024 Marco Cetica" << std::endl;
std::cout << "License GPLv3+: GNU GPL version 3 or later\n" << std::endl;
Expand Down Expand Up @@ -126,6 +127,7 @@ int main(int argc, char **argv) {
// Handle errors
if(err != std::nullopt) {
std::cerr << err.value() << std::endl;
return 1;
}
}

Expand Down
60 changes: 59 additions & 1 deletion man.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: dc
section: 1
header: General Commands Manual
footer: Marco Cetica
date: March 15, 2024
date: March 19, 2024
---


Expand Down Expand Up @@ -46,6 +46,23 @@ options(see below).
**dc** reads from the standard input, but it can also work with text files using the `-f` flag. Futhermore, you can decide to evaluate an expression
without opening the REPL by using the `-e` flag.

# PROGRAMMING IN DC
As a stack-based, concatenative and procedural programming language, **dc** programs follow a *bottom up* approach where the program is built by
starting with the most minimal facts about the problem and then is build up towards the complete solution. Following this programming paradigm means
creating many short and simple routines that are defined either in terms or existing routines or in terms of built-in primitives.

The main strength of this paradigm is that you have full control on what happens at the lower levels of your program. This means that your
programming logic is not abstracted away into generic protocols, everything you write has to as concrete as possible.
Another advantage of this approach is the ability to test and debug interactively each definition and each routines as you build up your solution, without having
to rely on external testing framework.
Finally, the last major advantage is that dc is very extensible: the core language consists on only a few primitives, which are enough for building solutions
and extending the programming language. Furthermore, just as LISP languages, dc does not make any distinctions between code and data, thus it is homoiconic.

On the other hand, the *bottom up* approach is not suitable for building large infrastructures: the ability to abstract away methods, procedures and to shape
real-world objects into templates is essential for many modern programming use cases. As a result, dc excels when used for what it was originally
developed for: computations and small math-oriented programs.


# ARCHITECTURE
As an advanced scientific calculator, **dc** has a complex architecture defined by the following two data structures:

Expand Down Expand Up @@ -104,6 +121,7 @@ their invocation. The user can store both numeric and alphanumeric values on the

Arrays are homogeneous data structures that implement the same data type of the stack, i.e. the string.


# COMMANDS
Below, there is a list of supported **dc** commands.

Expand Down Expand Up @@ -222,6 +240,34 @@ Prints the value on the top of the stack in base 8, without altering the stack.

Prints the value on the top of the stack in base 16, without altering the stack. A newline is printed after the value.

## Bitwise Operations
**dc** supports various bitwise operations. These operations support integral types only and are represented using
a 64bit data type.

**{**

Pops two numbers from the stack and computes bitwise *AND* between the second one popped and the first one popped. Pushes the result.

**}**

Pops two numbers from the stack and computes bitwise *OR* between the second one popped and the first one popped. Pushes the result.

**l**

Pops one **signed integer** from the stack and computes bitwise *NOT*(one's complement). Pushes the result.

**L**

Pops two numbers from the stack and computes bitwise *XOR* between the second one popped and the first one popped. Pushes the result.

**m**

Pops two numbers from the stack *left shift* the second one popped by *n* bits, where *n* is the first one popped. Pushes the result.

**M**

Pops two numbers from the stack *right shift* the second one popped by *n* bits, where *n* is the first one popped. Pushes the result.

## Stack Control

**c**
Expand Down Expand Up @@ -517,6 +563,18 @@ lL x 0 ;A lN /
4 * p
```

14. Convert an hex color to RGB:
```
16 i
[ Enter hex value: ] P R ? sV
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
[ RED: ] P R 0 ;A p
```

# 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.
Expand Down
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ set(HEADER_FILES
eval.h
macro.h
mathematics.h
bitwise.h
operation.h
stack.h
adt.h
Expand All @@ -13,6 +14,7 @@ set(SOURCE_FILES
eval.cpp
macro.cpp
mathematics.cpp
bitwise.cpp
stack.cpp
adt.cpp
)
Expand Down
202 changes: 202 additions & 0 deletions src/bitwise.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
#include <bitset>
#include <iomanip>
#include <cmath>

#include "adt.cpp"
#include "bitwise.h"
#include "is_num.h"

std::optional<std::string> Bitwise::exec(dc::Stack<std::string> &stack, dc::Parameters &parameters, __attribute__((unused)) std::unordered_map<char, dc::Register> &regs) {
std::optional<std::string> err = std::nullopt;

switch(this->op_type) {
case OPType::BAND: err = fn_bitwise_and(stack, parameters); break;
case OPType::BOR: err = fn_bitwise_or(stack, parameters); break;
case OPType::BNOT: err = fn_bitwise_not(stack, parameters); break;
case OPType::BXOR: err = fn_bitwise_xor(stack, parameters); break;
case OPType::BSL: err = fn_bitwise_lshift(stack, parameters); break;
case OPType::BSR: err = fn_bitwise_rshift(stack, parameters); break;
default: break;
}

return err;
}

std::optional<std::string> Bitwise::fn_bitwise_and(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
// Check if stack has enough elements
if(stack.size() < 2) {
return "'{' requires two operands";
}

// Extract two entries from the stack
auto len = stack.size() - 1;
auto x = stack[len];
auto y = stack[len-1];
auto is_x_num = is_num<double>(x);
auto is_y_num = is_num<double>(y);

// Check whether both entries are numbers
if(is_x_num && is_y_num) {
stack.copy_xyz();
std::bitset<64> rhs{std::stoul(stack.pop(true))};
std::bitset<64> lhs{std::stoul(stack.pop(true))};

// Compute bitwise AND and push back the result
std::bitset<64> result = (lhs & rhs);
stack.push(trim_digits(result.to_ullong(), parameters.precision));
} else {
return "'{' requires numeric values";
}

return std::nullopt;
}

std::optional<std::string> Bitwise::fn_bitwise_or(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
// Check if stack has enough elements
if(stack.size() < 2) {
return "'}' requires two operands";
}

// Extract two entries from the stack
auto len = stack.size() - 1;
auto x = stack[len];
auto y = stack[len-1];
auto is_x_num = is_num<double>(x);
auto is_y_num = is_num<double>(y);

// Check whether both entries are numbers
if(is_x_num && is_y_num) {
stack.copy_xyz();
std::bitset<64> rhs{std::stoul(stack.pop(true))};
std::bitset<64> lhs{std::stoul(stack.pop(true))};

// Compute bitwise AND and push back the result
std::bitset<64> result = (lhs | rhs);
stack.push(trim_digits(result.to_ullong(), parameters.precision));
} else {
return "'}' requires numeric values";
}

return std::nullopt;
}
std::optional<std::string> Bitwise::fn_bitwise_not(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
// Check if stack has enough elements
if(stack.empty()) {
return "'l' requires one operand";
}

// Extract one entry from the stack
auto x = stack.pop(false);
auto is_x_num = is_num<double>(x);

// Check whether popped value is a number
if(is_x_num) {
stack.copy_xyz();
// Compute bitwise NOT
int result = ~std::stoi(stack.pop(true));
stack.push(trim_digits(result, parameters.precision));
} else {
return "'l' requires numeric values";
}

return std::nullopt;
}
std::optional<std::string> Bitwise::fn_bitwise_xor(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
// Check if stack has enough elements
if(stack.size() < 2) {
return "'L' requires two operands";
}

// Extract two entries from the stack
auto len = stack.size() - 1;
auto x = stack[len];
auto y = stack[len-1];
auto is_x_num = is_num<double>(x);
auto is_y_num = is_num<double>(y);

// Check whether both entries are numbers
if(is_x_num && is_y_num) {
stack.copy_xyz();
std::bitset<64> rhs{std::stoul(stack.pop(true))};
std::bitset<64> lhs{std::stoul(stack.pop(true))};

// Compute bitwise AND and push back the result
std::bitset<64> result = (lhs ^ rhs);
stack.push(trim_digits(result.to_ullong(), parameters.precision));
} else {
return "'L' requires numeric values";
}

return std::nullopt;
}
std::optional<std::string> Bitwise::fn_bitwise_lshift(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
// Check if stack has enough elements
if(stack.size() < 2) {
return "'m' requires two operands";
}

// Extract two entries from the stack
auto len = stack.size() - 1;
auto x = stack[len];
auto y = stack[len-1];
auto is_x_num = is_num<double>(x);
auto is_y_num = is_num<double>(y);

// Check whether both entries are numbers
if(is_x_num && is_y_num) {
stack.copy_xyz();
std::bitset<64> pos{std::stoul(stack.pop(true))};
std::bitset<64> value{std::stoul(stack.pop(true))};

// Compute bitwise left shift and push back the result
std::bitset<64> result = (value << pos.to_ulong());
stack.push(trim_digits(result.to_ullong(), parameters.precision));
} else {
return "'m' requires numeric values";
}

return std::nullopt;
}

std::optional<std::string> Bitwise::fn_bitwise_rshift(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
// Check if stack has enough elements
if(stack.size() < 2) {
return "'M' requires two operands";
}

// Extract two entries from the stack
auto len = stack.size() - 1;
auto x = stack[len];
auto y = stack[len-1];
auto is_x_num = is_num<double>(x);
auto is_y_num = is_num<double>(y);

// Check whether both entries are numbers
if(is_x_num && is_y_num) {
stack.copy_xyz();
std::bitset<64> pos{std::stoul(stack.pop(true))};
std::bitset<64> value{std::stoul(stack.pop(true))};

// Compute bitwise right shift and push back the result
std::bitset<64> result = (value >> pos.to_ulong());
stack.push(trim_digits(result.to_ullong(), parameters.precision));
} else {
return "'M' requires numeric values";
}

return std::nullopt;
}

std::string Bitwise::trim_digits(double number, unsigned int precision) {
std::ostringstream oss;

// Preserve non-zero decimal numbers even when precision is zero
if(precision == 0 && std::fmod(number, 1.0) != 0.0) {
precision = 2;
}

oss << std::fixed << std::setprecision(static_cast<int>(precision)) << number;
std::string s = oss.str();

return s;
}
Loading

0 comments on commit 62ad335

Please sign in to comment.