-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
312 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
#!/usr/bin/env python3 | ||
# -*- coding: utf-8 -*- | ||
|
||
import json | ||
import platform | ||
import subprocess | ||
import sys | ||
|
||
# get/sanity check chapter number | ||
cc: str = sys.argv[1] | ||
chapter: int = int(sys.argv[2]) | ||
if chapter < 1 or chapter > 20: | ||
exit("Bad chapter number") | ||
|
||
# do we need rosetta? | ||
prefix = "" | ||
if platform.machine().lower() == "arm64": | ||
prefix = "arch -x86_64 " | ||
|
||
# define the stages we're going to test | ||
stages: list[str] = ["lex", "parse", "tacky", "validate", "codegen", "run"] | ||
if chapter == 1: | ||
# tacky and validate stages not added yet | ||
stages.remove("tacky") | ||
stages.remove("validate") | ||
elif chapter < 5: | ||
# TACKY stage added, validate not | ||
stages.remove("validate") | ||
elif chapter > 18: | ||
# don't test intermediate stages for chapters 19/20 | ||
stages = ["run"] | ||
|
||
# info about extra credit features | ||
ALL_EXTRA_CRED = { | ||
"bitwise": 3, | ||
"compound": 5, | ||
"increment": 5, | ||
"goto": 6, | ||
"switch": 8, | ||
"nan": 13, | ||
"union": 18, | ||
} | ||
|
||
|
||
def get_idx(flag: str) -> tuple[int, str]: | ||
# sort flags by chapter, use alphabetical order as tiebreaker | ||
return (ALL_EXTRA_CRED[flag], flag) | ||
|
||
|
||
all_implemented_extra_credit: tuple = tuple( | ||
sorted([f for f in ALL_EXTRA_CRED if ALL_EXTRA_CRED[f] <= chapter], key=get_idx) | ||
) | ||
|
||
|
||
# find set of all extra-credit flag combinations we should test | ||
extra_credit_combos: set[tuple] = { | ||
# none (empty set) | ||
(), | ||
# all | ||
all_implemented_extra_credit, | ||
} | ||
|
||
with open("./test_properties.json", encoding="utf-8") as f: | ||
extra_cred_flags = json.load(f)["extra_credit_tests"] | ||
for path, flags in extra_cred_flags.items(): | ||
# all keys in this dict start with chapter_N/ | ||
chapter_num = int(path.split("/")[0].removeprefix("chapter_")) | ||
if chapter_num <= chapter: | ||
extra_credit_combos.add(tuple(sorted(flags, key=get_idx))) | ||
|
||
|
||
for flags in sorted(extra_credit_combos): | ||
extra_cred_args: str = "" | ||
if flags: | ||
flag_str = " ".join("--" + f for f in flags) | ||
test_compiler_arg = ( | ||
"--extra-credit" if flags == all_implemented_extra_credit else flag_str | ||
) | ||
extra_cred_args = f"{test_compiler_arg} -- {flag_str}" | ||
print(f"Testing with extra credit features: {flag_str}") | ||
for stage in stages: | ||
print(f"Testing '{stage}' stage") | ||
cmd = f"{prefix}./test_compiler {cc} --chapter {chapter} --stage {stage} {extra_cred_args}" | ||
print(cmd) | ||
subprocess.run( | ||
cmd, | ||
shell=True, | ||
check=True, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
# 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 | ||
|
||
# NOTE: for chapters with extra-credit features we have two commits: | ||
# one for regular feature, one for extra credit. The '-n 1' options limits | ||
# us to the first (i.e. later i.e. extra credit) commit hash | ||
- name: Get commit hash | ||
run: | | ||
git log --grep "chapter $CHAPTER" -i --format='%H' -n 1 | ||
commit=$(git log --grep "chapter $CHAPTER" -i --format='%H' -n 1) | ||
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 }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
name: Build and test the compiler | ||
|
||
on: | ||
workflow_call: | ||
inputs: | ||
os: | ||
required: true | ||
type: string | ||
chapter: | ||
required: true | ||
type: number | ||
branch: | ||
type: string | ||
required: false | ||
default: main | ||
|
||
jobs: | ||
build: | ||
uses: ./.github/workflows/build.yaml | ||
with: | ||
chapter: ${{ inputs.chapter }} | ||
os: ${{ inputs.os }} | ||
branch: ${{ inputs.branch }} | ||
|
||
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.py | ||
sparse-checkout-cone-mode: false | ||
path: script | ||
|
||
- 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.py script to test each intermediate stage | ||
- name: Run the tests | ||
run: ./script/.github/run_tests.py nqcc/main.exe "${CHAPTER}" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
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: ./.github/workflows/build_and_test.yaml | ||
with: | ||
chapter: ${{ matrix.chapter }} | ||
os: ${{ matrix.os }} | ||
branch: ${{ github.ref_name }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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! | ||
``` | ||