Bob/headers/Value.h
Bobby Lucero eacb86ec77 feat: comprehensive language enhancements and code quality improvements
Major additions and improvements across the Bob language ecosystem:

Core Language Features:

- Add comprehensive dictionary support with full CRUD operations

- Implement built-in functions: keys(), values(), has() for dictionaries

- Add string multiplication operator (string * number)

- Enhance error reporting with detailed context and call stacks

- Add ternary operator support (condition ? true_expr : false_expr)

- Implement do-while loops with break/continue support

- Add array increment/decrement operators (++, --)

- Add cross-type comparison operators with proper type coercion

- Implement toInt() function for float-to-integer conversion

- Add float array index auto-truncation (like JavaScript/Lua)

Code Quality & Linter Fixes:

- Remove all "using namespace std;" statements (best practice)

- Add proper std:: prefixes throughout codebase

- Fix const correctness in helper functions

- Resolve class/struct declaration mismatches

- Fix sign comparison warnings in array indexing

- Remove unused lambda captures in built-in functions

- Fix brace initialization warnings in parser

Documentation & Tooling:

- Significantly expand BOB_LANGUAGE_REFERENCE.md with new features

- Update VS Code extension with enhanced syntax highlighting

- Add comprehensive code snippets for new language features

- Update version information and package metadata

Test Suite:

- Add extensive dictionary functionality tests

- Add tests for new operators and built-in functions

- Add comprehensive copy behavior tests (by value vs by reference)

- Add performance and edge case testing

Architecture Improvements:

- Enhance Value system with proper move semantics

- Improve memory management with shared_ptr for complex types

- Add trampoline-based tail call optimization

- Implement proper error context propagation

This represents a major milestone in Bob language development with production-ready dictionary support, comprehensive testing, and significantly improved code quality.
2025-08-07 00:12:04 -04:00

390 lines
16 KiB
C++

#pragma once
#include "helperFunctions/ErrorUtils.h"
#include <string>
#include <vector>
#include <unordered_map>
#include <utility>
#include <cmath>
#include <stdexcept>
#include <algorithm>
// Forward declarations
struct Environment;
struct Function;
struct BuiltinFunction;
struct Thunk;
// Type tags for the Value union
enum ValueType {
VAL_NONE,
VAL_NUMBER,
VAL_BOOLEAN,
VAL_STRING,
VAL_FUNCTION,
VAL_BUILTIN_FUNCTION,
VAL_THUNK,
VAL_ARRAY,
VAL_DICT
};
// Tagged value system (like Lua) - no heap allocation for simple values
struct Value {
union {
double number;
bool boolean;
Function* function;
BuiltinFunction* builtin_function;
Thunk* thunk;
};
ValueType type;
std::string string_value; // Store strings outside the union for safety
std::shared_ptr<std::vector<Value> > array_value; // Store arrays as shared_ptr for mutability
std::shared_ptr<std::unordered_map<std::string, Value> > dict_value; // Store dictionaries as shared_ptr for mutability
// Constructors
Value() : number(0.0), type(ValueType::VAL_NONE) {}
Value(double n) : number(n), type(ValueType::VAL_NUMBER) {}
Value(bool b) : boolean(b), type(ValueType::VAL_BOOLEAN) {}
Value(const char* s) : type(ValueType::VAL_STRING), string_value(s ? s : "") {}
Value(const std::string& s) : type(ValueType::VAL_STRING), string_value(s) {}
Value(std::string&& s) : type(ValueType::VAL_STRING), string_value(std::move(s)) {}
Value(Function* f) : function(f), type(ValueType::VAL_FUNCTION) {}
Value(BuiltinFunction* bf) : builtin_function(bf), type(ValueType::VAL_BUILTIN_FUNCTION) {}
Value(Thunk* t) : thunk(t), type(ValueType::VAL_THUNK) {}
Value(const std::vector<Value>& arr) : type(ValueType::VAL_ARRAY), array_value(std::make_shared<std::vector<Value> >(arr)) {}
Value(std::vector<Value>&& arr) : type(ValueType::VAL_ARRAY), array_value(std::make_shared<std::vector<Value> >(std::move(arr))) {}
Value(const std::unordered_map<std::string, Value>& dict) : type(ValueType::VAL_DICT), dict_value(std::make_shared<std::unordered_map<std::string, Value> >(dict)) {}
Value(std::unordered_map<std::string, Value>&& dict) : type(ValueType::VAL_DICT), dict_value(std::make_shared<std::unordered_map<std::string, Value> >(std::move(dict))) {}
// Move constructor
Value(Value&& other) noexcept
: type(other.type), string_value(std::move(other.string_value)), array_value(std::move(other.array_value)), dict_value(std::move(other.dict_value)) {
if (type != ValueType::VAL_STRING && type != ValueType::VAL_ARRAY && type != ValueType::VAL_DICT) {
number = other.number; // Copy the union
}
other.type = ValueType::VAL_NONE;
}
// Move assignment
Value& operator=(Value&& other) noexcept {
if (this != &other) {
type = other.type;
if (type == ValueType::VAL_STRING) {
string_value = std::move(other.string_value);
} else if (type == ValueType::VAL_ARRAY) {
array_value = std::move(other.array_value); // shared_ptr automatically handles moving
} else if (type == ValueType::VAL_DICT) {
dict_value = std::move(other.dict_value); // shared_ptr automatically handles moving
} else {
number = other.number; // Copy the union
}
other.type = ValueType::VAL_NONE;
}
return *this;
}
// Copy constructor (only when needed)
Value(const Value& other) : type(other.type) {
if (type == ValueType::VAL_STRING) {
string_value = other.string_value;
} else if (type == ValueType::VAL_ARRAY) {
array_value = other.array_value; // shared_ptr automatically handles sharing
} else if (type == ValueType::VAL_DICT) {
dict_value = other.dict_value; // shared_ptr automatically handles sharing
} else {
number = other.number; // Copy the union
}
}
// Copy assignment (only when needed)
Value& operator=(const Value& other) {
if (this != &other) {
type = other.type;
if (type == ValueType::VAL_STRING) {
string_value = other.string_value;
} else if (type == ValueType::VAL_ARRAY) {
array_value = other.array_value; // shared_ptr automatically handles sharing
} else if (type == ValueType::VAL_DICT) {
dict_value = other.dict_value; // shared_ptr automatically handles sharing
} else {
number = other.number; // Copy the union
}
}
return *this;
}
// Type checking (fast, no dynamic casting) - inline for performance
inline bool isNumber() const { return type == ValueType::VAL_NUMBER; }
inline bool isBoolean() const { return type == ValueType::VAL_BOOLEAN; }
inline bool isString() const { return type == ValueType::VAL_STRING; }
inline bool isFunction() const { return type == ValueType::VAL_FUNCTION; }
inline bool isBuiltinFunction() const { return type == ValueType::VAL_BUILTIN_FUNCTION; }
inline bool isArray() const { return type == ValueType::VAL_ARRAY; }
inline bool isDict() const { return type == ValueType::VAL_DICT; }
inline bool isThunk() const { return type == ValueType::VAL_THUNK; }
inline bool isNone() const { return type == ValueType::VAL_NONE; }
// Get type name as string for error messages
inline std::string getType() const {
switch (type) {
case ValueType::VAL_NONE: return "none";
case ValueType::VAL_NUMBER: return "number";
case ValueType::VAL_BOOLEAN: return "boolean";
case ValueType::VAL_STRING: return "string";
case ValueType::VAL_FUNCTION: return "function";
case ValueType::VAL_BUILTIN_FUNCTION: return "builtin_function";
case ValueType::VAL_THUNK: return "thunk";
case ValueType::VAL_ARRAY: return "array";
case ValueType::VAL_DICT: return "dict";
default: return "unknown";
}
}
// Value extraction (safe, with type checking) - inline for performance
inline double asNumber() const { return isNumber() ? number : 0.0; }
inline bool asBoolean() const { return isBoolean() ? boolean : false; }
inline const std::string& asString() const { return string_value; }
inline const std::vector<Value>& asArray() const {
return *array_value;
}
inline std::vector<Value>& asArray() {
return *array_value;
}
inline const std::unordered_map<std::string, Value>& asDict() const {
return *dict_value;
}
inline std::unordered_map<std::string, Value>& asDict() {
return *dict_value;
}
inline Function* asFunction() const { return isFunction() ? function : nullptr; }
inline BuiltinFunction* asBuiltinFunction() const { return isBuiltinFunction() ? builtin_function : nullptr; }
inline Thunk* asThunk() const { return isThunk() ? thunk : nullptr; }
// Truthiness check - inline for performance
inline bool isTruthy() const {
switch (type) {
case ValueType::VAL_NONE: return false;
case ValueType::VAL_BOOLEAN: return boolean;
case ValueType::VAL_NUMBER: return number != 0.0;
case ValueType::VAL_STRING: return !string_value.empty();
case ValueType::VAL_FUNCTION: return function != nullptr;
case ValueType::VAL_BUILTIN_FUNCTION: return builtin_function != nullptr;
case ValueType::VAL_THUNK: return thunk != nullptr;
case ValueType::VAL_ARRAY: return !array_value->empty();
case ValueType::VAL_DICT: return !dict_value->empty();
default: return false;
}
}
// Equality comparison - inline for performance
inline bool equals(const Value& other) const {
if (type != other.type) return false;
switch (type) {
case ValueType::VAL_NONE: return true;
case ValueType::VAL_BOOLEAN: return boolean == other.boolean;
case ValueType::VAL_NUMBER: return number == other.number;
case ValueType::VAL_STRING: return string_value == other.string_value;
case ValueType::VAL_FUNCTION: return function == other.function;
case ValueType::VAL_BUILTIN_FUNCTION: return builtin_function == other.builtin_function;
case ValueType::VAL_THUNK: return thunk == other.thunk;
case ValueType::VAL_ARRAY: {
if (array_value->size() != other.array_value->size()) return false;
for (size_t i = 0; i < array_value->size(); i++) {
if (!(*array_value)[i].equals((*other.array_value)[i])) return false;
}
return true;
}
case ValueType::VAL_DICT: {
if (dict_value->size() != other.dict_value->size()) return false;
for (const auto& pair : *dict_value) {
auto it = other.dict_value->find(pair.first);
if (it == other.dict_value->end() || !pair.second.equals(it->second)) return false;
}
return true;
}
default: return false;
}
}
// String representation
std::string toString() const {
switch (type) {
case ValueType::VAL_NONE: return "none";
case ValueType::VAL_BOOLEAN: return boolean ? "true" : "false";
case ValueType::VAL_NUMBER: {
// Format numbers like the original stringify function
if (number == std::floor(number)) {
return std::to_string(static_cast<long long>(number));
} else {
std::string str = std::to_string(number);
// Remove trailing zeros
str.erase(str.find_last_not_of('0') + 1, std::string::npos);
if (str.back() == '.') str.pop_back();
return str;
}
}
case ValueType::VAL_STRING: return string_value;
case ValueType::VAL_FUNCTION: return "<function>";
case ValueType::VAL_BUILTIN_FUNCTION: return "<builtin_function>";
case ValueType::VAL_THUNK: return "<thunk>";
case ValueType::VAL_ARRAY: {
const std::vector<Value>& arr = *array_value;
std::string result = "[";
for (size_t i = 0; i < arr.size(); i++) {
if (i > 0) result += ", ";
result += arr[i].toString();
}
result += "]";
return result;
}
case ValueType::VAL_DICT: {
const std::unordered_map<std::string, Value>& dict = *dict_value;
std::string result = "{";
bool first = true;
for (const auto& pair : dict) {
if (!first) result += ", ";
result += "\"" + pair.first + "\": " + pair.second.toString();
first = false;
}
result += "}";
return result;
}
default: return "unknown";
}
}
// Equality operator
bool operator==(const Value& other) const {
return equals(other);
}
bool operator!=(const Value& other) const {
return !equals(other);
}
// Arithmetic operators
Value operator+(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(number + other.number);
}
if (isString() && other.isString()) {
return Value(string_value + other.string_value);
}
if (isString() && other.isNumber()) {
return Value(string_value + other.toString());
}
if (isNumber() && other.isString()) {
return Value(toString() + other.string_value);
}
// Handle none values by converting to string
if (isString() && other.isNone()) {
return Value(string_value + "none");
}
if (isNone() && other.isString()) {
return Value("none" + other.string_value);
}
if (isString() && !other.isString() && !other.isNumber()) {
return Value(string_value + other.toString());
}
if (!isString() && !isNumber() && other.isString()) {
return Value(toString() + other.string_value);
}
throw std::runtime_error(ErrorUtils::makeOperatorError("+", getType(), other.getType()));
}
Value operator-(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(number - other.number);
}
throw std::runtime_error(ErrorUtils::makeOperatorError("-", getType(), other.getType()));
}
Value operator*(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(number * other.number);
}
if (isString() && other.isNumber()) {
std::string result;
for (int i = 0; i < static_cast<int>(other.number); ++i) {
result += string_value;
}
return Value(result);
}
if (isNumber() && other.isString()) {
std::string result;
for (int i = 0; i < static_cast<int>(number); ++i) {
result += other.string_value;
}
return Value(result);
}
throw std::runtime_error(ErrorUtils::makeOperatorError("*", getType(), other.getType()));
}
Value operator/(const Value& other) const {
if (isNumber() && other.isNumber()) {
if (other.number == 0) {
throw std::runtime_error("Division by zero");
}
return Value(number / other.number);
}
throw std::runtime_error(ErrorUtils::makeOperatorError("/", getType(), other.getType()));
}
Value operator%(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(fmod(number, other.number));
}
throw std::runtime_error(ErrorUtils::makeOperatorError("%", getType(), other.getType()));
}
Value operator&(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(static_cast<double>(static_cast<long>(number) & static_cast<long>(other.number)));
}
throw std::runtime_error(ErrorUtils::makeOperatorError("&", getType(), other.getType()));
}
Value operator|(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(static_cast<double>(static_cast<long>(number) | static_cast<long>(other.number)));
}
throw std::runtime_error(ErrorUtils::makeOperatorError("|", getType(), other.getType()));
}
Value operator^(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(static_cast<double>(static_cast<long>(number) ^ static_cast<long>(other.number)));
}
throw std::runtime_error(ErrorUtils::makeOperatorError("^", getType(), other.getType()));
}
Value operator<<(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(static_cast<double>(static_cast<long>(number) << static_cast<long>(other.number)));
}
throw std::runtime_error(ErrorUtils::makeOperatorError("<<", getType(), other.getType()));
}
Value operator>>(const Value& other) const {
if (isNumber() && other.isNumber()) {
return Value(static_cast<double>(static_cast<long>(number) >> static_cast<long>(other.number)));
}
throw std::runtime_error(ErrorUtils::makeOperatorError(">>", getType(), other.getType()));
}
};
// Global constants for common values
extern const Value NONE_VALUE;
extern const Value TRUE_VALUE;
extern const Value FALSE_VALUE;
extern const Value ZERO_VALUE;
extern const Value ONE_VALUE;