From 91b6fe41699c9316fe94ab956b60358fdcd2bdc5 Mon Sep 17 00:00:00 2001 From: JohnnyT Date: Tue, 2 Sep 2025 06:47:52 -0600 Subject: [PATCH 1/3] Fixes nested if parsing in SAX parser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds support for nested if blocks by handling the case where an if element ends while nested inside another if container. Previously, nested if blocks were lost during parsing instead of being created as separate IfAction instances. Changes: - Adds new pattern match case in handle_if_end for nested if containers - Creates IfAction from nested if container and adds to parent block - Enables proper parsing of complex conditional logic with nested if/else Fixes SCION test if_else/test0 which now passes. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- lib/statifier/parser/scxml/state_stack.ex | 11 +++++++++++ test/passing_tests.json | 3 ++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/statifier/parser/scxml/state_stack.ex b/lib/statifier/parser/scxml/state_stack.ex index 8a4122b..eb0b34f 100644 --- a/lib/statifier/parser/scxml/state_stack.ex +++ b/lib/statifier/parser/scxml/state_stack.ex @@ -584,6 +584,17 @@ defmodule Statifier.Parser.SCXML.StateStack do {:ok, %{state | stack: [{"transition", updated_transition} | rest]}} end + # Handle nested if action within parent if container + def handle_if_end( + %{stack: [{_element_name, nested_if_container} | [{"if", parent_if_container} | rest]]} = + state + ) do + # Create IfAction from nested if container and add to parent if container + nested_if_action = create_if_action_from_container(nested_if_container) + updated_parent_container = add_action_to_current_block(parent_if_container, nested_if_action) + {:ok, %{state | stack: [{"if", updated_parent_container} | rest]}} + end + def handle_if_end(state) do # If element not in an onentry/onexit context, just pop it {:ok, pop_element(state)} diff --git a/test/passing_tests.json b/test/passing_tests.json index 3a96a73..e40280a 100644 --- a/test/passing_tests.json +++ b/test/passing_tests.json @@ -5,7 +5,7 @@ "test/statifier/**/*_test.exs", "test/mix/**/*_test.exs" ], - "last_updated": "2025-08-31", + "last_updated": "2025-09-02", "scion_tests": [ "test/scion_tests/actionSend/send1_test.exs", "test/scion_tests/actionSend/send2_test.exs", @@ -46,6 +46,7 @@ "test/scion_tests/history/history4b_test.exs", "test/scion_tests/history/history5_test.exs", "test/scion_tests/history/history6_test.exs", + "test/scion_tests/if_else/test0_test.exs", "test/scion_tests/misc/deep_initial_test.exs", "test/scion_tests/more_parallel/test0_test.exs", "test/scion_tests/more_parallel/test10_test.exs", From cf2f38c319d16fa941d63cce78b20ddd2a37d8e5 Mon Sep 17 00:00:00 2001 From: JohnnyT Date: Tue, 2 Sep 2025 06:57:54 -0600 Subject: [PATCH 2/3] Adds tests for nested if parsing --- config/config.exs | 12 +-- lib/statifier/parser/scxml/state_stack.ex | 3 +- .../parser/scxml/state_stack_if_test.exs | 88 +++++++++++++++++++ 3 files changed, 91 insertions(+), 12 deletions(-) diff --git a/config/config.exs b/config/config.exs index 4603fb2..9bfdea8 100644 --- a/config/config.exs +++ b/config/config.exs @@ -7,17 +7,7 @@ if config_env() == :dev do config :logger, :console, format: "[$level] $message $metadata\n", - metadata: [ - :current_state, - :event, - :action_type, - :state_id, - :phase, - :transition_type, - :source_state, - :condition, - :targets - ] + metadata: :all # Default Statifier logging configuration for dev config :statifier, diff --git a/lib/statifier/parser/scxml/state_stack.ex b/lib/statifier/parser/scxml/state_stack.ex index eb0b34f..9d69231 100644 --- a/lib/statifier/parser/scxml/state_stack.ex +++ b/lib/statifier/parser/scxml/state_stack.ex @@ -6,6 +6,8 @@ defmodule Statifier.Parser.SCXML.StateStack do and updating parent elements when child elements are completed. """ + alias Statifier.Actions.IfAction + @doc """ Handle the end of a state element by adding it to its parent. """ @@ -853,7 +855,6 @@ defmodule Statifier.Parser.SCXML.StateStack do end) # Create IfAction with collected blocks - alias Statifier.Actions.IfAction IfAction.new(conditional_blocks, if_container[:location]) end diff --git a/test/statifier/parser/scxml/state_stack_if_test.exs b/test/statifier/parser/scxml/state_stack_if_test.exs index d737da3..13a5c4e 100644 --- a/test/statifier/parser/scxml/state_stack_if_test.exs +++ b/test/statifier/parser/scxml/state_stack_if_test.exs @@ -289,5 +289,93 @@ defmodule Statifier.Parser.SCXML.StateStackIfTest do assert length(current_block.actions) == 1 assert hd(current_block.actions) == assign_action end + + test "nested if within parent if container" do + # Test nested if action added to current conditional block within parent if container + nested_if_container = %{ + conditional_blocks: [ + %{ + type: :if, + cond: "x === 40", + actions: [ + %AssignAction{location: "x", expr: "x + 10"}, + %LogAction{label: "nested", expr: "x"} + ] + } + ], + current_block_index: 0, + location: %{line: 2, column: 5} + } + + parent_if_container = %{ + conditional_blocks: [ + %{ + type: :if, + cond: "x === 28", + actions: [ + %AssignAction{location: "x", expr: "x + 12"}, + %LogAction{label: "after_assign", expr: "x"} + ] + } + ], + current_block_index: 0, + location: %{line: 1, column: 1} + } + + state = %{ + stack: [ + {"if", nested_if_container}, + {"if", parent_if_container}, + {"onentry", :onentry_block}, + {"state", %Statifier.State{id: "test_state"}} + ] + } + + {:ok, result} = StateStack.handle_if_end(state) + + # Should have parent if container with nested if added as an action + [{"if", updated_parent_container} | _rest] = result.stack + current_block = Enum.at(updated_parent_container.conditional_blocks, 0) + + # Should have 3 actions: assign, log, nested_if_action + assert length(current_block.actions) == 3 + + # The last action should be the nested IfAction + nested_if_action = List.last(current_block.actions) + assert match?(%Statifier.Actions.IfAction{}, nested_if_action) + + # Verify the nested IfAction has correct structure + assert length(nested_if_action.conditional_blocks) == 1 + nested_block = hd(nested_if_action.conditional_blocks) + assert nested_block[:type] == :if + assert nested_block[:cond] == "x === 40" + assert length(nested_block[:actions]) == 2 + end + + test "nested if not in parent if context" do + # Test nested if element not within parent if container (fallback case) + nested_if_container = %{ + conditional_blocks: [ + %{type: :if, cond: "true", actions: [%LogAction{label: "test", expr: "value"}]} + ], + current_block_index: 0, + location: %{line: 1, column: 1} + } + + state = %{ + stack: [ + {"if", nested_if_container}, + {"onentry", :onentry_block}, + {"state", %Statifier.State{id: "test_state"}} + ] + } + + {:ok, result} = StateStack.handle_if_end(state) + + # Should create IfAction and add to onentry (normal if handling) + [{"onentry", actions} | _rest] = result.stack + assert length(actions) == 1 + assert match?(%Statifier.Actions.IfAction{}, hd(actions)) + end end end From 359edc18bd33b71de94d528eb2f7979182068414 Mon Sep 17 00:00:00 2001 From: JohnnyT Date: Tue, 2 Sep 2025 07:04:42 -0600 Subject: [PATCH 3/3] Updates badges and adds active dev notice --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ed3a40b..5d366cc 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,12 @@ # Statifier - SCXML State Machines for Elixir -[![CI](https://github.com/riddler/statifier/workflows/CI/badge.svg?branch=main)](https://github.com/riddler/statifier/actions) -[![Coverage](https://codecov.io/gh/riddler/statifier/branch/main/graph/badge.svg)](https://codecov.io/gh/riddler/statifier) +[![CI](https://github.com/riddler/statifier/actions/workflows/ci.yml/badge.svg)](https://github.com/riddler/statifier/actions/workflows/ci.yml) +[![codecov](https://codecov.io/gh/riddler/statifier/branch/main/graph/badge.svg)](https://codecov.io/gh/riddler/statifier) +[![Hex.pm Version](https://img.shields.io/hexpm/v/statifier.svg)](https://hex.pm/packages/statifier) +[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/statifier/) + +> ⚠️ **Active Development Notice** +> This project is still under active development and things may change even though it is passed version 1. APIs, features, and behaviors may evolve as we continue improving SCXML compliance and functionality. An Elixir implementation of SCXML (State Chart XML) state charts with a focus on W3C compliance.