@jhlagado/azm 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/cli.d.ts CHANGED
@@ -8,6 +8,7 @@ type CliOptions = {
8
8
  entryFile: string;
9
9
  outputPath?: string;
10
10
  outputType: 'hex' | 'bin';
11
+ sourceRoot?: string;
11
12
  emitBin: boolean;
12
13
  emitHex: boolean;
13
14
  emitD8m: boolean;
package/dist/src/cli.js CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { mkdir, writeFile } from 'node:fs/promises';
3
- import { createRequire } from 'node:module';
4
3
  import { dirname, extname, resolve } from 'node:path';
5
4
  import { fileURLToPath } from 'node:url';
6
5
  import { compile } from './compile.js';
7
6
  import { isSupportedSourcePath, sourceExtensions } from './frontend/sourceExtensions.js';
8
7
  import { defaultFormatWriters } from './formats/index.js';
9
8
  import { normalizePathForCompare } from './pathCompare.js';
9
+ import { readPackageVersion } from './packageInfo.js';
10
10
  function usage() {
11
11
  return [
12
12
  'azm [options] <entry.asm|entry.z80>',
@@ -19,6 +19,7 @@ function usage() {
19
19
  ' --nohex Suppress .hex',
20
20
  ' --nod8m Suppress .d8.json',
21
21
  ' --asm80 Emit assembler-valid lowered source (.z80)',
22
+ ' --source-root <d> Normalize D8 source paths relative to this directory',
22
23
  ' --case-style <m> Case-style lint mode: off|upper|lower|consistent',
23
24
  ' --rc <m> Register-care mode: off|audit|warn|error|strict',
24
25
  ' --reg-report Emit .regcare.txt report',
@@ -132,6 +133,13 @@ function parseCaseStyleArg(arg, argv, indexRef, state) {
132
133
  state.caseStyle = value;
133
134
  return true;
134
135
  }
136
+ function parseSourceRootArg(arg, argv, indexRef, state) {
137
+ const parsed = readMatchedFlagValue(arg, argv, indexRef, ['--source-root', '--map-root']);
138
+ if (!parsed)
139
+ return false;
140
+ state.sourceRoot = parsed.value;
141
+ return true;
142
+ }
135
143
  function parseRegisterCareArg(arg, argv, indexRef, state) {
136
144
  const parsed = readMatchedFlagValue(arg, argv, indexRef, ['--register-care', '--rc']);
137
145
  if (!parsed)
@@ -199,11 +207,7 @@ function handleCliFastPath(arg) {
199
207
  return { code: 0 };
200
208
  }
201
209
  if (arg === '-V' || arg === '--version') {
202
- const require = createRequire(import.meta.url);
203
- const here = dirname(fileURLToPath(import.meta.url));
204
- const packageJsonPath = resolve(here, '..', '..', 'package.json');
205
- const pkg = require(packageJsonPath);
206
- process.stdout.write(`${String(pkg.version ?? '0.0.0')}\n`);
210
+ process.stdout.write(`${readPackageVersion()}\n`);
207
211
  return { code: 0 };
208
212
  }
209
213
  return undefined;
@@ -239,6 +243,7 @@ function finalizeCliOptions(state) {
239
243
  entryFile: state.entryFile,
240
244
  ...(state.outputPath ? { outputPath: state.outputPath } : {}),
241
245
  outputType: state.outputType,
246
+ ...(state.sourceRoot !== undefined ? { sourceRoot: state.sourceRoot } : {}),
242
247
  emitBin: state.emitBin,
243
248
  emitHex: state.emitHex,
244
249
  emitD8m: state.emitD8m,
@@ -293,6 +298,8 @@ export function parseCliArgs(argv) {
293
298
  }
294
299
  if (parseCaseStyleArg(arg, argv, indexRef, state))
295
300
  continue;
301
+ if (parseSourceRootArg(arg, argv, indexRef, state))
302
+ continue;
296
303
  if (parseDirectiveAliasFileArg(arg, argv, indexRef, state))
297
304
  continue;
298
305
  if (parseRegisterCareArg(arg, argv, indexRef, state))
@@ -457,6 +464,9 @@ export async function runCli(argv) {
457
464
  if ('code' in parsed)
458
465
  return parsed.code;
459
466
  const base = artifactBase(parsed.entryFile, parsed.outputType, parsed.outputPath);
467
+ const hexPath = `${base}.hex`;
468
+ const binPath = `${base}.bin`;
469
+ const lstPath = `${base}.lst`;
460
470
  const compileOptions = {
461
471
  emitBin: parsed.emitBin,
462
472
  emitHex: parsed.emitHex,
@@ -466,6 +476,12 @@ export async function runCli(argv) {
466
476
  caseStyle: parsed.caseStyle,
467
477
  includeDirs: parsed.includeDirs,
468
478
  directiveAliasFiles: parsed.directiveAliasFiles,
479
+ ...(parsed.sourceRoot !== undefined ? { sourceRoot: parsed.sourceRoot } : {}),
480
+ d8mInputs: {
481
+ ...(parsed.sourceRoot !== undefined && parsed.emitListing ? { listing: lstPath } : {}),
482
+ ...(parsed.sourceRoot !== undefined && parsed.emitHex ? { hex: hexPath } : {}),
483
+ ...(parsed.sourceRoot !== undefined && parsed.emitBin ? { bin: binPath } : {}),
484
+ },
469
485
  requireMain: false,
470
486
  defaultCodeBase: 0,
471
487
  registerCare: parsed.registerCare,
@@ -9,6 +9,7 @@ import { emitProgram } from './lowering/emit.js';
9
9
  import { loadProgram } from './sourceLoader.js';
10
10
  import { analyzeRegisterCare } from './registerCare/analyze.js';
11
11
  import { parseInterfaceContracts } from './registerCare/smartComments.js';
12
+ import { readPackageVersion } from './packageInfo.js';
12
13
  function withDefaults(options) {
13
14
  const anyPrimaryEmitSpecified = [options.emitBin, options.emitHex, options.emitD8m].some((v) => v !== undefined);
14
15
  const emitBin = anyPrimaryEmitSpecified ? (options.emitBin ?? false) : true;
@@ -135,8 +136,18 @@ export const compile = async (entryFile, options, deps) => {
135
136
  }
136
137
  if (emit.emitD8m) {
137
138
  const mainEntry = symbols.find((s) => s.kind === 'label' && s.name.toLowerCase() === 'main');
139
+ const d8mRoot = options.sourceRoot ?? dirname(entryPath);
138
140
  artifacts.push(deps.formats.writeD8m(map, symbols, {
139
- rootDir: dirname(entryPath),
141
+ rootDir: d8mRoot,
142
+ packageVersion: readPackageVersion(),
143
+ inputs: {
144
+ entry: entryPath,
145
+ ...(options.d8mInputs?.listing !== undefined
146
+ ? { listing: options.d8mInputs.listing }
147
+ : {}),
148
+ ...(options.d8mInputs?.hex !== undefined ? { hex: options.d8mInputs.hex } : {}),
149
+ ...(options.d8mInputs?.bin !== undefined ? { bin: options.d8mInputs.bin } : {}),
150
+ },
140
151
  ...(mainEntry
141
152
  ? {
142
153
  entrySymbol: mainEntry.name,
@@ -82,6 +82,21 @@ export interface WriteD8mOptions {
82
82
  * When provided, file paths are made project-relative and use `/` separators.
83
83
  */
84
84
  rootDir?: string;
85
+ /**
86
+ * AZM package version to record in generator metadata.
87
+ */
88
+ packageVersion?: string;
89
+ /**
90
+ * Source/output paths used to produce this map.
91
+ *
92
+ * Paths are normalized with `rootDir` when present.
93
+ */
94
+ inputs?: {
95
+ entry?: string;
96
+ listing?: string;
97
+ hex?: string;
98
+ bin?: string;
99
+ };
85
100
  /**
86
101
  * Optional runnable entry symbol metadata for harnesses.
87
102
  */
@@ -12,6 +12,14 @@ function normalizeD8mPath(file, rootDir) {
12
12
  }
13
13
  return rel.replace(/\\/g, '/');
14
14
  }
15
+ function normalizeD8mInputs(inputs, rootDir) {
16
+ if (!inputs)
17
+ return undefined;
18
+ const entries = Object.entries(inputs)
19
+ .filter((entry) => typeof entry[1] === 'string' && entry[1] !== '')
20
+ .map(([key, value]) => [key, normalizeD8mPath(value, rootDir)]);
21
+ return entries.length > 0 ? Object.fromEntries(entries) : undefined;
22
+ }
15
23
  function toSerializedSymbol(symbol) {
16
24
  if (symbol.kind === 'constant') {
17
25
  return {
@@ -213,6 +221,7 @@ export function writeD8m(map, symbols, opts) {
213
221
  ...(entry.symbols.length > 0 ? { symbols: entry.symbols } : {}),
214
222
  },
215
223
  ]));
224
+ const generatorInputs = normalizeD8mInputs(opts?.inputs, opts?.rootDir);
216
225
  const json = {
217
226
  format: 'd8-debug-map',
218
227
  version: 1,
@@ -223,17 +232,14 @@ export function writeD8m(map, symbols, opts) {
223
232
  segments,
224
233
  ...(fileList.length > 0 ? { fileList } : {}),
225
234
  symbols: serializedSymbols,
226
- ...(opts?.entrySymbol !== undefined || opts?.entryAddress !== undefined
227
- ? {
228
- generator: {
229
- tool: 'azm',
230
- ...(opts.entrySymbol !== undefined ? { entrySymbol: opts.entrySymbol } : {}),
231
- ...(opts.entryAddress !== undefined
232
- ? { entryAddress: opts.entryAddress & 0xffff }
233
- : {}),
234
- },
235
- }
236
- : {}),
235
+ generator: {
236
+ name: 'azm',
237
+ tool: 'azm',
238
+ ...(opts?.packageVersion !== undefined ? { version: opts.packageVersion } : {}),
239
+ ...(generatorInputs !== undefined ? { inputs: generatorInputs } : {}),
240
+ ...(opts?.entrySymbol !== undefined ? { entrySymbol: opts.entrySymbol } : {}),
241
+ ...(opts?.entryAddress !== undefined ? { entryAddress: opts.entryAddress & 0xffff } : {}),
242
+ },
237
243
  };
238
244
  return { kind: 'd8m', json };
239
245
  }
@@ -0,0 +1 @@
1
+ export declare function readPackageVersion(): string;
@@ -0,0 +1,15 @@
1
+ import { createRequire } from 'node:module';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ export function readPackageVersion() {
5
+ const require = createRequire(import.meta.url);
6
+ const here = dirname(fileURLToPath(import.meta.url));
7
+ let pkg;
8
+ try {
9
+ pkg = require(resolve(here, '..', 'package.json'));
10
+ }
11
+ catch {
12
+ pkg = require(resolve(here, '..', '..', 'package.json'));
13
+ }
14
+ return String(pkg.version ?? '0.0.0');
15
+ }
@@ -19,6 +19,21 @@ export interface CompilerOptions {
19
19
  outputPath?: string;
20
20
  /** Primary output type (future). */
21
21
  outputType?: 'hex' | 'bin';
22
+ /**
23
+ * Root used for source paths in debug maps.
24
+ *
25
+ * When present, D8 file keys and generator input paths are emitted relative
26
+ * to this directory with `/` separators.
27
+ */
28
+ sourceRoot?: string;
29
+ /**
30
+ * Output paths recorded in D8 generator metadata when known by the caller.
31
+ */
32
+ d8mInputs?: {
33
+ listing?: string;
34
+ hex?: string;
35
+ bin?: string;
36
+ };
22
37
  /** Emit flat binary (`.bin`). */
23
38
  emitBin?: boolean;
24
39
  /** Emit Intel HEX (`.hex`). */
@@ -40,6 +40,12 @@ Load project directive aliases:
40
40
  azm --aliases azm.aliases.json program.asm
41
41
  ```
42
42
 
43
+ Normalize Debug80 map source paths against the project root:
44
+
45
+ ```sh
46
+ azm --source-root . --output build/program.hex src/program.asm
47
+ ```
48
+
43
49
  ## Output Artifacts
44
50
 
45
51
  By default AZM writes the primary output plus useful side artifacts using the
@@ -73,6 +79,7 @@ azm --nobin --nohex --reg-report --rc audit program.asm
73
79
  | `--nohex` | Do not write `.hex`. |
74
80
  | `--nod8m` | Do not write `.d8.json`. |
75
81
  | `--asm80` | Write lowered assembler source as `.z80`. |
82
+ | `--source-root <dir>` | Emit project-relative source paths in `.d8.json`. |
76
83
  | `--case-style <mode>` | Lint opcode/register case: `off`, `upper`, `lower`, or `consistent`. |
77
84
  | `--rc, --register-care <mode>` | Register-care mode: `off`, `audit`, `warn`, `error`, or `strict`. |
78
85
  | `--reg-report, --emit-register-report` | Write `.regcare.txt`. |
@@ -87,6 +94,14 @@ azm --nobin --nohex --reg-report --rc audit program.asm
87
94
  | `-V, --version` | Print package version. |
88
95
  | `-h, --help` | Print CLI help. |
89
96
 
97
+ ## Debug80 Maps
98
+
99
+ The `.d8.json` artifact records AZM as the generator, the package version, and
100
+ the input/output paths used for the map. When `--source-root` is supplied, file
101
+ keys and generator input paths are written relative to that directory with `/`
102
+ separators. Constants are emitted as `value` metadata without fake addresses;
103
+ labels and addressable data carry `address`.
104
+
90
105
  ## Register-Care Examples
91
106
 
92
107
  Audit inferred contracts without failing the build:
@@ -58,6 +58,12 @@ const result = await compile(
58
58
  '/abs/path/to/main.asm',
59
59
  {
60
60
  includeDirs: ['/abs/path/to/includes'],
61
+ sourceRoot: '/abs/path/to/project',
62
+ d8mInputs: {
63
+ listing: '/abs/path/to/project/build/main.lst',
64
+ hex: '/abs/path/to/project/build/main.hex',
65
+ bin: '/abs/path/to/project/build/main.bin',
66
+ },
61
67
  outputType: 'hex',
62
68
  emitBin: true,
63
69
  emitHex: true,
@@ -84,6 +90,12 @@ The integration contract is:
84
90
  writing those artifacts to disk
85
91
  - Debug80 should consume the `d8m` artifact for source/address metadata and the
86
92
  `bin` or `hex` artifact for loadable bytes
93
+ - pass `sourceRoot` so D8 file keys are stable project-relative source paths
94
+ rather than basename-only paths
95
+ - pass `d8mInputs` when Debug80 knows the intended artifact paths; AZM records
96
+ those under `generator.inputs`
97
+ - D8 constants use `value` without `address`; only labels and addressable data
98
+ are breakpoint anchors
87
99
  - diagnostics are data objects and should be displayed directly rather than
88
100
  parsed from CLI text
89
101
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jhlagado/azm",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "AZM assembler for the Z80 family (Node.js CLI)",
5
5
  "license": "GPL-3.0-only",
6
6
  "engines": {