33. Tooling and CLI
The Quorlin Smart Contract Language toolchain and the Kortana Virtual Machine (KVM) execution environment provide a deterministic, highly secure pipeline for transforming English-like, Java-style smart contracts into verified binary modules.
This chapter outlines the end-to-end toolchain architecture—tracing how source code written in Quorlin (.ql) passes through lexical analysis, AST parsing, semantic analysis, binary code generation, EVM-compatible ABI emission, and byte-level KVM module packaging.
33.1 Compilation Pipeline Overview
The primary entry point for compiling Quorlin smart contracts is the compile function defined in quorlin/compiler.cpp. The compiler enforces strict phase-gating: every compilation phase acts as a diagnostic gate. If a phase encounters an error, the pipeline halts immediately, preventing malformed or invalid state transitions from ever generating executable bytecode.
+-----------------------+
| Quorlin Source (.ql) |
+-----------------------+
|
v
+-----------------------+
| 1. Lexer Phase |
+-----------------------+
|
v
+-----------------------+
| 2. Parser Phase |
+-----------------------+
|
v
+-----------------------+
| 3. Analyzer (Sema) |
+-----------------------+
|
[Diagnostic Gate]
|
+----------------+----------------+
| |
v v
+----------------------+ +----------------------+
| 4a. Code Generator | | 4b. ABI Generator |
+----------------------+ +----------------------+
| |
v v
+----------------------+ +----------------------+
| KVM Module (.kvm) | | Contract ABI (.json) |
+----------------------+ +----------------------+
The Four Compilation Stages
- Lexical Analysis (
Lexer): Converts raw.qlsource text into a stream of discrete strongly-typedTokenobjects. It processes identifiers, numeric literals, string literals, and keywords (contract,reads,writes,event, etc.). - Syntactic Parsing (
Parser): Processes the token stream into an Abstract Syntax Tree (AST) rooted in aSourceUnit. The parser ensures that top-level constructs conform to contract syntax, constructor declarations, variable definitions, and method signatures. - Semantic Analysis (
Analyzer): Performs type checking, identifier resolution, scope verification, and storage slot layout calculations. The analyzer enforces Quorlin’s strict typing rules and reports semantic violations via theDiagnosticBag. - Code and Metadata Generation (
CodeGenerator&ABI Emitter): If semantic analysis succeeds with zero errors, the compiler splits execution:- The CodeGenerator compiles the typed AST into KVM machine instructions, resolves register assignments and branch labels, and packages the code into a binary
.kvmmodule. - The ABI Emitter produces an Ethereum-compatible JSON ABI description for client software and wallet interoperation.
- The CodeGenerator compiles the typed AST into KVM machine instructions, resolves register assignments and branch labels, and packages the code into a binary
33.2 Quorlin Type System and Syntax Mapping
Quorlin intentionally uses intuitive, readable keywords for its primitive data types. However, when interfacing with standard Web3 tooling and emitting EVM-compatible ABIs, these internal types map directly to standard Ethereum ABI types.
| Quorlin AST Type | Quorlin Syntax Keyword | Ethereum ABI Type Name | KVM Machine Representation |
|---|---|---|---|
Type::U256 | number | uint256 | 256-bit Unsigned Integer |
Type::Bool | truth | bool | 256-bit Word (0 or 1) |
Type::Address | address | address | 160-bit Value (20-byte word) |
Type::Text | text | string | Dynamic Byte Array |
Type::Void | nothing | void | Return size 0 |
In addition to primitive types, Quorlin supports key-value mapping structures defined as map<KeyType, ValueType> and user-defined struct definitions initialized with the record keyword.
Language Keywords and Literal Representation
Quorlin maps standard boolean logic to natural vocabulary:
- Boolean literals:
yes(true) andno(false). - Context environment variable:
caller(representsmsg.sender). - Function Mutability:
reads: Read-only storage access (EVMview).writes: Storage-mutating operation (EVM non-payable state modification).
contract TokenVault { number totalDeposited; map<address, number> balances; event Deposit(address indexed account, number amount); event Withdraw(address indexed account, number amount); constructor { totalDeposited = 0; } reads number getBalance(address account) { return balances[account]; } writes truth deposit(number amount) { require amount > 0, "Deposit must be greater than zero"; balances[caller] = balances[caller] + amount; totalDeposited = totalDeposited + amount; emit Deposit(caller, amount); return yes; } writes truth withdraw(number amount) { number userBalance = balances[caller]; require userBalance >= amount, "Insufficient funds"; balances[caller] = userBalance - amount; totalDeposited = totalDeposited - amount; emit Withdraw(caller, amount); return yes; } }
33.3 ABI Generation Specification
The ABI emitter in quorlin/abi.cpp formats contract interfaces into valid JSON arrays. The ABI generation ensures seamless integration with external wallets, front-end dApps, and cross-chain execution engines.
Parameter JSON Construction
Each argument in a method, event, or constructor is emitted as a JSON object with three core metadata keys:
name: Parameter identifier string.type: Standard Ethereum ABI type name (uint256,bool,address,string).internalType: Quorlin-native syntax type name (number,truth,address,text).indexed(events only): Boolean flag indicating if the field constitutes an EVM log topic filter.
Generated ABI JSON Output Example
For the TokenVault contract defined above, the Quorlin compiler outputs the following ABI JSON structure:
[ { "type": "constructor", "inputs": [] }, { "type": "event", "name": "Deposit", "inputs": [ { "name": "account", "type": "address", "internalType": "address", "indexed": true }, { "name": "amount", "type": "uint256", "internalType": "number", "indexed": false } ] }, { "type": "function", "name": "getBalance", "stateMutability": "view", "inputs": [ { "name": "account", "type": "address", "internalType": "address" } ], "outputs": [ { "name": "", "type": "uint256", "internalType": "number" } ] }, { "type": "function", "name": "deposit", "stateMutability": "nonpayable", "inputs": [ { "name": "amount", "type": "uint256", "internalType": "number" } ], "outputs": [ { "name": "", "type": "bool", "internalType": "truth" } ] } ]
33.4 The KVM Module Binary Format (.kvm)
When compiling a Quorlin contract, the code generator (quorlin/codegen.cpp) and module packer (kvm/module.cpp) produce a binary file conforming to the KVM Bytecode Specification. All multi-byte integer fields in the header, constant table, and instruction stream are serialized in Big-Endian byte order.
Header Byte Structure
The KVM module header occupies a fixed size of exactly 18 bytes:
+-------------------+-------------------+-------------------+-------------------+
| Offset (Bytes) | Field Name | Data Type | Description |
+-------------------+-------------------+-------------------+-------------------+
| 0x00 - 0x03 | Magic Identifier | uint8_t[4] | Magic "KVM\0" |
| 0x04 - 0x05 | Version | uint16_t (BE) | Module Spec Ver |
| 0x06 - 0x09 | Constant Count | uint32_t (BE) | Number of 32B pool|
| 0x0A - 0x0D | Instruction Count | uint32_t (BE) | Total instructions|
| 0x0E - 0x11 | Entry Point | uint32_t (BE) | Start Instr Index |
+-------------------+-------------------+-------------------+-------------------+
Following the 18-byte fixed header, the binary contains two primary sections:
- Constant Table: An array of 32-byte (256-bit) constants encoded sequentially. Total byte length equals
Constant Count * 32. - Instruction Stream: An array of 32-bit (4-byte) fixed-width KVM instructions. Total byte length equals
Instruction Count * 4.
+-------------------------------------------------------------------------------+
| KVM Binary Module Layout |
+-------------------------------------------------------------------------------+
| [0x00..0x11] Fixed Header (18 Bytes) |
| ├── Magic: 0x4B 0x56 0x4D 0x00 ("KVM\0") |
| ├── Version: uint16 |
| ├── Constant Count: uint32 |
| ├── Instruction Count: uint32 |
| └── Entry Point: uint32 |
+-------------------------------------------------------------------------------+
| Pool Constants Array (Constant Count * 32 Bytes) |
| ├── Constant 0 [32 Bytes] |
| ├── Constant 1 [32 Bytes] |
| └── ... |
+-------------------------------------------------------------------------------+
| Instruction Stream Array (Instruction Count * 4 Bytes) |
| ├── Instruction 0 [4 Bytes / 32-bit Encoded] |
| ├── Instruction 1 [4 Bytes / 32-bit Encoded] |
| └── ... |
+-------------------------------------------------------------------------------+
33.5 Instruction Encoding & ISA Reference
The Kortana Virtual Machine implements a register-based Architecture utilizing 32-bit fixed-length instruction formats (kvm/isa.cpp). Instructions manipulate a 32-register set (r0 to r31), indexed via 5-bit register specifiers.
Instruction Formats
Instructions belong to one of four encoding formats depending on operational operands:
Format R (Register Arithmetic / Bitwise):
+---------------+---------------+---------------+---------------+---------------+
| Opcode (8b) | Rd (5b) | Rs1 (5b) | Rs2 (5b) | Funct (9b) |
+---------------+---------------+---------------+---------------+---------------+
31 24 23 19 18 14 13 9 8 0
Format I (Immediate Arithmetic / Memory Load):
+---------------+---------------+---------------+-------------------------------+
| Opcode (8b) | Rd (5b) | Rs1 (5b) | Immediate (14b) |
+---------------+---------------+---------------+-------------------------------+
31 24 23 19 18 14 13 0
Format J (Unconditional Jump / Branching):
+---------------+---------------------------------------------------------------+
| Opcode (8b) | Immediate (24b) |
+---------------+---------------------------------------------------------------+
31 24 23 0
Format None (Terminal Instructions / System Control):
+---------------+---------------------------------------------------------------+
| Opcode (8b) | Unused (24b) |
+---------------+---------------------------------------------------------------+
31 24 23 0
Bitfield Shift Constants
kOpcodeShift = 24: Opcode resides in bits[31:24].kRdShift = 19: Destination Register (Rd) resides in bits[23:19].kRs1Shift = 14: First Source Register (Rs1) resides in bits[18:14].kRs2Shift = 9: Second Source Register (Rs2) resides in bits[13:9].kRegisterMask = 0x1F: 5-bit register mask (32 general-purpose registers).
33.6 Execution Host & Gas Mechanics
The runtime execution of compiled bytecode is managed by the KVM Interpreter (kvm/interpreter.cpp) backed by a state host (kvm/state_host.cpp) connected to Kortana’s unified state trie.
Gas Schedule Rules
Gas costs are categorized into deterministic baseline tiers (kvm/gas.cpp):
// Gas Tier Accounting Table // kGasZero = 0 gas (Free: Stop, Return, Revert, Invalid) // kGasVeryLow = 3 gas (ALU Ops: Add, Sub, Lt, Gt, Eq, And, Or, Mov, LoadK) // kGasLow = 5 gas (Complex Math: Mul, Div, Mod) // kGasMid = 8 gas (Advanced ALU: AddMod, MulMod) // kGasHigh = 10 gas (Jumps and state reads)
- Free Execution Tier (
0 Gas): Instructions terminating call frames (Stop,Return,Revert,Invalid) incur zero base gas cost to ensure uniform termination pricing regardless of exit strategy. - Very Low Cost Tier (
3 Gas): Basic arithmetic, comparison logic, register moves (Mov), constant loads (LoadK), memory loads (MLoad), and call data reads (CallDataLoad). - Low / Mid Cost Tier (
5–8 Gas): Multiplication (Mul), division (Div), modulo (Mod), and modulo-arithmetic (AddMod,MulMod). - State Trie Access Tier: Dynamic gas pricing depending on slot lifecycle. Writing to an uninitialized storage slot requires substantially higher gas than updating a non-zero slot.
Dynamic Storage Writes
When executing a storage assignment, the KVM interpreter queries StateHost::set_storage. The execution host inspects the previous slot state directly from the unified state trie:
// Extract from kvm/state_host.cpp Result<uint256_t> StateHost::set_storage(const Address& address, const uint256_t& key, const uint256_t& value) { // The previous value is returned so the interpreter can price the write: // filling an empty slot costs several times an overwrite. KORTANA_TRY_ASSIGN(const uint256_t previous, world_.get_storage(address, key)); KORTANA_TRY(world_.put_storage(address, key, value)); return previous; }
33.7 Diagnostic Error Management
During tokenization, parsing, or semantic analysis, errors are captured into a unified DiagnosticBag. If any errors accumulate within this bag, compilation halts prior to code generation.
Compiler Diagnostic Workflow Example
Consider an error where a contract developer attempts to pass an invalid type to a state storage mapping:
contract ErrorExample { map<address, number> accounts; writes truth invalidAssignment() { // Semantic Error: Attempting to assign text string to number slot accounts[caller] = "Not a number"; return yes; } }
When invoking the Quorlin compiler pipeline on this contract:
[ERROR] Line 6, Column 28: Type mismatch in assignment.
Expected type 'number', found type 'text'.
Compilation aborted. 0 bytes written.
Because semantic validation explicitly protects code generation, partial or unverified binaries are never written to disk, guaranteeing that invalid code cannot be deployed onto the Kortana blockchain network.