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.
390 lines
16 KiB
C++
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;
|