Support var
This commit is contained in:
40
README.md
40
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` 关键字 |
|
||||
|
||||
## 许可证
|
||||
|
||||
|
||||
12
example.chun
12
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"]);
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user