Tolk vs FunC:详细介绍
下面是一份非常庞大的清单。有人有足够的耐心读到最后吗?
✅ Traditional comments :)
FunC | Tolk |
---|---|
;; comment | // comment |
{- multiline comment -} | /* multiline comment */ |
✅ 2+2
是 4,不是标识符。标识符只能是字母数字
在 FunC 中,几乎所有字符都可以作为标识符的一部分。
例如,2+2
(不含空格)就是一个标识符。
你甚至可以用这样的名称声明一个变量。
在 Tolk 中,空格不是必须的。2+2
是 4,如所料。3+~x
是 3 + (~ x)
,以此类推。
FunC | Tolk |
---|---|
return 2+2; ;; undefined function `2+2` | return 2+2; // 4 |
更确切地说,一个标识符可以从 [a-zA-Z$_]
开始,并由 [a-zA-Z0-9$_]
继续。请注意,?
、:
和其他符号都不是有效的符号,found?
和 op::increase
也不是有效的标识符。
您可以使用反标包围标识符,然后它可以包含任何符号(类似于 Kotlin 和其他一些语言)。它的潜在用途是允许将关键字用作标识符,例如在使用方案生成代码时。
FunC | Tolk |
---|---|
const op::increase = 0x1234; | const OP_INCREASE = 0x1234; |
;; even 2%&!2 is valid | // don't do like this :) |
✅ 默认情况下不纯净,编译器不会放弃用户函数调用
FunC 有一个 impure
函数指定符。如果没有,函数将被视为纯函数。如果其结果未被使用,则编译器删除了其调用。
虽然这种行为已经记录在案,但对于新手来说,还是非常出乎意料。 例如,各种不返回任何内容的函数(如在不匹配时抛出异常), ,都会被默默删除。FunC 不检查和验证函数体, 允许在纯函数内部进行不纯净的操作,从而破坏了这种情况。
在 Tolk,默认所有功能都是不纯洁的。 你可以用注释标记纯函数, 然后禁止其身体中的不纯操作(异常、全局修改、 调用非纯函数等)。
✅ 新函数语法:fun
关键字、@
属性、右侧的类型(如 TypeScript、Kotlin、Python 等)
FunC | Tolk |
---|---|
cell parse_data(slice cs) { } | fun parse_data(cs: slice): cell { } |
(cell, int) load_storage() { } | fun load_storage(): (cell, int) { } |
() main() { ... } | fun main() { ... } |
变量类型 - 也在右侧:
FunC | Tolk |
---|---|
slice cs = ...; | var cs: slice = ...; |
(cell c, int n) = parse_data(cs); | var (c: cell, n: int) = parse_data(cs); |
global int stake_at; | global stake_at: int; |
修改器 inline
及其他 - 带注释:
FunC | Tolk |
---|---|
| @inline |
| @inline_ref |
global int stake_at; | global stake_at: int; |
forall
- 是这样的:
FunC | Tolk |
---|---|
forall X -> tuple cons(X head, tuple tail) | fun cons<X>(head: X, tail: tuple): tuple |
asm
实现--与 FunC 中一样,但由于正确对齐,看起来更漂亮:
@pure
fun third<X>(t: tuple): X
asm "THIRD";
@pure
fun iDictDeleteGet(dict: cell, keyLen: int, index: int): (cell, slice, int)
asm(index dict keyLen) "DICTIDELGET NULLSWAPIFNOT";
@pure
fun mulDivFloor(x: int, y: int, z: int): int
builtin;
还有一个 @deprecated
属性,不影响编译,但可用于人和 IDE。
✅ get
代替 method_id
在 FunC 中,method_id
(不含参数)实际上声明了一个 get 方法。而在 Tolk 中,使用的是简单明了的语法:
FunC | Tolk |
---|---|
int seqno() method_id { ... } | get seqno(): int { ... } |
get methodName()
和 get fun methodName()
都是可以接受的。
对于 method_id(xxx)
(在实践中不常见,但有效),有一个属性:
FunC | Tolk |
---|---|
| @method_id(1666) |
✅ 必须声明参数类型(尽管本地参数可有可无)
// not allowed
fun do_smth(c, n)
// types are mandatory
fun do_smth(c: cell, n: int)
有一种 auto
类型,因此 fun f(a: auto)
是有效的,但不推荐使用。
如果参数类型是强制性的,则返回类型不是(这通常是显而易见的啰嗦)。如果省略,则表示 "自动":
fun x() { ... } // auto infer return
对于局部变量,类型也是可选的:
var i = 10; // ok, int
var b = beginCell(); // ok, builder
var (i, b) = (10, beginCell()); // ok, two variables, int and builder
// types can be specified manually, of course:
var b: builder = beginCell();
var (i: int, b: builder) = (10, beginCell());
✅ 不允许在同一作用域中重新声明变量
var a = 10;
...
var a = 20; // error, correct is just `a = 20`
if (1) {
var a = 30; // it's okay, it's another scope
}
因此,不允许部分重新分配:
var a = 10;
...
var (a, b) = (20, 30); // error, releclaration of a
请注意,这对 loadUint()
和其他方法来说不是问题。在 FunC 中,它们返回一个修改后的对象,因此 var (cs, int value) = cs.load_int(32)
这种模式非常常见。在 Tolk 中,此类方法会改变对象:var value = cs.loadInt(32)
,因此不太可能需要重新声明。
fun send(msg: cell) {
var msg = ...; // error, redeclaration of msg
// solution 1: intruduce a new variable
var msgWrapped = ...;
// solution 2: use `redef`, though not recommended
var msg redef = ...;
✅ 类型系统的变化
Tolk 第一个版本中的类型系统与 FunC 中的相同,但做了以下修改:
void
实际上是一个空张量(命名为unit
更规范,但void
更可靠);另外,return
(不含表达式)实际上是return()
,是从 void 函数返回的一种方便方式。
fun setContractData(c: cell): void
asm "c4 POP";
auto
表示 "自动推断";在 FunC 中,_
用于此目的;注意,如果函数没有指定返回类型,它就是auto
,而不是void
。self
,以创建可链式方法,如下所述;实际上,它不是一种类型,它只能出现在函数中,而不是函数的返回类型中cont
更名为continuation
✅ recv_internal / recv_external 的另一种命名方式
fun onInternalMessage
fun onExternalMessage
fun onTickTock
fun onSplitPrepare
fun onSplitInstall
所有参数类型及其顺序重命名不变,只是命名有所改变。fun main
也可用。
✅ #include → import.严格导入
FunC | Tolk |
---|---|
#include "another.fc"; | import "another.tolk" |
在 Tolk 中,如果不导入该文件,就无法使用 a.tolk
中的符号。换句话说,就是 用什么导入什么
。
所有 stdlib 函数开箱即用,无需下载 stdlib 和 #include "stdlib.fc"
。有关嵌入式 stdlib,请参阅下文。
命名仍有全局范围。如果 f
在两个不同的文件中声明,就会出错。我们 "导入 "的是整个文件,而不是每个文件的可见性,export
关键字现在还不支持,但将来可能会支持。
✅ #pragma → 编译器选项
在 FunC 中,"允许事后修改"(allow-post-modifications)等 "试验性 "功能是通过 .fc 文件中的一个 pragma 打开的(导致有些文件包含,有些不包含的问题)。事实上,这不是文件的 pragma,而是编译选项。
在 Tolk 中,所有实用程序都被移除。allow-post-modification
和 compute-asm-ltr
被合并到 Tolk 源中(就像它们在 FunC 中一直处于开启状态一样)。现在可以传递实验选项来代替语法标记。
目前,我们引入了一个实验性选项-- remove-unused-functions
(删除未使用的函数),它不会将未使用的符号 包含到 Fift 输出中。
#pragma version xxx
被 tolk xxx
代替(没有 >=,只有严格版本)。注释您正在使用的编译器版本是一个很好的做法。如果不匹配,Tolk 会发出警告。
tolk 0.6
✅ 后期符号解析。AST 表示
在 FunC 中(如在 С 中),不能访问下面声明的函数:
int b() { a(); } ;; error
int a() { ... } ;; since it's declared below
为避免出错,程序员应首先创建一个正向声明。因为符号解析是在解析时进行的。
Tolk 编译器将这两个步骤分开。首先是解析,然后是符号解析。因此,上述代码段不会出错。
听起来很简单,但在内部却是一项非常艰巨的工作。为了实现这一点,我引入了 FunC 完全没有的中间 AST 表示法。这是未来修改和执行语义代码分析的关键点。
✅ null
关键字
创建空值和检查变量是否为空现在看起来非常漂亮。
FunC | Tolk |
---|---|
a = null() | a = null |
if (null?(a)) | if (a == null) |
if (~ null?(b)) | if (b != null) |
if (~ cell_null?(c)) | if (c != null) |
请注意,这并不意味着 Tolk 语言具有可空性。不,你仍然可以为一个整数变量赋值 null
--就像在 FunC 中一样,只是在语法上更友好而已。经过对类型系统的努力,真正的可空性总有一天会实现。