• Introduction to OOC Programming Language


    Introduction to OOC Programming Language

    文/akisann @ cnblogs.com

    本文遵循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一一对应,并且有非常简单的特征,比如:

    ooc c
    Char char
    UChar unsigned char
    Int int
    UInt unsigned int
    Float float
    ...

     

    循环与判断

    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 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 trees of depth %d check: %d" printfln(iterations*2, i, check); i+=2 } "long lived tree fo depth %d check %d" printfln(depth, longlived.itemCheck()); return 0 }

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

    ooc c
    16.95 16.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的编译器更加完善。

  • 相关阅读:
    心情日记:【原创诗歌】怆情吟
    心情日记:2008年3月3日 奶奶去世
    心情日记:健身日记
    金融基础概念期货
    FXDD点值获利计算
    外汇基础概念汇率
    报告论文:是学生都copy下来,现在不用,将来绝对要用(转)
    情感日记:毕业临走物语
    美元为什么坚挺
    英特尔首席技术官:人机智能鸿沟将于2050年消失
  • 原文地址:https://www.cnblogs.com/akisan/p/4238912.html
Copyright © 2020-2023  润新知