Skip to content

Instantly share code, notes, and snippets.

@kassane
Created June 14, 2025 20:54
Show Gist options
  • Select an option

  • Save kassane/5b8bf8fabfe7ea83368848353694ea9f to your computer and use it in GitHub Desktop.

Select an option

Save kassane/5b8bf8fabfe7ea83368848353694ea9f to your computer and use it in GitHub Desktop.
Clang-ast-dump: convert C++ to D
import json
import subprocess
import sys
import argparse
import re
from typing import Dict, List, Optional
from pathlib import Path
import datetime
import os
# Counter for anonymous struct names
_anon_counter = 0
def get_clang_include_paths() -> List[str]:
"""Detect standard C++ include paths using clang++."""
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
cmd = ['clang++', '-E', '-x', 'c++', '-', '-v']
try:
result = subprocess.run(
cmd,
input='',
capture_output=True,
text=True,
check=True
)
include_paths = []
include_section = False
for line in result.stderr.splitlines():
line = line.strip()
if line.startswith('#include <...> search starts here:'):
include_section = True
continue
if line.startswith('End of search list.'):
include_section = False
continue
if include_section and os.path.isdir(line):
include_paths.append(line)
return include_paths
except subprocess.CalledProcessError as e:
with open('clang_stderr.log', 'a') as f:
f.write(f"[{timestamp}] Error detecting include paths: {e}\n")
f.write(f"[{timestamp}] STDERR:\n{e.stderr}\n")
return []
def clang(csrc_path: str, include_paths: List[str] = [], stdout_log: str = "clang_stdout.log", stderr_log: str = "clang_stderr.log") -> Dict:
"""Run Clang to generate AST dump in JSON format, logging stdout and stderr separately."""
# Delete previous log files
for log_file in [stdout_log, stderr_log]:
if os.path.exists(log_file):
os.remove(log_file)
if not os.path.isfile(csrc_path):
with open(stderr_log, 'a') as f:
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
f.write(f"[{timestamp}] Error: {csrc_path} does not exist or is not a file\n")
print(f"Error: {csrc_path} does not exist")
sys.exit(1)
auto_includes = get_clang_include_paths()
cmd = ['clang', '-Xclang', '-ast-dump=json', '-c', csrc_path, '-fparse-all-comments']
for path in set(auto_includes + include_paths):
if os.path.isdir(path):
cmd.append(f'-I{path}')
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
check=True
)
try:
ast_data = json.loads(result.stdout)
except json.JSONDecodeError as e:
with open(stderr_log, 'a') as f:
f.write(f"[{timestamp}] JSON Parse Error: {e}\n")
f.write(f"[{timestamp}] STDOUT:\n{result.stdout}\n")
f.write(f"[{timestamp}] STDERR:\n{result.stderr}\n")
print(f"Error parsing Clang JSON output, see {stderr_log} for details")
sys.exit(1)
if not ast_data.get('inner'):
with open(stderr_log, 'a') as f:
f.write(f"[{timestamp}] Warning: Empty AST; check input file or include paths\n")
with open(stdout_log, 'a') as f:
f.write(f"[{timestamp}] Command: {' '.join(cmd)}\n")
f.write(f"[{timestamp}] STDOUT:\n{result.stdout}\n")
if result.stderr:
with open(stderr_log, 'a') as f:
f.write(f"[{timestamp}] Command: {' '.join(cmd)}\n")
f.write(f"[{timestamp}] STDERR:\n{result.stderr}\n")
return ast_data
except subprocess.CalledProcessError as e:
with open(stderr_log, 'a') as f:
f.write(f"[{timestamp}] Command: {' '.join(cmd)}\n")
f.write(f"[{timestamp}] ERROR: Command returned non-zero exit status {e.returncode}\n")
f.write(f"[{timestamp}] STDOUT:\n{e.stdout}\n")
f.write(f"[{timestamp}] STDERR:\n{e.stderr}\n")
print(f"Error running Clang, see {stderr_log} for details")
sys.exit(1)
def detect_module(csrc_path: str, stderr_log: str) -> str:
"""Detect module name from file name."""
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
module = Path(csrc_path).stem
with open(stderr_log, 'a') as f:
f.write(f"[{timestamp}] Detected module: {module}\n")
return module
def get_d_type(cpp_type: str, stderr_log: str, timestamp: str) -> str:
"""Map C++ types to D types."""
type_map = {
'int': 'int',
'double': 'double',
'float': 'float',
'char': 'char',
'void': 'void',
'bool': 'bool',
'unsigned int': 'uint',
'unsigned char': 'ubyte',
'unsigned short': 'ushort',
'unsigned long': 'ulong',
'signed char': 'byte',
'short': 'short',
'long': 'long',
'__int128_t': 'cent',
'__uint128_t': 'ucent',
'std::allocator': 'allocator',
'std::string': 'string',
'std::vector': 'vector',
'std::basic_string': 'basic_string'
}
if cpp_type.startswith('std::vector<'):
match = re.match(r'std::vector<([^,>]+)(?:,\s*([^>]+))?>', cpp_type)
if match:
t_type = get_d_type(match.group(1).strip(), stderr_log, timestamp)
return f"vector!{t_type}"
if cpp_type.endswith('*'):
return f"{get_d_type(cpp_type[:-1], stderr_log, timestamp)}*"
if cpp_type.endswith('&'):
return get_d_type(cpp_type[:-1], stderr_log, timestamp)
if cpp_type.endswith('[]'):
return f"{get_d_type(cpp_type[:-2], stderr_log, timestamp)}[]"
if ('std::' in cpp_type or 'std::pmr::' in cpp_type) and cpp_type not in type_map:
with open(stderr_log, 'a') as f:
f.write(f"[{timestamp}] Warning: Unmapped standard library type {cpp_type} mapped directly\n")
return cpp_type.replace('std::', '').replace('std::pmr::', '')
return type_map.get(cpp_type, cpp_type)
def process_typedef(node: Dict, namespace: Optional[str], source: str, stderr_log: str, timestamp: str) -> Dict:
"""Generate IR for typedef or using declarations."""
if node.get('kind') not in ['TypedefDecl', 'TypeAliasDecl']:
return {}
name = node.get('name', '')
underlying_type = get_d_type(node.get('type', {}).get('qualType', ''), stderr_log, timestamp)
# Skip problematic aliases
if name in ['__int128_t', '__uint128_t', '__NSConstantString', '__builtin_ms_va_list', '__builtin_va_list']:
with open(stderr_log, 'a') as f:
f.write(f"[{timestamp}] Skipped typedef {name} to avoid invalid D alias\n")
return {}
if not name or not underlying_type or 'std' in (namespace or '') or 'std::' in underlying_type:
with open(stderr_log, 'a') as f:
f.write(f"[{timestamp}] Skipped typedef {name} due to std namespace or type\n")
return {}
outp = {
'kind': 'alias',
'name': name,
'type': underlying_type,
'namespace': namespace
}
for inner in node.get('inner', []):
if inner.get('kind') == 'FullComment':
comment = source[inner['range']['begin']['offset']:inner['range']['end']['offset']+1].rstrip()
outp['comment'] = comment
return outp
def process_function(node: Dict, namespace: Optional[str], source: str, stderr_log: str, timestamp: str) -> Dict:
"""Generate IR for function or method declarations."""
if node.get('kind') not in ['FunctionDecl', 'CXXMethodDecl', 'CXXConstructorDecl', 'CXXDestructorDecl']:
return {}
name = node.get('name', '')
mangled_name = node.get('mangledName', '')
qual_type = node.get('type', {}).get('qualType', '')
is_constructor = node.get('kind') == 'CXXConstructorDecl'
is_destructor = node.get('kind') == 'CXXDestructorDecl'
if (name.startswith('operator') or 'std' in (namespace or '') or
'std::' in qual_type or 'std::pmr::' in qual_type):
with open(stderr_log, 'a') as f:
f.write(f"[{timestamp}] Skipped function {name} due to std namespace or type\n")
return {}
return_type = 'void' if is_constructor or is_destructor else get_d_type(
qual_type.split('(')[0].strip(), stderr_log, timestamp)
params = []
# All functions/methods are @nogc and @safe by default, nothrow if noexcept
attributes = ['@nogc', '@safe']
if node.get('isNoexcept', False):
attributes.append('nothrow')
if node.get('isVirtual', False):
attributes.append('abstract')
for inner_node in node.get('inner', []):
if inner_node.get('kind') == 'ParmVarDecl':
param_type = get_d_type(inner_node.get('type', {}).get('qualType', ''), stderr_log, timestamp)
if param_type.endswith('...'):
with open(stderr_log, 'a') as f:
f.write(f"[{timestamp}] Warning: Skipping function {name} with variadic parameters\n")
return {}
param_name = inner_node.get('name', f"param{len(params)}")
params.append({'type': param_type, 'name': param_name})
elif inner_node.get('kind') == 'FullComment':
comment = source[inner_node['range']['begin']['offset']:inner_node['range']['end']['offset']+1].rstrip()
outp = {
'kind': 'func' if not is_constructor and not is_destructor else 'constructor' if is_constructor else 'destructor',
'name': name,
'return': return_type,
'params': params,
'namespace': namespace,
'mangled': mangled_name if mangled_name and mangled_name.startswith('_Z') else None,
'attributes': attributes
}
if 'comment' in locals():
outp['comment'] = comment
return outp
def process_record(node: Dict, namespace: Optional[str], source: str, stderr_log: str, timestamp: str) -> Dict:
"""Generate IR for class or struct declarations, avoiding anonymous unions."""
global _anon_counter
if node.get('kind') not in ['CXXRecordDecl']:
return {}
tag = node.get('tagUsed', '')
name = node.get('name', '')
if not name and tag == 'struct':
name = f"AnonStruct_{_anon_counter}"
_anon_counter += 1
if (not name or name in ['vector', 'memory_resource', 'allocator', 'basic_string'] or
'std' in (namespace or '')):
with open(stderr_log, 'a') as f:
f.write(f"[{timestamp}] Skipped record {name} due to std namespace or standard type\n")
return {}
is_class = tag == 'class'
has_virtual = any(inner.get('isVirtual', False) for inner in node.get('inner', []) if inner.get('kind') == 'CXXMethodDecl')
has_constructors = any(inner.get('kind') == 'CXXConstructorDecl' for inner in node.get('inner', []))
# Map to D class if C++ class or has virtual methods/constructors
d_type = 'class' if is_class or has_virtual or has_constructors else 'struct'
fields = []
methods = []
aliases = []
is_incomplete = not node.get('completeDefinition', False)
if is_incomplete:
with open(stderr_log, 'a') as f:
f.write(f"[{timestamp}] Warning: Processing incomplete definition for {name}\n")
with open(stderr_log, 'a') as f:
f.write(f"[{timestamp}] Processing record {name} as D {d_type}, has_constructors={has_constructors}\n")
for inner_node in node.get('inner', []):
if inner_node.get('kind') == 'FieldDecl':
field_type = get_d_type(inner_node.get('type', {}).get('qualType', ''), stderr_log, timestamp)
field_name = inner_node.get('name', '')
if field_name and 'std::' not in field_type and 'std::pmr::' not in field_type:
field_comment = None
for sub_inner in inner_node.get('inner', []):
if sub_inner.get('kind') == 'FullComment':
field_comment = source[sub_inner['range']['begin']['offset']:sub_inner['range']['end']['offset']+1].rstrip()
fields.append({'type': field_type, 'name': field_name, 'comment': field_comment})
elif inner_node.get('kind') == 'CXXRecordDecl' and not inner_node.get('name'):
anon_tag = inner_node.get('tagUsed', '')
if anon_tag == 'struct':
field_name = inner_node.get('fieldName', f"anon_struct_{_anon_counter}")
_anon_counter += 1
if field_name:
anon_fields = []
for anon_inner in inner_node.get('inner', []):
if anon_inner.get('kind') == 'FieldDecl':
anon_type = get_d_type(anon_inner.get('type', {}).get('qualType', ''), stderr_log, timestamp)
anon_name = anon_inner.get('name', '')
if anon_name and 'std::' not in anon_type and 'std::pmr::' not in anon_type:
anon_fields.append({'type': anon_type, 'name': anon_name})
if anon_fields:
fields.append({'type': 'struct', 'name': field_name, 'fields': anon_fields})
elif anon_tag == 'union':
with open(stderr_log, 'a') as f:
f.write(f"[{timestamp}] Skipped anonymous union in {name}\n")
continue
elif inner_node.get('kind') in ['CXXMethodDecl', 'CXXConstructorDecl', 'CXXDestructorDecl']:
if not inner_node.get('isStatic', False):
method = process_function(inner_node, namespace, source, stderr_log, timestamp)
if method:
methods.append(method)
elif inner_node.get('kind') == 'FullComment':
comment = source[inner_node['range']['begin']['offset']:inner_node['range']['end']['offset']+1].rstrip()
elif inner_node.get('kind') in ['TypedefDecl', 'TypeAliasDecl']:
typedef = process_typedef(inner_node, namespace, source, stderr_log, timestamp)
if typedef:
aliases.append(typedef)
if is_incomplete and not (fields or methods or aliases):
return {'kind': d_type, 'name': name, 'namespace': namespace, 'is_incomplete': True}
if not is_incomplete and not (fields or methods or aliases):
return {}
outp = {
'kind': d_type,
'name': name,
'fields': fields,
'methods': methods,
'aliases': aliases,
'namespace': namespace,
'is_incomplete': is_incomplete
}
if 'comment' in locals():
outp['comment'] = comment
return outp
def detect_std_types(ast: Dict) -> set:
"""Detect standard library types and 128-bit integer types used in the AST, excluding std::pmr."""
std_types = set()
def check_nodes(nodes: List[Dict]):
for node in nodes:
if node.get('kind') == 'TypeRef':
qual_type = node.get('type', {}).get('qualType', '')
if qual_type.startswith('std::') and not qual_type.startswith('std::pmr::'):
std_types.add(qual_type.split('<')[0])
elif qual_type in ['__int128_t', '__uint128_t']:
std_types.add(qual_type)
elif node.get('kind') == 'CXXRecordDecl':
ns = node.get('namespace', '')
if ns and ns.startswith('std') and not ns.startswith('std::pmr'):
std_types.add(f"std::{node.get('name', '')}")
check_nodes(node.get('inner', []))
check_nodes(ast.get('inner', []))
return std_types
def generate_ir(csrc_path: str, include_paths: List[str], stdout_log: str, stderr_log: str, json_file: str, source: str) -> Dict:
"""Generate intermediate representation from Clang AST."""
ast = clang(csrc_path, include_paths, stdout_log, stderr_log)
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
module = detect_module(csrc_path, stderr_log)
std_types = detect_std_types(ast)
outp = {
'module': module,
'decls': [],
'header_comment': '',
'license': '',
'authors': '',
'uses_memory': bool(re.search(r'#include\s*<memory>', source)),
'std_types': list(std_types)
}
# Header parsing
match = re.search(r"/\*\*?(.*?)\*/", source, re.DOTALL)
if match:
header = match.group(1).strip()
outp['header_comment'] = header
if 'Copyright' in header:
copyright_match = re.search(r'Copyright\s*:\s*(.*?)(?:\n|$)', header, re.I)
if copyright_match:
outp['license'] = copyright_match.group(1).strip()
if 'Authors' in header:
authors_match = re.search(r'Authors\s*:\s*(.*?)(?:\n|$)', header, re.I)
if authors_match:
outp['authors'] = authors_match.group(1).strip()
def traverse_nodes(nodes: List[Dict], namespace: Optional[str] = None):
for node in nodes:
if node.get('kind') == 'NamespaceDecl':
ns_name = node.get('name', '')
if ns_name == 'std' or ns_name.startswith('std::') or (namespace and namespace.startswith('std')):
with open(stderr_log, 'a') as f:
f.write(f"[{timestamp}] Skipped namespace {ns_name} due to std prefix\n")
continue
new_ns = f'{namespace}.{ns_name}' if namespace else ns_name
traverse_nodes(node.get('inner', []), new_ns)
elif node.get('kind') in ['FunctionDecl', 'CXXMethodDecl', 'CXXConstructorDecl', 'CXXDestructorDecl']:
decl = process_function(node, namespace, source, stderr_log, timestamp)
if decl:
outp['decls'].append(decl)
elif node.get('kind') == 'CXXRecordDecl':
if node.get('fieldName'):
continue
decl = process_record(node, namespace, source, stderr_log, timestamp)
if decl:
outp['decls'].append(decl)
elif node.get('kind') == 'FieldDecl' and 'type' in node and 'Record' in node.get('type', {}).get('qualType', ''):
inner_record = next((inner for inner in node.get('inner', []) if inner.get('kind') == 'CXXRecordDecl'), None)
if inner_record and not inner_record.get('name'):
inner_record['fieldName'] = node.get('name', '')
decl = process_record(inner_record, namespace, source, stderr_log, timestamp)
if decl:
outp['decls'].append(decl)
elif node.get('kind') in ['TypedefDecl', 'TypeAliasDecl']:
decl = process_typedef(node, namespace, source, stderr_log, timestamp)
if decl:
outp['decls'].append(decl)
traverse_nodes(ast.get('inner', []))
with open(stderr_log, 'a') as f:
f.write(f"[{timestamp}] Processed {len(outp['decls'])} declarations\n")
if not outp['decls']:
f.write(f"[{timestamp}] Warning: No declarations generated; check include paths or input file\n")
with open(json_file, 'w') as f:
json.dump(outp, f, indent=2)
return outp
def generate_d_code(ir: Dict) -> str:
"""Convert IR to D code."""
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# Header comments
d_code = []
if ir['header_comment']:
for line in ir['header_comment'].splitlines():
d_code.append(f"/// {line.strip()}")
d_code.append(f"/// License: {ir['license']}" if ir['license'] else '// License: Unknown')
d_code.append(f"/// Authors: {ir['authors']}" if ir['authors'] else '// Authors: Unknown')
d_code.append('// Automatically generated D bindings')
# Add imports only if needed
std_imports = []
memory_needed = ir.get('uses_memory') and any(t in ir['std_types'] for t in ['std::allocator'])
has_classes = any(decl['kind'] == 'class' for decl in ir['decls'])
if memory_needed:
std_imports.append('import core.stdcpp.memory;')
if any(t in ir['std_types'] for t in ['std::string', 'std::basic_string']):
std_imports.append('import core.stdcpp.string;')
if any(t in ir['std_types'] for t in ['std::vector']):
std_imports.append('import core.stdcpp.vector;')
if any(t in ir['std_types'] for t in ['std::allocator']):
std_imports.append('import core.stdcpp.allocator;')
if any(t in ir['std_types'] for t in ['__int128_t', '__uint128_t']):
std_imports.append('import core.int128;')
if has_classes:
std_imports.append('import core.stdcpp.new_;')
if std_imports:
d_code.extend(std_imports)
d_code.append('')
# Add 128-bit integer aliases if needed
if any(t in ir['std_types'] for t in ['__int128_t', '__uint128_t']):
d_code.append('alias int128_t = cent;')
d_code.append('alias uint128_t = ucent;')
d_code.append('')
namespace_decls = {}
def add_decl(decl: Dict, ns_key: str):
if ns_key not in namespace_decls:
namespace_decls[ns_key] = []
namespace_decls[ns_key].append(decl)
for decl in ir['decls']:
ns_key = decl.get('namespace', '')
if decl['kind'] == 'alias':
code = []
if 'comment' in decl:
comment_lines = decl['comment'].strip().splitlines()
code.append('/++')
for line in comment_lines:
code.append(f" + {line.strip().replace('/*', '').replace('*/', '')}")
code.append('+/')
code.append(f"alias {decl['name']} = {decl['type']};")
add_decl('\n'.join(code), ns_key)
elif decl['kind'] in ['struct', 'class']:
code = []
if decl.get('is_incomplete'):
if 'comment' in decl:
comment_lines = decl['comment'].strip().splitlines()
code.append('/++')
for line in comment_lines:
code.append(f" + {line.strip().replace('/*', '').replace('*/', '')}")
code.append('+/')
code.append(f"/// Warning: Incomplete definition")
code.append(f"extern(C++, {decl['kind']}) struct {decl['name']};")
else:
if 'comment' in decl:
comment_lines = decl['comment'].strip().splitlines()
code.append('/++')
for line in comment_lines:
code.append(f" + {line.strip().replace('/*', '').replace('*/', '')}")
code.append('+/')
if decl['kind'] == 'class':
code.append(f"/// Note: Instantiate with `new {decl['name']}(...)` or `core.stdcpp.new_.cpp_new!{decl['name']}(...)`")
code.append(f"extern(C++, {decl['kind']}) class {decl['name']} {{")
for field in decl['fields']:
if field.get('comment'):
comment_lines = field['comment'].strip().splitlines()
code.append(' /++')
for line in comment_lines:
code.append(f" + {line.strip().replace('/*', '').replace('*/', '')}")
code.append(' +/')
code.append(f" {field['type']} {field['name']};")
for alias in decl['aliases']:
if 'comment' in alias:
comment_lines = alias['comment'].strip().splitlines()
code.append(' /++')
for line in comment_lines:
code.append(f" + {line.strip().replace('/*', '').replace('*/', '')}")
code.append(' +/')
code.append(f" alias {alias['name']} = {alias['type']};")
for method in decl['methods']:
method_code = []
if 'comment' in method:
comment_lines = method['comment'].strip().splitlines()
method_code.append(' /++')
for line in comment_lines:
method_code.append(f" + {line.strip().replace('/*', '').replace('*/', '')}")
method_code.append(' +/')
if method.get('mangled'):
method_code.append(f" pragma(mangle, \"{method['mangled']}\")")
attrs = ' '.join(method['attributes']) + (' ' if method['attributes'] else '')
if method['kind'] == 'constructor':
# Skip constructors for structs
if decl['kind'] == 'struct':
with open('clang_stderr.log', 'a') as f:
f.write(f"[{timestamp}] Skipped constructor for struct {decl['name']} (not allowed in D)\n")
continue
method_code.append(f" {attrs}this({', '.join(f'{p['type']} {p['name']}' for p in method['params'])});")
elif method['kind'] == 'destructor':
method_code.append(f" {attrs}~this();")
else:
method_code.append(f" {attrs}{method['return']} {method['name']}({', '.join(f'{p['type']} {p['name']}' for p in method['params'])});")
code.extend(method_code)
code.append('}')
add_decl('\n'.join(code), ns_key)
elif decl['kind'] == 'func':
code = []
if 'comment' in decl:
comment_lines = decl['comment'].strip().splitlines()
code.append('/++')
for line in comment_lines:
code.append(f" + {line.strip().replace('/*', '').replace('*/', '')}")
code.append('+/')
if decl.get('mangled'):
code.append(f"pragma(mangle, \"{decl['mangled']}\")")
attrs = ' '.join(decl['attributes']) + (' ' if decl['attributes'] else '')
code.append(f"{attrs}{decl['return']} {decl['name']}({', '.join(f'{p['type']} {p['name']}' for p in decl['params'])});")
add_decl('\n'.join(code), ns_key)
for ns_key, decls in namespace_decls.items():
if ns_key:
d_code.append(f'extern(C++, {ns_key}) {{')
d_code.extend(decls)
d_code.append('}')
else:
d_code.extend(decls)
return '\n'.join(d_code)
def main():
parser = argparse.ArgumentParser(description='Generate D bindings from C++ source file.')
parser.add_argument('cpp_source_file', help='Path to the C++ source file')
parser.add_argument('-I', '--include', action='append', default=[], help='Additional include paths for Clang')
parser.add_argument('-o', '--output', help='Output D file path (default: <input>.d)')
parser.add_argument('--stdout-log', default='clang_stdout.log', help='Log file for Clang stdout (default: clang_stdout.log)')
parser.add_argument('--stderr-log', default='clang_stderr.log', help='Log file for Clang stderr (default: clang_stderr.log)')
parser.add_argument('--json-file', default='bindings_ir.json', help='Output file for IR JSON (default: bindings_ir.json)')
args = parser.parse_args()
csrc_path = args.cpp_source_file
include_paths = args.include
output_path = args.output or csrc_path.replace('.cpp', '.d').replace('.hpp', '.d')
stdout_log = args.stdout_log
stderr_log = args.stderr_log
json_file = args.json_file
with open(csrc_path, mode='r', newline='') as f:
source = f.read()
ir = generate_ir(csrc_path, include_paths, stdout_log, stderr_log, json_file, source)
d_bindings = generate_d_code(ir)
try:
with open(output_path, 'w') as f:
f.write(d_bindings)
print(f"D bindings written to {output_path}")
except IOError as e:
print(f"Error writing to {output_path}: {e}")
sys.exit(1)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment