Crystal Crystal 语言, 书写 Ruby 式语法, 编译执行代码

jiyinyiyong · 发布于 2013年12月29日 · 最后由 jiyinyiyong 回复于 2014年01月01日 · 3813 次阅读
90

玩的时候用了 .cr 作为后缀, 结果被 GitHub 识别成其他语言了, 跑过去一看, 真有这么个编程语言抢我后缀.. Crystal 语言, Ruby 的语法, 代码是编译执行的:

http://crystal-lang.org/

# Compute prime numbers up to 100 with the Sieve of Eratosthenes
max = 100

sieve = Array.new(max, true)
sieve[0] = false
sieve[1] = false

(2...max).each do |i|
  if sieve[i]
    (2 * i).step(max - 1, i) do |j|
      sieve[j] = false
    end
  end
end

sieve.each_with_index do |prime, number|
  puts number if prime
end

搜索论坛被提到过一次 http://ruby-china.org/topics/10389

Crystal 的博客上提到下面几点:

  • Garbage Collector
  • Bootstraped the compiler
  • Type inference (part 1)
  • Shortcut syntax for writing one-argument blocks
  • Null Pointer Exception

具体代码还是 pre-alpha 的... 等正式发布玩一下... 不过不知道发布要几几年啊..

共收到 23 条回复
96

用的ruby-llvm 之前也想用来着 但是本地一直没装上 T_T

172

我也初步玩了下 感觉确实不赖啊 不过很奇怪为何单引号没有实现??

4755

语法好亲切。

90

疑惑起来了.. 在知乎发了个帖问 http://www.zhihu.com/question/22399630 自己在学做很简单的脚本语言,, 类型推断究竟能多大程度让动态语言有静态语言的效率呢? 是所有的语言, 能够类型推断的话, 就都能够被编译成二进制代码吗?

96

#4楼 @jiyinyiyong

不能啊,类型推断算法本身有复杂度限制啊 ...

Inferring Recursive Data Types 里面总结了几种不同的类型系统,对应的类型推断算法的复杂度,有几种是 P/NP HARD 的 ...

是所有的语言, 能够类型推断的话, 就都能够被编译成二进制代码吗?

不需要类型推断,也能编译成二进制代码的。只是运行起来可能还没解释执行快

2622

看了下官网,这个语言好像最近几个月才有GC...

2466

#5楼 @bhuztez 我理解的类型推断,就是基于递归的,对前束范式,把全称量词都替换掉,除此之外还有其他途径吗?

96

#7楼 @rasefon 我看不懂那么多术语 ....

2466

#8楼 @bhuztez 就是倒Ax x->y, 然后把x,y用各种实际的类型去替换,比如int, bool这类,然后就可以做比较了。

2466

有时候这种类型变量(占位符)可能不是只有一种类型,会是一个可能的类型的集合,这种时候就比较难办了。

3472

#5楼 @bhuztez #4楼 @jiyinyiyong

JIT 不就是编译成二进制码执行吗

96

#10楼 @rasefon

我理解的类型推断相当于在编译期运行个类似Prolog的东西

比如

fun f(a, b) { return a+b; }

改写成

f(A, B, Ret) :-
  +(A, B, Ret)

就好了

2466

#12楼 @bhuztez 这个例子里面的类型,假如这个语言只支持int,那么类型就应该是int x int -> int。

2880

#4楼 @jiyinyiyong 编译到二进制很简单的, 把 vm 指令都改成函数调用就可以了, 其实和解释执行区别不大... 如果把 vm 指令的实现内联进来, 就能省掉 call 的开销, 但编译出来的结果就会很大 (这里很矛盾的, 如果用提取公共子表达式之类的优化手段, 就退化回前面那种代码了). 所以一般都选择性的内联, 和选择性的优化(编译时间比执行时间还长的话, 即时编译也没有意义了)

编译期类型作用就是去掉运行时的类型检查和方法查找开销, 如果调用目标代码固定了就能内联进来 -- 但动态语言就是不固定的, 很多脚本的大部分代码就是只运行一遍, 延迟到运行时检查速度更快. 还有个作用是利用数据类型的知识, 减少从 tagged pointer 拆箱/装箱的开销, 减少从对象取成员/写成员的开销 -- 但是这种优化之前往往要做逃逸分析, 对象逃不出去才能这么做, 而只要语言支持 continuation 或者比较强的运行时反射, 基本都逃逸了... 另外, 声明成 immutable 的变量就可以不用做逃逸分析直接拆箱.

2466

#14楼 @luikore 都设计成传值调用不就不用关心逃逸变量了嘛哈哈。

90

#14楼 @luikore 术语超多 >_< 还只会很基础的解释器... 直观感觉是一路 type assertion...

if block.Tag == "block" {
  if item, ok := block.Value.(context); ok {
    runtime := Env{}
    for i, para := range item.args {
      // println("i is:", i)
      // debugPrint(xs)
      if token, ok := para.(cirru.Token); ok {
        runtime[token.Text] = cirruGet(env, xs[i+1:i+2])
      }
    }
    for _, line := range item.code {
      if exp, ok := line.(cirru.List); ok {
        ret = Evaluate(&runtime, exp)
      }
    }
    return
  }
}

https://github.com/Cirru/cirru-gopher/blob/master/block.go#L25

距离编译好远..

96

#16楼 @jiyinyiyong 果断Erlang搞起啊 ...

96

#17楼 @bhuztez 直接elixir啊

96

#18楼 @krazy Elixir丑爆了 ...

90

#17楼 @bhuztez 太跳跃啊, 为啥要 Erlang?

96

#19楼 @bhuztez 仔细一想 =/= =:=操作符, 各种module attributes和表达式后面的逗号句号以及字符串处理也没什么不可以接受的...

2880

#16楼 @jiyinyiyong 编译器和解释器代码构造相似, 真正干事情的地方改成打印或者拼字符串就可以了. 你可以先弄个输出 js/C 代码的编译器, 会玩了就可以尝试输出 llvm 字节码了 (如果用 llvm 的话, 建议实现语言选 C++, 可以少走很多弯路). 想直接输出 x86 也不难, 可以看看 libjit 和 xbyak, 不过要先学习 calling convention 和熟悉 stack layout.

.NET DLR 上做语言更简单, 把树给它就好了.

关键词都给你了, 自己看 wiki 咯.

90

#22楼 @luikore thanks, long way to go.

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册