Support var
This commit is contained in:
40
README.md
40
README.md
@@ -7,7 +7,7 @@
|
|||||||
- **0-based 索引**:数组和列表从 0 开始索引
|
- **0-based 索引**:数组和列表从 0 开始索引
|
||||||
- **明确的类型系统**:区分 list 和 map
|
- **明确的类型系统**:区分 list 和 map
|
||||||
- **类支持**:支持 class 定义,包含字段和方法
|
- **类支持**:支持 class 定义,包含字段和方法
|
||||||
- **静态类型声明**:变量需要类型声明(number, string, bool, list, map)
|
- **类型推断**:使用 `var` 关键字自动推断变量类型,也支持显式类型注解
|
||||||
- **类 Lua 的 API**:提供简单的嵌入式 API
|
- **类 Lua 的 API**:提供简单的嵌入式 API
|
||||||
|
|
||||||
## 语法示例
|
## 语法示例
|
||||||
@@ -15,15 +15,21 @@
|
|||||||
### 基本类型
|
### 基本类型
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
number x = 10;
|
// 类型自动推断
|
||||||
string name = "Alice";
|
var x = 10;
|
||||||
bool flag = true;
|
var name = "Alice";
|
||||||
|
var flag = true;
|
||||||
|
|
||||||
|
// 显式类型声明
|
||||||
|
var y : number = 3.14;
|
||||||
|
var title : string = "Developer";
|
||||||
|
var active : bool = false;
|
||||||
```
|
```
|
||||||
|
|
||||||
### List(0-indexed)
|
### List(0-indexed)
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
list numbers = [10, 20, 30];
|
var numbers = [10, 20, 30]; // 自动推断为 list
|
||||||
print(numbers[0]); // 输出: 10
|
print(numbers[0]); // 输出: 10
|
||||||
numbers[1] = 99;
|
numbers[1] = 99;
|
||||||
```
|
```
|
||||||
@@ -31,7 +37,7 @@ numbers[1] = 99;
|
|||||||
### Map
|
### Map
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
map person = {"name": "Bob", "age": "25"};
|
var person = {"name": "Bob", "age": "25"}; // 自动推断为 map
|
||||||
print(person["name"]); // 输出: Bob
|
print(person["name"]); // 输出: Bob
|
||||||
person["city"] = "New York";
|
person["city"] = "New York";
|
||||||
```
|
```
|
||||||
@@ -43,15 +49,15 @@ func add(number a, number b) -> number {
|
|||||||
return a + b;
|
return a + b;
|
||||||
}
|
}
|
||||||
|
|
||||||
number result = add(10, 20);
|
var result = add(10, 20); // 自动推断返回值类型
|
||||||
```
|
```
|
||||||
|
|
||||||
### 类
|
### 类
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
class Person {
|
class Person {
|
||||||
number age;
|
var age : number; // 类成员必须显式声明类型
|
||||||
string name;
|
var name : string;
|
||||||
|
|
||||||
func sayHi() -> string {
|
func sayHi() -> string {
|
||||||
print(name, "says: I'm", age, "years old");
|
print(name, "says: I'm", age, "years old");
|
||||||
@@ -59,7 +65,7 @@ class Person {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Person p;
|
var p : Person; // 实例化类
|
||||||
p.age = 10;
|
p.age = 10;
|
||||||
p.name = "Peter";
|
p.name = "Peter";
|
||||||
p.sayHi();
|
p.sayHi();
|
||||||
@@ -76,14 +82,14 @@ if (x > 10) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// while loop
|
// while loop
|
||||||
number i = 0;
|
var i = 0;
|
||||||
while (i < 5) {
|
while (i < 5) {
|
||||||
print("Count:", i);
|
print("Count:", i);
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// for loop
|
// for loop
|
||||||
for (number j = 0; j < 3; j = j + 1) {
|
for (var j = 0; j < 3; j = j + 1) {
|
||||||
print("For loop:", j);
|
print("For loop:", j);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -117,8 +123,8 @@ int main() {
|
|||||||
// 执行脚本字符串
|
// 执行脚本字符串
|
||||||
const char* script = R"(
|
const char* script = R"(
|
||||||
class Person {
|
class Person {
|
||||||
number age;
|
var age : number;
|
||||||
string name;
|
var name : string;
|
||||||
|
|
||||||
func sayHi() -> string {
|
func sayHi() -> string {
|
||||||
print(name, "says: I'm", age, "years old");
|
print(name, "says: I'm", age, "years old");
|
||||||
@@ -126,7 +132,7 @@ int main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Person p;
|
var p : Person;
|
||||||
p.age = 10;
|
p.age = 10;
|
||||||
p.name = "Peter";
|
p.name = "Peter";
|
||||||
p.sayHi();
|
p.sayHi();
|
||||||
@@ -203,10 +209,10 @@ camellya/
|
|||||||
|------|-----|----------|
|
|------|-----|----------|
|
||||||
| 索引起始 | 1 | 0 |
|
| 索引起始 | 1 | 0 |
|
||||||
| Table | 统一的 table | 区分 list 和 map |
|
| Table | 统一的 table | 区分 list 和 map |
|
||||||
| 类型 | 动态 | 静态声明 |
|
| 类型 | 动态 | 类型推断 + 可选类型注解 |
|
||||||
| 类 | 通过 metatable | 原生支持 |
|
| 类 | 通过 metatable | 原生支持 |
|
||||||
| 语法 | `function` | `func` |
|
| 语法 | `function` | `func` |
|
||||||
| 类型注解 | 无 | 必须声明 |
|
| 变量声明 | 无需声明 | `var` 关键字 |
|
||||||
|
|
||||||
## 许可证
|
## 许可证
|
||||||
|
|
||||||
|
|||||||
12
example.chun
12
example.chun
@@ -2,8 +2,8 @@
|
|||||||
// This demonstrates the Person class from the specification
|
// This demonstrates the Person class from the specification
|
||||||
|
|
||||||
class Person {
|
class Person {
|
||||||
number age;
|
var age : number;
|
||||||
string name;
|
var name : string;
|
||||||
|
|
||||||
func sayHi() -> string {
|
func sayHi() -> string {
|
||||||
print(name, "says: I'm", age, "years old");
|
print(name, "says: I'm", age, "years old");
|
||||||
@@ -18,7 +18,7 @@ class Person {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create an instance
|
// Create an instance
|
||||||
Person p;
|
var p : Person;
|
||||||
p.age = 10;
|
p.age = 10;
|
||||||
p.name = "Peter";
|
p.name = "Peter";
|
||||||
|
|
||||||
@@ -29,12 +29,12 @@ p.sayHi();
|
|||||||
|
|
||||||
// Test lists (0-indexed)
|
// Test lists (0-indexed)
|
||||||
print("\n=== List Demo ===");
|
print("\n=== List Demo ===");
|
||||||
list numbers = [1, 2, 3, 4, 5];
|
var numbers = [1, 2, 3, 4, 5];
|
||||||
print("List:", numbers);
|
print("List:", numbers);
|
||||||
print("First element (index 0):", numbers[0]);
|
print("First element (index 0):", numbers[0]);
|
||||||
print("测试 element (index 2):", numbers[2]);
|
print("测试 element (index 2):", numbers[2]);
|
||||||
|
|
||||||
for(number i = 0; i < len(numbers); i = i + 1) {
|
for(var i = 0; i < len(numbers); i = i + 1) {
|
||||||
print("List element", numbers[i]);
|
print("List element", numbers[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ while(true) {
|
|||||||
}
|
}
|
||||||
// Test maps
|
// Test maps
|
||||||
print("\n=== Map Demo ===");
|
print("\n=== Map Demo ===");
|
||||||
map config = {"host": "localhost", "port": "8080"};
|
var config = {"host": "localhost", "port": "8080"};
|
||||||
print("Config:", config);
|
print("Config:", config);
|
||||||
print("Host:", config["host"]);
|
print("Host:", config["host"]);
|
||||||
|
|
||||||
|
|||||||
@@ -133,12 +133,13 @@ struct ExprStmt : public Stmt {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct VarDecl : public Stmt {
|
struct VarDecl : public Stmt {
|
||||||
std::string type_name;
|
std::string type_name; // Can be empty if type inference is needed
|
||||||
std::string name;
|
std::string name;
|
||||||
ExprPtr initializer;
|
ExprPtr initializer;
|
||||||
|
bool infer_type; // true if type should be inferred from initializer
|
||||||
|
|
||||||
VarDecl(std::string type_name, std::string name, ExprPtr initializer = nullptr)
|
VarDecl(std::string type_name, std::string name, ExprPtr initializer = nullptr, bool infer_type = false)
|
||||||
: type_name(std::move(type_name)), name(std::move(name)), initializer(std::move(initializer)) {}
|
: type_name(std::move(type_name)), name(std::move(name)), initializer(std::move(initializer)), infer_type(infer_type) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BlockStmt : public Stmt {
|
struct BlockStmt : public Stmt {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user