use crate::utility::cert_c::ast_utils::get_node_text;
use std::collections::{HashMap, HashSet};
use tree_sitter::Node;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum LValue {
Var(String),
Field(Box<LValue>, String),
}
impl LValue {
pub fn root_var(&self) -> &str {
match self {
LValue::Var(name) => name,
LValue::Field(base, _) => base.root_var(),
}
}
pub fn is_field(&self) -> bool {
matches!(self, LValue::Field(..))
}
}
pub fn lvalue_of(node: &Node, source: &str) -> Option<LValue> {
match node.kind() {
"identifier" => Some(LValue::Var(get_node_text(node, source).to_string())),
"field_expression" => {
let base_node = node.child_by_field_name("argument")?;
let base = lvalue_of(&base_node, source)?;
let field_node = node.child_by_field_name("field")?;
let field_name = get_node_text(&field_node, source).to_string();
Some(LValue::Field(Box::new(base), field_name))
}
"pointer_expression" | "subscript_expression" => {
let base_node = node.child_by_field_name("argument")?;
lvalue_of(&base_node, source)
}
"parenthesized_expression" => {
for i in 0..node.child_count() {
let child = node.child(i)?;
if child.kind() != "(" && child.kind() != ")" {
return lvalue_of(&child, source);
}
}
None
}
"cast_expression" => {
let value = node.child_by_field_name("value")?;
lvalue_of(&value, source)
}
_ => None,
}
}
pub type AliasMap = HashMap<LValue, LValue>;
pub fn resolve_canonical(map: &AliasMap, lv: &LValue) -> LValue {
let mut current = lv.clone();
let mut visited: HashSet<LValue> = HashSet::new();
while let Some(target) = map.get(¤t) {
if visited.contains(target) {
break;
}
visited.insert(current.clone());
current = target.clone();
}
current
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::CParser;
fn parse_expr(src: &str) -> (tree_sitter::Tree, String) {
let source = format!("void f(void) {{ {} }}", src);
let mut parser = CParser::new().expect("parser");
let tree = parser.parse_source(&source).expect("parse");
(tree, source)
}
fn first_expr_node<'a>(tree: &'a tree_sitter::Tree, source: &str, kind: &str) -> Node<'a> {
fn find<'a>(node: Node<'a>, kind: &str, source: &str) -> Option<Node<'a>> {
if node.kind() == kind {
return Some(node);
}
for i in 0..node.child_count() {
if let Some(c) = node.child(i) {
if let Some(found) = find(c, kind, source) {
return Some(found);
}
}
}
None
}
find(tree.root_node(), kind, source).unwrap_or_else(|| panic!("no {} found", kind))
}
#[test]
fn field_and_deref_field_unify() {
let (tree1, src1) = parse_expr("p->buf;");
let n1 = first_expr_node(&tree1, &src1, "field_expression");
let lv1 = lvalue_of(&n1, &src1).expect("lvalue");
let (tree2, src2) = parse_expr("(*p).buf;");
let n2 = first_expr_node(&tree2, &src2, "field_expression");
let lv2 = lvalue_of(&n2, &src2).expect("lvalue");
assert_eq!(lv1, lv2);
assert_eq!(
lv1,
LValue::Field(Box::new(LValue::Var("p".to_string())), "buf".to_string())
);
}
#[test]
fn subscript_is_index_insensitive() {
let (tree1, src1) = parse_expr("arr[i]->x;");
let n1 = first_expr_node(&tree1, &src1, "field_expression");
let lv1 = lvalue_of(&n1, &src1).expect("lvalue");
let (tree2, src2) = parse_expr("arr[j]->x;");
let n2 = first_expr_node(&tree2, &src2, "field_expression");
let lv2 = lvalue_of(&n2, &src2).expect("lvalue");
assert_eq!(lv1, lv2);
}
#[test]
fn root_var_on_nested_field_chain() {
let (tree, src) = parse_expr("a->b->c;");
let n = first_expr_node(&tree, &src, "field_expression");
let lv = lvalue_of(&n, &src).expect("lvalue");
assert_eq!(lv.root_var(), "a");
assert!(lv.is_field());
}
#[test]
fn resolve_canonical_breaks_cycles() {
let mut map: AliasMap = HashMap::new();
let a = LValue::Var("a".to_string());
let b = LValue::Var("b".to_string());
map.insert(a.clone(), b.clone());
map.insert(b.clone(), a.clone());
let _ = resolve_canonical(&map, &a);
}
#[test]
fn non_lvalue_expressions_return_none() {
let (tree, src) = parse_expr("foo();");
let n = first_expr_node(&tree, &src, "call_expression");
assert_eq!(lvalue_of(&n, &src), None);
}
}