diff --git a/doc/language_reference/language_reference.md b/doc/language_reference/language_reference.md index 54389b3da..c430c09df 100644 --- a/doc/language_reference/language_reference.md +++ b/doc/language_reference/language_reference.md @@ -144,9 +144,9 @@ union_type ::= (constructor "|")* constructor type_alias ::= type_name (* type name declared using typedef*) ["<" type_spec [("," type_spec)*] ">"] (* type arguments *) -constructor ::= cons_name (* constructor without fields *) - | cons_name "{" [field ("," field)*] "}" -field ::= field_name ":" simple_type_spec +constructor ::= [attributes] cons_name (* constructor without fields *) + | [attributes] cons_name "{" [field ("," field)*] "}" +field ::= [attributes] field_name ":" simple_type_spec ``` ### Constraints on types @@ -590,8 +590,8 @@ rhs_clause ::= atom (* 1.atom *) | "not" atom (* 2.negated atom *) | expr (* 3.condition *) | expr "=" expr (* 4.assignment *) - | "FlatMap" "(" var_name "=" expr ")" (* 5.flat map *) - | "var " var_name = "Aggregate" "(" (* 6.aggregation *) + | "var" var_name "=" "FlatMap" "(" expr ")" (* 5.flat map *) + | "var" var_name = "Aggregate" "(" (* 6.aggregation *) "(" [var_name ("," var_name)*] ")" "," func_name "(" expr ")" ")" ``` @@ -653,7 +653,7 @@ variable, e.g.: Logical_Switch_Port_ips(lsp, mac, ip) :- Logical_Switch_Port_addresses(lsp, addrs), (mac, ips) = extract_mac(addrs), - FlatMap(ip = extract_ips(ips)) + var ip = FlatMap(extract_ips(ips)) ``` Here, `extract_ips` must return a *set* of IP addresses: @@ -781,15 +781,3 @@ blockStatement ::= "{" statement ( ";" statement )* "}" emptyStatement ::= "skip" ``` - -# Supported meta-attributes - -Only one attribute is supported at the moment, namely the `size` attribute, -applicable to `extern type` declarations. It specifies the size of the -corresponding Rust data type in bytes and serves as a hint to compiler -to optimize data structures layout. - -``` -#[size=4] -extern type IObj<'A> -``` diff --git a/doc/tutorial/tutorial.md b/doc/tutorial/tutorial.md index d738840d1..7830fcc22 100644 --- a/doc/tutorial/tutorial.md +++ b/doc/tutorial/tutorial.md @@ -783,6 +783,30 @@ SanitizedHTTPEndpoint(endpoint) :- not Blacklisted(endpoint). ``` +#### `@`-bindings + +When performing pattern matching inside rules, it is often helpful to +simultaneously refer to a struct and its individual fields by name. This can be +achieved using `@`-bindings: + +``` +typedef Book = Book { + author: string, + title: string +} + +input relation Library(book: Book) +input relation Author(name: string, born: u32) + +output relation BookByAuthor(book: Book, author: Author) + +BookByAuthor(b, author) :- + // Variable `b` will be bound to the entire `Book` struct; + // `author_name` will be bound to `b.author`. + Library(.book = b@Book{.author = author_name}), + author in Author(.name = author_name). +``` + #### Container types, FlatMap, and for-loops DDlog supports three built-in container data types: `Vec`, `Set`, and `Map`. These @@ -827,7 +851,7 @@ result that is the union of all the resulting sets: ``` HostIP(host, addr) :- HostAddress(host, addrs), - FlatMap(addr=split_ip_list(addrs)). + var addr = FlatMap(split_ip_list(addrs)). ``` You can read this rule as follows: @@ -1642,6 +1666,90 @@ purpose. They do not affect the performance of the DDlog program and cannot be used as an optimization technique (DDlog does use indexes for performance, but these indexes are constructed automatically and are not visible to the user). +## Meta-attributes + +Meta-attributes are annotations that can be attached to various DDlog program +declarations (types, constructors, fields, etc.). They affect compilation +process and program's external behavior in ways that are outside of the language +semantics. We describe currently supported meta-attributes in the following +sections. + +### Size attribute: `#[size=N]` + +This attribute is applicable to `extern type` declarations. It specifies the +size of the corresponding Rust data type in bytes and serves as a hint to compiler +to optimize data structures layout: + +``` +#[size=4] +extern type IObj<'A> +``` + +### `#[deserialize_from_array=func()]` + +This attribute is used in conjunction with `json.dl` library (and potentially +other serialization libraries). When working with JSON, it is common to have +to deserialize an array of entities that encodes a map, with each entity containing +a unique key. Deserializing directly to a map rather than a vector (which would +be the default representation) can be beneficial for performance if +frequent map lookups are required. + +The `deserialize_from_array` attribute can be associated with a field of a struct +of type `Map<'K,'V>`. The annotation takes a function name as its value. The function, +whose signature must be: `function f(V): K` is used to project each +value `V` to key `K`: + +``` +// We will be deserializing a JSON array of these structures. +typedef StructWithKey = StructWithKey { + key: u64, + payload: string +} + +// Key function. +function key_structWithKey(x: StructWithKey): u64 = { + x.key +} + +// This struct's serialized representaion is an array of +// `StructWithKey`; however it is deserialized into a map rather than +// vector. +typedef StructWithMap = StructWithMap { + #[deserialize_from_array=key_structWithKey()] + f: Map +} + +Example JSON representation of `StructWithMap` +{"f": [{"key": 100, "payload": "foo"}]} +``` + +### `#[rust="..."]` + +This attribute is applicable to type definitions, constructors, and individual +fields of a constructor. Its value is copied directly to the generated Rust +type definition. It is particularly useful in controling +serialization/deserialization behavior of the type. DDlog generates +`serde::Serialize` and `serde::Deserialize` implementations for all types in +the program. Meta-attributes defined in the `serde` Rust crate can be used to +control the behavior of these traits. For example, the following declaration: + +``` +#[rust="serde(tag = \"@type\")"] +typedef TaggedEnum = #[rust="serde(rename = \"t.V1\")"] + TVariant1 { b: bool } + | #[rust="serde(rename = \"t.V2\")"] + TVariant2 { u: u32 } +``` + +creates a type whose JSON representation is: + +``` +{"@type": "t.V1", "b": true} +``` + +Other useful serde attributes are `default` and `flatten`. See [serde +documentation](https://serde.rs/attributes.html) for details. + ## Input/output to DDlog DDlog offers several ways to feed data to a program: @@ -1656,6 +1764,8 @@ DDlog offers several ways to feed data to a program: 1. From a Java program. +1. From a Go program. + In the following sections, we expand on each method. ### Specifying ground facts statically in the program source code @@ -1707,6 +1817,11 @@ If you plan to use the library from a Java program, make sure to use See [Java API documentation](../java_api.md) for a detailed description of the Java API. +1. **Go** + +See [Go API documentation](../../go/README.md) for a detailed description of the +Go API. + 1. The text-based interface is implemented by an auto-generated executable `./playpen_ddlog/target/release/playpen_cli`. This interface is primarily meant for testing and debugging purposes, as it does not offer the same performance and diff --git a/test/datalog_tests/tutorial.dat b/test/datalog_tests/tutorial.dat index d43b61e1e..434c20ed9 100644 --- a/test/datalog_tests/tutorial.dat +++ b/test/datalog_tests/tutorial.dat @@ -142,6 +142,16 @@ dump HostIP; echo HostIPVSep:; dump HostIPVSep; +start; +insert Library(Book{"Ernest Hemingway", "For Whom the Bell Tolls"}), +insert Library(Book{"Dav Pilkey", "For Whom the Ball Rolls"}), +insert Author("Ernest Hemingway", 1899), +insert Author("Dav Pilkey", 1966), +commit; + +echo BookByAuthor; +dump BookByAuthor; + start; insert EvensAndOdds([0,1,2,3,4,5]), insert EvensAndOdds([1,3,5,7]), diff --git a/test/datalog_tests/tutorial.dl b/test/datalog_tests/tutorial.dl index 5c4f0a066..87ea68460 100644 --- a/test/datalog_tests/tutorial.dl +++ b/test/datalog_tests/tutorial.dl @@ -164,6 +164,25 @@ SanitizedEndpoint(endpoint) :- var endpoint = addr_port(ip, proto, preferred_port), not Blacklisted(endpoint). +/* + * Example: @-bindings. + */ +typedef Book = Book { + author: string, + title: string +} + +input relation Library(book: Book) +input relation Author(name: string, born: u32) + +output relation BookByAuthor(book: Book, author: Author) + +BookByAuthor(b, author) :- + // Variable `b` will be bound to the entire `Book` struct; + // `author_name` will be bound to `b.author`. + Library(.book = b@Book{.author = author_name}), + author in Author(.name = author_name). + /* * Example: recursion * (see path.dl) diff --git a/test/datalog_tests/tutorial.dump.expected b/test/datalog_tests/tutorial.dump.expected index c3c6d822d..7ec799f3d 100644 --- a/test/datalog_tests/tutorial.dump.expected +++ b/test/datalog_tests/tutorial.dump.expected @@ -70,6 +70,9 @@ HostIPVSep: HostIPVSep{.host = 1, .addrs = "10.10.10.101\n10.10.10.102\n"} HostIPVSep{.host = 2, .addrs = "192.168.0.1\n"} HostIPVSep{.host = 3, .addrs = "192.168.0.3\n192.168.0.4\n192.168.0.5\n192.168.0.6\n"} +BookByAuthor +BookByAuthor{.book = Book{.author = "Dav Pilkey", .title = "For Whom the Ball Rolls"}, .author = Author{.name = "Dav Pilkey", .born = 1966}} +BookByAuthor{.book = Book{.author = "Ernest Hemingway", .title = "For Whom the Bell Tolls"}, .author = Author{.name = "Ernest Hemingway", .born = 1899}} Evens: Evens{.evens_and_odds = [0, 1, 2, 3, 4, 5], .evens = [0, 2, 4]} Evens{.evens_and_odds = [1, 3, 5, 7], .evens = []}