mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-31 07:23:06 +08:00
Merge main
This commit is contained in:
@@ -76,6 +76,8 @@ title: "Limitations"
|
||||
|
||||
<include>../limitations/builtin-apis/filesystem-flags.mdx</include>
|
||||
|
||||
<include>../limitations/builtin-apis/glob.mdx</include>
|
||||
|
||||
<include>../limitations/builtin-apis/filehandle-and-descriptor-i-o.mdx</include>
|
||||
|
||||
<include>../limitations/builtin-apis/crypto.mdx</include>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
- **Glob:** `fs.globSync`, `fs.glob` and `fs/promises` `glob` support every option. `withFileTypes` must be a literal `true` or `false`, and `exclude` functions cannot be combined with `withFileTypes`. The `fs/promises` iterator is supported only as the source of a `for await` loop, and its walk completes when the loop starts. `fs.glob` callbacks receive file names, not Dirents.
|
||||
@@ -15,4 +15,4 @@ Other immutable aliases of table-backed Node builtins support direct calls, but
|
||||
|
||||
Synchronous and promise filesystem APIs with trailing arguments that affect behavior remain call-only. This includes writers, open, directory creation/removal, metadata readers, and directory readers. Passing them as values could discard modes, flags, encodings, or options during function-signature adaptation.
|
||||
|
||||
Native error-first filesystem callbacks can be stored and passed to platform adapters. Reads support Buffer and UTF-8 results; writes preserve string flags and modes; copy supports regular files and recursive directories. Reads and writes reject already-aborted signals, but native file operations complete synchronously before the deferred callback, so cancellation cannot interrupt I/O in progress. Symbolic-link copy, copy filters and clone modes, recursive glob patterns, glob exclusions, recursive directory reads, and Dirent results report a native limitation.
|
||||
Native error-first filesystem callbacks can be stored and passed to platform adapters. Reads support Buffer and UTF-8 results; writes preserve string flags and modes; copy supports regular files and recursive directories. Reads and writes reject already-aborted signals, but native file operations complete synchronously before the deferred callback, so cancellation cannot interrupt I/O in progress. Symbolic-link copy, copy filters and clone modes, recursive directory reads, and Dirent results report a native limitation. `fs.glob` compiles only as a direct call; a stored `fs.glob` value throws `SC2020`.
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
"schemaVersion": 3,
|
||||
"nodeVersion": "24.22.0",
|
||||
"nodeCommit": "f14cd3aa7bf9ca8df2cc5b8bb14beb0f335b1d93",
|
||||
"artifactVersion": "821b258757b8e2c146c4",
|
||||
"artifactVersion": "797302915a7200576d4e",
|
||||
"rowCount": 3880
|
||||
}
|
||||
|
||||
@@ -314,9 +314,9 @@
|
||||
"apiEntries": 280,
|
||||
"static": {
|
||||
"supported": 7,
|
||||
"partial": 101,
|
||||
"partial": 131,
|
||||
"refused": 0,
|
||||
"not-implemented": 172,
|
||||
"not-implemented": 142,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
@@ -29690,9 +29690,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -30702,9 +30702,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -31921,9 +31921,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -33554,9 +33554,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -33577,9 +33577,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -33600,9 +33600,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -33623,9 +33623,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -33646,9 +33646,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -33669,9 +33669,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -33692,9 +33692,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -33715,9 +33715,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -33784,9 +33784,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -33807,9 +33807,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -33830,9 +33830,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -33853,9 +33853,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -33876,9 +33876,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -33899,9 +33899,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -33922,9 +33922,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -33945,9 +33945,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Filesystem-capacity fields preserve unsigned platform values as numbers or BigInts.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -33968,9 +33968,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -33991,9 +33991,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -34037,9 +34037,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -34060,9 +34060,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -34083,9 +34083,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -34106,9 +34106,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -34129,9 +34129,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -34152,9 +34152,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -34175,9 +34175,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -34198,9 +34198,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -34221,9 +34221,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
"tiers": {
|
||||
"static": {
|
||||
"replace-refusal": 35,
|
||||
"verify-gap": 2457,
|
||||
"audit-partial": 893,
|
||||
"verify-gap": 2427,
|
||||
"audit-partial": 923,
|
||||
"classify": 2,
|
||||
"implement": 85
|
||||
}
|
||||
@@ -26182,12 +26182,19 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.fsPromises.glob",
|
||||
"tests": [
|
||||
"tests/corpus/5448-fs-glob-patterns.ts",
|
||||
"tests/corpus/5449-fs-glob-options.ts",
|
||||
"tests/corpus/5450-fs-glob-validation.js",
|
||||
"tests/corpus/5451-fs-promises-glob.ts",
|
||||
"tests/corpus/5452-fs-glob-callback.ts",
|
||||
"tests/fixtures/node-types/fs-glob.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -27362,12 +27369,19 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.fs.glob",
|
||||
"tests": [
|
||||
"tests/corpus/5448-fs-glob-patterns.ts",
|
||||
"tests/corpus/5449-fs-glob-options.ts",
|
||||
"tests/corpus/5450-fs-glob-validation.js",
|
||||
"tests/corpus/5451-fs-promises-glob.ts",
|
||||
"tests/corpus/5452-fs-glob-callback.ts",
|
||||
"tests/fixtures/node-types/fs-glob.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -28783,12 +28797,19 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.fs.globSync",
|
||||
"tests": [
|
||||
"tests/corpus/5448-fs-glob-patterns.ts",
|
||||
"tests/corpus/5449-fs-glob-options.ts",
|
||||
"tests/corpus/5450-fs-glob-validation.js",
|
||||
"tests/corpus/5451-fs-promises-glob.ts",
|
||||
"tests/corpus/5452-fs-glob-callback.ts",
|
||||
"tests/fixtures/node-types/fs-glob.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -30683,12 +30704,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.fs.Stats",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -30708,12 +30731,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.stats.isBlockDevice",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -30733,12 +30758,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.stats.isCharacterDevice",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -30758,12 +30785,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.stats.isDirectory",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -30783,12 +30812,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.stats.isFIFO",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -30808,12 +30839,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.stats.isFile",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -30833,12 +30866,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.stats.isSocket",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -30858,12 +30893,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.stats.isSymbolicLink",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -30937,12 +30974,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.mode",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -30962,12 +31001,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.nlink",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -30987,12 +31028,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.uid",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -31012,12 +31055,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.gid",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -31037,12 +31082,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.rdev",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -31062,12 +31109,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.size",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -31087,12 +31136,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.blksize",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -31112,12 +31163,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.blocks",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -31137,12 +31190,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.atimeMs",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -31162,12 +31217,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.mtimeMs",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -31214,12 +31271,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.birthtimeMs",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -31239,12 +31298,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.atimeNs",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -31264,12 +31325,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.mtimeNs",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -31289,12 +31352,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.ctimeNs",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -31314,12 +31379,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.birthtimeNs",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -31339,12 +31406,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.atime",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -31364,12 +31433,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.mtime",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -31389,12 +31460,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.ctime",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -31414,12 +31487,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:fs",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:fs.birthtime",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -326,9 +326,9 @@
|
||||
"apiEntries": 280,
|
||||
"static": {
|
||||
"supported": 7,
|
||||
"partial": 101,
|
||||
"partial": 131,
|
||||
"refused": 0,
|
||||
"not-implemented": 172,
|
||||
"not-implemented": 142,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
@@ -43939,7 +43939,13 @@
|
||||
"tests/corpus/3135-fs-file-handle-controls.ts",
|
||||
"tests/corpus/3136-fs-file-handle-options.ts",
|
||||
"tests/corpus/3137-fs-vector-callbacks.cjs",
|
||||
"tests/corpus/5448-fs-glob-patterns.ts",
|
||||
"tests/corpus/5449-fs-glob-options.ts",
|
||||
"tests/corpus/5450-fs-glob-validation.js",
|
||||
"tests/corpus/5451-fs-promises-glob.ts",
|
||||
"tests/corpus/5452-fs-glob-callback.ts",
|
||||
"tests/corpus/5540-fs-callback-forms.cjs",
|
||||
"tests/corpus/5541-fs-stats-fields.cjs",
|
||||
"tests/corpus/fs-native-realpath.ts",
|
||||
"tests/corpus/fs-read-encodings.ts",
|
||||
"tests/corpus/node24-fs-links-callbacks.cjs",
|
||||
@@ -43957,6 +43963,7 @@
|
||||
"tests/corpus/node24-fs-timestamps-promises.ts",
|
||||
"tests/corpus/node24-fs-timestamps-symlinks.ts",
|
||||
"tests/corpus/node24-fs-timestamps.ts",
|
||||
"tests/fixtures/node-types/fs-glob.ts",
|
||||
"tests/fixtures/node-types/fs-links.ts",
|
||||
"tests/fixtures/node-types/fs-statfs.ts",
|
||||
"tests/fixtures/node-types/fs-timestamps.ts"
|
||||
@@ -44984,8 +44991,16 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.fsPromises.glob",
|
||||
"tests": [
|
||||
"tests/corpus/5448-fs-glob-patterns.ts",
|
||||
"tests/corpus/5449-fs-glob-options.ts",
|
||||
"tests/corpus/5450-fs-glob-validation.js",
|
||||
"tests/corpus/5451-fs-promises-glob.ts",
|
||||
"tests/corpus/5452-fs-glob-callback.ts",
|
||||
"tests/fixtures/node-types/fs-glob.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -46143,8 +46158,16 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.fs.glob",
|
||||
"tests": [
|
||||
"tests/corpus/5448-fs-glob-patterns.ts",
|
||||
"tests/corpus/5449-fs-glob-options.ts",
|
||||
"tests/corpus/5450-fs-glob-validation.js",
|
||||
"tests/corpus/5451-fs-promises-glob.ts",
|
||||
"tests/corpus/5452-fs-glob-callback.ts",
|
||||
"tests/fixtures/node-types/fs-glob.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -47645,8 +47668,16 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.fs.globSync",
|
||||
"tests": [
|
||||
"tests/corpus/5448-fs-glob-patterns.ts",
|
||||
"tests/corpus/5449-fs-glob-options.ts",
|
||||
"tests/corpus/5450-fs-glob-validation.js",
|
||||
"tests/corpus/5451-fs-promises-glob.ts",
|
||||
"tests/corpus/5452-fs-glob-callback.ts",
|
||||
"tests/fixtures/node-types/fs-glob.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -49453,8 +49484,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.fs.Stats",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -49499,8 +49533,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.stats.isBlockDevice",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -49522,8 +49559,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.stats.isCharacterDevice",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -49545,8 +49585,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.stats.isDirectory",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -49568,8 +49611,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.stats.isFIFO",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -49591,8 +49637,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.stats.isFile",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -49614,8 +49663,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.stats.isSocket",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -49637,8 +49689,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.stats.isSymbolicLink",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -49712,8 +49767,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.mode",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
@@ -49735,8 +49793,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.nlink",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
@@ -49758,8 +49819,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.uid",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
@@ -49781,8 +49845,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.gid",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
@@ -49804,8 +49871,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.rdev",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
@@ -49827,8 +49897,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.size",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
@@ -49850,8 +49923,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.blksize",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
@@ -49873,8 +49949,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.blocks",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
@@ -49896,8 +49975,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.atimeMs",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
@@ -49919,8 +50001,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.mtimeMs",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
@@ -49968,8 +50053,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.birthtimeMs",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
@@ -49991,8 +50079,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.atimeNs",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
@@ -50014,8 +50105,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.mtimeNs",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
@@ -50037,8 +50131,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.ctimeNs",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
@@ -50060,8 +50157,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.birthtimeNs",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
@@ -50083,8 +50183,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.atime",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
@@ -50106,8 +50209,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.mtime",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
@@ -50129,8 +50235,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.ctime",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
@@ -50152,8 +50261,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:fs"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:fs.birthtime",
|
||||
"tests": [
|
||||
"tests/corpus/5541-fs-stats-fields.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
|
||||
@@ -282,7 +282,10 @@
|
||||
{ "chapter": "child_process", "signatures": ["`stdout` Type: {stream.Readable|null|undefined}", "`stderr` Type: {stream.Readable|null|undefined}"], "status": "partial", "evidence": ["tests/corpus/1565-spawn-pipe-streams.ts", "tests/corpus/2939-child-shell-text.ts"] },
|
||||
{ "chapter": "child_process", "signatures": ["`stdin` Type: {stream.Writable|null|undefined}"], "status": "partial", "evidence": ["tests/corpus/2893-child-stdin-duplex.ts", "tests/corpus/2894-child-stdin-backpressure.ts", "tests/corpus/2895-child-stdin-write-after-end.ts", "tests/corpus/2896-child-stdin-write-after-finish.ts", "tests/corpus/2897-child-stdin-destroy.ts", "tests/corpus/2898-child-stdin-spawn-failure.ts"] },
|
||||
{ "chapter": "fs", "signatures": ["`fsPromises.lstat(path[, options])`"], "status": "partial", "evidence": ["tests/corpus/2941-fsp-lstat-identity.ts"] },
|
||||
{ "chapter": "fs", "signatures": ["`fs.globSync(pattern[, options])`", "`fs.glob(pattern[, options], callback)`", "`fsPromises.glob(pattern[, options])`"], "status": "partial", "evidence": ["tests/corpus/5448-fs-glob-patterns.ts", "tests/corpus/5449-fs-glob-options.ts", "tests/corpus/5450-fs-glob-validation.js", "tests/corpus/5451-fs-promises-glob.ts", "tests/corpus/5452-fs-glob-callback.ts", "tests/fixtures/node-types/fs-glob.ts"] },
|
||||
{ "chapter": "process", "signatures": ["`process.send(message[, sendHandle[, options]][, callback])`", "`process.disconnect()`", "`connected` Type: {boolean}", "Event: `'message'`", "Event: `'disconnect'`"], "status": "partial", "evidence": ["tests/corpus/2946-child-fork-ipc/main.ts"] },
|
||||
{ "chapter": "fs", "symbols": ["fs.Stats", "stats.isBlockDevice", "stats.isCharacterDevice", "stats.isDirectory", "stats.isFIFO", "stats.isFile", "stats.isSocket", "stats.isSymbolicLink"], "status": "partial", "evidence": ["tests/corpus/5541-fs-stats-fields.cjs"] },
|
||||
{ "chapter": "fs", "parent": "Class: `fs.Stats`", "signatures": ["`mode` Type: {number|bigint}", "`nlink` Type: {number|bigint}", "`uid` Type: {number|bigint}", "`gid` Type: {number|bigint}", "`rdev` Type: {number|bigint}", "`size` Type: {number|bigint}", "`blksize` Type: {number|bigint}", "`blocks` Type: {number|bigint}", "`atimeMs` Type: {number|bigint}", "`mtimeMs` Type: {number|bigint}", "`birthtimeMs` Type: {number|bigint}", "`atimeNs` Type: {bigint}", "`mtimeNs` Type: {bigint}", "`ctimeNs` Type: {bigint}", "`birthtimeNs` Type: {bigint}", "`atime` Type: {Date}", "`mtime` Type: {Date}", "`ctime` Type: {Date}", "`birthtime` Type: {Date}"], "status": "partial", "evidence": ["tests/corpus/5541-fs-stats-fields.cjs"] },
|
||||
{ "chapter": "fs", "signatures": ["`dev` Type: {number|bigint}", "`ino` Type: {number|bigint}", "`ctimeMs` Type: {number|bigint}"], "status": "partial", "evidence": ["tests/corpus/2941-fsp-lstat-identity.ts"] },
|
||||
{ "chapter": "fs", "signatures": ["`filehandle[Symbol.asyncDispose]()`"], "status": "partial", "evidence": ["tests/corpus/2844-node-disposable-resources.ts"] },
|
||||
{ "chapter": "fs", "signatures": ["`fsPromises.realpath(path[, options])`"], "status": "partial", "evidence": ["tests/corpus/2940-fsp-realpath.ts"] },
|
||||
|
||||
+60
-1
@@ -1324,6 +1324,36 @@ declare module "node:fs" {
|
||||
* fences by name at the call site; undocumented keys drop like Node. */
|
||||
options: { withFileTypes: true; recursive?: boolean; [option: string]: unknown },
|
||||
): Dirent[];
|
||||
/* fs.globSync / fs.glob: Node's glob walk over the file system. Patterns
|
||||
* are minimatch globs (case-insensitive on macOS and Windows); results
|
||||
* are relative to cwd unless the pattern is absolute. withFileTypes must
|
||||
* be a literal so the result type is known. */
|
||||
export interface GlobOptions {
|
||||
cwd?: string | URL;
|
||||
exclude?: readonly string[] | ((fileName: string) => boolean);
|
||||
followSymlinks?: boolean;
|
||||
withFileTypes?: false;
|
||||
}
|
||||
export interface GlobOptionsWithFileTypes {
|
||||
cwd?: string | URL;
|
||||
exclude?: readonly string[];
|
||||
followSymlinks?: boolean;
|
||||
withFileTypes: true;
|
||||
}
|
||||
export function globSync(pattern: string | readonly string[], options?: GlobOptions): string[];
|
||||
export function globSync(
|
||||
pattern: string | readonly string[],
|
||||
options: GlobOptionsWithFileTypes,
|
||||
): Dirent[];
|
||||
export function glob(
|
||||
pattern: string | readonly string[],
|
||||
callback: (err: NodeJS.ErrnoException | null, matches?: string[]) => void,
|
||||
): void;
|
||||
export function glob(
|
||||
pattern: string | readonly string[],
|
||||
options: GlobOptions,
|
||||
callback: (err: NodeJS.ErrnoException | null, matches?: string[]) => void,
|
||||
): void;
|
||||
export function accessSync(path: string, mode?: number): void;
|
||||
export function mkdtempSync(prefix: string): string;
|
||||
export const constants: {
|
||||
@@ -1350,14 +1380,28 @@ declare module "node:fs" {
|
||||
isFile(): boolean;
|
||||
isDirectory(): boolean;
|
||||
isSymbolicLink(): boolean;
|
||||
isBlockDevice(): boolean;
|
||||
isCharacterDevice(): boolean;
|
||||
isFIFO(): boolean;
|
||||
isSocket(): boolean;
|
||||
readonly dev: number;
|
||||
readonly mode: number;
|
||||
readonly nlink: number;
|
||||
readonly uid: number;
|
||||
readonly gid: number;
|
||||
readonly rdev: number;
|
||||
readonly blksize: number;
|
||||
readonly ino: number;
|
||||
readonly size: number;
|
||||
readonly blocks: number;
|
||||
readonly nlink: number;
|
||||
readonly atimeMs: number;
|
||||
readonly mtimeMs: number;
|
||||
readonly ctimeMs: number;
|
||||
readonly birthtimeMs: number;
|
||||
readonly atime: Date;
|
||||
readonly mtime: Date;
|
||||
readonly ctime: Date;
|
||||
readonly birthtime: Date;
|
||||
}
|
||||
export function statSync(path: string): Stats;
|
||||
export function lstatSync(path: string): Stats;
|
||||
@@ -1672,6 +1716,15 @@ declare module "fs/promises" {
|
||||
[option: string]: unknown;
|
||||
},
|
||||
): Promise<import("node:fs").Dirent[]>;
|
||||
/* The async iterator form of fs.globSync: iterate it with for await. */
|
||||
export function glob(
|
||||
pattern: string | readonly string[],
|
||||
options?: import("node:fs").GlobOptions,
|
||||
): AsyncIterableIterator<string>;
|
||||
export function glob(
|
||||
pattern: string | readonly string[],
|
||||
options: import("node:fs").GlobOptionsWithFileTypes,
|
||||
): AsyncIterableIterator<import("node:fs").Dirent>;
|
||||
export function rm(path: string): Promise<void>;
|
||||
export function stat(path: string): Promise<import("node:fs").Stats>;
|
||||
export function statfs(
|
||||
@@ -3413,6 +3466,9 @@ declare module "node:zlib" {
|
||||
* 'error'. */
|
||||
declare module "net" {
|
||||
export interface Socket {
|
||||
/* Whether the socket keeps the event loop alive (ref'd by default). */
|
||||
ref(): void;
|
||||
unref(): void;
|
||||
readonly remoteAddress: string | undefined;
|
||||
/* true once the fd is gone (destroy() or full close) — Node's flag. */
|
||||
readonly destroyed: boolean;
|
||||
@@ -3477,6 +3533,9 @@ declare module "net" {
|
||||
once(event: "session", listener: (session: Buffer) => void): void;
|
||||
}
|
||||
export interface Server {
|
||||
/* Whether the server keeps the event loop alive (ref'd by default). */
|
||||
ref(): void;
|
||||
unref(): void;
|
||||
readonly listening: boolean;
|
||||
/* Node answers the server itself (`return this` chaining). */
|
||||
listen(port: number, callback?: () => void): Server;
|
||||
|
||||
@@ -27,7 +27,7 @@ const SCR_ARR_VALUE = 1;
|
||||
* module-visible memory, or trap without returning to the caller (the trap
|
||||
* writes runtime-private stderr/TLS state). Declaring this lets LLVM keep
|
||||
* array header loads out of loops whose only calls are these cold paths. */
|
||||
const READ_FALLBACK = " cold memory(read, inaccessiblemem: readwrite)";
|
||||
export const READ_FALLBACK = " cold memory(read, inaccessiblemem: readwrite)";
|
||||
|
||||
interface DenseGuard {
|
||||
/** The checked dense offset, in the target's size type. */
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { type ArraySearchKind, emitArraySearchHelpers } from "./array-search.js";
|
||||
import { emitStringInputs } from "./string-lifetimes.js";
|
||||
import { emitLlvmLayouts } from "./layouts.js";
|
||||
import { compileRegexMatcher, type RegexMatcherCode } from "./regex-matchers.js";
|
||||
import { CYC_BUF, CYC_HDR, FIRST_EMITTED_CYC_KIND, inlineAllocSupported } from "./alloc.js";
|
||||
@@ -77,7 +78,7 @@ import {
|
||||
type StringSplit,
|
||||
} from "./split-loops.js";
|
||||
import { scalarizeNumericRecords } from "../../ir/scalar-records.js";
|
||||
import { addRecordResultEntries } from "./record-results.js";
|
||||
import { RESULT_ENTRY_PREFIX, addRecordResultEntries } from "./record-results.js";
|
||||
import { specializeNumericCalls } from "../../ir/numeric-call-specialization.js";
|
||||
import { everyExpr, everyExprChild, everyStmtChild, everyStmtList } from "../../ir/traverse.js";
|
||||
import { analyzeIntegerRanges, INT32_RANGE, type IntegerRanges } from "../../ir/integer-ranges.js";
|
||||
@@ -6012,6 +6013,7 @@ export class LlEmitter {
|
||||
// tests small enough to inline at each call site.
|
||||
if (fn.name.startsWith("%dyn.class.protocol.fallback.")) attrs = `noinline ${attrs}`;
|
||||
else if (fn.name.startsWith("%dyn.class.protocol.")) attrs = `inlinehint ${attrs}`;
|
||||
if (fn.name.startsWith(RESULT_ENTRY_PREFIX)) attrs = `inlinehint ${attrs}`;
|
||||
const debug = this.debugScope === null ? "" : ` !dbg ${this.debugScope}`;
|
||||
const symbol = borrowed ? mangleBorrowedFunction(fn.name) : mangleFunction(fn.name);
|
||||
let body = `define internal ${ret} @${symbol}${int32Abi ? INT32_ABI_SUFFIX : ""}(${params.join(", ")}) ${attrs}${debug} { ; ${fn.name}\n${B.render()}\n}`;
|
||||
@@ -6836,10 +6838,9 @@ export class LlEmitter {
|
||||
storeLocalUnion(this, localStorage, s.value);
|
||||
break;
|
||||
}
|
||||
const concat = s.value;
|
||||
const suffix = matchStringSelfConcat(s.localId, concat);
|
||||
if (suffix && concat.kind === "strConcat") {
|
||||
this.emitStringSelfConcatAssign(s.localId, concat.left, suffix, false);
|
||||
const self = matchStringSelfConcat(s.localId, s.value);
|
||||
if (self) {
|
||||
this.emitStringSelfConcatAssign(s.localId, self.left, self.suffix, false);
|
||||
break;
|
||||
}
|
||||
const b = this.binding(s.localId);
|
||||
@@ -8226,7 +8227,11 @@ export class LlEmitter {
|
||||
e.kind === "seqExpr" &&
|
||||
isRefCounted(e.result.type) &&
|
||||
e.result.kind !== "strLit" &&
|
||||
this.discardableRead(e.result)
|
||||
(this.discardableRead(e.result) ||
|
||||
// A function-local binding's read has no effect of its own (a
|
||||
// compound assignment's result, say); a module binding's read may
|
||||
// throw before initialization, so it stays.
|
||||
(e.result.kind === "varRef" && this.binding(e.result.localId).kind === "local"))
|
||||
) {
|
||||
this.emitSequence(() => {
|
||||
for (const s of e.stmts) this.emitStmt(s);
|
||||
@@ -8287,8 +8292,11 @@ export class LlEmitter {
|
||||
// of first building the suffix as a separate string.
|
||||
const parts = stringParts(suffix);
|
||||
const mixed = parts.length > 1 || numberPart(parts[0]!) !== null;
|
||||
const charCode = mixed ? null : this.emitSingleCharCode(suffix);
|
||||
const inputs = mixed ? emitConcatInputs(this, parts) : [];
|
||||
const right = mixed ? null : this.emitExpr(suffix);
|
||||
// The concat only reads the suffix; relinquishing the binding below
|
||||
// runs no code that could rebind a borrowed local.
|
||||
const right = mixed || charCode !== null ? null : emitStringInputs(this, [suffix])[0]!;
|
||||
const b = this.binding(localId);
|
||||
const B = this.B;
|
||||
if (b.kind === "boxed") {
|
||||
@@ -8301,7 +8309,15 @@ export class LlEmitter {
|
||||
this.releaseValue(old, b.type);
|
||||
}
|
||||
let raw: string;
|
||||
if (right === null) {
|
||||
if (charCode !== null) {
|
||||
// The snapshot moves into the consuming character append.
|
||||
this.moveTemp(snapshot);
|
||||
this.declare(`declare ptr @scr_str_concat_move_char_code(ptr, double)`);
|
||||
raw = B.tmp();
|
||||
B.line(
|
||||
`${raw} = call ptr @scr_str_concat_move_char_code(ptr ${snapshot.name}, double ${charCode.name})`,
|
||||
);
|
||||
} else if (right === null) {
|
||||
raw = emitMixedConcat(this, snapshot.name, inputs);
|
||||
} else {
|
||||
this.declare(`declare ptr @scr_str_concat(ptr, ptr)`);
|
||||
@@ -8321,6 +8337,17 @@ export class LlEmitter {
|
||||
return result;
|
||||
}
|
||||
|
||||
/** The code of a one-argument `String.fromCharCode(c)` suffix, emitted, so
|
||||
* `s += String.fromCharCode(c)` appends the character without building
|
||||
* the one-character string; null for any other suffix. */
|
||||
private emitSingleCharCode(suffix: IrExpr): LlValue | null {
|
||||
if (suffix.kind !== "libCall" || suffix.fn !== "string.fromCharCode") return null;
|
||||
const packed = suffix.args[0];
|
||||
if (packed?.kind !== "arrayLit" || packed.elems.length !== 1 || packed.spreads?.length)
|
||||
return null;
|
||||
return this.emitExpr(packed.elems[0]!);
|
||||
}
|
||||
|
||||
/** `local = local + suffix` when the suffix cannot read or write the
|
||||
* local (an unboxed binding is only reachable through this function's own
|
||||
* expressions): reading the binding after the suffix gives the value the
|
||||
@@ -8347,14 +8374,22 @@ export class LlEmitter {
|
||||
return null;
|
||||
const parts = stringParts(suffix);
|
||||
const mixed = parts.length > 1 || numberPart(parts[0]!) !== null;
|
||||
const charCode = mixed ? null : this.emitSingleCharCode(suffix);
|
||||
const inputs = mixed ? emitConcatInputs(this, parts) : [];
|
||||
const right = mixed ? null : this.emitExpr(suffix);
|
||||
// The concat only reads the suffix, which may stay borrowed.
|
||||
const right = mixed || charCode !== null ? null : emitStringInputs(this, [suffix])[0]!;
|
||||
this.materializeSplitLocal(localId);
|
||||
const B = this.B;
|
||||
const head = B.tmp();
|
||||
B.line(`${head} = load ptr, ptr ${b.slot}`);
|
||||
let raw: string;
|
||||
if (right === null) {
|
||||
if (charCode !== null) {
|
||||
this.declare(`declare ptr @scr_str_concat_move_char_code(ptr, double)`);
|
||||
raw = B.tmp();
|
||||
B.line(
|
||||
`${raw} = call ptr @scr_str_concat_move_char_code(ptr ${head}, double ${charCode.name})`,
|
||||
);
|
||||
} else if (right === null) {
|
||||
raw = emitMixedConcat(this, head, inputs, true);
|
||||
} else {
|
||||
this.declare(`declare ptr @scr_str_concat_move(ptr, ptr)`);
|
||||
|
||||
@@ -26,7 +26,12 @@ import {
|
||||
withinUint32,
|
||||
} from "../../ir/integer-ranges.js";
|
||||
import { emitArrayValues } from "./expr-containers.js";
|
||||
import { emitCheckedRemainder, emitSignedIntegerRemainder } from "./integer-remainder.js";
|
||||
import {
|
||||
constantDivisor,
|
||||
emitCheckedRemainder,
|
||||
emitPowerOfTwoRemainder,
|
||||
emitSignedIntegerRemainder,
|
||||
} from "./integer-remainder.js";
|
||||
import { emitDenseArrayGet, emitDenseArrayState } from "./dense-array-access.js";
|
||||
import { emitWrappingSum, markWrappingOperands } from "./wrapping-sums.js";
|
||||
|
||||
@@ -300,7 +305,14 @@ export function emitOperatorExpr(
|
||||
}
|
||||
}
|
||||
if (e.op === "%") {
|
||||
const checked = emitCheckedRemainder(host, l, r, li, ri);
|
||||
const constant = constantDivisor(e.right, ri);
|
||||
const power = constant === null ? null : emitPowerOfTwoRemainder(host, l, constant);
|
||||
if (power) return power;
|
||||
// A fractional constant divisor never passes the integer check.
|
||||
const checked =
|
||||
constant !== null && !Number.isInteger(constant)
|
||||
? null
|
||||
: emitCheckedRemainder(host, l, r, li, ri);
|
||||
if (checked) return checked;
|
||||
}
|
||||
if (arith[e.op] !== undefined) {
|
||||
@@ -443,11 +455,8 @@ export function emitOperatorExpr(
|
||||
// `x = e` in expression position: the binding takes its OWN
|
||||
// reference (retain for ref kinds), the temp stays the yielded
|
||||
// value — release the old value before storing the retained value.
|
||||
const concat = e.value;
|
||||
const suffix = matchStringSelfConcat(e.localId, concat);
|
||||
if (suffix && concat.kind === "strConcat") {
|
||||
return host.emitStringSelfConcatAssign(e.localId, concat.left, suffix, true);
|
||||
}
|
||||
const self = matchStringSelfConcat(e.localId, e.value);
|
||||
if (self) return host.emitStringSelfConcatAssign(e.localId, self.left, self.suffix, true);
|
||||
const b = host.binding(e.localId);
|
||||
const v = host.emitExpr(e.value);
|
||||
if (host.storeInt32Local(e.localId, v, e.value)) return v;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { F64 } from "../../ir/ir.js";
|
||||
import { F64, type IrExpr } from "../../ir/ir.js";
|
||||
import type { LlValue, LlvmEmitterContext } from "./expr-context.js";
|
||||
import { f64Lit } from "./common.js";
|
||||
import { integerNumber, widenInteger, type LlInteger } from "./integer-values.js";
|
||||
import { emitTruncF64 } from "./trunc.js";
|
||||
|
||||
/** Integer proofs exclude negative zero and stay inside the exact-number
|
||||
* range, so signed i64 division cannot overflow. A possibly zero divisor
|
||||
@@ -43,14 +44,16 @@ export function emitSignedIntegerRemainder(
|
||||
return { name: result, type: F64 };
|
||||
}
|
||||
|
||||
const EXACT = 2 ** 53;
|
||||
/** 2^62, the bound on dividends the inline integer path accepts, as an
|
||||
* i64 literal (`String(2 ** 62)` rounds its digits). */
|
||||
const DIVIDEND_LIMIT = "4611686018427387904";
|
||||
|
||||
/** Convert an unproven double to a candidate i64. `freeze` keeps NaN and
|
||||
* out-of-range conversions from producing poison; the returned condition
|
||||
* is the exact round trip, so it rejects every non-integer. Integral
|
||||
* doubles inside the i64 range convert exactly; a passing value outside
|
||||
* it (|x| >= 2^63) can only pair with a frozen integer of magnitude above
|
||||
* 2^53. `-0` converts to 0 and passes. */
|
||||
* 2^62. `-0` converts to 0 and passes. */
|
||||
function candidateInteger(host: LlvmEmitterContext, value: string): [string, string] {
|
||||
const B = host.B;
|
||||
const raw = B.tmp(),
|
||||
@@ -66,13 +69,17 @@ function candidateInteger(host: LlvmEmitterContext, value: string): [string, str
|
||||
|
||||
/** JS `%` with an inline integer path for operands that are not both
|
||||
* proven integers with a nonzero divisor. At run time the dividend must be
|
||||
* an integer of magnitude <= 2^53 and the divisor a nonzero integer; then
|
||||
* an integer from -2^62 up to but not including 2^62 and the divisor a
|
||||
* nonzero integer; then
|
||||
* i64 `srem` is exact (no INT64_MIN / -1 overflow is possible), takes the
|
||||
* dividend's sign like JS, and an exact zero takes the dividend's sign
|
||||
* (`-4 % 2` and `-0 % 3` are -0). A divisor that passes the round trip
|
||||
* with magnitude above 2^53 exceeds the dividend's, so `srem` returns the
|
||||
* dividend exactly as JS does. Everything else (fractions, NaN, ±Infinity,
|
||||
* zero divisors, huge dividends) takes the floating remainder, unchanged.
|
||||
* (`-4 % 2` and `-0 % 3` are -0). The remainder of two doubles is itself a
|
||||
* double, so its conversion back is exact, including for dividends above
|
||||
* 2^53 (every double there is an integer). A divisor that passes the round
|
||||
* trip with magnitude above 2^62 exceeds the dividend's, so `srem` returns
|
||||
* the dividend exactly as JS does. Everything else (fractions, NaN,
|
||||
* ±Infinity, zero divisors, huge dividends) takes the floating remainder,
|
||||
* unchanged.
|
||||
* Returns null when the divisor is the constant zero (always NaN). */
|
||||
export function emitCheckedRemainder(
|
||||
host: LlvmEmitterContext,
|
||||
@@ -90,8 +97,9 @@ export function emitCheckedRemainder(
|
||||
const [integer, exact] = candidateInteger(host, left.name);
|
||||
const biased = B.tmp(),
|
||||
inRange = B.tmp();
|
||||
B.line(`${biased} = add i64 ${integer}, ${EXACT}`);
|
||||
B.line(`${inRange} = icmp ule i64 ${biased}, ${2 * EXACT}`);
|
||||
// -2^62 <= integer < 2^62: the biased value is nonnegative.
|
||||
B.line(`${biased} = add i64 ${integer}, ${DIVIDEND_LIMIT}`);
|
||||
B.line(`${inRange} = icmp sge i64 ${biased}, 0`);
|
||||
conditions.push(exact, inRange);
|
||||
x = integer;
|
||||
}
|
||||
@@ -147,3 +155,76 @@ export function emitCheckedRemainder(
|
||||
B.line(`${result} = phi double [ ${fastValue}, %${fast} ], [ ${floating}, %${slow} ]`);
|
||||
return { name: result, type: F64 };
|
||||
}
|
||||
|
||||
/** The value of a divisor known at compile time: a number literal, its
|
||||
* negation, `2 ** k` for an integer literal k from 0 to 1023 (exactly 2^k),
|
||||
* or an integer whose proven range is a single value. */
|
||||
export function constantDivisor(expr: IrExpr, integer: LlInteger | null): number | null {
|
||||
if (integer && integer.range.min === integer.range.max) return integer.range.min;
|
||||
if (expr.kind === "numLit") return expr.value;
|
||||
if (expr.kind === "unary" && expr.op === "-") {
|
||||
const value = constantDivisor(expr.operand, null);
|
||||
return value === null ? null : -value;
|
||||
}
|
||||
if (
|
||||
expr.kind === "bin" &&
|
||||
expr.op === "**" &&
|
||||
expr.left.kind === "numLit" &&
|
||||
expr.left.value === 2 &&
|
||||
expr.right.kind === "numLit" &&
|
||||
Number.isInteger(expr.right.value) &&
|
||||
expr.right.value >= 0 &&
|
||||
expr.right.value <= 1023
|
||||
)
|
||||
return 2 ** expr.right.value;
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Whether `value` is a power of two of at least 1. */
|
||||
function isPowerOfTwo(value: number): boolean {
|
||||
if (!Number.isFinite(value) || value < 1) return false;
|
||||
let power = 1;
|
||||
while (power < value) power *= 2;
|
||||
return power === value;
|
||||
}
|
||||
|
||||
/** JS `%` by a constant divisor whose magnitude c is a power of two of at
|
||||
* least 1, as `x - trunc(x / c) * c`, which is exact for every double x.
|
||||
* When |x| < c the quotient's magnitude is below 1 and truncates to zero,
|
||||
* so the result is x. Otherwise x / c is a normal double and exact, and
|
||||
* the truncated quotient times c is a multiple of c of magnitude at most
|
||||
* |x|, also exact. When c is below x's ulp, x is itself a multiple of c and
|
||||
* the difference is zero; otherwise both terms are multiples of x's ulp and
|
||||
* the difference, smaller than c, is exact. ±Infinity gives the default
|
||||
* NaN of `Infinity - Infinity`, the NaN fmod also returns for it, and a NaN
|
||||
* dividend propagates. A zero difference takes the dividend's sign
|
||||
* (`-4 % 2` and `-0 % 1` are -0). The divisor's sign does not change the
|
||||
* result. Returns null for any other divisor; a power of two below 1 keeps
|
||||
* frem, because x / c can overflow. */
|
||||
export function emitPowerOfTwoRemainder(
|
||||
host: LlvmEmitterContext,
|
||||
left: LlValue,
|
||||
divisor: number,
|
||||
): LlValue | null {
|
||||
const magnitude = Math.abs(divisor);
|
||||
if (!isPowerOfTwo(magnitude)) return null;
|
||||
const B = host.B;
|
||||
const c = f64Lit(magnitude);
|
||||
const quotient = B.tmp();
|
||||
B.line(`${quotient} = fdiv double ${left.name}, ${c}`);
|
||||
const whole = emitTruncF64(host, quotient);
|
||||
const multiple = B.tmp(),
|
||||
difference = B.tmp(),
|
||||
zero = B.tmp(),
|
||||
signedZero = B.tmp(),
|
||||
result = B.tmp();
|
||||
host.declare("declare double @llvm.copysign.f64(double, double)");
|
||||
B.line(`${multiple} = fmul double ${whole}, ${c}`);
|
||||
B.line(`${difference} = fsub double ${left.name}, ${multiple}`);
|
||||
B.line(`${zero} = fcmp oeq double ${difference}, ${f64Lit(0)}`);
|
||||
B.line(
|
||||
`${signedZero} = call double @llvm.copysign.f64(double ${f64Lit(0)}, double ${left.name})`,
|
||||
);
|
||||
B.line(`${result} = select i1 ${zero}, double ${signedZero}, double ${difference}`);
|
||||
return { name: result, type: F64 };
|
||||
}
|
||||
|
||||
@@ -381,6 +381,69 @@ export function emitFilesystemLibCall(host: LlvmEmitterContext, e: LibCallExpr):
|
||||
B.line(`call void @scr_watcher_close(ptr ${w.name})`);
|
||||
return { name: "", type: e.type };
|
||||
}
|
||||
if (e.fn === "fs.globTypesSync" || e.fn === "fsp.globTypes") {
|
||||
// Dirent rows from one glob snapshot (scr_glob.c): mode 1 is the
|
||||
// globSync form, 3 the fs/promises form with resolved parent paths.
|
||||
if (e.type.kind !== "array" || e.type.elem.kind !== "record") {
|
||||
throw new InternalCompilerError(
|
||||
`llvm emitter bug: ${e.fn} result is not a Dirent record array`,
|
||||
);
|
||||
}
|
||||
const arrayT = e.type;
|
||||
const recT = e.type.elem;
|
||||
const pattern = host.emitExpr(e.args[0]!);
|
||||
const options = host.emitExpr(e.args[1]!);
|
||||
const mode = e.fn === "fsp.globTypes" ? "3.0" : "1.0";
|
||||
host.declare(`declare ptr @scr_fs_glob(ptr, ptr, double)`);
|
||||
host.declare(`declare ${host.sizeType} @scr_fs_glob_count(ptr)`);
|
||||
host.declare(`declare ptr @scr_fs_glob_name(ptr, ${host.sizeType})`);
|
||||
host.declare(`declare ptr @scr_fs_glob_parent(ptr, ${host.sizeType})`);
|
||||
host.declare(`declare double @scr_fs_glob_type(ptr, ${host.sizeType})`);
|
||||
host.declare(`declare void @scr_fs_glob_free(ptr)`);
|
||||
const snap = B.tmp();
|
||||
B.line(
|
||||
`${snap} = call ptr @scr_fs_glob(ptr ${pattern.name}, ptr ${options.name}, double ${mode})`,
|
||||
);
|
||||
host.emitPendingCheck();
|
||||
const cnt = B.tmp();
|
||||
B.line(`${cnt} = call ${host.sizeType} @scr_fs_glob_count(ptr ${snap})`);
|
||||
const arr = B.tmp();
|
||||
B.line(`${arr} = ${arrNewCall(host.shapeHost, recT, cnt)}`);
|
||||
const arrayOut = host.own({ name: arr, type: arrayT });
|
||||
const iSlot = B.slot();
|
||||
B.entryAllocas.push(`${iSlot} = alloca ${host.sizeType}`);
|
||||
B.line(`store ${host.sizeType} 0, ptr ${iSlot}`);
|
||||
const lc = B.newLabel("gl.c");
|
||||
const lb = B.newLabel("gl.b");
|
||||
const le = B.newLabel("gl.e");
|
||||
B.br(lc);
|
||||
B.startBlock(lc);
|
||||
const i = B.tmp();
|
||||
const cont = B.tmp();
|
||||
B.line(`${i} = load ${host.sizeType}, ptr ${iSlot}`);
|
||||
B.line(`${cont} = icmp ult ${host.sizeType} ${i}, ${cnt}`);
|
||||
B.condBr(cont, lb, le);
|
||||
B.startBlock(lb);
|
||||
const row = B.tmp();
|
||||
B.line(`${row} = call ptr @${mangleRecordNew(recT.shapeId)}()`);
|
||||
const dt = B.tmp();
|
||||
B.line(`${dt} = call double @scr_fs_glob_type(ptr ${snap}, ${host.sizeType} ${i})`);
|
||||
host.storeField(host.recordFieldPtr(row, recT.shapeId, "%dtype").ptr, F64, dt, false);
|
||||
const nm = B.tmp();
|
||||
B.line(`${nm} = call ptr @scr_fs_glob_name(ptr ${snap}, ${host.sizeType} ${i}) ; +1`);
|
||||
B.line(`store ptr ${nm}, ptr ${host.recordFieldPtr(row, recT.shapeId, "name").ptr}`);
|
||||
const parent = B.tmp();
|
||||
B.line(`${parent} = call ptr @scr_fs_glob_parent(ptr ${snap}, ${host.sizeType} ${i}) ; +1`);
|
||||
B.line(`store ptr ${parent}, ptr ${host.recordFieldPtr(row, recT.shapeId, "parentPath").ptr}`);
|
||||
host.arrPush(arr, "ref", row); // push takes ownership of the row
|
||||
const i2 = B.tmp();
|
||||
B.line(`${i2} = add ${host.sizeType} ${i}, 1`);
|
||||
B.line(`store ${host.sizeType} ${i2}, ptr ${iSlot}`);
|
||||
B.br(lc);
|
||||
B.startBlock(le);
|
||||
B.line(`call void @scr_fs_glob_free(ptr ${snap})`);
|
||||
return arrayOut;
|
||||
}
|
||||
if (e.fn === "fs.readdirTypesSync" || e.fn === "fsp.readdirTypes") {
|
||||
// Dirent rows assembled inline from one scandir snapshot — the C
|
||||
// emitter's flat loop. The sync form checks a scandir error before
|
||||
|
||||
@@ -325,6 +325,10 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"fs.rmRetrySync": "scr_fs_rm_opts_retry",
|
||||
"fs.rmdirSync": "scr_fs_rmdir",
|
||||
"fs.readdirSync": "scr_fs_readdir",
|
||||
"fs.globSync": "scr_fs_glob_strings",
|
||||
"fs.callbackGlob": "scr_fs_glob_callback",
|
||||
"fsp.globStrings": "scr_fsp_glob_strings",
|
||||
"fsp.globError": "scr_fsp_glob_error",
|
||||
"fs.realpathSync": "scr_fs_realpath",
|
||||
"fs.realpathNativeSync": "scr_fs_realpath_promise",
|
||||
"fs.unlinkSync": "scr_fs_unlink",
|
||||
@@ -451,6 +455,20 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"stats.atimeMs": "scr_stats_atime_ms",
|
||||
"stats.mtimeMs": "scr_stats_mtime_ms",
|
||||
"stats.ctimeMs": "scr_stats_ctime_ms",
|
||||
"stats.birthtimeMs": "scr_stats_birthtime_ms",
|
||||
"stats.mode": "scr_stats_mode",
|
||||
"stats.uid": "scr_stats_uid",
|
||||
"stats.gid": "scr_stats_gid",
|
||||
"stats.rdev": "scr_stats_rdev",
|
||||
"stats.blksize": "scr_stats_blksize",
|
||||
"stats.isBlockDevice": "scr_stats_is_block_device",
|
||||
"stats.isCharacterDevice": "scr_stats_is_character_device",
|
||||
"stats.isFIFO": "scr_stats_is_fifo",
|
||||
"stats.isSocket": "scr_stats_is_socket",
|
||||
"stats.atime": "scr_stats_atime",
|
||||
"stats.mtime": "scr_stats_mtime",
|
||||
"stats.ctime": "scr_stats_ctime",
|
||||
"stats.birthtime": "scr_stats_birthtime",
|
||||
"text.decode": "scr_text_decode",
|
||||
"bytes.bufferSource": "scr_bytes_buffer_source",
|
||||
"text.decodeBufferSource": "scr_text_decode_buffer_source",
|
||||
@@ -924,6 +942,8 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
// the client request forms have non-generic shapes — special-cased.
|
||||
"net.serverPort": "scr_net_server_port",
|
||||
"net.serverListening": "scr_net_server_listening",
|
||||
"net.sockSetRef": "scr_net_sock_set_ref",
|
||||
"net.serverSetRef": "scr_net_server_set_ref",
|
||||
"net.sockWrite": "scr_net_sock_write_str",
|
||||
"net.sockWriteBytes": "scr_net_sock_write_bytes",
|
||||
"net.sockEnd": "scr_net_sock_end",
|
||||
@@ -1321,6 +1341,7 @@ export const USES_TIMERS_LIB_FNS = new Set<string>([
|
||||
"fs.existsChk",
|
||||
"fs.callbackValue",
|
||||
"fs.callbackCall",
|
||||
"fs.callbackGlob",
|
||||
"http.createServer",
|
||||
"http.createServerEmpty",
|
||||
"http.request",
|
||||
|
||||
@@ -912,9 +912,16 @@ test.each([
|
||||
expect(ir).not.toContain("@scr_union_new");
|
||||
expect(ir).not.toContain("@scr_union_release");
|
||||
expect(ir).not.toContain("@scr_union_get");
|
||||
// Holes, sparse and noncanonical indices keep the runtime state check.
|
||||
expect(ir).toContain("@scr_arr_state");
|
||||
expect(ir).toContain(elem.kind === "f64" ? "@scr_arr_get_f64" : "@scr_arr_get_bool");
|
||||
// Holes, sparse and noncanonical indices keep the runtime state check,
|
||||
// a read-only cold call, so loop-invariant header loads can be hoisted.
|
||||
expect(ir).toMatch(
|
||||
/call double @scr_arr_state\(.*\) cold memory\(read, inaccessiblemem: readwrite\)/,
|
||||
);
|
||||
expect(ir).toMatch(
|
||||
elem.kind === "f64"
|
||||
? /call double @scr_arr_get_f64\(.*\) cold memory\(read, inaccessiblemem: readwrite\)/
|
||||
: /call zeroext i1 @scr_arr_get_bool\(.*\) cold memory\(read, inaccessiblemem: readwrite\)/,
|
||||
);
|
||||
if (consumer !== "local") expect(ir).toContain("local.array.dense");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@ import type { LlValue, LlvmEmitterContext } from "./expr-context.js";
|
||||
import { ReferenceEffects } from "./reference-effects.js";
|
||||
import { SCR_ARR_FIELD, exactIndexLines } from "./common.js";
|
||||
import { exactInteger, widenInteger } from "./integer-values.js";
|
||||
import { READ_FALLBACK } from "./dense-array-access.js";
|
||||
|
||||
export interface LocalArrayRead {
|
||||
type: IrType;
|
||||
@@ -696,10 +697,15 @@ export function emitLocalArrayRead(
|
||||
if (scalar) {
|
||||
// Holes, present undefined, sparse and noncanonical indices: the
|
||||
// runtime state decides presence, then the strict getter reads it.
|
||||
// Both only read (the getter runs for a present element, so it does
|
||||
// not trap), which lets LLVM keep the array's header loads out of a
|
||||
// loop around an inlined read.
|
||||
const state = B.tmp(),
|
||||
present = B.tmp();
|
||||
host.declare(`declare double @scr_arr_state(ptr, double)`);
|
||||
B.line(`${state} = call double @scr_arr_state(ptr ${array.name}, double ${index.name})`);
|
||||
B.line(
|
||||
`${state} = call double @scr_arr_state(ptr ${array.name}, double ${index.name})${READ_FALLBACK}`,
|
||||
);
|
||||
B.line(`${present} = fcmp oeq double ${state}, 1.0`);
|
||||
B.condBr(present, slowValue, no);
|
||||
B.startBlock(slowValue);
|
||||
@@ -707,12 +713,14 @@ export function emitLocalArrayRead(
|
||||
bits = B.tmp();
|
||||
if (read.element.kind === "f64") {
|
||||
host.declare(`declare double @scr_arr_get_f64(ptr, double)`);
|
||||
B.line(`${value} = call double @scr_arr_get_f64(ptr ${array.name}, double ${index.name})`);
|
||||
B.line(
|
||||
`${value} = call double @scr_arr_get_f64(ptr ${array.name}, double ${index.name})${READ_FALLBACK}`,
|
||||
);
|
||||
B.line(`${bits} = bitcast double ${value} to i64`);
|
||||
} else {
|
||||
host.declare(`declare zeroext i1 @scr_arr_get_bool(ptr, double)`);
|
||||
B.line(
|
||||
`${value} = call zeroext i1 @scr_arr_get_bool(ptr ${array.name}, double ${index.name})`,
|
||||
`${value} = call zeroext i1 @scr_arr_get_bool(ptr ${array.name}, double ${index.name})${READ_FALLBACK}`,
|
||||
);
|
||||
B.line(`${bits} = zext i1 ${value} to i64`);
|
||||
}
|
||||
|
||||
@@ -73,6 +73,9 @@ test("projection-only records live in frame storage and producers fill it", asyn
|
||||
expect(projected).not.toMatch(/@sc_rnew_/);
|
||||
expect(projected).toMatch(/call void @sc_\w*into_add\(/);
|
||||
expect(projected).toMatch(/call void @sc_\w*into_shortest\(/);
|
||||
// The result-slot entries are small and called per use: hinted inline.
|
||||
expect(ir).toMatch(/^define internal void @sc_\w*into_add\([^)]*\) inlinehint /m);
|
||||
expect(ir).toMatch(/^define internal void @sc_\w*into_shortest\([^)]*\) inlinehint /m);
|
||||
// A literal stores every field: its storage is not zeroed first.
|
||||
expect(projected).not.toContain("zeroinitializer");
|
||||
// Stored values keep the heap path and the ordinary producer.
|
||||
|
||||
@@ -904,12 +904,21 @@ export function lowerFileHandleMethodCall(
|
||||
);
|
||||
}
|
||||
|
||||
/** Method calls on Stats-typed receivers: isFile()/isDirectory()/
|
||||
* isSymbolicLink() are pure reads on the stat snapshot (a followed
|
||||
* statSync snapshot never answers true to isSymbolicLink — take
|
||||
* lstatSync's, Node's own split). Everything else @types/node declares
|
||||
* (mtime, mode, ...) fences member-qualified. Null for non-Stats
|
||||
* receivers. */
|
||||
/** The Stats type tests, each a read of the snapshot's mode. */
|
||||
export const STATS_TYPE_TESTS: Record<string, IrLibFn> = {
|
||||
isFile: "stats.isFile",
|
||||
isDirectory: "stats.isDirectory",
|
||||
isSymbolicLink: "stats.isSymbolicLink",
|
||||
isBlockDevice: "stats.isBlockDevice",
|
||||
isCharacterDevice: "stats.isCharacterDevice",
|
||||
isFIFO: "stats.isFIFO",
|
||||
isSocket: "stats.isSocket",
|
||||
};
|
||||
|
||||
/** Method calls on Stats-typed receivers: the type tests are pure reads on
|
||||
* the stat snapshot (a followed statSync snapshot never answers true to
|
||||
* isSymbolicLink — take lstatSync's, Node's own split). Anything else
|
||||
* fences member-qualified. Null for non-Stats receivers. */
|
||||
export function lowerStatsMethodCall(
|
||||
lowerer: Lowerer,
|
||||
call: ts.CallExpression,
|
||||
@@ -919,23 +928,15 @@ export function lowerStatsMethodCall(
|
||||
if (lowerer.mapTypeOf(lowerer.typeOf(access.expression))?.kind !== "stats") return null;
|
||||
if (!lowerer.isStdlibMember(access)) return null;
|
||||
const name = access.name.text;
|
||||
if (
|
||||
(name === "isFile" || name === "isDirectory" || name === "isSymbolicLink") &&
|
||||
call.arguments.length === 0
|
||||
) {
|
||||
const fn = Object.hasOwn(STATS_TYPE_TESTS, name) ? STATS_TYPE_TESTS[name] : undefined;
|
||||
if (fn && call.arguments.length === 0) {
|
||||
const receiver = lowerer.lowerExpr(access.expression);
|
||||
const fn =
|
||||
name === "isFile"
|
||||
? "stats.isFile"
|
||||
: name === "isDirectory"
|
||||
? "stats.isDirectory"
|
||||
: "stats.isSymbolicLink";
|
||||
return { kind: "libCall", fn, args: [receiver], type: BOOL, loc: locOf(call) };
|
||||
}
|
||||
lowerer.noLowering(
|
||||
`Stats.${name}`,
|
||||
call,
|
||||
"isFile(), isDirectory(), isSymbolicLink(), dev, ino, size, blocks, nlink, atimeMs, mtimeMs, and ctimeMs are the supported Stats members",
|
||||
"isFile(), isDirectory(), isSymbolicLink(), isBlockDevice(), isCharacterDevice(), isFIFO(), isSocket(), dev, mode, nlink, uid, gid, rdev, blksize, ino, size, blocks, atimeMs, mtimeMs, ctimeMs, birthtimeMs, atime, mtime, ctime, and birthtime are the supported Stats members",
|
||||
lowerer.checker.getSymbolAtLocation(access.name),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
import * as ts from "../../ts7/adapter.js";
|
||||
import { type Lowerer } from "../lowerer.js";
|
||||
import {
|
||||
BOOL,
|
||||
DYN,
|
||||
F64,
|
||||
STRING,
|
||||
VOID,
|
||||
arrayOf,
|
||||
type IrExpr,
|
||||
type IrStmt,
|
||||
type IrType,
|
||||
type SrcLoc,
|
||||
} from "../../../ir/ir.js";
|
||||
import { dynUndefinedExpr, numLit } from "../../../ir/build.js";
|
||||
import { InternalCompilerError } from "../../../errors.js";
|
||||
import { locOf } from "../../program.js";
|
||||
import { checkForAwaitBinding } from "../lower-async-iteration.js";
|
||||
import { builtinImportOf } from "./module-bindings.js";
|
||||
|
||||
/* fs.globSync, fs.glob and fs/promises glob (scr_glob.c, a port of Node's
|
||||
* lib/internal/fs/glob.js). The pattern and options cross as dyn values so
|
||||
* the runtime validates them in Node's order. The result form (strings or
|
||||
* Dirent rows) comes from the call's mapped result type, so withFileTypes
|
||||
* must be statically known; the runtime checks that the option agrees. */
|
||||
|
||||
/** The interned Dirent record array (type-mapper.ts), or null. */
|
||||
function direntArray(lowerer: Lowerer, type: IrType | null | undefined): boolean {
|
||||
if (type?.kind !== "array" || type.elem.kind !== "record") return false;
|
||||
const shape = lowerer.shapes.get(type.elem.shapeId);
|
||||
return (
|
||||
!!shape &&
|
||||
!shape.tuple &&
|
||||
!shape.indexValue &&
|
||||
shape.fields.length === 3 &&
|
||||
shape.fields.some((f) => f.name === "%dtype")
|
||||
);
|
||||
}
|
||||
|
||||
/** An exclude function receives Node's Dirent objects when withFileTypes is
|
||||
* set, which have no native representation here. */
|
||||
function fenceDirentExclude(lowerer: Lowerer, call: ts.CallExpression, what: string): void {
|
||||
const options = call.arguments[1];
|
||||
if (!options) return;
|
||||
const exclude = lowerer.checker.getPropertyOfType(lowerer.typeOf(options), "exclude");
|
||||
if (!exclude) return;
|
||||
const type = lowerer.checker.getTypeOfSymbolAtLocation(exclude, options);
|
||||
if (lowerer.checker.getCallSignatures(type).length === 0) return;
|
||||
lowerer.noLowering(
|
||||
`${what} with an exclude function and withFileTypes`,
|
||||
options,
|
||||
"pass exclude as a string[] of glob patterns, or match on names without withFileTypes",
|
||||
);
|
||||
}
|
||||
|
||||
function globArgs(lowerer: Lowerer, call: ts.CallExpression, what: string): IrExpr[] {
|
||||
if (call.arguments.some(ts.isSpreadElement)) {
|
||||
lowerer.noLowering(`${what} with spread arguments`, call, "write the arguments positionally");
|
||||
}
|
||||
return call.arguments.map((arg) => lowerer.lowerExprExpecting(arg, DYN));
|
||||
}
|
||||
|
||||
/** `globSync(pattern, options?)`: string[] or Dirent[]. */
|
||||
export function lowerFsGlobSyncCall(
|
||||
lowerer: Lowerer,
|
||||
call: ts.CallExpression,
|
||||
loc: SrcLoc,
|
||||
): IrExpr {
|
||||
const args = globArgs(lowerer, call, "fs.globSync");
|
||||
const callType = lowerer.mapTypeOf(lowerer.typeOf(call));
|
||||
const types = direntArray(lowerer, callType);
|
||||
if (types) fenceDirentExclude(lowerer, call, "fs.globSync");
|
||||
if (!types && !(callType?.kind === "array" && callType.elem.kind === "string")) {
|
||||
lowerer.noLowering(
|
||||
"fs.globSync where the result is not string[] or Dirent[]",
|
||||
call,
|
||||
"pass withFileTypes as a literal true or false so the result type is known",
|
||||
);
|
||||
}
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: types ? "fs.globTypesSync" : "fs.globSync",
|
||||
args: [args[0] ?? dynUndefinedExpr(loc), args[1] ?? dynUndefinedExpr(loc)],
|
||||
type: types ? callType! : arrayOf(STRING),
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** `fs.glob(pattern[, options], callback)`: the argument vector goes to the
|
||||
* runtime, which validates it and calls back with string[]. */
|
||||
export function lowerFsGlobCallbackCall(
|
||||
lowerer: Lowerer,
|
||||
call: ts.CallExpression,
|
||||
loc: SrcLoc,
|
||||
): IrExpr {
|
||||
const args = globArgs(lowerer, call, "fs.glob");
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "fs.callbackGlob",
|
||||
args: [{ kind: "dynArrLit", elems: args, type: DYN, loc }],
|
||||
type: VOID,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** True when `node` calls fs/promises glob (any import spelling). */
|
||||
export function isFspGlobCall(lowerer: Lowerer, node: ts.Expression): node is ts.CallExpression {
|
||||
let expr = node;
|
||||
while (ts.isParenthesizedExpression(expr)) expr = expr.expression;
|
||||
if (!ts.isCallExpression(expr) || expr.questionDotToken) return false;
|
||||
const callee = expr.expression;
|
||||
const bi = ts.isIdentifier(callee)
|
||||
? builtinImportOf(lowerer, callee)
|
||||
: ts.isPropertyAccessExpression(callee) && !callee.questionDotToken
|
||||
? lowerer.builtinMemberOf(callee)
|
||||
: null;
|
||||
return bi?.module === "fs/promises" && bi.member === "glob";
|
||||
}
|
||||
|
||||
/** fs/promises `glob(pattern, options?)` as the head of a `for await` loop
|
||||
* (lowerForOf marks the call): the matches as an array the loop awaits in
|
||||
* order. The walk runs when the loop starts, like the other fs/promises
|
||||
* operations, which complete before their continuations. Other uses of
|
||||
* the async iterator have no lowering. */
|
||||
export function lowerFspGlobCall(lowerer: Lowerer, call: ts.CallExpression, loc: SrcLoc): IrExpr {
|
||||
if (lowerer.fspGlobHead !== call) {
|
||||
lowerer.noLowering(
|
||||
"fs/promises glob outside a for await loop",
|
||||
call,
|
||||
"iterate the matches directly with for await (const path of glob(pattern)), or use fs.globSync",
|
||||
);
|
||||
}
|
||||
const args = globArgs(lowerer, call, "fs/promises glob");
|
||||
// The async iterator's yielded type (its first type argument).
|
||||
const yielded = lowerer.checker.getTypeArguments(lowerer.typeOf(call) as ts.TypeReference)[0];
|
||||
const element = yielded ? lowerer.mapTypeOf(yielded) : null;
|
||||
const rows: IrType | null = element ? { kind: "array", elem: element } : null;
|
||||
const types = direntArray(lowerer, rows);
|
||||
if (types) fenceDirentExclude(lowerer, call, "fs/promises glob");
|
||||
if (!types && element?.kind !== "string") {
|
||||
lowerer.noLowering(
|
||||
"fs/promises glob where the yielded value is not a string or a Dirent",
|
||||
call,
|
||||
"pass withFileTypes as a literal true or false so the yielded type is known",
|
||||
);
|
||||
}
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: types ? "fsp.globTypes" : "fsp.globStrings",
|
||||
args: [args[0] ?? dynUndefinedExpr(loc), args[1] ?? dynUndefinedExpr(loc)],
|
||||
type: types ? rows! : arrayOf(STRING),
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** `for await (const path of glob(pattern, options))`: the walk runs when
|
||||
* the loop starts and its matches bind in order, one microtask hop per
|
||||
* step. When the walk ended with an error (an exclude callback threw), the
|
||||
* matches found before it bind first and the error is thrown when the loop
|
||||
* reaches the end, as Node's async iterator yields them before rejecting;
|
||||
* leaving the loop early with break or return skips it. */
|
||||
export function lowerFspGlobForAwait(
|
||||
lowerer: Lowerer,
|
||||
stmt: ts.ForOfStatement,
|
||||
call: ts.CallExpression,
|
||||
): IrStmt {
|
||||
const binding = checkForAwaitBinding(lowerer, stmt);
|
||||
const loc = locOf(stmt);
|
||||
lowerer.fspGlobHead = call;
|
||||
let rowsExpr: IrExpr;
|
||||
try {
|
||||
rowsExpr = lowerer.lowerExpr(call);
|
||||
} finally {
|
||||
lowerer.fspGlobHead = null;
|
||||
}
|
||||
if (rowsExpr.type.kind !== "array") {
|
||||
throw new InternalCompilerError("fs/promises glob head did not lower to an array");
|
||||
}
|
||||
const rowsT = rowsExpr.type;
|
||||
const elemT = rowsT.elem;
|
||||
lowerer.scopes.push(new Map());
|
||||
try {
|
||||
const rows = lowerer.declareHiddenLocal("%globRows", rowsT);
|
||||
const error = lowerer.declareHiddenLocal("%globError", DYN);
|
||||
const index = lowerer.declareHiddenLocal("%globIndex", F64);
|
||||
index.mutable = true;
|
||||
const item = lowerer.declareLocal(
|
||||
binding.declaration.name,
|
||||
binding.declaration.name.text,
|
||||
elemT,
|
||||
binding.mutable,
|
||||
);
|
||||
const rowsRef = (): IrExpr => ({ kind: "varRef", localId: rows.id, type: rowsT, loc });
|
||||
const indexRef = (): IrExpr => ({ kind: "varRef", localId: index.id, type: F64, loc });
|
||||
const errorRef = (): IrExpr => ({ kind: "varRef", localId: error.id, type: DYN, loc });
|
||||
const body = lowerer.inCtl("loop", () => lowerer.lowerScopedBlock(stmt.statement));
|
||||
const loop: IrStmt = {
|
||||
kind: "while",
|
||||
cond: { kind: "boolLit", value: true, type: BOOL, loc },
|
||||
body: [
|
||||
{
|
||||
kind: "exprStmt",
|
||||
expr: { kind: "libCall", fn: "async.hop", args: [], type: VOID, loc },
|
||||
loc,
|
||||
},
|
||||
{
|
||||
kind: "if",
|
||||
cond: {
|
||||
kind: "unary",
|
||||
op: "!",
|
||||
operand: { kind: "arrayHas", arr: rowsRef(), index: indexRef(), type: BOOL, loc },
|
||||
type: BOOL,
|
||||
loc,
|
||||
},
|
||||
then: [
|
||||
{
|
||||
kind: "if",
|
||||
cond: {
|
||||
kind: "unary",
|
||||
op: "!",
|
||||
operand: { kind: "dynTest", test: "undefined", value: errorRef(), type: BOOL, loc },
|
||||
type: BOOL,
|
||||
loc,
|
||||
},
|
||||
then: [{ kind: "throw", value: errorRef(), loc }],
|
||||
else_: null,
|
||||
loc,
|
||||
},
|
||||
{ kind: "break", loc },
|
||||
],
|
||||
else_: null,
|
||||
loc,
|
||||
},
|
||||
{
|
||||
kind: "varDecl",
|
||||
localId: item.id,
|
||||
init: { kind: "arrayGet", arr: rowsRef(), index: indexRef(), type: elemT, loc },
|
||||
loc,
|
||||
},
|
||||
{
|
||||
kind: "assign",
|
||||
localId: index.id,
|
||||
value: { kind: "bin", op: "+", left: indexRef(), right: numLit(1, loc), type: F64, loc },
|
||||
loc,
|
||||
},
|
||||
...body,
|
||||
],
|
||||
loc,
|
||||
};
|
||||
return {
|
||||
kind: "block",
|
||||
body: [
|
||||
{ kind: "varDecl", localId: rows.id, init: rowsExpr, loc },
|
||||
{
|
||||
kind: "varDecl",
|
||||
localId: error.id,
|
||||
init: { kind: "libCall", fn: "fsp.globError", args: [], type: DYN, loc },
|
||||
loc,
|
||||
},
|
||||
{ kind: "varDecl", localId: index.id, init: numLit(0, loc), loc },
|
||||
loop,
|
||||
],
|
||||
loc,
|
||||
};
|
||||
} finally {
|
||||
lowerer.scopes.pop();
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
lowerPathParseCall,
|
||||
} from "./operating-system.js";
|
||||
import { lowerQuerystringParseCall, lowerQuerystringStringifyCall } from "./querystring.js";
|
||||
import { lowerFsGlobCallbackCall, lowerFsGlobSyncCall, lowerFspGlobCall } from "./fs-glob.js";
|
||||
|
||||
export function lowerBuiltinModuleCall(
|
||||
lowerer: Lowerer,
|
||||
@@ -59,6 +60,12 @@ export function lowerBuiltinModuleCall(
|
||||
if (bi.module === "buffer" && ["isAscii", "isUtf8", "transcode"].includes(bi.member)) {
|
||||
return lowerBufferEncodingCall(lowerer, expr, bi, fn, loc);
|
||||
}
|
||||
if (bi.module === "fs" && bi.member === "glob")
|
||||
return lowerFsGlobCallbackCall(lowerer, expr, loc);
|
||||
if (bi.module === "fs" && bi.member === "globSync")
|
||||
return lowerFsGlobSyncCall(lowerer, expr, loc);
|
||||
if (bi.module === "fs/promises" && bi.member === "glob")
|
||||
return lowerFspGlobCall(lowerer, expr, loc);
|
||||
if (fn.fn === "fs.callbackCall" && bi.member !== "rename") {
|
||||
return lowerFsCallbackCall(lowerer, expr, bi, loc);
|
||||
}
|
||||
|
||||
@@ -8,9 +8,11 @@ import {
|
||||
BOOL,
|
||||
CHILDSTREAM_T,
|
||||
CHILDWRITER_T,
|
||||
DATE_T,
|
||||
F64,
|
||||
FILEHANDLE_T,
|
||||
type IrExpr,
|
||||
type IrLibFn,
|
||||
type IrType,
|
||||
STRING,
|
||||
} from "../../../ir/ir.js";
|
||||
@@ -23,6 +25,28 @@ import { storedTextCodecClassOf } from "./text-codecs.js";
|
||||
* — null for a normal exit or spawn failure; a timeout kill reports its
|
||||
* killSignal, Node's shape). Null for everything else, so the ordinary
|
||||
* chain (and its fences) keeps going. */
|
||||
|
||||
/** Stats data members: each reads the snapshot (scr_stats_*). */
|
||||
const STATS_FIELDS: Record<string, IrLibFn> = {
|
||||
dev: "stats.dev",
|
||||
mode: "stats.mode",
|
||||
nlink: "stats.nlink",
|
||||
uid: "stats.uid",
|
||||
gid: "stats.gid",
|
||||
rdev: "stats.rdev",
|
||||
blksize: "stats.blksize",
|
||||
ino: "stats.ino",
|
||||
blocks: "stats.blocks",
|
||||
atimeMs: "stats.atimeMs",
|
||||
mtimeMs: "stats.mtimeMs",
|
||||
ctimeMs: "stats.ctimeMs",
|
||||
birthtimeMs: "stats.birthtimeMs",
|
||||
atime: "stats.atime",
|
||||
mtime: "stats.mtime",
|
||||
ctime: "stats.ctime",
|
||||
birthtime: "stats.birthtime",
|
||||
};
|
||||
const DATE_FIELDS = new Set(["atime", "mtime", "ctime", "birthtime"]);
|
||||
export function lowerBuiltinExtraProperty(
|
||||
lowerer: Lowerer,
|
||||
expr: ts.PropertyAccessExpression,
|
||||
@@ -199,26 +223,12 @@ export function lowerBuiltinExtraProperty(
|
||||
lowerer.checker.getSymbolAtLocation(expr.name),
|
||||
);
|
||||
}
|
||||
if (
|
||||
kind === "stats" &&
|
||||
(name === "dev" ||
|
||||
name === "ino" ||
|
||||
name === "blocks" ||
|
||||
name === "nlink" ||
|
||||
name === "atimeMs" ||
|
||||
name === "mtimeMs" ||
|
||||
name === "ctimeMs")
|
||||
) {
|
||||
if (kind === "stats" && Object.hasOwn(STATS_FIELDS, name)) {
|
||||
const receiver = lowerer.lowerExpr(expr.expression);
|
||||
const fn = `stats.${name}` as
|
||||
| "stats.dev"
|
||||
| "stats.ino"
|
||||
| "stats.blocks"
|
||||
| "stats.nlink"
|
||||
| "stats.atimeMs"
|
||||
| "stats.mtimeMs"
|
||||
| "stats.ctimeMs";
|
||||
return { kind: "libCall", fn, args: [receiver], type: F64, loc };
|
||||
const fn = STATS_FIELDS[name]!;
|
||||
// The Date fields are the *Ms values rounded, as Node's dateFromMs builds them.
|
||||
const type = DATE_FIELDS.has(name) ? DATE_T : F64;
|
||||
return { kind: "libCall", fn, args: [receiver], type, loc };
|
||||
}
|
||||
if (kind === "spawnRes" && name === "signal") {
|
||||
const receiver = lowerer.lowerExpr(expr.expression);
|
||||
|
||||
@@ -279,7 +279,7 @@ function classAsyncIteratorPlan(
|
||||
};
|
||||
}
|
||||
|
||||
function checkForAwaitBinding(
|
||||
export function checkForAwaitBinding(
|
||||
lowerer: Lowerer,
|
||||
stmt: ts.ForOfStatement,
|
||||
): { declaration: ts.VariableDeclaration & { name: ts.Identifier }; mutable: boolean } {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { isOptionalProcessStreamProperty } from "./builtins/process.js";
|
||||
import { lowerWorkerMetadata } from "./builtins/workers.js";
|
||||
import { lowerBlobInstanceof } from "./builtins/blob.js";
|
||||
import { lowerKeyObjectInstanceof } from "./builtins/key-object.js";
|
||||
import { STATS_TYPE_TESTS } from "./builtins/filesystem.js";
|
||||
import { threadsImportOf } from "../threads-import.js";
|
||||
import { experimentalImportOf, lifetimeRegionMemberOf } from "../experimental-import.js";
|
||||
import { dynUndefinedExpr, nodeThrowExpr, numLit, strLit, varRef } from "../../ir/build.js";
|
||||
@@ -29,7 +30,7 @@ import { checkedClassAssertion, checkedClassUnionAssertion } from "./class-asser
|
||||
import { checkedArmExtraction } from "./coercions/unions.js";
|
||||
import { narrowClassUnion, narrowStoredClassValue } from "./class-unions.js";
|
||||
import { lowerUnionFieldRead, lowerUnionFieldWrite } from "./expressions/union-fields.js";
|
||||
import { captureContextArguments } from "./function-context.js";
|
||||
import { captureContextArguments, declareContextLocal } from "./function-context.js";
|
||||
import { OBJECT_CALLABLE_VALUES } from "./surfaces.js";
|
||||
import { wasiGuestPath } from "../../wasi-paths.js";
|
||||
import {
|
||||
@@ -5081,13 +5082,13 @@ export function lowerIntrinsicProperty(
|
||||
const receiver = lowerer.lowerExpr(expr.expression);
|
||||
return { kind: "libCall", fn: "stats.size", args: [receiver], type: F64, loc: locOf(expr) };
|
||||
}
|
||||
if (name === "isFile" || name === "isDirectory") {
|
||||
if (Object.hasOwn(STATS_TYPE_TESTS, name)) {
|
||||
lowerer.unsupported("SC1090", expr, `Stats methods as values (call '${name}' directly)`);
|
||||
}
|
||||
lowerer.noLowering(
|
||||
`Stats.${name}`,
|
||||
expr,
|
||||
"isFile(), isDirectory(), isSymbolicLink(), dev, ino, size, blocks, nlink, atimeMs, mtimeMs, and ctimeMs are the supported Stats members",
|
||||
"isFile(), isDirectory(), isSymbolicLink(), isBlockDevice(), isCharacterDevice(), isFIFO(), isSocket(), dev, mode, nlink, uid, gid, rdev, blksize, ino, size, blocks, atimeMs, mtimeMs, ctimeMs, birthtimeMs, atime, mtime, ctime, and birthtime are the supported Stats members",
|
||||
lowerer.checker.getSymbolAtLocation(expr.name),
|
||||
);
|
||||
}
|
||||
@@ -15045,7 +15046,29 @@ function lowerFieldCompoundValue(
|
||||
target.obj = save(target.obj, "%compoundReceiver");
|
||||
// Accessors observe getter, RHS side effects, then setter, all through
|
||||
// the saved receiver.
|
||||
const read = save(lowerer.fieldGetExpr(target, locOf(access), access), "%compoundOld");
|
||||
const fieldRead = lowerer.fieldGetExpr(target, locOf(access), access);
|
||||
// `+=` on a stored string field reads it into a mutable local, so the
|
||||
// concat below can append to that local in place.
|
||||
const storedString =
|
||||
op === "+" &&
|
||||
fieldRead.type.kind === "string" &&
|
||||
target.fieldType.kind === "string" &&
|
||||
(target.container === "class" || target.container === "record");
|
||||
let read: IrExpr;
|
||||
if (storedString) {
|
||||
const old = declareContextLocal(
|
||||
lowerer.ctx,
|
||||
"%compoundOld",
|
||||
STRING,
|
||||
true,
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
body.push({ kind: "varDecl", localId: old.id, init: fieldRead, loc: fieldRead.loc });
|
||||
read = varRef(old.id, STRING, fieldRead.loc);
|
||||
} else {
|
||||
read = save(fieldRead, "%compoundOld");
|
||||
}
|
||||
const rhs = save(
|
||||
rhsNode ? lowerer.lowerExpr(rhsNode) : { kind: "numLit", value: 1, type: F64, loc },
|
||||
"%compoundRhs",
|
||||
@@ -15055,16 +15078,12 @@ function lowerFieldCompoundValue(
|
||||
? { kind: "dynCheck", value: rhs, type: F64, loc: rhs.loc }
|
||||
: lowerOptionalNumber(lowerer, rhs, loc);
|
||||
let value: IrExpr;
|
||||
let appended: IrExpr | null = null;
|
||||
const numericRead = lowerOptionalNumber(lowerer, read, loc, access);
|
||||
const bigintValue = rhsNode ? bigintCompoundValue(op, read, rhs, loc) : null;
|
||||
if (op === "+" && read.type.kind === "string") {
|
||||
value = {
|
||||
kind: "strConcat",
|
||||
left: read,
|
||||
right: lowerer.ensureString(rhs, rhsNode ?? access),
|
||||
type: STRING,
|
||||
loc,
|
||||
};
|
||||
appended = lowerer.ensureString(rhs, rhsNode ?? access);
|
||||
value = { kind: "strConcat", left: read, right: appended, type: STRING, loc };
|
||||
} else if (numericRead.type.kind === "f64" && numericRhs.type.kind === "f64") {
|
||||
value = { kind: "bin", op, left: numericRead, right: numericRhs, type: F64, loc };
|
||||
} else if (bigintValue) {
|
||||
@@ -15086,6 +15105,30 @@ function lowerFieldCompoundValue(
|
||||
} else {
|
||||
lowerer.unsupported("SC1043", access);
|
||||
}
|
||||
if (storedString && rhs.type.kind === "string" && read.kind === "varRef" && appended !== null) {
|
||||
// The field gives up its value before the concat (no code runs in
|
||||
// between: both operands are already strings), and the concat is the
|
||||
// self-append `old = old + rhs`, which hands the local's reference to
|
||||
// the runtime: the old string is then uniquely owned unless something
|
||||
// else holds it, and is appended to in place. Repeated `this.text += s`
|
||||
// is linear rather than a copy per append.
|
||||
const oldId = read.localId;
|
||||
const oldLoc = read.loc;
|
||||
const oldRef = (): IrExpr => varRef(oldId, STRING, oldLoc);
|
||||
const concat: IrExpr = {
|
||||
kind: "strConcat",
|
||||
left: oldRef(),
|
||||
right: appended,
|
||||
type: STRING,
|
||||
loc,
|
||||
};
|
||||
body.push(
|
||||
lowerer.fieldSetStmt(target, { kind: "strLit", value: "", type: STRING, loc }, loc, access),
|
||||
{ kind: "assign", localId: oldId, value: concat, loc },
|
||||
lowerer.fieldSetStmt(target, oldRef(), loc, access),
|
||||
);
|
||||
return { kind: "seqExpr", stmts: body, result: oldRef(), type: STRING, loc };
|
||||
}
|
||||
const result = save(value, "%compoundResult");
|
||||
body.push(
|
||||
lowerer.fieldSetStmt(target, lowerer.coerceInto(access, result, target.fieldType), loc, access),
|
||||
|
||||
@@ -1920,6 +1920,18 @@ function lowerNetServerMethodCall(
|
||||
loc,
|
||||
};
|
||||
}
|
||||
if ((name === "ref" || name === "unref") && args.length === 0) {
|
||||
// Whether the server keeps the event loop alive.
|
||||
requireStatementPosition(lowerer, call, `server.${name}()`);
|
||||
const receiver = coerceToHandle(lowerer, access.expression, NETSERVER_T);
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "net.serverSetRef",
|
||||
args: [receiver, boolLit(name === "ref", loc)],
|
||||
type: VOID,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
if (name === "address") {
|
||||
// The full AddressInfo record (the dgram.address materialization).
|
||||
// The composed `server.address().port` read already lowered through
|
||||
@@ -1964,6 +1976,18 @@ function lowerNetSocketMethodCall(
|
||||
const name = access.name.text;
|
||||
const loc = locOf(call);
|
||||
const args = call.arguments;
|
||||
if ((name === "ref" || name === "unref") && args.length === 0) {
|
||||
// Whether the socket keeps the event loop alive.
|
||||
requireStatementPosition(lowerer, call, `socket.${name}()`);
|
||||
const receiver = lowerer.lowerExpr(access.expression);
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "net.sockSetRef",
|
||||
args: [receiver, boolLit(name === "ref", loc)],
|
||||
type: VOID,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
if (name === "write" || name === "end") {
|
||||
requireStatementPosition(lowerer, call, `socket.${name}(...)`);
|
||||
// write(chunk, encoding) — the two-argument encoding form: 'buffer'
|
||||
|
||||
@@ -129,6 +129,7 @@ import {
|
||||
lowerServerCloseOverrideAssignment,
|
||||
} from "./lower-server.js";
|
||||
import { lowerUrlAssignment } from "./builtins/url.js";
|
||||
import { isFspGlobCall, lowerFspGlobForAwait } from "./builtins/fs-glob.js";
|
||||
import {
|
||||
isNativeFfiRequire,
|
||||
builtinMemberRequireDecl,
|
||||
@@ -7828,6 +7829,11 @@ export function lowerForOf(lowerer: Lowerer, stmt: ts.ForOfStatement): IrStmt {
|
||||
// A synchronous array iterator is also valid in `for await`. Its values
|
||||
// take the async-from-sync path below; other async sources retain their
|
||||
// dedicated request protocols.
|
||||
if (stmt.awaitModifier) {
|
||||
let head: ts.Expression = stmt.expression;
|
||||
while (ts.isParenthesizedExpression(head)) head = head.expression;
|
||||
if (isFspGlobCall(lowerer, head)) return lowerFspGlobForAwait(lowerer, stmt, head);
|
||||
}
|
||||
const awaitArray =
|
||||
!!stmt.awaitModifier && lowerer.mapTypeOf(lowerer.typeOf(stmt.expression))?.kind === "array";
|
||||
if (stmt.awaitModifier && !awaitArray) {
|
||||
|
||||
@@ -1819,6 +1819,9 @@ export class Lowerer {
|
||||
/** for-of loops over arrays that can hold neither holes nor undefined:
|
||||
* each pass reads the element payload directly. */
|
||||
readonly presentElementLoops = new Set<ts.ForOfStatement>();
|
||||
/** The fs/promises glob() call lowerForOf is lowering as a for await
|
||||
* head; other glob() calls have no lowering. */
|
||||
fspGlobHead: ts.Node | null = null;
|
||||
/** Capture entries and their origin share one mutable box. Normalize each
|
||||
* entry to the origin so writes and flow proofs stay synchronized. */
|
||||
readonly runtimeOptionalRoots = new Map<IrLocal, IrLocal>();
|
||||
@@ -7906,6 +7909,22 @@ export class Lowerer {
|
||||
return this.checkedNarrowCall(helper, expr, expected, expr.loc);
|
||||
}
|
||||
}
|
||||
// A record arm beside unit arms flowing into another layout of that
|
||||
// record (`R | undefined` into the runtime-optional variant of R): the
|
||||
// same checked extraction of the record arm, then the record's width
|
||||
// lift into the destination layout.
|
||||
if (expr.type.kind === "union" && expected.kind === "record") {
|
||||
const arms = this.unions.get(expr.type.unionId)?.arms ?? [];
|
||||
const records = arms.filter((arm) => !isUnitType(arm));
|
||||
const arm = records.length === 1 && records[0]!.kind === "record" ? records[0]! : null;
|
||||
const lift = arm ? this.widthLiftPlan(arm, expected) : null;
|
||||
const helper =
|
||||
arm && lift ? this.narrowedArmHelper(expr.type.unionId, arm, expr.loc) : null;
|
||||
if (arm && lift && helper) {
|
||||
const narrowed = this.checkedNarrowCall(helper, expr, arm, expr.loc);
|
||||
return this.applyWidthLift(lift, narrowed, expected, expr.loc);
|
||||
}
|
||||
}
|
||||
if (!typeEquals(expr.type, expected)) {
|
||||
const w = this.widthCoerce(expr, expected);
|
||||
if (w) return w;
|
||||
|
||||
@@ -1037,6 +1037,8 @@ export const BUILTIN_MODULE_FNS: Record<
|
||||
rmSync: { fn: "fs.rmSync", params: [STRING], result: VOID },
|
||||
rmdirSync: { fn: "fs.rmdirSync", params: [STRING], result: VOID },
|
||||
readdirSync: { fn: "fs.readdirSync", params: [STRING], result: arrayOf(STRING) },
|
||||
// Pattern and options are checked by the runtime (lowerFsGlobSyncCall).
|
||||
globSync: { fn: "fs.globSync", params: [DYN, DYN], result: arrayOf(STRING) },
|
||||
statSync: { fn: "fs.statSync", params: [STRING], result: STATS_T },
|
||||
mkdtempSync: { fn: "fs.mkdtempSync", params: [STRING], result: STRING },
|
||||
// accessSync's omitted mode completes to 0 (F_OK) in the special case
|
||||
@@ -1190,6 +1192,8 @@ export const BUILTIN_MODULE_FNS: Record<
|
||||
result: { kind: "promise", inner: arrayOf(STRING) },
|
||||
},
|
||||
rm: { fn: "fsp.rm", params: [STRING], result: { kind: "promise", inner: VOID } },
|
||||
// The async iterator lowers only as a for await head (lowerFspGlobCall).
|
||||
glob: { fn: "fsp.globStrings", params: [DYN, DYN], result: arrayOf(STRING) },
|
||||
stat: { fn: "fsp.stat", params: [STRING], result: { kind: "promise", inner: STATS_T } },
|
||||
realpath: { fn: "fsp.realpath", params: [STRING], result: { kind: "promise", inner: STRING } },
|
||||
lstat: { fn: "fsp.lstat", params: [STRING], result: { kind: "promise", inner: STATS_T } },
|
||||
@@ -1776,6 +1780,7 @@ export const BUILTIN_MODULE_FN_ALIASES: Record<
|
||||
rmSync: ["fs.rmOptsSync", "fs.rmRetrySync"],
|
||||
// The { withFileTypes: true } Dirent form.
|
||||
readdirSync: ["fs.readdirTypesSync"],
|
||||
globSync: ["fs.globTypesSync"],
|
||||
// The JS-source validation-ladder variant (lowerFsLadderCall): the
|
||||
// real mkdtemp runs when the options leave utf8 semantics.
|
||||
mkdtempSync: ["fs.mkdtempSyncChk"],
|
||||
@@ -1795,6 +1800,7 @@ export const BUILTIN_MODULE_FN_ALIASES: Record<
|
||||
writeFile: ["fsp.writeFileMode"],
|
||||
// The { withFileTypes: true } Dirent form.
|
||||
readdir: ["fsp.readdirTypes"],
|
||||
glob: ["fsp.globTypes"],
|
||||
},
|
||||
crypto: {
|
||||
// The composed randomBytes(n).toString(enc) chain keeps its one-libCall
|
||||
@@ -1889,7 +1895,7 @@ export const AMBIENT_SURFACE_FNS: readonly AmbientSurfaceRow[] = [
|
||||
id: "node-builtin.fs.callbacks",
|
||||
kind: "node-builtin",
|
||||
name: "fs error-first callbacks",
|
||||
fns: ["fs.callbackValue", "fs.callbackCall"],
|
||||
fns: ["fs.callbackValue", "fs.callbackCall", "fs.callbackGlob"],
|
||||
note: "native filesystem callbacks, including stored platform adapter functions",
|
||||
},
|
||||
// ── the Date slice (lowerDateCall/lowerNew): statics, construction,
|
||||
|
||||
@@ -42,14 +42,30 @@ test("union widening preserves record identity and recursive element layouts", (
|
||||
expect(unionWideningTags([node], [other])).toBeNull();
|
||||
});
|
||||
|
||||
test("matchStringSelfConcat recognizes only the immediate string self-concat", () => {
|
||||
test("matchStringSelfConcat recognizes the string self-concat", () => {
|
||||
const suffix = str("x");
|
||||
expect(matchStringSelfConcat("acc", concat(ref("acc"), suffix))).toBe(suffix);
|
||||
const target = ref("acc");
|
||||
expect(matchStringSelfConcat("acc", concat(target, suffix))).toEqual({ left: target, suffix });
|
||||
});
|
||||
|
||||
test("matchStringSelfConcat takes the operands after a leftmost target as the suffix", () => {
|
||||
const target = ref("acc");
|
||||
const self = matchStringSelfConcat(
|
||||
"acc",
|
||||
concat(concat(concat(target, str("x")), str("y")), str("z")),
|
||||
);
|
||||
expect(self?.left).toBe(target);
|
||||
expect(self?.suffix).toMatchObject({
|
||||
kind: "strConcat",
|
||||
left: { kind: "strConcat", left: { value: "x" }, right: { value: "y" } },
|
||||
right: { value: "z" },
|
||||
});
|
||||
});
|
||||
|
||||
test("matchStringSelfConcat rejects non-canonical and non-string shapes", () => {
|
||||
expect(matchStringSelfConcat("acc", concat(ref("other"), str("x")))).toBeNull();
|
||||
expect(matchStringSelfConcat("acc", concat(concat(ref("acc"), str("x")), str("y")))).toBeNull();
|
||||
expect(matchStringSelfConcat("acc", concat(concat(ref("other"), str("x")), str("y")))).toBeNull();
|
||||
expect(matchStringSelfConcat("acc", concat(str("x"), ref("acc")))).toBeNull();
|
||||
expect(matchStringSelfConcat("acc", str("x"))).toBeNull();
|
||||
expect(matchStringSelfConcat("acc", concat(ref("acc", F64), str("x")))).toBeNull();
|
||||
expect(matchStringSelfConcat("acc", concat(ref("acc"), str("x"), F64))).toBeNull();
|
||||
|
||||
@@ -27,28 +27,45 @@ export function unionWideningTags(from: readonly IrType[], to: readonly IrType[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Recognize the one concat shape whose destination binding can temporarily
|
||||
* Recognize the concat shape whose destination binding can temporarily
|
||||
* hand off its ownership to the left operand:
|
||||
*
|
||||
* target = target + suffix
|
||||
* target = target + a + b (the suffix is a + b)
|
||||
*
|
||||
* Both native emitters use this rather than attempting a wider purity or
|
||||
* alias analysis. In particular, fields, nested concat trees, dynamic
|
||||
* operations, and a different destination all retain the ordinary borrowed
|
||||
* concat lowering.
|
||||
* The target is the leftmost operand, so it is read first either way, and
|
||||
* the remaining operands keep their order in the suffix; every operand is
|
||||
* already a string, so regrouping them does not change the result. Both
|
||||
* native emitters use this rather than attempting a wider purity or alias
|
||||
* analysis. In particular, fields, dynamic operations, and a different
|
||||
* destination all retain the ordinary borrowed concat lowering.
|
||||
*/
|
||||
export function matchStringSelfConcat(targetLocalId: string, value: IrExpr): IrExpr | null {
|
||||
export function matchStringSelfConcat(
|
||||
targetLocalId: string,
|
||||
value: IrExpr,
|
||||
): { left: IrExpr; suffix: IrExpr } | null {
|
||||
if (
|
||||
value.kind !== "strConcat" ||
|
||||
value.type.kind !== "string" ||
|
||||
value.left.kind !== "varRef" ||
|
||||
value.left.type.kind !== "string" ||
|
||||
value.left.localId !== targetLocalId ||
|
||||
value.right.type.kind !== "string"
|
||||
) {
|
||||
)
|
||||
return null;
|
||||
if (value.left.kind === "varRef") {
|
||||
if (value.left.type.kind !== "string" || value.left.localId !== targetLocalId) return null;
|
||||
return { left: value.left, suffix: value.right };
|
||||
}
|
||||
return value.right;
|
||||
const inner = matchStringSelfConcat(targetLocalId, value.left);
|
||||
if (inner === null) return null;
|
||||
return {
|
||||
left: inner.left,
|
||||
suffix: {
|
||||
kind: "strConcat",
|
||||
left: inner.suffix,
|
||||
right: value.right,
|
||||
type: value.type,
|
||||
loc: value.loc,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Descendant tree needed by constructor queries. Parent links deliberately
|
||||
|
||||
@@ -523,6 +523,11 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"fs.rmdirSync",
|
||||
"fs.readdirSync",
|
||||
"fs.readdirTypesSync",
|
||||
"fs.globSync",
|
||||
"fs.globTypesSync",
|
||||
"fs.callbackGlob",
|
||||
"fsp.globStrings",
|
||||
"fsp.globTypes",
|
||||
"url.new",
|
||||
"url.newBase",
|
||||
"url.set",
|
||||
|
||||
@@ -444,6 +444,20 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
|
||||
* sorts), the directory's order on Windows; no "."/"..". Throws
|
||||
* Node's scandir errno error (may-throw seed set); fresh +1 array. */
|
||||
"fs.readdirTypesSync": { argTypes: [STRING], result: VOID },
|
||||
/** fs.globSync / fs.glob / fs/promises glob (scr_glob.c, Node's
|
||||
* lib/internal/fs/glob.js walk): pattern and options are dyn values the
|
||||
* runtime validates in Node's order. The string forms answer string[];
|
||||
* the Dirent forms answer the call site's Dirent record array (validated
|
||||
* in validate.ts) assembled from the snapshot; fs.callbackGlob takes the
|
||||
* argument vector and calls back with string[]. */
|
||||
"fs.globSync": { argTypes: [DYN, DYN], result: arrayOf(STRING) },
|
||||
"fs.globTypesSync": { argTypes: [DYN, DYN], result: VOID },
|
||||
"fs.callbackGlob": { argTypes: [DYN], result: VOID },
|
||||
"fsp.globStrings": { argTypes: [DYN, DYN], result: arrayOf(STRING) },
|
||||
"fsp.globTypes": { argTypes: [DYN, DYN], result: VOID },
|
||||
/** The error an fs/promises glob walk ended with (undefined when none),
|
||||
* read once right after fsp.globStrings / fsp.globTypes. */
|
||||
"fsp.globError": { argTypes: [], result: DYN },
|
||||
/** node:path (scr_path.c ports BOTH of Node's implementations
|
||||
* function-by-function): the `path.*` family is posix, the
|
||||
* `path.win32*` family is Node v24's path.win32 ported exactly — the
|
||||
@@ -1007,6 +1021,21 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
|
||||
"stats.atimeMs": { argTypes: [STATS_T], result: F64 },
|
||||
"stats.mtimeMs": { argTypes: [STATS_T], result: F64 },
|
||||
"stats.ctimeMs": { argTypes: [STATS_T], result: F64 },
|
||||
"stats.birthtimeMs": { argTypes: [STATS_T], result: F64 },
|
||||
"stats.mode": { argTypes: [STATS_T], result: F64 },
|
||||
"stats.uid": { argTypes: [STATS_T], result: F64 },
|
||||
"stats.gid": { argTypes: [STATS_T], result: F64 },
|
||||
"stats.rdev": { argTypes: [STATS_T], result: F64 },
|
||||
"stats.blksize": { argTypes: [STATS_T], result: F64 },
|
||||
"stats.isBlockDevice": { argTypes: [STATS_T], result: BOOL },
|
||||
"stats.isCharacterDevice": { argTypes: [STATS_T], result: BOOL },
|
||||
"stats.isFIFO": { argTypes: [STATS_T], result: BOOL },
|
||||
"stats.isSocket": { argTypes: [STATS_T], result: BOOL },
|
||||
/** The Date fields: the *Ms value rounded (Node's dateFromMs). */
|
||||
"stats.atime": { argTypes: [STATS_T], result: DATE_T },
|
||||
"stats.mtime": { argTypes: [STATS_T], result: DATE_T },
|
||||
"stats.ctime": { argTypes: [STATS_T], result: DATE_T },
|
||||
"stats.birthtime": { argTypes: [STATS_T], result: DATE_T },
|
||||
// The wider sync fs slice (unlink/chmod/chown/copyfile and the
|
||||
// mode-carrying write/mkdir forms).
|
||||
/** The wider sync fs slice (scr_lib.c), all throwing catchably with
|
||||
@@ -1137,6 +1166,10 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
|
||||
},
|
||||
"net.serverPort": { argTypes: [NETSERVER_T], result: F64 },
|
||||
"net.serverListening": { argTypes: [NETSERVER_T], result: BOOL },
|
||||
/** socket.ref()/unref() and server.ref()/unref(): whether the handle
|
||||
* keeps the event loop alive. */
|
||||
"net.sockSetRef": { argTypes: [NETSOCKET_T, BOOL], result: VOID },
|
||||
"net.serverSetRef": { argTypes: [NETSERVER_T, BOOL], result: VOID },
|
||||
// net.serverAddress's record result is shape-checked in the libCall case
|
||||
// (the dgram.address sentinel pattern).
|
||||
/** server.address() as the full AddressInfo record (the dgram.address
|
||||
|
||||
@@ -44,6 +44,7 @@ export interface RuntimeFeatures {
|
||||
const DYN_ASYNC_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"fs.callbackValue",
|
||||
"fs.callbackCall",
|
||||
"fs.callbackGlob",
|
||||
"async.awaitDyn",
|
||||
"timers.immediatePromise",
|
||||
"crypto.native",
|
||||
@@ -173,7 +174,14 @@ function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): Run
|
||||
fn === "dyn.nativeRegexIs" ||
|
||||
fn === "util.stripVTControlCharacters" ||
|
||||
fn === "path.matchesGlob" ||
|
||||
fn === "path.win32MatchesGlob"
|
||||
fn === "path.win32MatchesGlob" ||
|
||||
// fs.glob's walk compiles its patterns with the glob matcher.
|
||||
fn === "fs.globSync" ||
|
||||
fn === "fs.globTypesSync" ||
|
||||
fn === "fs.callbackGlob" ||
|
||||
fn === "fsp.globStrings" ||
|
||||
fn === "fsp.globTypes" ||
|
||||
fn === "fsp.globError"
|
||||
)
|
||||
features.regex = true;
|
||||
if (
|
||||
|
||||
@@ -4682,10 +4682,16 @@ function validateFunction(
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (e.fn === "fs.readdirTypesSync" || e.fn === "fsp.readdirTypes") {
|
||||
if (
|
||||
e.fn === "fs.readdirTypesSync" ||
|
||||
e.fn === "fsp.readdirTypes" ||
|
||||
e.fn === "fs.globTypesSync" ||
|
||||
e.fn === "fsp.globTypes"
|
||||
) {
|
||||
// Result: the interned Dirent record array — {%dtype: f64,
|
||||
// name: string, parentPath: string} rows (canonical field
|
||||
// order; the structure lowerFsReaddirTypesCall pinned).
|
||||
// order; the structure lowerFsReaddirTypesCall pinned). The glob
|
||||
// forms answer the array itself.
|
||||
const result =
|
||||
e.fn === "fsp.readdirTypes"
|
||||
? e.type.kind === "promise"
|
||||
|
||||
@@ -941,6 +941,12 @@
|
||||
"name": "fs.futimesSync",
|
||||
"status": "static"
|
||||
},
|
||||
{
|
||||
"id": "node-builtin.fs.globSync",
|
||||
"kind": "node-builtin",
|
||||
"name": "fs.globSync",
|
||||
"status": "static"
|
||||
},
|
||||
{
|
||||
"id": "node-builtin.fs.linkSync",
|
||||
"kind": "node-builtin",
|
||||
@@ -990,6 +996,12 @@
|
||||
"name": "fs/promises.chmod",
|
||||
"status": "static"
|
||||
},
|
||||
{
|
||||
"id": "node-builtin.fs.promises.glob",
|
||||
"kind": "node-builtin",
|
||||
"name": "fs/promises.glob",
|
||||
"status": "static"
|
||||
},
|
||||
{
|
||||
"id": "node-builtin.fs.promises.link",
|
||||
"kind": "node-builtin",
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { expect, test } from "vitest";
|
||||
import { compile } from "../src/index.js";
|
||||
|
||||
async function compileSource(source: string) {
|
||||
const directory = mkdtempSync(join(tmpdir(), "scriptc-fs-glob-"));
|
||||
try {
|
||||
const entry = join(directory, "main.ts");
|
||||
writeFileSync(entry, source);
|
||||
return await compile(entry, {
|
||||
outDir: directory,
|
||||
outPath: join(directory, "program.ir"),
|
||||
outputKind: "ir",
|
||||
});
|
||||
} finally {
|
||||
rmSync(directory, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
// Forms whose result type or element values have no native representation
|
||||
// fail during compilation.
|
||||
test.each([
|
||||
[
|
||||
"fs/promises glob outside a for await loop",
|
||||
'import { glob } from "node:fs/promises";\nconst matches = glob("*");\nvoid matches;\n',
|
||||
],
|
||||
])("%s fails during compilation", async (what, source) => {
|
||||
const result = await compileSource(source);
|
||||
expect(result.ok).toBe(false);
|
||||
if (result.ok) throw new Error("expected an fs.glob diagnostic");
|
||||
expect(result.diagnostics).toEqual(
|
||||
expect.arrayContaining([expect.objectContaining({ message: expect.stringContaining(what) })]),
|
||||
);
|
||||
expect(result.diagnostics.some((diagnostic) => diagnostic.code === "SC9001")).toBe(false);
|
||||
});
|
||||
|
||||
test("the fs.glob forms compile", async () => {
|
||||
const result = await compileSource(`import { globSync, glob as globCallback } from "node:fs";
|
||||
import { glob } from "node:fs/promises";
|
||||
console.log(globSync("**/*.ts", { cwd: ".", exclude: ["node_modules/**"] }).length);
|
||||
console.log(globSync(["*.md"], { exclude: (path) => path.startsWith(".") }).length);
|
||||
console.log(globSync("*", { withFileTypes: true }).map((d) => d.isFile()).length);
|
||||
globCallback("*", (err, matches) => console.log(err, matches?.length));
|
||||
async function main(): Promise<void> {
|
||||
for await (const path of glob("*")) console.log(path);
|
||||
for await (const entry of glob("*", { withFileTypes: true })) console.log(entry.name, entry.isDirectory());
|
||||
}
|
||||
void main();
|
||||
`);
|
||||
expect(result.ok, JSON.stringify(result.ok ? [] : result.diagnostics)).toBe(true);
|
||||
});
|
||||
@@ -15,14 +15,18 @@ test("remainder uses integer proofs and preserves floating fallback and signed z
|
||||
`
|
||||
function remainder(x: number, y: number): number { return x % y; }
|
||||
function constants(x: number): number {
|
||||
let value = x % 8;
|
||||
let value = x % 10;
|
||||
value %= 3;
|
||||
return value;
|
||||
}
|
||||
function powers(x: number): number {
|
||||
return (x % 8) + (x % -(2 ** 32)) + (x % 1) + (x % 0.5);
|
||||
}
|
||||
function power(x: number, y: number): number { return x ** y; }
|
||||
function signed(x: number): number { return (x | 0) % -7; }
|
||||
function maybeZero(x: number, y: number): number { return (x | 0) % (y | 0); }
|
||||
console.log(remainder(-9.5, 8), constants(17.25), power(2, 3), signed(-7), maybeZero(7, 0));
|
||||
console.log(powers(-9.75));
|
||||
// Fractional arguments keep the parameters arbitrary (no call-site proof).
|
||||
console.log(remainder(5, 2.5), signed(-7.5), maybeZero(7.5, 2.5));
|
||||
`,
|
||||
@@ -36,25 +40,40 @@ console.log(remainder(5, 2.5), signed(-7.5), maybeZero(7.5, 2.5));
|
||||
new RegExp(`^define internal [^\\n]*@sc_[bf]+_${name}(?:\\.i32)?\\([^]*?^}`, "m").exec(
|
||||
ll,
|
||||
)![0];
|
||||
// Four source remainders keep a floating fallback behind a runtime
|
||||
// integer check; ToUint32 coercion fallbacks use integer arithmetic.
|
||||
expect(ll.match(/ = frem double /g)).toHaveLength(4);
|
||||
// Five source remainders keep a floating fallback: four behind a
|
||||
// runtime integer check and the one by 0.5. ToUint32 coercion
|
||||
// fallbacks use integer arithmetic.
|
||||
expect(ll.match(/ = frem double /g)).toHaveLength(5);
|
||||
expect(ll).toContain("uint32.coerce.slow");
|
||||
expect(ll.match(/ = srem i64 /g)).toHaveLength(5);
|
||||
expect(ll).toMatch(/ = select i1 [^\n]+double [^\n]+double /);
|
||||
// Unproven operands: both round-trip checks, the 2^53 dividend bound,
|
||||
// Unproven operands: both round-trip checks, the 2^62 dividend bound,
|
||||
// and a nonzero divisor guard the integer path.
|
||||
const unproven = fn("remainder");
|
||||
expect(unproven.match(/ = freeze i64 /g)).toHaveLength(2);
|
||||
expect(unproven).toMatch(/ = add i64 [^\n]+, 9007199254740992$/m);
|
||||
expect(unproven).toMatch(/ = icmp ule i64 [^\n]+, 18014398509481984$/m);
|
||||
expect(unproven).toMatch(/ = add i64 [^\n]+, 4611686018427387904$/m);
|
||||
expect(unproven).toMatch(/ = icmp sge i64 [^\n]+, 0$/m);
|
||||
expect(unproven).toMatch(/ = icmp ne i64 [^\n]+, 0$/m);
|
||||
expect(unproven).toContain("@llvm.expect.i1(");
|
||||
expect(unproven).toContain("rem.float");
|
||||
// A constant divisor needs only the dividend check.
|
||||
const constant = fn("constants");
|
||||
expect(constant).toMatch(/ = srem i64 [^\n]+, 8$/m);
|
||||
expect(constant).toMatch(/ = srem i64 [^\n]+, 10$/m);
|
||||
expect(constant).not.toMatch(/ = icmp ne i64 [^\n]+, 0$/m);
|
||||
// Power-of-two divisors of at least 1 subtract the truncated multiple,
|
||||
// with no integer check; the divisor's sign is dropped and 0.5 keeps
|
||||
// frem.
|
||||
const powers = fn("powers");
|
||||
expect(powers).not.toContain("freeze");
|
||||
expect(powers).not.toContain("srem");
|
||||
expect(powers.match(/ = fdiv double [^\n]+, 0x/g)).toHaveLength(3);
|
||||
expect(powers).toMatch(/ = fdiv double [^\n]+, 0x4020000000000000$/m);
|
||||
expect(powers).toMatch(/ = fdiv double [^\n]+, 0x41F0000000000000$/m);
|
||||
expect(powers).toMatch(/ = fdiv double [^\n]+, 0x3FF0000000000000$/m);
|
||||
expect(
|
||||
powers.match(/ = call double @llvm.copysign.f64\(double 0x0000000000000000, /g),
|
||||
).toHaveLength(3);
|
||||
expect(powers).toMatch(/ = frem double [^\n]+, 0x3FE0000000000000$/m);
|
||||
// Proven integers with a nonzero divisor need no check or fallback.
|
||||
const signed = fn("signed");
|
||||
expect(signed).not.toContain("freeze");
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5333-record-boundary-nullable-argument.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5448-fs-glob-patterns.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5449-fs-glob-options.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5450-fs-glob-validation.js"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5451-fs-promises-glob.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5452-fs-glob-callback.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5455-power-of-two-remainder.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5456-remainder-large-integer-dividends.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5457-lcg-modulus.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5458-fractional-remainder.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5541-fs-stats-fields.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5552-string-appends.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5553-string-append-char-code.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5585-record-results-inlined.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5595-net-ref-unref.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/fixtures/node-types/fs-glob.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -3340,6 +3340,12 @@ static SCR_TL bool (*scr_net_pending_fn)(void) = NULL;
|
||||
static SCR_TL void (*scr_net_dispatch_fn)(void) = NULL;
|
||||
static SCR_TL int (*scr_net_pollfd_fn)(void) = NULL;
|
||||
|
||||
/* Net io without liveness: unref'd sockets and servers still deliver
|
||||
* events while the loop runs for other work, but do not keep it alive. */
|
||||
static SCR_TL bool (*scr_net_io_fn)(void) = NULL;
|
||||
|
||||
void scr_loop_set_net_io(bool (*io)(void)) { scr_net_io_fn = io; }
|
||||
|
||||
void scr_loop_set_net(bool (*pending)(void), void (*dispatch)(void), int (*pollfd)(void)) {
|
||||
scr_net_pending_fn = pending;
|
||||
scr_net_dispatch_fn = dispatch;
|
||||
@@ -3621,6 +3627,9 @@ static bool scr_loop_run_pass(ScrPromise *top_level, bool first_checkpoint) {
|
||||
bool kids = scr_children_pending();
|
||||
bool events = scr_events_pending_fn != NULL && scr_events_pending_fn();
|
||||
bool net = scr_net_pending_fn != NULL && scr_net_pending_fn();
|
||||
/* `net` keeps the process alive; `net_io` (unref'd handles too) decides
|
||||
* whether the sleep polls the net unit's fd */
|
||||
bool net_io = net || (scr_net_io_fn != NULL && scr_net_io_fn());
|
||||
bool dgram = scr_dgram_pending_fn != NULL && scr_dgram_pending_fn();
|
||||
bool watch = scr_watch_pending_fn != NULL && scr_watch_pending_fn();
|
||||
bool ffi = scr_ffi_pending_fn != NULL && scr_ffi_pending_fn();
|
||||
@@ -3674,12 +3683,12 @@ static bool scr_loop_run_pass(ScrPromise *top_level, bool first_checkpoint) {
|
||||
#endif
|
||||
bool evw = scr_events_watching_fn != NULL && scr_events_watching_fn();
|
||||
#ifdef SCR_WORKERS
|
||||
if (workers && scr_workers_wait_fn && !kids && !evw && !net && !dgram && !watch && !ffi && !renames && !crypto) {
|
||||
if (workers && scr_workers_wait_fn && !kids && !evw && !net_io && !dgram && !watch && !ffi && !renames && !crypto) {
|
||||
scr_workers_wait_fn(due > now ? due - now : 0);
|
||||
now = scr_now_ms();
|
||||
} else
|
||||
#endif
|
||||
if (evw || net || dgram || watch || ffi || workers) {
|
||||
if (evw || net_io || dgram || watch || ffi || workers) {
|
||||
#if defined(_WIN32) || defined(__wasi__)
|
||||
/* The win32 arm, and WASI hosts whose poll_oneoff adapters do not
|
||||
* reliably wake for a closed inherited stdin pipe: the sleep is a capped nanosleep and
|
||||
@@ -3693,7 +3702,7 @@ static bool scr_loop_run_pass(ScrPromise *top_level, bool first_checkpoint) {
|
||||
* show up in a profile, the upgrade is a real waitable arm —
|
||||
* WaitForMultipleObjects over WSAEVENTs, or IOCP. */
|
||||
if (evw && due > now + SCR_SIGNAL_POLL_MS) due = now + SCR_SIGNAL_POLL_MS;
|
||||
if ((net || dgram || watch) && due > now + SCR_CHILD_POLL_MS) due = now + SCR_CHILD_POLL_MS;
|
||||
if ((net_io || dgram || watch) && due > now + SCR_CHILD_POLL_MS) due = now + SCR_CHILD_POLL_MS;
|
||||
if ((ffi || workers) && due > now + SCR_CHILD_POLL_MS) due = now + SCR_CHILD_POLL_MS;
|
||||
if (kids && due > now + SCR_CHILD_POLL_MS) due = now + SCR_CHILD_POLL_MS;
|
||||
if (due > now) {
|
||||
@@ -3720,7 +3729,7 @@ static bool scr_loop_run_pass(ScrPromise *top_level, bool first_checkpoint) {
|
||||
fds[nfds].events = POLLIN;
|
||||
fds[nfds++].revents = 0;
|
||||
}
|
||||
if (net) {
|
||||
if (net_io) {
|
||||
/* The net unit's poller fd: readable while socket events are
|
||||
* pending, so arrived data/accepts end the sleep immediately.
|
||||
* Dispatch (which also DRAINS the poller) happens at the next
|
||||
|
||||
@@ -23,13 +23,6 @@ static const ScrArrOps scr_bytes_io_bytes_ops = { scr_bytes_retain_v, scr_bytes_
|
||||
#ifndef O_SYNC
|
||||
#define O_SYNC 0 /* Windows CRT opens have no separate synchronous flag. */
|
||||
#endif
|
||||
/* Bionic added glob/globfree in API 28; Android packs target API 26. */
|
||||
#if !defined(_WIN32) && !defined(__wasi__) && (!defined(__ANDROID__) || __ANDROID_API__ >= 28)
|
||||
#include <glob.h>
|
||||
#define SCR_FS_HAS_GLOB 1
|
||||
#else
|
||||
#define SCR_FS_HAS_GLOB 0
|
||||
#endif
|
||||
#ifdef __APPLE__
|
||||
#include <sys/stat.h> /* lchmod(2) — the fs.lchmodSync ladder's real tail */
|
||||
#endif
|
||||
@@ -537,91 +530,11 @@ static void scr_fs_cb_copy(const ScrStr *source, const ScrStr *destination, bool
|
||||
}
|
||||
}
|
||||
|
||||
static ScrDyn *scr_fs_cb_stat_test(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
(void)args; (void)argc;
|
||||
return scr_dyn_new_bool(scr_box_get_bool(closure->caps[0]));
|
||||
}
|
||||
|
||||
static void scr_fs_cb_stat_method(ScrDyn *object, const char *name, bool answer) {
|
||||
ScrClosure *closure = scr_closure_new(NULL, 1);
|
||||
closure->caps[0] = scr_box_new(SCR_BOX_BOOL);
|
||||
scr_box_set_bool(closure->caps[0], answer);
|
||||
scr_dyn_obj_set(object, name, strlen(name), scr_dyn_new_func(closure, scr_fs_cb_stat_test, 0, "native:fs.Stats", name));
|
||||
}
|
||||
|
||||
static void scr_fs_cb_stat_number(ScrDyn *object, const char *name, int64_t number, bool bigint) {
|
||||
ScrDyn *value;
|
||||
if (bigint) {
|
||||
ScrBigInt *integer = scr_bigint_from_i64(number);
|
||||
value = scr_dyn_new_bigint(integer);
|
||||
scr_bigint_release(integer);
|
||||
} else value = scr_dyn_new_num((double)number);
|
||||
scr_dyn_obj_set(object, name, strlen(name), value);
|
||||
}
|
||||
|
||||
static void scr_fs_cb_stat_date(ScrDyn *object, const char *name, double milliseconds) {
|
||||
ScrDyn *arguments = scr_dyn_new_arr();
|
||||
scr_dyn_arr_push(arguments, scr_dyn_new_num(milliseconds));
|
||||
ScrDyn *date = scr_dyn_native_date_new(arguments);
|
||||
scr_dyn_release(arguments);
|
||||
if (date) scr_dyn_obj_set(object, name, strlen(name), date);
|
||||
}
|
||||
|
||||
static ScrDyn *scr_fs_cb_stats(const ScrStr *path, double fd, const ScrDyn *options, bool nofollow) {
|
||||
struct stat status;
|
||||
#ifdef _WIN32
|
||||
(void)nofollow;
|
||||
int rc = path ? stat(path->data, &status) : fstat((int)fd, &status);
|
||||
#else
|
||||
int rc = path ? (nofollow ? lstat(path->data, &status) : stat(path->data, &status)) : fstat((int)fd, &status);
|
||||
#endif
|
||||
if (rc < 0) { scr_fs_throw(errno, path ? (nofollow ? "lstat" : "stat") : "fstat", path); return NULL; }
|
||||
bool bigint = scr_fs_cb_option_bool(options, "bigint");
|
||||
ScrDyn *object = scr_dyn_new_obj();
|
||||
#define SCR_FS_STAT_FIELD(field) scr_fs_cb_stat_number(object, #field, (int64_t)status.st_##field, bigint)
|
||||
SCR_FS_STAT_FIELD(dev); SCR_FS_STAT_FIELD(ino); SCR_FS_STAT_FIELD(mode); SCR_FS_STAT_FIELD(nlink);
|
||||
SCR_FS_STAT_FIELD(uid); SCR_FS_STAT_FIELD(gid); SCR_FS_STAT_FIELD(rdev); SCR_FS_STAT_FIELD(size);
|
||||
#ifndef _WIN32
|
||||
SCR_FS_STAT_FIELD(blksize); SCR_FS_STAT_FIELD(blocks);
|
||||
#endif
|
||||
#undef SCR_FS_STAT_FIELD
|
||||
double atime = (double)status.st_atime * 1000, mtime = (double)status.st_mtime * 1000, ctime = (double)status.st_ctime * 1000;
|
||||
double birthtime = 0;
|
||||
#ifdef __APPLE__
|
||||
atime += status.st_atimespec.tv_nsec / 1000000.0; mtime += status.st_mtimespec.tv_nsec / 1000000.0;
|
||||
ctime += status.st_ctimespec.tv_nsec / 1000000.0;
|
||||
birthtime = (double)status.st_birthtimespec.tv_sec * 1000 + status.st_birthtimespec.tv_nsec / 1000000.0;
|
||||
#elif !defined(_WIN32) && !defined(__wasi__)
|
||||
atime += status.st_atim.tv_nsec / 1000000.0; mtime += status.st_mtim.tv_nsec / 1000000.0;
|
||||
ctime += status.st_ctim.tv_nsec / 1000000.0;
|
||||
#endif
|
||||
scr_fs_cb_stat_date(object, "atime", atime); scr_fs_cb_stat_date(object, "mtime", mtime);
|
||||
scr_fs_cb_stat_date(object, "ctime", ctime); scr_fs_cb_stat_date(object, "birthtime", birthtime);
|
||||
if (bigint) {
|
||||
scr_fs_cb_stat_number(object, "atimeMs", (int64_t)atime, true);
|
||||
scr_fs_cb_stat_number(object, "mtimeMs", (int64_t)mtime, true);
|
||||
scr_fs_cb_stat_number(object, "ctimeMs", (int64_t)ctime, true);
|
||||
scr_fs_cb_stat_number(object, "birthtimeMs", (int64_t)birthtime, true);
|
||||
} else {
|
||||
scr_dyn_obj_set(object, "atimeMs", 7, scr_dyn_new_num(atime));
|
||||
scr_dyn_obj_set(object, "mtimeMs", 7, scr_dyn_new_num(mtime));
|
||||
scr_dyn_obj_set(object, "ctimeMs", 7, scr_dyn_new_num(ctime));
|
||||
scr_dyn_obj_set(object, "birthtimeMs", 11, scr_dyn_new_num(birthtime));
|
||||
}
|
||||
scr_fs_cb_stat_method(object, "isFile", S_ISREG(status.st_mode));
|
||||
scr_fs_cb_stat_method(object, "isDirectory", S_ISDIR(status.st_mode));
|
||||
#ifndef _WIN32
|
||||
scr_fs_cb_stat_method(object, "isSymbolicLink", S_ISLNK(status.st_mode));
|
||||
scr_fs_cb_stat_method(object, "isBlockDevice", S_ISBLK(status.st_mode));
|
||||
scr_fs_cb_stat_method(object, "isCharacterDevice", S_ISCHR(status.st_mode));
|
||||
scr_fs_cb_stat_method(object, "isFIFO", S_ISFIFO(status.st_mode));
|
||||
scr_fs_cb_stat_method(object, "isSocket", S_ISSOCK(status.st_mode));
|
||||
#else
|
||||
scr_fs_cb_stat_method(object, "isSymbolicLink", false);
|
||||
scr_fs_cb_stat_method(object, "isBlockDevice", false);
|
||||
scr_fs_cb_stat_method(object, "isCharacterDevice", S_ISCHR(status.st_mode));
|
||||
scr_fs_cb_stat_method(object, "isFIFO", false); scr_fs_cb_stat_method(object, "isSocket", false);
|
||||
#endif
|
||||
ScrStats *snapshot = !path ? scr_fs_fstat(fd) : nofollow ? scr_fs_lstat((ScrStr *)path) : scr_fs_stat((ScrStr *)path);
|
||||
if (!snapshot) return NULL;
|
||||
ScrDyn *object = scr_stats_dyn(snapshot, scr_fs_cb_option_bool(options, "bigint"));
|
||||
scr_stats_release(snapshot);
|
||||
return object;
|
||||
}
|
||||
|
||||
@@ -634,7 +547,7 @@ static void scr_fs_cb_fire(ScrClosure *closure) {
|
||||
}
|
||||
#endif
|
||||
|
||||
static void scr_fs_cb_schedule(const ScrDyn *callback, ScrDyn *result, const ScrDyn *buffer) {
|
||||
void scr_fs_cb_schedule(const ScrDyn *callback, ScrDyn *result, const ScrDyn *buffer) {
|
||||
ScrDyn *arguments = scr_dyn_new_arr();
|
||||
if (scr_exc_pending()) {
|
||||
ScrCaught *caught = scr_exc_take();
|
||||
@@ -895,36 +808,10 @@ static ScrDyn *scr_fs_cb_invoke(ScrStr *member, ScrDyn *const *args, size_t argc
|
||||
scr_dyn_release(force); scr_str_release(destination);
|
||||
}
|
||||
} else if (!strcmp(op, "glob")) {
|
||||
#if SCR_FS_HAS_GLOB
|
||||
if (strstr(path->data, "**") || scr_fs_cb_option_bool(ARG(1), "withFileTypes")) {
|
||||
const char *message = "Recursive glob patterns and Dirent results have no native lowering";
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message, strlen(message), "SC2020");
|
||||
goto done;
|
||||
}
|
||||
ScrDyn *cwd = scr_fs_cb_option(ARG(1), "cwd");
|
||||
ScrDyn *exclude = scr_fs_cb_option(ARG(1), "exclude");
|
||||
if (exclude && exclude->kind != SCR_DYN_UNDEF) {
|
||||
const char *message = "fs.glob exclude options have no native lowering";
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message, strlen(message), "SC2020");
|
||||
} else {
|
||||
ScrStr *pattern = cwd && cwd->kind == SCR_DYN_STR ? scr_fs_cb_join(cwd->v.str, path->data) : scr_str_retain(path);
|
||||
glob_t matches = {0};
|
||||
int rc = glob(pattern->data, 0, NULL, &matches);
|
||||
if (rc && rc != GLOB_NOMATCH) scr_fs_throw(rc == GLOB_NOSPACE ? ENOMEM : EIO, "glob", path);
|
||||
else {
|
||||
result = scr_dyn_new_arr();
|
||||
size_t prefix = cwd && cwd->kind == SCR_DYN_STR ? cwd->v.str->len + 1 : 0;
|
||||
for (size_t i = 0; i < matches.gl_pathc; i++) {
|
||||
const char *name = matches.gl_pathv[i] + prefix;
|
||||
scr_dyn_arr_push(result, scr_fs_cb_string(scr_str_new(name, strlen(name))));
|
||||
}
|
||||
}
|
||||
globfree(&matches); scr_str_release(pattern);
|
||||
}
|
||||
scr_dyn_release(cwd); scr_dyn_release(exclude);
|
||||
#else
|
||||
scr_fs_throw(ENOSYS, "glob", path);
|
||||
#endif
|
||||
/* Direct fs.glob calls lower to scr_fs_glob_callback (scr_glob.c); a
|
||||
* stored fs.glob value is not supported. */
|
||||
const char *message = "fs.glob stored as a value has no native lowering; call fs.glob directly";
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message, strlen(message), "SC2020");
|
||||
} else if (!strcmp(op, "write") && ARG(1)->kind == SCR_DYN_STR) {
|
||||
/* write(fd, string[, position[, encoding]], callback): a non-number
|
||||
* position writes at the current offset; the encoding defaults to
|
||||
|
||||
@@ -34,42 +34,6 @@ struct ScrFileHandle {
|
||||
int fd;
|
||||
};
|
||||
|
||||
/* ScrStats is opaque at the public runtime boundary. FileHandle's fstat
|
||||
* snapshot completes the same layout privately in this translation unit. */
|
||||
struct ScrStats {
|
||||
size_t rc;
|
||||
bool is_file;
|
||||
bool is_dir;
|
||||
bool is_symlink;
|
||||
double dev;
|
||||
double ino;
|
||||
double size;
|
||||
double blocks;
|
||||
double nlink;
|
||||
double atime_ms;
|
||||
double mtime_ms;
|
||||
double ctime_ms;
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
/* Keep the FILETIME conversion identical to scr_lib.c's path-stat arm:
|
||||
* libuv splits the 100ns count into Unix seconds and nanoseconds before Node
|
||||
* combines it into the public millisecond value. */
|
||||
static double scr_file_handle_filetime_ms(FILETIME ft) {
|
||||
ULARGE_INTEGER raw;
|
||||
raw.LowPart = ft.dwLowDateTime;
|
||||
raw.HighPart = ft.dwHighDateTime;
|
||||
int64_t ticks = (int64_t)raw.QuadPart - INT64_C(116444736000000000);
|
||||
int64_t sec = ticks / INT64_C(10000000);
|
||||
int64_t rem = ticks % INT64_C(10000000);
|
||||
if (rem < 0) {
|
||||
sec--;
|
||||
rem += INT64_C(10000000);
|
||||
}
|
||||
return (double)sec * 1000.0 + (double)rem / 10000.0;
|
||||
}
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
char *data;
|
||||
size_t len;
|
||||
@@ -395,9 +359,9 @@ void scr_file_handle_write_file_bytes(ScrFileHandle *h, ScrBytes *data) {
|
||||
|
||||
ScrStats *scr_file_handle_stat(ScrFileHandle *h) {
|
||||
if (!scr_file_handle_require_open(h)) return NULL;
|
||||
struct stat st;
|
||||
if (fstat(h->fd, &st) != 0) {
|
||||
int err = errno;
|
||||
int err = 0;
|
||||
ScrStats *out = scr_stats_of_fd(h->fd, &err);
|
||||
if (!out) {
|
||||
const char *name = err == EBADF ? "EBADF" : err == EIO ? "EIO" : "EUNKNOWN";
|
||||
const char *text = err == EBADF ? "bad file descriptor" :
|
||||
err == EIO ? "i/o error" : strerror(err);
|
||||
@@ -406,66 +370,6 @@ ScrStats *scr_file_handle_stat(ScrFileHandle *h) {
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, msg, (size_t)len, name);
|
||||
return NULL;
|
||||
}
|
||||
ScrStats *out = malloc(sizeof(ScrStats));
|
||||
if (!out) scr_trap("scriptc: out of memory\n");
|
||||
out->rc = 1;
|
||||
out->is_file = S_ISREG(st.st_mode);
|
||||
out->is_dir = S_ISDIR(st.st_mode);
|
||||
out->is_symlink = false; /* fstat follows the open descriptor. */
|
||||
out->dev = (double)st.st_dev;
|
||||
out->ino = (double)st.st_ino;
|
||||
out->size = (double)st.st_size;
|
||||
#if defined(_WIN32)
|
||||
out->blocks = st.st_size <= 0 ? 0.0 : (double)(((uint64_t)st.st_size + 511) >> 9);
|
||||
out->nlink = (double)st.st_nlink;
|
||||
out->atime_ms = (double)st.st_atime * 1000.0;
|
||||
out->mtime_ms = (double)st.st_mtime * 1000.0;
|
||||
out->ctime_ms = (double)st.st_ctime * 1000.0;
|
||||
HANDLE os_handle = (HANDLE)_get_osfhandle(h->fd);
|
||||
if (os_handle != INVALID_HANDLE_VALUE) {
|
||||
BY_HANDLE_FILE_INFORMATION basic;
|
||||
if (GetFileInformationByHandle(os_handle, &basic)) {
|
||||
out->dev = (double)basic.dwVolumeSerialNumber;
|
||||
out->ino = (double)basic.nFileIndexHigh * 4294967296.0 +
|
||||
(double)basic.nFileIndexLow;
|
||||
out->nlink = (double)basic.nNumberOfLinks;
|
||||
out->atime_ms = scr_file_handle_filetime_ms(basic.ftLastAccessTime);
|
||||
out->mtime_ms = scr_file_handle_filetime_ms(basic.ftLastWriteTime);
|
||||
}
|
||||
FILE_BASIC_INFO times;
|
||||
if (GetFileInformationByHandleEx(
|
||||
os_handle, FileBasicInfo, ×, sizeof times)) {
|
||||
FILETIME changed;
|
||||
changed.dwLowDateTime = times.ChangeTime.LowPart;
|
||||
changed.dwHighDateTime = times.ChangeTime.HighPart;
|
||||
out->ctime_ms = scr_file_handle_filetime_ms(changed);
|
||||
}
|
||||
FILE_STANDARD_INFO standard;
|
||||
if (GetFileInformationByHandleEx(
|
||||
os_handle, FileStandardInfo, &standard, sizeof standard)) {
|
||||
out->blocks = (double)((uint64_t)standard.AllocationSize.QuadPart >> 9);
|
||||
out->nlink = (double)standard.NumberOfLinks;
|
||||
}
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
out->blocks = (double)st.st_blocks;
|
||||
out->nlink = (double)st.st_nlink;
|
||||
out->atime_ms = (double)st.st_atimespec.tv_sec * 1000.0 +
|
||||
(double)st.st_atimespec.tv_nsec / 1e6;
|
||||
out->mtime_ms = (double)st.st_mtimespec.tv_sec * 1000.0 +
|
||||
(double)st.st_mtimespec.tv_nsec / 1e6;
|
||||
out->ctime_ms = (double)st.st_ctimespec.tv_sec * 1000.0 +
|
||||
(double)st.st_ctimespec.tv_nsec / 1e6;
|
||||
#else
|
||||
out->blocks = (double)st.st_blocks;
|
||||
out->nlink = (double)st.st_nlink;
|
||||
out->atime_ms = (double)st.st_atim.tv_sec * 1000.0 +
|
||||
(double)st.st_atim.tv_nsec / 1e6;
|
||||
out->mtime_ms = (double)st.st_mtim.tv_sec * 1000.0 +
|
||||
(double)st.st_mtim.tv_nsec / 1e6;
|
||||
out->ctime_ms = (double)st.st_ctim.tv_sec * 1000.0 +
|
||||
(double)st.st_ctim.tv_nsec / 1e6;
|
||||
#endif
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
+1087
-22
File diff suppressed because it is too large
Load Diff
@@ -199,6 +199,7 @@ void scr_tls_fetch_client_wrap(ScrNetSocket *sock, void *ctx);
|
||||
* events hook, and `pollfd` exposes the unit's poller fd so the idle
|
||||
* poll(2) sleep wakes on socket readiness (-1 = no fd yet). */
|
||||
void scr_loop_set_net(bool (*pending)(void), void (*dispatch)(void), int (*pollfd)(void));
|
||||
void scr_loop_set_net_io(bool (*io)(void));
|
||||
/* True when fibers are queued on the microtask ready queue — dispatch
|
||||
* hooks use it to yield between event batches so promise jobs interleave
|
||||
* (net's sweep/drain alternation). */
|
||||
|
||||
@@ -101,6 +101,10 @@ ScrStr *scr_fs_read_file_encoded(ScrStr *path, ScrStr *encoding);
|
||||
ScrStr *scr_fs_read_fd_encoded(double fd, ScrStr *encoding);
|
||||
ScrDyn *scr_fs_callback_value(ScrStr *member);
|
||||
ScrDyn *scr_fs_callback_call(ScrStr *member, const ScrDyn *args);
|
||||
/* The error-first callback queue (scr_bytes_io.c): a pending exception
|
||||
* becomes [err], otherwise [null, result(, buffer)]; the callback runs on
|
||||
* a later loop turn. result moves in. */
|
||||
void scr_fs_cb_schedule(const ScrDyn *callback, ScrDyn *result, const ScrDyn *buffer);
|
||||
ScrBytes *scr_fs_read_fd_bytes(double fd); /* +1; the fd form (scr_lib.c) */
|
||||
void scr_fs_write_file_bytes(ScrStr *path, const ScrBytes *data);
|
||||
void scr_fs_append_file_bytes(ScrStr *path, const ScrBytes *data);
|
||||
|
||||
+607
-92
@@ -130,6 +130,17 @@ static ScrStr *scr_fs_win_utf8(const WCHAR *text, size_t length) {
|
||||
#if defined(__linux__)
|
||||
#include <sched.h> /* sched_getaffinity, CPU_COUNT (os.availableParallelism) */
|
||||
#include <sys/prctl.h> /* PR_SET_NAME (process.title) */
|
||||
#include <sys/syscall.h> /* SYS_statx (fs stats) */
|
||||
#ifndef SYS_statx
|
||||
#if defined(__x86_64__)
|
||||
#define SYS_statx 332
|
||||
#elif defined(__aarch64__)
|
||||
#define SYS_statx 291 /* the generic syscall table */
|
||||
#endif
|
||||
#endif
|
||||
#ifndef AT_EMPTY_PATH
|
||||
#define AT_EMPTY_PATH 0x1000
|
||||
#endif
|
||||
#elif defined(__APPLE__)
|
||||
#include <sys/sysctl.h> /* sysctlbyname (os.availableParallelism) */
|
||||
#endif
|
||||
@@ -2140,7 +2151,7 @@ static double scr_filetime_us(FILETIME ft) {
|
||||
/* A filesystem FILETIME is 100ns ticks since 1601. Match libuv's split into
|
||||
* Unix seconds + nanoseconds before doing Node's millisecond arithmetic; the
|
||||
* split matters for the last-bit rounding of dates far from the epoch. */
|
||||
static double scr_filetime_unix_ms(FILETIME ft) {
|
||||
static void scr_filetime_timespec(FILETIME ft, int64_t *sec_out, int64_t *nsec_out) {
|
||||
ULARGE_INTEGER raw;
|
||||
raw.LowPart = ft.dwLowDateTime;
|
||||
raw.HighPart = ft.dwHighDateTime;
|
||||
@@ -2151,7 +2162,8 @@ static double scr_filetime_unix_ms(FILETIME ft) {
|
||||
sec--;
|
||||
rem += INT64_C(10000000);
|
||||
}
|
||||
return (double)sec * 1000.0 + (double)rem / 10000.0;
|
||||
*sec_out = sec;
|
||||
*nsec_out = rem * 100;
|
||||
}
|
||||
double scr_cpu_user(void) {
|
||||
FILETIME c, e, k, u;
|
||||
@@ -4815,24 +4827,37 @@ void scr_process_stdin_destroy(void) {
|
||||
}
|
||||
|
||||
/* ── Stats values ────────────────────────────────────────────────────
|
||||
* An immutable snapshot of stat(2) results — the lowered type/mode, device,
|
||||
* inode, size, allocation/link, and access/write/change-time slice. statSync
|
||||
* THROWS like the other sync fs calls; the promise form rejects (see the fsp
|
||||
* section). */
|
||||
* An immutable snapshot of one stat(2) result: libuv's integers and its four
|
||||
* timestamps. statSync THROWS like the other sync fs calls; the promise form
|
||||
* rejects (see the fsp section). scr_stats_dyn builds Node's Stats and
|
||||
* BigIntStats objects from the same snapshot. */
|
||||
|
||||
/* The integers in the order Node's binding reports them. */
|
||||
enum {
|
||||
SCR_STATS_DEV,
|
||||
SCR_STATS_MODE,
|
||||
SCR_STATS_NLINK,
|
||||
SCR_STATS_UID,
|
||||
SCR_STATS_GID,
|
||||
SCR_STATS_RDEV,
|
||||
SCR_STATS_BLKSIZE,
|
||||
SCR_STATS_INO,
|
||||
SCR_STATS_SIZE,
|
||||
SCR_STATS_BLOCKS,
|
||||
SCR_STATS_FIELDS
|
||||
};
|
||||
|
||||
/* The timestamps, each kept as seconds + nanoseconds. */
|
||||
enum { SCR_STATS_ATIME, SCR_STATS_MTIME, SCR_STATS_CTIME, SCR_STATS_BIRTHTIME };
|
||||
|
||||
struct ScrStats {
|
||||
size_t rc;
|
||||
bool is_file;
|
||||
bool is_dir;
|
||||
bool is_symlink; /* lstat only — a followed stat never sees one */
|
||||
double dev;
|
||||
double ino;
|
||||
double size;
|
||||
double blocks; /* allocated size in 512-byte units (Node/libuv) */
|
||||
double nlink;
|
||||
double atime_ms;
|
||||
double mtime_ms; /* milliseconds with the nanosecond fraction (Node) */
|
||||
double ctime_ms;
|
||||
uint64_t fields[SCR_STATS_FIELDS]; /* blocks counts 512-byte units */
|
||||
int64_t sec[4];
|
||||
int64_t nsec[4];
|
||||
};
|
||||
|
||||
ScrStats *scr_stats_retain(ScrStats *s) {
|
||||
@@ -4848,20 +4873,66 @@ void scr_stats_release(ScrStats *s) {
|
||||
void *scr_stats_retain_v(void *p) { return scr_stats_retain(p); }
|
||||
void scr_stats_release_v(void *p) { scr_stats_release(p); }
|
||||
|
||||
/* The S_IFMT type bits, spelled out because Windows lacks some macros. */
|
||||
#define SCR_STATS_IFMT 0xF000u
|
||||
#define SCR_STATS_IFIFO 0x1000u
|
||||
#define SCR_STATS_IFCHR 0x2000u
|
||||
#define SCR_STATS_IFDIR 0x4000u
|
||||
#define SCR_STATS_IFBLK 0x6000u
|
||||
#define SCR_STATS_IFREG 0x8000u
|
||||
#define SCR_STATS_IFLNK 0xA000u
|
||||
#define SCR_STATS_IFSOCK 0xC000u
|
||||
|
||||
static bool scr_stats_type_is(const ScrStats *s, unsigned type) {
|
||||
#ifdef _WIN32
|
||||
/* Node answers false for the types Windows does not have. */
|
||||
if (type == SCR_STATS_IFIFO || type == SCR_STATS_IFBLK || type == SCR_STATS_IFSOCK) return false;
|
||||
#endif
|
||||
return (s->fields[SCR_STATS_MODE] & SCR_STATS_IFMT) == type;
|
||||
}
|
||||
|
||||
bool scr_stats_is_file(ScrStats *s) { return s->is_file; }
|
||||
bool scr_stats_is_dir(ScrStats *s) { return s->is_dir; }
|
||||
bool scr_stats_is_symlink(ScrStats *s) { return s->is_symlink; }
|
||||
double scr_stats_dev(ScrStats *s) { return s->dev; }
|
||||
double scr_stats_ino(ScrStats *s) { return s->ino; }
|
||||
double scr_stats_size(ScrStats *s) { return s->size; }
|
||||
double scr_stats_blocks(ScrStats *s) { return s->blocks; }
|
||||
double scr_stats_nlink(ScrStats *s) { return s->nlink; }
|
||||
double scr_stats_atime_ms(ScrStats *s) { return s->atime_ms; }
|
||||
double scr_stats_mtime_ms(ScrStats *s) { return s->mtime_ms; }
|
||||
double scr_stats_ctime_ms(ScrStats *s) { return s->ctime_ms; }
|
||||
bool scr_stats_is_block_device(ScrStats *s) { return scr_stats_type_is(s, SCR_STATS_IFBLK); }
|
||||
bool scr_stats_is_character_device(ScrStats *s) { return scr_stats_type_is(s, SCR_STATS_IFCHR); }
|
||||
bool scr_stats_is_fifo(ScrStats *s) { return scr_stats_type_is(s, SCR_STATS_IFIFO); }
|
||||
bool scr_stats_is_socket(ScrStats *s) { return scr_stats_type_is(s, SCR_STATS_IFSOCK); }
|
||||
double scr_stats_dev(ScrStats *s) { return (double)s->fields[SCR_STATS_DEV]; }
|
||||
double scr_stats_mode(ScrStats *s) { return (double)s->fields[SCR_STATS_MODE]; }
|
||||
double scr_stats_nlink(ScrStats *s) { return (double)s->fields[SCR_STATS_NLINK]; }
|
||||
double scr_stats_uid(ScrStats *s) { return (double)s->fields[SCR_STATS_UID]; }
|
||||
double scr_stats_gid(ScrStats *s) { return (double)s->fields[SCR_STATS_GID]; }
|
||||
double scr_stats_rdev(ScrStats *s) { return (double)s->fields[SCR_STATS_RDEV]; }
|
||||
double scr_stats_blksize(ScrStats *s) { return (double)s->fields[SCR_STATS_BLKSIZE]; }
|
||||
double scr_stats_ino(ScrStats *s) { return (double)s->fields[SCR_STATS_INO]; }
|
||||
double scr_stats_size(ScrStats *s) { return (double)s->fields[SCR_STATS_SIZE]; }
|
||||
double scr_stats_blocks(ScrStats *s) { return (double)s->fields[SCR_STATS_BLOCKS]; }
|
||||
|
||||
/* Node's msFromTimeSpec: seconds * 1000 + nanoseconds / 1e6. */
|
||||
static double scr_stats_ms(const ScrStats *s, int which) {
|
||||
return (double)s->sec[which] * 1000.0 + (double)s->nsec[which] / 1e6;
|
||||
}
|
||||
|
||||
double scr_stats_atime_ms(ScrStats *s) { return scr_stats_ms(s, SCR_STATS_ATIME); }
|
||||
double scr_stats_mtime_ms(ScrStats *s) { return scr_stats_ms(s, SCR_STATS_MTIME); }
|
||||
double scr_stats_ctime_ms(ScrStats *s) { return scr_stats_ms(s, SCR_STATS_CTIME); }
|
||||
double scr_stats_birthtime_ms(ScrStats *s) { return scr_stats_ms(s, SCR_STATS_BIRTHTIME); }
|
||||
|
||||
/* Math.round: the nearest integer, halves toward +Infinity. */
|
||||
static double scr_stats_round(double ms) {
|
||||
double low = floor(ms);
|
||||
return ms - low >= 0.5 ? low + 1 : low;
|
||||
}
|
||||
|
||||
/* The Date fields: Node rounds the millisecond value before new Date(). */
|
||||
double scr_stats_atime(ScrStats *s) { return scr_date_new_ms(scr_stats_round(scr_stats_ms(s, SCR_STATS_ATIME))); }
|
||||
double scr_stats_mtime(ScrStats *s) { return scr_date_new_ms(scr_stats_round(scr_stats_ms(s, SCR_STATS_MTIME))); }
|
||||
double scr_stats_ctime(ScrStats *s) { return scr_date_new_ms(scr_stats_round(scr_stats_ms(s, SCR_STATS_CTIME))); }
|
||||
double scr_stats_birthtime(ScrStats *s) { return scr_date_new_ms(scr_stats_round(scr_stats_ms(s, SCR_STATS_BIRTHTIME))); }
|
||||
|
||||
static ScrStats *scr_stats_new(void) {
|
||||
ScrStats *s = malloc(sizeof(ScrStats));
|
||||
ScrStats *s = calloc(1, sizeof(ScrStats));
|
||||
if (!s) {
|
||||
scr_trap("scriptc: out of memory\n");
|
||||
}
|
||||
@@ -4869,7 +4940,22 @@ static ScrStats *scr_stats_new(void) {
|
||||
return s;
|
||||
}
|
||||
|
||||
static void scr_stats_set_time(ScrStats *s, int which, int64_t sec, int64_t nsec) {
|
||||
s->sec[which] = sec;
|
||||
s->nsec[which] = nsec;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
static void scr_stats_set_filetime(ScrStats *s, int which, FILETIME ft) {
|
||||
scr_filetime_timespec(ft, &s->sec[which], &s->nsec[which]);
|
||||
}
|
||||
|
||||
/* libuv's Windows mode: the entry type plus read for everyone, and write
|
||||
* unless the entry is read-only. */
|
||||
static uint64_t scr_stats_win_mode(unsigned type, bool read_only) {
|
||||
return type | (read_only ? 0444u : 0666u);
|
||||
}
|
||||
|
||||
/* The CRT fallback is deliberately complete: callers either get every field
|
||||
* from this one stat() result or every field from one Win32 handle below,
|
||||
* never a snapshot spliced across two path resolutions. */
|
||||
@@ -4878,14 +4964,22 @@ static ScrStats *scr_stats_of_crt(const struct stat *st) {
|
||||
s->is_file = S_ISREG(st->st_mode);
|
||||
s->is_dir = S_ISDIR(st->st_mode);
|
||||
s->is_symlink = false;
|
||||
s->dev = (double)st->st_dev;
|
||||
s->ino = (double)st->st_ino;
|
||||
s->size = (double)st->st_size;
|
||||
s->blocks = st->st_size <= 0 ? 0.0 : (double)(((uint64_t)st->st_size + 511) >> 9);
|
||||
s->nlink = (double)st->st_nlink;
|
||||
s->atime_ms = (double)st->st_atime * 1000.0;
|
||||
s->mtime_ms = (double)st->st_mtime * 1000.0;
|
||||
s->ctime_ms = (double)st->st_ctime * 1000.0;
|
||||
s->fields[SCR_STATS_DEV] = (uint64_t)st->st_dev;
|
||||
/* Character devices and pipes carry only their type bits in libuv. */
|
||||
unsigned type = (unsigned)st->st_mode & SCR_STATS_IFMT;
|
||||
s->fields[SCR_STATS_MODE] = type == SCR_STATS_IFREG || type == SCR_STATS_IFDIR
|
||||
? scr_stats_win_mode(type, !(st->st_mode & 0200))
|
||||
: type;
|
||||
s->fields[SCR_STATS_NLINK] = (uint64_t)st->st_nlink;
|
||||
s->fields[SCR_STATS_BLKSIZE] = 4096;
|
||||
s->fields[SCR_STATS_INO] = (uint64_t)st->st_ino;
|
||||
s->fields[SCR_STATS_SIZE] = st->st_size <= 0 ? 0 : (uint64_t)st->st_size;
|
||||
s->fields[SCR_STATS_BLOCKS] = st->st_size <= 0 ? 0 : ((uint64_t)st->st_size + 511) >> 9;
|
||||
scr_stats_set_time(s, SCR_STATS_ATIME, (int64_t)st->st_atime, 0);
|
||||
scr_stats_set_time(s, SCR_STATS_MTIME, (int64_t)st->st_mtime, 0);
|
||||
/* The CRT's st_ctime is the creation time. */
|
||||
scr_stats_set_time(s, SCR_STATS_CTIME, (int64_t)st->st_ctime, 0);
|
||||
scr_stats_set_time(s, SCR_STATS_BIRTHTIME, (int64_t)st->st_ctime, 0);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -5127,57 +5221,188 @@ static ScrStats *scr_stats_of_path(const ScrStr *path, const char *op,
|
||||
s->is_file = !is_link && (have_standard ? !standard.Directory : !is_dir);
|
||||
s->is_dir = !is_link && (have_standard ? standard.Directory : is_dir);
|
||||
s->is_symlink = is_link;
|
||||
s->dev = (double)basic.dwVolumeSerialNumber;
|
||||
s->ino = (double)basic.nFileIndexHigh * 4294967296.0 +
|
||||
(double)basic.nFileIndexLow;
|
||||
s->size = is_link && link_size >= 0 ? link_size : s->is_dir ? 0.0
|
||||
: have_standard ? (double)standard.EndOfFile.QuadPart : (double)size;
|
||||
s->blocks = have_standard
|
||||
? (double)((uint64_t)standard.AllocationSize.QuadPart >> 9)
|
||||
: size == 0 ? 0.0 : (double)((size + 511) >> 9);
|
||||
s->nlink = have_standard
|
||||
? (double)standard.NumberOfLinks
|
||||
: (double)basic.nNumberOfLinks;
|
||||
s->atime_ms = scr_filetime_unix_ms(basic.ftLastAccessTime);
|
||||
s->mtime_ms = scr_filetime_unix_ms(basic.ftLastWriteTime);
|
||||
s->fields[SCR_STATS_DEV] = basic.dwVolumeSerialNumber;
|
||||
s->fields[SCR_STATS_MODE] = scr_stats_win_mode(
|
||||
is_link ? SCR_STATS_IFLNK : s->is_dir ? SCR_STATS_IFDIR : SCR_STATS_IFREG,
|
||||
(basic.dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0);
|
||||
s->fields[SCR_STATS_NLINK] = have_standard ? standard.NumberOfLinks : basic.nNumberOfLinks;
|
||||
s->fields[SCR_STATS_BLKSIZE] = 4096;
|
||||
s->fields[SCR_STATS_INO] = ((uint64_t)basic.nFileIndexHigh << 32) | basic.nFileIndexLow;
|
||||
s->fields[SCR_STATS_SIZE] = is_link && link_size >= 0 ? (uint64_t)link_size : s->is_dir ? 0
|
||||
: have_standard ? (uint64_t)standard.EndOfFile.QuadPart : size;
|
||||
s->fields[SCR_STATS_BLOCKS] = have_standard
|
||||
? (uint64_t)standard.AllocationSize.QuadPart >> 9
|
||||
: size == 0 ? 0 : (size + 511) >> 9;
|
||||
scr_stats_set_filetime(s, SCR_STATS_ATIME, basic.ftLastAccessTime);
|
||||
scr_stats_set_filetime(s, SCR_STATS_MTIME, basic.ftLastWriteTime);
|
||||
scr_stats_set_filetime(s, SCR_STATS_BIRTHTIME, basic.ftCreationTime);
|
||||
if (have_times) {
|
||||
FILETIME changed;
|
||||
changed.dwLowDateTime = times.ChangeTime.LowPart;
|
||||
changed.dwHighDateTime = times.ChangeTime.HighPart;
|
||||
s->ctime_ms = scr_filetime_unix_ms(changed);
|
||||
scr_stats_set_filetime(s, SCR_STATS_CTIME, changed);
|
||||
} else {
|
||||
s->ctime_ms = s->mtime_ms;
|
||||
scr_stats_set_time(s, SCR_STATS_CTIME, s->sec[SCR_STATS_MTIME], s->nsec[SCR_STATS_MTIME]);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/* An open descriptor: the CRT snapshot, completed from the Win32 handle the
|
||||
* way libuv's fstat reads it. */
|
||||
static ScrStats *scr_stats_of_fd_win(int fd, const struct stat *st) {
|
||||
ScrStats *out = scr_stats_of_crt(st);
|
||||
HANDLE os_handle = (HANDLE)_get_osfhandle(fd);
|
||||
if (os_handle == INVALID_HANDLE_VALUE) return out;
|
||||
BY_HANDLE_FILE_INFORMATION basic;
|
||||
if (GetFileInformationByHandle(os_handle, &basic)) {
|
||||
out->fields[SCR_STATS_DEV] = basic.dwVolumeSerialNumber;
|
||||
out->fields[SCR_STATS_INO] = ((uint64_t)basic.nFileIndexHigh << 32) | basic.nFileIndexLow;
|
||||
out->fields[SCR_STATS_NLINK] = basic.nNumberOfLinks;
|
||||
out->fields[SCR_STATS_MODE] = scr_stats_win_mode(
|
||||
out->is_dir ? SCR_STATS_IFDIR : SCR_STATS_IFREG,
|
||||
(basic.dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0);
|
||||
scr_stats_set_filetime(out, SCR_STATS_ATIME, basic.ftLastAccessTime);
|
||||
scr_stats_set_filetime(out, SCR_STATS_MTIME, basic.ftLastWriteTime);
|
||||
scr_stats_set_filetime(out, SCR_STATS_BIRTHTIME, basic.ftCreationTime);
|
||||
}
|
||||
FILE_BASIC_INFO times;
|
||||
if (GetFileInformationByHandleEx(os_handle, FileBasicInfo, ×, sizeof times)) {
|
||||
FILETIME changed;
|
||||
changed.dwLowDateTime = times.ChangeTime.LowPart;
|
||||
changed.dwHighDateTime = times.ChangeTime.HighPart;
|
||||
scr_stats_set_filetime(out, SCR_STATS_CTIME, changed);
|
||||
}
|
||||
FILE_STANDARD_INFO standard;
|
||||
if (GetFileInformationByHandleEx(os_handle, FileStandardInfo, &standard, sizeof standard)) {
|
||||
out->fields[SCR_STATS_BLOCKS] = (uint64_t)standard.AllocationSize.QuadPart >> 9;
|
||||
out->fields[SCR_STATS_NLINK] = standard.NumberOfLinks;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
#else
|
||||
/* libuv's uv__to_stat: macOS, FreeBSD and NetBSD report a creation time;
|
||||
* elsewhere birthtime repeats the change time. */
|
||||
static ScrStats *scr_stats_of(const struct stat *st) {
|
||||
ScrStats *s = scr_stats_new();
|
||||
s->is_file = S_ISREG(st->st_mode);
|
||||
s->is_dir = S_ISDIR(st->st_mode);
|
||||
s->is_symlink = S_ISLNK(st->st_mode);
|
||||
s->dev = (double)st->st_dev;
|
||||
s->ino = (double)st->st_ino;
|
||||
s->fields[SCR_STATS_DEV] = (uint64_t)st->st_dev;
|
||||
s->fields[SCR_STATS_MODE] = (uint64_t)st->st_mode;
|
||||
s->fields[SCR_STATS_NLINK] = (uint64_t)st->st_nlink;
|
||||
s->fields[SCR_STATS_UID] = (uint64_t)st->st_uid;
|
||||
s->fields[SCR_STATS_GID] = (uint64_t)st->st_gid;
|
||||
s->fields[SCR_STATS_RDEV] = (uint64_t)st->st_rdev;
|
||||
s->fields[SCR_STATS_BLKSIZE] = (uint64_t)st->st_blksize;
|
||||
s->fields[SCR_STATS_INO] = (uint64_t)st->st_ino;
|
||||
s->fields[SCR_STATS_SIZE] = (uint64_t)st->st_size;
|
||||
s->fields[SCR_STATS_BLOCKS] = (uint64_t)st->st_blocks;
|
||||
#if defined(__APPLE__)
|
||||
s->atime_ms = (double)st->st_atimespec.tv_sec * 1000.0 +
|
||||
(double)st->st_atimespec.tv_nsec / 1e6;
|
||||
s->mtime_ms = (double)st->st_mtimespec.tv_sec * 1000.0 +
|
||||
(double)st->st_mtimespec.tv_nsec / 1e6;
|
||||
s->ctime_ms = (double)st->st_ctimespec.tv_sec * 1000.0 +
|
||||
(double)st->st_ctimespec.tv_nsec / 1e6;
|
||||
scr_stats_set_time(s, SCR_STATS_ATIME, st->st_atimespec.tv_sec, st->st_atimespec.tv_nsec);
|
||||
scr_stats_set_time(s, SCR_STATS_MTIME, st->st_mtimespec.tv_sec, st->st_mtimespec.tv_nsec);
|
||||
scr_stats_set_time(s, SCR_STATS_CTIME, st->st_ctimespec.tv_sec, st->st_ctimespec.tv_nsec);
|
||||
scr_stats_set_time(s, SCR_STATS_BIRTHTIME, st->st_birthtimespec.tv_sec, st->st_birthtimespec.tv_nsec);
|
||||
#else
|
||||
s->atime_ms = (double)st->st_atim.tv_sec * 1000.0 +
|
||||
(double)st->st_atim.tv_nsec / 1e6;
|
||||
s->mtime_ms = (double)st->st_mtim.tv_sec * 1000.0 +
|
||||
(double)st->st_mtim.tv_nsec / 1e6;
|
||||
s->ctime_ms = (double)st->st_ctim.tv_sec * 1000.0 +
|
||||
(double)st->st_ctim.tv_nsec / 1e6;
|
||||
scr_stats_set_time(s, SCR_STATS_ATIME, st->st_atim.tv_sec, st->st_atim.tv_nsec);
|
||||
scr_stats_set_time(s, SCR_STATS_MTIME, st->st_mtim.tv_sec, st->st_mtim.tv_nsec);
|
||||
scr_stats_set_time(s, SCR_STATS_CTIME, st->st_ctim.tv_sec, st->st_ctim.tv_nsec);
|
||||
#if defined(__FreeBSD__) || defined(__NetBSD__)
|
||||
scr_stats_set_time(s, SCR_STATS_BIRTHTIME, st->st_birthtim.tv_sec, st->st_birthtim.tv_nsec);
|
||||
#else
|
||||
scr_stats_set_time(s, SCR_STATS_BIRTHTIME, st->st_ctim.tv_sec, st->st_ctim.tv_nsec);
|
||||
#endif
|
||||
#endif
|
||||
s->blocks = (double)st->st_blocks;
|
||||
s->nlink = (double)st->st_nlink;
|
||||
s->size = (double)st->st_size;
|
||||
return s;
|
||||
}
|
||||
|
||||
#if defined(__linux__) && defined(SYS_statx)
|
||||
/* The kernel's struct statx (include/uapi/linux/stat.h). libuv reads stat
|
||||
* results through statx, which also reports the creation time. */
|
||||
typedef struct {
|
||||
int64_t tv_sec;
|
||||
uint32_t tv_nsec;
|
||||
int32_t reserved;
|
||||
} ScrStatxTime;
|
||||
|
||||
typedef struct {
|
||||
uint32_t mask;
|
||||
uint32_t blksize;
|
||||
uint64_t attributes;
|
||||
uint32_t nlink;
|
||||
uint32_t uid;
|
||||
uint32_t gid;
|
||||
uint16_t mode;
|
||||
uint16_t spare0;
|
||||
uint64_t ino;
|
||||
uint64_t size;
|
||||
uint64_t blocks;
|
||||
uint64_t attributes_mask;
|
||||
ScrStatxTime atime, btime, ctime, mtime;
|
||||
uint32_t rdev_major, rdev_minor;
|
||||
uint32_t dev_major, dev_minor;
|
||||
uint64_t spare[14];
|
||||
} ScrStatx;
|
||||
|
||||
_Static_assert(sizeof(ScrStatx) == 256, "struct statx is 256 bytes");
|
||||
|
||||
/* glibc's and musl's makedev encoding, which libuv applies to statx. */
|
||||
static uint64_t scr_stats_makedev(uint64_t major, uint64_t minor) {
|
||||
return ((major & 0xfffff000u) << 32) | ((major & 0xfffu) << 8) |
|
||||
((minor & 0xffffff00u) << 12) | (minor & 0xffu);
|
||||
}
|
||||
|
||||
static SCR_TL bool scr_stats_no_statx = false;
|
||||
|
||||
static ScrStats *scr_stats_of_statx(const ScrStatx *sx) {
|
||||
ScrStats *s = scr_stats_new();
|
||||
unsigned type = sx->mode & SCR_STATS_IFMT;
|
||||
s->is_file = type == SCR_STATS_IFREG;
|
||||
s->is_dir = type == SCR_STATS_IFDIR;
|
||||
s->is_symlink = type == SCR_STATS_IFLNK;
|
||||
s->fields[SCR_STATS_DEV] = scr_stats_makedev(sx->dev_major, sx->dev_minor);
|
||||
s->fields[SCR_STATS_MODE] = sx->mode;
|
||||
s->fields[SCR_STATS_NLINK] = sx->nlink;
|
||||
s->fields[SCR_STATS_UID] = sx->uid;
|
||||
s->fields[SCR_STATS_GID] = sx->gid;
|
||||
s->fields[SCR_STATS_RDEV] = scr_stats_makedev(sx->rdev_major, sx->rdev_minor);
|
||||
s->fields[SCR_STATS_BLKSIZE] = sx->blksize;
|
||||
s->fields[SCR_STATS_INO] = sx->ino;
|
||||
s->fields[SCR_STATS_SIZE] = sx->size;
|
||||
s->fields[SCR_STATS_BLOCKS] = sx->blocks;
|
||||
scr_stats_set_time(s, SCR_STATS_ATIME, sx->atime.tv_sec, sx->atime.tv_nsec);
|
||||
scr_stats_set_time(s, SCR_STATS_MTIME, sx->mtime.tv_sec, sx->mtime.tv_nsec);
|
||||
scr_stats_set_time(s, SCR_STATS_CTIME, sx->ctime.tv_sec, sx->ctime.tv_nsec);
|
||||
scr_stats_set_time(s, SCR_STATS_BIRTHTIME, sx->btime.tv_sec, sx->btime.tv_nsec);
|
||||
return s;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* stat, lstat, or fstat when path is NULL, as libuv runs them: statx on
|
||||
* Linux, else the classic call. NULL with *error set on failure. */
|
||||
static ScrStats *scr_stats_posix(const char *path, int fd, bool no_follow, int *error) {
|
||||
#if defined(__linux__) && defined(SYS_statx)
|
||||
if (!scr_stats_no_statx) {
|
||||
ScrStatx sx;
|
||||
/* STATX_BASIC_STATS | STATX_BTIME */
|
||||
long rc = path ? syscall(SYS_statx, AT_FDCWD, path, no_follow ? AT_SYMLINK_NOFOLLOW : 0, 0xfffu, &sx)
|
||||
: syscall(SYS_statx, fd, "", AT_EMPTY_PATH, 0xfffu, &sx);
|
||||
if (rc == 0) return scr_stats_of_statx(&sx);
|
||||
if (rc == -1 && errno != EINVAL && errno != EPERM && errno != ENOSYS && errno != EOPNOTSUPP) {
|
||||
*error = errno;
|
||||
return NULL;
|
||||
}
|
||||
/* A seccomp filter or an old kernel: libuv uses stat from then on. */
|
||||
scr_stats_no_statx = true;
|
||||
}
|
||||
#endif
|
||||
struct stat st;
|
||||
int rc = path ? (no_follow ? lstat(path, &st) : stat(path, &st)) : fstat(fd, &st);
|
||||
if (rc != 0) {
|
||||
*error = errno;
|
||||
return NULL;
|
||||
}
|
||||
return scr_stats_of(&st);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Symbolic link targets are stored verbatim on POSIX. Windows junctions
|
||||
@@ -5418,12 +5643,10 @@ ScrStats *scr_fs_stat(ScrStr *path) {
|
||||
#ifdef _WIN32
|
||||
return scr_stats_of_path(path, "stat", false);
|
||||
#else
|
||||
struct stat st;
|
||||
if (stat(path->data, &st) != 0) { /* follows symlinks, like Node's statSync */
|
||||
scr_fs_throw(errno, "stat", path);
|
||||
return NULL;
|
||||
}
|
||||
return scr_stats_of(&st);
|
||||
int error = 0;
|
||||
ScrStats *s = scr_stats_posix(path->data, -1, false, &error); /* follows symlinks */
|
||||
if (!s) scr_fs_throw(error, "stat", path);
|
||||
return s;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -5431,38 +5654,330 @@ ScrStats *scr_fs_lstat(ScrStr *path) {
|
||||
#ifdef _WIN32
|
||||
return scr_stats_of_path(path, "lstat", true);
|
||||
#else
|
||||
int error = 0;
|
||||
ScrStats *s = scr_stats_posix(path->data, -1, true, &error); /* no follow */
|
||||
if (!s) scr_fs_throw(error, "lstat", path);
|
||||
return s;
|
||||
#endif
|
||||
}
|
||||
|
||||
ScrStats *scr_stats_of_fd(int fd, int *error) {
|
||||
#ifdef _WIN32
|
||||
struct stat st;
|
||||
if (lstat(path->data, &st) != 0) { /* no follow; Node reports lstat */
|
||||
scr_fs_throw(errno, "lstat", path);
|
||||
if (fstat(fd, &st) != 0) {
|
||||
*error = errno;
|
||||
return NULL;
|
||||
}
|
||||
return scr_stats_of(&st);
|
||||
return scr_stats_of_fd_win(fd, &st);
|
||||
#else
|
||||
return scr_stats_posix(NULL, fd, false, error);
|
||||
#endif
|
||||
}
|
||||
|
||||
ScrStats *scr_fs_fstat(double fd) {
|
||||
struct stat st;
|
||||
if (fstat((int)fd, &st) != 0) {
|
||||
scr_fs_throw_nopath(errno, "fstat");
|
||||
return NULL;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
ScrStats *s = scr_stats_new();
|
||||
s->is_file = S_ISREG(st.st_mode);
|
||||
s->is_dir = S_ISDIR(st.st_mode);
|
||||
s->is_symlink = false;
|
||||
s->dev = (double)st.st_dev;
|
||||
s->ino = (double)st.st_ino;
|
||||
s->size = (double)st.st_size;
|
||||
s->blocks = s->size <= 0 ? 0 : ceil(s->size / 512.0);
|
||||
s->nlink = (double)st.st_nlink;
|
||||
s->atime_ms = (double)st.st_atime * 1000.0;
|
||||
s->mtime_ms = (double)st.st_mtime * 1000.0;
|
||||
s->ctime_ms = (double)st.st_ctime * 1000.0;
|
||||
int error = 0;
|
||||
ScrStats *s = scr_stats_of_fd((int)fd, &error);
|
||||
if (!s) scr_fs_throw_nopath(error, "fstat");
|
||||
return s;
|
||||
#else
|
||||
return scr_stats_of(&st);
|
||||
}
|
||||
|
||||
/* ── Stats objects ───────────────────────────────────────────────────
|
||||
* Node's Stats and BigIntStats over a snapshot. The integers and the *Ms
|
||||
* fields (and BigIntStats' *Ns fields) are own properties in the binding's
|
||||
* order. Stats.prototype holds _checkModeProperty and lazy atime, mtime,
|
||||
* ctime and birthtime accessors, which define an own Date the first time
|
||||
* they run. The type tests (isFile, ...) sit on StatsBase.prototype and go
|
||||
* through this._checkModeProperty(S_IF...), which reads this.mode. */
|
||||
|
||||
static SCR_TL ScrDyn *scr_stats_base_proto = NULL;
|
||||
static SCR_TL ScrDyn *scr_stats_protos[2] = { NULL, NULL }; /* Stats, BigIntStats */
|
||||
|
||||
static const char *const scr_stats_date_names[4] = { "atime", "mtime", "ctime", "birthtime" };
|
||||
|
||||
/* Object.defineProperty(target, name, { value, writable } or { get, set },
|
||||
* enumerable, configurable: true). value, get and set move in. */
|
||||
static bool scr_stats_define(ScrDyn *target, const char *name, ScrDyn *value, ScrDyn *get, ScrDyn *set, bool enumerable) {
|
||||
ScrDyn *descriptor = scr_dyn_new_obj();
|
||||
if (value) {
|
||||
scr_dyn_obj_set(descriptor, "value", 5, value);
|
||||
scr_dyn_obj_set(descriptor, "writable", 8, scr_dyn_new_bool(true));
|
||||
}
|
||||
if (get) scr_dyn_obj_set(descriptor, "get", 3, get);
|
||||
if (set) scr_dyn_obj_set(descriptor, "set", 3, set);
|
||||
scr_dyn_obj_set(descriptor, "enumerable", 10, scr_dyn_new_bool(enumerable));
|
||||
scr_dyn_obj_set(descriptor, "configurable", 12, scr_dyn_new_bool(true));
|
||||
ScrStr *text = scr_str_new(name, strlen(name));
|
||||
ScrDyn *key = scr_dyn_new_str(text);
|
||||
scr_str_release(text);
|
||||
ScrDyn *defined = scr_dyn_define_property(target, key, descriptor);
|
||||
scr_dyn_release(key);
|
||||
scr_dyn_release(descriptor);
|
||||
if (!defined) return false;
|
||||
scr_dyn_release(defined);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* `this` for a prototype member, or NULL with Node's TypeError when the
|
||||
* receiver is undefined or null and a property read would throw. */
|
||||
static ScrDyn *scr_stats_receiver(const char *reading) {
|
||||
ScrDyn *self = scr_dyn_this_get();
|
||||
if (self->kind != SCR_DYN_UNDEF && self->kind != SCR_DYN_NULL) return self;
|
||||
char message[96];
|
||||
int n = snprintf(message, sizeof message, "Cannot read properties of %s (reading '%s')",
|
||||
self->kind == SCR_DYN_UNDEF ? "undefined" : "null", reading);
|
||||
scr_dyn_release(self);
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, (size_t)n);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* The lazy Date getter: setOwnProperty(this, name, new Date(Math.round(
|
||||
* Number(this.<name>Ms)))). */
|
||||
static ScrDyn *scr_stats_date_get(int which) {
|
||||
char ms_name[16];
|
||||
int len = snprintf(ms_name, sizeof ms_name, "%sMs", scr_stats_date_names[which]);
|
||||
ScrDyn *self = scr_stats_receiver(ms_name);
|
||||
if (!self) return NULL;
|
||||
ScrDyn *ms = scr_dyn_obj_read(self, ms_name, (size_t)len);
|
||||
double number = ms ? scr_dyn_number_constructor(ms) : NAN;
|
||||
scr_dyn_release(ms);
|
||||
ScrDyn *date = NULL;
|
||||
if (!scr_exc_pending()) {
|
||||
ScrDyn *arguments = scr_dyn_new_arr();
|
||||
scr_dyn_arr_push(arguments, scr_dyn_new_num(scr_stats_round(number)));
|
||||
date = scr_dyn_native_date_new(arguments);
|
||||
scr_dyn_release(arguments);
|
||||
}
|
||||
if (date && !scr_stats_define(self, scr_stats_date_names[which], scr_dyn_retain(date), NULL, NULL, true)) {
|
||||
scr_dyn_release(date);
|
||||
date = NULL;
|
||||
}
|
||||
scr_dyn_release(self);
|
||||
return date;
|
||||
}
|
||||
|
||||
/* The setter: setOwnProperty(this, name, value). */
|
||||
static ScrDyn *scr_stats_date_set(int which, ScrDyn *const *args, size_t argc) {
|
||||
ScrDyn *self = scr_dyn_this_get();
|
||||
bool ok = scr_stats_define(self, scr_stats_date_names[which],
|
||||
scr_dyn_retain(argc > 0 ? args[0] : scr_dyn_undefined()), NULL, NULL, true);
|
||||
scr_dyn_release(self);
|
||||
return ok ? scr_dyn_retain(scr_dyn_undefined()) : NULL;
|
||||
}
|
||||
|
||||
#define SCR_STATS_DATE_ACCESSORS(index) \
|
||||
static ScrDyn *scr_stats_date_get_##index(ScrClosure *closure, ScrDyn *const *args, size_t argc) { \
|
||||
(void)closure; \
|
||||
(void)args; \
|
||||
(void)argc; \
|
||||
return scr_stats_date_get(index); \
|
||||
} \
|
||||
static ScrDyn *scr_stats_date_set_##index(ScrClosure *closure, ScrDyn *const *args, size_t argc) { \
|
||||
(void)closure; \
|
||||
return scr_stats_date_set(index, args, argc); \
|
||||
}
|
||||
SCR_STATS_DATE_ACCESSORS(0)
|
||||
SCR_STATS_DATE_ACCESSORS(1)
|
||||
SCR_STATS_DATE_ACCESSORS(2)
|
||||
SCR_STATS_DATE_ACCESSORS(3)
|
||||
#undef SCR_STATS_DATE_ACCESSORS
|
||||
|
||||
static const ScrDynThunk scr_stats_date_getters[4] = {
|
||||
scr_stats_date_get_0, scr_stats_date_get_1, scr_stats_date_get_2, scr_stats_date_get_3,
|
||||
};
|
||||
static const ScrDynThunk scr_stats_date_setters[4] = {
|
||||
scr_stats_date_set_0, scr_stats_date_set_1, scr_stats_date_set_2, scr_stats_date_set_3,
|
||||
};
|
||||
|
||||
/* _checkModeProperty(property): (this.mode & S_IFMT) === property, with
|
||||
* BigInt operands on BigIntStats. */
|
||||
static ScrDyn *scr_stats_check_mode(ScrDyn *const *args, size_t argc, bool bigint) {
|
||||
const ScrDyn *property = argc > 0 ? args[0] : scr_dyn_undefined();
|
||||
#ifdef _WIN32
|
||||
if (property->kind == SCR_DYN_NUM &&
|
||||
(property->v.num == SCR_STATS_IFIFO || property->v.num == SCR_STATS_IFBLK || property->v.num == SCR_STATS_IFSOCK))
|
||||
return scr_dyn_new_bool(false);
|
||||
#endif
|
||||
ScrDyn *self = scr_stats_receiver("mode");
|
||||
if (!self) return NULL;
|
||||
ScrDyn *mode = scr_dyn_obj_read(self, "mode", 4);
|
||||
scr_dyn_release(self);
|
||||
if (!mode) return NULL;
|
||||
ScrDyn *mask;
|
||||
if (bigint) {
|
||||
ScrBigInt *value = scr_bigint_from_u64(SCR_STATS_IFMT);
|
||||
mask = scr_dyn_new_bigint(value);
|
||||
scr_bigint_release(value);
|
||||
} else mask = scr_dyn_new_num(SCR_STATS_IFMT);
|
||||
ScrStr *operation = scr_str_new("&", 1);
|
||||
ScrDyn *masked = scr_dyn_bitwise(mode, mask, operation);
|
||||
scr_str_release(operation);
|
||||
scr_dyn_release(mask);
|
||||
scr_dyn_release(mode);
|
||||
if (!masked) return NULL;
|
||||
ScrDyn *expected = NULL;
|
||||
if (bigint) {
|
||||
ScrBigInt *value = scr_dyn_bigint_constructor(property);
|
||||
if (value) {
|
||||
expected = scr_dyn_new_bigint(value);
|
||||
scr_bigint_release(value);
|
||||
}
|
||||
} else expected = scr_dyn_retain((ScrDyn *)property);
|
||||
ScrDyn *result = expected ? scr_dyn_new_bool(scr_dyn_strict_eq(masked, expected)) : NULL;
|
||||
scr_dyn_release(expected);
|
||||
scr_dyn_release(masked);
|
||||
return result;
|
||||
}
|
||||
|
||||
static ScrDyn *scr_stats_check_mode_number(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
(void)closure;
|
||||
return scr_stats_check_mode(args, argc, false);
|
||||
}
|
||||
|
||||
static ScrDyn *scr_stats_check_mode_bigint(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
(void)closure;
|
||||
return scr_stats_check_mode(args, argc, true);
|
||||
}
|
||||
|
||||
/* isFile() and the other type tests: this._checkModeProperty(S_IF...). */
|
||||
static ScrDyn *scr_stats_type_test(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
(void)args;
|
||||
(void)argc;
|
||||
ScrDyn *self = scr_stats_receiver("_checkModeProperty");
|
||||
if (!self) return NULL;
|
||||
ScrDyn *check = scr_dyn_obj_read(self, "_checkModeProperty", 18);
|
||||
ScrDyn *result = NULL;
|
||||
if (check) {
|
||||
ScrDyn *type = scr_dyn_new_num(scr_box_get_f64(closure->caps[0]));
|
||||
scr_dyn_this_push_dyn(self);
|
||||
result = scr_dyn_call(check, &type, 1, "this._checkModeProperty");
|
||||
scr_dyn_this_pop();
|
||||
scr_dyn_release(type);
|
||||
scr_dyn_release(check);
|
||||
}
|
||||
scr_dyn_release(self);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* The prototypes' constructor members are only named; calling one has no
|
||||
* lowering. The type tests and _checkModeProperty are anonymous functions in
|
||||
* Node (assigned function expressions), so they carry no name. */
|
||||
static ScrDyn *scr_stats_constructor(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
(void)closure;
|
||||
(void)args;
|
||||
(void)argc;
|
||||
static const char message[] = "Calling the fs.Stats constructors has no lowering";
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ScrDyn *scr_stats_method(ScrDynThunk thunk, uint32_t arity, const char *name, double type) {
|
||||
ScrClosure *closure = scr_closure_new(NULL, 1);
|
||||
closure->caps[0] = scr_box_new(SCR_BOX_F64);
|
||||
scr_box_set_f64(closure->caps[0], type);
|
||||
return scr_dyn_new_func(closure, thunk, arity, "native:fs.Stats", name);
|
||||
}
|
||||
|
||||
static void scr_stats_protos_teardown(void) {
|
||||
for (size_t i = 0; i < 2; i++) {
|
||||
if (!scr_stats_protos[i]) continue;
|
||||
scr_stats_protos[i]->v.obj.class_name = NULL;
|
||||
scr_dyn_release(scr_stats_protos[i]);
|
||||
scr_stats_protos[i] = NULL;
|
||||
}
|
||||
if (scr_stats_base_proto) {
|
||||
scr_stats_base_proto->v.obj.class_name = NULL;
|
||||
scr_dyn_release(scr_stats_base_proto);
|
||||
scr_stats_base_proto = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static ScrDyn *scr_stats_prototype(bool bigint) {
|
||||
if (scr_stats_protos[bigint]) return scr_stats_protos[bigint];
|
||||
if (!scr_stats_base_proto) {
|
||||
static const struct {
|
||||
const char *name;
|
||||
unsigned type;
|
||||
} tests[] = {
|
||||
{ "isDirectory", SCR_STATS_IFDIR },
|
||||
{ "isFile", SCR_STATS_IFREG },
|
||||
{ "isBlockDevice", SCR_STATS_IFBLK },
|
||||
{ "isCharacterDevice", SCR_STATS_IFCHR },
|
||||
{ "isSymbolicLink", SCR_STATS_IFLNK },
|
||||
{ "isFIFO", SCR_STATS_IFIFO },
|
||||
{ "isSocket", SCR_STATS_IFSOCK },
|
||||
};
|
||||
ScrDyn *base = scr_dyn_new_obj();
|
||||
base->v.obj.class_name = "StatsBase";
|
||||
scr_stats_define(base, "constructor", scr_stats_method(scr_stats_constructor, 10, "StatsBase", 0), NULL, NULL, false);
|
||||
for (size_t i = 0; i < sizeof tests / sizeof tests[0]; i++)
|
||||
scr_dyn_obj_set(base, tests[i].name, strlen(tests[i].name),
|
||||
scr_stats_method(scr_stats_type_test, 0, NULL, tests[i].type));
|
||||
scr_stats_base_proto = base;
|
||||
scr_atexit(scr_stats_protos_teardown);
|
||||
}
|
||||
const char *name = bigint ? "BigIntStats" : "Stats";
|
||||
ScrDyn *proto = scr_dyn_new_obj();
|
||||
proto->v.obj.class_name = name;
|
||||
proto->prototype = scr_dyn_retain(scr_stats_base_proto);
|
||||
scr_stats_define(proto, "constructor", scr_stats_method(scr_stats_constructor, 14, name, 0), NULL, NULL, false);
|
||||
for (int i = 0; i < 4; i++)
|
||||
scr_stats_define(proto, scr_stats_date_names[i], NULL,
|
||||
scr_stats_method(scr_stats_date_getters[i], 0, "get", 0),
|
||||
scr_stats_method(scr_stats_date_setters[i], 1, "set", 0), true);
|
||||
scr_dyn_obj_set(proto, "_checkModeProperty", 18,
|
||||
scr_stats_method(bigint ? scr_stats_check_mode_bigint : scr_stats_check_mode_number, 1, NULL, 0));
|
||||
scr_stats_protos[bigint] = proto;
|
||||
return proto;
|
||||
}
|
||||
|
||||
static ScrDyn *scr_stats_bigint_dyn(ScrBigInt *value /* moves */) {
|
||||
ScrDyn *boxed = scr_dyn_new_bigint(value);
|
||||
scr_bigint_release(value);
|
||||
return boxed;
|
||||
}
|
||||
|
||||
ScrDyn *scr_stats_dyn(ScrStats *s, bool bigint) {
|
||||
static const char *const names[SCR_STATS_FIELDS] = {
|
||||
"dev", "mode", "nlink", "uid", "gid", "rdev", "blksize", "ino", "size", "blocks",
|
||||
};
|
||||
ScrDyn *object = scr_dyn_new_obj();
|
||||
object->prototype = scr_dyn_retain(scr_stats_prototype(bigint));
|
||||
for (size_t i = 0; i < SCR_STATS_FIELDS; i++) {
|
||||
/* The binding's BigInt64Array holds the integers as signed values. */
|
||||
ScrDyn *value = bigint ? scr_stats_bigint_dyn(scr_bigint_from_i64((int64_t)s->fields[i]))
|
||||
: scr_dyn_new_num((double)s->fields[i]);
|
||||
scr_dyn_obj_set(object, names[i], strlen(names[i]), value);
|
||||
}
|
||||
ScrDyn *ns[4] = { NULL, NULL, NULL, NULL };
|
||||
for (int i = 0; i < 4; i++) {
|
||||
char name[16];
|
||||
int len = snprintf(name, sizeof name, "%sMs", scr_stats_date_names[i]);
|
||||
if (!bigint) {
|
||||
scr_dyn_obj_set(object, name, (size_t)len, scr_dyn_new_num(scr_stats_ms(s, i)));
|
||||
continue;
|
||||
}
|
||||
/* nsFromTimeSpecBigInt, then the millisecond quotient. */
|
||||
ScrBigInt *sec = scr_bigint_from_i64(s->sec[i]);
|
||||
ScrBigInt *billion = scr_bigint_from_i64(1000000000);
|
||||
ScrBigInt *scaled = scr_bigint_mul(sec, billion);
|
||||
ScrBigInt *nsec = scr_bigint_from_i64(s->nsec[i]);
|
||||
ScrBigInt *total = scr_bigint_add(scaled, nsec);
|
||||
ScrBigInt *million = scr_bigint_from_i64(1000000);
|
||||
ScrBigInt *ms = scr_bigint_div(total, million);
|
||||
scr_bigint_release(sec);
|
||||
scr_bigint_release(billion);
|
||||
scr_bigint_release(scaled);
|
||||
scr_bigint_release(nsec);
|
||||
scr_bigint_release(million);
|
||||
scr_dyn_obj_set(object, name, (size_t)len, scr_stats_bigint_dyn(ms));
|
||||
ns[i] = scr_stats_bigint_dyn(total);
|
||||
}
|
||||
for (int i = 0; bigint && i < 4; i++) {
|
||||
char name[16];
|
||||
int len = snprintf(name, sizeof name, "%sNs", scr_stats_date_names[i]);
|
||||
scr_dyn_obj_set(object, name, (size_t)len, ns[i]);
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
/* The names of one scandir listing: byte order on POSIX (libuv sorts),
|
||||
|
||||
@@ -365,6 +365,30 @@ double scr_stats_nlink(ScrStats *s);
|
||||
double scr_stats_atime_ms(ScrStats *s); /* ms with the sub-second fraction */
|
||||
double scr_stats_mtime_ms(ScrStats *s); /* ms with the ns fraction */
|
||||
double scr_stats_ctime_ms(ScrStats *s); /* ms with the ns fraction */
|
||||
double scr_stats_birthtime_ms(ScrStats *s);
|
||||
double scr_stats_mode(ScrStats *s);
|
||||
double scr_stats_uid(ScrStats *s);
|
||||
double scr_stats_gid(ScrStats *s);
|
||||
double scr_stats_rdev(ScrStats *s);
|
||||
double scr_stats_blksize(ScrStats *s);
|
||||
/* The type tests Node derives from mode (false on Windows for FIFOs, block
|
||||
* devices and sockets). */
|
||||
bool scr_stats_is_block_device(ScrStats *s);
|
||||
bool scr_stats_is_character_device(ScrStats *s);
|
||||
bool scr_stats_is_fifo(ScrStats *s);
|
||||
bool scr_stats_is_socket(ScrStats *s);
|
||||
/* The Date fields' time values: the *Ms value rounded, as Node's
|
||||
* dateFromMs does. */
|
||||
double scr_stats_atime(ScrStats *s);
|
||||
double scr_stats_mtime(ScrStats *s);
|
||||
double scr_stats_ctime(ScrStats *s);
|
||||
double scr_stats_birthtime(ScrStats *s);
|
||||
/* fstat without throwing: +1, or NULL with *error set to the errno. */
|
||||
ScrStats *scr_stats_of_fd(int fd, int *error);
|
||||
/* Node's Stats (or BigIntStats) object for the snapshot: own fields in the
|
||||
* binding's order, lazy Date accessors and the type tests on the
|
||||
* prototypes. Borrows s; +1. */
|
||||
ScrDyn *scr_stats_dyn(ScrStats *s, bool bigint);
|
||||
|
||||
/* fs/promises: the SAME sync operations, minting an already-settled
|
||||
* promise — success fulfills, failure REJECTS with the would-be thrown
|
||||
|
||||
@@ -365,6 +365,7 @@ struct ScrNetServer {
|
||||
int fd; /* -1 while not listening */
|
||||
int port;
|
||||
bool listening;
|
||||
bool unrefd; /* server.unref(): does not keep the loop alive */
|
||||
bool closing; /* close() called; 'close' fires when conns drain */
|
||||
bool close_emitted; /* settled: off the registry, listeners dropped */
|
||||
bool emit_listening;
|
||||
@@ -420,6 +421,7 @@ struct ScrNetSocket {
|
||||
int kind; /* FIRST: poller udata routes on it */
|
||||
size_t rc;
|
||||
int fd; /* -1 once closed */
|
||||
bool unrefd; /* socket.unref(): does not keep the loop alive */
|
||||
bool connecting;
|
||||
bool rd_eof; /* peer FIN seen (or read half abandoned) */
|
||||
bool enc_utf8; /* setEncoding('utf8'): 'data' delivers strings */
|
||||
@@ -3039,11 +3041,24 @@ static void scr_net_sweep(void) {
|
||||
|
||||
/* ── the loop hooks (scr_async.c) ────────────────────────────────────── */
|
||||
|
||||
/* Loop liveness: a ref'd server or socket, or pending protocol work. */
|
||||
static bool scr_net_pending(void) {
|
||||
if (scr_net_servers != NULL || scr_net_socks != NULL) return true;
|
||||
for (ScrNetServer *srv = scr_net_servers; srv; srv = srv->next)
|
||||
if (!srv->unrefd) return true;
|
||||
for (ScrNetSocket *sock = scr_net_socks; sock; sock = sock->next)
|
||||
if (!sock->unrefd) return true;
|
||||
return scr_net_proto_pending();
|
||||
}
|
||||
|
||||
/* Any handle, ref'd or not: the loop polls the net fd while it runs. */
|
||||
static bool scr_net_io(void) { return scr_net_servers != NULL || scr_net_socks != NULL; }
|
||||
|
||||
/* socket.ref() / socket.unref() and the server pair: whether the handle
|
||||
* keeps the event loop alive (Node's default is ref'd). */
|
||||
void scr_net_sock_set_ref(ScrNetSocket *s, bool ref) { s->unrefd = !ref; }
|
||||
void scr_net_server_set_ref(ScrNetServer *s, bool ref) { s->unrefd = !ref; }
|
||||
bool scr_net_sock_has_ref(ScrNetSocket *s) { return !s->unrefd; }
|
||||
|
||||
static int scr_net_pollfd(void) {
|
||||
return scr_net_poller != NULL ? scrp_poller_fd(scr_net_poller) : -1;
|
||||
}
|
||||
@@ -3203,6 +3218,7 @@ void scr_net_install(void) {
|
||||
installed = true;
|
||||
scr_atexit(scr_net_cleanup_atexit);
|
||||
scr_loop_set_net(&scr_net_pending, &scr_net_dispatch, &scr_net_pollfd);
|
||||
scr_loop_set_net_io(&scr_net_io);
|
||||
}
|
||||
|
||||
/* ── checked-dynamic handle dispatch (SCR_DYNH_NET_SOCKET ops) ─────────
|
||||
@@ -3385,13 +3401,17 @@ static ScrDyn *scr_net_dynh_sock_invoke(void *h, ScrDyn *self, const char *metho
|
||||
scr_str_release(str);
|
||||
return d;
|
||||
}
|
||||
if (strcmp(method, "ref") == 0 || strcmp(method, "unref") == 0) {
|
||||
scr_net_sock_set_ref(s, method[0] == 'r');
|
||||
return scr_dyn_retain(self);
|
||||
}
|
||||
if (scr_net_dynh_tls != NULL) {
|
||||
ScrDyn *out = NULL;
|
||||
if (scr_net_dynh_tls->invoke(s, method, args, argc, &out)) return out;
|
||||
}
|
||||
{
|
||||
static const char *const known[] = { "connect", "setKeepAlive",
|
||||
"address", "ref", "unref", "cork", "uncork", "read", "unpipe",
|
||||
"address", "cork", "uncork", "read", "unpipe",
|
||||
"off", "removeListener", "removeAllListeners", "emit", "prependListener",
|
||||
"prependOnceListener", "listenerCount", "listeners", "resetAndDestroy", NULL };
|
||||
for (size_t i = 0; known[i]; i++) {
|
||||
@@ -3619,6 +3639,10 @@ static ScrDyn *scr_net_dynh_srv_invoke(void *h, ScrDyn *self, const char *method
|
||||
scr_dyn_arg_type_fail("socket", "an instance of Socket", sockd);
|
||||
return NULL;
|
||||
}
|
||||
if (strcmp(method, "ref") == 0 || strcmp(method, "unref") == 0) {
|
||||
scr_net_server_set_ref((ScrNetServer *)h, method[0] == 'r');
|
||||
return scr_dyn_retain(self);
|
||||
}
|
||||
if (strcmp(method, "toString") == 0) {
|
||||
ScrStr *str = scr_str_new("[object Object]", 15);
|
||||
ScrDyn *d = scr_dyn_new_str(str);
|
||||
@@ -3626,7 +3650,7 @@ static ScrDyn *scr_net_dynh_srv_invoke(void *h, ScrDyn *self, const char *method
|
||||
return d;
|
||||
}
|
||||
{
|
||||
static const char *const known[] = { "ref", "unref", "setTimeout", "getConnections",
|
||||
static const char *const known[] = { "setTimeout", "getConnections",
|
||||
"off", "removeListener", "removeAllListeners", "emit", "prependListener",
|
||||
"prependOnceListener", "listenerCount", "listeners", "closeAllConnections",
|
||||
"closeIdleConnections", NULL };
|
||||
|
||||
@@ -137,6 +137,9 @@ void scr_net_listen_opts_reuse_port(ScrNetServer *s, double port, ScrStr *host /
|
||||
ScrClosure *cb /*moves, nullable*/);
|
||||
double scr_net_server_port(ScrNetServer *s); /* address().port */
|
||||
bool scr_net_server_listening(ScrNetServer *s);
|
||||
void scr_net_sock_set_ref(ScrNetSocket *s, bool ref);
|
||||
void scr_net_server_set_ref(ScrNetServer *s, bool ref);
|
||||
bool scr_net_sock_has_ref(ScrNetSocket *s);
|
||||
/* Writable http.Server timeout property storage. `field` is the compiler
|
||||
* ABI selector: timeout, keepAliveTimeout, headersTimeout, requestTimeout,
|
||||
* keepAliveTimeoutBuffer. Typed reads validate that no dynamic write left
|
||||
|
||||
@@ -61,5 +61,32 @@ ScrStr *scr_path_win32_format(ScrStr *root, ScrStr *dir, ScrStr *base, ScrStr *n
|
||||
bool scr_path_matches_glob(ScrStr *path, ScrStr *pattern);
|
||||
bool scr_path_win32_matches_glob(ScrStr *path, ScrStr *pattern);
|
||||
bool scr_glob_match(ScrStr *path, ScrStr *pattern, bool windows, bool nocase);
|
||||
/* fs.globSync / fs.glob / fs/promises glob (scr_glob.c): Node 24's
|
||||
* lib/internal/fs/glob.js walk. The pattern (a string or string array) and
|
||||
* options (exclude, cwd, followSymlinks, withFileTypes) arrive as dyn
|
||||
* values and are validated in Node's order with Node's errors (borrowed).
|
||||
* scr_fs_glob answers the result snapshot (+1, or NULL with the exception
|
||||
* pending); mode bit 0 asks for Dirent rows (name, parentPath, UV_DIRENT
|
||||
* kind) and must agree with withFileTypes, bit 1 is the fs/promises form,
|
||||
* whose Dirents carry resolved parent paths. strings is the string[] form.
|
||||
* callback takes fs.glob's argument vector, throws validation errors
|
||||
* synchronously and delivers the result or the walk's error through the
|
||||
* filesystem callback queue. */
|
||||
typedef struct ScrDyn ScrDyn; /* full definition in scr_dyn.h (C11 repeat) */
|
||||
typedef struct ScrFsGlob ScrFsGlob;
|
||||
ScrFsGlob *scr_fs_glob(const ScrDyn *pattern, const ScrDyn *options, double mode);
|
||||
size_t scr_fs_glob_count(const ScrFsGlob *g); /* NULL -> 0 */
|
||||
ScrStr *scr_fs_glob_path(const ScrFsGlob *g, size_t i); /* +1 */
|
||||
ScrStr *scr_fs_glob_name(const ScrFsGlob *g, size_t i); /* +1 */
|
||||
ScrStr *scr_fs_glob_parent(const ScrFsGlob *g, size_t i); /* +1 */
|
||||
double scr_fs_glob_type(const ScrFsGlob *g, size_t i);
|
||||
void scr_fs_glob_free(ScrFsGlob *g); /* NULL-tolerant */
|
||||
ScrArr *scr_fs_glob_strings(const ScrDyn *pattern, const ScrDyn *options);
|
||||
void scr_fs_glob_callback(ScrDyn *args);
|
||||
/* The fs/promises form: a walk error does not throw; the matches found
|
||||
* before it are returned and scr_fsp_glob_error answers the error (+1 dyn,
|
||||
* undefined when there is none) once, for the loop to throw after them. */
|
||||
ScrArr *scr_fsp_glob_strings(const ScrDyn *pattern, const ScrDyn *options);
|
||||
ScrDyn *scr_fsp_glob_error(void);
|
||||
|
||||
#endif /* SCR_PATH_H */
|
||||
|
||||
@@ -700,6 +700,28 @@ ScrStr *scr_str_concat_move(ScrStr *a, ScrStr *b) {
|
||||
return r;
|
||||
}
|
||||
|
||||
ScrStr *scr_str_concat_move_char_code(ScrStr *a, double code) {
|
||||
uint32_t cp = scr_to_uint32(code) & 0xFFFFu;
|
||||
if (cp < 0x80 && a->rc == 1 && a->cap > a->len) {
|
||||
/* scr_str_concat's in-place append of one ASCII byte: the string stays
|
||||
* ASCII if it was, and a non-ASCII string's index facts below the old
|
||||
* end stay valid (scr_sidx_concat_append). */
|
||||
size_t oldlen = a->len;
|
||||
scr_short_forget(a);
|
||||
scr_str_hash_forget(a);
|
||||
a->data[oldlen] = (char)cp;
|
||||
a->len = oldlen + 1;
|
||||
a->data[oldlen + 1] = '\0';
|
||||
if (!a->ascii) scr_sidx_concat_append(a, oldlen);
|
||||
return a;
|
||||
}
|
||||
ScrStr *c = scr_str_from_char_code_one(code);
|
||||
ScrStr *r = scr_str_concat(a, c);
|
||||
scr_str_release(a);
|
||||
scr_str_release(c);
|
||||
return r;
|
||||
}
|
||||
|
||||
ScrStr *scr_str_concat_mixed_move(ScrStr *head, ScrStr *const *parts, const double *nums,
|
||||
size_t count) {
|
||||
/* A head selects the general body directly (see scr_str_concat_mixed). */
|
||||
|
||||
@@ -242,6 +242,10 @@ ScrStr *scr_str_concat_mixed(ScrStr *head, ScrStr *const *parts, const double *n
|
||||
* else can observe `s`): one call instead of a snapshot retain, the
|
||||
* binding's release and the snapshot's release around a borrowing concat. */
|
||||
ScrStr *scr_str_concat_move(ScrStr *a, ScrStr *b);
|
||||
/* scr_str_concat_move(a, String.fromCharCode(code)) without building the
|
||||
* one-character string: an ASCII character appends to a uniquely owned a
|
||||
* in place. */
|
||||
ScrStr *scr_str_concat_move_char_code(ScrStr *a, double code);
|
||||
ScrStr *scr_str_concat_mixed_move(ScrStr *head, ScrStr *const *parts, const double *nums,
|
||||
size_t count);
|
||||
/* a + String(x): scr_str_concat's contract with a number right operand. */
|
||||
|
||||
@@ -15,6 +15,15 @@
|
||||
|
||||
long scr_str_live_count(void); /* provided by scr_string.c */
|
||||
|
||||
/* fs.glob's callback form (scr_glob.c) schedules through the filesystem
|
||||
* callback queue in scr_bytes_io.c, which this binary does not link; the
|
||||
* oracle never calls it. */
|
||||
void scr_fs_cb_schedule(const ScrDyn *callback, ScrDyn *result, const ScrDyn *buffer) {
|
||||
(void)callback;
|
||||
(void)buffer;
|
||||
scr_dyn_release(result);
|
||||
}
|
||||
|
||||
static int hex_val(char c) {
|
||||
if (c >= '0' && c <= '9') return c - '0';
|
||||
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// A record parameter widened from a literal argument also receives a loop
|
||||
// element whose storage admits undefined. The element converts to the
|
||||
// widened layout after the usual checked extraction of its record arm.
|
||||
interface Item {
|
||||
name: string;
|
||||
kind: number;
|
||||
}
|
||||
class Bag {
|
||||
readonly names = new Map<string, Item>();
|
||||
add(entry: Item): void {
|
||||
const existing = this.names.get(entry.name);
|
||||
if (existing === undefined || existing.kind < entry.kind) this.names.set(entry.name, entry);
|
||||
}
|
||||
}
|
||||
const seen = new Set<string>();
|
||||
seen.add("x");
|
||||
const parts: string[] = ["a", "d"];
|
||||
const bag = new Bag();
|
||||
const at = Number(process.argv.length > 99);
|
||||
bag.add({ name: parts[at], kind: 1 });
|
||||
function grow<T>(xs: T[], index: number, value: T): T[] {
|
||||
xs[index] = value;
|
||||
return xs;
|
||||
}
|
||||
const sparse = grow<number>([], 4, 1);
|
||||
const groups = [
|
||||
{ results: [{ name: "b", kind: 2 }, { name: "c", kind: 3 }] },
|
||||
{ results: [{ name: "b", kind: 5 }] },
|
||||
];
|
||||
for (const g of groups) for (const res of g.results) bag.add(res);
|
||||
const order: string[] = [];
|
||||
for (const [name, entry] of bag.names) order.push(name + ":" + entry.kind);
|
||||
console.log(order.join(","), sparse.length, seen.size);
|
||||
@@ -0,0 +1,82 @@
|
||||
// fs.globSync over a fixed tree: globstars, braces, extglobs, classes, dot
|
||||
// files, "." and ".." segments, trailing slashes, literal paths, missing
|
||||
// directories, absolute patterns and pattern arrays. Results come out in
|
||||
// Node's walk order over sorted directory listings.
|
||||
import { globSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
const root = mkdtempSync(join(tmpdir(), "scriptc-glob-"));
|
||||
for (const dir of ["a/b/c", "src/lib", ".hidden", "node_modules/x", "docs"]) {
|
||||
mkdirSync(join(root, dir), { recursive: true });
|
||||
}
|
||||
const files = [
|
||||
"a/one.ts",
|
||||
"a/b/two.ts",
|
||||
"a/b/c/three.js",
|
||||
"src/index.ts",
|
||||
"src/lib/util.ts",
|
||||
"src/lib/util.test.ts",
|
||||
"README.md",
|
||||
".hidden/secret.ts",
|
||||
".dot.ts",
|
||||
"node_modules/x/index.ts",
|
||||
"Upper.TS",
|
||||
"docs/guide.md",
|
||||
"docs/[draft].md",
|
||||
];
|
||||
for (const file of files) writeFileSync(join(root, file), "");
|
||||
|
||||
const show = (pattern: string | string[]): void => {
|
||||
console.log(JSON.stringify(pattern), globSync(pattern, { cwd: root }).join(" "));
|
||||
};
|
||||
|
||||
for (const pattern of [
|
||||
"*.ts",
|
||||
"*",
|
||||
"**",
|
||||
"**/*.ts",
|
||||
"a/**",
|
||||
"a/**/*.js",
|
||||
"**/b",
|
||||
"**/b/**",
|
||||
"src/*",
|
||||
"src/**/*.test.ts",
|
||||
"{a,src}/*.ts",
|
||||
"{a,src}/**/*.{ts,js}",
|
||||
"a/{one,missing}.ts",
|
||||
"?.ts",
|
||||
"a/???.ts",
|
||||
"[ab]/*",
|
||||
"[!a]*",
|
||||
"!(src|node_modules)/*.ts",
|
||||
"@(a|src)/*.ts",
|
||||
"+(a|b)/**",
|
||||
"*.md",
|
||||
"docs/\\[draft\\].md",
|
||||
".*",
|
||||
"**/.*",
|
||||
".hidden/*",
|
||||
"a/b/../one.ts",
|
||||
"./a/*.ts",
|
||||
"a/./b/*.ts",
|
||||
"a/",
|
||||
"a/b/",
|
||||
"**/",
|
||||
"a/b/c/three.js",
|
||||
"missing/**",
|
||||
"missing",
|
||||
"**/node_modules/**",
|
||||
"**/*.TS",
|
||||
"",
|
||||
]) {
|
||||
show(pattern);
|
||||
}
|
||||
show(["*.md", "a/*.ts", "*.md"]);
|
||||
show(["src/*", "src/**"]);
|
||||
show([]);
|
||||
|
||||
// Absolute patterns answer absolute paths; print them relative to the root.
|
||||
const absolute = globSync(join(root, "a", "**", "*.ts")).map((p) => p.slice(root.length));
|
||||
console.log("absolute", absolute.join(" "));
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
@@ -0,0 +1,84 @@
|
||||
// fs.globSync options: cwd as an absolute path, a relative path and a file:
|
||||
// URL; exclude as glob patterns (resolved against cwd, matched on whole
|
||||
// paths and on directories with a trailing slash) or a function of the
|
||||
// path; withFileTypes Dirents with name, parentPath (cwd-relative when cwd
|
||||
// is relative) and the type probes; and followSymlinks.
|
||||
import { type Dirent, globSync, mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join, relative } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
const root = mkdtempSync(join(tmpdir(), "scriptc-glob-options-"));
|
||||
for (const dir of ["pkg/src/lib", "pkg/test", "pkg/node_modules/dep", "pkg/dist"]) {
|
||||
mkdirSync(join(root, dir), { recursive: true });
|
||||
}
|
||||
for (const file of [
|
||||
"pkg/package.json",
|
||||
"pkg/src/index.ts",
|
||||
"pkg/src/lib/a.ts",
|
||||
"pkg/src/lib/a.test.ts",
|
||||
"pkg/test/main.test.ts",
|
||||
"pkg/node_modules/dep/index.ts",
|
||||
"pkg/dist/index.js",
|
||||
]) {
|
||||
writeFileSync(join(root, file), "{}");
|
||||
}
|
||||
let links = true;
|
||||
try {
|
||||
symlinkSync("src", join(root, "pkg", "linked"), "dir");
|
||||
} catch {
|
||||
links = false;
|
||||
}
|
||||
|
||||
const pkg = join(root, "pkg");
|
||||
console.log("absolute cwd", globSync("**/*.ts", { cwd: pkg }).join(" "));
|
||||
process.chdir(root);
|
||||
console.log("relative cwd", globSync("src/*.ts", { cwd: "pkg" }).join(" "));
|
||||
console.log("url cwd", globSync("*.json", { cwd: pathToFileURL(pkg) }).join(" "));
|
||||
console.log("default cwd", globSync("pkg/*.json").join(" "));
|
||||
|
||||
console.log(
|
||||
"exclude patterns",
|
||||
globSync("**/*.ts", { cwd: pkg, exclude: ["node_modules/**", "**/*.test.ts"] }).join(" "),
|
||||
);
|
||||
console.log("exclude directory", globSync("**", { cwd: pkg, exclude: ["dist/"] }).filter((p) => !p.includes("linked")).join(" "));
|
||||
console.log(
|
||||
"exclude function",
|
||||
globSync("**/*.ts", { cwd: pkg, exclude: (path) => path.startsWith("node_modules") || path.endsWith(".test.ts") }).join(
|
||||
" ",
|
||||
),
|
||||
);
|
||||
const visited: string[] = [];
|
||||
globSync("src/**", {
|
||||
cwd: pkg,
|
||||
exclude: (path) => {
|
||||
visited.push(path);
|
||||
return false;
|
||||
},
|
||||
});
|
||||
console.log("exclude sees", visited.join(" "));
|
||||
|
||||
const describe = (list: Dirent[]): string =>
|
||||
list
|
||||
.map((d) => {
|
||||
const kind = d.isSymbolicLink() ? "link" : d.isDirectory() ? "dir" : d.isFile() ? "file" : "other";
|
||||
const parent = d.parentPath.startsWith(root) ? `<root>${d.parentPath.slice(root.length)}` : d.parentPath;
|
||||
return `${d.name}:${parent}:${kind}`;
|
||||
})
|
||||
.join(" ");
|
||||
console.log("dirents absolute", describe(globSync("src/**", { cwd: pkg, withFileTypes: true })));
|
||||
console.log("dirents relative", describe(globSync("pkg/src/*", { withFileTypes: true })));
|
||||
console.log("dirents exclude", describe(globSync("**/*.ts", { cwd: pkg, withFileTypes: true, exclude: ["node_modules/**"] })));
|
||||
console.log("withFileTypes false", globSync("*", { cwd: pkg, withFileTypes: false }).join(" "));
|
||||
|
||||
if (links) {
|
||||
console.log("links", globSync("**/*.ts", { cwd: pkg, exclude: ["node_modules/**"] }).join(" "));
|
||||
console.log("links followed", globSync("**/*.ts", { cwd: pkg, followSymlinks: true, exclude: ["node_modules/**"] }).join(" "));
|
||||
console.log("link entry", describe(globSync("linked", { cwd: pkg, withFileTypes: true })));
|
||||
console.log("through link", globSync("linked/lib/*", { cwd: pkg }).join(" "));
|
||||
} else {
|
||||
console.log("links unavailable");
|
||||
}
|
||||
console.log("relative check", relative(root, pkg));
|
||||
process.chdir(tmpdir());
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
@@ -0,0 +1,80 @@
|
||||
// fs.glob argument handling from untyped code: Node's ERR_INVALID_ARG_TYPE
|
||||
// errors for patterns, options, exclude, followSymlinks, cwd and the
|
||||
// callback, the ERR_INTERNAL_ASSERTION a non-string cwd with exclude
|
||||
// patterns hits in Node, empty patterns, missing directories, exclude
|
||||
// callbacks that throw or return non-booleans, and the errors fs.glob's
|
||||
// callback and fs/promises glob deliver.
|
||||
import { globSync, glob as globCallback, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { glob } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
const any = (value) => /** @type {any} */ (value);
|
||||
const attempt = (label, run) => {
|
||||
try {
|
||||
console.log(label, run());
|
||||
} catch (error) {
|
||||
const e = /** @type {any} */ (error);
|
||||
console.log(label, "threw", e.name, e.code, JSON.stringify(e.message));
|
||||
}
|
||||
};
|
||||
|
||||
const root = mkdtempSync(join(tmpdir(), "scriptc-glob-validation-"));
|
||||
mkdirSync(join(root, "a"));
|
||||
for (const file of ["a/one.ts", "a/two.ts", "top.md"]) writeFileSync(join(root, file), "");
|
||||
process.chdir(root);
|
||||
|
||||
attempt("number pattern", () => globSync(any(5)));
|
||||
attempt("null pattern", () => globSync(any(null)));
|
||||
attempt("object pattern", () => globSync(any({})));
|
||||
attempt("function pattern", () => globSync(any(() => "*")));
|
||||
attempt("array with a number", () => globSync(any(["*", 5])));
|
||||
attempt("string options", () => globSync("*", any("x")));
|
||||
attempt("null options", () => globSync("*", any(null)));
|
||||
attempt("array options", () => globSync("*", any([])));
|
||||
attempt("number exclude", () => globSync("*", any({ exclude: 5 })));
|
||||
attempt("exclude with a number", () => globSync("*", any({ exclude: ["a", 1] })));
|
||||
attempt("number followSymlinks", () => globSync("*", any({ followSymlinks: 1 })));
|
||||
attempt("null followSymlinks", () => globSync("*", any({ followSymlinks: null })).join(" "));
|
||||
attempt("number cwd", () => globSync("*", any({ cwd: 5 })));
|
||||
attempt("number cwd with exclude", () => globSync("*", any({ cwd: 5, exclude: ["x"] })));
|
||||
attempt("Buffer cwd", () => globSync("*", any({ cwd: Buffer.from("a") })));
|
||||
attempt("http URL cwd", () => globSync("*", any({ cwd: new URL("http://example.com/") })));
|
||||
attempt("missing cwd", () => globSync("*", { cwd: join(root, "missing") }).length);
|
||||
attempt("empty pattern", () => globSync("").length);
|
||||
attempt("no patterns", () => globSync([]).length);
|
||||
attempt("exclude throws", () =>
|
||||
globSync("*", {
|
||||
exclude: () => {
|
||||
throw new RangeError("stop");
|
||||
},
|
||||
}),
|
||||
);
|
||||
attempt("exclude truthy values", () => globSync("**", { exclude: any((p) => (p.endsWith(".ts") ? 1 : "")) }).join(" "));
|
||||
attempt("missing callback", () => globCallback("*", any(undefined)));
|
||||
attempt("bad pattern with callback", () => globCallback(any(5), () => console.log("not reached")));
|
||||
|
||||
globCallback("a/*.ts", (err, files) => {
|
||||
console.log("callback", err, files.join(" "));
|
||||
globCallback("*", { cwd: "a" }, (err2, files2) => {
|
||||
console.log("callback with options", err2, files2.join(" "));
|
||||
globCallback("*", any({ cwd: 5 }), (error) => {
|
||||
console.log("callback cwd error", error.name, error.code, error.message);
|
||||
void loop();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function loop() {
|
||||
const seen = [];
|
||||
for await (const path of glob("**/*.ts")) seen.push(path);
|
||||
console.log("for await", seen.join(" "));
|
||||
try {
|
||||
for await (const path of glob(any(5))) console.log("not reached", path);
|
||||
} catch (error) {
|
||||
const e = /** @type {any} */ (error);
|
||||
console.log("for await bad pattern", e.name, e.code);
|
||||
}
|
||||
process.chdir(tmpdir());
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// fs/promises glob as a for await source: string matches, Dirent matches
|
||||
// (fs/promises Dirents carry the resolved parent directory), the named,
|
||||
// namespace and fs.promises spellings, early exits with break and continue,
|
||||
// nested loops, and an error from an exclude callback surfacing at the
|
||||
// loop.
|
||||
import * as fs from "node:fs";
|
||||
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import * as fsp from "node:fs/promises";
|
||||
import { glob } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
const root = mkdtempSync(join(tmpdir(), "scriptc-fsp-glob-"));
|
||||
for (const dir of ["one/deep", "two"]) mkdirSync(join(root, dir), { recursive: true });
|
||||
for (const file of ["one/a.txt", "one/deep/b.txt", "two/c.txt", "two/d.md", "e.txt"]) {
|
||||
writeFileSync(join(root, file), "");
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const all: string[] = [];
|
||||
for await (const path of glob("**/*.txt", { cwd: root })) all.push(path);
|
||||
console.log("named", all.join(" "));
|
||||
|
||||
const viaNamespace: string[] = [];
|
||||
for await (const path of fsp.glob("*/*", { cwd: root, exclude: ["**/*.md"] })) viaNamespace.push(path);
|
||||
console.log("namespace", viaNamespace.join(" "));
|
||||
|
||||
const viaFs: string[] = [];
|
||||
for await (const path of fs.promises.glob("two/*", { cwd: root })) viaFs.push(path);
|
||||
console.log("fs.promises", viaFs.join(" "));
|
||||
|
||||
const rows: string[] = [];
|
||||
for await (const entry of glob("one/**", { cwd: root, withFileTypes: true })) {
|
||||
rows.push(`${entry.name}|${entry.parentPath.slice(root.length)}|${entry.isDirectory()}`);
|
||||
}
|
||||
console.log("dirents", rows.join(" "));
|
||||
|
||||
let first = "";
|
||||
for await (const path of glob("**/*.txt", { cwd: root })) {
|
||||
first = path;
|
||||
break;
|
||||
}
|
||||
console.log("break", first);
|
||||
|
||||
const skipped: string[] = [];
|
||||
for await (const path of glob("**/*.txt", { cwd: root })) {
|
||||
if (path.startsWith("one")) continue;
|
||||
skipped.push(path);
|
||||
}
|
||||
console.log("continue", skipped.join(" "));
|
||||
|
||||
const pairs: string[] = [];
|
||||
for await (const dir of glob("*/", { cwd: root })) {
|
||||
for await (const file of glob(`${dir}/*.txt`, { cwd: root })) pairs.push(`${dir}>${file}`);
|
||||
}
|
||||
console.log("nested", pairs.join(" "));
|
||||
|
||||
try {
|
||||
for await (const path of glob("**", {
|
||||
cwd: root,
|
||||
exclude: (p) => {
|
||||
if (p === "two") throw new Error(`refused ${p}`);
|
||||
return false;
|
||||
},
|
||||
})) {
|
||||
console.log("visited", path);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("caught", (error as Error).message);
|
||||
}
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
void main();
|
||||
@@ -0,0 +1,44 @@
|
||||
// fs.glob's error-first callback form: with and without options, pattern
|
||||
// arrays, exclude patterns and functions, the error an exclude callback
|
||||
// throws arriving at the callback. Each call starts after the previous
|
||||
// callback ran, so the output order does not depend on I/O timing.
|
||||
import { glob, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
const root = mkdtempSync(join(tmpdir(), "scriptc-glob-callback-"));
|
||||
mkdirSync(join(root, "lib/inner"), { recursive: true });
|
||||
for (const file of ["lib/a.js", "lib/b.ts", "lib/inner/c.ts", "top.json"]) writeFileSync(join(root, file), "");
|
||||
process.chdir(root);
|
||||
|
||||
const steps: (() => void)[] = [
|
||||
() => glob("lib/**/*.ts", (err, files) => next("no options", err, files)),
|
||||
() => glob("**/*.ts", { cwd: "lib" }, (err, files) => next("cwd", err, files)),
|
||||
() => glob(["*.json", "lib/*.js"], {}, (err, files) => next("array", err, files)),
|
||||
() => glob("**/*", { exclude: ["lib/inner/**"] }, (err, files) => next("exclude patterns", err, files)),
|
||||
() => glob("**/*", { exclude: (path) => path.endsWith(".js") }, (err, files) => next("exclude function", err, files)),
|
||||
() =>
|
||||
glob(
|
||||
"**",
|
||||
{
|
||||
exclude: () => {
|
||||
throw new Error("exclude failed");
|
||||
},
|
||||
},
|
||||
(err, files) => next("exclude throws", err, files),
|
||||
),
|
||||
() => finish(),
|
||||
];
|
||||
|
||||
function next(label: string, err: NodeJS.ErrnoException | null, files: string[] | undefined): void {
|
||||
console.log(label, err ? `error ${err.message}` : null, files === undefined ? "no files" : files.join(" "));
|
||||
steps.shift()?.();
|
||||
}
|
||||
|
||||
function finish(): void {
|
||||
process.chdir(tmpdir());
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
console.log("done");
|
||||
}
|
||||
|
||||
steps.shift()?.();
|
||||
@@ -0,0 +1,66 @@
|
||||
// `%` by a constant power of two of at least 1 matches Node for every kind
|
||||
// of dividend: integers, fractions, subnormals, values beyond 2^53, -0,
|
||||
// NaN and the infinities. The divisor's sign does not change the result,
|
||||
// and a zero result takes the dividend's sign.
|
||||
|
||||
function show(label: string, v: number): void {
|
||||
console.log(label, Object.is(v, -0) ? "-0" : String(v));
|
||||
}
|
||||
|
||||
const dividends: number[] = [
|
||||
0, -0, 1, -1, 3, -4, 7.25, -7.25, 0.75, -0.75, 1.5, -1.5, 5e-324, -5e-324,
|
||||
2.2250738585072014e-308, -2.2250738585072014e-308, 1e-300, 2 ** 31 - 0.5, -(2 ** 31) + 0.5,
|
||||
2 ** 32 + 0.25, 2 ** 52 + 1, -(2 ** 52 + 3), 2 ** 52 + 0.5, 2 ** 53, -(2 ** 53), 2 ** 53 + 2,
|
||||
2 ** 60 + 1024, -(2 ** 60), 2 ** 63, 2 ** 64 + 4096, 1e21, -1e21, 1e300, -1e300,
|
||||
Number.MAX_VALUE, -Number.MAX_VALUE, 123456789.123, -98765.4321, NaN, Infinity, -Infinity,
|
||||
];
|
||||
|
||||
for (const x of dividends) {
|
||||
show(`${x} % 1`, x % 1);
|
||||
show(`${x} % -1`, x % -1);
|
||||
show(`${x} % 2`, x % 2);
|
||||
show(`${x} % 8`, x % 8);
|
||||
show(`${x} % -16`, x % -16);
|
||||
show(`${x} % 1024`, x % 1024);
|
||||
show(`${x} % 2147483648`, x % 2147483648);
|
||||
show(`${x} % -2147483648`, x % -2147483648);
|
||||
show(`${x} % 4294967296`, x % 4294967296);
|
||||
show(`${x} % 2 ** 32`, x % 2 ** 32);
|
||||
show(`${x} % -(2 ** 40)`, x % -(2 ** 40));
|
||||
show(`${x} % 2 ** 52`, x % 2 ** 52);
|
||||
show(`${x} % 2 ** 53`, x % 2 ** 53);
|
||||
show(`${x} % 2 ** 64`, x % 2 ** 64);
|
||||
show(`${x} % 2 ** 1023`, x % 2 ** 1023);
|
||||
show(`${x} % -(2 ** 1023)`, x % -(2 ** 1023));
|
||||
}
|
||||
|
||||
// Divisors that are not powers of two of at least 1 keep the general path.
|
||||
for (const x of dividends) {
|
||||
show(`${x} % 0.5`, x % 0.5);
|
||||
show(`${x} % 0.125`, x % 0.125);
|
||||
show(`${x} % 3`, x % 3);
|
||||
show(`${x} % 6`, x % 6);
|
||||
show(`${x} % 2 ** 0.5`, x % 2 ** 0.5);
|
||||
show(`${x} % 2 ** -1`, x % 2 ** -1);
|
||||
}
|
||||
|
||||
// Module constants, compound assignment, record fields and closures.
|
||||
const WORD = 4294967296;
|
||||
const HALF_WORD = 65536;
|
||||
interface Cell {
|
||||
value: number;
|
||||
}
|
||||
const cell: Cell = { value: -12345.678 };
|
||||
let total = 0;
|
||||
for (let i = 0; i < 64; i++) {
|
||||
cell.value = cell.value * -3.7 + i;
|
||||
let v = cell.value;
|
||||
v %= HALF_WORD;
|
||||
total += (v % WORD) + (cell.value % 256);
|
||||
}
|
||||
show("total", total);
|
||||
const wrap = (x: number): number => x % 2 ** 16;
|
||||
show("wrap", wrap(-1e15 - 0.5) + wrap(2 ** 70) + wrap(65536.5));
|
||||
let sum = 0;
|
||||
for (let i = -20; i <= 20; i++) sum += (i * 0.37) % 1;
|
||||
show("sum", sum);
|
||||
@@ -0,0 +1,50 @@
|
||||
// `%` with integer dividends beyond 2^53 (every double there is an integer)
|
||||
// and integer divisors: constant, int32 and unproven. Results match Node,
|
||||
// including the sign of zero and the bounds of the inline integer path at
|
||||
// 2^62 and 2^63.
|
||||
|
||||
function show(label: string, v: number): void {
|
||||
console.log(label, Object.is(v, -0) ? "-0" : String(v));
|
||||
}
|
||||
|
||||
const dividends: number[] = [
|
||||
2 ** 53, -(2 ** 53), 2 ** 53 + 2, -(2 ** 53 + 2), 2 ** 54 + 4, 2 ** 60 + 1024, -(2 ** 60 + 2048),
|
||||
2 ** 61 + 3 * 2 ** 9, 2 ** 62 - 1024, -(2 ** 62 - 1024), 2 ** 62, -(2 ** 62), 2 ** 62 + 1024,
|
||||
-(2 ** 62 + 1024), 2 ** 63 - 1024, -(2 ** 63 - 1024), 2 ** 63, -(2 ** 63), 2 ** 64, -(2 ** 64),
|
||||
1e19, -1e19, 1e21, 9.999999999999999e22,
|
||||
];
|
||||
const divisors: number[] = [
|
||||
1, -1, 2, 3, -3, 7, 10, 1000, 65535, 1000000007, 2147483647, -2147483647, 2 ** 31, 2 ** 32 - 1,
|
||||
2 ** 53 - 1, 2 ** 53 + 2, 2 ** 62, -(2 ** 62), 2 ** 62 + 1024, 2 ** 63, -(2 ** 63), 2 ** 64, 0,
|
||||
-0,
|
||||
];
|
||||
|
||||
// Unproven dividends and divisors read from arrays.
|
||||
let checksum = 0;
|
||||
for (let i = 0; i < dividends.length; i++) {
|
||||
for (let j = 0; j < divisors.length; j++) {
|
||||
const r = dividends[i]! % divisors[j]!;
|
||||
show(`${dividends[i]} % ${divisors[j]}`, r);
|
||||
if (r === r) checksum = (checksum * 31 + (r % 1000003)) % 1000000007;
|
||||
}
|
||||
}
|
||||
show("checksum", checksum);
|
||||
|
||||
// Constant divisors.
|
||||
for (const x of dividends) {
|
||||
show(`${x} % 3`, x % 3);
|
||||
show(`${x} % -10`, x % -10);
|
||||
show(`${x} % 1000000007`, x % 1000000007);
|
||||
show(`${x} % 2147483647`, x % 2147483647);
|
||||
show(`${x} % -1`, x % -1);
|
||||
}
|
||||
|
||||
// Int32 divisors: loop counters and `| 0` values.
|
||||
for (let k = 1; k <= 40; k += 13) {
|
||||
for (const x of dividends) {
|
||||
show(`${x} % ${k}`, x % k);
|
||||
const m = (k * 7919) | 0;
|
||||
show(`${x} % ${m}`, x % m);
|
||||
show(`${x} % ${-m}`, x % -m);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Linear congruential generators whose products exceed 2^53, reduced with
|
||||
// `%` by powers of two and by other constants. The products round, so every
|
||||
// step must reproduce Node's double arithmetic exactly.
|
||||
|
||||
let seed = 42;
|
||||
function lcg31(): number {
|
||||
seed = (seed * 1103515245 + 12345) % 2147483648;
|
||||
return seed;
|
||||
}
|
||||
let prime = 42;
|
||||
function parkMillerWide(): number {
|
||||
prime = (prime * 1103515245 + 12345) % 2147483647;
|
||||
return prime;
|
||||
}
|
||||
let word = 7;
|
||||
function lcg32(): number {
|
||||
word = (word * 6364136223846793 + 1442695040888963) % 2 ** 32;
|
||||
return word;
|
||||
}
|
||||
let negative = -99;
|
||||
function signedStep(): number {
|
||||
negative = (negative * -1103515245 - 12345) % 2147483648;
|
||||
return negative;
|
||||
}
|
||||
|
||||
const seen = new Map<number, number>();
|
||||
let a = 0;
|
||||
let b = 0;
|
||||
let c = 0;
|
||||
let d = 0;
|
||||
for (let i = 0; i < 20000; i++) {
|
||||
const x = lcg31();
|
||||
a = (a + x) % 1000000007;
|
||||
b = (b * 31 + parkMillerWide()) % 1000000007;
|
||||
c = (c + lcg32()) % 2 ** 40;
|
||||
d = (d + signedStep()) % 1000000007;
|
||||
if (i < 8) console.log(i, x, prime, word, negative);
|
||||
seen.set(x, (seen.get(x) ?? 0) + 1);
|
||||
}
|
||||
console.log("sums", a, b, c, d, Object.is(d, -0));
|
||||
console.log("distinct", seen.size);
|
||||
|
||||
// Bucket a stream into a small table with the low and high bits.
|
||||
const buckets = new Int32Array(16);
|
||||
for (let i = 0; i < 5000; i++) {
|
||||
const x = lcg31();
|
||||
buckets[x % 16]++;
|
||||
buckets[Math.floor(x / 134217728) % 16]++;
|
||||
}
|
||||
console.log(Array.from(buckets).join(","));
|
||||
@@ -0,0 +1,34 @@
|
||||
// Fractional dividends with `%`: Weyl sequences and hashes reduced `% 1`,
|
||||
// phases reduced by powers of two and by other divisors, and negative values
|
||||
// whose remainders keep the dividend's sign.
|
||||
|
||||
function show(label: string, v: number): void {
|
||||
console.log(label, Object.is(v, -0) ? "-0" : String(v));
|
||||
}
|
||||
|
||||
let weyl = 0.5;
|
||||
let hashSum = 0;
|
||||
for (let i = 0; i < 2000; i++) {
|
||||
weyl = (weyl + 0.6180339887498949) % 1;
|
||||
const h = (weyl * 2654435761.5) % 1;
|
||||
hashSum += h;
|
||||
if (i < 6) show(`weyl ${i}`, weyl);
|
||||
if (i < 6) show(`hash ${i}`, h);
|
||||
}
|
||||
show("hashSum", hashSum);
|
||||
|
||||
let phase = -3.75;
|
||||
for (let i = 0; i < 12; i++) {
|
||||
phase = phase * -2.5 + 0.125;
|
||||
show(`phase ${i} % 4`, phase % 4);
|
||||
show(`phase ${i} % 0.25`, phase % 0.25);
|
||||
show(`phase ${i} % 360`, phase % 360);
|
||||
show(`phase ${i} % (2 * Math.PI)`, phase % (2 * Math.PI));
|
||||
}
|
||||
|
||||
// Fractional parts of negative values and of values that are integers.
|
||||
for (const x of [-2.5, -2, -0.5, -0, 0, 0.5, 2, 2.5, -1e-310, 1e-310, 4503599627370495.5]) {
|
||||
show(`${x} % 1`, x % 1);
|
||||
show(`${x} % -1`, x % -1);
|
||||
show(`${x} % 2`, x % 2);
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// fs.Stats fields: statSync, lstatSync and fstatSync expose mode, uid,
|
||||
// gid, rdev, blksize, birthtimeMs, the Date fields (the *Ms values
|
||||
// rounded) and every type test. The callback forms build Node's Stats and
|
||||
// BigIntStats objects: own fields in the binding's order, lazy Date
|
||||
// accessors that become own properties when read, and type tests that go
|
||||
// through this._checkModeProperty and read this.mode.
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const os = require("node:os");
|
||||
const util = require("node:util");
|
||||
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "fs-stats-"));
|
||||
const file = path.join(dir, "f.txt");
|
||||
fs.writeFileSync(file, "stats text");
|
||||
const posix = process.platform !== "win32";
|
||||
|
||||
// The typed snapshot.
|
||||
const s = fs.statSync(file);
|
||||
console.log("type bits", (s.mode & 0o170000) === 0o100000, s.mode === fs.lstatSync(file).mode);
|
||||
console.log("ids", s.uid >= 0, s.gid >= 0, s.rdev, s.blksize > 0, s.nlink, s.size, s.blocks >= 0);
|
||||
console.log("tests", s.isFile(), s.isDirectory(), s.isSymbolicLink(), s.isBlockDevice(), s.isCharacterDevice(), s.isFIFO(), s.isSocket());
|
||||
console.log("dates", s.atime.getTime() === Math.round(s.atimeMs), s.mtime.getTime() === Math.round(s.mtimeMs), s.ctime.getTime() === Math.round(s.ctimeMs), s.birthtime.getTime() === Math.round(s.birthtimeMs));
|
||||
console.log("iso", s.mtime.toISOString() === new Date(Math.round(s.mtimeMs)).toISOString());
|
||||
const d = fs.statSync(dir);
|
||||
console.log("directory", d.isDirectory(), d.isFile(), (d.mode & 0o170000) === 0o040000);
|
||||
const fd = fs.openSync(file, "r");
|
||||
const f = fs.fstatSync(fd);
|
||||
console.log("fstat", f.ino === s.ino, f.mode === s.mode, f.birthtimeMs === s.birthtimeMs, f.blksize === s.blksize);
|
||||
fs.closeSync(fd);
|
||||
if (posix) {
|
||||
const n = fs.statSync("/dev/null");
|
||||
console.log("char device", n.isCharacterDevice(), n.isFile(), n.rdev > 0, (n.mode & 0o170000) === 0o020000);
|
||||
fs.symlinkSync("f.txt", path.join(dir, "link"));
|
||||
const l = fs.lstatSync(path.join(dir, "link"));
|
||||
console.log("link", l.isSymbolicLink(), (l.mode & 0o170000) === 0o120000, l.size);
|
||||
} else {
|
||||
console.log("device and link checks need POSIX");
|
||||
}
|
||||
|
||||
const mask = (text) => text.replace(/\n */g, " ").replace(/\d+(\.\d+)?n?/g, "N");
|
||||
|
||||
// The callback objects.
|
||||
fs.stat(file, (err, t) => {
|
||||
console.log("keys", err, Object.keys(t).join());
|
||||
const keys = [];
|
||||
for (const key in t) keys.push(key);
|
||||
console.log("for-in", keys.join());
|
||||
console.log("inspect", mask(util.inspect(t)));
|
||||
console.log("same snapshot", t.mode === s.mode, t.ino === s.ino, t.birthtimeMs === s.birthtimeMs, t.blksize === s.blksize);
|
||||
console.log("lazy", Object.keys(t).includes("mtime"), t.mtime === t.mtime, Object.keys(t).includes("mtime"));
|
||||
console.log("rounded", t.mtime.getTime() === Math.round(t.mtimeMs), t.birthtime.getTime() === s.birthtime.getTime());
|
||||
const own = Object.getOwnPropertyDescriptor(t, "mtime");
|
||||
console.log("own date", own.writable, own.enumerable, own.configurable);
|
||||
t.atime = 5;
|
||||
console.log("assigned", t.atime, Object.keys(t).slice(-2).join());
|
||||
const proto = Object.getPrototypeOf(t);
|
||||
console.log("prototypes", Object.getOwnPropertyNames(proto).join(), Object.getOwnPropertyNames(Object.getPrototypeOf(proto)).join());
|
||||
console.log("names", t.constructor.name, t.constructor.length, JSON.stringify(t.isFile.name), t.isFile.length);
|
||||
console.log("json", JSON.stringify(t).startsWith('{"dev":'), JSON.stringify(t).includes('"atime":5'));
|
||||
t.mode = 0o010644;
|
||||
console.log("mode read", t.isFIFO() === posix, t.isFile());
|
||||
t.mode = "x";
|
||||
console.log("odd mode", t.isFile());
|
||||
fs.lstat(file, { bigint: true }, (err2, b) => {
|
||||
console.log("bigint keys", err2, Object.keys(b).join());
|
||||
console.log("bigint inspect", mask(util.inspect(b)), b.constructor.name);
|
||||
console.log("bigint values", typeof b.mode, b.mode === BigInt(s.mode), b.ino === BigInt(s.ino), b.mtimeMs === b.mtimeNs / 1000000n);
|
||||
console.log("bigint dates", b.mtime.getTime() === Number(b.mtimeMs), b.isFile(), b.isDirectory());
|
||||
b.mode = 5;
|
||||
try {
|
||||
b.isFile();
|
||||
} catch (error) {
|
||||
console.log("mixed", error.name, error.message);
|
||||
}
|
||||
const { isFile } = b;
|
||||
try {
|
||||
isFile();
|
||||
} catch (error) {
|
||||
console.log("detached", error.name, error.message);
|
||||
}
|
||||
const handle = fs.openSync(file, "r");
|
||||
fs.fstat(handle, (err3, h) => {
|
||||
console.log("fstat callback", err3, h.isDirectory(), h.isFile(), h.ino === s.ino, h.mode === s.mode, h.birthtimeMs === s.birthtimeMs);
|
||||
fs.closeSync(handle);
|
||||
fs.rmSync(dir, { recursive: true });
|
||||
console.log("removed", fs.existsSync(dir));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,94 @@
|
||||
// String appends to fields, records, captured variables and through
|
||||
// concat chains: results, aliases of the old string (which must not
|
||||
// change), right sides that reassign the field, accessors, non-string right
|
||||
// sides, the value of the compound assignment, and surrogate halves joined
|
||||
// by appends, against Node.
|
||||
class Writer {
|
||||
text = "";
|
||||
count = 0;
|
||||
write(s: string): void {
|
||||
this.text += s;
|
||||
}
|
||||
reset(): string {
|
||||
this.text = "reset";
|
||||
return "!";
|
||||
}
|
||||
}
|
||||
class Sub extends Writer {
|
||||
extra = "e";
|
||||
}
|
||||
const w = new Writer();
|
||||
for (let i = 0; i < 300; i++) w.write("ab" + (i % 10));
|
||||
console.log(w.text.length, w.text.slice(0, 12), w.text.slice(-6));
|
||||
const before = w.text;
|
||||
w.text += "tail";
|
||||
console.log(before.length, before.endsWith("tail"), w.text.endsWith("tail"), w.text.length);
|
||||
w.text += w.reset();
|
||||
console.log(w.text.length, w.text.slice(-6));
|
||||
const v = (w.text += "X");
|
||||
console.log(v.slice(-2), w.text === v);
|
||||
w.count += 2;
|
||||
const n = new Writer();
|
||||
n.text += 5 as unknown as string;
|
||||
n.text += String(1.5);
|
||||
console.log(n.text, w.count);
|
||||
const sub = new Sub();
|
||||
sub.text += "s1";
|
||||
sub.extra += "x";
|
||||
sub.text += sub.extra;
|
||||
console.log(sub.text, sub.extra);
|
||||
|
||||
class Box {
|
||||
private _v = "a";
|
||||
get v(): string {
|
||||
return this._v;
|
||||
}
|
||||
set v(x: string) {
|
||||
this._v = x + "|";
|
||||
}
|
||||
}
|
||||
const bx = new Box();
|
||||
bx.v += "b";
|
||||
bx.v += "c";
|
||||
console.log(bx.v);
|
||||
|
||||
type Rec = { name: string; size: number };
|
||||
const rec: Rec = { name: "r", size: 1 };
|
||||
const alias = rec.name;
|
||||
for (let i = 0; i < 50; i++) rec.name += String.fromCharCode(97 + (i % 26));
|
||||
console.log(rec.name.length, alias, rec.name.slice(0, 5));
|
||||
|
||||
// Concat chains whose leftmost operand is the target.
|
||||
let s = "x";
|
||||
const t = s;
|
||||
for (let i = 0; i < 100; i++) s = s + "-" + i;
|
||||
console.log(s.length, t, s.slice(-8));
|
||||
let u = "u";
|
||||
u = u + 1 + 2;
|
||||
u = u + "a" + 3.5 + "b";
|
||||
console.log(u);
|
||||
let pair = "p";
|
||||
pair = pair + "\ud83d" + "\ude00";
|
||||
console.log(pair, pair.length);
|
||||
let joined = "";
|
||||
joined += "\ud83d";
|
||||
const half = joined;
|
||||
joined += "\ude00";
|
||||
console.log(joined, joined.length, half.length);
|
||||
|
||||
// Captured variables.
|
||||
function counter(): [(s: string) => void, () => string] {
|
||||
let out = "";
|
||||
return [
|
||||
(s: string) => {
|
||||
out += s;
|
||||
out = out + "," + s.length;
|
||||
},
|
||||
() => out,
|
||||
];
|
||||
}
|
||||
const [add, get] = counter();
|
||||
add("aa");
|
||||
const snapshot = get();
|
||||
add("b");
|
||||
console.log(get(), snapshot);
|
||||
@@ -0,0 +1,37 @@
|
||||
// `s += String.fromCharCode(c)` appends one character: ASCII, two- and
|
||||
// three-byte characters, surrogate halves that join into one character,
|
||||
// codes that wrap modulo 2^16, aliases of the old string (which must not
|
||||
// change), and growth from an empty string, against Node.
|
||||
let s = "";
|
||||
const seen: string[] = [];
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
s += String.fromCharCode(32 + (i % 95));
|
||||
if (i % 250 === 0) seen.push(s);
|
||||
}
|
||||
console.log(s.length, s.slice(0, 10), s.slice(-10), seen.map((x) => x.length).join(","));
|
||||
let t = "abc";
|
||||
const alias = t;
|
||||
t += String.fromCharCode(100);
|
||||
t += String.fromCharCode(0xe9);
|
||||
t += String.fromCharCode(0x65e5);
|
||||
t += String.fromCharCode(0x10041);
|
||||
t += String.fromCharCode(-1);
|
||||
console.log(t, t.length, alias, alias.length);
|
||||
let pair = "x";
|
||||
pair += String.fromCharCode(0xd83d);
|
||||
const half = pair;
|
||||
pair += String.fromCharCode(0xde00);
|
||||
console.log(pair, pair.length, half.length, half.charCodeAt(1).toString(16));
|
||||
let lone = "";
|
||||
lone += String.fromCharCode(0xde00);
|
||||
lone += String.fromCharCode(0xd83d);
|
||||
console.log(lone.length, lone.charCodeAt(0).toString(16), lone.charCodeAt(1).toString(16));
|
||||
let codes = "";
|
||||
for (const c of [72, 101, 108, 108, 111, 1.9, 65.5, 0x7f, 0x80]) codes += String.fromCharCode(c);
|
||||
console.log(JSON.stringify(codes), codes.length);
|
||||
function build(n: number): string {
|
||||
let out = "";
|
||||
for (let i = 0; i < n; i++) out += String.fromCharCode(97 + (i % 26));
|
||||
return out;
|
||||
}
|
||||
console.log(build(0), build(1), build(27), build(5000).length);
|
||||
@@ -0,0 +1,70 @@
|
||||
// Records returned from functions that read arrays, called in loops so the
|
||||
// result-slot entries inline: in-range reads, holes, reads past the end and
|
||||
// at fractional indices (undefined fields, which make the arithmetic NaN),
|
||||
// sparse arrays, boolean fields, and a recursive helper, against Node.
|
||||
interface Point {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
function pointAt(xs: number[], ys: number[], i: number): Point {
|
||||
return { x: xs[i], y: ys[i] };
|
||||
}
|
||||
function distance(p: Point, q: Point): number {
|
||||
const dx = p.x - q.x;
|
||||
const dy = p.y - q.y;
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
function pathLength(xs: number[], ys: number[], extra: number): number {
|
||||
let total = 0;
|
||||
for (let i = 0; i + 1 + extra < xs.length + extra; i++) total += distance(pointAt(xs, ys, i), pointAt(xs, ys, i + 1));
|
||||
return total;
|
||||
}
|
||||
const xs: number[] = [];
|
||||
const ys: number[] = [];
|
||||
for (let i = 0; i < 50; i++) {
|
||||
xs.push((i * 37) % 11);
|
||||
ys.push((i * 13) % 7);
|
||||
}
|
||||
console.log(pathLength(xs, ys, 0).toFixed(6), pathLength(xs, ys, 1), pathLength(xs, ys, 2));
|
||||
const p = pointAt(xs, ys, 60);
|
||||
const q = pointAt(xs, ys, 1.5);
|
||||
console.log(p.x, p.y, q.x === undefined, distance(p, pointAt(xs, ys, 0)), distance(pointAt(xs, ys, 3), pointAt(xs, ys, 3)));
|
||||
const holey: number[] = [1, 2, 3];
|
||||
holey.length = 6;
|
||||
holey[5] = 9;
|
||||
let viaHoles = "";
|
||||
for (let i = 0; i < 7; i++) viaHoles += String(pointAt(holey, holey, i).x) + ",";
|
||||
console.log(viaHoles);
|
||||
const sparse: number[] = [4, 5];
|
||||
sparse[100] = 6;
|
||||
console.log(pointAt(sparse, sparse, 100).y, pointAt(sparse, sparse, 50).y, distance(pointAt(sparse, xs, 0), pointAt(sparse, xs, 1)));
|
||||
interface Flags {
|
||||
on: boolean;
|
||||
next: boolean;
|
||||
}
|
||||
function flagsAt(bits: boolean[], i: number): Flags {
|
||||
return { on: bits[i], next: bits[i + 1] };
|
||||
}
|
||||
const bits = [true, false, true, true];
|
||||
let flagText = "";
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const f = flagsAt(bits, i);
|
||||
flagText += `${f.on}/${f.next} `;
|
||||
}
|
||||
console.log(flagText);
|
||||
interface Span {
|
||||
lo: number;
|
||||
hi: number;
|
||||
}
|
||||
function widen(span: Span, values: number[], from: number, to: number): Span {
|
||||
if (from >= to) return span;
|
||||
const v = values[from];
|
||||
return widen({ lo: Math.min(span.lo, v), hi: Math.max(span.hi, v) }, values, from + 1, to);
|
||||
}
|
||||
let spans = 0;
|
||||
for (let i = 0; i + 8 <= xs.length; i += 8) {
|
||||
const s = widen({ lo: xs[i], hi: xs[i] }, xs, i + 1, i + 8);
|
||||
spans += s.hi - s.lo;
|
||||
}
|
||||
const past = widen({ lo: 0, hi: 0 }, xs, 48, 52);
|
||||
console.log(spans, past.lo, past.hi);
|
||||
@@ -0,0 +1,30 @@
|
||||
// socket.unref() and server.unref() let the process exit while the handle
|
||||
// is open, and ref() undoes it. An unref'd socket still delivers its events
|
||||
// while other work (here a timer) keeps the loop running.
|
||||
import * as net from "node:net";
|
||||
import * as http from "node:http";
|
||||
|
||||
const server = net.createServer((socket) => {
|
||||
socket.unref();
|
||||
socket.on("data", (d: Buffer) => socket.write(`echo ${d.toString()}`));
|
||||
});
|
||||
server.listen(0, "127.0.0.1", () => {
|
||||
const port = (server.address() as { port: number }).port;
|
||||
server.unref();
|
||||
const client = net.connect({ port, host: "127.0.0.1" }, () => {
|
||||
client.write("hi");
|
||||
});
|
||||
client.on("data", (d: Buffer) => {
|
||||
console.log("client got", d.toString());
|
||||
client.unref();
|
||||
setTimeout(() => console.log("timer still ran"), 50);
|
||||
});
|
||||
const web = http.createServer((req, res) => res.end("ok"));
|
||||
web.listen(0, "127.0.0.1", () => {
|
||||
web.unref();
|
||||
web.ref();
|
||||
web.unref();
|
||||
console.log("web unref'd");
|
||||
});
|
||||
});
|
||||
process.on("exit", () => console.log("exit"));
|
||||
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
/* fs.globSync, fs/promises glob and fs.glob under @types/node: the same
|
||||
* native walk as under the bundled declarations. */
|
||||
import { type Dirent, glob as globCallback, globSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { glob } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
const root = mkdtempSync(join(tmpdir(), "scriptc-node-types-glob-"));
|
||||
mkdirSync(join(root, "src/util"), { recursive: true });
|
||||
for (const file of ["src/a.ts", "src/util/b.ts", "src/util/b.test.ts", "notes.md"]) writeFileSync(join(root, file), "");
|
||||
|
||||
console.log(globSync("**/*.ts", { cwd: root, exclude: ["**/*.test.ts"] }).join(" "));
|
||||
const entries: Dirent[] = globSync("src/*", { cwd: root, withFileTypes: true });
|
||||
console.log(entries.map((d) => `${d.name}:${d.isDirectory()}`).join(" "));
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const found: string[] = [];
|
||||
for await (const path of glob("src/**", { cwd: root })) found.push(path);
|
||||
console.log(found.join(" "));
|
||||
globCallback("*.md", { cwd: root }, (err, matches) => {
|
||||
console.log(err, matches.join(" "));
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
});
|
||||
}
|
||||
|
||||
void main();
|
||||
+1
-1
@@ -14,7 +14,7 @@ const concat: IrExpr = { kind: "strConcat", left: text, right: suffix, type: STR
|
||||
// Execute backend-independent analyses against the same real, recursive
|
||||
// IR types that the builders consume. Fail before generating any module
|
||||
// if an analysis loses a variant or reads the wrong payload.
|
||||
if (matchStringSelfConcat("text.0", concat) !== suffix) throw new Error("concat recognition");
|
||||
if (matchStringSelfConcat("text.0", concat)?.suffix !== suffix) throw new Error("concat recognition");
|
||||
if (matchStringSelfConcat("other.0", concat) !== null) throw new Error("concat target");
|
||||
if (matchStringSelfConcat("text.0", numLit(0, loc)) !== null) throw new Error("concat variant");
|
||||
if (!isStableReceiverOperand(numLit(3, loc), "sum.0")) throw new Error("stable literal");
|
||||
|
||||
@@ -548,6 +548,20 @@ test("node-types: Blob and File match Node under @types/node", async () => {
|
||||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||||
});
|
||||
|
||||
test("node-types: fs.glob forms match Node under @types/node", async () => {
|
||||
const outDir = outDirFor("node-fs-glob");
|
||||
const entry = join(nodeTypesDir, "fs-glob.ts");
|
||||
const result = await compile(entry, { outPath: join(outDir, "fs-glob"), outDir, sanitize });
|
||||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||||
if (!result.ok) return;
|
||||
const [native, node] = await Promise.all([
|
||||
execFileAsync(result.binaryPath),
|
||||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||||
]);
|
||||
expect(native.stdout).toBe(node.stdout);
|
||||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||||
});
|
||||
|
||||
test("node-types: util function helpers match Node under @types/node", async () => {
|
||||
const outDir = outDirFor("node-util-callables");
|
||||
const entry = join(nodeTypesDir, "util-callables.ts");
|
||||
|
||||
Reference in New Issue
Block a user