Plato.TypeScriptWriter

August 5, 2026 · View on GitHub

Generates TypeScript source code from a Plato compilation. This project mirrors the architecture of Plato.CSharpWriter:

Plato.CSharpWriterPlato.TypeScriptWriterRole
CSharpWriterTypeScriptWriterTop level orchestrator: walks the compilation and produces output files
CSharpTypeWriterTypeScriptTypeWriterWrites code for one type (or standalone functions); resolves type names
CSharpConcreteTypeWriterTypeScriptConcreteTypeWriterWrites a concrete type (struct in C#; class or native prototype extension in TS)
CSharpFunctionInfoTypeScriptFunctionInfoComputes signatures for a function instance
CSharpFunctionBodyWriterTypeScriptFunctionBodyWriterRecursively translates the Plato symbol tree into expressions/statements
ITypeToCSharpITypeToTypeScriptType-name resolution abstraction

Usage

using Ara3D.Geometry.TypeScriptWriter;

var writer = compilation.ToTypeScript(outputFolder);
foreach (var kv in writer.Files)
    outputFolder.RelativeFile(kv.Key).WriteAllText(kv.Value.ToString());

Or via Plato.CLI:

Plato.CLI [inputFolder] [outputFolder] --typescript

Output model

A single self-contained module plato.g.ts is produced (TypeScript modules are closed; splitting output would create cyclic imports). It contains, in order:

  1. A hand-written intrinsics prelude (Intrinsics namespace: Install, MakeArray, Range, structural equality, throw helpers) plus minimal IArray2D/IArray3D interfaces, and the List<T> / Buffer<T> affine builder classes (see below).
  2. One export interface per Plato interface.
  3. The IArray<T> interface and the Arr<T> class — a memoizing functional view (count plus indexing function): each element is computed at most once, and a fully-read view drops its indexing closure so chained views release the layers beneath them (plato-436). Library functions whose first parameter is an IArray and that are generic over the element type become methods of both; functions over a concrete element type become module-level functions.
  4. An export class Constants with one static getter per library constant.
  5. Native prototype extensions for the Plato primitives.
  6. One export class per remaining concrete type.

Fluent syntax on native values

Plato's Number and Integer map to the native number, Boolean to boolean, String and Character to string. Their Plato functions are installed on the native prototypes via Object.defineProperty (non-enumerable), with matching declare global interface augmentations for typing. This gives fluent syntax on plain values with zero wrappers:

(0.5).Turns().Cos()
x.Sqrt().Clamp(0, 1)
a.Lerp(b, t)

Native arithmetic operators keep working alongside, and generated function bodies use them for the intrinsic operators (this + y). Integer literals used as receivers are parenthesized automatically ((1).Subtract(t)).

Because Number and Integer share one prototype, colliding overloads are resolved first-writer-wins (Number is processed first); e.g. a separate truncating Integer.Divide cannot coexist with Number.Divide under the same name. This mirrors JavaScript's single number type.

The affine builders

The prelude ships List<T> and Buffer<T>, the unique builder types of primitives.plato. Plato guarantees a single live reference, so the generated rebind-after-mutate style (xs = xs.Add(x)) is honoured by mutating in place and returning this; Freeze hands the storage to an Arr without copying. Buffer intentionally shadows Node's global byte Buffer inside the generated module.

This is what lets statement-bodied stdlib functions that accumulate — the sorting and spatial-structure builders — run under TypeScript rather than throwing. demos/typescript/geometry-samples/tests/stdlib-builders.test.ts executes them.

Methods, not getters

Declared fields are the only properties (v.X). Every Plato function becomes a method: single-parameter functions are zero-argument methods (v.Length()), matching the extension-method convention on the C# side. Call sites distinguish field access from calls via the compilation-wide field-name set.

Concrete classes get: readonly constructor parameter properties, With{Field} functions, static Create, static get Default (when all fields have defaults), structural Equals/NotEquals, toString, and their Plato member functions. Bodiless intrinsics get native implementations from TypeScriptTypeWriter.TryGetIntrinsicBody (Math.*, comparisons, trig on Angle); anything unknown throws Not implemented at run time.

Other conventions

  • implements clauses are omitted (structural typing; the classes intentionally omit some interface members such as casts).
  • Generic constraints are not emitted.
  • No member overloading: colliding names are skipped with a // Skipped: comment.
  • The array interface (Array or IArray depending on dialect) maps to the generated IArray<T>; the parser accepts both interface and interface keywords.

Known limitations

  • Functions overloaded across Number/Integer collapse (see above).
  • Static functions on primitives (first parameter _) are skipped.
  • Library functions on IArray are only callable on values that are actually Arr instances (or otherwise expose the methods).