Tea-功能细节一览
本文读者应该首先会一门C风格的语言(如JavaScript、C++、Java 和 C#)
一、概述
Tea 语言代码最终被编译为机器码(汇编语言)执行,不需要任何虚拟机。这个功能决定了 Tea 语言语法的基本特色:
- 强类型。 所有变量都应有明确的类型。同时语言提供强大的类型推导功能,以减少编码复杂度。
- 支持面向对象。 这为语言增加很多复杂度,但对于本来学过面向对象的读者,仍会觉得很轻松。
二、hello world
hello world 代码如下:
void main(){
system.Console.writeLine("hello world");
}
其中,main
是一个函数定义,其返回类型是 void
,其参数是空的。任何程序都是从 main 函数开始执行的。
其中,system
是提供底层 API 支持的系统模块。
代码中的所有点都可以省略,如:
import System;
void main(){
Console.writeLine("hello world");
}
更可以写成:
import System.Console;
void main(){
writeLine("hello world");
}
三、 基本开发模型
1. 编译和执行
使用如下命令,可以直接编译源码
tea hello.tea
Windows 下,将生成一个独立的 hello.exe 文件,复制此文件到任何电脑都可以直接运行。
2. 开发一个模块
实际的项目不会只有一个源码。需要通过模块的方式组织源码:
src1.tea 源码1
src2.tea 源码2
...
hello.teamod 存储模块配置
使用如下命令,可以直接编译模块
tea hello.teamod
Windows 下,将根据配置生成一个 hello.dll 库文件。
3. 开发一个项目
将多个模块组织在一起,可以成为一个项目。 如开发一个扫雷游戏时,目录结构为:
root/ 存储整个项目
gui/ GUI 模块(第三方库)
gui,teamod
saolei/ 扫雷游戏核心模块
src.tea 实际的源码
saolei.teamode
saolei.teaproj
使用如下命令,可以直接编译项目
tea saolei.teaprj
Windows 下,将根据配置生成 saolei.exe, gui.dll 文件。
小结
要开发一个完整的产品, 需要新建一个项目(Project), 一个项目中由多个模块(Module)组成, 一个模块由多个源文件(SourceUnit)组成。
四、语句
1. 变量定义语句
int i; // 创建一个类型为 int 的变量 i。
int a = 1, b = 2; // 同时创建 2 个变量。
var k = 1; // 让编译器自动推导 k 的类型。
2. if 语句
if (a) {}
if (a) {} else {}
if (a) {} else if (b) else {}
3. for 语句
for (int i = 0; i < 100; i++) {}
for (int i in arr) {} // 快速遍历一个数组
for (int i = 0 to 100) {} // 等效于: i = 0 ; i < 100 ; i++
for (int i = 100 to 0; i--) {}
4. while 语句、 until 语句和 loop 语句
while (a) {}
do {} while(a);
until (a) {} // 同 while(!a)
do {} until(a);
loop {} // 死循环
loop(100) {} // 循环100次
5. switch 语句
每个 case 之间不会互相贯穿。
switch(val) {
case 1:
b = 1;
case 2:
goto case 1; // 使用 goto 强制跨 case 。
case 3, 4: // 允许多个值使用逗号分开,而不是写多个 case 。
b = 2;
case else: // 匹配剩余情况。注意没有 default 关键字。
b = 4;
}
switch { // 等效于多个 if else if 。
case a > 1:
csae a < 0 && a > 5:
}
6. try 语句
一个 try 语句最多只能有一个 catch 段和一个 finnaly 段。
try {} catch {}
try {} catch(Exception e) {} // 只接受指定类型的错误。
try {} finally {}
try {} catch {} finally {}
try {} catch(Exception e) {} finally {}
throw "Error"; // 所有的异常都是 Exception 对象,如果 throw 的参数是一个字符串,会自动包装为 Exception 对象。
7. 标签语句和跳转语句
label:
goto label; // 跳转到 label 。
return 1; // 返回值。
yield 2: // 在迭代器中返回值。
8 with 语句
with 语句可以保证资源被正确释放。
with (SqlConnection sql = MySql.open("连接字符串")){ // 打开一个数据库连接。
sql.execute("SQL 语句");
} // 在代码执行完后,系统自动调用 sql.close() 关闭数据库连接资源。
9. lock 语句
lock 语句控制一段代码不会在多个线程内同时执行。
lock { } // lock 里面的代码不会同时执行。
lock (myLockObj) {} // 使用自定义的线程锁标记。
10. runonce 语句
runonce 语句控制一段代码,并确保它在程序声明周期内执行一次。
runonce { /* 一些初始化代码 */ }
11. checked 语句和 unchecked 语句
unchecked 语句内所有的溢出异常都被忽略。checked 相反。
unchecked { /* 不测试溢出 */ }
checked { /* 测试溢出 */ }
12. 调试语句
> "hello world"; // 相当于 Debugger.trace("hello world");
: a > 0; // 相当于 Debugger.assert(a > 3);
四、 表达式
1. 常量
1.1 数字
int a = 0
int b = 0xFFFFFF
double c = -1.2
1.2 布尔和空
bool a = true
bool b = false
object c = null
1.3. 字符串
string a = '' // 单引号是无任何转义的字符串。
string b = "" // 双引号是支持\转义的字符串。
string c = ‘’‘aaa''' // 三引号:长字符串。
int a = 1;
string s = `my value is $a`; // 输出 my value is 1
1.4 列表和字典
List<int> a = [1, 2, 3] // List
Dict<int> b = {aa:1, bb:2} // Dict
1.5 函数
Func<bool, int> = x -> x > 0;
Func<int int, int> = (x, y) -> {
return x + y;
};
2. 操作符
2.1 四则运算
+, -, *, /, %(取余), ^(次方),++, --
2.2 比较运算
==, !=, <=, >=, <, >
2.3 比较运算
==, !=, <=, >=, <, >
2.4 逻辑运算
&&, ||, !
2.5 赋值运算
=, +=, -=, *=, /=, %=, =>, :=, &= |=
其中,=> 叫反向赋值操作符 a => b; // 等效于 b = a;
2.6 流程控制
true ? a : b
a = value | "a"; // a = value == null ? "a" : value;
a = value & "a"; // a = value != null ? "a" : value;
2.7 位运算
^^、^&、^|、^<、^>、^~
2.8 区间操作符
List<int> a = 1 ~ 6; // 等效于 a = [1, 2, 3, 4, 5]
a[0] // 访问索引 0 的值。
a[0 ~ 4] // 访问索引 0 到 4 的子数组。
a[0 ~ ] // 访问索引 0 到最后的字数组。
2.9 成员访问操作符
a.b
a..b()..c()..d() // 链式点操作,相当于 a.b();a.c();a.d();return a;
2.10 类型操作符
a is string // 判断 a 是否是 String
a as string // 将 a 转为 String
2.11 or 操作符
fn() or null // 相当于 `try{return fn()}catch{return null}`
2.12 await 操作符和 async 操作符
var s = await fn(); // 等待异步执行函数。
var a = async load(); // 异步执行函数。
2.13 指针操作
int* a = &b;
*a++ = 6;
3. 函数调用和创建实例
fn1(4); // 基本调用方式
fn2(p:4); // 命名参数
fn3(ref p); // 引用参数
fn(out p); // 输出参数
MtObj a = new MtObj();
string[] s = new string[5];
var a = new MyClass (a: 1, b:2);
4. 特殊变量
1. this & base
this 表示当前类 base 表示基类
2. 魔法变量
代码中可以直接使用一些魔法变量,这些变量的值由编译器来提供。
@outputPath 当前生成的文件名 @version 当前编译的版本号 @line 当前行号 @sourcePath 当前源码位置 @global 全局对象 @self 当前类 @callee 当前函数
void fn(){
fn(); // 递归调用 fn
}
void fn(){
@callee(); // 同上,只是用 @callee 代替 fn
}
@index 当前循环的次数 @variables 当前块的所有变量集合。 @arguments 当前函数的实参集合。
五、函数和类
1. 函数定义
void fn(int value){}
func fn(value){} // 弱类型函数定义
参数默认值
void fn(int value = 2){}
void fn(int value?){} // 即 value = 0
可变参数
void printf(string format, params string[] args) {
}
// 调用时, printf 第2个参数可以是任意个。
输出参数
void fn(ref int value){}
void fn(out int value){}
// 输出参数表示这个参数是返回值,不是输入值。
2. 内嵌函数
void fn(){
void sub(){
}
sub(); // 内嵌函数
}
3. 类定义
class Cat : Animal {
// 这是一个方法。语法同函数定义。
void say(){
}
// 这是一个字段。语法同变量定义。
int _prop = 1;
// 这是一个属性。
int prop {
get {
return _prop;
}
set {
_prop = value;
}
}
}
静态成员
在成员前加 static
表示静态成员。静态方法必须通过类名访问。
访问修饰
public: 全部可以访问
internal: 同一个模块可以访问
protected: 当前类及子类可以访问
private: 当前类内部访问
Tea 语言默认设置全部成员为 public,但设置名字前有 _ 的成员为 private 。
字段修饰符
const 指示字段是常量
readonly 指示字段对外只读
final 指示字段全部只读
volatile 指示字段为可变的
虚函数和抽象函数
virtual int fn(){ return 3};
abstract int gn();
override int fn(){return 5;};
3. 其它类型定义
除了 class, 还可以声明以下成员:
namespace: 如果一个类全部是 static 成员,应该改成 namespace 。
interface: 如果一个类全部是 abstract 成员,应该改成 interface 。
struct: 代表值类型的类。
enum:代表枚举类型的类。
4. 泛型
class List<T> {}
5. 注解
@inline void fn(){}
6. 内置类型
内置基本数据类型是所有类型的基础,其它类型都是这些类型的组合。
常用: Char Bool
整数: SByte Short Int Long
Byte UShort UInt ULong
小数: Float Double
引用: Void Object String
为了方便使用这些内置类型,Tea 提供了一些关键字来代表这些类型。
char
bool
byte
short
int
long
float
double
void
object
string
7. 数组
类型后加 [] 即该类型对应数组类型。、
int[] // int 数组,其实是 Array<int> 简写。
8. 指针
int a;
Ptr<int> pa = &a; // 获取 a 的指针。
9. 扩展类成员
class Array{
}
extend Array {
void fff(){}
}
// 所有 Array 类都可以访问 fff 函数
10. 反射
Type s = typeof(String);
int a = s.getProperty("length").call("aaa") as int;
六、懒人模式
1. 自动追加分号
每个独立语句都允许省略分号,但是这会引起一些歧义。因此 Tea 采用的策略是尽量认为分号是没有省略的。如以下代码,编译器不会自动添加分号。
return // 分号不会自动添加。因此空 return 需要用户强制添加分号。
fn()
2. 自动追加括号
大部分语句块在只有一行语句时,可以省略大括号。 大部语句均允许省略小括号。 但是,请不要同时省略大括号和小括号,虽然编译器对此不作限制。
七、文档注释和预处理器
1. 文档注释
/// 表示后面是文档注释,文档注释会提取为 API 文档
/// 输出值
/// @param a 要输出的内容
/// @return 输出的个数
int print(string s){
}
2. 预处理器
所有源码内的预处理符只对当前文件有效,项目文件里的预处理符对整个项目有效。
4.1 宏
宏主要用于编译代码时根据不同的平台使用不同的代码。
#define WIN32
#undef WIN32
#if WIN32
#elif LINUX
#else
#endif
4.2 错误和警告控制
#error 强制编译器显示一个错误
#warning 强制编译器显示一个警告
4.3 编译器宏
编译器宏是预留的用于告诉编译器一些编译信息的宏。
#pragma 宏名字 宏参数
4.3.1 警告信息控制
#pragma warning disable // 通知编译器忽略所有警告
#pragma warning disable 1023 // 通知编译器忽略此警告
#pragma warning enable 1023 // 通知编译器重新提示此警告
#pragma error 1 // 通知编译器将警告1作为错误处理
4.3.2 检验码
检验码用于标识代码文件。具有相同校验码的代码被认为是同一份代码。 如果用户没有指定检验码,编译器会自动根据文件生成。
#pragma checksum 检验码 // 设置当前文件的检验码
#pragma checksum 检验码 文件名 // 设置其他文件的检验码
4.4 代码行
代码行用于告诉编译器源码的实际位置。此命令主要用于自动生成的代码。
#line 1 // 设置当前文件的行号
#line 1 文件位置 // 设置当前文件的行号及映射的文件
#line hidden // 设置隐藏当前行
#line default // 设置恢复默认行
4.5 代码区域
代码区域和代码无关,它的作用是告诉代码编辑器产生一个折叠块。
#region 区域名 // 其中区域名可以省略
#endregion 区域名 // 其中区域名可以省略
4.6 代码标记宏
代码标记宏仅用于代码注解。
#todo 这是一个未完成的代码
#tofix 这是一个需要修复的代码
#mark 标记代码(比如读源码时,可以标注一些位置)
4.7 代码宏
代码宏主要用于让编译器自动复制代码。
#sippet 代码片段名字
// 这里是代码片段
#endsippet
#usesippet 代码片段名字 // 会自动填充定义的代码宏内的代码片段。
八、 运行库支持
[本节内容暂不提供]