core/serializer.ts

import { Piece } from "./piece.ts";
import { Registry } from "./registry.ts";

/**
 * Abstract base class for piece serializers.
 *
 * For each piece type there is a serializer that can convert the piece back and
 * forth to a key, and that also provides metadata about the type for the map
 * editor. Mirrors the Java Serializer<T> interface exactly.
 *
 * create() and tag() are implemented by every registered serializer with no
 * exceptions, so they're enforced at compile time. store()/example()/template()
 * default to throwing since not every serializer needs to implement all three
 * directly (see TypeOnlySerializer).
 */
export abstract class Serializer {
  /**
   * Reconstruct a piece from its resolved args array.
   * String args are plain values; Piece args are already-resolved nested
   * registry lookups (the Registry resolves "^"-escaped sub-keys before
   * calling create, exactly as it does today).
   */
  abstract create(args: Array<string | Piece>): Piece;
  /**
   * Return the canonical serialized key for this piece, e.g. "Door|Blue|off".
   * The key must be parseable back via create().
   */
  store(piece: Piece): string {
    throw new Error("Serializer.store() not implemented");
  }
  /**
   * Return a representative instance of this type for display in the map editor.
   */
  example(): Piece {
    throw new Error("Serializer.example() not implemented");
  }
  /**
   * Given the typeId, return a human-readable template showing the key format,
   * e.g. "Door|{color}|{state}". Used by the editor to guide authoring.
   */
  template(typeId: string): string {
    throw new Error("Serializer.template() not implemented");
  }
  /**
   * Return the editor category for this type, e.g. "Terrain", "Room Features".
   */
  abstract tag(): string;
}

/**
 * A serializer base that works for any piece defined only by its type.
 * The key is just the typeId with no arguments (e.g. "Floor", "Wall", "Fire").
 * Subclasses implement create() (return a new instance) and tag().
 */
export abstract class TypeOnlySerializer extends Serializer {
  _typeId: string;
  constructor(typeId: string) {
    super();
    this._typeId = typeId;
  }
  store(piece: Piece): string {
    return this._typeId;
  }
  example(): Piece {
    return this.create([]);
  }
  template(): string {
    return this._typeId;
  }
}

/**
 * Base support for serializers that escape/unescape pieces as part of their
 * serialization key. Provides esc() and unesc() helpers for building and
 * parsing keys that embed other pieces. Subclasses must implement create()
 * and tag(), plus whichever of store()/example()/template() they need.
 */
export abstract class BaseSerializer extends Serializer {
  /**
   * Escape a piece reference for embedding inside another key.
   * Replaces "|" with "^" so the nested key does not break pipe-splitting.
   */
  esc(piece: Piece): string {
    return Registry.serialize(piece).replaceAll("|", "^");
  }

  /**
   * Unescape a piece reference embedded in a key arg.
   * The Registry has already resolved "^"-escaped args into Piece instances
   * before calling create(), so this helper is provided only for cases where
   * manual resolution is needed outside of create().
   */
  unesc<T extends Piece = Piece>(key: string): T {
    return Registry.get(key.replaceAll("^", "|")) as T;
  }
}