diff --git a/.github/run-tests.sh b/.github/run-tests.sh new file mode 100755 index 0000000..d4e60dc --- /dev/null +++ b/.github/run-tests.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +# sanity check +if (( CHAPTER < 1 | CHAPTER > 20)); then + echo "Bogus chapter number" + exit 1 +fi + +# define the stages we're going to test +declare -a STAGES +if ((CHAPTER == 1)); then + # no --tacky or --validate stage yet + STAGES=(lex parse codegen run) +elif ((CHAPTER < 5)); then + # --tacky but not --validate + STAGES=(lex parse tacky codegen run) +elif ((CHAPTER < 19)); then + # all the stages! + STAGES=(lex parse tacky validate codegen run) +else + # don't run intermediate stages for optimization tests + STAGES=(run) +fi +readonly STAGES + +for stage in "${STAGES[@]}"; do + ./test_compiler "${CC}" --chapter "${CHAPTER}" --stage "${stage}" +done diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..e57d425 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,94 @@ +# Build tagged versions of the reference implementation compiler +name: Build the compiler + +on: + workflow_dispatch: + inputs: + chapter: + required: true + type: string + os: + required: true + default: ubuntu-latest + + workflow_call: + inputs: + chapter: + required: true + type: number + os: + type: string + required: true + default: ubuntu-latest + branch: + type: string + required: false + default: main + +env: + EXE_DIR: _build/default/bin/ + EXE_PATH: _build/default/bin/main.exe + +jobs: + + build: + runs-on: ${{ inputs.os }} + env: + CHAPTER: ${{ inputs.chapter }} + + steps: + + # first check out at specified branch so we can find commit hash for this chapter + - uses: actions/checkout@v4 + with: + repository: nlsandler/nqcc2 + ref: ${{ inputs.branch }} + fetch-depth: 0 # need this to get commit history + + - name: Get commit hash + run: | + git log --grep "chapter $CHAPTER:" -i --format='%H' + commit=$(git log --grep "chapter $CHAPTER:" -i --format='%H') + echo "commit=$commit" >> $GITHUB_ENV + + - name: Check out NQCC at chapter ${{ inputs.chapter }} + uses: actions/checkout@v4 + with: + repository: nlsandler/nqcc2 + ref: ${{ env.commit }} + + - name: Construct cache key + id: make-key + env: + runner_os: ${{ runner.os }} + run: | + commit=$(git rev-parse --short "$commit") + echo "cache-key=${runner_os}-${commit}-nqcc" >> $GITHUB_OUTPUT + + - name: Cache build result + id: cache-nqcc + uses: actions/cache@v4 + with: + path: ${{ env.EXE_PATH }} + key: ${{ steps.make-key.outputs.cache-key }} + + # skip building if we get a cache hit + - name: Set up OCaml + if: steps.cache-nqcc.outputs.cache-hit != 'true' + uses: ocaml/setup-ocaml@v2 + with: + ocaml-compiler: 4.14.x + # necessary to avoid random errors, see https://github.com/ocaml/setup-ocaml/issues/400 + dune-cache: false + + - name: Build it + if: steps.cache-nqcc.outputs.cache-hit != 'true' + run: | + opam install . --deps-only + opam exec -- dune build + + - name: Upload binary + uses: actions/upload-artifact@v4 + with: + name: nqcc-${{inputs.os}}-${{ inputs.chapter }} + path: ${{ env.EXE_PATH }} diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml new file mode 100644 index 0000000..ae904e2 --- /dev/null +++ b/.github/workflows/build_and_test.yaml @@ -0,0 +1,60 @@ +name: Build and test the compiler + +on: + workflow_call: + inputs: + os: + required: true + type: string + chapter: + required: true + type: number + +jobs: + build: + uses: nlsandler/nqcc2/.github/workflows/build.yaml@main + with: + chapter: ${{ inputs.chapter }} + os: ${{ inputs.os }} + + test: + runs-on: ${{ inputs.os }} + needs: [build] + env: + CHAPTER: ${{ inputs.chapter }} + steps: + + - name: Check out tests + uses: actions/checkout@v4 + with: + repository: nlsandler/writing-a-c-compiler-tests + ref: complete-test-suite # TODO use main once this is merged + + - name: Check out test runner script + uses: actions/checkout@v4 + with: + repository: nlsandler/nqcc2 + sparse-checkout: | + .github/run-tests.sh + sparse-checkout-cone-mode: false + + - name: Download the compiler + uses: actions/download-artifact@v4 + with: + name: nqcc-${{inputs.os}}-${{ inputs.chapter }} + path: nqcc + + # make NQCC executable + - run: chmod u+x nqcc/main.exe + + # Invoke the run-tests script to test each intermediate stage + + # use Rosetta on macOS since that's what most readers are doing + - name: Run the tests (Apple Silicon) + if: runner.arch == 'ARM64' + run: arch -x86_64 ./github/run-tests.sh + + - name: Run the tests (x86-64) + if: runner.arch != 'ARM64' + run: ./github/run-tests.sh + diff --git a/.github/workflows/test_each_chapter.yaml b/.github/workflows/test_each_chapter.yaml new file mode 100644 index 0000000..d35495f --- /dev/null +++ b/.github/workflows/test_each_chapter.yaml @@ -0,0 +1,18 @@ +name: Build and test each chapter + +on: + workflow_dispatch: + +jobs: + + + build-and-test: + strategy: + fail-fast: true + matrix: + os: [macos-latest, ubuntu-latest] + chapter: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] + uses: nlsandler/nqcc2/.github/workflows/build_and_test.yaml@main + with: + chapter: ${{ matrix.chapter }} + os: ${{ matrix.os }} diff --git a/README.md b/README.md new file mode 100644 index 0000000..6b66b92 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# NQCC2, the Not-Quite-C Compiler + +**CAUTION: FREQUENT FORCE PUSHES** + +This is the reference implementation for the upcoming book [Writing a C Compiler](https://nostarch.com/writing-c-compiler), a hands-on guide to writing your own compiler for a big chunk of C. + +Each commit corresponds to one chapter in the book, to illustrate what needs to change at each step. Because commits are structured this way, this repository sees frequent rebases and force pushes - fork with caution! + +This implementation is still a work in progress - the functionality is complete, but needs more work on the readability front. + +# Building the Compiler + +This compiler is written in OCaml. Building it requires opam, the OCaml package manager (installation instructions [here](https://opam.ocaml.org/doc/Install.html)). + +Then do: + +``` +git clone https://github.com/nlsandler/nqcc2.git +cd nqcc2 +opam install . --deps-only +dune build +``` + +This puts the executable at `_build/default/bin/main.exe`. + +# Usage Example + +Assume we have this source file at ~/hello_world.c: +```c +int puts(char *c); + +int main(void) { + puts("Hello, world!"); +} +``` + +To compile and run it: +``` +$ _build/default/bin/main.exe ~/hello_world.c +$ ~/hello_world +Hello, World! +``` + + + + + +