#!/usr/bin/env bash

# stop script when a command returns with non-zero
set -e

# important names
fpath=""
fname=""
basename=""
build_dir="_pml_build"
builtins_ar="pml_builtins.a"
stdlib_dir="stdlib"

# get tools
POCAML="dune exec -- bin/main.exe"
LLC=llc
CC=cc
OCAMLC=ocamlc

KEEP_BUILD_DIR=false
COMPILE_C_LIB_ONLY=false
CLEAN_PREV_BUILD=false
RUN_AFTER_COMPILATION=false
SKIP_BUILD_C_LIB=false
TYPECHECK=false
POCAMLC_FLAGS=""

Usage() {
  echo
  echo "usage: pocamlc [otpions] [-f <path_to_pml_file>]"
  echo
  echo "options:"
  echo "-a	Only compile Pocaml's C static library."
  echo "-b	Use existing ./_pml_build and its files for build"
  echo "-c	Clean C object files from previous build before rebuild."
  echo "-d	Compile Pocaml C builtins with debugging information."
  echo "-r	Run the executable after compilation."
  echo "-s	Skip building Pocaml's C static library."
  echo "-t	Typecheck with ocamlc."
  echo "-x	Clean and remove all build artifacts."
  echo "-h	Display pocamlc usage."
  echo

  exit 0;
}

BuildCLib() {
    [ "$SKIP_BUILD_C_LIB" = true ] && return

    cd builtins
    ([ "$CLEAN_PREV_BUILD" = true ] || [ "$COMPILE_C_LIB_ONLY" = true ]) && make clean
    make PML_CFLAGS="${POCAMLC_FLAGS}"
    cp ${builtins_ar} ../${build_dir}
    cd ..

    [ "$COMPILE_C_LIB_ONLY" = true ] && exit 0
    echo
}

[ $# -eq 0 ] && Usage

# parse options and argument
while getopts 'abcdrstxf:h' opt; do
  case $opt in
    a)	# only compile pocaml C library into $build_dir
	COMPILE_C_LIB_ONLY=true
	BuildCLib
	;;
    b)	# keep &build_dir if already exists
	KEEP_BUILD_DIR=true
	;;
    c)	# clean C object files
	CLEAN_PREV_BUILD=true
	;;
    d) # compile with debugging info
	POCAMLC_FLAGS+=" -D BUILTIN_DEBUG"
	;;
    r) # run compiled executable
	RUN_AFTER_COMPILATION=true
	;;
    s)	# skip building pocaml C library
	SKIP_BUILD_C_LIB=true
	;;
    t)	# type check before compilation
	TYPECHECK=true
	;;
    f) # file
	fpath=$OPTARG
	fname=${fpath##*/}
	basename="${fname%.*}"
	;;
    x) # clean build artifacts
	rm -rf $build_dir
	dune clean
	cd builtins
	make clean
	cd ..
	exit
	;;
    h | *) # help
	Usage
	;;
  esac
done

# create build_dir
[ "$KEEP_BUILD_DIR" = false ] &&
(
    rm -rf $build_dir
    mkdir $build_dir
)

# if '-f' specified, copy source file to build_dir
# else, get input from stdin
if [[ $fpath ]]; then
  cp ${fpath} ${build_dir}/${fname}.orig
else
  fname="a.pml"
  fpath=${build_dir}/${fname}
  basename="${fname%.*}"
  cat > $fpath.orig
fi

# prepend stdlib
fname_with_stdlib=${build_dir}/${fname}
echo > $fname_with_stdlib
for stdlib_file in ${stdlib_dir}/**; do
  { printf "(* pocamlc: %s *)\n" $stdlib_file;
    cat $stdlib_file;
    printf "\n" >> $fname_with_stdlib;
  } >> $fname_with_stdlib
done
{ printf "(* pocamlc: %s *)\n" $fname;
  cat ${build_dir}/${fname}.orig
} >> $fname_with_stdlib


# typecheck with ocamlc
if [ $TYPECHECK = true ]; then
  cp builtins/builtins.ml ${build_dir}/
  cd $build_dir
  {
    cat builtins.ml
    printf "\n"
    cat $fname
  } > ${basename}.ml
  $OCAMLC -i ${basename}.ml
  rm -f builtins.ml ${basename}.ml
  cd ..
fi

# pocaml -> llvm
$POCAML -c ${build_dir}/${fname}> ${build_dir}/${basename}.ll

# build builtins C static library
BuildCLib

# link the generated llvm with builtin
cd ${build_dir}
$LLC -relocation-model=pic ${basename}.ll > ${basename}.s
$CC -o ${basename}.exe ${basename}.s ${builtins_ar}

# if '-r' specified, execute
[ "$RUN_AFTER_COMPILATION" = true ] && ./${basename}.exe

# exit successfully
exit 0
