local test = dofile("test/test_harness.lua") package.path = package.path .. ";lua/?.lua" local fb_parser = require("scl.fb_parser") local function test_fb_parser_basic() local content = [[ FUNCTION_BLOCK "test_fb" TITLE = Test FB VERSION : 0.1 VAR_INPUT enable : Bool; count : Int; END_VAR VAR_OUTPUT done : Bool; result : DInt; END_VAR VAR_IN_OUT data : "some_udt"; END_VAR END_FUNCTION_BLOCK ]] local fb, err = fb_parser.parse_fb_content(content, "test.scl") test.assert_not_nil(fb, "parse_fb_content should return fb") test.assert_equals(fb.name, "test_fb", "FB name should be extracted") test.assert_equals(fb.type, "FUNCTION_BLOCK", "FB type should be FUNCTION_BLOCK") test.assert_equals(#fb.inputs, 2, "should have 2 inputs") test.assert_equals(#fb.outputs, 2, "should have 2 outputs") test.assert_equals(#fb.inouts, 1, "should have 1 inout") test.assert_equals(fb.inputs[1].name, "enable", "first input name") test.assert_equals(fb.inouts[1].is_quoted_type, true, "inout is quoted type") end local function test_fb_parser_attributes() local content = [[ FUNCTION_BLOCK "attr_fb" TITLE = Attr FB VERSION : 0.1 VAR_INPUT attrInput{S7_SetPoint := 'True'}: Bool; normalInput : Int; END_VAR END_FUNCTION_BLOCK ]] local fb = fb_parser.parse_fb_content(content, "attr.scl") test.assert_not_nil(fb, "parse_fb_content should return fb") test.assert_equals(fb.inputs[1].attributes.S7_SetPoint, "True", "attribute should be parsed") end local function test_fb_parser_comments() local content = [[ FUNCTION_BLOCK "comment_fb" TITLE = Comment FB VERSION : 0.1 VAR_INPUT input1 : Bool; //enable flag input2 : Int; //counter END_VAR END_FUNCTION_BLOCK ]] local fb = fb_parser.parse_fb_content(content, "comment.scl") test.assert_not_nil(fb, "parse_fb_content should return fb") test.assert_equals(fb.inputs[1].comment, "enable flag", "input comment should be extracted") end local function test_fb_parser_function() local content = [[ FUNCTION "test_func" : Int TITLE = Test Function VERSION : 0.1 VAR_INPUT a : Int; b : Int; END_VAR BEGIN END_FUNCTION ]] local fb = fb_parser.parse_fb_content(content, "test_func.scl") test.assert_not_nil(fb, "parse_fb_content should return fb") test.assert_equals(fb.name, "test_func", "Function name should be extracted") test.assert_equals(fb.type, "FUNCTION", "FB type should be FUNCTION") test.assert_equals(fb.return_type, "Int", "return type should be extracted") end local function test_fb_parser_default_values() local content = [[ FUNCTION_BLOCK "default_fb" TITLE = Default FB VERSION : 0.1 VAR_INPUT withDefault : Int := 5; withoutDefault : Bool; END_VAR END_FUNCTION_BLOCK ]] local fb = fb_parser.parse_fb_content(content, "default.scl") test.assert_not_nil(fb, "parse_fb_content should return fb") test.assert(fb.inputs[1].has_default, "first input has default") test.assert_equals(fb.inputs[1].default_value, "5", "default value should be extracted") test.assert(not fb.inputs[2].has_default, "second input has no default") end local function test_fb_cache_operations() fb_parser.clear_cache() test.assert_equals(fb_parser.get_cache_count(), 0, "cache should be empty after clear") local content = [[ FUNCTION_BLOCK "cached_fb" TITLE = Cached FB VERSION : 0.1 VAR_INPUT input1 : Bool; END_VAR END_FUNCTION_BLOCK ]] local fb = fb_parser.parse_fb_content(content, "cached.scl") test.assert_not_nil(fb, "FB should be parsed") test.assert_equals(fb.name, "cached_fb", "FB name should be extracted") end local function test_fb_is_type() fb_parser.clear_cache() test.assert(true, "is_fb_type requires file-based caching - tested separately") end local function test_fb_interface() fb_parser.clear_cache() local content = [[ FUNCTION_BLOCK "interface_fb" TITLE = Interface FB VERSION : 0.1 VAR_INPUT enable : Bool; value : Int; END_VAR VAR_OUTPUT done : Bool; error : Bool; END_VAR VAR_IN_OUT data : "some_type"; END_VAR END_FUNCTION_BLOCK ]] local fb = fb_parser.parse_fb_content(content, "interface.scl") test.assert_not_nil(fb, "FB should be parsed") if fb then test.assert_equals(#fb.inputs, 2, "should have 2 inputs") test.assert_equals(#fb.outputs, 2, "should have 2 outputs") test.assert_equals(#fb.inouts, 1, "should have 1 inout") end end local function test_fb_reference_files() fb_parser.clear_cache() local ref_path = test.REF_PROJECT .. "/Program blocks/01_Machine/MachineMain.scl" local file = io.open(ref_path, "r") if not file then print("Warning: Reference SCL file not found: " .. ref_path) return end local content = file:read("*all") file:close() local fb = fb_parser.parse_fb_content(content, ref_path) test.assert_not_nil(fb, "should parse reference SCL file") test.assert_equals(fb.name, "MachineMain", "FB name should match") test.assert(fb.type == "FUNCTION_BLOCK", "should be FUNCTION_BLOCK") end local function test_fb_multiple_reference_files() fb_parser.clear_cache() local files = { test.REF_PROJECT .. "/Program blocks/01_Machine/MachineMain.scl", test.REF_PROJECT .. "/Program blocks/01_Machine/ReadInputs.scl", test.REF_PROJECT .. "/Program blocks/01_Machine/WriteOutputs.scl", } local parsed_count = 0 for _, filepath in ipairs(files) do local file = io.open(filepath, "r") if file then local content = file:read("*all") file:close() local fb = fb_parser.parse_fb_content(content, filepath) if fb then parsed_count = parsed_count + 1 end end end test.assert_gt(parsed_count, 0, "should parse at least one reference SCL") end local function run_fb_tests() test.run_suite("FB Parser Tests", function() print("\n--- Basic Tests ---") test_fb_parser_basic() test_fb_parser_attributes() test_fb_parser_comments() test_fb_parser_function() test_fb_parser_default_values() print("\n--- Cache Tests ---") test_fb_cache_operations() test_fb_is_type() test_fb_interface() print("\n--- Reference Project Tests ---") test_fb_reference_files() test_fb_multiple_reference_files() end) end if vim then vim.api.nvim_create_user_command("TestFb", function() run_fb_tests() end, {}) end run_fb_tests()