diff options
| author | Jakob L. Kreuze | 2023-05-10 19:48:57 -0400 |
|---|---|---|
| committer | Jakob L. Kreuze | 2023-05-10 19:48:57 -0400 |
| commit | 1ab7d7a0c7cc40ab37b2f3285252dc0b062915c2 (patch) | |
| tree | 13ad496000fd5df930469fbe30749d54af71d2b0 | |
| parent | d763080d6b0877dc76c8816f833d29aceda5ad38 (diff) | |
Join files
| -rw-r--r-- | compiler/passes/parse-policy.scm | 196 | ||||
| -rw-r--r-- | compiler/passes/preprocess-policy.scm | 203 |
2 files changed, 189 insertions, 210 deletions
diff --git a/compiler/passes/parse-policy.scm b/compiler/passes/parse-policy.scm index e85a613..b4e2075 100644 --- a/compiler/passes/parse-policy.scm +++ b/compiler/passes/parse-policy.scm @@ -26,6 +26,182 @@ #:use-module (srfi srfi-26) #:export (parse-policy)) +(define (extract-package ast) + "Return, as a string, the name of the package specified by `ast'" + (define package-spec (assoc-ref ast "package")) + (define path-spec (assoc-ref package-spec "path")) + (define components + (map (lambda (component) + (assoc-ref component "value")) + (array->list path-spec))) + + ;; Ensure no component is #f + (assert (every identity components)) + + (string-join components ".")) + +(define (extract-imports ast) + "Return, as a list of strings, the package imports in `ast'" + (define import-spec (assoc-ref ast "imports")) + (define import-paths + (map (lambda (package-spec) + (assoc-ref package-spec "path")) + (if import-spec + (array->list import-spec) + '()))) + + ;; Ensure no path is #f + (assert (every identity import-paths)) + ;; Ensure every path is a `ref' + (assert (every (lambda (import-path) + (string= "ref" (assoc-ref import-path "type"))) + import-paths)) + + (let* ((values (map (cut assoc-ref <> "value") import-paths)) + (values (map array->list values))) + (map (lambda (value-list) + (string-join (map (cut assoc-ref <> "value") + value-list) + ".")) + values))) + +(define (preprocess-rules ast) + (define rules (array->list (assoc-ref ast "rules"))) + (define (recur rule) + (if (assoc-ref rule "else") + (list (assoc-ref rule "default") + (preprocess-head (assoc-ref rule "head")) + (make <body> + #:conjunction (preprocess-body (assoc-ref rule "body"))) + (recur (assoc-ref rule "else"))) + (list (assoc-ref rule "default") + (preprocess-head (assoc-ref rule "head")) + (make <body> + #:conjunction (preprocess-body (assoc-ref rule "body")))))) + (map recur rules)) + +(define (determine-head-kind head-obj) + "Return the appropriate symbol describing the kind of rule that `head-obj' represents + +value-eqv: assignment to a variable (varname IS value IF condition) +field-eqv: assignment to a reference (varname[field] IS value IF condition) + set-memq: assertion of set membership (varname CONTAINS value IF condition)" + (cond ((and (assoc "key" head-obj) (assoc "value" head-obj)) + 'field-eqv) + ((and (assoc "ref" head-obj) (assoc "value" head-obj)) + 'value-eqv) + ((and (assoc "key" head-obj) (assoc "ref" head-obj)) + 'set-memq) + (else (error "unidentified rule kind" head-obj)))) + +(define (preprocess-head head-obj) + ;; Head rules will sometimes have an "assign" key, but this is only when the + ;; right-hand side is a scalar value. + (let ((kind (determine-head-kind head-obj))) + (make <head> + #:name (assoc-ref head-obj "name") + #:kind kind + #:lhs (parse-reference (assoc-ref head-obj "ref")) + #:rhs (parse-literal + (if (eqv? kind 'set-memq) + (assoc-ref head-obj "key") + (assoc-ref head-obj "value")))))) + +(define (preprocess-body body-obj) + (cond ;; Non-array strings should be turned into lists. + ((and (array? body-obj) (not (string? body-obj))) + (preprocess-body (array->list body-obj))) + ;; Objects with a `type' field represent constants in Rego, so parse + ;; them into a Scheme type. + ((and (alist? body-obj) (assoc "type" body-obj)) + (parse-literal (map-key preprocess-body "value" body-obj))) + ;; Otherwise, map over all values in the object. + ((alist? body-obj) + (map (lambda (kv) + (cons (car kv) (preprocess-body (cdr kv)))) + body-obj)) + ;; Recurse on list. + ((list? body-obj) (map preprocess-body body-obj)) + ;; Look inside the `identifiers' of an `<exists-some>' + ((is-a? body-obj <exists-some>) (preprocess-body (identifiers body-obj))) + ;; Anything else is the identity. + (else body-obj))) + +(define (parse-literal term-obj) + "Parse into a Scheme record `term-obj' containing `type,' `value' keys" + (match (assoc-ref term-obj "type") + ("var" (parse-variable-ref term-obj)) + ("ref" (make <reference> #:parts (assoc-ref term-obj "value"))) + ("call" (let ((parts (assoc-ref term-obj "value"))) + (make <funcall> #:name (first parts) #:args (drop parts 1)))) + (_ (parse-constant term-obj)))) + +(define (pairs->object-literal pairs) + (let* ((pairs (array->list pairs)) + (pairs (map array->list pairs))) + (make <object-literal> + #:pairs + (map (lambda (pair) + ;; Assumption that the object is represented as pairs of constant + ;; objects where the first is the key name and the second is the + ;; value. + (assert (string= "string" (assoc-ref (first pair) "type"))) + (let ((key (assoc-ref (first pair) "value"))) + (cons key (parse-literal (second pair))))) + pairs)))) + +(define (parse-constant term-obj) + "Parse a constant literal (boolean, number, string, array, set, or object)" + (let ((type (assoc-ref term-obj "type")) + (value (assoc-ref term-obj "value"))) + (make <constant> + #:value + (cond ((or (boolean? value) + (number? value) + (string? value)) value) + ((string= "array" type) (if (null? value) + #() + (list->vector (map parse-literal (array->list value))))) + ((string= "set" type) (make <set-literal> + #:elems (map parse-literal (array->list value)))) + ((string= "object" type) (pairs->object-literal value)) + ((string= "arraycomprehension" type) + (make <comprehension> + #:kind 'array + #:key (parse-variable-ref (assoc-ref value "term")) + #:value #f + #:body (preprocess-body (assoc-ref value "body")))) + ((string= "setcomprehension" type) + (make <comprehension> + #:kind 'set + #:key (parse-variable-ref (assoc-ref value "term")) + #:value #f + #:body (preprocess-body (assoc-ref value "body")))) + ((string= "objectcomprehension" type) + (make <comprehension> + #:kind 'object + #:key (parse-variable-ref (assoc-ref value "key")) + #:value (parse-variable-ref (assoc-ref value "value")) + #:body (preprocess-body (assoc-ref value "body")))) + ;; (else (error "unidentified type" type)) + (else value) + )))) + +(define (parse-variable-ref term-obj) + "Parse a reference to a named variable" + (make <variable-ref> #:name (assoc-ref term-obj "value"))) + +(define (parse-reference ref-obj) + (assert (array? ref-obj)) + (make <reference> #:parts (map parse-literal (array->list ref-obj)))) + +(define (preprocess-policy ast) + "Lift metadata and pre-process rules of `ast' to be more easily worked with in `parse-policy'" + (assert (list? ast)) + (make <policy> + #:package (extract-package ast) + #:imports (extract-imports ast) + #:rules (preprocess-rules ast))) (define (extract-rules rules) "Group rules in `ast' by name, parsing each into a <rule> record" @@ -44,12 +220,18 @@ result) -(define (extract-rule default head body) +(define* (extract-rule default head body #:optional else-arm) "Extract an individual parsed rule" - (make <rule> - #:default default - #:head (extract-head head) - #:body (make <body> #:conjunction (extract-conjunction (inner body))))) + (if else-arm + (make <rule> + #:default default + #:head (extract-head head) + #:body (make <body> #:conjunction (extract-conjunction (inner body))) + #:else-arm (apply extract-rule else-arm)) + (make <rule> + #:default default + #:head (extract-head head) + #:body (make <body> #:conjunction (extract-conjunction (inner body)))))) (define (extract-head head) "Process any nested conjunctions in HEAD" @@ -89,6 +271,6 @@ (make <exists-some> #:identifiers (assoc-ref term-obj "symbols")) term-obj)) -(define (parse-policy policy) +(define (parse-policy ast) "Parse `ast', an abstract syntax tree from OPA, into a `<policy>'" - (over (ι-policy-rules) extract-rules policy)) + (over (ι-policy-rules) extract-rules (preprocess-policy ast))) diff --git a/compiler/passes/preprocess-policy.scm b/compiler/passes/preprocess-policy.scm deleted file mode 100644 index 51eded7..0000000 --- a/compiler/passes/preprocess-policy.scm +++ /dev/null @@ -1,203 +0,0 @@ -;;; Copyright © 2023 Jakob L. Kreuze <zerodaysfordays@sdf.org> -;;; -;;; This program is free software; you can redistribute it and/or -;;; modify it under the terms of the GNU General Public License as -;;; published by the Free Software Foundation; either version 3 of the -;;; License, or (at your option) any later version. -;;; -;;; This program is distributed in the hope that it will be useful, -;;; but WITHOUT ANY WARRANTY; without even the implied warranty of -;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -;;; General Public License for more details. -;;; -;;; You should have received a copy of the GNU General Public License -;;; along with this program. If not, see -;;; <http://www.gnu.org/licenses/>. - -(define-module (compiler passes preprocess-policy) - #:use-module ((rnrs base) #:select (assert)) - #:use-module (compiler types) - #:use-module (compiler utilities) - #:use-module (ice-9 match) - #:use-module (oop goops) - #:use-module (srfi srfi-1) - #:use-module (srfi srfi-26) - #:export (preprocess-policy - %test-preprocess-policy)) - -(define (extract-package ast) - "Return, as a string, the name of the package specified by `ast'" - (define package-spec (assoc-ref ast "package")) - (define path-spec (assoc-ref package-spec "path")) - (define components - (map (lambda (component) - (assoc-ref component "value")) - (array->list path-spec))) - - ;; Ensure no component is #f - (assert (every identity components)) - - (string-join components ".")) - -(define (extract-imports ast) - "Return, as a list of strings, the package imports in `ast'" - (define import-spec (assoc-ref ast "imports")) - (define import-paths - (map (lambda (package-spec) - (assoc-ref package-spec "path")) - (if import-spec - (array->list import-spec) - '()))) - - ;; Ensure no path is #f - (assert (every identity import-paths)) - ;; Ensure every path is a `ref' - (assert (every (lambda (import-path) - (string= "ref" (assoc-ref import-path "type"))) - import-paths)) - - (let* ((values (map (cut assoc-ref <> "value") import-paths)) - (values (map array->list values))) - (map (lambda (value-list) - (string-join (map (cut assoc-ref <> "value") - value-list) - ".")) - values))) - -(define (preprocess-rules ast) - (define rules (array->list (assoc-ref ast "rules"))) - (define (recur rule) - (if (assoc-ref rule "else") - (list (assoc-ref rule "default") - (preprocess-head (assoc-ref rule "head")) - (make <body> - #:conjunction (preprocess-body (assoc-ref rule "body"))) - (recur (assoc-ref rule "else"))) - (list (assoc-ref rule "default") - (preprocess-head (assoc-ref rule "head")) - (make <body> - #:conjunction (preprocess-body (assoc-ref rule "body")))))) - (map recur rules)) - -(define (determine-head-kind head-obj) - "Return the appropriate symbol describing the kind of rule that `head-obj' represents - -value-eqv: assignment to a variable (varname IS value IF condition) -field-eqv: assignment to a reference (varname[field] IS value IF condition) - set-memq: assertion of set membership (varname CONTAINS value IF condition)" - (cond ((and (assoc "key" head-obj) (assoc "value" head-obj)) - 'field-eqv) - ((and (assoc "ref" head-obj) (assoc "value" head-obj)) - 'value-eqv) - ((and (assoc "key" head-obj) (assoc "ref" head-obj)) - 'set-memq) - (else (error "unidentified rule kind" head-obj)))) - -(define (preprocess-head head-obj) - ;; Head rules will sometimes have an "assign" key, but this is only when the - ;; right-hand side is a scalar value. - (let ((kind (determine-head-kind head-obj))) - (make <head> - #:name (assoc-ref head-obj "name") - #:kind kind - #:lhs (parse-reference (assoc-ref head-obj "ref")) - #:rhs (parse-literal - (if (eqv? kind 'set-memq) - (assoc-ref head-obj "key") - (assoc-ref head-obj "value")))))) - -(define (preprocess-body body-obj) - (cond ;; Non-array strings should be turned into lists. - ((and (array? body-obj) (not (string? body-obj))) - (preprocess-body (array->list body-obj))) - ;; Objects with a `type' field represent constants in Rego, so parse - ;; them into a Scheme type. - ((and (alist? body-obj) (assoc "type" body-obj)) - (parse-literal (map-key preprocess-body "value" body-obj))) - ;; Otherwise, map over all values in the object. - ((alist? body-obj) - (map (lambda (kv) - (cons (car kv) (preprocess-body (cdr kv)))) - body-obj)) - ;; Recurse on list. - ((list? body-obj) (map preprocess-body body-obj)) - ;; Look inside the `identifiers' of an `<exists-some>' - ((is-a? body-obj <exists-some>) (preprocess-body (identifiers body-obj))) - ;; Anything else is the identity. - (else body-obj))) - -(define (parse-literal term-obj) - "Parse into a Scheme record `term-obj' containing `type,' `value' keys" - (match (assoc-ref term-obj "type") - ("var" (parse-variable-ref term-obj)) - ("ref" (make <reference> #:parts (assoc-ref term-obj "value"))) - ("call" (let ((parts (assoc-ref term-obj "value"))) - (make <funcall> #:name (first parts) #:args (drop parts 1)))) - (_ (parse-constant term-obj)))) - -(define (pairs->object-literal pairs) - (let* ((pairs (array->list pairs)) - (pairs (map array->list pairs))) - (make <object-literal> - #:pairs - (map (lambda (pair) - ;; Assumption that the object is represented as pairs of constant - ;; objects where the first is the key name and the second is the - ;; value. - (assert (string= "string" (assoc-ref (first pair) "type"))) - (let ((key (assoc-ref (first pair) "value"))) - (cons key (parse-literal (second pair))))) - pairs)))) - -(define (parse-constant term-obj) - "Parse a constant literal (boolean, number, string, array, set, or object)" - (let ((type (assoc-ref term-obj "type")) - (value (assoc-ref term-obj "value"))) - (make <constant> - #:value - (cond ((or (boolean? value) - (number? value) - (string? value)) value) - ((string= "array" type) (if (null? value) - #() - (list->vector (map parse-literal (array->list value))))) - ((string= "set" type) (make <set-literal> - #:elems (map parse-literal (array->list value)))) - ((string= "object" type) (pairs->object-literal value)) - ((string= "arraycomprehension" type) - (make <comprehension> - #:kind 'array - #:key (parse-variable-ref (assoc-ref value "term")) - #:value #f - #:body (preprocess-body (assoc-ref value "body")))) - ((string= "setcomprehension" type) - (make <comprehension> - #:kind 'set - #:key (parse-variable-ref (assoc-ref value "term")) - #:value #f - #:body (preprocess-body (assoc-ref value "body")))) - ((string= "objectcomprehension" type) - (make <comprehension> - #:kind 'object - #:key (parse-variable-ref (assoc-ref value "key")) - #:value (parse-variable-ref (assoc-ref value "value")) - #:body (preprocess-body (assoc-ref value "body")))) - ;; (else (error "unidentified type" type)) - (else value) - )))) - -(define (parse-variable-ref term-obj) - "Parse a reference to a named variable" - (make <variable-ref> #:name (assoc-ref term-obj "value"))) - -(define (parse-reference ref-obj) - (assert (array? ref-obj)) - (make <reference> #:parts (map parse-literal (array->list ref-obj)))) - -(define (preprocess-policy ast) - "Lift metadata and pre-process rules of `ast' to be more easily worked with in `parse-policy'" - (assert (list? ast)) - (make <policy> - #:package (extract-package ast) - #:imports (extract-imports ast) - #:rules (preprocess-rules ast))) |