feat(core): virtual file system and Swift string semantics

The core gains Swift's virtual file system (paths, case-insensitive
lookup that keeps the spelling, quotas, atomic changes, the defaults of
a new device) and the string behaviour the ported code relies on without
saying so. Both are measured in the Swift oracle: 11 file system cases
(the three Swift tests among them) step by step, and trimming, lower
case, equality, ordering and Base64 case by case — whitespace and control
characters over all of Unicode.

Found by measuring:
- Swift trims NEL and ZERO WIDTH SPACE, not the BOM; JS trim() does the
  opposite. The .fls import now trims with swiftTrim.
- lowercased() maps scalar by scalar: a word-final sigma stays σ.
- Foundation decodes "====" and "Y===" as empty data.
- Foundation on Linux counts 24,735 plane-14 scalars as control
  characters, a table artefact; the port keeps Unicode Cc+Cf.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-29 21:23:45 +00:00
co-authored by Claude Opus 5.5
parent 51a83ea68d
commit a198d10b4a
15 changed files with 5247 additions and 18 deletions
+23 -17
View File
@@ -38,6 +38,7 @@ import {
inDeterministicRenderOrder,
interfaceKey,
isSafeFrame,
swiftTrim,
topologyNode,
DOCUMENTATION_BLACK,
DOCUMENTATION_PALE_YELLOW,
@@ -235,9 +236,14 @@ function importNode(source: FlsNode, newId: () => Id, warnings: string[]): Topol
});
}
/** Swift's `value?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""`. */
function trimmedOrEmpty(value: string | null | undefined): string {
return value == null ? '' : swiftTrim(value);
}
/** Swift's normalizedOrDefault; Java's null (a rejected address) counts as empty. */
function orDefault(value: string | null, fallback: string): string {
const trimmed = value?.trim() ?? '';
const trimmed = trimmedOrEmpty(value);
return trimmed === '' ? fallback : trimmed;
}
@@ -253,8 +259,8 @@ function registerConfigurations(
deviceConfigurations.set(node.id, {
ipAddress: orDefault(nic?.ip ?? null, '192.168.0.10'),
subnetMask: orDefault(nic?.mask ?? null, '255.255.255.0'),
defaultGateway: nic?.gateway?.trim() ?? '',
dnsServer: nic?.dns?.trim() ?? '',
defaultGateway: trimmedOrEmpty(nic?.gateway),
dnsServer: trimmedOrEmpty(nic?.dns),
});
return;
}
@@ -272,14 +278,14 @@ function registerConfigurations(
// Swift's model has no router gateway at all; the core reads it from the
// router's device configuration, which is set only when there is one.
const first = source.interfaces[0];
const routerGateway = first?.gateway?.trim() ?? '';
const routerGateway = trimmedOrEmpty(first?.gateway);
if (node.kind === 'router' && first !== undefined && routerGateway !== '') {
const firstConfiguration = interfaceConfigurations.get(interfaceKey(node.id, node.ports[0]!.id));
deviceConfigurations.set(node.id, {
ipAddress: firstConfiguration?.ipAddress ?? '',
subnetMask: firstConfiguration?.subnetMask ?? '',
defaultGateway: routerGateway,
dnsServer: first.dns?.trim() ?? '',
dnsServer: trimmedOrEmpty(first.dns),
});
}
const wan = source.interfaces[0];
@@ -288,8 +294,8 @@ function registerConfigurations(
deviceConfigurations.set(node.id, {
ipAddress: wanConfiguration?.ipAddress ?? '',
subnetMask: wanConfiguration?.subnetMask ?? '',
defaultGateway: wan.gateway?.trim() ?? '',
dnsServer: wan.dns?.trim() ?? '',
defaultGateway: trimmedOrEmpty(wan.gateway),
dnsServer: trimmedOrEmpty(wan.dns),
});
}
}
@@ -310,7 +316,7 @@ type SystemConfigurations = Pick<
/** A row of text cells, trimmed, or undefined unless it has exactly four. */
function fourCells(row: (string | null)[]): [string, string, string, string] | undefined {
if (row.length !== 4 || row.some((cell) => cell === null)) return undefined;
return row.map((cell) => cell!.trim()) as [string, string, string, string];
return row.map((cell) => swiftTrim(cell!)) as [string, string, string, string];
}
/**
@@ -334,10 +340,10 @@ function registerServiceConfigurations(
if (server !== undefined) {
const configuration = dhcpServerConfiguration({
isActive: server.isActive,
lowerBoundIPAddress: server.lowerBound?.trim() ?? '',
upperBoundIPAddress: server.upperBound?.trim() ?? '',
gatewayIPAddress: server.gateway?.trim() ?? '',
dnsServerIPAddress: server.dnsServer?.trim() ?? '',
lowerBoundIPAddress: trimmedOrEmpty(server.lowerBound),
upperBoundIPAddress: trimmedOrEmpty(server.upperBound),
gatewayIPAddress: trimmedOrEmpty(server.gateway),
dnsServerIPAddress: trimmedOrEmpty(server.dnsServer),
useOwnSettings: server.useOwnSettings,
staticAssignments: server.staticAssignedAddresses.map((entry) => ({
id: newId(),
@@ -393,7 +399,7 @@ function registerSystemConfigurations(
warnings: string[],
): void {
const where = `node '${node.displayName}'`;
const ssid = source.system.ssid?.trim() ?? '';
const ssid = trimmedOrEmpty(source.system.ssid);
if (node.kind === 'networkSwitch') {
// Without an SSID in the file, Java rolls one; Swift derives it from the node id.
system.switchConfigurations.set(node.id, {
@@ -435,10 +441,10 @@ function firewallConfigurationOf(firewall: FlsFirewall, where: string, warnings:
const protocol = PROTOCOLS[rule.protocol];
if (protocol === undefined) warnings.push(`${where}: firewall protocol ${rule.protocol} unknown, used TCP`);
return {
sourceIPAddress: rule.srcIP?.trim() ?? '',
sourceSubnetMask: rule.srcMask?.trim() ?? '',
destinationIPAddress: rule.destIP?.trim() ?? '',
destinationSubnetMask: rule.destMask?.trim() ?? '',
sourceIPAddress: trimmedOrEmpty(rule.srcIP),
sourceSubnetMask: trimmedOrEmpty(rule.srcMask),
destinationIPAddress: trimmedOrEmpty(rule.destIP),
destinationSubnetMask: trimmedOrEmpty(rule.destMask),
port: rule.port,
protocol: protocol ?? 'tcp',
action: action(rule.action, 'action'),