# gasm-devkit: developer tooling for Go's Plan 9 assembler (GAsm).

binary := "gasm"
package := "./cmd/gasm"

# What the test and bench recipes sweep. The whole suite runs, so the
# packages that need hardware and the CLI glue stay under the gates.
packages := "./..."

# The coverage floor is computed over these product packages only, so the
# hardware-bound (debug) and the CLI glue (cmd/gasm) do not dilute the
# number. Keep this equal to the Coverage gate in .gitea/workflows/test.yml.
coverpkg := "./arch/...,./asm/...,./ast/...,./disasm/...,./format/...,./lexer/...,./lint/...,./lsp/...,./parser/...,./token/...,./verify/..."

bindir := env_var_or_default("BINDIR", env_var("HOME") / ".local" / "bin")

default:
    @just --list

# Compile. Zero errors, zero warnings.
build:
    CGO_ENABLED=0 go build -ldflags "-s -w" -o bin/{{binary}} {{package}}

# The test gate: the suite, no cache, the coverage floor.
test:
    #!/usr/bin/env perl
    my $coverpkg = q({{coverpkg}});
    system(q{go}, q{test}, q{-count=1}, q{-timeout}, q{30m},
      q{-coverprofile}, q{coverage.out}, q{-coverpkg}, $coverpkg,
      qw({{packages}})) == 0
      or die qq{the test suite failed\n};
    open(my $c, q{-|}, q{go}, q{tool}, q{cover}, q{-func=coverage.out}) or die qq{cover: $!};
    my $total;
    while (my $l = <$c>) { $total = $1 if $l =~ m{^total:\s+\S+\s+([0-9.]+)%} }
    close($c);
    die qq{no total line in coverage.out\n} unless defined $total;
    printf qq{Total coverage: %s%%\n}, $total;
    exit($total < 80 ? 1 : 0);

# The same suite under the race detector. The expensive one.
race:
    go test -race -count=1 -timeout 30m {{packages}}

# Fast scoped run for iterating. This is the one that runs after every edit.
unit pkgs=packages run=".*":
    go test {{pkgs}} -run '{{run}}'

# Time-boxed fuzz of one target in one package. The package is required; never a gate.
fuzz target pkg fuzztime="60s":
    go test -run '^$' -fuzz '{{target}}' -fuzztime={{fuzztime}} {{pkg}}

# Benchmarks. On an idle machine only.
bench pkgs=packages:
    go test -run '^$' -bench=. -benchmem -count=5 {{pkgs}}

# Format in place.
fmt:
    gofmt -w .

# Zero diff. Prints nothing when everything is formatted.
fmt-check:
    #!/usr/bin/env perl
    open(my $g, q{-|}, q{gofmt}, q{-l}, q{.}) or die qq{gofmt: $!};
    my @bad = <$g>;
    close($g);
    print @bad;
    exit(@bad ? 1 : 0);

# Both static gates: go vet and go fix -diff.
vet:
    go vet ./...
    go fix -diff ./...

# The definition of done, in one command. Once per task, never per edit.
gates: build fmt-check vet test race

# Build artefacts only, not the installed binary.
clean:
    rm -rf bin/ coverage.out

# Build, then copy the binary into bindir.
install: build
    install -d "{{bindir}}"
    install -m 755 bin/{{binary}} "{{bindir}}/{{binary}}"

# Remove the installed binary.
uninstall:
    rm -f "{{bindir}}/{{binary}}"

# Run the program. The flag is there because `go run` does not stamp the build otherwise.
run *ARGS:
    go run -buildvcs=true {{package}} {{ARGS}}

# Run with watch or hot reload, where the project has one.
dev:
    go run -buildvcs=true {{package}}

# Regenerate the architecture instruction tables from the Go toolchain source. Not a gate.
gen:
    go run _gen/gen.go
    gofmt -w arch/
