Skip to content

Latest commit

 

History

History
336 lines (260 loc) · 10.5 KB

expressions.md

File metadata and controls

336 lines (260 loc) · 10.5 KB
title Expressions
description Complete RF expression grammar, literal types, operators, precedence, typing rules, JSON encoding, and rejected syntax.

RF expressions are a strict subset of HCL expressions. The accepted shape depends on position:

Position Purpose Accepted expression family
Rule or member match.where Test one resource instance's available values Predicate
Policy assert Test facts of a selected architecture stage within a Form Policy assertion
as, to, Policy target references Name a semantic symbol Typed reference only
via, by Read instance or saved-plan evidence Traversal only
Static string fields Metadata or closed enum Constant expression producing a string
target.rules, target.dialects Static target filters Nonempty list with position-specific item type

General HCL expression evaluation is not available in where or assert. Static string fields use separate constant evaluation described below.

Literal types

Type Native examples Accepted positions Notes
String "prod", static heredoc Predicate comparison operand Valid UTF-8; no interpolation
Boolean true, false Predicate and Policy assertion Lowercase keywords
Integer-valued number 0, 3, 1.0, 1e3 Predicate comparison and Policy assertion Exact integer value within signed 64-bit range
Floating point 1.5 None Rejected
Null null None Rejected
Collection or object [], {} No general value position Only dedicated static list fields accept lists

RF validates numeric value, not spelling: 1.0 and 1e3 are accepted because their values are exact integers. 1.5 is not. Although runtime scalar evidence can contain signed integers, native RF source has no unary minus operator. Authorable number literals are therefore non-negative. -1 is rejected as an unsupported unary expression.

Static metadata strings may be empty only where the block-specific table permits it. For example, definition description may be empty, while Policy message may not.

Predicate expressions

A match.where predicate tests the instance currently considered by its surrounding Rule or member match. Unknown or sensitive instance values stay unknown, so a predicate cannot prove that such a candidate matches.

predicate       = boolean-literal
                | "!", predicate
                | "(", predicate, ")"
                | predicate, ("&&" | "||"), predicate
                | comparison ;

comparison      = predicate-operand, comparison-operator, predicate-operand ;

predicate-operand
                = string-literal
                | boolean-literal
                | integer-valued-number-literal
                | source-traversal ;

comparison-operator
                = "==" | "!=" | "<" | "<=" | ">" | ">=" ;

Examples:

where = source.enabled == true
where = source.mode == "ACTIVE"
where = source.replicas >= 2
where = source.enabled == true && source.replicas >= 2
where = !(source.mode == "DISABLED")

Rules:

  • only source.* traversal is accepted;
  • a bare Boolean literal is accepted;
  • bare traversal is not a predicate;
  • equality and inequality require the same runtime scalar type;
  • ordering requires numbers;
  • missing, unknown, collection-valued, or type-incompatible evidence makes the comparison unknown;
  • only a known true accepts a Rule candidate.

The compiler cannot always know a traversal's result type. A syntactically accepted comparison such as source.enabled > true becomes unknown at evaluation, rather than inventing ordering for Booleans.

Policy assertions

Policy assertions operate on queries over facts in the selected architecture stage of a Form. They cannot traverse raw plan or state values.

assertion       = boolean-value ;

boolean-value   = boolean-literal
                | exists-call
                | "!", boolean-value
                | "(", boolean-value, ")"
                | boolean-value, ("&&" | "||"), boolean-value
                | boolean-value, ("==" | "!="), boolean-value
                | number-value, numeric-operator, number-value ;

number-value    = integer-valued-number-literal
                | length-call
                | "(", number-value, ")" ;

numeric-operator
                = "==" | "!=" | "<" | "<=" | ">" | ">=" ;

exists-call     = "exists", "(", query, ")" ;
length-call     = "length", "(", query, ")" ;
query           = contexts-call | relations-call | contributions-call ;

Grammar is type-directed and precedence follows table below. Because a comparison produces a Boolean, its result can participate in another Boolean equality or logical operation. HCL left associativity makes both expressions valid:

assert = true == false == false
assert = 1 < 2 == true

Practical examples:

assert = true
assert = exists(contexts(rf.context.network))
assert = length(contexts(rf.context.network)) >= 1
assert = !exists(relations(aws.relation.subscribes-to))
assert = (
  exists(contexts(rf.context.network)) &&
  length(contributions(aws.rule.s3-bucket-versioning)) == 0
)

This complete Pack verifies recursive Boolean results:

policy_pack "expression-results" {
  version = "0.1.0"
}

policy "recursive-booleans" {
  target {
    rules = [aws.rule.subnet]
  }

  assert = (
    true == false == false &&
    1 < 2 == true
  )

  message = "Recursive Boolean expressions must remain true."
}

exists and length each take exactly one query call. A query call cannot be stored, compared directly, nested in another function, or passed as a general collection. See Built-ins for exact signatures.

Operators and types

Operator Operand type Result Predicate Policy
! Boolean Boolean Yes Yes
&&, || Boolean, Boolean Boolean Yes Yes
==, != Same scalar type Boolean String, Boolean, integer Boolean or number
<, <=, >, >= Integer, integer Boolean Yes Yes

No implicit conversion exists. "3" == 3 is never true. In Policy source it is rejected by static typing; in predicate evaluation mismatched runtime types produce unknown.

Precedence and associativity

From highest to lowest:

Precedence Operators
1 Parentheses
2 Unary !
3 <, <=, >, >=
4 ==, !=
5 &&
6 ||

Binary operators are left-associative. Use parentheses when mixing comparisons or Boolean operators. A chained comparison such as 1 < source.count < 5 is invalid; write source.count > 1 && source.count < 5.

Parentheses

Parentheses do not add an expression node to the compiled RF artifact; they only control grouping.

where = (source.enabled == true) && (source.replicas >= 2)

Static string expressions

Fields such as version, description, message, kind, type, provider constraints, and fact-match strategy use HCL constant evaluation separately from predicate/assertion grammar.

Result must be known, non-null string in empty evaluation context. Therefore:

  • quoted strings and static heredocs are accepted;
  • parentheses and constant string conditionals are accepted;
  • a pure "${expression}" wrapper is accepted only when wrapped constant result is string;
  • variables and runtime traversals are unavailable;
  • function calls are unavailable;
  • multi-part interpolation is rejected.

For native .rf.hcl, acceptance is result-based: any HCL expression meeting that rule is accepted, except a multi-part template. This constant-expression surface is separate from closed where and assert grammars. In .rf.json, static string fields are ordinary JSON strings.

description = "Production workload"

kind = true ? "resource" : "data"

message = <<-EOT
  Workloads must declare a runtime.
EOT

Direct literals are canonical and recommended. Dynamic templates are rejected:

description = "Workload for ${source.environment}"

This complete Dialect demonstrates accepted constant string conditionals:

dialect "constant-example" {
  version = true ? "0.1.0" : "9.9.9"

  provider "hashicorp/example" {
    version = true ? ">= 1.0.0" : "< 1.0.0"
  }
}

concept "application" {
  description = true ? "A deployable application." : "Unused"
}

rule "application" {
  match {
    kind = true ? "resource" : "data"
    type = true ? "example_application" : "other"
  }

  as = concept.application
}

JSON expression carrier

HCL JSON stores expression-valued fields in strings using "${...}":

{
  "match": {
    "type": "example_service",
    "where": "${source.enabled == true}"
  },
  "as": "${concept.application}"
}

Native .rf.hcl also accepts a pure "${expression}" wrapper for full-expression fields and unwraps it to the enclosed value. Direct native syntax is canonical:

where = source.enabled == true

Boolean and numeric JSON values can directly carry literal expressions. Raw string fields such as message remain ordinary JSON strings. Rootform does not parse arbitrary JSON strings as RF expressions.

Rejected full-expression families

In where and assert, Rootform language 0.1.0 rejects:

  • arithmetic: +, -, *, /, %;
  • unary minus;
  • conditional expressions in where and assert;
  • multi-part templates and interpolation mixed with literal text;
  • tuple, list, set, map, and object values outside dedicated list fields;
  • comprehensions and for expressions;
  • splats;
  • dynamic or string indexes;
  • relative traversals;
  • arbitrary function calls;
  • expanded function arguments;
  • bare architecture queries.

Examples:

where  = source.enabled
where  = source.replicas >= -1
where  = source.cpu > 1.5
assert = contexts(rf.context.network)
assert = "yes"
assert = length(contexts(rf.context.network))
assert = length(contexts(rf.context.network)) + 1 > 1

Depending on position, these produce INVALID_EXPRESSION, PREDICATE_UNRESOLVED, or POLICY_INVALID.

Bounds

Bound Limit
Authored expression source 4,096 bytes
Compiled expression tree depth 64
Compiled expression nodes 1,024 per expression

Exceeded source shape fails compilation. Compiled Policy Pack has additional aggregate limits under Diagnostics and limits.