use super::expression::collect_base_names_from_expression;
use crate::types::{Property, PropertyType, PropertyValue, Signal, SignalParameter};
pub fn strip_comment(line: &str) -> &str {
let mut in_string = false;
let mut prev = ' ';
for (i, ch) in line.char_indices() {
match ch {
'"' | '\'' if prev != '\\' => in_string = !in_string,
'/' if !in_string && prev == '/' => {
return &line[..i - 1];
}
_ => {}
}
prev = ch;
}
line
}
pub fn strip_block_comments(source: &str) -> String {
if !source.contains("/*") {
return source.to_string();
}
let mut result = String::with_capacity(source.len());
let mut chars = source.chars().peekable();
while let Some(ch) = chars.next() {
if ch == '/' && chars.peek() == Some(&'*') {
chars.next(); loop {
match chars.next() {
Some('*') if chars.peek() == Some(&'/') => {
chars.next(); break;
}
Some('\n') => result.push('\n'), None => break,
_ => {}
}
}
} else {
result.push(ch);
}
}
result
}
pub fn parse_type_open(line: &str) -> Option<String> {
let line = strip_comment(line).trim();
if !line.ends_with('{') {
return None;
}
let without_brace = line[..line.len() - 1].trim();
if without_brace.is_empty() {
return None;
}
if without_brace.contains(':') {
return None;
}
let type_name = if let Some(pos) = without_brace.find(" on ") {
without_brace[..pos].trim()
} else {
without_brace
};
if type_name.chars().next().is_some_and(|c| c.is_uppercase()) {
return Some(type_name.to_string());
}
None
}
pub fn parse_type_open_single_line(line: &str) -> Option<String> {
let line = strip_comment(line).trim();
if !line.ends_with('}') {
return None;
}
let brace_pos = line.find('{')?;
let type_part = line[..brace_pos].trim();
if type_part.is_empty() || type_part.contains(':') {
return None;
}
let type_name = if let Some(pos) = type_part.find(" on ") {
type_part[..pos].trim()
} else {
type_part
};
if type_name.chars().next().is_some_and(|c| c.is_uppercase()) {
return Some(type_name.to_string());
}
None
}
pub fn try_parse_id(line: &str) -> Option<String> {
let line = strip_comment(line).trim();
let rest = line.strip_prefix("id:")?;
let val = rest.trim().trim_end_matches(';').to_string();
if val.is_empty() { None } else { Some(val) }
}
pub fn extract_loader_source_types(line: &str) -> Vec<String> {
let line = strip_comment(line).trim();
let rest = if let Some(after_source) = line.strip_prefix("source") {
let after = after_source.trim_start();
if let Some(after_colon) = after.strip_prefix(':') {
after_colon.trim()
} else {
return vec![];
}
} else {
return vec![];
};
let mut results = Vec::new();
let mut remaining = rest;
while let Some(dot_pos) = remaining.find(".qml") {
let before = &remaining[..dot_pos];
let stem_start = before.rfind(['/', '"', '\'']).map_or(0, |p| p + 1);
let stem = &before[stem_start..];
if !stem.is_empty() && stem.chars().next().is_some_and(|c| c.is_uppercase()) {
results.push(stem.to_string());
}
remaining = &remaining[dot_pos + 4..]; }
results
}
pub fn parse_signal_decl(line: &str) -> Option<Signal> {
let line = strip_comment(line).trim();
let rest = line.strip_prefix("signal ")?.trim();
let (name, params_str) = if let Some(paren_pos) = rest.find('(') {
let n = rest[..paren_pos].trim().to_string();
let close = rest.find(')')?;
let p = &rest[paren_pos + 1..close];
(n, Some(p))
} else {
(rest.trim_end_matches(';').to_string(), None)
};
if name.is_empty() {
return None;
}
let parameters = params_str.map(parse_signal_params).unwrap_or_default();
Some(Signal { name, parameters })
}
fn parse_signal_params(params: &str) -> Vec<SignalParameter> {
if params.trim().is_empty() {
return vec![];
}
params
.split(',')
.filter_map(|p| {
let parts: Vec<&str> = p.trim().splitn(2, char::is_whitespace).collect();
if parts.len() == 2 {
Some(SignalParameter {
param_type: parts[0].to_string(),
param_name: parts[1].to_string(),
})
} else if parts.len() == 1 && !parts[0].is_empty() {
Some(SignalParameter {
param_type: parts[0].to_string(),
param_name: String::new(),
})
} else {
None
}
})
.collect()
}
pub fn parse_property_decl(line: &str) -> Option<Property> {
let line = strip_comment(line).trim();
let rest = line.strip_prefix("property ")?.trim();
let mut parts = rest.splitn(2, char::is_whitespace);
let type_str = parts.next()?.trim();
let after_type = parts.next()?.trim();
let (name, value_str) = if let Some(colon_pos) = after_type.find(':') {
let n = after_type[..colon_pos].trim().to_string();
let v = after_type[colon_pos + 1..].trim().to_string();
(n, Some(v))
} else {
(after_type.trim_end_matches(';').to_string(), None)
};
if name.is_empty() {
return None;
}
let prop_type = PropertyType::from_token(type_str);
let (value, accessed_properties, is_simple_ref) = match value_str.as_deref() {
None | Some("") => (PropertyValue::Unset, vec![], false),
Some(v) => parse_property_value(v),
};
Some(Property {
name,
prop_type,
value,
accessed_properties,
is_simple_ref,
line: 0, })
}
pub fn parse_property_value(expr: &str) -> (PropertyValue, Vec<String>, bool) {
let expr = expr.trim().trim_end_matches(';');
if expr == "true" {
return (PropertyValue::Bool(true), vec![], false);
}
if expr == "false" {
return (PropertyValue::Bool(false), vec![], false);
}
if expr == "null" || expr == "undefined" {
return (PropertyValue::Null, vec![], false);
}
if let Ok(i) = expr.parse::<i64>() {
return (PropertyValue::Int(i), vec![], false);
}
if let Ok(f) = expr.parse::<f64>() {
return (PropertyValue::Double(f), vec![], false);
}
if (expr.starts_with('"') && expr.ends_with('"')) || (expr.starts_with('\'') && expr.ends_with('\'')) {
let inner = expr[1..expr.len() - 1].to_string();
return (PropertyValue::String(inner), vec![], false);
}
let accessed = collect_base_names_from_expression(expr);
let is_simple_ref = accessed.len() == 1 && accessed[0].as_str() == expr;
(PropertyValue::TooComplex, accessed, is_simple_ref)
}
pub fn parse_function_header(line: &str) -> Option<(String, Vec<String>, bool)> {
let line = strip_comment(line).trim();
let rest = line.strip_prefix("function ")?.trim();
let paren_pos = rest.find('(')?;
let name = rest[..paren_pos].trim().to_string();
let close_paren = rest.find(')')?;
let params_str = &rest[paren_pos + 1..close_paren];
let params: Vec<String> = params_str
.split(',')
.map(|p| {
let s = p.trim();
s.find('=').map_or(s, |eq| s[..eq].trim()).to_string()
})
.filter(|p| !p.is_empty())
.collect();
let after_paren = rest[close_paren + 1..].trim();
let has_open_brace = after_paren.contains('{');
Some((name, params, has_open_brace))
}
pub fn try_parse_property_element_open(line: &str) -> Option<String> {
let line = strip_comment(line).trim();
if !line.ends_with('{') {
return None;
}
let colon_pos = line.find(':')?;
let key = line[..colon_pos].trim();
if key.is_empty() || key.contains(' ') || key.contains('.') {
return None;
}
let rhs = line[colon_pos + 1..].trim(); let type_name = rhs.trim_end_matches('{').trim(); if type_name.is_empty() {
return None;
}
if type_name.chars().next().is_some_and(|c| c.is_uppercase()) {
Some(type_name.to_string())
} else {
None
}
}
pub fn is_signal_handler_block(line: &str) -> bool {
let line = strip_comment(line).trim();
if let Some(colon_pos) = line.find(':') {
let key = line[..colon_pos].trim();
if key.contains(' ') {
return false; }
if key.starts_with("on") && key.len() > 2 && key.chars().nth(2).is_some_and(|c| c.is_uppercase()) {
return true;
}
if let Some(dot_pos) = key.rfind('.') {
let after_dot = &key[dot_pos + 1..];
if after_dot.starts_with("on")
&& after_dot.len() > 2
&& after_dot.chars().nth(2).is_some_and(|c| c.is_uppercase())
{
return true;
}
}
}
false
}