ajhahn.de
← Flash
Shell 150 lines
# Flash shell helpers. Source from ~/.zshrc:
#   [[ -f /Users/antonhahn/Flash/flash.env.zsh ]] && source /Users/antonhahn/Flash/flash.env.zsh
#
# Public interface — one verb dispatcher:
#   flash build|test|gates|run|emit|tree|fmt|tokens|lsp|version|clean|help
#
#   flash build                 build flashc (stage0 seed) into zig-out/bin
#   flash test [suite]          run the host suite, or one of:
#                               std | selfhost | flash | lsp | driver
#   flash gates                 the full pre-commit gate: test + fixpoint + diff-corpus
#   flash run <file>            transpile a .flash file to stdout
#   flash emit <file> <out>     transpile a .flash file into <out>.zig
#   flash tree <src> <out>      transpile a whole tree, mirroring relative paths
#   flash fmt [--check] <file>  reformat a .flash file in place
#   flash tokens <file>         dump the token stream for a .flash file
#   flash lsp                   build flashd, the language server
#   flash version               print the flashc version
#   flash clean                 remove .zig-cache and zig-out
#   flash help                  this list
#
# run/emit/tree/fmt/tokens go through flashc-stage1 — the live, self-hosted
# compiler. The stage0 seed in zig-out/bin/flashc is frozen at the v0.5
# bootstrap surface and rejects newer grammar, so it is the wrong binary for
# day-to-day source work.

# Directory of this file, captured at source time (%x still points at the
# file being sourced here; inside a function it would name the function).
typeset -g _FLASH_DIR="${${(%):-%x}:A:h}"

typeset -g _FLASH_RED=$'\033[0;31m'
typeset -g _FLASH_GREEN=$'\033[0;32m'
typeset -g _FLASH_NC=$'\033[0m'

_flash_err() { print -u2 -- "${_FLASH_RED}$*${_FLASH_NC}"; }
_flash_ok()  { print    -- "${_FLASH_GREEN}$*${_FLASH_NC}"; }
# Run argv from the project root in a subshell, leaving the caller's cwd alone.
_flash_root() { ( cd "$_FLASH_DIR" && "$@" ); }

# Build stage1 (quiet on success, build errors still reach stderr) and exec
# flashc-stage1 with the given arguments.
_flash_stage1() {
  _flash_root sh -c 'zig build stage1 >/dev/null && exec ./zig-out/bin/flashc-stage1 "$@"' _ "$@"
}

# Absolutize every non-flag argument, so file paths survive the cd to the
# project root. Result in $reply.
_flash_abs() {
  reply=()
  local a
  for a in "$@"; do
    if [[ "$a" == -* ]]; then reply+=("$a"); else reply+=("${a:A}"); fi
  done
}

_flash_help() {
  print -- "flash — dev helpers for the Flash compiler"
  print -- ""
  print -- "  flash build                 build flashc (stage0 seed) into zig-out/bin"
  print -- "  flash test [suite]          host suite, or: std|selfhost|flash|lsp|driver"
  print -- "  flash gates                 test + fixpoint + diff-corpus"
  print -- "  flash run <file>            transpile a .flash file to stdout"
  print -- "  flash emit <file> <out>     transpile a .flash file into <out>.zig"
  print -- "  flash tree <src> <out>      transpile a whole tree (mirrors paths)"
  print -- "  flash fmt [--check] <file>  reformat a .flash file in place"
  print -- "  flash tokens <file>         dump the token stream"
  print -- "  flash lsp                   build flashd, the language server"
  print -- "  flash version               print the flashc version"
  print -- "  flash clean                 remove .zig-cache and zig-out"
  print -- "  flash help                  this list"
  print -- ""
  print -- "  run/emit/tree/fmt/tokens use flashc-stage1 (the live compiler);"
  print -- "  --anchors / --plain-diagnostics pass through to it."
}

flash() {
  emulate -L zsh
  local verb="${1:-help}"
  (( $# )) && shift
  case "$verb" in
    build)  _flash_root zig build "$@" ;;
    test)
      case "${1:-}" in
        std|selfhost|flash|lsp|driver)
          local suite="$1"; shift
          _flash_root zig build "test-$suite" "$@"
          ;;
        *) _flash_root zig build test "$@" ;;
      esac
      ;;
    gates)
      _flash_root zig build test &&
      _flash_root zig build fixpoint &&
      _flash_root zig build diff-corpus &&
      _flash_ok "gates green: test + fixpoint + diff-corpus"
      ;;
    run)
      [[ -z "$1" ]] && { _flash_err "usage: flash run <file.flash> [flags]"; return 1; }
      _flash_abs "$@"; _flash_stage1 "${reply[@]}"
      ;;
    emit)
      (( $# < 2 )) && { _flash_err "usage: flash emit <file.flash> <out.zig> [flags]"; return 1; }
      _flash_abs "$@"; _flash_stage1 "${reply[1]}" -o "${reply[2]}" "${reply[@]:2}"
      ;;
    tree)
      (( $# < 2 )) && { _flash_err "usage: flash tree <srcdir> <outdir> [flags]"; return 1; }
      _flash_abs "$@"; _flash_stage1 build "${reply[@]}"
      ;;
    fmt)
      [[ -z "$1" ]] && { _flash_err "usage: flash fmt [--check] <file.flash>"; return 1; }
      _flash_abs "$@"; _flash_stage1 fmt "${reply[@]}"
      ;;
    tokens)
      [[ -z "$1" ]] && { _flash_err "usage: flash tokens <file.flash>"; return 1; }
      _flash_abs "$@"; _flash_stage1 --dump-tokens "${reply[@]}"
      ;;
    lsp)    _flash_root zig build lsp && _flash_ok "flashd built into zig-out/bin" ;;
    version)
      _flash_root sh -c 'zig build >/dev/null && exec ./zig-out/bin/flashc --version'
      ;;
    clean)  _flash_root rm -rf .zig-cache zig-out && _flash_ok "cleaned .zig-cache + zig-out" ;;
    help|-h|--help) _flash_help ;;
    *) _flash_err "unknown verb: $verb"; _flash_help; return 1 ;;
  esac
}

# Warn once at source time when the installed Zig disagrees with the pin —
# build.zig would reject it anyway, this just says so up front.
() {
  local want have
  [[ -r "$_FLASH_DIR/.zigversion" ]] || return 0
  want="$(<"$_FLASH_DIR/.zigversion")"
  have="$(command zig version 2>/dev/null)" || return 0
  [[ "$have" == "$want" ]] ||
    print -u2 -- "${_FLASH_RED}flash: zig $have installed, but the project pins $want (.zigversion)${_FLASH_NC}"
}

# Verb completion (only when the completion system is initialized).
if (( $+functions[compdef] )); then
  _flash_verbs() {
    if (( CURRENT == 2 )); then
      compadd -- build test gates run emit tree fmt tokens lsp version clean help
    elif [[ "${words[2]}" == "test" ]] && (( CURRENT == 3 )); then
      compadd -- std selfhost flash lsp driver
    else
      _files
    fi
  }
  compdef _flash_verbs flash
fi