mirror of
https://github.com/XRPLF/rippled.git
synced 2026-03-16 17:52:28 +00:00
207 lines
6.3 KiB
Python
207 lines
6.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Generate C++ wrapper classes for XRP Ledger entry types from ledger_entries.macro.
|
|
|
|
This script parses the ledger_entries.macro file and generates type-safe wrapper
|
|
classes for each ledger entry type, similar to the transaction wrapper classes.
|
|
|
|
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_ledger_entry_parser():
|
|
"""Create a pyparsing parser for LEDGER_ENTRY macros.
|
|
|
|
This parser extracts the full LEDGER_ENTRY macro call and parses its arguments
|
|
using pyparsing's nesting-aware delimited list parsing.
|
|
"""
|
|
# Match the exact words
|
|
ledger_entry = pp.Keyword("LEDGER_ENTRY") | pp.Keyword("LEDGER_ENTRY_DUPLICATE")
|
|
|
|
# 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: LEDGER_ENTRY(args) or LEDGER_ENTRY_DUPLICATE(args)
|
|
macro_parser = (
|
|
ledger_entry + pp.Suppress("(") + pp.Group(args_list)("args") + pp.Suppress(")")
|
|
)
|
|
|
|
return macro_parser
|
|
|
|
|
|
def parse_ledger_entry_args(args_list):
|
|
"""Parse the arguments of a LEDGER_ENTRY macro call.
|
|
|
|
Args:
|
|
args_list: A list of parsed arguments from pyparsing, e.g.,
|
|
['ltACCOUNT_ROOT', '0x0061', 'AccountRoot', 'account', '({...})']
|
|
|
|
Returns:
|
|
A dict with parsed ledger entry information.
|
|
"""
|
|
if len(args_list) < 5:
|
|
raise ValueError(
|
|
f"Expected at least 5 parts in LEDGER_ENTRY, got {len(args_list)}: {args_list}"
|
|
)
|
|
|
|
tag = args_list[0]
|
|
value = args_list[1]
|
|
name = args_list[2]
|
|
rpc_name = args_list[3]
|
|
fields_str = args_list[-1]
|
|
|
|
# Parse fields: ({field1, field2, ...})
|
|
fields = parse_field_list(fields_str)
|
|
|
|
return {
|
|
"tag": tag,
|
|
"value": value,
|
|
"name": name,
|
|
"rpc_name": rpc_name,
|
|
"fields": fields,
|
|
}
|
|
|
|
|
|
def parse_macro_file(file_path):
|
|
"""Parse the ledger_entries.macro file and return a list of ledger entry definitions.
|
|
|
|
Uses pcpp to preprocess the file and pyparsing to parse the LEDGER_ENTRY macros.
|
|
"""
|
|
with open(file_path, "r") as f:
|
|
c_code = f.read()
|
|
|
|
# Step 1: Clean the C++ code using pcpp
|
|
cleaner = CppCleaner("LEDGER_ENTRY_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_ledger_entry_parser()
|
|
entries = []
|
|
|
|
for match, _, _ in parser.scan_string(clean_text):
|
|
# Extract the macro name and arguments
|
|
raw_args = match.args
|
|
|
|
# Parse the arguments
|
|
entry_data = parse_ledger_entry_args(raw_args)
|
|
entries.append(entry_data)
|
|
|
|
return entries
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Generate C++ ledger entry classes from ledger_entries.macro"
|
|
)
|
|
parser.add_argument("macro_path", help="Path to ledger_entries.macro")
|
|
parser.add_argument(
|
|
"--header-dir",
|
|
help="Output directory for header files",
|
|
default="include/xrpl/protocol_autogen/ledger_objects",
|
|
)
|
|
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 ledger_entries.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
|
|
entries = parse_macro_file(args.macro_path)
|
|
|
|
print(f"Found {len(entries)} ledger entries\n")
|
|
|
|
for entry in entries:
|
|
print(f"Ledger Entry: {entry['name']}")
|
|
print(f" Tag: {entry['tag']}")
|
|
print(f" Value: {entry['value']}")
|
|
print(f" RPC Name: {entry['rpc_name']}")
|
|
print(f" Fields: {len(entry['fields'])}")
|
|
for field in entry["fields"]:
|
|
mpt_info = f" ({field['mpt_support']})" if "mpt_support" in field else ""
|
|
print(f" - {field['name']}: {field['requirement']}{mpt_info}")
|
|
print()
|
|
|
|
# Set up template directory
|
|
script_dir = Path(__file__).parent
|
|
template_dir = script_dir / "templates"
|
|
|
|
# Generate C++ classes
|
|
header_dir = Path(args.header_dir)
|
|
header_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
for entry in entries:
|
|
generate_cpp_class(
|
|
entry, header_dir, template_dir, field_types, "LedgerEntry.h.mako"
|
|
)
|
|
|
|
print(f"\nGenerated {len(entries)} ledger entry classes")
|
|
|
|
# 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 entry in entries:
|
|
# Fields are already enriched from generate_cpp_class above
|
|
generate_from_template(
|
|
entry, test_dir, template_dir, "LedgerEntryTests.cpp.mako", "Tests.cpp"
|
|
)
|
|
|
|
print(f"\nGenerated {len(entries)} ledger entry test files")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|