mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
feat(azure-policy): add compiler skeleton with core types and stubs (#674)
Add the compiler module structure with: - core.rs: Compiler struct, CountBinding, new(), compile() pipeline, register allocation, span/emit helpers - mod.rs: module declarations, public entry points (compile_policy_rule, compile_policy_definition, etc.) - utils.rs: pure helper functions (path splitting, JSON conversion) - Stub files for conditions, expressions, fields, template dispatch, count, effects, and metadata — real implementations follow in subsequent commits.
This commit is contained in:
committed by
GitHub
parent
ce235356bc
commit
f727096a1d
20
src/languages/azure_policy/compiler/conditions.rs
Normal file
20
src/languages/azure_policy/compiler/conditions.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
#![allow(clippy::pattern_type_mismatch)]
|
||||
|
||||
//! Constraint / condition / LHS compilation.
|
||||
//!
|
||||
//! Stub — real implementation added in a later commit.
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
use crate::languages::azure_policy::ast::Constraint;
|
||||
|
||||
use super::core::Compiler;
|
||||
|
||||
impl Compiler {
|
||||
pub(super) fn compile_constraint(&mut self, _constraint: &Constraint) -> Result<u8> {
|
||||
let _ = self;
|
||||
bail!("condition compilation not yet implemented")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! Implicit allOf for unbound `[*]` wildcard fields.
|
||||
//!
|
||||
//! Stub — real implementation added in a later commit.
|
||||
195
src/languages/azure_policy/compiler/core.rs
Normal file
195
src/languages/azure_policy/compiler/core.rs
Normal file
@@ -0,0 +1,195 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
#![allow(dead_code)]
|
||||
#![allow(clippy::pattern_type_mismatch)]
|
||||
|
||||
//! Core `Compiler` struct, main compilation pipeline, and register/emit
|
||||
//! infrastructure.
|
||||
|
||||
use alloc::collections::{BTreeMap, BTreeSet};
|
||||
use alloc::string::{String, ToString as _};
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
|
||||
use crate::rvm::program::{Program, SpanInfo};
|
||||
use crate::rvm::Instruction;
|
||||
use crate::{Rc, Value};
|
||||
|
||||
use crate::languages::azure_policy::ast::PolicyRule;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct CountBinding {
|
||||
pub(super) name: Option<String>,
|
||||
pub(super) field_wildcard_prefix: Option<String>,
|
||||
pub(super) current_reg: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(super) struct Compiler {
|
||||
pub(super) program: Program,
|
||||
pub(super) register_counter: u8,
|
||||
/// High-water mark of `register_counter`.
|
||||
pub(super) register_high_water: u8,
|
||||
pub(super) source_to_index: BTreeMap<String, usize>,
|
||||
pub(super) builtin_index: BTreeMap<String, u16>,
|
||||
pub(super) count_bindings: Vec<CountBinding>,
|
||||
/// Cached register for `LoadInput` — allocated once on first use.
|
||||
pub(super) cached_input_reg: Option<u8>,
|
||||
/// Cached register for `LoadContext` — allocated once on first use.
|
||||
pub(super) cached_context_reg: Option<u8>,
|
||||
/// Map from lowercase fully-qualified alias name → short name.
|
||||
pub(super) alias_map: BTreeMap<String, String>,
|
||||
/// Map from lowercase fully-qualified alias name → modifiable flag.
|
||||
pub(super) alias_modifiable: BTreeMap<String, bool>,
|
||||
/// Default values for policy parameters.
|
||||
pub(super) parameter_defaults: Option<Value>,
|
||||
/// When set, field conditions resolve against this register instead of
|
||||
/// `input.resource`. Used for `existenceCondition`.
|
||||
pub(super) resource_override_reg: Option<u8>,
|
||||
|
||||
// -- Metadata accumulators ---------------------------------------------
|
||||
pub(super) observed_field_kinds: BTreeSet<String>,
|
||||
pub(super) observed_aliases: BTreeSet<String>,
|
||||
pub(super) observed_tag_names: BTreeSet<String>,
|
||||
pub(super) observed_operators: BTreeSet<String>,
|
||||
pub(super) observed_resource_types: BTreeSet<String>,
|
||||
pub(super) observed_uses_count: bool,
|
||||
pub(super) observed_has_dynamic_fields: bool,
|
||||
pub(super) observed_has_wildcard_aliases: bool,
|
||||
|
||||
/// When `true`, unknown aliases are silently treated as raw property paths.
|
||||
pub(super) alias_fallback_to_raw: bool,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Core infrastructure
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
impl Compiler {
|
||||
pub(super) fn new() -> Self {
|
||||
Self {
|
||||
register_counter: 0,
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn compile(mut self, rule: &PolicyRule) -> Result<Rc<Program>> {
|
||||
let cond_reg = self.compile_constraint(&rule.condition)?;
|
||||
self.emit(
|
||||
Instruction::ReturnUndefinedIfNotTrue {
|
||||
condition: cond_reg,
|
||||
},
|
||||
&rule.span,
|
||||
);
|
||||
|
||||
let effect_reg = self.compile_effect(rule)?;
|
||||
self.emit(
|
||||
Instruction::Return { value: effect_reg },
|
||||
&rule.then_block.span,
|
||||
);
|
||||
|
||||
self.program.main_entry_point = 0;
|
||||
self.program.entry_points.insert("main".to_string(), 0);
|
||||
self.program.dispatch_window_size = self.register_high_water.max(2);
|
||||
self.program.max_rule_window_size = 0;
|
||||
|
||||
if !self.program.builtin_info_table.is_empty() {
|
||||
self.program.initialize_resolved_builtins()?;
|
||||
}
|
||||
|
||||
self.program
|
||||
.validate_limits()
|
||||
.map_err(|message| anyhow!(message))?;
|
||||
|
||||
self.populate_compiled_annotations();
|
||||
|
||||
Ok(Rc::new(self.program))
|
||||
}
|
||||
|
||||
// -- register / span / emit helpers ------------------------------------
|
||||
|
||||
/// Restore `register_counter` to `saved` while protecting cached registers.
|
||||
pub(super) fn restore_register_counter(&mut self, saved: u8) {
|
||||
let mut floor = saved;
|
||||
if let Some(r) = self.cached_input_reg {
|
||||
floor = floor.max(r.saturating_add(1));
|
||||
}
|
||||
if let Some(r) = self.cached_context_reg {
|
||||
floor = floor.max(r.saturating_add(1));
|
||||
}
|
||||
self.register_counter = floor;
|
||||
}
|
||||
|
||||
pub(super) fn alloc_register(&mut self) -> Result<u8> {
|
||||
if self.register_counter == u8::MAX {
|
||||
bail!("azure-policy compiler exhausted RVM registers");
|
||||
}
|
||||
let reg = self.register_counter;
|
||||
self.register_counter = self.register_counter.saturating_add(1);
|
||||
if self.register_counter > self.register_high_water {
|
||||
self.register_high_water = self.register_counter;
|
||||
}
|
||||
Ok(reg)
|
||||
}
|
||||
|
||||
pub(super) fn span_info(&mut self, span: &crate::lexer::Span) -> SpanInfo {
|
||||
let path = span.source.get_path().to_string();
|
||||
let source_index = if let Some(index) = self.source_to_index.get(path.as_str()) {
|
||||
*index
|
||||
} else {
|
||||
let index = self
|
||||
.program
|
||||
.add_source(path.clone(), span.source.get_contents().to_string());
|
||||
self.source_to_index.insert(path, index);
|
||||
index
|
||||
};
|
||||
|
||||
SpanInfo::from_lexer_span(span, source_index)
|
||||
}
|
||||
|
||||
pub(super) fn emit(&mut self, instruction: Instruction, span: &crate::lexer::Span) {
|
||||
let span_info = self.span_info(span);
|
||||
self.program.add_instruction(instruction, Some(span_info));
|
||||
}
|
||||
|
||||
/// Return the PC (instruction index) that the *next* emitted instruction
|
||||
/// will occupy.
|
||||
pub(super) fn current_pc(&self) -> Result<u16> {
|
||||
u16::try_from(self.program.instructions.len())
|
||||
.map_err(|_| anyhow!("instruction index overflow"))
|
||||
}
|
||||
|
||||
/// Patch tracked instruction indices, setting their `end_pc` field.
|
||||
pub(super) fn patch_end_pc(&mut self, pcs: &[u16], end_pc: u16) -> Result<()> {
|
||||
for &pc in pcs {
|
||||
let idx = usize::from(pc);
|
||||
let instr = self
|
||||
.program
|
||||
.instructions
|
||||
.get_mut(idx)
|
||||
.ok_or_else(|| anyhow!("patch_end_pc: pc {} out of bounds", pc))?;
|
||||
match instr {
|
||||
Instruction::LogicalBlockStart {
|
||||
end_pc: ref mut ep, ..
|
||||
}
|
||||
| Instruction::AllOfNext {
|
||||
end_pc: ref mut ep, ..
|
||||
}
|
||||
| Instruction::AnyOfNext {
|
||||
end_pc: ref mut ep, ..
|
||||
} => {
|
||||
*ep = end_pc;
|
||||
}
|
||||
_ => {
|
||||
bail!("patch_end_pc: unexpected instruction at pc {}", pc);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
29
src/languages/azure_policy/compiler/count.rs
Normal file
29
src/languages/azure_policy/compiler/count.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
#![allow(dead_code)]
|
||||
|
||||
//! `count` / `count.where` loop compilation.
|
||||
//!
|
||||
//! Stub — real implementation added in a later commit.
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
use crate::languages::azure_policy::ast::{Condition, CountNode};
|
||||
|
||||
use super::core::Compiler;
|
||||
|
||||
impl Compiler {
|
||||
pub(super) fn compile_count(&mut self, _count_node: &CountNode) -> Result<u8> {
|
||||
let _ = self;
|
||||
bail!("count compilation not yet implemented")
|
||||
}
|
||||
|
||||
pub(super) const fn try_compile_count_as_any(
|
||||
&mut self,
|
||||
_count_node: &CountNode,
|
||||
_condition: &Condition,
|
||||
) -> Result<Option<u8>> {
|
||||
_ = self.register_counter;
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
6
src/languages/azure_policy/compiler/count_any.rs
Normal file
6
src/languages/azure_policy/compiler/count_any.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! Existence-pattern optimization (count → Any loop).
|
||||
//!
|
||||
//! Stub — real implementation added in a later commit.
|
||||
40
src/languages/azure_policy/compiler/count_bindings.rs
Normal file
40
src/languages/azure_policy/compiler/count_bindings.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
#![allow(dead_code)]
|
||||
|
||||
//! Count-binding resolution and `current()` references.
|
||||
//!
|
||||
//! Stub — real implementation added in a later commit.
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
use super::core::{Compiler, CountBinding};
|
||||
|
||||
impl Compiler {
|
||||
pub(super) const fn resolve_count_binding(
|
||||
&self,
|
||||
_field_path: &str,
|
||||
) -> Result<Option<CountBinding>> {
|
||||
_ = self.register_counter;
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub(super) fn compile_from_binding(
|
||||
&mut self,
|
||||
_binding: CountBinding,
|
||||
_field_path: &str,
|
||||
_span: &crate::lexer::Span,
|
||||
) -> Result<u8> {
|
||||
let _ = self;
|
||||
bail!("count binding compilation not yet implemented")
|
||||
}
|
||||
|
||||
pub(super) fn compile_current_reference(
|
||||
&mut self,
|
||||
_key: &str,
|
||||
_span: &crate::lexer::Span,
|
||||
) -> Result<u8> {
|
||||
let _ = self;
|
||||
bail!("current() reference not yet implemented")
|
||||
}
|
||||
}
|
||||
30
src/languages/azure_policy/compiler/effects.rs
Normal file
30
src/languages/azure_policy/compiler/effects.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
#![allow(dead_code)]
|
||||
|
||||
//! Effect compilation (dispatch + cross-resource).
|
||||
//!
|
||||
//! Stub — real implementation added in a later commit.
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
use crate::languages::azure_policy::ast::PolicyRule;
|
||||
|
||||
use super::core::Compiler;
|
||||
|
||||
impl Compiler {
|
||||
pub(super) fn compile_effect(&mut self, _rule: &PolicyRule) -> Result<u8> {
|
||||
let _ = self;
|
||||
bail!("effect compilation not yet implemented")
|
||||
}
|
||||
|
||||
pub(super) fn wrap_effect_result(
|
||||
&mut self,
|
||||
_effect_name_reg: u8,
|
||||
_details_reg: Option<u8>,
|
||||
_span: &crate::lexer::Span,
|
||||
) -> Result<u8> {
|
||||
let _ = self;
|
||||
bail!("wrap_effect_result not yet implemented")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! Modify / Append effect detail compilation.
|
||||
//!
|
||||
//! Stub — real implementation added in a later commit.
|
||||
53
src/languages/azure_policy/compiler/expressions.rs
Normal file
53
src/languages/azure_policy/compiler/expressions.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
#![allow(dead_code)]
|
||||
|
||||
//! Template-expression and call-expression compilation.
|
||||
//!
|
||||
//! Stub — real implementation added in a later commit.
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
use crate::languages::azure_policy::ast::{Expr, JsonValue, ValueOrExpr};
|
||||
|
||||
use super::core::Compiler;
|
||||
|
||||
impl Compiler {
|
||||
pub(super) fn compile_value_or_expr(
|
||||
&mut self,
|
||||
_voe: &ValueOrExpr,
|
||||
_span: &crate::lexer::Span,
|
||||
) -> Result<u8> {
|
||||
let _ = self;
|
||||
bail!("expression compilation not yet implemented")
|
||||
}
|
||||
|
||||
pub(super) fn compile_json_value(
|
||||
&mut self,
|
||||
_value: &JsonValue,
|
||||
_span: &crate::lexer::Span,
|
||||
) -> Result<u8> {
|
||||
let _ = self;
|
||||
bail!("JSON value compilation not yet implemented")
|
||||
}
|
||||
|
||||
pub(super) fn compile_expr(&mut self, _expr: &Expr) -> Result<u8> {
|
||||
let _ = self;
|
||||
bail!("expression compilation not yet implemented")
|
||||
}
|
||||
|
||||
pub(super) fn compile_call_expr(
|
||||
&mut self,
|
||||
_span: &crate::lexer::Span,
|
||||
_func: &str,
|
||||
_args: &[Expr],
|
||||
) -> Result<u8> {
|
||||
let _ = self;
|
||||
bail!("call expression compilation not yet implemented")
|
||||
}
|
||||
|
||||
pub(super) fn compile_call_args(&mut self, _args: &[Expr]) -> Result<alloc::vec::Vec<u8>> {
|
||||
let _ = self;
|
||||
bail!("call args compilation not yet implemented")
|
||||
}
|
||||
}
|
||||
56
src/languages/azure_policy/compiler/fields.rs
Normal file
56
src/languages/azure_policy/compiler/fields.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
#![allow(dead_code)]
|
||||
|
||||
//! Field-kind and resource-path compilation.
|
||||
//!
|
||||
//! Stub — real implementation added in a later commit.
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
use crate::languages::azure_policy::ast::FieldKind;
|
||||
|
||||
use super::core::Compiler;
|
||||
|
||||
impl Compiler {
|
||||
pub(super) fn compile_field_kind(
|
||||
&mut self,
|
||||
_kind: &FieldKind,
|
||||
_span: &crate::lexer::Span,
|
||||
) -> Result<u8> {
|
||||
let _ = self;
|
||||
bail!("field compilation not yet implemented")
|
||||
}
|
||||
|
||||
pub(super) fn compile_field_path_expression(
|
||||
&mut self,
|
||||
_field_path: &str,
|
||||
_span: &crate::lexer::Span,
|
||||
) -> Result<u8> {
|
||||
let _ = self;
|
||||
bail!("field path compilation not yet implemented")
|
||||
}
|
||||
|
||||
pub(super) fn compile_resource_path_value(
|
||||
&mut self,
|
||||
_field_path: &str,
|
||||
_span: &crate::lexer::Span,
|
||||
) -> Result<u8> {
|
||||
let _ = self;
|
||||
bail!("resource path compilation not yet implemented")
|
||||
}
|
||||
|
||||
pub(super) fn compile_resource_root(&mut self, _span: &crate::lexer::Span) -> Result<u8> {
|
||||
let _ = self;
|
||||
bail!("resource root compilation not yet implemented")
|
||||
}
|
||||
|
||||
pub(super) fn compile_field_wildcard_collect(
|
||||
&mut self,
|
||||
_field_path: &str,
|
||||
_span: &crate::lexer::Span,
|
||||
) -> Result<u8> {
|
||||
let _ = self;
|
||||
bail!("wildcard collect not yet implemented")
|
||||
}
|
||||
}
|
||||
52
src/languages/azure_policy/compiler/metadata.rs
Normal file
52
src/languages/azure_policy/compiler/metadata.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
#![allow(dead_code)]
|
||||
|
||||
//! Annotation accumulation and metadata population.
|
||||
//!
|
||||
//! Stub — real implementation added in a later commit.
|
||||
|
||||
use crate::languages::azure_policy::ast::{EffectNode, OperatorKind, PolicyDefinition, PolicyRule};
|
||||
|
||||
use super::core::Compiler;
|
||||
|
||||
impl Compiler {
|
||||
pub(super) const fn record_field_kind(&mut self, _name: &str) {
|
||||
_ = self.register_counter;
|
||||
}
|
||||
pub(super) const fn record_alias(&mut self, _path: &str) {
|
||||
_ = self.register_counter;
|
||||
}
|
||||
pub(super) const fn record_tag_name(&mut self, _tag: &str) {
|
||||
_ = self.register_counter;
|
||||
}
|
||||
pub(super) const fn record_operator(&mut self, _kind: &OperatorKind) {
|
||||
_ = self.register_counter;
|
||||
}
|
||||
pub(super) const fn record_resource_type_from_condition(
|
||||
&mut self,
|
||||
_condition: &crate::languages::azure_policy::ast::Condition,
|
||||
) {
|
||||
_ = self.register_counter;
|
||||
}
|
||||
|
||||
#[allow(clippy::unused_self)]
|
||||
pub(super) fn resolve_effect_annotation(&self, rule: &PolicyRule) -> alloc::string::String {
|
||||
rule.then_block.effect.raw.clone()
|
||||
}
|
||||
|
||||
#[allow(clippy::unused_self)]
|
||||
pub(super) fn resolve_effect_kind(
|
||||
&self,
|
||||
effect: &EffectNode,
|
||||
) -> crate::languages::azure_policy::ast::EffectKind {
|
||||
effect.kind.clone()
|
||||
}
|
||||
|
||||
pub(super) const fn populate_compiled_annotations(&mut self) {
|
||||
_ = self.register_counter;
|
||||
}
|
||||
pub(super) const fn populate_definition_metadata(&mut self, _defn: &PolicyDefinition) {
|
||||
_ = self.register_counter;
|
||||
}
|
||||
}
|
||||
153
src/languages/azure_policy/compiler/mod.rs
Normal file
153
src/languages/azure_policy/compiler/mod.rs
Normal file
@@ -0,0 +1,153 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! Azure Policy AST → RVM compiler.
|
||||
//!
|
||||
//! The compiler is split across several files:
|
||||
//! - [`core`]: `Compiler` struct, main pipeline, register/emit helpers
|
||||
//! - [`conditions`]: constraint / condition / LHS compilation
|
||||
//! - [`conditions_wildcard`]: implicit allOf for unbound `[*]` fields
|
||||
//! - [`count`]: `count` / `count.where` loops
|
||||
//! - [`count_any`]: existence-pattern optimization (count → Any loop)
|
||||
//! - [`count_bindings`]: count-binding resolution and `current()` references
|
||||
//! - [`expressions`]: template-expression and call-expression compilation
|
||||
//! - [`fields`]: field-kind and resource-path compilation
|
||||
//! - [`template_dispatch`]: ARM template function dispatch
|
||||
//! - [`effects`]: effect compilation (dispatch + cross-resource)
|
||||
//! - [`effects_modify_append`]: Modify / Append detail compilation
|
||||
//! - [`metadata`]: annotation accumulation and population
|
||||
//! - [`utils`]: pure helper functions (path splitting, JSON conversion)
|
||||
|
||||
mod conditions;
|
||||
mod conditions_wildcard;
|
||||
mod core;
|
||||
mod count;
|
||||
mod count_any;
|
||||
mod count_bindings;
|
||||
mod effects;
|
||||
mod effects_modify_append;
|
||||
mod expressions;
|
||||
mod fields;
|
||||
mod metadata;
|
||||
mod template_dispatch;
|
||||
mod utils;
|
||||
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::string::{String, ToString as _};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::languages::azure_policy::ast::{PolicyDefinition, PolicyRule};
|
||||
use crate::rvm::program::Program;
|
||||
use crate::{Rc, Value};
|
||||
|
||||
use self::core::Compiler;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Initialise compiler language metadata and effect annotation.
|
||||
fn init_effect_annotation(compiler: &mut Compiler, rule: &PolicyRule) {
|
||||
compiler.program.metadata.language = "azure_policy".to_string();
|
||||
let effect = compiler.resolve_effect_annotation(rule);
|
||||
compiler
|
||||
.program
|
||||
.metadata
|
||||
.annotations
|
||||
.insert("effect".to_string(), Value::String(effect.as_str().into()));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public entry points
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Compile a parsed Azure Policy rule into an RVM program.
|
||||
pub fn compile_policy_rule(rule: &PolicyRule) -> Result<Rc<Program>> {
|
||||
let mut compiler = Compiler::new();
|
||||
init_effect_annotation(&mut compiler, rule);
|
||||
compiler.compile(rule)
|
||||
}
|
||||
|
||||
/// Compile a parsed Azure Policy rule with alias resolution.
|
||||
///
|
||||
/// The `alias_map` maps lowercase fully-qualified alias names to their short
|
||||
/// names. Obtain it from
|
||||
/// [`AliasRegistry::alias_map()`](crate::languages::azure_policy::aliases::AliasRegistry::alias_map).
|
||||
pub fn compile_policy_rule_with_aliases(
|
||||
rule: &PolicyRule,
|
||||
alias_map: BTreeMap<String, String>,
|
||||
alias_modifiable: BTreeMap<String, bool>,
|
||||
) -> Result<Rc<Program>> {
|
||||
let mut compiler = Compiler::new();
|
||||
compiler.alias_map = alias_map;
|
||||
compiler.alias_modifiable = alias_modifiable;
|
||||
init_effect_annotation(&mut compiler, rule);
|
||||
compiler.compile(rule)
|
||||
}
|
||||
|
||||
/// Compile a parsed Azure Policy definition into an RVM program.
|
||||
///
|
||||
/// This extracts the `policyRule` from the definition and compiles it.
|
||||
/// Parameter `defaultValue`s are collected so that later compiler passes
|
||||
/// (effect compilation, metadata population) can reference them.
|
||||
pub fn compile_policy_definition(defn: &PolicyDefinition) -> Result<Rc<Program>> {
|
||||
let mut compiler = Compiler::new();
|
||||
compiler.parameter_defaults = Some(build_parameter_defaults(&defn.parameters)?);
|
||||
compiler.populate_definition_metadata(defn);
|
||||
init_effect_annotation(&mut compiler, &defn.policy_rule);
|
||||
compiler.compile(&defn.policy_rule)
|
||||
}
|
||||
|
||||
/// Compile a parsed Azure Policy definition with alias resolution.
|
||||
pub fn compile_policy_definition_with_aliases(
|
||||
defn: &PolicyDefinition,
|
||||
alias_map: BTreeMap<String, String>,
|
||||
alias_modifiable: BTreeMap<String, bool>,
|
||||
) -> Result<Rc<Program>> {
|
||||
let mut compiler = Compiler::new();
|
||||
compiler.alias_map = alias_map;
|
||||
compiler.alias_modifiable = alias_modifiable;
|
||||
compiler.parameter_defaults = Some(build_parameter_defaults(&defn.parameters)?);
|
||||
compiler.populate_definition_metadata(defn);
|
||||
init_effect_annotation(&mut compiler, &defn.policy_rule);
|
||||
compiler.compile(&defn.policy_rule)
|
||||
}
|
||||
|
||||
/// Compile a parsed Azure Policy definition with alias resolution and
|
||||
/// optional fallback behaviour for unknown aliases.
|
||||
///
|
||||
/// When `alias_fallback_to_raw` is `true`, field paths that do not resolve to
|
||||
/// a known alias are silently treated as raw property paths.
|
||||
pub fn compile_policy_definition_with_aliases_opts(
|
||||
defn: &PolicyDefinition,
|
||||
alias_map: BTreeMap<String, String>,
|
||||
alias_modifiable: BTreeMap<String, bool>,
|
||||
alias_fallback_to_raw: bool,
|
||||
) -> Result<Rc<Program>> {
|
||||
let mut compiler = Compiler::new();
|
||||
compiler.alias_map = alias_map;
|
||||
compiler.alias_modifiable = alias_modifiable;
|
||||
compiler.alias_fallback_to_raw = alias_fallback_to_raw;
|
||||
compiler.parameter_defaults = Some(build_parameter_defaults(&defn.parameters)?);
|
||||
compiler.populate_definition_metadata(defn);
|
||||
init_effect_annotation(&mut compiler, &defn.policy_rule);
|
||||
compiler.compile(&defn.policy_rule)
|
||||
}
|
||||
|
||||
/// Build a `Value::Object` of `{ param_name: defaultValue }` from
|
||||
/// the parsed parameter definitions.
|
||||
fn build_parameter_defaults(
|
||||
params: &[crate::languages::azure_policy::ast::ParameterDefinition],
|
||||
) -> Result<Value> {
|
||||
use crate::languages::azure_policy::compiler::utils::json_value_to_runtime;
|
||||
let mut obj = Value::new_object();
|
||||
let map = obj.as_object_mut()?;
|
||||
for param in params {
|
||||
if let Some(ref default_val) = param.default_value {
|
||||
let runtime_val = json_value_to_runtime(default_val)?;
|
||||
map.insert(Value::from(param.name.clone()), runtime_val);
|
||||
}
|
||||
}
|
||||
Ok(obj)
|
||||
}
|
||||
25
src/languages/azure_policy/compiler/template_dispatch.rs
Normal file
25
src/languages/azure_policy/compiler/template_dispatch.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
#![allow(dead_code)]
|
||||
|
||||
//! ARM template function dispatch.
|
||||
//!
|
||||
//! Stub — real implementation added in a later commit.
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::languages::azure_policy::ast::Expr;
|
||||
|
||||
use super::core::Compiler;
|
||||
|
||||
impl Compiler {
|
||||
pub(super) const fn compile_arm_template_function(
|
||||
&mut self,
|
||||
_function_name: &str,
|
||||
_span: &crate::lexer::Span,
|
||||
_args: &[Expr],
|
||||
) -> Result<Option<u8>> {
|
||||
_ = self.register_counter;
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
427
src/languages/azure_policy/compiler/utils.rs
Normal file
427
src/languages/azure_policy/compiler/utils.rs
Normal file
@@ -0,0 +1,427 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
#![allow(dead_code, clippy::pattern_type_mismatch, clippy::redundant_pub_crate)]
|
||||
|
||||
//! Free helper functions used by the Azure Policy compiler.
|
||||
|
||||
use alloc::string::{String, ToString as _};
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
|
||||
use crate::languages::azure_policy::ast::{Expr, ExprLiteral, JsonValue, ObjectEntry};
|
||||
use crate::Value;
|
||||
|
||||
/// Extract a string literal from an expression, or bail.
|
||||
pub(super) fn extract_string_literal(expr: &Expr) -> Result<String> {
|
||||
match expr {
|
||||
Expr::Literal {
|
||||
value: ExprLiteral::String(value),
|
||||
..
|
||||
} => Ok(value.clone()),
|
||||
other => bail!("expected string literal argument, found {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
/// Split a count field path at the `[*]` wildcard into `(prefix, optional_suffix)`.
|
||||
pub(super) fn split_count_wildcard_path(path: &str) -> Result<(String, Option<String>)> {
|
||||
let wildcard_index = path
|
||||
.find("[*]")
|
||||
.ok_or_else(|| anyhow!("count.field must contain [*]: {}", path))?;
|
||||
|
||||
let (prefix_str, rest) = path.split_at(wildcard_index);
|
||||
let prefix = prefix_str.trim_end_matches('.');
|
||||
if prefix.is_empty() {
|
||||
bail!(
|
||||
"count.field must have a non-empty prefix before [*]: {}",
|
||||
path
|
||||
);
|
||||
}
|
||||
let after_wildcard = &rest[3..];
|
||||
if after_wildcard.contains("[*]") {
|
||||
bail!("nested [*] wildcards are not supported: {}", path);
|
||||
}
|
||||
let suffix_str = after_wildcard.trim_start_matches('.');
|
||||
let suffix = if suffix_str.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(suffix_str.to_string())
|
||||
};
|
||||
|
||||
Ok((prefix.to_string(), suffix))
|
||||
}
|
||||
|
||||
/// Split a dotted path (without `[*]` wildcards) into its component segments.
|
||||
///
|
||||
/// Handles bracket notation:
|
||||
/// - `tags['key']` → `["tags", "key"]`
|
||||
/// - `properties['network-acls']` → `["properties", "network-acls"]`
|
||||
/// - `properties.ipRules[0].value` → `["properties", "ipRules", "0", "value"]`
|
||||
pub(super) fn split_path_without_wildcards(path: &str) -> Result<Vec<String>> {
|
||||
if path.trim().is_empty() {
|
||||
bail!("empty path");
|
||||
}
|
||||
if path.contains("[*]") {
|
||||
bail!(
|
||||
"wildcard field paths are not supported in this context: {}",
|
||||
path
|
||||
);
|
||||
}
|
||||
if path.ends_with('.') {
|
||||
bail!("path must not end with '.': {}", path);
|
||||
}
|
||||
|
||||
let mut parts = Vec::new();
|
||||
let mut token = String::new();
|
||||
let mut bracket = String::new();
|
||||
let mut in_bracket = false;
|
||||
let mut after_bracket = false;
|
||||
|
||||
for ch in path.chars() {
|
||||
match ch {
|
||||
'.' if !in_bracket => {
|
||||
let t = token.trim();
|
||||
if t.is_empty() && !after_bracket {
|
||||
bail!("empty segment in path: {}", path);
|
||||
}
|
||||
if !t.is_empty() {
|
||||
parts.push(t.to_string());
|
||||
}
|
||||
token.clear();
|
||||
after_bracket = false;
|
||||
}
|
||||
'[' => {
|
||||
if in_bracket {
|
||||
bail!("nested brackets in path: {}", path);
|
||||
}
|
||||
in_bracket = true;
|
||||
after_bracket = false;
|
||||
let t = token.trim();
|
||||
if !t.is_empty() {
|
||||
parts.push(t.to_string());
|
||||
}
|
||||
token.clear();
|
||||
}
|
||||
']' => {
|
||||
if !in_bracket {
|
||||
bail!("unexpected closing bracket in path: {}", path);
|
||||
}
|
||||
in_bracket = false;
|
||||
after_bracket = true;
|
||||
let cleaned = bracket
|
||||
.trim()
|
||||
.trim_matches('"')
|
||||
.trim_matches('\'')
|
||||
.to_string();
|
||||
if cleaned.is_empty() {
|
||||
bail!("empty bracket in path: {}", path);
|
||||
}
|
||||
parts.push(cleaned);
|
||||
bracket.clear();
|
||||
}
|
||||
_ => {
|
||||
if in_bracket {
|
||||
bracket.push(ch);
|
||||
} else {
|
||||
if after_bracket {
|
||||
bail!("expected '.' or '[' after bracket in path: {}", path);
|
||||
}
|
||||
token.push(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if in_bracket {
|
||||
bail!("unclosed bracket in path: {}", path);
|
||||
}
|
||||
|
||||
let t = token.trim();
|
||||
if !t.is_empty() {
|
||||
parts.push(t.to_string());
|
||||
}
|
||||
|
||||
Ok(parts)
|
||||
}
|
||||
|
||||
/// Convert a parsed JSON value from the Azure Policy AST into a runtime [`Value`].
|
||||
pub(crate) fn json_value_to_runtime(value: &JsonValue) -> Result<Value> {
|
||||
match value {
|
||||
JsonValue::Null(_) => Ok(Value::Null),
|
||||
JsonValue::Bool(_, b) => Ok(Value::Bool(*b)),
|
||||
JsonValue::Number(_, raw) => {
|
||||
Value::from_numeric_string(raw).map_err(|_| anyhow!("invalid number literal: {}", raw))
|
||||
}
|
||||
JsonValue::Str(_, s) => Ok(Value::from(s.clone())),
|
||||
JsonValue::Array(_, items) => {
|
||||
let mut out = Vec::with_capacity(items.len());
|
||||
for item in items {
|
||||
out.push(json_value_to_runtime(item)?);
|
||||
}
|
||||
Ok(Value::from(out))
|
||||
}
|
||||
JsonValue::Object(_, entries) => {
|
||||
let mut obj = Value::new_object();
|
||||
let map = obj.as_object_mut()?;
|
||||
for ObjectEntry {
|
||||
key,
|
||||
value: entry_value,
|
||||
..
|
||||
} in entries
|
||||
{
|
||||
map.insert(
|
||||
Value::from(key.clone()),
|
||||
json_value_to_runtime(entry_value)?,
|
||||
);
|
||||
}
|
||||
Ok(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
use alloc::vec;
|
||||
|
||||
use crate::lexer::Source;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn dummy_span() -> crate::lexer::Span {
|
||||
let source = Source::from_contents("test".into(), " ".into()).unwrap();
|
||||
crate::lexer::Span {
|
||||
source,
|
||||
line: 1,
|
||||
col: 1,
|
||||
start: 0,
|
||||
end: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// extract_string_literal
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn extract_string_literal_ok() {
|
||||
let expr = Expr::Literal {
|
||||
span: dummy_span(),
|
||||
value: ExprLiteral::String("hello".into()),
|
||||
};
|
||||
assert_eq!(extract_string_literal(&expr).unwrap(), "hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_string_literal_number_err() {
|
||||
let expr = Expr::Literal {
|
||||
span: dummy_span(),
|
||||
value: ExprLiteral::Number("42".into()),
|
||||
};
|
||||
extract_string_literal(&expr).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_string_literal_ident_err() {
|
||||
let expr = Expr::Ident {
|
||||
span: dummy_span(),
|
||||
name: "x".into(),
|
||||
};
|
||||
extract_string_literal(&expr).unwrap_err();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// json_value_to_runtime
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn json_null() {
|
||||
let v = json_value_to_runtime(&JsonValue::Null(dummy_span())).unwrap();
|
||||
assert_eq!(v, Value::Null);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_bool() {
|
||||
let v = json_value_to_runtime(&JsonValue::Bool(dummy_span(), true)).unwrap();
|
||||
assert_eq!(v, Value::Bool(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_number_int() {
|
||||
let v = json_value_to_runtime(&JsonValue::Number(dummy_span(), "42".into())).unwrap();
|
||||
assert_eq!(v, Value::from(42_i64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_number_float() {
|
||||
let v = json_value_to_runtime(&JsonValue::Number(dummy_span(), "1.5".into())).unwrap();
|
||||
assert_eq!(v, Value::from(1.5_f64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_number_invalid() {
|
||||
json_value_to_runtime(&JsonValue::Number(dummy_span(), "abc".into())).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_string() {
|
||||
let v = json_value_to_runtime(&JsonValue::Str(dummy_span(), "hello".into())).unwrap();
|
||||
assert_eq!(v, Value::from("hello".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_array() {
|
||||
let arr = JsonValue::Array(
|
||||
dummy_span(),
|
||||
vec![
|
||||
JsonValue::Bool(dummy_span(), true),
|
||||
JsonValue::Null(dummy_span()),
|
||||
],
|
||||
);
|
||||
let v = json_value_to_runtime(&arr).unwrap();
|
||||
let items = v.as_array().unwrap();
|
||||
assert_eq!(items.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_object() {
|
||||
let obj = JsonValue::Object(
|
||||
dummy_span(),
|
||||
vec![ObjectEntry {
|
||||
key_span: dummy_span(),
|
||||
key: "k".into(),
|
||||
value: JsonValue::Bool(dummy_span(), false),
|
||||
}],
|
||||
);
|
||||
let v = json_value_to_runtime(&obj).unwrap();
|
||||
let map = v.as_object().unwrap();
|
||||
assert_eq!(map.len(), 1);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// split_count_wildcard_path
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn wildcard_simple() {
|
||||
let (prefix, suffix) = split_count_wildcard_path("a.b[*].c").unwrap();
|
||||
assert_eq!(prefix, "a.b");
|
||||
assert_eq!(suffix.as_deref(), Some("c"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wildcard_no_suffix() {
|
||||
let (prefix, suffix) = split_count_wildcard_path("a[*]").unwrap();
|
||||
assert_eq!(prefix, "a");
|
||||
assert_eq!(suffix, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wildcard_trailing_dot_prefix() {
|
||||
let (prefix, _) = split_count_wildcard_path("a.[*].c").unwrap();
|
||||
assert_eq!(prefix, "a");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wildcard_missing() {
|
||||
split_count_wildcard_path("a.b.c").unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wildcard_empty_prefix() {
|
||||
split_count_wildcard_path("[*].c").unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wildcard_nested() {
|
||||
split_count_wildcard_path("a[*].b[*].c").unwrap_err();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// split_path_without_wildcards
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn path_simple_dotted() {
|
||||
assert_eq!(
|
||||
split_path_without_wildcards("a.b.c").unwrap(),
|
||||
vec!["a", "b", "c"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_bracket_quoted() {
|
||||
assert_eq!(
|
||||
split_path_without_wildcards("tags['key']").unwrap(),
|
||||
vec!["tags", "key"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_bracket_numeric() {
|
||||
assert_eq!(
|
||||
split_path_without_wildcards("a[0].b").unwrap(),
|
||||
vec!["a", "0", "b"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_single_segment() {
|
||||
assert_eq!(split_path_without_wildcards("name").unwrap(), vec!["name"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_empty() {
|
||||
split_path_without_wildcards("").unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_whitespace_only() {
|
||||
split_path_without_wildcards(" ").unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_trailing_dot() {
|
||||
split_path_without_wildcards("a.").unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_leading_dot() {
|
||||
split_path_without_wildcards(".a").unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_consecutive_dots() {
|
||||
split_path_without_wildcards("a..b").unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_wildcard_rejected() {
|
||||
split_path_without_wildcards("a[*].b").unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_nested_brackets() {
|
||||
split_path_without_wildcards("a[[0]]").unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_stray_close_bracket() {
|
||||
split_path_without_wildcards("a]b").unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_unclosed_bracket() {
|
||||
split_path_without_wildcards("a[0").unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_empty_bracket() {
|
||||
split_path_without_wildcards("a[]").unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_char_after_bracket_without_separator() {
|
||||
split_path_without_wildcards("a[0]b").unwrap_err();
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@
|
||||
#[allow(clippy::pattern_type_mismatch)]
|
||||
pub mod aliases;
|
||||
pub mod ast;
|
||||
#[cfg(feature = "rvm")]
|
||||
pub mod compiler;
|
||||
pub mod expr;
|
||||
pub mod parser;
|
||||
pub mod strings;
|
||||
|
||||
Reference in New Issue
Block a user