polix.parser

Parser for the policy DSL.

Transforms policy expressions (vector-based S-expressions) into AST nodes that can be evaluated. Supports document accessors, function calls, literals, and thunks for delayed evaluation.

binding-accessor?

(binding-accessor? k)

Returns true if k is a binding accessor keyword.

Binding accessors are namespaced keywords that reference a bound variable from a quantifier, such as :u/role or :team/members. They have a namespace that is not a reserved namespace (doc, fn, self, param, event).

classify-token

(classify-token token position)

Classifies a token into its AST node type based on position.

Returns a Result containing an ast/ASTNode with the appropriate type: - ::ast/doc-accessor for document accessors (value is a path vector) - ::ast/self-accessor for self accessors (value is a path vector) - ::ast/param-accessor for param accessors (value is keyword) - ::ast/event-accessor for event accessors (value is a path vector) - ::ast/doc-accessor for binding accessors (value is path, metadata has namespace) - ::ast/thunk for thunkable forms - ::ast/literal for all other values

For document accessors, the token’s name is parsed as a dot-separated path: - :doc/role becomes [:role] - :doc/user.name becomes [:user :name]

For binding accessors, the namespace is preserved in metadata: - :u/role becomes [:role] with metadata {:binding-ns "u"}

Returns {:ok ast-node} on success or {:error error-map} on failure.

doc-accessor?

(doc-accessor? k)

Returns true if k is a document accessor keyword.

Document accessors are namespaced keywords with namespace "doc", such as :doc/actor-role.

event-accessor?

(event-accessor? k)

Returns true if k is an event accessor keyword.

Event accessors reference event data in triggers, such as :event/target-id or :event/amount.

extract-doc-keys

(extract-doc-keys ast)

Extracts all document accessor paths from a policy ast.

Returns a set of path vectors representing nested document access paths. Each path is a vector of keywords.

(extract-doc-keys (parse-policy [:= :doc/role "admin"]))
;=> #{[:role]}

(extract-doc-keys (parse-policy [:= :doc/user.name "Alice"]))
;=> #{[:user :name]}

extract-param-keys

(extract-param-keys ast)

Extracts all parameter keys from a policy ast.

Returns a set of keywords representing required parameters. Traverses the entire AST including quantifier bodies and let bindings.

(extract-param-keys (r/unwrap (parse-policy [:= :doc/role :param/role])))
;=> #{:role}

(extract-param-keys (r/unwrap (parse-policy [:and
                                              [:= :doc/role :param/role]
                                              [:> :doc/level :param/min-level]])))
;=> #{:role :min-level}

fn-accessor?

(fn-accessor? k)

Returns true if k is a function accessor keyword.

Function accessors are namespaced keywords with namespace "fn", such as :fn/count or :fn/sum.

let-binding?

(let-binding? form)

Returns true if form is a let binding expression.

Let bindings have the form [:let [bindings...] body].

param-accessor?

(param-accessor? k)

Returns true if k is a parameter accessor keyword.

Parameter accessors reference policy parameters, such as :param/role or :param/min-level.

parse-binding

(parse-binding binding-form position)

Parses a quantifier binding form.

Supports two forms: - [name collection-path] - basic binding - [name collection-path :where predicate] - filtered binding

The binding form specifies a variable name and the collection to iterate over. The name can be a symbol or keyword, and the collection path must be a :doc/ accessor or a binding accessor (for nested quantifiers).

(parse-binding '[u :doc/users] [0 0])
;=> {:ok {:name :u, :namespace "doc", :path [:users]}}

(parse-binding '[u :doc/users :where [:= :u/active true]] [0 0])
;=> {:ok {:name :u, :namespace "doc", :path [:users], :where <ast>}}

Returns {:ok binding-map} on success or {:error error-map} on failure.

parse-doc-path

(parse-doc-path path-str)

Parses a dot-separated path string into a vector of keywords.

Takes a path string from a document accessor keyword and returns a vector of keywords representing the nested path.

(parse-doc-path "role")              ;=> [:role]
(parse-doc-path "user.name")         ;=> [:user :name]
(parse-doc-path "user.profile.email") ;=> [:user :profile :email]

Returns {:ok path-vector} on success or {:error error-map} on failure for malformed paths (empty segments, leading/trailing dots).

parse-policy

(parse-policy expr)(parse-policy expr position)

Parses a policy DSL expression expr into an AST.

The DSL supports: - Document accessors: :doc/key-name - Self accessors: :self/computed-value (from let bindings) - Parameter accessors: :param/role (policy parameters) - Event accessors: :event/target-id (trigger event data) - Binding accessors: :u/field (within quantifier bodies) - Function calls: [:fn-name arg1 arg2 ...] - Quantifiers: [:forall [u :doc/users] body], [:exists [t :doc/teams] body] - Value functions: [:fn/count :doc/users], [:fn/count [:u :doc/users :where [...]]] - Policy references: [:auth/admin], [:auth/has-role {:role "editor"}] - Let bindings: [:let [x :doc/value] [:= :self/x 5]] - Literals: strings, numbers, keywords, etc. - Thunks: Clojure vars and function calls wrapped for delayed evaluation

Takes an optional position vector [start end] for tracking location (defaults to [0 0]).

Returns {:ok ASTNode} on success or {:error error-map} on failure.

policy-reference?

(policy-reference? form)

Returns true if form is a policy reference.

Policy references are vectors starting with a namespaced keyword that is not a built-in operator or accessor: - [:auth/admin] - [:auth/has-role {:role "editor"}]

quantifier-op?

(quantifier-op? op)

Returns true if op is a quantifier operator (:forall or :exists).

self-accessor?

(self-accessor? k)

Returns true if k is a self accessor keyword.

Self accessors reference values bound in let expressions, such as :self/computed-value.

thunkable?

(thunkable? form)

Returns true if form should be wrapped in a thunk for delayed evaluation.

Vars and non-empty lists (function calls) should be thunked.

valid-function-name?

(valid-function-name? v)

Returns true if v is a valid function name for the policy DSL.

Valid function names are keywords or symbols.