mirror of
https://github.com/XRPLF/rippled.git
synced 2026-03-16 17:52:28 +00:00
227 lines
6.6 KiB
Python
227 lines
6.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Parse transactions.macro file to extract transaction information
|
|
and generate C++ classes for each transaction type.
|
|
|
|
Uses pcpp to preprocess the macro file and pyparsing to parse the DSL.
|
|
"""
|
|
|
|
# cspell:words sfields
|
|
|
|
import io
|
|
import argparse
|
|
from pathlib import Path
|
|
import pyparsing as pp
|
|
|
|
# Import common utilities
|
|
from macro_parser_common import (
|
|
CppCleaner,
|
|
parse_sfields_macro,
|
|
parse_field_list,
|
|
generate_cpp_class,
|
|
generate_from_template,
|
|
)
|
|
|
|
|
|
def create_transaction_parser():
|
|
"""Create a pyparsing parser for TRANSACTION macros.
|
|
|
|
This parser extracts the full TRANSACTION macro call and parses its arguments
|
|
using pyparsing's nesting-aware delimited list parsing.
|
|
"""
|
|
# Define nested structures so pyparsing protects them
|
|
nested_braces = pp.original_text_for(pp.nested_expr("{", "}"))
|
|
nested_parens = pp.original_text_for(pp.nested_expr("(", ")"))
|
|
|
|
# Define standard text (anything that isn't a comma, parens, or braces)
|
|
plain_text = pp.Word(pp.printables + " \t\n", exclude_chars=",{}()")
|
|
|
|
# A single argument is any combination of the above
|
|
single_arg = pp.Combine(pp.OneOrMore(nested_braces | nested_parens | plain_text))
|
|
single_arg.set_parse_action(lambda t: t[0].strip())
|
|
|
|
# The arguments are a delimited list
|
|
args_list = pp.DelimitedList(single_arg)
|
|
|
|
# The full macro: TRANSACTION(args)
|
|
macro_parser = (
|
|
pp.Keyword("TRANSACTION")
|
|
+ pp.Suppress("(")
|
|
+ pp.Group(args_list)("args")
|
|
+ pp.Suppress(")")
|
|
)
|
|
|
|
return macro_parser
|
|
|
|
|
|
def parse_transaction_args(args_list):
|
|
"""Parse the arguments of a TRANSACTION macro call.
|
|
|
|
Args:
|
|
args_list: A list of parsed arguments from pyparsing, e.g.,
|
|
['ttPAYMENT', '0', 'Payment', 'Delegation::delegable',
|
|
'uint256{}', 'createAcct', '({...})']
|
|
|
|
Returns:
|
|
A dict with parsed transaction information.
|
|
"""
|
|
if len(args_list) < 7:
|
|
raise ValueError(
|
|
f"Expected at least 7 parts in TRANSACTION, got {len(args_list)}: {args_list}"
|
|
)
|
|
|
|
tag = args_list[0]
|
|
value = args_list[1]
|
|
name = args_list[2]
|
|
delegable = args_list[3]
|
|
amendments = args_list[4]
|
|
privileges = args_list[5]
|
|
fields_str = args_list[-1]
|
|
|
|
# Parse fields: ({field1, field2, ...})
|
|
fields = parse_field_list(fields_str)
|
|
|
|
return {
|
|
"tag": tag,
|
|
"value": value,
|
|
"name": name,
|
|
"delegable": delegable,
|
|
"amendments": amendments,
|
|
"privileges": privileges,
|
|
"fields": fields,
|
|
}
|
|
|
|
|
|
def parse_macro_file(filepath):
|
|
"""Parse the transactions.macro file.
|
|
|
|
Uses pcpp to preprocess the file and pyparsing to parse the TRANSACTION macros.
|
|
"""
|
|
with open(filepath, "r") as f:
|
|
c_code = f.read()
|
|
|
|
# Step 1: Clean the C++ code using pcpp
|
|
cleaner = CppCleaner("TRANSACTION_INCLUDE")
|
|
cleaner.parse(c_code)
|
|
|
|
out = io.StringIO()
|
|
cleaner.write(out)
|
|
clean_text = out.getvalue()
|
|
|
|
# Step 2: Parse the clean text using pyparsing
|
|
parser = create_transaction_parser()
|
|
transactions = []
|
|
|
|
for match, _, _ in parser.scan_string(clean_text):
|
|
# Extract the macro name and arguments
|
|
raw_args = match.args
|
|
|
|
# Parse the arguments
|
|
tx_data = parse_transaction_args(raw_args)
|
|
transactions.append(tx_data)
|
|
|
|
return transactions
|
|
|
|
|
|
# TransactionBase is a static file in the repository at:
|
|
# - include/xrpl/protocol/TransactionBase.h
|
|
# - src/libxrpl/protocol/TransactionBase.cpp
|
|
# It is NOT generated by this script.
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Generate C++ transaction classes from transactions.macro"
|
|
)
|
|
parser.add_argument("macro_path", help="Path to transactions.macro")
|
|
parser.add_argument(
|
|
"--header-dir",
|
|
help="Output directory for header files",
|
|
default="include/xrpl/protocol_autogen/transactions",
|
|
)
|
|
parser.add_argument(
|
|
"--test-dir",
|
|
help="Output directory for test files (optional)",
|
|
default=None,
|
|
)
|
|
parser.add_argument(
|
|
"--sfields-macro",
|
|
help="Path to sfields.macro (default: auto-detect from macro_path)",
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Auto-detect sfields.macro path if not provided
|
|
if args.sfields_macro:
|
|
sfields_path = Path(args.sfields_macro)
|
|
else:
|
|
# Assume sfields.macro is in the same directory as transactions.macro
|
|
macro_path = Path(args.macro_path)
|
|
sfields_path = macro_path.parent / "sfields.macro"
|
|
|
|
# Parse sfields.macro to get field type information
|
|
print(f"Parsing {sfields_path}...")
|
|
field_types = parse_sfields_macro(sfields_path)
|
|
print(
|
|
f"Found {len(field_types)} field definitions ({sum(1 for f in field_types.values() if f['typed'])} typed, {sum(1 for f in field_types.values() if not f['typed'])} untyped)\n"
|
|
)
|
|
|
|
# Parse the file
|
|
transactions = parse_macro_file(args.macro_path)
|
|
|
|
print(f"Found {len(transactions)} transactions\n")
|
|
|
|
for tx in transactions:
|
|
print(f"Transaction: {tx['name']}")
|
|
print(f" Tag: {tx['tag']}")
|
|
print(f" Value: {tx['value']}")
|
|
print(f" Fields: {len(tx['fields'])}")
|
|
for field in tx["fields"]:
|
|
print(f" - {field['name']}: {field['requirement']}")
|
|
print()
|
|
|
|
# Set up output directory
|
|
header_dir = Path(args.header_dir)
|
|
header_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
print(f"\nGenerating header-only template classes...")
|
|
print(f" Headers: {header_dir}\n")
|
|
|
|
# Set up template directory
|
|
script_dir = Path(__file__).parent
|
|
template_dir = script_dir / "templates"
|
|
|
|
generated_files = []
|
|
for tx_info in transactions:
|
|
header_path = generate_cpp_class(
|
|
tx_info, header_dir, template_dir, field_types, "Transaction.h.mako"
|
|
)
|
|
generated_files.append(header_path)
|
|
print(f" Generated: {tx_info['name']}.h")
|
|
|
|
print(
|
|
f"\nGenerated {len(transactions)} transaction classes ({len(generated_files)} header files)"
|
|
)
|
|
print(f" Headers: {header_dir.absolute()}")
|
|
|
|
# Generate unit tests if --test-dir is provided
|
|
if args.test_dir:
|
|
test_dir = Path(args.test_dir)
|
|
test_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
for tx_info in transactions:
|
|
# Fields are already enriched from generate_cpp_class above
|
|
generate_from_template(
|
|
tx_info,
|
|
test_dir,
|
|
template_dir,
|
|
"TransactionTests.cpp.mako",
|
|
"Tests.cpp",
|
|
)
|
|
|
|
print(f"\nGenerated {len(transactions)} transaction test files")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|