Wildcards without the ? prefix (#422)

This commit is contained in:
Andrew Twyman 2025-09-12 13:08:17 -07:00 committed by GitHub
parent 7e04eb51ff
commit 5de08da32c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 287 additions and 262 deletions

View file

@ -3,24 +3,24 @@
``` ```
eth_dos_distance(src_or, src_key, dst_or, dst_key, distance_or, distance_key) = OR( eth_dos_distance(src_or, src_key, dst_or, dst_key, distance_or, distance_key) = OR(
eth_dos_distance_ind_0(?src_or, ?src_key, ?dst_or, ?dst_key, ?distance_or, ?distance_key), eth_dos_distance_ind_0(src_or, src_key, dst_or, dst_key, distance_or, distance_key),
eth_dos_distance_base(?src_or, ?src_key, ?dst_or, ?dst_key, ?distance_or, ?distance_key) eth_dos_distance_base(src_or, src_key, dst_or, dst_key, distance_or, distance_key)
) )
eth_dos_distance_base(src_or, src_key, dst_or, dst_key, distance_or, distance_key) = AND( eth_dos_distance_base(src_or, src_key, dst_or, dst_key, distance_or, distance_key) = AND(
Equal(?src_or[?src_key], ?dst_or[?dst_key]), Equal(src_or[src_key], dst_or[dst_key]),
ValueOf(?distance_or[?distance_key], 0) ValueOf(distance_or[distance_key], 0)
) )
eth_dos_distance_ind_0(src_or, src_key, dst_or, dst_key, distance_or, distance_key, private: intermed_or, intermed_key, shorter_distance_or, shorter_distance_key, one_or, one_key) = AND( eth_dos_distance_ind_0(src_or, src_key, dst_or, dst_key, distance_or, distance_key, private: intermed_or, intermed_key, shorter_distance_or, shorter_distance_key, one_or, one_key) = AND(
eth_dos_distance(?src_or, ?src_key, ?intermed_or, ?intermed_key, ?shorter_distance_or, ?shorter_distance_key) eth_dos_distance(src_or, src_key, intermed_or, intermed_key, shorter_distance_or, shorter_distance_key)
// distance == shorter_distance + 1 // distance == shorter_distance + 1
ValueOf(?one_or[?one_key], 1) ValueOf(one_or[one_key], 1)
SumOf(?distance_or[?distance_key], ?shorter_distance_or[?shorter_distance_key], ?one_or[?one_key]) SumOf(distance_or[distance_key], shorter_distance_or[shorter_distance_key], one_or[one_key])
// intermed is a friend of dst // intermed is a friend of dst
eth_friend(?intermed_or, ?intermed_key, ?dst_or, ?dst_key) eth_friend(intermed_or, intermed_key, dst_or, dst_key)
) )
``` ```

View file

@ -11,17 +11,17 @@ The syntax of a custom operation is best explained with an example.
Original example with anchored keys, origins, and keys. Original example with anchored keys, origins, and keys.
| Args | Condition | Output | | Args | Condition | Output |
|------------|-----------------------------------------|----| |------------|-----------------------------------------|----|
| signed_dict: Dict, <br> signer: PublicKey, <br> good_boy_issuers: AnchoredKey::MerkleRoot, <br> receiver: AnchoredKey | SignedBy(?signed_dict, ?signer), <br> Contains(?good_boy_issuers, ?signer), <br> Equals(?signed_dict["friend"], ?receiver) | GoodBoy(?receiver, ?good_boy_issuers) | | signed_dict: Dict, <br> signer: PublicKey, <br> good_boy_issuers: AnchoredKey::MerkleRoot, <br> receiver: AnchoredKey | SignedBy(signed_dict, signer), <br> Contains(good_boy_issuers, signer), <br> Equals(signed_dict["friend"], receiver) | GoodBoy(receiver, good_boy_issuers) |
Compiled example with only origins and keys. Compiled example with only origins and keys.
| Args | Condition | Output | | Args | Condition | Output |
|------------|-----------------------------------------|----| |------------|-----------------------------------------|----|
| signed_dict: Dict, <br> signer: PublicKey, <br> good_boy_issuers_origin: Origin, <br> good_boy_issuers_key: Key::MerkleRoot, <br> receiver_origin: Origin, <br> receiver_key: Key | SignedBy(?signed_dict, ?signer), <br> Contains(?good_boy_issuers_origin[?good_boy_issuers_key], ?signer), <br> Equals(?signed_dict["friend"], ?receiver_origin[?receiver_key]) | GoodBoy(?receiver_origin[?receiver_key]), ?good_boy_issuers_origin[?good_boy_issuers_key]) | | signed_dict: Dict, <br> signer: PublicKey, <br> good_boy_issuers_origin: Origin, <br> good_boy_issuers_key: Key::MerkleRoot, <br> receiver_origin: Origin, <br> receiver_key: Key | SignedBy(signed_dict, signer), <br> Contains(good_boy_issuers_origin[good_boy_issuers_key], signer), <br> Equals(signed_dict["friend"], receiver_origin[receiver_key]) | GoodBoy(receiver_origin[receiver_key]), good_boy_issuers_origin[good_boy_issuers_key]) |
A custom operation accepts as input a number of statements (the `Condition`); A custom operation accepts as input a number of statements (the `Condition`);
each statement has a number of arguments, which may be constants or anchored keys; and an [anchored key](./anchoredkeys.md) in turn can optionally be decomposed as a pair of a Dict and a Key. each statement has a number of arguments, which may be constants or anchored keys; and an [anchored key](./anchoredkeys.md) in turn can optionally be decomposed as a pair of a Dict and a Key.
In the "original example" above, the anchored keys `good_boy_issuers` and `receiver` are not broken down, but `?signed_dict["friend"]` is. The purpose of breaking it down, in this case, is to use an entry of a dictionary that has been signed. In the "original example" above, the anchored keys `good_boy_issuers` and `receiver` are not broken down, but `signed_dict["friend"]` is. The purpose of breaking it down, in this case, is to use an entry of a dictionary that has been signed.
In the "compiled example", all the anchored keys have been broken down into dictionaries and keys. In the "compiled example", all the anchored keys have been broken down into dictionaries and keys.

View file

@ -13,21 +13,21 @@ rules, the inductive case and the base case.
``` ```
// src, dst: PubKey, attetation_pod: Pod // src, dst: PubKey, attetation_pod: Pod
eth_dos_friend(src, dst, private: attestation_pod) = AND( eth_dos_friend(src, dst, private: attestation_pod) = AND(
ValueOf(?attestation_pod[KEY_TYPE], SIGNATURE) ValueOf(attestation_pod[KEY_TYPE], SIGNATURE)
Equal(?attestation_pod[KEY_SIGNER], src) Equal(attestation_pod[KEY_SIGNER], src)
Equal(?attestation_pod["attestation"], dst) Equal(attestation_pod["attestation"], dst)
) )
// src, intermed, dst: PubKey, distance, shorter_distance: Int // src, intermed, dst: PubKey, distance, shorter_distance: Int
eth_dos_distance(src, dst, distance, private: shorter_distance, intermed) = OR( eth_dos_distance(src, dst, distance, private: shorter_distance, intermed) = OR(
AND( AND(
eth_dos_distance(?src, ?intermed, ?shorter) eth_dos_distance(src, intermed, shorter)
SumOf(?distance, ?shorter_distance, 1) SumOf(distance, shorter_distance, 1)
eth_friend(?intermed, ?dst) eth_friend(intermed, dst)
) )
AND( AND(
Equal(?src, ?dst) Equal(src, dst)
Equal(?distance, 0) Equal(distance, 0)
) )
) )
``` ```
@ -52,29 +52,29 @@ A ZuKYC Pod exposes a single custom statement with one custom deduction rule.
``` ```
// receiver: PubKey, gov_id, paystub, sk_pok: Pod, nullifier, sk: Raw // receiver: PubKey, gov_id, paystub, sk_pok: Pod, nullifier, sk: Raw
loan_check(receiver, private: gov_id, paystub, nullifier, sk, sk_pok) = AND( loan_check(receiver, private: gov_id, paystub, nullifier, sk, sk_pok) = AND(
Equal(?gov_id["pk"], receiver) Equal(gov_id["pk"], receiver)
// Not in the sanction list // Not in the sanction list
SetNotContains(SANCTION_LIST, receiver) SetNotContains(SANCTION_LIST, receiver)
// Valid government-issued ID // Valid government-issued ID
Equal(?gov_id[KEY_SIGNER], ZOO_GOV) Equal(gov_id[KEY_SIGNER], ZOO_GOV)
Equal(?gov_id[KEY_TYPE], SIGNATURE) Equal(gov_id[KEY_TYPE], SIGNATURE)
// At least 18 years old // At least 18 years old
Lt(?gov_id["date_of_birth"], NOW_MINUS_18Y) # date_of_birdth is more than 18y old Lt(gov_id["date_of_birth"], NOW_MINUS_18Y) # date_of_birdth is more than 18y old
Equal(?paystub[KEY_SIGNER], ZOO_DEEL) Equal(paystub[KEY_SIGNER], ZOO_DEEL)
Equal(?paystub[KEY_TYPE], SIGNATURE) Equal(paystub[KEY_TYPE], SIGNATURE)
Equal(?paystub[ssn], ?gov_id["ssn"]) Equal(paystub[ssn], gov_id["ssn"])
// At least one year of consistent employment with your current employer // At least one year of consistent employment with your current employer
Lt(?paystub["start_date"], NOW_MINUS_1Y) # start_date is more than 1y old Lt(paystub["start_date"], NOW_MINUS_1Y) # start_date is more than 1y old
Gt(?paystub["issue_date"], NOW_MINUS_7D) # issue_date is less than 7d old Gt(paystub["issue_date"], NOW_MINUS_7D) # issue_date is less than 7d old
// Annual salary is at least $20,000 // Annual salary is at least $20,000
Gt(?paystub["annual_salary"], 20000) Gt(paystub["annual_salary"], 20000)
// Private key knowledge // Private key knowledge
Equal(?sk_pok[KEY_SIGNER], receiver) Equal(sk_pok[KEY_SIGNER], receiver)
Equal(?sk_pok[KEY_TYPE], SIGNATURE) Equal(sk_pok[KEY_TYPE], SIGNATURE)
Equal(?sk_pok["auth"], "ZUKYC_V1_AUTH") Equal(sk_pok["auth"], "ZUKYC_V1_AUTH")
HashOf(, 0, ?sk) HashOf(, 0, sk)
// Nullifier // Nullifier
HashOf(nullifier, "ZUKYC_V1_NULLIFIER", ?sk) HashOf(nullifier, "ZUKYC_V1_NULLIFIER", sk)
) )
``` ```
@ -95,19 +95,19 @@ A ZuKYC Pod exposes a single custom statement with one custom deduction rule.
``` ```
// receiver: String, gov_pk, paystub_pk: PubKey, gov_id, paystub: Pod // receiver: String, gov_pk, paystub_pk: PubKey, gov_id, paystub: Pod
loan_check(receiver, gov_pk, paystub_pk, private: gov_id, paystub) = AND( loan_check(receiver, gov_pk, paystub_pk, private: gov_id, paystub) = AND(
Equal(?gov_id["id_number"], ?receiver) Equal(gov_id["id_number"], receiver)
// Not in the sanction list // Not in the sanction list
SetNotContains(SANCTION_LIST, ?gov_id["id_number"]) SetNotContains(SANCTION_LIST, gov_id["id_number"])
// Valid government-issued ID // Valid government-issued ID
ValueOf(?gov_id[KEY_SIGNER], ?gov_pk) ValueOf(gov_id[KEY_SIGNER], gov_pk)
Equal(?gov_id[KEY_TYPE], SIGNATURE) Equal(gov_id[KEY_TYPE], SIGNATURE)
// At least 18 years old // At least 18 years old
Lt(?gov_id["date_of_birth"], NOW_MINUS_18Y) # date_of_birdth is more than 18y old Lt(gov_id["date_of_birth"], NOW_MINUS_18Y) # date_of_birdth is more than 18y old
ValueOf(?paystub[KEY_SIGNER], ?paystub_pk) ValueOf(paystub[KEY_SIGNER], paystub_pk)
Equal(?paystub[KEY_TYPE], SIGNATURE) Equal(paystub[KEY_TYPE], SIGNATURE)
Equal(?paystub["ssn"], ?gov_id["ssn"]) Equal(paystub["ssn"], gov_id["ssn"])
// At least one year of consistent employment with your current employer // At least one year of consistent employment with your current employer
Lt(?paystub["start_date"], NOW_MINUS_1Y) # start_date is more than 1y old Lt(paystub["start_date"], NOW_MINUS_1Y) # start_date is more than 1y old
) )
``` ```
@ -118,11 +118,11 @@ A Good Boy Pod exposes one custom statement with one custom deduction rule.
``` ```
// user: PubKey, good_boy_issuers: Set, pod: Pod, age: Int // user: PubKey, good_boy_issuers: Set, pod: Pod, age: Int
is_good_boy(user, good_boy_issuers, private: pod, age) = AND( is_good_boy(user, good_boy_issuers, private: pod, age) = AND(
Equal(?pod[KEY_TYPE], SIGNATURE) Equal(pod[KEY_TYPE], SIGNATURE)
SetContains(?good_boy_issuers, ?pod[KEY_SIGNER]) SetContains(good_boy_issuers, pod[KEY_SIGNER])
// A good boy issuer says this user is a good boy // A good boy issuer says this user is a good boy
Equal(?pod["user"], ?user) Equal(pod["user"], user)
Equal(?pod["age"], ?age) Equal(pod["age"], age)
) )
``` ```
@ -131,12 +131,12 @@ A Friend Pod exposes one custom statement with one custom deduction rule.
``` ```
// good_boy, friend: PubKey, good_boy_issuers: Set, friend_pod: Pod // good_boy, friend: PubKey, good_boy_issuers: Set, friend_pod: Pod
is_friend(good_boy, friend, good_boy_issuers, friend_pod) = AND( is_friend(good_boy, friend, good_boy_issuers, friend_pod) = AND(
Equal(?pod[KEY_TYPE], SIGNATURE) Equal(pod[KEY_TYPE], SIGNATURE)
// The issuer is a good boy // The issuer is a good boy
is_good_boy(?good_boy, ?good_boy_issuers) is_good_boy(good_boy, good_boy_issuers)
// A good boy says this is their friend // A good boy says this is their friend
Equal(?pod[KEY_SIGNER], ?good_boy) Equal(pod[KEY_SIGNER], good_boy)
Equal(?pod["friend"], ?friend) Equal(pod["friend"], friend)
) )
``` ```
@ -147,10 +147,10 @@ with one custom deduction rule.
great_boy: PubKey, good_boy_issuers: Set, friend_pod_0, friend_pod_1: Pod great_boy: PubKey, good_boy_issuers: Set, friend_pod_0, friend_pod_1: Pod
is_great_boy(great_boy, good_boy_issuers, private: friend_pod_0, friend_pod_1) = AND is_great_boy(great_boy, good_boy_issuers, private: friend_pod_0, friend_pod_1) = AND
// Two good boys consider this user their friend // Two good boys consider this user their friend
is_friend(?friend_pod_0[KEY_SIGNER], ?great_boy) is_friend(friend_pod_0[KEY_SIGNER], great_boy)
is_friend(?friend_pod_1[KEY_SIGNER], ?great_boy) is_friend(friend_pod_1[KEY_SIGNER], great_boy)
// good boy 0 != good boy 1 // good boy 0 != good boy 1
NotEqual(?friend_pod_0[KEY_SIGNER], ?friend_pod_1[KEY_SIGNER]) NotEqual(friend_pod_0[KEY_SIGNER], friend_pod_1[KEY_SIGNER])
``` ```
## Attested GreatBoy ## Attested GreatBoy
@ -161,13 +161,13 @@ An Attested Great Boy Pod is like a Great Boy Pod, but the names of the signers
// great_boy: PubKey, friend0, friend1: String, good_boy_issuers: Set, friend_pod_0, friend_pod_1: Pod // great_boy: PubKey, friend0, friend1: String, good_boy_issuers: Set, friend_pod_0, friend_pod_1: Pod
is_great_boy(great_boy, friend0, friend1, good_boy_issuers, private: friend_pod_0, friend_pod_1) = AND is_great_boy(great_boy, friend0, friend1, good_boy_issuers, private: friend_pod_0, friend_pod_1) = AND
// Two good boys consider this user their friend // Two good boys consider this user their friend
is_friend(?friend_pod_0[KEY_SIGNER], ?great_boy) is_friend(friend_pod_0[KEY_SIGNER], great_boy)
is_friend(?friend_pod_1[KEY_SIGNER], ?great_boy) is_friend(friend_pod_1[KEY_SIGNER], great_boy)
// good boy 0 != good boy 1 // good boy 0 != good boy 1
NotEqual(?friend_pod_0[KEY_SIGNER], ?friend_pod_1[KEY_SIGNER]) NotEqual(friend_pod_0[KEY_SIGNER], friend_pod_1[KEY_SIGNER])
// publicize signer names // publicize signer names
ValueOf(?friend_pod_0["name"], ?friend0) ValueOf(friend_pod_0["name"], friend0)
ValueOf(?friend_pod_1["name"], ?friend1) ValueOf(friend_pod_1["name"], friend1)
``` ```
To produce a Great Boy Pod, you need two Friend Pods, `friend_pod0` and `friend_pod1`, each of which reveals its `signer`. To produce a Great Boy Pod, you need two Friend Pods, `friend_pod0` and `friend_pod1`, each of which reveals its `signer`.
@ -197,9 +197,9 @@ A post is popular if it has at least two comments from different signers.
``` ```
// post, comment1, comment2: Pod // post, comment1, comment2: Pod
statement is_popular(post, private: comment1, comment2) = AND( statement is_popular(post, private: comment1, comment2) = AND(
IsEqual(?comment1["referenced_post"], ?post) IsEqual(comment1["referenced_post"], post)
IsEqual(?comment2["referenced_post"], ?post) IsEqual(comment2["referenced_post"], post)
NotEqual(?comment1[KEY_SIGNER], ?comment2[KEY_SIGNER]) NotEqual(comment1[KEY_SIGNER], comment2[KEY_SIGNER])
) )
``` ```
@ -209,7 +209,7 @@ Suppose I want to prove that two different people are over 18, and a third perso
``` ```
// age: Int // age: Int
over_18(age) = AND( over_18(age) = AND(
GtEq(?age, 18) GtEq(age, 18)
) )
``` ```
@ -229,10 +229,10 @@ over_18(?1) = AND(
Maybe I have two input pods `gov_id1` and `gov_id2`, and I want to prove that these pods refer to two different people, both of whom are over 18; and a third pods `gov_id3` refers to someone under 18. So in my public output statements, I want to have: Maybe I have two input pods `gov_id1` and `gov_id2`, and I want to prove that these pods refer to two different people, both of whom are over 18; and a third pods `gov_id3` refers to someone under 18. So in my public output statements, I want to have:
``` ```
NotEqual(?gov_id1["name"], ?gov_id2["name"]) NotEqual(gov_id1["name"], gov_id2["name"])
over_18(?gov_id1["age"]) over_18(gov_id1["age"])
over_18(?gov_id2["age"]) over_18(gov_id2["age"])
under_18(?gov_id3["age"]). under_18(gov_id3["age"]).
``` ```
I would prove this with the following sequence of deductions: I would prove this with the following sequence of deductions:

View file

@ -118,10 +118,10 @@ The wildcard system handles this very naturally, since the dict of the anchored
``` ```
eth_friend(src_or, src_key, dst_or, dst_key) = and< eth_friend(src_or, src_key, dst_or, dst_key) = and<
// the attestation dict is signed by (src_or, src_key) // the attestation dict is signed by (src_or, src_key)
SignedBy(?attestation_dict, ?src_or[?src_key]) SignedBy(attestation_dict, src_or[src_key])
// that same attestation pod has an "attestation" // that same attestation pod has an "attestation"
Equal(?attestation_dict["attestation"], ?dst_or[?dst_key]) Equal(attestation_dict["attestation"], dst_or[dst_key])
> >
``` ```

View file

@ -72,17 +72,17 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let input = format!( let input = format!(
r#" r#"
points(player, level, points, private: points_dict) = AND( points(player, level, points, private: points_dict) = AND(
SignedBy(?points_dict, PublicKey({game_pk})) SignedBy(points_dict, PublicKey({game_pk}))
Contains(?points_dict, "player", ?player) Contains(points_dict, "player", player)
Contains(?points_dict, "level", ?level) Contains(points_dict, "level", level)
Contains(?points_dict, "points", ?points) Contains(points_dict, "points", points)
) )
over_9000(player, private: points_lvl_1, points_lvl_2, points_total) = AND( over_9000(player, private: points_lvl_1, points_lvl_2, points_total) = AND(
points(?player, 1, ?points_lvl_1) points(player, 1, points_lvl_1)
points(?player, 2, ?points_lvl_2) points(player, 2, points_lvl_2)
SumOf(?points_total, ?points_lvl_1, ?points_lvl_2) SumOf(points_total, points_lvl_1, points_lvl_2)
Gt(?points_total, 9000) Gt(points_total, 9000)
) )
"#, "#,
game_pk = game_pk, game_pk = game_pk,

View file

@ -12,24 +12,24 @@ use crate::{
pub fn eth_dos_batch(params: &Params) -> Result<Arc<CustomPredicateBatch>> { pub fn eth_dos_batch(params: &Params) -> Result<Arc<CustomPredicateBatch>> {
let input = r#" let input = r#"
eth_friend(src, dst, private: attestation) = AND( eth_friend(src, dst, private: attestation) = AND(
SignedBy(?attestation, ?src) SignedBy(attestation, src)
Contains(?attestation, "attestation", ?dst) Contains(attestation, "attestation", dst)
) )
eth_dos_base(src, dst, distance) = AND( eth_dos_base(src, dst, distance) = AND(
Equal(?src, ?dst) Equal(src, dst)
Equal(?distance, 0) Equal(distance, 0)
) )
eth_dos_ind(src, dst, distance, private: shorter_distance, intermed) = AND( eth_dos_ind(src, dst, distance, private: shorter_distance, intermed) = AND(
eth_dos(?src, ?intermed, ?shorter_distance) eth_dos(src, intermed, shorter_distance)
SumOf(?distance, ?shorter_distance, 1) SumOf(distance, shorter_distance, 1)
eth_friend(?intermed, ?dst) eth_friend(intermed, dst)
) )
eth_dos(src, dst, distance) = OR( eth_dos(src, dst, distance) = OR(
eth_dos_base(?src, ?dst, ?distance) eth_dos_base(src, dst, distance)
eth_dos_ind(?src, ?dst, ?distance) eth_dos_ind(src, dst, distance)
) )
"#; "#;
let batch = parse(input, params, &[]).expect("lang parse").custom_batch; let batch = parse(input, params, &[]).expect("lang parse").custom_batch;
@ -47,7 +47,7 @@ pub fn eth_dos_request() -> Result<PodRequest> {
r#" r#"
use _, _, _, eth_dos from 0x{batch_id} use _, _, _, eth_dos from 0x{batch_id}
REQUEST( REQUEST(
eth_dos(?src, ?dst, ?distance) eth_dos(src, dst, distance)
) )
"#, "#,
); );

View file

@ -82,12 +82,12 @@ pub fn zu_kyc_pod_request(gov_signer: &Value, pay_signer: &Value) -> Result<PodR
let input = format!( let input = format!(
r#" r#"
REQUEST( REQUEST(
SetNotContains({sanction_set}, ?gov.idNumber) SetNotContains({sanction_set}, gov.idNumber)
Lt(?gov.dateOfBirth, {ZU_KYC_NOW_MINUS_18Y}) Lt(gov.dateOfBirth, {ZU_KYC_NOW_MINUS_18Y})
Equal(?pay.startDate, {ZU_KYC_NOW_MINUS_1Y}) Equal(pay.startDate, {ZU_KYC_NOW_MINUS_1Y})
Equal(?gov.socialSecurityNumber, ?pay.socialSecurityNumber) Equal(gov.socialSecurityNumber, pay.socialSecurityNumber)
SignedBy(?gov, {gov_signer}) SignedBy(gov, {gov_signer})
SignedBy(?pay, {pay_signer}) SignedBy(pay, {pay_signer})
// TODO: Ownership check and watermarking // TODO: Ownership check and watermarking
// Depends partly on https://github.com/0xPARC/pod2/issues/351 // Depends partly on https://github.com/0xPARC/pod2/issues/351
) )

View file

@ -21,8 +21,8 @@ pub enum BuilderArg {
} }
/// When defining a `BuilderArg`, it can be done from 3 different inputs: /// When defining a `BuilderArg`, it can be done from 3 different inputs:
/// i. (&str, &str): this is to define a origin-key pair, ie. ?attestation_pod["attestation"]) /// i. (&str, &str): this is to define a origin-key pair, ie. attestation_pod["attestation"])
/// ii. &str: this is to define a Value wildcard, ie. ?distance /// ii. &str: this is to define a Value wildcard, ie. distance
/// ///
/// case i. /// case i.
impl From<(&str, &str)> for BuilderArg { impl From<(&str, &str)> for BuilderArg {

View file

@ -240,8 +240,8 @@ mod tests {
let request = parse( let request = parse(
r#" r#"
REQUEST( REQUEST(
SumOf(?a, ?b, ?c) SumOf(a, b, c)
Equal(?a, 10) Equal(a, 10)
) )
"#, "#,
&params, &params,

View file

@ -11,15 +11,14 @@ WHITESPACE = _{ (" " | "\t" | NEWLINE)+ }
// COMMENT matches a line comment (//...\n) or block comment (/*...*/). // COMMENT matches a line comment (//...\n) or block comment (/*...*/).
COMMENT = _{ ("//" ~ (!NEWLINE ~ ANY)* | "/*" ~ (!"*/" ~ ANY)* ~ "*/" ) } COMMENT = _{ ("//" ~ (!NEWLINE ~ ANY)* | "/*" ~ (!"*/" ~ ANY)* ~ "*/" ) }
// Define rules for identifiers (predicate names, variable names without '?') reserved_identifier = { "private" | "true" | "false" }
// Define rules for identifiers (predicate names, wildcard names)
// Must start with alpha or _, followed by alpha, numeric, or _ // Must start with alpha or _, followed by alpha, numeric, or _
identifier = @{ !("private") ~ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* } identifier = @{ !reserved_identifier ~ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* }
private_kw = { "private:" } private_kw = { "private:" }
// Define wildcard names (start with '?')
wildcard = @{ "?" ~ identifier }
arg_section = { arg_section = {
public_arg_list ~ ("," ~ private_kw ~ private_arg_list)? public_arg_list ~ ("," ~ private_kw ~ private_arg_list)?
} }
@ -49,15 +48,15 @@ custom_predicate_def = {
statement_list = { statement+ } statement_list = { statement+ }
statement_arg = { anchored_key | wildcard | literal_value } statement_arg = { literal_value | anchored_key | identifier }
statement_arg_list = { statement_arg ~ ("," ~ statement_arg)* } statement_arg_list = { statement_arg ~ ("," ~ statement_arg)* }
statement = { identifier ~ "(" ~ statement_arg_list? ~ ")" } statement = { identifier ~ "(" ~ statement_arg_list? ~ ")" }
// Anchored Key: ?Var["key_literal"] or ?Var.key_identifier // Anchored Key: Var["key_literal"] or Var.key_identifier
anchored_key = { anchored_key = {
(wildcard ~ "[" ~ literal_string ~ "]") (identifier ~ "[" ~ literal_string ~ "]")
| (wildcard ~ "." ~ identifier) | (identifier ~ "." ~ identifier)
} }
// Literal Values (ordered to avoid ambiguity, e.g., string before int) // Literal Values (ordered to avoid ambiguity, e.g., string before int)
@ -110,10 +109,10 @@ dict_pair = { literal_string ~ ":" ~ literal_value }
// --- Rules for testing full input matching --- // --- Rules for testing full input matching ---
test_identifier = { SOI ~ identifier ~ EOI } test_identifier = { SOI ~ identifier ~ EOI }
test_wildcard = { SOI ~ wildcard ~ EOI }
test_literal_int = { SOI ~ literal_int ~ EOI } test_literal_int = { SOI ~ literal_int ~ EOI }
test_hash_hex = { SOI ~ hash_hex ~ EOI } test_hash_hex = { SOI ~ hash_hex ~ EOI }
test_literal_raw = { SOI ~ literal_raw ~ EOI } test_literal_raw = { SOI ~ literal_raw ~ EOI }
test_literal_value = { SOI ~ literal_value ~ EOI } test_literal_value = { SOI ~ literal_value ~ EOI }
test_statement = { SOI ~ statement ~ EOI } test_statement = { SOI ~ statement ~ EOI }
test_statement_arg = { SOI ~ statement_arg ~ EOI }
test_custom_predicate_def = { SOI ~ custom_predicate_def ~ EOI } test_custom_predicate_def = { SOI ~ custom_predicate_def ~ EOI }

View file

@ -62,7 +62,7 @@ mod tests {
fn test_e2e_simple_predicate() -> Result<(), LangError> { fn test_e2e_simple_predicate() -> Result<(), LangError> {
let input = r#" let input = r#"
is_equal(PodA, PodB) = AND( is_equal(PodA, PodB) = AND(
Equal(?PodA["the_key"], ?PodB["the_key"]) Equal(PodA["the_key"], PodB["the_key"])
) )
"#; "#;
@ -80,8 +80,8 @@ mod tests {
let expected_statements = vec![StatementTmpl { let expected_statements = vec![StatementTmpl {
pred: Predicate::Native(NativePredicate::Equal), pred: Predicate::Native(NativePredicate::Equal),
args: vec![ args: vec![
sta_ak(("PodA", 0), "the_key"), // ?PodA["the_key"] -> Wildcard(0), Key("the_key") sta_ak(("PodA", 0), "the_key"), // PodA["the_key"] -> Wildcard(0), Key("the_key")
sta_ak(("PodB", 1), "the_key"), // ?PodB["the_key"] -> Wildcard(1), Key("the_key") sta_ak(("PodB", 1), "the_key"), // PodB["the_key"] -> Wildcard(1), Key("the_key")
], ],
}]; }];
let expected_predicate = CustomPredicate::and( let expected_predicate = CustomPredicate::and(
@ -106,8 +106,8 @@ mod tests {
fn test_e2e_simple_request() -> Result<(), LangError> { fn test_e2e_simple_request() -> Result<(), LangError> {
let input = r#" let input = r#"
REQUEST( REQUEST(
Equal(?ConstPod["my_val"], Raw(0x0000000000000000000000000000000000000000000000000000000000000001)) Equal(ConstPod["my_val"], Raw(0x0000000000000000000000000000000000000000000000000000000000000001))
Lt(?GovPod["dob"], ?ConstPod["my_val"]) Lt(GovPod["dob"], ConstPod["my_val"])
) )
"#; "#;
@ -124,15 +124,15 @@ mod tests {
StatementTmpl { StatementTmpl {
pred: Predicate::Native(NativePredicate::Equal), pred: Predicate::Native(NativePredicate::Equal),
args: vec![ args: vec![
sta_ak(("ConstPod", 0), "my_val"), // ?ConstPod["my_val"] -> Wildcard(0), Key("my_val") sta_ak(("ConstPod", 0), "my_val"), // ConstPod["my_val"] -> Wildcard(0), Key("my_val")
sta_lit(RawValue::from(1)), sta_lit(RawValue::from(1)),
], ],
}, },
StatementTmpl { StatementTmpl {
pred: Predicate::Native(NativePredicate::Lt), pred: Predicate::Native(NativePredicate::Lt),
args: vec![ args: vec![
sta_ak(("GovPod", 1), "dob"), // ?GovPod["dob"] -> Wildcard(1), Key("dob") sta_ak(("GovPod", 1), "dob"), // GovPod["dob"] -> Wildcard(1), Key("dob")
sta_ak(("ConstPod", 0), "my_val"), // ?ConstPod["my_val"] -> Wildcard(0), Key("my_val") sta_ak(("ConstPod", 0), "my_val"), // ConstPod["my_val"] -> Wildcard(0), Key("my_val")
], ],
}, },
]; ];
@ -146,8 +146,8 @@ mod tests {
fn test_e2e_predicate_with_private_var() -> Result<(), LangError> { fn test_e2e_predicate_with_private_var() -> Result<(), LangError> {
let input = r#" let input = r#"
uses_private(A, private: Temp) = AND( uses_private(A, private: Temp) = AND(
Equal(?A["input_key"], ?Temp["const_key"]) Equal(A["input_key"], Temp["const_key"])
Equal(?Temp["const_key"], "some_value") Equal(Temp["const_key"], "some_value")
) )
"#; "#;
@ -166,14 +166,14 @@ mod tests {
StatementTmpl { StatementTmpl {
pred: Predicate::Native(NativePredicate::Equal), pred: Predicate::Native(NativePredicate::Equal),
args: vec![ args: vec![
sta_ak(("A", 0), "input_key"), // ?A["input_key"] -> Wildcard(0), Key("input_key") sta_ak(("A", 0), "input_key"), // A["input_key"] -> Wildcard(0), Key("input_key")
sta_ak(("Temp", 1), "const_key"), // ?Temp["const_key"] -> Wildcard(1), Key("const_key") sta_ak(("Temp", 1), "const_key"), // Temp["const_key"] -> Wildcard(1), Key("const_key")
], ],
}, },
StatementTmpl { StatementTmpl {
pred: Predicate::Native(NativePredicate::Equal), pred: Predicate::Native(NativePredicate::Equal),
args: vec![ args: vec![
sta_ak(("Temp", 1), "const_key"), // ?Temp["const_key"] -> Wildcard(1), Key("const_key") sta_ak(("Temp", 1), "const_key"), // Temp["const_key"] -> Wildcard(1), Key("const_key")
sta_lit("some_value"), // Literal("some_value") sta_lit("some_value"), // Literal("some_value")
], ],
}, },
@ -200,11 +200,11 @@ mod tests {
fn test_e2e_request_with_custom_call() -> Result<(), LangError> { fn test_e2e_request_with_custom_call() -> Result<(), LangError> {
let input = r#" let input = r#"
my_pred(X, Y) = AND( my_pred(X, Y) = AND(
Equal(?X["val"], ?Y["val"]) Equal(X["val"], Y["val"])
) )
REQUEST( REQUEST(
my_pred(?Pod1, ?Pod2) my_pred(Pod1, Pod2)
) )
"#; "#;
@ -222,8 +222,8 @@ mod tests {
let expected_pred_statements = vec![StatementTmpl { let expected_pred_statements = vec![StatementTmpl {
pred: Predicate::Native(NativePredicate::Equal), pred: Predicate::Native(NativePredicate::Equal),
args: vec![ args: vec![
sta_ak(("X", 0), "val"), // ?X["val"] -> Wildcard(0), Key("val") sta_ak(("X", 0), "val"), // X["val"] -> Wildcard(0), Key("val")
sta_ak(("Y", 1), "val"), // ?Y["val"] -> Wildcard(1), Key("val") sta_ak(("Y", 1), "val"), // Y["val"] -> Wildcard(1), Key("val")
], ],
}]; }];
let expected_predicate = CustomPredicate::and( let expected_predicate = CustomPredicate::and(
@ -259,15 +259,15 @@ mod tests {
#[test] #[test]
fn test_e2e_request_with_various_args() -> Result<(), LangError> { fn test_e2e_request_with_various_args() -> Result<(), LangError> {
let input = r#" let input = r#"
some_pred(A, B, C) = AND( Equal(?A["foo"], ?B["bar"]) ) some_pred(A, B, C) = AND( Equal(A["foo"], B["bar"]) )
REQUEST( REQUEST(
some_pred( some_pred(
?Var1, // Wildcard Var1, // Wildcard
12345, // Int Literal 12345, // Int Literal
"hello_string" // String Literal (Removed invalid AK args) "hello_string" // String Literal (Removed invalid AK args)
) )
Equal(?AnotherPod["another_key"], ?Var1["some_field"]) Equal(AnotherPod["another_key"], Var1["some_field"])
) )
"#; "#;
@ -280,15 +280,15 @@ mod tests {
assert!(!request_templates.is_empty()); assert!(!request_templates.is_empty());
// Expected Wildcard Indices in Request Scope: // Expected Wildcard Indices in Request Scope:
// ?Var1 -> 0 // Var1 -> 0
// ?AnotherPod -> 1 // AnotherPod -> 1
// Expected structure // Expected structure
let expected_templates = vec![ let expected_templates = vec![
StatementTmpl { StatementTmpl {
pred: Predicate::Custom(CustomPredicateRef::new(batch_result, 0)), // Refers to some_pred pred: Predicate::Custom(CustomPredicateRef::new(batch_result, 0)), // Refers to some_pred
args: vec![ args: vec![
StatementTmplArg::Wildcard(wc("Var1", 0)), // ?Var1 StatementTmplArg::Wildcard(wc("Var1", 0)), // Var1
StatementTmplArg::Literal(Value::from(12345i64)), // 12345 StatementTmplArg::Literal(Value::from(12345i64)), // 12345
StatementTmplArg::Literal(Value::from("hello_string")), // "hello_string" StatementTmplArg::Literal(Value::from("hello_string")), // "hello_string"
], ],
@ -296,9 +296,9 @@ mod tests {
StatementTmpl { StatementTmpl {
pred: Predicate::Native(NativePredicate::Equal), pred: Predicate::Native(NativePredicate::Equal),
args: vec![ args: vec![
// ?AnotherPod["another_key"] -> Wildcard(1), Key("another_key") // AnotherPod["another_key"] -> Wildcard(1), Key("another_key")
sta_ak(("AnotherPod", 1), "another_key"), sta_ak(("AnotherPod", 1), "another_key"),
// ?Var1["some_field"] -> Wildcard(0), Key("some_field") // Var1["some_field"] -> Wildcard(0), Key("some_field")
sta_ak(("Var1", 0), "some_field"), sta_ak(("Var1", 0), "some_field"),
], ],
}, },
@ -313,11 +313,11 @@ mod tests {
fn test_e2e_syntactic_sugar_predicates() -> Result<(), LangError> { fn test_e2e_syntactic_sugar_predicates() -> Result<(), LangError> {
let input = r#" let input = r#"
REQUEST( REQUEST(
GtEq(?A["foo"], ?B["bar"]) GtEq(A["foo"], B["bar"])
Gt(?C["baz"], ?D["qux"]) Gt(C["baz"], D["qux"])
DictContains(?A["foo"], ?B["bar"], ?C["baz"]) DictContains(A["foo"], B["bar"], C["baz"])
DictNotContains(?A["foo"], ?B["bar"]) DictNotContains(A["foo"], B["bar"])
ArrayContains(?A["foo"], ?B["bar"], ?C["baz"]) ArrayContains(A["foo"], B["bar"], C["baz"])
) )
"#; "#;
@ -370,12 +370,12 @@ mod tests {
let input = r#" let input = r#"
REQUEST( REQUEST(
// Order matters for comparison with the hardcoded templates // Order matters for comparison with the hardcoded templates
SetNotContains(?sanctions["sanctionList"], ?gov["idNumber"]) SetNotContains(sanctions["sanctionList"], gov["idNumber"])
Lt(?gov["dateOfBirth"], ?SELF_HOLDER_18Y["const_18y"]) Lt(gov["dateOfBirth"], SELF_HOLDER_18Y["const_18y"])
Equal(?pay["startDate"], ?SELF_HOLDER_1Y["const_1y"]) Equal(pay["startDate"], SELF_HOLDER_1Y["const_1y"])
Equal(?gov["socialSecurityNumber"], ?pay["socialSecurityNumber"]) Equal(gov["socialSecurityNumber"], pay["socialSecurityNumber"])
Equal(?SELF_HOLDER_18Y["const_18y"], 1169909388) Equal(SELF_HOLDER_18Y["const_18y"], 1169909388)
Equal(?SELF_HOLDER_1Y["const_1y"], 1706367566) Equal(SELF_HOLDER_1Y["const_1y"], 1706367566)
) )
"#; "#;
@ -406,7 +406,7 @@ mod tests {
// Define the request templates using wildcards for constants // Define the request templates using wildcards for constants
let expected_templates = vec![ let expected_templates = vec![
// 1. NotContains(?sanctions["sanctionList"], ?gov["idNumber"]) // 1. NotContains(sanctions["sanctionList"], gov["idNumber"])
StatementTmpl { StatementTmpl {
pred: Predicate::Native(NativePredicate::NotContains), pred: Predicate::Native(NativePredicate::NotContains),
args: vec![ args: vec![
@ -417,7 +417,7 @@ mod tests {
sta_ak((wc_gov.name.as_str(), wc_gov.index), id_num_key), sta_ak((wc_gov.name.as_str(), wc_gov.index), id_num_key),
], ],
}, },
// 2. Lt(?gov["dateOfBirth"], ?SELF_HOLDER_18Y["const_18y"]) // 2. Lt(gov["dateOfBirth"], SELF_HOLDER_18Y["const_18y"])
StatementTmpl { StatementTmpl {
pred: Predicate::Native(NativePredicate::Lt), pred: Predicate::Native(NativePredicate::Lt),
args: vec![ args: vec![
@ -428,7 +428,7 @@ mod tests {
), ),
], ],
}, },
// 3. Equal(?pay["startDate"], ?SELF_HOLDER_1Y["const_1y"]) // 3. Equal(pay["startDate"], SELF_HOLDER_1Y["const_1y"])
StatementTmpl { StatementTmpl {
pred: Predicate::Native(NativePredicate::Equal), pred: Predicate::Native(NativePredicate::Equal),
args: vec![ args: vec![
@ -436,7 +436,7 @@ mod tests {
sta_ak((wc_self_1y.name.as_str(), wc_self_1y.index), const_1y_key), sta_ak((wc_self_1y.name.as_str(), wc_self_1y.index), const_1y_key),
], ],
}, },
// 4. Equal(?gov["socialSecurityNumber"], ?pay["socialSecurityNumber"]) // 4. Equal(gov["socialSecurityNumber"], pay["socialSecurityNumber"])
StatementTmpl { StatementTmpl {
pred: Predicate::Native(NativePredicate::Equal), pred: Predicate::Native(NativePredicate::Equal),
args: vec![ args: vec![
@ -444,7 +444,7 @@ mod tests {
sta_ak((wc_pay.name.as_str(), wc_pay.index), ssn_key), sta_ak((wc_pay.name.as_str(), wc_pay.index), ssn_key),
], ],
}, },
// 5. Equal(?SELF_HOLDER_18Y["const_18y"], 1169909388) // 5. Equal(SELF_HOLDER_18Y["const_18y"], 1169909388)
StatementTmpl { StatementTmpl {
pred: Predicate::Native(NativePredicate::Equal), pred: Predicate::Native(NativePredicate::Equal),
args: vec![ args: vec![
@ -455,7 +455,7 @@ mod tests {
sta_lit(now_minus_18y_val.clone()), sta_lit(now_minus_18y_val.clone()),
], ],
}, },
// 6. Equal(?SELF_HOLDER_1Y["const_1y"], 1706367566) // 6. Equal(SELF_HOLDER_1Y["const_1y"], 1706367566)
StatementTmpl { StatementTmpl {
pred: Predicate::Native(NativePredicate::Equal), pred: Predicate::Native(NativePredicate::Equal),
args: vec![ args: vec![
@ -494,24 +494,24 @@ mod tests {
let input = r#" let input = r#"
eth_friend(src, dst, private: attestation_dict) = AND( eth_friend(src, dst, private: attestation_dict) = AND(
SignedBy(?attestation_dict, ?src) SignedBy(attestation_dict, src)
Equal(?attestation_dict["attestation"], ?dst) Equal(attestation_dict["attestation"], dst)
) )
eth_dos_distance_base(src, dst, distance) = AND( eth_dos_distance_base(src, dst, distance) = AND(
Equal(?src, ?dst) Equal(src, dst)
Equal(?distance, 0) Equal(distance, 0)
) )
eth_dos_distance_ind(src, dst, distance, private: shorter_distance, intermed) = AND( eth_dos_distance_ind(src, dst, distance, private: shorter_distance, intermed) = AND(
eth_dos_distance(?src, ?dst, ?distance) eth_dos_distance(src, dst, distance)
SumOf(?distance, ?shorter_distance, 1) SumOf(distance, shorter_distance, 1)
eth_friend(?intermed, ?dst) eth_friend(intermed, dst)
) )
eth_dos_distance(src, dst, distance) = OR( eth_dos_distance(src, dst, distance) = OR(
eth_dos_distance_base(?src, ?dst, ?distance) eth_dos_distance_base(src, dst, distance)
eth_dos_distance_ind(?src, ?dst, ?distance) eth_dos_distance_ind(src, dst, distance)
) )
"#; "#;
@ -668,8 +668,8 @@ mod tests {
let imported_pred_stmts = vec![StatementTmpl { let imported_pred_stmts = vec![StatementTmpl {
pred: Predicate::Native(NativePredicate::Equal), pred: Predicate::Native(NativePredicate::Equal),
args: vec![ args: vec![
sta_ak(("A", 0), "foo"), // ?A["foo"] sta_ak(("A", 0), "foo"), // A["foo"]
sta_ak(("B", 1), "bar"), // ?B["bar"] sta_ak(("B", 1), "bar"), // B["bar"]
], ],
}]; }];
let imported_predicate = CustomPredicate::and( let imported_predicate = CustomPredicate::and(
@ -690,7 +690,7 @@ mod tests {
use imported_pred from 0x{} use imported_pred from 0x{}
REQUEST( REQUEST(
imported_pred(?Pod1, ?Pod2) imported_pred(Pod1, Pod2)
) )
"#, "#,
batch_id_str batch_id_str
@ -741,8 +741,8 @@ mod tests {
use pred_one, _, pred_three from 0x{} use pred_one, _, pred_three from 0x{}
REQUEST( REQUEST(
pred_one(?Pod1) pred_one(Pod1)
pred_three(?Pod2) pred_three(Pod2)
) )
"#, "#,
batch_id_str batch_id_str
@ -799,7 +799,7 @@ mod tests {
use imported_eq from 0x{} use imported_eq from 0x{}
wrapper_pred(X, Y) = AND( wrapper_pred(X, Y) = AND(
imported_eq(?X, ?Y) imported_eq(X, Y)
) )
"#, "#,
batch_id_str batch_id_str
@ -848,12 +848,12 @@ mod tests {
let input = format!( let input = format!(
r#" r#"
REQUEST( REQUEST(
Equal(?A["pk"], {}) Equal(A["pk"], {})
Equal(?B["raw"], {}) Equal(B["raw"], {})
Equal(?C["string"], {}) Equal(C["string"], {})
Equal(?D["int"], {}) Equal(D["int"], {})
Equal(?E["bool"], {}) Equal(E["bool"], {})
Equal(?F["sk"], {}) Equal(F["sk"], {})
) )
"#, "#,
Value::from(pk).to_podlang_string(), Value::from(pk).to_podlang_string(),
@ -865,13 +865,13 @@ mod tests {
); );
/* /*
REQUEST( REQUEST(
Equal(?A["pk"], PublicKey(3t9fNuU194n7mSJPRdeaJRMqw6ZQCUddzvECWNe1k2b1rdBezXpJxF)) Equal(A["pk"], PublicKey(3t9fNuU194n7mSJPRdeaJRMqw6ZQCUddzvECWNe1k2b1rdBezXpJxF))
Equal(?C["raw"], Raw(0x0000000000000000000000000000000000000000000000000000000000000001)) Equal(C["raw"], Raw(0x0000000000000000000000000000000000000000000000000000000000000001))
Equal(?D["string"], "hello") Equal(D["string"], "hello")
Equal(?E["int"], 123) Equal(E["int"], 123)
Equal(?F["bool"], true) Equal(F["bool"], true)
Equal(?G["sk"], SecretKey(random_secret_key_base_64)) Equal(G["sk"], SecretKey(random_secret_key_base_64))
Equal(?H["self"], SELF) Equal(H["self"], SELF)
) )
*/ */
@ -947,8 +947,8 @@ mod tests {
let input = r#" let input = r#"
identity_verified(username, private: identity_dict) = AND( identity_verified(username, private: identity_dict) = AND(
Equal(?identity_dict["username"], ?username) Equal(identity_dict["username"], username)
Equal(?identity_dict["user_public_key"], ?user_public_key) Equal(identity_dict["user_public_key"], user_public_key)
) )
"# "#
.to_string(); .to_string();

View file

@ -32,9 +32,9 @@ pub fn parse_podlang(input: &str) -> Result<Pairs<'_, Rule>, ParseError> {
mod tests { mod tests {
use super::*; use super::*;
fn assert_parses(rule: Rule, input: &str) { fn assert_parses(rule: Rule, input: &str) -> Pairs<'_, Rule> {
match PodlangParser::parse(rule, input) { match PodlangParser::parse(rule, input) {
Ok(_) => (), // Successfully parsed Ok(pairs) => pairs, // Successfully parsed
Err(e) => panic!("Failed to parse input:\n{}\nError: {}", input, e), Err(e) => panic!("Failed to parse input:\n{}\nError: {}", input, e),
} }
} }
@ -79,26 +79,52 @@ mod tests {
#[test] #[test]
fn test_parse_wildcard() { fn test_parse_wildcard() {
assert_parses(Rule::wildcard, "?Var"); assert_parses(Rule::identifier, "Var");
assert_parses(Rule::wildcard, "?_Internal"); assert_parses(Rule::identifier, "_Internal");
assert_parses(Rule::wildcard, "?X1"); assert_parses(Rule::identifier, "X1");
assert_fails(Rule::test_wildcard, "NotAVar"); // Use test rule assert_fails(Rule::test_identifier, ""); // Use test rule
assert_fails(Rule::test_wildcard, "?"); // Use test rule assert_fails(Rule::test_identifier, "invalid-char"); // Use test rule
assert_fails(Rule::test_wildcard, "?invalid-char"); // Use test rule assert_fails(Rule::test_identifier, "?noMoreQuestionMarks"); // Use test rule
assert_fails(Rule::test_identifier, "123noStartingDigits"); // Use test rule
assert_fails(Rule::test_identifier, "true"); // Use test rule
assert_fails(Rule::test_identifier, "false"); // Use test rule
} }
#[test] #[test]
fn test_parse_anchored_key() { fn test_parse_anchored_key() {
assert_parses(Rule::anchored_key, "?PodVar[\"literal key\"]"); assert_parses(Rule::anchored_key, "PodVar[\"literal_key\"]");
assert_parses(Rule::anchored_key, "?PodVar.literal_key"); assert_parses(Rule::anchored_key, "PodVar.literal_key");
assert_fails(Rule::anchored_key, "PodVar[\"key\"]"); // Needs wildcard for pod assert_fails(Rule::anchored_key, "PodVar[invalid_key]"); // Key must be literal string
assert_fails(Rule::anchored_key, "PodVar.key"); // Needs wildcard for pod assert_fails(Rule::anchored_key, "PodVar.123"); // Key must be valid identifier
assert_fails(Rule::anchored_key, "?PodVar[invalid_key]"); // Key must be literal string assert_fails(Rule::anchored_key, "PodVar[]"); // Key cannot be empty
assert_fails(Rule::anchored_key, "?PodVar.123"); // Key must be valid identifier assert_fails(Rule::anchored_key, "PodVar."); // Key cannot be empty
assert_fails(Rule::anchored_key, "?PodVar[]"); // Key cannot be empty assert_fails(Rule::anchored_key, "?PodVar[\"key\"]"); // No more question marks on wildcards
assert_fails(Rule::anchored_key, "?PodVar."); // Key cannot be empty assert_fails(Rule::anchored_key, "?PodVar.key"); // No more question marks on wildcards
assert_fails(Rule::anchored_key, "?PodVar[?key]"); // Key cannot be wildcard }
assert_fails(Rule::anchored_key, "?PodVar.?key"); // Key cannot be wildcard
#[test]
fn test_parse_arg_ambiguity() {
fn assert_inner(rule: &Rule, input: &str) {
assert_eq!(
assert_parses(Rule::test_statement_arg, input)
.next()
.unwrap()
.into_inner()
.next()
.unwrap()
.into_inner()
.next()
.unwrap()
.as_rule(),
*rule
);
}
// Ensure different types of args parse in the right priority order.
assert_inner(&Rule::identifier, "someVar");
assert_inner(&Rule::anchored_key, "someVar[\"key\"]");
assert_inner(&Rule::literal_value, "true");
assert_inner(&Rule::literal_value, "PublicKey(abc)");
} }
#[test] #[test]
@ -179,10 +205,10 @@ mod tests {
// Trimmed leading/trailing whitespace // Trimmed leading/trailing whitespace
r#"REQUEST( r#"REQUEST(
// Check equality // Check equality
Equal(?gov["socialSecurityNumber"], ?pay["socialSecurityNumber"]) Equal(gov["socialSecurityNumber"], pay["socialSecurityNumber"])
// Check age > 18 // Check age > 18
ValueOf(?const_holder["const_18y"], 1169909388) ValueOf(const_holder["const_18y"], 1169909388)
Lt(?gov["dateOfBirth"], ?const_holder["const_18y"]) Lt(gov["dateOfBirth"], const_holder["const_18y"])
)"#, )"#,
); );
} }
@ -193,14 +219,14 @@ mod tests {
Rule::test_custom_predicate_def, Rule::test_custom_predicate_def,
// Trimmed leading/trailing whitespace // Trimmed leading/trailing whitespace
r#"my_pred(A, B) = AND( r#"my_pred(A, B) = AND(
Equal(?A["foo"], ?B["bar"]) Equal(A["foo"], B["bar"])
)"#, )"#,
); );
assert_parses( assert_parses(
Rule::test_custom_predicate_def, Rule::test_custom_predicate_def,
// Trimmed leading/trailing whitespace // Trimmed leading/trailing whitespace
r#"pred_with_private(X, private: TempKey) = OR( r#"pred_with_private(X, private: TempKey) = OR(
Equal(?X["key"], 1234) Equal(X["key"], 1234)
)"#, )"#,
); );
assert_fails( assert_fails(
@ -216,15 +242,15 @@ mod tests {
r#"// File defining one predicate and one request r#"// File defining one predicate and one request
is_valid_user(UserPod, private: ConstVal) = AND( is_valid_user(UserPod, private: ConstVal) = AND(
// User age must be > 18 (using a constant value) // User age must be > 18 (using a constant value)
ValueOf(?ConstVal["min_age"], 18) ValueOf(ConstVal["min_age"], 18)
Gt(?UserPod["age"], ?ConstVal["min_age"]) Gt(UserPod["age"], ConstVal["min_age"])
// User must not be banned // User must not be banned
NotContains(?_BANNED_USERS.list, ?UserPod.userId) NotContains(_BANNED_USERS.list, UserPod.userId)
) )
REQUEST( REQUEST(
is_valid_user(?SomeUser) is_valid_user(SomeUser)
Equal(?SomeUser["country"], ?Other["country"]) Equal(SomeUser["country"], Other["country"])
)"#, )"#,
); );
} }

View file

@ -227,7 +227,7 @@ mod tests {
fn test_simple_predicate_pretty_print() { fn test_simple_predicate_pretty_print() {
let params = Params::default(); let params = Params::default();
// Create a simple predicate: is_equal(PodA, PodB) = AND(Equal(?PodA["key"], ?PodB["key"])) // Create a simple predicate: is_equal(PodA, PodB) = AND(Equal(PodA["key"], PodB["key"]))
let statements = vec![StatementTmpl { let statements = vec![StatementTmpl {
pred: Predicate::Native(NativePredicate::Equal), pred: Predicate::Native(NativePredicate::Equal),
args: vec![ args: vec![
@ -254,7 +254,7 @@ mod tests {
let pretty_printed = predicate.to_podlang_string(); let pretty_printed = predicate.to_podlang_string();
let expected = r#"is_equal(PodA, PodB) = AND( let expected = r#"is_equal(PodA, PodB) = AND(
Equal(?PodA["key"], ?PodB["key"]) Equal(PodA["key"], PodB["key"])
)"#; )"#;
assert_eq!(pretty_printed, expected); assert_eq!(pretty_printed, expected);
} }
@ -263,7 +263,7 @@ mod tests {
fn test_predicate_with_private_args() { fn test_predicate_with_private_args() {
let params = Params::default(); let params = Params::default();
// Create: uses_private(A, private: Temp) = AND(Equal(?A["input"], ?Temp["const"])) // Create: uses_private(A, private: Temp) = AND(Equal(A["input"], Temp["const"]))
let statements = vec![StatementTmpl { let statements = vec![StatementTmpl {
pred: Predicate::Native(NativePredicate::Equal), pred: Predicate::Native(NativePredicate::Equal),
args: vec![ args: vec![
@ -290,7 +290,7 @@ mod tests {
let pretty_printed = predicate.to_podlang_string(); let pretty_printed = predicate.to_podlang_string();
let expected = r#"uses_private(A, private: Temp) = AND( let expected = r#"uses_private(A, private: Temp) = AND(
Equal(?A["input"], ?Temp["const"]) Equal(A["input"], Temp["const"])
)"#; )"#;
assert_eq!(pretty_printed, expected); assert_eq!(pretty_printed, expected);
} }
@ -299,7 +299,7 @@ mod tests {
fn test_statement_with_literal_args() { fn test_statement_with_literal_args() {
let params = Params::default(); let params = Params::default();
// Create: check_value(Pod) = AND(Equal(?Pod["field"], 42)) // Create: check_value(Pod) = AND(Equal(Pod["field"], 42))
let statements = vec![StatementTmpl { let statements = vec![StatementTmpl {
pred: Predicate::Native(NativePredicate::Equal), pred: Predicate::Native(NativePredicate::Equal),
args: vec![ args: vec![
@ -323,7 +323,7 @@ mod tests {
let pretty_printed = predicate.to_podlang_string(); let pretty_printed = predicate.to_podlang_string();
let expected = r#"check_value(Pod) = AND( let expected = r#"check_value(Pod) = AND(
Equal(?Pod["field"], 42) Equal(Pod["field"], 42)
)"#; )"#;
assert_eq!(pretty_printed, expected); assert_eq!(pretty_printed, expected);
} }
@ -332,7 +332,7 @@ mod tests {
fn test_or_predicate() { fn test_or_predicate() {
let params = Params::default(); let params = Params::default();
// Create: either_or(A, B) = OR(Equal(?A["x"], 1), Equal(?B["y"], 2)) // Create: either_or(A, B) = OR(Equal(A["x"], 1), Equal(B["y"], 2))
let statements = vec![ let statements = vec![
StatementTmpl { StatementTmpl {
pred: Predicate::Native(NativePredicate::Equal), pred: Predicate::Native(NativePredicate::Equal),
@ -368,8 +368,8 @@ mod tests {
let pretty_printed = predicate.to_podlang_string(); let pretty_printed = predicate.to_podlang_string();
let expected = r#"either_or(A, B) = OR( let expected = r#"either_or(A, B) = OR(
Equal(?A["x"], 1) Equal(A["x"], 1)
Equal(?B["y"], 2) Equal(B["y"], 2)
)"#; )"#;
assert_eq!(pretty_printed, expected); assert_eq!(pretty_printed, expected);
} }
@ -402,7 +402,7 @@ mod tests {
fn test_round_trip_simple_predicate() { fn test_round_trip_simple_predicate() {
let input = r#" let input = r#"
simple_equal(PodA, PodB) = AND( simple_equal(PodA, PodB) = AND(
Equal(?PodA["key"], ?PodB["key"]) Equal(PodA["key"], PodB["key"])
) )
"#; "#;
assert_round_trip(input); assert_round_trip(input);
@ -412,8 +412,8 @@ mod tests {
fn test_round_trip_predicate_with_private_args() { fn test_round_trip_predicate_with_private_args() {
let input = r#" let input = r#"
uses_private(A, private: Temp) = AND( uses_private(A, private: Temp) = AND(
Equal(?A["input_key"], ?Temp["const_key"]) Equal(A["input_key"], Temp["const_key"])
Equal(?Temp["const_key"], "some_value") Equal(Temp["const_key"], "some_value")
) )
"#; "#;
assert_round_trip(input); assert_round_trip(input);
@ -423,8 +423,8 @@ mod tests {
fn test_round_trip_or_predicate() { fn test_round_trip_or_predicate() {
let input = r#" let input = r#"
either_condition(X, Y) = OR( either_condition(X, Y) = OR(
Equal(?X["status"], "active") Equal(X["status"], "active")
Equal(?Y["type"], 1) Equal(Y["type"], 1)
) )
"#; "#;
assert_round_trip(input); assert_round_trip(input);
@ -434,12 +434,12 @@ mod tests {
fn test_round_trip_multiple_predicates() { fn test_round_trip_multiple_predicates() {
let input = r#" let input = r#"
pred_one(A) = AND( pred_one(A) = AND(
Equal(?A["field"], 42) Equal(A["field"], 42)
) )
pred_two(B, C) = AND( pred_two(B, C) = AND(
Equal(?B["value"], ?C["value"]) Equal(B["value"], C["value"])
NotEqual(?B["id"], ?C["id"]) NotEqual(B["id"], C["id"])
) )
"#; "#;
assert_round_trip(input); assert_round_trip(input);
@ -449,10 +449,10 @@ mod tests {
fn test_round_trip_various_literals() { fn test_round_trip_various_literals() {
let input = r#" let input = r#"
literal_test(Pod) = AND( literal_test(Pod) = AND(
Equal(?Pod["int_field"], 123) Equal(Pod["int_field"], 123)
Equal(?Pod["string_field"], "hello world") Equal(Pod["string_field"], "hello world")
Equal(?Pod["bool_field"], true) Equal(Pod["bool_field"], true)
NotEqual(?Pod["other_bool"], false) NotEqual(Pod["other_bool"], false)
) )
"#; "#;
assert_round_trip(input); assert_round_trip(input);
@ -462,11 +462,11 @@ mod tests {
fn test_round_trip_complex_predicate() { fn test_round_trip_complex_predicate() {
let input = r#" let input = r#"
complex_predicate(User, Document, private: Verifier, Timestamp) = AND( complex_predicate(User, Document, private: Verifier, Timestamp) = AND(
Equal(?User["active"], true) Equal(User["active"], true)
Equal(?Document["owner"], ?User["id"]) Equal(Document["owner"], User["id"])
Equal(?Verifier["type"], 1) Equal(Verifier["type"], 1)
Lt(?Timestamp["created"], ?Timestamp["expires"]) Lt(Timestamp["created"], Timestamp["expires"])
NotContains(?Document["blocked_users"], ?User["id"]) NotContains(Document["blocked_users"], User["id"])
) )
"#; "#;
assert_round_trip(input); assert_round_trip(input);
@ -476,10 +476,10 @@ mod tests {
fn test_round_trip_with_sum_and_hash_operations() { fn test_round_trip_with_sum_and_hash_operations() {
let input = r#" let input = r#"
math_operations(A, B, C) = AND( math_operations(A, B, C) = AND(
SumOf(?A["value"], ?B["value"], ?C["total"]) SumOf(A["value"], B["value"], C["total"])
ProductOf(?A["factor"], ?B["factor"], ?C["product"]) ProductOf(A["factor"], B["factor"], C["product"])
MaxOf(?A["score"], ?B["score"], ?C["max_score"]) MaxOf(A["score"], B["score"], C["max_score"])
HashOf(?A["data"], ?B["salt"], ?C["hash"]) HashOf(A["data"], B["salt"], C["hash"])
) )
"#; "#;
assert_round_trip(input); assert_round_trip(input);
@ -489,13 +489,13 @@ mod tests {
fn test_round_trip_nested_custom_calls() { fn test_round_trip_nested_custom_calls() {
let input = r#" let input = r#"
base_check(Pod) = AND( base_check(Pod) = AND(
Equal(?Pod["status"], "valid") Equal(Pod["status"], "valid")
) )
derived_check(PodA, PodB) = AND( derived_check(PodA, PodB) = AND(
base_check(?PodA) base_check(PodA)
base_check(?PodB) base_check(PodB)
NotEqual(?PodA["id"], ?PodB["id"]) NotEqual(PodA["id"], PodB["id"])
) )
"#; "#;
assert_round_trip(input); assert_round_trip(input);
@ -505,8 +505,8 @@ mod tests {
fn test_round_trip_container_operations() { fn test_round_trip_container_operations() {
let input = r#" let input = r#"
container_checks(List, Item, Dict, Key, Value) = AND( container_checks(List, Item, Dict, Key, Value) = AND(
Contains(?List, ?Item, ?Value) Contains(List, Item, Value)
NotContains(?Dict, ?Key) NotContains(Dict, Key)
) )
"#; "#;
assert_round_trip(input); assert_round_trip(input);
@ -518,7 +518,7 @@ mod tests {
let input = format!( let input = format!(
r#" r#"
secret_key_test(Pod) = AND( secret_key_test(Pod) = AND(
Equal(?Pod["sk"], {}) Equal(Pod["sk"], {})
) )
"#, "#,
Value::from(sk.clone()).to_podlang_string() Value::from(sk.clone()).to_podlang_string()
@ -530,13 +530,13 @@ mod tests {
fn test_pretty_print_demonstration() { fn test_pretty_print_demonstration() {
let input = r#" let input = r#"
base_check(Pod) = AND( base_check(Pod) = AND(
Equal(?Pod["status"], "valid") Equal(Pod["status"], "valid")
) )
derived_check(PodA, PodB) = AND( derived_check(PodA, PodB) = AND(
base_check(?PodA) base_check(PodA)
base_check(?PodB) base_check(PodB)
NotEqual(?PodA["id"], ?PodB["id"]) NotEqual(PodA["id"], PodB["id"])
) )
"#; "#;
@ -603,7 +603,7 @@ mod tests {
let input = format!( let input = format!(
r#" r#"
test_pred(Pod) = AND( test_pred(Pod) = AND(
Equal(?Pod["field"], "{}") Equal(Pod["field"], "{}")
) )
"#, "#,
// Manually escape for the input - this simulates what would be in actual Podlang source // Manually escape for the input - this simulates what would be in actual Podlang source

View file

@ -319,8 +319,8 @@ fn pest_pair_to_builder_arg(
let value = process_literal_value(params, arg_content_pair)?; let value = process_literal_value(params, arg_content_pair)?;
Ok(BuilderArg::Literal(value)) Ok(BuilderArg::Literal(value))
} }
Rule::wildcard => { Rule::identifier => {
let wc_str = arg_content_pair.as_str().strip_prefix("?").unwrap(); let wc_str = arg_content_pair.as_str();
if let StatementContext::CustomPredicate { if let StatementContext::CustomPredicate {
argument_names, argument_names,
pred_name, pred_name,
@ -339,7 +339,7 @@ fn pest_pair_to_builder_arg(
Rule::anchored_key => { Rule::anchored_key => {
let mut inner_ak_pairs = arg_content_pair.clone().into_inner(); let mut inner_ak_pairs = arg_content_pair.clone().into_inner();
let root_pair = inner_ak_pairs.next().unwrap(); let root_pair = inner_ak_pairs.next().unwrap();
let root_wc_str = root_pair.as_str().strip_prefix("?").unwrap(); let root_wc_str = root_pair.as_str();
if let StatementContext::CustomPredicate { if let StatementContext::CustomPredicate {
argument_names, argument_names,
@ -1072,7 +1072,7 @@ mod processor_tests {
#[test] #[test]
fn test_fp_only_request() -> Result<(), ProcessorError> { fn test_fp_only_request() -> Result<(), ProcessorError> {
let input = "REQUEST( Equal(?A[\"k\"],?B.k) )"; // Escaped quotes let input = "REQUEST( Equal(A[\"k\"],B.k) )"; // Escaped quotes
let pairs = get_document_content_pairs(input)?; let pairs = get_document_content_pairs(input)?;
let params = Params::default(); let params = Params::default();
let mut ctx = ProcessingContext::new(&params); let mut ctx = ProcessingContext::new(&params);
@ -1089,7 +1089,7 @@ mod processor_tests {
#[test] #[test]
fn test_fp_simple_predicate() -> Result<(), ProcessorError> { fn test_fp_simple_predicate() -> Result<(), ProcessorError> {
let input = "my_pred(A, B) = AND( Equal(?A[\"k\"],?B.k) )"; // Escaped quotes let input = "my_pred(A, B) = AND( Equal(A[\"k\"],B.k) )"; // Escaped quotes
let pairs = get_document_content_pairs(input)?; let pairs = get_document_content_pairs(input)?;
let params = Params::default(); let params = Params::default();
let mut ctx = ProcessingContext::new(&params); let mut ctx = ProcessingContext::new(&params);
@ -1111,8 +1111,8 @@ mod processor_tests {
#[test] #[test]
fn test_fp_multiple_predicates() -> Result<(), ProcessorError> { fn test_fp_multiple_predicates() -> Result<(), ProcessorError> {
let input = r#" let input = r#"
pred1(X) = AND( Equal(?X["k"],?X.k) ) pred1(X) = AND( Equal(X["k"],X.k) )
pred2(Y, Z) = OR( Equal(?Y["v"], 123) ) pred2(Y, Z) = OR( Equal(Y["v"], 123) )
"#; "#;
let pairs = get_document_content_pairs(input)?; let pairs = get_document_content_pairs(input)?;
let params = Params::default(); let params = Params::default();
@ -1203,7 +1203,7 @@ mod processor_tests {
fn test_fp_mixed_content() -> Result<(), ProcessorError> { fn test_fp_mixed_content() -> Result<(), ProcessorError> {
let input = r#" let input = r#"
pred_one(X) = AND(None()) pred_one(X) = AND(None())
REQUEST( pred_one(?A) ) REQUEST( pred_one(A) )
pred_two(Y, Z) = OR(None()) pred_two(Y, Z) = OR(None())
"#; "#;
let pairs = get_document_content_pairs(input)?; let pairs = get_document_content_pairs(input)?;
@ -1231,7 +1231,7 @@ mod processor_tests {
.as_ref() .as_ref()
.unwrap() .unwrap()
.as_str() .as_str()
.contains("pred_one(?A)")); .contains("pred_one(A)"));
Ok(()) Ok(())
} }
@ -1241,7 +1241,7 @@ mod processor_tests {
// Undefined predicates will be flagged as an error on the second pass // Undefined predicates will be flagged as an error on the second pass
let input = r#" let input = r#"
REQUEST( REQUEST(
pred_one(?A) pred_one(A)
) )
"#; "#;
let pairs = get_document_content_pairs(input)?; let pairs = get_document_content_pairs(input)?;
@ -1260,7 +1260,7 @@ mod processor_tests {
// Native predicate names are case-sensitive // Native predicate names are case-sensitive
let input = r#" let input = r#"
REQUEST( REQUEST(
EQUAL(?A["b"], ?C.d) EQUAL(A["b"], C.d)
) )
"#; "#;
let pairs = get_document_content_pairs(input)?; let pairs = get_document_content_pairs(input)?;

View file

@ -24,9 +24,9 @@ impl Wildcard {
impl fmt::Display for Wildcard { impl fmt::Display for Wildcard {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if f.alternate() { if f.alternate() {
write!(f, "?{}:{}", self.index, self.name) write!(f, "{}:{}", self.index, self.name)
} else { } else {
write!(f, "?{}", self.name) write!(f, "{}", self.name)
} }
} }
} }