solidity/preprocessor/README.md
A preprocessor for Solidity files that resolves Yul function imports within assembly blocks, enabling better code organization and reusability for inline assembly code.
.presl files into your assembly blocksforge fmt on output files for clean, consistent formattingNo installation required! Just use Python 3.6+:
python3 yul_preprocessor.py <directory>
Optional: Install Foundry for automatic formatting of output files with forge fmt.
Process all .presl and .t.presl files in a directory:
python3 yul_preprocessor.py ./contracts
The preprocessor processes both .presl and .t.presl files (for test files), generating corresponding .post.sol and .t.post.sol output files respectively.
The preprocessor automatically runs forge fmt on all generated .post.sol files to ensure clean, consistent formatting. If forge is not available in your PATH, the preprocessor will skip formatting with a warning.
The preprocessor supports three import patterns:
// import <function_name> from <file_path>
Example:
assembly {
// import add5 from utils.presl
let result := add5(10)
}
// import <func1>, <func2>, <func3> from <file_path>
Example:
assembly {
// import add, multiply, divide from math.presl
let sum := add(5, 10)
let product := multiply(3, 7)
}
// import <function_name> from self
Import functions from a different assembly block in the same file:
contract Example {
function defineHelpers() external pure {
assembly {
function helper(x) -> result {
result := mul(x, 2)
}
}
}
function useHelpers() external pure {
assembly {
// import helper from self
let doubled := helper(5)
}
}
}
Import from subdirectories or parent directories:
// import compute_fold from ../base/MathUtil.presl
// import err from ./errors/Errors.sol
// import safe_add from lib/SafeMath.presl
utils.presl// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Utils {
function process() external pure returns (uint256) {
assembly {
function add5(x) -> result {
result := add(x, 5)
}
function multiply2(x) -> result {
result := mul(x, 2)
}
}
}
}
main.presl// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Main {
function compute() external pure returns (uint256) {
assembly {
// import add5, multiply2 from utils.presl
let a := add5(10) // a = 15
let b := multiply2(a) // b = 30
}
}
}
main.post.sol// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Main {
function compute() external pure returns (uint256) {
assembly {
function add5(x) -> result {
result := add(x, 5)
}
function multiply2(x) -> result {
result := mul(x, 2)
}
let a := add5(10) // a = 15
let b := multiply2(a) // b = 30
}
}
}
*.presl - Standard files with import statements*.t.presl - Test files with import statements*.post.sol - Processed standard files with imports resolved*.t.post.sol - Processed test files with imports resolvedThe preprocessor automatically generates .post.sol files from .presl files and .t.post.sol files from .t.presl files.
Circular dependencies are fully supported! When files A and B import from each other, they are processed as a unified dependency group.
When a circular dependency is detected (e.g., A imports from B, B imports from A), the preprocessor:
file_a.presl:
assembly {
// import funcB from file_b.presl
function funcA() -> result { result := 1 }
}
file_b.presl:
assembly {
// import funcA from file_a.presl
function funcB() -> result { result := 2 }
}
After processing, both files will have both funcA and funcB in their assembly blocks.
The preprocessor handles complex scenarios where:
All transitive dependencies are correctly resolved and propagated.
The preprocessor detects and reports several types of errors:
ValueError: Function 'nonExistent' not found in utils.presl
Available functions: add5, multiply2
ValueError: Function signature mismatch for 'add':
Existing: function add(a, b) -> result
New: function add(x) -> result
Input .presl file
↓
Find assembly blocks
↓
For each assembly block:
- Parse import statements
- Resolve imported functions
- Process dependencies recursively
- Deduplicate functions
- Insert functions at block start
↓
Generate .post.sol file
Run the test suite:
python3 -m pytest test_yul_preprocessor.py -v
Test coverage includes:
If the same function is imported multiple times, only one copy is included:
assembly {
// import add from math.presl
// import add from math.presl // Deduplicated
let x := add(1, 2)
}
The preprocessor handles multiple assembly blocks within a single contract:
contract Multi {
function first() external pure {
assembly {
// import func1 from lib.presl
}
}
function second() external pure {
assembly {
// import func2 from lib.presl
}
}
}
Processed files are cached to improve performance when the same file is imported multiple times in a dependency tree.
// import <names> from <path>assembly {} blocks are extracted and importedTo add new features or fix bugs:
test_files/ directorytest_yul_preprocessor.py with corresponding testsyul_preprocessor.py