@jhlagado/azm 0.2.11 → 0.2.13

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.
Files changed (52) hide show
  1. package/dist/src/api-compile.d.ts +7 -1
  2. package/dist/src/api-compile.js +17 -5
  3. package/dist/src/api-register-contracts.js +69 -2
  4. package/dist/src/api-tooling.d.ts +1 -1
  5. package/dist/src/assembly/import-visibility.js +108 -33
  6. package/dist/src/cli/artifact-files.d.ts +1 -0
  7. package/dist/src/cli/artifact-files.js +5 -0
  8. package/dist/src/cli/parse-args.d.ts +6 -1
  9. package/dist/src/cli/parse-args.js +59 -0
  10. package/dist/src/cli/run.js +2 -2
  11. package/dist/src/cli/usage.js +5 -0
  12. package/dist/src/cli/write-artifacts.d.ts +1 -1
  13. package/dist/src/cli/write-artifacts.js +15 -2
  14. package/dist/src/core/compile.js +1 -0
  15. package/dist/src/expansion/op-expand-selected.js +8 -1
  16. package/dist/src/expansion/op-expansion.d.ts +3 -0
  17. package/dist/src/expansion/op-expansion.js +12 -1
  18. package/dist/src/index.d.ts +1 -1
  19. package/dist/src/node/source-host.js +5 -2
  20. package/dist/src/outputs/types.d.ts +13 -1
  21. package/dist/src/register-contracts/analyze-helpers.d.ts +6 -1
  22. package/dist/src/register-contracts/analyze-helpers.js +67 -0
  23. package/dist/src/register-contracts/analyze.d.ts +8 -1
  24. package/dist/src/register-contracts/analyze.js +353 -16
  25. package/dist/src/register-contracts/interfaceContracts.js +45 -0
  26. package/dist/src/register-contracts/liveness.js +23 -0
  27. package/dist/src/register-contracts/policy.d.ts +2 -0
  28. package/dist/src/register-contracts/policy.js +54 -0
  29. package/dist/src/register-contracts/programModel-boundaries.d.ts +5 -1
  30. package/dist/src/register-contracts/programModel-boundaries.js +20 -5
  31. package/dist/src/register-contracts/programModel-routines.js +37 -6
  32. package/dist/src/register-contracts/ratchet.d.ts +3 -0
  33. package/dist/src/register-contracts/ratchet.js +88 -0
  34. package/dist/src/register-contracts/report.d.ts +8 -1
  35. package/dist/src/register-contracts/report.js +174 -0
  36. package/dist/src/register-contracts/smartCommentParsing.js +22 -0
  37. package/dist/src/register-contracts/summary-boundary.js +9 -2
  38. package/dist/src/register-contracts/tooling.d.ts +2 -1
  39. package/dist/src/register-contracts/tooling.js +2 -0
  40. package/dist/src/register-contracts/types.d.ts +157 -0
  41. package/dist/src/source/logical-lines.d.ts +1 -0
  42. package/dist/src/source/source-span.d.ts +1 -0
  43. package/dist/src/syntax/parse-layout-declarations.js +1 -0
  44. package/dist/src/syntax/parse-line.js +4 -0
  45. package/docs/codebase/02-source-loading-and-parsing.md +10 -6
  46. package/docs/codebase/04-ops-and-register-contracts.md +49 -4
  47. package/docs/codebase/05-interfaces-and-output-artifacts.md +56 -6
  48. package/docs/codebase/06-verification-and-maintenance.md +10 -2
  49. package/docs/codebase/appendices/a-directory-file-reference.md +3 -1
  50. package/docs/codebase/appendices/b-compile-flow-reference.md +7 -5
  51. package/docs/codebase/appendices/c-public-surface-reference.md +19 -5
  52. package/package.json +1 -1
@@ -46,14 +46,20 @@ export function artifactBase(entryFile, outputType, outputPath) {
46
46
  const entryExt = extname(resolvedEntry);
47
47
  return entryExt.length > 0 ? resolvedEntry.slice(0, -entryExt.length) : resolvedEntry;
48
48
  }
49
- export async function writeArtifacts(base, artifacts, outputType) {
49
+ export async function writeArtifacts(base, artifacts, outputType, registerContractsReportFormat = 'text') {
50
+ const registerContractsReportExt = registerContractsReportFormat === 'json' ? 'json' : 'txt';
51
+ const inference = artifacts.find((artifact) => artifact.kind === 'register-contracts-inference');
52
+ const registerContractsInferenceExt = inference?.kind === 'register-contracts-inference' && inference.format === 'markdown'
53
+ ? 'md'
54
+ : 'json';
50
55
  const result = await writeArtifactFiles(artifacts, {
51
56
  hex: `${base}.hex`,
52
57
  bin: `${base}.bin`,
53
58
  d8m: `${base}.d8.json`,
54
59
  asm80: `${base}.z80`,
55
- registerContractsReport: `${base}.regcontracts.txt`,
60
+ registerContractsReport: `${base}.regcontracts.${registerContractsReportExt}`,
56
61
  registerContractsInterface: `${base}.asmi`,
62
+ registerContractsInference: `${base}.regcontracts.inference.${registerContractsInferenceExt}`,
57
63
  }, outputType);
58
64
  return result.primaryPath ?? result.registerContractsPath;
59
65
  }
@@ -70,7 +76,14 @@ export function buildCompileOptions(parsed, base) {
70
76
  caseStyle: parsed.caseStyle,
71
77
  registerContracts: parsed.registerContracts,
72
78
  emitRegisterReport: parsed.emitRegisterReport,
79
+ registerContractsReportFormat: parsed.registerContractsReportFormat,
80
+ ...(parsed.registerContractsBaseline !== undefined
81
+ ? { registerContractsBaseline: parsed.registerContractsBaseline }
82
+ : {}),
83
+ registerContractsRatchet: parsed.registerContractsRatchet,
73
84
  emitRegisterInterface: parsed.emitRegisterInterface,
85
+ emitRegisterInference: parsed.emitRegisterInference,
86
+ registerContractsInferenceFormat: parsed.registerContractsInferenceFormat,
74
87
  emitRegisterAnnotations: parsed.emitRegisterAnnotations,
75
88
  fixRegisterContracts: parsed.fixRegisterContracts,
76
89
  acceptRegisterOutputCandidates: parsed.acceptRegisterOutputCandidates,
@@ -158,6 +158,7 @@ function spanAt(line, column) {
158
158
  column,
159
159
  ...(line.sourceUnit !== undefined ? { sourceUnit: line.sourceUnit } : {}),
160
160
  ...(line.sourceRelation !== undefined ? { sourceRelation: line.sourceRelation } : {}),
161
+ ...(line.sourceUnitRelation !== undefined ? { sourceUnitRelation: line.sourceUnitRelation } : {}),
161
162
  };
162
163
  }
163
164
  function firstColumn(text) {
@@ -60,7 +60,14 @@ function expandTemplateItem(ops, item, bindings, line, diagnostics, overload, ex
60
60
  }
61
61
  function opEmittedSource(line) {
62
62
  return {
63
- span: { sourceName: line.sourceName, line: line.line, column: firstColumn(line.text) },
63
+ span: {
64
+ sourceName: line.sourceName,
65
+ line: line.line,
66
+ column: firstColumn(line.text),
67
+ ...(line.sourceUnit !== undefined ? { sourceUnit: line.sourceUnit } : {}),
68
+ ...(line.sourceRelation !== undefined ? { sourceRelation: line.sourceRelation } : {}),
69
+ ...(line.sourceUnitRelation !== undefined ? { sourceUnitRelation: line.sourceUnitRelation } : {}),
70
+ },
64
71
  kind: 'macro',
65
72
  };
66
73
  }
@@ -6,6 +6,9 @@ export type LogicalLineLike = {
6
6
  readonly sourceName: string;
7
7
  readonly line: number;
8
8
  readonly text: string;
9
+ readonly sourceUnit?: string;
10
+ readonly sourceRelation?: 'entry' | 'include' | 'import';
11
+ readonly sourceUnitRelation?: 'entry' | 'include' | 'import';
9
12
  };
10
13
  interface OpParam {
11
14
  readonly name: string;
@@ -127,7 +127,18 @@ function parseOpBodyTemplates(line, paramNames, diagnostics, parseOptions) {
127
127
  kind: 'label',
128
128
  name: label.name,
129
129
  ...(label.isEntry ? { isEntry: true } : {}),
130
- span: { sourceName: segmentLine.sourceName, line: segmentLine.line, column: label.labelColumn },
130
+ span: {
131
+ sourceName: segmentLine.sourceName,
132
+ line: segmentLine.line,
133
+ column: label.labelColumn,
134
+ ...(segmentLine.sourceUnit !== undefined ? { sourceUnit: segmentLine.sourceUnit } : {}),
135
+ ...(segmentLine.sourceRelation !== undefined
136
+ ? { sourceRelation: segmentLine.sourceRelation }
137
+ : {}),
138
+ ...(segmentLine.sourceUnitRelation !== undefined
139
+ ? { sourceUnitRelation: segmentLine.sourceUnitRelation }
140
+ : {}),
141
+ },
131
142
  },
132
143
  ],
133
144
  }),
@@ -11,5 +11,5 @@ export { compile, defaultFormatWriters, writeHex } from './api-compile.js';
11
11
  export type { AddressRange, Artifact, CompileDependencies, CompileFunctionOptions, CompileResult, CompileNextDependencies, CompileNextFunctionOptions, EmittedByteMap, FormatWriters, CompileNextResult as CompileNextProgramResult, } from './api-compile.js';
12
12
  export type { AnalyzeProgramOptions, AnalyzeProgramResult, LoadProgramOptions, LoadProgramResult, LoadedProgram, AnalyzeProgramNextOptions, AnalyzeProgramNextResult, LoadProgramNextOptions, LoadProgramNextResult, LoadedProgramNext, } from './tooling/api.js';
13
13
  export type { CaseStyleMode } from './tooling/case-style.js';
14
- export type { RegisterCareMode, RegisterCareOutputCandidate, RegisterCareUnit, RegisterContractsMode, RegisterContractsOutputCandidate, RegisterContractsUnit, } from './register-contracts/types.js';
14
+ export type { RegisterCareMode, RegisterCareOutputCandidate, RegisterCareUnit, RegisterContractsMode, RegisterContractsFinding, RegisterContractsFindingKind, RegisterContractsInferenceFormat, RegisterContractsInferenceModel, RegisterContractsInferenceRoutine, RegisterContractsOutputCandidate, RegisterContractsPolicy, RegisterContractsPolicyMode, RegisterContractsReportFormat, RegisterContractsUnit, } from './register-contracts/types.js';
15
15
  export type { D8mArtifact, D8mGenerator, D8mJson, D8mFileEntry, D8mFileSymbol, D8mSegment, D8mSymbol, SymbolEntry, WriteD8mOptions, } from './outputs/types.js';
@@ -30,6 +30,7 @@ export async function expandSourceForTooling(options) {
30
30
  sourceStack: [],
31
31
  sourceUnit: entryFile,
32
32
  sourceRelation: 'entry',
33
+ sourceUnitRelation: 'entry',
33
34
  ...(options.preloadedText !== undefined ? { preloadedText: options.preloadedText } : {}),
34
35
  ...(options.signal !== undefined ? { signal: options.signal } : {}),
35
36
  });
@@ -60,7 +61,7 @@ async function expandFile(options) {
60
61
  recordLineComment(options.sourceLineComments, line);
61
62
  const directive = parseSourceLoadDirective(line.text);
62
63
  if (!directive) {
63
- output.push(withSourceOwnership(line, options.sourceUnit, options.sourceRelation));
64
+ output.push(withSourceOwnership(line, options.sourceUnit, options.sourceRelation, options.sourceUnitRelation));
64
65
  continue;
65
66
  }
66
67
  const result = await resolveSourcePath(sourcePath, directive.path, options.includeDirs);
@@ -88,6 +89,7 @@ async function expandFile(options) {
88
89
  sourceStack: [...options.sourceStack, { sourcePath }],
89
90
  sourceUnit: directive.kind === 'import' ? result.resolved : options.sourceUnit,
90
91
  sourceRelation: directive.kind,
92
+ sourceUnitRelation: directive.kind === 'import' ? 'import' : options.sourceUnitRelation,
91
93
  });
92
94
  if (included !== undefined) {
93
95
  output.push(...included);
@@ -95,11 +97,12 @@ async function expandFile(options) {
95
97
  }
96
98
  return output;
97
99
  }
98
- function withSourceOwnership(line, sourceUnit, sourceRelation) {
100
+ function withSourceOwnership(line, sourceUnit, sourceRelation, sourceUnitRelation) {
99
101
  return {
100
102
  ...line,
101
103
  sourceUnit,
102
104
  sourceRelation,
105
+ sourceUnitRelation,
103
106
  };
104
107
  }
105
108
  function recordLineComment(comments, line) {
@@ -1,4 +1,5 @@
1
1
  import type { SourceItem } from '../model/source-item.js';
2
+ import type { RegisterContractsFinding, RegisterContractsInferenceFormat, RegisterContractsInferenceModel, RegisterContractsJsonReportModel, RegisterContractsReportFormat } from '../register-contracts/types.js';
2
3
  /** Half-open address range in the Z80 16-bit address space. */
3
4
  export interface AddressRange {
4
5
  /** Inclusive start address. */
@@ -54,7 +55,10 @@ export interface HexArtifact {
54
55
  export interface RegisterContractsReportArtifact {
55
56
  kind: 'register-contracts-report';
56
57
  path?: string;
58
+ format?: RegisterContractsReportFormat;
57
59
  text: string;
60
+ json?: RegisterContractsJsonReportModel;
61
+ findings?: RegisterContractsFinding[];
58
62
  }
59
63
  /** @deprecated Use RegisterContractsReportArtifact. */
60
64
  export type RegisterCareReportArtifact = RegisterContractsReportArtifact;
@@ -66,6 +70,14 @@ export interface RegisterContractsInterfaceArtifact {
66
70
  }
67
71
  /** @deprecated Use RegisterContractsInterfaceArtifact. */
68
72
  export type RegisterCareInterfaceArtifact = RegisterContractsInterfaceArtifact;
73
+ /** In-memory inferred register contracts review artifact. */
74
+ export interface RegisterContractsInferenceArtifact {
75
+ kind: 'register-contracts-inference';
76
+ path?: string;
77
+ format: RegisterContractsInferenceFormat;
78
+ text: string;
79
+ json?: RegisterContractsInferenceModel;
80
+ }
69
81
  /** In-memory register contracts source annotation artifact. */
70
82
  export interface RegisterContractsAnnotationsArtifact {
71
83
  kind: 'register-contracts-annotations';
@@ -166,7 +178,7 @@ export interface WriteD8mOptions {
166
178
  }
167
179
  export interface WriteAsm80Options {
168
180
  }
169
- export type Artifact = BinArtifact | HexArtifact | D8mArtifact | Asm80Artifact | RegisterContractsReportArtifact | RegisterContractsInterfaceArtifact | RegisterContractsAnnotationsArtifact;
181
+ export type Artifact = BinArtifact | HexArtifact | D8mArtifact | Asm80Artifact | RegisterContractsReportArtifact | RegisterContractsInterfaceArtifact | RegisterContractsInferenceArtifact | RegisterContractsAnnotationsArtifact;
170
182
  /** Writer contract used by the compile API. */
171
183
  export interface FormatWriters {
172
184
  writeHex(map: EmittedByteMap, symbols: readonly SymbolEntry[], opts?: WriteHexOptions): HexArtifact;
@@ -1,6 +1,6 @@
1
1
  import type { Diagnostic } from '../model/diagnostic.js';
2
2
  import { unknownCallList } from './summaries.js';
3
- import type { AnalyzeRegisterContractsOptions, RegisterContractsOutputCandidate, RegisterContractsReportModel, RegisterContractsRoutine, RegisterContractsUnit, RoutineSummary } from './types.js';
3
+ import type { AnalyzeRegisterContractsOptions, RegisterContractsDirectCall, RegisterContractsFinding, RegisterContractsOutputCandidate, RegisterContractsReportModel, RegisterContractsRoutine, RegisterContractsUnit, RoutineSummary } from './types.js';
4
4
  export declare function candidateMessageWithFixability(candidate: RegisterContractsOutputCandidate, autoFixable: boolean): string;
5
5
  export declare function knownRoutineNames(routines: readonly RegisterContractsRoutine[], contractNames: Iterable<string>, profile: AnalyzeRegisterContractsOptions['registerContractsProfile']): Set<string>;
6
6
  export declare function outputCandidatesWithFixability(routines: readonly RegisterContractsRoutine[], outputCandidates: readonly RegisterContractsOutputCandidate[]): {
@@ -14,12 +14,17 @@ export declare function diagnosticsForConflicts(conflicts: readonly {
14
14
  message: string;
15
15
  }[], mode: AnalyzeRegisterContractsOptions['mode']): Diagnostic[];
16
16
  export declare function strictStackDiagnostics(routines: readonly RegisterContractsRoutine[], summaries: readonly RoutineSummary[]): Diagnostic[];
17
+ export declare function strictStackFindings(routines: readonly RegisterContractsRoutine[], summaries: readonly RoutineSummary[]): RegisterContractsFinding[];
18
+ export declare function unknownBoundaryFindings(directBoundaries: readonly RegisterContractsDirectCall[], knownRoutines: ReadonlySet<string>): RegisterContractsFinding[];
19
+ export declare function diagnosticsForFindings(findings: readonly RegisterContractsFinding[], mode: AnalyzeRegisterContractsOptions['mode']): Diagnostic[];
17
20
  export declare function summariesForAnnotations(summariesByName: ReadonlyMap<string, RoutineSummary>, outputCandidates: readonly RegisterContractsOutputCandidate[]): Map<string, RoutineSummary>;
18
21
  export declare function buildRegisterContractsReportModel(input: {
19
22
  entryFile: string;
20
23
  mode: AnalyzeRegisterContractsOptions['mode'];
21
24
  summaries: readonly RoutineSummary[];
22
25
  profileSummaries: readonly RoutineSummary[];
26
+ findings: readonly RegisterContractsFinding[];
27
+ suppressedFindings?: RegisterContractsReportModel['suppressedFindings'];
23
28
  conflicts: RegisterContractsReportModel['conflicts'];
24
29
  outputCandidates: readonly RegisterContractsOutputCandidate[];
25
30
  profile: AnalyzeRegisterContractsOptions['registerContractsProfile'];
@@ -24,6 +24,7 @@ export function outputCandidatesWithFixability(routines, outputCandidates) {
24
24
  const autoFixable = outputCandidateFixability.get(outputCandidateKey(candidate.file, candidate.line, candidate.column)) ?? false;
25
25
  return {
26
26
  ...candidate,
27
+ kind: 'output_candidate',
27
28
  autoFixable,
28
29
  message: candidateMessageWithFixability(candidate, autoFixable),
29
30
  };
@@ -63,6 +64,68 @@ export function strictStackDiagnostics(routines, summaries) {
63
64
  }
64
65
  return diagnostics;
65
66
  }
67
+ export function strictStackFindings(routines, summaries) {
68
+ const routinesByName = new Map(routines.map((routine) => [routine.name, routine]));
69
+ const findings = [];
70
+ for (const summary of summaries) {
71
+ const routine = routinesByName.get(summary.name);
72
+ if (routine === undefined)
73
+ continue;
74
+ const stackIssues = strictStackIssueText(summary);
75
+ if (stackIssues === undefined)
76
+ continue;
77
+ findings.push({
78
+ kind: 'unknown_control_flow',
79
+ routine: summary.name,
80
+ stackBalanced: summary.stackBalanced,
81
+ ...(summary.hasUnknownStackEffect !== undefined
82
+ ? { hasUnknownStackEffect: summary.hasUnknownStackEffect }
83
+ : {}),
84
+ file: routine.span.file,
85
+ line: routine.span.start.line,
86
+ column: routine.span.start.column,
87
+ ...(routine.span.sourceUnit !== undefined ? { sourceUnit: routine.span.sourceUnit } : {}),
88
+ ...(routine.span.sourceRelation !== undefined
89
+ ? { sourceRelation: routine.span.sourceRelation }
90
+ : {}),
91
+ ...(routine.span.sourceUnitRelation !== undefined
92
+ ? { sourceUnitRelation: routine.span.sourceUnitRelation }
93
+ : {}),
94
+ message: `Register contracts cannot prove stack discipline for ${summary.name}: ${stackIssues}. Keep PUSH/POP pairs and stack-changing exits inside one @ routine boundary, or split the code into explicit callable routines.`,
95
+ });
96
+ }
97
+ return findings;
98
+ }
99
+ export function unknownBoundaryFindings(directBoundaries, knownRoutines) {
100
+ return directBoundaries
101
+ .filter((boundary) => !knownRoutines.has(boundary.target))
102
+ .map((boundary) => ({
103
+ kind: 'missing_callee_contract',
104
+ callTarget: boundary.target,
105
+ subject: boundary.subject,
106
+ file: boundary.file,
107
+ line: boundary.line,
108
+ column: boundary.column,
109
+ ...(boundary.sourceUnit !== undefined ? { sourceUnit: boundary.sourceUnit } : {}),
110
+ ...(boundary.sourceRelation !== undefined ? { sourceRelation: boundary.sourceRelation } : {}),
111
+ ...(boundary.sourceUnitRelation !== undefined
112
+ ? { sourceUnitRelation: boundary.sourceUnitRelation }
113
+ : {}),
114
+ message: `Register contracts cannot prove ${boundary.subject}; add a routine body or .asmi extern contract.`,
115
+ }));
116
+ }
117
+ export function diagnosticsForFindings(findings, mode) {
118
+ if (mode === 'audit')
119
+ return [];
120
+ return findings.map((finding) => ({
121
+ severity: mode === 'error' || mode === 'strict' ? 'error' : 'warning',
122
+ code: 'AZMN_REGISTER_CONTRACTS',
123
+ sourceName: finding.file,
124
+ line: finding.line,
125
+ column: finding.column,
126
+ message: finding.message,
127
+ }));
128
+ }
66
129
  function strictStackIssueText(summary) {
67
130
  const issues = [];
68
131
  if (!summary.stackBalanced)
@@ -99,6 +162,10 @@ export function buildRegisterContractsReportModel(input) {
99
162
  entryFile: input.entryFile,
100
163
  mode: input.mode,
101
164
  summaries: [...input.summaries, ...input.profileSummaries],
165
+ findings: [...input.findings],
166
+ ...(input.suppressedFindings !== undefined && input.suppressedFindings.length > 0
167
+ ? { suppressedFindings: [...input.suppressedFindings] }
168
+ : {}),
102
169
  conflicts: [...input.conflicts],
103
170
  outputCandidates: [...input.outputCandidates],
104
171
  ...(input.profile !== undefined ? { profile: input.profile } : {}),
@@ -1,11 +1,18 @@
1
1
  import type { Diagnostic } from '../model/diagnostic.js';
2
2
  import type { SourceItem } from '../model/source-item.js';
3
- import type { AnalyzeRegisterContractsOptions, RegisterContractsAnnotationFile, RegisterContractsOutputCandidate } from './types.js';
3
+ import type { AnalyzeRegisterContractsOptions, RegisterContractsFinding, RegisterContractsAnnotationFile, RegisterContractsJsonReportModel, RegisterContractsOutputCandidate } from './types.js';
4
+ import { buildRegisterContractsInference } from './report.js';
4
5
  interface AnalyzeRegisterContractsResult {
5
6
  diagnostics: Diagnostic[];
7
+ findings?: RegisterContractsFinding[];
6
8
  outputCandidates?: RegisterContractsOutputCandidate[];
7
9
  reportText?: string;
10
+ reportJson?: RegisterContractsJsonReportModel;
11
+ reportFormat?: 'text' | 'json';
8
12
  interfaceText?: string;
13
+ inferenceText?: string;
14
+ inferenceJson?: ReturnType<typeof buildRegisterContractsInference>;
15
+ inferenceFormat?: 'json' | 'markdown';
9
16
  annotations?: readonly RegisterContractsAnnotationFile[];
10
17
  unknownCalls?: string[];
11
18
  }