factorio-frontend 0.1.0

Rust frontend that lowers Factorio mod sources into factorio-ir
Documentation
mod common;

use common::{must_ok, must_ok_parse};
use factorio_codegen::LuaGenerator;
use factorio_frontend::parse_module;
use factorio_ir::{statement::Statement, r#type::Type};

const BOUND_DETECTOR_SOURCE: &str = r"
fn helper() -> i64 {
    return 1;
}

pub fn on_init(_event: ()) {
    let count: i32 = 0;
}
";

#[test]
fn parses_module_with_private_helper_and_exported_handler() {
    let module = must_ok_parse(parse_module(
        BOUND_DETECTOR_SOURCE,
        "control.bound_detector",
    ));

    assert_eq!(module.name, "control.bound_detector");
    assert_eq!(module.symbols.len(), 1);

    let Statement::FunctionDecl(helper) = &module.body.statements[0] else {
        assert_eq!(1, 0, "expected helper function");
        return;
    };
    assert_eq!(helper.name, "helper");
    assert_eq!(
        helper
            .debug
            .as_ref()
            .map(|debug| debug.header_comment.as_str()),
        Some("fn helper() -> i64")
    );

    let Statement::FunctionDecl(on_init) = &module.symbols[0].statement else {
        assert_eq!(1, 0, "expected on_init function");
        return;
    };
    assert_eq!(on_init.name, "on_init");
    assert_eq!(on_init.params.len(), 1);
    assert_eq!(on_init.params[0].name, "_event");
    assert_eq!(on_init.params[0].source_type.as_deref(), Some("()"));

    let Statement::VariableDecl {
        name,
        ty,
        source_type,
        ..
    } = &on_init.body.statements[0]
    else {
        assert_eq!(1, 0, "expected count variable");
        return;
    };
    assert_eq!(name, "count");
    assert_eq!(*ty, Type::Int);
    assert_eq!(source_type.as_deref(), Some("i32"));
}

#[test]
fn parsed_module_generates_expected_lua() {
    let module = must_ok_parse(parse_module(
        BOUND_DETECTOR_SOURCE,
        "control.bound_detector",
    ));
    let output = must_ok(LuaGenerator::new().generate_module(&module));

    assert_eq!(
        output,
        concat!(
            "-- Generated by factorio-rs@0.1.0\n",
            "-- Module: `control.bound_detector`\n",
            "local function helper()\n",
            "\treturn 1\n",
            "end\n",
            "local controlBoundDetector = {}\n",
            "function controlBoundDetector.on_init(_event)\n",
            "\tlocal count = 0\n",
            "end\n",
            "return controlBoundDetector\n",
        )
    );
}

#[test]
fn debug_level_zero_emits_rust_header_comments() {
    let module = must_ok_parse(parse_module(
        BOUND_DETECTOR_SOURCE,
        "control.bound_detector",
    ));
    let output = must_ok(LuaGenerator::with_debug_level(0).generate_module(&module));

    assert!(output.contains("-- fn helper() -> i64"));
    assert!(output.contains("-- pub fn on_init(_event: ())"));
}

#[test]
fn debug_level_one_emits_type_annotations() {
    let module = must_ok_parse(parse_module(
        BOUND_DETECTOR_SOURCE,
        "control.bound_detector",
    ));
    let output = must_ok(LuaGenerator::with_debug_level(1).generate_module(&module));

    assert!(output.contains("function controlBoundDetector.on_init(_event --[[ () ]])"));
    assert!(output.contains("local count --[[ i32 ]] = 0"));
}