Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 28 additions & 6 deletions .deslop.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,44 @@
# Rust/C#/Python/Dart, so this gates the Rust workspace (the C runtime is out
# of scope here).
# Docs: https://deslop.live/docs/for-ai/
# Wired into `make ci` via the `deslop` target ([CI-DESLOP]); CI fails when
# measured duplication exceeds the ceiling below (deslop exits 3).
[threshold]
# Ratcheted DOWN to 6.5% (2026-06-30) per project directive. Was 9.5%; measured
# duplication is driven below this ceiling by deduping real code clones and
# scoping the gate to hand-written product code (see `exclude` below). Ratchet
# DOWN only as duplication is removed — worst real-code offenders are codegen's
# pattern.rs/effects.rs and the per-file type-checker test harnesses.
max_duplication_percent = 6.5
# Ceiling = 6.1% (2026-07-12). Measured product-code duplication is ~6.10%
# after a round of genuine dedup: the three AST expression visitors
# (osprey-codegen `freevars`/`effects`, osprey-lsp `analysis`) now share one
# `osprey_ast::walk_each` collection-walker instead of each carrying its own
# `walk_all`/`walk_named`/`walk_fields` trio; `wire.rs`'s request-field
# accessors share a `nested` two-level lookup; and `default/lower.rs` folds its
# "collect children of a kind, lower each" sites into one `map_of_kind`. 8.0%
# remains the AUTHORISED HARD CAP — never raise above it; this ceiling sits well
# below that and ratchets DOWN only as duplication is removed. Worst remaining
# offenders are the `builtin_docs_*` documentation TABLES (repeated data rows,
# not logic — see the [category=data] note deslop prints) and structural-only
# CST-walk shapes that don't factor without obscuring intent.
max_duplication_percent = 6.1

[defaults]
exclude = [
# Test code, in every layout the repo uses. The gate scopes to hand-written
# PRODUCT code; test suites intentionally repeat setup/fixture scaffolding
# (that repetition is what keeps each test independently readable), so
# deduping them trades clarity for a metric. Covers Rust `tests/`, the VS Code
# extension's `test/suite/` (DAP harness + e2e), and any `*.test.ts`.
"**/tests/**",
"**/test/**",
"**/*.test.ts",
# Vendored, generated tree-sitter grammar bindings — not hand-written.
"tree-sitter-osprey/**",
# Cross-language benchmark baselines: each case is a standalone single-file
# program compiled in isolation by benchmarks/run.sh (no shared crate exists
# to factor into), so the identical scaffolding across cases is intrinsic to
# the benchmark design, not a refactorable product-code clone.
"benchmarks/**",
# One-off website build/generation tooling (e.g. add-ml-twins.py, which emits
# the ML twin of each Default snippet for the docs). Same category as the
# benchmark baselines above: standalone generator scripts, not the shipped
# compiler product code the gate exists to guard. Their per-case scaffolding
# is intrinsic to a code generator, not a refactorable product clone.
"website/scripts/**",
]
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ jobs:
sudo apt-get install -y jq xvfb
cargo install cargo-llvm-cov --locked || true

# [CI-DESLOP] The duplication gate. `make lint` runs the `deslop` target,
# which enforces the ceiling in .deslop.toml (deslop exits 3 when over).
# Installed from the Nimblesite Homebrew tap so the gate is ENFORCED here;
# the Makefile only skips it when the binary is entirely absent.
- name: Install deslop (duplication gate)
uses: Homebrew/actions/setup-homebrew@master
- name: Install deslop
run: brew install nimblesite/tap/deslop

# [SWR-VERSION-MANIFEST] keep shipwright.json valid against the in-repo
# Shipwright schema. Do not use @nimblesite/shipwright-validate-manifest:
# published versions currently ship without their bundled schemas.
Expand Down
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@
"rust-analyzer.check.command": "clippy",
"rust-analyzer.check.allTargets": true,
"basilisk.enabled": true,
"basilisk.uv.enabled": true
"basilisk.uv.enabled": true,
"deslop.topOffenders.groupBy": "type"
}
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

This file provides guidance for agents when working with code in this repository.

⚠️ NEVER ASk THE USER QUESTIONS! USER YOUR JUDGEMENT. ACT AUTONOMOUSLY ⚠️
⚠️ NEVER ASK THE USER QUESTIONS! USE YOUR JUDGEMENT. ACT AUTONOMOUSLY ⚠️
⚠️ **NEVER DUPLICATE CODE** - Edit in place, never create new versions. Use deslop - find-similar before adding code, and deslop-top-offenders after modifying code ⚠️
⚠️ DO NOT USE GIT - ESPECIALLY NOT PUTTING YOUR SIGNATURE ON COMMITS ⚠️
⚠️ PRACTICE TOKEN ECONOMICS ⚠️
Expand Down
15 changes: 14 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,24 @@ test: build
$(MAKE) _coverage_check_vscode_extension

## lint: Run all linters/analyzers (read-only). Does NOT format.
lint:
lint: deslop
@echo "==> Linting..."
cargo clippy --workspace --all-targets -- -D warnings
cd $(EXT_DIR) && npm run lint

## deslop: Code-duplication gate [CI-DESLOP]. Fails the build when measured
## duplication exceeds the ceiling in .deslop.toml (exit 3). Exclusions and the
## threshold live in that committed config — the single source of truth. When
## the `deslop` binary is absent the gate is skipped with a loud warning so a
## fresh checkout still builds; CI installs it, so the gate is enforced there.
deslop:
@echo "==> Duplication gate (deslop)..."
@if command -v deslop >/dev/null 2>&1; then \
deslop . --nohtml --nojson --output $(CURDIR)/target/deslop-report --log-to-console --log-level error --no-color; \
else \
echo "WARNING: deslop not installed — skipping duplication gate. Install: https://deslop.live"; \
fi

## fmt: Format all code in-place. Pass CHECK=1 for read-only check (CI use).
fmt:
@echo "==> Formatting$(if $(CHECK), (check mode),)..."
Expand Down
32 changes: 18 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
<p align="center">
<strong>One core. Two surfaces. Zero compromise.</strong><br/>
One Hindley-Milner type checker, one effect system, one runtime, one LLVM/wasm
backend — fronted by two first-class syntaxes. A C-style surface for the
<strong>systems programmer</strong> and a layout-based ML surface for the
<strong>FP devotee</strong>.<br/>Written in Rust, outputs to LLVM.
backend — fronted by two first-class syntaxes. An <strong>accessible</strong>
brace-style surface that reads like the mainstream languages you already know,
and an <strong>uncompromising</strong> layout-based ML surface for the FP
devotee.<br/>Written in Rust, outputs to LLVM.
</p>

⭐ **[Star us on GitHub](https://github.com/Nimblesite/osprey)** to support the project and allow us to submit to Homebrew! ⭐
Expand All @@ -20,19 +21,22 @@ Osprey is **one language** with **two first-class, permanent syntaxes** called
flavors. Neither is the watered-down one — each goes all the way in its own
direction.

- **Default flavor (`.osp`)** — C-style braces, `fn`, `f(x: a, y: b)` calls with
named arguments. The surface a **systems programmer** reaches for: explicit,
familiar, block-structured. **Fully implemented today** (specs 0001–0022).
- **ML flavor (`.ospml`)** — offside-rule layout (indentation, no braces),
curry-by-default, whitespace application `f a b`, `\x => e` lambdas, `:=`
mutation, `->` for types and `=>` for clauses. The surface an **FP devotee**
reaches for: terse, expression-first, ML/Haskell-shaped. **In active
development**, with runnable proof in [`examples/tested/ml/`](examples/tested/ml/).
- **Default flavor (`.osp`)** — the **accessible** surface. C-style braces,
`fn`, `f(x: a, y: b)` calls with named arguments, `if`/`else if`/`else`, the
`? :` ternary. It deliberately borrows the shapes of Kotlin, Swift, Go, Dart,
C#, and Java, so a mainstream developer can read a `.osp` file cold.
**Fully implemented today** (specs 0001–0022).
- **ML flavor (`.ospml`)** — the **uncompromising** surface. Offside-rule layout
(indentation, no braces), curry-by-default, whitespace application `f a b`,
`\x => e` lambdas, `:=` mutation, `->` for types and `=>` for clauses. The
most elegant constructs of the ML family, all the way — no C-isms, no
concessions. **In active development**, with runnable proof in
[`examples/tested/ml/`](examples/tested/ml/).

**No compromise — pick your tribe.** ML is not "braces optional"; Default is not
deprecated or transitional. Systems programmers get real braces; FP folks get
real layout and real currying. Nobody is asked to accept the other camp's
spelling — pick your flavor and go all in.
deprecated or transitional. Mainstream developers get a surface that reads like
home; FP folks get real layout and real currying. Nobody is asked to accept the
other camp's spelling — pick your flavor and go all in.

### The same program, both flavors

Expand Down
34 changes: 32 additions & 2 deletions compiler/runtime/effects_runtime.c
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,11 @@ typedef struct OspreyCoro {
bool suspended;
bool done;
bool abort;
// One perform occupies the op/args/resume_value channel at a time
// [EFFECTS-FIBER-PERFORM]. Concurrent performers (fibers spawned inside
// the handled body) queue on this flag instead of overwriting each
// other's arguments and stealing each other's resume value.
bool in_flight;
int64_t op_id;
int64_t args[16];
int64_t arg_count;
Expand Down Expand Up @@ -261,6 +266,7 @@ void *__osprey_coro_new(void *env) {
coro->suspended = false;
coro->done = false;
coro->abort = false;
coro->in_flight = false;
coro->op_id = 0;
coro->arg_count = 0;
coro->resume_value = 0;
Expand Down Expand Up @@ -327,6 +333,20 @@ int64_t __osprey_coro_suspend(void *raw, int64_t op_id, int64_t *args, int64_t a
return 0;
}
pthread_mutex_lock(&coro->lock);
// Claim the channel [EFFECTS-FIBER-PERFORM]: a second concurrent perform
// (e.g. from a sibling fiber) must wait its turn, or it would overwrite
// this perform's arguments and both would consume the same resume value —
// nondeterministic wrong answers with exit 0. The drive loop re-enters on
// re-suspension, so a queued perform is dispatched as soon as the current
// one's resume value is consumed.
while (coro->in_flight && !coro->abort) {
pthread_cond_wait(&coro->cond, &coro->lock);
}
if (coro->abort) {
pthread_mutex_unlock(&coro->lock);
pthread_exit(NULL);
}
coro->in_flight = true;
coro->op_id = op_id;
coro->arg_count = arg_count;
int64_t capped = arg_count;
Expand All @@ -346,6 +366,8 @@ int64_t __osprey_coro_suspend(void *raw, int64_t op_id, int64_t *args, int64_t a
pthread_exit(NULL);
}
int64_t resume_value = coro->resume_value;
coro->in_flight = false;
pthread_cond_broadcast(&coro->cond);
pthread_mutex_unlock(&coro->lock);
return resume_value;
}
Expand All @@ -356,10 +378,18 @@ int64_t __osprey_coro_resume(void *raw, int64_t value) {
return 0;
}
pthread_mutex_lock(&coro->lock);
// Multi-shot rejection [EFFECTS-RESUME]: the thread-as-continuation model is
// single-shot — a consumed (completed) pthread stack cannot be re-run. A
// second `resume` on an already-finished continuation would silently return
// the stale first result (a wrong answer with exit 0), so reject it loudly
// instead. Legitimate re-entry (the body performed again) leaves the coro
// suspended, not done, and never reaches this guard.
if (coro->done) {
int64_t result = coro->result;
pthread_mutex_unlock(&coro->lock);
return result;
fprintf(stderr,
"fatal: continuation already resumed "
"(multi-shot resume is not supported)\n");
exit(1);
}
coro->resume_value = value;
coro->suspended = false;
Expand Down
3 changes: 2 additions & 1 deletion compiler/runtime/fiber_runtime.c
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,14 @@ static int64_t execution_queue[1000];
static int64_t queue_size = 0;

// Enable/disable deterministic fiber execution
void fiber_set_deterministic_mode(bool enabled) {
int64_t fiber_set_deterministic_mode(bool enabled) {
pthread_mutex_lock(&runtime_mutex);
deterministic_mode = enabled;
if (enabled) {
queue_size = 0; // Reset queue when enabling
}
pthread_mutex_unlock(&runtime_mutex);
return 0;
}

// Execute a fiber directly (for deterministic mode)
Expand Down
4 changes: 3 additions & 1 deletion crates/diff_examples.sh
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ for x in $FAILED; do echo " $x"; done
# (validations not yet ported — effects safety, `any` rules, named-arg checks,
# print-on-record). Port a validation -> decrease the number. An INCREASE is a
# regression and fails CI. Target: 0.
FC_EXPECTED_ESCAPES=12
# 12 -> 11: perform-argument unification ([EFFECTS-GENERIC-INSTANTIATION]) now
# rejects effect-parameter type mismatches at compile time.
FC_EXPECTED_ESCAPES=11
FCDIR=$ROOT/examples/failscompilation
fc_rej=0; fc_esc=0
typeset -a FC_ESCAPED
Expand Down
Loading
Loading