Chapter 5 - Interfaces and Output Artifacts

AZM has three public entry surfaces: the command-line binary, the compile API and the tooling API. They all use the same compiler pipeline. Output writers then serialize assembled facts for users, Debug80 and package consumers.

This chapter covers the boundary between the compiler and its callers: package exports, CLI flow, public TypeScript APIs and artifact shapes.

Package Exports

package.json exposes:

@jhlagado/azm
@jhlagado/azm/compile
@jhlagado/azm/tooling
@jhlagado/azm/cli

src/index.ts re-exports the stable public surface. src/api-compile.ts backs @jhlagado/azm/compile. src/api-tooling.ts backs @jhlagado/azm/tooling. src/cli.ts is the executable entry.

The root export gives consumers a broad import. The /compile path is the build-system path. The /tooling path is the editor and analysis path. The /cli path backs the executable entry.

CLI Flow

The executable path in package.json points to dist/src/cli.js, compiled from src/cli.ts. That file calls runCli(process.argv.slice(2)) and sets the process exit code.

src/cli/run.ts owns the CLI control flow:

runCli(argv)
  parseCliArgs(argv)
  artifactBase(entryFile, outputType, outputPath)
  compile(entryFile, buildCompileOptions(parsed, base))
  sort and print diagnostics
  writeArtifacts(base, artifacts, outputType)
  return exit code

The CLI returns 0 for a successful assembly, 1 when diagnostics include an error and 2 for argument or unexpected runtime failures. Diagnostics are printed to standard error. The primary output path is printed to standard output when artifact writing succeeds.

src/cli/parse-args.ts parses switches and validates the command shape. It recognises output selection, artifact suppression, include paths, source-root, case-style linting, directive aliases and register-care options.

src/cli/write-artifacts.ts maps parsed options into CompileNextFunctionOptions and writes in-memory artifacts to disk. The base path calculation is central to CLI behaviour. If the user supplies --output build/program.bin, the primary artifact is written to that path and side artifacts use the same base. If the user supplies only program.asm, AZM writes outputs next to the entry source using the source stem.

Compile API

src/api-compile.ts exports:

export async function compile(
  entryFile: string,
  options: CompileNextFunctionOptions = {},
  deps: CompileNextDependencies = { formats: defaultFormatWriters },
): Promise<CompileNextResult>

The compile API is file-backed. It reads source from disk, expands includes, analyses the program, assembles it and returns artifacts in memory.

Important options include:

Option Meaning
includeDirs Include search paths.
directiveAliasFiles Project alias profile files.
caseStyle Case-style lint mode.
outputType Primary output type, hex or bin.
sourceRoot Root used for portable D8 map paths.
d8mInputs Artifact paths recorded in D8 metadata.
emitBin, emitHex, emitD8m, emitAsm80 Artifact selection.
registerCare Register-care mode.
emitRegisterReport Emit .regcare.txt artifact.
emitRegisterInterface Emit .asmi artifact.
emitRegisterAnnotations Emit source annotation artifact.
fixRegisterContracts Apply conservative source fixes.
acceptRegisterOutputCandidates Promote selected output candidates.
registerCareProfile Built-in external contract profile.
registerCareInterfaces External .asmi contract files.
skipAssembly Run loading and analysis only.

compile() returns:

export interface CompileNextResult {
  readonly diagnostics: readonly Diagnostic[];
  readonly artifacts: readonly Artifact[];
}

Diagnostics describe every warning or error observed during loading, analysis, register care, assembly or artifact creation. Artifacts contain the in-memory outputs requested by options.

Tooling API

src/tooling/api.ts exports loadProgramNext() and analyzeProgramNext(). src/api-tooling.ts re-exports those functions with register-care tooling helpers.

loadProgramNext() returns a loaded program with source items, source texts and source line comments. analyzeProgramNext() runs semantic checks and returns symbols. analyzeRegisterCareForTools() returns register-care diagnostics and code actions in a form suitable for editors.

An editor integration usually starts with:

const loaded = await loadProgramNext({
  entryFile: '/project/src/main.asm',
  includeDirs: ['/project/include'],
  preloadedText: editorText,
});

When loaded.loadedProgram is present, the editor can call analyzeProgramNext() for symbols and case-style diagnostics. It can also call analyzeRegisterCareForTools() for register-care candidate diagnostics and code actions.

Artifact Types

The output layer uses structured artifact objects from src/outputs/types.ts:

  • BinArtifact
  • HexArtifact
  • D8mArtifact
  • Asm80Artifact
  • RegisterCareReportArtifact
  • RegisterCareInterfaceArtifact
  • RegisterCareAnnotationsArtifact

Each artifact has a kind field. Callers can switch on kind to find the artifact they need:

const d8m = result.artifacts.find((artifact) => artifact.kind === 'd8m');
const bin = result.artifacts.find((artifact) => artifact.kind === 'bin');

This shape keeps the compile API independent from output paths. A caller can write artifacts to disk, keep them in memory, send them to another process or compare them in a test.

Byte Maps, BIN and HEX

Assembly produces an EmittedByteMap. It represents sparse output: addresses map to byte values and source segments describe where those bytes came from.

src/outputs/range.ts provides range helpers. getWrittenSegments() identifies contiguous written address ranges. getWrittenRange() returns the overall written span.

src/outputs/write-bin.ts writes flat binary. It chooses the written range, fills gaps as needed and returns a Uint8Array artifact. src/outputs/write- hex.ts wraps src/outputs/hex.ts, which writes Intel HEX records and checksums.

D8 Debug Maps

src/outputs/write-d8.ts writes Debug80 metadata. It records generator details, input artifact paths, source files, source segments, addressable symbols and value-only constants.

The writer normalizes source paths through sourceRoot when provided. It also coalesces source segments and clips them to written ranges so Debug80 receives a clean map of source lines to emitted bytes.

The D8 map distinguishes addressable symbols from constants. Labels and addressable data carry addresses. Constants carry values. Debug80 can then use addressable symbols for breakpoints and display constants as metadata.

Lowered ASM80 and Register-Care Artifacts

src/outputs/write-asm80.ts serializes accepted AZM source items as ASM80-compatible .z80 text. It lowers supported AZM constructs into forms that can be compared against ASM80 output. The writer is larger than the other writers because it turns structured items back into source text.

Register-care report and interface artifacts are created by analyzeRegisterCare() and flow through the same compile result and CLI write path. The report is human-readable. The .asmi interface is metadata that can be loaded by later compile runs through --interface.

Public API Compatibility

The public API is defined by package exports and exported TypeScript types. Major-version planning is the point where these shapes can change:

  • exported function names
  • option object property names
  • result object shapes
  • artifact kinds
  • diagnostic object shape
  • D8 map type exports
  • register-care tooling result shapes

The type tests are the safety net for this boundary. When a public type changes, the change should be intentional and reflected in package documentation.