summaryrefslogtreecommitdiff
path: root/pixpat-native/codegen
diff options
context:
space:
mode:
Diffstat (limited to 'pixpat-native/codegen')
-rw-r--r--pixpat-native/codegen/gen_pixpat.py267
1 files changed, 0 insertions, 267 deletions
diff --git a/pixpat-native/codegen/gen_pixpat.py b/pixpat-native/codegen/gen_pixpat.py
deleted file mode 100644
index 36fac3b..0000000
--- a/pixpat-native/codegen/gen_pixpat.py
+++ /dev/null
@@ -1,267 +0,0 @@
-#!/usr/bin/env python3
-"""pixpat code generator.
-
-Reads a small user TOML config plus the hand-written catalogs
-(format_catalog.h, pattern_catalog.h — both X-macro lists) and emits
-the per-build capability tables consumed by pixpat.cpp /
-pixpat_convert.cpp / pixpat_pattern.cpp.
-
-Outputs:
- pixpat_config.h — PIXPAT_FEATURE_PATTERN / _CONVERT defines
- pixpat_caps.inc — s_format_caps[] indexed by FormatId
- s_pattern_caps[] indexed by PatternId
-
-The convert dispatch (dispatch_dst_convert / dispatch_src_convert /
-dispatch_convert) and pattern dispatch (try_pattern / try_default
-arms) are hand-written and consume the capability arrays via
-`if constexpr`.
-
-A --query mode prints 0/1 to stdout for use from meson.
-"""
-
-import argparse
-import re
-import sys
-import tomllib
-from pathlib import Path
-
-
-# === Catalog parsers =============================================================
-#
-# Both catalogs are hand-written X-macro lists; we parse them here so
-# that adding a format/pattern is a single-file edit. The macro form
-# is rigid enough that a simple regex over the body works.
-
-
-def _parse_x_macro(path: Path, macro_name: str, row_re: re.Pattern) -> list:
- """Find `#define <macro_name>(X) ...` and return row_re's captures.
-
- The macro body extends from the `#define` line through the first
- line that does not end with a backslash.
- """
- lines = path.read_text().splitlines()
- header_re = re.compile(rf'#define\s+{macro_name}\s*\(\s*X\s*\)')
- i = 0
- while i < len(lines) and not header_re.search(lines[i]):
- i += 1
- if i == len(lines):
- raise SystemExit(f'{macro_name} not found in {path}')
- body_lines: list[str] = []
- while True:
- body_lines.append(lines[i])
- if not lines[i].rstrip().endswith('\\'):
- break
- i += 1
- if i == len(lines):
- break
- rows = row_re.findall('\n'.join(body_lines))
- if not rows:
- raise SystemExit(f'{macro_name} is empty in {path}')
- return rows
-
-
-_FORMAT_ROW = re.compile(r'X\(\s*(\w+)\s*\)')
-_PATTERN_ROW = re.compile(r'X\(\s*(\w+)\s*,\s*(\w+)\s*,\s*(\w+)\s*,\s*"([^"]+)"\s*\)')
-
-
-def parse_format_catalog(path: Path) -> list[str]:
- return _parse_x_macro(path, 'PIXPAT_FORMAT_LIST', _FORMAT_ROW)
-
-
-def parse_pattern_catalog(path: Path) -> list[tuple[str, str, str, str]]:
- """Return [(label, rgb_type, yuv_type, name), …] from pattern_catalog.h.
-
- `label` is the C++ identifier doubling as the PatternId enum value
- and the s_pattern_caps[] index. `rgb_type` and `yuv_type` are the
- C++ classes implementing the pattern in each color kind, or the
- sentinel `void` if the pattern has no variant in that kind.
- `name` is the lowercase string exposed via the C ABI.
- """
- return _parse_x_macro(path, 'PIXPAT_PATTERN_LIST', _PATTERN_ROW)
-
-
-VALID_CAPS = {'rw', 'r', 'w', 'off'}
-
-
-# === Resolution =================================================================
-
-
-def resolve(
- cfg: dict, format_catalog: list[str], pattern_catalog: list[tuple[str, str, str, str]]
-) -> dict:
- """Combine catalogs + user config; return concrete settings."""
- features = cfg.get('features', {})
- have_pattern = bool(features.get('pattern', True))
- have_convert = bool(features.get('convert', True))
- default_caps = features.get('default_format_caps', 'rw')
- if default_caps not in VALID_CAPS:
- raise SystemExit(f'invalid default_format_caps: {default_caps!r}')
-
- catalog_names = set(format_catalog)
- overrides = cfg.get('formats', {}) or {}
- for name in overrides:
- if name not in catalog_names:
- raise SystemExit(f'[formats] override for unknown format: {name!r}')
- if overrides[name] not in VALID_CAPS:
- raise SystemExit(f'invalid caps for {name!r}: {overrides[name]!r}')
-
- formats = []
- for name in format_catalog:
- caps = overrides.get(name, default_caps)
- read = 'r' in caps and caps != 'off'
- write = 'w' in caps and caps != 'off'
- # Reading only matters for the convert path; if convert is off
- # we suppress all reads so unpack_for<Read=false, ...> is the
- # only instantiation seen and unpack_to_norm never gets
- # referenced.
- if not have_convert:
- read = False
- formats.append(
- {
- 'name': name,
- 'read': read,
- 'write': write,
- }
- )
-
- enabled_names = {f['name'] for f in formats if f['read'] or f['write']}
- hot_pivots = cfg.get('hot_pivots', []) or []
- for p in hot_pivots:
- if p not in enabled_names:
- raise SystemExit(
- f'hot_pivot {p!r} is not an enabled format (must have read or write enabled)'
- )
-
- catalog_names = {n for (_lbl, _rgb, _yuv, n) in pattern_catalog}
- patterns = cfg.get('patterns', []) or []
- for p in patterns:
- if p not in catalog_names:
- raise SystemExit(f'unknown pattern: {p!r} (known: {sorted(catalog_names)})')
-
- # An empty pattern list collapses pattern feature to 'off'.
- have_pattern = have_pattern and bool(patterns)
-
- return {
- 'have_pattern': have_pattern,
- 'have_convert': have_convert,
- 'hot_pivots': hot_pivots,
- 'patterns': patterns,
- 'formats': formats,
- 'pattern_catalog': pattern_catalog,
- }
-
-
-# === Emitters ===================================================================
-
-HEADER = '// Auto-generated by gen_pixpat.py. Do not edit by hand.'
-
-
-def emit_config_h(r: dict) -> str:
- return '\n'.join(
- [
- HEADER,
- '#pragma once',
- '',
- f'#define PIXPAT_FEATURE_PATTERN {1 if r["have_pattern"] else 0}',
- f'#define PIXPAT_FEATURE_CONVERT {1 if r["have_convert"] else 0}',
- '',
- ]
- )
-
-
-def _caps_row(f: dict, hot: set[str]) -> str:
- cells = [
- 'true,' if f['read'] else 'false,',
- 'true,' if f['write'] else 'false,',
- 'true,' if (f['name'] in hot) and f['read'] else 'false,',
- 'true' if (f['name'] in hot) and f['write'] else 'false',
- ]
- return f'\t{{ {cells[0]:<7}{cells[1]:<7}{cells[2]:<7}{cells[3]:<6}}}, // {f["name"]}'
-
-
-def emit_caps_inc(r: dict) -> str:
- out = [HEADER, '']
-
- # Per-format build capabilities, one row per FormatId (catalog
- # order). The FormatCaps schema and the size sanity-check live in
- # static source (pixpat_internal.h / pixpat.cpp); this file is pure
- # data. Consumers pull individual bools via .readable / .writable /
- # .hot_src / .hot_dst — member accesses on a constexpr array
- # element are themselves constant expressions, so they work as
- # `if constexpr` conditions and as non-type template arguments.
- out += [
- 'inline constexpr FormatCaps s_format_caps[] = {',
- '\t// readable writable hot_src hot_dst',
- ]
- hot = set(r['hot_pivots'])
- for f in r['formats']:
- out.append(_caps_row(f, hot))
- out.append('};')
- out.append('')
-
- # Per-pattern build capabilities, indexed by PatternId. An unknown
- # or disabled pattern name in pixpat_draw_pattern is an error, so
- # there's no default-fallback row.
- user_patterns = set(r['patterns'])
- out += [
- 'inline constexpr PatternCaps s_pattern_caps[] = {',
- '\t// enabled',
- ]
- for _label, _rgb, _yuv, str_name in r['pattern_catalog']:
- enabled = 'true' if str_name in user_patterns else 'false'
- out.append(f'\t{{ {enabled:<5} }}, // {str_name}')
- out.append('};')
- out.append('')
- return '\n'.join(out)
-
-
-# === CLI ========================================================================
-
-
-def main() -> int:
- ap = argparse.ArgumentParser()
- ap.add_argument('--config', type=Path, required=True)
- ap.add_argument('--format-catalog', type=Path, required=True, help='Path to format_catalog.h.')
- ap.add_argument(
- '--pattern-catalog', type=Path, required=True, help='Path to pattern_catalog.h.'
- )
- ap.add_argument('--out-config-h', type=Path)
- ap.add_argument('--out-caps-inc', type=Path)
- ap.add_argument(
- '--query',
- type=str,
- default=None,
- help='Print 0/1 for: feature_pattern, feature_convert, have_both (= pattern && convert).',
- )
- args = ap.parse_args()
-
- format_catalog = parse_format_catalog(args.format_catalog)
- pattern_catalog = parse_pattern_catalog(args.pattern_catalog)
- with args.config.open('rb') as fh:
- cfg = tomllib.load(fh)
- r = resolve(cfg, format_catalog, pattern_catalog)
-
- if args.query:
- flag = {
- 'feature_pattern': r['have_pattern'],
- 'feature_convert': r['have_convert'],
- 'have_both': r['have_pattern'] and r['have_convert'],
- }.get(args.query)
- if flag is None:
- raise SystemExit(f'unknown query: {args.query!r}')
- print(1 if flag else 0)
- return 0
-
- outs = [
- (args.out_config_h, emit_config_h(r)),
- (args.out_caps_inc, emit_caps_inc(r)),
- ]
- if any(o[0] is None for o in outs):
- raise SystemExit('all --out-* flags are required when not in --query mode')
- for path, content in outs:
- path.write_text(content)
- return 0
-
-
-if __name__ == '__main__':
- sys.exit(main())