tmpvar.com wrench logo

Using C/C++ as a scripting language (Part 1)

Those familiar with web development will probably have used some form of hot module reloading. This dramatically reduces the development to test iteration time. It is also very common to run a Javascript file via node path/to/file.js or similar. Since I've been preferring C/C++ lately, how can these conveniences be ported to a lower-level language?

Typically when starting a C/C++ project there is this annoying step of setting up a build system. Makefiles, CMake, Ninja, Gyp, Gn, Bake, Meson, etc, etc... Many of these do not play well together and encourage you to buy fully into them to manage dependencies and do a bunch of things that are not even remotely needed to solve the problem at hand.

So what is the problem at hand? Lowering the friction between writing a C file, building, and launching it. I want it to feel effortless to create a new c project.

Goals:

First round: Bash scripts

~/bin/c.sh

#!/usr/bin/bash
CPATH=$(dirname "${BASH_SOURCE[0]}")
SRC=$1
DST=$(mktemp)
EXT="${SRC##*.}"

# Allow the source file to include some C flags that is requires
FOUND_CFLAGS=$(
 grep "#pragma CFLAGS=" $SRC \
 | sed 's/#pragma CFLAGS=//' \
 | paste -s -d " " -
)

CFLAGS="$CFLAGS $FOUND_CFLAGS"
CC=clang++
if [ ${SRC: -2} == ".c" ]; then
  CC=clang
fi

$CC $SRC -O3 -g $CFLAGS -o $DST &&
chmod +x $DST &&
$DST ${@:2} &&
rm $DST

in ~/.bashrc

# compile and run a .c/cpp file
c() {
 ~/bin/c.sh $@
}

# recompile + relaunch a .c/cpp file
cwatch() {
 rp=$(realpath $1)
 rd=$(dirname $rp)

 # Note: you might want to add --poll 100ms if you are working under WSL
 watchexec --clear=reset --restart --poll 100ms -w $rd "bash ~/bin/c.sh $@"
}

Usage:

c path/to/file.{c,cpp}
cwatch path/to/file.{c,cpp}

Issues: