init
This commit is contained in:
33
.gitignore
vendored
Normal file
33
.gitignore
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Build directories
|
||||||
|
build/
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# IDE directories
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
.vs/
|
||||||
|
|
||||||
|
# Compiled files
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.exe
|
||||||
|
|
||||||
|
# CMake files
|
||||||
|
CMakeCache.txt
|
||||||
|
CMakeFiles/
|
||||||
|
cmake_install.cmake
|
||||||
|
Makefile
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
*.log
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Backup files
|
||||||
|
*~
|
||||||
|
*.bak
|
||||||
|
*.swp
|
||||||
56
CMakeLists.txt
Normal file
56
CMakeLists.txt
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.30)
|
||||||
|
project(camellya)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
|
||||||
|
# Library sources
|
||||||
|
set(LIB_SOURCES
|
||||||
|
library.cpp
|
||||||
|
lexer.cpp
|
||||||
|
parser.cpp
|
||||||
|
value.cpp
|
||||||
|
interpreter.cpp
|
||||||
|
state.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
set(LIB_HEADERS
|
||||||
|
library.h
|
||||||
|
lexer.h
|
||||||
|
parser.h
|
||||||
|
ast.h
|
||||||
|
value.h
|
||||||
|
interpreter.h
|
||||||
|
state.h
|
||||||
|
)
|
||||||
|
|
||||||
|
# Build static library
|
||||||
|
add_library(camellya STATIC ${LIB_SOURCES} ${LIB_HEADERS})
|
||||||
|
|
||||||
|
include(FetchContent)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
Catch2
|
||||||
|
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
|
||||||
|
GIT_TAG v3.6.0
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_MakeAvailable(Catch2)
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
add_executable(camellya_tests
|
||||||
|
tests/test_basic.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(camellya_tests
|
||||||
|
PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(camellya_tests
|
||||||
|
PRIVATE
|
||||||
|
camellya
|
||||||
|
Catch2::Catch2WithMain
|
||||||
|
)
|
||||||
|
|
||||||
|
add_test(NAME camellya_tests COMMAND camellya_tests)
|
||||||
217
README.md
Normal file
217
README.md
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
# Camellya Script Language
|
||||||
|
|
||||||
|
一个类似 Lua 的脚本语言,使用 C++23 实现,具有以下特性:
|
||||||
|
|
||||||
|
## 特性
|
||||||
|
|
||||||
|
- **0-based 索引**:数组和列表从 0 开始索引
|
||||||
|
- **明确的类型系统**:区分 list 和 map
|
||||||
|
- **类支持**:支持 class 定义,包含字段和方法
|
||||||
|
- **静态类型声明**:变量需要类型声明(number, string, bool, list, map)
|
||||||
|
- **类 Lua 的 API**:提供简单的嵌入式 API
|
||||||
|
|
||||||
|
## 语法示例
|
||||||
|
|
||||||
|
### 基本类型
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
number x = 10;
|
||||||
|
string name = "Alice";
|
||||||
|
bool flag = true;
|
||||||
|
```
|
||||||
|
|
||||||
|
### List(0-indexed)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
list numbers = [10, 20, 30];
|
||||||
|
print(numbers[0]); // 输出: 10
|
||||||
|
numbers[1] = 99;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Map
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
map person = {"name": "Bob", "age": "25"};
|
||||||
|
print(person["name"]); // 输出: Bob
|
||||||
|
person["city"] = "New York";
|
||||||
|
```
|
||||||
|
|
||||||
|
### 函数
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
func add(number a, number b) -> number {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
number result = add(10, 20);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 类
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
class Person {
|
||||||
|
number age;
|
||||||
|
string name;
|
||||||
|
|
||||||
|
func sayHi() -> string {
|
||||||
|
print(name, "says: I'm", age, "years old");
|
||||||
|
return "Done";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Person p;
|
||||||
|
p.age = 10;
|
||||||
|
p.name = "Peter";
|
||||||
|
p.sayHi();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 控制流
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// if-else
|
||||||
|
if (x > 10) {
|
||||||
|
print("x is greater than 10");
|
||||||
|
} else {
|
||||||
|
print("x is less than or equal to 10");
|
||||||
|
}
|
||||||
|
|
||||||
|
// while loop
|
||||||
|
number i = 0;
|
||||||
|
while (i < 5) {
|
||||||
|
print("Count:", i);
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for loop
|
||||||
|
for (number j = 0; j < 3; j = j + 1) {
|
||||||
|
print("For loop:", j);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 编译和使用
|
||||||
|
|
||||||
|
### 编译
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
cmake ..
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./camellya_test
|
||||||
|
```
|
||||||
|
|
||||||
|
### 嵌入到 C++ 项目
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "library.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
camellya::State state;
|
||||||
|
|
||||||
|
// 执行脚本字符串
|
||||||
|
const char* script = R"(
|
||||||
|
class Person {
|
||||||
|
number age;
|
||||||
|
string name;
|
||||||
|
|
||||||
|
func sayHi() -> string {
|
||||||
|
print(name, "says: I'm", age, "years old");
|
||||||
|
return "Done";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Person p;
|
||||||
|
p.age = 10;
|
||||||
|
p.name = "Peter";
|
||||||
|
p.sayHi();
|
||||||
|
)";
|
||||||
|
|
||||||
|
if (!state.do_string(script)) {
|
||||||
|
std::cerr << "Error: " << state.get_error() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 或者从文件执行
|
||||||
|
state.do_file("script.chun");
|
||||||
|
|
||||||
|
// 注册 C++ 函数
|
||||||
|
state.register_function("my_func",
|
||||||
|
[](const std::vector<camellya::ValuePtr>& args) -> camellya::ValuePtr {
|
||||||
|
// 你的实现
|
||||||
|
return std::make_shared<camellya::NilValue>();
|
||||||
|
});
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API 参考
|
||||||
|
|
||||||
|
### State 类
|
||||||
|
|
||||||
|
主要 API 类,类似于 `lua_State`:
|
||||||
|
|
||||||
|
- `bool do_string(const std::string& script)` - 执行脚本字符串
|
||||||
|
- `bool do_file(const std::string& filename)` - 执行脚本文件
|
||||||
|
- `void register_function(const std::string& name, NativeFunction func)` - 注册 C++ 函数
|
||||||
|
- `ValuePtr get_global(const std::string& name)` - 获取全局变量
|
||||||
|
- `void set_global(const std::string& name, ValuePtr value)` - 设置全局变量
|
||||||
|
- `const std::string& get_error()` - 获取最后的错误信息
|
||||||
|
|
||||||
|
### 栈操作(类似 Lua)
|
||||||
|
|
||||||
|
- `void push_number(double value)`
|
||||||
|
- `void push_string(const std::string& value)`
|
||||||
|
- `void push_bool(bool value)`
|
||||||
|
- `void push_nil()`
|
||||||
|
- `double to_number(int index)`
|
||||||
|
- `std::string to_string(int index)`
|
||||||
|
- `bool to_bool(int index)`
|
||||||
|
- `int get_top()`
|
||||||
|
- `void pop(int n = 1)`
|
||||||
|
|
||||||
|
## 内置函数
|
||||||
|
|
||||||
|
- `print(...)` - 打印多个参数到标准输出
|
||||||
|
- `len(container)` - 返回 list、map 或 string 的长度
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
camellya/
|
||||||
|
├── library.h # 主头文件
|
||||||
|
├── library.cpp # 主实现
|
||||||
|
├── lexer.h/cpp # 词法分析器
|
||||||
|
├── parser.h/cpp # 语法分析器
|
||||||
|
├── ast.h # 抽象语法树定义
|
||||||
|
├── value.h/cpp # 值类型系统
|
||||||
|
├── interpreter.h/cpp # 解释器
|
||||||
|
├── state.h/cpp # 主 API 接口
|
||||||
|
├── main.cpp # 示例程序
|
||||||
|
├── example.cml # 示例脚本
|
||||||
|
└── CMakeLists.txt # 构建配置
|
||||||
|
```
|
||||||
|
|
||||||
|
## 特性对比
|
||||||
|
|
||||||
|
| 特性 | Lua | Camellya |
|
||||||
|
|------|-----|----------|
|
||||||
|
| 索引起始 | 1 | 0 |
|
||||||
|
| Table | 统一的 table | 区分 list 和 map |
|
||||||
|
| 类型 | 动态 | 静态声明 |
|
||||||
|
| 类 | 通过 metatable | 原生支持 |
|
||||||
|
| 语法 | `function` | `func` |
|
||||||
|
| 类型注解 | 无 | 必须声明 |
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
## 版本
|
||||||
|
|
||||||
|
当前版本:0.1.0
|
||||||
217
ast.h
Normal file
217
ast.h
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
#ifndef CAMELLYA_AST_H
|
||||||
|
#define CAMELLYA_AST_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
namespace camellya {
|
||||||
|
|
||||||
|
// Forward declarations
|
||||||
|
struct Expr;
|
||||||
|
struct Stmt;
|
||||||
|
|
||||||
|
using ExprPtr = std::unique_ptr<Expr>;
|
||||||
|
using StmtPtr = std::unique_ptr<Stmt>;
|
||||||
|
|
||||||
|
// Value types
|
||||||
|
enum class ValueType {
|
||||||
|
NUMBER, STRING, BOOL, LIST, MAP, CLASS, NIL
|
||||||
|
};
|
||||||
|
|
||||||
|
// Expressions
|
||||||
|
struct Expr {
|
||||||
|
virtual ~Expr() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BinaryExpr : public Expr {
|
||||||
|
ExprPtr left;
|
||||||
|
std::string op;
|
||||||
|
ExprPtr right;
|
||||||
|
|
||||||
|
BinaryExpr(ExprPtr left, std::string op, ExprPtr right)
|
||||||
|
: left(std::move(left)), op(std::move(op)), right(std::move(right)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UnaryExpr : public Expr {
|
||||||
|
std::string op;
|
||||||
|
ExprPtr operand;
|
||||||
|
|
||||||
|
UnaryExpr(std::string op, ExprPtr operand)
|
||||||
|
: op(std::move(op)), operand(std::move(operand)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LiteralExpr : public Expr {
|
||||||
|
std::variant<double, std::string, bool, std::monostate> value;
|
||||||
|
|
||||||
|
explicit LiteralExpr(double value) : value(value) {}
|
||||||
|
explicit LiteralExpr(std::string value) : value(std::move(value)) {}
|
||||||
|
explicit LiteralExpr(bool value) : value(value) {}
|
||||||
|
LiteralExpr() : value(std::monostate{}) {} // nil
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VariableExpr : public Expr {
|
||||||
|
std::string name;
|
||||||
|
|
||||||
|
explicit VariableExpr(std::string name) : name(std::move(name)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AssignExpr : public Expr {
|
||||||
|
std::string name;
|
||||||
|
ExprPtr value;
|
||||||
|
|
||||||
|
AssignExpr(std::string name, ExprPtr value)
|
||||||
|
: name(std::move(name)), value(std::move(value)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CallExpr : public Expr {
|
||||||
|
ExprPtr callee;
|
||||||
|
std::vector<ExprPtr> arguments;
|
||||||
|
|
||||||
|
CallExpr(ExprPtr callee, std::vector<ExprPtr> arguments)
|
||||||
|
: callee(std::move(callee)), arguments(std::move(arguments)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GetExpr : public Expr {
|
||||||
|
ExprPtr object;
|
||||||
|
std::string name;
|
||||||
|
|
||||||
|
GetExpr(ExprPtr object, std::string name)
|
||||||
|
: object(std::move(object)), name(std::move(name)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SetExpr : public Expr {
|
||||||
|
ExprPtr object;
|
||||||
|
std::string name;
|
||||||
|
ExprPtr value;
|
||||||
|
|
||||||
|
SetExpr(ExprPtr object, std::string name, ExprPtr value)
|
||||||
|
: object(std::move(object)), name(std::move(name)), value(std::move(value)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IndexExpr : public Expr {
|
||||||
|
ExprPtr object;
|
||||||
|
ExprPtr index;
|
||||||
|
|
||||||
|
IndexExpr(ExprPtr object, ExprPtr index)
|
||||||
|
: object(std::move(object)), index(std::move(index)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IndexSetExpr : public Expr {
|
||||||
|
ExprPtr object;
|
||||||
|
ExprPtr index;
|
||||||
|
ExprPtr value;
|
||||||
|
|
||||||
|
IndexSetExpr(ExprPtr object, ExprPtr index, ExprPtr value)
|
||||||
|
: object(std::move(object)), index(std::move(index)), value(std::move(value)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ListExpr : public Expr {
|
||||||
|
std::vector<ExprPtr> elements;
|
||||||
|
|
||||||
|
explicit ListExpr(std::vector<ExprPtr> elements)
|
||||||
|
: elements(std::move(elements)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MapExpr : public Expr {
|
||||||
|
std::vector<std::pair<ExprPtr, ExprPtr>> pairs;
|
||||||
|
|
||||||
|
explicit MapExpr(std::vector<std::pair<ExprPtr, ExprPtr>> pairs)
|
||||||
|
: pairs(std::move(pairs)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Statements
|
||||||
|
struct Stmt {
|
||||||
|
virtual ~Stmt() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ExprStmt : public Stmt {
|
||||||
|
ExprPtr expression;
|
||||||
|
|
||||||
|
explicit ExprStmt(ExprPtr expression) : expression(std::move(expression)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VarDecl : public Stmt {
|
||||||
|
std::string type_name;
|
||||||
|
std::string name;
|
||||||
|
ExprPtr initializer;
|
||||||
|
|
||||||
|
VarDecl(std::string type_name, std::string name, ExprPtr initializer = nullptr)
|
||||||
|
: type_name(std::move(type_name)), name(std::move(name)), initializer(std::move(initializer)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BlockStmt : public Stmt {
|
||||||
|
std::vector<StmtPtr> statements;
|
||||||
|
|
||||||
|
explicit BlockStmt(std::vector<StmtPtr> statements)
|
||||||
|
: statements(std::move(statements)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IfStmt : public Stmt {
|
||||||
|
ExprPtr condition;
|
||||||
|
StmtPtr then_branch;
|
||||||
|
StmtPtr else_branch;
|
||||||
|
|
||||||
|
IfStmt(ExprPtr condition, StmtPtr then_branch, StmtPtr else_branch = nullptr)
|
||||||
|
: condition(std::move(condition)), then_branch(std::move(then_branch)),
|
||||||
|
else_branch(std::move(else_branch)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WhileStmt : public Stmt {
|
||||||
|
ExprPtr condition;
|
||||||
|
StmtPtr body;
|
||||||
|
|
||||||
|
WhileStmt(ExprPtr condition, StmtPtr body)
|
||||||
|
: condition(std::move(condition)), body(std::move(body)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ForStmt : public Stmt {
|
||||||
|
StmtPtr initializer;
|
||||||
|
ExprPtr condition;
|
||||||
|
ExprPtr increment;
|
||||||
|
StmtPtr body;
|
||||||
|
|
||||||
|
ForStmt(StmtPtr initializer, ExprPtr condition, ExprPtr increment, StmtPtr body)
|
||||||
|
: initializer(std::move(initializer)), condition(std::move(condition)),
|
||||||
|
increment(std::move(increment)), body(std::move(body)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ReturnStmt : public Stmt {
|
||||||
|
ExprPtr value;
|
||||||
|
|
||||||
|
explicit ReturnStmt(ExprPtr value = nullptr) : value(std::move(value)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FunctionDecl : public Stmt {
|
||||||
|
std::string name;
|
||||||
|
std::vector<std::pair<std::string, std::string>> parameters; // (type, name)
|
||||||
|
std::string return_type;
|
||||||
|
std::shared_ptr<BlockStmt> body;
|
||||||
|
|
||||||
|
FunctionDecl(std::string name,
|
||||||
|
std::vector<std::pair<std::string, std::string>> parameters,
|
||||||
|
std::string return_type,
|
||||||
|
std::shared_ptr<BlockStmt> body)
|
||||||
|
: name(std::move(name)), parameters(std::move(parameters)),
|
||||||
|
return_type(std::move(return_type)), body(std::move(body)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ClassDecl : public Stmt {
|
||||||
|
std::string name;
|
||||||
|
std::vector<StmtPtr> members; // VarDecl and FunctionDecl
|
||||||
|
|
||||||
|
ClassDecl(std::string name, std::vector<StmtPtr> members)
|
||||||
|
: name(std::move(name)), members(std::move(members)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Program {
|
||||||
|
std::vector<StmtPtr> statements;
|
||||||
|
|
||||||
|
explicit Program(std::vector<StmtPtr> statements)
|
||||||
|
: statements(std::move(statements)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace camellya
|
||||||
|
|
||||||
|
#endif // CAMELLYA_AST_H
|
||||||
52
example.chun
Normal file
52
example.chun
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// Example Camellya script
|
||||||
|
// This demonstrates the Person class from the specification
|
||||||
|
|
||||||
|
class Person {
|
||||||
|
number age;
|
||||||
|
string name;
|
||||||
|
|
||||||
|
func sayHi() -> string {
|
||||||
|
print(name, "says: I'm", age, "years old");
|
||||||
|
return "Done";
|
||||||
|
}
|
||||||
|
|
||||||
|
func birthday() -> number {
|
||||||
|
age = age + 1;
|
||||||
|
print(name, "is now", age, "years old!");
|
||||||
|
return age;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an instance
|
||||||
|
Person p;
|
||||||
|
p.age = 10;
|
||||||
|
p.name = "Peter";
|
||||||
|
|
||||||
|
// Call methods
|
||||||
|
p.sayHi();
|
||||||
|
p.birthday();
|
||||||
|
p.sayHi();
|
||||||
|
|
||||||
|
// Test lists (0-indexed)
|
||||||
|
print("\n=== List Demo ===");
|
||||||
|
list numbers = [1, 2, 3, 4, 5];
|
||||||
|
print("List:", numbers);
|
||||||
|
print("First element (index 0):", numbers[0]);
|
||||||
|
print("Third element (index 2):", numbers[2]);
|
||||||
|
|
||||||
|
// Test maps
|
||||||
|
print("\n=== Map Demo ===");
|
||||||
|
map config = {"host": "localhost", "port": "8080"};
|
||||||
|
print("Config:", config);
|
||||||
|
print("Host:", config["host"]);
|
||||||
|
|
||||||
|
// Functions
|
||||||
|
print("\n=== Function Demo ===");
|
||||||
|
func fibonacci(number n) -> number {
|
||||||
|
if (n <= 1) {
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
return fibonacci(n - 1) + fibonacci(n - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Fibonacci(10) =", fibonacci(10));
|
||||||
524
interpreter.cpp
Normal file
524
interpreter.cpp
Normal file
@@ -0,0 +1,524 @@
|
|||||||
|
#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* 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) {
|
||||||
|
while (is_truthy(evaluate(*stmt.condition))) {
|
||||||
|
execute_statement(*stmt.body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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))) {
|
||||||
|
execute_statement(*stmt.body);
|
||||||
|
|
||||||
|
if (stmt.increment) {
|
||||||
|
evaluate(*stmt.increment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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_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
|
||||||
151
interpreter.h
Normal file
151
interpreter.h
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
#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 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_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
|
||||||
235
lexer.cpp
Normal file
235
lexer.cpp
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
#include "lexer.h"
|
||||||
|
#include <cctype>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace camellya {
|
||||||
|
|
||||||
|
Lexer::Lexer(std::string source) : source_(std::move(source)) {}
|
||||||
|
|
||||||
|
std::vector<Token> Lexer::tokenize() {
|
||||||
|
while (!is_at_end()) {
|
||||||
|
start_ = current_;
|
||||||
|
scan_token();
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens_.emplace_back(TokenType::END_OF_FILE, "", line_, column_);
|
||||||
|
return tokens_;
|
||||||
|
}
|
||||||
|
|
||||||
|
char Lexer::advance() {
|
||||||
|
column_++;
|
||||||
|
return source_[current_++];
|
||||||
|
}
|
||||||
|
|
||||||
|
char Lexer::peek() const {
|
||||||
|
if (is_at_end()) return '\0';
|
||||||
|
return source_[current_];
|
||||||
|
}
|
||||||
|
|
||||||
|
char Lexer::peek_next() const {
|
||||||
|
if (current_ + 1 >= source_.length()) return '\0';
|
||||||
|
return source_[current_ + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Lexer::match(char expected) {
|
||||||
|
if (is_at_end()) return false;
|
||||||
|
if (source_[current_] != expected) return false;
|
||||||
|
|
||||||
|
current_++;
|
||||||
|
column_++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lexer::skip_whitespace() {
|
||||||
|
while (!is_at_end()) {
|
||||||
|
char c = peek();
|
||||||
|
switch (c) {
|
||||||
|
case ' ':
|
||||||
|
case '\r':
|
||||||
|
case '\t':
|
||||||
|
advance();
|
||||||
|
break;
|
||||||
|
case '\n':
|
||||||
|
line_++;
|
||||||
|
column_ = 0;
|
||||||
|
advance();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lexer::skip_comment() {
|
||||||
|
if (peek() == '/' && peek_next() == '/') {
|
||||||
|
while (peek() != '\n' && !is_at_end()) {
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lexer::scan_token() {
|
||||||
|
skip_whitespace();
|
||||||
|
|
||||||
|
if (is_at_end()) return;
|
||||||
|
|
||||||
|
start_ = current_;
|
||||||
|
int start_column = column_;
|
||||||
|
char c = advance();
|
||||||
|
|
||||||
|
switch (c) {
|
||||||
|
case '(': add_token(TokenType::LEFT_PAREN); break;
|
||||||
|
case ')': add_token(TokenType::RIGHT_PAREN); break;
|
||||||
|
case '{': add_token(TokenType::LEFT_BRACE); break;
|
||||||
|
case '}': add_token(TokenType::RIGHT_BRACE); break;
|
||||||
|
case '[': add_token(TokenType::LEFT_BRACKET); break;
|
||||||
|
case ']': add_token(TokenType::RIGHT_BRACKET); break;
|
||||||
|
case ',': add_token(TokenType::COMMA); break;
|
||||||
|
case '.': add_token(TokenType::DOT); break;
|
||||||
|
case ';': add_token(TokenType::SEMICOLON); break;
|
||||||
|
case ':': add_token(TokenType::COLON); break;
|
||||||
|
case '+': add_token(TokenType::PLUS); break;
|
||||||
|
case '*': add_token(TokenType::STAR); break;
|
||||||
|
case '%': add_token(TokenType::PERCENT); break;
|
||||||
|
case '-':
|
||||||
|
if (match('>')) {
|
||||||
|
add_token(TokenType::ARROW);
|
||||||
|
} else {
|
||||||
|
add_token(TokenType::MINUS);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '!':
|
||||||
|
add_token(match('=') ? TokenType::BANG_EQUAL : TokenType::BANG);
|
||||||
|
break;
|
||||||
|
case '=':
|
||||||
|
add_token(match('=') ? TokenType::EQUAL_EQUAL : TokenType::EQUAL);
|
||||||
|
break;
|
||||||
|
case '<':
|
||||||
|
add_token(match('=') ? TokenType::LESS_EQUAL : TokenType::LESS);
|
||||||
|
break;
|
||||||
|
case '>':
|
||||||
|
add_token(match('=') ? TokenType::GREATER_EQUAL : TokenType::GREATER);
|
||||||
|
break;
|
||||||
|
case '/':
|
||||||
|
if (peek() == '/') {
|
||||||
|
skip_comment();
|
||||||
|
} else {
|
||||||
|
add_token(TokenType::SLASH);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '"':
|
||||||
|
scan_string();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (std::isdigit(c)) {
|
||||||
|
scan_number();
|
||||||
|
} else if (std::isalpha(c) || c == '_') {
|
||||||
|
scan_identifier();
|
||||||
|
} else {
|
||||||
|
add_token(TokenType::INVALID);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lexer::add_token(TokenType type) {
|
||||||
|
std::string text = source_.substr(start_, current_ - start_);
|
||||||
|
tokens_.emplace_back(type, text, line_, column_ - static_cast<int>(text.length()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lexer::add_token(TokenType type, std::variant<std::monostate, double, std::string> literal) {
|
||||||
|
std::string text = source_.substr(start_, current_ - start_);
|
||||||
|
tokens_.emplace_back(type, text, literal, line_, column_ - static_cast<int>(text.length()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lexer::scan_string() {
|
||||||
|
std::string value;
|
||||||
|
|
||||||
|
while (peek() != '"' && !is_at_end()) {
|
||||||
|
if (peek() == '\n') {
|
||||||
|
line_++;
|
||||||
|
column_ = 0;
|
||||||
|
}
|
||||||
|
if (peek() == '\\' && peek_next() != '\0') {
|
||||||
|
advance(); // consume backslash
|
||||||
|
char escaped = advance();
|
||||||
|
switch (escaped) {
|
||||||
|
case 'n': value += '\n'; break;
|
||||||
|
case 't': value += '\t'; break;
|
||||||
|
case 'r': value += '\r'; break;
|
||||||
|
case '\\': value += '\\'; break;
|
||||||
|
case '"': value += '"'; break;
|
||||||
|
default: value += escaped; break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value += advance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_at_end()) {
|
||||||
|
add_token(TokenType::INVALID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
advance(); // closing "
|
||||||
|
add_token(TokenType::STRING_LITERAL, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lexer::scan_number() {
|
||||||
|
while (std::isdigit(peek())) {
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (peek() == '.' && std::isdigit(peek_next())) {
|
||||||
|
advance(); // consume '.'
|
||||||
|
while (std::isdigit(peek())) {
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string text = source_.substr(start_, current_ - start_);
|
||||||
|
double value = std::stod(text);
|
||||||
|
add_token(TokenType::NUMBER_LITERAL, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lexer::scan_identifier() {
|
||||||
|
while (std::isalnum(peek()) || peek() == '_') {
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string text = source_.substr(start_, current_ - start_);
|
||||||
|
TokenType type = get_keyword_type(text);
|
||||||
|
add_token(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
TokenType Lexer::get_keyword_type(const std::string& text) const {
|
||||||
|
static const std::unordered_map<std::string, TokenType> keywords = {
|
||||||
|
{"class", TokenType::CLASS},
|
||||||
|
{"func", TokenType::FUNC},
|
||||||
|
{"number", TokenType::NUMBER},
|
||||||
|
{"string", TokenType::STRING},
|
||||||
|
{"bool", TokenType::BOOL},
|
||||||
|
{"list", TokenType::LIST},
|
||||||
|
{"map", TokenType::MAP},
|
||||||
|
{"if", TokenType::IF},
|
||||||
|
{"else", TokenType::ELSE},
|
||||||
|
{"while", TokenType::WHILE},
|
||||||
|
{"for", TokenType::FOR},
|
||||||
|
{"return", TokenType::RETURN},
|
||||||
|
{"var", TokenType::VAR},
|
||||||
|
{"true", TokenType::TRUE},
|
||||||
|
{"false", TokenType::FALSE},
|
||||||
|
{"nil", TokenType::NIL},
|
||||||
|
{"and", TokenType::AND},
|
||||||
|
{"or", TokenType::OR},
|
||||||
|
{"this", TokenType::THIS},
|
||||||
|
};
|
||||||
|
|
||||||
|
auto it = keywords.find(text);
|
||||||
|
if (it != keywords.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
return TokenType::IDENTIFIER;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace camellya
|
||||||
88
lexer.h
Normal file
88
lexer.h
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
#ifndef CAMELLYA_LEXER_H
|
||||||
|
#define CAMELLYA_LEXER_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <optional>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
namespace camellya {
|
||||||
|
|
||||||
|
enum class TokenType {
|
||||||
|
// Keywords
|
||||||
|
CLASS, FUNC, NUMBER, STRING, BOOL, LIST, MAP,
|
||||||
|
IF, ELSE, WHILE, FOR, RETURN, VAR,
|
||||||
|
TRUE, FALSE, NIL, THIS,
|
||||||
|
|
||||||
|
// Operators
|
||||||
|
PLUS, MINUS, STAR, SLASH, PERCENT,
|
||||||
|
EQUAL, EQUAL_EQUAL, BANG_EQUAL,
|
||||||
|
LESS, LESS_EQUAL, GREATER, GREATER_EQUAL,
|
||||||
|
AND, OR, BANG,
|
||||||
|
|
||||||
|
// Delimiters
|
||||||
|
LEFT_PAREN, RIGHT_PAREN,
|
||||||
|
LEFT_BRACE, RIGHT_BRACE,
|
||||||
|
LEFT_BRACKET, RIGHT_BRACKET,
|
||||||
|
COMMA, DOT, SEMICOLON, COLON,
|
||||||
|
ARROW,
|
||||||
|
|
||||||
|
// Literals
|
||||||
|
IDENTIFIER, NUMBER_LITERAL, STRING_LITERAL,
|
||||||
|
|
||||||
|
// Special
|
||||||
|
END_OF_FILE, INVALID
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Token {
|
||||||
|
TokenType type;
|
||||||
|
std::string lexeme;
|
||||||
|
std::variant<std::monostate, double, std::string> literal;
|
||||||
|
int line;
|
||||||
|
int column;
|
||||||
|
|
||||||
|
Token(TokenType type, std::string lexeme, int line, int column)
|
||||||
|
: type(type), lexeme(std::move(lexeme)), line(line), column(column) {}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
Token(TokenType type, std::string lexeme, T literal, int line, int column)
|
||||||
|
: type(type), lexeme(std::move(lexeme)), literal(std::move(literal)), line(line), column(column) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Lexer {
|
||||||
|
public:
|
||||||
|
explicit Lexer(std::string source);
|
||||||
|
|
||||||
|
std::vector<Token> tokenize();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string source_;
|
||||||
|
size_t start_ = 0;
|
||||||
|
size_t current_ = 0;
|
||||||
|
int line_ = 1;
|
||||||
|
int column_ = 1;
|
||||||
|
std::vector<Token> tokens_;
|
||||||
|
|
||||||
|
bool is_at_end() const { return current_ >= source_.length(); }
|
||||||
|
char advance();
|
||||||
|
char peek() const;
|
||||||
|
char peek_next() const;
|
||||||
|
bool match(char expected);
|
||||||
|
|
||||||
|
void skip_whitespace();
|
||||||
|
void skip_comment();
|
||||||
|
|
||||||
|
void scan_token();
|
||||||
|
void add_token(TokenType type);
|
||||||
|
void add_token(TokenType type, std::variant<std::monostate, double, std::string> literal);
|
||||||
|
|
||||||
|
void scan_string();
|
||||||
|
void scan_number();
|
||||||
|
void scan_identifier();
|
||||||
|
|
||||||
|
TokenType get_keyword_type(const std::string& text) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace camellya
|
||||||
|
|
||||||
|
#endif // CAMELLYA_LEXER_H
|
||||||
4
library.cpp
Normal file
4
library.cpp
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#include "library.h"
|
||||||
|
|
||||||
|
// Implementation file for Camellya scripting language
|
||||||
|
// All implementation is in separate source files
|
||||||
23
library.h
Normal file
23
library.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#ifndef CAMELLYA_LIBRARY_H
|
||||||
|
#define CAMELLYA_LIBRARY_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 "lexer.h"
|
||||||
|
#include "parser.h"
|
||||||
|
#include "interpreter.h"
|
||||||
|
#include "ast.h"
|
||||||
|
|
||||||
|
namespace camellya {
|
||||||
|
|
||||||
|
// Version info
|
||||||
|
constexpr const char* VERSION = "0.1.0";
|
||||||
|
constexpr const char* VERSION_NAME = "Camellya";
|
||||||
|
|
||||||
|
} // namespace camellya
|
||||||
|
|
||||||
|
#endif // CAMELLYA_LIBRARY_H
|
||||||
482
parser.cpp
Normal file
482
parser.cpp
Normal file
@@ -0,0 +1,482 @@
|
|||||||
|
#include "parser.h"
|
||||||
|
#include <format>
|
||||||
|
|
||||||
|
namespace camellya {
|
||||||
|
|
||||||
|
Parser::Parser(std::vector<Token> tokens) : tokens_(std::move(tokens)) {}
|
||||||
|
|
||||||
|
Program Parser::parse() {
|
||||||
|
std::vector<StmtPtr> statements;
|
||||||
|
|
||||||
|
while (!is_at_end()) {
|
||||||
|
try {
|
||||||
|
statements.push_back(declaration());
|
||||||
|
} catch (const ParseError& error) {
|
||||||
|
synchronize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Program(std::move(statements));
|
||||||
|
}
|
||||||
|
|
||||||
|
Token Parser::peek() const {
|
||||||
|
return tokens_[current_];
|
||||||
|
}
|
||||||
|
|
||||||
|
Token Parser::previous() const {
|
||||||
|
return tokens_[current_ - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Parser::is_at_end() const {
|
||||||
|
return peek().type == TokenType::END_OF_FILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Token Parser::advance() {
|
||||||
|
if (!is_at_end()) current_++;
|
||||||
|
return previous();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Parser::check(TokenType type) const {
|
||||||
|
if (is_at_end()) return false;
|
||||||
|
return peek().type == type;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Parser::match(std::initializer_list<TokenType> types) {
|
||||||
|
for (TokenType type : types) {
|
||||||
|
if (check(type)) {
|
||||||
|
advance();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Token Parser::consume(TokenType type, const std::string& message) {
|
||||||
|
if (check(type)) return advance();
|
||||||
|
throw error(peek(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseError Parser::error(const Token& token, const std::string& message) {
|
||||||
|
std::string error_msg = std::format("Line {}: Error at '{}': {}",
|
||||||
|
token.line, token.lexeme, message);
|
||||||
|
return ParseError(error_msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Parser::synchronize() {
|
||||||
|
advance();
|
||||||
|
|
||||||
|
while (!is_at_end()) {
|
||||||
|
if (previous().type == TokenType::SEMICOLON) return;
|
||||||
|
|
||||||
|
switch (peek().type) {
|
||||||
|
case TokenType::CLASS:
|
||||||
|
case TokenType::FUNC:
|
||||||
|
case TokenType::VAR:
|
||||||
|
case TokenType::FOR:
|
||||||
|
case TokenType::IF:
|
||||||
|
case TokenType::WHILE:
|
||||||
|
case TokenType::RETURN:
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StmtPtr Parser::declaration() {
|
||||||
|
if (match({TokenType::CLASS})) {
|
||||||
|
return class_declaration();
|
||||||
|
}
|
||||||
|
if (match({TokenType::FUNC})) {
|
||||||
|
return function_declaration();
|
||||||
|
}
|
||||||
|
if (check(TokenType::NUMBER) || check(TokenType::STRING) ||
|
||||||
|
check(TokenType::BOOL) || check(TokenType::LIST) ||
|
||||||
|
check(TokenType::MAP) || check(TokenType::IDENTIFIER)) {
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
StmtPtr Parser::class_declaration() {
|
||||||
|
Token name = consume(TokenType::IDENTIFIER, "Expected class name.");
|
||||||
|
consume(TokenType::LEFT_BRACE, "Expected '{' before class body.");
|
||||||
|
|
||||||
|
std::vector<StmtPtr> members;
|
||||||
|
|
||||||
|
while (!check(TokenType::RIGHT_BRACE) && !is_at_end()) {
|
||||||
|
// Parse field or method
|
||||||
|
if (check(TokenType::FUNC)) {
|
||||||
|
advance();
|
||||||
|
members.push_back(function_declaration());
|
||||||
|
} else {
|
||||||
|
members.push_back(var_declaration());
|
||||||
|
consume(TokenType::SEMICOLON, "Expected ';' after field declaration.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
consume(TokenType::RIGHT_BRACE, "Expected '}' after class body.");
|
||||||
|
|
||||||
|
return std::make_unique<ClassDecl>(name.lexeme, std::move(members));
|
||||||
|
}
|
||||||
|
|
||||||
|
StmtPtr Parser::function_declaration() {
|
||||||
|
Token name = consume(TokenType::IDENTIFIER, "Expected function name.");
|
||||||
|
consume(TokenType::LEFT_PAREN, "Expected '(' after function name.");
|
||||||
|
|
||||||
|
std::vector<std::pair<std::string, std::string>> parameters;
|
||||||
|
|
||||||
|
if (!check(TokenType::RIGHT_PAREN)) {
|
||||||
|
do {
|
||||||
|
Token type_token = advance();
|
||||||
|
std::string type_name = type_token.lexeme;
|
||||||
|
Token param_name = consume(TokenType::IDENTIFIER, "Expected parameter name.");
|
||||||
|
parameters.emplace_back(type_name, param_name.lexeme);
|
||||||
|
} while (match({TokenType::COMMA}));
|
||||||
|
}
|
||||||
|
|
||||||
|
consume(TokenType::RIGHT_PAREN, "Expected ')' after parameters.");
|
||||||
|
|
||||||
|
std::string return_type = "nil";
|
||||||
|
if (match({TokenType::ARROW})) {
|
||||||
|
Token type_token = advance();
|
||||||
|
return_type = type_token.lexeme;
|
||||||
|
}
|
||||||
|
|
||||||
|
consume(TokenType::LEFT_BRACE, "Expected '{' before function body.");
|
||||||
|
StmtPtr body = block_statement();
|
||||||
|
|
||||||
|
auto body_block = std::shared_ptr<BlockStmt>(static_cast<BlockStmt*>(body.release()));
|
||||||
|
|
||||||
|
return std::make_unique<FunctionDecl>(name.lexeme, std::move(parameters),
|
||||||
|
return_type, std::move(body_block));
|
||||||
|
}
|
||||||
|
|
||||||
|
StmtPtr Parser::var_declaration() {
|
||||||
|
Token type_token = advance();
|
||||||
|
std::string type_name = type_token.lexeme;
|
||||||
|
Token name = consume(TokenType::IDENTIFIER, "Expected variable name.");
|
||||||
|
|
||||||
|
ExprPtr initializer = nullptr;
|
||||||
|
if (match({TokenType::EQUAL})) {
|
||||||
|
initializer = expression();
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_unique<VarDecl>(type_name, name.lexeme, std::move(initializer));
|
||||||
|
}
|
||||||
|
|
||||||
|
StmtPtr Parser::statement() {
|
||||||
|
if (match({TokenType::IF})) return if_statement();
|
||||||
|
if (match({TokenType::WHILE})) return while_statement();
|
||||||
|
if (match({TokenType::FOR})) return for_statement();
|
||||||
|
if (match({TokenType::RETURN})) return return_statement();
|
||||||
|
if (match({TokenType::LEFT_BRACE})) return block_statement();
|
||||||
|
|
||||||
|
return expression_statement();
|
||||||
|
}
|
||||||
|
|
||||||
|
StmtPtr Parser::if_statement() {
|
||||||
|
consume(TokenType::LEFT_PAREN, "Expected '(' after 'if'.");
|
||||||
|
ExprPtr condition = expression();
|
||||||
|
consume(TokenType::RIGHT_PAREN, "Expected ')' after if condition.");
|
||||||
|
|
||||||
|
StmtPtr then_branch = statement();
|
||||||
|
StmtPtr else_branch = nullptr;
|
||||||
|
|
||||||
|
if (match({TokenType::ELSE})) {
|
||||||
|
else_branch = statement();
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_unique<IfStmt>(std::move(condition), std::move(then_branch),
|
||||||
|
std::move(else_branch));
|
||||||
|
}
|
||||||
|
|
||||||
|
StmtPtr Parser::while_statement() {
|
||||||
|
consume(TokenType::LEFT_PAREN, "Expected '(' after 'while'.");
|
||||||
|
ExprPtr condition = expression();
|
||||||
|
consume(TokenType::RIGHT_PAREN, "Expected ')' after while condition.");
|
||||||
|
|
||||||
|
StmtPtr body = statement();
|
||||||
|
|
||||||
|
return std::make_unique<WhileStmt>(std::move(condition), std::move(body));
|
||||||
|
}
|
||||||
|
|
||||||
|
StmtPtr Parser::for_statement() {
|
||||||
|
consume(TokenType::LEFT_PAREN, "Expected '(' after 'for'.");
|
||||||
|
|
||||||
|
StmtPtr initializer = nullptr;
|
||||||
|
if (!match({TokenType::SEMICOLON})) {
|
||||||
|
initializer = declaration();
|
||||||
|
consume(TokenType::SEMICOLON, "Expected ';' after for initializer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprPtr condition = nullptr;
|
||||||
|
if (!check(TokenType::SEMICOLON)) {
|
||||||
|
condition = expression();
|
||||||
|
}
|
||||||
|
consume(TokenType::SEMICOLON, "Expected ';' after for condition.");
|
||||||
|
|
||||||
|
ExprPtr increment = nullptr;
|
||||||
|
if (!check(TokenType::RIGHT_PAREN)) {
|
||||||
|
increment = expression();
|
||||||
|
}
|
||||||
|
consume(TokenType::RIGHT_PAREN, "Expected ')' after for clauses.");
|
||||||
|
|
||||||
|
StmtPtr body = statement();
|
||||||
|
|
||||||
|
return std::make_unique<ForStmt>(std::move(initializer), std::move(condition),
|
||||||
|
std::move(increment), std::move(body));
|
||||||
|
}
|
||||||
|
|
||||||
|
StmtPtr Parser::return_statement() {
|
||||||
|
ExprPtr value = nullptr;
|
||||||
|
|
||||||
|
if (!check(TokenType::SEMICOLON)) {
|
||||||
|
value = expression();
|
||||||
|
}
|
||||||
|
|
||||||
|
consume(TokenType::SEMICOLON, "Expected ';' after return value.");
|
||||||
|
return std::make_unique<ReturnStmt>(std::move(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
StmtPtr Parser::block_statement() {
|
||||||
|
std::vector<StmtPtr> statements;
|
||||||
|
|
||||||
|
while (!check(TokenType::RIGHT_BRACE) && !is_at_end()) {
|
||||||
|
auto stmt = declaration();
|
||||||
|
// If declaration returned a VarDecl (not a class/function/statement), consume semicolon
|
||||||
|
if (dynamic_cast<VarDecl*>(stmt.get())) {
|
||||||
|
consume(TokenType::SEMICOLON, "Expected ';' after variable declaration.");
|
||||||
|
}
|
||||||
|
statements.push_back(std::move(stmt));
|
||||||
|
}
|
||||||
|
|
||||||
|
consume(TokenType::RIGHT_BRACE, "Expected '}' after block.");
|
||||||
|
return std::make_unique<BlockStmt>(std::move(statements));
|
||||||
|
}
|
||||||
|
|
||||||
|
StmtPtr Parser::expression_statement() {
|
||||||
|
ExprPtr expr = expression();
|
||||||
|
consume(TokenType::SEMICOLON, "Expected ';' after expression.");
|
||||||
|
return std::make_unique<ExprStmt>(std::move(expr));
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprPtr Parser::expression() {
|
||||||
|
return assignment();
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprPtr Parser::assignment() {
|
||||||
|
ExprPtr expr = logical_or();
|
||||||
|
|
||||||
|
if (match({TokenType::EQUAL})) {
|
||||||
|
Token equals = previous();
|
||||||
|
ExprPtr value = assignment();
|
||||||
|
|
||||||
|
if (auto* var_expr = dynamic_cast<VariableExpr*>(expr.get())) {
|
||||||
|
return std::make_unique<AssignExpr>(var_expr->name, std::move(value));
|
||||||
|
} else if (auto* get_expr = dynamic_cast<GetExpr*>(expr.get())) {
|
||||||
|
return std::make_unique<SetExpr>(std::move(get_expr->object),
|
||||||
|
get_expr->name, std::move(value));
|
||||||
|
} else if (auto* index_expr = dynamic_cast<IndexExpr*>(expr.get())) {
|
||||||
|
return std::make_unique<IndexSetExpr>(std::move(index_expr->object),
|
||||||
|
std::move(index_expr->index),
|
||||||
|
std::move(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error(equals, "Invalid assignment target.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprPtr Parser::logical_or() {
|
||||||
|
ExprPtr expr = logical_and();
|
||||||
|
|
||||||
|
while (match({TokenType::OR})) {
|
||||||
|
Token op = previous();
|
||||||
|
ExprPtr right = logical_and();
|
||||||
|
expr = std::make_unique<BinaryExpr>(std::move(expr), op.lexeme, std::move(right));
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprPtr Parser::logical_and() {
|
||||||
|
ExprPtr expr = equality();
|
||||||
|
|
||||||
|
while (match({TokenType::AND})) {
|
||||||
|
Token op = previous();
|
||||||
|
ExprPtr right = equality();
|
||||||
|
expr = std::make_unique<BinaryExpr>(std::move(expr), op.lexeme, std::move(right));
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprPtr Parser::equality() {
|
||||||
|
ExprPtr expr = comparison();
|
||||||
|
|
||||||
|
while (match({TokenType::EQUAL_EQUAL, TokenType::BANG_EQUAL})) {
|
||||||
|
Token op = previous();
|
||||||
|
ExprPtr right = comparison();
|
||||||
|
expr = std::make_unique<BinaryExpr>(std::move(expr), op.lexeme, std::move(right));
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprPtr Parser::comparison() {
|
||||||
|
ExprPtr expr = term();
|
||||||
|
|
||||||
|
while (match({TokenType::GREATER, TokenType::GREATER_EQUAL,
|
||||||
|
TokenType::LESS, TokenType::LESS_EQUAL})) {
|
||||||
|
Token op = previous();
|
||||||
|
ExprPtr right = term();
|
||||||
|
expr = std::make_unique<BinaryExpr>(std::move(expr), op.lexeme, std::move(right));
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprPtr Parser::term() {
|
||||||
|
ExprPtr expr = factor();
|
||||||
|
|
||||||
|
while (match({TokenType::MINUS, TokenType::PLUS})) {
|
||||||
|
Token op = previous();
|
||||||
|
ExprPtr right = factor();
|
||||||
|
expr = std::make_unique<BinaryExpr>(std::move(expr), op.lexeme, std::move(right));
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprPtr Parser::factor() {
|
||||||
|
ExprPtr expr = unary();
|
||||||
|
|
||||||
|
while (match({TokenType::SLASH, TokenType::STAR, TokenType::PERCENT})) {
|
||||||
|
Token op = previous();
|
||||||
|
ExprPtr right = unary();
|
||||||
|
expr = std::make_unique<BinaryExpr>(std::move(expr), op.lexeme, std::move(right));
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprPtr Parser::unary() {
|
||||||
|
if (match({TokenType::BANG, TokenType::MINUS})) {
|
||||||
|
Token op = previous();
|
||||||
|
ExprPtr right = unary();
|
||||||
|
return std::make_unique<UnaryExpr>(op.lexeme, std::move(right));
|
||||||
|
}
|
||||||
|
|
||||||
|
return call();
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprPtr Parser::call() {
|
||||||
|
ExprPtr expr = primary();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (match({TokenType::LEFT_PAREN})) {
|
||||||
|
expr = finish_call(std::move(expr));
|
||||||
|
} else if (match({TokenType::DOT})) {
|
||||||
|
Token name = consume(TokenType::IDENTIFIER, "Expected property name after '.'.");
|
||||||
|
expr = std::make_unique<GetExpr>(std::move(expr), name.lexeme);
|
||||||
|
} else if (match({TokenType::LEFT_BRACKET})) {
|
||||||
|
ExprPtr index = expression();
|
||||||
|
consume(TokenType::RIGHT_BRACKET, "Expected ']' after index.");
|
||||||
|
expr = std::make_unique<IndexExpr>(std::move(expr), std::move(index));
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprPtr Parser::primary() {
|
||||||
|
if (match({TokenType::TRUE})) {
|
||||||
|
return std::make_unique<LiteralExpr>(true);
|
||||||
|
}
|
||||||
|
if (match({TokenType::FALSE})) {
|
||||||
|
return std::make_unique<LiteralExpr>(false);
|
||||||
|
}
|
||||||
|
if (match({TokenType::NIL})) {
|
||||||
|
return std::make_unique<LiteralExpr>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match({TokenType::NUMBER_LITERAL})) {
|
||||||
|
return std::make_unique<LiteralExpr>(std::get<double>(previous().literal));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match({TokenType::STRING_LITERAL})) {
|
||||||
|
return std::make_unique<LiteralExpr>(std::get<std::string>(previous().literal));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match({TokenType::THIS})) {
|
||||||
|
return std::make_unique<VariableExpr>("this");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match({TokenType::IDENTIFIER})) {
|
||||||
|
return std::make_unique<VariableExpr>(previous().lexeme);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match({TokenType::LEFT_PAREN})) {
|
||||||
|
ExprPtr expr = expression();
|
||||||
|
consume(TokenType::RIGHT_PAREN, "Expected ')' after expression.");
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match({TokenType::LEFT_BRACKET})) {
|
||||||
|
std::vector<ExprPtr> elements;
|
||||||
|
|
||||||
|
if (!check(TokenType::RIGHT_BRACKET)) {
|
||||||
|
do {
|
||||||
|
elements.push_back(expression());
|
||||||
|
} while (match({TokenType::COMMA}));
|
||||||
|
}
|
||||||
|
|
||||||
|
consume(TokenType::RIGHT_BRACKET, "Expected ']' after list elements.");
|
||||||
|
return std::make_unique<ListExpr>(std::move(elements));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match({TokenType::LEFT_BRACE})) {
|
||||||
|
std::vector<std::pair<ExprPtr, ExprPtr>> pairs;
|
||||||
|
|
||||||
|
if (!check(TokenType::RIGHT_BRACE)) {
|
||||||
|
do {
|
||||||
|
ExprPtr key = expression();
|
||||||
|
consume(TokenType::COLON, "Expected ':' after map key.");
|
||||||
|
ExprPtr value = expression();
|
||||||
|
pairs.emplace_back(std::move(key), std::move(value));
|
||||||
|
} while (match({TokenType::COMMA}));
|
||||||
|
}
|
||||||
|
|
||||||
|
consume(TokenType::RIGHT_BRACE, "Expected '}' after map pairs.");
|
||||||
|
return std::make_unique<MapExpr>(std::move(pairs));
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error(peek(), "Expected expression.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprPtr Parser::finish_call(ExprPtr callee) {
|
||||||
|
std::vector<ExprPtr> arguments;
|
||||||
|
|
||||||
|
if (!check(TokenType::RIGHT_PAREN)) {
|
||||||
|
do {
|
||||||
|
arguments.push_back(expression());
|
||||||
|
} while (match({TokenType::COMMA}));
|
||||||
|
}
|
||||||
|
|
||||||
|
consume(TokenType::RIGHT_PAREN, "Expected ')' after arguments.");
|
||||||
|
|
||||||
|
return std::make_unique<CallExpr>(std::move(callee), std::move(arguments));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace camellya
|
||||||
66
parser.h
Normal file
66
parser.h
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#ifndef CAMELLYA_PARSER_H
|
||||||
|
#define CAMELLYA_PARSER_H
|
||||||
|
|
||||||
|
#include "lexer.h"
|
||||||
|
#include "ast.h"
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace camellya {
|
||||||
|
|
||||||
|
class ParseError : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
explicit ParseError(const std::string& message) : std::runtime_error(message) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Parser {
|
||||||
|
public:
|
||||||
|
explicit Parser(std::vector<Token> tokens);
|
||||||
|
|
||||||
|
Program parse();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Token> tokens_;
|
||||||
|
size_t current_ = 0;
|
||||||
|
|
||||||
|
// Utility methods
|
||||||
|
Token peek() const;
|
||||||
|
Token previous() const;
|
||||||
|
bool is_at_end() const;
|
||||||
|
Token advance();
|
||||||
|
bool check(TokenType type) const;
|
||||||
|
bool match(std::initializer_list<TokenType> types);
|
||||||
|
Token consume(TokenType type, const std::string& message);
|
||||||
|
|
||||||
|
ParseError error(const Token& token, const std::string& message);
|
||||||
|
void synchronize();
|
||||||
|
|
||||||
|
// Parsing methods
|
||||||
|
StmtPtr declaration();
|
||||||
|
StmtPtr class_declaration();
|
||||||
|
StmtPtr function_declaration();
|
||||||
|
StmtPtr var_declaration();
|
||||||
|
StmtPtr statement();
|
||||||
|
StmtPtr if_statement();
|
||||||
|
StmtPtr while_statement();
|
||||||
|
StmtPtr for_statement();
|
||||||
|
StmtPtr return_statement();
|
||||||
|
StmtPtr block_statement();
|
||||||
|
StmtPtr expression_statement();
|
||||||
|
|
||||||
|
ExprPtr expression();
|
||||||
|
ExprPtr assignment();
|
||||||
|
ExprPtr logical_or();
|
||||||
|
ExprPtr logical_and();
|
||||||
|
ExprPtr equality();
|
||||||
|
ExprPtr comparison();
|
||||||
|
ExprPtr term();
|
||||||
|
ExprPtr factor();
|
||||||
|
ExprPtr unary();
|
||||||
|
ExprPtr call();
|
||||||
|
ExprPtr primary();
|
||||||
|
ExprPtr finish_call(ExprPtr callee);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace camellya
|
||||||
|
|
||||||
|
#endif // CAMELLYA_PARSER_H
|
||||||
135
state.cpp
Normal file
135
state.cpp
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
#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
state.h
Normal file
63
state.h
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#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
|
||||||
150
tests/test_basic.cpp
Normal file
150
tests/test_basic.cpp
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
#include <catch2/benchmark/catch_benchmark.hpp>
|
||||||
|
#include "library.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
using namespace camellya;
|
||||||
|
|
||||||
|
TEST_CASE("basic arithmetic", "[script]") {
|
||||||
|
State state;
|
||||||
|
const char* script = R"(
|
||||||
|
number x = 10;
|
||||||
|
number y = 20;
|
||||||
|
number z = x + y;
|
||||||
|
)";
|
||||||
|
|
||||||
|
REQUIRE(state.do_string(script));
|
||||||
|
|
||||||
|
auto z = state.get_global("z");
|
||||||
|
REQUIRE(z);
|
||||||
|
REQUIRE(z->type() == Type::NUMBER);
|
||||||
|
|
||||||
|
auto nz = std::dynamic_pointer_cast<NumberValue>(z);
|
||||||
|
REQUIRE(nz);
|
||||||
|
REQUIRE(nz->value == 30.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("basic function", "[script][func]") {
|
||||||
|
State state;
|
||||||
|
const char* script = R"(
|
||||||
|
func add(number x, number y) -> number {
|
||||||
|
return x + y;
|
||||||
|
}
|
||||||
|
number z = add(10, 20);
|
||||||
|
)";
|
||||||
|
|
||||||
|
REQUIRE(state.do_string(script));
|
||||||
|
|
||||||
|
auto z = state.get_global("z");
|
||||||
|
REQUIRE(z);
|
||||||
|
REQUIRE(z->type() == Type::NUMBER);
|
||||||
|
|
||||||
|
auto nz = std::dynamic_pointer_cast<NumberValue>(z);
|
||||||
|
REQUIRE(nz);
|
||||||
|
REQUIRE(nz->value == 30.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("list indexing is 0-based", "[list]") {
|
||||||
|
State state;
|
||||||
|
const char* script = R"(
|
||||||
|
list numbers = [10, 20, 30];
|
||||||
|
)";
|
||||||
|
|
||||||
|
REQUIRE(state.do_string(script));
|
||||||
|
|
||||||
|
auto list_val = state.get_global("numbers");
|
||||||
|
REQUIRE(list_val);
|
||||||
|
REQUIRE(list_val->type() == Type::LIST);
|
||||||
|
|
||||||
|
auto list = std::dynamic_pointer_cast<ListValue>(list_val);
|
||||||
|
REQUIRE(list);
|
||||||
|
REQUIRE(list->size() == 3);
|
||||||
|
|
||||||
|
auto first = list->get(0);
|
||||||
|
auto third = list->get(2);
|
||||||
|
|
||||||
|
REQUIRE(first->type() == Type::NUMBER);
|
||||||
|
REQUIRE(third->type() == Type::NUMBER);
|
||||||
|
|
||||||
|
auto first_n = std::dynamic_pointer_cast<NumberValue>(first);
|
||||||
|
auto third_n = std::dynamic_pointer_cast<NumberValue>(third);
|
||||||
|
|
||||||
|
REQUIRE(first_n->value == 10.0);
|
||||||
|
REQUIRE(third_n->value == 30.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("class init is called on declaration", "[class][init]") {
|
||||||
|
State state;
|
||||||
|
const char* script = R"(
|
||||||
|
class Person {
|
||||||
|
number age;
|
||||||
|
string name;
|
||||||
|
|
||||||
|
func init() -> nil {
|
||||||
|
age = 18;
|
||||||
|
name = "Default";
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAge() -> number {
|
||||||
|
return this.age;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Person p;
|
||||||
|
number a = p.getAge();
|
||||||
|
)";
|
||||||
|
|
||||||
|
REQUIRE(state.do_string(script));
|
||||||
|
|
||||||
|
auto p_val = state.get_global("p");
|
||||||
|
REQUIRE(p_val);
|
||||||
|
REQUIRE(p_val->type() == Type::INSTANCE);
|
||||||
|
|
||||||
|
auto instance = std::dynamic_pointer_cast<InstanceValue>(p_val);
|
||||||
|
REQUIRE(instance);
|
||||||
|
|
||||||
|
auto age_val = instance->get("age");
|
||||||
|
auto name_val = instance->get("name");
|
||||||
|
|
||||||
|
REQUIRE(age_val->type() == Type::NUMBER);
|
||||||
|
REQUIRE(name_val->type() == Type::STRING);
|
||||||
|
|
||||||
|
auto age_num = std::dynamic_pointer_cast<NumberValue>(age_val);
|
||||||
|
auto name_str = std::dynamic_pointer_cast<StringValue>(name_val);
|
||||||
|
|
||||||
|
REQUIRE(age_num->value == 18.0);
|
||||||
|
REQUIRE(name_str->value == "Default");
|
||||||
|
|
||||||
|
auto a_val = state.get_global("a");
|
||||||
|
REQUIRE(a_val);
|
||||||
|
REQUIRE(a_val->type() == Type::NUMBER);
|
||||||
|
auto a_num = std::dynamic_pointer_cast<NumberValue>(a_val);
|
||||||
|
REQUIRE(a_num->value == 18.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("interpreter performance: simple loop", "[perf][script]") {
|
||||||
|
State state;
|
||||||
|
const char* script = R"(
|
||||||
|
func sum_to(number n) -> number {
|
||||||
|
number s = 0;
|
||||||
|
for (number i = 0; i < n; i = i + 1) {
|
||||||
|
s = s + i;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
number r = sum_to(1000);
|
||||||
|
)";
|
||||||
|
|
||||||
|
BENCHMARK("sum_to(1000)") {
|
||||||
|
if (!state.do_string(script)) {
|
||||||
|
auto last_error = state.get_error();
|
||||||
|
REQUIRE(last_error.empty());
|
||||||
|
}
|
||||||
|
auto r_val = state.get_global("r");
|
||||||
|
REQUIRE(r_val);
|
||||||
|
REQUIRE(r_val->type() == Type::NUMBER);
|
||||||
|
auto r_num = std::dynamic_pointer_cast<NumberValue>(r_val);
|
||||||
|
REQUIRE(r_num->value == 499500.0);
|
||||||
|
};
|
||||||
|
}
|
||||||
124
value.cpp
Normal file
124
value.cpp
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
#include "value.h"
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace camellya {
|
||||||
|
|
||||||
|
std::string NumberValue::to_string() const {
|
||||||
|
if (std::floor(value) == value) {
|
||||||
|
return std::to_string(static_cast<int64_t>(value));
|
||||||
|
}
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << std::fixed << std::setprecision(6) << value;
|
||||||
|
std::string str = oss.str();
|
||||||
|
str.erase(str.find_last_not_of('0') + 1, std::string::npos);
|
||||||
|
if (str.back() == '.') str.pop_back();
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ListValue::to_string() const {
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << "[";
|
||||||
|
for (size_t i = 0; i < elements.size(); ++i) {
|
||||||
|
if (i > 0) oss << ", ";
|
||||||
|
oss << elements[i]->to_string();
|
||||||
|
}
|
||||||
|
oss << "]";
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
ValuePtr ListValue::clone() const {
|
||||||
|
std::vector<ValuePtr> cloned_elements;
|
||||||
|
cloned_elements.reserve(elements.size());
|
||||||
|
for (const auto& elem : elements) {
|
||||||
|
cloned_elements.push_back(elem->clone());
|
||||||
|
}
|
||||||
|
return std::make_shared<ListValue>(std::move(cloned_elements));
|
||||||
|
}
|
||||||
|
|
||||||
|
ValuePtr ListValue::get(size_t index) const {
|
||||||
|
if (index >= elements.size()) {
|
||||||
|
return std::make_shared<NilValue>();
|
||||||
|
}
|
||||||
|
return elements[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListValue::set(size_t index, ValuePtr value) {
|
||||||
|
if (index >= elements.size()) {
|
||||||
|
elements.resize(index + 1, std::make_shared<NilValue>());
|
||||||
|
}
|
||||||
|
elements[index] = std::move(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string MapValue::to_string() const {
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << "{";
|
||||||
|
bool first = true;
|
||||||
|
for (const auto& [key, value] : pairs) {
|
||||||
|
if (!first) oss << ", ";
|
||||||
|
first = false;
|
||||||
|
oss << key << ": " << value->to_string();
|
||||||
|
}
|
||||||
|
oss << "}";
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
ValuePtr MapValue::clone() const {
|
||||||
|
std::map<std::string, ValuePtr> cloned_pairs;
|
||||||
|
for (const auto& [key, value] : pairs) {
|
||||||
|
cloned_pairs[key] = value->clone();
|
||||||
|
}
|
||||||
|
return std::make_shared<MapValue>(std::move(cloned_pairs));
|
||||||
|
}
|
||||||
|
|
||||||
|
ValuePtr MapValue::get(const std::string& key) const {
|
||||||
|
auto it = pairs.find(key);
|
||||||
|
if (it == pairs.end()) {
|
||||||
|
return std::make_shared<NilValue>();
|
||||||
|
}
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
ValuePtr ClassValue::clone() const {
|
||||||
|
auto cloned = std::make_shared<ClassValue>(name);
|
||||||
|
cloned->fields = fields;
|
||||||
|
cloned->methods = methods;
|
||||||
|
return cloned;
|
||||||
|
}
|
||||||
|
|
||||||
|
ValuePtr InstanceValue::clone() const {
|
||||||
|
auto cloned = std::make_shared<InstanceValue>(klass);
|
||||||
|
for (const auto& [key, value] : fields) {
|
||||||
|
cloned->fields[key] = value->clone();
|
||||||
|
}
|
||||||
|
return cloned;
|
||||||
|
}
|
||||||
|
|
||||||
|
ValuePtr InstanceValue::get(const std::string& name) const {
|
||||||
|
// First check fields
|
||||||
|
auto field_it = fields.find(name);
|
||||||
|
if (field_it != fields.end()) {
|
||||||
|
return field_it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then check methods and bind 'this'
|
||||||
|
auto method_it = klass->methods.find(name);
|
||||||
|
if (method_it != klass->methods.end()) {
|
||||||
|
auto bound = std::make_shared<FunctionValue>(*method_it->second);
|
||||||
|
auto self = std::static_pointer_cast<InstanceValue>(
|
||||||
|
const_cast<InstanceValue*>(this)->shared_from_this());
|
||||||
|
bound->bound_instance = self;
|
||||||
|
return bound;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_shared<NilValue>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceValue::set(const std::string& name, ValuePtr value) {
|
||||||
|
if (fields.find(name) != fields.end()) {
|
||||||
|
fields[name] = std::move(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace camellya
|
||||||
211
value.h
Normal file
211
value.h
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
#ifndef CAMELLYA_VALUE_H
|
||||||
|
#define CAMELLYA_VALUE_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <variant>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace camellya {
|
||||||
|
|
||||||
|
class Value;
|
||||||
|
|
||||||
|
using ValuePtr = std::shared_ptr<Value>;
|
||||||
|
using NativeFunction = std::function<ValuePtr(const std::vector<ValuePtr>&)>;
|
||||||
|
|
||||||
|
enum class Type {
|
||||||
|
NIL,
|
||||||
|
NUMBER,
|
||||||
|
STRING,
|
||||||
|
BOOL,
|
||||||
|
LIST,
|
||||||
|
MAP,
|
||||||
|
FUNCTION,
|
||||||
|
CLASS,
|
||||||
|
INSTANCE
|
||||||
|
};
|
||||||
|
|
||||||
|
class Value {
|
||||||
|
public:
|
||||||
|
virtual ~Value() = default;
|
||||||
|
virtual Type type() const = 0;
|
||||||
|
virtual std::string to_string() const = 0;
|
||||||
|
virtual ValuePtr clone() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class NilValue : public Value {
|
||||||
|
public:
|
||||||
|
Type type() const override { return Type::NIL; }
|
||||||
|
std::string to_string() const override { return "nil"; }
|
||||||
|
ValuePtr clone() const override { return std::make_shared<NilValue>(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class NumberValue : public Value {
|
||||||
|
public:
|
||||||
|
double value;
|
||||||
|
|
||||||
|
explicit NumberValue(double value) : value(value) {}
|
||||||
|
Type type() const override { return Type::NUMBER; }
|
||||||
|
std::string to_string() const override;
|
||||||
|
ValuePtr clone() const override { return std::make_shared<NumberValue>(value); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class StringValue : public Value {
|
||||||
|
public:
|
||||||
|
std::string value;
|
||||||
|
|
||||||
|
explicit StringValue(std::string value) : value(std::move(value)) {}
|
||||||
|
Type type() const override { return Type::STRING; }
|
||||||
|
std::string to_string() const override { return value; }
|
||||||
|
ValuePtr clone() const override { return std::make_shared<StringValue>(value); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class BoolValue : public Value {
|
||||||
|
public:
|
||||||
|
bool value;
|
||||||
|
|
||||||
|
explicit BoolValue(bool value) : value(value) {}
|
||||||
|
Type type() const override { return Type::BOOL; }
|
||||||
|
std::string to_string() const override { return value ? "true" : "false"; }
|
||||||
|
ValuePtr clone() const override { return std::make_shared<BoolValue>(value); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class ListValue : public Value {
|
||||||
|
public:
|
||||||
|
std::vector<ValuePtr> elements;
|
||||||
|
|
||||||
|
ListValue() = default;
|
||||||
|
explicit ListValue(std::vector<ValuePtr> elements) : elements(std::move(elements)) {}
|
||||||
|
|
||||||
|
Type type() const override { return Type::LIST; }
|
||||||
|
std::string to_string() const override;
|
||||||
|
ValuePtr clone() const override;
|
||||||
|
|
||||||
|
void push(ValuePtr value) { elements.push_back(std::move(value)); }
|
||||||
|
ValuePtr get(size_t index) const;
|
||||||
|
void set(size_t index, ValuePtr value);
|
||||||
|
size_t size() const { return elements.size(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class MapValue : public Value {
|
||||||
|
public:
|
||||||
|
std::map<std::string, ValuePtr> pairs;
|
||||||
|
|
||||||
|
MapValue() = default;
|
||||||
|
explicit MapValue(std::map<std::string, ValuePtr> pairs) : pairs(std::move(pairs)) {}
|
||||||
|
|
||||||
|
Type type() const override { return Type::MAP; }
|
||||||
|
std::string to_string() const override;
|
||||||
|
ValuePtr clone() const override;
|
||||||
|
|
||||||
|
void set(const std::string& key, ValuePtr value) { pairs[key] = std::move(value); }
|
||||||
|
ValuePtr get(const std::string& key) const;
|
||||||
|
bool has(const std::string& key) const { return pairs.find(key) != pairs.end(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Forward declarations
|
||||||
|
struct FunctionDecl;
|
||||||
|
class ClassValue;
|
||||||
|
class InstanceValue;
|
||||||
|
|
||||||
|
class FunctionValue : public Value {
|
||||||
|
public:
|
||||||
|
std::string name;
|
||||||
|
std::vector<std::pair<std::string, std::string>> parameters;
|
||||||
|
std::string return_type;
|
||||||
|
std::shared_ptr<FunctionDecl> declaration;
|
||||||
|
NativeFunction native_func;
|
||||||
|
bool is_native;
|
||||||
|
std::shared_ptr<InstanceValue> bound_instance;
|
||||||
|
|
||||||
|
// Script function
|
||||||
|
FunctionValue(std::string name, std::shared_ptr<FunctionDecl> declaration,
|
||||||
|
std::shared_ptr<InstanceValue> bound_instance = nullptr)
|
||||||
|
: name(std::move(name)), declaration(std::move(declaration)),
|
||||||
|
is_native(false), bound_instance(std::move(bound_instance)) {}
|
||||||
|
|
||||||
|
// Native function
|
||||||
|
FunctionValue(std::string name, NativeFunction func)
|
||||||
|
: name(std::move(name)), native_func(std::move(func)), is_native(true) {}
|
||||||
|
|
||||||
|
Type type() const override { return Type::FUNCTION; }
|
||||||
|
std::string to_string() const override { return "<function " + name + ">"; }
|
||||||
|
ValuePtr clone() const override { return std::make_shared<FunctionValue>(*this); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class ClassValue : public Value {
|
||||||
|
public:
|
||||||
|
std::string name;
|
||||||
|
std::map<std::string, std::string> fields; // field_name -> type_name
|
||||||
|
std::map<std::string, std::shared_ptr<FunctionValue>> methods;
|
||||||
|
|
||||||
|
explicit ClassValue(std::string name) : name(std::move(name)) {}
|
||||||
|
|
||||||
|
Type type() const override { return Type::CLASS; }
|
||||||
|
std::string to_string() const override { return "<class " + name + ">"; }
|
||||||
|
ValuePtr clone() const override;
|
||||||
|
|
||||||
|
void add_field(const std::string& name, const std::string& type) {
|
||||||
|
fields[name] = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_method(const std::string& name, std::shared_ptr<FunctionValue> method) {
|
||||||
|
methods[name] = std::move(method);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class InstanceValue : public Value, public std::enable_shared_from_this<InstanceValue> {
|
||||||
|
public:
|
||||||
|
std::shared_ptr<ClassValue> klass;
|
||||||
|
std::map<std::string, ValuePtr> fields;
|
||||||
|
|
||||||
|
explicit InstanceValue(std::shared_ptr<ClassValue> klass) : klass(std::move(klass)) {
|
||||||
|
// Initialize fields with nil
|
||||||
|
for (const auto& [field_name, type_name] : this->klass->fields) {
|
||||||
|
fields[field_name] = std::make_shared<NilValue>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Type type() const override { return Type::INSTANCE; }
|
||||||
|
std::string to_string() const override { return "<instance of " + klass->name + ">"; }
|
||||||
|
ValuePtr clone() const override;
|
||||||
|
|
||||||
|
ValuePtr get(const std::string& name) const;
|
||||||
|
void set(const std::string& name, ValuePtr value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
inline bool is_truthy(const ValuePtr& value) {
|
||||||
|
if (!value || value->type() == Type::NIL) return false;
|
||||||
|
if (value->type() == Type::BOOL) {
|
||||||
|
return std::dynamic_pointer_cast<BoolValue>(value)->value;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool values_equal(const ValuePtr& a, const ValuePtr& b) {
|
||||||
|
if (!a || !b) return false;
|
||||||
|
if (a->type() != b->type()) return false;
|
||||||
|
|
||||||
|
switch (a->type()) {
|
||||||
|
case Type::NIL:
|
||||||
|
return true;
|
||||||
|
case Type::NUMBER:
|
||||||
|
return std::dynamic_pointer_cast<NumberValue>(a)->value ==
|
||||||
|
std::dynamic_pointer_cast<NumberValue>(b)->value;
|
||||||
|
case Type::STRING:
|
||||||
|
return std::dynamic_pointer_cast<StringValue>(a)->value ==
|
||||||
|
std::dynamic_pointer_cast<StringValue>(b)->value;
|
||||||
|
case Type::BOOL:
|
||||||
|
return std::dynamic_pointer_cast<BoolValue>(a)->value ==
|
||||||
|
std::dynamic_pointer_cast<BoolValue>(b)->value;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace camellya
|
||||||
|
|
||||||
|
#endif // CAMELLYA_VALUE_H
|
||||||
Reference in New Issue
Block a user