From 29556467a817a6c885d5fc31711765958e633cb2 Mon Sep 17 00:00:00 2001 From: Chloe Date: Tue, 7 Apr 2026 22:08:59 -0400 Subject: [PATCH 1/4] Enhance expression evaluation with syntax-driven parsing and type validation; update event sheet resources to escape string values. --- .../flowkit/runtime/expression_evaluator.gd | 121 ++++++++++++++---- .../event_sheet/1035986417700190942.tres | 2 +- .../event_sheet/2450759864276140733.tres | 18 +-- .../event_sheet/3722860173612438292.tres | 8 +- .../event_sheet/4953409392252150460.tres | 30 ++--- .../event_sheet/6104984665772134237.tres | 18 +-- .../event_sheet/6180251268833091125.tres | 8 +- .../event_sheet/7830454191141003914.tres | 18 +-- 8 files changed, 148 insertions(+), 75 deletions(-) diff --git a/addons/flowkit/runtime/expression_evaluator.gd b/addons/flowkit/runtime/expression_evaluator.gd index 2e1e8cb..06d3b8c 100644 --- a/addons/flowkit/runtime/expression_evaluator.gd +++ b/addons/flowkit/runtime/expression_evaluator.gd @@ -17,12 +17,15 @@ class_name FKExpressionEvaluator ## Evaluate a string expression and returns the result ## Tries to parse as literal first, then as GDScript expression +## Evaluation is syntax-driven: the expression is parsed/evaluated based on its syntax alone. +## After evaluation, the result type is validated against expected_type (if provided). ## context_node: the base instance for expression execution (determines where get_node() resolves from) ## scene_root: optional scene root node, exposed as 'scene_root' variable in expressions ## target_node: optional action target node, used for n_ variable lookups (falls back to context_node) -static func evaluate(expr_str: String, context_node: Node = null, scene_root: Node = null, target_node: Node = null) -> Variant: +## expected_type: optional Variant.Type to validate the result against (-1 = no validation) +static func evaluate(expr_str: String, context_node: Node = null, scene_root: Node = null, target_node: Node = null, expected_type: int = -1) -> Variant: if expr_str.is_empty(): - return "" + return _check_type("", expected_type, expr_str) # Trim whitespace expr_str = expr_str.strip_edges() @@ -37,21 +40,22 @@ static func evaluate(expr_str: String, context_node: Node = null, scene_root: No if var_name.is_valid_identifier(): var result = _resolve_n_variable(n_var_node, var_name) if result[0]: # Variable was found - return result[1] + return _check_type(result[1], expected_type, expr_str) # Variable not found - still try as expression below # Try to parse as a literal value first var literal_result = _try_parse_literal(expr_str) - if literal_result != null: - return literal_result + if literal_result[0]: + return _check_type(literal_result[1], expected_type, expr_str) # If not a literal, try to evaluate as a GDScript expression var expr_result = _evaluate_expression(expr_str, context_node, scene_root, target_node) - if expr_result != null: - return expr_result + if expr_result[0]: + return _check_type(expr_result[1], expected_type, expr_str) - # If all else fails, return as string - return expr_str + # Evaluation failed - do not fall back to raw string + push_error("FlowKit: Failed to evaluate expression: '%s'" % expr_str) + return null ## Resolve an n_ variable from a node. Checks FlowKitSystem node variables first, @@ -95,35 +99,38 @@ static func _resolve_n_variable(node: Node, var_name: String) -> Array: ## Try to parse the string as a literal value (not an expression) -## Returns the parsed value, or null if it's not a literal -static func _try_parse_literal(expr: String) -> Variant: +## Returns [found: bool, value: Variant] to distinguish 'not a literal' from a literal null +static func _try_parse_literal(expr: String) -> Array: # Boolean literals if expr.to_lower() == "true": - return true + return [true, true] if expr.to_lower() == "false": - return false + return [true, false] # Null literal if expr.to_lower() == "null": - return null + return [true, null] # String literals (quoted) if _is_quoted_string(expr): - return _parse_quoted_string(expr) + return [true, _parse_quoted_string(expr)] # Numeric literals if _is_numeric(expr): if "." in expr or "e" in expr.to_lower(): - return float(expr) + return [true, float(expr)] else: - return int(expr) + return [true, int(expr)] # Vector/Color literals (e.g., "Vector2(0,0)", "Color(1,0,0,1)") if _is_constructor_literal(expr): - return _evaluate_expression(expr, null) + var result = _evaluate_expression(expr, null) + if result[0]: + return result + return [false, null] # Not a literal - return null + return [false, null] ## Check if string is a quoted string literal @@ -211,7 +218,7 @@ static func _is_constructor_literal(expr: String) -> bool: ## context_node: used as the base instance for Expression.execute() (where get_node() resolves from) ## scene_root: optional scene root node, exposed as 'scene_root' in expressions ## target_node: optional action target node, exposed as 'node' in expressions (falls back to context_node) -static func _evaluate_expression(expr_str: String, context_node: Node, scene_root: Node = null, target_node: Node = null) -> Variant: +static func _evaluate_expression(expr_str: String, context_node: Node, scene_root: Node = null, target_node: Node = null) -> Array: var expression = Expression.new() # Build input variables for the expression @@ -310,16 +317,16 @@ static func _evaluate_expression(expr_str: String, context_node: Node, scene_roo var parse_error = expression.parse(expr_str, input_names) if parse_error != OK: # Silently fail - not an expression - return null + return [false, null] # Execute it var result = expression.execute(input_values, context_node, false) if expression.has_execute_failed(): # Silently fail - expression execution failed - return null + return [false, null] - return result + return [true, result] ## Convenience method to evaluate all inputs in a dictionary @@ -327,7 +334,8 @@ static func _evaluate_expression(expr_str: String, context_node: Node, scene_roo ## context_node: the base instance for expression execution ## scene_root: optional scene root node, forwarded to evaluate() ## target_node: optional action target node for n_ variable lookups -static func evaluate_inputs(inputs: Dictionary, context_node: Node = null, scene_root: Node = null, target_node: Node = null) -> Dictionary: +## type_hints: optional dictionary mapping input names to Variant.Type int values for post-evaluation validation +static func evaluate_inputs(inputs: Dictionary, context_node: Node = null, scene_root: Node = null, target_node: Node = null, type_hints: Dictionary = {}) -> Dictionary: var evaluated: Dictionary = {} for key in inputs.keys(): @@ -335,8 +343,71 @@ static func evaluate_inputs(inputs: Dictionary, context_node: Node = null, scene # Only evaluate if the value is a string if value is String: - evaluated[key] = evaluate(value, context_node, scene_root, target_node) + var expected_type: int = type_hints.get(key, -1) + evaluated[key] = evaluate(value, context_node, scene_root, target_node, expected_type) else: evaluated[key] = value return evaluated + + +## Validate that a result matches the expected Variant.Type +## Returns the value as-is but pushes an error if type does not match +static func _check_type(value: Variant, expected_type: int, expr_str: String) -> Variant: + if expected_type < 0: + return value + if typeof(value) != expected_type: + push_error("FlowKit: Expression '%s' evaluated to %s (type %s) but expected type %s" % [ + expr_str, str(value), type_string(typeof(value)), type_string(expected_type)]) + return value + + +## Convert a type name string (as used in provider get_inputs()) to a Variant.Type int +## Returns -1 for unknown or "Variant" types (no validation) +static func type_name_to_variant_type(type_name: String) -> int: + match type_name: + "bool": + return TYPE_BOOL + "int": + return TYPE_INT + "float": + return TYPE_FLOAT + "String": + return TYPE_STRING + "Vector2": + return TYPE_VECTOR2 + "Vector2i": + return TYPE_VECTOR2I + "Vector3": + return TYPE_VECTOR3 + "Vector3i": + return TYPE_VECTOR3I + "Vector4": + return TYPE_VECTOR4 + "Vector4i": + return TYPE_VECTOR4I + "Color": + return TYPE_COLOR + "Rect2": + return TYPE_RECT2 + "Transform2D": + return TYPE_TRANSFORM2D + "Plane": + return TYPE_PLANE + "Quaternion": + return TYPE_QUATERNION + "AABB": + return TYPE_AABB + "Basis": + return TYPE_BASIS + "Transform3D": + return TYPE_TRANSFORM3D + "NodePath": + return TYPE_NODE_PATH + "Array": + return TYPE_ARRAY + "Dictionary": + return TYPE_DICTIONARY + "Variant", "": + return -1 + return -1 diff --git a/addons/flowkit/saved/event_sheet/1035986417700190942.tres b/addons/flowkit/saved/event_sheet/1035986417700190942.tres index ed91428..3d302ec 100644 --- a/addons/flowkit/saved/event_sheet/1035986417700190942.tres +++ b/addons/flowkit/saved/event_sheet/1035986417700190942.tres @@ -44,7 +44,7 @@ script = ExtResource("5_1wrk8") condition_id = "compare_node_variable" target_node = NodePath("CharacterBody2D") inputs = { -"Comparison": ">", +"Comparison": "\">\"", "Property": "node.position.y", "Value": "n_lowest_y" } diff --git a/addons/flowkit/saved/event_sheet/2450759864276140733.tres b/addons/flowkit/saved/event_sheet/2450759864276140733.tres index 1a6ae27..faef458 100644 --- a/addons/flowkit/saved/event_sheet/2450759864276140733.tres +++ b/addons/flowkit/saved/event_sheet/2450759864276140733.tres @@ -12,7 +12,7 @@ script = ExtResource("3_7puo2") action_id = "set_window_mode" target_node = NodePath("System") inputs = { -"Mode": "windowed" +"Mode": "\"windowed\"" } block_type = "action" @@ -31,7 +31,7 @@ script = ExtResource("3_7puo2") action_id = "set_variable" target_node = NodePath("System") inputs = { -"Name": "times", +"Name": "\"times\"", "Value": "1" } block_type = "action" @@ -41,7 +41,9 @@ script = ExtResource("3_7puo2") action_id = "Print Message" target_node = NodePath(".") inputs = { -"color": "red", +"Color": "\"red\"", +"Message": "\"Counting from 1 - 10\"", +"color": "\"red\"", "message": "\"Counting from 1 to 10\"" } block_type = "action" @@ -51,7 +53,7 @@ script = ExtResource("3_7puo2") action_id = "Print Message" target_node = NodePath(".") inputs = { -"color": "blue", +"color": "\"blue\"", "message": "system.get_var(\"times\")" } block_type = "action" @@ -61,7 +63,7 @@ script = ExtResource("3_7puo2") action_id = "set_variable" target_node = NodePath("System") inputs = { -"Name": "times", +"Name": "\"times\"", "Value": "system.get_var(\"times\")+1" } block_type = "action" @@ -82,14 +84,14 @@ script = ExtResource("3_7puo2") action_id = "Print Message" target_node = NodePath(".") inputs = { -"color": "red", -"message": "Done!" +"color": "\"red\"", +"message": "\"Done!\"" } block_type = "action" [sub_resource type="Resource" id="Resource_5nvf0"] script = ExtResource("2_o0xcg") -block_id = "event_1775232400_3648767005" +block_id = "event_1775613196_2355312579" event_id = "on_ready" target_node = NodePath(".") actions = Array[ExtResource("3_7puo2")]([SubResource("Resource_xv5k1"), SubResource("Resource_o0xcg"), SubResource("Resource_7puo2"), SubResource("Resource_ko5ym"), SubResource("Resource_v5tg8"), SubResource("Resource_l83bg")]) diff --git a/addons/flowkit/saved/event_sheet/3722860173612438292.tres b/addons/flowkit/saved/event_sheet/3722860173612438292.tres index 84cb585..156faf7 100644 --- a/addons/flowkit/saved/event_sheet/3722860173612438292.tres +++ b/addons/flowkit/saved/event_sheet/3722860173612438292.tres @@ -22,7 +22,7 @@ script = ExtResource("3_uraip") action_id = "set_window_mode" target_node = NodePath("System") inputs = { -"Mode": "windowed" +"Mode": "\"windowed\"" } block_type = "action" @@ -58,7 +58,7 @@ action_id = "Print Message" target_node = NodePath("Logic") inputs = { "color": "", -"message": "First timer hit timeout. Starting the second one." +"message": "\"First timer hit timeout. Starting the second one.\"" } block_type = "action" @@ -82,7 +82,7 @@ action_id = "Print Message" target_node = NodePath("Logic") inputs = { "color": "", -"message": "Second timer hit timeout. Starting the third one." +"message": "\"Second timer hit timeout. Starting the third one.\"" } block_type = "action" @@ -106,7 +106,7 @@ action_id = "Print Message" target_node = NodePath("Logic") inputs = { "color": "", -"message": "Third timer hit timeout. That's all for this demo scene." +"message": "\"Third timer hit timeout. That's all for this demo scene.\"" } block_type = "action" diff --git a/addons/flowkit/saved/event_sheet/4953409392252150460.tres b/addons/flowkit/saved/event_sheet/4953409392252150460.tres index 2792c42..8019523 100644 --- a/addons/flowkit/saved/event_sheet/4953409392252150460.tres +++ b/addons/flowkit/saved/event_sheet/4953409392252150460.tres @@ -12,8 +12,8 @@ script = ExtResource("2_e5301") action_id = "get_project_setting" target_node = NodePath("System") inputs = { -"Path": "physics/2d/default_gravity", -"Store In": "gravity" +"Path": "\"physics/2d/default_gravity\"", +"Store In": "\"gravity\"" } block_type = "action" @@ -23,7 +23,7 @@ action_id = "set_node_variable" target_node = NodePath(".") inputs = { "Value": "-400.0", -"Variable Name": "jump_force" +"Variable Name": "\"jump_force\"" } block_type = "action" @@ -33,7 +33,7 @@ action_id = "set_node_variable" target_node = NodePath(".") inputs = { "Value": "200.0", -"Variable Name": "speed" +"Variable Name": "\"speed\"" } block_type = "action" @@ -103,7 +103,7 @@ script = ExtResource("3_6310v") condition_id = "is_key_pressed" target_node = NodePath("System") inputs = { -"Key": "Space" +"Key": "\"Space\"" } block_type = "condition" @@ -120,9 +120,9 @@ script = ExtResource("2_e5301") action_id = "get_input_axis_2way" target_node = NodePath("System") inputs = { -"Negative Action": "ui_left", -"Positive Action": "ui_right", -"Store In": "direction" +"Negative Action": "\"ui_left\"", +"Positive Action": "\"ui_right\"", +"Store In": "\"direction\"" } block_type = "action" @@ -140,7 +140,7 @@ script = ExtResource("3_6310v") condition_id = "compare_node_variable" target_node = NodePath(".") inputs = { -"Comparison": "!=", +"Comparison": "\"!=\"", "Property": "direction", "Value": "0" } @@ -211,7 +211,7 @@ script = ExtResource("3_6310v") condition_id = "compare_node_variable" target_node = NodePath(".") inputs = { -"Comparison": "<", +"Comparison": "\"<\"", "Property": "velocity.x", "Value": "0" } @@ -240,7 +240,7 @@ script = ExtResource("3_6310v") condition_id = "compare_node_variable" target_node = NodePath(".") inputs = { -"Comparison": ">", +"Comparison": "\">\"", "Property": "velocity.x", "Value": "0" } @@ -280,7 +280,7 @@ target_node = NodePath("AnimatedSprite2D") inputs = { "Custom Speed": "1", "From End": "false", -"Name": "default" +"Name": "\"default\"" } block_type = "action" @@ -289,7 +289,7 @@ script = ExtResource("3_6310v") condition_id = "compare_node_variable" target_node = NodePath(".") inputs = { -"Comparison": "==", +"Comparison": "\"==\"", "Property": "node.velocity.x", "Value": "0" } @@ -322,7 +322,7 @@ target_node = NodePath("AnimatedSprite2D") inputs = { "Custom Speed": "1", "From End": "false", -"Name": "run" +"Name": "\"run\"" } block_type = "action" @@ -331,7 +331,7 @@ script = ExtResource("3_6310v") condition_id = "compare_node_variable" target_node = NodePath(".") inputs = { -"Comparison": "!=", +"Comparison": "\"!=\"", "Property": "node.velocity.x", "Value": "0" } diff --git a/addons/flowkit/saved/event_sheet/6104984665772134237.tres b/addons/flowkit/saved/event_sheet/6104984665772134237.tres index 1f8c997..09a4f4e 100644 --- a/addons/flowkit/saved/event_sheet/6104984665772134237.tres +++ b/addons/flowkit/saved/event_sheet/6104984665772134237.tres @@ -15,7 +15,7 @@ inputs = { "Color": "", "Message": "", "color": "", -"message": "This is a message printed with the default color." +"message": "\"This is a message printed with the default color.\"" } block_type = "action" @@ -34,7 +34,7 @@ script = ExtResource("3_5poua") action_id = "set_window_mode" target_node = NodePath("System") inputs = { -"Mode": "windowed" +"Mode": "\"windowed\"" } block_type = "action" @@ -43,8 +43,8 @@ script = ExtResource("3_5poua") action_id = "Print Message" target_node = NodePath(".") inputs = { -"color": "red", -"message": "This is a message printed in red text." +"color": "\"red\"", +"message": "\"This is a message printed in red text.\"" } block_type = "action" @@ -53,8 +53,8 @@ script = ExtResource("3_5poua") action_id = "Print Message" target_node = NodePath(".") inputs = { -"color": "green", -"message": "[i]This is a message printed in green italicized text.[/i]" +"color": "\"green\"", +"message": "\"[i]This is a message printed in green italicized text.[/i]\"" } block_type = "action" @@ -63,8 +63,8 @@ script = ExtResource("3_5poua") action_id = "Print Message" target_node = NodePath(".") inputs = { -"color": "yellow", -"message": "[b]Yellow bold text.[/b]" +"color": "\"yellow\"", +"message": "\"[b]Yellow bold text.[/b]\"" } block_type = "action" @@ -74,7 +74,7 @@ action_id = "Print Message" target_node = NodePath(".") inputs = { "color": "", -"message": "[wave][color=#FF0000]W[/color][color=#FF5500]a[/color][color=#FFAA00]v[/color][color=#FFFF00]y[/color] [color=#54FF00]r[/color][color=#00FF00]a[/color][color=#00FF55]i[/color][color=#00FFAA]n[/color][color=#00FEFF]b[/color][color=#00A9FF]o[/color][color=#0054FF]w[/color] [color=#5500FF]t[/color][color=#AA00FF]e[/color][color=#FF00FE]x[/color][color=#FF00AA]t[/color][color=#FF0054]![/color][/wave]" +"message": "\"[wave][color=#FF0000]W[/color][color=#FF5500]a[/color][color=#FFAA00]v[/color][color=#FFFF00]y[/color] [color=#54FF00]r[/color][color=#00FF00]a[/color][color=#00FF55]i[/color][color=#00FFAA]n[/color][color=#00FEFF]b[/color][color=#00A9FF]o[/color][color=#0054FF]w[/color] [color=#5500FF]t[/color][color=#AA00FF]e[/color][color=#FF00FE]x[/color][color=#FF00AA]t[/color][color=#FF0054]![/color][/wave]\"" } block_type = "action" diff --git a/addons/flowkit/saved/event_sheet/6180251268833091125.tres b/addons/flowkit/saved/event_sheet/6180251268833091125.tres index 0be5692..e26f644 100644 --- a/addons/flowkit/saved/event_sheet/6180251268833091125.tres +++ b/addons/flowkit/saved/event_sheet/6180251268833091125.tres @@ -17,7 +17,7 @@ script = ExtResource("3_ytm5k") action_id = "set_window_mode" target_node = NodePath("System") inputs = { -"Mode": "windowed" +"Mode": "\"windowed\"" } block_type = "action" @@ -101,7 +101,7 @@ script = ExtResource("3_ytm5k") action_id = "Wait For Input" target_node = NodePath("System") inputs = { -"Input Binding Name": "ui_accept" +"Input Binding Name": "\"ui_accept\"" } block_type = "action" @@ -136,7 +136,7 @@ script = ExtResource("3_ytm5k") action_id = "Wait For Input" target_node = NodePath("System") inputs = { -"Input Binding Name": "ui_accept" +"Input Binding Name": "\"ui_accept\"" } block_type = "action" @@ -171,7 +171,7 @@ script = ExtResource("3_ytm5k") action_id = "Wait For Input" target_node = NodePath("System") inputs = { -"Input Binding Name": "ui_accept" +"Input Binding Name": "\"ui_accept\"" } block_type = "action" diff --git a/addons/flowkit/saved/event_sheet/7830454191141003914.tres b/addons/flowkit/saved/event_sheet/7830454191141003914.tres index 50d52e9..68ece39 100644 --- a/addons/flowkit/saved/event_sheet/7830454191141003914.tres +++ b/addons/flowkit/saved/event_sheet/7830454191141003914.tres @@ -12,7 +12,7 @@ script = ExtResource("3_j21vh") action_id = "set_window_mode" target_node = NodePath("System") inputs = { -"Mode": "windowed" +"Mode": "\"windowed\"" } block_type = "action" @@ -389,7 +389,7 @@ script = ExtResource("4_s7nm7") condition_id = "compare_variable" target_node = NodePath("System") inputs = { -"Comparison": "==", +"Comparison": "\"==\"", "Name": "\"chosenAnswer\"", "Value": "system.get_var(\"currentCorrectAnswer\")" } @@ -440,7 +440,7 @@ action_id = "Print Message" target_node = NodePath(".") inputs = { "color": "", -"message": "Response to second answer's button press" +"message": "\"Response to second answer's button press\"" } block_type = "action" @@ -475,7 +475,7 @@ script = ExtResource("4_s7nm7") condition_id = "compare_variable" target_node = NodePath("System") inputs = { -"Comparison": "==", +"Comparison": "\"==\"", "Name": "\"chosenAnswer\"", "Value": "system.get_var(\"currentCorrectAnswer\")" } @@ -516,7 +516,7 @@ action_id = "Print Message" target_node = NodePath(".") inputs = { "color": "", -"message": "Response to third answer's button press" +"message": "\"Response to third answer's button press\"" } block_type = "action" @@ -551,7 +551,7 @@ script = ExtResource("4_s7nm7") condition_id = "compare_variable" target_node = NodePath("System") inputs = { -"Comparison": "==", +"Comparison": "\"==\"", "Name": "\"chosenAnswer\"", "Value": "system.get_var(\"currentCorrectAnswer\")" } @@ -592,7 +592,7 @@ action_id = "Print Message" target_node = NodePath(".") inputs = { "color": "", -"message": "Response to fourth answer's button press" +"message": "\"Response to fourth answer's button press\"" } block_type = "action" @@ -627,7 +627,7 @@ script = ExtResource("4_s7nm7") condition_id = "compare_variable" target_node = NodePath("System") inputs = { -"Comparison": "==", +"Comparison": "\"==\"", "Name": "\"chosenAnswer\"", "Value": "system.get_var(\"currentCorrectAnswer\")" } @@ -668,7 +668,7 @@ action_id = "Print Message" target_node = NodePath(".") inputs = { "color": "", -"message": "First answer button's text changed" +"message": "\"First answer button's text changed\"" } block_type = "action" From fed26ec50310c087c8a7654fb8f7a025ff10973e Mon Sep 17 00:00:00 2001 From: Chloe Date: Thu, 9 Apr 2026 12:13:39 -0400 Subject: [PATCH 2/4] Refactor expression evaluation to use FKEvalResult for success handling; update scene paths for consistency. --- addons/flowkit/editor/editor_globals.gd | 4 +- .../flowkit/runtime/expression_evaluator.gd | 63 +++++++++---------- addons/flowkit/runtime/fk_eval_result.gd | 19 ++++++ 3 files changed, 52 insertions(+), 34 deletions(-) create mode 100644 addons/flowkit/runtime/fk_eval_result.gd diff --git a/addons/flowkit/editor/editor_globals.gd b/addons/flowkit/editor/editor_globals.gd index 5f4332e..e589380 100644 --- a/addons/flowkit/editor/editor_globals.gd +++ b/addons/flowkit/editor/editor_globals.gd @@ -3,8 +3,8 @@ class_name FKEditorGlobals const EVENT_ROW_SCENE := preload("res://addons/flowkit/ui/workspace/event_row_ui.tscn") const COMMENT_SCENE := preload("res://addons/flowkit/ui/workspace/comment_ui.tscn") -const CONDITION_ITEM_SCENE := preload("res://addons/flowkit/ui/workspace/condition_item_ui.tscn") +const CONDITION_ITEM_SCENE := preload("res://addons/flowkit/ui/workspace/condition_unit_ui.tscn") const ACTION_ITEM_SCENE := preload("res://addons/flowkit/ui/workspace/action_unit_ui.tscn") -const BRANCH_ITEM_SCENE_PATH := "res://addons/flowkit/ui/workspace/branch_item_ui.tscn" +const BRANCH_ITEM_SCENE_PATH := "res://addons/flowkit/ui/workspace/branch_unit_ui.tscn" const BRANCH_ITEM_SCENE := preload(BRANCH_ITEM_SCENE_PATH) diff --git a/addons/flowkit/runtime/expression_evaluator.gd b/addons/flowkit/runtime/expression_evaluator.gd index 06d3b8c..57c58f9 100644 --- a/addons/flowkit/runtime/expression_evaluator.gd +++ b/addons/flowkit/runtime/expression_evaluator.gd @@ -38,20 +38,20 @@ static func evaluate(expr_str: String, context_node: Node = null, scene_root: No var var_name = expr_str.substr(2) # Only treat as standalone n_ variable if it's a simple identifier (no operators/spaces) if var_name.is_valid_identifier(): - var result = _resolve_n_variable(n_var_node, var_name) - if result[0]: # Variable was found - return _check_type(result[1], expected_type, expr_str) + var result := _resolve_n_variable(n_var_node, var_name) + if result.success: + return _check_type(result.value, expected_type, expr_str) # Variable not found - still try as expression below # Try to parse as a literal value first - var literal_result = _try_parse_literal(expr_str) - if literal_result[0]: - return _check_type(literal_result[1], expected_type, expr_str) + var literal_result := _try_parse_literal(expr_str) + if literal_result.success: + return _check_type(literal_result.value, expected_type, expr_str) # If not a literal, try to evaluate as a GDScript expression - var expr_result = _evaluate_expression(expr_str, context_node, scene_root, target_node) - if expr_result[0]: - return _check_type(expr_result[1], expected_type, expr_str) + var expr_result := _evaluate_expression(expr_str, context_node, scene_root, target_node) + if expr_result.success: + return _check_type(expr_result.value, expected_type, expr_str) # Evaluation failed - do not fall back to raw string push_error("FlowKit: Failed to evaluate expression: '%s'" % expr_str) @@ -60,14 +60,13 @@ static func evaluate(expr_str: String, context_node: Node = null, scene_root: No ## Resolve an n_ variable from a node. Checks FlowKitSystem node variables first, ## then node metadata (inspector-defined), then script properties. -## Returns a 2-element array [found: bool, value: Variant] to distinguish -## 'not found' from 'found with value null'. -static func _resolve_n_variable(node: Node, var_name: String) -> Array: +## Returns an FKEvalResult to distinguish 'not found' from 'found with value null'. +static func _resolve_n_variable(node: Node, var_name: String) -> FKEvalResult: var system = node.get_tree().root.get_node_or_null("/root/FlowKitSystem") if system and system.has_method("get_node_var"): # Check if the variable exists before getting it if system.has_method("has_node_var") and system.has_node_var(node, var_name): - return [true, system.get_node_var(node, var_name, null)] + return FKEvalResult.succeeded(system.get_node_var(node, var_name, null)) # Fallback: check node metadata directly (inspector-defined FlowKit variables) if node.has_meta("flowkit_variables"): @@ -89,48 +88,48 @@ static func _resolve_n_variable(node: Node, var_name: String) -> Array: "bool": if value is String: value = value.to_lower() == "true" - return [true, value] + return FKEvalResult.succeeded(value) # Fallback: check if the node itself has this property (script-exported variables) if var_name in node: - return [true, node.get(var_name)] + return FKEvalResult.succeeded(node.get(var_name)) - return [false, null] + return FKEvalResult.failed() ## Try to parse the string as a literal value (not an expression) -## Returns [found: bool, value: Variant] to distinguish 'not a literal' from a literal null -static func _try_parse_literal(expr: String) -> Array: +## Returns an FKEvalResult to distinguish 'not a literal' from a literal null +static func _try_parse_literal(expr: String) -> FKEvalResult: # Boolean literals if expr.to_lower() == "true": - return [true, true] + return FKEvalResult.succeeded(true) if expr.to_lower() == "false": - return [true, false] + return FKEvalResult.succeeded(false) # Null literal if expr.to_lower() == "null": - return [true, null] + return FKEvalResult.succeeded(null) # String literals (quoted) if _is_quoted_string(expr): - return [true, _parse_quoted_string(expr)] + return FKEvalResult.succeeded(_parse_quoted_string(expr)) # Numeric literals if _is_numeric(expr): if "." in expr or "e" in expr.to_lower(): - return [true, float(expr)] + return FKEvalResult.succeeded(float(expr)) else: - return [true, int(expr)] + return FKEvalResult.succeeded(int(expr)) # Vector/Color literals (e.g., "Vector2(0,0)", "Color(1,0,0,1)") if _is_constructor_literal(expr): - var result = _evaluate_expression(expr, null) - if result[0]: + var result := _evaluate_expression(expr, null) + if result.success: return result - return [false, null] + return FKEvalResult.failed() # Not a literal - return [false, null] + return FKEvalResult.failed() ## Check if string is a quoted string literal @@ -218,7 +217,7 @@ static func _is_constructor_literal(expr: String) -> bool: ## context_node: used as the base instance for Expression.execute() (where get_node() resolves from) ## scene_root: optional scene root node, exposed as 'scene_root' in expressions ## target_node: optional action target node, exposed as 'node' in expressions (falls back to context_node) -static func _evaluate_expression(expr_str: String, context_node: Node, scene_root: Node = null, target_node: Node = null) -> Array: +static func _evaluate_expression(expr_str: String, context_node: Node, scene_root: Node = null, target_node: Node = null) -> FKEvalResult: var expression = Expression.new() # Build input variables for the expression @@ -317,16 +316,16 @@ static func _evaluate_expression(expr_str: String, context_node: Node, scene_roo var parse_error = expression.parse(expr_str, input_names) if parse_error != OK: # Silently fail - not an expression - return [false, null] + return FKEvalResult.failed() # Execute it var result = expression.execute(input_values, context_node, false) if expression.has_execute_failed(): # Silently fail - expression execution failed - return [false, null] + return FKEvalResult.failed() - return [true, result] + return FKEvalResult.succeeded(result) ## Convenience method to evaluate all inputs in a dictionary diff --git a/addons/flowkit/runtime/fk_eval_result.gd b/addons/flowkit/runtime/fk_eval_result.gd new file mode 100644 index 0000000..4b7fa31 --- /dev/null +++ b/addons/flowkit/runtime/fk_eval_result.gd @@ -0,0 +1,19 @@ +extends RefCounted +class_name FKEvalResult + +## Result of an expression evaluation attempt. +## Wraps a success flag and the resulting value to distinguish +## 'evaluation failed' from 'evaluated to null'. + +var success: bool +var value: Variant + +func _init(p_success: bool = false, p_value: Variant = null) -> void: + success = p_success + value = p_value + +static func succeeded(p_value: Variant) -> FKEvalResult: + return FKEvalResult.new(true, p_value) + +static func failed() -> FKEvalResult: + return FKEvalResult.new(false, null) From 99c779dfbe43226623da9d859a0ddd24b512553f Mon Sep 17 00:00:00 2001 From: Chloe Date: Thu, 9 Apr 2026 12:29:36 -0400 Subject: [PATCH 3/4] Add branch and condition item UI scenes; introduce FKEvalResult UID --- addons/flowkit/editor/editor_globals.gd | 2 +- addons/flowkit/runtime/fk_eval_result.gd.uid | 1 + .../ui/workspace/{branch_item_ui.tscn => branch_unit_ui.tscn} | 0 .../{condition_item_ui.tscn => condition_unit_ui.tscn} | 0 4 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 addons/flowkit/runtime/fk_eval_result.gd.uid rename addons/flowkit/ui/workspace/{branch_item_ui.tscn => branch_unit_ui.tscn} (100%) rename addons/flowkit/ui/workspace/{condition_item_ui.tscn => condition_unit_ui.tscn} (100%) diff --git a/addons/flowkit/editor/editor_globals.gd b/addons/flowkit/editor/editor_globals.gd index e589380..cc661e2 100644 --- a/addons/flowkit/editor/editor_globals.gd +++ b/addons/flowkit/editor/editor_globals.gd @@ -7,4 +7,4 @@ const CONDITION_ITEM_SCENE := preload("res://addons/flowkit/ui/workspace/conditi const ACTION_ITEM_SCENE := preload("res://addons/flowkit/ui/workspace/action_unit_ui.tscn") const BRANCH_ITEM_SCENE_PATH := "res://addons/flowkit/ui/workspace/branch_unit_ui.tscn" -const BRANCH_ITEM_SCENE := preload(BRANCH_ITEM_SCENE_PATH) +const BRANCH_ITEM_SCENE := preload(BRANCH_ITEM_SCENE_PATH) \ No newline at end of file diff --git a/addons/flowkit/runtime/fk_eval_result.gd.uid b/addons/flowkit/runtime/fk_eval_result.gd.uid new file mode 100644 index 0000000..b7241e9 --- /dev/null +++ b/addons/flowkit/runtime/fk_eval_result.gd.uid @@ -0,0 +1 @@ +uid://cftd1n7sl41wv diff --git a/addons/flowkit/ui/workspace/branch_item_ui.tscn b/addons/flowkit/ui/workspace/branch_unit_ui.tscn similarity index 100% rename from addons/flowkit/ui/workspace/branch_item_ui.tscn rename to addons/flowkit/ui/workspace/branch_unit_ui.tscn diff --git a/addons/flowkit/ui/workspace/condition_item_ui.tscn b/addons/flowkit/ui/workspace/condition_unit_ui.tscn similarity index 100% rename from addons/flowkit/ui/workspace/condition_item_ui.tscn rename to addons/flowkit/ui/workspace/condition_unit_ui.tscn From c80fb8d7667a36b1d370945bdbb3c79bd61c257f Mon Sep 17 00:00:00 2001 From: CG-Tespy Date: Sat, 11 Apr 2026 14:14:13 -0400 Subject: [PATCH 4/4] Added string field to FKEvalResult | Small reorganization in expression_evaluator (#86) --- addons/flowkit/runtime/expression_evaluator.gd | 12 ++++++++---- addons/flowkit/runtime/fk_eval_result.gd | 15 ++++++++++----- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/addons/flowkit/runtime/expression_evaluator.gd b/addons/flowkit/runtime/expression_evaluator.gd index 57c58f9..2775dd6 100644 --- a/addons/flowkit/runtime/expression_evaluator.gd +++ b/addons/flowkit/runtime/expression_evaluator.gd @@ -23,7 +23,8 @@ class_name FKExpressionEvaluator ## scene_root: optional scene root node, exposed as 'scene_root' variable in expressions ## target_node: optional action target node, used for n_ variable lookups (falls back to context_node) ## expected_type: optional Variant.Type to validate the result against (-1 = no validation) -static func evaluate(expr_str: String, context_node: Node = null, scene_root: Node = null, target_node: Node = null, expected_type: int = -1) -> Variant: +static func evaluate(expr_str: String, context_node: Node = null, scene_root: Node = null, \ +target_node: Node = null, expected_type: int = -1) -> Variant: if expr_str.is_empty(): return _check_type("", expected_type, expr_str) @@ -204,7 +205,8 @@ static func _is_numeric(expr: String) -> bool: ## Check if string is a constructor literal like "Vector2(0,0)" or "Color(1,0,0)" static func _is_constructor_literal(expr: String) -> bool: - var constructors = ["Vector2", "Vector3", "Vector4", "Color", "Rect2", "Transform2D", "Plane", "Quaternion", "AABB", "Basis", "Transform3D"] + var constructors = ["Vector2", "Vector3", "Vector4", "Color", "Rect2", + "Transform2D", "Plane", "Quaternion", "AABB", "Basis", "Transform3D"] for constructor in constructors: if expr.begins_with(constructor + "(") and expr.ends_with(")"): @@ -217,7 +219,8 @@ static func _is_constructor_literal(expr: String) -> bool: ## context_node: used as the base instance for Expression.execute() (where get_node() resolves from) ## scene_root: optional scene root node, exposed as 'scene_root' in expressions ## target_node: optional action target node, exposed as 'node' in expressions (falls back to context_node) -static func _evaluate_expression(expr_str: String, context_node: Node, scene_root: Node = null, target_node: Node = null) -> FKEvalResult: +static func _evaluate_expression(expr_str: String, context_node: Node, scene_root: Node = null, \ +target_node: Node = null) -> FKEvalResult: var expression = Expression.new() # Build input variables for the expression @@ -334,7 +337,8 @@ static func _evaluate_expression(expr_str: String, context_node: Node, scene_roo ## scene_root: optional scene root node, forwarded to evaluate() ## target_node: optional action target node for n_ variable lookups ## type_hints: optional dictionary mapping input names to Variant.Type int values for post-evaluation validation -static func evaluate_inputs(inputs: Dictionary, context_node: Node = null, scene_root: Node = null, target_node: Node = null, type_hints: Dictionary = {}) -> Dictionary: +static func evaluate_inputs(inputs: Dictionary, context_node: Node = null, \ +scene_root: Node = null, target_node: Node = null, type_hints: Dictionary = {}) -> Dictionary: var evaluated: Dictionary = {} for key in inputs.keys(): diff --git a/addons/flowkit/runtime/fk_eval_result.gd b/addons/flowkit/runtime/fk_eval_result.gd index 4b7fa31..48e90b7 100644 --- a/addons/flowkit/runtime/fk_eval_result.gd +++ b/addons/flowkit/runtime/fk_eval_result.gd @@ -8,12 +8,17 @@ class_name FKEvalResult var success: bool var value: Variant -func _init(p_success: bool = false, p_value: Variant = null) -> void: +## Good for providing further details about the evaluation. +var message := "" + + +func _init(p_success: bool = false, p_value: Variant = null, p_message = "") -> void: success = p_success value = p_value + message = p_message -static func succeeded(p_value: Variant) -> FKEvalResult: - return FKEvalResult.new(true, p_value) +static func succeeded(p_value: Variant, p_message = "") -> FKEvalResult: + return FKEvalResult.new(true, p_value, p_message) -static func failed() -> FKEvalResult: - return FKEvalResult.new(false, null) +static func failed(p_message = "") -> FKEvalResult: + return FKEvalResult.new(false, null, p_message)