OPA将从外部加载的数据成为基本文档(base documents),有规则产生的值成为虚拟文档(virtual documents),此处"虚拟"的意思表示文档由策略进行了计算,且不是外部加载的。Rego中可以使用名为data
的全局变量访问这两种数据。
异步加载的基本文档可以通过data
全局变量进行访问。另一方面,如果软件需要查询OPA来获取策略决策时,也可以将基础文档同步推入或拉入OPA,此时需要通过input
全局变量来引用同步推送的基本文档。同步加载的数据保存在data
之外,防止命名冲突。
逻辑与
| { |
| "servers": [ |
| {"id": "app", "protocols": ["https", "ssh"], "ports": ["p1", "p2", "p3"]}, |
| {"id": "db", "protocols": ["mysql"], "ports": ["p3"]}, |
| {"id": "cache", "protocols": ["memcache"], "ports": ["p3"]}, |
| {"id": "ci", "protocols": ["http"], "ports": ["p1", "p2"]}, |
| {"id": "busybox", "protocols": ["telnet"], "ports": ["p1"]} |
| ], |
| "networks": [ |
| {"id": "net1", "public": false}, |
| {"id": "net2", "public": false}, |
| {"id": "net3", "public": true}, |
| {"id": "net4", "public": true} |
| ], |
| "ports": [ |
| {"id": "p1", "network": "net1"}, |
| {"id": "p2", "network": "net3"}, |
| {"id": "p3", "network": "net2"} |
| ] |
| } |
OPA使用;
来表示逻辑AND
| input.servers[0].id == "app"; input.servers[0].protocols[0] == "https" |
| |
| $ true |
也可以使用多行来忽略;
| input.servers[0].id == "app" |
| input.servers[0].protocols[0] == "https" |
| |
| $ rue |
如果引用的内容不存在或匹配失败,则返回的结果为undefined
| s := input.servers[0] |
| s.id == "app1" |
| |
| $undefined decision |
| s := input.servers[1110] |
| |
| s.id == "app1" |
| $ undefined decision |
变量
OPA的变量一旦赋值之后就是不可变的:
| s := input.servers[0] |
| s := input.servers[1] |
| |
| $ 1 error occurred: 2:1: rego_compile_error: var s assigned above |
迭代
找出连接到public
网络的ports
的id
,下面使用some
关键字定义循环的变量,使用id
变量提取符合要求的ports
的id
,最后一行input.networks[j].public
也可以写为input.networks[j].public==true
,同时注意条件之间是逻辑与的关系
| some i, j |
| id := input.ports[i].id |
| input.ports[i].network == input.networks[j].id |
| input.networks[j].public |
| |
| $ +---+------+---+ |
| | i | id | j | |
| +---+------+---+ |
| | 1 | "p2" | 2 | |
| +---+------+---+ |
可以使用下划线_
(通配符)进行遍历,使用下划线来表示实例的单独变量。如使用如下方式找出input.servers
中协议为http
的id
| s := input.servers[_] |
| id := s.id |
| s.protocols[_] == "http" |
| |
| $ true |
规则(Rules)
使用规则可以重用判定逻辑,可以看作是一种函数实现。规则可以是"完整(complete)"或"部分(partial)"的。
完整规则
每个规则都包含一个head和一个body,如下head为any_public_networks = true
,body为net := input.networks[_]; net.public。如果忽略= <value>
部分(= <value>
用于使用右值赋予左边的变量,如apps_by_hostname[hostname] = app,key为hostname,value为app),则默认为true
。
| package example.rules |
| |
| any_public_networks = true { # is true if... |
| net := input.networks[_] # some network exists and.. |
| net.public # it is public. |
| } |
可以为规则定义默认值,这样在结果返回"undefined decision"时,会将any_public_networks
置为false
| package example.rules |
| |
| default any_public_networks = false |
| any_public_networks = true { # is true if... |
| net := input.no_exist_networks[_] # some network exists and.. |
| net.public # it is public. |
| } |
| |
| any_public_networks |
| |
| $ false |
可以使用如下方式进行访问:
| any_public_networks |
| |
| $ true |
也可以使用全局变量data
进行访问,访问方式为data.<package-path>.<rule-name>
| data.example.rules.any_public_networks |
| |
| $ true |
可以使用如下方式定义常量
| package example.constants |
| |
| pi := 3.14 |
部分规则
head为public_network[net.id]
,body为net := input.networks[_]; net.public
,可以看作是带返回值的函数
| package example.rules |
| |
| public_network[net.id] { |
| net := input.networks[_] |
| net.public |
| } |
可以遍历返回值
| public_network[_] |
| |
| $ + |
| | public_network[_] | |
| + |
| | "net3" | |
| | "net4" | |
| + |
逻辑或
完整规则的逻辑或
使用逻辑或时,要求多条规则的名称相同。如下用于校验servers是否暴露了telnet
或ssh
协议:
| package example.logical_or |
| |
| default shell_accessible = false |
| |
| shell_accessible = true { |
| input.servers[_].protocols[_] == "telnet" |
| } |
| |
| shell_accessible = true { |
| input.servers[_].protocols[_] == "ssh" |
| } |
部分规则的逻辑或
如下规则用于找出协议为telnet
或ssh
的server的id
:
| package example.logical_or |
| |
| shell_accessible[server.id] { |
| server := input.servers[_] |
| server.protocols[_] == "telnet" |
| } |
| |
| shell_accessible[server.id] { |
| server := input.servers[_] |
| server.protocols[_] == "ssh" |
| } |
rego语法
标量
rego支持字符串、数字、布尔和null
| greeting := "Hello" |
| max_height := 42 |
| pi := 3.14159 |
| allowed := true |
| location := null |
字符串
支持两种类型的字符串:双引号包围的字符串和原始字符串,后者一般用于正则表达式。
复合值
定义了数值集合
| cube := {"width": 3, "height": 4, "depth":true} |
对象
可以看作golang的map
| ips_by_port := { |
| 80: ["1.1.1.1", "1.1.1.2"], |
| 443: ["2.2.2.1"], |
| } |
| ips_by_port[80] |
| |
| $ [ |
| "1.1.1.1", |
| "1.1.1.2" |
| ] |
数组
数组使用下标进行索引
| s1 := [1, 2, 3] |
| s2 := [2, 1, 3] |
集合
它是唯一值的无序集合。它没有key,且无法使用下标进行索引。注意在解析为JSON格式时,集合体现为数组格式。
| s1 := {1, 2, 3} |
| s2 := {2, 1, 3} |
变量
变量位于规则的head和body,规则head中的变量可以认为是输入或输出,如果提供了确定的值,则认为是输入,否则认为是输出。例如:
| sites := [ |
| {"name": "prod"}, |
| {"name": "smoke1"}, |
| {"name": "dev"} |
| ] |
| |
| q[name] { name := sites[_].name } |
如下x
并没有绑定到某个值,则返回所有x
和q[x]
的值
| q[x] |
| $ +----------+----------+ |
| | x | q[x] | |
| +----------+----------+ |
| | "dev" | "dev" | |
| | "prod" | "prod" | |
| | "smoke1" | "smoke1" | |
| +----------+----------+ |
如下"dev"是一个确定的值,作为输入,用于判断name
中是否存在该值
引用
有两种方式访问嵌套文档:点访问方式和方括号访问方式,如下:
| sites[0].servers[1].hostname |
| sites[0]["servers"][1]["hostname"] |
变量键
引用可以使用变量作为键,这种方式用于选择所有元素的值
| sites[i].servers[j].hostname |
| |
| $ + |
| | i | j | sites[i].servers[j].hostname | |
| + |
| | 0 | 0 | "hydrogen" | |
| | 0 | 1 | "helium" | |
| | 0 | 2 | "lithium" | |
| | 1 | 0 | "beryllium" | |
| | 1 | 1 | "boron" | |
| | 1 | 2 | "carbon" | |
| | 2 | 0 | "nitrogen" | |
| | 2 | 1 | "oxygen" | |
| + |
如果迭代时不需要用到变量,则可以使用下划线_
| sites[_].servers[_].hostname |
| $ + |
| | sites[_].servers[_].hostname | |
| + |
| | "hydrogen" | |
| | "helium" | |
| | "lithium" | |
| | "beryllium" | |
| | "boron" | |
| | "carbon" | |
| | "nitrogen" | |
| | "oxygen" | |
| + |
复合键
| s := {[1, 2], [1, 4], [2, 6]} |
| s[[1, x]] |
| $ + |
| | x | s[[1, x]] | |
| + |
| | 2 | [1,2] | |
| | 4 | [1,4] | |
| + |
多表达式
规则通常是多表达式的,包含到documents的引用。下面定义了一个数组,每个数组包含一个服务的应用名称和主机名称
| apps_and_hostnames[[name, hostname]] { |
| some i, j, k |
| name := apps[i].name |
| server := apps[i].servers[_] |
| sites[j].servers[k].name == server |
| hostname := sites[j].servers[k].hostname |
| } |
| apps_and_hostnames[x] |
| $ + |
| | x | apps_and_hostnames[x] | |
| + |
| | ["mongodb","oxygen"] | ["mongodb","oxygen"] | |
| | ["mysql","carbon"] | ["mysql","carbon"] | |
| | ["mysql","lithium"] | ["mysql","lithium"] | |
| | ["web","beryllium"] | ["web","beryllium"] | |
| | ["web","boron"] | ["web","boron"] | |
| | ["web","helium"] | ["web","helium"] | |
| | ["web","hydrogen"] | ["web","hydrogen"] | |
| | ["web","nitrogen"] | ["web","nitrogen"] | |
| + |
推导式
与规则类似,推导式有一个head和一个body。
| region := |
| names := [name | sites[i].region == region; name := sites[i].name] |
| $ +-----------------+--------+ |
| | names | region | |
| +-----------------+--------+ |
| | [,] | | |
| +-----------------+--------+ |
这与python中的推导式类似
| |
| names = [site.name for site in sites if site.region == "west"] |
数组推导式
格式如下:
| app_to_hostnames[app_name] = hostnames { |
| app := apps[_] |
| app_name := app.name |
| hostnames := [hostname | name := app.servers[_] |
| s := sites[_].servers[_] |
| s.name == name |
| hostname := s.hostname] |
| } |
| app_to_hostnames[app] |
| $ + |
| | app | app_to_hostnames[app] | |
| + |
| | "mongodb" | ["oxygen"] | |
| | "mysql" | ["lithium","carbon"] | |
| | "web" | ["hydrogen","helium","beryllium","boron","nitrogen"] | |
| + |
对象推导式
格式如下:
| { <key>: <term> | <body> } |
注意key不能有冲突
| app_to_hostnames := {app.name: hostnames | |
| app := apps[_] |
| hostnames := [hostname | |
| name := app.servers[_] |
| s := sites[_].servers[_] |
| s.name == name |
| hostname := s.hostname] |
| } |
| app_to_hostnames[app] |
| $ + |
| | app | app_to_hostnames[app] | |
| + |
| | "mongodb" | ["oxygen"] | |
| | "mysql" | ["lithium","carbon"] | |
| | "web" | ["hydrogen","helium","beryllium","boron","nitrogen"] | |
| + |
集合推导式
格式如下:
| a := [1, 2, 3, 4, 3, 4, 3, 4, 5] |
| b := {x | x = a[_]} |
| $ +---------------------+-------------+ |
| | a | b | |
| +---------------------+-------------+ |
| | [1,2,3,4,3,4,3,4,5] | [1,2,3,4,5] | |
| +---------------------+-------------+ |
规则
集合
返回结果是一个集合
| hostnames[name] { name := sites[_].servers[_].hostname } |
| hostnames[name] |
| $ + |
| | name | hostnames[name] | |
| + |
| | "beryllium" | "beryllium" | |
| | "boron" | "boron" | |
| | "carbon" | "carbon" | |
| + |
对象
返回结果是一个可检索的对象
| apps_by_hostname[hostname] = app { |
| some i |
| server := sites[_].servers[_] |
| hostname := server.hostname |
| apps[i].servers[_] == server.name |
| app := apps[i].name |
| } |
| apps_by_hostname["helium"] |
| $ "web" |
增量定义
增量定义实际就是逻辑或
如下,将servers
和containers
数据抽象为 instances
:
| instances[instance] { |
| server := sites[_].servers[_] |
| instance := {"address": server.hostname, "name": server.name} |
| } |
| |
| instances[instance] { |
| container := containers[_] |
| instance := {"address": container.ipaddress, "name": container.name} |
| } |
完整定义
除了使用部分规则定义集合和对象,还可以使用完整规则,完整规则忽略了head中的key,通常用于表示常量
完整定义一次性赋予一个值,如下将32和4赋值给max_memory就会发生错误
| |
| max_memory = 32 |
| |
| |
| max_memory = 4 |
| |
| $ module.rego:8: eval_conflict_error: complete rules must not produce multiple outputs |
使用:=
时,每个包中只能声明一个相同名称的完整定义:
| package example |
| |
| pi := 3.14 |
| |
| # some other rules... |
| |
| pi := 3.14156 # Redeclaration error because 'pi' already declared above. |
函数
Rego支持自定义函数,这些函数可以与内置函数一样调用。函数可以有任意多个输入,但只能有一个输出
| trim_and_split(s) = x { |
| t := trim(s, " ") |
| x := split(t, ".") |
| } |
| trim_and_split(" foo.bar ") |
| $ [ |
| "foo", |
| "bar" |
| ] |
一个函数可以定义多次,用于实现通过条件来选择所要执行的函数:
| q(1, x) = y { |
| y := x |
| } |
| q(2, x) = y { |
| y := x*4 |
| } |
但在调用时需要注意,入参不能匹配多个函数
| r(1, x) = y { |
| y := x |
| } |
| |
| r(x, 2) = y { |
| y := x*4 |
| } |
| r(1, 2) |
| $ module.rego:3: eval_conflict_error: functions must not produce multiple outputs for same inputs |
注意,如果无法匹配到函数,则结果是未定义的:
| s(x, 2) = y { |
| y := x * 4 |
| } |
| s(5, 3) |
| $ undefined decision |
否定
| t { |
| greeting := "hello" |
| not greeting == "goodbye" |
| } |
下面用于分别找出在和不在prod
环境的app:
| prod_servers[name] { |
| site := sites[_] |
| site.name == "prod" |
| name := site.servers[_].name |
| } |
| |
| apps_in_prod[name] { |
| app := apps[_] |
| server := app.servers[_] |
| prod_servers[server] #过滤出在prod的app,行与行之间是与的关系,如果不存在则不会执行下一个语句,即name不会被赋值 |
| name := app.name |
| } |
| |
| apps_not_in_prod[name] { |
| name := apps[_].name |
| not apps_in_prod[name] |
| } |
全量(FOR ALL)
Rego没有直接的方式来表示全量("FOR ALL")。例如需要找出名称非"bitcoin-miner"的app时,使用如下方式是错误的,无论apps中是否存在名为"bitcoin-miner"的app,最终都会返回true
| no_bitcoin_miners { |
| app := apps[_] |
| app.name != "bitcoin-miner" |
| } |
可以使用如下方式来实现上述目的:
| no_bitcoin_miners_using_negation { |
| not any_bitcoin_miners |
| } |
| |
| any_bitcoin_miners { |
| some i |
| app := apps[i] |
| app.name == "bitcoin-miner" |
| } |
此外还可以使用推导式实现:
| no_bitcoin_miners_using_comprehension { |
| bitcoin_miners := {app | app := apps[_]; app.name == "bitcoin-miner"} |
| count(bitcoin_miners) == 0 |
| } |
模块
在rego中,策略被定义在模块中,一个模块需要包含:
注释
使用#
进行注释
package
包可以将一个或多个模块中的规则打包到特定的命名空间中。
import
模块中可以使用data
和input
引用文本
如在 kubernetes.admission
中定义了一个规则 deny:
| package kubernetes.admission |
| |
| deny[msg] { |
| input.request.kind.kind == "Pod" |
| some i |
| image := input.request.object.spec.containers[i].image |
| not startswith(image, "hooli.com/") |
| msg := sprintf("image '%v' comes from untrusted registry", [image]) |
| } |
在另一个包中可以通过如下方式引用deny规则:
| { |
| "user": "alice", |
| "action": "read", |
| "object": "id123", |
| "type": "dog" |
| } |
| package app.rbac |
| import data.kubernetes.admission |
| |
| deny[input.user] |
With 关键字
with关键字允许查询以编程方式指定嵌套在input 文档 和data 文档下的值。with
关键字充当表达式的修饰符。一个表达式可以有零或多个with
修饰符。
格式如下,必须是对input文档或data文档中的值的引用。当应用于data文档时,不能尝试部分定义虚拟文档(例如一个给定的虚拟文档路径为data.foo.bar
,如果策略试图替换data.foo.bar.baz
,那么编译器将产生错误)。
| <expr> with <target-1> as <value-1> [with <target-2> as <value-2> [...]] |
举例如下:
| allow with input as {"user": "charlie", "method": "GET"} with data.roles as {"dev": ["charlie"]} |
with
关键字仅影响连接表达符,后续表达式将看到未修改的值。下面是一种例外(input.foo=1,input.bar=2),outer中的输入在middle中进行了计算
| inner := [x, y] { |
| x := input.foo |
| y := input.bar |
| } |
| |
| middle := [a, b] { |
| a := inner with input.foo as 100 |
| b := input |
| } |
| |
| outer := result { |
| result := middle with input as {"foo": 200, "bar": 300} |
| } |
| { |
| "inner": [ |
| 1, |
| 2 |
| ], |
| "middle": [ |
| [ |
| 100, |
| 2 |
| ], |
| { |
| "bar": 2, |
| "foo": 1 |
| } |
| ], |
| "outer": [ |
| [ |
| 100, |
| 300 |
| ], |
| { |
| "bar": 300, |
| "foo": 200 |
| } |
| ] |
| } |
Default 关键字
default关键字允许策略为具有完整定义的规则生成的文档定义默认值。格式如下:
用法如下,如果没有default,则会返回undefined
| default allow = false |
| |
| allow { |
| input.user == "bodddb" |
| input.method == "GEdddT" |
| } |
| |
| allow { |
| input.user == "aliddce" |
| } |
Else 关键字
与编程语言中的else类似
| authorize = "allow" { |
| input.user == "superuser" |
| } else = "deny" { |
| input.path[0] == "admin" |
| input.source_network == "external" |
| } |
操作符
成员和迭代:in
需要import future.keywords。成员操作符in
用于检查一个元素是否存在于array, set, 或 object中,返回true
或false
。
| import future.keywords.in |
| |
| p = [x, y, z] { |
| x := 3 in [1, 2, 3] # array |
| y := 3 in {1, 2, 3} # set |
| z := 3 in {"foo": 1, "bar": 3} # object |
| } |
| { |
| "p": [ |
| true, |
| true, |
| true |
| ] |
| } |
当在in
操作符左侧提供两个参数,且右侧为object或array,则第一个参数作为key(object)或index(array):
| import future.keywords.in |
| |
| p := [ x, y ] { |
| x := "foo", "bar" in {"foo": "bar"} # key, val with object |
| y := 2, "baz" in ["foo", "bar", "baz"] # key, val with array |
| } |
注意在列表(如集合或数组以及函数参数)上下文中需要使用圆括号来让两侧参数一一对应
| import future.keywords.in |
| |
| p := x { |
| x := { 0, 2 in [2] } #这是一个集合,表示0和2 in [2] |
| } |
| q := x { |
| x := { (0, 2 in [2]) }#这是一个集合,但计算的是0, 2 in [2] |
| } |
| w := x { |
| x := g((0, 2 in [2]))#g(x)只有一个参数,需要使用圆括号括起来 |
| } |
| z := x { |
| x := f(0, 2 in [2])#f(x)有两个参数,第一个参数是0,第二个参数是2 in [2] |
| } |
| |
| f(x, y) = sprintf("two function arguments: %v, %v", [x, y]) |
| g(x) = sprintf("one function argument: %v", [x]) |
与not
结合使用,可以很方便地断言一个元素是否是数组的成员:
| import future.keywords.in |
| |
| deny { |
| not "admin" in input.user.roles |
| } |
| |
| test_deny { |
| deny with input.user.roles as ["operator", "user"] |
| } |
使用some
,可以根据不同的类型引入新的变量
| import future.keywords.in |
| |
| p[x] { |
| some x in ["a", "r", "r", "a", "y"] |
| } |
| |
| q[x] { |
| some x in {"s", "e", "t"} |
| } |
| |
| r[x] { |
| some x in {"foo": "bar", "baz": "quz"} |
| } |
| { |
| "p": [ |
| "a", |
| "r", |
| "y" |
| ], |
| "q": [ |
| "e", |
| "s", |
| "t" |
| ], |
| "r": [ |
| "bar", |
| "quz" |
| ] |
| } |
使用两个参数可以检索object的关键字和array的索引
| import future.keywords.in |
| |
| p[x] { |
| some x, "r" in ["a", "r", "r", "a", "y"] |
| } |
| |
| q[x] = y { |
| some x, y in ["a", "r", "r", "a", "y"] |
| } |
| |
| r[y] = x { |
| some x, y in {"foo": "bar", "baz": "quz"} |
| } |
| { |
| "p": [ |
| 1, |
| 2 |
| ], |
| "q": { |
| "0": "a", |
| "1": "r", |
| "2": "r", |
| "3": "a", |
| "4": "y" |
| }, |
| "r": { |
| "bar": "foo", |
| "quz": "baz" |
| } |
| } |
some
变量的任何参数都可以是复合的非基础值:
| import future.keywords.in |
| |
| p[x] = y { |
| some x, {"foo": y} in [{"foo": 100}, {"bar": 200}]#x为key为foo的数组索引,y为key为foo的值 |
| } |
| p[x] = y { |
| some {"bar": x}, {"foo": y} in {{"bar": "b"}: {"foo": "f"}} # x为bar的值,y为foo的值 |
| } |
| { |
| "p": { |
| "0": 100, |
| "b": "f" |
| } |
| } |
等式:赋值,比较和联合
Rego支持三种等式:赋值(:=),比较()和联合(=)。建议使用赋值(:=)和比较()。
赋值 :=
可以使用一种简单的解构形式将数组中的值解包并将其分配给变量
| address := ["3 Abbey Road", "NW8 9AY", "London", "England"] |
| |
| in_london { |
| [_, _, city, country] := address |
| city == "London" |
| country == "England" |
| } |
| { |
| "address": [ |
| "3 Abbey Road", |
| "NW8 9AY", |
| "London", |
| "England" |
| ], |
| "in_london": true |
| } |
比较 ==
联合 =
Rego会将比较为真的值赋于变量。联合可以赋予变量使表达式为true的值。
| sites[i].servers[j].name = apps[k].servers[m] |
| +---+---+---+---+ |
| | i | j | k | m | |
| +---+---+---+---+ |
| | 0 | 0 | 0 | 0 | |
| | 0 | 1 | 0 | 1 | |
| | 0 | 2 | 1 | 0 | |
| | 1 | 0 | 0 | 2 | |
| | 1 | 1 | 0 | 3 | |
| | 1 | 2 | 1 | 1 | |
| | 2 | 0 | 0 | 4 | |
| | 2 | 1 | 2 | 0 | |
| +---+---+---+---+ |
比较表达式
| a == b # `a` is equal to `b`. |
| a != b # `a` is not equal to `b`. |
| a < b # `a` is less than `b`. |
| a <= b # `a` is less than or equal to `b`. |
| a > b # `a` is greater than `b`. |
| a >= b # `a` is greater than or equal to `b`. |
内置函数
内置函数的格式如下:
| <name>(<arg-1>, <arg-2>, ..., <arg-n>) |
错误
默认情况下,遇到运行时错误的内置函数调用会将结果设为undefined (通常可以被视为false),且不会停止策略计算。这种方式可以保证在使用调用内置函数时,输入无效参数不会导致整个策略停止计算。
Assignment and Equality
| |
| x := input.foo.bar.baz |
| |
| |
| x == y |
| |
| |
| x == {"foo", "bar"} |
| |
| |
| |
| {"foo", "bar"} == x |
Lookup
Arrays
| |
| val := arr[0] |
| |
| |
| "foo" == arr[0] |
| |
| |
| "foo" == arr[i] |
| |
| |
| val := arr[count(arr)-1] |
| |
| |
| some 0, val in arr |
| 0, "foo" in arr |
| some i, "foo" in arr |
Objects
| |
| val := obj["foo"] |
| |
| |
| "bar" == obj["foo"] |
| |
| |
| |
| "bar" == obj.foo |
| |
| |
| obj.foo |
| |
| |
| k := "foo" |
| obj[k] |
| |
| |
| obj.foo.bar.baz |
| |
| |
| not obj.foo.bar.baz |
| |
| |
| o := {"foo": false} |
| |
| false in o |
| |
| "foo", false in o |
Sets
| # check if "foo" belongs to the set |
| a_set["foo"] |
| |
| # check if "foo" DOES NOT belong to the set |
| not a_set["foo"] |
| |
| # check if the array ["a", "b", "c"] belongs to the set |
| a_set[["a", "b", "c"]] |
| |
| # find all arrays of the form [x, "b", z] in the set |
| a_set[[x, "b", z]] |
| |
| # with `import future.keywords.in` |
| "foo" in a_set |
| not "foo" in a_set |
| some ["a", "b", "c"] in a_set |
| some [x, "b", z] in a_set |
Iteration
Arrays
| # iterate over indices i |
| arr[i] |
| |
| # iterate over values |
| val := arr[_] |
| |
| # iterate over index/value pairs |
| val := arr[i] |
| |
| # with `import future.keywords.in` |
| some val in arr # iterate over values |
| some i, _ in arr # iterate over indices |
| some i, val in arr # iterate over index/value pairs |
Objects
| # iterate over keys |
| obj[key] |
| |
| # iterate over values |
| val := obj[_] |
| |
| # iterate over key/value pairs |
| val := obj[key] |
| |
| # with `import future.keywords.in` |
| some val in obj # iterate over values |
| some key, _ in obj # iterate over keys |
| some key, val in obj # key/value pairs |
Sets
| # iterate over values |
| set[val] |
| |
| # with `import future.keywords.in` |
| some val in set |
Advanced
| |
| foo[k].bar.baz[i] == 7 |
| |
| |
| foo[k1] == bar[k2] |
| |
| |
| foo[k1] == foo[k2] |
| |
| |
| foo[k].bar.baz[i] == 7 |
For All
| # assert no values in set match predicate |
| count({x | set[x]; f(x)}) == 0 |
| |
| # assert all values in set make function f true |
| count({x | set[x]; f(x)}) == count(set) |
| |
| # assert no values in set make function f true (using negation and helper rule) |
| not any_match |
| |
| # assert all values in set make function f true (using negation and helper rule) |
| not any_not_match |
| any_match { |
| set[x] |
| f(x) |
| } |
| |
| any_not_match { |
| set[x] |
| not f(x) |
| } |
Rules
In the examples below ...
represents one or more conditions.
Constants
| a = {1, 2, 3} |
| b = {4, 5, 6} |
| c = a | b |
Conditionals (Boolean)
| |
| p = true { ... } |
| |
| |
| |
| p { ... } |
Conditionals
| default a = 1 |
| a = 5 { ... } |
| a = 100 { ... } |
Incremental
| |
| a_set[x] { ... } |
| a_set[y] { ... } |
| |
| |
| a_map[x] = y { ... } |
| a_map[w] = z { ... } |
Ordered (Else)
| default a = 1 |
| a = 5 { ... } |
| else = 10 { ... } |
Functions (Boolean)
| f(x, y) { |
| ... |
| } |
| |
| # OR |
| |
| f(x, y) = true { |
| ... |
| } |
| |
Functions (Conditionals)
| f(x) = "A" { x >= 90 } |
| f(x) = "B" { x >= 80 |
| f(x) = "C" { x >= 70 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
2021-02-09 jenkins:实现Jenkinsfile与Json的转换