- Add while, for, and do-while loops with break/continue - Implement assignment statements (prevents if(x=10) bugs) - Keep assignment expressions only for for-loop clauses - Fix critical memory management bug (dangling pointers in cleanup) - Add automatic memory cleanup with conservative reference counting - Consolidate documentation into single reference file - Add comprehensive test coverage for all loop types and edge cases - VSCode extension for bob highlighting and snippets
263 lines
11 KiB
C++
263 lines
11 KiB
C++
#include "../headers/StdLib.h"
|
|
#include "../headers/Interpreter.h"
|
|
#include "../headers/ErrorReporter.h"
|
|
#include <chrono>
|
|
|
|
void StdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter& interpreter, ErrorReporter* errorReporter) {
|
|
// Create a built-in toString function
|
|
auto toStringFunc = std::make_shared<BuiltinFunction>("toString",
|
|
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
if (args.size() != 1) {
|
|
if (errorReporter) {
|
|
errorReporter->reportError(line, column, "StdLib Error",
|
|
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
|
}
|
|
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
|
}
|
|
|
|
return Value(interpreter.stringify(args[0]));
|
|
});
|
|
env->define("toString", Value(toStringFunc.get()));
|
|
|
|
// Store the shared_ptr in the interpreter to keep it alive
|
|
interpreter.addBuiltinFunction(toStringFunc);
|
|
|
|
// Create a built-in print function
|
|
auto printFunc = std::make_shared<BuiltinFunction>("print",
|
|
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
if (args.size() != 1) {
|
|
if (errorReporter) {
|
|
errorReporter->reportError(line, column, "StdLib Error",
|
|
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
|
}
|
|
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
|
}
|
|
// Use the interpreter's stringify function
|
|
std::cout << interpreter.stringify(args[0]) << std::endl;
|
|
return NONE_VALUE;
|
|
});
|
|
env->define("print", Value(printFunc.get()));
|
|
|
|
// Store the shared_ptr in the interpreter to keep it alive
|
|
interpreter.addBuiltinFunction(printFunc);
|
|
|
|
// Create a built-in assert function
|
|
auto assertFunc = std::make_shared<BuiltinFunction>("assert",
|
|
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
if (args.size() != 1 && args.size() != 2) {
|
|
if (errorReporter) {
|
|
errorReporter->reportError(line, column, "StdLib Error",
|
|
"Expected 1 or 2 arguments but got " + std::to_string(args.size()) + ".", "", true);
|
|
}
|
|
throw std::runtime_error("Expected 1 or 2 arguments but got " + std::to_string(args.size()) + ".");
|
|
}
|
|
|
|
// Simple truthy check without calling interpreter.isTruthy
|
|
bool isTruthy = false;
|
|
if (args[0].isBoolean()) {
|
|
isTruthy = args[0].asBoolean();
|
|
} else if (args[0].isNone()) {
|
|
isTruthy = false;
|
|
} else {
|
|
isTruthy = true; // Numbers, strings, functions are truthy
|
|
}
|
|
|
|
if (!isTruthy) {
|
|
std::string message = "Assertion failed: condition is false";
|
|
if (args.size() == 2) {
|
|
if (args[1].isString()) {
|
|
message += " - " + std::string(args[1].asString());
|
|
}
|
|
}
|
|
if (errorReporter) {
|
|
errorReporter->reportError(line, column, "StdLib Error", message, "", true);
|
|
}
|
|
throw std::runtime_error(message);
|
|
}
|
|
|
|
return NONE_VALUE;
|
|
});
|
|
env->define("assert", Value(assertFunc.get()));
|
|
|
|
// Store the shared_ptr in the interpreter to keep it alive
|
|
interpreter.addBuiltinFunction(assertFunc);
|
|
|
|
// Create a built-in time function (returns microseconds since Unix epoch)
|
|
auto timeFunc = std::make_shared<BuiltinFunction>("time",
|
|
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
if (args.size() != 0) {
|
|
if (errorReporter) {
|
|
errorReporter->reportError(line, column, "StdLib Error",
|
|
"Expected 0 arguments but got " + std::to_string(args.size()) + ".", "", true);
|
|
}
|
|
throw std::runtime_error("Expected 0 arguments but got " + std::to_string(args.size()) + ".");
|
|
}
|
|
|
|
auto now = std::chrono::high_resolution_clock::now();
|
|
auto duration = now.time_since_epoch();
|
|
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
|
|
|
|
return Value(static_cast<double>(microseconds));
|
|
});
|
|
env->define("time", Value(timeFunc.get()));
|
|
|
|
// Store the shared_ptr in the interpreter to keep it alive
|
|
interpreter.addBuiltinFunction(timeFunc);
|
|
|
|
// Create a built-in input function
|
|
auto inputFunc = std::make_shared<BuiltinFunction>("input",
|
|
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
if (args.size() > 1) {
|
|
if (errorReporter) {
|
|
errorReporter->reportError(line, column, "StdLib Error",
|
|
"Expected 0 or 1 arguments but got " + std::to_string(args.size()) + ".", "", true);
|
|
}
|
|
throw std::runtime_error("Expected 0 or 1 arguments but got " + std::to_string(args.size()) + ".");
|
|
}
|
|
|
|
// Optional prompt
|
|
if (args.size() == 1) {
|
|
std::cout << interpreter.stringify(args[0]);
|
|
}
|
|
|
|
// Get user input
|
|
std::string userInput;
|
|
std::getline(std::cin, userInput);
|
|
|
|
return Value(userInput);
|
|
});
|
|
env->define("input", Value(inputFunc.get()));
|
|
|
|
// Store the shared_ptr in the interpreter to keep it alive
|
|
interpreter.addBuiltinFunction(inputFunc);
|
|
|
|
// Create a built-in type function
|
|
auto typeFunc = std::make_shared<BuiltinFunction>("type",
|
|
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
if (args.size() != 1) {
|
|
if (errorReporter) {
|
|
errorReporter->reportError(line, column, "StdLib Error",
|
|
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
|
}
|
|
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
|
}
|
|
|
|
std::string typeName;
|
|
if (args[0].isNumber()) {
|
|
typeName = "number";
|
|
} else if (args[0].isString()) {
|
|
typeName = "string";
|
|
} else if (args[0].isBoolean()) {
|
|
typeName = "boolean";
|
|
} else if (args[0].isNone()) {
|
|
typeName = "none";
|
|
} else if (args[0].isFunction()) {
|
|
typeName = "function";
|
|
} else if (args[0].isBuiltinFunction()) {
|
|
typeName = "builtin_function";
|
|
} else {
|
|
typeName = "unknown";
|
|
}
|
|
|
|
return Value(typeName);
|
|
});
|
|
env->define("type", Value(typeFunc.get()));
|
|
|
|
// Store the shared_ptr in the interpreter to keep it alive
|
|
interpreter.addBuiltinFunction(typeFunc);
|
|
|
|
// Create a built-in toNumber function for string-to-number conversion
|
|
auto toNumberFunc = std::make_shared<BuiltinFunction>("toNumber",
|
|
[](std::vector<Value> args, int line, int column) -> Value {
|
|
if (args.size() != 1) {
|
|
return NONE_VALUE; // Return none for wrong argument count
|
|
}
|
|
|
|
if (!args[0].isString()) {
|
|
return NONE_VALUE; // Return none for wrong type
|
|
}
|
|
|
|
std::string str = args[0].asString();
|
|
|
|
// Remove leading/trailing whitespace
|
|
str.erase(0, str.find_first_not_of(" \t\n\r"));
|
|
str.erase(str.find_last_not_of(" \t\n\r") + 1);
|
|
|
|
if (str.empty()) {
|
|
return NONE_VALUE; // Return none for empty string
|
|
}
|
|
|
|
try {
|
|
double value = std::stod(str);
|
|
return Value(value);
|
|
} catch (const std::invalid_argument&) {
|
|
return NONE_VALUE; // Return none for invalid conversion
|
|
} catch (const std::out_of_range&) {
|
|
return NONE_VALUE; // Return none for out of range
|
|
}
|
|
});
|
|
env->define("toNumber", Value(toNumberFunc.get()));
|
|
|
|
// Store the shared_ptr in the interpreter to keep it alive
|
|
interpreter.addBuiltinFunction(toNumberFunc);
|
|
|
|
// Create a built-in toBoolean function for explicit boolean conversion
|
|
auto toBooleanFunc = std::make_shared<BuiltinFunction>("toBoolean",
|
|
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
|
if (args.size() != 1) {
|
|
if (errorReporter) {
|
|
errorReporter->reportError(line, column, "StdLib Error",
|
|
"Expected 1 argument but got " + std::to_string(args.size()) + ".", "", true);
|
|
}
|
|
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
|
}
|
|
|
|
// Use the same logic as isTruthy() for consistency
|
|
Value value = args[0];
|
|
|
|
if (value.isNone()) {
|
|
return Value(false);
|
|
}
|
|
|
|
if (value.isBoolean()) {
|
|
return value; // Already a boolean
|
|
}
|
|
|
|
if (value.isNumber()) {
|
|
return Value(value.asNumber() != 0.0);
|
|
}
|
|
|
|
if (value.isString()) {
|
|
return Value(!value.asString().empty());
|
|
}
|
|
|
|
// For any other type (functions, etc.), consider them truthy
|
|
return Value(true);
|
|
});
|
|
env->define("toBoolean", Value(toBooleanFunc.get()));
|
|
|
|
// Store the shared_ptr in the interpreter to keep it alive
|
|
interpreter.addBuiltinFunction(toBooleanFunc);
|
|
|
|
// Create a built-in exit function to terminate the program
|
|
auto exitFunc = std::make_shared<BuiltinFunction>("exit",
|
|
[](std::vector<Value> args, int line, int column) -> Value {
|
|
int exitCode = 0; // Default exit code
|
|
|
|
if (args.size() > 0) {
|
|
if (args[0].isNumber()) {
|
|
exitCode = static_cast<int>(args[0].asNumber());
|
|
}
|
|
// If not a number, just use default exit code 0
|
|
}
|
|
|
|
std::exit(exitCode);
|
|
return NONE_VALUE; // This line should never be reached
|
|
});
|
|
env->define("exit", Value(exitFunc.get()));
|
|
|
|
// Store the shared_ptr in the interpreter to keep it alive
|
|
interpreter.addBuiltinFunction(exitFunc);
|
|
|
|
|
|
}
|