diff --git a/doc/language_reference/language_reference.md b/doc/language_reference/language_reference.md index aa9a04a3c..76b685b3c 100644 --- a/doc/language_reference/language_reference.md +++ b/doc/language_reference/language_reference.md @@ -334,6 +334,9 @@ term ::= "_" (* wildcard *) | match_term (* match term *) | ite_term (* if-then-else term *) | for_term (* for-loop *) + | "continue" (* abort current loop iteration *) + | "break" (* break out of a loop *) + | return_term (* return from a function *) | vardecl_term (* local variable declaration *) ``` @@ -398,6 +401,7 @@ apply_term ::= func_name "(" [expr (,expr)*] ")" var_term ::= var_name ite_term ::= "if" term term [ "else" term ] for_term ::= "for" "(" var_name "in" expr ")" term +return_term ::= "return" [expr] vardecl_term ::= "var" var_name match_term ::= "match" "(" expr ")" "{" match_clause (,match_clause)*"}" diff --git a/doc/tutorial/tutorial.md b/doc/tutorial/tutorial.md index 89ca4dc32..f79268c35 100644 --- a/doc/tutorial/tutorial.md +++ b/doc/tutorial/tutorial.md @@ -609,6 +609,12 @@ Evaluation order can be controlled using several constructs: 1. A for loop +1. `continue` terminates current loop iteration + +1. `break` terminates a loop + +1. `return [expr]` returns from a function + The following example illustrates the first four of these constructs. Loops are explained [below](#container-types-flatmap-and-for-loops). ``` @@ -621,7 +627,7 @@ function addr_port(ip: ip_addr_t, proto: string, preferred_port: bit<16>): strin if (preferred_port != 0) preferred_port // return preferred_port if specified else - 16'd80 // assume HTTP otherwise + return "${ip}:80" // assume HTTP otherwise } }; // semicolon required for sequencing // Return the address:port string @@ -629,12 +635,12 @@ function addr_port(ip: ip_addr_t, proto: string, preferred_port: bit<16>): strin } ``` -The result computed by a function is the result of the last expression -evaluated. There is no `return` statement. If the `else` is -missing the value `()` (empty tuple) is used for the `else` branch. -In `match` expressions the patterns must cover -all possible cases (for instance, the match expression above would not -be correct without the last "catch-all" (`_`) case). +The result computed by a function is the value of the last expression evaluated +or the value produced by the first `return` statement encountered when +evaluating the function. If the `else` is missing the value `()` (empty tuple) +is used for the `else` branch. In `match` expressions the patterns must cover +all possible cases (for instance, the match expression above would not be +correct without the last "catch-all" (`_`) case). DDlog variables must always be initialized when declared. In this example, the `port` variable is assigned the result of the `match` @@ -841,6 +847,35 @@ Loops can only iterate over container types: sets, maps, and vectors. When iter and vectors, the loop variable (`s` in the above example) has the same type as elements of the container (e.g., `string`). When iterating over maps, the loop variable is a 2-tuple `(key,value)`. +A `continue` statement used anywhere inside the body of a loop terminates the +current loop iteration: + +``` +// Returns only even elements of the vector. +function evens(vec: Vec): Vec = { + var res: Vec = vec_empty(); + for (x in vec) { + if (x % 2 != 0) { continue }; + vec_push(res, x) + }; + res +} +``` + +A `break` statement used anywhere inside the body of a loop terminates the loop: + +``` +// Returns prefix of `vec` before the first occurrence of value `v`. +function prefixBefore(vec: Vec<'A>, v: 'A): Vec<'A> = { + var res: Vec<'A> = vec_empty(); + for (x in vec) { + if (x == v) { break }; + vec_push(res, x) + }; + res +} +``` + #### Rules with multiple heads The following program computes the sums and products of pairs of values from `X`: diff --git a/test/datalog_tests/tutorial.ast.expected b/test/datalog_tests/tutorial.ast.expected index 28c224dfb..61033adc9 100644 --- a/test/datalog_tests/tutorial.ast.expected +++ b/test/datalog_tests/tutorial.ast.expected @@ -6,6 +6,8 @@ typedef Bytes = Bytes{b3: bit<8>, b2: bit<8>, b1: bit<8>, b0: bit<8>} typedef Category = CategoryStarWars{} | CategoryOther{} typedef Endpoint = Endpoint{ip: ip_addr_t, proto: string, preferred_port: bit<16>} typedef EndpointString = EndpointString{s: string} +typedef Evens = Evens{evens_and_odds: std.Vec, evens: std.Vec} +typedef EvensAndOdds = EvensAndOdds{vec: std.Vec} typedef First5 = First5{str: string} typedef Flow = Flow{lr: bigint, stage: stage, prio: bigint, matchStr: string, actionStr: string} typedef Flow1 = Flow1{lr: bigint, stage: stage, prio: bigint, matchStr: string, actionStr: string} @@ -30,6 +32,7 @@ typedef Packet = Packet{pkt: eth_pkt_t} typedef Person = Person{name: string, nationality: string, occupation: string} typedef Phrases = Phrases{phrase: string} typedef Pow2 = Pow2{p: string} +typedef Prefix = Prefix{vec: std.Vec} typedef Price = Price{item: string, vendor: string, price: bit<64>} typedef Product = Product{x: bit<16>, y: bit<16>, prod: bit<16>} typedef SanitizedEndpoint = SanitizedEndpoint{ep: string} @@ -43,6 +46,7 @@ typedef TopScore = TopScore{school: string, top_score: bit<16>} typedef UDPDstPort = UDPDstPort{port: bit<16>} typedef UDPDstPort2 = UDPDstPort2{port: bit<16>} typedef UUID = bit<128> +typedef Vector = Vector{vec: std.Vec, sep: string} typedef Word1 = Word1{word: string, cat: Category} typedef Word2 = Word2{word: string, cat: Category} typedef WorstPrice = WorstPrice{item: string, price: bit<64>} @@ -79,7 +83,7 @@ function addr_port (ip: ip_addr_t, proto: string, preferred_port: bit<16>): stri _ -> if (preferred_port != 16'd0) { preferred_port } else { - 16'd80 + return (("" ++ ip_addr_t2string(ip)) ++ ":80") } }; ((("" ++ ip_addr_t2string(ip)) ++ ":") ++ std.__builtin_2string(port))) @@ -97,6 +101,17 @@ function best_vendor (g: std.Group<(string, bit<64>)>): (string, bit<64>) = } }; (min_vendor, min_price)))) +function evens (vec: std.Vec): std.Vec = + ((var res: std.Vec) = std.vec_empty(); + (for (x in vec) { + (if ((x % 2) != 0) { + continue + } else { + () + }; + std.vec_push(res, x)) + }; + res)) function ip_addr_t2string (ip: ip_addr_t): string = ((((((("" ++ std.__builtin_2string(ip.addr[31:24])) ++ ".") ++ std.__builtin_2string(ip.addr[23:16])) ++ ".") ++ std.__builtin_2string(ip.addr[15:8])) ++ ".") ++ std.__builtin_2string(ip.addr[7:0])) function ip_from_bytes (b3: bit<8>, b2: bit<8>, b1: bit<8>, b0: bit<8>): ip_addr_t = @@ -132,6 +147,17 @@ function pkt_udp_port2 (pkt: eth_pkt_t): std.Option> = EthPacket{.src=_, .dst=_, .payload=EthIP6{.ip6=IP6Pkt{.ttl=_, .src=_, .dst=_, .payload=IPUDP{.udp=UDPPkt{.src=_, .dst=var port, .len=_}}}}} -> std.Some{.x=port}, _ -> std.None{} } +function prefixBefore (vec: std.Vec<'A>, v: 'A): std.Vec<'A> = + ((var res: std.Vec<'A>) = std.vec_empty(); + (for (x in vec) { + (if (x == v) { + break + } else { + () + }; + std.vec_push(res, x)) + }; + res)) extern function split (s: string, sep: string): std.Vec function split_ip_list (x: string): std.Vec = split(x, " ") @@ -264,6 +290,8 @@ input relation Blacklisted [Blacklisted] input relation Bytes [Bytes] input relation Endpoint [Endpoint] output relation EndpointString [EndpointString] +output relation Evens [Evens] +input relation EvensAndOdds [EvensAndOdds] output relation First5 [First5] output relation Flow [Flow] output relation Flow1 [Flow1] @@ -286,6 +314,7 @@ input relation Packet [Packet] input relation Person [Person] output relation Phrases [Phrases] output relation Pow2 [Pow2] +output relation Prefix [Prefix] input relation Price [Price] output relation Product [Product] output relation SanitizedEndpoint [SanitizedEndpoint] @@ -299,6 +328,7 @@ output relation TargetAudience [Person] output relation TopScore [TopScore] output relation UDPDstPort [UDPDstPort] output relation UDPDstPort2 [UDPDstPort2] +input relation Vector [Vector] input relation Word1 [Word1] input relation Word2 [Word2] output relation WorstPrice [WorstPrice] @@ -316,6 +346,8 @@ First5(.str=string_slice_unsafe(p, 64'd0, 64'd5)) :- Phrases(.phrase=p). SanitizedEndpoint(.ep=endpoint) :- Endpoint(.ip=ip, .proto=proto, .preferred_port=preferred_port), var endpoint = addr_port(ip, proto, preferred_port), not Blacklisted(.ep=endpoint). HostIP(.host=host, .addr=addr) :- HostAddress(.host=host, .addrs=addrs), var addr = FlatMap(split_ip_list(addrs)). HostIPVSep(.host=host, .addrs=vaddrs) :- HostAddress(.host=host, .addrs=addrs), var vaddrs = vsep(split_ip_list(addrs)). +Evens(.evens_and_odds=vec, .evens=evens(vec)) :- EvensAndOdds(.vec=vec). +Prefix(.vec=prefixBefore(vec, sep)) :- Vector(.vec=vec, .sep=sep). Sum(.x=x, .y=y, .sum=(x + y)), Product(.x=x, .y=y, .prod=(x * y)) :- X(.x=x), X(.x=y). BestPrice(.item=item, .price=best_price) :- Price(.item=item, .vendor=_, .price=price), var best_price = Aggregate((item), std.group_min(price)). diff --git a/test/datalog_tests/tutorial.dat b/test/datalog_tests/tutorial.dat index 76163e82c..318d4446b 100644 --- a/test/datalog_tests/tutorial.dat +++ b/test/datalog_tests/tutorial.dat @@ -142,6 +142,22 @@ dump HostIP; echo HostIPVSep:; dump HostIPVSep; +start; +insert EvensAndOdds([0,1,2,3,4,5]), +insert EvensAndOdds([1,3,5,7]), +commit; + +echo Evens:; +dump Evens; + +start; +insert Vector(["a", "--", "b"], "--"), +insert Vector(["--", "a", "b"], "--"), +insert Vector(["a", "b", "--"], "--"), +commit; + +echo Prefix:; +dump Prefix; start; diff --git a/test/datalog_tests/tutorial.dl b/test/datalog_tests/tutorial.dl index cc5f0154f..0fe699385 100644 --- a/test/datalog_tests/tutorial.dl +++ b/test/datalog_tests/tutorial.dl @@ -128,7 +128,7 @@ function addr_port(ip: ip_addr_t, if (preferred_port != 0) preferred_port else - 16'd80 // assume HTTP + return "${ip}:80" // assume HTTP } }; "${ip}:${port}" @@ -196,6 +196,40 @@ output relation HostIPVSep(host: bit<64>, addrs: string) HostIPVSep(host, vaddrs) :- HostAddress(host, addrs), var vaddrs = vsep(split_ip_list(addrs)). +/* + * Example: `continue` and `break` statements. + */ + +// Returns only even elements of the vector. +function evens(vec: Vec): Vec = { + var res: Vec = vec_empty(); + for (x in vec) { + if (x % 2 != 0) { continue }; + vec_push(res, x) + }; + res +} + +input relation EvensAndOdds(vec: Vec) +output relation Evens(evens_and_odds: Vec, evens: Vec) + +Evens(vec, evens(vec)) :- EvensAndOdds(vec). + +// Returns prefix of `vec` before the first occurrence of value `v`. +function prefixBefore(vec: Vec<'A>, v: 'A): Vec<'A> = { + var res: Vec<'A> = vec_empty(); + for (x in vec) { + if (x == v) { break }; + vec_push(res, x) + }; + res +} + +input relation Vector(vec: Vec, sep: string) +output relation Prefix(vec: Vec) + +Prefix(prefixBefore(vec, sep)) :- Vector(vec, sep). + /* * Example: Multiple heads */ diff --git a/test/datalog_tests/tutorial.dump.expected b/test/datalog_tests/tutorial.dump.expected index e0a6c60f6..7ff1bc06d 100644 --- a/test/datalog_tests/tutorial.dump.expected +++ b/test/datalog_tests/tutorial.dump.expected @@ -70,6 +70,13 @@ HostIPVSep: HostIPVSep{1,"10.10.10.101\n10.10.10.102\n"} HostIPVSep{2,"192.168.0.1\n"} HostIPVSep{3,"192.168.0.3\n192.168.0.4\n192.168.0.5\n192.168.0.6\n"} +Evens: +Evens{[0,1,2,3,4,5],[0,2,4]} +Evens{[1,3,5,7],[]} +Prefix: +Prefix{[]} +Prefix{["a"]} +Prefix{["a","b"]} Sum: Sum{10,10,20} Sum{10,20,30}