feat(fls): write the topology of a .fls, loaded by Java as the original
topologyWriter.ts writes konfiguration.xml from Java's view of a project: the version line, the node items (label position, device with name, host label flags, interfaces or ports), the cable items and the documentation items. What a device's constructor makes is changed through getters, as XMLEncoder does, and more is added; fewer than it makes set the list anew. Cable ends name the port objects the getters return (the writer now names a getter's result, <void id="Port0" property="port"/>), and each cable carries the nodes the editor draws it between. Checked by Java against Java: every corpus file, read and written again, loads in Java FILIUS (the oracle's loading method, --sha256 ties its result to the written bytes) to the same devices, names, positions, interfaces, cables and documentation as the original; so does a synthetic topology with what the corpus never sets. The oracle and the reader now also report a cable's drawing ends (JCablePanel ziel1/ziel2): in the corpus the nodes it joins, for all 183 cables. Mutation probes rewrite the files and let Java load each mutant. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,7 +21,22 @@ export type BeanValue =
|
||||
| BeanPrimitive
|
||||
| BeanObject
|
||||
| BeanArray
|
||||
| { readonly kind: 'class'; readonly name: string };
|
||||
| { readonly kind: 'class'; readonly name: string }
|
||||
| BeanReference;
|
||||
|
||||
/**
|
||||
* An object only Java makes — a getter's result, such as the port a network
|
||||
* interface's constructor creates — named where the getter is called
|
||||
* (`<void id="Port0" property="port"/>`) and referenced after.
|
||||
*/
|
||||
export interface BeanReference {
|
||||
readonly kind: 'reference';
|
||||
readonly name: string;
|
||||
}
|
||||
|
||||
export function reference(name: string): BeanReference {
|
||||
return { kind: 'reference', name };
|
||||
}
|
||||
|
||||
export interface BeanObject {
|
||||
readonly kind: 'object';
|
||||
@@ -42,14 +57,14 @@ export interface BeanArray {
|
||||
export type BeanStatement =
|
||||
/** target.setP(value) */
|
||||
| { readonly set: string; readonly value: BeanValue }
|
||||
/** target.getP(), then the statements on its result */
|
||||
| { readonly get: string; readonly statements: readonly BeanStatement[] }
|
||||
/** target.getP(), then the statements on its result; `as` names the result */
|
||||
| { readonly get: string; readonly statements: readonly BeanStatement[]; readonly as?: BeanReference }
|
||||
/** target.m(args) */
|
||||
| { readonly call: string; readonly args: readonly BeanValue[] }
|
||||
/** target.set(index, value) on a list */
|
||||
| { readonly setIndex: number; readonly value: BeanValue }
|
||||
/** target.get(index), then the statements on its result */
|
||||
| { readonly getIndex: number; readonly statements: readonly BeanStatement[] };
|
||||
/** target.get(index), then the statements on its result; `as` names the result */
|
||||
| { readonly getIndex: number; readonly statements: readonly BeanStatement[]; readonly as?: BeanReference };
|
||||
|
||||
export const int = (value: number): BeanPrimitive => ({ kind: 'int', value });
|
||||
export const long = (value: number | bigint): BeanPrimitive => ({ kind: 'long', value });
|
||||
@@ -70,7 +85,7 @@ export function encodeJavaBeans(values: readonly BeanValue[]): string {
|
||||
}
|
||||
|
||||
class Writer {
|
||||
private readonly ids = new Map<BeanObject | BeanArray, string>();
|
||||
private readonly ids = new Map<BeanObject | BeanArray | BeanReference, string>();
|
||||
private readonly counters = new Map<string, number>();
|
||||
/** Objects written more than once need an id; found before writing. */
|
||||
constructor(private readonly shared: ReadonlySet<BeanObject | BeanArray>) {}
|
||||
@@ -109,6 +124,12 @@ class Writer {
|
||||
case 'object':
|
||||
this.object(value, depth, lines);
|
||||
break;
|
||||
case 'reference': {
|
||||
const id = this.ids.get(value);
|
||||
if (id === undefined) throw new Error(`reference '${value.name}' used before the getter that names it`);
|
||||
lines.push(`${indent}<object idref="${id}"/>`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -171,10 +192,7 @@ class Writer {
|
||||
if ('set' in statement) {
|
||||
nested(`<void property="${escapeAttribute(statement.set)}"`, () => this.value(statement.value, depth + 1, lines));
|
||||
} else if ('get' in statement) {
|
||||
if (statement.statements.length === 0) return;
|
||||
nested(`<void property="${escapeAttribute(statement.get)}"`, () => {
|
||||
for (const inner of statement.statements) this.statement(inner, depth + 1, lines);
|
||||
});
|
||||
this.getter(`property="${escapeAttribute(statement.get)}"`, statement.statements, statement.as, depth, lines);
|
||||
} else if ('call' in statement) {
|
||||
if (statement.args.length === 0) {
|
||||
lines.push(`${indent}<void method="${escapeAttribute(statement.call)}"/>`);
|
||||
@@ -186,13 +204,38 @@ class Writer {
|
||||
} else if ('setIndex' in statement) {
|
||||
nested(`<void index="${statement.setIndex}"`, () => this.value(statement.value, depth + 1, lines));
|
||||
} else {
|
||||
if (statement.statements.length === 0) return;
|
||||
nested(`<void index="${statement.getIndex}"`, () => {
|
||||
for (const inner of statement.statements) this.statement(inner, depth + 1, lines);
|
||||
});
|
||||
this.getter(`index="${statement.getIndex}"`, statement.statements, statement.as, depth, lines);
|
||||
}
|
||||
}
|
||||
|
||||
/** A getter's call: left out when nothing happens to its result and nothing names it. */
|
||||
private getter(
|
||||
attribute: string,
|
||||
statements: readonly BeanStatement[],
|
||||
name: BeanReference | undefined,
|
||||
depth: number,
|
||||
lines: string[],
|
||||
): void {
|
||||
const indent = ' '.repeat(depth);
|
||||
if (statements.length === 0 && name === undefined) return;
|
||||
let idAttribute = '';
|
||||
if (name !== undefined) {
|
||||
if (this.ids.has(name)) throw new Error(`reference '${name.name}' named twice`);
|
||||
const count = this.counters.get(name.name) ?? 0;
|
||||
this.counters.set(name.name, count + 1);
|
||||
const id = `${name.name}${count}`;
|
||||
this.ids.set(name, id);
|
||||
idAttribute = ` id="${id}"`;
|
||||
}
|
||||
if (statements.length === 0) {
|
||||
lines.push(`${indent}<void${idAttribute} ${attribute}/>`);
|
||||
return;
|
||||
}
|
||||
lines.push(`${indent}<void${idAttribute} ${attribute}>`);
|
||||
for (const inner of statements) this.statement(inner, depth + 1, lines);
|
||||
lines.push(`${indent}</void>`);
|
||||
}
|
||||
|
||||
private idFor(value: BeanObject | BeanArray, name: string): string | undefined {
|
||||
if (!this.shared.has(value)) return undefined;
|
||||
const count = this.counters.get(name) ?? 0;
|
||||
|
||||
Reference in New Issue
Block a user