Compare commits
6 Commits
57def6137b
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 36d273e09f | |||
| 5aa4efede7 | |||
| 8c5197c760 | |||
| 2839c0daff | |||
| 1a66d45204 | |||
| 0833a2229b |
17
.zed/debug.json
Normal file
17
.zed/debug.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// Project-local debug tasks
|
||||||
|
//
|
||||||
|
// For more documentation on how to configure debug tasks,
|
||||||
|
// see: https://zed.dev/docs/debugger
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"label": "Debug Test",
|
||||||
|
"build": {
|
||||||
|
"command": "make",
|
||||||
|
"args": ["-j8"],
|
||||||
|
"cwd": "$ZED_WORKTREE_ROOT/build",
|
||||||
|
},
|
||||||
|
"program": "$ZED_WORKTREE_ROOT/build/camellya_tests",
|
||||||
|
"request": "launch",
|
||||||
|
"adapter": "GDB",
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -10,11 +10,13 @@ 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/interpreter.cpp
|
src/chunk.cpp
|
||||||
src/state.cpp
|
src/compiler.cpp
|
||||||
|
src/vm.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Library headers
|
# Library headers
|
||||||
@@ -24,26 +26,29 @@ set(LIB_HEADERS
|
|||||||
src/parser.h
|
src/parser.h
|
||||||
src/ast.h
|
src/ast.h
|
||||||
src/value.h
|
src/value.h
|
||||||
src/interpreter.h
|
src/chunk.h
|
||||||
src/state.h
|
src/compiler.h
|
||||||
|
src/vm.h
|
||||||
|
src/opcode.h
|
||||||
|
src/exceptions.h
|
||||||
)
|
)
|
||||||
|
|
||||||
if(CAMELLYA_BUILD_STATIC)
|
if(CAMELLYA_BUILD_STATIC)
|
||||||
add_library(libcamellya STATIC ${LIB_SOURCES} ${LIB_HEADERS})
|
add_library(camellya STATIC ${LIB_SOURCES} ${LIB_HEADERS})
|
||||||
elseif()
|
elseif()
|
||||||
add_library(libcamellya SHARED ${LIB_SOURCES} ${LIB_HEADERS})
|
add_library(camellya SHARED ${LIB_SOURCES} ${LIB_HEADERS})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_include_directories(libcamellya PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
target_include_directories(camellya PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||||
|
|
||||||
|
|
||||||
if(CAMELLYA_BUILD_CLI)
|
if(CAMELLYA_BUILD_CLI)
|
||||||
add_executable(camellya
|
add_executable(chun
|
||||||
cli/main.cpp
|
cli/main.cpp
|
||||||
)
|
)
|
||||||
target_link_libraries(camellya
|
target_link_libraries(chun
|
||||||
PRIVATE
|
PRIVATE
|
||||||
libcamellya
|
camellya
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -63,6 +68,10 @@ if(CAMELLYA_BUILD_TESTS)
|
|||||||
add_executable(camellya_tests
|
add_executable(camellya_tests
|
||||||
tests/test_basic.cpp
|
tests/test_basic.cpp
|
||||||
tests/test_utf8.cpp
|
tests/test_utf8.cpp
|
||||||
|
tests/test_vm.cpp
|
||||||
|
tests/test_list.cpp
|
||||||
|
tests/test_map_builtin.cpp
|
||||||
|
tests/test_generic_builtin.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(camellya_tests
|
target_include_directories(camellya_tests
|
||||||
@@ -72,7 +81,7 @@ if(CAMELLYA_BUILD_TESTS)
|
|||||||
|
|
||||||
target_link_libraries(camellya_tests
|
target_link_libraries(camellya_tests
|
||||||
PRIVATE
|
PRIVATE
|
||||||
libcamellya
|
camellya
|
||||||
Catch2::Catch2WithMain
|
Catch2::Catch2WithMain
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
40
README.md
40
README.md
@@ -7,7 +7,7 @@
|
|||||||
- **0-based 索引**:数组和列表从 0 开始索引
|
- **0-based 索引**:数组和列表从 0 开始索引
|
||||||
- **明确的类型系统**:区分 list 和 map
|
- **明确的类型系统**:区分 list 和 map
|
||||||
- **类支持**:支持 class 定义,包含字段和方法
|
- **类支持**:支持 class 定义,包含字段和方法
|
||||||
- **静态类型声明**:变量需要类型声明(number, string, bool, list, map)
|
- **类型推断**:使用 `var` 关键字自动推断变量类型,也支持显式类型注解
|
||||||
- **类 Lua 的 API**:提供简单的嵌入式 API
|
- **类 Lua 的 API**:提供简单的嵌入式 API
|
||||||
|
|
||||||
## 语法示例
|
## 语法示例
|
||||||
@@ -15,15 +15,21 @@
|
|||||||
### 基本类型
|
### 基本类型
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
number x = 10;
|
// 类型自动推断
|
||||||
string name = "Alice";
|
var x = 10;
|
||||||
bool flag = true;
|
var name = "Alice";
|
||||||
|
var flag = true;
|
||||||
|
|
||||||
|
// 显式类型声明
|
||||||
|
var y : number = 3.14;
|
||||||
|
var title : string = "Developer";
|
||||||
|
var active : bool = false;
|
||||||
```
|
```
|
||||||
|
|
||||||
### List(0-indexed)
|
### List(0-indexed)
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
list numbers = [10, 20, 30];
|
var numbers = [10, 20, 30]; // 自动推断为 list
|
||||||
print(numbers[0]); // 输出: 10
|
print(numbers[0]); // 输出: 10
|
||||||
numbers[1] = 99;
|
numbers[1] = 99;
|
||||||
```
|
```
|
||||||
@@ -31,7 +37,7 @@ numbers[1] = 99;
|
|||||||
### Map
|
### Map
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
map person = {"name": "Bob", "age": "25"};
|
var person = {"name": "Bob", "age": "25"}; // 自动推断为 map
|
||||||
print(person["name"]); // 输出: Bob
|
print(person["name"]); // 输出: Bob
|
||||||
person["city"] = "New York";
|
person["city"] = "New York";
|
||||||
```
|
```
|
||||||
@@ -43,15 +49,15 @@ func add(number a, number b) -> number {
|
|||||||
return a + b;
|
return a + b;
|
||||||
}
|
}
|
||||||
|
|
||||||
number result = add(10, 20);
|
var result = add(10, 20); // 自动推断返回值类型
|
||||||
```
|
```
|
||||||
|
|
||||||
### 类
|
### 类
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
class Person {
|
class Person {
|
||||||
number age;
|
var age : number; // 类成员必须显式声明类型
|
||||||
string name;
|
var name : string;
|
||||||
|
|
||||||
func sayHi() -> string {
|
func sayHi() -> string {
|
||||||
print(name, "says: I'm", age, "years old");
|
print(name, "says: I'm", age, "years old");
|
||||||
@@ -59,7 +65,7 @@ class Person {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Person p;
|
var p : Person; // 实例化类
|
||||||
p.age = 10;
|
p.age = 10;
|
||||||
p.name = "Peter";
|
p.name = "Peter";
|
||||||
p.sayHi();
|
p.sayHi();
|
||||||
@@ -76,14 +82,14 @@ if (x > 10) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// while loop
|
// while loop
|
||||||
number i = 0;
|
var i = 0;
|
||||||
while (i < 5) {
|
while (i < 5) {
|
||||||
print("Count:", i);
|
print("Count:", i);
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// for loop
|
// for loop
|
||||||
for (number j = 0; j < 3; j = j + 1) {
|
for (var j = 0; j < 3; j = j + 1) {
|
||||||
print("For loop:", j);
|
print("For loop:", j);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -117,8 +123,8 @@ int main() {
|
|||||||
// 执行脚本字符串
|
// 执行脚本字符串
|
||||||
const char* script = R"(
|
const char* script = R"(
|
||||||
class Person {
|
class Person {
|
||||||
number age;
|
var age : number;
|
||||||
string name;
|
var name : string;
|
||||||
|
|
||||||
func sayHi() -> string {
|
func sayHi() -> string {
|
||||||
print(name, "says: I'm", age, "years old");
|
print(name, "says: I'm", age, "years old");
|
||||||
@@ -126,7 +132,7 @@ int main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Person p;
|
var p : Person;
|
||||||
p.age = 10;
|
p.age = 10;
|
||||||
p.name = "Peter";
|
p.name = "Peter";
|
||||||
p.sayHi();
|
p.sayHi();
|
||||||
@@ -203,10 +209,10 @@ camellya/
|
|||||||
|------|-----|----------|
|
|------|-----|----------|
|
||||||
| 索引起始 | 1 | 0 |
|
| 索引起始 | 1 | 0 |
|
||||||
| Table | 统一的 table | 区分 list 和 map |
|
| Table | 统一的 table | 区分 list 和 map |
|
||||||
| 类型 | 动态 | 静态声明 |
|
| 类型 | 动态 | 类型推断 + 可选类型注解 |
|
||||||
| 类 | 通过 metatable | 原生支持 |
|
| 类 | 通过 metatable | 原生支持 |
|
||||||
| 语法 | `function` | `func` |
|
| 语法 | `function` | `func` |
|
||||||
| 类型注解 | 无 | 必须声明 |
|
| 变量声明 | 无需声明 | `var` 关键字 |
|
||||||
|
|
||||||
## 许可证
|
## 许可证
|
||||||
|
|
||||||
|
|||||||
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: camellya <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;
|
||||||
}
|
}
|
||||||
|
|||||||
12
example.chun
12
example.chun
@@ -2,8 +2,8 @@
|
|||||||
// This demonstrates the Person class from the specification
|
// This demonstrates the Person class from the specification
|
||||||
|
|
||||||
class Person {
|
class Person {
|
||||||
number age;
|
var age : number;
|
||||||
string name;
|
var name : string;
|
||||||
|
|
||||||
func sayHi() -> string {
|
func sayHi() -> string {
|
||||||
print(name, "says: I'm", age, "years old");
|
print(name, "says: I'm", age, "years old");
|
||||||
@@ -18,7 +18,7 @@ class Person {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create an instance
|
// Create an instance
|
||||||
Person p;
|
var p : Person;
|
||||||
p.age = 10;
|
p.age = 10;
|
||||||
p.name = "Peter";
|
p.name = "Peter";
|
||||||
|
|
||||||
@@ -29,12 +29,12 @@ p.sayHi();
|
|||||||
|
|
||||||
// Test lists (0-indexed)
|
// Test lists (0-indexed)
|
||||||
print("\n=== List Demo ===");
|
print("\n=== List Demo ===");
|
||||||
list numbers = [1, 2, 3, 4, 5];
|
var numbers = [1, 2, 3, 4, 5];
|
||||||
print("List:", numbers);
|
print("List:", numbers);
|
||||||
print("First element (index 0):", numbers[0]);
|
print("First element (index 0):", numbers[0]);
|
||||||
print("测试 element (index 2):", numbers[2]);
|
print("测试 element (index 2):", numbers[2]);
|
||||||
|
|
||||||
for(number i = 0; i < len(numbers); i = i + 1) {
|
for(var i = 0; i < len(numbers); i = i + 1) {
|
||||||
print("List element", numbers[i]);
|
print("List element", numbers[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ while(true) {
|
|||||||
}
|
}
|
||||||
// Test maps
|
// Test maps
|
||||||
print("\n=== Map Demo ===");
|
print("\n=== Map Demo ===");
|
||||||
map config = {"host": "localhost", "port": "8080"};
|
var config = {"host": "localhost", "port": "8080"};
|
||||||
print("Config:", config);
|
print("Config:", config);
|
||||||
print("Host:", config["host"]);
|
print("Host:", config["host"]);
|
||||||
|
|
||||||
|
|||||||
@@ -133,12 +133,13 @@ struct ExprStmt : public Stmt {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct VarDecl : public Stmt {
|
struct VarDecl : public Stmt {
|
||||||
std::string type_name;
|
std::string type_name; // Can be empty if type inference is needed
|
||||||
std::string name;
|
std::string name;
|
||||||
ExprPtr initializer;
|
ExprPtr initializer;
|
||||||
|
bool infer_type; // true if type should be inferred from initializer
|
||||||
|
|
||||||
VarDecl(std::string type_name, std::string name, ExprPtr initializer = nullptr)
|
VarDecl(std::string type_name, std::string name, ExprPtr initializer = nullptr, bool infer_type = false)
|
||||||
: type_name(std::move(type_name)), name(std::move(name)), initializer(std::move(initializer)) {}
|
: type_name(std::move(type_name)), name(std::move(name)), initializer(std::move(initializer)), infer_type(infer_type) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BlockStmt : public Stmt {
|
struct BlockStmt : public Stmt {
|
||||||
|
|||||||
171
src/camellya.cpp
Normal file
171
src/camellya.cpp
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
#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) {
|
||||||
|
d->vm->register_native_function(name, func);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Camellya::register_builtin_class(const std::string &type_name, std::shared_ptr<ClassValue> klass) {
|
||||||
|
d->vm->register_builtin_class(type_name, klass);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,23 +1,62 @@
|
|||||||
#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"
|
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
|
// Register built-in class for any type (including user native types)
|
||||||
|
void register_builtin_class(const std::string &type_name, std::shared_ptr<ClassValue> klass);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|||||||
184
src/chunk.cpp
Normal file
184
src/chunk.cpp
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
#include "chunk.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <format>
|
||||||
|
|
||||||
|
namespace camellya {
|
||||||
|
|
||||||
|
void Chunk::write(uint8_t byte, int line) {
|
||||||
|
code.push_back(byte);
|
||||||
|
lines.push_back(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chunk::write_opcode(OpCode op, int line) {
|
||||||
|
write(static_cast<uint8_t>(op), line);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Chunk::add_constant(ValuePtr value) {
|
||||||
|
constants.push_back(value);
|
||||||
|
return constants.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ValuePtr Chunk::get_constant(size_t index) const {
|
||||||
|
if (index >= constants.size()) {
|
||||||
|
return std::make_shared<NilValue>();
|
||||||
|
}
|
||||||
|
return constants[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
int Chunk::get_line(size_t offset) const {
|
||||||
|
if (offset >= lines.size()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return lines[offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chunk::patch_jump(size_t offset) {
|
||||||
|
// Calculate the jump offset
|
||||||
|
// -2 to adjust for the bytecode for the jump offset itself
|
||||||
|
size_t jump = code.size() - offset - 2;
|
||||||
|
|
||||||
|
if (jump > UINT16_MAX) {
|
||||||
|
throw std::runtime_error("Too much code to jump over.");
|
||||||
|
}
|
||||||
|
|
||||||
|
code[offset] = (jump >> 8) & 0xff;
|
||||||
|
code[offset + 1] = jump & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chunk::disassemble(const std::string& name) const {
|
||||||
|
std::cout << "== " << name << " ==" << std::endl;
|
||||||
|
|
||||||
|
for (size_t offset = 0; offset < code.size();) {
|
||||||
|
offset = disassemble_instruction(offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Chunk::disassemble_instruction(size_t offset) const {
|
||||||
|
std::cout << std::format("{:04d} ", offset);
|
||||||
|
|
||||||
|
if (offset > 0 && get_line(offset) == get_line(offset - 1)) {
|
||||||
|
std::cout << " | ";
|
||||||
|
} else {
|
||||||
|
std::cout << std::format("{:4d} ", get_line(offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t instruction = code[offset];
|
||||||
|
OpCode op = static_cast<OpCode>(instruction);
|
||||||
|
|
||||||
|
switch (op) {
|
||||||
|
case OpCode::OP_CONSTANT:
|
||||||
|
return constant_instruction("OP_CONSTANT", offset);
|
||||||
|
case OpCode::OP_NIL:
|
||||||
|
return simple_instruction("OP_NIL", offset);
|
||||||
|
case OpCode::OP_TRUE:
|
||||||
|
return simple_instruction("OP_TRUE", offset);
|
||||||
|
case OpCode::OP_FALSE:
|
||||||
|
return simple_instruction("OP_FALSE", offset);
|
||||||
|
case OpCode::OP_ADD:
|
||||||
|
return simple_instruction("OP_ADD", offset);
|
||||||
|
case OpCode::OP_SUBTRACT:
|
||||||
|
return simple_instruction("OP_SUBTRACT", offset);
|
||||||
|
case OpCode::OP_MULTIPLY:
|
||||||
|
return simple_instruction("OP_MULTIPLY", offset);
|
||||||
|
case OpCode::OP_DIVIDE:
|
||||||
|
return simple_instruction("OP_DIVIDE", offset);
|
||||||
|
case OpCode::OP_MODULO:
|
||||||
|
return simple_instruction("OP_MODULO", offset);
|
||||||
|
case OpCode::OP_NEGATE:
|
||||||
|
return simple_instruction("OP_NEGATE", offset);
|
||||||
|
case OpCode::OP_EQUAL:
|
||||||
|
return simple_instruction("OP_EQUAL", offset);
|
||||||
|
case OpCode::OP_NOT_EQUAL:
|
||||||
|
return simple_instruction("OP_NOT_EQUAL", offset);
|
||||||
|
case OpCode::OP_GREATER:
|
||||||
|
return simple_instruction("OP_GREATER", offset);
|
||||||
|
case OpCode::OP_GREATER_EQUAL:
|
||||||
|
return simple_instruction("OP_GREATER_EQUAL", offset);
|
||||||
|
case OpCode::OP_LESS:
|
||||||
|
return simple_instruction("OP_LESS", offset);
|
||||||
|
case OpCode::OP_LESS_EQUAL:
|
||||||
|
return simple_instruction("OP_LESS_EQUAL", offset);
|
||||||
|
case OpCode::OP_NOT:
|
||||||
|
return simple_instruction("OP_NOT", offset);
|
||||||
|
case OpCode::OP_POP:
|
||||||
|
return simple_instruction("OP_POP", offset);
|
||||||
|
case OpCode::OP_POPN:
|
||||||
|
return byte_instruction("OP_POPN", offset);
|
||||||
|
case OpCode::OP_DUP:
|
||||||
|
return simple_instruction("OP_DUP", offset);
|
||||||
|
case OpCode::OP_GET_GLOBAL:
|
||||||
|
return constant_instruction("OP_GET_GLOBAL", offset);
|
||||||
|
case OpCode::OP_SET_GLOBAL:
|
||||||
|
return constant_instruction("OP_SET_GLOBAL", offset);
|
||||||
|
case OpCode::OP_DEFINE_GLOBAL:
|
||||||
|
return constant_instruction("OP_DEFINE_GLOBAL", offset);
|
||||||
|
case OpCode::OP_GET_LOCAL:
|
||||||
|
return byte_instruction("OP_GET_LOCAL", offset);
|
||||||
|
case OpCode::OP_SET_LOCAL:
|
||||||
|
return byte_instruction("OP_SET_LOCAL", offset);
|
||||||
|
case OpCode::OP_JUMP:
|
||||||
|
return jump_instruction("OP_JUMP", 1, offset);
|
||||||
|
case OpCode::OP_JUMP_IF_FALSE:
|
||||||
|
return jump_instruction("OP_JUMP_IF_FALSE", 1, offset);
|
||||||
|
case OpCode::OP_LOOP:
|
||||||
|
return jump_instruction("OP_LOOP", -1, offset);
|
||||||
|
case OpCode::OP_CALL:
|
||||||
|
return byte_instruction("OP_CALL", offset);
|
||||||
|
case OpCode::OP_RETURN:
|
||||||
|
return simple_instruction("OP_RETURN", offset);
|
||||||
|
case OpCode::OP_BUILD_LIST:
|
||||||
|
return byte_instruction("OP_BUILD_LIST", offset);
|
||||||
|
case OpCode::OP_BUILD_MAP:
|
||||||
|
return byte_instruction("OP_BUILD_MAP", offset);
|
||||||
|
case OpCode::OP_INDEX:
|
||||||
|
return simple_instruction("OP_INDEX", offset);
|
||||||
|
case OpCode::OP_INDEX_SET:
|
||||||
|
return simple_instruction("OP_INDEX_SET", offset);
|
||||||
|
case OpCode::OP_CLASS:
|
||||||
|
return constant_instruction("OP_CLASS", offset);
|
||||||
|
case OpCode::OP_GET_PROPERTY:
|
||||||
|
return constant_instruction("OP_GET_PROPERTY", offset);
|
||||||
|
case OpCode::OP_SET_PROPERTY:
|
||||||
|
return constant_instruction("OP_SET_PROPERTY", offset);
|
||||||
|
case OpCode::OP_METHOD:
|
||||||
|
return constant_instruction("OP_METHOD", offset);
|
||||||
|
case OpCode::OP_PRINT:
|
||||||
|
return simple_instruction("OP_PRINT", offset);
|
||||||
|
case OpCode::OP_HALT:
|
||||||
|
return simple_instruction("OP_HALT", offset);
|
||||||
|
default:
|
||||||
|
std::cout << "Unknown opcode " << static_cast<int>(instruction) << std::endl;
|
||||||
|
return offset + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Chunk::simple_instruction(const std::string& name, size_t offset) const {
|
||||||
|
std::cout << name << std::endl;
|
||||||
|
return offset + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Chunk::constant_instruction(const std::string& name, size_t offset) const {
|
||||||
|
uint8_t constant_idx = code[offset + 1];
|
||||||
|
std::cout << std::format("{:<16} {:4d} '", name, constant_idx);
|
||||||
|
if (constant_idx < constants.size()) {
|
||||||
|
std::cout << constants[constant_idx]->to_string();
|
||||||
|
}
|
||||||
|
std::cout << "'" << std::endl;
|
||||||
|
return offset + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Chunk::byte_instruction(const std::string& name, size_t offset) const {
|
||||||
|
uint8_t slot = code[offset + 1];
|
||||||
|
std::cout << std::format("{:<16} {:4d}", name, slot) << std::endl;
|
||||||
|
return offset + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Chunk::jump_instruction(const std::string& name, int sign, size_t offset) const {
|
||||||
|
uint16_t jump = (static_cast<uint16_t>(code[offset + 1]) << 8) | code[offset + 2];
|
||||||
|
std::cout << std::format("{:<16} {:4d} -> {:4d}", name, offset,
|
||||||
|
offset + 3 + sign * jump) << std::endl;
|
||||||
|
return offset + 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace camellya
|
||||||
66
src/chunk.h
Normal file
66
src/chunk.h
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#ifndef CAMELLYA_CHUNK_H
|
||||||
|
#define CAMELLYA_CHUNK_H
|
||||||
|
|
||||||
|
#include "opcode.h"
|
||||||
|
#include "value.h"
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace camellya {
|
||||||
|
|
||||||
|
// A chunk represents a sequence of bytecode instructions
|
||||||
|
// along with associated constants and debug information
|
||||||
|
class Chunk {
|
||||||
|
public:
|
||||||
|
Chunk() = default;
|
||||||
|
|
||||||
|
// Write a byte to the chunk
|
||||||
|
void write(uint8_t byte, int line);
|
||||||
|
|
||||||
|
// Write an opcode to the chunk
|
||||||
|
void write_opcode(OpCode op, int line);
|
||||||
|
|
||||||
|
// Add a constant to the constant pool
|
||||||
|
// Returns the index of the constant
|
||||||
|
size_t add_constant(ValuePtr value);
|
||||||
|
|
||||||
|
// Get a constant from the constant pool
|
||||||
|
ValuePtr get_constant(size_t index) const;
|
||||||
|
|
||||||
|
// Get the size of the bytecode
|
||||||
|
size_t size() const { return code.size(); }
|
||||||
|
|
||||||
|
// Get the bytecode at an index
|
||||||
|
uint8_t get_code(size_t index) const { return code[index]; }
|
||||||
|
|
||||||
|
// Get line number for a bytecode offset
|
||||||
|
int get_line(size_t offset) const;
|
||||||
|
|
||||||
|
// Disassemble the chunk for debugging
|
||||||
|
void disassemble(const std::string& name) const;
|
||||||
|
|
||||||
|
// Disassemble a single instruction
|
||||||
|
size_t disassemble_instruction(size_t offset) const;
|
||||||
|
|
||||||
|
// Patch a jump instruction with the correct offset
|
||||||
|
void patch_jump(size_t offset);
|
||||||
|
|
||||||
|
// Get current offset (useful for jump patching)
|
||||||
|
size_t current_offset() const { return code.size(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<uint8_t> code; // Bytecode instructions
|
||||||
|
std::vector<ValuePtr> constants; // Constant pool
|
||||||
|
std::vector<int> lines; // Line information for debugging
|
||||||
|
|
||||||
|
// Helper for disassembly
|
||||||
|
size_t simple_instruction(const std::string& name, size_t offset) const;
|
||||||
|
size_t constant_instruction(const std::string& name, size_t offset) const;
|
||||||
|
size_t byte_instruction(const std::string& name, size_t offset) const;
|
||||||
|
size_t jump_instruction(const std::string& name, int sign, size_t offset) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace camellya
|
||||||
|
|
||||||
|
#endif // CAMELLYA_CHUNK_H
|
||||||
636
src/compiler.cpp
Normal file
636
src/compiler.cpp
Normal file
@@ -0,0 +1,636 @@
|
|||||||
|
#include "compiler.h"
|
||||||
|
#include "exceptions.h"
|
||||||
|
|
||||||
|
namespace camellya {
|
||||||
|
|
||||||
|
Compiler::Compiler()
|
||||||
|
: current_chunk(nullptr), scope_depth(0), had_error(false) {}
|
||||||
|
|
||||||
|
std::shared_ptr<Chunk> Compiler::compile(const Program &program) {
|
||||||
|
current_chunk = std::make_shared<Chunk>();
|
||||||
|
had_error = false;
|
||||||
|
error_message.clear();
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const auto &stmt : program.statements) {
|
||||||
|
compile_stmt(*stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit halt instruction at the end
|
||||||
|
emit_opcode(OpCode::OP_HALT);
|
||||||
|
|
||||||
|
if (had_error) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return current_chunk;
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
report_error(e.what());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::compile_expr(const Expr &expr) {
|
||||||
|
if (auto *binary = dynamic_cast<const BinaryExpr *>(&expr)) {
|
||||||
|
compile_binary(*binary);
|
||||||
|
} else if (auto *unary = dynamic_cast<const UnaryExpr *>(&expr)) {
|
||||||
|
compile_unary(*unary);
|
||||||
|
} else if (auto *literal = dynamic_cast<const LiteralExpr *>(&expr)) {
|
||||||
|
compile_literal(*literal);
|
||||||
|
} else if (auto *variable = dynamic_cast<const VariableExpr *>(&expr)) {
|
||||||
|
compile_variable(*variable);
|
||||||
|
} else if (auto *assign = dynamic_cast<const AssignExpr *>(&expr)) {
|
||||||
|
compile_assign(*assign);
|
||||||
|
} else if (auto *call = dynamic_cast<const CallExpr *>(&expr)) {
|
||||||
|
compile_call(*call);
|
||||||
|
} else if (auto *get = dynamic_cast<const GetExpr *>(&expr)) {
|
||||||
|
compile_get(*get);
|
||||||
|
} else if (auto *set = dynamic_cast<const SetExpr *>(&expr)) {
|
||||||
|
compile_set(*set);
|
||||||
|
} else if (auto *index = dynamic_cast<const IndexExpr *>(&expr)) {
|
||||||
|
compile_index(*index);
|
||||||
|
} else if (auto *index_set = dynamic_cast<const IndexSetExpr *>(&expr)) {
|
||||||
|
compile_index_set(*index_set);
|
||||||
|
} else if (auto *list = dynamic_cast<const ListExpr *>(&expr)) {
|
||||||
|
compile_list(*list);
|
||||||
|
} else if (auto *map = dynamic_cast<const MapExpr *>(&expr)) {
|
||||||
|
compile_map(*map);
|
||||||
|
} else {
|
||||||
|
report_error("Unknown expression type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::compile_binary(const BinaryExpr &expr) {
|
||||||
|
// Special handling for logical operators (short-circuit evaluation)
|
||||||
|
if (expr.op == "and") {
|
||||||
|
compile_expr(*expr.left);
|
||||||
|
size_t end_jump = emit_jump(OpCode::OP_JUMP_IF_FALSE);
|
||||||
|
emit_opcode(OpCode::OP_POP);
|
||||||
|
compile_expr(*expr.right);
|
||||||
|
patch_jump(end_jump);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expr.op == "or") {
|
||||||
|
compile_expr(*expr.left);
|
||||||
|
size_t else_jump = emit_jump(OpCode::OP_JUMP_IF_FALSE);
|
||||||
|
size_t end_jump = emit_jump(OpCode::OP_JUMP);
|
||||||
|
patch_jump(else_jump);
|
||||||
|
emit_opcode(OpCode::OP_POP);
|
||||||
|
compile_expr(*expr.right);
|
||||||
|
patch_jump(end_jump);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular binary operators
|
||||||
|
compile_expr(*expr.left);
|
||||||
|
compile_expr(*expr.right);
|
||||||
|
|
||||||
|
if (expr.op == "+") {
|
||||||
|
emit_opcode(OpCode::OP_ADD);
|
||||||
|
} else if (expr.op == "-") {
|
||||||
|
emit_opcode(OpCode::OP_SUBTRACT);
|
||||||
|
} else if (expr.op == "*") {
|
||||||
|
emit_opcode(OpCode::OP_MULTIPLY);
|
||||||
|
} else if (expr.op == "/") {
|
||||||
|
emit_opcode(OpCode::OP_DIVIDE);
|
||||||
|
} else if (expr.op == "%") {
|
||||||
|
emit_opcode(OpCode::OP_MODULO);
|
||||||
|
} else if (expr.op == "==") {
|
||||||
|
emit_opcode(OpCode::OP_EQUAL);
|
||||||
|
} else if (expr.op == "!=") {
|
||||||
|
emit_opcode(OpCode::OP_NOT_EQUAL);
|
||||||
|
} else if (expr.op == ">") {
|
||||||
|
emit_opcode(OpCode::OP_GREATER);
|
||||||
|
} else if (expr.op == ">=") {
|
||||||
|
emit_opcode(OpCode::OP_GREATER_EQUAL);
|
||||||
|
} else if (expr.op == "<") {
|
||||||
|
emit_opcode(OpCode::OP_LESS);
|
||||||
|
} else if (expr.op == "<=") {
|
||||||
|
emit_opcode(OpCode::OP_LESS_EQUAL);
|
||||||
|
} else {
|
||||||
|
report_error("Unknown binary operator: " + expr.op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::compile_unary(const UnaryExpr &expr) {
|
||||||
|
compile_expr(*expr.operand);
|
||||||
|
|
||||||
|
if (expr.op == "-") {
|
||||||
|
emit_opcode(OpCode::OP_NEGATE);
|
||||||
|
} else if (expr.op == "!") {
|
||||||
|
emit_opcode(OpCode::OP_NOT);
|
||||||
|
} else {
|
||||||
|
report_error("Unknown unary operator: " + expr.op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::compile_literal(const LiteralExpr &expr) {
|
||||||
|
std::visit(
|
||||||
|
[this](auto &&arg) {
|
||||||
|
using T = std::decay_t<decltype(arg)>;
|
||||||
|
if constexpr (std::is_same_v<T, double>) {
|
||||||
|
emit_constant(std::make_shared<NumberValue>(arg));
|
||||||
|
} else if constexpr (std::is_same_v<T, std::string>) {
|
||||||
|
emit_constant(std::make_shared<StringValue>(arg));
|
||||||
|
} else if constexpr (std::is_same_v<T, bool>) {
|
||||||
|
if (arg) {
|
||||||
|
emit_opcode(OpCode::OP_TRUE);
|
||||||
|
} else {
|
||||||
|
emit_opcode(OpCode::OP_FALSE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emit_opcode(OpCode::OP_NIL);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expr.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::compile_variable(const VariableExpr &expr) {
|
||||||
|
int local = resolve_local(expr.name);
|
||||||
|
if (local != -1) {
|
||||||
|
emit_bytes(static_cast<uint8_t>(OpCode::OP_GET_LOCAL),
|
||||||
|
static_cast<uint8_t>(local));
|
||||||
|
} else {
|
||||||
|
emit_bytes(static_cast<uint8_t>(OpCode::OP_GET_GLOBAL),
|
||||||
|
identifier_constant(expr.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::compile_assign(const AssignExpr &expr) {
|
||||||
|
compile_expr(*expr.value);
|
||||||
|
|
||||||
|
int local = resolve_local(expr.name);
|
||||||
|
if (local != -1) {
|
||||||
|
emit_bytes(static_cast<uint8_t>(OpCode::OP_SET_LOCAL),
|
||||||
|
static_cast<uint8_t>(local));
|
||||||
|
} else {
|
||||||
|
emit_bytes(static_cast<uint8_t>(OpCode::OP_SET_GLOBAL),
|
||||||
|
identifier_constant(expr.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::compile_call(const CallExpr &expr) {
|
||||||
|
compile_expr(*expr.callee);
|
||||||
|
|
||||||
|
for (const auto &arg : expr.arguments) {
|
||||||
|
compile_expr(*arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit_bytes(static_cast<uint8_t>(OpCode::OP_CALL),
|
||||||
|
static_cast<uint8_t>(expr.arguments.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::compile_get(const GetExpr &expr) {
|
||||||
|
compile_expr(*expr.object);
|
||||||
|
emit_bytes(static_cast<uint8_t>(OpCode::OP_GET_PROPERTY),
|
||||||
|
identifier_constant(expr.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::compile_set(const SetExpr &expr) {
|
||||||
|
compile_expr(*expr.object);
|
||||||
|
compile_expr(*expr.value);
|
||||||
|
emit_bytes(static_cast<uint8_t>(OpCode::OP_SET_PROPERTY),
|
||||||
|
identifier_constant(expr.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::compile_index(const IndexExpr &expr) {
|
||||||
|
compile_expr(*expr.object);
|
||||||
|
compile_expr(*expr.index);
|
||||||
|
emit_opcode(OpCode::OP_INDEX);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::compile_index_set(const IndexSetExpr &expr) {
|
||||||
|
compile_expr(*expr.object);
|
||||||
|
compile_expr(*expr.index);
|
||||||
|
compile_expr(*expr.value);
|
||||||
|
emit_opcode(OpCode::OP_INDEX_SET);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::compile_list(const ListExpr &expr) {
|
||||||
|
for (const auto &elem : expr.elements) {
|
||||||
|
compile_expr(*elem);
|
||||||
|
}
|
||||||
|
emit_bytes(static_cast<uint8_t>(OpCode::OP_BUILD_LIST),
|
||||||
|
static_cast<uint8_t>(expr.elements.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::compile_map(const MapExpr &expr) {
|
||||||
|
for (const auto &[key, value] : expr.pairs) {
|
||||||
|
compile_expr(*key);
|
||||||
|
compile_expr(*value);
|
||||||
|
}
|
||||||
|
emit_bytes(static_cast<uint8_t>(OpCode::OP_BUILD_MAP),
|
||||||
|
static_cast<uint8_t>(expr.pairs.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::compile_stmt(const Stmt &stmt) {
|
||||||
|
if (auto *expr_stmt = dynamic_cast<const ExprStmt *>(&stmt)) {
|
||||||
|
compile_expr_stmt(*expr_stmt);
|
||||||
|
} else if (auto *var_decl = dynamic_cast<const VarDecl *>(&stmt)) {
|
||||||
|
compile_var_decl(*var_decl);
|
||||||
|
} else if (auto *block = dynamic_cast<const BlockStmt *>(&stmt)) {
|
||||||
|
compile_block(*block);
|
||||||
|
} else if (auto *if_stmt = dynamic_cast<const IfStmt *>(&stmt)) {
|
||||||
|
compile_if(*if_stmt);
|
||||||
|
} else if (auto *while_stmt = dynamic_cast<const WhileStmt *>(&stmt)) {
|
||||||
|
compile_while(*while_stmt);
|
||||||
|
} else if (auto *for_stmt = dynamic_cast<const ForStmt *>(&stmt)) {
|
||||||
|
compile_for(*for_stmt);
|
||||||
|
} else if (auto *return_stmt = dynamic_cast<const ReturnStmt *>(&stmt)) {
|
||||||
|
compile_return(*return_stmt);
|
||||||
|
} else if (auto *break_stmt = dynamic_cast<const BreakStmt *>(&stmt)) {
|
||||||
|
compile_break(*break_stmt);
|
||||||
|
} else if (auto *continue_stmt = dynamic_cast<const ContinueStmt *>(&stmt)) {
|
||||||
|
compile_continue(*continue_stmt);
|
||||||
|
} else if (auto *func_decl = dynamic_cast<const FunctionDecl *>(&stmt)) {
|
||||||
|
compile_function_decl(*func_decl);
|
||||||
|
} else if (auto *class_decl = dynamic_cast<const ClassDecl *>(&stmt)) {
|
||||||
|
compile_class_decl(*class_decl);
|
||||||
|
} else {
|
||||||
|
report_error("Unknown statement type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::compile_expr_stmt(const ExprStmt &stmt) {
|
||||||
|
compile_expr(*stmt.expression);
|
||||||
|
emit_opcode(OpCode::OP_POP);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::compile_var_decl(const VarDecl &stmt) {
|
||||||
|
if (stmt.initializer) {
|
||||||
|
compile_expr(*stmt.initializer);
|
||||||
|
} else if (!stmt.type_name.empty() && stmt.type_name != "number" &&
|
||||||
|
stmt.type_name != "string" && stmt.type_name != "bool" &&
|
||||||
|
stmt.type_name != "list" && stmt.type_name != "map") {
|
||||||
|
// It's a class type - emit code to get the class and call it
|
||||||
|
emit_bytes(static_cast<uint8_t>(OpCode::OP_GET_GLOBAL),
|
||||||
|
identifier_constant(stmt.type_name));
|
||||||
|
emit_bytes(static_cast<uint8_t>(OpCode::OP_CALL),
|
||||||
|
0); // Call with 0 arguments
|
||||||
|
} else {
|
||||||
|
emit_opcode(OpCode::OP_NIL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scope_depth == 0) {
|
||||||
|
// Global variable
|
||||||
|
emit_bytes(static_cast<uint8_t>(OpCode::OP_DEFINE_GLOBAL),
|
||||||
|
identifier_constant(stmt.name));
|
||||||
|
} else {
|
||||||
|
// Local variable
|
||||||
|
add_local(stmt.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::compile_block(const BlockStmt &stmt) {
|
||||||
|
begin_scope();
|
||||||
|
for (const auto &statement : stmt.statements) {
|
||||||
|
compile_stmt(*statement);
|
||||||
|
}
|
||||||
|
end_scope();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::compile_if(const IfStmt &stmt) {
|
||||||
|
compile_expr(*stmt.condition);
|
||||||
|
|
||||||
|
size_t then_jump = emit_jump(OpCode::OP_JUMP_IF_FALSE);
|
||||||
|
emit_opcode(OpCode::OP_POP); // Pop condition if it's truthy
|
||||||
|
compile_stmt(*stmt.then_branch);
|
||||||
|
|
||||||
|
if (stmt.else_branch) {
|
||||||
|
size_t else_jump = emit_jump(OpCode::OP_JUMP);
|
||||||
|
patch_jump(then_jump);
|
||||||
|
emit_opcode(OpCode::OP_POP); // Pop condition if it's falsey
|
||||||
|
compile_stmt(*stmt.else_branch);
|
||||||
|
patch_jump(else_jump);
|
||||||
|
} else {
|
||||||
|
size_t end_jump = emit_jump(OpCode::OP_JUMP);
|
||||||
|
patch_jump(then_jump);
|
||||||
|
emit_opcode(OpCode::OP_POP); // Pop condition if it's falsey
|
||||||
|
patch_jump(end_jump);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::compile_while(const WhileStmt &stmt) {
|
||||||
|
size_t loop_start = current_chunk->current_offset();
|
||||||
|
|
||||||
|
// Push loop info for break/continue
|
||||||
|
loops.push_back({loop_start, {}, scope_depth});
|
||||||
|
|
||||||
|
compile_expr(*stmt.condition);
|
||||||
|
|
||||||
|
size_t exit_jump = emit_jump(OpCode::OP_JUMP_IF_FALSE);
|
||||||
|
emit_opcode(OpCode::OP_POP);
|
||||||
|
|
||||||
|
compile_stmt(*stmt.body);
|
||||||
|
emit_loop(loop_start);
|
||||||
|
|
||||||
|
patch_jump(exit_jump);
|
||||||
|
emit_opcode(OpCode::OP_POP);
|
||||||
|
|
||||||
|
// Patch all break statements
|
||||||
|
for (size_t break_jump : loops.back().breaks) {
|
||||||
|
patch_jump(break_jump);
|
||||||
|
}
|
||||||
|
|
||||||
|
loops.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::compile_for(const ForStmt &stmt) {
|
||||||
|
begin_scope();
|
||||||
|
|
||||||
|
if (stmt.initializer) {
|
||||||
|
compile_stmt(*stmt.initializer);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t loop_start = current_chunk->current_offset();
|
||||||
|
|
||||||
|
// Push loop info for break/continue
|
||||||
|
loops.push_back({loop_start, {}, scope_depth});
|
||||||
|
|
||||||
|
size_t exit_jump = 0;
|
||||||
|
if (stmt.condition) {
|
||||||
|
compile_expr(*stmt.condition);
|
||||||
|
exit_jump = emit_jump(OpCode::OP_JUMP_IF_FALSE);
|
||||||
|
emit_opcode(OpCode::OP_POP);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jump over increment for first iteration
|
||||||
|
size_t body_jump = emit_jump(OpCode::OP_JUMP);
|
||||||
|
|
||||||
|
size_t increment_start = current_chunk->current_offset();
|
||||||
|
if (stmt.increment) {
|
||||||
|
compile_expr(*stmt.increment);
|
||||||
|
emit_opcode(OpCode::OP_POP);
|
||||||
|
}
|
||||||
|
emit_loop(loop_start);
|
||||||
|
|
||||||
|
// Update loop start to increment (for continue)
|
||||||
|
loops.back().start = increment_start;
|
||||||
|
|
||||||
|
patch_jump(body_jump);
|
||||||
|
compile_stmt(*stmt.body);
|
||||||
|
emit_loop(increment_start);
|
||||||
|
|
||||||
|
if (stmt.condition) {
|
||||||
|
patch_jump(exit_jump);
|
||||||
|
emit_opcode(OpCode::OP_POP);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch all break statements
|
||||||
|
for (size_t break_jump : loops.back().breaks) {
|
||||||
|
patch_jump(break_jump);
|
||||||
|
}
|
||||||
|
|
||||||
|
loops.pop_back();
|
||||||
|
end_scope();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::compile_return(const ReturnStmt &stmt) {
|
||||||
|
if (stmt.value) {
|
||||||
|
compile_expr(*stmt.value);
|
||||||
|
} else {
|
||||||
|
emit_opcode(OpCode::OP_NIL);
|
||||||
|
}
|
||||||
|
emit_opcode(OpCode::OP_RETURN);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::compile_break(const BreakStmt &stmt) {
|
||||||
|
if (loops.empty()) {
|
||||||
|
report_error("Cannot use 'break' outside of a loop.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop locals until we're at the loop's scope
|
||||||
|
for (int i = static_cast<int>(locals.size()) - 1; i >= 0; i--) {
|
||||||
|
if (locals[i].depth <= loops.back().scope_depth) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
emit_opcode(OpCode::OP_POP);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit jump and record it for later patching
|
||||||
|
size_t jump = emit_jump(OpCode::OP_JUMP);
|
||||||
|
loops.back().breaks.push_back(jump);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::compile_continue(const ContinueStmt &stmt) {
|
||||||
|
if (loops.empty()) {
|
||||||
|
report_error("Cannot use 'continue' outside of a loop.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop locals until we're at the loop's scope
|
||||||
|
for (int i = static_cast<int>(locals.size()) - 1; i >= 0; i--) {
|
||||||
|
if (locals[i].depth <= loops.back().scope_depth) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
emit_opcode(OpCode::OP_POP);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jump back to loop start
|
||||||
|
emit_loop(loops.back().start);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::compile_function_decl(const FunctionDecl &stmt) {
|
||||||
|
// Save current state
|
||||||
|
auto prev_chunk = current_chunk;
|
||||||
|
auto prev_locals = std::move(locals);
|
||||||
|
auto prev_scope_depth = scope_depth;
|
||||||
|
auto prev_loops = std::move(loops);
|
||||||
|
|
||||||
|
// Setup new state for function
|
||||||
|
current_chunk = std::make_shared<Chunk>();
|
||||||
|
locals.clear();
|
||||||
|
loops.clear();
|
||||||
|
scope_depth = 0;
|
||||||
|
|
||||||
|
// Add an empty local for the function itself (or 'this') at slot 0
|
||||||
|
add_local("this");
|
||||||
|
|
||||||
|
// Add parameters as locals at depth 0
|
||||||
|
for (const auto ¶m : stmt.parameters) {
|
||||||
|
add_local(param.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile body
|
||||||
|
try {
|
||||||
|
compile_stmt(*stmt.body);
|
||||||
|
} catch (const CompileError &) {
|
||||||
|
// Error already reported
|
||||||
|
had_error = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure function returns
|
||||||
|
emit_opcode(OpCode::OP_NIL);
|
||||||
|
emit_opcode(OpCode::OP_RETURN);
|
||||||
|
|
||||||
|
auto func_chunk = current_chunk;
|
||||||
|
|
||||||
|
// Restore state
|
||||||
|
current_chunk = prev_chunk;
|
||||||
|
locals = std::move(prev_locals);
|
||||||
|
scope_depth = prev_scope_depth;
|
||||||
|
loops = std::move(prev_loops);
|
||||||
|
|
||||||
|
auto func_decl = std::make_shared<FunctionDecl>(stmt);
|
||||||
|
auto func = std::make_shared<FunctionValue>(stmt.name, func_decl, func_chunk);
|
||||||
|
|
||||||
|
emit_constant(func);
|
||||||
|
|
||||||
|
if (scope_depth == 0) {
|
||||||
|
emit_bytes(static_cast<uint8_t>(OpCode::OP_DEFINE_GLOBAL),
|
||||||
|
identifier_constant(stmt.name));
|
||||||
|
} else {
|
||||||
|
add_local(stmt.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::compile_class_decl(const ClassDecl &stmt) {
|
||||||
|
// Create class value with fields and methods
|
||||||
|
auto klass = std::make_shared<ClassValue>(stmt.name);
|
||||||
|
|
||||||
|
// Add fields and methods to the class
|
||||||
|
for (const auto &member : stmt.members) {
|
||||||
|
if (auto *var_decl = dynamic_cast<VarDecl *>(member.get())) {
|
||||||
|
// Field declaration
|
||||||
|
klass->add_field(var_decl->name, var_decl->type_name);
|
||||||
|
} else if (auto *func_decl = dynamic_cast<FunctionDecl *>(member.get())) {
|
||||||
|
// Method declaration - compile to bytecode
|
||||||
|
auto prev_chunk = current_chunk;
|
||||||
|
auto prev_locals = std::move(locals);
|
||||||
|
auto prev_scope_depth = scope_depth;
|
||||||
|
auto prev_loops = std::move(loops);
|
||||||
|
|
||||||
|
current_chunk = std::make_shared<Chunk>();
|
||||||
|
locals.clear();
|
||||||
|
loops.clear();
|
||||||
|
scope_depth = 0;
|
||||||
|
|
||||||
|
add_local("this");
|
||||||
|
for (const auto ¶m : func_decl->parameters) {
|
||||||
|
add_local(param.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
compile_stmt(*func_decl->body);
|
||||||
|
} catch (const CompileError &) {
|
||||||
|
had_error = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (func_decl->name == "init") {
|
||||||
|
emit_bytes(static_cast<uint8_t>(OpCode::OP_GET_LOCAL), 0);
|
||||||
|
} else {
|
||||||
|
emit_opcode(OpCode::OP_NIL);
|
||||||
|
}
|
||||||
|
emit_opcode(OpCode::OP_RETURN);
|
||||||
|
|
||||||
|
auto method_chunk = current_chunk;
|
||||||
|
|
||||||
|
current_chunk = prev_chunk;
|
||||||
|
locals = std::move(prev_locals);
|
||||||
|
scope_depth = prev_scope_depth;
|
||||||
|
loops = std::move(prev_loops);
|
||||||
|
|
||||||
|
auto func_decl_ptr = std::make_shared<FunctionDecl>(*func_decl);
|
||||||
|
auto func = std::make_shared<FunctionValue>(func_decl->name,
|
||||||
|
func_decl_ptr, method_chunk);
|
||||||
|
klass->add_method(func_decl->name, func);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push the class as a constant and define it as a global
|
||||||
|
emit_constant(klass);
|
||||||
|
emit_bytes(static_cast<uint8_t>(OpCode::OP_DEFINE_GLOBAL),
|
||||||
|
identifier_constant(stmt.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper methods
|
||||||
|
|
||||||
|
void Compiler::emit_byte(uint8_t byte) {
|
||||||
|
current_chunk->write(byte, 0); // Line number tracking could be improved
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::emit_opcode(OpCode op) { emit_byte(static_cast<uint8_t>(op)); }
|
||||||
|
|
||||||
|
void Compiler::emit_bytes(uint8_t byte1, uint8_t byte2) {
|
||||||
|
emit_byte(byte1);
|
||||||
|
emit_byte(byte2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::emit_constant(ValuePtr value) {
|
||||||
|
emit_bytes(static_cast<uint8_t>(OpCode::OP_CONSTANT), make_constant(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Compiler::emit_jump(OpCode op) {
|
||||||
|
emit_opcode(op);
|
||||||
|
emit_byte(0xff);
|
||||||
|
emit_byte(0xff);
|
||||||
|
return current_chunk->current_offset() - 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::patch_jump(size_t offset) { current_chunk->patch_jump(offset); }
|
||||||
|
|
||||||
|
void Compiler::emit_loop(size_t loop_start) {
|
||||||
|
emit_opcode(OpCode::OP_LOOP);
|
||||||
|
|
||||||
|
size_t offset = current_chunk->current_offset() - loop_start + 2;
|
||||||
|
if (offset > UINT16_MAX) {
|
||||||
|
report_error("Loop body too large.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit_byte((offset >> 8) & 0xff);
|
||||||
|
emit_byte(offset & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::begin_scope() { scope_depth++; }
|
||||||
|
|
||||||
|
void Compiler::end_scope() {
|
||||||
|
scope_depth--;
|
||||||
|
|
||||||
|
// Pop all local variables in this scope
|
||||||
|
while (!locals.empty() && locals.back().depth > scope_depth) {
|
||||||
|
emit_opcode(OpCode::OP_POP);
|
||||||
|
locals.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::add_local(const std::string &name) {
|
||||||
|
if (locals.size() >= UINT8_MAX) {
|
||||||
|
report_error("Too many local variables in scope.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
locals.push_back({name, scope_depth, false});
|
||||||
|
}
|
||||||
|
|
||||||
|
int Compiler::resolve_local(const std::string &name) {
|
||||||
|
for (int i = static_cast<int>(locals.size()) - 1; i >= 0; i--) {
|
||||||
|
if (locals[i].name == name) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t Compiler::make_constant(ValuePtr value) {
|
||||||
|
size_t constant = current_chunk->add_constant(value);
|
||||||
|
if (constant > UINT8_MAX) {
|
||||||
|
report_error("Too many constants in one chunk.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return static_cast<uint8_t>(constant);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t Compiler::identifier_constant(const std::string &name) {
|
||||||
|
return make_constant(std::make_shared<StringValue>(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compiler::report_error(const std::string &message) {
|
||||||
|
had_error = true;
|
||||||
|
error_message = message;
|
||||||
|
throw CompileError(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace camellya
|
||||||
99
src/compiler.h
Normal file
99
src/compiler.h
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
#ifndef CAMELLYA_COMPILER_H
|
||||||
|
#define CAMELLYA_COMPILER_H
|
||||||
|
|
||||||
|
#include "ast.h"
|
||||||
|
#include "chunk.h"
|
||||||
|
#include "value.h"
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace camellya {
|
||||||
|
|
||||||
|
// Local variable information
|
||||||
|
struct Local {
|
||||||
|
std::string name;
|
||||||
|
int depth;
|
||||||
|
bool is_captured;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Loop information for break/continue
|
||||||
|
struct LoopInfo {
|
||||||
|
size_t start; // Loop start position (for continue)
|
||||||
|
std::vector<size_t> breaks; // Break jump positions to patch
|
||||||
|
int scope_depth; // Scope depth at loop start
|
||||||
|
};
|
||||||
|
|
||||||
|
// Compiler class for converting AST to bytecode
|
||||||
|
class Compiler {
|
||||||
|
public:
|
||||||
|
Compiler();
|
||||||
|
|
||||||
|
// Compile a program into a chunk
|
||||||
|
std::shared_ptr<Chunk> compile(const Program &program);
|
||||||
|
|
||||||
|
// Get the last error message
|
||||||
|
const std::string &get_error() const { return error_message; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<Chunk> current_chunk;
|
||||||
|
std::vector<Local> locals;
|
||||||
|
std::vector<LoopInfo> loops; // Stack of loop information
|
||||||
|
int scope_depth;
|
||||||
|
std::string error_message;
|
||||||
|
bool had_error;
|
||||||
|
|
||||||
|
// Compilation methods for expressions
|
||||||
|
void compile_expr(const Expr &expr);
|
||||||
|
void compile_binary(const BinaryExpr &expr);
|
||||||
|
void compile_unary(const UnaryExpr &expr);
|
||||||
|
void compile_literal(const LiteralExpr &expr);
|
||||||
|
void compile_variable(const VariableExpr &expr);
|
||||||
|
void compile_assign(const AssignExpr &expr);
|
||||||
|
void compile_call(const CallExpr &expr);
|
||||||
|
void compile_get(const GetExpr &expr);
|
||||||
|
void compile_set(const SetExpr &expr);
|
||||||
|
void compile_index(const IndexExpr &expr);
|
||||||
|
void compile_index_set(const IndexSetExpr &expr);
|
||||||
|
void compile_list(const ListExpr &expr);
|
||||||
|
void compile_map(const MapExpr &expr);
|
||||||
|
|
||||||
|
// Compilation methods for statements
|
||||||
|
void compile_stmt(const Stmt &stmt);
|
||||||
|
void compile_expr_stmt(const ExprStmt &stmt);
|
||||||
|
void compile_var_decl(const VarDecl &stmt);
|
||||||
|
void compile_block(const BlockStmt &stmt);
|
||||||
|
void compile_if(const IfStmt &stmt);
|
||||||
|
void compile_while(const WhileStmt &stmt);
|
||||||
|
void compile_for(const ForStmt &stmt);
|
||||||
|
void compile_return(const ReturnStmt &stmt);
|
||||||
|
void compile_break(const BreakStmt &stmt);
|
||||||
|
void compile_continue(const ContinueStmt &stmt);
|
||||||
|
void compile_function_decl(const FunctionDecl &stmt);
|
||||||
|
void compile_class_decl(const ClassDecl &stmt);
|
||||||
|
|
||||||
|
// Helper methods
|
||||||
|
void emit_byte(uint8_t byte);
|
||||||
|
void emit_opcode(OpCode op);
|
||||||
|
void emit_bytes(uint8_t byte1, uint8_t byte2);
|
||||||
|
void emit_constant(ValuePtr value);
|
||||||
|
size_t emit_jump(OpCode op);
|
||||||
|
void patch_jump(size_t offset);
|
||||||
|
void emit_loop(size_t loop_start);
|
||||||
|
|
||||||
|
// Variable management
|
||||||
|
void begin_scope();
|
||||||
|
void end_scope();
|
||||||
|
void add_local(const std::string &name);
|
||||||
|
int resolve_local(const std::string &name);
|
||||||
|
|
||||||
|
// Constant pool
|
||||||
|
uint8_t make_constant(ValuePtr value);
|
||||||
|
uint8_t identifier_constant(const std::string &name);
|
||||||
|
|
||||||
|
// Error handling
|
||||||
|
void report_error(const std::string &message);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace camellya
|
||||||
|
|
||||||
|
#endif // CAMELLYA_COMPILER_H
|
||||||
32
src/exceptions.h
Normal file
32
src/exceptions.h
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#ifndef CAMELLYA_EXCEPTIONS_H
|
||||||
|
#define CAMELLYA_EXCEPTIONS_H
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace camellya {
|
||||||
|
|
||||||
|
// Runtime error for both interpreter and VM
|
||||||
|
class RuntimeError : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
explicit RuntimeError(const std::string& message)
|
||||||
|
: std::runtime_error(message) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Compile-time error
|
||||||
|
class CompileError : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
explicit CompileError(const std::string& message)
|
||||||
|
: std::runtime_error(message) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse error
|
||||||
|
class ParseError : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
explicit ParseError(const std::string& message)
|
||||||
|
: std::runtime_error(message) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace camellya
|
||||||
|
|
||||||
|
#endif // CAMELLYA_EXCEPTIONS_H
|
||||||
@@ -1,551 +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);
|
|
||||||
} else {
|
|
||||||
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,156 +0,0 @@
|
|||||||
#ifndef CAMELLYA_INTERPRETER_H
|
|
||||||
#define CAMELLYA_INTERPRETER_H
|
|
||||||
|
|
||||||
#include "ast.h"
|
|
||||||
#include "value.h"
|
|
||||||
#include <map>
|
|
||||||
#include <memory>
|
|
||||||
#include <stdexcept>
|
|
||||||
|
|
||||||
namespace camellya {
|
|
||||||
|
|
||||||
class RuntimeError : public std::runtime_error {
|
|
||||||
public:
|
|
||||||
explicit RuntimeError(const std::string& message) : std::runtime_error(message) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
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
|
|
||||||
131
src/opcode.h
Normal file
131
src/opcode.h
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
#ifndef CAMELLYA_OPCODE_H
|
||||||
|
#define CAMELLYA_OPCODE_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace camellya {
|
||||||
|
|
||||||
|
// Bytecode operation codes
|
||||||
|
enum class OpCode : uint8_t {
|
||||||
|
// Constants and literals
|
||||||
|
OP_CONSTANT, // Load constant from constant pool
|
||||||
|
OP_NIL, // Push nil
|
||||||
|
OP_TRUE, // Push true
|
||||||
|
OP_FALSE, // Push false
|
||||||
|
|
||||||
|
// Arithmetic operations
|
||||||
|
OP_ADD, // Binary +
|
||||||
|
OP_SUBTRACT, // Binary -
|
||||||
|
OP_MULTIPLY, // Binary *
|
||||||
|
OP_DIVIDE, // Binary /
|
||||||
|
OP_MODULO, // Binary %
|
||||||
|
OP_NEGATE, // Unary -
|
||||||
|
|
||||||
|
// Comparison operations
|
||||||
|
OP_EQUAL, // ==
|
||||||
|
OP_NOT_EQUAL, // !=
|
||||||
|
OP_GREATER, // >
|
||||||
|
OP_GREATER_EQUAL, // >=
|
||||||
|
OP_LESS, // <
|
||||||
|
OP_LESS_EQUAL, // <=
|
||||||
|
|
||||||
|
// Logical operations
|
||||||
|
OP_NOT, // Unary !
|
||||||
|
OP_AND, // and
|
||||||
|
OP_OR, // or
|
||||||
|
|
||||||
|
// Variables
|
||||||
|
OP_GET_GLOBAL, // Get global variable
|
||||||
|
OP_SET_GLOBAL, // Set global variable
|
||||||
|
OP_DEFINE_GLOBAL, // Define global variable
|
||||||
|
OP_GET_LOCAL, // Get local variable
|
||||||
|
OP_SET_LOCAL, // Set local variable
|
||||||
|
|
||||||
|
// Control flow
|
||||||
|
OP_JUMP, // Unconditional jump
|
||||||
|
OP_JUMP_IF_FALSE, // Jump if top of stack is false
|
||||||
|
OP_LOOP, // Loop back (negative jump)
|
||||||
|
|
||||||
|
// Functions
|
||||||
|
OP_CALL, // Call function with N arguments
|
||||||
|
OP_RETURN, // Return from function
|
||||||
|
OP_CLOSURE, // Create closure
|
||||||
|
|
||||||
|
// Collections
|
||||||
|
OP_BUILD_LIST, // Build list from N stack values
|
||||||
|
OP_BUILD_MAP, // Build map from N key-value pairs
|
||||||
|
OP_INDEX, // Index access obj[index]
|
||||||
|
OP_INDEX_SET, // Index assignment obj[index] = value
|
||||||
|
|
||||||
|
// Object-oriented
|
||||||
|
OP_CLASS, // Define class
|
||||||
|
OP_GET_PROPERTY, // Get object property
|
||||||
|
OP_SET_PROPERTY, // Set object property
|
||||||
|
OP_METHOD, // Define method
|
||||||
|
OP_INVOKE, // Optimized method call
|
||||||
|
|
||||||
|
// Stack operations
|
||||||
|
OP_POP, // Pop and discard top of stack
|
||||||
|
OP_POPN, // Pop N values from stack
|
||||||
|
OP_DUP, // Duplicate top of stack
|
||||||
|
|
||||||
|
// Other
|
||||||
|
OP_PRINT, // Built-in print (for debugging)
|
||||||
|
OP_HALT, // Halt execution
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get human-readable name for opcode
|
||||||
|
inline std::string opcode_name(OpCode op) {
|
||||||
|
switch (op) {
|
||||||
|
case OpCode::OP_CONSTANT: return "OP_CONSTANT";
|
||||||
|
case OpCode::OP_NIL: return "OP_NIL";
|
||||||
|
case OpCode::OP_TRUE: return "OP_TRUE";
|
||||||
|
case OpCode::OP_FALSE: return "OP_FALSE";
|
||||||
|
case OpCode::OP_ADD: return "OP_ADD";
|
||||||
|
case OpCode::OP_SUBTRACT: return "OP_SUBTRACT";
|
||||||
|
case OpCode::OP_MULTIPLY: return "OP_MULTIPLY";
|
||||||
|
case OpCode::OP_DIVIDE: return "OP_DIVIDE";
|
||||||
|
case OpCode::OP_MODULO: return "OP_MODULO";
|
||||||
|
case OpCode::OP_NEGATE: return "OP_NEGATE";
|
||||||
|
case OpCode::OP_EQUAL: return "OP_EQUAL";
|
||||||
|
case OpCode::OP_NOT_EQUAL: return "OP_NOT_EQUAL";
|
||||||
|
case OpCode::OP_GREATER: return "OP_GREATER";
|
||||||
|
case OpCode::OP_GREATER_EQUAL: return "OP_GREATER_EQUAL";
|
||||||
|
case OpCode::OP_LESS: return "OP_LESS";
|
||||||
|
case OpCode::OP_LESS_EQUAL: return "OP_LESS_EQUAL";
|
||||||
|
case OpCode::OP_NOT: return "OP_NOT";
|
||||||
|
case OpCode::OP_AND: return "OP_AND";
|
||||||
|
case OpCode::OP_OR: return "OP_OR";
|
||||||
|
case OpCode::OP_GET_GLOBAL: return "OP_GET_GLOBAL";
|
||||||
|
case OpCode::OP_SET_GLOBAL: return "OP_SET_GLOBAL";
|
||||||
|
case OpCode::OP_DEFINE_GLOBAL: return "OP_DEFINE_GLOBAL";
|
||||||
|
case OpCode::OP_GET_LOCAL: return "OP_GET_LOCAL";
|
||||||
|
case OpCode::OP_SET_LOCAL: return "OP_SET_LOCAL";
|
||||||
|
case OpCode::OP_JUMP: return "OP_JUMP";
|
||||||
|
case OpCode::OP_JUMP_IF_FALSE: return "OP_JUMP_IF_FALSE";
|
||||||
|
case OpCode::OP_LOOP: return "OP_LOOP";
|
||||||
|
case OpCode::OP_CALL: return "OP_CALL";
|
||||||
|
case OpCode::OP_RETURN: return "OP_RETURN";
|
||||||
|
case OpCode::OP_CLOSURE: return "OP_CLOSURE";
|
||||||
|
case OpCode::OP_BUILD_LIST: return "OP_BUILD_LIST";
|
||||||
|
case OpCode::OP_BUILD_MAP: return "OP_BUILD_MAP";
|
||||||
|
case OpCode::OP_INDEX: return "OP_INDEX";
|
||||||
|
case OpCode::OP_INDEX_SET: return "OP_INDEX_SET";
|
||||||
|
case OpCode::OP_CLASS: return "OP_CLASS";
|
||||||
|
case OpCode::OP_GET_PROPERTY: return "OP_GET_PROPERTY";
|
||||||
|
case OpCode::OP_SET_PROPERTY: return "OP_SET_PROPERTY";
|
||||||
|
case OpCode::OP_METHOD: return "OP_METHOD";
|
||||||
|
case OpCode::OP_INVOKE: return "OP_INVOKE";
|
||||||
|
case OpCode::OP_POP: return "OP_POP";
|
||||||
|
case OpCode::OP_POPN: return "OP_POPN";
|
||||||
|
case OpCode::OP_DUP: return "OP_DUP";
|
||||||
|
case OpCode::OP_PRINT: return "OP_PRINT";
|
||||||
|
case OpCode::OP_HALT: return "OP_HALT";
|
||||||
|
default: return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace camellya
|
||||||
|
|
||||||
|
#endif // CAMELLYA_OPCODE_H
|
||||||
@@ -94,14 +94,9 @@ StmtPtr Parser::declaration() {
|
|||||||
if (match({TokenType::FUNC})) {
|
if (match({TokenType::FUNC})) {
|
||||||
return function_declaration();
|
return function_declaration();
|
||||||
}
|
}
|
||||||
if (check(TokenType::NUMBER) || check(TokenType::STRING) ||
|
// Handle var keyword for variable declaration
|
||||||
check(TokenType::BOOL) || check(TokenType::LIST) ||
|
if (match({TokenType::VAR})) {
|
||||||
check(TokenType::MAP) || check(TokenType::IDENTIFIER)) {
|
return var_declaration();
|
||||||
// Type name followed by identifier
|
|
||||||
Token lookahead = tokens_[current_];
|
|
||||||
if (current_ + 1 < tokens_.size() && tokens_[current_ + 1].type == TokenType::IDENTIFIER) {
|
|
||||||
return var_declaration();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return statement();
|
return statement();
|
||||||
@@ -118,8 +113,10 @@ StmtPtr Parser::class_declaration() {
|
|||||||
if (check(TokenType::FUNC)) {
|
if (check(TokenType::FUNC)) {
|
||||||
advance();
|
advance();
|
||||||
members.push_back(function_declaration());
|
members.push_back(function_declaration());
|
||||||
} else {
|
} else if (match({TokenType::VAR})) {
|
||||||
members.push_back(var_declaration());
|
members.push_back(var_declaration());
|
||||||
|
} else {
|
||||||
|
throw error(peek(), "Expected 'var' or 'func' in class body.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,17 +158,35 @@ StmtPtr Parser::function_declaration() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StmtPtr Parser::var_declaration() {
|
StmtPtr Parser::var_declaration() {
|
||||||
Token type_token = advance();
|
// var keyword has already been consumed
|
||||||
std::string type_name = type_token.lexeme;
|
Token name = consume(TokenType::IDENTIFIER, "Expected variable name after 'var'.");
|
||||||
Token name = consume(TokenType::IDENTIFIER, "Expected variable name.");
|
|
||||||
|
|
||||||
|
std::string type_name;
|
||||||
|
bool infer_type = false;
|
||||||
ExprPtr initializer = nullptr;
|
ExprPtr initializer = nullptr;
|
||||||
if (match({TokenType::EQUAL})) {
|
|
||||||
|
// Check for explicit type annotation: var x : type
|
||||||
|
if (match({TokenType::COLON})) {
|
||||||
|
Token type_token = advance();
|
||||||
|
type_name = type_token.lexeme;
|
||||||
|
|
||||||
|
// Optional initializer
|
||||||
|
if (match({TokenType::EQUAL})) {
|
||||||
|
initializer = expression();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No type annotation, must have initializer for type inference
|
||||||
|
if (!match({TokenType::EQUAL})) {
|
||||||
|
throw error(peek(), "Variable declaration without type annotation must have an initializer.");
|
||||||
|
}
|
||||||
initializer = expression();
|
initializer = expression();
|
||||||
|
infer_type = true;
|
||||||
|
type_name = ""; // Will be inferred at runtime
|
||||||
}
|
}
|
||||||
|
|
||||||
consume(TokenType::SEMICOLON, "Expected ';' after variable declaration.");
|
consume(TokenType::SEMICOLON, "Expected ';' after variable declaration.");
|
||||||
|
|
||||||
return std::make_unique<VarDecl>(type_name, name.lexeme, std::move(initializer));
|
return std::make_unique<VarDecl>(type_name, name.lexeme, std::move(initializer), infer_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
StmtPtr Parser::statement() {
|
StmtPtr Parser::statement() {
|
||||||
|
|||||||
@@ -3,15 +3,10 @@
|
|||||||
|
|
||||||
#include "lexer.h"
|
#include "lexer.h"
|
||||||
#include "ast.h"
|
#include "ast.h"
|
||||||
#include <stdexcept>
|
#include "exceptions.h"
|
||||||
|
|
||||||
namespace camellya {
|
namespace camellya {
|
||||||
|
|
||||||
class ParseError : public std::runtime_error {
|
|
||||||
public:
|
|
||||||
explicit ParseError(const std::string& message) : std::runtime_error(message) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class Parser {
|
class Parser {
|
||||||
public:
|
public:
|
||||||
explicit Parser(std::vector<Token> tokens);
|
explicit Parser(std::vector<Token> tokens);
|
||||||
|
|||||||
135
src/state.cpp
135
src/state.cpp
@@ -1,135 +0,0 @@
|
|||||||
#include "state.h"
|
|
||||||
#include <fstream>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
namespace camellya {
|
|
||||||
|
|
||||||
State::State() : interpreter_(std::make_unique<Interpreter>()) {}
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
interpreter_->execute(program);
|
|
||||||
|
|
||||||
last_error_.clear();
|
|
||||||
return true;
|
|
||||||
} 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);
|
|
||||||
interpreter_->global_environment->define(name, func_value);
|
|
||||||
}
|
|
||||||
|
|
||||||
ValuePtr State::get_global(const std::string& name) {
|
|
||||||
return interpreter_->global_environment->get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
void State::set_global(const std::string& name, ValuePtr value) {
|
|
||||||
interpreter_->global_environment->define(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];
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace camellya
|
|
||||||
63
src/state.h
63
src/state.h
@@ -1,63 +0,0 @@
|
|||||||
#ifndef CAMELLYA_STATE_H
|
|
||||||
#define CAMELLYA_STATE_H
|
|
||||||
|
|
||||||
#include "lexer.h"
|
|
||||||
#include "parser.h"
|
|
||||||
#include "interpreter.h"
|
|
||||||
#include "value.h"
|
|
||||||
#include <string>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
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<Interpreter> interpreter_;
|
|
||||||
std::vector<ValuePtr> stack_;
|
|
||||||
std::string last_error_;
|
|
||||||
|
|
||||||
ValuePtr get_stack_value(int index);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace camellya
|
|
||||||
|
|
||||||
#endif // CAMELLYA_STATE_H
|
|
||||||
171
src/value.cpp
171
src/value.cpp
@@ -1,124 +1,125 @@
|
|||||||
#include "value.h"
|
#include "value.h"
|
||||||
#include <sstream>
|
|
||||||
#include <iomanip>
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
namespace camellya {
|
namespace camellya {
|
||||||
|
|
||||||
std::string NumberValue::to_string() const {
|
std::string NumberValue::to_string() const {
|
||||||
if (std::floor(value) == value) {
|
if (std::floor(value) == value) {
|
||||||
return std::to_string(static_cast<int64_t>(value));
|
return std::to_string(static_cast<int64_t>(value));
|
||||||
}
|
}
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
oss << std::fixed << std::setprecision(6) << value;
|
oss << std::fixed << std::setprecision(6) << value;
|
||||||
std::string str = oss.str();
|
std::string str = oss.str();
|
||||||
str.erase(str.find_last_not_of('0') + 1, std::string::npos);
|
str.erase(str.find_last_not_of('0') + 1, std::string::npos);
|
||||||
if (str.back() == '.') str.pop_back();
|
if (str.back() == '.')
|
||||||
return str;
|
str.pop_back();
|
||||||
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ListValue::to_string() const {
|
std::string ListValue::to_string() const {
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
oss << "[";
|
oss << "[";
|
||||||
for (size_t i = 0; i < elements.size(); ++i) {
|
for (size_t i = 0; i < elements.size(); ++i) {
|
||||||
if (i > 0) oss << ", ";
|
if (i > 0)
|
||||||
oss << elements[i]->to_string();
|
oss << ", ";
|
||||||
}
|
oss << elements[i]->to_string();
|
||||||
oss << "]";
|
}
|
||||||
return oss.str();
|
oss << "]";
|
||||||
|
return oss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
ValuePtr ListValue::clone() const {
|
ValuePtr ListValue::clone() const {
|
||||||
std::vector<ValuePtr> cloned_elements;
|
std::vector<ValuePtr> cloned_elements;
|
||||||
cloned_elements.reserve(elements.size());
|
cloned_elements.reserve(elements.size());
|
||||||
for (const auto& elem : elements) {
|
for (const auto &elem : elements) {
|
||||||
cloned_elements.push_back(elem->clone());
|
cloned_elements.push_back(elem->clone());
|
||||||
}
|
}
|
||||||
return std::make_shared<ListValue>(std::move(cloned_elements));
|
return std::make_shared<ListValue>(std::move(cloned_elements));
|
||||||
}
|
}
|
||||||
|
|
||||||
ValuePtr ListValue::get(size_t index) const {
|
ValuePtr ListValue::get(size_t index) const {
|
||||||
if (index >= elements.size()) {
|
if (index >= elements.size()) {
|
||||||
return std::make_shared<NilValue>();
|
return std::make_shared<NilValue>();
|
||||||
}
|
}
|
||||||
return elements[index];
|
return elements[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
void ListValue::set(size_t index, ValuePtr value) {
|
void ListValue::set(size_t index, ValuePtr value) {
|
||||||
if (index >= elements.size()) {
|
if (index >= elements.size()) {
|
||||||
elements.resize(index + 1, std::make_shared<NilValue>());
|
elements.resize(index + 1, std::make_shared<NilValue>());
|
||||||
}
|
}
|
||||||
elements[index] = std::move(value);
|
elements[index] = std::move(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string MapValue::to_string() const {
|
std::string MapValue::to_string() const {
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
oss << "{";
|
oss << "{";
|
||||||
bool first = true;
|
bool first = true;
|
||||||
for (const auto& [key, value] : pairs) {
|
for (const auto &[key, value] : pairs) {
|
||||||
if (!first) oss << ", ";
|
if (!first)
|
||||||
first = false;
|
oss << ", ";
|
||||||
oss << key << ": " << value->to_string();
|
first = false;
|
||||||
}
|
oss << key << ": " << value->to_string();
|
||||||
oss << "}";
|
}
|
||||||
return oss.str();
|
oss << "}";
|
||||||
|
return oss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
ValuePtr MapValue::clone() const {
|
ValuePtr MapValue::clone() const {
|
||||||
std::map<std::string, ValuePtr> cloned_pairs;
|
std::map<std::string, ValuePtr> cloned_pairs;
|
||||||
for (const auto& [key, value] : pairs) {
|
for (const auto &[key, value] : pairs) {
|
||||||
cloned_pairs[key] = value->clone();
|
cloned_pairs[key] = value->clone();
|
||||||
}
|
}
|
||||||
return std::make_shared<MapValue>(std::move(cloned_pairs));
|
return std::make_shared<MapValue>(std::move(cloned_pairs));
|
||||||
}
|
}
|
||||||
|
|
||||||
ValuePtr MapValue::get(const std::string& key) const {
|
ValuePtr MapValue::get(const std::string &key) const {
|
||||||
auto it = pairs.find(key);
|
auto it = pairs.find(key);
|
||||||
if (it == pairs.end()) {
|
if (it == pairs.end()) {
|
||||||
return std::make_shared<NilValue>();
|
return std::make_shared<NilValue>();
|
||||||
}
|
}
|
||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
ValuePtr ClassValue::clone() const {
|
ValuePtr ClassValue::clone() const {
|
||||||
auto cloned = std::make_shared<ClassValue>(name);
|
auto cloned = std::make_shared<ClassValue>(name);
|
||||||
cloned->fields = fields;
|
cloned->fields = fields;
|
||||||
cloned->methods = methods;
|
cloned->methods = methods;
|
||||||
return cloned;
|
return cloned;
|
||||||
}
|
}
|
||||||
|
|
||||||
ValuePtr InstanceValue::clone() const {
|
ValuePtr InstanceValue::clone() const {
|
||||||
auto cloned = std::make_shared<InstanceValue>(klass);
|
auto cloned = std::make_shared<InstanceValue>(klass);
|
||||||
for (const auto& [key, value] : fields) {
|
for (const auto &[key, value] : fields) {
|
||||||
cloned->fields[key] = value->clone();
|
cloned->fields[key] = value->clone();
|
||||||
}
|
}
|
||||||
return cloned;
|
return cloned;
|
||||||
}
|
}
|
||||||
|
|
||||||
ValuePtr InstanceValue::get(const std::string& name) const {
|
ValuePtr InstanceValue::get(const std::string &name) const {
|
||||||
// First check fields
|
// First check fields
|
||||||
auto field_it = fields.find(name);
|
auto field_it = fields.find(name);
|
||||||
if (field_it != fields.end()) {
|
if (field_it != fields.end()) {
|
||||||
return field_it->second;
|
return field_it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then check methods and bind 'this'
|
// Then check methods and bind 'this'
|
||||||
auto method_it = klass->methods.find(name);
|
auto method_it = klass->methods.find(name);
|
||||||
if (method_it != klass->methods.end()) {
|
if (method_it != klass->methods.end()) {
|
||||||
auto bound = std::make_shared<FunctionValue>(*method_it->second);
|
auto bound = std::make_shared<FunctionValue>(*method_it->second);
|
||||||
auto self = std::static_pointer_cast<InstanceValue>(
|
bound->bound_instance = const_cast<InstanceValue *>(this)->shared_from_this();
|
||||||
const_cast<InstanceValue*>(this)->shared_from_this());
|
return bound;
|
||||||
bound->bound_instance = self;
|
}
|
||||||
return bound;
|
|
||||||
}
|
return std::make_shared<NilValue>();
|
||||||
|
|
||||||
return std::make_shared<NilValue>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstanceValue::set(const std::string& name, ValuePtr value) {
|
void InstanceValue::set(const std::string &name, ValuePtr value) {
|
||||||
if (fields.find(name) != fields.end()) {
|
if (fields.find(name) != fields.end()) {
|
||||||
fields[name] = std::move(value);
|
fields[name] = std::move(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace camellya
|
} // namespace camellya
|
||||||
|
|||||||
39
src/value.h
39
src/value.h
@@ -24,13 +24,15 @@ enum class Type {
|
|||||||
MAP,
|
MAP,
|
||||||
FUNCTION,
|
FUNCTION,
|
||||||
CLASS,
|
CLASS,
|
||||||
INSTANCE
|
INSTANCE,
|
||||||
|
NATIVE
|
||||||
};
|
};
|
||||||
|
|
||||||
class Value {
|
class Value : public std::enable_shared_from_this<Value> {
|
||||||
public:
|
public:
|
||||||
virtual ~Value() = default;
|
virtual ~Value() = default;
|
||||||
virtual Type type() const = 0;
|
virtual Type type() const = 0;
|
||||||
|
virtual std::string type_name() const = 0;
|
||||||
virtual std::string to_string() const = 0;
|
virtual std::string to_string() const = 0;
|
||||||
virtual ValuePtr clone() const = 0;
|
virtual ValuePtr clone() const = 0;
|
||||||
};
|
};
|
||||||
@@ -38,6 +40,7 @@ public:
|
|||||||
class NilValue : public Value {
|
class NilValue : public Value {
|
||||||
public:
|
public:
|
||||||
Type type() const override { return Type::NIL; }
|
Type type() const override { return Type::NIL; }
|
||||||
|
std::string type_name() const override { return "nil"; }
|
||||||
std::string to_string() const override { return "nil"; }
|
std::string to_string() const override { return "nil"; }
|
||||||
ValuePtr clone() const override { return std::make_shared<NilValue>(); }
|
ValuePtr clone() const override { return std::make_shared<NilValue>(); }
|
||||||
};
|
};
|
||||||
@@ -48,6 +51,7 @@ public:
|
|||||||
|
|
||||||
explicit NumberValue(double value) : value(value) {}
|
explicit NumberValue(double value) : value(value) {}
|
||||||
Type type() const override { return Type::NUMBER; }
|
Type type() const override { return Type::NUMBER; }
|
||||||
|
std::string type_name() const override { return "number"; }
|
||||||
std::string to_string() const override;
|
std::string to_string() const override;
|
||||||
ValuePtr clone() const override { return std::make_shared<NumberValue>(value); }
|
ValuePtr clone() const override { return std::make_shared<NumberValue>(value); }
|
||||||
};
|
};
|
||||||
@@ -58,6 +62,7 @@ public:
|
|||||||
|
|
||||||
explicit StringValue(std::string value) : value(std::move(value)) {}
|
explicit StringValue(std::string value) : value(std::move(value)) {}
|
||||||
Type type() const override { return Type::STRING; }
|
Type type() const override { return Type::STRING; }
|
||||||
|
std::string type_name() const override { return "string"; }
|
||||||
std::string to_string() const override { return value; }
|
std::string to_string() const override { return value; }
|
||||||
ValuePtr clone() const override { return std::make_shared<StringValue>(value); }
|
ValuePtr clone() const override { return std::make_shared<StringValue>(value); }
|
||||||
};
|
};
|
||||||
@@ -68,6 +73,7 @@ public:
|
|||||||
|
|
||||||
explicit BoolValue(bool value) : value(value) {}
|
explicit BoolValue(bool value) : value(value) {}
|
||||||
Type type() const override { return Type::BOOL; }
|
Type type() const override { return Type::BOOL; }
|
||||||
|
std::string type_name() const override { return "bool"; }
|
||||||
std::string to_string() const override { return value ? "true" : "false"; }
|
std::string to_string() const override { return value ? "true" : "false"; }
|
||||||
ValuePtr clone() const override { return std::make_shared<BoolValue>(value); }
|
ValuePtr clone() const override { return std::make_shared<BoolValue>(value); }
|
||||||
};
|
};
|
||||||
@@ -80,6 +86,7 @@ public:
|
|||||||
explicit ListValue(std::vector<ValuePtr> elements) : elements(std::move(elements)) {}
|
explicit ListValue(std::vector<ValuePtr> elements) : elements(std::move(elements)) {}
|
||||||
|
|
||||||
Type type() const override { return Type::LIST; }
|
Type type() const override { return Type::LIST; }
|
||||||
|
std::string type_name() const override { return "list"; }
|
||||||
std::string to_string() const override;
|
std::string to_string() const override;
|
||||||
ValuePtr clone() const override;
|
ValuePtr clone() const override;
|
||||||
|
|
||||||
@@ -97,6 +104,7 @@ public:
|
|||||||
explicit MapValue(std::map<std::string, ValuePtr> pairs) : pairs(std::move(pairs)) {}
|
explicit MapValue(std::map<std::string, ValuePtr> pairs) : pairs(std::move(pairs)) {}
|
||||||
|
|
||||||
Type type() const override { return Type::MAP; }
|
Type type() const override { return Type::MAP; }
|
||||||
|
std::string type_name() const override { return "map"; }
|
||||||
std::string to_string() const override;
|
std::string to_string() const override;
|
||||||
ValuePtr clone() const override;
|
ValuePtr clone() const override;
|
||||||
|
|
||||||
@@ -106,6 +114,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Forward declarations
|
// Forward declarations
|
||||||
|
class Chunk;
|
||||||
struct FunctionDecl;
|
struct FunctionDecl;
|
||||||
class ClassValue;
|
class ClassValue;
|
||||||
class InstanceValue;
|
class InstanceValue;
|
||||||
@@ -116,21 +125,25 @@ public:
|
|||||||
std::vector<std::pair<std::string, std::string>> parameters;
|
std::vector<std::pair<std::string, std::string>> parameters;
|
||||||
std::string return_type;
|
std::string return_type;
|
||||||
std::shared_ptr<FunctionDecl> declaration;
|
std::shared_ptr<FunctionDecl> declaration;
|
||||||
|
std::shared_ptr<Chunk> chunk;
|
||||||
NativeFunction native_func;
|
NativeFunction native_func;
|
||||||
bool is_native;
|
bool is_native;
|
||||||
std::shared_ptr<InstanceValue> bound_instance;
|
ValuePtr bound_instance;
|
||||||
|
|
||||||
// Script function
|
// Script function
|
||||||
FunctionValue(std::string name, std::shared_ptr<FunctionDecl> declaration,
|
FunctionValue(std::string name, std::shared_ptr<FunctionDecl> declaration,
|
||||||
std::shared_ptr<InstanceValue> bound_instance = nullptr)
|
std::shared_ptr<Chunk> chunk = nullptr,
|
||||||
|
ValuePtr bound_instance = nullptr)
|
||||||
: name(std::move(name)), declaration(std::move(declaration)),
|
: name(std::move(name)), declaration(std::move(declaration)),
|
||||||
is_native(false), bound_instance(std::move(bound_instance)) {}
|
chunk(std::move(chunk)), is_native(false),
|
||||||
|
bound_instance(std::move(bound_instance)) {}
|
||||||
|
|
||||||
// Native function
|
// Native function
|
||||||
FunctionValue(std::string name, NativeFunction func)
|
FunctionValue(std::string name, NativeFunction func)
|
||||||
: name(std::move(name)), native_func(std::move(func)), is_native(true) {}
|
: name(std::move(name)), native_func(std::move(func)), is_native(true) {}
|
||||||
|
|
||||||
Type type() const override { return Type::FUNCTION; }
|
Type type() const override { return Type::FUNCTION; }
|
||||||
|
std::string type_name() const override { return "function"; }
|
||||||
std::string to_string() const override { return "<function " + name + ">"; }
|
std::string to_string() const override { return "<function " + name + ">"; }
|
||||||
ValuePtr clone() const override { return std::make_shared<FunctionValue>(*this); }
|
ValuePtr clone() const override { return std::make_shared<FunctionValue>(*this); }
|
||||||
};
|
};
|
||||||
@@ -144,6 +157,7 @@ public:
|
|||||||
explicit ClassValue(std::string name) : name(std::move(name)) {}
|
explicit ClassValue(std::string name) : name(std::move(name)) {}
|
||||||
|
|
||||||
Type type() const override { return Type::CLASS; }
|
Type type() const override { return Type::CLASS; }
|
||||||
|
std::string type_name() const override { return "class"; }
|
||||||
std::string to_string() const override { return "<class " + name + ">"; }
|
std::string to_string() const override { return "<class " + name + ">"; }
|
||||||
ValuePtr clone() const override;
|
ValuePtr clone() const override;
|
||||||
|
|
||||||
@@ -156,7 +170,7 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class InstanceValue : public Value, public std::enable_shared_from_this<InstanceValue> {
|
class InstanceValue : public Value {
|
||||||
public:
|
public:
|
||||||
std::shared_ptr<ClassValue> klass;
|
std::shared_ptr<ClassValue> klass;
|
||||||
std::map<std::string, ValuePtr> fields;
|
std::map<std::string, ValuePtr> fields;
|
||||||
@@ -169,6 +183,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
Type type() const override { return Type::INSTANCE; }
|
Type type() const override { return Type::INSTANCE; }
|
||||||
|
std::string type_name() const override { return klass->name; }
|
||||||
std::string to_string() const override { return "<instance of " + klass->name + ">"; }
|
std::string to_string() const override { return "<instance of " + klass->name + ">"; }
|
||||||
ValuePtr clone() const override;
|
ValuePtr clone() const override;
|
||||||
|
|
||||||
@@ -176,6 +191,18 @@ public:
|
|||||||
void set(const std::string& name, ValuePtr value);
|
void set(const std::string& name, ValuePtr value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class NativeValue : public Value {
|
||||||
|
public:
|
||||||
|
std::string _type_name;
|
||||||
|
|
||||||
|
explicit NativeValue(std::string type_name) : _type_name(std::move(type_name)) {}
|
||||||
|
|
||||||
|
Type type() const override { return Type::NATIVE; }
|
||||||
|
std::string type_name() const override { return _type_name; }
|
||||||
|
std::string to_string() const override { return "<native " + _type_name + ">"; }
|
||||||
|
ValuePtr clone() const override { return std::make_shared<NativeValue>(_type_name); }
|
||||||
|
};
|
||||||
|
|
||||||
// Helper functions
|
// Helper functions
|
||||||
inline bool is_truthy(const ValuePtr& value) {
|
inline bool is_truthy(const ValuePtr& value) {
|
||||||
if (!value || value->type() == Type::NIL) return false;
|
if (!value || value->type() == Type::NIL) return false;
|
||||||
|
|||||||
734
src/vm.cpp
Normal file
734
src/vm.cpp
Normal file
@@ -0,0 +1,734 @@
|
|||||||
|
#include "vm.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <format>
|
||||||
|
#include <cmath>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace camellya {
|
||||||
|
|
||||||
|
VM::VM() : current_frame(nullptr) {
|
||||||
|
register_builtin_functions();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VM::execute(std::shared_ptr<Chunk> chunk) {
|
||||||
|
if (!chunk) {
|
||||||
|
runtime_error("Cannot execute null chunk");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
frames.clear();
|
||||||
|
frames.emplace_back(chunk, 0);
|
||||||
|
current_frame = &frames.back();
|
||||||
|
stack.clear();
|
||||||
|
|
||||||
|
try {
|
||||||
|
return run();
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
runtime_error(e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VM::run() {
|
||||||
|
while (true) {
|
||||||
|
// Debug: print instruction
|
||||||
|
#ifdef DEBUG_TRACE_EXECUTION
|
||||||
|
std::cout << "Stack: ";
|
||||||
|
for (const auto& value : stack) {
|
||||||
|
std::cout << "[ " << value->to_string() << " ]";
|
||||||
|
}
|
||||||
|
std::cout << std::endl;
|
||||||
|
current_frame->chunk->disassemble_instruction(current_frame->ip);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
OpCode instruction = static_cast<OpCode>(read_byte());
|
||||||
|
|
||||||
|
switch (instruction) {
|
||||||
|
case OpCode::OP_CONSTANT: {
|
||||||
|
ValuePtr constant = read_constant();
|
||||||
|
push(constant);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OpCode::OP_NIL:
|
||||||
|
push(std::make_shared<NilValue>());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OpCode::OP_TRUE:
|
||||||
|
push(std::make_shared<BoolValue>(true));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OpCode::OP_FALSE:
|
||||||
|
push(std::make_shared<BoolValue>(false));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OpCode::OP_ADD:
|
||||||
|
case OpCode::OP_SUBTRACT:
|
||||||
|
case OpCode::OP_MULTIPLY:
|
||||||
|
case OpCode::OP_DIVIDE:
|
||||||
|
case OpCode::OP_MODULO:
|
||||||
|
case OpCode::OP_GREATER:
|
||||||
|
case OpCode::OP_GREATER_EQUAL:
|
||||||
|
case OpCode::OP_LESS:
|
||||||
|
case OpCode::OP_LESS_EQUAL:
|
||||||
|
if (!binary_op(instruction)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OpCode::OP_EQUAL: {
|
||||||
|
ValuePtr b = pop();
|
||||||
|
ValuePtr a = pop();
|
||||||
|
push(std::make_shared<BoolValue>(values_equal(a, b)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OpCode::OP_NOT_EQUAL: {
|
||||||
|
ValuePtr b = pop();
|
||||||
|
ValuePtr a = pop();
|
||||||
|
push(std::make_shared<BoolValue>(!values_equal(a, b)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OpCode::OP_NEGATE: {
|
||||||
|
ValuePtr value = pop();
|
||||||
|
if (value->type() != Type::NUMBER) {
|
||||||
|
runtime_error("Operand must be a number.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
double num = std::dynamic_pointer_cast<NumberValue>(value)->value;
|
||||||
|
push(std::make_shared<NumberValue>(-num));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OpCode::OP_NOT: {
|
||||||
|
ValuePtr value = pop();
|
||||||
|
push(std::make_shared<BoolValue>(is_falsey(value)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OpCode::OP_POP:
|
||||||
|
pop();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OpCode::OP_POPN: {
|
||||||
|
uint8_t count = read_byte();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
pop();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OpCode::OP_DUP:
|
||||||
|
push(peek(0));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OpCode::OP_GET_GLOBAL: {
|
||||||
|
std::string name = read_string();
|
||||||
|
auto it = globals.find(name);
|
||||||
|
if (it == globals.end()) {
|
||||||
|
// Fallback: check if 'this' is at slot 0 and has the property
|
||||||
|
ValuePtr receiver = stack[current_frame->stack_offset];
|
||||||
|
if (receiver && receiver->type() == Type::INSTANCE) {
|
||||||
|
auto instance = std::dynamic_pointer_cast<InstanceValue>(receiver);
|
||||||
|
if (instance->fields.find(name) != instance->fields.end()) {
|
||||||
|
push(instance->get(name));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runtime_error("Undefined variable '" + name + "'.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
push(it->second);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OpCode::OP_SET_GLOBAL: {
|
||||||
|
std::string name = read_string();
|
||||||
|
auto it = globals.find(name);
|
||||||
|
if (it == globals.end()) {
|
||||||
|
// Fallback: check if 'this' is at slot 0 and has the property
|
||||||
|
ValuePtr receiver = stack[current_frame->stack_offset];
|
||||||
|
if (receiver && receiver->type() == Type::INSTANCE) {
|
||||||
|
auto instance = std::dynamic_pointer_cast<InstanceValue>(receiver);
|
||||||
|
if (instance->fields.find(name) != instance->fields.end()) {
|
||||||
|
instance->set(name, peek(0));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runtime_error("Undefined variable '" + name + "'.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
it->second = peek(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OpCode::OP_DEFINE_GLOBAL: {
|
||||||
|
std::string name = read_string();
|
||||||
|
globals[name] = pop();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OpCode::OP_GET_LOCAL: {
|
||||||
|
uint8_t slot = read_byte();
|
||||||
|
push(stack[current_frame->stack_offset + slot]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OpCode::OP_SET_LOCAL: {
|
||||||
|
uint8_t slot = read_byte();
|
||||||
|
stack[current_frame->stack_offset + slot] = peek(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OpCode::OP_JUMP: {
|
||||||
|
uint16_t offset = read_short();
|
||||||
|
current_frame->ip += offset;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OpCode::OP_JUMP_IF_FALSE: {
|
||||||
|
uint16_t offset = read_short();
|
||||||
|
if (is_falsey(peek(0))) {
|
||||||
|
current_frame->ip += offset;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OpCode::OP_LOOP: {
|
||||||
|
uint16_t offset = read_short();
|
||||||
|
current_frame->ip -= offset;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OpCode::OP_CALL: {
|
||||||
|
uint8_t arg_count = read_byte();
|
||||||
|
ValuePtr callee = peek(arg_count);
|
||||||
|
|
||||||
|
if (callee->type() == Type::FUNCTION) {
|
||||||
|
auto func = std::dynamic_pointer_cast<FunctionValue>(callee);
|
||||||
|
|
||||||
|
if (func->is_native) {
|
||||||
|
// Native function call
|
||||||
|
std::vector<ValuePtr> args;
|
||||||
|
if (func->bound_instance) {
|
||||||
|
args.push_back(func->bound_instance);
|
||||||
|
}
|
||||||
|
for (int i = arg_count - 1; i >= 0; i--) {
|
||||||
|
args.push_back(peek(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
ValuePtr result = func->native_func(args);
|
||||||
|
|
||||||
|
// Pop arguments and function
|
||||||
|
for (int i = 0; i <= arg_count; i++) {
|
||||||
|
pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
push(result);
|
||||||
|
} else {
|
||||||
|
// Script function - call bytecode
|
||||||
|
if (frames.size() >= 64) {
|
||||||
|
runtime_error("Stack overflow.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parameters are already on the stack
|
||||||
|
// Function is at stack index: stack.size() - arg_count - 1
|
||||||
|
size_t stack_offset = stack.size() - arg_count - 1;
|
||||||
|
|
||||||
|
// If it's a bound method, replace function on stack with 'this'
|
||||||
|
if (func->bound_instance) {
|
||||||
|
stack[stack_offset] = func->bound_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
frames.emplace_back(func->chunk, stack_offset);
|
||||||
|
current_frame = &frames.back();
|
||||||
|
}
|
||||||
|
} else if (callee->type() == Type::CLASS) {
|
||||||
|
// Class instantiation
|
||||||
|
auto klass = std::dynamic_pointer_cast<ClassValue>(callee);
|
||||||
|
auto instance = std::make_shared<InstanceValue>(klass);
|
||||||
|
|
||||||
|
// The class is at stack[stack.size() - arg_count - 1]
|
||||||
|
// Replace it with the instance
|
||||||
|
stack[stack.size() - arg_count - 1] = instance;
|
||||||
|
|
||||||
|
// Check if class has init method
|
||||||
|
auto init_method = klass->methods.find("init");
|
||||||
|
if (init_method != klass->methods.end()) {
|
||||||
|
auto init_func = init_method->second;
|
||||||
|
if (init_func->is_native) {
|
||||||
|
// ... native init (rare)
|
||||||
|
std::vector<ValuePtr> args;
|
||||||
|
for (int i = arg_count - 1; i >= 0; i--) {
|
||||||
|
args.push_back(peek(i));
|
||||||
|
}
|
||||||
|
init_func->native_func(args);
|
||||||
|
// pop arguments, instance stays on stack
|
||||||
|
for (int i = 0; i < arg_count; i++) pop();
|
||||||
|
} else {
|
||||||
|
// Script init - push a new frame
|
||||||
|
if (frames.size() >= 64) {
|
||||||
|
runtime_error("Stack overflow.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stack_offset points to the instance we just put there
|
||||||
|
size_t stack_offset = stack.size() - arg_count - 1;
|
||||||
|
frames.emplace_back(init_func->chunk, stack_offset);
|
||||||
|
current_frame = &frames.back();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No init, just pop arguments
|
||||||
|
for (int i = 0; i < arg_count; i++) {
|
||||||
|
pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
runtime_error("Can only call functions and classes.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OpCode::OP_RETURN: {
|
||||||
|
ValuePtr result = pop();
|
||||||
|
|
||||||
|
// Pop the frame
|
||||||
|
size_t stack_offset = current_frame->stack_offset;
|
||||||
|
frames.pop_back();
|
||||||
|
|
||||||
|
// Pop locals and function from the stack
|
||||||
|
while (stack.size() > stack_offset) {
|
||||||
|
pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frames.empty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_frame = &frames.back();
|
||||||
|
push(result);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OpCode::OP_BUILD_LIST: {
|
||||||
|
uint8_t count = read_byte();
|
||||||
|
auto list = std::make_shared<ListValue>();
|
||||||
|
|
||||||
|
std::vector<ValuePtr> elements;
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
elements.push_back(pop());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse because we popped in reverse order
|
||||||
|
for (int i = count - 1; i >= 0; i--) {
|
||||||
|
list->push(elements[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
push(list);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OpCode::OP_BUILD_MAP: {
|
||||||
|
uint8_t count = read_byte();
|
||||||
|
auto map = std::make_shared<MapValue>();
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
ValuePtr value = pop();
|
||||||
|
ValuePtr key = pop();
|
||||||
|
|
||||||
|
if (key->type() != Type::STRING) {
|
||||||
|
runtime_error("Map keys must be strings.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string key_str = std::dynamic_pointer_cast<StringValue>(key)->value;
|
||||||
|
map->set(key_str, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
push(map);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OpCode::OP_INDEX: {
|
||||||
|
ValuePtr index = pop();
|
||||||
|
ValuePtr object = pop();
|
||||||
|
|
||||||
|
if (object->type() == Type::LIST) {
|
||||||
|
auto list = std::dynamic_pointer_cast<ListValue>(object);
|
||||||
|
if (index->type() != Type::NUMBER) {
|
||||||
|
runtime_error("List index must be a number.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
size_t idx = static_cast<size_t>(
|
||||||
|
std::dynamic_pointer_cast<NumberValue>(index)->value);
|
||||||
|
push(list->get(idx));
|
||||||
|
} else if (object->type() == Type::MAP) {
|
||||||
|
auto map = std::dynamic_pointer_cast<MapValue>(object);
|
||||||
|
if (index->type() != Type::STRING) {
|
||||||
|
runtime_error("Map key must be a string.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::string key = std::dynamic_pointer_cast<StringValue>(index)->value;
|
||||||
|
push(map->get(key));
|
||||||
|
} else {
|
||||||
|
runtime_error("Only lists and maps support indexing.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OpCode::OP_INDEX_SET: {
|
||||||
|
ValuePtr value = pop();
|
||||||
|
ValuePtr index = pop();
|
||||||
|
ValuePtr object = pop();
|
||||||
|
|
||||||
|
if (object->type() == Type::LIST) {
|
||||||
|
auto list = std::dynamic_pointer_cast<ListValue>(object);
|
||||||
|
if (index->type() != Type::NUMBER) {
|
||||||
|
runtime_error("List index must be a number.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
size_t idx = static_cast<size_t>(
|
||||||
|
std::dynamic_pointer_cast<NumberValue>(index)->value);
|
||||||
|
list->set(idx, value);
|
||||||
|
push(value);
|
||||||
|
} else if (object->type() == Type::MAP) {
|
||||||
|
auto map = std::dynamic_pointer_cast<MapValue>(object);
|
||||||
|
if (index->type() != Type::STRING) {
|
||||||
|
runtime_error("Map key must be a string.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::string key = std::dynamic_pointer_cast<StringValue>(index)->value;
|
||||||
|
map->set(key, value);
|
||||||
|
push(value);
|
||||||
|
} else {
|
||||||
|
runtime_error("Only lists and maps support index assignment.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OpCode::OP_CLASS: {
|
||||||
|
std::string name = read_string();
|
||||||
|
auto klass = std::make_shared<ClassValue>(name);
|
||||||
|
push(klass);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OpCode::OP_GET_PROPERTY: {
|
||||||
|
std::string name = read_string();
|
||||||
|
ValuePtr object = pop();
|
||||||
|
|
||||||
|
if (object->type() == Type::INSTANCE) {
|
||||||
|
auto instance = std::dynamic_pointer_cast<InstanceValue>(object);
|
||||||
|
push(instance->get(name));
|
||||||
|
} else {
|
||||||
|
auto it = builtin_classes.find(object->type_name());
|
||||||
|
if (it != builtin_classes.end()) {
|
||||||
|
auto klass = it->second;
|
||||||
|
auto method_it = klass->methods.find(name);
|
||||||
|
if (method_it != klass->methods.end()) {
|
||||||
|
auto bound = std::make_shared<FunctionValue>(*method_it->second);
|
||||||
|
bound->bound_instance = object;
|
||||||
|
push(bound);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runtime_error("Only instances and types with registered built-in classes have properties/methods.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OpCode::OP_SET_PROPERTY: {
|
||||||
|
std::string name = read_string();
|
||||||
|
ValuePtr value = pop();
|
||||||
|
ValuePtr object = pop();
|
||||||
|
|
||||||
|
if (object->type() == Type::INSTANCE) {
|
||||||
|
auto instance = std::dynamic_pointer_cast<InstanceValue>(object);
|
||||||
|
instance->set(name, value);
|
||||||
|
push(value);
|
||||||
|
} else {
|
||||||
|
runtime_error("Only instances have fields.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OpCode::OP_PRINT: {
|
||||||
|
std::cout << pop()->to_string() << std::endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OpCode::OP_HALT:
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
runtime_error("Unknown opcode: " +
|
||||||
|
std::to_string(static_cast<int>(instruction)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t VM::read_byte() {
|
||||||
|
return current_frame->chunk->get_code(current_frame->ip++);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t VM::read_short() {
|
||||||
|
uint16_t high = read_byte();
|
||||||
|
uint16_t low = read_byte();
|
||||||
|
return (high << 8) | low;
|
||||||
|
}
|
||||||
|
|
||||||
|
ValuePtr VM::read_constant() {
|
||||||
|
uint8_t constant_idx = read_byte();
|
||||||
|
return current_frame->chunk->get_constant(constant_idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string VM::read_string() {
|
||||||
|
ValuePtr constant = read_constant();
|
||||||
|
if (constant->type() != Type::STRING) {
|
||||||
|
throw RuntimeError("Expected string constant");
|
||||||
|
}
|
||||||
|
return std::dynamic_pointer_cast<StringValue>(constant)->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VM::runtime_error(const std::string& message) {
|
||||||
|
error_message = message;
|
||||||
|
std::cerr << "Runtime Error: " << message << std::endl;
|
||||||
|
|
||||||
|
// Print stack trace
|
||||||
|
for (auto it = frames.rbegin(); it != frames.rend(); ++it) {
|
||||||
|
size_t instruction = it->ip - 1;
|
||||||
|
int line = it->chunk->get_line(instruction);
|
||||||
|
std::cerr << "[line " << line << "] in script" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VM::is_falsey(ValuePtr value) {
|
||||||
|
if (!value || value->type() == Type::NIL) return true;
|
||||||
|
if (value->type() == Type::BOOL) {
|
||||||
|
return !std::dynamic_pointer_cast<BoolValue>(value)->value;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VM::binary_op(OpCode op) {
|
||||||
|
ValuePtr b = pop();
|
||||||
|
ValuePtr a = pop();
|
||||||
|
|
||||||
|
if (op == OpCode::OP_ADD) {
|
||||||
|
if (a->type() == Type::NUMBER && b->type() == Type::NUMBER) {
|
||||||
|
double av = std::dynamic_pointer_cast<NumberValue>(a)->value;
|
||||||
|
double bv = std::dynamic_pointer_cast<NumberValue>(b)->value;
|
||||||
|
push(std::make_shared<NumberValue>(av + bv));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (a->type() == Type::STRING && b->type() == Type::STRING) {
|
||||||
|
std::string av = std::dynamic_pointer_cast<StringValue>(a)->value;
|
||||||
|
std::string bv = std::dynamic_pointer_cast<StringValue>(b)->value;
|
||||||
|
push(std::make_shared<StringValue>(av + bv));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
runtime_error("Operands must be two numbers or two strings.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other arithmetic operations require numbers
|
||||||
|
if (a->type() != Type::NUMBER || b->type() != Type::NUMBER) {
|
||||||
|
runtime_error("Operands must be numbers.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
double av = std::dynamic_pointer_cast<NumberValue>(a)->value;
|
||||||
|
double bv = std::dynamic_pointer_cast<NumberValue>(b)->value;
|
||||||
|
|
||||||
|
switch (op) {
|
||||||
|
case OpCode::OP_SUBTRACT:
|
||||||
|
push(std::make_shared<NumberValue>(av - bv));
|
||||||
|
break;
|
||||||
|
case OpCode::OP_MULTIPLY:
|
||||||
|
push(std::make_shared<NumberValue>(av * bv));
|
||||||
|
break;
|
||||||
|
case OpCode::OP_DIVIDE:
|
||||||
|
if (bv == 0) {
|
||||||
|
runtime_error("Division by zero.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
push(std::make_shared<NumberValue>(av / bv));
|
||||||
|
break;
|
||||||
|
case OpCode::OP_MODULO:
|
||||||
|
push(std::make_shared<NumberValue>(std::fmod(av, bv)));
|
||||||
|
break;
|
||||||
|
case OpCode::OP_GREATER:
|
||||||
|
push(std::make_shared<BoolValue>(av > bv));
|
||||||
|
break;
|
||||||
|
case OpCode::OP_GREATER_EQUAL:
|
||||||
|
push(std::make_shared<BoolValue>(av >= bv));
|
||||||
|
break;
|
||||||
|
case OpCode::OP_LESS:
|
||||||
|
push(std::make_shared<BoolValue>(av < bv));
|
||||||
|
break;
|
||||||
|
case OpCode::OP_LESS_EQUAL:
|
||||||
|
push(std::make_shared<BoolValue>(av <= bv));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
runtime_error("Unknown binary operator.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VM::register_builtin_functions() {
|
||||||
|
// print function
|
||||||
|
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>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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>();
|
||||||
|
});
|
||||||
|
globals["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.");
|
||||||
|
});
|
||||||
|
globals["len"] = len_func;
|
||||||
|
|
||||||
|
// Define list class
|
||||||
|
auto list_klass = std::make_shared<ClassValue>("list");
|
||||||
|
list_klass->add_method("push", std::make_shared<FunctionValue>("push",
|
||||||
|
[](const std::vector<ValuePtr>& args) -> ValuePtr {
|
||||||
|
if (args.size() != 2) throw RuntimeError("list.push() expects 1 argument.");
|
||||||
|
auto list = std::dynamic_pointer_cast<ListValue>(args[0]);
|
||||||
|
list->push(args[1]);
|
||||||
|
return args[1];
|
||||||
|
}));
|
||||||
|
list_klass->add_method("pop", std::make_shared<FunctionValue>("pop",
|
||||||
|
[](const std::vector<ValuePtr>& args) -> ValuePtr {
|
||||||
|
if (args.size() != 1) throw RuntimeError("list.pop() expects 0 arguments.");
|
||||||
|
auto list = std::dynamic_pointer_cast<ListValue>(args[0]);
|
||||||
|
if (list->elements.empty()) return std::make_shared<NilValue>();
|
||||||
|
ValuePtr val = list->elements.back();
|
||||||
|
list->elements.pop_back();
|
||||||
|
return val;
|
||||||
|
}));
|
||||||
|
list_klass->add_method("len", std::make_shared<FunctionValue>("len",
|
||||||
|
[](const std::vector<ValuePtr>& args) -> ValuePtr {
|
||||||
|
if (args.size() != 1) throw RuntimeError("list.len() expects 0 arguments.");
|
||||||
|
auto list = std::dynamic_pointer_cast<ListValue>(args[0]);
|
||||||
|
return std::make_shared<NumberValue>(static_cast<double>(list->size()));
|
||||||
|
}));
|
||||||
|
register_builtin_class("list", list_klass);
|
||||||
|
|
||||||
|
// Define map class
|
||||||
|
auto map_klass = std::make_shared<ClassValue>("map");
|
||||||
|
map_klass->add_method("set", std::make_shared<FunctionValue>("set",
|
||||||
|
[](const std::vector<ValuePtr>& args) -> ValuePtr {
|
||||||
|
if (args.size() != 3) throw RuntimeError("map.set() expects 2 arguments.");
|
||||||
|
auto map = std::dynamic_pointer_cast<MapValue>(args[0]);
|
||||||
|
if (args[1]->type() != Type::STRING) throw RuntimeError("Map key must be a string.");
|
||||||
|
std::string key = std::dynamic_pointer_cast<StringValue>(args[1])->value;
|
||||||
|
map->set(key, args[2]);
|
||||||
|
return args[2];
|
||||||
|
}));
|
||||||
|
map_klass->add_method("get", std::make_shared<FunctionValue>("get",
|
||||||
|
[](const std::vector<ValuePtr>& args) -> ValuePtr {
|
||||||
|
if (args.size() != 2) throw RuntimeError("map.get() expects 1 argument.");
|
||||||
|
auto map = std::dynamic_pointer_cast<MapValue>(args[0]);
|
||||||
|
if (args[1]->type() != Type::STRING) throw RuntimeError("Map key must be a string.");
|
||||||
|
std::string key = std::dynamic_pointer_cast<StringValue>(args[1])->value;
|
||||||
|
return map->get(key);
|
||||||
|
}));
|
||||||
|
map_klass->add_method("has", std::make_shared<FunctionValue>("has",
|
||||||
|
[](const std::vector<ValuePtr>& args) -> ValuePtr {
|
||||||
|
if (args.size() != 2) throw RuntimeError("map.has() expects 1 argument.");
|
||||||
|
auto map = std::dynamic_pointer_cast<MapValue>(args[0]);
|
||||||
|
if (args[1]->type() != Type::STRING) throw RuntimeError("Map key must be a string.");
|
||||||
|
std::string key = std::dynamic_pointer_cast<StringValue>(args[1])->value;
|
||||||
|
return std::make_shared<BoolValue>(map->has(key));
|
||||||
|
}));
|
||||||
|
map_klass->add_method("len", std::make_shared<FunctionValue>("len",
|
||||||
|
[](const std::vector<ValuePtr>& args) -> ValuePtr {
|
||||||
|
if (args.size() != 1) throw RuntimeError("map.len() expects 0 arguments.");
|
||||||
|
auto map = std::dynamic_pointer_cast<MapValue>(args[0]);
|
||||||
|
return std::make_shared<NumberValue>(static_cast<double>(map->pairs.size()));
|
||||||
|
}));
|
||||||
|
register_builtin_class("map", map_klass);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VM::register_native_function(const std::string& name, NativeFunction func) {
|
||||||
|
auto func_value = std::make_shared<FunctionValue>(name, func);
|
||||||
|
globals[name] = func_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VM::register_builtin_class(const std::string& type_name, std::shared_ptr<ClassValue> klass) {
|
||||||
|
builtin_classes[type_name] = klass;
|
||||||
|
globals[klass->name] = klass;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VM::set_global(const std::string& name, ValuePtr value) {
|
||||||
|
globals[name] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
ValuePtr VM::get_global(const std::string& name) {
|
||||||
|
auto it = globals.find(name);
|
||||||
|
if (it == globals.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VM::push(ValuePtr value) {
|
||||||
|
stack.push_back(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
ValuePtr VM::pop() {
|
||||||
|
if (stack.empty()) {
|
||||||
|
throw RuntimeError("Stack underflow");
|
||||||
|
}
|
||||||
|
ValuePtr value = stack.back();
|
||||||
|
stack.pop_back();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
ValuePtr VM::peek(int distance) {
|
||||||
|
if (distance >= static_cast<int>(stack.size())) {
|
||||||
|
throw RuntimeError("Stack underflow in peek");
|
||||||
|
}
|
||||||
|
return stack[stack.size() - 1 - distance];
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace camellya
|
||||||
79
src/vm.h
Normal file
79
src/vm.h
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#ifndef CAMELLYA_VM_H
|
||||||
|
#define CAMELLYA_VM_H
|
||||||
|
|
||||||
|
#include "chunk.h"
|
||||||
|
#include "value.h"
|
||||||
|
#include "exceptions.h"
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace camellya {
|
||||||
|
|
||||||
|
// Call frame for function calls
|
||||||
|
struct CallFrame {
|
||||||
|
std::shared_ptr<Chunk> chunk;
|
||||||
|
size_t ip; // Instruction pointer
|
||||||
|
size_t stack_offset; // Where this frame's locals start on the stack
|
||||||
|
|
||||||
|
CallFrame(std::shared_ptr<Chunk> chunk, size_t stack_offset)
|
||||||
|
: chunk(std::move(chunk)), ip(0), stack_offset(stack_offset) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Virtual Machine - stack-based bytecode interpreter
|
||||||
|
class VM {
|
||||||
|
public:
|
||||||
|
VM();
|
||||||
|
|
||||||
|
// Execute a chunk of bytecode
|
||||||
|
bool execute(std::shared_ptr<Chunk> chunk);
|
||||||
|
|
||||||
|
// Get the last error message
|
||||||
|
const std::string& get_error() const { return error_message; }
|
||||||
|
|
||||||
|
// Register native functions
|
||||||
|
void register_native_function(const std::string& name, NativeFunction func);
|
||||||
|
|
||||||
|
// Register built-in classes for types (like list, map)
|
||||||
|
void register_builtin_class(const std::string& type_name, std::shared_ptr<ClassValue> klass);
|
||||||
|
|
||||||
|
// Global variable access
|
||||||
|
void set_global(const std::string& name, ValuePtr value);
|
||||||
|
ValuePtr get_global(const std::string& name);
|
||||||
|
|
||||||
|
// Stack operations (for C++ API)
|
||||||
|
void push(ValuePtr value);
|
||||||
|
ValuePtr pop();
|
||||||
|
ValuePtr peek(int distance = 0);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<ValuePtr> stack;
|
||||||
|
std::vector<CallFrame> frames;
|
||||||
|
CallFrame* current_frame;
|
||||||
|
std::map<std::string, ValuePtr> globals;
|
||||||
|
std::map<std::string, std::shared_ptr<ClassValue>> builtin_classes;
|
||||||
|
std::string error_message;
|
||||||
|
|
||||||
|
// Main execution loop
|
||||||
|
bool run();
|
||||||
|
|
||||||
|
// Helper methods
|
||||||
|
uint8_t read_byte();
|
||||||
|
uint16_t read_short();
|
||||||
|
ValuePtr read_constant();
|
||||||
|
std::string read_string();
|
||||||
|
|
||||||
|
// Stack operations
|
||||||
|
void runtime_error(const std::string& message);
|
||||||
|
bool is_falsey(ValuePtr value);
|
||||||
|
|
||||||
|
// Binary operations
|
||||||
|
bool binary_op(OpCode op);
|
||||||
|
|
||||||
|
// Built-in functions
|
||||||
|
void register_builtin_functions();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace camellya
|
||||||
|
|
||||||
|
#endif // CAMELLYA_VM_H
|
||||||
@@ -1,85 +1,85 @@
|
|||||||
#include <catch2/catch_test_macros.hpp>
|
|
||||||
#include <catch2/benchmark/catch_benchmark.hpp>
|
|
||||||
#include "camellya.h"
|
#include "camellya.h"
|
||||||
|
#include <catch2/benchmark/catch_benchmark.hpp>
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
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"(
|
||||||
number x = 10;
|
var x = 10;
|
||||||
number y = 20;
|
var y = 20;
|
||||||
number z = x + y;
|
var z = x + y;
|
||||||
)";
|
)";
|
||||||
|
|
||||||
REQUIRE(state.do_string(script));
|
REQUIRE(state.do_string(script));
|
||||||
|
|
||||||
auto z = state.get_global("z");
|
auto z = state.get_global("z");
|
||||||
REQUIRE(z);
|
REQUIRE(z);
|
||||||
REQUIRE(z->type() == Type::NUMBER);
|
REQUIRE(z->type() == Type::NUMBER);
|
||||||
|
|
||||||
auto nz = std::dynamic_pointer_cast<NumberValue>(z);
|
auto nz = std::dynamic_pointer_cast<NumberValue>(z);
|
||||||
REQUIRE(nz);
|
REQUIRE(nz);
|
||||||
REQUIRE(nz->value == 30.0);
|
REQUIRE(nz->value == 30.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
number z = add(10, 20);
|
var z = add(10, 20);
|
||||||
)";
|
)";
|
||||||
|
|
||||||
REQUIRE(state.do_string(script));
|
REQUIRE(state.do_string(script));
|
||||||
|
|
||||||
auto z = state.get_global("z");
|
auto z = state.get_global("z");
|
||||||
REQUIRE(z);
|
REQUIRE(z);
|
||||||
REQUIRE(z->type() == Type::NUMBER);
|
REQUIRE(z->type() == Type::NUMBER);
|
||||||
|
|
||||||
auto nz = std::dynamic_pointer_cast<NumberValue>(z);
|
auto nz = std::dynamic_pointer_cast<NumberValue>(z);
|
||||||
REQUIRE(nz);
|
REQUIRE(nz);
|
||||||
REQUIRE(nz->value == 30.0);
|
REQUIRE(nz->value == 30.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
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"(
|
||||||
list numbers = [10, 20, 30];
|
var numbers = [10, 20, 30];
|
||||||
)";
|
)";
|
||||||
|
|
||||||
REQUIRE(state.do_string(script));
|
REQUIRE(state.do_string(script));
|
||||||
|
|
||||||
auto list_val = state.get_global("numbers");
|
auto list_val = state.get_global("numbers");
|
||||||
REQUIRE(list_val);
|
REQUIRE(list_val);
|
||||||
REQUIRE(list_val->type() == Type::LIST);
|
REQUIRE(list_val->type() == Type::LIST);
|
||||||
|
|
||||||
auto list = std::dynamic_pointer_cast<ListValue>(list_val);
|
auto list = std::dynamic_pointer_cast<ListValue>(list_val);
|
||||||
REQUIRE(list);
|
REQUIRE(list);
|
||||||
REQUIRE(list->size() == 3);
|
REQUIRE(list->size() == 3);
|
||||||
|
|
||||||
auto first = list->get(0);
|
auto first = list->get(0);
|
||||||
auto third = list->get(2);
|
auto third = list->get(2);
|
||||||
|
|
||||||
REQUIRE(first->type() == Type::NUMBER);
|
REQUIRE(first->type() == Type::NUMBER);
|
||||||
REQUIRE(third->type() == Type::NUMBER);
|
REQUIRE(third->type() == Type::NUMBER);
|
||||||
|
|
||||||
auto first_n = std::dynamic_pointer_cast<NumberValue>(first);
|
auto first_n = std::dynamic_pointer_cast<NumberValue>(first);
|
||||||
auto third_n = std::dynamic_pointer_cast<NumberValue>(third);
|
auto third_n = std::dynamic_pointer_cast<NumberValue>(third);
|
||||||
|
|
||||||
REQUIRE(first_n->value == 10.0);
|
REQUIRE(first_n->value == 10.0);
|
||||||
REQUIRE(third_n->value == 30.0);
|
REQUIRE(third_n->value == 30.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
number age;
|
var age : number;
|
||||||
string name;
|
var name : string;
|
||||||
|
|
||||||
func init() -> nil {
|
func init() -> nil {
|
||||||
age = 18;
|
age = 18;
|
||||||
@@ -91,69 +91,70 @@ TEST_CASE("class init is called on declaration", "[class][init]") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Person p;
|
var p : Person;
|
||||||
number a = p.getAge();
|
var a = p.getAge();
|
||||||
)";
|
)";
|
||||||
|
|
||||||
REQUIRE(state.do_string(script));
|
REQUIRE(state.do_string(script));
|
||||||
|
|
||||||
auto p_val = state.get_global("p");
|
auto p_val = state.get_global("p");
|
||||||
REQUIRE(p_val);
|
REQUIRE(p_val);
|
||||||
REQUIRE(p_val->type() == Type::INSTANCE);
|
REQUIRE(p_val->type() == Type::INSTANCE);
|
||||||
|
|
||||||
auto instance = std::dynamic_pointer_cast<InstanceValue>(p_val);
|
auto instance = std::dynamic_pointer_cast<InstanceValue>(p_val);
|
||||||
REQUIRE(instance);
|
REQUIRE(instance);
|
||||||
|
|
||||||
auto age_val = instance->get("age");
|
auto age_val = instance->get("age");
|
||||||
auto name_val = instance->get("name");
|
auto name_val = instance->get("name");
|
||||||
|
|
||||||
REQUIRE(age_val->type() == Type::NUMBER);
|
REQUIRE(age_val->type() == Type::NUMBER);
|
||||||
REQUIRE(name_val->type() == Type::STRING);
|
REQUIRE(name_val->type() == Type::STRING);
|
||||||
|
|
||||||
auto age_num = std::dynamic_pointer_cast<NumberValue>(age_val);
|
auto age_num = std::dynamic_pointer_cast<NumberValue>(age_val);
|
||||||
auto name_str = std::dynamic_pointer_cast<StringValue>(name_val);
|
auto name_str = std::dynamic_pointer_cast<StringValue>(name_val);
|
||||||
|
|
||||||
REQUIRE(age_num->value == 18.0);
|
REQUIRE(age_num->value == 18.0);
|
||||||
REQUIRE(name_str->value == "Default");
|
REQUIRE(name_str->value == "Default");
|
||||||
|
|
||||||
auto a_val = state.get_global("a");
|
auto a_val = state.get_global("a");
|
||||||
REQUIRE(a_val);
|
REQUIRE(a_val);
|
||||||
REQUIRE(a_val->type() == Type::NUMBER);
|
REQUIRE(a_val->type() == Type::NUMBER);
|
||||||
auto a_num = std::dynamic_pointer_cast<NumberValue>(a_val);
|
auto a_num = std::dynamic_pointer_cast<NumberValue>(a_val);
|
||||||
REQUIRE(a_num->value == 18.0);
|
REQUIRE(a_num->value == 18.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
// number s = 0;
|
var s = 0;
|
||||||
// for (number i = 0; i < n; i = i + 1) {
|
for (var i = 0; i < n; i = i + 1) {
|
||||||
// s = s + i;
|
s = s + i;
|
||||||
// }
|
}
|
||||||
// return s;
|
return s;
|
||||||
// }
|
}
|
||||||
// number r = sum_to(1000);
|
var r = sum_to(1000);
|
||||||
// )";
|
)";
|
||||||
|
|
||||||
// BENCHMARK("sum_to(1000)") {
|
BENCHMARK("sum_to(1000)") {
|
||||||
// if (!state.do_string(script)) {
|
if (!state.do_string(script)) {
|
||||||
// auto last_error = state.get_error();
|
auto last_error = state.get_error();
|
||||||
// REQUIRE(last_error.empty());
|
REQUIRE(last_error.empty());
|
||||||
// }
|
}
|
||||||
// auto r_val = state.get_global("r");
|
|
||||||
// REQUIRE(r_val);
|
auto r_val = state.get_global("r");
|
||||||
// REQUIRE(r_val->type() == Type::NUMBER);
|
REQUIRE(r_val);
|
||||||
// auto r_num = std::dynamic_pointer_cast<NumberValue>(r_val);
|
REQUIRE(r_val->type() == Type::NUMBER);
|
||||||
// REQUIRE(r_num->value == 499500.0);
|
auto r_num = std::dynamic_pointer_cast<NumberValue>(r_val);
|
||||||
// };
|
REQUIRE(r_num->value == 499500.0);
|
||||||
// }
|
};
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("loop break", "[script][loop]") {
|
TEST_CASE("loop break", "[script][loop]") {
|
||||||
State state;
|
Camellya state;
|
||||||
const char* script = R"(
|
const char *script = R"(
|
||||||
number sum = 0;
|
var sum = 0;
|
||||||
for (number i = 0; i < 10; i = i + 1) {
|
for (var i = 0; i < 10; i = i + 1) {
|
||||||
if (i == 5) {
|
if (i == 5) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -161,18 +162,18 @@ TEST_CASE("loop break", "[script][loop]") {
|
|||||||
}
|
}
|
||||||
)";
|
)";
|
||||||
|
|
||||||
REQUIRE(state.do_string(script));
|
REQUIRE(state.do_string(script));
|
||||||
auto sum_val = state.get_global("sum");
|
auto sum_val = state.get_global("sum");
|
||||||
REQUIRE(sum_val);
|
REQUIRE(sum_val);
|
||||||
auto sum_num = std::dynamic_pointer_cast<NumberValue>(sum_val);
|
auto sum_num = std::dynamic_pointer_cast<NumberValue>(sum_val);
|
||||||
REQUIRE(sum_num->value == 10.0); // 0+1+2+3+4 = 10
|
REQUIRE(sum_num->value == 10.0); // 0+1+2+3+4 = 10
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("loop continue", "[script][loop]") {
|
TEST_CASE("loop continue", "[script][loop]") {
|
||||||
State state;
|
Camellya state;
|
||||||
const char* script = R"(
|
const char *script = R"(
|
||||||
number sum = 0;
|
var sum = 0;
|
||||||
for (number i = 0; i < 5; i = i + 1) {
|
for (var i = 0; i < 5; i = i + 1) {
|
||||||
if (i == 2) {
|
if (i == 2) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -180,18 +181,18 @@ TEST_CASE("loop continue", "[script][loop]") {
|
|||||||
}
|
}
|
||||||
)";
|
)";
|
||||||
|
|
||||||
REQUIRE(state.do_string(script));
|
REQUIRE(state.do_string(script));
|
||||||
auto sum_val = state.get_global("sum");
|
auto sum_val = state.get_global("sum");
|
||||||
REQUIRE(sum_val);
|
REQUIRE(sum_val);
|
||||||
auto sum_num = std::dynamic_pointer_cast<NumberValue>(sum_val);
|
auto sum_num = std::dynamic_pointer_cast<NumberValue>(sum_val);
|
||||||
REQUIRE(sum_num->value == 8.0); // 0+1+3+4 = 8
|
REQUIRE(sum_num->value == 8.0); // 0+1+3+4 = 8
|
||||||
}
|
}
|
||||||
|
|
||||||
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"(
|
||||||
number i = 0;
|
var i = 0;
|
||||||
number sum = 0;
|
var sum = 0;
|
||||||
while (i < 10) {
|
while (i < 10) {
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
if (i == 3) {
|
if (i == 3) {
|
||||||
@@ -204,16 +205,16 @@ TEST_CASE("while break and continue", "[script][loop]") {
|
|||||||
}
|
}
|
||||||
)";
|
)";
|
||||||
|
|
||||||
REQUIRE(state.do_string(script));
|
REQUIRE(state.do_string(script));
|
||||||
auto sum_val = state.get_global("sum");
|
auto sum_val = state.get_global("sum");
|
||||||
REQUIRE(sum_val);
|
REQUIRE(sum_val);
|
||||||
auto sum_num = std::dynamic_pointer_cast<NumberValue>(sum_val);
|
auto sum_num = std::dynamic_pointer_cast<NumberValue>(sum_val);
|
||||||
REQUIRE(sum_num->value == 12.0);
|
REQUIRE(sum_num->value == 12.0);
|
||||||
// 1st iter: i=1, sum=1
|
// 1st iter: i=1, sum=1
|
||||||
// 2nd iter: i=2, sum=1+2=3
|
// 2nd iter: i=2, sum=1+2=3
|
||||||
// 3rd iter: i=3, continue
|
// 3rd iter: i=3, continue
|
||||||
// 4th iter: i=4, sum=3+4=7
|
// 4th iter: i=4, sum=3+4=7
|
||||||
// 5th iter: i=5, sum=7+5=12
|
// 5th iter: i=5, sum=7+5=12
|
||||||
// 6th iter: i=6, break
|
// 6th iter: i=6, break
|
||||||
// Result should be 12.0
|
// Result should be 12.0
|
||||||
}
|
}
|
||||||
|
|||||||
59
tests/test_generic_builtin.cpp
Normal file
59
tests/test_generic_builtin.cpp
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#include "camellya.h"
|
||||||
|
#include "exceptions.h"
|
||||||
|
#include <catch2/catch_all.hpp>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
using namespace camellya;
|
||||||
|
|
||||||
|
// Simple Vector3D wrapper for testing
|
||||||
|
class Vector3DValue : public NativeValue {
|
||||||
|
public:
|
||||||
|
double x, y, z;
|
||||||
|
Vector3DValue(double x, double y, double z) : NativeValue("Vector3D"), x(x), y(y), z(z) {}
|
||||||
|
|
||||||
|
std::string to_string() const override {
|
||||||
|
return "Vector3D(" + std::to_string(x) + ", " + std::to_string(y) + ", " + std::to_string(z) + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
ValuePtr clone() const override {
|
||||||
|
return std::make_shared<Vector3DValue>(x, y, z);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_CASE("Generic built-in class (Vector3D) test", "[generic]") {
|
||||||
|
Camellya c;
|
||||||
|
|
||||||
|
// 1. Create the ClassValue for Vector3D
|
||||||
|
auto v3d_klass = std::make_shared<ClassValue>("Vector3D");
|
||||||
|
|
||||||
|
// 2. Add a native method 'length'
|
||||||
|
v3d_klass->add_method("length", std::make_shared<FunctionValue>("length",
|
||||||
|
[](const std::vector<ValuePtr>& args) -> ValuePtr {
|
||||||
|
if (args.size() != 1) throw RuntimeError("Vector3D.length() expects 0 arguments.");
|
||||||
|
auto vec = std::dynamic_pointer_cast<Vector3DValue>(args[0]);
|
||||||
|
double len = std::sqrt(vec->x * vec->x + vec->y * vec->y + vec->z * vec->z);
|
||||||
|
return std::make_shared<NumberValue>(len);
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 3. Register it as a built-in class for type "Vector3D"
|
||||||
|
c.register_builtin_class("Vector3D", v3d_klass);
|
||||||
|
|
||||||
|
// 4. Register a factory function to create Vector3D instances from script
|
||||||
|
c.register_function("Vector3D", [](const std::vector<ValuePtr>& args) -> ValuePtr {
|
||||||
|
if (args.size() != 3) throw RuntimeError("Vector3D() expects 3 arguments.");
|
||||||
|
double x = std::dynamic_pointer_cast<NumberValue>(args[0])->value;
|
||||||
|
double y = std::dynamic_pointer_cast<NumberValue>(args[1])->value;
|
||||||
|
double z = std::dynamic_pointer_cast<NumberValue>(args[2])->value;
|
||||||
|
return std::make_shared<Vector3DValue>(x, y, z);
|
||||||
|
});
|
||||||
|
|
||||||
|
SECTION("vector3d methods") {
|
||||||
|
REQUIRE(c.do_string(R"(
|
||||||
|
var v = Vector3D(3, 4, 0);
|
||||||
|
var len = v.length();
|
||||||
|
)"));
|
||||||
|
|
||||||
|
auto len = c.get_global("len");
|
||||||
|
REQUIRE(std::dynamic_pointer_cast<NumberValue>(len)->value == 5.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
tests/test_list.cpp
Normal file
18
tests/test_list.cpp
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#include "camellya.h"
|
||||||
|
#include <catch2/catch_all.hpp>
|
||||||
|
|
||||||
|
using namespace camellya;
|
||||||
|
TEST_CASE("Basic List test", "[list]") {
|
||||||
|
Camellya c;
|
||||||
|
|
||||||
|
SECTION("len function") {
|
||||||
|
REQUIRE(c.do_string(R"(
|
||||||
|
var arr = [1, 2, 3, 4];
|
||||||
|
var size = len(arr);
|
||||||
|
arr.push(5);
|
||||||
|
size = arr.len();
|
||||||
|
)"));
|
||||||
|
auto size = c.get_global("size");
|
||||||
|
REQUIRE(std::dynamic_pointer_cast<NumberValue>(size)->value == 5.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
32
tests/test_map_builtin.cpp
Normal file
32
tests/test_map_builtin.cpp
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#include "camellya.h"
|
||||||
|
#include <catch2/catch_all.hpp>
|
||||||
|
|
||||||
|
using namespace camellya;
|
||||||
|
|
||||||
|
TEST_CASE("Map built-in class test", "[map]") {
|
||||||
|
Camellya c;
|
||||||
|
|
||||||
|
SECTION("map methods") {
|
||||||
|
REQUIRE(c.do_string(R"(
|
||||||
|
var m = {"a": 1, "b": 2};
|
||||||
|
var s1 = m.len();
|
||||||
|
m.set("c", 3);
|
||||||
|
var s2 = m.len();
|
||||||
|
var has_a = m.has("a");
|
||||||
|
var has_z = m.has("z");
|
||||||
|
var val_b = m.get("b");
|
||||||
|
)"));
|
||||||
|
|
||||||
|
auto s1 = c.get_global("s1");
|
||||||
|
auto s2 = c.get_global("s2");
|
||||||
|
auto has_a = c.get_global("has_a");
|
||||||
|
auto has_z = c.get_global("has_z");
|
||||||
|
auto val_b = c.get_global("val_b");
|
||||||
|
|
||||||
|
REQUIRE(std::dynamic_pointer_cast<NumberValue>(s1)->value == 2.0);
|
||||||
|
REQUIRE(std::dynamic_pointer_cast<NumberValue>(s2)->value == 3.0);
|
||||||
|
REQUIRE(std::dynamic_pointer_cast<BoolValue>(has_a)->value == true);
|
||||||
|
REQUIRE(std::dynamic_pointer_cast<BoolValue>(has_z)->value == false);
|
||||||
|
REQUIRE(std::dynamic_pointer_cast<NumberValue>(val_b)->value == 2.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
193
tests/test_vm.cpp
Normal file
193
tests/test_vm.cpp
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
#include "src/camellya.h"
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
|
using namespace camellya;
|
||||||
|
|
||||||
|
TEST_CASE("VM - Basic arithmetic", "[vm]") {
|
||||||
|
Camellya state;
|
||||||
|
|
||||||
|
SECTION("Addition") {
|
||||||
|
REQUIRE(state.do_string("var x = 10 + 20;"));
|
||||||
|
auto x = state.get_global("x");
|
||||||
|
REQUIRE(x != nullptr);
|
||||||
|
REQUIRE(x->type() == Type::NUMBER);
|
||||||
|
REQUIRE(std::dynamic_pointer_cast<NumberValue>(x)->value == 30.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Subtraction") {
|
||||||
|
REQUIRE(state.do_string("var y = 50 - 15;"));
|
||||||
|
auto y = state.get_global("y");
|
||||||
|
REQUIRE(y != nullptr);
|
||||||
|
REQUIRE(std::dynamic_pointer_cast<NumberValue>(y)->value == 35.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Multiplication") {
|
||||||
|
REQUIRE(state.do_string("var z = 7 * 8;"));
|
||||||
|
auto z = state.get_global("z");
|
||||||
|
REQUIRE(z != nullptr);
|
||||||
|
REQUIRE(std::dynamic_pointer_cast<NumberValue>(z)->value == 56.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Division") {
|
||||||
|
REQUIRE(state.do_string("var w = 100 / 4;"));
|
||||||
|
auto w = state.get_global("w");
|
||||||
|
REQUIRE(w != nullptr);
|
||||||
|
REQUIRE(std::dynamic_pointer_cast<NumberValue>(w)->value == 25.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("VM - Variables and assignment", "[vm]") {
|
||||||
|
Camellya state;
|
||||||
|
|
||||||
|
SECTION("Variable declaration and initialization") {
|
||||||
|
REQUIRE(state.do_string("var a = 42;"));
|
||||||
|
auto a = state.get_global("a");
|
||||||
|
REQUIRE(a != nullptr);
|
||||||
|
REQUIRE(std::dynamic_pointer_cast<NumberValue>(a)->value == 42.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Variable assignment") {
|
||||||
|
REQUIRE(state.do_string(R"(
|
||||||
|
var b = 10;
|
||||||
|
b = 20;
|
||||||
|
)"));
|
||||||
|
auto b = state.get_global("b");
|
||||||
|
REQUIRE(std::dynamic_pointer_cast<NumberValue>(b)->value == 20.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("VM - String operations", "[vm]") {
|
||||||
|
Camellya state;
|
||||||
|
|
||||||
|
SECTION("String concatenation") {
|
||||||
|
REQUIRE(state.do_string(R"(var greeting = "Hello" + " " + "World";)"));
|
||||||
|
auto greeting = state.get_global("greeting");
|
||||||
|
REQUIRE(greeting != nullptr);
|
||||||
|
REQUIRE(greeting->type() == Type::STRING);
|
||||||
|
REQUIRE(std::dynamic_pointer_cast<StringValue>(greeting)->value ==
|
||||||
|
"Hello World");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("VM - Comparison operators", "[vm]") {
|
||||||
|
Camellya state;
|
||||||
|
|
||||||
|
SECTION("Equality") {
|
||||||
|
REQUIRE(state.do_string("var eq = 10 == 10;"));
|
||||||
|
auto eq = state.get_global("eq");
|
||||||
|
REQUIRE(std::dynamic_pointer_cast<BoolValue>(eq)->value == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Greater than") {
|
||||||
|
REQUIRE(state.do_string("var gt = 20 > 10;"));
|
||||||
|
auto gt = state.get_global("gt");
|
||||||
|
REQUIRE(std::dynamic_pointer_cast<BoolValue>(gt)->value == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Less than") {
|
||||||
|
REQUIRE(state.do_string("var lt = 5 < 10;"));
|
||||||
|
auto lt = state.get_global("lt");
|
||||||
|
REQUIRE(std::dynamic_pointer_cast<BoolValue>(lt)->value == true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("VM - Lists", "[vm]") {
|
||||||
|
Camellya state;
|
||||||
|
|
||||||
|
SECTION("Create list") {
|
||||||
|
REQUIRE(state.do_string("var numbers = [1, 2, 3, 4, 5];"));
|
||||||
|
auto numbers = state.get_global("numbers");
|
||||||
|
REQUIRE(numbers != nullptr);
|
||||||
|
REQUIRE(numbers->type() == Type::LIST);
|
||||||
|
auto list = std::dynamic_pointer_cast<ListValue>(numbers);
|
||||||
|
REQUIRE(list->size() == 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("List indexing") {
|
||||||
|
REQUIRE(state.do_string(R"(
|
||||||
|
var arr = [10, 20, 30];
|
||||||
|
var item = arr[1];
|
||||||
|
)"));
|
||||||
|
auto item = state.get_global("item");
|
||||||
|
REQUIRE(std::dynamic_pointer_cast<NumberValue>(item)->value == 20.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("VM - Maps", "[vm]") {
|
||||||
|
Camellya state;
|
||||||
|
|
||||||
|
SECTION("Create map") {
|
||||||
|
REQUIRE(state.do_string(R"(var person = {"name": "Alice", "age": "30"};)"));
|
||||||
|
auto person = state.get_global("person");
|
||||||
|
REQUIRE(person != nullptr);
|
||||||
|
REQUIRE(person->type() == Type::MAP);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Map access") {
|
||||||
|
REQUIRE(state.do_string(R"(
|
||||||
|
var data = {"key": "value"};
|
||||||
|
var val = data["key"];
|
||||||
|
)"));
|
||||||
|
auto val = state.get_global("val");
|
||||||
|
REQUIRE(val->type() == Type::STRING);
|
||||||
|
REQUIRE(std::dynamic_pointer_cast<StringValue>(val)->value == "value");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("VM - If statements", "[vm]") {
|
||||||
|
Camellya state;
|
||||||
|
|
||||||
|
SECTION("If branch taken") {
|
||||||
|
REQUIRE(state.do_string(R"(
|
||||||
|
var x = 10;
|
||||||
|
if (x > 5) {
|
||||||
|
x = 100;
|
||||||
|
}
|
||||||
|
)"));
|
||||||
|
auto x = state.get_global("x");
|
||||||
|
REQUIRE(std::dynamic_pointer_cast<NumberValue>(x)->value == 100.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Else branch taken") {
|
||||||
|
REQUIRE(state.do_string(R"(
|
||||||
|
var y = 3;
|
||||||
|
if (y > 5) {
|
||||||
|
y = 100;
|
||||||
|
} else {
|
||||||
|
y = 200;
|
||||||
|
}
|
||||||
|
)"));
|
||||||
|
auto y = state.get_global("y");
|
||||||
|
REQUIRE(std::dynamic_pointer_cast<NumberValue>(y)->value == 200.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("VM - While loops", "[vm]") {
|
||||||
|
Camellya state;
|
||||||
|
|
||||||
|
SECTION("While loop") {
|
||||||
|
REQUIRE(state.do_string(R"(
|
||||||
|
var counter = 0;
|
||||||
|
var sum = 0;
|
||||||
|
while (counter < 5) {
|
||||||
|
sum = sum + counter;
|
||||||
|
counter = counter + 1;
|
||||||
|
}
|
||||||
|
)"));
|
||||||
|
auto sum = state.get_global("sum");
|
||||||
|
REQUIRE(std::dynamic_pointer_cast<NumberValue>(sum)->value == 10.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("VM - Native functions", "[vm]") {
|
||||||
|
Camellya state;
|
||||||
|
|
||||||
|
SECTION("len function") {
|
||||||
|
REQUIRE(state.do_string(R"(
|
||||||
|
var arr = [1, 2, 3, 4];
|
||||||
|
var size = len(arr);
|
||||||
|
)"));
|
||||||
|
auto size = state.get_global("size");
|
||||||
|
REQUIRE(std::dynamic_pointer_cast<NumberValue>(size)->value == 4.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user