Class ControlFlowChainGenerator

java.lang.Object
org.ek9lang.compiler.phase7.generator.AbstractGenerator
org.ek9lang.compiler.phase7.generator.ControlFlowChainGenerator
All Implemented Interfaces:
Function<ControlFlowChainDetails, List<IRInstr>>

public final class ControlFlowChainGenerator extends AbstractGenerator implements Function<ControlFlowChainDetails, List<IRInstr>>
Unified IR generator for EK9 control flow operators using CONTROL_FLOW_CHAIN.

Supported Operators:

  • Question operator (?) - null/set checking
  • Assign-if-unset (:=?) - conditional assignment
  • Null coalescing operator (??) - returns LHS if allocated, else RHS
  • Elvis coalescing operator (:?) - returns LHS if allocated AND set, else RHS
  • Comparison coalescing operators (<?, >?, <=?, >=?) - safe comparison with null/unset handling

Key architectural benefits:
- Single source of truth for operator control flow logic
- Consistent memory management patterns across all operators
- Unified optimization metadata for backend code generation
- Reduced code duplication and maintenance burden

NOTE: If/else, switch, while, do-while, for, and try/catch statements have their own dedicated IR generators and are NOT generated by this class.

  • Constructor Details

  • Method Details

    • apply

      public List<IRInstr> apply(ControlFlowChainDetails details)
      Specified by:
      apply in interface Function<ControlFlowChainDetails, List<IRInstr>>
    • generateQuestionOperator

      public List<IRInstr> generateQuestionOperator(ExprProcessingDetails exprDetails)
      Generate SWITCH_CHAIN_BLOCK for Question operator (?). Converts "operand?" to a two-case chain: null check + _isSet() default. Question operator is self-contained - no RETAIN/SCOPE_REGISTER needed on input.
    • generateQuestionOperatorForVariable

      public List<IRInstr> generateQuestionOperatorForVariable(ISymbol variableSymbol, String resultVariable, DebugInfo debugInfo)
      Generate SWITCH_CHAIN_BLOCK for Question operator applied to a variable. Used by assign-if-unset composition.
    • generateQuestionOperatorForTemp

      public List<IRInstr> generateQuestionOperatorForTemp(String variableName, ISymbol variableSymbol, boolean isTraitCall, String resultVariable, DebugInfo debugInfo)
      Generate SWITCH_CHAIN_BLOCK for Question operator applied to a temp variable. Used when the variable is a temp holding an expression result, not a named symbol.
      Parameters:
      variableName - The IR variable name (temp variable)
      variableSymbol - The symbol whose type both names the target and supplies the _isSet metadata
      isTraitCall - Whether to use INVOKEINTERFACE vs INVOKEVIRTUAL
      resultVariable - The variable to store the Boolean result into
      debugInfo - Debug information
      Returns:
      IR instructions implementing the question operator
    • generateAssignIfUnsetTestForTemp

      public List<IRInstr> generateAssignIfUnsetTestForTemp(String variableName, ISymbol variableSymbol, boolean isTraitCall, String resultVariable, DebugInfo debugInfo)
      The question operator on a temp that holds the current value of a :=? target: a null there is the never-written slot :=? exists to write, so it is NOT a strict-absent observation (StrictAbsent).
    • generateAssignIfUnset

      public List<IRInstr> generateAssignIfUnset(ISymbol lhsSymbol, List<IRInstr> assignmentEvaluation, String assignmentResult, DebugInfo debugInfo)
      Generate SWITCH_CHAIN_BLOCK for assign-if-unset (:=?). Composes question operator logic: assign only if LHS is null OR !LHS._isSet().
    • generateExternalFieldAssignIfUnset

      public List<IRInstr> generateExternalFieldAssignIfUnset(String isSetBoolean, List<IRInstr> assignmentBody, DebugInfo debugInfo)
      Generate CONTROL_FLOW_CHAIN for assign-if-unset on external field.

      Inverts the isSetBoolean (assign when NOT set) and wraps the assignment body in a ASSIGN_IF_UNSET control flow chain.

      Parameters:
      isSetBoolean - The Boolean variable holding the isSet result of the field's current value
      assignmentBody - The IR instructions to execute if field is unset (evaluate RHS + STORE_FIELD)
      debugInfo - Debug information
      Returns:
      IR instructions implementing the conditional field assignment
    • generateNullCoalescing

      public List<IRInstr> generateNullCoalescing(ExprProcessingDetails lhsDetails, ExprProcessingDetails rhsDetails)
      Generate CONTROL_FLOW_CHAIN for Null Coalescing operator (??). Returns LHS if memory allocated (even if unset), else evaluates and returns RHS.

      IR Structure: 1 case + default
      Case 1: NULL_CHECK - if LHS is null → evaluate and return RHS
      Default: LHS not null → return LHS (even if unset)

    • generateElvisCoalescing

      public List<IRInstr> generateElvisCoalescing(ExprProcessingDetails lhsDetails, ExprProcessingDetails rhsDetails)
      Generate CONTROL_FLOW_CHAIN for Elvis Coalescing operator (:?). Returns LHS if memory allocated AND set, else evaluates and returns RHS.

      IR Structure: 1 case + default
      Case: EXPRESSION - if LHS is absent OR not set → evaluate and return RHS
      Default: LHS is set → return LHS

      The case's condition is the null-safe ? of the LHS (the QUESTION_OPERATOR chain: absent is not set, and _isSet() is only called on a value that is present), inverted. So the RHS is needed in ONE case and its code is generated ONCE. It used to be returned by two cases - "absent" and "not set" - and generated for each, so a nested chain of n doubled n times (triage B13 follow-up; the shared-list alternative put the same labels at two bytecode positions and corrupted the stack map). It is still evaluated only when needed.

    • generateComparisonCoalescing

      public List<IRInstr> generateComparisonCoalescing(ExprProcessingDetails lhsDetails, ExprProcessingDetails rhsDetails, String comparisonOperator, String chainType)
      Generate CONTROL_FLOW_CHAIN for Comparison Coalescing operators (<?, >?, <=?, >=?). Returns LHS if both operands valid and comparison true, else gracefully handles null/unset.

      IR Structure: 3 cases + default - LHS then RHS evaluated once, before any case
      Case 1: LHS absent or not set (the null-safe ?, inverted) → return RHS
      Case 2: RHS absent or not set → return LHS
      Case 3: COMPARISON — derived from <=>: (LHS <=> RHS) <op> 0 → if true, return LHS
      Default: return RHS

      The RHS is generated ONCE. It was generated for each case that needed it - three times - so a nested chain grew as 3^n (the shared-list alternative put the same labels at two bytecode positions and corrupted the stack map). Every path evaluates it exactly once, so evaluating it up front changes nothing observable.

      Parameters:
      comparisonOperator - The relational method name applied to the <=> Integer result vs 0 (e.g., "_lt", "_gt", "_lteq", "_gteq")
      chainType - The chain type for IR (e.g., "LESS_THAN_COALESCING_OPERATOR")
    • generateTernary

      public List<IRInstr> generateTernary(ExprProcessingDetails details)
      Generate IR for ternary operator: condition <- thenValue : elseValue.

      Pattern: CONTROL_FLOW_CHAIN with TERNARY_OPERATOR chain type - Condition evaluation in tight scope - True branch evaluates thenValue - False branch (default) evaluates elseValue

      Parameters:
      details - ExprProcessingDetails containing result variable and ternary expression context
      Returns:
      List of IR instructions for ternary operator