From 0833a2229b0dec19897ef4d238aa64fa23445233 Mon Sep 17 00:00:00 2001 From: zekexiao Date: Mon, 19 Jan 2026 22:07:25 +0800 Subject: [PATCH] Support var --- README.md | 40 +++++++++++++++++++++++----------------- example.chun | 12 ++++++------ src/ast.h | 7 ++++--- src/interpreter.cpp | 16 ++++++++++++++++ src/parser.cpp | 43 +++++++++++++++++++++++++++++-------------- tests/test_basic.cpp | 30 +++++++++++++++--------------- 6 files changed, 93 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index fc22979..54ecfce 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ - **0-based 索引**:数组和列表从 0 开始索引 - **明确的类型系统**:区分 list 和 map - **类支持**:支持 class 定义,包含字段和方法 -- **静态类型声明**:变量需要类型声明(number, string, bool, list, map) +- **类型推断**:使用 `var` 关键字自动推断变量类型,也支持显式类型注解 - **类 Lua 的 API**:提供简单的嵌入式 API ## 语法示例 @@ -15,15 +15,21 @@ ### 基本类型 ```javascript -number x = 10; -string name = "Alice"; -bool flag = true; +// 类型自动推断 +var x = 10; +var name = "Alice"; +var flag = true; + +// 显式类型声明 +var y : number = 3.14; +var title : string = "Developer"; +var active : bool = false; ``` ### List(0-indexed) ```javascript -list numbers = [10, 20, 30]; +var numbers = [10, 20, 30]; // 自动推断为 list print(numbers[0]); // 输出: 10 numbers[1] = 99; ``` @@ -31,7 +37,7 @@ numbers[1] = 99; ### Map ```javascript -map person = {"name": "Bob", "age": "25"}; +var person = {"name": "Bob", "age": "25"}; // 自动推断为 map print(person["name"]); // 输出: Bob person["city"] = "New York"; ``` @@ -43,15 +49,15 @@ func add(number a, number b) -> number { return a + b; } -number result = add(10, 20); +var result = add(10, 20); // 自动推断返回值类型 ``` ### 类 ```javascript class Person { - number age; - string name; + var age : number; // 类成员必须显式声明类型 + var name : string; func sayHi() -> string { print(name, "says: I'm", age, "years old"); @@ -59,7 +65,7 @@ class Person { } } -Person p; +var p : Person; // 实例化类 p.age = 10; p.name = "Peter"; p.sayHi(); @@ -76,14 +82,14 @@ if (x > 10) { } // while loop -number i = 0; +var i = 0; while (i < 5) { print("Count:", i); i = i + 1; } // for loop -for (number j = 0; j < 3; j = j + 1) { +for (var j = 0; j < 3; j = j + 1) { print("For loop:", j); } ``` @@ -117,8 +123,8 @@ int main() { // 执行脚本字符串 const char* script = R"( class Person { - number age; - string name; + var age : number; + var name : string; func sayHi() -> string { print(name, "says: I'm", age, "years old"); @@ -126,7 +132,7 @@ int main() { } } - Person p; + var p : Person; p.age = 10; p.name = "Peter"; p.sayHi(); @@ -203,10 +209,10 @@ camellya/ |------|-----|----------| | 索引起始 | 1 | 0 | | Table | 统一的 table | 区分 list 和 map | -| 类型 | 动态 | 静态声明 | +| 类型 | 动态 | 类型推断 + 可选类型注解 | | 类 | 通过 metatable | 原生支持 | | 语法 | `function` | `func` | -| 类型注解 | 无 | 必须声明 | +| 变量声明 | 无需声明 | `var` 关键字 | ## 许可证 diff --git a/example.chun b/example.chun index c803a90..cb01ede 100644 --- a/example.chun +++ b/example.chun @@ -2,8 +2,8 @@ // This demonstrates the Person class from the specification class Person { - number age; - string name; + var age : number; + var name : string; func sayHi() -> string { print(name, "says: I'm", age, "years old"); @@ -18,7 +18,7 @@ class Person { } // Create an instance -Person p; +var p : Person; p.age = 10; p.name = "Peter"; @@ -29,12 +29,12 @@ p.sayHi(); // Test lists (0-indexed) print("\n=== List Demo ==="); -list numbers = [1, 2, 3, 4, 5]; +var numbers = [1, 2, 3, 4, 5]; print("List:", numbers); print("First element (index 0):", numbers[0]); 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]); } @@ -44,7 +44,7 @@ while(true) { } // Test maps print("\n=== Map Demo ==="); -map config = {"host": "localhost", "port": "8080"}; +var config = {"host": "localhost", "port": "8080"}; print("Config:", config); print("Host:", config["host"]); diff --git a/src/ast.h b/src/ast.h index 80b67fe..2b4f673 100644 --- a/src/ast.h +++ b/src/ast.h @@ -133,12 +133,13 @@ struct ExprStmt : 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; ExprPtr initializer; + bool infer_type; // true if type should be inferred from 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)) {} + 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)), infer_type(infer_type) {} }; struct BlockStmt : public Stmt { diff --git a/src/interpreter.cpp b/src/interpreter.cpp index 3a117b2..bd07086 100644 --- a/src/interpreter.cpp +++ b/src/interpreter.cpp @@ -367,7 +367,23 @@ void Interpreter::exec_var_decl(const VarDecl& stmt) { if (stmt.initializer) { value = evaluate(*stmt.initializer); + + // If type inference is needed, we use the value directly + if (stmt.infer_type) { + // Type is automatically inferred from the initializer value + environment->define(stmt.name, value); + return; + } + + // If explicit type is provided, verify the value matches + // (This is a simple check, could be more sophisticated) + if (!stmt.type_name.empty()) { + // Just use the value; type checking could be added here + environment->define(stmt.name, value); + return; + } } else { + // No initializer, use default value based on type value = create_default_value(stmt.type_name); } diff --git a/src/parser.cpp b/src/parser.cpp index b7c74b8..54d94ef 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -94,14 +94,9 @@ StmtPtr Parser::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(); - } + // Handle var keyword for variable declaration + if (match({TokenType::VAR})) { + return var_declaration(); } return statement(); @@ -118,8 +113,10 @@ StmtPtr Parser::class_declaration() { if (check(TokenType::FUNC)) { advance(); members.push_back(function_declaration()); - } else { + } else if (match({TokenType::VAR})) { 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() { - Token type_token = advance(); - std::string type_name = type_token.lexeme; - Token name = consume(TokenType::IDENTIFIER, "Expected variable name."); + // var keyword has already been consumed + Token name = consume(TokenType::IDENTIFIER, "Expected variable name after 'var'."); + std::string type_name; + bool infer_type = false; 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(); + infer_type = true; + type_name = ""; // Will be inferred at runtime } + consume(TokenType::SEMICOLON, "Expected ';' after variable declaration."); - return std::make_unique(type_name, name.lexeme, std::move(initializer)); + return std::make_unique(type_name, name.lexeme, std::move(initializer), infer_type); } StmtPtr Parser::statement() { diff --git a/tests/test_basic.cpp b/tests/test_basic.cpp index b2f0f14..facf156 100644 --- a/tests/test_basic.cpp +++ b/tests/test_basic.cpp @@ -9,9 +9,9 @@ using namespace camellya; TEST_CASE("basic arithmetic", "[script]") { State state; const char* script = R"( - number x = 10; - number y = 20; - number z = x + y; + var x = 10; + var y = 20; + var z = x + y; )"; REQUIRE(state.do_string(script)); @@ -31,7 +31,7 @@ TEST_CASE("basic function", "[script][func]") { func add(number x, number y) -> number { return x + y; } - number z = add(10, 20); + var z = add(10, 20); )"; REQUIRE(state.do_string(script)); @@ -48,7 +48,7 @@ TEST_CASE("basic function", "[script][func]") { TEST_CASE("list indexing is 0-based", "[list]") { State state; const char* script = R"( - list numbers = [10, 20, 30]; + var numbers = [10, 20, 30]; )"; REQUIRE(state.do_string(script)); @@ -78,8 +78,8 @@ TEST_CASE("class init is called on declaration", "[class][init]") { State state; const char* script = R"( class Person { - number age; - string name; + var age : number; + var name : string; func init() -> nil { age = 18; @@ -91,8 +91,8 @@ TEST_CASE("class init is called on declaration", "[class][init]") { } } - Person p; - number a = p.getAge(); + var p : Person; + var a = p.getAge(); )"; 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]") { State state; const char* script = R"( - number sum = 0; - for (number i = 0; i < 10; i = i + 1) { + var sum = 0; + for (var i = 0; i < 10; i = i + 1) { if (i == 5) { break; } @@ -171,8 +171,8 @@ TEST_CASE("loop break", "[script][loop]") { TEST_CASE("loop continue", "[script][loop]") { State state; const char* script = R"( - number sum = 0; - for (number i = 0; i < 5; i = i + 1) { + var sum = 0; + for (var i = 0; i < 5; i = i + 1) { if (i == 2) { continue; } @@ -190,8 +190,8 @@ TEST_CASE("loop continue", "[script][loop]") { TEST_CASE("while break and continue", "[script][loop]") { State state; const char* script = R"( - number i = 0; - number sum = 0; + var i = 0; + var sum = 0; while (i < 10) { i = i + 1; if (i == 3) {