Rename files
This commit is contained in:
@@ -10,10 +10,10 @@ option(CAMELLYA_BUILD_CLI "Build command line interface" ON)
|
|||||||
|
|
||||||
# Library sources
|
# Library sources
|
||||||
set(LIB_SOURCES
|
set(LIB_SOURCES
|
||||||
|
src/camellya.cpp
|
||||||
src/lexer.cpp
|
src/lexer.cpp
|
||||||
src/parser.cpp
|
src/parser.cpp
|
||||||
src/value.cpp
|
src/value.cpp
|
||||||
src/state.cpp
|
|
||||||
src/chunk.cpp
|
src/chunk.cpp
|
||||||
src/compiler.cpp
|
src/compiler.cpp
|
||||||
src/vm.cpp
|
src/vm.cpp
|
||||||
@@ -26,7 +26,6 @@ set(LIB_HEADERS
|
|||||||
src/parser.h
|
src/parser.h
|
||||||
src/ast.h
|
src/ast.h
|
||||||
src/value.h
|
src/value.h
|
||||||
src/state.h
|
|
||||||
src/chunk.h
|
src/chunk.h
|
||||||
src/compiler.h
|
src/compiler.h
|
||||||
src/vm.h
|
src/vm.h
|
||||||
|
|||||||
38
cli/main.cpp
38
cli/main.cpp
@@ -1,22 +1,26 @@
|
|||||||
#include "camellya.h"
|
#include "camellya.h"
|
||||||
#include <iostream>
|
|
||||||
#include <format>
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <format>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char **argv) {
|
||||||
if(argc < 2){
|
if (argc < 2) {
|
||||||
std::cout << std::format("Usage: chun <script> \n") << std::endl;
|
std::cout << std::format("Usage: chun <script> \n") << std::endl;
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now();
|
|
||||||
camellya::State state;
|
|
||||||
bool success = state.do_file(argv[1]);
|
|
||||||
if (!success) {
|
|
||||||
std::cerr << "Error: " << state.get_error() << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now();
|
|
||||||
std::chrono::duration<double> duration = end - start;
|
|
||||||
std::cout << std::format("Execution completed in {} seconds. \n", duration.count()) << std::endl;
|
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
std::chrono::high_resolution_clock::time_point start =
|
||||||
|
std::chrono::high_resolution_clock::now();
|
||||||
|
camellya::Camellya c;
|
||||||
|
bool success = c.do_file(argv[1]);
|
||||||
|
if (!success) {
|
||||||
|
std::cerr << "Error: " << c.get_error() << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
std::chrono::high_resolution_clock::time_point end =
|
||||||
|
std::chrono::high_resolution_clock::now();
|
||||||
|
std::chrono::duration<double> duration = end - start;
|
||||||
|
std::cout << std::format("Execution completed in {} seconds. \n",
|
||||||
|
duration.count())
|
||||||
|
<< std::endl;
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
168
src/camellya.cpp
Normal file
168
src/camellya.cpp
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
#include "camellya.h"
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "compiler.h"
|
||||||
|
#include "lexer.h"
|
||||||
|
#include "parser.h"
|
||||||
|
#include "vm.h"
|
||||||
|
|
||||||
|
namespace camellya {
|
||||||
|
|
||||||
|
class CamellyaImpl {
|
||||||
|
public:
|
||||||
|
CamellyaImpl()
|
||||||
|
: vm(std::make_unique<VM>()), compiler(std::make_unique<Compiler>()),
|
||||||
|
stack(), last_error() {}
|
||||||
|
std::unique_ptr<VM> vm;
|
||||||
|
std::unique_ptr<Compiler> compiler;
|
||||||
|
std::vector<ValuePtr> stack;
|
||||||
|
std::string last_error;
|
||||||
|
|
||||||
|
bool execute_program(const Program &program) {
|
||||||
|
// Use VM
|
||||||
|
auto chunk = compiler->compile(program);
|
||||||
|
if (!chunk) {
|
||||||
|
last_error = compiler->get_error();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = vm->execute(chunk);
|
||||||
|
if (!success) {
|
||||||
|
last_error = vm->get_error();
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Camellya::Camellya() : d(std::make_unique<CamellyaImpl>()) {}
|
||||||
|
|
||||||
|
Camellya::~Camellya() = default;
|
||||||
|
|
||||||
|
bool Camellya::do_string(const std::string &script) {
|
||||||
|
try {
|
||||||
|
Lexer lexer(script);
|
||||||
|
auto tokens = lexer.tokenize();
|
||||||
|
|
||||||
|
Parser parser(std::move(tokens));
|
||||||
|
Program program = parser.parse();
|
||||||
|
|
||||||
|
bool success = d->execute_program(program);
|
||||||
|
if (success) {
|
||||||
|
d->last_error.clear();
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
d->last_error = e.what();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Camellya::do_file(const std::string &filename) {
|
||||||
|
std::ifstream file(filename);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
d->last_error = "Failed to open file: " + filename;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::stringstream buffer;
|
||||||
|
buffer << file.rdbuf();
|
||||||
|
return do_string(buffer.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Camellya::register_function(const std::string &name, NativeFunction func) {
|
||||||
|
auto func_value = std::make_shared<FunctionValue>(name, func);
|
||||||
|
d->vm->register_native_function(name, func);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Camellya::get_top() const { return static_cast<int>(d->stack.size()); }
|
||||||
|
ValuePtr Camellya::get_global(const std::string &name) {
|
||||||
|
return d->vm->get_global(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Camellya::set_global(const std::string &name, ValuePtr value) {
|
||||||
|
d->vm->set_global(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Camellya::push_number(double value) {
|
||||||
|
d->stack.push_back(std::make_shared<NumberValue>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Camellya::push_string(const std::string &value) {
|
||||||
|
d->stack.push_back(std::make_shared<StringValue>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Camellya::push_bool(bool value) {
|
||||||
|
d->stack.push_back(std::make_shared<BoolValue>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Camellya::push_nil() { d->stack.push_back(std::make_shared<NilValue>()); }
|
||||||
|
|
||||||
|
void Camellya::push_value(ValuePtr value) {
|
||||||
|
d->stack.push_back(std::move(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
double Camellya::to_number(int index) {
|
||||||
|
ValuePtr val = get_stack_value(index);
|
||||||
|
if (val && val->type() == Type::NUMBER) {
|
||||||
|
return std::dynamic_pointer_cast<NumberValue>(val)->value;
|
||||||
|
}
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Camellya::to_string(int index) {
|
||||||
|
ValuePtr val = get_stack_value(index);
|
||||||
|
if (val) {
|
||||||
|
return val->to_string();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Camellya::to_bool(int index) {
|
||||||
|
ValuePtr val = get_stack_value(index);
|
||||||
|
return is_truthy(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
ValuePtr Camellya::to_value(int index) { return get_stack_value(index); }
|
||||||
|
|
||||||
|
void Camellya::set_top(int index) {
|
||||||
|
if (index < 0) {
|
||||||
|
index = static_cast<int>(d->stack.size()) + index + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < 0) {
|
||||||
|
d->stack.clear();
|
||||||
|
} else if (static_cast<size_t>(index) < d->stack.size()) {
|
||||||
|
d->stack.resize(index);
|
||||||
|
} else {
|
||||||
|
while (static_cast<size_t>(index) > d->stack.size()) {
|
||||||
|
d->stack.push_back(std::make_shared<NilValue>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Camellya::pop(int n) {
|
||||||
|
if (n > static_cast<int>(d->stack.size())) {
|
||||||
|
d->stack.clear();
|
||||||
|
} else {
|
||||||
|
d->stack.resize(d->stack.size() - n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ValuePtr Camellya::get_stack_value(int index) {
|
||||||
|
if (index < 0) {
|
||||||
|
index = static_cast<int>(d->stack.size()) + index;
|
||||||
|
} else {
|
||||||
|
index -= 1; // Convert to 0-based
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < 0 || static_cast<size_t>(index) >= d->stack.size()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return d->stack[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string &Camellya::get_error() const { return d->last_error; }
|
||||||
|
|
||||||
|
} // namespace camellya
|
||||||
@@ -1,28 +1,59 @@
|
|||||||
#ifndef CAMELLYA_LIBRARY_H
|
#ifndef CAMELLYA_STATE_H
|
||||||
#define CAMELLYA_LIBRARY_H
|
#define CAMELLYA_STATE_H
|
||||||
|
|
||||||
// Camellya Scripting Language
|
|
||||||
// A Lua-like scripting language with 0-based indexing,
|
|
||||||
// distinct list/map types, and class support
|
|
||||||
|
|
||||||
#include "state.h"
|
|
||||||
#include "value.h"
|
#include "value.h"
|
||||||
#include "lexer.h"
|
#include <memory>
|
||||||
#include "parser.h"
|
#include <string>
|
||||||
#include "interpreter.h"
|
|
||||||
#include "ast.h"
|
|
||||||
#include "vm.h"
|
|
||||||
#include "compiler.h"
|
|
||||||
#include "chunk.h"
|
|
||||||
#include "opcode.h"
|
|
||||||
#include "exceptions.h"
|
|
||||||
|
|
||||||
namespace camellya {
|
namespace camellya {
|
||||||
|
|
||||||
// Version info
|
// Main state class - similar to lua_State
|
||||||
constexpr const char* VERSION = "0.1.0";
|
class CamellyaImpl;
|
||||||
constexpr const char* VERSION_NAME = "Camellya";
|
class Camellya {
|
||||||
|
public:
|
||||||
|
Camellya();
|
||||||
|
~Camellya();
|
||||||
|
|
||||||
|
// Execute script from string
|
||||||
|
bool do_string(const std::string &script);
|
||||||
|
|
||||||
|
// Execute script from file
|
||||||
|
bool do_file(const std::string &filename);
|
||||||
|
|
||||||
|
// Register native function
|
||||||
|
void register_function(const std::string &name, NativeFunction func);
|
||||||
|
|
||||||
|
// Get global variable
|
||||||
|
ValuePtr get_global(const std::string &name);
|
||||||
|
|
||||||
|
// Set global variable
|
||||||
|
void set_global(const std::string &name, ValuePtr value);
|
||||||
|
|
||||||
|
// Stack operations (Lua-like API)
|
||||||
|
void push_number(double value);
|
||||||
|
void push_string(const std::string &value);
|
||||||
|
void push_bool(bool value);
|
||||||
|
void push_nil();
|
||||||
|
void push_value(ValuePtr value);
|
||||||
|
|
||||||
|
double to_number(int index);
|
||||||
|
std::string to_string(int index);
|
||||||
|
bool to_bool(int index);
|
||||||
|
ValuePtr to_value(int index);
|
||||||
|
|
||||||
|
int get_top() const;
|
||||||
|
void set_top(int index);
|
||||||
|
void pop(int n = 1);
|
||||||
|
|
||||||
|
// Error handling
|
||||||
|
const std::string &get_error() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<CamellyaImpl> d;
|
||||||
|
|
||||||
|
ValuePtr get_stack_value(int index);
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace camellya
|
} // namespace camellya
|
||||||
|
|
||||||
#endif // CAMELLYA_LIBRARY_H
|
#endif // CAMELLYA_STATE_H
|
||||||
|
|||||||
1086
src/compiler.cpp
1086
src/compiler.cpp
File diff suppressed because it is too large
Load Diff
144
src/compiler.h
144
src/compiler.h
@@ -4,96 +4,94 @@
|
|||||||
#include "ast.h"
|
#include "ast.h"
|
||||||
#include "chunk.h"
|
#include "chunk.h"
|
||||||
#include "value.h"
|
#include "value.h"
|
||||||
#include "exceptions.h"
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <map>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace camellya {
|
namespace camellya {
|
||||||
|
|
||||||
// Local variable information
|
// Local variable information
|
||||||
struct Local {
|
struct Local {
|
||||||
std::string name;
|
std::string name;
|
||||||
int depth;
|
int depth;
|
||||||
bool is_captured;
|
bool is_captured;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Loop information for break/continue
|
// Loop information for break/continue
|
||||||
struct LoopInfo {
|
struct LoopInfo {
|
||||||
size_t start; // Loop start position (for continue)
|
size_t start; // Loop start position (for continue)
|
||||||
std::vector<size_t> breaks; // Break jump positions to patch
|
std::vector<size_t> breaks; // Break jump positions to patch
|
||||||
int scope_depth; // Scope depth at loop start
|
int scope_depth; // Scope depth at loop start
|
||||||
};
|
};
|
||||||
|
|
||||||
// Compiler class for converting AST to bytecode
|
// Compiler class for converting AST to bytecode
|
||||||
class Compiler {
|
class Compiler {
|
||||||
public:
|
public:
|
||||||
Compiler();
|
Compiler();
|
||||||
|
|
||||||
// Compile a program into a chunk
|
// Compile a program into a chunk
|
||||||
std::shared_ptr<Chunk> compile(const Program& program);
|
std::shared_ptr<Chunk> compile(const Program &program);
|
||||||
|
|
||||||
// Get the last error message
|
// Get the last error message
|
||||||
const std::string& get_error() const { return error_message; }
|
const std::string &get_error() const { return error_message; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<Chunk> current_chunk;
|
std::shared_ptr<Chunk> current_chunk;
|
||||||
std::vector<Local> locals;
|
std::vector<Local> locals;
|
||||||
std::vector<LoopInfo> loops; // Stack of loop information
|
std::vector<LoopInfo> loops; // Stack of loop information
|
||||||
int scope_depth;
|
int scope_depth;
|
||||||
std::string error_message;
|
std::string error_message;
|
||||||
bool had_error;
|
bool had_error;
|
||||||
|
|
||||||
// Compilation methods for expressions
|
// Compilation methods for expressions
|
||||||
void compile_expr(const Expr& expr);
|
void compile_expr(const Expr &expr);
|
||||||
void compile_binary(const BinaryExpr& expr);
|
void compile_binary(const BinaryExpr &expr);
|
||||||
void compile_unary(const UnaryExpr& expr);
|
void compile_unary(const UnaryExpr &expr);
|
||||||
void compile_literal(const LiteralExpr& expr);
|
void compile_literal(const LiteralExpr &expr);
|
||||||
void compile_variable(const VariableExpr& expr);
|
void compile_variable(const VariableExpr &expr);
|
||||||
void compile_assign(const AssignExpr& expr);
|
void compile_assign(const AssignExpr &expr);
|
||||||
void compile_call(const CallExpr& expr);
|
void compile_call(const CallExpr &expr);
|
||||||
void compile_get(const GetExpr& expr);
|
void compile_get(const GetExpr &expr);
|
||||||
void compile_set(const SetExpr& expr);
|
void compile_set(const SetExpr &expr);
|
||||||
void compile_index(const IndexExpr& expr);
|
void compile_index(const IndexExpr &expr);
|
||||||
void compile_index_set(const IndexSetExpr& expr);
|
void compile_index_set(const IndexSetExpr &expr);
|
||||||
void compile_list(const ListExpr& expr);
|
void compile_list(const ListExpr &expr);
|
||||||
void compile_map(const MapExpr& expr);
|
void compile_map(const MapExpr &expr);
|
||||||
|
|
||||||
// Compilation methods for statements
|
// Compilation methods for statements
|
||||||
void compile_stmt(const Stmt& stmt);
|
void compile_stmt(const Stmt &stmt);
|
||||||
void compile_expr_stmt(const ExprStmt& stmt);
|
void compile_expr_stmt(const ExprStmt &stmt);
|
||||||
void compile_var_decl(const VarDecl& stmt);
|
void compile_var_decl(const VarDecl &stmt);
|
||||||
void compile_block(const BlockStmt& stmt);
|
void compile_block(const BlockStmt &stmt);
|
||||||
void compile_if(const IfStmt& stmt);
|
void compile_if(const IfStmt &stmt);
|
||||||
void compile_while(const WhileStmt& stmt);
|
void compile_while(const WhileStmt &stmt);
|
||||||
void compile_for(const ForStmt& stmt);
|
void compile_for(const ForStmt &stmt);
|
||||||
void compile_return(const ReturnStmt& stmt);
|
void compile_return(const ReturnStmt &stmt);
|
||||||
void compile_break(const BreakStmt& stmt);
|
void compile_break(const BreakStmt &stmt);
|
||||||
void compile_continue(const ContinueStmt& stmt);
|
void compile_continue(const ContinueStmt &stmt);
|
||||||
void compile_function_decl(const FunctionDecl& stmt);
|
void compile_function_decl(const FunctionDecl &stmt);
|
||||||
void compile_class_decl(const ClassDecl& stmt);
|
void compile_class_decl(const ClassDecl &stmt);
|
||||||
|
|
||||||
// Helper methods
|
// Helper methods
|
||||||
void emit_byte(uint8_t byte);
|
void emit_byte(uint8_t byte);
|
||||||
void emit_opcode(OpCode op);
|
void emit_opcode(OpCode op);
|
||||||
void emit_bytes(uint8_t byte1, uint8_t byte2);
|
void emit_bytes(uint8_t byte1, uint8_t byte2);
|
||||||
void emit_constant(ValuePtr value);
|
void emit_constant(ValuePtr value);
|
||||||
size_t emit_jump(OpCode op);
|
size_t emit_jump(OpCode op);
|
||||||
void patch_jump(size_t offset);
|
void patch_jump(size_t offset);
|
||||||
void emit_loop(size_t loop_start);
|
void emit_loop(size_t loop_start);
|
||||||
|
|
||||||
// Variable management
|
// Variable management
|
||||||
void begin_scope();
|
void begin_scope();
|
||||||
void end_scope();
|
void end_scope();
|
||||||
void add_local(const std::string& name);
|
void add_local(const std::string &name);
|
||||||
int resolve_local(const std::string& name);
|
int resolve_local(const std::string &name);
|
||||||
|
|
||||||
// Constant pool
|
// Constant pool
|
||||||
uint8_t make_constant(ValuePtr value);
|
uint8_t make_constant(ValuePtr value);
|
||||||
uint8_t identifier_constant(const std::string& name);
|
uint8_t identifier_constant(const std::string &name);
|
||||||
|
|
||||||
// Error handling
|
// Error handling
|
||||||
void report_error(const std::string& message);
|
void report_error(const std::string &message);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace camellya
|
} // namespace camellya
|
||||||
|
|||||||
@@ -1,567 +0,0 @@
|
|||||||
#include "interpreter.h"
|
|
||||||
#include <iostream>
|
|
||||||
#include <format>
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
namespace camellya {
|
|
||||||
|
|
||||||
Interpreter::Interpreter() {
|
|
||||||
global_environment = std::make_shared<Environment>();
|
|
||||||
environment = global_environment;
|
|
||||||
register_native_functions();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::register_native_functions() {
|
|
||||||
// print function - supports format strings
|
|
||||||
auto print_func = std::make_shared<FunctionValue>("print",
|
|
||||||
[](const std::vector<ValuePtr>& args) -> ValuePtr {
|
|
||||||
if (args.empty()) {
|
|
||||||
std::cout << std::endl;
|
|
||||||
return std::make_shared<NilValue>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simple print: just concatenate all arguments
|
|
||||||
for (size_t i = 0; i < args.size(); ++i) {
|
|
||||||
if (i > 0) std::cout << " ";
|
|
||||||
std::cout << args[i]->to_string();
|
|
||||||
}
|
|
||||||
std::cout << std::endl;
|
|
||||||
|
|
||||||
return std::make_shared<NilValue>();
|
|
||||||
});
|
|
||||||
global_environment->define("print", print_func);
|
|
||||||
|
|
||||||
// len function
|
|
||||||
auto len_func = std::make_shared<FunctionValue>("len",
|
|
||||||
[](const std::vector<ValuePtr>& args) -> ValuePtr {
|
|
||||||
if (args.size() != 1) {
|
|
||||||
throw RuntimeError("len() expects 1 argument.");
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& arg = args[0];
|
|
||||||
if (arg->type() == Type::LIST) {
|
|
||||||
auto list = std::dynamic_pointer_cast<ListValue>(arg);
|
|
||||||
return std::make_shared<NumberValue>(static_cast<double>(list->size()));
|
|
||||||
} else if (arg->type() == Type::STRING) {
|
|
||||||
auto str = std::dynamic_pointer_cast<StringValue>(arg);
|
|
||||||
return std::make_shared<NumberValue>(static_cast<double>(str->value.length()));
|
|
||||||
} else if (arg->type() == Type::MAP) {
|
|
||||||
auto map = std::dynamic_pointer_cast<MapValue>(arg);
|
|
||||||
return std::make_shared<NumberValue>(static_cast<double>(map->pairs.size()));
|
|
||||||
}
|
|
||||||
|
|
||||||
throw RuntimeError("len() expects list, string, or map.");
|
|
||||||
});
|
|
||||||
global_environment->define("len", len_func);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::execute(const Program& program) {
|
|
||||||
for (const auto& stmt : program.statements) {
|
|
||||||
execute_statement(*stmt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ValuePtr Interpreter::evaluate(const Expr& expr) {
|
|
||||||
if (auto* binary = dynamic_cast<const BinaryExpr*>(&expr)) {
|
|
||||||
return eval_binary(*binary);
|
|
||||||
} else if (auto* unary = dynamic_cast<const UnaryExpr*>(&expr)) {
|
|
||||||
return eval_unary(*unary);
|
|
||||||
} else if (auto* literal = dynamic_cast<const LiteralExpr*>(&expr)) {
|
|
||||||
return eval_literal(*literal);
|
|
||||||
} else if (auto* variable = dynamic_cast<const VariableExpr*>(&expr)) {
|
|
||||||
return eval_variable(*variable);
|
|
||||||
} else if (auto* assign = dynamic_cast<const AssignExpr*>(&expr)) {
|
|
||||||
return eval_assign(*assign);
|
|
||||||
} else if (auto* call = dynamic_cast<const CallExpr*>(&expr)) {
|
|
||||||
return eval_call(*call);
|
|
||||||
} else if (auto* get = dynamic_cast<const GetExpr*>(&expr)) {
|
|
||||||
return eval_get(*get);
|
|
||||||
} else if (auto* set = dynamic_cast<const SetExpr*>(&expr)) {
|
|
||||||
return eval_set(*set);
|
|
||||||
} else if (auto* index = dynamic_cast<const IndexExpr*>(&expr)) {
|
|
||||||
return eval_index(*index);
|
|
||||||
} else if (auto* index_set = dynamic_cast<const IndexSetExpr*>(&expr)) {
|
|
||||||
return eval_index_set(*index_set);
|
|
||||||
} else if (auto* list = dynamic_cast<const ListExpr*>(&expr)) {
|
|
||||||
return eval_list(*list);
|
|
||||||
} else if (auto* map = dynamic_cast<const MapExpr*>(&expr)) {
|
|
||||||
return eval_map(*map);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw RuntimeError("Unknown expression type.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::execute_statement(const Stmt& stmt) {
|
|
||||||
if (auto* expr_stmt = dynamic_cast<const ExprStmt*>(&stmt)) {
|
|
||||||
exec_expr_stmt(*expr_stmt);
|
|
||||||
} else if (auto* var_decl = dynamic_cast<const VarDecl*>(&stmt)) {
|
|
||||||
exec_var_decl(*var_decl);
|
|
||||||
} else if (auto* block = dynamic_cast<const BlockStmt*>(&stmt)) {
|
|
||||||
exec_block(*block);
|
|
||||||
} else if (auto* if_stmt = dynamic_cast<const IfStmt*>(&stmt)) {
|
|
||||||
exec_if(*if_stmt);
|
|
||||||
} else if (auto* while_stmt = dynamic_cast<const WhileStmt*>(&stmt)) {
|
|
||||||
exec_while(*while_stmt);
|
|
||||||
} else if (auto* for_stmt = dynamic_cast<const ForStmt*>(&stmt)) {
|
|
||||||
exec_for(*for_stmt);
|
|
||||||
} else if (auto* return_stmt = dynamic_cast<const ReturnStmt*>(&stmt)) {
|
|
||||||
exec_return(*return_stmt);
|
|
||||||
} else if (auto* break_stmt = dynamic_cast<const BreakStmt*>(&stmt)) {
|
|
||||||
exec_break(*break_stmt);
|
|
||||||
} else if (auto* continue_stmt = dynamic_cast<const ContinueStmt*>(&stmt)) {
|
|
||||||
exec_continue(*continue_stmt);
|
|
||||||
} else if (auto* func_decl = dynamic_cast<const FunctionDecl*>(&stmt)) {
|
|
||||||
exec_function_decl(*func_decl);
|
|
||||||
} else if (auto* class_decl = dynamic_cast<const ClassDecl*>(&stmt)) {
|
|
||||||
exec_class_decl(*class_decl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ValuePtr Interpreter::eval_binary(const BinaryExpr& expr) {
|
|
||||||
ValuePtr left = evaluate(*expr.left);
|
|
||||||
ValuePtr right = evaluate(*expr.right);
|
|
||||||
|
|
||||||
if (expr.op == "+") {
|
|
||||||
if (left->type() == Type::NUMBER && right->type() == Type::NUMBER) {
|
|
||||||
double l = std::dynamic_pointer_cast<NumberValue>(left)->value;
|
|
||||||
double r = std::dynamic_pointer_cast<NumberValue>(right)->value;
|
|
||||||
return std::make_shared<NumberValue>(l + r);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (left->type() == Type::STRING && right->type() == Type::STRING) {
|
|
||||||
const auto& l = std::dynamic_pointer_cast<StringValue>(left)->value;
|
|
||||||
const auto& r = std::dynamic_pointer_cast<StringValue>(right)->value;
|
|
||||||
return std::make_shared<StringValue>(l + r);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw RuntimeError("Operands of '+' must be both numbers or both strings.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expr.op == "-" || expr.op == "*" ||
|
|
||||||
expr.op == "/" || expr.op == "%") {
|
|
||||||
if (left->type() != Type::NUMBER || right->type() != Type::NUMBER) {
|
|
||||||
throw RuntimeError("Operands must be numbers.");
|
|
||||||
}
|
|
||||||
|
|
||||||
double l = std::dynamic_pointer_cast<NumberValue>(left)->value;
|
|
||||||
double r = std::dynamic_pointer_cast<NumberValue>(right)->value;
|
|
||||||
|
|
||||||
if (expr.op == "-") return std::make_shared<NumberValue>(l - r);
|
|
||||||
if (expr.op == "*") return std::make_shared<NumberValue>(l * r);
|
|
||||||
if (expr.op == "/") {
|
|
||||||
if (r == 0) throw RuntimeError("Division by zero.");
|
|
||||||
return std::make_shared<NumberValue>(l / r);
|
|
||||||
}
|
|
||||||
if (expr.op == "%") return std::make_shared<NumberValue>(std::fmod(l, r));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expr.op == "==" ) {
|
|
||||||
return std::make_shared<BoolValue>(values_equal(left, right));
|
|
||||||
}
|
|
||||||
if (expr.op == "!=") {
|
|
||||||
return std::make_shared<BoolValue>(!values_equal(left, right));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expr.op == "<" || expr.op == "<=" || expr.op == ">" || expr.op == ">=") {
|
|
||||||
if (left->type() != Type::NUMBER || right->type() != Type::NUMBER) {
|
|
||||||
throw RuntimeError("Operands must be numbers.");
|
|
||||||
}
|
|
||||||
|
|
||||||
double l = std::dynamic_pointer_cast<NumberValue>(left)->value;
|
|
||||||
double r = std::dynamic_pointer_cast<NumberValue>(right)->value;
|
|
||||||
|
|
||||||
if (expr.op == "<") return std::make_shared<BoolValue>(l < r);
|
|
||||||
if (expr.op == "<=") return std::make_shared<BoolValue>(l <= r);
|
|
||||||
if (expr.op == ">") return std::make_shared<BoolValue>(l > r);
|
|
||||||
if (expr.op == ">=") return std::make_shared<BoolValue>(l >= r);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expr.op == "and") {
|
|
||||||
if (!is_truthy(left)) return left;
|
|
||||||
return right;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expr.op == "or") {
|
|
||||||
if (is_truthy(left)) return left;
|
|
||||||
return right;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw RuntimeError("Unknown binary operator: " + expr.op);
|
|
||||||
}
|
|
||||||
|
|
||||||
ValuePtr Interpreter::eval_unary(const UnaryExpr& expr) {
|
|
||||||
ValuePtr operand = evaluate(*expr.operand);
|
|
||||||
|
|
||||||
if (expr.op == "-") {
|
|
||||||
if (operand->type() != Type::NUMBER) {
|
|
||||||
throw RuntimeError("Operand must be a number.");
|
|
||||||
}
|
|
||||||
double value = std::dynamic_pointer_cast<NumberValue>(operand)->value;
|
|
||||||
return std::make_shared<NumberValue>(-value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expr.op == "!") {
|
|
||||||
return std::make_shared<BoolValue>(!is_truthy(operand));
|
|
||||||
}
|
|
||||||
|
|
||||||
throw RuntimeError("Unknown unary operator: " + expr.op);
|
|
||||||
}
|
|
||||||
|
|
||||||
ValuePtr Interpreter::eval_literal(const LiteralExpr& expr) {
|
|
||||||
return std::visit([](auto&& arg) -> ValuePtr {
|
|
||||||
using T = std::decay_t<decltype(arg)>;
|
|
||||||
if constexpr (std::is_same_v<T, double>) {
|
|
||||||
return std::make_shared<NumberValue>(arg);
|
|
||||||
} else if constexpr (std::is_same_v<T, std::string>) {
|
|
||||||
return std::make_shared<StringValue>(arg);
|
|
||||||
} else if constexpr (std::is_same_v<T, bool>) {
|
|
||||||
return std::make_shared<BoolValue>(arg);
|
|
||||||
} else {
|
|
||||||
return std::make_shared<NilValue>();
|
|
||||||
}
|
|
||||||
}, expr.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
ValuePtr Interpreter::eval_variable(const VariableExpr& expr) {
|
|
||||||
return environment->get(expr.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
ValuePtr Interpreter::eval_assign(const AssignExpr& expr) {
|
|
||||||
ValuePtr value = evaluate(*expr.value);
|
|
||||||
environment->set(expr.name, value);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
ValuePtr Interpreter::eval_call(const CallExpr& expr) {
|
|
||||||
ValuePtr callee = evaluate(*expr.callee);
|
|
||||||
|
|
||||||
std::vector<ValuePtr> arguments;
|
|
||||||
for (const auto& arg : expr.arguments) {
|
|
||||||
arguments.push_back(evaluate(*arg));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (callee->type() == Type::FUNCTION) {
|
|
||||||
auto func = std::dynamic_pointer_cast<FunctionValue>(callee);
|
|
||||||
return call_function(*func, arguments);
|
|
||||||
} else if (callee->type() == Type::CLASS) {
|
|
||||||
// Class instantiation
|
|
||||||
auto klass = std::dynamic_pointer_cast<ClassValue>(callee);
|
|
||||||
auto instance = std::make_shared<InstanceValue>(klass);
|
|
||||||
|
|
||||||
// If there is an init method, call it like a constructor
|
|
||||||
ValuePtr init_val = instance->get("init");
|
|
||||||
if (init_val && init_val->type() == Type::FUNCTION) {
|
|
||||||
auto init_func = std::dynamic_pointer_cast<FunctionValue>(init_val);
|
|
||||||
call_function(*init_func, arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw RuntimeError("Can only call functions and classes.");
|
|
||||||
}
|
|
||||||
|
|
||||||
ValuePtr Interpreter::eval_get(const GetExpr& expr) {
|
|
||||||
ValuePtr object = evaluate(*expr.object);
|
|
||||||
|
|
||||||
if (object->type() == Type::INSTANCE) {
|
|
||||||
auto instance = std::dynamic_pointer_cast<InstanceValue>(object);
|
|
||||||
return instance->get(expr.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw RuntimeError("Only instances have properties.");
|
|
||||||
}
|
|
||||||
|
|
||||||
ValuePtr Interpreter::eval_set(const SetExpr& expr) {
|
|
||||||
ValuePtr object = evaluate(*expr.object);
|
|
||||||
|
|
||||||
if (object->type() == Type::INSTANCE) {
|
|
||||||
auto instance = std::dynamic_pointer_cast<InstanceValue>(object);
|
|
||||||
ValuePtr value = evaluate(*expr.value);
|
|
||||||
instance->set(expr.name, value);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw RuntimeError("Only instances have fields.");
|
|
||||||
}
|
|
||||||
|
|
||||||
ValuePtr Interpreter::eval_index(const IndexExpr& expr) {
|
|
||||||
ValuePtr object = evaluate(*expr.object);
|
|
||||||
ValuePtr index = evaluate(*expr.index);
|
|
||||||
|
|
||||||
if (object->type() == Type::LIST) {
|
|
||||||
auto list = std::dynamic_pointer_cast<ListValue>(object);
|
|
||||||
if (index->type() != Type::NUMBER) {
|
|
||||||
throw RuntimeError("List index must be a number.");
|
|
||||||
}
|
|
||||||
size_t idx = static_cast<size_t>(std::dynamic_pointer_cast<NumberValue>(index)->value);
|
|
||||||
return list->get(idx);
|
|
||||||
} else if (object->type() == Type::MAP) {
|
|
||||||
auto map = std::dynamic_pointer_cast<MapValue>(object);
|
|
||||||
if (index->type() != Type::STRING) {
|
|
||||||
throw RuntimeError("Map key must be a string.");
|
|
||||||
}
|
|
||||||
std::string key = std::dynamic_pointer_cast<StringValue>(index)->value;
|
|
||||||
return map->get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw RuntimeError("Only lists and maps support indexing.");
|
|
||||||
}
|
|
||||||
|
|
||||||
ValuePtr Interpreter::eval_index_set(const IndexSetExpr& expr) {
|
|
||||||
ValuePtr object = evaluate(*expr.object);
|
|
||||||
ValuePtr index = evaluate(*expr.index);
|
|
||||||
ValuePtr value = evaluate(*expr.value);
|
|
||||||
|
|
||||||
if (object->type() == Type::LIST) {
|
|
||||||
auto list = std::dynamic_pointer_cast<ListValue>(object);
|
|
||||||
if (index->type() != Type::NUMBER) {
|
|
||||||
throw RuntimeError("List index must be a number.");
|
|
||||||
}
|
|
||||||
size_t idx = static_cast<size_t>(std::dynamic_pointer_cast<NumberValue>(index)->value);
|
|
||||||
list->set(idx, value);
|
|
||||||
return value;
|
|
||||||
} else if (object->type() == Type::MAP) {
|
|
||||||
auto map = std::dynamic_pointer_cast<MapValue>(object);
|
|
||||||
if (index->type() != Type::STRING) {
|
|
||||||
throw RuntimeError("Map key must be a string.");
|
|
||||||
}
|
|
||||||
std::string key = std::dynamic_pointer_cast<StringValue>(index)->value;
|
|
||||||
map->set(key, value);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw RuntimeError("Only lists and maps support index assignment.");
|
|
||||||
}
|
|
||||||
|
|
||||||
ValuePtr Interpreter::eval_list(const ListExpr& expr) {
|
|
||||||
auto list = std::make_shared<ListValue>();
|
|
||||||
for (const auto& elem : expr.elements) {
|
|
||||||
list->push(evaluate(*elem));
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
ValuePtr Interpreter::eval_map(const MapExpr& expr) {
|
|
||||||
auto map = std::make_shared<MapValue>();
|
|
||||||
for (const auto& [key_expr, value_expr] : expr.pairs) {
|
|
||||||
ValuePtr key = evaluate(*key_expr);
|
|
||||||
ValuePtr value = evaluate(*value_expr);
|
|
||||||
|
|
||||||
if (key->type() != Type::STRING) {
|
|
||||||
throw RuntimeError("Map keys must be strings.");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string key_str = std::dynamic_pointer_cast<StringValue>(key)->value;
|
|
||||||
map->set(key_str, value);
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::exec_expr_stmt(const ExprStmt& stmt) {
|
|
||||||
evaluate(*stmt.expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::exec_var_decl(const VarDecl& stmt) {
|
|
||||||
ValuePtr value;
|
|
||||||
|
|
||||||
if (stmt.initializer) {
|
|
||||||
value = evaluate(*stmt.initializer);
|
|
||||||
|
|
||||||
// If type inference is needed, we use the value directly
|
|
||||||
if (stmt.infer_type) {
|
|
||||||
// Type is automatically inferred from the initializer value
|
|
||||||
environment->define(stmt.name, value);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If explicit type is provided, verify the value matches
|
|
||||||
// (This is a simple check, could be more sophisticated)
|
|
||||||
if (!stmt.type_name.empty()) {
|
|
||||||
// Just use the value; type checking could be added here
|
|
||||||
environment->define(stmt.name, value);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// No initializer, use default value based on type
|
|
||||||
value = create_default_value(stmt.type_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
environment->define(stmt.name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::exec_block(const BlockStmt& stmt) {
|
|
||||||
auto previous = environment;
|
|
||||||
environment = std::make_shared<Environment>(environment);
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (const auto& statement : stmt.statements) {
|
|
||||||
execute_statement(*statement);
|
|
||||||
}
|
|
||||||
} catch (...) {
|
|
||||||
environment = previous;
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
environment = previous;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::exec_if(const IfStmt& stmt) {
|
|
||||||
ValuePtr condition = evaluate(*stmt.condition);
|
|
||||||
|
|
||||||
if (is_truthy(condition)) {
|
|
||||||
execute_statement(*stmt.then_branch);
|
|
||||||
} else if (stmt.else_branch) {
|
|
||||||
execute_statement(*stmt.else_branch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::exec_while(const WhileStmt& stmt) {
|
|
||||||
try {
|
|
||||||
while (is_truthy(evaluate(*stmt.condition))) {
|
|
||||||
try {
|
|
||||||
execute_statement(*stmt.body);
|
|
||||||
} catch (const ContinueException&) {
|
|
||||||
// Continue: just proceed to the next iteration (check condition again)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (const BreakException&) {
|
|
||||||
// Break: exit the loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::exec_for(const ForStmt& stmt) {
|
|
||||||
auto previous = environment;
|
|
||||||
environment = std::make_shared<Environment>(environment);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (stmt.initializer) {
|
|
||||||
execute_statement(*stmt.initializer);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!stmt.condition || is_truthy(evaluate(*stmt.condition))) {
|
|
||||||
try {
|
|
||||||
execute_statement(*stmt.body);
|
|
||||||
} catch (const ContinueException&) {
|
|
||||||
// Continue: proceed to increment
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stmt.increment) {
|
|
||||||
evaluate(*stmt.increment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (const BreakException&) {
|
|
||||||
// Break: exit the loop
|
|
||||||
} catch (...) {
|
|
||||||
environment = previous;
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
environment = previous;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::exec_return(const ReturnStmt& stmt) {
|
|
||||||
ValuePtr value = stmt.value ? evaluate(*stmt.value) : std::make_shared<NilValue>();
|
|
||||||
throw ReturnException(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::exec_break(const BreakStmt& stmt) {
|
|
||||||
throw BreakException();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::exec_continue(const ContinueStmt& stmt) {
|
|
||||||
throw ContinueException();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::exec_function_decl(const FunctionDecl& stmt) {
|
|
||||||
auto func_decl = std::make_shared<FunctionDecl>(stmt);
|
|
||||||
auto func = std::make_shared<FunctionValue>(stmt.name, func_decl);
|
|
||||||
environment->define(stmt.name, func);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::exec_class_decl(const ClassDecl& stmt) {
|
|
||||||
auto klass = std::make_shared<ClassValue>(stmt.name);
|
|
||||||
|
|
||||||
for (const auto& member : stmt.members) {
|
|
||||||
if (auto* var_decl = dynamic_cast<VarDecl*>(member.get())) {
|
|
||||||
klass->add_field(var_decl->name, var_decl->type_name);
|
|
||||||
} else if (auto* func_decl = dynamic_cast<FunctionDecl*>(member.get())) {
|
|
||||||
auto func_decl_ptr = std::make_shared<FunctionDecl>(*func_decl);
|
|
||||||
auto func = std::make_shared<FunctionValue>(func_decl->name, func_decl_ptr);
|
|
||||||
klass->add_method(func_decl->name, func);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
environment->define(stmt.name, klass);
|
|
||||||
}
|
|
||||||
|
|
||||||
ValuePtr Interpreter::call_function(const FunctionValue& func, const std::vector<ValuePtr>& arguments) {
|
|
||||||
if (func.is_native) {
|
|
||||||
return func.native_func(arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind parameters
|
|
||||||
if (func.declaration->parameters.size() != arguments.size()) {
|
|
||||||
throw RuntimeError(std::format("Expected {} arguments but got {}.",
|
|
||||||
func.declaration->parameters.size(),
|
|
||||||
arguments.size()));
|
|
||||||
}
|
|
||||||
|
|
||||||
auto previous = environment;
|
|
||||||
environment = std::make_shared<Environment>(global_environment);
|
|
||||||
|
|
||||||
// Bind 'this' for methods
|
|
||||||
if (func.bound_instance) {
|
|
||||||
environment->define("this", func.bound_instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind parameters to the new environment
|
|
||||||
for (size_t i = 0; i < arguments.size(); ++i) {
|
|
||||||
environment->define(func.declaration->parameters[i].second, arguments[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
execute_statement(*func.declaration->body);
|
|
||||||
} catch (const ReturnException& ret) {
|
|
||||||
environment = previous;
|
|
||||||
return ret.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
environment = previous;
|
|
||||||
return std::make_shared<NilValue>();
|
|
||||||
}
|
|
||||||
|
|
||||||
ValuePtr Interpreter::create_default_value(const std::string& type_name) {
|
|
||||||
if (type_name == "number") {
|
|
||||||
return std::make_shared<NumberValue>(0.0);
|
|
||||||
} else if (type_name == "string") {
|
|
||||||
return std::make_shared<StringValue>("");
|
|
||||||
} else if (type_name == "bool") {
|
|
||||||
return std::make_shared<BoolValue>(false);
|
|
||||||
} else if (type_name == "list") {
|
|
||||||
return std::make_shared<ListValue>();
|
|
||||||
} else if (type_name == "map") {
|
|
||||||
return std::make_shared<MapValue>();
|
|
||||||
} else if (environment->has(type_name)) {
|
|
||||||
// It's a class type
|
|
||||||
ValuePtr klass = environment->get(type_name);
|
|
||||||
if (klass->type() == Type::CLASS) {
|
|
||||||
auto klass_ptr = std::dynamic_pointer_cast<ClassValue>(klass);
|
|
||||||
auto instance = std::make_shared<InstanceValue>(klass_ptr);
|
|
||||||
|
|
||||||
// If the class defines init(), call it with no arguments
|
|
||||||
ValuePtr init_val = instance->get("init");
|
|
||||||
if (init_val && init_val->type() == Type::FUNCTION) {
|
|
||||||
auto init_func = std::dynamic_pointer_cast<FunctionValue>(init_val);
|
|
||||||
call_function(*init_func, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::make_shared<NilValue>();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace camellya
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
#ifndef CAMELLYA_INTERPRETER_H
|
|
||||||
#define CAMELLYA_INTERPRETER_H
|
|
||||||
|
|
||||||
#include "ast.h"
|
|
||||||
#include "value.h"
|
|
||||||
#include "exceptions.h"
|
|
||||||
#include <map>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
namespace camellya {
|
|
||||||
|
|
||||||
class ReturnException : public std::exception {
|
|
||||||
public:
|
|
||||||
ValuePtr value;
|
|
||||||
explicit ReturnException(ValuePtr value) : value(std::move(value)) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class BreakException : public std::exception {};
|
|
||||||
class ContinueException : public std::exception {};
|
|
||||||
|
|
||||||
class Environment {
|
|
||||||
public:
|
|
||||||
std::shared_ptr<Environment> parent;
|
|
||||||
std::map<std::string, ValuePtr> values;
|
|
||||||
|
|
||||||
Environment() : parent(nullptr) {}
|
|
||||||
explicit Environment(std::shared_ptr<Environment> parent) : parent(std::move(parent)) {}
|
|
||||||
|
|
||||||
void define(const std::string& name, ValuePtr value) {
|
|
||||||
values[name] = std::move(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
ValuePtr get(const std::string& name) const {
|
|
||||||
auto it = values.find(name);
|
|
||||||
if (it != values.end()) {
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parent && parent->has(name)) {
|
|
||||||
return parent->get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: resolve as field on 'this' instance if present
|
|
||||||
const Environment* env = this;
|
|
||||||
while (env) {
|
|
||||||
auto this_it = env->values.find("this");
|
|
||||||
if (this_it != env->values.end() &&
|
|
||||||
this_it->second && this_it->second->type() == Type::INSTANCE) {
|
|
||||||
auto instance = std::dynamic_pointer_cast<InstanceValue>(this_it->second);
|
|
||||||
auto field_it = instance->fields.find(name);
|
|
||||||
if (field_it != instance->fields.end()) {
|
|
||||||
return field_it->second;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
env = env->parent.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw RuntimeError("Undefined variable '" + name + "'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void set(const std::string& name, ValuePtr value) {
|
|
||||||
auto it = values.find(name);
|
|
||||||
if (it != values.end()) {
|
|
||||||
it->second = std::move(value);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parent && parent->has(name)) {
|
|
||||||
parent->set(name, std::move(value));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: assign to field on 'this' instance if present
|
|
||||||
Environment* env = this;
|
|
||||||
while (env) {
|
|
||||||
auto this_it = env->values.find("this");
|
|
||||||
if (this_it != env->values.end() &&
|
|
||||||
this_it->second && this_it->second->type() == Type::INSTANCE) {
|
|
||||||
auto instance = std::dynamic_pointer_cast<InstanceValue>(this_it->second);
|
|
||||||
auto field_it = instance->fields.find(name);
|
|
||||||
if (field_it != instance->fields.end()) {
|
|
||||||
field_it->second = std::move(value);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
env = env->parent.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw RuntimeError("Undefined variable '" + name + "'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool has(const std::string& name) const {
|
|
||||||
if (values.find(name) != values.end()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (parent) {
|
|
||||||
return parent->has(name);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class Interpreter {
|
|
||||||
public:
|
|
||||||
Interpreter();
|
|
||||||
|
|
||||||
void execute(const Program& program);
|
|
||||||
ValuePtr evaluate(const Expr& expr);
|
|
||||||
void execute_statement(const Stmt& stmt);
|
|
||||||
|
|
||||||
std::shared_ptr<Environment> environment;
|
|
||||||
std::shared_ptr<Environment> global_environment;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void register_native_functions();
|
|
||||||
|
|
||||||
ValuePtr eval_binary(const BinaryExpr& expr);
|
|
||||||
ValuePtr eval_unary(const UnaryExpr& expr);
|
|
||||||
ValuePtr eval_literal(const LiteralExpr& expr);
|
|
||||||
ValuePtr eval_variable(const VariableExpr& expr);
|
|
||||||
ValuePtr eval_assign(const AssignExpr& expr);
|
|
||||||
ValuePtr eval_call(const CallExpr& expr);
|
|
||||||
ValuePtr eval_get(const GetExpr& expr);
|
|
||||||
ValuePtr eval_set(const SetExpr& expr);
|
|
||||||
ValuePtr eval_index(const IndexExpr& expr);
|
|
||||||
ValuePtr eval_index_set(const IndexSetExpr& expr);
|
|
||||||
ValuePtr eval_list(const ListExpr& expr);
|
|
||||||
ValuePtr eval_map(const MapExpr& expr);
|
|
||||||
|
|
||||||
void exec_expr_stmt(const ExprStmt& stmt);
|
|
||||||
void exec_var_decl(const VarDecl& stmt);
|
|
||||||
void exec_block(const BlockStmt& stmt);
|
|
||||||
void exec_if(const IfStmt& stmt);
|
|
||||||
void exec_while(const WhileStmt& stmt);
|
|
||||||
void exec_for(const ForStmt& stmt);
|
|
||||||
void exec_return(const ReturnStmt& stmt);
|
|
||||||
void exec_break(const BreakStmt& stmt);
|
|
||||||
void exec_continue(const ContinueStmt& stmt);
|
|
||||||
void exec_function_decl(const FunctionDecl& stmt);
|
|
||||||
void exec_class_decl(const ClassDecl& stmt);
|
|
||||||
|
|
||||||
ValuePtr call_function(const FunctionValue& func, const std::vector<ValuePtr>& arguments);
|
|
||||||
|
|
||||||
ValuePtr create_default_value(const std::string& type_name);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace camellya
|
|
||||||
|
|
||||||
#endif // CAMELLYA_INTERPRETER_H
|
|
||||||
146
src/state.cpp
146
src/state.cpp
@@ -1,146 +0,0 @@
|
|||||||
#include "state.h"
|
|
||||||
#include <fstream>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
namespace camellya {
|
|
||||||
|
|
||||||
State::State()
|
|
||||||
: vm_(std::make_unique<VM>()), compiler_(std::make_unique<Compiler>()) {}
|
|
||||||
|
|
||||||
bool State::do_string(const std::string &script) {
|
|
||||||
try {
|
|
||||||
Lexer lexer(script);
|
|
||||||
auto tokens = lexer.tokenize();
|
|
||||||
|
|
||||||
Parser parser(std::move(tokens));
|
|
||||||
Program program = parser.parse();
|
|
||||||
|
|
||||||
bool success = execute_program(program);
|
|
||||||
if (success) {
|
|
||||||
last_error_.clear();
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
} catch (const std::exception &e) {
|
|
||||||
last_error_ = e.what();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool State::do_file(const std::string &filename) {
|
|
||||||
std::ifstream file(filename);
|
|
||||||
if (!file.is_open()) {
|
|
||||||
last_error_ = "Failed to open file: " + filename;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::stringstream buffer;
|
|
||||||
buffer << file.rdbuf();
|
|
||||||
return do_string(buffer.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
void State::register_function(const std::string &name, NativeFunction func) {
|
|
||||||
auto func_value = std::make_shared<FunctionValue>(name, func);
|
|
||||||
vm_->register_native_function(name, func);
|
|
||||||
}
|
|
||||||
|
|
||||||
ValuePtr State::get_global(const std::string &name) {
|
|
||||||
return vm_->get_global(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
void State::set_global(const std::string &name, ValuePtr value) {
|
|
||||||
vm_->set_global(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void State::push_number(double value) {
|
|
||||||
stack_.push_back(std::make_shared<NumberValue>(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
void State::push_string(const std::string &value) {
|
|
||||||
stack_.push_back(std::make_shared<StringValue>(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
void State::push_bool(bool value) {
|
|
||||||
stack_.push_back(std::make_shared<BoolValue>(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
void State::push_nil() { stack_.push_back(std::make_shared<NilValue>()); }
|
|
||||||
|
|
||||||
void State::push_value(ValuePtr value) { stack_.push_back(std::move(value)); }
|
|
||||||
|
|
||||||
double State::to_number(int index) {
|
|
||||||
ValuePtr val = get_stack_value(index);
|
|
||||||
if (val && val->type() == Type::NUMBER) {
|
|
||||||
return std::dynamic_pointer_cast<NumberValue>(val)->value;
|
|
||||||
}
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string State::to_string(int index) {
|
|
||||||
ValuePtr val = get_stack_value(index);
|
|
||||||
if (val) {
|
|
||||||
return val->to_string();
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
bool State::to_bool(int index) {
|
|
||||||
ValuePtr val = get_stack_value(index);
|
|
||||||
return is_truthy(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
ValuePtr State::to_value(int index) { return get_stack_value(index); }
|
|
||||||
|
|
||||||
void State::set_top(int index) {
|
|
||||||
if (index < 0) {
|
|
||||||
index = static_cast<int>(stack_.size()) + index + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index < 0) {
|
|
||||||
stack_.clear();
|
|
||||||
} else if (static_cast<size_t>(index) < stack_.size()) {
|
|
||||||
stack_.resize(index);
|
|
||||||
} else {
|
|
||||||
while (static_cast<size_t>(index) > stack_.size()) {
|
|
||||||
stack_.push_back(std::make_shared<NilValue>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void State::pop(int n) {
|
|
||||||
if (n > static_cast<int>(stack_.size())) {
|
|
||||||
stack_.clear();
|
|
||||||
} else {
|
|
||||||
stack_.resize(stack_.size() - n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ValuePtr State::get_stack_value(int index) {
|
|
||||||
if (index < 0) {
|
|
||||||
index = static_cast<int>(stack_.size()) + index;
|
|
||||||
} else {
|
|
||||||
index -= 1; // Convert to 0-based
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index < 0 || static_cast<size_t>(index) >= stack_.size()) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return stack_[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
bool State::execute_program(const Program &program) {
|
|
||||||
// Use VM
|
|
||||||
auto chunk = compiler_->compile(program);
|
|
||||||
if (!chunk) {
|
|
||||||
last_error_ = compiler_->get_error();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool success = vm_->execute(chunk);
|
|
||||||
if (!success) {
|
|
||||||
last_error_ = vm_->get_error();
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace camellya
|
|
||||||
68
src/state.h
68
src/state.h
@@ -1,68 +0,0 @@
|
|||||||
#ifndef CAMELLYA_STATE_H
|
|
||||||
#define CAMELLYA_STATE_H
|
|
||||||
|
|
||||||
#include "compiler.h"
|
|
||||||
#include "lexer.h"
|
|
||||||
#include "parser.h"
|
|
||||||
#include "value.h"
|
|
||||||
#include "vm.h"
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace camellya {
|
|
||||||
|
|
||||||
// Main state class - similar to lua_State
|
|
||||||
class State {
|
|
||||||
public:
|
|
||||||
State();
|
|
||||||
~State() = default;
|
|
||||||
|
|
||||||
// Execute script from string
|
|
||||||
bool do_string(const std::string &script);
|
|
||||||
|
|
||||||
// Execute script from file
|
|
||||||
bool do_file(const std::string &filename);
|
|
||||||
|
|
||||||
// Register native function
|
|
||||||
void register_function(const std::string &name, NativeFunction func);
|
|
||||||
|
|
||||||
// Get global variable
|
|
||||||
ValuePtr get_global(const std::string &name);
|
|
||||||
|
|
||||||
// Set global variable
|
|
||||||
void set_global(const std::string &name, ValuePtr value);
|
|
||||||
|
|
||||||
// Stack operations (Lua-like API)
|
|
||||||
void push_number(double value);
|
|
||||||
void push_string(const std::string &value);
|
|
||||||
void push_bool(bool value);
|
|
||||||
void push_nil();
|
|
||||||
void push_value(ValuePtr value);
|
|
||||||
|
|
||||||
double to_number(int index);
|
|
||||||
std::string to_string(int index);
|
|
||||||
bool to_bool(int index);
|
|
||||||
ValuePtr to_value(int index);
|
|
||||||
|
|
||||||
int get_top() const { return static_cast<int>(stack_.size()); }
|
|
||||||
void set_top(int index);
|
|
||||||
void pop(int n = 1);
|
|
||||||
|
|
||||||
// Error handling
|
|
||||||
const std::string &get_error() const { return last_error_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::unique_ptr<VM> vm_;
|
|
||||||
std::unique_ptr<Compiler> compiler_;
|
|
||||||
std::vector<ValuePtr> stack_;
|
|
||||||
std::string last_error_;
|
|
||||||
|
|
||||||
ValuePtr get_stack_value(int index);
|
|
||||||
|
|
||||||
// Helper for execution
|
|
||||||
bool execute_program(const Program &program);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace camellya
|
|
||||||
|
|
||||||
#endif // CAMELLYA_STATE_H
|
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
using namespace camellya;
|
using namespace camellya;
|
||||||
|
|
||||||
TEST_CASE("basic arithmetic", "[script]") {
|
TEST_CASE("basic arithmetic", "[script]") {
|
||||||
State state;
|
Camellya state;
|
||||||
const char *script = R"(
|
const char *script = R"(
|
||||||
var x = 10;
|
var x = 10;
|
||||||
var y = 20;
|
var y = 20;
|
||||||
@@ -26,7 +26,7 @@ TEST_CASE("basic arithmetic", "[script]") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("basic function", "[script][func]") {
|
TEST_CASE("basic function", "[script][func]") {
|
||||||
State state;
|
Camellya state;
|
||||||
const char *script = R"(
|
const char *script = R"(
|
||||||
func add(number x, number y) -> number {
|
func add(number x, number y) -> number {
|
||||||
return x + y;
|
return x + y;
|
||||||
@@ -46,7 +46,7 @@ TEST_CASE("basic function", "[script][func]") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("list indexing is 0-based", "[list]") {
|
TEST_CASE("list indexing is 0-based", "[list]") {
|
||||||
State state;
|
Camellya state;
|
||||||
const char *script = R"(
|
const char *script = R"(
|
||||||
var numbers = [10, 20, 30];
|
var numbers = [10, 20, 30];
|
||||||
)";
|
)";
|
||||||
@@ -75,7 +75,7 @@ TEST_CASE("list indexing is 0-based", "[list]") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("class init is called on declaration", "[class][init]") {
|
TEST_CASE("class init is called on declaration", "[class][init]") {
|
||||||
State state;
|
Camellya state;
|
||||||
const char *script = R"(
|
const char *script = R"(
|
||||||
class Person {
|
class Person {
|
||||||
var age : number;
|
var age : number;
|
||||||
@@ -124,7 +124,7 @@ TEST_CASE("class init is called on declaration", "[class][init]") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("interpreter performance: simple loop", "[perf][script]") {
|
TEST_CASE("interpreter performance: simple loop", "[perf][script]") {
|
||||||
State state;
|
Camellya state;
|
||||||
const char *script = R"(
|
const char *script = R"(
|
||||||
func sum_to(number n) -> number {
|
func sum_to(number n) -> number {
|
||||||
var s = 0;
|
var s = 0;
|
||||||
@@ -151,7 +151,7 @@ TEST_CASE("interpreter performance: simple loop", "[perf][script]") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("loop break", "[script][loop]") {
|
TEST_CASE("loop break", "[script][loop]") {
|
||||||
State state;
|
Camellya state;
|
||||||
const char *script = R"(
|
const char *script = R"(
|
||||||
var sum = 0;
|
var sum = 0;
|
||||||
for (var i = 0; i < 10; i = i + 1) {
|
for (var i = 0; i < 10; i = i + 1) {
|
||||||
@@ -170,7 +170,7 @@ TEST_CASE("loop break", "[script][loop]") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("loop continue", "[script][loop]") {
|
TEST_CASE("loop continue", "[script][loop]") {
|
||||||
State state;
|
Camellya state;
|
||||||
const char *script = R"(
|
const char *script = R"(
|
||||||
var sum = 0;
|
var sum = 0;
|
||||||
for (var i = 0; i < 5; i = i + 1) {
|
for (var i = 0; i < 5; i = i + 1) {
|
||||||
@@ -189,7 +189,7 @@ TEST_CASE("loop continue", "[script][loop]") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("while break and continue", "[script][loop]") {
|
TEST_CASE("while break and continue", "[script][loop]") {
|
||||||
State state;
|
Camellya state;
|
||||||
const char *script = R"(
|
const char *script = R"(
|
||||||
var i = 0;
|
var i = 0;
|
||||||
var sum = 0;
|
var sum = 0;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
using namespace camellya;
|
using namespace camellya;
|
||||||
|
|
||||||
TEST_CASE("VM - Basic arithmetic", "[vm]") {
|
TEST_CASE("VM - Basic arithmetic", "[vm]") {
|
||||||
State state;
|
Camellya state;
|
||||||
|
|
||||||
SECTION("Addition") {
|
SECTION("Addition") {
|
||||||
REQUIRE(state.do_string("var x = 10 + 20;"));
|
REQUIRE(state.do_string("var x = 10 + 20;"));
|
||||||
@@ -37,7 +37,7 @@ TEST_CASE("VM - Basic arithmetic", "[vm]") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("VM - Variables and assignment", "[vm]") {
|
TEST_CASE("VM - Variables and assignment", "[vm]") {
|
||||||
State state;
|
Camellya state;
|
||||||
|
|
||||||
SECTION("Variable declaration and initialization") {
|
SECTION("Variable declaration and initialization") {
|
||||||
REQUIRE(state.do_string("var a = 42;"));
|
REQUIRE(state.do_string("var a = 42;"));
|
||||||
@@ -57,7 +57,7 @@ TEST_CASE("VM - Variables and assignment", "[vm]") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("VM - String operations", "[vm]") {
|
TEST_CASE("VM - String operations", "[vm]") {
|
||||||
State state;
|
Camellya state;
|
||||||
|
|
||||||
SECTION("String concatenation") {
|
SECTION("String concatenation") {
|
||||||
REQUIRE(state.do_string(R"(var greeting = "Hello" + " " + "World";)"));
|
REQUIRE(state.do_string(R"(var greeting = "Hello" + " " + "World";)"));
|
||||||
@@ -70,7 +70,7 @@ TEST_CASE("VM - String operations", "[vm]") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("VM - Comparison operators", "[vm]") {
|
TEST_CASE("VM - Comparison operators", "[vm]") {
|
||||||
State state;
|
Camellya state;
|
||||||
|
|
||||||
SECTION("Equality") {
|
SECTION("Equality") {
|
||||||
REQUIRE(state.do_string("var eq = 10 == 10;"));
|
REQUIRE(state.do_string("var eq = 10 == 10;"));
|
||||||
@@ -92,7 +92,7 @@ TEST_CASE("VM - Comparison operators", "[vm]") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("VM - Lists", "[vm]") {
|
TEST_CASE("VM - Lists", "[vm]") {
|
||||||
State state;
|
Camellya state;
|
||||||
|
|
||||||
SECTION("Create list") {
|
SECTION("Create list") {
|
||||||
REQUIRE(state.do_string("var numbers = [1, 2, 3, 4, 5];"));
|
REQUIRE(state.do_string("var numbers = [1, 2, 3, 4, 5];"));
|
||||||
@@ -114,7 +114,7 @@ TEST_CASE("VM - Lists", "[vm]") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("VM - Maps", "[vm]") {
|
TEST_CASE("VM - Maps", "[vm]") {
|
||||||
State state;
|
Camellya state;
|
||||||
|
|
||||||
SECTION("Create map") {
|
SECTION("Create map") {
|
||||||
REQUIRE(state.do_string(R"(var person = {"name": "Alice", "age": "30"};)"));
|
REQUIRE(state.do_string(R"(var person = {"name": "Alice", "age": "30"};)"));
|
||||||
@@ -135,7 +135,7 @@ TEST_CASE("VM - Maps", "[vm]") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("VM - If statements", "[vm]") {
|
TEST_CASE("VM - If statements", "[vm]") {
|
||||||
State state;
|
Camellya state;
|
||||||
|
|
||||||
SECTION("If branch taken") {
|
SECTION("If branch taken") {
|
||||||
REQUIRE(state.do_string(R"(
|
REQUIRE(state.do_string(R"(
|
||||||
@@ -163,7 +163,7 @@ TEST_CASE("VM - If statements", "[vm]") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("VM - While loops", "[vm]") {
|
TEST_CASE("VM - While loops", "[vm]") {
|
||||||
State state;
|
Camellya state;
|
||||||
|
|
||||||
SECTION("While loop") {
|
SECTION("While loop") {
|
||||||
REQUIRE(state.do_string(R"(
|
REQUIRE(state.do_string(R"(
|
||||||
@@ -180,7 +180,7 @@ TEST_CASE("VM - While loops", "[vm]") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("VM - Native functions", "[vm]") {
|
TEST_CASE("VM - Native functions", "[vm]") {
|
||||||
State state;
|
Camellya state;
|
||||||
|
|
||||||
SECTION("len function") {
|
SECTION("len function") {
|
||||||
REQUIRE(state.do_string(R"(
|
REQUIRE(state.do_string(R"(
|
||||||
|
|||||||
Reference in New Issue
Block a user