Optional dot syntax for anchored keys (#423)

This commit is contained in:
Andrew Twyman 2025-09-11 12:23:46 -07:00 committed by GitHub
parent f95a27b412
commit 7e04eb51ff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 36 additions and 18 deletions

View file

@ -12,10 +12,13 @@ some of its arguments is proved, it means that a valid Merkle proof of the
value behind it exists and was used at some point to construct a `Contains` value behind it exists and was used at some point to construct a `Contains`
statement that introduced that anchored key. statement that introduced that anchored key.
In PODLang, anchored key indexing can use subscript syntax `foo["bar"]` which
allows any string key, or dot syntax `foo.bar` if the key is a valid identifier.
For example: For example:
``` ```
0: None 0: None
1: Contains(foo, bar, 42) <- ContainsFromEntries 0 0 0 mt_proof 1: Contains(foo, "bar", 42) <- ContainsFromEntries 0 0 0 mt_proof
2: Lt(foo[bar], 100) <- LtFromEntries 1 0 2: Lt(foo["bar"], 100) <- LtFromEntries 1 0
3: NotEqual(foo[bar], 100) <- LtToNotEqual 2 3: NotEqual(foo.bar, 100) <- LtToNotEqual 2
``` ```

View file

@ -59,7 +59,7 @@ Equals(a, c)
First, we need to decompose all the anchored keys as (dict, key) pairs. This is the frontend description of the deduction rule. First, we need to decompose all the anchored keys as (dict, key) pairs. This is the frontend description of the deduction rule.
``` ```
IF IF
Equals(a_or[a_key], b_or[b_key) Equals(a_or[a_key], b_or[b_key])
AND AND
Equals(b_or[b_key], c_or[c_key]) Equals(b_or[b_key], c_or[c_key])
THEN THEN

View file

@ -82,10 +82,10 @@ 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

View file

@ -54,8 +54,11 @@ statement_arg_list = { statement_arg ~ ("," ~ statement_arg)* }
statement = { identifier ~ "(" ~ statement_arg_list? ~ ")" } statement = { identifier ~ "(" ~ statement_arg_list? ~ ")" }
// Anchored Key: ?Var["key_literal"] // Anchored Key: ?Var["key_literal"] or ?Var.key_identifier
anchored_key = { wildcard ~ "[" ~ literal_string ~ "]" } anchored_key = {
(wildcard ~ "[" ~ literal_string ~ "]")
| (wildcard ~ "." ~ identifier)
}
// Literal Values (ordered to avoid ambiguity, e.g., string before int) // Literal Values (ordered to avoid ambiguity, e.g., string before int)
literal_value = { literal_value = {

View file

@ -89,11 +89,16 @@ mod tests {
#[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_fails(Rule::anchored_key, "PodVar[\"key\"]"); // Needs wildcard for pod assert_fails(Rule::anchored_key, "PodVar[\"key\"]"); // Needs wildcard for pod
assert_fails(Rule::anchored_key, "?PodVar[invalid_key]"); // Key must be literal string or wildcard 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.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 cannot be empty
assert_fails(Rule::anchored_key, "?PodVar[?key]"); // Key cannot be wildcard assert_fails(Rule::anchored_key, "?PodVar[?key]"); // Key cannot be wildcard
assert_fails(Rule::anchored_key, "?PodVar.?key"); // Key cannot be wildcard
} }
#[test] #[test]
@ -214,7 +219,7 @@ mod tests {
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(

View file

@ -356,7 +356,14 @@ fn pest_pair_to_builder_arg(
} }
let key_part_pair = inner_ak_pairs.next().unwrap(); let key_part_pair = inner_ak_pairs.next().unwrap();
let key_str = parse_pest_string_literal(&key_part_pair)?; let key_str = match key_part_pair.as_rule() {
Rule::literal_string => parse_pest_string_literal(&key_part_pair)?,
Rule::identifier => key_part_pair.as_str().to_string(),
_ => unreachable!(
"unknown key type in anchored key: {:?}",
key_part_pair.as_rule()
),
};
Ok(BuilderArg::Key(root_wc_str.to_string(), key_str)) Ok(BuilderArg::Key(root_wc_str.to_string(), key_str))
} }
_ => unreachable!("Unexpected rule: {:?}", arg_content_pair.as_rule()), _ => unreachable!("Unexpected rule: {:?}", arg_content_pair.as_rule()),
@ -1065,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);
@ -1082,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);
@ -1104,7 +1111,7 @@ 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)?;
@ -1253,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)?;