use super::super::configs::LanguageValueConfig;
use super::super::types::{LiteralValue, SymbolicValue};
use super::helpers::{
extract_binary_op, node_text, parse_float, parse_integer, strip_numeric_suffix, unquote,
};
pub(super) fn extract_callee_name(node: tree_sitter::Node, source: &[u8]) -> String {
if let Some(func_node) = node.child_by_field_name("function") {
return node_text(func_node, source).to_string();
}
if let Some(name_node) = node.child_by_field_name("name") {
let name = node_text(name_node, source);
if let Some(obj_node) = node.child_by_field_name("object") {
let obj = node_text(obj_node, source);
return format!("{obj}.{name}");
}
return name.to_string();
}
if let Some(first) = node.named_child(0) {
return node_text(first, source).to_string();
}
"<unknown>".to_string()
}
pub fn node_to_symbolic(
node: tree_sitter::Node,
source: &[u8],
config: &LanguageValueConfig,
_func_qn: &str,
) -> SymbolicValue {
let kind = node.kind();
if LanguageValueConfig::matches(config.identifier_kinds, kind) {
return SymbolicValue::Variable(node_text(node, source).to_string());
}
let matches_true = LanguageValueConfig::matches(config.bool_true_kinds, kind);
let matches_false = LanguageValueConfig::matches(config.bool_false_kinds, kind);
if matches_true || matches_false {
if matches_true && matches_false {
let text = node_text(node, source);
return SymbolicValue::Literal(LiteralValue::Boolean(text == "true"));
}
if matches_true {
return SymbolicValue::Literal(LiteralValue::Boolean(true));
}
return SymbolicValue::Literal(LiteralValue::Boolean(false));
}
if LanguageValueConfig::matches(config.null_kinds, kind) {
return SymbolicValue::Literal(LiteralValue::Null);
}
let matches_int = LanguageValueConfig::matches(config.integer_literal_kinds, kind);
let matches_float = LanguageValueConfig::matches(config.float_literal_kinds, kind);
if matches_int || matches_float {
let text = node_text(node, source);
let cleaned = strip_numeric_suffix(text);
if matches_int {
if let Some(n) = parse_integer(cleaned) {
return SymbolicValue::Literal(LiteralValue::Integer(n));
}
}
if matches_float {
if let Some(f) = parse_float(cleaned) {
return SymbolicValue::Literal(LiteralValue::Float(f));
}
}
return SymbolicValue::Unknown;
}
if LanguageValueConfig::matches(config.string_literal_kinds, kind) {
return convert_string_literal(node, source, config, _func_qn);
}
if LanguageValueConfig::matches(config.binary_op_kinds, kind) {
let op = extract_binary_op(node, source);
let lhs = node
.child_by_field_name("left")
.or_else(|| node.named_child(0))
.map(|n| node_to_symbolic(n, source, config, _func_qn))
.unwrap_or(SymbolicValue::Unknown);
let rhs = node
.child_by_field_name("right")
.or_else(|| {
let count = node.named_child_count();
if count >= 2 {
node.named_child(count - 1)
} else {
None
}
})
.map(|n| node_to_symbolic(n, source, config, _func_qn))
.unwrap_or(SymbolicValue::Unknown);
return SymbolicValue::BinaryOp(op, Box::new(lhs), Box::new(rhs));
}
if LanguageValueConfig::matches(config.call_kinds, kind) {
return convert_call_expr(node, source, config, _func_qn);
}
if LanguageValueConfig::matches(config.field_access_kinds, kind) {
return convert_field_access(node, source, config, _func_qn);
}
if LanguageValueConfig::matches(config.subscript_kinds, kind) {
return convert_subscript(node, source, config, _func_qn);
}
if LanguageValueConfig::matches(config.list_kinds, kind) {
let mut items = Vec::new();
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
items.push(node_to_symbolic(child, source, config, _func_qn));
}
return SymbolicValue::Literal(LiteralValue::list(items));
}
if LanguageValueConfig::matches(config.dict_kinds, kind) {
let mut entries = Vec::new();
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
if child.kind() == "pair" {
let key = child
.child_by_field_name("key")
.map(|n| node_to_symbolic(n, source, config, _func_qn))
.unwrap_or(SymbolicValue::Unknown);
let value = child
.child_by_field_name("value")
.map(|n| node_to_symbolic(n, source, config, _func_qn))
.unwrap_or(SymbolicValue::Unknown);
entries.push((key, value));
}
}
return SymbolicValue::Literal(LiteralValue::dict(entries));
}
if LanguageValueConfig::matches(config.conditional_kinds, kind) {
let mut arms = Vec::new();
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
arms.push(node_to_symbolic(child, source, config, _func_qn));
}
return SymbolicValue::phi(arms);
}
if kind == "parenthesized_expression" {
if let Some(inner) = node.named_child(0) {
return node_to_symbolic(inner, source, config, _func_qn);
}
}
if kind == "unary_operator"
|| kind == "not_operator"
|| kind == "unary_expression"
|| kind == "prefix_unary_expression"
{
if let Some(operand) = node
.child_by_field_name("argument")
.or_else(|| node.child_by_field_name("operand"))
.or(node.named_child(0))
{
let op_text = node.child(0).map(|c| node_text(c, source)).unwrap_or("");
let inner = node_to_symbolic(operand, source, config, _func_qn);
if op_text == "-" {
if let SymbolicValue::Literal(LiteralValue::Integer(n)) = &inner {
return SymbolicValue::Literal(LiteralValue::Integer(-n));
}
if let SymbolicValue::Literal(LiteralValue::Float(f)) = &inner {
return SymbolicValue::Literal(LiteralValue::Float(-f));
}
}
return inner;
}
}
if kind == "expression_statement" {
if let Some(inner) = node.named_child(0) {
return node_to_symbolic(inner, source, config, _func_qn);
}
}
SymbolicValue::Unknown
}
fn convert_string_literal(
node: tree_sitter::Node,
source: &[u8],
config: &LanguageValueConfig,
func_qn: &str,
) -> SymbolicValue {
let kind = node.kind();
if kind == "concatenated_string" {
let mut parts = Vec::new();
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
parts.push(node_to_symbolic(child, source, config, func_qn));
}
return if parts.len() == 1 {
parts.into_iter().next().expect("checked len == 1")
} else {
SymbolicValue::Concat(parts)
};
}
if has_interpolation_children(node) {
let mut parts = Vec::new();
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
let ck = child.kind();
if ck == "interpolation" {
if let Some(expr) = child.named_child(0) {
parts.push(node_to_symbolic(expr, source, config, func_qn));
}
} else if ck == "string_content" {
parts.push(SymbolicValue::Literal(LiteralValue::String(
node_text(child, source).to_string(),
)));
}
}
return if parts.is_empty() {
SymbolicValue::Literal(LiteralValue::String(unquote(node_text(node, source))))
} else {
SymbolicValue::Concat(parts)
};
}
let text = node_text(node, source);
SymbolicValue::Literal(LiteralValue::String(unquote(text)))
}
fn convert_call_expr(
node: tree_sitter::Node,
source: &[u8],
config: &LanguageValueConfig,
func_qn: &str,
) -> SymbolicValue {
let callee = extract_callee_name(node, source);
let mut args = Vec::new();
if let Some(args_node) = node.child_by_field_name("arguments") {
let mut cursor = args_node.walk();
for child in args_node.named_children(&mut cursor) {
let ck = child.kind();
if ck == "keyword_argument" || ck == "named_argument" {
if let Some(val) = child.child_by_field_name("value") {
args.push(node_to_symbolic(val, source, config, func_qn));
}
} else if ck == "spread_element" || ck == "rest_pattern" {
args.push(SymbolicValue::Unknown);
} else {
args.push(node_to_symbolic(child, source, config, func_qn));
}
}
}
SymbolicValue::Call(callee, args)
}
fn convert_field_access(
node: tree_sitter::Node,
source: &[u8],
config: &LanguageValueConfig,
func_qn: &str,
) -> SymbolicValue {
let obj = node
.child_by_field_name("object")
.or_else(|| node.child_by_field_name("value"))
.or_else(|| node.child_by_field_name("operand"))
.or_else(|| node.child_by_field_name("argument"))
.or_else(|| node.child_by_field_name("expression"))
.or_else(|| node.named_child(0))
.map(|n| node_to_symbolic(n, source, config, func_qn))
.unwrap_or(SymbolicValue::Unknown);
let attr_name = node
.child_by_field_name("attribute")
.or_else(|| node.child_by_field_name("property"))
.or_else(|| node.child_by_field_name("field"))
.or_else(|| node.child_by_field_name("name"))
.or_else(|| {
let count = node.named_child_count();
if count >= 2 {
node.named_child(count - 1)
} else {
None
}
})
.map(|n| node_text(n, source).to_string())
.unwrap_or_default();
SymbolicValue::FieldAccess(Box::new(obj), attr_name)
}
fn convert_subscript(
node: tree_sitter::Node,
source: &[u8],
config: &LanguageValueConfig,
func_qn: &str,
) -> SymbolicValue {
let obj = node
.child_by_field_name("value")
.or_else(|| node.child_by_field_name("object"))
.or_else(|| node.child_by_field_name("operand"))
.or_else(|| node.child_by_field_name("array"))
.or_else(|| node.child_by_field_name("argument"))
.or_else(|| node.child_by_field_name("expression"))
.or_else(|| node.named_child(0))
.map(|n| node_to_symbolic(n, source, config, func_qn))
.unwrap_or(SymbolicValue::Unknown);
let key = node
.child_by_field_name("subscript")
.or_else(|| node.child_by_field_name("index"))
.or_else(|| {
let count = node.named_child_count();
if count >= 2 {
node.named_child(count - 1)
} else {
None
}
})
.map(|n| node_to_symbolic(n, source, config, func_qn))
.unwrap_or(SymbolicValue::Unknown);
SymbolicValue::Index(Box::new(obj), Box::new(key))
}
fn has_interpolation_children(node: tree_sitter::Node) -> bool {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "interpolation" {
return true;
}
}
false
}