use seqc::{CodeGen, CompilerConfig, Parser, TypeChecker};
#[derive(Debug, Clone)]
pub(crate) struct AnalysisResult {
pub(crate) errors: Vec<String>,
pub(crate) llvm_ir: Option<String>,
}
pub(crate) fn analyze(source: &str) -> AnalysisResult {
analyze_with_config(source, &CompilerConfig::default())
}
pub(crate) fn analyze_expression(expr: &str) -> Option<Vec<String>> {
let expr = expr.trim();
if expr.is_empty() {
return None;
}
let source = format!(
": __expr__ ( ..a -- ..b )\n {}\n;\n: main ( -- ) ;\n",
expr
);
let result = analyze(&source);
if let Some(ir) = result.llvm_ir {
let lines = extract_function_ir(&ir, "@seq___expr__");
if !lines.is_empty() {
return Some(lines);
}
}
None
}
fn extract_function_ir(ir: &str, func_name: &str) -> Vec<String> {
let mut lines = Vec::new();
let mut in_function = false;
for line in ir.lines() {
if line.contains("define") && line.contains(func_name) {
in_function = true;
}
if in_function {
lines.push(line.to_string());
if line.trim() == "}" {
break;
}
}
}
lines
}
fn analyze_with_config(source: &str, config: &CompilerConfig) -> AnalysisResult {
let mut errors = Vec::new();
let mut llvm_ir = None;
let mut parser = Parser::new(source);
let mut program = match parser.parse() {
Ok(prog) => prog,
Err(e) => {
errors.push(format!("Parse error: {}", e));
return AnalysisResult { errors, llvm_ir };
}
};
if !program.unions.is_empty()
&& let Err(e) = program.generate_constructors()
{
errors.push(format!("Constructor generation error: {}", e));
}
let mut typechecker = TypeChecker::new();
if !config.external_builtins.is_empty() {
for builtin in &config.external_builtins {
if builtin.effect.is_none() {
errors.push(format!(
"External builtin '{}' is missing a stack effect declaration.",
builtin.seq_name
));
}
}
let external_effects: Vec<_> = config
.external_builtins
.iter()
.filter_map(|b| b.effect.as_ref().map(|e| (b.seq_name.as_str(), e)))
.collect();
typechecker.register_external_words(&external_effects);
}
if let Err(e) = typechecker.check_program(&program) {
errors.push(format!("Type error: {}", e));
}
if errors.is_empty() {
let quotation_types = typechecker.take_quotation_types();
let statement_types = typechecker.take_statement_top_types();
let aux_max_depths = typechecker.take_aux_max_depths();
let resolved_sugar = typechecker.take_resolved_sugar();
let mut codegen = CodeGen::new();
codegen.set_aux_slot_counts(aux_max_depths);
codegen.set_resolved_sugar(resolved_sugar);
match codegen.codegen_program_with_config(
&program,
quotation_types,
statement_types,
config,
) {
Ok(ir) => llvm_ir = Some(ir),
Err(e) => errors.push(format!("Codegen error: {}", e)),
}
}
AnalysisResult { errors, llvm_ir }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_analyze_simple_program() {
let source = r#"
: main ( -- )
42 drop
;
"#;
let result = analyze(source);
assert!(result.errors.is_empty(), "Errors: {:?}", result.errors);
assert!(result.llvm_ir.is_some());
}
#[test]
fn test_analyze_expression_simple() {
let result = analyze_expression("5");
assert!(result.is_some(), "Should produce IR for '5'");
let ir = result.unwrap();
assert!(ir.iter().any(|l| l.contains("seq___expr__")));
let result = analyze_expression("5 10 i.add");
assert!(result.is_some(), "Should produce IR for '5 10 i.add'");
}
#[test]
fn test_analyze_type_error() {
let source = r#"
: main ( -- )
"hello" 42 i.add
;
"#;
let result = analyze(source);
assert!(!result.errors.is_empty());
assert!(result.errors[0].contains("error") || result.errors[0].contains("mismatch"));
}
}