|
| 1 | +# Native Functions Integration |
| 2 | + |
| 3 | +This document describes how to integrate custom Python logic into generated Rune code using **Native Functions**. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +Native functions allow developers to provide custom Python implementations for Rune functions that either cannot be expressed in the Rosetta DSL or require optimized low-level logic. |
| 8 | + |
| 9 | +**Audience**: Rune developers and Python engineers. |
| 10 | + |
| 11 | +**Contents**: |
| 12 | + |
| 13 | +- Definitions of native functions. |
| 14 | +- Mechanism of registration and execution. |
| 15 | +- Implementation guidelines and troubleshooting. |
| 16 | +- Comprehensive end-to-end example. |
| 17 | + |
| 18 | +--- |
| 19 | + |
| 20 | +## 1. What are Native Functions |
| 21 | + |
| 22 | +A function is treated as "Native" by the Python generator if: |
| 23 | + |
| 24 | +- It is explicitly annotated with `[codeImplementation]`. (**Recommended**) |
| 25 | +- It has no operations (assignment or `add` statements) in its body. |
| 26 | + |
| 27 | +### Exclusions |
| 28 | + |
| 29 | +**Enum Dispatchers** (functions that act as switchboards) are excluded from the implicit "no operations" rule. Dispatcher headers are always generated as `match` statements in Python and will not be treated as native unless explicitly annotated with `[codeImplementation]`. |
| 30 | + |
| 31 | +--- |
| 32 | + |
| 33 | +## 2. Integration Mechanism |
| 34 | + |
| 35 | +### Registration and Discovery |
| 36 | + |
| 37 | +The generated code includes a call to `rune_attempt_register_native_functions` during module initialization. This mechanism: |
| 38 | + |
| 39 | +1. Identifies all functions flagged as native during generation. |
| 40 | +2. Attempts to dynamically import these functions from a specific package prefix (default: `rune.native`). |
| 41 | +3. Registers successfully imported callables in a global registry. |
| 42 | + |
| 43 | +### The Runtime Hook |
| 44 | + |
| 45 | +At runtime, the generated code uses `rune_execute_native`. If a function is called but no implementation was successfully registered, it will raise a `NotImplementedError` listing the available functions. |
| 46 | + |
| 47 | +--- |
| 48 | + |
| 49 | +## 3. Implementation Guidelines |
| 50 | + |
| 51 | +### Naming Contract |
| 52 | + |
| 53 | +The Python module must follow the **Fully Qualified Name (FQN)** of the Rune definition, prefixed with `rune.native`. |
| 54 | + |
| 55 | +- **Rune Namespace**: `rosetta_dsl.test.functions` |
| 56 | + - **Rune Function**: `RoundToNearest` |
| 57 | +- **Expected Python Import Path**: `rune.native.rosetta_dsl.test.functions.RoundToNearest` |
| 58 | + |
| 59 | +### Signature Matching |
| 60 | + |
| 61 | +The Python function must accept exactly the same parameters as defined in Rune, in the same order. Use Python type hints where possible to maintain type safety. |
| 62 | + |
| 63 | +### Troubleshooting |
| 64 | + |
| 65 | +If your native function is not being called: |
| 66 | + |
| 67 | +1. **Check Logs**: The initialization logic logs a `WARNING` if a module import fails or if the attribute found is not callable. |
| 68 | +2. **Verify Package**: Ensure your native code is installed in the current environment (e.g., via `pip install -e`). |
| 69 | +3. **Implicit Init**: Ensure all directories in your path contain an `__init__.py` file to be treated as a valid Python package. |
| 70 | + |
| 71 | +--- |
| 72 | + |
| 73 | +## 4. End-to-End Example |
| 74 | + |
| 75 | +### A. Rune Definition |
| 76 | + |
| 77 | +**File**: `NativeFunctionTest.rosetta` |
| 78 | + |
| 79 | +```rosetta |
| 80 | +namespace rosetta_dsl.test.functions |
| 81 | +
|
| 82 | +// This function is implemented in Python |
| 83 | +func RoundToNearest: |
| 84 | + [codeImplementation] |
| 85 | + inputs: |
| 86 | + value number (1..1) |
| 87 | + digits int (1..1) |
| 88 | + roundingMode RoundingModeEnum (1..1) |
| 89 | + output: |
| 90 | + roundedValue number (1..1) |
| 91 | +
|
| 92 | +// This function is a regular Rune function that calls the native one |
| 93 | +func RoundUp: |
| 94 | + inputs: |
| 95 | + value number (1..1) |
| 96 | + digits int (1..1) |
| 97 | + output: |
| 98 | + roundedValue number (1..1) |
| 99 | + set roundedValue: |
| 100 | + RoundToNearest(value, digits, RoundingModeEnum -> Up) |
| 101 | +``` |
| 102 | + |
| 103 | +### B. Native Python Implementation |
| 104 | + |
| 105 | +Place your logic in the directory structure matching the namespace. |
| 106 | + |
| 107 | +**File**: `src/rune/native/rosetta_dsl/test/functions/RoundToNearest.py` |
| 108 | + |
| 109 | +```python |
| 110 | +from decimal import Decimal |
| 111 | +from rosetta_dsl.test.functions.RoundingModeEnum import RoundingModeEnum |
| 112 | + |
| 113 | +def RoundToNearest(value: Decimal, digits: int, roundingMode: RoundingModeEnum) -> Decimal: |
| 114 | + """ |
| 115 | + Implementation using Python's Decimal quantization. |
| 116 | + """ |
| 117 | + decimal_mode = "ROUND_UP" if roundingMode == RoundingModeEnum.UP else "ROUND_DOWN" |
| 118 | + quantifier = Decimal("1").scaleb(-digits) |
| 119 | + return value.quantize(quantifier, rounding=decimal_mode) |
| 120 | +``` |
| 121 | + |
| 122 | +### C. Packaging and Installation |
| 123 | + |
| 124 | +Use a standard Python packaging tool (like `setuptools`) to make the `rune.native` namespace discoverable. |
| 125 | + |
| 126 | +**File**: `pyproject.toml` |
| 127 | + |
| 128 | +```toml |
| 129 | +[project] |
| 130 | +name = "rosetta-dsl-native-functions" |
| 131 | +version = "0.1.0" |
| 132 | + |
| 133 | +[tool.setuptools.packages.find] |
| 134 | +where = ["src"] |
| 135 | +``` |
| 136 | + |
| 137 | +**Installation Command**: |
| 138 | + |
| 139 | +```bash |
| 140 | +python -m pip install -e . |
| 141 | +``` |
| 142 | + |
| 143 | +### D. Invocation and Generated Code |
| 144 | + |
| 145 | +When Rune generates code for a native function, it creates a wrapper that handles the bridge to your Python implementation using `rune_execute_native`. |
| 146 | + |
| 147 | +**Note**: The examples are mocked up and do not include the complete code. |
| 148 | + |
| 149 | +**Generated Wrapper for `RoundToNearest`**: |
| 150 | + |
| 151 | +```python |
| 152 | +@replaceable |
| 153 | +@validate_call |
| 154 | +def RoundToNearest(value: Decimal, digits: int, roundingMode: RoundingModeEnum) -> Decimal: |
| 155 | + # ... initialization ... |
| 156 | + roundedValue = rune_execute_native('rosetta_dsl.test.functions.RoundToNearest', value, digits, roundingMode) |
| 157 | + |
| 158 | + return roundedValue |
| 159 | +``` |
| 160 | + |
| 161 | +Other generated functions call this wrapper just like any other function: |
| 162 | + |
| 163 | +**Generated Code for `RoundUp`**: |
| 164 | + |
| 165 | +```python |
| 166 | +@replaceable |
| 167 | +@validate_call |
| 168 | +def RoundUp(value: Decimal, digits: int) -> Decimal: |
| 169 | + # ... |
| 170 | + # Calls the native wrapper defined above |
| 171 | + roundedValue = RoundToNearest(value, digits, RoundingModeEnum.UP) |
| 172 | + |
| 173 | + return roundedValue |
| 174 | +``` |
| 175 | + |
| 176 | +#### Runtime Behaviour |
| 177 | + |
| 178 | +Once installed, any Rune code calling `RoundUp` will automatically route through the native bridge to your Python logic. |
0 commit comments