use std::collections::HashMap;
use vb6core::types::VBType;
use vb6parse::parsers::cst::CstNode;
use vb6parse::parsers::SyntaxKind;
pub(crate) fn type_from_keyword(node: &CstNode) -> Option<VBType> {
match node.kind() {
SyntaxKind::ByteKeyword => Some(VBType::Byte),
SyntaxKind::IntegerKeyword => Some(VBType::Integer),
SyntaxKind::LongKeyword => Some(VBType::Long),
SyntaxKind::SingleKeyword => Some(VBType::Single),
SyntaxKind::DoubleKeyword => Some(VBType::Double),
SyntaxKind::CurrencyKeyword => Some(VBType::Currency),
SyntaxKind::StringKeyword => Some(VBType::String),
SyntaxKind::BooleanKeyword => Some(VBType::Boolean),
SyntaxKind::DateKeyword => Some(VBType::Date),
SyntaxKind::VariantKeyword => Some(VBType::Variant),
SyntaxKind::ObjectKeyword => Some(VBType::Object),
SyntaxKind::DecimalKeyword => Some(VBType::Double),
_ => None,
}
}
pub(crate) fn is_identifier_like(node: &CstNode) -> bool {
let text = node.text().trim();
if text.is_empty() {
return false;
}
let mut chars = text.chars();
let first_ok = matches!(chars.next(), Some(c) if c.is_ascii_alphabetic() || c == '_');
first_ok
&& chars.all(|c| {
c.is_ascii_alphanumeric() || c == '_' || matches!(c, '$' | '%' | '&' | '!' | '#' | '@')
})
}
pub(crate) fn identifier_name(node: &CstNode) -> String {
node.significant_children()
.find(|c| is_identifier_like(c))
.map(|c| c.text().trim().to_string())
.unwrap_or_default()
}
pub(crate) fn is_statement_kind(kind: SyntaxKind) -> bool {
matches!(
kind,
SyntaxKind::AssignmentStatement
| SyntaxKind::DimStatement
| SyntaxKind::ConstStatement
| SyntaxKind::ReDimStatement
| SyntaxKind::IfStatement
| SyntaxKind::ForStatement
| SyntaxKind::ForEachStatement
| SyntaxKind::DoStatement
| SyntaxKind::WhileStatement
| SyntaxKind::SelectCaseStatement
| SyntaxKind::CallStatement
| SyntaxKind::SetStatement
| SyntaxKind::LetStatement
| SyntaxKind::ExitStatement
| SyntaxKind::EndStatement
| SyntaxKind::StopStatement
| SyntaxKind::BeepStatement
| SyntaxKind::PrintStatement
| SyntaxKind::OpenStatement
| SyntaxKind::CloseStatement
| SyntaxKind::GoSubStatement
| SyntaxKind::GotoStatement
| SyntaxKind::OnErrorStatement
| SyntaxKind::ReturnStatement
| SyntaxKind::ResumeStatement
| SyntaxKind::EraseStatement
| SyntaxKind::TypeStatement
| SyntaxKind::EnumStatement
| SyntaxKind::MidStatement
| SyntaxKind::MidBStatement
| SyntaxKind::LSetStatement
| SyntaxKind::RSetStatement
| SyntaxKind::DateStatement
| SyntaxKind::TimeStatement
| SyntaxKind::ErrorStatement
| SyntaxKind::RandomizeStatement
| SyntaxKind::AppActivateStatement
| SyntaxKind::SendKeysStatement
| SyntaxKind::SavePictureStatement
| SyntaxKind::OptionStatement
| SyntaxKind::DeclareStatement
| SyntaxKind::AttributeStatement
)
}
#[derive(Debug, Clone)]
pub struct Param {
pub name: String,
pub by_ref: bool,
pub ty: VBType,
pub optional: bool,
}
#[derive(Debug, Clone)]
pub struct Procedure {
pub name: String,
pub is_function: bool,
pub params: Vec<Param>,
pub return_type: VBType,
pub body: Option<CstNode>,
pub line: usize,
pub end_line: usize,
}
impl Procedure {
pub fn key(&self) -> String {
self.name.to_lowercase()
}
}
#[derive(Debug, Clone)]
pub struct Program {
pub root: CstNode,
pub procedures: HashMap<String, Procedure>,
pub entry: String,
}
fn procedure_return_type(node: &CstNode) -> VBType {
let mut significant = node.significant_children().peekable();
while let Some(child) = significant.next() {
if child.kind() == SyntaxKind::AsKeyword {
if let Some(next) = significant.next() {
if let Some(ty) = type_from_keyword(next) {
return ty;
}
}
}
}
VBType::Variant
}
fn parameter_list(node: &CstNode) -> Option<&CstNode> {
node.first_child_by_kind(SyntaxKind::ParameterList)
}
fn parse_params(parameter_list: &CstNode) -> Vec<Param> {
let mut params = Vec::new();
let mut current: Option<Param> = None;
for child in parameter_list.significant_children() {
match child.kind() {
SyntaxKind::Identifier => {
if let Some(prev) = current.take() {
params.push(prev);
}
current = Some(Param {
name: child.text().trim().to_string(),
by_ref: true,
ty: VBType::Variant,
optional: false,
});
}
SyntaxKind::ByValKeyword | SyntaxKind::ByRefKeyword => {
if let Some(param) = current.as_mut() {
param.by_ref = child.kind() == SyntaxKind::ByRefKeyword;
}
}
SyntaxKind::OptionalKeyword => {
if let Some(param) = current.as_mut() {
param.optional = true;
}
}
_ => {
if let Some(ty) = type_from_keyword(child) {
if let Some(param) = current.as_mut() {
param.ty = ty;
}
}
}
}
}
if let Some(last) = current {
params.push(last);
}
params
}
pub(crate) fn build_program(root: &CstNode, module_name: &str) -> Program {
let mut procedures: HashMap<String, Procedure> = HashMap::new();
let mut entry: Option<String> = None;
let mut line = 1;
for child in root.children() {
match child.kind() {
SyntaxKind::Newline => line += 1,
SyntaxKind::SubStatement | SyntaxKind::FunctionStatement => {
let name = procedure_name(child);
let key = name.to_lowercase();
let is_function = child.kind() == SyntaxKind::FunctionStatement;
let params = parameter_list(child).map(parse_params).unwrap_or_default();
let return_type = if is_function {
procedure_return_type(child)
} else {
VBType::Variant
};
let body = child
.first_child_by_kind(SyntaxKind::StatementList)
.cloned();
let end_line = line
+ child
.children()
.iter()
.take_while(|c| c.kind() != SyntaxKind::EndKeyword)
.map(crate::exec::count_newlines)
.sum::<usize>();
if entry.is_none() {
entry = Some(key.clone());
}
procedures.insert(
key,
Procedure {
name,
is_function,
params,
return_type,
body,
line,
end_line,
},
);
line += crate::exec::count_newlines(child);
}
_ => line += crate::exec::count_newlines(child),
}
}
let entry = if procedures.contains_key("main") {
"main".to_string()
} else {
entry.unwrap_or_else(|| "main".to_string())
};
let _ = module_name;
Program {
root: root.clone(),
procedures,
entry,
}
}
fn procedure_name(node: &CstNode) -> String {
node.significant_children()
.find(|c| c.kind() == SyntaxKind::Identifier)
.map(|c| c.text().trim().to_string())
.unwrap_or_default()
}