#! /bin/sh
set -euf

## SYNOPSIS
# [debug=true] build compile SRCFILE DSTFILE
# [debug=true] build deps SRCFILE...
build() {

  ## Load macro definitions.
  defmacro_pattern='## usage: \(.*\) -> \([^ ]\+\) \(.*\)'
  script='s/^'"$defmacro_pattern"'$/\2_macro='"'"'\1'"'"'/p' \
    setf defmacros '$(sed -n "$script" "$%s")' 0
  eval "$defmacros"

  ## Dispatch.
  case "$1" in
    compile) build_compile "$2" "$3";;
    deps) shift; build_deps "$@";;
    *) echo "build: $1: bad command" >&2; return 23;;
  esac
}

###
### macros
###

## usage: #@include \([0-9A-Za-z_]\+\) -> build_include \1 \2
build_include() {
  if buildcache_has "#@include:$2"; then
    printf '%da\\\n##include %s: already done\n' $1 $2
  else
    buildcache_add "#@include:$2"
    cat<<EOF
$1a\\
# begin $2
$1r$(build_resolve $2)
$1a\\
# end $2
EOF
  fi
}

## usage: #@strict -> build_strict_mode \1
build_strict_mode() { cat<<EOF
$1a\\
set -euf\\
set -o posix || :
EOF
}

## usage: #@info -> build_info \1
build_info() {
    gitinfo=$(git describe --always --dirty --abbrev=0 2>/dev/null || :)
cat<<EOF
$1a\\
# this file was generated by //ship/build\\
#   build date: $(date -u --rfc-3339=s)\\
#   git describe: ${gitinfo:-not under version control}
EOF
}

## usage: #@mainifyme\( \([A-Za-z_][A-Za-z0-9_]*\)\)\? -> build_mainifyme \1 \3
build_mainifyme() {
  mainifyme_name="${2:-main}"
  cat<<EOF
  $1a\\
$mainifyme_name(){
  \$a\\
}\\
$mainifyme_name "\$@"
EOF
}

###
### main subroutines
###

## usage: build_compile SRCFILE DSTFILE
build_compile() {

  script='s/^'"$defmacro_pattern"'$/\2_macro/p' \
    setf macro_names '$(sed -n "$script" "$%s")' 0

  setf unexpanded_macros_pattern \
    '$(make_unexpanded_macros_pattern $%s)' macro_names

  script='
      s/^'"$defmacro_pattern"'$/s:^ *\\([0-9]\\+\\) \1$:\2 \3:/p
      $a\
t;s:^ *\\([0-9]\\+\\) .*:echo \\1p:
      ' \
    setf input_parser '$(sed -n "$script" "$%s")' 0

  SRCFILE="$1" setf src '$(cat "$%s")' SRCFILE

  buildcache_initialize "$2"

  while echo "$src" | grep -q "$unexpanded_macros_pattern"; do
    setf sedgen '$(echo "$%s" | nl -b a -s \  | sed "$%s")' src input_parser
    setf sedscript '$(eval "$%s")' sedgen
    setf src '$(echo "$%s" | sed -n "$%s")' src sedscript
  done

  buildcache_finalize

  echo "$src" > "$2"
  chmod +x "$2"
}

## usage: build_deps SRCFILE...
# Print all the dependencies of SRCFILE... to stdout. (alphabetic order)
build_deps() {
  while test $# -gt 0; do
    deps="$(
      for f; do
        for d in $(sed -n 's:^'"$build_include_macro"'$:\1:p' "$f"); do
          build_resolve $d
        done
      done
    )"
    set -- $deps
    if test $# -gt 0; then
      echo "$deps"
    fi
  done | sort | uniq
}

###
### misc utilities
###

## usage: build_resolve LIBNAME
build_resolve() {
  echo "$BUILD_PATH" | tr : \\n |
    xargs -I: printf '%s/%s\n' : "$1" |
    xargs -I: ls -d : 2>/dev/null |
    head -n 1 |
    grep . ||
  {
    echo "build resolve: $1: library not found" >&2
    return 23
  }
}

## usage: make_unexpanded_macros_pattern BUILD_DIRECTIVES...
make_unexpanded_macros_pattern() {
  echo "^\\($(
    for macro; do
      eval echo \"\$$macro\"
    done |
      tr \\n \| |
      sed 's/|/\\|/'
  )\\)$"
}

## usage: setf NAME FMT [ARG...]
setf() {
  value_script="$(shift; printf "$@")"

  eval "$1=$value_script"

  if is_debug_mode; then
    eval 'echo "$1=\"$value_script\""'
    eval 'echo "'"\$$1"'"' | nl -b a
  fi >&2
}

## usage: is_debug_mode
is_debug_mode() {
  test "${debug-false}" = true
}

###
### buildcache utilities
###

## usage: buildcache_initialize DESTFILE
buildcache_initialize() {
  buildcache="$1.buildcache"
  cat /dev/null > "$buildcache"
}

## usage: buildcache_finalize
buildcache_finalize() {
  if is_debug_mode; then
    rm "$buildcache"
  fi
}

## usage: buildcache_has BRE
# Check if buildcache contains a line matching BRE.
buildcache_has() {
  grep -q "^$1\$" "$buildcache"
}

## usage: buildcache_add LINE
# Add LINE to buildcache.
buildcache_add() {
  echo "$1" >> "$buildcache"
}



###
### main invocation
###

if echo "$0" | grep -q '^\(.*/\)\?build$'; then
  build "$@"
fi