Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
95d08fe
Change build output path in README example
phocks Jul 28, 2026
af144df
runtime: add MSVC POSIX shims for Windows native builds
swadhinbiswas Jul 28, 2026
55cbb4d
ci: add MSVC verification workflow for #25
swadhinbiswas Jul 28, 2026
9bebebb
runtime: guard struct timespec and drop windows.h from header
swadhinbiswas Jul 28, 2026
042c37d
runtime: remove redundant struct timespec — UCRT already defines it
swadhinbiswas Jul 28, 2026
80d27b7
runtime: add POSIX compat shims for MSVC (PATH_MAX, mode_t, S_IS*)
swadhinbiswas Jul 28, 2026
02c5a90
runtime: null-terminate d_name in MSVC readdir shim
swadhinbiswas Jul 28, 2026
9c73be4
fix(compiler): preserve FFI binding initializer calls
newbpydev Jul 28, 2026
86b96ce
test(compiler): cover FFI call ownership boundaries
newbpydev Jul 28, 2026
d89404c
fix(review): avoid redundant FFI symbol probes
newbpydev Jul 28, 2026
311850e
feat(compiler): add HeadersInit to island ambient types
3kaiu Jul 29, 2026
6d126e9
feat(compiler): improve global crypto diagnostic and remove opaque token
3kaiu Jul 29, 2026
8e399af
feat(compiler): support new URL(url, base) with string literals
3kaiu Jul 29, 2026
db5b1cc
fix: preserve guarded global Buffer aliases
patlux Jul 29, 2026
728d3a6
fix: preserve concrete union receivers during lowering
patlux Jul 29, 2026
25f0aeb
feat: support unknown-typed class fields
patlux Jul 29, 2026
16ec424
test: key differential oracle cache by environment
patlux Jul 29, 2026
96bffc7
test: cover guarded Buffer alias fences
patlux Jul 29, 2026
c1aa6bb
test: cover direct concrete union receivers
patlux Jul 29, 2026
c8b0f13
test: cover inherited unknown fields
patlux Jul 29, 2026
466412e
test: fingerprint the complete oracle environment
patlux Jul 29, 2026
9010c87
fix: lower void conditionals as statements
CooperSheroy Aug 1, 2026
27537e0
Fix dynamic promise runtime gating
eli-sterling Aug 10, 2026
92a5a1c
coverage: distinguish partial dynamic shims
dismonjames Aug 11, 2026
7252e02
fix: classify all partial dynamic shims
ctate Aug 11, 2026
955d6b1
fix(compiler): keep maybeNaN on the failed edge of ordered comparisons
codeAnqiang-ma Aug 12, 2026
5bf752b
feat(math): lower DSP scalar functions statically
iplanwebsites Aug 13, 2026
c589e9d
docs: add native MIDI messaging port plan and TODO
claude Aug 14, 2026
f1d5da7
feat(compiler): add node:midi front-matter — ambient types, IR handle…
claude Aug 14, 2026
c663d1d
feat(runtime): add scr_midi.c native MIDI unit (ALSA/CoreMIDI/WinMM)
claude Aug 14, 2026
013019f
docs: reconcile MIDI ABI table with runtime prototype (thunks, send m…
claude Aug 14, 2026
01f0452
feat(compiler): add node:midi lowering spoke
claude Aug 14, 2026
b82f85c
feat(compiler): wire midi.* lib fns through emitter and validator
claude Aug 14, 2026
77e14f9
feat(compiler): wire node:midi build inclusion, install hook, and WAS…
claude Aug 14, 2026
651f34e
test/docs: add node:midi tests, docs, manifest entry, and Node baseli…
claude Aug 14, 2026
9e83e48
fix(midi): validate native package loopback
iplanwebsites Aug 14, 2026
b6a74de
test(midi): record TS7 order parity
iplanwebsites Aug 14, 2026
895247d
docs: remove completed MIDI implementation plan
iplanwebsites Aug 14, 2026
91ec382
fix(compiler): allow bare side-effect imports of ambient-only modules
techfreaque Aug 22, 2026
20dc90f
fix(compiler): cap rendered diagnostics to avoid RangeError on large …
techfreaque Aug 22, 2026
b4eb36b
fix(compiler): dynamic-import lowering resolves bare project specifiers
techfreaque Aug 22, 2026
7e81976
fix(compiler): adopt jsx and lib from the project's tsconfig in ts7
techfreaque Aug 22, 2026
357d99a
fix(compiler): adopt tsconfig paths/baseUrl for the ts7 checker
techfreaque Aug 22, 2026
4655f34
fix: support native builds on Windows via MinGW-w64 (clang) and zig's…
techfreaque Aug 22, 2026
763ed24
fix: load JsxEmit as a hidden TS7 enum instead of a hardcoded table
techfreaque Aug 22, 2026
295602f
merge: adopt tsconfig paths/baseUrl (#197)
techfreaque Aug 22, 2026
3b2d38a
merge: adopt jsx/lib from tsconfig (#196)
techfreaque Aug 22, 2026
481c817
merge: ambient CSS side-effect import no-op (#198)
techfreaque Aug 22, 2026
0b20bfb
merge: dynamic-import lowering resolves bare project specifiers (#200)
techfreaque Aug 22, 2026
8270f7e
merge: cap rendered diagnostics to avoid RangeError (#201)
techfreaque Aug 22, 2026
e6c13fb
merge: support native builds on Windows via MinGW-w64/zig (#202)
techfreaque Aug 22, 2026
575db1e
merge: lower DSP scalar functions statically (#146)
techfreaque Aug 22, 2026
1fabc84
merge: fix void conditional lowering (#72)
techfreaque Aug 22, 2026
2d2cdb2
merge: fix guarded global Buffer aliases (#48)
techfreaque Aug 22, 2026
f5d2f26
merge: fix dynamic promise runtime gating (#117)
techfreaque Aug 22, 2026
a1fcbeb
merge: distinguish partial dynamic shims in coverage (#130)
techfreaque Aug 22, 2026
6af196b
merge: support unknown-typed class fields (#50)
techfreaque Aug 22, 2026
0f5408e
merge: key differential oracle cache by environment (#51)
techfreaque Aug 22, 2026
ef12eef
merge: preserve concrete union receivers during lowering (#49)
techfreaque Aug 22, 2026
d6ceac9
merge: add HeadersInit to island ambient types (#45)
techfreaque Aug 22, 2026
c4df6fa
merge: support new URL(url, base) with string literals (#43)
techfreaque Aug 22, 2026
9c82bc0
merge: improve global crypto diagnostic and remove opaque token (#44)
techfreaque Aug 22, 2026
5f630d7
merge: keep maybeNaN on failed edge of ordered comparisons (#141)
techfreaque Aug 22, 2026
c6bcf7e
merge: docs update build output path (#24)
techfreaque Aug 22, 2026
26f39dd
merge: MSVC POSIX shims for Windows native builds (#27)
techfreaque Aug 22, 2026
2e1f1e4
merge: preserve FFI calls in binding initializers (#41)
techfreaque Aug 22, 2026
b3cd6b4
merge: add native MIDI input/output support (#151)
techfreaque Aug 22, 2026
ab85454
review: restore retain/release balance note, align test cache dir wit…
techfreaque Aug 22, 2026
1a64c1c
review: fix stale doc comment listing aliasable globals (missing Buffer)
techfreaque Aug 22, 2026
10746b9
review: fix stale doc comment and remove unreachable dead-code after …
techfreaque Aug 22, 2026
284b7ce
review: add missing WHY comment on IEEE-754 bit-exactness of sqrt vs …
techfreaque Aug 22, 2026
a0a2293
review: rewrap comment to match project's ~80-char convention
techfreaque Aug 22, 2026
c3d0c77
review: keep TOKEN_OPAQUE_GLOBALS crypto entry (PR-44's removal broke…
techfreaque Aug 22, 2026
2679a27
review: fix stale branch trigger in msvc-verify.yml (was scoped to th…
techfreaque Aug 22, 2026
a4966a5
review: add missing midi field to EarlyExecutableNativeFeatures test …
techfreaque Aug 22, 2026
657dc7f
merge external PR #146 into fork main
techfreaque Aug 22, 2026
172173a
merge external PR #141 into fork main
techfreaque Aug 22, 2026
3a1e584
merge external PR #130 into fork main
techfreaque Aug 22, 2026
d956d84
merge external PR #72 into fork main
techfreaque Aug 22, 2026
d06c71b
merge external PR #51 into fork main
techfreaque Aug 22, 2026
8b86697
merge external PR #50 into fork main
techfreaque Aug 22, 2026
6d08587
merge external PR #49 into fork main
techfreaque Aug 22, 2026
ea754a0
merge external PR #48 into fork main
techfreaque Aug 22, 2026
44a36ba
merge external PR #45 into fork main
techfreaque Aug 22, 2026
65cc9e2
merge external PR #43 into fork main
techfreaque Aug 22, 2026
dbb3a28
merge external PR #24 into fork main
techfreaque Aug 22, 2026
bf61824
merge external PR #44 into fork main
techfreaque Aug 22, 2026
04881cc
merge external PR #41 into fork main
techfreaque Aug 22, 2026
b7cd72c
merge external PR #27 into fork main
techfreaque Aug 22, 2026
e95d72c
merge external PR #151 into fork main
techfreaque Aug 22, 2026
4bc60e9
merge external PR #117 into fork main
techfreaque Aug 22, 2026
e9e3fd8
fix: three jsval-representation gaps in checked-dynamic lowering
techfreaque Aug 22, 2026
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
65 changes: 65 additions & 0 deletions .github/workflows/msvc-verify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: MSVC POSIX Shims

on:
push:
branches: [main]
pull_request:

jobs:
msvc_compile:
name: verify MSVC compilation
runs-on: windows-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 11
- uses: actions/setup-node@v4
with:
node-version-file: .node-version
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm build
- name: Compile runtime with MSVC clang
shell: pwsh
run: |
# Verify clang is MSVC-targeted (not mingw)
clang --version
# Compile a simple program — this is the exact scenario from #25
node packages/cli/dist/main.js run tests/corpus/001-hello.ts --backend c
- name: Compile Map + sort program (the bare.ts pattern)
shell: pwsh
run: |
$ts = @'
const m = new Map<number, number>();
m.set(1, 10);
m.set(2, 5);
m.set(3, 20);
const sorted = [...m.entries()].sort((a, b) => a[1] - b[1]);
console.log(sorted[0][0], sorted[0][1]);
'@
$ts | Out-File -Encoding utf8 test-msvc.ts
node packages/cli/dist/main.js run test-msvc.ts --backend c

linux_regression:
name: verify no Linux regressions
runs-on: ubuntu-24.04
timeout-minutes: 10
env:
SCRIPTC_NO_CACHE: "1"
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 11
- uses: actions/setup-node@v4
with:
node-version-file: .node-version
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm build
- name: Corpus smoke tests
run: >-
SCRIPTC_TEST_WORKERS=4 pnpm exec vitest run tests/harness/differential.test.ts
-t "001-hello|518-array-sort|520-map-basics|path"
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to scriptc will be documented in this file.

## Unreleased

### Features

- **Native MIDI messaging.** `node:midi` (API-compatible with node-midi/@julusian/midi) enumerates ports, opens inputs and outputs including virtual ports, sends raw messages, and receives time-stamped messages through the `"message"` event on the dependency-free event loop. Backends bind each platform's MIDI stack — ALSA on Linux, CoreMIDI on macOS, WinMM on Windows — and are linked only into binaries that use the surface. An open input holds the loop alive like a bound `dgram` socket; the runtime is byte-transparent and does not parse MIDI semantics. `openVirtualPort` is POSIX-only (WinMM has no user-space virtual ports), and any MIDI surface on `wasm32-wasi` refuses before linking with `SC3002`.

<!-- release:start -->

## 0.0.35
Expand Down
2 changes: 1 addition & 1 deletion docs/src/app/how-it-works/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fib.ir.json

- **Memory** — values are reference-counted; an acyclic value is freed the moment its last reference drops. Reference cycles are collected at deterministic points by a cycle collector, not a concurrent GC. There are no GC pauses and no tracing heap.
- **Concurrency** — `async`/`await` runs on stackful fibers with JS-exact scheduling: microtasks drain in the same order Node's do, timers fire in the same order, and the event loop (kqueue on macOS, epoll on Linux) has no external dependencies.
- **The server stack** — `net`, `http`, `https`, `tls` (vendored mbedTLS), `dgram`, `dns` are native implementations on that same loop.
- **The server stack** — `net`, `http`, `https`, `tls` (vendored mbedTLS), `dgram`, `dns` are native implementations on that same loop, as is `midi` (ALSA/CoreMIDI/WinMM, linked only into binaries that use it).
- **Numbers** — JS-exact f64 semantics, including shortest-roundtrip number-to-string formatting fuzz-verified against Node's output.
- **Regular expressions** — the same ECMAScript-exact bytecode interpreter QuickJS uses, linked only into regex-using binaries.

Expand Down
2 changes: 1 addition & 1 deletion docs/src/app/introduction/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ The static surface covers the language and the standard library real programs us

- **The language** — classes with single inheritance and dynamic dispatch, closures with JS capture semantics, generic function declarations (monomorphized), discriminated unions driven by TypeScript's own narrowing, `async`/`await` with JS-exact scheduling, exceptions with `finally`, destructuring, spread, optional/default/rest parameters, getters and setters, iterators, template literals, bitwise operators with JS-exact ToInt32 semantics, and the static slice of regular expressions.
- **The standard library** — strings with UTF-16-exact surface semantics, arrays, `Map` and `Set` with JS-exact ordering, read-only `Date` values and calendar getters, `JSON` with runtime-validated casts, `Math`, typed arrays and `Buffer`, `Error` hierarchies with typed `catch`.
- **Node's API surface** — `fs` (sync and promises), `path`, `process`, `child_process`, `os`, `crypto`, `url`/`URL`, `zlib`, timers and signal handlers on a dependency-free event loop, and the server stack: `net`, `http`, `https`, `tls`, `dgram`, `dns`, `readline`. Real servers compile:
- **Node's API surface** — `fs` (sync and promises), `path`, `process`, `child_process`, `os`, `crypto`, `url`/`URL`, `zlib`, timers and signal handlers on a dependency-free event loop, the server stack: `net`, `http`, `https`, `tls`, `dgram`, `dns`, `readline`, and native `midi` (raw MIDI messaging over the same loop). Real servers compile:

```ts:server.ts
import { createServer } from "node:http";
Expand Down
10 changes: 10 additions & 0 deletions docs/src/app/limitations/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,16 @@ const who = process.argv.length > 2 ? process.argv[2] : "world";

The production <code>wasm32-wasi</code> target supports the complete executable language tier through LLVM: async/await, promises, generators, timers and other portable event-loop work, stdin/readline, filesystem callbacks and promises, and the <code>--dynamic</code> island. Portable WASI Preview 1 has no socket, process-spawn, OS-signal, network-interface, or filesystem-notification capabilities, so networking/fetch, child processes, signal APIs, <code>os.networkInterfaces()</code>, and <code>fs.watch</code> are rejected before linking with <code>SC3002</code>. <code>--sanitize</code>, native FFI, and library-mode archive builds are also unavailable. Filesystem access is bounded by the host's preopens; <code>scriptc run</code> exposes the current working directory and <code>/tmp</code>. See [Platform Support](/platforms) for build and run details.

## MIDI limits

`node:midi` is raw MIDI messaging, modeled on node-midi/@julusian/midi — enumerate ports, open input/output (including virtual ports), send raw messages, and receive time-stamped messages via the `"message"` event.

- **The runtime is byte-transparent.** It carries raw message bytes (Note On/Off, CC, Program Change, Pitch Bend, SysEx as a byte run) and neither parses nor validates MIDI semantics. Higher-level semantic events (`noteon`, `cc`, …), MIDI file parsing, sequencing/clock scheduling, and MIDI 2.0 / UMP are out of scope.
- **Virtual ports are POSIX-only.** `openVirtualPort` works on Linux (ALSA) and macOS (CoreMIDI); on Windows WinMM it fails at runtime with a clear error, because WinMM has no user-space virtual ports. See [Platform Support](/platforms).
- **No MIDI on WASI.** WASI Preview 1 has no MIDI capability, so any `node:midi` surface is rejected before linking with `SC3002`. Browser Web MIDI is a separate runtime the WASI target does not cover.
- **`on`/`once` accept only the `"message"` event**, with a `(deltaTime, message)` listener. `deltaTime` is seconds since the previous message on that input (`0` for the first) and is inherently nondeterministic — a differential test must never print it.
- **A Linux host without ALSA** (many CI containers) has no MIDI backend: the runtime enumerates zero ports and throws on open. The hardware-free loopback tests use a virtual-port pair on a capable host.

## Tooling gaps

- `scriptc run` does not forward extra CLI arguments to the program — `build` and invoke the binary directly.
Expand Down
38 changes: 38 additions & 0 deletions docs/src/app/platforms/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,44 @@ WASI is a production LLVM target with the same language tiers as the native targ

The remaining executable boundary is host capability, not language coverage. WASI Preview 1 has no portable socket, process-spawn, OS-signal, network-interface, or filesystem-notification APIs. Networking/fetch, child processes, signal APIs, <code>os.networkInterfaces()</code>, and <code>fs.watch</code> therefore fail before linking with diagnostic <code>SC3002</code>. <code>--sanitize</code>, native FFI, and library-mode archive builds are unavailable too. Filesystem behavior is bounded by the host's preopens, and process/OS introspection follows WASI's reduced model.

## MIDI (`node:midi`)

Raw MIDI messaging (`node:midi`, API-compatible with node-midi/@julusian/midi) is a native runtime unit linked only into binaries that use it. Each platform binds its own MIDI stack, so the availability is per target:

<table>
<thead>
<tr>
<th>Platform</th>
<th>Backend</th>
<th>Virtual ports</th>
</tr>
</thead>
<tbody>
<tr>
<td>Linux</td>
<td>ALSA sequencer (<code>libasound</code>, linked as <code>-lasound</code>)</td>
<td>Yes — a native ALSA port other clients connect to</td>
</tr>
<tr>
<td>macOS</td>
<td>CoreMIDI (<code>-framework CoreMIDI</code>)</td>
<td>Yes — <code>MIDISourceCreate</code>/<code>MIDIDestinationCreate</code></td>
</tr>
<tr>
<td>Windows</td>
<td>WinMM (<code>winmm.lib</code>)</td>
<td>No — WinMM has no user-space virtual ports; <code>openVirtualPort</code> fails at runtime with a clear error</td>
</tr>
<tr>
<td>WASI</td>
<td>None</td>
<td>No — any midi surface fences before linking with <code>SC3002</code></td>
</tr>
</tbody>
</table>

An open Input is a live pollable source that holds the event loop alive (like a bound `dgram` socket); an Output is fire-and-forget. Port enumeration (`getPortCount`/`getPortName`) works on a fresh handle before `openPort`. The runtime is byte-transparent — it neither parses nor validates MIDI message semantics. Note that a Linux host without an ALSA sound stack (many CI containers) enumerates zero ports and throws on open.

## Cross-target limits

- `--sanitize` is a host-build lane.
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"devDependencies": {
"@types/node": "^24.0.0",
"eslint": "^9.20.0",
"midi": "npm:@julusian/midi@^3.8.1",
"tsx": "^4.19.0",
"typescript": "5.9.3",
"typescript-eslint": "^8.24.0",
Expand Down
45 changes: 44 additions & 1 deletion packages/compiler/ambient/scriptc-node-fallback.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3032,6 +3032,47 @@ declare module "node:dns" {
export * from "dns";
}

/* node:midi — raw MIDI messaging over the event loop (scr_midi.c, linked
* only into using binaries — the moduleUsesMidi switch). API-compatible
* with node-midi/@julusian/midi so the Node differential baseline is a
* real, installable package. Input is a live pollable source (an open
* port holds the loop alive, like a bound dgram socket); Output is
* fire-and-forget (send never holds the loop). Port enumeration
* (getPortCount/getPortName) works on a fresh handle before openPort —
* enumerate then open, like node-midi. openVirtualPort is POSIX-only and
* fences at runtime on Windows (WinMM has no user-space virtual ports).
* on/once accept ONLY the "message" event with a (deltaTime, message)
* handler; message bytes arrive as a number[] with deltaTime (seconds
* since the previous message, 0 for the first) as the leading argument —
* the node-midi callback shape. sendMessage takes an array literal or a
* Uint8Array; the runtime is byte-transparent (it neither parses nor
* validates MIDI semantics). */
declare module "midi" {
export class Input {
getPortCount(): number;
getPortName(port: number): string;
openPort(port: number): void;
openVirtualPort(name: string): void;
closePort(): void;
isPortOpen(): boolean;
ignoreTypes(sysex: boolean, timing: boolean, activeSensing: boolean): void;
on(event: "message", listener: (deltaTime: number, message: number[]) => void): void;
once(event: "message", listener: (deltaTime: number, message: number[]) => void): void;
}
export class Output {
getPortCount(): number;
getPortName(port: number): string;
openPort(port: number): void;
openVirtualPort(name: string): void;
closePort(): void;
isPortOpen(): boolean;
sendMessage(message: number[] | Uint8Array): void;
}
}
declare module "node:midi" {
export * from "midi";
}

/* node:worker_threads — the MAIN-THREAD slice only. A compiled binary is
* always the main thread (no JS-engine thread machinery exists), so
* isMainThread lowers to `true` and threadId to 0 — Node's main-thread
Expand Down Expand Up @@ -3133,7 +3174,9 @@ declare module "node:async_hooks" {
* checks annotated listener parameters against it (unannotated non-empty
* parameter lists have no static types and fence at the registration). */
declare module "events" {
class EventEmitter {
// Node 24 makes EventEmitter generic; the native surface remains
// intentionally event-name agnostic, but accepts that type argument.
class EventEmitter<T = any> {
constructor();
static defaultMaxListeners: number;
on(eventName: string, listener: (...args: any[]) => void): this;
Expand Down
Loading