@@ -21,7 +21,7 @@ GödelScript 编译器主要应用场景为:
21
21
22
22
1 . 面向用户编写简单或复杂查询,提供更便捷的写法,提高编写查询的效率;
23
23
2 . 提供严格类型检查与类型推导,给予更智能的代码修改提示;
24
- 3 . 提供严格的 [ ungrounded] ( #ungrounded-error ) 检测,避免容易触发的 Soufflé Ungrounded Error;
24
+ 3 . 提供严格的 [ ungrounded(未赋值/未绑定) ] ( #ungrounded-error-未赋值未绑定错误 ) 检测,避免触发 Soufflé Ungrounded Error;
25
25
4 . Language Server 以及 IDE Extension 支持。
26
26
27
27
### 基本程序构成
@@ -105,11 +105,9 @@ GödelScript 采用类 C 语言的注释方式。
105
105
106
106
#### ` main ` 函数
107
107
108
- GödelScript 查询脚本可以包含` main ` 函数,该函数无返回值。
108
+ GödelScript 查询脚本可以包含` main ` 函数,该函数无返回值。在不实现 ` main ` 函数,且没有写 query 声明的情况下,程序不会输出。
109
109
110
- 在` main ` 中可以多次使用` output(...) ` 来表明要输出多个查询结果。
111
-
112
- ` main ` 函数只允许使用` output ` ,其他语句会导致编译错误。
110
+ 更多详细内容请看 [ ` main ` 函数] ( #gödelscript-main-函数 ) 。
113
111
114
112
``` rust
115
113
fn main () {
@@ -118,11 +116,9 @@ fn main() {
118
116
}
119
117
```
120
118
121
- 在不实现` main ` 函数,且没有写 query 声明的情况下,程序不会输出。
122
-
123
119
### 基础类型和编译器内建函数
124
120
125
- GödelScript 包含基础类型` int``string ` ,` bool ` 属于基础类型,但是不能作为值存储。
121
+ GödelScript 包含基础类型` int ` ` string ` ,` bool ` 属于基础类型,但是不能作为值存储。
126
122
127
123
#### ` int ` 类型 native 函数
128
124
@@ -204,7 +200,7 @@ GödelScript 包含基础类型`int``string`,`bool`属于基础类型,但是
204
200
| to<T > | (self) -> T | 转换到其他类型的 schema,采用 duck type 检测。 |
205
201
| is<T > | (self) -> bool | 判断是否可以是其他类型的 schema,采用 duck type 检测。如果自身 schema 有主键,则底层只会通过主键判断是否可以是其他类型。 |
206
202
| key_eq | (self, T) -> bool | 检查两个 schema 实例的主键是否相等。 |
207
- | key_neq | (self, T) -> bool | 检查两个 schema 实例的主键是否 ** 不 ** 相等 。 |
203
+ | key_neq | (self, T) -> bool | 检查两个 schema 实例的主键是否不等 。 |
208
204
209
205
schema native 函数实例:
210
206
@@ -232,42 +228,101 @@ fn convert() -> *ElementParent {
232
228
233
229
### 函数
234
230
235
- #### `main` 函数
231
+ #### G ödelScript `main` 函数
236
232
237
- 在上文中已经提及,该函数是 G ödelScript 中唯一不需要声明返回值的函数 。
233
+ `main`函数是 G ödelScript 中唯一不声明返回值的函数。`main`函数只允许使用`output`,其他语句会导致编译错误;多次使用`output( ... )`可以输出多个查询结果,查询结果会分表显示,表名即为`output`中调用的查询函数的函数名 。
238
234
239
235
#### 查询函数
240
236
241
237
查询函数的返回值类型推荐为`bool `,需要输出查询结果时,需要使用`output()`函数。
242
238
239
+ 在`output()`中调用的查询函数不再是常规思路中的用传参调用函数。参数列表在此时会变化为输出表的表结构,下面是两个查询函数的应用实例:
240
+
241
+ 1. 单表`output`
242
+
243
+ 单表`output`特指在`main`函数中,只使用一次`output`来输出。
244
+
245
+ ```rust
246
+ fn example(a: int, b: string) -> bool {... }
247
+
248
+ fn main() {
249
+ output(example()) // 此时参数列表变为输出表结构,不需要传参
250
+ }
251
+ ```
252
+
253
+ 对应的输出表结构为:
254
+
255
+ ```json
256
+ [
257
+ {"a": 0, "b": "xxx"},
258
+ {"a": 1, "b": "xxx"}
259
+ ]
260
+ ```
261
+
262
+ 2. 多表`output`
263
+
264
+ 多表`output`是指在`main`函数中,使用多次`output`来输出。在这种情况下,输出数据会附带对应的表名。
265
+
266
+ ```rust
267
+ fn example0(a: int, b: string) -> bool {... }
268
+ fn example1(a: string, b: int) -> bool {... }
269
+
270
+ fn main() {
271
+ output(example0())
272
+ output(example1())
273
+ }
274
+ ```
275
+
276
+ 对应的输出表结构为:
277
+
278
+ ```json
279
+ {
280
+ "example0": [
281
+ {"a": 0, "b": "xxx"},
282
+ {"a": 1, "b": "xxx"}
283
+ ],
284
+ "example1": [
285
+ {"a": "xxx", "b": 0},
286
+ {"a": "xxx", "b": 1}
287
+ ]
288
+ }
289
+ ```
290
+
291
+ 下面是一个比较详细的例子,在这个例子中,我们直接构造了两组数据并输出。在下列代码中,需要注意的是:
292
+
293
+ 1. G ödelScript 中,布尔值可以使用`true`和`false`关键字。
294
+
295
+ 2. `= `符号在 G ödelScript 中是比较特殊的符号,不能用常规的编程语言的思路来理解。G ödelScript 是一种 Datalog 语言。在这里,`= `符号同时具备两种语义,一个是 __赋值__ 一个是 __判等__。详情可看[`= `运算符](#赋值和判等运算符)。
296
+
297
+ 3. 在这个例子的条件语句中,`a`和`b`均使用了`= `的赋值语义,因为`int`和`string`类型参数在函数体中被认为是`ungrounded(未赋值/ 未绑定)`,必须要被赋值才能使用。
298
+
299
+ 4. `= `赋值语句的返回值是`true`。
300
+
243
301
```rust
244
- fn myQuery(a: int, b: string) -> bool {
245
- if (a = 1 && b = "hello") {
302
+ fn example(a: int, b: string) -> bool {
303
+ // = 符号同时具有赋值和比较的功能,取决于此时的左值是否已经“被赋值”
304
+ // 这里的 a 和 b 所用的 = 符号均是赋值语义
305
+ if (a = 1 && b = "1") {
306
+ // GödelScript 使用关键字 true 和 false 来表示布尔值
246
307
return true
247
308
}
248
- if (... ) {
249
- ...
309
+ if (a = 2 && b = "2" ) {
310
+ return true
250
311
}
251
- ...
252
312
}
253
313
254
314
fn main() {
255
- output(myQuery ()) // 这里无需填写传入参数
315
+ output(example ())
256
316
}
257
317
```
258
318
259
- `output()`会根据该函数的参数列表格式来输出表结构。所以在`main`中使用时,`output()`并不需要你提供这个函数的传入参数。对应的输出表结构大致如下 :
319
+ 预期的输出结果应该为 :
260
320
261
- | a | b |
262
- | --- | --- |
263
- | 1 | "hello" |
264
- | ... | ... |
265
-
266
- G ödelScript 使用`true`和`false`关键字来代表返回的`bool `类型结果:
267
-
268
- ```rust
269
- return true
270
- return false
321
+ ```json
322
+ [
323
+ {"a": 1, "b": "1"},
324
+ {"a": 2, "b": "2"}
325
+ ]
271
326
```
272
327
273
328
#### 普通函数
@@ -352,11 +407,70 @@ if (f.getName().contains("util") || f.getName().contains("com")) {
352
407
353
408
条件可以使用这些逻辑运算符进行连接:`! `取反,`|| `或,`&& `与。
354
409
355
- 条件中的比较运算符:`>`大于,`<`小于,`>= `大于等于,`<= `小于等于,`= `等于 ,`!= `不等于。
410
+ 条件中的比较运算符:`>`大于,`<`小于,`>= `大于等于,`<= `小于等于,`= `等于或者赋值 ,`!= `不等于。
356
411
357
412
常规算术运算可以使用如下运算符:`+ `加法,`- `减法/ 取负,`* `乘法,`/ `除法。
358
413
359
- ** 注意:比较运算符中的** `* *= ** `** 在左侧变量没有被绑定数据时,会执行绑定操作并返回** `* * true** `** ,类似于赋值操作。**
414
+ ##### 赋值和判等运算符`= `
415
+
416
+ `= `符号在 G ödelScript 中具有两种不同的语义:赋值和判等,具体的语义需要分情况进行讨论:
417
+
418
+ 1. 赋值
419
+
420
+ 赋值一般出现在`int` `string`这类基础类型的变量参数上,这类变量作为函数的参数出现时,一般被认为是未赋值的。而具有这类变量的函数被调用时,传入的参数,实际上是作为筛选条件存在。
421
+
422
+ ```rust
423
+ fn example(a: int) -> bool {
424
+ // 这里比较反直觉,在过程式语言中,这里通常会被认为是判断 a == 1
425
+ // 但是在 datalog 方言中,datalog 的每个函数实际上都是在算一个中间表 (view)
426
+ // 所以这个函数本质上是生成了一个 view,数据为 [{"a": 1}]
427
+ return a = 1 // assign a = 1
428
+ }
429
+
430
+ fn test() -> bool {
431
+ // 这里看似是在通过传参让 a = 2,实际上并不是
432
+ // example() 自己会返回 view: [{"a": 1}]
433
+ // 然后通过 a = 2 来约束结果,可以看到,我们这里没有拿到任何结果
434
+ // 所以返回了 false
435
+ return example(2) // false
436
+ }
437
+ ```
438
+
439
+ 2. 判等
440
+
441
+ 对于 schema 类型来说,任何一个 schema 背后都有一个全集,所以参数列表中的 schema 类型一般被认为是已经被赋值的。对于已经赋值的变量来说,`= `就是判等操作。
442
+
443
+ ```rust
444
+ // 声明 schema
445
+ schema A {... }
446
+
447
+ // 实现 schema 的成员函数
448
+ impl A {
449
+ // 这里定义了 schema A 的全集
450
+ @ data_constraint
451
+ pub fn __all__() -> * A {... }
452
+ }
453
+
454
+ fn example(a: A ) -> bool {
455
+ for (temp in A :: __all__()) {
456
+ if (a = temp) {
457
+ return true
458
+ }
459
+ }
460
+ }
461
+ ```
462
+
463
+ 同样,对于中间声明的有初始值的`int`或者`string`,`= `也是判等操作。
464
+
465
+ ```rust
466
+ fn example() -> bool {
467
+ let (a = 1) { // assign a = 1
468
+ if (a = 1) { // compare a = 1
469
+ return true
470
+ }
471
+ }
472
+ }
473
+ ```
360
474
361
475
#### match 语句
362
476
@@ -944,9 +1058,9 @@ fn class_method(className: string, methodName: string, methodSignature: string)
944
1058
}
945
1059
```
946
1060
947
- ### Ungrounded Error
1061
+ ### Ungrounded Error : 未赋值 / 未绑定错误
948
1062
949
- G ödelScript 会将未与集合绑定的符号判定为 `ungrounded`。基本判定规则为:
1063
+ G ödelScript 会将未与数据绑定的符号判定为 `ungrounded(未赋值 / 未绑定) `。基本判定规则为:
950
1064
951
1065
- 未初始化的/ 未被使用的/ 未与集合绑定的符号
952
1066
- 未被绑定的`int``string`参数
@@ -2086,9 +2200,9 @@ e, p 的笛卡尔积就变成了 e, i 的笛卡尔积,从运算的层面来看
2086
2200
### 不要滥用`@ inline`/ 必须用`@ inline`的优化策略
2087
2201
2088
2202
inline 函数的底层机制是在** 调用处展开** ,如果该函数不存在大量的 schema 传参,并且在很多位置都被调用,inline 可能会导致** 编译结果膨胀且重复计算次数指数级增加** ,有时反而不利于减少运行时间。
2089
- 如果存在必须要使用 inline 的情况 (比如规避 ungrounded),但是使用之后反而出现运行速度变慢的情况,可以采取将内嵌语句拆分为 predicate 的方式来避免展开导致的编译结果膨胀。
2203
+ 如果存在必须要使用 inline 的情况 (比如规避` ungrounded` ),但是使用之后反而出现运行速度变慢的情况,可以采取将内嵌语句拆分为 predicate 的方式来避免展开导致的编译结果膨胀。
2090
2204
2091
- 下面的例子中,`getValueByAttributeNameByDefaultValue`为了避免`attributeName`被识别为 ungrounded 所以标注 inline,后续在 if 分支中添加了一个条件语句,但是导致了执行时间从 3 秒变成 35 秒:
2205
+ 下面的例子中,`getValueByAttributeNameByDefaultValue`为了避免`attributeName`被识别为` ungrounded` 所以标注` inline` ,后续在 if 分支中添加了一个条件语句,但是导致了执行时间从 3 秒变成 35 秒:
2092
2206
2093
2207
```rust
2094
2208
impl XmlElementBase {
0 commit comments