FiWiManager/fiwi/reflex_lang.py

275 lines
9.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Acroname **on-device Reflex** (embedded language on BrainStem modules).
This module does **not** run Reflex on the hub; it helps you **compile** ``.reflex`` sources to
``.map`` files using the BrainStem Development Kit **arc** compiler. Load maps with Acroname
**ReflexLoader** or **HubTool** (see their docs).
The pip ``brainstem`` package is the host Python API; **arc** ships with the separate dev kit.
"""
from __future__ import annotations
import os
import shutil
import subprocess
import sys
from pathlib import Path
# Primary language reference (path may move; search Acroname “Reflex language” if stale).
REFLEX_DOC_URL = "https://acroname.com/reference/brainstem/reflex/"
# Full SDK tarball includes ``bin/arc``, Reflex API ``*.reflex`` headers, HubTool, etc.
BRAINSTEM_SDK_DOWNLOAD_URL = "https://acroname.com/software/brainstem-development-kit"
def _dedupe_paths(paths: list[Path]) -> list[Path]:
seen: set[str] = set()
out: list[Path] = []
for p in paths:
try:
key = str(p.resolve())
except OSError:
key = str(p)
if key not in seen:
seen.add(key)
out.append(p)
return out
def _env_path_list(key: str) -> list[Path]:
raw = os.environ.get(key, "").strip()
if not raw:
return []
return [Path(p).expanduser() for p in raw.split(os.pathsep) if p.strip()]
def _fiwi_repo_brainstem_sdk_roots() -> list[Path]:
"""
Optional checkout next to this repo: ``FiWiManager/brainstem_sdk`` (and nested ``BrainStem2``
after extracting the official ``.tgz``).
"""
root = Path(__file__).resolve().parent.parent
out: list[Path] = []
b = root / "brainstem_sdk"
if b.is_dir():
out.append(b)
inner = b / "BrainStem2"
if inner.is_dir():
out.append(inner)
return out
def brainstem_dev_roots() -> list[Path]:
"""Likely BrainStem2 checkout or SDK install roots (for ``bin/arc`` and reflex API includes)."""
roots: list[Path] = []
for key in (
"BRAINSTEM2_DEV_ROOT",
"BRAINSTEM_DEV_ROOT",
"ACRONAME_BRAINSTEM_ROOT",
"BRAINSTEM_ROOT",
"REFLEX_SDK_ROOT",
"FIWI_BRAINSTEM_SDK",
):
roots.extend(_env_path_list(key))
roots.extend(_fiwi_repo_brainstem_sdk_roots())
return _dedupe_paths(roots)
def _likely_sdk_roots_under_home() -> list[Path]:
"""Common extract locations when the user has not set env vars (Linux/macOS)."""
home = Path.home()
names = (
"BrainStem2",
"brainstem2",
"BrainStemDevelopment",
"brainstem-development-kit",
"acroname-brainstem",
)
out: list[Path] = []
for name in names:
p = home / name
if p.is_dir():
out.append(p)
# Nested layout: ~/Downloads/BrainStem2-2.12.2-Linux/BrainStem2
try:
for child in home.iterdir():
if not child.is_dir():
continue
if "BrainStem" in child.name or "brainstem" in child.name.lower():
nested = child / "BrainStem2"
if nested.is_dir():
out.append(nested)
out.append(child)
except OSError:
pass
return _dedupe_paths(out)
def default_reflex_include_dirs() -> list[Path]:
"""
``-I`` paths for ``#include <aUSBHub3p.reflex>``-style API headers.
Set ``FIWI_REFLEX_INCLUDE`` to ``os.pathsep``-separated directories (required if arc is not
configured elsewhere). Optionally set ``BRAINSTEM2_DEV_ROOT`` to a dev kit tree containing
``api/lib/BrainStem2`` (or similar) with ``*.reflex`` API files.
"""
inc: list[Path] = []
inc.extend(_env_path_list("FIWI_REFLEX_INCLUDE"))
for root in brainstem_dev_roots():
for sub in (
"api/lib/BrainStem2",
"lib/BrainStem2",
"BrainStem2/api/lib/BrainStem2",
"include/reflex",
):
p = root / sub
if p.is_dir():
inc.append(p)
return _dedupe_paths(inc)
def find_arc() -> str | None:
"""Return executable path for **arc**, or None."""
explicit = os.environ.get("FIWI_REFLEX_ARC", "").strip()
if explicit:
p = Path(explicit).expanduser()
if p.is_file() and os.access(p, os.X_OK):
return str(p)
return None
w = shutil.which("arc")
if w:
return w
roots = _dedupe_paths(brainstem_dev_roots() + _likely_sdk_roots_under_home())
for root in roots:
cand = root / "bin" / "arc"
if cand.is_file() and os.access(cand, os.X_OK):
return str(cand)
return None
def print_arc_missing_help() -> None:
print(
"The Reflex compiler **arc** is not on PATH and was not found under common install paths.\n"
"\n"
"It is **not** installed by ``pip install brainstem``. Get the BrainStem **Development Kit** "
"(Linux .tgz), extract it, then either:\n"
"\n"
f" • Add the kits ``bin`` to PATH (contains ``arc``, ``ReflexLoader``, ``HubTool``), or\n"
" • export BRAINSTEM2_DEV_ROOT=/path/to/extracted/BrainStem2\n"
" (FiWi searches $BRAINSTEM2_DEV_ROOT/bin/arc and api/lib/BrainStem2 for includes), or\n"
" • export FIWI_REFLEX_ARC=/full/path/to/arc\n"
"\n"
f"Download: {BRAINSTEM_SDK_DOWNLOAD_URL}\n"
"\n"
"If you keep the SDK under ``FiWiManager/brainstem_sdk``, extract the Linux ``.tgz`` there "
"so you get ``brainstem_sdk/BrainStem2/bin/arc`` (the ``api/`` tree alone does not ship "
"``arc``).\n"
"\n"
"Check: python3 fiwi.py reflex which\n",
file=sys.stderr,
flush=True,
)
def reflex_compile(
source: Path,
output_map: Path | None,
*,
extra_argv: list[str] | None = None,
verbose: bool = False,
) -> int:
"""
Run **arc** to compile ``source`` → ``output_map`` (default: same basename, ``.map``).
``extra_argv`` are appended before the output/source (for unusual arc builds). Returns
subprocess exit code.
"""
arc = find_arc()
if not arc:
print("fiwi reflex: no 'arc' executable.", file=sys.stderr, flush=True)
print_arc_missing_help()
return 1
src = source.expanduser().resolve()
if not src.is_file():
print(f"fiwi reflex: source not found: {src}", file=sys.stderr, flush=True)
return 1
out = (
output_map.expanduser().resolve()
if output_map is not None
else src.with_suffix(".map")
)
includes = default_reflex_include_dirs()
argv: list[str] = [arc]
for d in includes:
argv.extend(["-I", str(d.resolve())])
if extra_argv:
argv.extend(extra_argv)
argv.extend(["-o", str(out), str(src)])
if verbose:
print(subprocess.list2cmdline(argv), flush=True)
return subprocess.run(argv, cwd=str(src.parent)).returncode
def print_reflex_cli_help() -> None:
print(
"On-device Reflex (Acroname) — compile only; load .map with ReflexLoader / HubTool.\n"
"\n"
"Usage:\n"
" fiwi.py reflex help | doc | which\n"
" fiwi.py reflex compile <source.reflex> [output.map] [-- <extra arc args...>]\n"
"\n"
"Environment:\n"
" FIWI_REFLEX_ARC Full path to arc (if not on PATH)\n"
" FIWI_REFLEX_INCLUDE Include dirs for aUSBHub*.reflex APIs (os.pathsep-separated)\n"
" BRAINSTEM2_DEV_ROOT BrainStem Development Kit root (finds bin/arc, api/lib/BrainStem2)\n"
" REFLEX_SDK_ROOT, FIWI_BRAINSTEM_SDK — same idea as BRAINSTEM2_DEV_ROOT\n"
"\n"
f"SDK (includes arc): {BRAINSTEM_SDK_DOWNLOAD_URL}\n"
"\n"
"Example:\n"
" export BRAINSTEM2_DEV_ROOT=~/BrainStem2\n"
" fiwi.py reflex compile reflex/inrush.reflex reflex/inrush.map\n"
" ./scripts/compile_inrush_reflex.sh\n"
f"\nLanguage reference: {REFLEX_DOC_URL}\n"
)
def cli_main(argv: list[str]) -> int:
"""Entry for ``fiwi.py reflex …`` (invoked before BrainStem Python load)."""
if not argv or argv[0].lower() in ("help", "-h", "--help"):
print_reflex_cli_help()
return 0
cmd = argv[0].lower()
if cmd == "doc":
print(REFLEX_DOC_URL, flush=True)
return 0
if cmd == "which":
arc = find_arc()
print(f"arc: {arc or '(not found)'}", flush=True)
incs = default_reflex_include_dirs()
if incs:
print("include (-I):", flush=True)
for d in incs:
print(f" {d}", flush=True)
else:
print("include (-I): (none — set FIWI_REFLEX_INCLUDE or BRAINSTEM2_DEV_ROOT)", flush=True)
return 0
if cmd != "compile":
print(f"fiwi reflex: unknown subcommand {argv[0]!r}", file=sys.stderr, flush=True)
print("Try: fiwi.py reflex help", file=sys.stderr, flush=True)
return 2
rest = argv[1:]
if not rest:
print("fiwi reflex: compile requires <source.reflex>", file=sys.stderr, flush=True)
return 2
extra: list[str] = []
for i, a in enumerate(rest):
if a == "--":
extra = rest[i + 1 :]
rest = rest[:i]
break
src_s = rest[0]
out_s = rest[1] if len(rest) >= 2 else None
verbose = os.environ.get("FIWI_REFLEX_VERBOSE", "").strip() in ("1", "yes", "true")
return reflex_compile(Path(src_s), Path(out_s) if out_s else None, extra_argv=extra or None, verbose=verbose)