use crate::graph::resolver::{
BooleanGuardExpression, PreprocessorContextCache, PreprocessorGuard,
callable_preprocessor_guard_requirements_with_cache, direct_unmatched_closing_brace,
flattened_macro_namespace_components, is_recovered_declaration_scope_container,
preprocessor_conditional_family_for_declaration_with_cache,
preprocessor_guard_environment_with_cache,
};
use brokk_bifrost_core::analyzer::CodeUnit;
use brokk_bifrost_core::analyzer::cpp_facts::{CppGuardNode, CppGuardSet};
use brokk_bifrost_core::hash::HashSet;
use tree_sitter::Node;
pub(crate) struct CppSourceContext<'tree> {
pub guard_requirements: Option<CppGuardSet>,
pub callable_guards: Option<CppGuardSet>,
pub callable_activation: Option<usize>,
pub callable_guard_completion_byte: Option<usize>,
pub exhaustive_conditional_family: Option<Node<'tree>>,
pub flattened_macro_namespace: Option<Vec<String>>,
pub displaced_namespace_closing_brace: Option<Node<'tree>>,
}
pub(crate) fn capture_declaration_context<'tree>(
node: Node<'tree>,
unit: &CodeUnit,
source: &str,
cache: &mut PreprocessorContextCache,
) -> CppSourceContext<'tree> {
let declaration = callable_declaration_node(node);
let callable = (unit.is_function() || unit.is_field()) && declaration.is_some();
let callable_nameable =
callable && declaration.is_some_and(|node| callable_declaration_is_nameable(node, source));
let callable_declarator = declaration.and_then(function_declarator);
let callable_guards = callable_nameable
.then(|| declaration.expect("callable declaration"))
.and_then(|declaration| {
callable_preprocessor_guard_requirements_with_cache(declaration, source, cache)
})
.map(|guards| cpp_guard_set_from_runtime(&guards));
let callable_activation = callable_nameable.then(|| {
let declaration = declaration.expect("callable declaration");
if declaration.kind() == "function_definition" {
callable_declarator.map_or(declaration.end_byte(), |node| node.end_byte())
} else {
declaration.end_byte()
}
});
let flattened_macro_namespace = flattened_macro_namespace_components(node, source);
let displaced_namespace_closing_brace = declaration
.filter(|declaration| {
declaration
.parent()
.is_some_and(|parent| parent.kind() == "translation_unit")
&& macro_displaced_return_type(*declaration, source)
})
.and_then(namespace_closing_brace);
CppSourceContext {
guard_requirements: preprocessor_guard_environment_with_cache(node, source, cache)
.as_ref()
.map(cpp_guard_set_from_runtime),
callable_guards,
callable_activation,
callable_guard_completion_byte:
crate::graph::resolver::callable_guard_completion_byte_with_cache(node, source, cache),
exhaustive_conditional_family: preprocessor_conditional_family_for_declaration_with_cache(
node, cache,
),
flattened_macro_namespace,
displaced_namespace_closing_brace,
}
}
fn callable_declaration_node<'tree>(node: Node<'tree>) -> Option<Node<'tree>> {
let mut current = node;
loop {
if matches!(
current.kind(),
"declaration" | "field_declaration" | "function_definition"
) {
return Some(current);
}
current = current.parent()?;
}
}
fn function_declarator(declaration: Node<'_>) -> Option<Node<'_>> {
if declaration.kind() == "function_declarator" {
return Some(declaration);
}
let mut current = declaration.child_by_field_name("declarator")?;
loop {
if current.kind() == "function_declarator" {
return Some(current);
}
current = current.child_by_field_name("declarator")?;
}
}
fn callable_declaration_is_nameable(declaration: Node<'_>, source: &str) -> bool {
let mut ancestor = declaration.parent();
while let Some(node) = ancestor {
if node.kind() == "function_definition"
&& is_recovered_declaration_scope_container(node, source)
{
ancestor = node.parent();
continue;
}
if node.kind() == "compound_statement"
&& node
.parent()
.is_some_and(|parent| is_recovered_declaration_scope_container(parent, source))
{
ancestor = node.parent().and_then(|parent| parent.parent());
continue;
}
if node.kind() == "field_declaration_list" {
return true;
}
if matches!(
node.kind(),
"compound_statement" | "function_definition" | "lambda_expression"
) {
return false;
}
ancestor = node.parent();
}
true
}
fn macro_displaced_return_type(declaration: Node<'_>, source: &str) -> bool {
let Some(ty) = declaration.child_by_field_name("type") else {
return false;
};
let name = crate::declarations::node_text(ty, source).trim();
if !name.is_empty()
&& name
.chars()
.all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
&& (0..declaration.named_child_count()).any(|index| {
declaration
.named_child(index)
.is_some_and(|child| child.kind() == "ERROR")
})
{
return true;
}
let mut cursor = declaration.walk();
declaration.named_children(&mut cursor).any(|child| {
crate::declarations::macro_modifier_identifier(child).is_some_and(|identifier| {
let name = crate::declarations::node_text(identifier, source).trim();
!name.is_empty()
&& name
.chars()
.all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
})
})
}
fn namespace_closing_brace(node: Node<'_>) -> Option<Node<'_>> {
let mut root = node;
while let Some(parent) = root.parent() {
root = parent;
}
let mut cursor = root.walk();
root.named_children(&mut cursor).find(|sibling| {
sibling.start_byte() >= node.end_byte() && direct_unmatched_closing_brace(*sibling)
})
}
pub(crate) fn cpp_guard_set_from_runtime(guards: &HashSet<PreprocessorGuard>) -> CppGuardSet {
let mut encoded = guards
.iter()
.map(|guard| {
let mut nodes = Vec::new();
let root = append_guard(guard, &mut nodes);
(nodes, root)
})
.collect::<Vec<_>>();
encoded.sort_unstable_by(|left, right| left.0.cmp(&right.0).then_with(|| left.1.cmp(&right.1)));
let mut nodes = Vec::new();
let mut roots = Vec::with_capacity(encoded.len());
for (mut guard_nodes, root) in encoded {
let offset = nodes.len();
for node in &mut guard_nodes {
remap_guard_node(node, offset);
}
nodes.extend(guard_nodes);
roots.push(root + offset);
}
CppGuardSet::new(nodes, roots)
}
fn remap_guard_node(node: &mut CppGuardNode, offset: usize) {
match node {
CppGuardNode::Boolean(child) => *child += offset,
CppGuardNode::All(children) | CppGuardNode::Any(children) => {
for child in children {
*child += offset;
}
}
CppGuardNode::Defined(_)
| CppGuardNode::Undefined(_)
| CppGuardNode::Expression(_)
| CppGuardNode::NegatedExpression(_)
| CppGuardNode::Constant(_)
| CppGuardNode::Truthy(_)
| CppGuardNode::Falsy(_)
| CppGuardNode::Opaque(_)
| CppGuardNode::NegatedOpaque(_) => {}
}
}
enum GuardWork<'a> {
Guard(&'a PreprocessorGuard),
Boolean(&'a BooleanGuardExpression),
EmitBoolean,
EmitChildren { all: bool, count: usize },
}
fn append_guard(guard: &PreprocessorGuard, nodes: &mut Vec<CppGuardNode>) -> usize {
let mut work = vec![GuardWork::Guard(guard)];
let mut values = Vec::new();
while let Some(work_item) = work.pop() {
match work_item {
GuardWork::Guard(guard) => match guard {
PreprocessorGuard::Defined(value) => {
values.push(push_guard_node(nodes, CppGuardNode::Defined(value.clone())))
}
PreprocessorGuard::Undefined(value) => values.push(push_guard_node(
nodes,
CppGuardNode::Undefined(value.clone()),
)),
PreprocessorGuard::Boolean(expression) => {
work.push(GuardWork::EmitBoolean);
work.push(GuardWork::Boolean(expression));
}
PreprocessorGuard::Expression(value) => values.push(push_guard_node(
nodes,
CppGuardNode::Expression(value.clone()),
)),
PreprocessorGuard::NegatedExpression(value) => values.push(push_guard_node(
nodes,
CppGuardNode::NegatedExpression(value.clone()),
)),
PreprocessorGuard::Constant(value) => {
values.push(push_guard_node(nodes, CppGuardNode::Constant(*value)))
}
},
GuardWork::Boolean(expression) => match expression {
BooleanGuardExpression::Defined(value) => {
values.push(push_guard_node(nodes, CppGuardNode::Defined(value.clone())))
}
BooleanGuardExpression::Undefined(value) => values.push(push_guard_node(
nodes,
CppGuardNode::Undefined(value.clone()),
)),
BooleanGuardExpression::Truthy(value) => {
values.push(push_guard_node(nodes, CppGuardNode::Truthy(value.clone())))
}
BooleanGuardExpression::Falsy(value) => {
values.push(push_guard_node(nodes, CppGuardNode::Falsy(value.clone())))
}
BooleanGuardExpression::Opaque(value) => {
values.push(push_guard_node(nodes, CppGuardNode::Opaque(value.clone())))
}
BooleanGuardExpression::NegatedOpaque(value) => values.push(push_guard_node(
nodes,
CppGuardNode::NegatedOpaque(value.clone()),
)),
BooleanGuardExpression::All(expressions) => {
work.push(GuardWork::EmitChildren {
all: true,
count: expressions.len(),
});
for expression in expressions.iter().rev() {
work.push(GuardWork::Boolean(expression));
}
}
BooleanGuardExpression::Any(expressions) => {
work.push(GuardWork::EmitChildren {
all: false,
count: expressions.len(),
});
for expression in expressions.iter().rev() {
work.push(GuardWork::Boolean(expression));
}
}
BooleanGuardExpression::Constant(value) => {
values.push(push_guard_node(nodes, CppGuardNode::Constant(*value)))
}
},
GuardWork::EmitBoolean => {
let root = values.pop().expect("boolean guard root");
values.push(push_guard_node(nodes, CppGuardNode::Boolean(root)));
}
GuardWork::EmitChildren { all, count } => {
let start = values.len().checked_sub(count).expect("guard children");
let children = values.split_off(start);
values.push(push_guard_node(
nodes,
if all {
CppGuardNode::All(children)
} else {
CppGuardNode::Any(children)
},
));
}
}
}
values.pop().expect("guard root")
}
fn push_guard_node(nodes: &mut Vec<CppGuardNode>, node: CppGuardNode) -> usize {
let index = nodes.len();
nodes.push(node);
index
}
pub(crate) fn cpp_guard_set_to_runtime(guards: &CppGuardSet) -> Option<HashSet<PreprocessorGuard>> {
if !guards.valid() {
return None;
}
let mut boolean_nodes = vec![None; guards.nodes.len()];
for (index, node) in guards.nodes.iter().enumerate() {
boolean_nodes[index] = match node {
CppGuardNode::Defined(value) => Some(BooleanGuardExpression::Defined(value.clone())),
CppGuardNode::Undefined(value) => {
Some(BooleanGuardExpression::Undefined(value.clone()))
}
CppGuardNode::Truthy(value) => Some(BooleanGuardExpression::Truthy(value.clone())),
CppGuardNode::Falsy(value) => Some(BooleanGuardExpression::Falsy(value.clone())),
CppGuardNode::Opaque(value) => Some(BooleanGuardExpression::Opaque(value.clone())),
CppGuardNode::NegatedOpaque(value) => {
Some(BooleanGuardExpression::NegatedOpaque(value.clone()))
}
CppGuardNode::All(children) => Some(BooleanGuardExpression::All(
children
.iter()
.map(|child| boolean_nodes[*child].take())
.collect::<Option<Vec<_>>>()?,
)),
CppGuardNode::Any(children) => Some(BooleanGuardExpression::Any(
children
.iter()
.map(|child| boolean_nodes[*child].take())
.collect::<Option<Vec<_>>>()?,
)),
CppGuardNode::Constant(value) => Some(BooleanGuardExpression::Constant(*value)),
CppGuardNode::Boolean(child) => boolean_nodes[*child].take(),
CppGuardNode::Expression(_) | CppGuardNode::NegatedExpression(_) => None,
};
}
let mut result = HashSet::default();
for root in &guards.roots {
let guard = match &guards.nodes[*root] {
CppGuardNode::Defined(value) => PreprocessorGuard::Defined(value.clone()),
CppGuardNode::Undefined(value) => PreprocessorGuard::Undefined(value.clone()),
CppGuardNode::Boolean(_) => PreprocessorGuard::Boolean(boolean_nodes[*root].take()?),
CppGuardNode::Expression(value) => PreprocessorGuard::Expression(value.clone()),
CppGuardNode::NegatedExpression(value) => {
PreprocessorGuard::NegatedExpression(value.clone())
}
CppGuardNode::Constant(value) => PreprocessorGuard::Constant(*value),
CppGuardNode::Truthy(_)
| CppGuardNode::Falsy(_)
| CppGuardNode::Opaque(_)
| CppGuardNode::NegatedOpaque(_)
| CppGuardNode::All(_)
| CppGuardNode::Any(_) => return None,
};
result.insert(guard);
}
Some(result)
}
#[cfg(test)]
mod grammar_boundary_tests {
#[test]
fn displaced_namespace_brace_adjacency() {
for gap in ["", "\n"] {
let source = format!(
"#define FMT_API\nFMT_API void vformat_to(int& out);{gap}}}\nvoid run(){{}}"
);
let tree = crate::diagnostics::parse_cpp_tree(&source);
let mut cursor = tree.root_node().walk();
let declaration = tree
.root_node()
.named_children(&mut cursor)
.find(|node| node.kind() == "declaration")
.unwrap();
let brace_start = source.find('}').unwrap();
assert_eq!(declaration.end_byte() + gap.len(), brace_start);
let brace = super::namespace_closing_brace(declaration).unwrap();
assert_eq!(brace.byte_range(), brace_start..brace_start + 1);
assert!(brace.is_error());
let clean = tree
.root_node()
.named_children(&mut tree.root_node().walk())
.find(|node| node.kind() == "function_definition")
.unwrap();
assert!(super::namespace_closing_brace(clean).is_none());
}
}
#[test]
fn boolean_constant_row_round_trip() {
use super::{BooleanGuardExpression as Bool, PreprocessorGuard as Guard};
for value in [false, true] {
for expression in [
Bool::All(vec![Bool::Defined("ON".into()), Bool::Constant(value)]),
Bool::Any(vec![Bool::Defined("ON".into()), Bool::Constant(value)]),
] {
let runtime = [Guard::Boolean(expression), Guard::Constant(value)]
.into_iter()
.collect();
let rows = super::cpp_guard_set_from_runtime(&runtime);
assert_eq!(super::cpp_guard_set_to_runtime(&rows), Some(runtime));
}
}
}
}