# silang **Repository Path**: treert/silang ## Basic Information - **Project Name**: silang - **Description**: git://git.coding.net/jadedrip/Silang.git - **Primary Language**: C++ - **License**: BSD-3-Clause - **Default Branch**: om_learn - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2018-03-03 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README Si 语言 ====== A compiler for silang which is a new language. Compiler = Flex + Bison + llvm 目标 & 简介 ============== Si 是拍脑袋的产物,试验性的产品,现在仅仅出于最初级阶段。随时会变化。 糅合了 go, Java, C++, Lua 等语言中我觉得“爽”的部分,作为个人兴趣爱好实现,不必太过期待。 它应该有以下特点: * 它应该可以和 c 较好的融合 也就是方便的调用 c 语言编译出来的库,并且用它编译的库,也可以被 c 语言较好的调用。 *在后端采用 LLVM 的情况下,这个目标似乎不难实现。站在巨人的肩上,也可能让它更好的发展。* * 它应该可以和 c++ 较好的融合 * 语法上会比较接近 Java,抛弃所有 C/C++ 中的复杂部分,但引入一些高级特性。 * 编译型语言,编译的代码应该有较高的效率。所有的类型都是“可决”(在编译期完全确定)的。 * 简洁&优雅,表现力强,行尾推荐不加 ; 但不强制 * 原生的支持委托(或者说函数对象?)和闭包 * 原生的支持协程,多线程 * 原生支持 utf-8, unicode * 支持国际化 * 可选的 GC * 谨慎的支持操作符重载 * 使用一个简化的异常系统来处理错误。 * 支持模板 设计思想: * 尽量避免对象的复制,凡是复制,都需要明确使用 clone 基础语法 ============= 编程风格 ------------ * 变量使用小写开头,驼峰格式 * 类使用大写开头,驼峰格式(内部类型除外) * 行尾不需要分号,而分号可以替代空大括号 {} 变量类型 ------------ 支持的内置类型: 名称 描述 符号 位宽 ----------------- boolean 无 1 bit byte 无 8 bit char 有 8 bit short 有 16 bit ushort 无 16 bit int 有 32 bit uint 无 32 bit long 有 64 bit ulong 无 64 bit float 有 32 bit double 有 64 bit 注:内部类型在函数中使用传值方式传递,其他类型传引用 其他类型: class interface singleton // 单例 TRUE 真类型(用于推导) FALSE 假类型(用于推导) 语言级别支持的内部类 Tuple 元组 String 字符串 Any 可变类型 Func 函数对象 Delay 延迟对象 Delegate 委托(保留) 使用 const 来定义常量: const pi=3.1415 // 注意:常量不需要类型定义 以及枚举: enum MyEnum{ red=0, blue=2 } // 枚举和 int 可以无缝转换 枚举使用时为: MyEnum.red 变量 ------------ Si 语言中除了内置类型(整数和浮点),其他类型都保存在指针中。 定义变量可以用 var 来自动推导,用右值推断类型,甚至允许先定义,使用时再推导。 var i=10 var a if(..){ a = 30.0f } 不过变量的类型是确定不变的。因此以下代码是错误的 var name = "Hei" // 这里 name 自动成为 String 类型 name = 15 // 而这里会出错,因为 name 不能改变类型 另外,内部变量有初始值。 数组 ------------ 除 singleton 外都可以定义成数组,数组有**固定长度**,有个默认打开的编译选项,会让越界访问抛出异常。 数组下标从0开始 int[] a=[ 0, 1 ] // 数组可以直接初始化 var array2=int[10] // 初始化一个空的数组 int v=array2[1] // 数组取值 // 通过内置的 size 成员函数来获取数组的长度 var len = a.size() 当数组作为函数参数时,可以指定大小,也可以不指定: func myFunc( int[20] array ){ ... } func myFunc( int[] array ){ ... } 另外数组也支持切片 int[] s=arr[startIndex:length] 需要注意的是,切片是引用,因此如果源改变了,切片同样会改。 内部类 Any ----------- Any类型可以保存任何值,并且保存类型信息。 Any 类型允许在**运行期**保存任何对象,可以判断它内部是什么类型。 Any i=20.0f // 保存一个 float 然后你可以这样用 assert( i is Any ) when(i){ // 用 when 关键字进行运行期类型判断 int : print(i) // 在这个分支,i 被自动转换为 int 类型 float : { // do float } default : { printf("unknown Type") } } Type type=i.type // 获取类型(参见**反射**) float x=i // 或者 (float)i 强制获取 float 类型的值 但是,如果取值时,类型不一致,并且不能进行隐含转换,将抛出一个异常,这个过程是**运行期**的。 判断类型时,如果用 is,就是编译期的,用 when 就是运行期的。 分支 ------------ if 采用 c 的语法, 增强 switch。 switch 语法如下,支持多种数据以及多种比较,只要是测试相等就可以。冒号后如果是多行,需要大括号。 switch vegetable { "celery": vegetableComment = "Add some raisins and make ants on a log." "cucumber", "watercress": { vegetableComment = "That would make a good tea sandwich." comm : "多行需要大括号" } default: vegetableComment = "Everything tastes good in soup." 也可以使用范围 switch intValue { 1..10: v="1 to 10" 20..30: v="20 to 30" } 元组 -------------- Si 语言中支持元组,替代 C++ 中的 pair, tuple。Si 里的元组用圆括号括起来,必须从明确的值创建,并且创建后 **不可修改** 元组必须在创建时赋值,并且值不可更改。可以通过 := 来解构,或者使用[]取值,元组可以参与编译期运算。 var a=(1, "second", x) // 直接创建 print( a[0] ) // 取第一个值(注:[] 操作是编译期的) if( a[1] is int ) print("yes") // 通过索引取类型 for( var i : a ){ // 通过 for 循环,在编译期解开元组 print( i ) } var b0, b1 := a // 元组的自动解构,注意复制的是引用,变量数量可以比实际的少,但不能多。 int c0, float c1 := (10, 10.0) var c=(0) // 这不是元组,c是 int 类型 另外,元组的成员也可以命名: var tuple=( key=1, value=2 ) print( tuple.key ) 循环 -------------- 支持 while, for 循环,c 的语法。但不支持逗号多定义,多步进,不支持 do-while。 另外支持 for-each 形式 int[] arrays = {10, 20} for( var i : arrays ){ i = 30 // 将改变 arrays 的内容 } 作为一个语法糖,在循环内使用 free 语句释放迭代器,编译器会尝试调用容器的 remove 方法(如果有),将对象从容器中移除。remove 的返回值赋值给 i, 重新开始循环体 (continue) for( var i : list ){ free i } 特别的,如果列表里保存的是元组(比如 map ),可以使用自动解构的语法 for( var k,v : map ){ // 同样,注意复制的是引用,因此改变 v 会改变 map 内的值 map.remove(k) // 这里不能用 free 了 } 字符串 -------------- 在 Si 里的字符串类是个内部类型,String,字符串类内部维护字符串编码,一般情况下内部使用 UCS-4 存放 String eng="Hello world!" // 纯英文字符串,直接创建为 utf-8 编码 String str="中文" // 包含非 ascii, 使用 UCS-4 编码 ushort aChar=str[0] // a='中', 直接取出,为 UCS-4 编码 String str( bytes, Charsets.utf8) ) // 通过 bytes 指定编码来创建 str.bytes() // 获取 byte,不指定编码,会默认返回 utf-8 也支持多行字符串: string str="""Hello ^ World ^ ! """ print(str) 输出: Hello World ! **需要特别注意的是,多行字符串每行前后的空格将被忽略。** 想加入前后空格,需要使用 ^ 标记起始位置。 另外,字符串也支持切片。 String s=str[0:1] // 注意,这里不会复制内部字符串 字符串允许进行 + 操作,并且可以和整数、浮点数加,结果是字符串。 String s="" + 16 // 结果是字符串 "16" s=R("Hello", "zhCN“) // 从资源文件获取字符串 *未来支持字符串模板* **注意:字符串内容不能更改。** 如果需要可变字符串,使用另一个兼容类 StringBuffer。 操作符 ------------- 支持大部分 C++ 操作符,但是不支持前置 --, ++。 支持 >>> 运算符(无符号右移) 函数定义 ------------- 函数这样定义 func first( int a ) : int { return a } 调用时: var x = first( 10 ) 并且为了保证定义的清晰,如果省略返回值的定义,就是表示不返回。 参数允许可变参数,不过必须定义在函数的最后,也只允许一个可变参数. 另外允许多返回值,这时返回值会被包装为一个元组。 多返回值函数定义方式如下,返回值可以匿名也可以命名,命名的返回值在函数内可以直接作为变量操作. func second( int a, var b, int ... args ) : int retval, int val { // 这里 args 被视为数组 array if( args.size > 0 ) val = args[0] retval = a + b // 直接操作返回值 return // 可以省略 } 注意,返回值如果命名,那么在函数开始,就会调用无参数构造函数构造返回值,如果没有无参数构造函数, 编译会失败,可以使用 ? 定义为可空类型。 单行函数(语法糖) func add(int a, int b) = a + b 单号函数使用推导获取返回值,必定有返回值,因此也不需要 return。 return ========== * return 语句必须写在区块的最后,本区块后面不能有其他语句 * 如果函数返回值都是命名的,那么可以不带后面的返回参数,特别的,没填充的变量会以默认值返回 * 如果 return 后面带了返回参数,那么必须写全 * 函数最后的 return 允许省略 可变参数可以是同一个类型的,或者使用 var 让它成为可变模板函数 func varFunc( int a, var ... args ){ for( var i : args ){ // 这个 For 会在编译期被展开 run(i) } } 这个 for 循环会在编译时被展开成顺序的多个代码块。 函数可以使用返回元组的形式来返回多个变量,并且你可以用自动解构。 first, second := fun( 10, 20 ) 返回值如果有非命名参数,那么**必须**写在列表的前面 func second( int a, var b ) : int, float, int { return 0, 15.0, 2 } 传参 ================ Si 语言中,传入的参数(包括 int 等内部参数)都被视为指针,因此**它的内容**可以在函数中可以被更改。 比如: string old="Hello" cut2(old) print(old) // 输出 "Hell" 另外,如果参数可以为 null, 需要使用 ? 明确指出 func third( int? a ) : int? { if(&a){ return a+1; } return null; } 参数中可以使用 var 关键字,这时候它同样被视为模板函数。 返回值也可以使用 var,这时通过 return 来推断返回值 func myFunc( var a, int b ) : var ret{ return a+b // 这时推断返回值为 int 类型 } int x=myFunc( 10, 20 ) 函数调用 ----- 函数使用比较宽松的调用形式,可以是顺序的,比如: first, second = fun( 10, 20 ) 或者命名的形式: var v = fun( name="myname", value=20 ) 也可以2者组合 fun( 10, 20, name="myname" ) 但是有个约束,包括,顺序形式的必须在函数调用的开头。并且所有的参数(除非有默认参数)都必须充满。 下面的写法是非法的(除非第一个参数就是 name): var v= fun( name="myname", 10, 20 ) 函数对象、匿名函数 ------- Si 在语言级别支持函数对象、匿名函数 var add=func(int a, int b) : int{ return a+b } // 匿名函数 int a=add(10, 20) // 执行 如果没返回值,没有参数,都可以省略 var my1=func(int a){ ... } // 无返回值 var my2=func{ ... } // 无参数、无返回值的函数 var my3=func{ return 10 } // 返回值类型自动推导 func(int):float itIsAFunctor // 明确的类型 特别的,操作符 -> 后面可以省略 func, 另外,string 也支持 -> 操作符,并且在定义时就会被调用, 这个特性可以用来写“函数块” "块的注释"->{ int a=0 } 推荐这样写函数块,以便让 IDE 实现函数内的块缩进。 同时,匿名函数是闭包的: int myVal = 10; var x=func(int a){ return a+myVal; } // 注意这个 myVal 是引用,在匿名函数调用时取*当前*值 assert( x(20)==30 ); 上面的代码演示了简单闭包,不过要注意的是,并行的情况下,myVal 可能被更改、互斥,这时候使用闭包需要特别小心。 另外,匿名函数里包含的是对象引用,因此如果在匿名函数里修改 myVal 的值,当匿名函数被调用时,myVal 就会被更改。 这点也需要注意。 异常 ------- Si 支持的异常。 /// 语言内部异常,所有异常的基类 class Exception( string resource; // 字符串资源 ID,默认会直接使用类名 } // 定义一个异常 class IOException : Exception("IOException") // 定义一个新异常 class HttpException : IOException{ // resource="HttpException" int code; } // 如果函数会抛出异常,那么必须要加 throws 描述 func( int ) : int throws CodeException, HttpException { if( false ) throw HttpException{ code=404 } return 10 } try{ var a=func( 10 ) }catch( HttpException | IOException e ){ // catch 允许多个异常类型 print( e.message ) }finally{ // 最后会执行的代码 } // 简化的异常处理格式,对函数的异常直接处理 var i=func( 10 ) catch (IOException e){ String m = exception(e) } catch(Exception e){ print(e) } func(10) catch(Exception); //明确忽略这个异常, 注意:分号不能省略 如果函数会抛出异常,那么必须处理,或者放函数 throws 签名中再抛出,或者用 try (包括简化语法) 处理。 空指针 ---------- 某个默认打开的编译参数可以在运行时让空指针抛出 NullPointerException 异常,当然,这会略微的降低执行效率。 对于线程、协程,默认的处理方式就是输出错误日志,并退出线程。 ?: 操作符可以在指针值为空时提供默认值 var a = mayNullOrInt() ?: 10 类 ----------- 基础 ================ 每个文件(.sc)可以定义一个类。 si 中的类类似 Java。成员变量、方法只有**公开**和**包内**的,公开的可以被包外访问, 否则只允许同名包或者*子包*内的方法访问。 *包 org.first.second 是 org.first 的子包* 代码开始 /* File MyCls.sc 开始 * package org.jadedrip // 定义包 class MyCls { // 定义类 init(){ // 无参数的构造函数 } init(int a, int .value){ // 带参数的构造函数,参数前带.可以省略 this.value=value 的代码 } finalize{ // 析构函数始终是无参数的,不需要括号 } clone{ // 克隆函数 return MyCls(){ this.key = clone(key) this.value = clone(value); }; } int key = 1 // 创建时初始化(先于构造函数) int value // 创建时默认为0 func do_something(){ this.key++ // this 是自己 } protected: // 类中有且仅能有一个 protected 分割线,分割线上部的是公开的变量、方法,下部是 // 保护的,只有在同一个类、子类或继承类内可以访问 int? privateValue // 可为 null 的变量 } class MyDataClass( int a, int b ){ // 这里直接定义了主构造函数、内部变量为 a, b init(){ // 主构造函数不包含函数体,因此无参数构造函数仍然会被调用(赋值后) } } 继承&重载 =================== 继承和重载的概念被简化,类可以单一继承,并且**不允许**重载方法,当你需要一个可以重载的类、或者虚函数时,需要把它定义成函数对象。 class Base{ func cantOverload(){ // 普通函数不可以重载 } func( int v ) virtualFunction // 定义一个函数对象,可以实现纯虚函数的功能 var canOverload = func(int v){ // 以函数对象的语法定义函数,可以通过替换函数对象的方法达到重载的目的 print(v) virtualFunction(v) // 调用虚函数 } } class Second : Base{ // 类可以继承,但只能单继承 func cantOverload(){ // 这会是个全新函数 } virtualFunction=func(int v){ // 实例之 print(v); } canOverload = func(int v){ canOverload(v) // 这可以视为调用基类函数 } } 类的构造 ================= 对象构造使用构造函数的形式,括号不可省略。 var a=MyCls(10, 20) // 通过构造函数构造,参数表使用逗号分割,当然可以 var 推断 var c=MyCls(){ // 在构造时,后面直接接大括号,将在构造后,直接执行语句块 key=0 // 在构造函数执行后执行 val=20 } 对象不构造时,可以认为是一个指针: MyCls? a; // 这时 a 可以认为是指针,? 不可省略,表示对象被初始化为 null a.var = 10 // 编译错误,构造前赋值 对象的传递,都是浅copy,除非明确指明复制 MyCls a=clone(b) // clone 会调用复制函数进行深 copy。 对象可以被理解为都保存在智能指针中, 指针的赋值需要使用等号,并且指针不能进行任何运算,包括比较。 而操作符重载不能重载等号。 除了 &, = 等指针专用符号,其他一切符号,都是对对象的操作(操作符重载)。 if( &x ){ // 指针判断是否为存在 x=Cs() // 这里会改变 x 指向的对象 } if( x == null ){ // 注意这种写法并非判断x是否为空,而是内部对象和 null 比对(或重载的 == 方法) } var a = Cs() Cs b = a // b 指向 a b.val = 1 // 这里同时会改变 a 指向的对象值 Set 和 Get -------------- 类的成员变量支持 Set & Get 方法。 class MyClass{ int a set a( int nv ){ // set, a=x 时调用 print("New value:" + nv) a=nv } get a{ // 使用 var a=myClass.a 的代码时被调用 return a // 返回 } } MyClass x x.a = 10 // 这里就会调用 set a 循环解偶 ----------------- 两个类互相引用对方的情况是被禁止的(即使是间接引用)。但可以定义子类。 子类只允许在类内被构造,并且可以访问父类的变量。 class MyCls3{ int var=0 class SubCls{ int subVar=2 func incVar(){ var+=subVar // 子类可以访问父类的成员变量 super.print() // super 是父类的指针 } } SubCls cls=SubCls() func print(){ Console.print(cls.subVar) } } 垃圾收集、引用计数 ------------------ 默认情况下,类通过垃圾收集器/引用计数来管理内存。类可以包含析构函数,这时会使用引用计数的管理方式。 对于没有析构函数的类,默认使用 GC 来管理。 另外,支持手工管理,使用 new 关键字,new 生成的对象,必须使用 free 来释放 MyClass a=new MyClass() free a // 析构、删除对象,并清空指针 assert( !&a ) a.name = "Hello" // 这里编译就会报错(使用未初始化的对象) 语法糖:对象作用区 ---------------- 通过在对象后直接挂接代码块,可以以这个对象为“基准”来执行代码。区块中的所有函数、变量会先在这个 对象内部查找。 var i=a{ doSomething() name = "hello" } 这相当于: var i=a a.doSomething() a.name = "hello" 显式类型转换 -------------------- 除了默认类型转换,还可以使用下面的显式转换. var a=(MyClass)b // 尝试将 b 转换为 a (MyClass)b.funcInMyClass() // 先转换在调用 (MyClass)b{ // 转换在调用对象作用区 funcInMyclass() } 但显式转换也是有限制的,比如你并不能吧 int 转为一个不兼容对象。 单例 --------------------- 单例受到语言级别的支持。它的声明类似 class, 仅仅是把关键字 class 改为 singleton ////// 文件开始 //////// package org.silang; singleton MySingleton{ // 单例的公开定义部分 void AstCallit(){} } 单例会在首次被使用时,被**线程安全**的初始化,并且直到程序关闭时被析构。 MySingleton.AstCallit() 类型空指针 ------------------- 当一样参数为**可空的**,那么就可以通过 null 来初始化。 var? k = int // 可以理解为 var? k = (int)null k = 10 k = null k = 20 模板 ------------------ 可以通过把类中的成员函数,定义为 var 来创建模板类,模板类必须在构造时,能推导所有模板成员的类型。 class MyMap(var? k, var? v){ // 允许使用空指针来初始化 func templateFunc( var a ){ // 函数直接使用 var 来定义模板函数 if( a is int ){ // 这里的 is 是编译期的操作符,因此这里的 if 也是编译期的 // 只有 a 的类型是 int 类型的,这个代码块才会编译 a++ } typeof(a) b? // b 定义为 a 相同的类型 Type c=typeof(a) // Type 是种描述类型的特殊类型 when( a ){ // 用 when 判断类型 int : { } float : { } default : { } } } } var my = MyMap("Hello",9) // 构造函数必须可以推导所有类型 var you = MyMap(1.0, 9) // 注意:typeof(my)!=typeof(you) var my2 = MyMap(int, string) // 使用类型空指针来初始化 类型定义 def ----------- 可以使用 def 来复制类型,可以认为是继承了一个新类 def AFun = func(int, int):int def MapClass = MyMap(int, string) // 定义模板函数(会虚拟的调用构造函数,以推断类中的模板参数) var m = MyMap( 10, "Hi" ) def IntMap = m // 可以理解为 def IntMap typeof(m) 的语法糖 def StringList = List(string) // 其实是 def StringList typeof(List(string)) StringList myList // 注意,myList 是 StringList 类型,而不是 List 需要注意的是,def 定义的类型是一个新类型,可以视为从原类型继承的类。 常量推导 ================== 如果常量传入模板函数,那么函数在编译器,就会被计算 func myTemp( var a ) : var c{ if( a == 1 ){ return a+10 }else if(a=="Hello"){ // 传入 常量整数时,这分支都不会被编译 return "World" } } myTemp( 10 ) // 在编译时不会生成函数,直接替换为 a+10 var x = 10 myTemp( x ) // 编译错误,传入的是变量,参数固定为 int const y = "Hello" myTems( y ) // 传入的是常量,按模板编译 类型操作符 ------------------- Si 将支持一些类型操作符,结合模板,可以实现编译期编程。因此特别定义了两个内置类型·TRUE·和·FALSE·。 操作符 is ============ 在编译期判断类型可以使用 is,语法是 A is B 结果将是内置类型 TRUE 或 FALSE。A 可以是变量,或者模板参数,B 为类型或接口。需要注意的是,这里并非要求 A,B 是完全一致的类型,当 B 是从 A 继承,或A实现了接口B,也将返回 TRUE。 但相对的,如果 A 和 B 都是模板类型,那么模板参数不一致(即使他们有继承关系),那也返回 FALSE 操作符 === ============= 在编译期判断类型是否相等 A === B B 必须为类型或接口,A 可以是变量,或者模板参数,和 is 相近,但 === 要求类型完全相等。 操作符 if ============ 当 if 后的括号内,是一个类型,那么它就成为一个编译期的类型操作符,仅当类型为 FALSE 时,编译 else 语句块, 其他任何类型都编译 then 语句块。 if(a == int){ // 当 a 类型为 int 时被编译 int b = a+10 }else{ a = "Hello" // 虽然类型错误,但这里没编译,因此不会报错 } 操作符 `(反引号) ================= 反引号将尝试从类型中按名称取出成员,如果成员不存在返回 FALSE,存在返回成员的类型。 这可以在编译期判断类是否包含某个成员。 if( a`name` ) ... if( a`myfunc` && a`myfunc` is func(int, string):string ) ... 操作符 for ================= for 可以用来解开通过 ... 传入的多个参数等,也可以解开元组,这个过程是编译期的。 func my( var ... attrs ){ for( var i: attrs ){ // 这里 i 的类型会按输入变化 } var x=( 10, 20, "Hello") for( var i: x ){ print i } } 操作符 ==,!=,||,&& ================ 当两个常量进行布尔运算,他们也会在编译期运算为 TRUE 或 FALSE。 var a=(1==1) // TRUE 接口 ------------- Si 可以通过 interface 关键字定义接口,接口所有的方法、变量都是公开的。 接口内的函数**可以**有默认实现。 接口可以直接用在函数、方法的参数上,它可以用来约束函数、方法的参数, 它实现了部分需要模板的功能,只要类实现了接口里的所有方法和变量,并且他们是公开的,那么就视为他实现了该接口,可以传送给接受该接口的方法。 比如 interface MyInterface{ int a func getSome():int // 这是个函数定义 } void aFunc( MyInterface inc ){ // aFunc 实际上是一个模板函数。 // 这样,任何对象,只要包含 int a 的成员变量,以及 getSome 这样一个方法, // 就可以被传入这个函数 } 需要注意的是,这是编译期的动作,接口被视为有约束的模板参数。 另外,比较简单的接口可以直接在参数中定义(匿名接口) void aFunc( {int a; func getSome():int} inc ){ } 接口也可以用来明确的强制一个类去符合某种契约: class MyCalss : Base, MyInterface{ } 模板推导函数 -------------- 由于函数的返回值可以通过 return 来推导,因此可以通过写一个完全静态的函数,来实现计算类型的模板函数。 func TplFunc( var? a, var? b ) : var{ if( a is int && b is int) return TRUE() // 真类型,if( TRUE ) 永远真,并且在编译期就处理 else return FALSE() } var a = 10, b=10 if( TplFunc(a,b) ){ // 静态语句,在编译期展开 int c = 10 } 延迟优化 ---- si 的函数参数,允许使用延迟生成的技术以优化效率。它让参数仅在被首次使用的时候,才会被生成它。 比如: trans_data( get_data(), x) 而 trans_data 的代码如下: func trans_data( var v, bool x ){ if(x) print(v) } 这段代码里,v 通过 get_data() 获取值,但在 trans_data 中,如果 x=false,v根本不会被使用。这个时候 get_data() 的调用 是完全没有必要的。而通过延迟生成技术,只有在v参数实际被使用时才会尝试“构造”它,因此,如果 x=false,get_data() 会被直接 放弃,生成的代码类似下面的 void trans_data( bool x ){ if(x) print( get_data() ) } 要启用延迟优化,调用代码写成如下 trans_data( *[get_data()], x) 但需要注意,延迟优化只用于模板函数,因为编译器实际上是生成了一个 Delay 内部模板对象,传参如果类型是定的,Delay 对象会立刻解开,不会延迟。 其实也不一定用在函数里 Delay x=*[get_data()] // 这时 get_date() 其实没有被执行 var k=x // 这时才会执行,并且只会执行一次 var k2=x // 注意,这里不会重复执行 线程 -------------- 创建线程由线程库支持,类似 Java Thread thread(daemon=true, level=3) thread->{ // 运行线程 aFunc() } thread.join() // 等待结束 而同时,Si 包含一个小的语言库,直接支持并行任务,并且尽可能自动维护。 在一个函数、区块调用前加上 go 关键字,这次调用就会在一个新的并行任务中并发执行。 当被调用的函数返回时,这个并行任务也自动结束。需要注意的是,如果这个函数有返回值, 那么这个返回值会被包装为 Chan。 go{ // 通过go来创建一个并行任务 print( "go" ) } go dosomthing() // 并行执行函数 Barrier()->{ // 通过一个内存栅栏,保证并行项都执行完毕后才进行下一步 for( int i; i<10; ++i ) go{ // 并行任务 dosomething(i) // 这个函数会被并行执行(OpenMP) } } Si 通过库来支持管道(Chan),以便实现异步数据交换。 管道可以输入输出多次,并且可取消。 Chan s=Chan(int) go{ // 并行执行语句块 s <- i // 写入管道,这里会阻塞,直到值被取出 } go{ sleep(1000) if( s.cancel() ){ // 交换器可以取消,取消成功返回 true, 而如果管道已经被使用,那么返回 false // 超时代码 } } 可以明确的“等待异步线程事件”完成 var a = *s // 从管道取出值,这里会被阻塞,直到有值 或者异步等待 s->int a{ // 异步输出,回调函数会在有输入时被调用。另外,这是缩写(语法糖),完整的写法是 s->func(int a) print a; } 使用 go 调用的函数,如果有返回值,会自动包装为 Chan,并且返回值自动输入 func inThread( int a, int b ) : int s { ... return a; // } Chan x=go inThread(10, 20); x->var a{ // 异步返回的值写入 a 并执行后面的代码块 ... // 需要特别注意的是,管道输出后执行的代码块, // 是在**异步线程**内执行的 } 另外,管道输出只能选择一种,同时使用同步和异步取,会导致编译错误。 如果需要锁,代码如下 Mutex myMutex=Mutex() // 定义一个锁 myMutex -> { // 锁内 } // 这里解锁 原子操作由库来支持: var a=Atomic(10) // 原子的 int int v=a++ // 原子的 +1 并返回新值 int cur=a.compareAndSet( 11, newValue ); import ============ Si 通过 import 导入包 import org.silang.net import ( // 导入多个 org.silang.math.sin org.silang.io.print ) 注意:import 只能写在文件头部,package 定义之下 和其他语言协作 ===================== 可以引入 c 或其他语言写的库会被编译成包,以便被 Si 调用。Si 语言可以输出标准 c 函数,以便被其他语言调用。 lib 文件、需要对应的头文件及适配文件等需要的东西,会被某个处理软件打成一个包,并放在编译程序能找到的地方。 而 Si 可以编译成 c lib, 函数会按**约定**转换成 c 的格式。 备选(思考中) -------------- 交叉类型 ===== 交叉类型是将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一 起成为一种类型,它包含了所需的所有类型的特性。 var a = (MyClass & Second)() // 实际类型为 Intersection a 可以直接使用 MyClass 和 Second 内的属性,方法,特别的,如果 MyClass 和 Second 中有重复的属性或方法,可以这样使用: (MyClass)a.funcInMyclass() var b = (MyClass)a 操作符重载 ================ Si 支持有限的操作符重载。对于类内的操作符,可以通过一个函数重载 class MyClass{ operator + ( int right ){ // 二元操作符的函数重载(默认的返回 MyClass 类型) return this } operator ++{ // 自增在后 // return this 可省略 } } operator + ( int left, MyClass cls ) : MyClass{ // MyClass 在操作符的右边 } 注解、反射 ================== si 支持注解及反射。(抄 Java) class MyClass{ @ReflectName( name="value", idx=12 ) // 使用注解对象来进行注解 int a ; void doFun(){ } } Package pkg=Reflect.packages["org.example"]; // 获取包 Type cls=typeof(MyClass); // Type 描述类型 Type cls=Reflect.classes["org.example.MyClass"]; // 通过全名获取 Type cls=pkg.classes["MyClass"]; // 通过包获取对象 for( string name, field v : cls.fields ){ var annotation=v.annotations; // 获取注解 } 注:Reflect 其实是一个语言支持的单例,因此如果程序中没有使用到它,那么它并不会初始化。 C 对象定义 ============= @Clang("my_c_object") class MyCObject { func at( int idx ) : char; // int my_c_object_at( my_c_object* me, int idx ); @Clang("add") operator + ( MyCObject other ) : MyCObject // my_c_object* my_c_object_add(my_c_object* me, my_c_object* other); }