Introduction to OOC Programming Language

Introduction to OOC Programming Language

文/akisann @ cnblogs.com / zhaihj @ github.com

本文同时发布在github上:https://github.com/zhaihj/intro-ooc

本文遵循CC-BY-NC。

我想试一门新语言……但:

  • 我希望这门语言能简洁易懂 —— 排除了Perl/Rust...
  • 我不想自己管理内存 —— 排除了C/C++/Object Pascal...
  • 最好它能跟C差不多快 —— 排除了Python/Ruby...
  • 并且最好能在任何地方编译&运行 —— 排除了D
  • 我不想带着一个数百兆的运行库 —— 排除了Java
  • 我不怕Bug —— 欢迎来到OOC的世界

Why OOC?

  • Compile to C,所有的代码都会首先编译成C,然后由clang或者gcc编译成二进制代码 这意味着,只要你有一台能运行ooc编译器的电脑,那么你的代码就可以在几乎任何有C编译器的平台上运行。

  • Class,Function overloading,Extend,Operator Overloading.... OOC拥有绝大部分你所期待的高级语言的功能。 ooc借鉴了很多语言,尝试这把这些语言里优秀(并且有趣)的元素融合到一起。

  • Easy interface to c. OOC可以直接使用C的头文件,也可以在C里简单的使用ooc的函数。

OOC的官方网站里有更多介绍和参考资料。

First Impression

一个求Fibonacci数的程序看起来是这样:

fibonacci: func(n: Int) -> Int{
    n < 2 ? n : fibonacci(n-1) + fibonacci(n-2)
}

for(i in 0..30) "f(#{i})=#{fibonacci(i)}" println()

 

这段程序输出了前30个Fibonacci数。看起来是不是很容易懂? 函数通过函数名: func(参数)->返回类型来定义, 而它的内容则与C一模一样。同时,它不需要分号,不需要return,因为最后一个表达式的值会作为函数的返回值(当然也可以显式的使用return)。

Getting Started

如果在看了上面的代码之后你有兴趣继续,那么是时候准备一下编译环境了。OOC的一个实现可以在github上简单的获得。好的,让我们一步一步来:

  • 首先,clone编译器(rock)的源代码:

    git clone https://github.com/fasterthanlime/rock
    

     

  • 随后,运行make即可

    cd rock && make rescue
    

     

rock是一个bootstrap的编译器,也就是说它本身是由OOC写成的。首先makefile会下载一套预编译的C源代码,用C编译得到的编译器来进一步编译现行代码。不出意外,make之后就会得到一个可以运行的ooc编译器了,它默认是./bin/rock,你可以用soft link把它放到任何地方。

使用rock编译ooc的程序非常简单,只需要简单的执行

rock yourfile.ooc

 

就会得到可执行文件yourfile

Hello World

为了确认编译器是不是正常工作,首先来编译一个简单的Hello World

"Hello world!" println()

 

把上面的代码保存在hello.ooc里,然后执行

rock hello.ooc

 

你就会得到hello,执行它,看看结果吧。

当然,你也可以加一些佐料:

rock --cc=clang --O3 --pr hello.ooc

 

--cc用来指定使用的编译器,--O3与gcc下的意思是一样的,代表了最高优化,而--pr则表示是发行版。同样,你可以指定--pg来获得调试版。

Basic Elements

OOC来自与C,编译成C。因此绝大部分内容跟C十分类似,在这里仅仅介绍一些不同的元素。

变量定义

foo : Int
bar : String
cstyle : Float*
p : Pointer

 

ooc的变量定义类似Pascal,用变量名:类型的格式,同时需要注意的是所有的类型首字母都是大写,并且c里面的void*在ooc里是Pointer类型。当然,在变量定义时也可以赋值:

error: String = "System Error!"

 

不仅如此,在变量定义有初始值时,ooc还允许省略类型,这个特性跟Go或者IO语言里的特性是类似的:

error := "System Error!"

 

OOC里,变量的类型跟C一一对应,并且有非常简单的特征,比如:

 

oocc
Charchar
UCharunsigned char
Intint
UIntunsigned int
Floatfloat
...

 

循环与判断

if/while语句与c完全一样:

if(c) { return true }
else { renurn false }
while(c){ c -= 1 }

 

稍微有些不同的是ooc的for:

for(i in 0..100) { a += i }

 

ooc的for语句不支持c格式,for仅仅能够遍历一个范围,但这在普通情况下已经够用了。并且,你不但可以对简单的整数范围取for,还可以对对象做类似foreach的动作:

map := HashMap<Int, String> new()
// do something to map ...
for((i,j) in map){
   // use i as key and j as value   
}

 

同时,对于c里的switch语句,ooc里使用的是match:

match (token) {
    case "if" => 1
    case "case" => 2
    case "while" => 3
    case => -1
}

 可以看到,跟c里的switch不一样,这里的match可以用来配对字符串。 实际是不仅仅是字符串,ooc的match可以用来配对几乎任何东西,即使对于对象(后述),只要它有matches这么一个成员函数,就能够用在match里。

另外,跟C里不一样的地方是, ooc里matc并不是一个Statement,而是一个Expression——也就是说这个match是有值的,于是你可以写出下面这种代码:

 

opcode := match(text){
    case "plus" => 0
    case "minus" => 1
    case "if" => 2
    // ... more opcodes here
    case => -1   
}

 而不必在每个分支里不停的赋值了。

最后,要注意的是这里每一个case执行完之后就会自动中断,并不会继续执行下一个case,这与c是不同的。而最后一个什么都没有的case则相当与c里面的default,在没有任何东西能够成功配对时,就会执行这个语句。

 

函数

就像在最初所展示的一样,ooc的函数定义类似Pascal(或者Ada):

myfunc: func(argument: Int, argument2: String) -> Int{
    // do what you want
}

 

函数的返回值不仅仅可以是单个的值,也可以是tuple:

swap: func(a, b: Int) -> (Int, Int){ (b, a) }
(a, b) := swap(10, 5) // result is a=5, b=10

 

当然,跟其他语言一样,ooc也支持First-class Function:

foo: func(add: Func(Int,Int)->Int) -> Int{ add(10, 20) }
bar: func(a,b: Int) -> Int{ a + b }
foo(bar)
foo(|x, y| x + y)

 

这里foo函数的参数add是一个函数(或者也可以说是“函数指针”),需要注意这里的Func是大写——因为在ooc里,所有的类型的首字母都是大写。bar可以直接作为参数使用。同时,|x, y| x + y是lambda表达式,它定义了一个以参数x,y为输入的函数。当然,函数也可以作为变量(闭包):

fileFilter: func(name: String) -> Bool{
    getName := func(s: String) -> String{
        // get name ...
    }

    // use getName as function
}

 

对于指针,ooc里几乎与c是一样的:

mul2: func(v : Int*){ mul2@ *= 2 }
mul2ref: func(v : Int@){ mul2 *= 2 }

myvar := 3
mul2(myvar&)
mul2ref(myvar&)

 

这里定义了一个叫mul2的函数,它的它的定义与使用跟c并没有太大差别,唯一需要注意的就是在ooc里,访问指针不再是*var而是var@,类似的取地址也不是&var而是var&。在另外一个函数mul2ref里,我们用了Int@,它代表了变量v是一个参照——也就是说虽然它通过指针传递,但在使用时会被自动取值。

最后,函数是可以overloading的,比如下面的例子:

foo: func (i: Int) -> Int{ i * 3 }
foo: func ~withj (i: Int, j: Int) -> Int{ i * j }

 

ooc里,函数的特征(signature)并不仅仅是函数名和参数列表,你还可以给任何一个函数添加”后缀“,也就是这里~withj的地方,通过后缀,可以实现函数的overloading。后缀跟函数名不同的地方在于,在执行时,即使不加后缀,编译器会自动寻找最合适的函数去执行,而通过制定后缀,可以硬性的指定一个函数, 比如:

foo(1) // 执行第一个
foo(1,2) //执行第二个
foo ~withj (1,2) //执行第二个

 对于前两个函数,编译器会搜索所有叫做foo的函数定义,然后比较参数列表,并找出最合适的那一个。对于第三个语句,不但函数要有相同的名称, 同时还要有相同的定义和后缀才能正常执行。

类与覆盖

虽然最终编译成C,与其他高级语言一样,ooc里有类的概念,类的定义十分简单:

myclass: class{ init: func }

 

其中init是类的构造器,但它并不需要是static(静态)的,因为编译器会自动生成真正的构造器new,也就是说,在使用myclass,应该像下面这样:

v := myclass new() // new is auto-generated static function

 

注意,每一个类必须有至少一个init,因为如果没有init,编译器就不会为它生成new,因此也就无法初始化。当然,你也可以自己定义new:

myclass: class{
    new: static func -> This{
        // do initialization
    }
}

 

我们之前已经说了很多次,ooc里所有的类型都是首字母大写,因此代表着当前对象的this首字母大写之后代表这当前类。不过需要注意,自己定义new并不是见好事情,因为class最终还是由c里的struct实现的,因此你需要自己分配内存,管理初始化……等等。静态的new只在包装c函数时有用处,比如包装SDL-ttf时:

TTFFont: cover from TTF_Font*{
    new: static func(filename: String, ptSize: Int) -> This{
        TTF open(filename toCString(), ptSize)
    }

    new: static func ~rw (data: Pointer, freedata: Int, ptSize: Int) -> This{
        TTF openRW(data, freedata, ptSize)
    }

    ....
}

 

这样就可以非常自然的将c函数转换成了类。当然,在这里代码使用了cover(覆盖),它在地位上等同与c的struct,但可以拥有函数,也可以被扩展,你可以认为cover是一个仅仅在使用c代码时才会用到的特殊类(class)。对于普通的类,只能继承(extends)其他的类,但对于覆盖,它既可以来自其他覆盖,也可以来自c的struct。比如:

Array: cover from _lang_array__Array {
    length: extern SizeT
    data: extern Pointer

    free: extern(_lang_array__Array_free) func
}

 

这段代码里_lang_array__Array是定义在c的头文件里的struct,而length和data都是它的成员,运用cover,可以很简单的将c中struct转换成ooc里可用的类型。

一个类的函数成员可以是static,可以是final。当它是final时,你是不能继承它的,比如:

foo: class{
    init: func
    a: final func
}

bar: class extends foo{
    init: func
    a: func
}

 

这段代码会出现编译错误:

$ rock ff.ooc 

test.ooc:8:5 error Can not inherit from final function 'a'
    a: func
    ~      

test.ooc:3:5 info ...first definition was here:
    a: final func
    ~            

[FAIL]

 

最后,类与覆盖都是可以被扩展(extend)的,它类似与ruby的extend,允许你向已经存在的类或覆盖里追加新的成员函数:

extend Int{
    isOdd?: func -> Bool{
        this % 2 == 1
    }
}

 

这里我们给Int类型添加了一个isOdd?函数,这里问号没有特殊意义,仅仅是函数名的一部分,ooc允许函数名的最后一个字符是问号,用来表示这是一个“查询”函数,在编译成c是,问号会被翻译成字符串“query”。

好的现在我们可以是一下新定义的函数了,成员函数的访问与其他语言不一样,不是通过点(.)来实现, 而是简单的空格:

1 isOdd?() toString() println()
2 isOdd?() toString() println()

 

可以看到它们已经能够正常执行了。这里,isOdd?是Int的成员函数,而toString是Bool的成员函数,println()又是String(toString的返回类型)的成员函数,这点跟ruby非常接近。 同时,你可能已经注意到了,所有的函数调用都必须加上括号,否则编译器会抛出错误,那么有没有办法让不加括号呢? 答案是有的,至于要定义属性即可:

extend Int{
    isOdd : Bool {
        get { this % 2 == 1 }
    }
}

 

然后就可以像普通成员变量一样使用它了:

1 isOdd toString() println()
2 isOdd toString() println()

 

在这里,我们定义了一个只读的属性,对于这种属性,ooc提供了一个更简单的定义方式:(pdfe)

extend Int{
    isOdd ::= (this % 2 == 1)
}

 

这段代码的效果跟上面是完全一样的。

最后,类还支持运算符重载, 比如:

vector: class{
    operator + (v: This) -> This{
        // add this and v
    }
}

 

泛型

ooc里存在泛型,但它完全不同与其他语言里的泛型,在ooc里,它可以“接受任何类型的变量”,而在其他语言里,泛型意味这“自动生成对应类型的实现”。这种差别决定了ooc的泛型远没有其他语言里强力。 你可以认为,ooc里的泛型是为集合(Collection)引入的,比如ArrayList和HashMap,对于普通函数,大量使用泛型不会有任何优势。

这里仅仅举一个简单的例子:

foo: class <T>{
    data: T*
    length: Int

    init: func(=length){
        data = gc_malloc(T size * length)
    }
}

myfoo := foo<Int> new(10)

 

在这里,=length代表了参数直接赋值给成员,随后我们会根据参数大小分配一块内存给数据。这里的T可以是任何类型。

在ooc里,泛型是一个非常容易出错的部分,具体的设计思想可以参见我翻译的一篇文章。在这里不多描述。

库与头文件

ooc里使用库是非常简单的,所需要的就是简单的import而已:

import structs/ArrayList // 使用ArrayList

 

编译器会在../sdk或者$OOC_LIBS里寻找所有的库,然后自动使用和编译它们。对于C语言的头文件,可以通过include来使用:

include stdbool

 

这样就可以使用stdbool里面定义的内容了。

一个小例子

这个小例子是Computer Langugae Benchamark Game里BinaryTree的一个实现,可以拿它跟C语言的版本来做比较。

Node: class{
    item: Int
    left,right: Node
  
  // 在这里,item会被直接赋值给成员变量 init: func(depth: Int, =item){ if(depth<=0) return left = Node new(depth-1, 2*item-1); right = Node new(depth-1, 2*item); } itemCheck: func() -> Int{ if(!left) return item return item+left itemCheck()-right itemCheck() } } mindep := 4 // main函数与C中的main函数有着相同的含义,但是它可以有不同的定义,除了这里用的C形式,还可以使用: // main: func(args: String[]) -> Int // main: func(args: ArrayList<String>) -> Int main: func(argv: Int, argc: CString*) -> Int{ depth: Int if(argv>1) depth = argc[1] toString() toInt() else return 1 stretch := depth+1 check := Node new(stretch,0) itemCheck() "stretch tree of depth %d\t check: %d" printfln(stretch, check) longlived := Node new(depth,0) i := mindep while(i<=depth){ iterations := 1<<(depth-i+mindep) check: Int = 0 for(j in 1..iterations+1){ check += Node new(i,j) itemCheck() check += Node new(i,-j) itemCheck() } "%d\ttrees of depth %d\t check: %d" printfln(iterations*2, i, check); i+=2 } "long lived tree fo depth %d\t check %d" printfln(depth, longlived.itemCheck()); return 0 }

 

至于这个程序的执行结果:(参见我的Github Repo)

 

oocc
16.9516.45

 

可以看到,二者几乎没有差别。

结语

OOC是一个很不错的第二,或者第三语言。虽然有公司在用ooc做些事情,但我并不认为那很明智。的确,ooc兼具执行效率和开发效率,但目前它的编译器还远远没有完美。比如当你在通过PDFE(Property Definition Fast Expression)定义了一个属性,但却当作成员函数使用时,编译器会直接出错。又比如使用尖括号来初始化泛型函数时:

foo: func<T>(a: T) -> T{ a }
foo<Int>(1)

 编译器也会好不客气的出错。虽然目前OOC已经能够编译自己,也能够支持其一些中型的项目(比如ooc-kean和vamos),但距离完美还有很大的距离。

如果你有兴趣,那么不妨fork一下,让ooc的编译器更加完善。

转载于:https://www.cnblogs.com/akisan/p/4238912.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值