use crate::ast::{GeomStmt, MetaStmt, SourceLocation};
use crate::parser::meta::MetadataStatement;
use crate::{BoxPair, DslEntry, ParseError};
use serde_json::Value;
use std::collections::BTreeMap;
#[derive(Debug, Clone, PartialEq)]
pub struct MetadataAssignment {
pub value: Value,
pub source: SourceLocation,
}
#[derive(Debug, Clone, PartialEq)]
pub struct EvaluatedRegionTable {
pub regions: BTreeMap<String, EvaluatedRegionData>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct EvaluatedRegionData {
pub boxes: Option<Vec<BoxPair>>,
pub metadata: BTreeMap<String, MetadataAssignment>,
}
impl EvaluatedRegionTable {
pub fn new() -> Self {
Self {
regions: BTreeMap::new(),
}
}
pub fn set_region_boxes(&mut self, region: String, boxes: Option<Vec<BoxPair>>) {
let entry = self
.regions
.entry(region)
.or_insert_with(|| EvaluatedRegionData {
boxes: None,
metadata: BTreeMap::new(),
});
entry.boxes = boxes;
}
pub fn add_metadata(
&mut self,
region: String,
key: String,
assignment: MetadataAssignment,
) -> Result<(), ParseError> {
let entry = self
.regions
.entry(region.clone())
.or_insert_with(|| EvaluatedRegionData {
boxes: None,
metadata: BTreeMap::new(),
});
if let Some(existing) = entry.metadata.get(&key) {
if existing.value != assignment.value {
return Err(ParseError::MetadataConflict(Box::new(
crate::MetadataConflictError {
region,
key,
first_value: existing.value.clone(),
first_source: existing.source.clone(),
conflict_value: assignment.value,
conflict_source: assignment.source,
},
)));
}
} else {
entry.metadata.insert(key, assignment);
}
Ok(())
}
}
impl Default for EvaluatedRegionTable {
fn default() -> Self {
Self::new()
}
}
fn find_last_geometry_in_tuple(geom_stmts: &[GeomStmt], tuple_idx: usize) -> Option<String> {
let mut last_region: Option<String> = None;
for stmt in geom_stmts {
if stmt.tuple_idx == tuple_idx {
if let Some(region) = stmt.region() {
last_region = Some(region.to_string());
} else {
last_region = Some(stmt.anonymous_key());
}
}
}
last_region
}
fn is_wildcard_pattern(target: &str) -> bool {
target.contains('*')
}
fn matches_wildcard(region_name: &str, pattern: &str) -> bool {
if let Some(prefix) = pattern.strip_suffix('*') {
region_name.starts_with(prefix)
} else if let Some(suffix) = pattern.strip_prefix('*') {
region_name.ends_with(suffix)
} else {
region_name == pattern
}
}
pub fn apply_metadata_pass(
evaluated_table: &mut EvaluatedRegionTable,
geom_stmts: &[GeomStmt],
meta_stmts: &[MetaStmt],
) -> Result<(), ParseError> {
for meta_stmt in meta_stmts {
let source = SourceLocation::new(meta_stmt.tuple_idx, meta_stmt.stmt_idx);
match &meta_stmt.statement {
MetadataStatement::Current { key, value } => {
let target_region = find_last_geometry_in_tuple(geom_stmts, meta_stmt.tuple_idx)
.ok_or_else(|| ParseError::NoCurrentRegion {
source: source.clone(),
})?;
let assignment = MetadataAssignment {
value: value.clone(),
source: source.clone(),
};
evaluated_table.add_metadata(target_region, key.clone(), assignment)?;
}
MetadataStatement::Targeted { target, key, value } => {
let assignment = MetadataAssignment {
value: value.clone(),
source: source.clone(),
};
if is_wildcard_pattern(target) {
evaluated_table.add_metadata(
target.clone(),
key.clone(),
assignment.clone(),
)?;
let matching_regions: Vec<String> = evaluated_table
.regions
.keys()
.filter(|region| matches_wildcard(region, target) && *region != target)
.cloned()
.collect();
for region in matching_regions {
evaluated_table.add_metadata(region, key.clone(), assignment.clone())?;
}
} else {
evaluated_table.add_metadata(target.clone(), key.clone(), assignment)?;
}
}
}
}
Ok(())
}
pub fn build_dsl_map(evaluated_table: EvaluatedRegionTable) -> BTreeMap<String, DslEntry> {
let mut dsl_map = BTreeMap::new();
for (region_name, region_data) in evaluated_table.regions {
let metadata: BTreeMap<String, Value> = region_data
.metadata
.into_iter()
.map(|(key, assignment)| (key, assignment.value))
.collect();
let is_anonymous = region_name.starts_with("__anon_");
if is_anonymous && metadata.is_empty() {
continue;
}
let entry = DslEntry {
bounding_boxes: region_data.boxes,
metadata,
};
dsl_map.insert(region_name, entry);
}
dsl_map
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{GeomStmt, MetaStmt};
use crate::parser::geom::GeometryStatement;
use crate::parser::meta::MetadataStatement;
use serde_json::json;
fn make_geom_stmt(tuple_idx: usize, stmt_idx: usize, statement: GeometryStatement) -> GeomStmt {
GeomStmt::new(tuple_idx, stmt_idx, statement)
}
fn make_meta_stmt(tuple_idx: usize, stmt_idx: usize, statement: MetadataStatement) -> MetaStmt {
MetaStmt::new(tuple_idx, stmt_idx, statement)
}
fn make_rc(region: Option<String>, corners: ([i32; 3], [i32; 3])) -> GeometryStatement {
GeometryStatement::RelativeCoordinate { region, corners }
}
#[test]
fn test_find_last_geometry_in_tuple() {
let geom_stmts = vec![
make_geom_stmt(
0,
0,
make_rc(Some("first".to_string()), ([0, 0, 0], [1, 1, 1])),
),
make_geom_stmt(
0,
1,
make_rc(Some("second".to_string()), ([2, 2, 2], [3, 3, 3])),
),
make_geom_stmt(
1,
0,
make_rc(Some("third".to_string()), ([4, 4, 4], [5, 5, 5])),
),
];
assert_eq!(
find_last_geometry_in_tuple(&geom_stmts, 0),
Some("second".to_string())
);
assert_eq!(
find_last_geometry_in_tuple(&geom_stmts, 1),
Some("third".to_string())
);
assert_eq!(find_last_geometry_in_tuple(&geom_stmts, 2), None);
}
#[test]
fn test_find_last_geometry_anonymous() {
let geom_stmts = vec![
make_geom_stmt(0, 0, make_rc(None, ([0, 0, 0], [1, 1, 1]))),
make_geom_stmt(
0,
1,
make_rc(Some("named".to_string()), ([2, 2, 2], [3, 3, 3])),
),
make_geom_stmt(0, 2, make_rc(None, ([4, 4, 4], [5, 5, 5]))),
];
assert_eq!(
find_last_geometry_in_tuple(&geom_stmts, 0),
Some("__anon_0_2".to_string())
);
}
#[test]
fn test_wildcard_matching() {
assert!(matches_wildcard("cpu.core", "cpu.*"));
assert!(matches_wildcard("cpu.cache", "cpu.*"));
assert!(!matches_wildcard("gpu.core", "cpu.*"));
assert!(!matches_wildcard("cpu", "cpu.*"));
assert!(matches_wildcard("core.cpu", "*.cpu"));
assert!(matches_wildcard("cache.cpu", "*.cpu"));
assert!(!matches_wildcard("core.gpu", "*.cpu"));
assert!(matches_wildcard("exact", "exact"));
assert!(!matches_wildcard("exact2", "exact"));
}
#[test]
fn test_current_region_metadata() {
let mut evaluated_table = EvaluatedRegionTable::new();
evaluated_table.set_region_boxes(
"test_region".to_string(),
Some(vec![([0, 0, 0], [1, 1, 1])]),
);
let geom_stmts = vec![make_geom_stmt(
0,
0,
make_rc(Some("test_region".to_string()), ([0, 0, 0], [1, 1, 1])),
)];
let meta_stmts = vec![make_meta_stmt(
0,
1,
MetadataStatement::Current {
key: "label".to_string(),
value: json!("Test Label"),
},
)];
apply_metadata_pass(&mut evaluated_table, &geom_stmts, &meta_stmts).unwrap();
let region_data = evaluated_table.regions.get("test_region").unwrap();
assert_eq!(region_data.metadata.len(), 1);
assert_eq!(region_data.metadata["label"].value, json!("Test Label"));
}
#[test]
fn test_explicit_target_metadata() {
let mut evaluated_table = EvaluatedRegionTable::new();
let geom_stmts = vec![];
let meta_stmts = vec![make_meta_stmt(
0,
0,
MetadataStatement::Targeted {
target: "new_region".to_string(),
key: "type".to_string(),
value: json!("special"),
},
)];
apply_metadata_pass(&mut evaluated_table, &geom_stmts, &meta_stmts).unwrap();
let region_data = evaluated_table.regions.get("new_region").unwrap();
assert_eq!(region_data.boxes, None);
assert_eq!(region_data.metadata.len(), 1);
assert_eq!(region_data.metadata["type"].value, json!("special"));
}
#[test]
fn test_metadata_conflict_detection() {
let mut evaluated_table = EvaluatedRegionTable::new();
let geom_stmts = vec![
make_geom_stmt(
0,
0,
make_rc(Some("test".to_string()), ([0, 0, 0], [1, 1, 1])),
),
make_geom_stmt(
1,
0,
make_rc(Some("test".to_string()), ([2, 2, 2], [3, 3, 3])),
),
];
let meta_stmts = vec![
make_meta_stmt(
0,
1,
MetadataStatement::Current {
key: "label".to_string(),
value: json!("First"),
},
),
make_meta_stmt(
1,
1,
MetadataStatement::Current {
key: "label".to_string(),
value: json!("Second"), },
),
];
let result = apply_metadata_pass(&mut evaluated_table, &geom_stmts, &meta_stmts);
assert!(result.is_err());
match result.unwrap_err() {
ParseError::MetadataConflict(err) => {
assert_eq!(err.region, "test");
assert_eq!(err.key, "label");
assert_eq!(err.first_value, json!("First"));
assert_eq!(err.conflict_value, json!("Second"));
}
_ => panic!("Expected MetadataConflict error"),
}
}
#[test]
fn test_identical_duplicate_allowed() {
let mut evaluated_table = EvaluatedRegionTable::new();
let geom_stmts = vec![
make_geom_stmt(
0,
0,
make_rc(Some("test".to_string()), ([0, 0, 0], [1, 1, 1])),
),
make_geom_stmt(
1,
0,
make_rc(Some("test".to_string()), ([2, 2, 2], [3, 3, 3])),
),
];
let meta_stmts = vec![
make_meta_stmt(
0,
1,
MetadataStatement::Current {
key: "label".to_string(),
value: json!("Same"),
},
),
make_meta_stmt(
1,
1,
MetadataStatement::Current {
key: "label".to_string(),
value: json!("Same"), },
),
];
let result = apply_metadata_pass(&mut evaluated_table, &geom_stmts, &meta_stmts);
assert!(result.is_ok());
let region_data = evaluated_table.regions.get("test").unwrap();
assert_eq!(region_data.metadata["label"].value, json!("Same"));
}
#[test]
fn test_wildcard_metadata() {
let mut evaluated_table = EvaluatedRegionTable::new();
evaluated_table
.set_region_boxes("cpu.core".to_string(), Some(vec![([0, 0, 0], [1, 1, 1])]));
evaluated_table
.set_region_boxes("cpu.cache".to_string(), Some(vec![([2, 2, 2], [3, 3, 3])]));
evaluated_table
.set_region_boxes("gpu.core".to_string(), Some(vec![([4, 4, 4], [5, 5, 5])]));
let geom_stmts = vec![];
let meta_stmts = vec![make_meta_stmt(
0,
0,
MetadataStatement::Targeted {
target: "cpu.*".to_string(),
key: "power".to_string(),
value: json!("low"),
},
)];
apply_metadata_pass(&mut evaluated_table, &geom_stmts, &meta_stmts).unwrap();
assert!(evaluated_table.regions.contains_key("cpu.*"));
assert_eq!(evaluated_table.regions["cpu.*"].boxes, None); assert_eq!(
evaluated_table.regions["cpu.*"].metadata["power"].value,
json!("low")
);
assert_eq!(
evaluated_table.regions["cpu.core"].metadata["power"].value,
json!("low")
);
assert_eq!(
evaluated_table.regions["cpu.cache"].metadata["power"].value,
json!("low")
);
assert!(!evaluated_table.regions["gpu.core"]
.metadata
.contains_key("power"));
}
#[test]
fn test_global_metadata() {
let mut evaluated_table = EvaluatedRegionTable::new();
let geom_stmts = vec![];
let meta_stmts = vec![make_meta_stmt(
0,
0,
MetadataStatement::Targeted {
target: "$global".to_string(),
key: "version".to_string(),
value: json!("1.0"),
},
)];
apply_metadata_pass(&mut evaluated_table, &geom_stmts, &meta_stmts).unwrap();
let global_data = evaluated_table.regions.get("$global").unwrap();
assert_eq!(global_data.boxes, None); assert_eq!(global_data.metadata["version"].value, json!("1.0"));
}
#[test]
fn test_build_dsl_map_excludes_empty_anonymous() {
let mut evaluated_table = EvaluatedRegionTable::new();
evaluated_table
.set_region_boxes("__anon_0_0".to_string(), Some(vec![([0, 0, 0], [1, 1, 1])]));
evaluated_table
.set_region_boxes("__anon_0_1".to_string(), Some(vec![([2, 2, 2], [3, 3, 3])]));
evaluated_table
.add_metadata(
"__anon_0_1".to_string(),
"label".to_string(),
MetadataAssignment {
value: json!("labeled"),
source: SourceLocation::new(0, 2),
},
)
.unwrap();
evaluated_table.set_region_boxes("named".to_string(), Some(vec![([4, 4, 4], [5, 5, 5])]));
let dsl_map = build_dsl_map(evaluated_table);
assert!(!dsl_map.contains_key("__anon_0_0"));
assert!(dsl_map.contains_key("__anon_0_1"));
assert!(dsl_map.contains_key("named"));
}
#[test]
fn test_no_current_region_error() {
let mut evaluated_table = EvaluatedRegionTable::new();
let geom_stmts = vec![];
let meta_stmts = vec![make_meta_stmt(
0,
0,
MetadataStatement::Current {
key: "label".to_string(),
value: json!("orphan"),
},
)];
let result = apply_metadata_pass(&mut evaluated_table, &geom_stmts, &meta_stmts);
assert!(result.is_err());
match result.unwrap_err() {
ParseError::NoCurrentRegion { source } => {
assert_eq!(source.tuple_idx, 0);
assert_eq!(source.stmt_idx, 0);
}
_ => panic!("Expected NoCurrentRegion error"),
}
}
}