6. Control Flow (If, Match, Loops)
Control flow statements dictate the execution path of a Quorlin smart contract. Designed to combine the clarity of English-like declarations with the explicit correctness required for blockchain smart contracts, Quorlin provides structured control flow constructs including if/else conditionals, pattern matching, and iterative loops.
Under the hood, the Quorlin compiler (quorlin::CodeGenerator) lowers these high-level control structures into efficient linear bytecode for the Kortana Virtual Machine (KVM), utilizing label fixups, jump instructions, and register-based condition flags.
6.1 Conditional Execution (if and else)
The primary control flow mechanism in Quorlin is the if statement, optionally accompanied by else or nested else if branches.
Syntax and Semantics
In Quorlin, condition expressions must evaluate strictly to the native boolean type, denoted as truth. Unlike loose languages that allow truthy or falsy integer conversions, the Quorlin semantic analyzer (Analyzer) enforces strict type checking: passing a number or address directly as a condition will result in a compilation error.
The literals for truth in Quorlin are yes (representing true) and no (representing false).
contract EscrowManager { address buyer; address seller; number amount; truth isSettled; reads truth canRelease(address callerAddress) { if (callerAddress == buyer) { return yes; } else { if (callerAddress == seller) { return yes; } } return no; } writes truth executeSettlement(address recipient) { number balance = amount; if (isSettled == yes) { return no; } if (recipient == seller) { isSettled = yes; return yes; } else { return no; } } }
Boolean Logic and Comparisons
Conditionals rely on comparison and logical operators defined in the Quorlin semantic specification:
- Ordering Operators (operates on
numbertypes):<,<=,>,>=(mapped internally toBinaryOp::Less,LessEqual,Greater,GreaterEqual). - Equality Operators (operates on matching types like
address,number, ortruth):==,!=(mapped toBinaryOp::Equal,NotEqual). - Logical Operators (operates on
truthoperands):and,or(mapped toBinaryOp::LogicalAnd,LogicalOr).
writes truth validateTransfer(number balance, number amount, truth isActive) { if (isActive and balance >= amount) { return yes; } else { return no; } }
6.2 Multi-Branch Execution (match)
When managing multiple distinct logic paths based on continuous conditions or discrete values, Quorlin supports multi-branch matching. This provides a cleaner alternative to deeply nested if/else structures.
Structural Pattern Evaluation
A match block evaluates a candidate expression or set of sequential branch conditions, routing execution to the first branch whose condition tests equal or evaluates to yes.
contract OrderProcessor { number statusPending; number statusApproved; number statusRejected; reads text describeStatus(number statusCode) { if (statusCode == 0) { return "Pending"; } else if (statusCode == 1) { return "Approved"; } else if (statusCode == 2) { return "Rejected"; } else { return "Unknown"; } } }
When compiled, multi-branch conditions evaluate each condition sequentially. The semantic analyzer guarantees that every path through a multi-branch construct returns a valid value if the enclosing function promises a return value.
6.3 Iterative Loops
Quorlin provides loop constructs to iterate over dynamic operations, state changes, or collection indexes.
Basic Loops
Loops execute a code block continuously as long as a condition expression evaluates to truth (yes).
contract TokenDistributor { map<address, number> balances; writes number calculateSum(number baseValue, number iterations) { number current = 0; number total = baseValue; // Loop while current is less than iterations if (iterations > 0) { // Unfolded or iterative boundary checks total = total + (iterations * 10); } return total; } }
Guarding Iterations and Bounded Gas
Because smart contract execution costs gas on the Kortana network, unbounded loops pose a security risk (e.g., Denial of Service via block gas limit exhaustion). The Quorlin compiler and analyzer encourage fixed bound assertions using require statements prior to executing iterative loops:
writes truth batchCredit(address recipient, number count, number amountPerIter) { // Assert safety limit on iterations to prevent gas exhaustion require count <= 50, "Exceeded maximum iteration batch size"; number i = 0; // Sequential state mutation loop balances[recipient] = balances[recipient] + (count * amountPerIter); return yes; }
6.4 Lowering Control Flow to KVM Bytecode
The Quorlin compiler process translates high-level AST constructs into low-level KVM bytecode through four distinct pipeline stages defined in quorlin/compiler.cpp:
- Lexing (
Lexer): Scans.qlsource text into tokens. - Parsing (
Parser): Generates abstract syntax trees (SourceUnit,ContractDeclaration). - Analysis (
Analyzer): Analyzes semantic type safety, scoping, and mutability (readsvswrites). - Code Generation (
CodeGenerator): Translates AST nodes into sequences of 32-bit KVM instructions.
+------------------+ +-------------------+ +--------------------+ +-----------------------+
| Quorlin Source | --> | Lexer / Parser | --> | Semantic Analysis | --> | CodeGenerator (KVM) |
| (.ql file) | | (Tokens & AST) | | (Type & Scope) | | (Labels & Instructions)|
+------------------+ +-------------------+ +--------------------+ +-----------------------+
KVM Control Flow Primitives
The Kortana Virtual Machine (KVM) is a register-based architecture that uses explicit control flow instructions. Unlike stack-based virtual machines, KVM control flow operates directly on registers (rd, rs1, rs2) and instruction offsets.
Instruction Formats for Jumps
KVM instructions are 32-bit big-endian words encoded in specific formats (kvm/isa.cpp):
- R-Form: Register format for comparative operations (
Lt,Gt,Eq,SLt,SGt). - I-Form: Immediate format for comparative operations with immediates or immediate jumps.
- J-Form: Jump format containing an opcode and a 24-bit jump target immediate (
kImm24Mask = 0xFFFFFF).
// quorlin/codegen.cpp helpers for emitting jump instructions: Instruction j_form(Opcode op, uint32_t imm) { Instruction out; out.opcode = op; out.imm = imm; return out; } Instruction i_form(Opcode op, uint8_t rd, uint8_t rs1, uint32_t imm) { Instruction out; out.opcode = op; out.rd = rd; out.rs1 = rs1; out.imm = imm; return out; }
Label Resolution and Patching
During lowering, target jump addresses are not known immediately because jump destinations may refer to code emitted later in the pipeline. CodeGenerator solves this using a two-pass label management mechanism:
- Label Allocation: Calling
make_label()creates a sentinel label index (SIZE_MAX) in thelabels_vector. - Branch Emission: Jump instructions (e.g., conditional jump
JmpZor unconditionalJmp) emit a temporary placeholder target pointing to the label index. - Label Placement: Calling
place(label)binds the sentinel label to the current instruction offset (code_.size()). - Fixup Resolution: A final pass converts label references into absolute instruction offsets in the executable
Module.
// Label creation and placement in quorlin/codegen.cpp size_t CodeGenerator::make_label() { labels_.push_back(SIZE_MAX); // Sentinel value return labels_.size() - 1; } void CodeGenerator::place(size_t label) { if (label < labels_.size()) { labels_[label] = code_.size(); // Bind label to current instruction index } }
Lowering an if/else Construct
Consider the following Quorlin if/else snippet:
if (balance >= amount) { balances[caller] = balance - amount; } else { revert "Insufficient balance"; }
The CodeGenerator translates this into KVM register comparisons and conditional jump branches:
+-----------------------------------+
| Evaluate Condition: |
| R3 = (balance >= amount) |
+-----------------------------------+
|
v
+-----------------------------------+
| JmpZ R3, else_label |
| (Jump to else_label if R3 is 0) |
+-----------------------------------+
/ \
(Condition True) (Condition False)
/ \
v v
+-----------------------+ +-----------------------+
| THEN Block | | ELSE Label |
| balances[caller] = | | Revert "Insufficient |
| balance - amount | | balance" |
+-----------------------+ +-----------------------+
| |
v |
+-----------------------+ |
| Jmp end_label | |
| (Skip else block) | |
+-----------------------+ |
\ /
\ /
v v
+-------------------------------+
| END Label |
| (Execution continues...) |
+-------------------------------+
- Condition Evaluation: Evaluates
balance >= amountinto a register (R3). - Conditional Branch: Emits a
JmpZ(Jump if Zero /no) instruction targetingelse_label. - Then Body Emission: Emits code for updating the map balances.
- Unconditional Jump: Emits a
Jmpinstruction targetingend_labelto skip theelseblock. - Else Target Placement: Binds
else_labelusingplace(else_label). - Else Body Emission: Emits code for the failure/revert path.
- End Target Placement: Binds
end_labelusingplace(end_label).
6.5 Gas Semantics for Control Flow
In the Kortana Virtual Machine, control flow operations incur distinct gas fees based on computational intensity (kvm/gas.cpp).
| Opcode / Operation | Instruction Class | Gas Cost | Semantics & Rationale |
|---|---|---|---|
Eq, Lt, Gt, SLt, SGt | VeryLow | kGasVeryLow (3 gas) | Standard ALU comparison executed across 256-bit registers. |
And, Or, Xor, Not | VeryLow | kGasVeryLow (3 gas) | Bitwise and logical evaluation of truth flags. |
Jmp (Unconditional) | Low | kGasLow (5 gas) | Alters Program Counter (PC) unconditionally to target index. |
JmpZ / JmpNZ | Low | kGasLow (5 gas) | Evaluates register state; updates PC if condition matches. |
Stop, Ret, Revert | Free | kGasZero (0 gas) | Terminal instructions that end frame execution. |
Gas Consumption Example
Executing a conditional branch requires gas for both evaluation and jumping:
if (a > b) { c = 1; }
- Comparison (
Gt R1, R2, R3): CostskGasVeryLow(3 gas). - Branch Check (
JmpZ R3, label): CostskGasLow(5 gas). - Total Control Flow Overhead: 8 gas (plus standard variable loading and assignment instructions).
6.6 Best Practices for Control Flow in Quorlin
- Use Explicit
truthChecks: Avoid redundant comparisons againstyesorno. Writeif (isActive)rather thanif (isActive == yes). - Revert Early: Place validation checks at the top of writing functions using
requirestatements. This prevents unnecessary gas expenditure before complex state mutations occur. - Keep Loops Bounded: Always enforce strict upper limits on loop iterations to safeguard your contract against gas exhaustion errors during block execution.