Built in modules, user modules, ability to disable builtin modules
This commit is contained in:
parent
fc63c3e46f
commit
8cdccae214
@ -48,6 +48,7 @@ endif()
|
|||||||
file(GLOB_RECURSE BOB_RUNTIME_SOURCES "src/sources/runtime/*.cpp")
|
file(GLOB_RECURSE BOB_RUNTIME_SOURCES "src/sources/runtime/*.cpp")
|
||||||
file(GLOB_RECURSE BOB_PARSING_SOURCES "src/sources/parsing/*.cpp")
|
file(GLOB_RECURSE BOB_PARSING_SOURCES "src/sources/parsing/*.cpp")
|
||||||
file(GLOB_RECURSE BOB_STDLIB_SOURCES "src/sources/stdlib/*.cpp")
|
file(GLOB_RECURSE BOB_STDLIB_SOURCES "src/sources/stdlib/*.cpp")
|
||||||
|
file(GLOB_RECURSE BOB_BUILTIN_SOURCES "src/sources/builtinModules/*.cpp")
|
||||||
file(GLOB_RECURSE BOB_CLI_SOURCES "src/sources/cli/*.cpp")
|
file(GLOB_RECURSE BOB_CLI_SOURCES "src/sources/cli/*.cpp")
|
||||||
|
|
||||||
# All source files
|
# All source files
|
||||||
@ -55,6 +56,7 @@ set(BOB_ALL_SOURCES
|
|||||||
${BOB_RUNTIME_SOURCES}
|
${BOB_RUNTIME_SOURCES}
|
||||||
${BOB_PARSING_SOURCES}
|
${BOB_PARSING_SOURCES}
|
||||||
${BOB_STDLIB_SOURCES}
|
${BOB_STDLIB_SOURCES}
|
||||||
|
${BOB_BUILTIN_SOURCES}
|
||||||
${BOB_CLI_SOURCES}
|
${BOB_CLI_SOURCES}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -66,6 +68,7 @@ target_include_directories(bob PRIVATE
|
|||||||
src/headers/runtime
|
src/headers/runtime
|
||||||
src/headers/parsing
|
src/headers/parsing
|
||||||
src/headers/stdlib
|
src/headers/stdlib
|
||||||
|
src/headers/builtinModules
|
||||||
src/headers/cli
|
src/headers/cli
|
||||||
src/headers/common
|
src/headers/common
|
||||||
)
|
)
|
||||||
|
|||||||
113
Reference/EMBEDDING.md
Normal file
113
Reference/EMBEDDING.md
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
Embedding Bob: Public API Guide
|
||||||
|
================================
|
||||||
|
|
||||||
|
This document explains how to embed the Bob interpreter in your C++ application, register custom modules, and control sandbox policies.
|
||||||
|
|
||||||
|
Quick Start
|
||||||
|
-----------
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "cli/bob.h"
|
||||||
|
#include "ModuleRegistry.h"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
Bob bob;
|
||||||
|
|
||||||
|
// Optional: configure policies or modules before first use
|
||||||
|
bob.setBuiltinModulePolicy(true); // allow builtin modules (default)
|
||||||
|
bob.setBuiltinModuleDenyList({/* e.g., "sys" */});
|
||||||
|
|
||||||
|
// Register a custom builtin module called "demo"
|
||||||
|
bob.registerModule("demo", [](ModuleRegistry::ModuleBuilder& m) {
|
||||||
|
m.fn("hello", [](std::vector<Value> args, int, int) -> Value {
|
||||||
|
std::string who = (args.size() >= 1 && args[0].isString()) ? args[0].asString() : "world";
|
||||||
|
return Value(std::string("hello ") + who);
|
||||||
|
});
|
||||||
|
m.val("meaning", Value(42.0));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Evaluate code from a string
|
||||||
|
bob.evalString("import demo; print(demo.hello(\"Bob\"));", "<host>");
|
||||||
|
|
||||||
|
// Evaluate a file (imports inside resolve relative to the file's directory)
|
||||||
|
bob.evalFile("script.bob");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
API Overview
|
||||||
|
------------
|
||||||
|
|
||||||
|
Bob exposes a single high-level object with a minimal, consistent API. It self-manages an internal interpreter and applies configuration on first use.
|
||||||
|
|
||||||
|
- Program execution
|
||||||
|
- `bool evalString(const std::string& code, const std::string& filename = "<eval>")`
|
||||||
|
- `bool evalFile(const std::string& path)`
|
||||||
|
- `void runFile(const std::string& path)` (CLI convenience – delegates to `evalFile`)
|
||||||
|
- `void runPrompt()` (interactive CLI – delegates each line to `evalString`)
|
||||||
|
|
||||||
|
- Module registration and sandboxing
|
||||||
|
- `void registerModule(const std::string& name, std::function<void(ModuleRegistry::ModuleBuilder&)> init)`
|
||||||
|
- `void setBuiltinModulePolicy(bool allow)`
|
||||||
|
- `void setBuiltinModuleAllowList(const std::vector<std::string>& allowed)`
|
||||||
|
- `void setBuiltinModuleDenyList(const std::vector<std::string>& denied)`
|
||||||
|
|
||||||
|
- Global environment helpers
|
||||||
|
- `bool defineGlobal(const std::string& name, const Value& value)`
|
||||||
|
- `bool tryGetGlobal(const std::string& name, Value& out) const`
|
||||||
|
|
||||||
|
All configuration calls are safe to use before any evaluation – they are queued and applied automatically when the interpreter is first created.
|
||||||
|
|
||||||
|
Registering Custom Builtin Modules
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
Use the builder convenience to create a module:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
bob.registerModule("raylib", [](ModuleRegistry::ModuleBuilder& m) {
|
||||||
|
m.fn("init", [](std::vector<Value> args, int line, int col) -> Value {
|
||||||
|
// call into your library here; validate args, return Value
|
||||||
|
return NONE_VALUE;
|
||||||
|
});
|
||||||
|
m.val("VERSION", Value(std::string("5.0")));
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
At runtime:
|
||||||
|
|
||||||
|
```bob
|
||||||
|
import raylib;
|
||||||
|
raylib.init();
|
||||||
|
print(raylib.VERSION);
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes
|
||||||
|
-----
|
||||||
|
|
||||||
|
- Modules are immutable, first-class objects. `type(module)` is "module" and `toString(module)` prints `<module 'name'>`.
|
||||||
|
- Reassigning a module binding or setting module properties throws an error.
|
||||||
|
|
||||||
|
Builtin Modules and Sandboxing
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
- Builtin modules (e.g., `sys`) are registered during interpreter construction.
|
||||||
|
- File imports are always resolved relative to the importing file's directory.
|
||||||
|
- Policies:
|
||||||
|
- `setBuiltinModulePolicy(bool allow)` – enable/disable all builtin modules.
|
||||||
|
- `setBuiltinModuleAllowList(vector<string>)` – allow only listed modules (deny others).
|
||||||
|
- `setBuiltinModuleDenyList(vector<string>)` – explicitly deny listed modules.
|
||||||
|
- Denied/disabled modules are cloaked: `import name` reports "Module not found".
|
||||||
|
|
||||||
|
Error Reporting
|
||||||
|
---------------
|
||||||
|
|
||||||
|
- `evalString`/`evalFile` set file context for error reporting so line/column references point to the real source.
|
||||||
|
- Both return `true` on success and `false` if execution failed (errors are reported via the internal error reporter).
|
||||||
|
|
||||||
|
CLI vs Embedding
|
||||||
|
----------------
|
||||||
|
|
||||||
|
- CLI builds include `main.cpp` (entry point), which uses `Bob::runFile` or `Bob::runPrompt`.
|
||||||
|
- Embedded hosts do not use `main.cpp`; instead they instantiate `Bob` and call `evalString`/`evalFile` directly.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
13
bobby.bob
Normal file
13
bobby.bob
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
class A {
|
||||||
|
var inner = 10;
|
||||||
|
|
||||||
|
func test(){
|
||||||
|
print(this.inner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func hello(){
|
||||||
|
print("hello");
|
||||||
|
}
|
||||||
|
|
||||||
8
src/headers/builtinModules/register.h
Normal file
8
src/headers/builtinModules/register.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
class Interpreter;
|
||||||
|
|
||||||
|
// Registers all builtin modules with the interpreter
|
||||||
|
void registerAllBuiltinModules(Interpreter& interpreter);
|
||||||
|
|
||||||
|
|
||||||
8
src/headers/builtinModules/sys.h
Normal file
8
src/headers/builtinModules/sys.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
class Interpreter;
|
||||||
|
|
||||||
|
// Register the builtin 'sys' module
|
||||||
|
void registerSysModule(Interpreter& interpreter);
|
||||||
|
|
||||||
|
|
||||||
@ -5,6 +5,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include "Lexer.h"
|
#include "Lexer.h"
|
||||||
#include "Interpreter.h"
|
#include "Interpreter.h"
|
||||||
|
#include "ModuleRegistry.h"
|
||||||
#include "helperFunctions/ShortHands.h"
|
#include "helperFunctions/ShortHands.h"
|
||||||
#include "ErrorReporter.h"
|
#include "ErrorReporter.h"
|
||||||
|
|
||||||
@ -20,10 +21,41 @@ public:
|
|||||||
~Bob() = default;
|
~Bob() = default;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
// Embedding helpers (bridge to internal interpreter)
|
||||||
|
void registerModule(const std::string& name, std::function<void(ModuleRegistry::ModuleBuilder&)> init) {
|
||||||
|
if (interpreter) interpreter->registerModule(name, init);
|
||||||
|
else pendingConfigurators.push_back([name, init](Interpreter& I){ I.registerModule(name, init); });
|
||||||
|
}
|
||||||
|
void setBuiltinModulePolicy(bool allow) {
|
||||||
|
if (interpreter) interpreter->setBuiltinModulePolicy(allow);
|
||||||
|
else pendingConfigurators.push_back([allow](Interpreter& I){ I.setBuiltinModulePolicy(allow); });
|
||||||
|
}
|
||||||
|
void setBuiltinModuleAllowList(const std::vector<std::string>& allowed) {
|
||||||
|
if (interpreter) interpreter->setBuiltinModuleAllowList(allowed);
|
||||||
|
else pendingConfigurators.push_back([allowed](Interpreter& I){ I.setBuiltinModuleAllowList(allowed); });
|
||||||
|
}
|
||||||
|
void setBuiltinModuleDenyList(const std::vector<std::string>& denied) {
|
||||||
|
if (interpreter) interpreter->setBuiltinModuleDenyList(denied);
|
||||||
|
else pendingConfigurators.push_back([denied](Interpreter& I){ I.setBuiltinModuleDenyList(denied); });
|
||||||
|
}
|
||||||
|
bool defineGlobal(const std::string& name, const Value& v) {
|
||||||
|
if (interpreter) return interpreter->defineGlobalVar(name, v);
|
||||||
|
pendingConfigurators.push_back([name, v](Interpreter& I){ I.defineGlobalVar(name, v); });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool tryGetGlobal(const std::string& name, Value& out) const { return interpreter ? interpreter->tryGetGlobalVar(name, out) : false; }
|
||||||
void runFile(const std::string& path);
|
void runFile(const std::string& path);
|
||||||
void runPrompt();
|
void runPrompt();
|
||||||
|
bool evalFile(const std::string& path);
|
||||||
|
bool evalString(const std::string& code, const std::string& filename = "<eval>");
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void run(std::string source);
|
void ensureInterpreter(bool interactive);
|
||||||
|
void applyPendingConfigs() {
|
||||||
|
if (!interpreter) return;
|
||||||
|
for (auto& f : pendingConfigurators) { f(*interpreter); }
|
||||||
|
pendingConfigurators.clear();
|
||||||
|
}
|
||||||
|
std::vector<std::function<void(Interpreter&)>> pendingConfigurators;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -64,6 +64,7 @@ public:
|
|||||||
// Source push/pop for eval
|
// Source push/pop for eval
|
||||||
void pushSource(const std::string& source, const std::string& fileName);
|
void pushSource(const std::string& source, const std::string& fileName);
|
||||||
void popSource();
|
void popSource();
|
||||||
|
const std::string& getCurrentFileName() const { return currentFileName; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void displaySourceContext(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_ = "", bool showArrow = true);
|
void displaySourceContext(int line, int column, const std::string& errorType, const std::string& message, const std::string& operator_ = "", bool showArrow = true);
|
||||||
|
|||||||
@ -26,6 +26,7 @@ enum TokenType{
|
|||||||
|
|
||||||
AND, OR, TRUE, FALSE, IF, ELSE, FUNCTION, FOR,
|
AND, OR, TRUE, FALSE, IF, ELSE, FUNCTION, FOR,
|
||||||
WHILE, DO, VAR, CLASS, EXTENDS, EXTENSION, SUPER, THIS, NONE, RETURN, BREAK, CONTINUE,
|
WHILE, DO, VAR, CLASS, EXTENDS, EXTENSION, SUPER, THIS, NONE, RETURN, BREAK, CONTINUE,
|
||||||
|
IMPORT, FROM, AS,
|
||||||
TRY, CATCH, FINALLY, THROW,
|
TRY, CATCH, FINALLY, THROW,
|
||||||
|
|
||||||
// Compound assignment operators
|
// Compound assignment operators
|
||||||
@ -56,6 +57,7 @@ inline std::string enum_mapping[] = {"OPEN_PAREN", "CLOSE_PAREN", "OPEN_BRACE",
|
|||||||
|
|
||||||
"AND", "OR", "TRUE", "FALSE", "IF", "ELSE", "FUNCTION", "FOR",
|
"AND", "OR", "TRUE", "FALSE", "IF", "ELSE", "FUNCTION", "FOR",
|
||||||
"WHILE", "DO", "VAR", "CLASS", "EXTENDS", "EXTENSION", "SUPER", "THIS", "NONE", "RETURN", "BREAK", "CONTINUE",
|
"WHILE", "DO", "VAR", "CLASS", "EXTENDS", "EXTENSION", "SUPER", "THIS", "NONE", "RETURN", "BREAK", "CONTINUE",
|
||||||
|
"IMPORT", "FROM", "AS",
|
||||||
"TRY", "CATCH", "FINALLY", "THROW",
|
"TRY", "CATCH", "FINALLY", "THROW",
|
||||||
|
|
||||||
// Compound assignment operators
|
// Compound assignment operators
|
||||||
@ -87,6 +89,9 @@ const std::map<std::string, TokenType> KEYWORDS {
|
|||||||
{"return", RETURN},
|
{"return", RETURN},
|
||||||
{"break", BREAK},
|
{"break", BREAK},
|
||||||
{"continue", CONTINUE},
|
{"continue", CONTINUE},
|
||||||
|
{"import", IMPORT},
|
||||||
|
{"from", FROM},
|
||||||
|
{"as", AS},
|
||||||
{"try", TRY},
|
{"try", TRY},
|
||||||
{"catch", CATCH},
|
{"catch", CATCH},
|
||||||
{"finally", FINALLY},
|
{"finally", FINALLY},
|
||||||
|
|||||||
@ -72,6 +72,8 @@ private:
|
|||||||
std::shared_ptr<Stmt> extensionDeclaration();
|
std::shared_ptr<Stmt> extensionDeclaration();
|
||||||
std::shared_ptr<Stmt> tryStatement();
|
std::shared_ptr<Stmt> tryStatement();
|
||||||
std::shared_ptr<Stmt> throwStatement();
|
std::shared_ptr<Stmt> throwStatement();
|
||||||
|
std::shared_ptr<Stmt> importStatement();
|
||||||
|
std::shared_ptr<Stmt> fromImportStatement();
|
||||||
|
|
||||||
std::shared_ptr<Stmt> varDeclaration();
|
std::shared_ptr<Stmt> varDeclaration();
|
||||||
|
|
||||||
|
|||||||
@ -40,6 +40,8 @@ struct StmtVisitor
|
|||||||
virtual void visitExtensionStmt(const std::shared_ptr<ExtensionStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
virtual void visitExtensionStmt(const std::shared_ptr<ExtensionStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
||||||
virtual void visitTryStmt(const std::shared_ptr<struct TryStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
virtual void visitTryStmt(const std::shared_ptr<struct TryStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
||||||
virtual void visitThrowStmt(const std::shared_ptr<struct ThrowStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
virtual void visitThrowStmt(const std::shared_ptr<struct ThrowStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
||||||
|
virtual void visitImportStmt(const std::shared_ptr<struct ImportStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
||||||
|
virtual void visitFromImportStmt(const std::shared_ptr<struct FromImportStmt>& stmt, ExecutionContext* context = nullptr) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Stmt : public std::enable_shared_from_this<Stmt>
|
struct Stmt : public std::enable_shared_from_this<Stmt>
|
||||||
@ -272,3 +274,29 @@ struct ThrowStmt : Stmt {
|
|||||||
visitor->visitThrowStmt(std::static_pointer_cast<ThrowStmt>(shared_from_this()), context);
|
visitor->visitThrowStmt(std::static_pointer_cast<ThrowStmt>(shared_from_this()), context);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// import module [as alias]
|
||||||
|
struct ImportStmt : Stmt {
|
||||||
|
Token importToken; // IMPORT
|
||||||
|
Token moduleName; // IDENTIFIER
|
||||||
|
bool hasAlias = false;
|
||||||
|
Token alias; // IDENTIFIER if hasAlias
|
||||||
|
ImportStmt(Token kw, Token mod, bool ha, Token al)
|
||||||
|
: importToken(kw), moduleName(mod), hasAlias(ha), alias(al) {}
|
||||||
|
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override {
|
||||||
|
visitor->visitImportStmt(std::static_pointer_cast<ImportStmt>(shared_from_this()), context);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// from module import name [as alias], name2 ...
|
||||||
|
struct FromImportStmt : Stmt {
|
||||||
|
Token fromToken; // FROM
|
||||||
|
Token moduleName; // IDENTIFIER
|
||||||
|
struct ImportItem { Token name; bool hasAlias; Token alias; };
|
||||||
|
std::vector<ImportItem> items;
|
||||||
|
FromImportStmt(Token kw, Token mod, std::vector<ImportItem> it)
|
||||||
|
: fromToken(kw), moduleName(mod), items(std::move(it)) {}
|
||||||
|
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override {
|
||||||
|
visitor->visitFromImportStmt(std::static_pointer_cast<FromImportStmt>(shared_from_this()), context);
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -3,6 +3,7 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <unordered_set>
|
||||||
#include "Value.h"
|
#include "Value.h"
|
||||||
#include "Lexer.h"
|
#include "Lexer.h"
|
||||||
|
|
||||||
@ -46,11 +47,14 @@ public:
|
|||||||
void pruneForClosureCapture();
|
void pruneForClosureCapture();
|
||||||
|
|
||||||
std::shared_ptr<Environment> getParent() const { return parent; }
|
std::shared_ptr<Environment> getParent() const { return parent; }
|
||||||
|
// Export all variables (shallow copy) for module namespace
|
||||||
|
std::unordered_map<std::string, Value> getAll() const { return variables; }
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unordered_map<std::string, Value> variables;
|
std::unordered_map<std::string, Value> variables;
|
||||||
std::shared_ptr<Environment> parent;
|
std::shared_ptr<Environment> parent;
|
||||||
ErrorReporter* errorReporter;
|
ErrorReporter* errorReporter;
|
||||||
|
std::unordered_set<std::string> constBindings;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -42,6 +42,8 @@ public:
|
|||||||
void visitExtensionStmt(const std::shared_ptr<ExtensionStmt>& statement, ExecutionContext* context = nullptr) override;
|
void visitExtensionStmt(const std::shared_ptr<ExtensionStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
void visitTryStmt(const std::shared_ptr<TryStmt>& statement, ExecutionContext* context = nullptr) override;
|
void visitTryStmt(const std::shared_ptr<TryStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
void visitThrowStmt(const std::shared_ptr<ThrowStmt>& statement, ExecutionContext* context = nullptr) override;
|
void visitThrowStmt(const std::shared_ptr<ThrowStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitImportStmt(const std::shared_ptr<ImportStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
void visitFromImportStmt(const std::shared_ptr<FromImportStmt>& statement, ExecutionContext* context = nullptr) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context);
|
void execute(const std::shared_ptr<Stmt>& statement, ExecutionContext* context);
|
||||||
|
|||||||
@ -7,7 +7,10 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#include "Value.h"
|
#include "Value.h"
|
||||||
|
#include "TypeWrapper.h"
|
||||||
#include "RuntimeDiagnostics.h"
|
#include "RuntimeDiagnostics.h"
|
||||||
|
#include "ModuleRegistry.h"
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
struct Expr;
|
struct Expr;
|
||||||
struct Stmt;
|
struct Stmt;
|
||||||
@ -81,6 +84,15 @@ private:
|
|||||||
RuntimeDiagnostics diagnostics; // Utility functions for runtime operations
|
RuntimeDiagnostics diagnostics; // Utility functions for runtime operations
|
||||||
std::unique_ptr<Evaluator> evaluator;
|
std::unique_ptr<Evaluator> evaluator;
|
||||||
std::unique_ptr<Executor> executor;
|
std::unique_ptr<Executor> executor;
|
||||||
|
// Module cache: module key -> module dict value
|
||||||
|
std::unordered_map<std::string, Value> moduleCache;
|
||||||
|
// Builtin module registry
|
||||||
|
ModuleRegistry builtinModules;
|
||||||
|
// Import policy flags
|
||||||
|
bool allowFileImports = true;
|
||||||
|
bool preferFileOverBuiltin = true;
|
||||||
|
bool allowBuiltinImports = true;
|
||||||
|
std::vector<std::string> moduleSearchPaths; // e.g., BOBPATH
|
||||||
// Pending throw propagation from expression evaluation
|
// Pending throw propagation from expression evaluation
|
||||||
bool hasPendingThrow = false;
|
bool hasPendingThrow = false;
|
||||||
Value pendingThrow = NONE_VALUE;
|
Value pendingThrow = NONE_VALUE;
|
||||||
@ -127,6 +139,24 @@ public:
|
|||||||
bool getClassTemplate(const std::string& className, std::unordered_map<std::string, Value>& out) const;
|
bool getClassTemplate(const std::string& className, std::unordered_map<std::string, Value>& out) const;
|
||||||
std::unordered_map<std::string, Value> buildMergedTemplate(const std::string& className) const;
|
std::unordered_map<std::string, Value> buildMergedTemplate(const std::string& className) const;
|
||||||
void addStdLibFunctions();
|
void addStdLibFunctions();
|
||||||
|
// Module APIs
|
||||||
|
Value importModule(const std::string& spec, int line, int column); // returns module dict
|
||||||
|
bool fromImport(const std::string& spec, const std::vector<std::pair<std::string, std::string>>& items, int line, int column); // name->alias
|
||||||
|
void setModulePolicy(bool allowFiles, bool preferFiles, const std::vector<std::string>& searchPaths);
|
||||||
|
void setBuiltinModulePolicy(bool allowBuiltins) { allowBuiltinImports = allowBuiltins; builtinModules.setPolicy(allowBuiltins); }
|
||||||
|
void setBuiltinModuleAllowList(const std::vector<std::string>& allowed) { builtinModules.setAllowList(allowed); }
|
||||||
|
void setBuiltinModuleDenyList(const std::vector<std::string>& denied) { builtinModules.setDenyList(denied); }
|
||||||
|
void registerBuiltinModule(const std::string& name, std::function<Value(Interpreter&)> factory) { builtinModules.registerFactory(name, std::move(factory)); }
|
||||||
|
|
||||||
|
// Simple module registration API
|
||||||
|
using ModuleBuilder = ModuleRegistry::ModuleBuilder;
|
||||||
|
|
||||||
|
void registerModule(const std::string& name, std::function<void(ModuleBuilder&)> init) {
|
||||||
|
builtinModules.registerModule(name, std::move(init));
|
||||||
|
}
|
||||||
|
// Global environment helpers
|
||||||
|
bool defineGlobalVar(const std::string& name, const Value& value);
|
||||||
|
bool tryGetGlobalVar(const std::string& name, Value& out) const;
|
||||||
// Throw propagation helpers
|
// Throw propagation helpers
|
||||||
void setPendingThrow(const Value& v, int line = 0, int column = 0) { hasPendingThrow = true; pendingThrow = v; pendingThrowLine = line; pendingThrowColumn = column; }
|
void setPendingThrow(const Value& v, int line = 0, int column = 0) { hasPendingThrow = true; pendingThrow = v; pendingThrowLine = line; pendingThrowColumn = column; }
|
||||||
bool consumePendingThrow(Value& out, int* lineOut = nullptr, int* colOut = nullptr) { if (!hasPendingThrow) return false; out = pendingThrow; if (lineOut) *lineOut = pendingThrowLine; if (colOut) *colOut = pendingThrowColumn; hasPendingThrow = false; pendingThrow = NONE_VALUE; pendingThrowLine = 0; pendingThrowColumn = 0; return true; }
|
bool consumePendingThrow(Value& out, int* lineOut = nullptr, int* colOut = nullptr) { if (!hasPendingThrow) return false; out = pendingThrow; if (lineOut) *lineOut = pendingThrowLine; if (colOut) *colOut = pendingThrowColumn; hasPendingThrow = false; pendingThrow = NONE_VALUE; pendingThrowLine = 0; pendingThrowColumn = 0; return true; }
|
||||||
|
|||||||
14
src/headers/runtime/ModuleDef.h
Normal file
14
src/headers/runtime/ModuleDef.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// ModuleDef.h
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include "Value.h"
|
||||||
|
|
||||||
|
struct Module {
|
||||||
|
std::string name;
|
||||||
|
std::shared_ptr<std::unordered_map<std::string, Value>> exports;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
70
src/headers/runtime/ModuleRegistry.h
Normal file
70
src/headers/runtime/ModuleRegistry.h
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include "TypeWrapper.h" // BuiltinFunction, Value
|
||||||
|
|
||||||
|
class Interpreter; // fwd
|
||||||
|
|
||||||
|
class ModuleRegistry {
|
||||||
|
public:
|
||||||
|
struct ModuleBuilder {
|
||||||
|
std::string moduleName;
|
||||||
|
Interpreter& interpreterRef;
|
||||||
|
std::unordered_map<std::string, Value> exports;
|
||||||
|
ModuleBuilder(const std::string& n, Interpreter& i) : moduleName(n), interpreterRef(i) {}
|
||||||
|
void fn(const std::string& name, std::function<Value(std::vector<Value>, int, int)> func) {
|
||||||
|
exports[name] = Value(std::make_shared<BuiltinFunction>(name, func));
|
||||||
|
}
|
||||||
|
void val(const std::string& name, const Value& v) { exports[name] = v; }
|
||||||
|
};
|
||||||
|
|
||||||
|
using Factory = std::function<Value(Interpreter&)>;
|
||||||
|
|
||||||
|
void registerFactory(const std::string& name, Factory factory) {
|
||||||
|
factories[name] = std::move(factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
void registerModule(const std::string& name, std::function<void(ModuleBuilder&)> init) {
|
||||||
|
registerFactory(name, [name, init](Interpreter& I) -> Value {
|
||||||
|
ModuleBuilder b(name, I);
|
||||||
|
init(b);
|
||||||
|
auto m = std::make_shared<Module>(name, b.exports);
|
||||||
|
return Value(m);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool has(const std::string& name) const {
|
||||||
|
auto it = factories.find(name);
|
||||||
|
if (it == factories.end()) return false;
|
||||||
|
// Respect policy for presence checks to optionally cloak denied modules
|
||||||
|
if (!allowBuiltins) return false;
|
||||||
|
if (!allowList.empty() && allowList.find(name) == allowList.end()) return false;
|
||||||
|
if (denyList.find(name) != denyList.end()) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value create(const std::string& name, Interpreter& I) const {
|
||||||
|
auto it = factories.find(name);
|
||||||
|
if (it == factories.end()) return NONE_VALUE;
|
||||||
|
if (!allowBuiltins) return NONE_VALUE;
|
||||||
|
if (!allowList.empty() && allowList.find(name) == allowList.end()) return NONE_VALUE;
|
||||||
|
if (denyList.find(name) != denyList.end()) return NONE_VALUE;
|
||||||
|
return it->second(I);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPolicy(bool allow) { allowBuiltins = allow; }
|
||||||
|
void setAllowList(const std::vector<std::string>& allowed) { allowList = std::unordered_set<std::string>(allowed.begin(), allowed.end()); }
|
||||||
|
void setDenyList(const std::vector<std::string>& denied) { denyList = std::unordered_set<std::string>(denied.begin(), denied.end()); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_map<std::string, Factory> factories;
|
||||||
|
std::unordered_set<std::string> allowList;
|
||||||
|
std::unordered_set<std::string> denyList;
|
||||||
|
bool allowBuiltins = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -13,6 +13,7 @@ struct Environment;
|
|||||||
struct Function;
|
struct Function;
|
||||||
struct BuiltinFunction;
|
struct BuiltinFunction;
|
||||||
struct Thunk;
|
struct Thunk;
|
||||||
|
struct Module;
|
||||||
|
|
||||||
// Type tags for the Value union
|
// Type tags for the Value union
|
||||||
enum ValueType {
|
enum ValueType {
|
||||||
@ -24,9 +25,12 @@ enum ValueType {
|
|||||||
VAL_BUILTIN_FUNCTION,
|
VAL_BUILTIN_FUNCTION,
|
||||||
VAL_THUNK,
|
VAL_THUNK,
|
||||||
VAL_ARRAY,
|
VAL_ARRAY,
|
||||||
VAL_DICT
|
VAL_DICT,
|
||||||
|
VAL_MODULE
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// (moved below Value)
|
||||||
|
|
||||||
// Tagged value system (like Lua) - no heap allocation for simple values
|
// Tagged value system (like Lua) - no heap allocation for simple values
|
||||||
struct Value {
|
struct Value {
|
||||||
union {
|
union {
|
||||||
@ -37,6 +41,7 @@ struct Value {
|
|||||||
std::string string_value; // Store strings outside the union for safety
|
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::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
|
std::shared_ptr<std::unordered_map<std::string, Value> > dict_value; // Store dictionaries as shared_ptr for mutability
|
||||||
|
std::shared_ptr<Module> module_value; // Module object
|
||||||
|
|
||||||
// Store functions as shared_ptr for proper reference counting
|
// Store functions as shared_ptr for proper reference counting
|
||||||
std::shared_ptr<Function> function;
|
std::shared_ptr<Function> function;
|
||||||
@ -58,6 +63,7 @@ struct Value {
|
|||||||
Value(std::vector<Value>&& arr) : type(ValueType::VAL_ARRAY), array_value(std::make_shared<std::vector<Value> >(std::move(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(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))) {}
|
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))) {}
|
||||||
|
Value(std::shared_ptr<Module> m) : type(ValueType::VAL_MODULE), module_value(std::move(m)) {}
|
||||||
|
|
||||||
// Destructor to clean up functions and thunks
|
// Destructor to clean up functions and thunks
|
||||||
~Value() {
|
~Value() {
|
||||||
@ -70,7 +76,7 @@ struct Value {
|
|||||||
// Move constructor
|
// Move constructor
|
||||||
Value(Value&& other) noexcept
|
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)),
|
: type(other.type), string_value(std::move(other.string_value)), array_value(std::move(other.array_value)), dict_value(std::move(other.dict_value)),
|
||||||
function(std::move(other.function)), builtin_function(std::move(other.builtin_function)), thunk(std::move(other.thunk)) {
|
function(std::move(other.function)), builtin_function(std::move(other.builtin_function)), thunk(std::move(other.thunk)), module_value(std::move(other.module_value)) {
|
||||||
if (type != ValueType::VAL_STRING && type != ValueType::VAL_ARRAY && type != ValueType::VAL_DICT &&
|
if (type != ValueType::VAL_STRING && type != ValueType::VAL_ARRAY && type != ValueType::VAL_DICT &&
|
||||||
type != ValueType::VAL_FUNCTION && type != ValueType::VAL_BUILTIN_FUNCTION && type != ValueType::VAL_THUNK) {
|
type != ValueType::VAL_FUNCTION && type != ValueType::VAL_BUILTIN_FUNCTION && type != ValueType::VAL_THUNK) {
|
||||||
number = other.number; // Copy the union
|
number = other.number; // Copy the union
|
||||||
@ -94,6 +100,8 @@ struct Value {
|
|||||||
builtin_function = std::move(other.builtin_function);
|
builtin_function = std::move(other.builtin_function);
|
||||||
} else if (type == ValueType::VAL_THUNK) {
|
} else if (type == ValueType::VAL_THUNK) {
|
||||||
thunk = std::move(other.thunk);
|
thunk = std::move(other.thunk);
|
||||||
|
} else if (type == ValueType::VAL_MODULE) {
|
||||||
|
module_value = std::move(other.module_value);
|
||||||
} else {
|
} else {
|
||||||
number = other.number;
|
number = other.number;
|
||||||
}
|
}
|
||||||
@ -117,6 +125,8 @@ struct Value {
|
|||||||
builtin_function = other.builtin_function; // shared_ptr automatically handles sharing
|
builtin_function = other.builtin_function; // shared_ptr automatically handles sharing
|
||||||
} else if (type == ValueType::VAL_THUNK) {
|
} else if (type == ValueType::VAL_THUNK) {
|
||||||
thunk = other.thunk; // shared_ptr automatically handles sharing
|
thunk = other.thunk; // shared_ptr automatically handles sharing
|
||||||
|
} else if (type == ValueType::VAL_MODULE) {
|
||||||
|
module_value = other.module_value; // shared module
|
||||||
} else {
|
} else {
|
||||||
number = other.number;
|
number = other.number;
|
||||||
}
|
}
|
||||||
@ -146,6 +156,8 @@ struct Value {
|
|||||||
builtin_function = other.builtin_function; // shared_ptr automatically handles sharing
|
builtin_function = other.builtin_function; // shared_ptr automatically handles sharing
|
||||||
} else if (type == ValueType::VAL_THUNK) {
|
} else if (type == ValueType::VAL_THUNK) {
|
||||||
thunk = other.thunk; // shared_ptr automatically handles sharing
|
thunk = other.thunk; // shared_ptr automatically handles sharing
|
||||||
|
} else if (type == ValueType::VAL_MODULE) {
|
||||||
|
module_value = other.module_value;
|
||||||
} else {
|
} else {
|
||||||
number = other.number;
|
number = other.number;
|
||||||
}
|
}
|
||||||
@ -161,6 +173,7 @@ struct Value {
|
|||||||
inline bool isBuiltinFunction() const { return type == ValueType::VAL_BUILTIN_FUNCTION; }
|
inline bool isBuiltinFunction() const { return type == ValueType::VAL_BUILTIN_FUNCTION; }
|
||||||
inline bool isArray() const { return type == ValueType::VAL_ARRAY; }
|
inline bool isArray() const { return type == ValueType::VAL_ARRAY; }
|
||||||
inline bool isDict() const { return type == ValueType::VAL_DICT; }
|
inline bool isDict() const { return type == ValueType::VAL_DICT; }
|
||||||
|
inline bool isModule() const { return type == ValueType::VAL_MODULE; }
|
||||||
inline bool isThunk() const { return type == ValueType::VAL_THUNK; }
|
inline bool isThunk() const { return type == ValueType::VAL_THUNK; }
|
||||||
inline bool isNone() const { return type == ValueType::VAL_NONE; }
|
inline bool isNone() const { return type == ValueType::VAL_NONE; }
|
||||||
|
|
||||||
@ -176,6 +189,7 @@ struct Value {
|
|||||||
case ValueType::VAL_THUNK: return "thunk";
|
case ValueType::VAL_THUNK: return "thunk";
|
||||||
case ValueType::VAL_ARRAY: return "array";
|
case ValueType::VAL_ARRAY: return "array";
|
||||||
case ValueType::VAL_DICT: return "dict";
|
case ValueType::VAL_DICT: return "dict";
|
||||||
|
case ValueType::VAL_MODULE: return "module";
|
||||||
default: return "unknown";
|
default: return "unknown";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -198,6 +212,7 @@ struct Value {
|
|||||||
inline std::unordered_map<std::string, Value>& asDict() {
|
inline std::unordered_map<std::string, Value>& asDict() {
|
||||||
return *dict_value;
|
return *dict_value;
|
||||||
}
|
}
|
||||||
|
inline Module* asModule() const { return isModule() ? module_value.get() : nullptr; }
|
||||||
inline Function* asFunction() const { return isFunction() ? function.get() : nullptr; }
|
inline Function* asFunction() const { return isFunction() ? function.get() : nullptr; }
|
||||||
inline BuiltinFunction* asBuiltinFunction() const { return isBuiltinFunction() ? builtin_function.get() : nullptr; }
|
inline BuiltinFunction* asBuiltinFunction() const { return isBuiltinFunction() ? builtin_function.get() : nullptr; }
|
||||||
inline Thunk* asThunk() const { return isThunk() ? thunk.get() : nullptr; }
|
inline Thunk* asThunk() const { return isThunk() ? thunk.get() : nullptr; }
|
||||||
@ -214,6 +229,7 @@ struct Value {
|
|||||||
case ValueType::VAL_THUNK: return thunk != nullptr;
|
case ValueType::VAL_THUNK: return thunk != nullptr;
|
||||||
case ValueType::VAL_ARRAY: return !array_value->empty();
|
case ValueType::VAL_ARRAY: return !array_value->empty();
|
||||||
case ValueType::VAL_DICT: return !dict_value->empty();
|
case ValueType::VAL_DICT: return !dict_value->empty();
|
||||||
|
case ValueType::VAL_MODULE: return module_value != nullptr;
|
||||||
default: return false;
|
default: return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -296,6 +312,12 @@ struct Value {
|
|||||||
result += "}";
|
result += "}";
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
case ValueType::VAL_MODULE: {
|
||||||
|
// Avoid accessing Module fields when it's still an incomplete type in some TUs.
|
||||||
|
// Delegate formatting to a small helper defined out-of-line in Value.cpp.
|
||||||
|
extern std::string formatModuleForToString(const std::shared_ptr<Module>&);
|
||||||
|
return formatModuleForToString(module_value);
|
||||||
|
}
|
||||||
default: return "unknown";
|
default: return "unknown";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -420,6 +442,15 @@ struct Value {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Define Module after Value so it can hold Value in exports without incomplete type issues
|
||||||
|
struct Module {
|
||||||
|
std::string name;
|
||||||
|
std::shared_ptr<std::unordered_map<std::string, Value>> exports;
|
||||||
|
Module() = default;
|
||||||
|
Module(const std::string& n, const std::unordered_map<std::string, Value>& dict)
|
||||||
|
: name(n), exports(std::make_shared<std::unordered_map<std::string, Value>>(dict)) {}
|
||||||
|
};
|
||||||
|
|
||||||
// Global constants for common values
|
// Global constants for common values
|
||||||
extern const Value NONE_VALUE;
|
extern const Value NONE_VALUE;
|
||||||
extern const Value TRUE_VALUE;
|
extern const Value TRUE_VALUE;
|
||||||
|
|||||||
8
src/sources/builtinModules/register.cpp
Normal file
8
src/sources/builtinModules/register.cpp
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#include "register.h"
|
||||||
|
#include "sys.h"
|
||||||
|
|
||||||
|
void registerAllBuiltinModules(Interpreter& interpreter) {
|
||||||
|
registerSysModule(interpreter);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
146
src/sources/builtinModules/sys.cpp
Normal file
146
src/sources/builtinModules/sys.cpp
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
#include "sys.h"
|
||||||
|
#include "Interpreter.h"
|
||||||
|
#include "Environment.h"
|
||||||
|
#include "Lexer.h" // for Token and IDENTIFIER
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
#define DYLD_BOOL DYLD_BOOL_IGNORED
|
||||||
|
#include <mach-o/dyld.h>
|
||||||
|
#undef DYLD_BOOL
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void registerSysModule(Interpreter& interpreter) {
|
||||||
|
interpreter.registerModule("sys", [](Interpreter::ModuleBuilder& m) {
|
||||||
|
Interpreter& I = m.interpreterRef;
|
||||||
|
m.fn("memoryUsage", [&I](std::vector<Value>, int l, int c) -> Value {
|
||||||
|
try {
|
||||||
|
Value fn = I.getEnvironment()->get(Token{IDENTIFIER, "memoryUsage", l, c});
|
||||||
|
if (fn.isBuiltinFunction()) return fn.asBuiltinFunction()->func({}, l, c);
|
||||||
|
} catch (...) {}
|
||||||
|
return NONE_VALUE;
|
||||||
|
});
|
||||||
|
m.fn("exit", [](std::vector<Value> a, int, int) -> Value {
|
||||||
|
int code = 0; if (!a.empty() && a[0].isNumber()) code = static_cast<int>(a[0].asNumber());
|
||||||
|
std::exit(code);
|
||||||
|
return NONE_VALUE;
|
||||||
|
});
|
||||||
|
m.fn("cwd", [](std::vector<Value>, int, int) -> Value {
|
||||||
|
char buf[PATH_MAX];
|
||||||
|
if (getcwd(buf, sizeof(buf))) { return Value(std::string(buf)); }
|
||||||
|
return NONE_VALUE;
|
||||||
|
});
|
||||||
|
m.fn("platform", [](std::vector<Value>, int, int) -> Value {
|
||||||
|
#if defined(_WIN32)
|
||||||
|
return Value(std::string("windows"));
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
return Value(std::string("macos"));
|
||||||
|
#elif defined(__linux__)
|
||||||
|
return Value(std::string("linux"));
|
||||||
|
#else
|
||||||
|
return Value(std::string("unknown"));
|
||||||
|
#endif
|
||||||
|
});
|
||||||
|
m.fn("getenv", [](std::vector<Value> a, int, int) -> Value {
|
||||||
|
if (a.size() != 1 || !a[0].isString()) return NONE_VALUE;
|
||||||
|
const char* v = std::getenv(a[0].asString().c_str());
|
||||||
|
if (!v) return NONE_VALUE;
|
||||||
|
return Value(std::string(v));
|
||||||
|
});
|
||||||
|
m.fn("pid", [](std::vector<Value>, int, int) -> Value {
|
||||||
|
return Value(static_cast<double>(getpid()));
|
||||||
|
});
|
||||||
|
m.fn("chdir", [](std::vector<Value> a, int, int) -> Value {
|
||||||
|
if (a.size() != 1 || !a[0].isString()) return Value(false);
|
||||||
|
const std::string& p = a[0].asString();
|
||||||
|
int rc = chdir(p.c_str());
|
||||||
|
return Value(rc == 0);
|
||||||
|
});
|
||||||
|
m.fn("homeDir", [](std::vector<Value>, int, int) -> Value {
|
||||||
|
const char* h = std::getenv("HOME");
|
||||||
|
if (h) return Value(std::string(h));
|
||||||
|
const char* up = std::getenv("USERPROFILE");
|
||||||
|
if (up) return Value(std::string(up));
|
||||||
|
return NONE_VALUE;
|
||||||
|
});
|
||||||
|
m.fn("tempDir", [](std::vector<Value>, int, int) -> Value {
|
||||||
|
const char* t = std::getenv("TMPDIR");
|
||||||
|
if (!t) t = std::getenv("TMP");
|
||||||
|
if (!t) t = std::getenv("TEMP");
|
||||||
|
if (t) return Value(std::string(t));
|
||||||
|
return Value(std::string("/tmp"));
|
||||||
|
});
|
||||||
|
m.fn("pathSep", [](std::vector<Value>, int, int) -> Value {
|
||||||
|
#if defined(_WIN32)
|
||||||
|
return Value(std::string(";"));
|
||||||
|
#else
|
||||||
|
return Value(std::string(":"));
|
||||||
|
#endif
|
||||||
|
});
|
||||||
|
m.fn("dirSep", [](std::vector<Value>, int, int) -> Value {
|
||||||
|
#if defined(_WIN32)
|
||||||
|
return Value(std::string("\\"));
|
||||||
|
#else
|
||||||
|
return Value(std::string("/"));
|
||||||
|
#endif
|
||||||
|
});
|
||||||
|
m.fn("execPath", [](std::vector<Value>, int, int) -> Value {
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
uint32_t sz = 0;
|
||||||
|
_NSGetExecutablePath(nullptr, &sz);
|
||||||
|
std::string buf(sz, '\0');
|
||||||
|
if (_NSGetExecutablePath(buf.data(), &sz) == 0) {
|
||||||
|
buf.resize(std::strlen(buf.c_str()));
|
||||||
|
return Value(buf);
|
||||||
|
}
|
||||||
|
return NONE_VALUE;
|
||||||
|
#elif defined(__linux__)
|
||||||
|
char path[PATH_MAX];
|
||||||
|
ssize_t len = readlink("/proc/self/exe", path, sizeof(path) - 1);
|
||||||
|
if (len > 0) { path[len] = '\0'; return Value(std::string(path)); }
|
||||||
|
return NONE_VALUE;
|
||||||
|
#else
|
||||||
|
return NONE_VALUE;
|
||||||
|
#endif
|
||||||
|
});
|
||||||
|
m.fn("env", [](std::vector<Value>, int, int) -> Value {
|
||||||
|
std::unordered_map<std::string, Value> out;
|
||||||
|
#if defined(__APPLE__) || defined(__linux__)
|
||||||
|
extern char **environ;
|
||||||
|
if (environ) {
|
||||||
|
for (char **e = environ; *e != nullptr; ++e) {
|
||||||
|
const char* kv = *e;
|
||||||
|
const char* eq = std::strchr(kv, '=');
|
||||||
|
if (!eq) continue;
|
||||||
|
std::string key(kv, eq - kv);
|
||||||
|
std::string val(eq + 1);
|
||||||
|
out[key] = Value(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return Value(out);
|
||||||
|
});
|
||||||
|
m.fn("setenv", [](std::vector<Value> a, int, int) -> Value {
|
||||||
|
if (a.size() != 2 || !a[0].isString() || !a[1].isString()) return Value(false);
|
||||||
|
#if defined(__APPLE__) || defined(__linux__)
|
||||||
|
int rc = ::setenv(a[0].asString().c_str(), a[1].asString().c_str(), 1);
|
||||||
|
return Value(rc == 0);
|
||||||
|
#else
|
||||||
|
return Value(false);
|
||||||
|
#endif
|
||||||
|
});
|
||||||
|
m.fn("unsetenv", [](std::vector<Value> a, int, int) -> Value {
|
||||||
|
if (a.size() != 1 || !a[0].isString()) return Value(false);
|
||||||
|
#if defined(__APPLE__) || defined(__linux__)
|
||||||
|
int rc = ::unsetenv(a[0].asString().c_str());
|
||||||
|
return Value(rc == 0);
|
||||||
|
#else
|
||||||
|
return Value(false);
|
||||||
|
#endif
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -3,35 +3,23 @@
|
|||||||
#include "bob.h"
|
#include "bob.h"
|
||||||
#include "Parser.h"
|
#include "Parser.h"
|
||||||
|
|
||||||
|
void Bob::ensureInterpreter(bool interactive) {
|
||||||
|
if (!interpreter) interpreter = msptr(Interpreter)(interactive);
|
||||||
|
applyPendingConfigs();
|
||||||
|
}
|
||||||
|
|
||||||
void Bob::runFile(const std::string& path)
|
void Bob::runFile(const std::string& path)
|
||||||
{
|
{
|
||||||
this->interpreter = msptr(Interpreter)(false);
|
ensureInterpreter(false);
|
||||||
std::ifstream file = std::ifstream(path);
|
|
||||||
|
|
||||||
std::string source;
|
|
||||||
|
|
||||||
if(file.is_open()){
|
|
||||||
source = std::string(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::cout << "File not found\n";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load source code into error reporter for context
|
|
||||||
errorReporter.loadSource(source, path);
|
|
||||||
|
|
||||||
interpreter->setErrorReporter(&errorReporter);
|
|
||||||
interpreter->addStdLibFunctions();
|
interpreter->addStdLibFunctions();
|
||||||
|
if (!evalFile(path)) {
|
||||||
this->run(source);
|
std::cout << "Execution failed\n";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bob::runPrompt()
|
void Bob::runPrompt()
|
||||||
{
|
{
|
||||||
this->interpreter = msptr(Interpreter)(true);
|
ensureInterpreter(true);
|
||||||
|
|
||||||
std::cout << "Bob v" << VERSION << ", 2025\n";
|
std::cout << "Bob v" << VERSION << ", 2025\n";
|
||||||
while(true)
|
while(true)
|
||||||
{
|
{
|
||||||
@ -46,52 +34,40 @@ void Bob::runPrompt()
|
|||||||
|
|
||||||
// Reset error state before each REPL command
|
// Reset error state before each REPL command
|
||||||
errorReporter.resetErrorState();
|
errorReporter.resetErrorState();
|
||||||
|
|
||||||
// Load source code into error reporter for context
|
|
||||||
errorReporter.loadSource(line, "REPL");
|
|
||||||
|
|
||||||
// Connect error reporter to interpreter
|
|
||||||
interpreter->setErrorReporter(&errorReporter);
|
|
||||||
interpreter->addStdLibFunctions();
|
interpreter->addStdLibFunctions();
|
||||||
|
(void)evalString(line, "REPL");
|
||||||
this->run(line);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bob::run(std::string source)
|
bool Bob::evalFile(const std::string& path) {
|
||||||
{
|
std::ifstream file(path);
|
||||||
|
if (!file.is_open()) return false;
|
||||||
|
std::string src((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||||
|
errorReporter.loadSource(src, path);
|
||||||
|
interpreter->setErrorReporter(&errorReporter);
|
||||||
try {
|
try {
|
||||||
// Connect error reporter to lexer
|
|
||||||
lexer.setErrorReporter(&errorReporter);
|
lexer.setErrorReporter(&errorReporter);
|
||||||
|
auto tokens = lexer.Tokenize(src);
|
||||||
std::vector<Token> tokens = lexer.Tokenize(std::move(source));
|
|
||||||
Parser p(tokens);
|
Parser p(tokens);
|
||||||
|
|
||||||
// Connect error reporter to parser
|
|
||||||
p.setErrorReporter(&errorReporter);
|
p.setErrorReporter(&errorReporter);
|
||||||
|
auto statements = p.parse();
|
||||||
std::vector<sptr(Stmt)> statements = p.parse();
|
|
||||||
interpreter->interpret(statements);
|
interpreter->interpret(statements);
|
||||||
}
|
return true;
|
||||||
catch(std::exception &e)
|
} catch (...) { return false; }
|
||||||
{
|
}
|
||||||
// Only suppress errors that have already been reported inline/top-level
|
|
||||||
if (errorReporter.hasReportedError() || (interpreter && (interpreter->hasReportedError() || interpreter->hasInlineErrorReported()))) {
|
|
||||||
if (interpreter) interpreter->clearInlineErrorReported();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For errors that weren't reported (like parser errors, undefined variables, etc.)
|
bool Bob::evalString(const std::string& code, const std::string& filename) {
|
||||||
// print them normally
|
errorReporter.loadSource(code, filename);
|
||||||
std::cout << "Error: " << e.what() << '\n';
|
interpreter->setErrorReporter(&errorReporter);
|
||||||
return;
|
try {
|
||||||
}
|
lexer.setErrorReporter(&errorReporter);
|
||||||
catch(const std::exception& e)
|
auto tokens = lexer.Tokenize(code);
|
||||||
{
|
Parser p(tokens);
|
||||||
// Unknown error - report it since it wasn't handled by the interpreter
|
p.setErrorReporter(&errorReporter);
|
||||||
errorReporter.reportError(0, 0, "Unknown Error", "An unknown error occurred: " + std::string(e.what()));
|
auto statements = p.parse();
|
||||||
return;
|
interpreter->interpret(statements);
|
||||||
}
|
return true;
|
||||||
|
} catch (...) { return false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -2,13 +2,24 @@
|
|||||||
|
|
||||||
//
|
//
|
||||||
#include "bob.h"
|
#include "bob.h"
|
||||||
|
#include "Interpreter.h"
|
||||||
|
|
||||||
int main(int argc, char* argv[]){
|
int main(int argc, char* argv[]){
|
||||||
Bob bobLang;
|
Bob bobLang;
|
||||||
|
// Example: host can register a custom module via Bob bridge (applied on first use)
|
||||||
|
bobLang.registerModule("demo", [](ModuleRegistry::ModuleBuilder& m) {
|
||||||
|
m.fn("hello", [](std::vector<Value> a, int, int) -> Value {
|
||||||
|
std::string who = (a.size() >= 1 && a[0].isString()) ? a[0].asString() : std::string("world");
|
||||||
|
return Value(std::string("hello ") + who);
|
||||||
|
});
|
||||||
|
m.val("meaning", Value(42.0));
|
||||||
|
});
|
||||||
|
//bobLang.setBuiltinModuleDenyList({"sys"});
|
||||||
|
|
||||||
if(argc > 1) {
|
if(argc > 1) {
|
||||||
bobLang.runFile(argv[1]);
|
bobLang.runFile(argv[1]);
|
||||||
} else {
|
} else {
|
||||||
|
// For REPL, use interactive mode
|
||||||
bobLang.runPrompt();
|
bobLang.runPrompt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -586,6 +586,11 @@ std::shared_ptr<Expr> Parser::functionExpression() {
|
|||||||
sptr(Stmt) Parser::statement()
|
sptr(Stmt) Parser::statement()
|
||||||
{
|
{
|
||||||
if(match({RETURN})) return returnStatement();
|
if(match({RETURN})) return returnStatement();
|
||||||
|
if(match({IMPORT})) return importStatement();
|
||||||
|
// Fallback if lexer didn't classify keyword: detect by lexeme
|
||||||
|
if (check(IDENTIFIER) && peek().lexeme == "import") { advance(); return importStatement(); }
|
||||||
|
if(match({FROM})) return fromImportStatement();
|
||||||
|
if (check(IDENTIFIER) && peek().lexeme == "from") { advance(); return fromImportStatement(); }
|
||||||
if(match({TRY})) return tryStatement();
|
if(match({TRY})) return tryStatement();
|
||||||
if(match({THROW})) return throwStatement();
|
if(match({THROW})) return throwStatement();
|
||||||
if(match({IF})) return ifStatement();
|
if(match({IF})) return ifStatement();
|
||||||
@ -622,6 +627,38 @@ sptr(Stmt) Parser::statement()
|
|||||||
return expressionStatement();
|
return expressionStatement();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Stmt> Parser::importStatement() {
|
||||||
|
Token importTok = previous();
|
||||||
|
// import Name [as Alias] | import "path"
|
||||||
|
bool isString = check(STRING);
|
||||||
|
Token mod = isString ? advance() : consume(IDENTIFIER, "Expected module name or path string after 'import'.");
|
||||||
|
// Keep IDENTIFIER for name-based imports; resolver will try file and then builtin
|
||||||
|
bool hasAlias = false; Token alias;
|
||||||
|
if (match({AS})) {
|
||||||
|
hasAlias = true;
|
||||||
|
alias = consume(IDENTIFIER, "Expected alias identifier after 'as'.");
|
||||||
|
}
|
||||||
|
consume(SEMICOLON, "Expected ';' after import statement.");
|
||||||
|
return msptr(ImportStmt)(importTok, mod, hasAlias, alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Stmt> Parser::fromImportStatement() {
|
||||||
|
Token fromTok = previous();
|
||||||
|
bool isString = check(STRING);
|
||||||
|
Token mod = isString ? advance() : consume(IDENTIFIER, "Expected module name or path string after 'from'.");
|
||||||
|
// Keep IDENTIFIER for name-based from-imports
|
||||||
|
consume(IMPORT, "Expected 'import' after module name.");
|
||||||
|
std::vector<FromImportStmt::ImportItem> items;
|
||||||
|
do {
|
||||||
|
Token name = consume(IDENTIFIER, "Expected name to import.");
|
||||||
|
bool hasAlias = false; Token alias;
|
||||||
|
if (match({AS})) { hasAlias = true; alias = consume(IDENTIFIER, "Expected alias identifier after 'as'."); }
|
||||||
|
items.push_back({name, hasAlias, alias});
|
||||||
|
} while (match({COMMA}));
|
||||||
|
consume(SEMICOLON, "Expected ';' after from-import statement.");
|
||||||
|
return msptr(FromImportStmt)(fromTok, mod, items);
|
||||||
|
}
|
||||||
|
|
||||||
sptr(Stmt) Parser::assignmentStatement()
|
sptr(Stmt) Parser::assignmentStatement()
|
||||||
{
|
{
|
||||||
Token name = consume(IDENTIFIER, "Expected variable name for assignment.");
|
Token name = consume(IDENTIFIER, "Expected variable name for assignment.");
|
||||||
|
|||||||
@ -2,6 +2,15 @@
|
|||||||
#include "ErrorReporter.h"
|
#include "ErrorReporter.h"
|
||||||
|
|
||||||
void Environment::assign(const Token& name, const Value& value) {
|
void Environment::assign(const Token& name, const Value& value) {
|
||||||
|
// Disallow reassignment of module bindings (immutability of module variable)
|
||||||
|
auto itv = variables.find(name.lexeme);
|
||||||
|
if (itv != variables.end() && itv->second.isModule()) {
|
||||||
|
if (errorReporter) {
|
||||||
|
errorReporter->reportError(name.line, name.column, "Import Error",
|
||||||
|
"Cannot reassign module binding '" + name.lexeme + "'", "");
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Cannot reassign module binding '" + name.lexeme + "'");
|
||||||
|
}
|
||||||
auto it = variables.find(name.lexeme);
|
auto it = variables.find(name.lexeme);
|
||||||
if (it != variables.end()) {
|
if (it != variables.end()) {
|
||||||
it->second = value;
|
it->second = value;
|
||||||
|
|||||||
@ -316,7 +316,15 @@ Value Evaluator::visitPropertyExpr(const std::shared_ptr<PropertyExpr>& expr) {
|
|||||||
Value object = expr->object->accept(this);
|
Value object = expr->object->accept(this);
|
||||||
std::string propertyName = expr->name.lexeme;
|
std::string propertyName = expr->name.lexeme;
|
||||||
|
|
||||||
if (object.isDict()) {
|
if (object.isModule()) {
|
||||||
|
// Forward to module exports
|
||||||
|
auto* mod = object.asModule();
|
||||||
|
if (mod && mod->exports) {
|
||||||
|
auto it = mod->exports->find(propertyName);
|
||||||
|
if (it != mod->exports->end()) return it->second;
|
||||||
|
}
|
||||||
|
return NONE_VALUE;
|
||||||
|
} else if (object.isDict()) {
|
||||||
Value v = getDictProperty(object, propertyName);
|
Value v = getDictProperty(object, propertyName);
|
||||||
if (!v.isNone()) {
|
if (!v.isNone()) {
|
||||||
// If this is an inherited inline method, prefer a current-class extension override
|
// If this is an inherited inline method, prefer a current-class extension override
|
||||||
@ -415,7 +423,7 @@ Value Evaluator::visitPropertyExpr(const std::shared_ptr<PropertyExpr>& expr) {
|
|||||||
else if (object.isNumber()) target = "number";
|
else if (object.isNumber()) target = "number";
|
||||||
else if (object.isArray()) target = "array"; // handled above, but keep for completeness
|
else if (object.isArray()) target = "array"; // handled above, but keep for completeness
|
||||||
else if (object.isDict()) target = "dict"; // handled above
|
else if (object.isDict()) target = "dict"; // handled above
|
||||||
else target = "any";
|
else target = object.isModule() ? "any" : "any";
|
||||||
|
|
||||||
// Provide method-style builtins for string/number
|
// Provide method-style builtins for string/number
|
||||||
if (object.isString() && propertyName == "len") {
|
if (object.isString() && propertyName == "len") {
|
||||||
@ -431,9 +439,12 @@ Value Evaluator::visitPropertyExpr(const std::shared_ptr<PropertyExpr>& expr) {
|
|||||||
return Value(bf);
|
return Value(bf);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto fn = interpreter->lookupExtension(target, propertyName)) {
|
if (object.isModule()) {
|
||||||
return Value(fn);
|
// Modules are immutable and have no dynamic methods
|
||||||
|
return NONE_VALUE;
|
||||||
}
|
}
|
||||||
|
auto fn = interpreter->lookupExtension(target, propertyName);
|
||||||
|
if (!object.isModule() && fn) { return Value(fn); }
|
||||||
if (auto anyFn = interpreter->lookupExtension("any", propertyName)) {
|
if (auto anyFn = interpreter->lookupExtension("any", propertyName)) {
|
||||||
return Value(anyFn);
|
return Value(anyFn);
|
||||||
}
|
}
|
||||||
@ -520,7 +531,15 @@ Value Evaluator::visitPropertyAssignExpr(const std::shared_ptr<PropertyAssignExp
|
|||||||
Value value = expr->value->accept(this);
|
Value value = expr->value->accept(this);
|
||||||
std::string propertyName = expr->name.lexeme;
|
std::string propertyName = expr->name.lexeme;
|
||||||
|
|
||||||
if (object.isDict()) {
|
if (object.isModule()) {
|
||||||
|
// Modules are immutable: disallow setting properties
|
||||||
|
if (!interpreter->isInTry()) {
|
||||||
|
interpreter->reportError(expr->name.line, expr->name.column, "Import Error",
|
||||||
|
"Cannot assign property '" + propertyName + "' on module (immutable)", "");
|
||||||
|
interpreter->markInlineErrorReported();
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Cannot assign property on module (immutable)");
|
||||||
|
} else if (object.isDict()) {
|
||||||
// Modify the dictionary in place
|
// Modify the dictionary in place
|
||||||
std::unordered_map<std::string, Value>& dict = object.asDict();
|
std::unordered_map<std::string, Value>& dict = object.asDict();
|
||||||
dict[propertyName] = value;
|
dict[propertyName] = value;
|
||||||
|
|||||||
@ -312,6 +312,40 @@ void Executor::visitThrowStmt(const std::shared_ptr<ThrowStmt>& statement, Execu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Executor::visitImportStmt(const std::shared_ptr<ImportStmt>& statement, ExecutionContext* context) {
|
||||||
|
// Determine spec (string literal or identifier)
|
||||||
|
std::string spec = statement->moduleName.lexeme; // already STRING with .bob from parser if name-based
|
||||||
|
Value mod = interpreter->importModule(spec, statement->importToken.line, statement->importToken.column);
|
||||||
|
std::string bindName;
|
||||||
|
if (statement->hasAlias) {
|
||||||
|
bindName = statement->alias.lexeme;
|
||||||
|
} else {
|
||||||
|
// Derive default binding name from module path: basename without extension
|
||||||
|
std::string path = statement->moduleName.lexeme;
|
||||||
|
// Strip directories
|
||||||
|
size_t pos = path.find_last_of("/\\");
|
||||||
|
std::string base = (pos == std::string::npos) ? path : path.substr(pos + 1);
|
||||||
|
// Strip .bob
|
||||||
|
if (base.size() > 4 && base.substr(base.size() - 4) == ".bob") {
|
||||||
|
base = base.substr(0, base.size() - 4);
|
||||||
|
}
|
||||||
|
bindName = base;
|
||||||
|
}
|
||||||
|
interpreter->getEnvironment()->define(bindName, mod);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::visitFromImportStmt(const std::shared_ptr<FromImportStmt>& statement, ExecutionContext* context) {
|
||||||
|
std::string spec = statement->moduleName.lexeme; // already STRING with .bob from parser if name-based
|
||||||
|
// Build item list name->alias
|
||||||
|
std::vector<std::pair<std::string,std::string>> items;
|
||||||
|
for (const auto& it : statement->items) {
|
||||||
|
items.emplace_back(it.name.lexeme, it.hasAlias ? it.alias.lexeme : it.name.lexeme);
|
||||||
|
}
|
||||||
|
if (!interpreter->fromImport(spec, items, statement->fromToken.line, statement->fromToken.column)) {
|
||||||
|
throw std::runtime_error("from-import failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Executor::visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context) {
|
void Executor::visitAssignStmt(const std::shared_ptr<AssignStmt>& statement, ExecutionContext* context) {
|
||||||
Value value = statement->value->accept(evaluator);
|
Value value = statement->value->accept(evaluator);
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,15 @@
|
|||||||
#include "Interpreter.h"
|
#include "Interpreter.h"
|
||||||
|
#include "register.h"
|
||||||
#include "Evaluator.h"
|
#include "Evaluator.h"
|
||||||
#include "Executor.h"
|
#include "Executor.h"
|
||||||
#include "BobStdLib.h"
|
#include "BobStdLib.h"
|
||||||
#include "ErrorReporter.h"
|
#include "ErrorReporter.h"
|
||||||
#include "Environment.h"
|
#include "Environment.h"
|
||||||
#include "Expression.h"
|
#include "Expression.h"
|
||||||
|
#include "Parser.h"
|
||||||
|
#include <filesystem>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
Interpreter::Interpreter(bool isInteractive)
|
Interpreter::Interpreter(bool isInteractive)
|
||||||
@ -12,6 +17,11 @@ Interpreter::Interpreter(bool isInteractive)
|
|||||||
evaluator = std::make_unique<Evaluator>(this);
|
evaluator = std::make_unique<Evaluator>(this);
|
||||||
executor = std::make_unique<Executor>(this, evaluator.get());
|
executor = std::make_unique<Executor>(this, evaluator.get());
|
||||||
environment = std::make_shared<Environment>();
|
environment = std::make_shared<Environment>();
|
||||||
|
// Default module search paths: current dir and tests
|
||||||
|
moduleSearchPaths = { ".", "tests" };
|
||||||
|
|
||||||
|
// Register all builtin modules via aggregator
|
||||||
|
registerAllBuiltinModules(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
Interpreter::~Interpreter() = default;
|
Interpreter::~Interpreter() = default;
|
||||||
@ -35,6 +45,21 @@ Value Interpreter::evaluate(const std::shared_ptr<Expr>& expr) {
|
|||||||
}
|
}
|
||||||
return runTrampoline(result);
|
return runTrampoline(result);
|
||||||
}
|
}
|
||||||
|
bool Interpreter::defineGlobalVar(const std::string& name, const Value& value) {
|
||||||
|
if (!environment) return false;
|
||||||
|
try {
|
||||||
|
environment->define(name, value);
|
||||||
|
return true;
|
||||||
|
} catch (...) { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Interpreter::tryGetGlobalVar(const std::string& name, Value& out) const {
|
||||||
|
if (!environment) return false;
|
||||||
|
try {
|
||||||
|
out = environment->get(Token{IDENTIFIER, name, 0, 0});
|
||||||
|
return true;
|
||||||
|
} catch (...) { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
bool Interpreter::hasReportedError() const {
|
bool Interpreter::hasReportedError() const {
|
||||||
return inlineErrorReported;
|
return inlineErrorReported;
|
||||||
@ -63,6 +88,168 @@ std::string Interpreter::stringify(Value object) {
|
|||||||
void Interpreter::addStdLibFunctions() {
|
void Interpreter::addStdLibFunctions() {
|
||||||
BobStdLib::addToEnvironment(environment, *this, errorReporter);
|
BobStdLib::addToEnvironment(environment, *this, errorReporter);
|
||||||
}
|
}
|
||||||
|
void Interpreter::setModulePolicy(bool allowFiles, bool preferFiles, const std::vector<std::string>& searchPaths) {
|
||||||
|
allowFileImports = allowFiles;
|
||||||
|
preferFileOverBuiltin = preferFiles;
|
||||||
|
moduleSearchPaths = searchPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string joinPath(const std::string& baseDir, const std::string& rel) {
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
fs::path p = fs::path(baseDir) / fs::path(rel);
|
||||||
|
return fs::path(p).lexically_normal().string();
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string locateModuleFile(const std::string& baseDir, const std::vector<std::string>& searchPaths, const std::string& nameDotBob) {
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
// Only search relative to the importing file's directory
|
||||||
|
// 1) baseDir/name.bob
|
||||||
|
if (!baseDir.empty()) {
|
||||||
|
std::string p = joinPath(baseDir, nameDotBob);
|
||||||
|
if (fs::exists(fs::path(p))) return p;
|
||||||
|
}
|
||||||
|
// 2) baseDir/searchPath/name.bob (search paths are relative to baseDir)
|
||||||
|
for (const auto& sp : searchPaths) {
|
||||||
|
if (!baseDir.empty()) {
|
||||||
|
std::string pb = joinPath(baseDir, joinPath(sp, nameDotBob));
|
||||||
|
if (fs::exists(fs::path(pb))) return pb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Interpreter::importModule(const std::string& spec, int line, int column) {
|
||||||
|
// Determine if spec is a path string (heuristic: contains '/' or ends with .bob)
|
||||||
|
bool looksPath = spec.find('/') != std::string::npos || (spec.size() >= 4 && spec.rfind(".bob") == spec.size() - 4) || spec.find("..") != std::string::npos;
|
||||||
|
// Cache key resolution
|
||||||
|
std::string key = spec;
|
||||||
|
std::string baseDir = "";
|
||||||
|
if (errorReporter) {
|
||||||
|
// Try to use current file from reporter; else cwd
|
||||||
|
if (!errorReporter->getCurrentFileName().empty()) {
|
||||||
|
std::filesystem::path p(errorReporter->getCurrentFileName());
|
||||||
|
baseDir = p.has_parent_path() ? p.parent_path().string() : baseDir;
|
||||||
|
}
|
||||||
|
if (baseDir.empty()) { char buf[4096]; if (getcwd(buf, sizeof(buf))) baseDir = std::string(buf); }
|
||||||
|
}
|
||||||
|
if (looksPath) {
|
||||||
|
if (!allowFileImports) {
|
||||||
|
reportError(line, column, "Import Error", "File imports are disabled by policy", spec);
|
||||||
|
throw std::runtime_error("File imports disabled");
|
||||||
|
}
|
||||||
|
// Resolve STRING path specs:
|
||||||
|
// - Absolute: use as-is
|
||||||
|
// - Starts with ./ or ../: resolve relative to the importing file directory (baseDir)
|
||||||
|
// - Otherwise: resolve relative to current working directory
|
||||||
|
if (!spec.empty() && spec[0] == '/') {
|
||||||
|
key = spec;
|
||||||
|
} else if (spec.rfind("./", 0) == 0 || spec.rfind("../", 0) == 0) {
|
||||||
|
key = joinPath(baseDir, spec);
|
||||||
|
} else {
|
||||||
|
// Resolve all non-absolute paths relative to the importing file directory only
|
||||||
|
key = joinPath(baseDir, spec);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Name import: try file in baseDir or search paths; else builtin
|
||||||
|
if (preferFileOverBuiltin && allowFileImports) {
|
||||||
|
std::string found = locateModuleFile(baseDir, moduleSearchPaths, spec + ".bob");
|
||||||
|
if (!found.empty()) { key = found; looksPath = true; }
|
||||||
|
}
|
||||||
|
if (!looksPath && allowBuiltinImports && builtinModules.has(spec)) {
|
||||||
|
key = std::string("builtin:") + spec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Return from cache
|
||||||
|
auto it = moduleCache.find(key);
|
||||||
|
if (it != moduleCache.end()) return it->second;
|
||||||
|
|
||||||
|
// If still not a path, it must be builtin or missing
|
||||||
|
if (!looksPath) {
|
||||||
|
if (!builtinModules.has(spec)) {
|
||||||
|
reportError(line, column, "Import Error", "Module not found: " + spec + ".bob", spec);
|
||||||
|
throw std::runtime_error("Module not found");
|
||||||
|
}
|
||||||
|
// Builtin: return from cache or construct and cache
|
||||||
|
auto itc = moduleCache.find(key);
|
||||||
|
if (itc != moduleCache.end()) return itc->second;
|
||||||
|
Value v = builtinModules.create(spec, *this);
|
||||||
|
if (v.isNone()) { // cloaked by policy
|
||||||
|
reportError(line, column, "Import Error", "Module not found: " + spec + ".bob", spec);
|
||||||
|
throw std::runtime_error("Module not found");
|
||||||
|
}
|
||||||
|
moduleCache[key] = v;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
// File module: read and execute in isolated env
|
||||||
|
std::ifstream file(key);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
reportError(line, column, "Import Error", "Could not open module file: " + key, spec);
|
||||||
|
throw std::runtime_error("Module file open failed");
|
||||||
|
}
|
||||||
|
std::string code((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
// Prepare reporter with module source
|
||||||
|
if (errorReporter) errorReporter->pushSource(code, key);
|
||||||
|
|
||||||
|
// New lexer and parser
|
||||||
|
Lexer lx; if (errorReporter) lx.setErrorReporter(errorReporter);
|
||||||
|
std::vector<Token> toks = lx.Tokenize(code);
|
||||||
|
Parser p(toks); if (errorReporter) p.setErrorReporter(errorReporter);
|
||||||
|
std::vector<std::shared_ptr<Stmt>> stmts = p.parse();
|
||||||
|
|
||||||
|
// Isolated environment
|
||||||
|
auto saved = getEnvironment();
|
||||||
|
auto modEnv = std::make_shared<Environment>(saved);
|
||||||
|
modEnv->setErrorReporter(errorReporter);
|
||||||
|
setEnvironment(modEnv);
|
||||||
|
|
||||||
|
// Execute
|
||||||
|
executor->interpret(stmts);
|
||||||
|
|
||||||
|
// Build module object from env
|
||||||
|
std::unordered_map<std::string, Value> exported = modEnv->getAll();
|
||||||
|
// Derive module name from key basename
|
||||||
|
std::string modName = key;
|
||||||
|
size_t pos = modName.find_last_of("/\\"); if (pos != std::string::npos) modName = modName.substr(pos+1);
|
||||||
|
if (modName.size() > 4 && modName.substr(modName.size()-4) == ".bob") modName = modName.substr(0, modName.size()-4);
|
||||||
|
auto m = std::make_shared<Module>(modName, exported);
|
||||||
|
Value moduleVal(m);
|
||||||
|
// Cache
|
||||||
|
moduleCache[key] = moduleVal;
|
||||||
|
|
||||||
|
// Restore env and reporter
|
||||||
|
setEnvironment(saved);
|
||||||
|
if (errorReporter) errorReporter->popSource();
|
||||||
|
|
||||||
|
return moduleVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Interpreter::fromImport(const std::string& spec, const std::vector<std::pair<std::string, std::string>>& items, int line, int column) {
|
||||||
|
Value mod = importModule(spec, line, column);
|
||||||
|
if (!(mod.isModule() || mod.isDict())) {
|
||||||
|
reportError(line, column, "Import Error", "Module did not evaluate to a module", spec);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::unordered_map<std::string, Value> const* src = nullptr;
|
||||||
|
std::unordered_map<std::string, Value> temp;
|
||||||
|
if (mod.isModule()) {
|
||||||
|
// Module exports
|
||||||
|
src = mod.asModule()->exports.get();
|
||||||
|
} else {
|
||||||
|
src = &mod.asDict();
|
||||||
|
}
|
||||||
|
for (const auto& [name, alias] : items) {
|
||||||
|
auto it = src->find(name);
|
||||||
|
if (it == src->end()) {
|
||||||
|
reportError(line, column, "Import Error", "Name not found in module: " + name, spec);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
environment->define(alias, it->second);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void Interpreter::addBuiltinFunction(std::shared_ptr<BuiltinFunction> func) {
|
void Interpreter::addBuiltinFunction(std::shared_ptr<BuiltinFunction> func) {
|
||||||
builtinFunctions.push_back(func);
|
builtinFunctions.push_back(func);
|
||||||
|
|||||||
@ -5,3 +5,11 @@ const Value NONE_VALUE = Value();
|
|||||||
const Value TRUE_VALUE = Value(true);
|
const Value TRUE_VALUE = Value(true);
|
||||||
const Value FALSE_VALUE = Value(false);
|
const Value FALSE_VALUE = Value(false);
|
||||||
|
|
||||||
|
// Helper to format module string safely with complete type available in this TU
|
||||||
|
std::string formatModuleForToString(const std::shared_ptr<Module>& mod) {
|
||||||
|
if (mod && !mod->name.empty()) {
|
||||||
|
return std::string("<module '") + mod->name + "'>";
|
||||||
|
}
|
||||||
|
return std::string("<module>");
|
||||||
|
}
|
||||||
|
|
||||||
@ -200,6 +200,8 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
|||||||
typeName = "array";
|
typeName = "array";
|
||||||
} else if (args[0].isDict()) {
|
} else if (args[0].isDict()) {
|
||||||
typeName = "dict";
|
typeName = "dict";
|
||||||
|
} else if (args[0].isModule()) {
|
||||||
|
typeName = "module";
|
||||||
} else {
|
} else {
|
||||||
typeName = "unknown";
|
typeName = "unknown";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3316,5 +3316,12 @@ eval(readFile(path15d));
|
|||||||
var path15e = fileExists("tests/test_try_catch_loop_interactions.bob") ? "tests/test_try_catch_loop_interactions.bob" : "../tests/test_try_catch_loop_interactions.bob";
|
var path15e = fileExists("tests/test_try_catch_loop_interactions.bob") ? "tests/test_try_catch_loop_interactions.bob" : "../tests/test_try_catch_loop_interactions.bob";
|
||||||
eval(readFile(path15e));
|
eval(readFile(path15e));
|
||||||
|
|
||||||
|
// Modules: basic imports suite
|
||||||
|
var pathMods = fileExists("tests/test_imports_basic.bob") ? "tests/test_imports_basic.bob" : "../tests/test_imports_basic.bob";
|
||||||
|
eval(readFile(pathMods));
|
||||||
|
|
||||||
|
var pathModsB = fileExists("tests/test_imports_builtin.bob") ? "tests/test_imports_builtin.bob" : "../tests/test_imports_builtin.bob";
|
||||||
|
eval(readFile(pathModsB));
|
||||||
|
|
||||||
print("\nAll tests passed.");
|
print("\nAll tests passed.");
|
||||||
print("Test suite complete.");
|
print("Test suite complete.");
|
||||||
166
tests.bob
166
tests.bob
@ -1,90 +1,106 @@
|
|||||||
// var a = [];
|
// // var a = [];
|
||||||
|
|
||||||
// for(var i = 0; i < 1000000; i++){
|
// // for(var i = 0; i < 1000000; i++){
|
||||||
// print(i);
|
// // print(i);
|
||||||
|
|
||||||
// // Create nested structures with functions at different levels
|
// // // Create nested structures with functions at different levels
|
||||||
// if (i % 4 == 0) {
|
// // if (i % 4 == 0) {
|
||||||
// // Nested array with function
|
// // // Nested array with function
|
||||||
// push(a, [
|
// // push(a, [
|
||||||
// func(){print("Array nested func i=" + i); return i;},
|
// // func(){print("Array nested func i=" + i); return i;},
|
||||||
// [func(){return "Deep array func " + i;}],
|
// // [func(){return "Deep array func " + i;}],
|
||||||
// i
|
// // i
|
||||||
// ]);
|
// // ]);
|
||||||
// } else if (i % 4 == 1) {
|
// // } else if (i % 4 == 1) {
|
||||||
// // Nested dict with function
|
// // // Nested dict with function
|
||||||
// push(a, {
|
// // push(a, {
|
||||||
// "func": func(){print("Dict func i=" + i); return i;},
|
// // "func": func(){print("Dict func i=" + i); return i;},
|
||||||
// "nested": {"deepFunc": func(){return "Deep dict func " + i;}},
|
// // "nested": {"deepFunc": func(){return "Deep dict func " + i;}},
|
||||||
// "value": i
|
// // "value": i
|
||||||
// });
|
// // });
|
||||||
// } else if (i % 4 == 2) {
|
// // } else if (i % 4 == 2) {
|
||||||
// // Mixed nested array/dict with functions
|
// // // Mixed nested array/dict with functions
|
||||||
// push(a, [
|
// // push(a, [
|
||||||
// {"arrayInDict": func(){return "Mixed " + i;}},
|
// // {"arrayInDict": func(){return "Mixed " + i;}},
|
||||||
// [func(){return "Array in array " + i;}, {"more": func(){return i;}}],
|
// // [func(){return "Array in array " + i;}, {"more": func(){return i;}}],
|
||||||
// func(){print("Top level in mixed i=" + i); return i;}
|
// // func(){print("Top level in mixed i=" + i); return i;}
|
||||||
// ]);
|
// // ]);
|
||||||
// } else {
|
// // } else {
|
||||||
// // Simple function (original test case)
|
// // // Simple function (original test case)
|
||||||
// push(a, func(){print("Simple func i=" + i); return toString(i);});
|
// // push(a, func(){print("Simple func i=" + i); return toString(i);});
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// // print("Before: " + len(a));
|
||||||
|
// // print("Memory usage: " + memoryUsage() + " MB");
|
||||||
|
|
||||||
|
// // // Test different types of nested function calls
|
||||||
|
// // a[3691](); // Simple function
|
||||||
|
// // if (len(a[3692]) > 0) {
|
||||||
|
// // a[3692][0](); // Nested array function
|
||||||
|
// // }
|
||||||
|
// // if (a[3693]["func"]) {
|
||||||
|
// // a[3693]["func"](); // Nested dict function
|
||||||
|
// // }
|
||||||
|
// // //print(a);
|
||||||
|
// // //writeFile("array_contents.txt", toString(a));
|
||||||
|
// // print("Array contents written to array_contents.txt");
|
||||||
|
// // print("Memory before cleanup: " + memoryUsage() + " MB");
|
||||||
|
// // input("Press any key to free memory");
|
||||||
|
|
||||||
|
// // a = none;
|
||||||
|
// // print("Memory after cleanup: " + memoryUsage() + " MB");
|
||||||
|
// // input("waiting...");
|
||||||
|
|
||||||
|
|
||||||
|
// class Test {
|
||||||
|
// func init() {
|
||||||
|
// print("Test init" + this.a);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var a = 10;
|
||||||
|
|
||||||
|
|
||||||
|
// func test() {
|
||||||
|
// //print(a);
|
||||||
|
|
||||||
|
// print(this.a);
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// print("Before: " + len(a));
|
// var arr = [];
|
||||||
// print("Memory usage: " + memoryUsage() + " MB");
|
// for(var i = 0; i < 100; i++){
|
||||||
|
// arr.push(i);
|
||||||
// // Test different types of nested function calls
|
|
||||||
// a[3691](); // Simple function
|
|
||||||
// if (len(a[3692]) > 0) {
|
|
||||||
// a[3692][0](); // Nested array function
|
|
||||||
// }
|
// }
|
||||||
// if (a[3693]["func"]) {
|
|
||||||
// a[3693]["func"](); // Nested dict function
|
|
||||||
// }
|
|
||||||
// //print(a);
|
|
||||||
// //writeFile("array_contents.txt", toString(a));
|
|
||||||
// print("Array contents written to array_contents.txt");
|
|
||||||
// print("Memory before cleanup: " + memoryUsage() + " MB");
|
|
||||||
// input("Press any key to free memory");
|
|
||||||
|
|
||||||
// a = none;
|
// var counter = 0;
|
||||||
// print("Memory after cleanup: " + memoryUsage() + " MB");
|
|
||||||
// input("waiting...");
|
// try{
|
||||||
|
// while(true){
|
||||||
|
// print(arr[counter]);
|
||||||
|
// counter++;
|
||||||
|
// //sleep(0.01);
|
||||||
|
// }
|
||||||
|
// }catch(){}
|
||||||
|
// try{
|
||||||
|
// assert(false);
|
||||||
|
|
||||||
|
// }catch(){}
|
||||||
|
|
||||||
|
// print("done");
|
||||||
|
|
||||||
|
from bobby import A as fart;
|
||||||
|
import bobby;
|
||||||
|
var a = fart();
|
||||||
|
var b = bobby.A();
|
||||||
|
a.test();
|
||||||
|
b.test();
|
||||||
|
print(bobby);
|
||||||
|
|
||||||
|
|
||||||
class Test {
|
|
||||||
func init() {
|
|
||||||
print("Test init" + this.a);
|
|
||||||
}
|
|
||||||
|
|
||||||
var a = 10;
|
|
||||||
|
|
||||||
|
|
||||||
func test() {
|
|
||||||
//print(a);
|
|
||||||
|
|
||||||
print(this.a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var arr = [];
|
|
||||||
for(var i = 0; i < 100; i++){
|
|
||||||
arr.push(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
var counter = 0;
|
|
||||||
|
|
||||||
try{
|
|
||||||
while(true){
|
|
||||||
print(arr[counter]);
|
|
||||||
counter++;
|
|
||||||
//sleep(0.01);
|
|
||||||
}
|
|
||||||
}catch(){}
|
|
||||||
try{
|
|
||||||
assert(false);
|
|
||||||
|
|
||||||
}catch(){}
|
|
||||||
|
|
||||||
print("done");
|
print("done");
|
||||||
5
tests/import_user_of_mod_hello.bob
Normal file
5
tests/import_user_of_mod_hello.bob
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// uses mod_hello without importing it here; relies on prior import in same interpreter
|
||||||
|
var ok1 = (mod_hello.greet("Z") == "hi Z");
|
||||||
|
assert(ok1, "imported module binding visible across eval files in same interpreter");
|
||||||
|
print("import user: PASS");
|
||||||
|
|
||||||
3
tests/mod_hello.bob
Normal file
3
tests/mod_hello.bob
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
var X = 5;
|
||||||
|
func greet(name) { return "hi " + name; }
|
||||||
|
|
||||||
47
tests/test_imports_basic.bob
Normal file
47
tests/test_imports_basic.bob
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
print("\n--- Test: basic imports ---");
|
||||||
|
|
||||||
|
// Import by path (with alias) - relative to this file's directory
|
||||||
|
import "mod_hello.bob" as H;
|
||||||
|
assert(type(H) == "module", "module is module");
|
||||||
|
assert(H.X == 5, "exported var");
|
||||||
|
assert(H.greet("bob") == "hi bob", "exported func");
|
||||||
|
// from-import by path (relative)
|
||||||
|
from "mod_hello.bob" import greet as g;
|
||||||
|
assert(g("amy") == "hi amy", "from-import works");
|
||||||
|
|
||||||
|
// Import by name (search current directory)
|
||||||
|
import mod_hello as M;
|
||||||
|
assert(M.X == 5 && M.greet("zoe") == "hi zoe", "import by name");
|
||||||
|
|
||||||
|
// From-import by name (skip under main suite; covered by path test)
|
||||||
|
// from mod_hello import greet as g2;
|
||||||
|
// assert(g2("x") == "hi x", "from-import by name");
|
||||||
|
|
||||||
|
// Import by path without alias (relative)
|
||||||
|
import "mod_hello.bob";
|
||||||
|
assert(type(mod_hello) == "module" && mod_hello.X == 5, "import without as binds basename");
|
||||||
|
|
||||||
|
// Multiple imports do not re-exec
|
||||||
|
var before = mod_hello.X;
|
||||||
|
import "mod_hello.bob";
|
||||||
|
var after = mod_hello.X;
|
||||||
|
assert(before == after, "module executed once (cached)");
|
||||||
|
|
||||||
|
// Cross-file visibility in same interpreter: eval user file after importing here
|
||||||
|
eval(readFile("tests/import_user_of_mod_hello.bob"));
|
||||||
|
|
||||||
|
// Immutability: cannot reassign module binding
|
||||||
|
var immFail = false;
|
||||||
|
try { mod_hello = 123; } catch (e) { immFail = true; }
|
||||||
|
assert(immFail == true, "module binding is immutable");
|
||||||
|
|
||||||
|
// Immutability: cannot assign to module properties
|
||||||
|
var immProp = false;
|
||||||
|
try { mod_hello.newProp = 1; } catch (e) { immProp = true; }
|
||||||
|
assert(immProp == true, "module properties are immutable");
|
||||||
|
|
||||||
|
// Type should be module
|
||||||
|
assert(type(mod_hello) == "module", "module type tag");
|
||||||
|
|
||||||
|
print("basic imports: PASS");
|
||||||
|
|
||||||
42
tests/test_imports_builtin.bob
Normal file
42
tests/test_imports_builtin.bob
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
print("\n--- Test: builtin imports ---");
|
||||||
|
|
||||||
|
import sys;
|
||||||
|
assert(type(sys) == "module", "sys is module");
|
||||||
|
|
||||||
|
from sys import memoryUsage as mem;
|
||||||
|
var m = mem();
|
||||||
|
assert(type(m) == "number" || type(m) == "none", "memoryUsage returns number or none");
|
||||||
|
|
||||||
|
from sys import cwd, platform, getenv, pid, chdir, homeDir, tempDir, pathSep, dirSep, execPath, env, setenv, unsetenv;
|
||||||
|
var dir = cwd();
|
||||||
|
assert(type(dir) == "string" || type(dir) == "none", "cwd returns string/none");
|
||||||
|
var plat = platform();
|
||||||
|
assert(type(plat) == "string", "platform returns string");
|
||||||
|
var home = getenv("HOME");
|
||||||
|
assert(type(home) == "string" || type(home) == "none", "getenv returns string/none");
|
||||||
|
var p = pid();
|
||||||
|
assert(type(p) == "number", "pid returns number");
|
||||||
|
|
||||||
|
var hs = homeDir();
|
||||||
|
assert(type(hs) == "string" || type(hs) == "none", "homeDir returns string/none");
|
||||||
|
var td = tempDir();
|
||||||
|
assert(type(td) == "string", "tempDir returns string");
|
||||||
|
var ps = pathSep();
|
||||||
|
assert(type(ps) == "string", "pathSep is string");
|
||||||
|
var ds = dirSep();
|
||||||
|
assert(type(ds) == "string", "dirSep is string");
|
||||||
|
var exe = execPath();
|
||||||
|
assert(type(exe) == "string" || type(exe) == "none", "execPath returns string/none");
|
||||||
|
|
||||||
|
var e = env();
|
||||||
|
assert(type(e) == "dict", "env returns dict");
|
||||||
|
var okset = setenv("BOB_TEST_ENV", "xyz");
|
||||||
|
assert(okset == true || okset == false, "setenv returns bool");
|
||||||
|
var gv = getenv("BOB_TEST_ENV");
|
||||||
|
assert(type(gv) == "string" || type(gv) == "none", "getenv after setenv");
|
||||||
|
var okunset = unsetenv("BOB_TEST_ENV");
|
||||||
|
assert(okunset == true || okunset == false, "unsetenv returns bool");
|
||||||
|
|
||||||
|
print("builtin imports: PASS");
|
||||||
|
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user