Support var

This commit is contained in:
2026-01-19 22:07:25 +08:00
parent 57def6137b
commit 0833a2229b
6 changed files with 93 additions and 55 deletions

View File

@@ -7,7 +7,7 @@
- **0-based 索引**:数组和列表从 0 开始索引 - **0-based 索引**:数组和列表从 0 开始索引
- **明确的类型系统**:区分 list 和 map - **明确的类型系统**:区分 list 和 map
- **类支持**:支持 class 定义,包含字段和方法 - **类支持**:支持 class 定义,包含字段和方法
- **静态类型声明**变量需要类型声明number, string, bool, list, map - **类型推断**:使用 `var` 关键字自动推断变量类型,也支持显式类型注解
- **类 Lua 的 API**:提供简单的嵌入式 API - **类 Lua 的 API**:提供简单的嵌入式 API
## 语法示例 ## 语法示例
@@ -15,15 +15,21 @@
### 基本类型 ### 基本类型
```javascript ```javascript
number x = 10; // 类型自动推断
string name = "Alice"; var x = 10;
bool flag = true; var name = "Alice";
var flag = true;
// 显式类型声明
var y : number = 3.14;
var title : string = "Developer";
var active : bool = false;
``` ```
### List0-indexed ### List0-indexed
```javascript ```javascript
list numbers = [10, 20, 30]; var numbers = [10, 20, 30]; // 自动推断为 list
print(numbers[0]); // 输出: 10 print(numbers[0]); // 输出: 10
numbers[1] = 99; numbers[1] = 99;
``` ```
@@ -31,7 +37,7 @@ numbers[1] = 99;
### Map ### Map
```javascript ```javascript
map person = {"name": "Bob", "age": "25"}; var person = {"name": "Bob", "age": "25"}; // 自动推断为 map
print(person["name"]); // 输出: Bob print(person["name"]); // 输出: Bob
person["city"] = "New York"; person["city"] = "New York";
``` ```
@@ -43,15 +49,15 @@ func add(number a, number b) -> number {
return a + b; return a + b;
} }
number result = add(10, 20); var result = add(10, 20); // 自动推断返回值类型
``` ```
### 类 ### 类
```javascript ```javascript
class Person { class Person {
number age; var age : number; // 类成员必须显式声明类型
string name; var name : string;
func sayHi() -> string { func sayHi() -> string {
print(name, "says: I'm", age, "years old"); print(name, "says: I'm", age, "years old");
@@ -59,7 +65,7 @@ class Person {
} }
} }
Person p; var p : Person; // 实例化类
p.age = 10; p.age = 10;
p.name = "Peter"; p.name = "Peter";
p.sayHi(); p.sayHi();
@@ -76,14 +82,14 @@ if (x > 10) {
} }
// while loop // while loop
number i = 0; var i = 0;
while (i < 5) { while (i < 5) {
print("Count:", i); print("Count:", i);
i = i + 1; i = i + 1;
} }
// for loop // for loop
for (number j = 0; j < 3; j = j + 1) { for (var j = 0; j < 3; j = j + 1) {
print("For loop:", j); print("For loop:", j);
} }
``` ```
@@ -117,8 +123,8 @@ int main() {
// 执行脚本字符串 // 执行脚本字符串
const char* script = R"( const char* script = R"(
class Person { class Person {
number age; var age : number;
string name; var name : string;
func sayHi() -> string { func sayHi() -> string {
print(name, "says: I'm", age, "years old"); print(name, "says: I'm", age, "years old");
@@ -126,7 +132,7 @@ int main() {
} }
} }
Person p; var p : Person;
p.age = 10; p.age = 10;
p.name = "Peter"; p.name = "Peter";
p.sayHi(); p.sayHi();
@@ -203,10 +209,10 @@ camellya/
|------|-----|----------| |------|-----|----------|
| 索引起始 | 1 | 0 | | 索引起始 | 1 | 0 |
| Table | 统一的 table | 区分 list 和 map | | Table | 统一的 table | 区分 list 和 map |
| 类型 | 动态 | 静态声明 | | 类型 | 动态 | 类型推断 + 可选类型注解 |
| 类 | 通过 metatable | 原生支持 | | 类 | 通过 metatable | 原生支持 |
| 语法 | `function` | `func` | | 语法 | `function` | `func` |
| 类型注解 | 无 | 必须声明 | | 变量声明 | 无需声明 | `var` 关键字 |
## 许可证 ## 许可证

View File

@@ -2,8 +2,8 @@
// This demonstrates the Person class from the specification // This demonstrates the Person class from the specification
class Person { class Person {
number age; var age : number;
string name; var name : string;
func sayHi() -> string { func sayHi() -> string {
print(name, "says: I'm", age, "years old"); print(name, "says: I'm", age, "years old");
@@ -18,7 +18,7 @@ class Person {
} }
// Create an instance // Create an instance
Person p; var p : Person;
p.age = 10; p.age = 10;
p.name = "Peter"; p.name = "Peter";
@@ -29,12 +29,12 @@ p.sayHi();
// Test lists (0-indexed) // Test lists (0-indexed)
print("\n=== List Demo ==="); print("\n=== List Demo ===");
list numbers = [1, 2, 3, 4, 5]; var numbers = [1, 2, 3, 4, 5];
print("List:", numbers); print("List:", numbers);
print("First element (index 0):", numbers[0]); print("First element (index 0):", numbers[0]);
print("测试 element (index 2):", numbers[2]); print("测试 element (index 2):", numbers[2]);
for(number i = 0; i < len(numbers); i = i + 1) { for(var i = 0; i < len(numbers); i = i + 1) {
print("List element", numbers[i]); print("List element", numbers[i]);
} }
@@ -44,7 +44,7 @@ while(true) {
} }
// Test maps // Test maps
print("\n=== Map Demo ==="); print("\n=== Map Demo ===");
map config = {"host": "localhost", "port": "8080"}; var config = {"host": "localhost", "port": "8080"};
print("Config:", config); print("Config:", config);
print("Host:", config["host"]); print("Host:", config["host"]);

View File

@@ -133,12 +133,13 @@ struct ExprStmt : public Stmt {
}; };
struct VarDecl : public Stmt { struct VarDecl : public Stmt {
std::string type_name; std::string type_name; // Can be empty if type inference is needed
std::string name; std::string name;
ExprPtr initializer; ExprPtr initializer;
bool infer_type; // true if type should be inferred from initializer
VarDecl(std::string type_name, std::string name, ExprPtr initializer = nullptr) VarDecl(std::string type_name, std::string name, ExprPtr initializer = nullptr, bool infer_type = false)
: type_name(std::move(type_name)), name(std::move(name)), initializer(std::move(initializer)) {} : type_name(std::move(type_name)), name(std::move(name)), initializer(std::move(initializer)), infer_type(infer_type) {}
}; };
struct BlockStmt : public Stmt { struct BlockStmt : public Stmt {

View File

@@ -367,7 +367,23 @@ void Interpreter::exec_var_decl(const VarDecl& stmt) {
if (stmt.initializer) { if (stmt.initializer) {
value = evaluate(*stmt.initializer); value = evaluate(*stmt.initializer);
// If type inference is needed, we use the value directly
if (stmt.infer_type) {
// Type is automatically inferred from the initializer value
environment->define(stmt.name, value);
return;
}
// If explicit type is provided, verify the value matches
// (This is a simple check, could be more sophisticated)
if (!stmt.type_name.empty()) {
// Just use the value; type checking could be added here
environment->define(stmt.name, value);
return;
}
} else { } else {
// No initializer, use default value based on type
value = create_default_value(stmt.type_name); value = create_default_value(stmt.type_name);
} }

View File

@@ -94,14 +94,9 @@ StmtPtr Parser::declaration() {
if (match({TokenType::FUNC})) { if (match({TokenType::FUNC})) {
return function_declaration(); return function_declaration();
} }
if (check(TokenType::NUMBER) || check(TokenType::STRING) || // Handle var keyword for variable declaration
check(TokenType::BOOL) || check(TokenType::LIST) || if (match({TokenType::VAR})) {
check(TokenType::MAP) || check(TokenType::IDENTIFIER)) { return var_declaration();
// Type name followed by identifier
Token lookahead = tokens_[current_];
if (current_ + 1 < tokens_.size() && tokens_[current_ + 1].type == TokenType::IDENTIFIER) {
return var_declaration();
}
} }
return statement(); return statement();
@@ -118,8 +113,10 @@ StmtPtr Parser::class_declaration() {
if (check(TokenType::FUNC)) { if (check(TokenType::FUNC)) {
advance(); advance();
members.push_back(function_declaration()); members.push_back(function_declaration());
} else { } else if (match({TokenType::VAR})) {
members.push_back(var_declaration()); members.push_back(var_declaration());
} else {
throw error(peek(), "Expected 'var' or 'func' in class body.");
} }
} }
@@ -161,17 +158,35 @@ StmtPtr Parser::function_declaration() {
} }
StmtPtr Parser::var_declaration() { StmtPtr Parser::var_declaration() {
Token type_token = advance(); // var keyword has already been consumed
std::string type_name = type_token.lexeme; Token name = consume(TokenType::IDENTIFIER, "Expected variable name after 'var'.");
Token name = consume(TokenType::IDENTIFIER, "Expected variable name.");
std::string type_name;
bool infer_type = false;
ExprPtr initializer = nullptr; ExprPtr initializer = nullptr;
if (match({TokenType::EQUAL})) {
// Check for explicit type annotation: var x : type
if (match({TokenType::COLON})) {
Token type_token = advance();
type_name = type_token.lexeme;
// Optional initializer
if (match({TokenType::EQUAL})) {
initializer = expression();
}
} else {
// No type annotation, must have initializer for type inference
if (!match({TokenType::EQUAL})) {
throw error(peek(), "Variable declaration without type annotation must have an initializer.");
}
initializer = expression(); initializer = expression();
infer_type = true;
type_name = ""; // Will be inferred at runtime
} }
consume(TokenType::SEMICOLON, "Expected ';' after variable declaration."); consume(TokenType::SEMICOLON, "Expected ';' after variable declaration.");
return std::make_unique<VarDecl>(type_name, name.lexeme, std::move(initializer)); return std::make_unique<VarDecl>(type_name, name.lexeme, std::move(initializer), infer_type);
} }
StmtPtr Parser::statement() { StmtPtr Parser::statement() {

View File

@@ -9,9 +9,9 @@ using namespace camellya;
TEST_CASE("basic arithmetic", "[script]") { TEST_CASE("basic arithmetic", "[script]") {
State state; State state;
const char* script = R"( const char* script = R"(
number x = 10; var x = 10;
number y = 20; var y = 20;
number z = x + y; var z = x + y;
)"; )";
REQUIRE(state.do_string(script)); REQUIRE(state.do_string(script));
@@ -31,7 +31,7 @@ TEST_CASE("basic function", "[script][func]") {
func add(number x, number y) -> number { func add(number x, number y) -> number {
return x + y; return x + y;
} }
number z = add(10, 20); var z = add(10, 20);
)"; )";
REQUIRE(state.do_string(script)); REQUIRE(state.do_string(script));
@@ -48,7 +48,7 @@ TEST_CASE("basic function", "[script][func]") {
TEST_CASE("list indexing is 0-based", "[list]") { TEST_CASE("list indexing is 0-based", "[list]") {
State state; State state;
const char* script = R"( const char* script = R"(
list numbers = [10, 20, 30]; var numbers = [10, 20, 30];
)"; )";
REQUIRE(state.do_string(script)); REQUIRE(state.do_string(script));
@@ -78,8 +78,8 @@ TEST_CASE("class init is called on declaration", "[class][init]") {
State state; State state;
const char* script = R"( const char* script = R"(
class Person { class Person {
number age; var age : number;
string name; var name : string;
func init() -> nil { func init() -> nil {
age = 18; age = 18;
@@ -91,8 +91,8 @@ TEST_CASE("class init is called on declaration", "[class][init]") {
} }
} }
Person p; var p : Person;
number a = p.getAge(); var a = p.getAge();
)"; )";
REQUIRE(state.do_string(script)); REQUIRE(state.do_string(script));
@@ -152,8 +152,8 @@ TEST_CASE("class init is called on declaration", "[class][init]") {
TEST_CASE("loop break", "[script][loop]") { TEST_CASE("loop break", "[script][loop]") {
State state; State state;
const char* script = R"( const char* script = R"(
number sum = 0; var sum = 0;
for (number i = 0; i < 10; i = i + 1) { for (var i = 0; i < 10; i = i + 1) {
if (i == 5) { if (i == 5) {
break; break;
} }
@@ -171,8 +171,8 @@ TEST_CASE("loop break", "[script][loop]") {
TEST_CASE("loop continue", "[script][loop]") { TEST_CASE("loop continue", "[script][loop]") {
State state; State state;
const char* script = R"( const char* script = R"(
number sum = 0; var sum = 0;
for (number i = 0; i < 5; i = i + 1) { for (var i = 0; i < 5; i = i + 1) {
if (i == 2) { if (i == 2) {
continue; continue;
} }
@@ -190,8 +190,8 @@ TEST_CASE("loop continue", "[script][loop]") {
TEST_CASE("while break and continue", "[script][loop]") { TEST_CASE("while break and continue", "[script][loop]") {
State state; State state;
const char* script = R"( const char* script = R"(
number i = 0; var i = 0;
number sum = 0; var sum = 0;
while (i < 10) { while (i < 10) {
i = i + 1; i = i + 1;
if (i == 3) { if (i == 3) {