Started fleshing out built in modules.
added policy templates for module safety
This commit is contained in:
parent
8cdccae214
commit
7f7c6e438d
@ -45,11 +45,11 @@ elseif(MSVC)
|
||||
endif()
|
||||
|
||||
# Collect source files
|
||||
file(GLOB_RECURSE BOB_RUNTIME_SOURCES "src/sources/runtime/*.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_BUILTIN_SOURCES "src/sources/builtinModules/*.cpp")
|
||||
file(GLOB_RECURSE BOB_CLI_SOURCES "src/sources/cli/*.cpp")
|
||||
file(GLOB_RECURSE BOB_RUNTIME_SOURCES CONFIGURE_DEPENDS "src/sources/runtime/*.cpp")
|
||||
file(GLOB_RECURSE BOB_PARSING_SOURCES CONFIGURE_DEPENDS "src/sources/parsing/*.cpp")
|
||||
file(GLOB_RECURSE BOB_STDLIB_SOURCES CONFIGURE_DEPENDS "src/sources/stdlib/*.cpp")
|
||||
file(GLOB_RECURSE BOB_BUILTIN_SOURCES CONFIGURE_DEPENDS "src/sources/builtinModules/*.cpp")
|
||||
file(GLOB_RECURSE BOB_CLI_SOURCES CONFIGURE_DEPENDS "src/sources/cli/*.cpp")
|
||||
|
||||
# All source files
|
||||
set(BOB_ALL_SOURCES
|
||||
|
||||
8
src/headers/builtinModules/base64_module.h
Normal file
8
src/headers/builtinModules/base64_module.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
class Interpreter;
|
||||
|
||||
// Register the builtin 'base64' module
|
||||
void registerBase64Module(Interpreter& interpreter);
|
||||
|
||||
|
||||
8
src/headers/builtinModules/eval.h
Normal file
8
src/headers/builtinModules/eval.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
class Interpreter;
|
||||
|
||||
// Register the builtin 'eval' module providing eval(code) and evalFile(path)
|
||||
void registerEvalModule(Interpreter& interpreter);
|
||||
|
||||
|
||||
8
src/headers/builtinModules/io.h
Normal file
8
src/headers/builtinModules/io.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
class Interpreter;
|
||||
|
||||
// Register the builtin 'io' module (file I/O and input)
|
||||
void registerIoModule(Interpreter& interpreter);
|
||||
|
||||
|
||||
8
src/headers/builtinModules/json.h
Normal file
8
src/headers/builtinModules/json.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
class Interpreter;
|
||||
|
||||
// Register the builtin 'json' module
|
||||
void registerJsonModule(Interpreter& interpreter);
|
||||
|
||||
|
||||
8
src/headers/builtinModules/math_module.h
Normal file
8
src/headers/builtinModules/math_module.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
class Interpreter;
|
||||
|
||||
// Register the builtin 'math' module
|
||||
void registerMathModule(Interpreter& interpreter);
|
||||
|
||||
|
||||
8
src/headers/builtinModules/os.h
Normal file
8
src/headers/builtinModules/os.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
class Interpreter;
|
||||
|
||||
// Register the builtin 'os' module
|
||||
void registerOsModule(Interpreter& interpreter);
|
||||
|
||||
|
||||
8
src/headers/builtinModules/path_module.h
Normal file
8
src/headers/builtinModules/path_module.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
class Interpreter;
|
||||
|
||||
// Register the builtin 'path' module (path utilities)
|
||||
void registerPathModule(Interpreter& interpreter);
|
||||
|
||||
|
||||
8
src/headers/builtinModules/rand.h
Normal file
8
src/headers/builtinModules/rand.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
class Interpreter;
|
||||
|
||||
// Register the builtin 'rand' module
|
||||
void registerRandModule(Interpreter& interpreter);
|
||||
|
||||
|
||||
8
src/headers/builtinModules/time_module.h
Normal file
8
src/headers/builtinModules/time_module.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
class Interpreter;
|
||||
|
||||
// Register the builtin 'time' module (time functions)
|
||||
void registerTimeModule(Interpreter& interpreter);
|
||||
|
||||
|
||||
@ -49,6 +49,70 @@ public:
|
||||
bool evalFile(const std::string& path);
|
||||
bool evalString(const std::string& code, const std::string& filename = "<eval>");
|
||||
|
||||
// Safety policy helpers (public API)
|
||||
// Set all safety-related policies at once
|
||||
void setSafetyPolicy(
|
||||
bool allowBuiltins,
|
||||
const std::vector<std::string>& allowList,
|
||||
const std::vector<std::string>& denyList,
|
||||
bool allowFileImports,
|
||||
bool preferFileOverBuiltin,
|
||||
const std::vector<std::string>& searchPaths
|
||||
) {
|
||||
if (interpreter) {
|
||||
interpreter->setBuiltinModulePolicy(allowBuiltins);
|
||||
interpreter->setBuiltinModuleAllowList(allowList);
|
||||
interpreter->setBuiltinModuleDenyList(denyList);
|
||||
interpreter->setModulePolicy(allowFileImports, preferFileOverBuiltin, searchPaths);
|
||||
} else {
|
||||
pendingConfigurators.push_back([=](Interpreter& I){
|
||||
I.setBuiltinModulePolicy(allowBuiltins);
|
||||
I.setBuiltinModuleAllowList(allowList);
|
||||
I.setBuiltinModuleDenyList(denyList);
|
||||
I.setModulePolicy(allowFileImports, preferFileOverBuiltin, searchPaths);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Simple presets: "open", "safe", "locked"
|
||||
void setSafetyPreset(const std::string& preset) {
|
||||
if (preset == "open") {
|
||||
setSafetyPolicy(
|
||||
true, /* allowBuiltins */
|
||||
{}, /* allowList -> empty means allow all */
|
||||
{},
|
||||
true, /* allowFileImports */
|
||||
true, /* preferFileOverBuiltin */
|
||||
{} /* searchPaths */
|
||||
);
|
||||
} else if (preset == "safe") {
|
||||
// Allow only pure/harmless modules by default
|
||||
setSafetyPolicy(
|
||||
true,
|
||||
std::vector<std::string>{
|
||||
"sys", "time", "rand", "math", "path", "base64"
|
||||
},
|
||||
std::vector<std::string>{ /* denyList empty when allowList is used */ },
|
||||
false, /* disallow file-based imports */
|
||||
true,
|
||||
{}
|
||||
);
|
||||
} else if (preset == "locked") {
|
||||
// No builtins visible; no file imports
|
||||
setSafetyPolicy(
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
false,
|
||||
true,
|
||||
{}
|
||||
);
|
||||
} else {
|
||||
// Default to safe
|
||||
setSafetyPreset("safe");
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void ensureInterpreter(bool interactive);
|
||||
void applyPendingConfigs() {
|
||||
|
||||
@ -291,11 +291,14 @@ struct ImportStmt : Stmt {
|
||||
// from module import name [as alias], name2 ...
|
||||
struct FromImportStmt : Stmt {
|
||||
Token fromToken; // FROM
|
||||
Token moduleName; // IDENTIFIER
|
||||
Token moduleName; // IDENTIFIER or STRING
|
||||
struct ImportItem { Token name; bool hasAlias; Token alias; };
|
||||
std::vector<ImportItem> items;
|
||||
bool importAll = false; // true for: from module import *;
|
||||
FromImportStmt(Token kw, Token mod, std::vector<ImportItem> it)
|
||||
: fromToken(kw), moduleName(mod), items(std::move(it)) {}
|
||||
: fromToken(kw), moduleName(mod), items(std::move(it)), importAll(false) {}
|
||||
FromImportStmt(Token kw, Token mod, bool all)
|
||||
: fromToken(kw), moduleName(mod), importAll(all) {}
|
||||
void accept(StmtVisitor* visitor, ExecutionContext* context = nullptr) override {
|
||||
visitor->visitFromImportStmt(std::static_pointer_cast<FromImportStmt>(shared_from_this()), context);
|
||||
}
|
||||
|
||||
@ -173,8 +173,18 @@ public:
|
||||
int getLastErrorLine() const { return lastErrorLine; }
|
||||
int getLastErrorColumn() const { return lastErrorColumn; }
|
||||
|
||||
// Process/host metadata (for sys module)
|
||||
void setArgv(const std::vector<std::string>& args, const std::string& executablePath) { argvData = args; executableFile = executablePath; }
|
||||
std::vector<std::string> getArgv() const { return argvData; }
|
||||
std::string getExecutablePath() const { return executableFile; }
|
||||
std::unordered_map<std::string, Value> getModuleCacheSnapshot() const { return moduleCache; }
|
||||
|
||||
|
||||
|
||||
|
||||
private:
|
||||
Value runTrampoline(Value initialResult);
|
||||
// Stored argv/executable for sys module
|
||||
std::vector<std::string> argvData;
|
||||
std::string executableFile;
|
||||
};
|
||||
|
||||
41
src/sources/builtinModules/base64.cpp
Normal file
41
src/sources/builtinModules/base64.cpp
Normal file
@ -0,0 +1,41 @@
|
||||
#include "base64_module.h"
|
||||
#include "Interpreter.h"
|
||||
#include <string>
|
||||
|
||||
static const char* B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
static std::string b64encode(const std::string& in){
|
||||
std::string out; out.reserve(((in.size()+2)/3)*4);
|
||||
int val=0, valb=-6;
|
||||
for (unsigned char c : in){
|
||||
val = (val<<8) + c;
|
||||
valb += 8;
|
||||
while (valb >= 0){ out.push_back(B64[(val>>valb)&0x3F]); valb -= 6; }
|
||||
}
|
||||
if (valb>-6) out.push_back(B64[((val<<8)>>(valb+8))&0x3F]);
|
||||
while (out.size()%4) out.push_back('=');
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::string b64decode(const std::string& in){
|
||||
std::vector<int> T(256,-1); for (int i=0;i<64;i++) T[(unsigned char)B64[i]]=i;
|
||||
std::string out; out.reserve((in.size()*3)/4);
|
||||
int val=0, valb=-8;
|
||||
for (unsigned char c : in){ if (T[c]==-1) break; val=(val<<6)+T[c]; valb+=6; if (valb>=0){ out.push_back(char((val>>valb)&0xFF)); valb-=8; } }
|
||||
return out;
|
||||
}
|
||||
|
||||
void registerBase64Module(Interpreter& interpreter) {
|
||||
interpreter.registerModule("base64", [](Interpreter::ModuleBuilder& m) {
|
||||
m.fn("encode", [](std::vector<Value> a, int, int) -> Value {
|
||||
if (a.size()!=1 || !a[0].isString()) return NONE_VALUE;
|
||||
return Value(b64encode(a[0].asString()));
|
||||
});
|
||||
m.fn("decode", [](std::vector<Value> a, int, int) -> Value {
|
||||
if (a.size()!=1 || !a[0].isString()) return NONE_VALUE;
|
||||
return Value(b64decode(a[0].asString()));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
64
src/sources/builtinModules/eval.cpp
Normal file
64
src/sources/builtinModules/eval.cpp
Normal file
@ -0,0 +1,64 @@
|
||||
#include "eval.h"
|
||||
#include "Interpreter.h"
|
||||
#include "ErrorReporter.h"
|
||||
#include "Lexer.h"
|
||||
#include "Parser.h"
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
void registerEvalModule(Interpreter& interpreter) {
|
||||
interpreter.registerModule("eval", [](Interpreter::ModuleBuilder& m) {
|
||||
ErrorReporter* er = m.interpreterRef.getErrorReporter();
|
||||
m.fn("eval", [er, &I = m.interpreterRef](std::vector<Value> args, int line, int column) -> Value {
|
||||
if (args.size() != 1 || !args[0].isString()) {
|
||||
if (er) er->reportError(line, column, "Invalid Arguments", "eval expects exactly 1 string argument", "eval");
|
||||
throw std::runtime_error("eval expects exactly 1 string argument");
|
||||
}
|
||||
std::string code = args[0].asString();
|
||||
std::string evalName = "<eval>";
|
||||
try {
|
||||
if (er) er->pushSource(code, evalName);
|
||||
Lexer lx; if (er) lx.setErrorReporter(er);
|
||||
auto toks = lx.Tokenize(code);
|
||||
Parser p(toks); if (er) p.setErrorReporter(er);
|
||||
auto stmts = p.parse();
|
||||
I.interpret(stmts);
|
||||
return NONE_VALUE;
|
||||
} catch (...) {
|
||||
if (er) er->popSource();
|
||||
throw;
|
||||
}
|
||||
if (er) er->popSource();
|
||||
});
|
||||
|
||||
m.fn("evalFile", [er, &I = m.interpreterRef](std::vector<Value> args, int line, int column) -> Value {
|
||||
if (args.size() != 1 || !args[0].isString()) {
|
||||
if (er) er->reportError(line, column, "Invalid Arguments", "evalFile expects exactly 1 string argument (path)", "evalFile");
|
||||
throw std::runtime_error("evalFile expects exactly 1 string argument (path)");
|
||||
}
|
||||
std::string filename = args[0].asString();
|
||||
std::ifstream f(filename);
|
||||
if (!f.is_open()) {
|
||||
if (er) er->reportError(line, column, "StdLib Error", "Could not open file: " + filename, "");
|
||||
throw std::runtime_error("Could not open file: " + filename);
|
||||
}
|
||||
std::stringstream buf; buf << f.rdbuf(); f.close();
|
||||
std::string code = buf.str();
|
||||
try {
|
||||
if (er) er->pushSource(code, filename);
|
||||
Lexer lx; if (er) lx.setErrorReporter(er);
|
||||
auto toks = lx.Tokenize(code);
|
||||
Parser p(toks); if (er) p.setErrorReporter(er);
|
||||
auto stmts = p.parse();
|
||||
I.interpret(stmts);
|
||||
return NONE_VALUE;
|
||||
} catch (...) {
|
||||
if (er) er->popSource();
|
||||
throw;
|
||||
}
|
||||
if (er) er->popSource();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
72
src/sources/builtinModules/io.cpp
Normal file
72
src/sources/builtinModules/io.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include "io.h"
|
||||
#include "Interpreter.h"
|
||||
#include "ErrorReporter.h"
|
||||
|
||||
void registerIoModule(Interpreter& interpreter) {
|
||||
interpreter.registerModule("io", [](Interpreter::ModuleBuilder& m) {
|
||||
ErrorReporter* er = m.interpreterRef.getErrorReporter();
|
||||
|
||||
m.fn("readFile", [er](std::vector<Value> a, int line, int col) -> Value {
|
||||
if (a.empty() || !a[0].isString() || a.size() > 2 || (a.size() == 2 && !a[1].isString())) {
|
||||
if (er) er->reportError(line, col, "Invalid Arguments", "readFile(path[, mode]) expects 1-2 args (strings)", "readFile");
|
||||
throw std::runtime_error("readFile(path[, mode]) expects 1-2 string args");
|
||||
}
|
||||
std::string mode = (a.size() == 2) ? a[1].asString() : std::string("r");
|
||||
std::ios_base::openmode om = std::ios::in;
|
||||
if (mode.find('b') != std::string::npos) om |= std::ios::binary;
|
||||
std::ifstream f(a[0].asString(), om);
|
||||
if (!f.is_open()) {
|
||||
if (er) er->reportError(line, col, "StdLib Error", "Could not open file", a[0].asString());
|
||||
throw std::runtime_error("Could not open file");
|
||||
}
|
||||
std::stringstream buf; buf << f.rdbuf(); f.close();
|
||||
return Value(buf.str());
|
||||
});
|
||||
|
||||
m.fn("writeFile", [er](std::vector<Value> a, int line, int col) -> Value {
|
||||
if (a.size() < 2 || a.size() > 3 || !a[0].isString() || !a[1].isString() || (a.size() == 3 && !a[2].isString())) {
|
||||
if (er) er->reportError(line, col, "Invalid Arguments", "writeFile(path, data[, mode]) expects 2-3 args (strings)", "writeFile");
|
||||
throw std::runtime_error("writeFile(path, data[, mode]) expects 2-3 string args");
|
||||
}
|
||||
std::string mode = (a.size() == 3) ? a[2].asString() : std::string("w");
|
||||
std::ios_base::openmode om = std::ios::out;
|
||||
if (mode.find('b') != std::string::npos) om |= std::ios::binary;
|
||||
if (mode.find('a') != std::string::npos) om |= std::ios::app; else om |= std::ios::trunc;
|
||||
std::ofstream f(a[0].asString(), om);
|
||||
if (!f.is_open()) {
|
||||
if (er) er->reportError(line, col, "StdLib Error", "Could not create file", a[0].asString());
|
||||
throw std::runtime_error("Could not create file");
|
||||
}
|
||||
f << a[1].asString(); f.close();
|
||||
return NONE_VALUE;
|
||||
});
|
||||
|
||||
m.fn("readLines", [er](std::vector<Value> a, int line, int col) -> Value {
|
||||
if (a.size() != 1 || !a[0].isString()) {
|
||||
if (er) er->reportError(line, col, "Invalid Arguments", "readLines(path) expects 1 string arg", "readLines");
|
||||
throw std::runtime_error("readLines(path) expects 1 string arg");
|
||||
}
|
||||
std::ifstream f(a[0].asString());
|
||||
if (!f.is_open()) {
|
||||
if (er) er->reportError(line, col, "StdLib Error", "Could not open file", a[0].asString());
|
||||
throw std::runtime_error("Could not open file");
|
||||
}
|
||||
std::vector<Value> lines; std::string s;
|
||||
while (std::getline(f, s)) lines.emplace_back(s);
|
||||
f.close();
|
||||
return Value(lines);
|
||||
});
|
||||
|
||||
m.fn("exists", [](std::vector<Value> a, int, int) -> Value {
|
||||
if (a.size() != 1 || !a[0].isString()) return Value(false);
|
||||
std::ifstream f(a[0].asString()); bool ok = f.good(); f.close();
|
||||
return Value(ok);
|
||||
});
|
||||
|
||||
// input remains a global in stdlib; not provided here
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
83
src/sources/builtinModules/json.cpp
Normal file
83
src/sources/builtinModules/json.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
#include "json.h"
|
||||
#include "Interpreter.h"
|
||||
#include <string>
|
||||
#include <cctype>
|
||||
|
||||
// Minimal JSON parser/stringifier (numbers, strings, booleans, null, arrays, objects)
|
||||
namespace {
|
||||
struct Cursor { const std::string* s; size_t i = 0; };
|
||||
void skipWs(Cursor& c){ while (c.i < c.s->size() && std::isspace(static_cast<unsigned char>((*c.s)[c.i]))) ++c.i; }
|
||||
bool match(Cursor& c, char ch){ skipWs(c); if (c.i < c.s->size() && (*c.s)[c.i]==ch){ ++c.i; return true;} return false; }
|
||||
std::string parseString(Cursor& c){
|
||||
if (!match(c,'"')) return {};
|
||||
std::string out; while (c.i < c.s->size()){
|
||||
char ch = (*c.s)[c.i++];
|
||||
if (ch=='"') break;
|
||||
if (ch=='\\' && c.i < c.s->size()){
|
||||
char e = (*c.s)[c.i++];
|
||||
switch(e){ case '"': out+='"'; break; case '\\': out+='\\'; break; case '/': out+='/'; break; case 'b': out+='\b'; break; case 'f': out+='\f'; break; case 'n': out+='\n'; break; case 'r': out+='\r'; break; case 't': out+='\t'; break; default: out+=e; }
|
||||
} else out+=ch;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
double parseNumber(Cursor& c){ skipWs(c); size_t start=c.i; while (c.i<c.s->size() && (std::isdigit((*c.s)[c.i])||(*c.s)[c.i]=='-'||(*c.s)[c.i]=='+'||(*c.s)[c.i]=='.'||(*c.s)[c.i]=='e'||(*c.s)[c.i]=='E')) ++c.i; return std::stod(c.s->substr(start,c.i-start)); }
|
||||
Value parseValue(Cursor& c);
|
||||
Value parseArray(Cursor& c){
|
||||
match(c,'['); std::vector<Value> arr; skipWs(c); if (match(c,']')) return Value(arr);
|
||||
while (true){ arr.push_back(parseValue(c)); skipWs(c); if (match(c,']')) break; match(c,','); }
|
||||
return Value(arr);
|
||||
}
|
||||
Value parseObject(Cursor& c){
|
||||
match(c,'{'); std::unordered_map<std::string,Value> obj; skipWs(c); if (match(c,'}')) return Value(obj);
|
||||
while (true){ std::string k = parseString(c); match(c,':'); Value v = parseValue(c); obj.emplace(k, v); skipWs(c); if (match(c,'}')) break; match(c,','); }
|
||||
return Value(obj);
|
||||
}
|
||||
Value parseValue(Cursor& c){ skipWs(c); if (c.i>=c.s->size()) return NONE_VALUE; char ch=(*c.s)[c.i];
|
||||
if (ch=='"') return Value(parseString(c));
|
||||
if (ch=='[') return parseArray(c);
|
||||
if (ch=='{') return parseObject(c);
|
||||
if (!c.s->compare(c.i,4,"true")) { c.i+=4; return Value(true);}
|
||||
if (!c.s->compare(c.i,5,"false")) { c.i+=5; return Value(false);}
|
||||
if (!c.s->compare(c.i,4,"null")) { c.i+=4; return NONE_VALUE;}
|
||||
return Value(parseNumber(c));
|
||||
}
|
||||
|
||||
std::string escapeString(const std::string& s){
|
||||
std::string out; out.reserve(s.size()+2); out.push_back('"');
|
||||
for(char ch: s){
|
||||
switch(ch){ case '"': out+="\\\""; break; case '\\': out+="\\\\"; break; case '\n': out+="\\n"; break; case '\r': out+="\\r"; break; case '\t': out+="\\t"; break; default: out+=ch; }
|
||||
}
|
||||
out.push_back('"'); return out;
|
||||
}
|
||||
std::string stringifyValue(const Value& v){
|
||||
switch(v.type){
|
||||
case VAL_NONE: return "null";
|
||||
case VAL_BOOLEAN: return v.asBoolean()?"true":"false";
|
||||
case VAL_NUMBER: return v.toString();
|
||||
case VAL_STRING: return escapeString(v.asString());
|
||||
case VAL_ARRAY: {
|
||||
const auto& a=v.asArray(); std::string out="["; for(size_t i=0;i<a.size();++i){ if(i) out+=","; out+=stringifyValue(a[i]); } out+="]"; return out;
|
||||
}
|
||||
case VAL_DICT: {
|
||||
const auto& d=v.asDict(); std::string out="{"; bool first=true; for(const auto& kv:d){ if(!first) out+=","; first=false; out+=escapeString(kv.first); out+=":"; out+=stringifyValue(kv.second);} out+="}"; return out;
|
||||
}
|
||||
default: return "null";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void registerJsonModule(Interpreter& interpreter) {
|
||||
interpreter.registerModule("json", [](Interpreter::ModuleBuilder& m) {
|
||||
m.fn("parse", [](std::vector<Value> a, int, int) -> Value {
|
||||
if (a.size() != 1 || !a[0].isString()) return NONE_VALUE;
|
||||
Cursor c{&a[0].asString(), 0};
|
||||
return parseValue(c);
|
||||
});
|
||||
m.fn("stringify", [](std::vector<Value> a, int, int) -> Value {
|
||||
if (a.size() != 1) return Value(std::string("null"));
|
||||
return Value(stringifyValue(a[0]));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
56
src/sources/builtinModules/math.cpp
Normal file
56
src/sources/builtinModules/math.cpp
Normal file
@ -0,0 +1,56 @@
|
||||
#include "math_module.h"
|
||||
#include "Interpreter.h"
|
||||
#include <cmath>
|
||||
|
||||
static Value unary_math(std::vector<Value> a, double(*fn)(double)){
|
||||
if (a.size() != 1 || !a[0].isNumber()) return NONE_VALUE;
|
||||
return Value(fn(a[0].asNumber()));
|
||||
}
|
||||
|
||||
void registerMathModule(Interpreter& interpreter) {
|
||||
interpreter.registerModule("math", [](Interpreter::ModuleBuilder& m) {
|
||||
m.fn("sin", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::sin); });
|
||||
m.fn("cos", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::cos); });
|
||||
m.fn("tan", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::tan); });
|
||||
m.fn("asin", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::asin); });
|
||||
m.fn("acos", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::acos); });
|
||||
m.fn("atan", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::atan); });
|
||||
m.fn("sinh", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::sinh); });
|
||||
m.fn("cosh", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::cosh); });
|
||||
m.fn("tanh", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::tanh); });
|
||||
m.fn("exp", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::exp); });
|
||||
m.fn("log", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::log); });
|
||||
m.fn("log10", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::log10); });
|
||||
m.fn("sqrt", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::sqrt); });
|
||||
m.fn("ceil", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::ceil); });
|
||||
m.fn("floor", [](std::vector<Value> a, int, int)->Value{ return unary_math(a, std::floor); });
|
||||
m.fn("round", [](std::vector<Value> a, int, int)->Value{
|
||||
if (a.size() != 1 || !a[0].isNumber()) return NONE_VALUE;
|
||||
return Value(std::round(a[0].asNumber()));
|
||||
});
|
||||
m.fn("abs", [](std::vector<Value> a, int, int)->Value{
|
||||
if (a.size() != 1 || !a[0].isNumber()) return NONE_VALUE;
|
||||
return Value(std::fabs(a[0].asNumber()));
|
||||
});
|
||||
m.fn("pow", [](std::vector<Value> a, int, int)->Value{
|
||||
if (a.size() != 2 || !a[0].isNumber() || !a[1].isNumber()) return NONE_VALUE;
|
||||
return Value(std::pow(a[0].asNumber(), a[1].asNumber()));
|
||||
});
|
||||
m.fn("min", [](std::vector<Value> a, int, int)->Value{
|
||||
if (a.empty()) return NONE_VALUE;
|
||||
double mval = a[0].isNumber()? a[0].asNumber() : 0.0;
|
||||
for(size_t i=1;i<a.size();++i){ if (a[i].isNumber()) mval = std::min(mval, a[i].asNumber()); }
|
||||
return Value(mval);
|
||||
});
|
||||
m.fn("max", [](std::vector<Value> a, int, int)->Value{
|
||||
if (a.empty()) return NONE_VALUE;
|
||||
double mval = a[0].isNumber()? a[0].asNumber() : 0.0;
|
||||
for(size_t i=1;i<a.size();++i){ if (a[i].isNumber()) mval = std::max(mval, a[i].asNumber()); }
|
||||
return Value(mval);
|
||||
});
|
||||
m.val("pi", Value(3.14159265358979323846));
|
||||
m.val("e", Value(2.71828182845904523536));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
106
src/sources/builtinModules/os.cpp
Normal file
106
src/sources/builtinModules/os.cpp
Normal file
@ -0,0 +1,106 @@
|
||||
#include "os.h"
|
||||
#include "Interpreter.h"
|
||||
#include "Lexer.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
void registerOsModule(Interpreter& interpreter) {
|
||||
interpreter.registerModule("os", [](Interpreter::ModuleBuilder& m) {
|
||||
// Process
|
||||
m.fn("getcwd", [](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("chdir", [](std::vector<Value> a, int, int) -> Value {
|
||||
if (a.size() != 1 || !a[0].isString()) return Value(false);
|
||||
int rc = ::chdir(a[0].asString().c_str());
|
||||
return Value(rc == 0);
|
||||
});
|
||||
m.fn("getpid", [](std::vector<Value>, int, int) -> Value {
|
||||
return Value(static_cast<double>(getpid()));
|
||||
});
|
||||
m.fn("getppid", [](std::vector<Value>, int, int) -> Value {
|
||||
return Value(static_cast<double>(getppid()));
|
||||
});
|
||||
m.fn("name", [](std::vector<Value>, int, int) -> Value {
|
||||
#if defined(_WIN32)
|
||||
return Value(std::string("nt"));
|
||||
#else
|
||||
return Value(std::string("posix"));
|
||||
#endif
|
||||
});
|
||||
|
||||
// Filesystem
|
||||
m.fn("listdir", [](std::vector<Value> a, int, int) -> Value {
|
||||
std::string path = ".";
|
||||
if (!a.empty() && a[0].isString()) path = a[0].asString();
|
||||
std::vector<Value> out;
|
||||
try {
|
||||
for (const auto& entry : fs::directory_iterator(path)) {
|
||||
out.push_back(Value(entry.path().filename().string()));
|
||||
}
|
||||
} catch (...) {}
|
||||
return Value(out);
|
||||
});
|
||||
m.fn("mkdir", [](std::vector<Value> a, int, int) -> Value {
|
||||
if (a.size() != 1 || !a[0].isString()) return Value(false);
|
||||
try { return Value(fs::create_directory(a[0].asString())); } catch (...) { return Value(false); }
|
||||
});
|
||||
m.fn("rmdir", [](std::vector<Value> a, int, int) -> Value {
|
||||
if (a.size() != 1 || !a[0].isString()) return Value(false);
|
||||
try { return Value(fs::remove(a[0].asString())); } catch (...) { return Value(false); }
|
||||
});
|
||||
m.fn("remove", [](std::vector<Value> a, int, int) -> Value {
|
||||
if (a.size() != 1 || !a[0].isString()) return Value(false);
|
||||
try { return Value(fs::remove(a[0].asString())); } catch (...) { return Value(false); }
|
||||
});
|
||||
m.fn("exists", [](std::vector<Value> a, int, int) -> Value {
|
||||
if (a.size() != 1 || !a[0].isString()) return Value(false);
|
||||
try { return Value(fs::exists(a[0].asString())); } catch (...) { return Value(false); }
|
||||
});
|
||||
m.fn("isfile", [](std::vector<Value> a, int, int) -> Value {
|
||||
if (a.size() != 1 || !a[0].isString()) return Value(false);
|
||||
try { return Value(fs::is_regular_file(a[0].asString())); } catch (...) { return Value(false); }
|
||||
});
|
||||
m.fn("isdir", [](std::vector<Value> a, int, int) -> Value {
|
||||
if (a.size() != 1 || !a[0].isString()) return Value(false);
|
||||
try { return Value(fs::is_directory(a[0].asString())); } catch (...) { return Value(false); }
|
||||
});
|
||||
m.fn("rename", [](std::vector<Value> a, int, int) -> Value {
|
||||
if (a.size() != 2 || !a[0].isString() || !a[1].isString()) return Value(false);
|
||||
try { fs::rename(a[0].asString(), a[1].asString()); return Value(true); } catch (...) { return Value(false); }
|
||||
});
|
||||
|
||||
// Separators
|
||||
m.fn("sep", [](std::vector<Value>, int, int) -> Value {
|
||||
#if defined(_WIN32)
|
||||
return Value(std::string("\\"));
|
||||
#else
|
||||
return Value(std::string("/"));
|
||||
#endif
|
||||
});
|
||||
m.fn("pathsep", [](std::vector<Value>, int, int) -> Value {
|
||||
#if defined(_WIN32)
|
||||
return Value(std::string(";"));
|
||||
#else
|
||||
return Value(std::string(":"));
|
||||
#endif
|
||||
});
|
||||
m.fn("linesep", [](std::vector<Value>, int, int) -> Value {
|
||||
#if defined(_WIN32)
|
||||
return Value(std::string("\r\n"));
|
||||
#else
|
||||
return Value(std::string("\n"));
|
||||
#endif
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
49
src/sources/builtinModules/path.cpp
Normal file
49
src/sources/builtinModules/path.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
#include "path_module.h"
|
||||
#include "Interpreter.h"
|
||||
#include <filesystem>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
static std::string join_impl(const std::vector<Value>& parts){
|
||||
if (parts.empty()) return std::string();
|
||||
fs::path p;
|
||||
for (const auto& v : parts) if (v.isString()) p /= v.asString();
|
||||
return p.generic_string();
|
||||
}
|
||||
|
||||
void registerPathModule(Interpreter& interpreter) {
|
||||
interpreter.registerModule("path", [](Interpreter::ModuleBuilder& m) {
|
||||
m.fn("join", [](std::vector<Value> a, int, int) -> Value {
|
||||
return Value(join_impl(a));
|
||||
});
|
||||
m.fn("dirname", [](std::vector<Value> a, int, int) -> Value {
|
||||
if (a.size()!=1 || !a[0].isString()) return NONE_VALUE;
|
||||
return Value(fs::path(a[0].asString()).parent_path().generic_string());
|
||||
});
|
||||
m.fn("basename", [](std::vector<Value> a, int, int) -> Value {
|
||||
if (a.size()!=1 || !a[0].isString()) return NONE_VALUE;
|
||||
return Value(fs::path(a[0].asString()).filename().generic_string());
|
||||
});
|
||||
m.fn("splitext", [](std::vector<Value> a, int, int) -> Value {
|
||||
if (a.size()!=1 || !a[0].isString()) return NONE_VALUE;
|
||||
fs::path p(a[0].asString());
|
||||
return Value(std::vector<Value>{ Value(p.replace_extension("").generic_string()), Value(p.extension().generic_string()) });
|
||||
});
|
||||
m.fn("normalize", [](std::vector<Value> a, int, int) -> Value {
|
||||
if (a.size()!=1 || !a[0].isString()) return NONE_VALUE;
|
||||
return Value(fs::path(a[0].asString()).lexically_normal().generic_string());
|
||||
});
|
||||
m.fn("isabs", [](std::vector<Value> a, int, int) -> Value {
|
||||
if (a.size()!=1 || !a[0].isString()) return Value(false);
|
||||
return Value(fs::path(a[0].asString()).is_absolute());
|
||||
});
|
||||
m.fn("relpath", [](std::vector<Value> a, int, int) -> Value {
|
||||
if (a.size()<1 || a.size()>2 || !a[0].isString() || (a.size()==2 && !a[1].isString())) return NONE_VALUE;
|
||||
fs::path target(a[0].asString());
|
||||
fs::path base = (a.size()==2)? fs::path(a[1].asString()) : fs::current_path();
|
||||
return Value(fs::relative(target, base).generic_string());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
35
src/sources/builtinModules/rand.cpp
Normal file
35
src/sources/builtinModules/rand.cpp
Normal file
@ -0,0 +1,35 @@
|
||||
#include "rand.h"
|
||||
#include "Interpreter.h"
|
||||
#include <random>
|
||||
|
||||
void registerRandModule(Interpreter& interpreter) {
|
||||
interpreter.registerModule("rand", [](Interpreter::ModuleBuilder& m) {
|
||||
static std::mt19937_64 rng{std::random_device{}()};
|
||||
m.fn("seed", [](std::vector<Value> a, int, int) -> Value {
|
||||
if (a.size() == 1 && a[0].isNumber()) {
|
||||
rng.seed(static_cast<uint64_t>(a[0].asNumber()));
|
||||
}
|
||||
return NONE_VALUE;
|
||||
});
|
||||
m.fn("random", [](std::vector<Value>, int, int) -> Value {
|
||||
std::uniform_real_distribution<double> dist(0.0, 1.0);
|
||||
return Value(dist(rng));
|
||||
});
|
||||
m.fn("randint", [](std::vector<Value> a, int, int) -> Value {
|
||||
if (a.size() != 2 || !a[0].isNumber() || !a[1].isNumber()) return NONE_VALUE;
|
||||
long long lo = static_cast<long long>(a[0].asNumber());
|
||||
long long hi = static_cast<long long>(a[1].asNumber());
|
||||
if (hi < lo) std::swap(lo, hi);
|
||||
std::uniform_int_distribution<long long> dist(lo, hi);
|
||||
return Value(static_cast<double>(dist(rng)));
|
||||
});
|
||||
m.fn("choice", [](std::vector<Value> a, int, int) -> Value {
|
||||
if (a.size() != 1 || !a[0].isArray() || a[0].asArray().empty()) return NONE_VALUE;
|
||||
const auto& arr = a[0].asArray();
|
||||
std::uniform_int_distribution<size_t> dist(0, arr.size() - 1);
|
||||
return arr[dist(rng)];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,25 @@
|
||||
#include "register.h"
|
||||
#include "sys.h"
|
||||
#include "os.h"
|
||||
#include "eval.h"
|
||||
#include "io.h"
|
||||
#include "time_module.h"
|
||||
#include "rand.h"
|
||||
#include "math_module.h"
|
||||
#include "path_module.h"
|
||||
#include "base64_module.h"
|
||||
|
||||
void registerAllBuiltinModules(Interpreter& interpreter) {
|
||||
registerSysModule(interpreter);
|
||||
registerOsModule(interpreter);
|
||||
registerEvalModule(interpreter);
|
||||
registerIoModule(interpreter);
|
||||
registerTimeModule(interpreter);
|
||||
registerRandModule(interpreter);
|
||||
registerMathModule(interpreter);
|
||||
registerPathModule(interpreter);
|
||||
registerBase64Module(interpreter);
|
||||
// registerJsonModule(interpreter); // deferred pending extensive testing
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -6,140 +6,43 @@
|
||||
#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
|
||||
#include <vector>
|
||||
|
||||
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"));
|
||||
return Value(std::string("win32"));
|
||||
#elif defined(__APPLE__)
|
||||
return Value(std::string("macos"));
|
||||
return Value(std::string("darwin"));
|
||||
#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
|
||||
m.fn("version", [](std::vector<Value>, int, int) -> Value { return Value(std::string("0.0.3")); });
|
||||
// argv(): array of strings
|
||||
m.fn("argv", [&I](std::vector<Value>, int, int) -> Value {
|
||||
std::vector<Value> out;
|
||||
for (const auto& s : I.getArgv()) out.push_back(Value(s));
|
||||
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
|
||||
// executable(): absolute path to the running binary (host-provided)
|
||||
m.fn("executable", [&I](std::vector<Value>, int, int) -> Value { return Value(I.getExecutablePath()); });
|
||||
// modules(): read-only snapshot of module cache
|
||||
m.fn("modules", [&I](std::vector<Value>, int, int) -> Value {
|
||||
Value dictVal = Value(std::unordered_map<std::string, Value>{});
|
||||
auto snapshot = I.getModuleCacheSnapshot();
|
||||
return Value(snapshot);
|
||||
});
|
||||
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
|
||||
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;
|
||||
});
|
||||
// env/cwd/pid moved to os; keep sys minimal
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
31
src/sources/builtinModules/time.cpp
Normal file
31
src/sources/builtinModules/time.cpp
Normal file
@ -0,0 +1,31 @@
|
||||
#include "time_module.h"
|
||||
#include "Interpreter.h"
|
||||
#include "Environment.h"
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
void registerTimeModule(Interpreter& interpreter) {
|
||||
interpreter.registerModule("time", [](Interpreter::ModuleBuilder& m) {
|
||||
m.fn("now", [](std::vector<Value>, int, int) -> Value {
|
||||
using namespace std::chrono;
|
||||
auto now = system_clock::now().time_since_epoch();
|
||||
auto us = duration_cast<microseconds>(now).count();
|
||||
return Value(static_cast<double>(us));
|
||||
});
|
||||
m.fn("monotonic", [](std::vector<Value>, int, int) -> Value {
|
||||
using namespace std::chrono;
|
||||
auto now = steady_clock::now().time_since_epoch();
|
||||
auto us = duration_cast<microseconds>(now).count();
|
||||
return Value(static_cast<double>(us));
|
||||
});
|
||||
m.fn("sleep", [](std::vector<Value> a, int, int) -> Value {
|
||||
if (a.size() != 1 || !a[0].isNumber()) return NONE_VALUE;
|
||||
double seconds = a[0].asNumber();
|
||||
if (seconds < 0) return NONE_VALUE;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(static_cast<int>(seconds * 1000)));
|
||||
return NONE_VALUE;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -6,20 +6,23 @@
|
||||
|
||||
int main(int argc, char* argv[]){
|
||||
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"});
|
||||
// Enable open preset (all builtins, file imports allowed)
|
||||
bobLang.setSafetyPreset("open");
|
||||
|
||||
|
||||
if(argc > 1) {
|
||||
// Seed argv/executable for sys module
|
||||
std::vector<std::string> args; for (int i = 2; i < argc; ++i) args.emplace_back(argv[i]);
|
||||
bobLang.registerModule("__configure_sys_argv__", [args, execPath = std::string(argv[0])](ModuleRegistry::ModuleBuilder& m){
|
||||
m.interpreterRef.setArgv(args, execPath);
|
||||
});
|
||||
bobLang.runFile(argv[1]);
|
||||
} else {
|
||||
// For REPL, use interactive mode
|
||||
// For REPL, use interactive mode and seed empty argv
|
||||
std::vector<std::string> args;
|
||||
bobLang.registerModule("__configure_sys_argv__", [args, execPath = std::string(argv[0])](ModuleRegistry::ModuleBuilder& m){
|
||||
m.interpreterRef.setArgv(args, execPath);
|
||||
});
|
||||
bobLang.runPrompt();
|
||||
}
|
||||
|
||||
|
||||
@ -648,6 +648,11 @@ std::shared_ptr<Stmt> Parser::fromImportStatement() {
|
||||
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.");
|
||||
// Support star-import: from module import *;
|
||||
if (match({STAR})) {
|
||||
consume(SEMICOLON, "Expected ';' after from-import statement.");
|
||||
return msptr(FromImportStmt)(fromTok, mod, true);
|
||||
}
|
||||
std::vector<FromImportStmt::ImportItem> items;
|
||||
do {
|
||||
Token name = consume(IDENTIFIER, "Expected name to import.");
|
||||
|
||||
@ -336,6 +336,20 @@ void Executor::visitImportStmt(const std::shared_ptr<ImportStmt>& statement, Exe
|
||||
|
||||
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
|
||||
// Star-import case
|
||||
if (statement->importAll) {
|
||||
// Import the module and bind all public exports into current environment
|
||||
Value mod = interpreter->importModule(spec, statement->fromToken.line, statement->fromToken.column);
|
||||
const std::unordered_map<std::string, Value>* src = nullptr;
|
||||
if (mod.isModule()) src = mod.asModule()->exports.get(); else if (mod.isDict()) src = &mod.asDict();
|
||||
if (!src) { throw std::runtime_error("from-import * on non-module"); }
|
||||
for (const auto& kv : *src) {
|
||||
const std::string& name = kv.first;
|
||||
if (!name.empty() && name[0] == '_') continue; // skip private
|
||||
interpreter->getEnvironment()->define(name, kv.second);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Build item list name->alias
|
||||
std::vector<std::pair<std::string,std::string>> items;
|
||||
for (const auto& it : statement->items) {
|
||||
|
||||
@ -124,13 +124,9 @@ Value Interpreter::importModule(const std::string& spec, int line, int column) {
|
||||
// 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 (errorReporter && !errorReporter->getCurrentFileName().empty()) {
|
||||
std::filesystem::path p(errorReporter->getCurrentFileName());
|
||||
baseDir = p.has_parent_path() ? p.parent_path().string() : baseDir;
|
||||
}
|
||||
if (looksPath) {
|
||||
if (!allowFileImports) {
|
||||
@ -222,7 +218,6 @@ Value Interpreter::importModule(const std::string& spec, int line, int column) {
|
||||
// Restore env and reporter
|
||||
setEnvironment(saved);
|
||||
if (errorReporter) errorReporter->popSource();
|
||||
|
||||
return moduleVal;
|
||||
}
|
||||
|
||||
|
||||
@ -119,31 +119,7 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
||||
// Store the shared_ptr in the interpreter to keep it alive
|
||||
interpreter.addBuiltinFunction(assertFunc);
|
||||
|
||||
// Create a built-in time function (returns strictly increasing microseconds)
|
||||
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()) + ".");
|
||||
}
|
||||
|
||||
static long long lastReturnedMicros = 0;
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
auto duration = now.time_since_epoch();
|
||||
long long microseconds = std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
|
||||
if (microseconds <= lastReturnedMicros) {
|
||||
microseconds = lastReturnedMicros + 1;
|
||||
}
|
||||
lastReturnedMicros = microseconds;
|
||||
return Value(static_cast<double>(microseconds));
|
||||
});
|
||||
env->define("time", Value(timeFunc));
|
||||
|
||||
// Store the shared_ptr in the interpreter to keep it alive
|
||||
interpreter.addBuiltinFunction(timeFunc);
|
||||
// time-related globals moved into builtin time module
|
||||
|
||||
// Create a built-in input function
|
||||
auto inputFunc = std::make_shared<BuiltinFunction>("input",
|
||||
@ -333,44 +309,49 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
||||
// Store the shared_ptr in the interpreter to keep it alive
|
||||
interpreter.addBuiltinFunction(exitFunc);
|
||||
|
||||
// Create a built-in sleep function for animations and timing
|
||||
auto sleepFunc = std::make_shared<BuiltinFunction>("sleep",
|
||||
[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);
|
||||
// sleep moved into builtin time module
|
||||
|
||||
// Introspection: dir(obj) and functions(obj)
|
||||
auto dirFunc = std::make_shared<BuiltinFunction>("dir",
|
||||
[](std::vector<Value> args, int, int) -> Value {
|
||||
if (args.size() != 1) return Value(std::vector<Value>{});
|
||||
Value obj = args[0];
|
||||
std::vector<Value> out;
|
||||
if (obj.isModule()) {
|
||||
auto* mod = obj.asModule();
|
||||
if (mod && mod->exports) {
|
||||
for (const auto& kv : *mod->exports) out.push_back(Value(kv.first));
|
||||
}
|
||||
throw std::runtime_error("Expected 1 argument but got " + std::to_string(args.size()) + ".");
|
||||
} else if (obj.isDict()) {
|
||||
const auto& d = obj.asDict();
|
||||
for (const auto& kv : d) out.push_back(Value(kv.first));
|
||||
}
|
||||
|
||||
if (!args[0].isNumber()) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(line, column, "StdLib Error",
|
||||
"sleep() argument must be a number", "", true);
|
||||
}
|
||||
throw std::runtime_error("sleep() argument must be a number");
|
||||
}
|
||||
|
||||
double seconds = args[0].asNumber();
|
||||
if (seconds < 0) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(line, column, "StdLib Error",
|
||||
"sleep() argument cannot be negative", "", true);
|
||||
}
|
||||
throw std::runtime_error("sleep() argument cannot be negative");
|
||||
}
|
||||
|
||||
// Convert to milliseconds and sleep
|
||||
int milliseconds = static_cast<int>(seconds * 1000);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
|
||||
|
||||
return NONE_VALUE;
|
||||
return Value(out);
|
||||
});
|
||||
env->define("sleep", Value(sleepFunc));
|
||||
env->define("dir", Value(dirFunc));
|
||||
interpreter.addBuiltinFunction(dirFunc);
|
||||
|
||||
// Store the shared_ptr in the interpreter to keep it alive
|
||||
interpreter.addBuiltinFunction(sleepFunc);
|
||||
auto functionsFunc = std::make_shared<BuiltinFunction>("functions",
|
||||
[](std::vector<Value> args, int, int) -> Value {
|
||||
if (args.size() != 1) return Value(std::vector<Value>{});
|
||||
Value obj = args[0];
|
||||
std::vector<Value> out;
|
||||
auto pushIfFn = [&out](const std::pair<const std::string, Value>& kv){
|
||||
if (kv.second.isFunction() || kv.second.isBuiltinFunction()) out.push_back(Value(kv.first));
|
||||
};
|
||||
if (obj.isModule()) {
|
||||
auto* mod = obj.asModule();
|
||||
if (mod && mod->exports) {
|
||||
for (const auto& kv : *mod->exports) pushIfFn(kv);
|
||||
}
|
||||
} else if (obj.isDict()) {
|
||||
const auto& d = obj.asDict();
|
||||
for (const auto& kv : d) pushIfFn(kv);
|
||||
}
|
||||
return Value(out);
|
||||
});
|
||||
env->define("functions", Value(functionsFunc));
|
||||
interpreter.addBuiltinFunction(functionsFunc);
|
||||
|
||||
// Create a built-in random function
|
||||
auto randomFunc = std::make_shared<BuiltinFunction>("random",
|
||||
@ -397,231 +378,11 @@ void BobStdLib::addToEnvironment(std::shared_ptr<Environment> env, Interpreter&
|
||||
// Store the shared_ptr in the interpreter to keep it alive
|
||||
interpreter.addBuiltinFunction(randomFunc);
|
||||
|
||||
// Create a built-in eval function (like Python's eval)
|
||||
auto evalFunc = std::make_shared<BuiltinFunction>("eval",
|
||||
[&interpreter, errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
||||
if (args.size() != 1) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(line, column, "Invalid Arguments",
|
||||
"eval expects exactly 1 argument (string)", "eval");
|
||||
}
|
||||
throw std::runtime_error("eval expects exactly 1 argument");
|
||||
}
|
||||
|
||||
if (!args[0].isString()) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(line, column, "Invalid Type",
|
||||
"eval argument must be a string", "eval");
|
||||
}
|
||||
throw std::runtime_error("eval argument must be a string");
|
||||
}
|
||||
|
||||
std::string code = args[0].asString();
|
||||
std::string evalName = "<eval>";
|
||||
|
||||
try {
|
||||
// Push eval source for correct error context
|
||||
if (errorReporter) {
|
||||
errorReporter->pushSource(code, evalName);
|
||||
}
|
||||
// Create a new lexer for the code string
|
||||
Lexer lexer;
|
||||
lexer.setErrorReporter(errorReporter);
|
||||
std::vector<Token> tokens = lexer.Tokenize(code);
|
||||
|
||||
// Create a new parser
|
||||
Parser parser(tokens);
|
||||
parser.setErrorReporter(errorReporter);
|
||||
std::vector<std::shared_ptr<Stmt>> statements = parser.parse();
|
||||
|
||||
// Execute the statements in the current environment
|
||||
// Note: This runs in the current scope, so variables are shared
|
||||
interpreter.interpret(statements);
|
||||
|
||||
// For now, return NONE_VALUE since we don't have a way to get the last expression value
|
||||
// In a more sophisticated implementation, we'd track the last expression result
|
||||
return NONE_VALUE;
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(line, column, "Eval Error",
|
||||
"Failed to evaluate code: " + std::string(e.what()), code);
|
||||
}
|
||||
throw std::runtime_error("eval failed: " + std::string(e.what()));
|
||||
} catch (...) {
|
||||
if (errorReporter) {
|
||||
errorReporter->popSource();
|
||||
}
|
||||
throw;
|
||||
}
|
||||
if (errorReporter) {
|
||||
errorReporter->popSource();
|
||||
}
|
||||
});
|
||||
env->define("eval", Value(evalFunc));
|
||||
|
||||
// Store the shared_ptr in the interpreter to keep it alive
|
||||
interpreter.addBuiltinFunction(evalFunc);
|
||||
// (eval and evalFile moved to eval module)
|
||||
|
||||
|
||||
|
||||
// Create a built-in readFile function
|
||||
auto readFileFunc = std::make_shared<BuiltinFunction>("readFile",
|
||||
[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()) + ".");
|
||||
}
|
||||
|
||||
if (!args[0].isString()) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(line, column, "StdLib Error",
|
||||
"readFile() argument must be a string", "", true);
|
||||
}
|
||||
throw std::runtime_error("readFile() argument must be a string");
|
||||
}
|
||||
|
||||
std::string filename = args[0].asString();
|
||||
std::ifstream file(filename);
|
||||
|
||||
if (!file.is_open()) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(line, column, "StdLib Error",
|
||||
"Could not open file: " + filename, "", true);
|
||||
}
|
||||
throw std::runtime_error("Could not open file: " + filename);
|
||||
}
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << file.rdbuf();
|
||||
file.close();
|
||||
|
||||
return Value(buffer.str());
|
||||
});
|
||||
env->define("readFile", Value(readFileFunc));
|
||||
interpreter.addBuiltinFunction(readFileFunc);
|
||||
|
||||
// Create a built-in writeFile function
|
||||
auto writeFileFunc = std::make_shared<BuiltinFunction>("writeFile",
|
||||
[errorReporter](std::vector<Value> args, int line, int column) -> Value {
|
||||
if (args.size() != 2) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(line, column, "StdLib Error",
|
||||
"Expected 2 arguments but got " + std::to_string(args.size()) + ".", "", true);
|
||||
}
|
||||
throw std::runtime_error("Expected 2 arguments but got " + std::to_string(args.size()) + ".");
|
||||
}
|
||||
|
||||
if (!args[0].isString()) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(line, column, "StdLib Error",
|
||||
"First argument to writeFile() must be a string", "", true);
|
||||
}
|
||||
throw std::runtime_error("First argument to writeFile() must be a string");
|
||||
}
|
||||
|
||||
if (!args[1].isString()) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(line, column, "StdLib Error",
|
||||
"Second argument to writeFile() must be a string", "", true);
|
||||
}
|
||||
throw std::runtime_error("Second argument to writeFile() must be a string");
|
||||
}
|
||||
|
||||
std::string filename = args[0].asString();
|
||||
std::string content = args[1].asString();
|
||||
|
||||
std::ofstream file(filename);
|
||||
if (!file.is_open()) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(line, column, "StdLib Error",
|
||||
"Could not create file: " + filename, "", true);
|
||||
}
|
||||
throw std::runtime_error("Could not create file: " + filename);
|
||||
}
|
||||
|
||||
file << content;
|
||||
file.close();
|
||||
|
||||
return NONE_VALUE;
|
||||
});
|
||||
env->define("writeFile", Value(writeFileFunc));
|
||||
interpreter.addBuiltinFunction(writeFileFunc);
|
||||
|
||||
// Create a built-in readLines function
|
||||
auto readLinesFunc = std::make_shared<BuiltinFunction>("readLines",
|
||||
[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()) + ".");
|
||||
}
|
||||
|
||||
if (!args[0].isString()) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(line, column, "StdLib Error",
|
||||
"readLines() argument must be a string", "", true);
|
||||
}
|
||||
throw std::runtime_error("readLines() argument must be a string");
|
||||
}
|
||||
|
||||
std::string filename = args[0].asString();
|
||||
std::ifstream file(filename);
|
||||
|
||||
if (!file.is_open()) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(line, column, "StdLib Error",
|
||||
"Could not open file: " + filename, "", true);
|
||||
}
|
||||
throw std::runtime_error("Could not open file: " + filename);
|
||||
}
|
||||
|
||||
std::vector<Value> lines;
|
||||
std::string line_content;
|
||||
|
||||
while (std::getline(file, line_content)) {
|
||||
lines.push_back(Value(line_content));
|
||||
}
|
||||
|
||||
file.close();
|
||||
return Value(lines);
|
||||
});
|
||||
env->define("readLines", Value(readLinesFunc));
|
||||
interpreter.addBuiltinFunction(readLinesFunc);
|
||||
|
||||
// Create a built-in fileExists function
|
||||
auto fileExistsFunc = std::make_shared<BuiltinFunction>("fileExists",
|
||||
[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()) + ".");
|
||||
}
|
||||
|
||||
if (!args[0].isString()) {
|
||||
if (errorReporter) {
|
||||
errorReporter->reportError(line, column, "StdLib Error",
|
||||
"fileExists() argument must be a string", "", true);
|
||||
}
|
||||
throw std::runtime_error("fileExists() argument must be a string");
|
||||
}
|
||||
|
||||
std::string filename = args[0].asString();
|
||||
std::ifstream file(filename);
|
||||
bool exists = file.good();
|
||||
file.close();
|
||||
|
||||
return Value(exists);
|
||||
});
|
||||
env->define("fileExists", Value(fileExistsFunc));
|
||||
interpreter.addBuiltinFunction(fileExistsFunc);
|
||||
// (file I/O moved to io module)
|
||||
|
||||
// Create a built-in memoryUsage function (platform-specific, best effort)
|
||||
auto memoryUsageFunc = std::make_shared<BuiltinFunction>("memoryUsage",
|
||||
|
||||
@ -3274,54 +3274,57 @@ print(" * Error reporting in both modes");
|
||||
// Additional Tests: Classes and Extensions
|
||||
print("\n--- Additional Tests: Classes and Extensions ---");
|
||||
var path1 = fileExists("tests/test_method_calls.bob") ? "tests/test_method_calls.bob" : "../tests/test_method_calls.bob";
|
||||
eval(readFile(path1));
|
||||
evalFile(path1);
|
||||
var path2 = fileExists("tests/test_class_basic.bob") ? "tests/test_class_basic.bob" : "../tests/test_class_basic.bob";
|
||||
eval(readFile(path2));
|
||||
evalFile(path2);
|
||||
var path3 = fileExists("tests/test_class_with_this.bob") ? "tests/test_class_with_this.bob" : "../tests/test_class_with_this.bob";
|
||||
eval(readFile(path3));
|
||||
evalFile(path3);
|
||||
var path4 = fileExists("tests/test_class_init.bob") ? "tests/test_class_init.bob" : "../tests/test_class_init.bob";
|
||||
eval(readFile(path4));
|
||||
evalFile(path4);
|
||||
var path5 = fileExists("tests/test_class_extension_user.bob") ? "tests/test_class_extension_user.bob" : "../tests/test_class_extension_user.bob";
|
||||
eval(readFile(path5));
|
||||
evalFile(path5);
|
||||
var path6 = fileExists("tests/test_extension_methods.bob") ? "tests/test_extension_methods.bob" : "../tests/test_extension_methods.bob";
|
||||
eval(readFile(path6));
|
||||
evalFile(path6);
|
||||
var path7 = fileExists("tests/test_class_inheritance.bob") ? "tests/test_class_inheritance.bob" : "../tests/test_class_inheritance.bob";
|
||||
eval(readFile(path7));
|
||||
evalFile(path7);
|
||||
var path8 = fileExists("tests/test_classes_comprehensive.bob") ? "tests/test_classes_comprehensive.bob" : "../tests/test_classes_comprehensive.bob";
|
||||
eval(readFile(path8));
|
||||
evalFile(path8);
|
||||
var path9 = fileExists("tests/test_class_super.bob") ? "tests/test_class_super.bob" : "../tests/test_class_super.bob";
|
||||
eval(readFile(path9));
|
||||
evalFile(path9);
|
||||
var path10 = fileExists("tests/test_classes_extensive.bob") ? "tests/test_classes_extensive.bob" : "../tests/test_classes_extensive.bob";
|
||||
eval(readFile(path10));
|
||||
evalFile(path10);
|
||||
var path11 = fileExists("tests/test_class_edge_cases.bob") ? "tests/test_class_edge_cases.bob" : "../tests/test_class_edge_cases.bob";
|
||||
eval(readFile(path11));
|
||||
evalFile(path11);
|
||||
var path12 = fileExists("tests/test_polymorphism.bob") ? "tests/test_polymorphism.bob" : "../tests/test_polymorphism.bob";
|
||||
eval(readFile(path12));
|
||||
evalFile(path12);
|
||||
var path13 = fileExists("tests/test_polymorphism_practical.bob") ? "tests/test_polymorphism_practical.bob" : "../tests/test_polymorphism_practical.bob";
|
||||
eval(readFile(path13));
|
||||
evalFile(path13);
|
||||
|
||||
var path14 = fileExists("tests/test_builtin_methods_style.bob") ? "tests/test_builtin_methods_style.bob" : "../tests/test_builtin_methods_style.bob";
|
||||
eval(readFile(path14));
|
||||
evalFile(path14);
|
||||
|
||||
var path15 = fileExists("tests/test_try_catch.bob") ? "tests/test_try_catch.bob" : "../tests/test_try_catch.bob";
|
||||
eval(readFile(path15));
|
||||
evalFile(path15);
|
||||
var path15a = fileExists("tests/test_try_catch_runtime.bob") ? "tests/test_try_catch_runtime.bob" : "../tests/test_try_catch_runtime.bob";
|
||||
eval(readFile(path15a));
|
||||
evalFile(path15a);
|
||||
var path15b = fileExists("tests/test_try_catch_extensive.bob") ? "tests/test_try_catch_extensive.bob" : "../tests/test_try_catch_extensive.bob";
|
||||
eval(readFile(path15b));
|
||||
evalFile(path15b);
|
||||
var path15c = fileExists("tests/test_try_catch_edge_cases2.bob") ? "tests/test_try_catch_edge_cases2.bob" : "../tests/test_try_catch_edge_cases2.bob";
|
||||
eval(readFile(path15c));
|
||||
evalFile(path15c);
|
||||
var path15d = fileExists("tests/test_try_catch_cross_function.bob") ? "tests/test_try_catch_cross_function.bob" : "../tests/test_try_catch_cross_function.bob";
|
||||
eval(readFile(path15d));
|
||||
evalFile(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";
|
||||
eval(readFile(path15e));
|
||||
evalFile(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));
|
||||
evalFile(pathMods);
|
||||
|
||||
var pathModsB = fileExists("tests/test_imports_builtin.bob") ? "tests/test_imports_builtin.bob" : "../tests/test_imports_builtin.bob";
|
||||
eval(readFile(pathModsB));
|
||||
evalFile(pathModsB);
|
||||
|
||||
var pathOs = fileExists("tests/test_os_basic.bob") ? "tests/test_os_basic.bob" : "../tests/test_os_basic.bob";
|
||||
evalFile(pathOs);
|
||||
|
||||
print("\nAll tests passed.");
|
||||
print("Test suite complete.");
|
||||
16
tests.bob
16
tests.bob
@ -89,18 +89,8 @@
|
||||
|
||||
// print("done");
|
||||
|
||||
from bobby import A as fart;
|
||||
import bobby;
|
||||
var a = fart();
|
||||
var b = bobby.A();
|
||||
a.test();
|
||||
b.test();
|
||||
print(bobby);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Modules: basic imports suite
|
||||
var pathMods = fileExists("tests/test_imports_basic.bob") ? "tests/test_imports_basic.bob" : "../tests/test_imports_basic.bob";
|
||||
evalFile(pathMods);
|
||||
|
||||
print("done");
|
||||
@ -28,7 +28,7 @@ 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"));
|
||||
evalFile("tests/import_user_of_mod_hello.bob");
|
||||
|
||||
// Immutability: cannot reassign module binding
|
||||
var immFail = false;
|
||||
|
||||
@ -3,39 +3,18 @@ print("\n--- Test: builtin imports ---");
|
||||
import sys;
|
||||
assert(type(sys) == "module", "sys is module");
|
||||
|
||||
from sys import memoryUsage as mem;
|
||||
from sys import memoryUsage as mem, platform, version;
|
||||
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 ver = version();
|
||||
assert(type(ver) == "string", "version returns string");
|
||||
|
||||
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");
|
||||
// OS functions are in os module now
|
||||
import os;
|
||||
var dir = os.getcwd();
|
||||
assert(type(dir) == "string" || type(dir) == "none", "getcwd returns string/none");
|
||||
|
||||
print("builtin imports: PASS");
|
||||
|
||||
|
||||
29
tests/test_os_basic.bob
Normal file
29
tests/test_os_basic.bob
Normal file
@ -0,0 +1,29 @@
|
||||
print("\n--- Test: os basic ---");
|
||||
|
||||
import os;
|
||||
|
||||
var cwd = os.getcwd();
|
||||
assert(type(cwd) == "string", "getcwd returns string");
|
||||
|
||||
var pid = os.getpid();
|
||||
assert(type(pid) == "number", "getpid returns number");
|
||||
|
||||
var nm = os.name();
|
||||
assert(type(nm) == "string", "name returns string");
|
||||
|
||||
var sep = os.sep();
|
||||
var psep = os.pathsep();
|
||||
var lsep = os.linesep();
|
||||
assert(type(sep) == "string" && type(psep) == "string" && type(lsep) == "string", "separators are strings");
|
||||
|
||||
// listdir should return array
|
||||
var listing = os.listdir(".");
|
||||
assert(type(listing) == "array", "listdir returns array");
|
||||
|
||||
// exists/isdir should be true for current dir
|
||||
assert(os.exists(".") == true, "exists(.) is true");
|
||||
assert(os.isdir(".") == true, "isdir(.) is true");
|
||||
|
||||
print("os basic: PASS");
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user