factorio-codegen 0.1.8

Lua code generator for the factorio-rs Rust-to-Lua Factorio mod toolchain
Documentation
mod common;

use common::must_ok;
use factorio_codegen::LuaGenerator;
use factorio_ir::{
    block::Block,
    expression::Expression,
    function::{Function, Parameter},
    literal::Literal,
    module::{Module, Symbol},
    scope::Scope,
    stage::Stage,
    statement::Statement,
    r#type::Type,
};

#[test]
fn generates_module_with_private_helper_and_exported_handler() {
    let module = Module {
        name: "bound_detector".to_string(),
        stage: Stage::Control,
        body: Block {
            statements: vec![Statement::FunctionDecl(Function {
                name: "helper".to_string(),
                params: vec![],
                body: Block {
                    statements: vec![Statement::Return(Some(Expression::Literal(Literal::Int(
                        1,
                    ))))],
                },
                doc: None,
                debug: None,
                event: None,
                event_filter: None,
                export: None,
            })],
        },
        imports: vec![],
        submodules: vec![],
        locales: vec![],
        pending_locales: vec![],
        symbols: vec![Symbol {
            scope: Scope::Public,
            statement: Statement::FunctionDecl(Function {
                name: "on_init".to_string(),
                params: vec![Parameter {
                    name: "event".to_string(),
                    r#type: Type::Void,
                    source_type: None,
                }],
                body: Block {
                    statements: vec![Statement::VariableDecl {
                        name: "count".to_string(),
                        ty: Type::Int,
                        source_type: None,
                        value: Expression::Literal(Literal::Int(0)),
                    }],
                },
                doc: None,
                debug: None,
                event: None,
                event_filter: None,
                export: None,
            }),
        }],
    };

    let output = must_ok(LuaGenerator::new().generate_module(&module));

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

#[test]
fn omits_unreachable_private_helper_when_pruned() {
    use factorio_ir::prune::prune_modules;

    let mut module = Module {
        name: "bound_detector".to_string(),
        stage: Stage::Control,
        body: Block {
            statements: vec![Statement::FunctionDecl(Function {
                name: "helper".to_string(),
                params: vec![],
                body: Block {
                    statements: vec![Statement::Return(Some(Expression::Literal(Literal::Int(
                        1,
                    ))))],
                },
                doc: None,
                debug: None,
                event: None,
                event_filter: None,
                export: None,
            })],
        },
        imports: vec![],
        submodules: vec![],
        locales: vec![],
        pending_locales: vec![],
        symbols: vec![Symbol {
            scope: Scope::Public,
            statement: Statement::FunctionDecl(Function {
                name: "on_init".to_string(),
                params: vec![Parameter {
                    name: "event".to_string(),
                    r#type: Type::Void,
                    source_type: None,
                }],
                body: Block {
                    statements: vec![Statement::VariableDecl {
                        name: "count".to_string(),
                        ty: Type::Int,
                        source_type: None,
                        value: Expression::Literal(Literal::Int(0)),
                    }],
                },
                doc: None,
                debug: None,
                event: Some("on_init".to_string()),
                event_filter: None,
                export: None,
            }),
        }],
    };

    prune_modules(std::slice::from_mut(&mut module));

    let output = must_ok(LuaGenerator::new().generate_module(&module));

    assert_eq!(
        output,
        concat!(
            "-- Generated by factorio-rs@0.1.8\n",
            "-- Module: `bound_detector`\n",
            "local boundDetector = {}\n",
            "function boundDetector.on_init(event)\n",
            "\tlocal count = 0\n",
            "end\n",
            "return boundDetector\n",
        )
    );
}

#[test]
fn qualifies_exported_function_identifiers_used_as_values() {
    let module = Module {
        name: "control".to_string(),
        stage: Stage::Control,
        body: Block { statements: vec![] },
        imports: vec![],
        submodules: vec![],
        locales: vec![],
        pending_locales: vec![],
        symbols: vec![
            Symbol {
                scope: Scope::Public,
                statement: Statement::FunctionDecl(Function {
                    name: "greet".to_string(),
                    params: vec![],
                    body: Block { statements: vec![] },
                    doc: None,
                    debug: None,
                    event: None,
                    event_filter: None,
                    export: None,
                }),
            },
            Symbol {
                scope: Scope::Public,
                statement: Statement::FunctionDecl(Function {
                    name: "on_init".to_string(),
                    params: vec![],
                    body: Block {
                        statements: vec![Statement::Expr(Expression::MethodCall {
                            receiver: Box::new(Expression::Identifier("commands".to_string())),
                            method: "add_command".to_string(),
                            args: vec![
                                Expression::Literal(Literal::String("greet".to_string())),
                                Expression::Identifier("greet".to_string()),
                            ],
                        })],
                    },
                    doc: None,
                    debug: None,
                    event: Some("on_init".to_string()),
                    event_filter: None,
                    export: None,
                }),
            },
        ],
    };

    let output = must_ok(LuaGenerator::new().generate_module(&module));

    assert!(
        output.contains("commands.add_command(\"greet\", control.greet)"),
        "pub fn references must qualify through the module table, got:\n{output}"
    );
    assert!(
        !output.contains("add_command(\"greet\", greet)"),
        "bare exported fn name would be nil at runtime:\n{output}"
    );
}