Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -637,3 +637,4 @@ event = %Event{name: "go"}
- no need to mention code quality improvements as they are expected (unless the functional change is about code quality improvements)
- commit titles should be less than 50 characters and be in the simple present tense (active voice) - examples: 'Adds ..., Fixes ...'
- commit descriptions should wrap at about 72 characters and also be in the simple present tense (active voice)
- When writing functions that take a state_chart, put the state_chart as the first argument to help with threading the state_chart through code execution using Elixir pipelines
32 changes: 14 additions & 18 deletions lib/statifier/actions/action_executor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ defmodule Statifier.Actions.ActionExecutor do
})

# Delegate to LogAction's own execute method
LogAction.execute(log_action, state_chart)
LogAction.execute(state_chart, log_action)
end

defp execute_single_action(%RaiseAction{} = raise_action, state_id, phase, state_chart) do
Expand All @@ -125,7 +125,7 @@ defmodule Statifier.Actions.ActionExecutor do
})

# Delegate to RaiseAction's own execute method
RaiseAction.execute(raise_action, state_chart)
RaiseAction.execute(state_chart, raise_action)
end

defp execute_single_action(%AssignAction{} = assign_action, state_id, phase, state_chart) do
Expand All @@ -140,7 +140,7 @@ defmodule Statifier.Actions.ActionExecutor do
})

# Use the AssignAction's execute method which handles all the logic
AssignAction.execute(assign_action, state_chart)
AssignAction.execute(state_chart, assign_action)
end

defp execute_single_action(%IfAction{} = if_action, state_id, phase, state_chart) do
Expand All @@ -154,7 +154,7 @@ defmodule Statifier.Actions.ActionExecutor do
})

# Use the IfAction's execute method which handles all the conditional logic
IfAction.execute(if_action, state_chart)
IfAction.execute(state_chart, if_action)
end

defp execute_single_action(%ForeachAction{} = foreach_action, state_id, phase, state_chart) do
Expand All @@ -171,7 +171,7 @@ defmodule Statifier.Actions.ActionExecutor do
})

# Use the ForeachAction's execute method which handles all the iteration logic
ForeachAction.execute(foreach_action, state_chart)
ForeachAction.execute(state_chart, foreach_action)
end

defp execute_single_action(%SendAction{} = send_action, state_id, phase, state_chart) do
Expand All @@ -186,7 +186,7 @@ defmodule Statifier.Actions.ActionExecutor do
})

# Use the SendAction's execute method which handles all the send logic
SendAction.execute(send_action, state_chart)
SendAction.execute(state_chart, send_action)
end

defp execute_single_action(unknown_action, state_id, phase, state_chart) do
Expand All @@ -204,18 +204,14 @@ defmodule Statifier.Actions.ActionExecutor do
end

# Execute actions for a single transition
defp execute_single_transition_actions(state_chart, transition) do
case transition.actions do
[] ->
state_chart

actions ->
# Execute each action in the transition
actions
|> Enum.reduce(state_chart, fn action, acc_state_chart ->
execute_single_action(acc_state_chart, action)
end)
end
defp execute_single_transition_actions(state_chart, %{actions: []}),
do: state_chart

defp execute_single_transition_actions(state_chart, %{actions: actions}) do
# Execute each action in the transition
Enum.reduce(actions, state_chart, fn action, acc_state_chart ->
execute_single_action(acc_state_chart, action)
end)
end

# Create a summary of actions for logging
Expand Down
24 changes: 7 additions & 17 deletions lib/statifier/actions/assign_action.ex
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ defmodule Statifier.Actions.AssignAction do
@doc """
Create a new AssignAction from parsed attributes.

The expr is compiled for performance during creation.
The expr will be compiled during document validation for performance.

## Examples

Expand All @@ -53,32 +53,22 @@ defmodule Statifier.Actions.AssignAction do
"user.name"
iex> action.expr
"'John'"
iex> is_list(action.compiled_expr)
true
iex> action.compiled_expr
nil

"""
@spec new(String.t(), String.t(), map() | nil) :: t()
def new(location, expr, source_location \\ nil)
when is_binary(location) and is_binary(expr) do
# Pre-compile expression for performance
compiled_expr = compile_safe(expr, :expression)

%__MODULE__{
location: location,
expr: expr,
compiled_expr: compiled_expr,
# Will be compiled during validation
compiled_expr: nil,
source_location: source_location
}
end

# Safely compile expressions, returning nil on error
defp compile_safe(expr, _type) do
case Evaluator.compile_expression(expr) do
{:ok, compiled} -> compiled
{:error, _reason} -> nil
end
end

@doc """
Execute the assign action by evaluating the expression and assigning to the location.

Expand All @@ -89,8 +79,8 @@ defmodule Statifier.Actions.AssignAction do

Returns the updated StateChart with modified data model.
"""
@spec execute(t(), StateChart.t()) :: StateChart.t()
def execute(%__MODULE__{} = assign_action, %StateChart{} = state_chart) do
@spec execute(StateChart.t(), t()) :: StateChart.t()
def execute(%StateChart{} = state_chart, %__MODULE__{} = assign_action) do
# Use Evaluator.evaluate_and_assign with pre-compiled expression if available
case Evaluator.evaluate_and_assign(
assign_action.location,
Expand Down
18 changes: 4 additions & 14 deletions lib/statifier/actions/foreach_action.ex
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,13 @@ defmodule Statifier.Actions.ForeachAction do
@spec new(String.t(), String.t(), String.t() | nil, [term()], map() | nil) :: t()
def new(array, item, index \\ nil, actions, source_location \\ nil)
when is_binary(array) and is_binary(item) and is_list(actions) do
# Pre-compile array expression for performance
compiled_array = compile_safe(array)

%__MODULE__{
array: array,
item: item,
index: index,
actions: actions,
compiled_array: compiled_array,
# Will be compiled during validation
compiled_array: nil,
source_location: source_location
}
end
Expand All @@ -101,8 +99,8 @@ defmodule Statifier.Actions.ForeachAction do

Returns the updated StateChart.
"""
@spec execute(t(), StateChart.t()) :: StateChart.t()
def execute(%__MODULE__{} = foreach_action, %StateChart{} = state_chart) do
@spec execute(StateChart.t(), t()) :: StateChart.t()
def execute(%StateChart{} = state_chart, %__MODULE__{} = foreach_action) do
# Step 1: Evaluate array expression
case evaluate_array(foreach_action, state_chart) do
{:ok, collection} when is_list(collection) ->
Expand All @@ -124,14 +122,6 @@ defmodule Statifier.Actions.ForeachAction do

# Private functions

# Safely compile expressions, returning nil on error
defp compile_safe(expr) when is_binary(expr) do
case Evaluator.compile_expression(expr) do
{:ok, compiled} -> compiled
{:error, _reason} -> nil
end
end

# Evaluate the array expression to get the collection
defp evaluate_array(%{compiled_array: compiled_array, array: array_expr}, state_chart) do
case Evaluator.evaluate_value(compiled_array || array_expr, state_chart) do
Expand Down
42 changes: 23 additions & 19 deletions lib/statifier/actions/if_action.ex
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,14 @@ defmodule Statifier.Actions.IfAction do
"""
@spec new([conditional_block()], map() | nil) :: t()
def new(conditional_blocks, source_location \\ nil) when is_list(conditional_blocks) do
# Pre-compile conditions for performance
compiled_blocks =
# Add nil compiled_cond to blocks - will be compiled during validation
blocks_with_nil_compiled =
Enum.map(conditional_blocks, fn block ->
compiled_cond = compile_safe(block[:cond])
Map.put(block, :compiled_cond, compiled_cond)
Map.put(block, :compiled_cond, nil)
end)

%__MODULE__{
conditional_blocks: compiled_blocks,
conditional_blocks: blocks_with_nil_compiled,
source_location: source_location
}
end
Expand All @@ -95,23 +94,13 @@ defmodule Statifier.Actions.IfAction do
4. Return the updated StateChart

"""
@spec execute(t(), StateChart.t()) :: StateChart.t()
def execute(%__MODULE__{} = if_action, %StateChart{} = state_chart) do
@spec execute(StateChart.t(), t()) :: StateChart.t()
def execute(%StateChart{} = state_chart, %__MODULE__{} = if_action) do
execute_conditional_blocks(if_action.conditional_blocks, state_chart)
end

# Private functions

# Safely compile expressions, returning nil on error
defp compile_safe(nil), do: nil

defp compile_safe(expr) when is_binary(expr) do
case Evaluator.compile_expression(expr) do
{:ok, compiled} -> compiled
{:error, _reason} -> nil
end
end

# Process conditional blocks in order until one condition is true
defp execute_conditional_blocks([], state_chart), do: state_chart

Expand All @@ -130,9 +119,24 @@ defmodule Statifier.Actions.IfAction do
# Determine if a conditional block should be executed
defp should_execute_block?(%{type: :else}, _state_chart), do: true

defp should_execute_block?(%{type: type, compiled_cond: compiled_cond}, state_chart)
defp should_execute_block?(%{type: type, compiled_cond: compiled_cond, cond: cond}, state_chart)
when type in [:if, :elseif] do
Evaluator.evaluate_condition(compiled_cond, state_chart)
case compiled_cond do
nil when is_binary(cond) ->
# Fallback to runtime compilation for expressions not compiled during validation
case Evaluator.compile_expression(cond) do
{:ok, compiled} -> Evaluator.evaluate_condition(compiled, state_chart)
{:error, _reason} -> false
end

nil ->
# No condition provided
false

compiled ->
# Use pre-compiled condition
Evaluator.evaluate_condition(compiled, state_chart)
end
end

# Execute all actions within a conditional block
Expand Down
4 changes: 2 additions & 2 deletions lib/statifier/actions/log_action.ex
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ defmodule Statifier.Actions.LogAction do
@doc """
Executes the log action by evaluating the expression and logging the result.
"""
@spec execute(t(), Statifier.StateChart.t()) :: Statifier.StateChart.t()
def execute(%__MODULE__{} = log_action, state_chart) do
@spec execute(Statifier.StateChart.t(), t()) :: Statifier.StateChart.t()
def execute(state_chart, %__MODULE__{} = log_action) do
# Use Evaluator to handle quoted strings and expressions properly
message = evaluate_log_expression(log_action.expr, state_chart)
label = log_action.label || "Log"
Expand Down
4 changes: 2 additions & 2 deletions lib/statifier/actions/raise_action.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ defmodule Statifier.Actions.RaiseAction do
@doc """
Executes the raise action by creating an internal event and adding it to the state chart's event queue.
"""
@spec execute(t(), Statifier.StateChart.t()) :: Statifier.StateChart.t()
def execute(%__MODULE__{} = raise_action, state_chart) do
@spec execute(Statifier.StateChart.t(), t()) :: Statifier.StateChart.t()
def execute(state_chart, %__MODULE__{} = raise_action) do
event_name = raise_action.event || "anonymous_event"
# Create internal event and enqueue it
internal_event = %Event{
Expand Down
Loading