@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
@@ -3,7 +3,7 @@ import { writeHex } from './outputs/write-hex.js';
3
3
  import type { AddressRange, Artifact, D8mArtifact, D8mFileEntry, D8mFileSymbol, D8mGenerator, D8mJson, D8mSegment, D8mSymbol, EmittedByteMap, FormatWriters, SymbolEntry, WriteD8mOptions } from './outputs/types.js';
4
4
  import type { Diagnostic } from './model/diagnostic.js';
5
5
  import type { CaseStyleMode } from './tooling/case-style.js';
6
- import type { RegisterContractsMode } from './register-contracts/types.js';
6
+ import type { RegisterContractsMode, RegisterContractsPolicy, RegisterContractsReportFormat } from './register-contracts/types.js';
7
7
  export { writeHex, defaultFormatWriters };
8
8
  export type { AddressRange, Artifact, D8mArtifact, D8mFileEntry, D8mFileSymbol, D8mGenerator, D8mJson, D8mSegment, D8mSymbol, EmittedByteMap, FormatWriters, SymbolEntry, WriteD8mOptions, };
9
9
  export type CompileDependencies = CompileNextDependencies;
@@ -30,8 +30,14 @@ export interface CompileNextFunctionOptions {
30
30
  readonly registerContracts?: RegisterContractsMode;
31
31
  /** @deprecated Use registerContracts. */
32
32
  readonly registerCare?: RegisterContractsMode;
33
+ readonly registerContractsPolicy?: RegisterContractsPolicy;
33
34
  readonly emitRegisterReport?: boolean;
35
+ readonly registerContractsReportFormat?: RegisterContractsReportFormat;
36
+ readonly registerContractsBaseline?: string;
37
+ readonly registerContractsRatchet?: boolean;
34
38
  readonly emitRegisterInterface?: boolean;
39
+ readonly emitRegisterInference?: boolean;
40
+ readonly registerContractsInferenceFormat?: 'json' | 'markdown';
35
41
  readonly emitRegisterAnnotations?: boolean;
36
42
  readonly fixRegisterContracts?: boolean;
37
43
  readonly acceptRegisterOutputCandidates?: string[];
@@ -5,12 +5,13 @@ import { runRegisterContracts, shouldAnalyzeRegisterContracts } from './api-regi
5
5
  import { analyzeProgramNext, loadProgramNext } from './tooling/api.js';
6
6
  import { defaultFormatWriters } from './outputs/index.js';
7
7
  import { writeHex } from './outputs/write-hex.js';
8
+ import { registerContractsPolicyModeForFile } from './register-contracts/policy.js';
8
9
  import { buildRegisterContractsProgramModel } from './register-contracts/programModel.js';
9
10
  function parseUnresolvedSymbolName(message) {
10
11
  const match = /^Unresolved symbol "([^"]+)"/.exec(message);
11
12
  return match?.[1];
12
13
  }
13
- function isSuppressedUnknownSymbolInRegisterContractsMode(diagnostic, directCalls) {
14
+ function isSuppressedUnknownSymbolInRegisterContractsMode(diagnostic, directCalls, policy, fallbackMode) {
14
15
  if (directCalls === undefined || directCalls.length === 0) {
15
16
  return false;
16
17
  }
@@ -27,7 +28,11 @@ function isSuppressedUnknownSymbolInRegisterContractsMode(diagnostic, directCall
27
28
  return directCalls.some((call) => call.target === symbol &&
28
29
  call.file === diagnostic.sourceName &&
29
30
  call.line === diagnostic.line &&
30
- call.column === diagnostic.column);
31
+ call.column === diagnostic.column &&
32
+ !isRegisterContractsPolicyOffForFile(call.file, policy, fallbackMode));
33
+ }
34
+ function isRegisterContractsPolicyOffForFile(file, policy, fallbackMode) {
35
+ return policy !== undefined && registerContractsPolicyModeForFile(file, policy, fallbackMode) === 'off';
31
36
  }
32
37
  export { writeHex, defaultFormatWriters };
33
38
  /**
@@ -56,10 +61,14 @@ export async function compile(entryFile, options = {}, deps = { formats: default
56
61
  .directCalls
57
62
  : undefined;
58
63
  diagnostics.push(...analysis.diagnostics.filter((diagnostic) => analyzeRegisterContractsNow
59
- ? !isSuppressedUnknownSymbolInRegisterContractsMode(diagnostic, directCalls)
64
+ ? !isSuppressedUnknownSymbolInRegisterContractsMode(diagnostic, directCalls, options.registerContractsPolicy, options.registerContracts ?? options.registerCare)
60
65
  : true));
61
66
  const artifacts = [];
62
67
  if (hasErrors(diagnostics)) {
68
+ if (analyzeRegisterContractsNow && options.emitRegisterReport === true) {
69
+ const registerContracts = await runRegisterContracts(loaded.loadedProgram, options);
70
+ artifacts.push(...registerContractsReportArtifacts(registerContracts.artifacts));
71
+ }
63
72
  sortDiagnosticsInPlace(diagnostics);
64
73
  return { diagnostics, artifacts };
65
74
  }
@@ -76,11 +85,11 @@ export async function compile(entryFile, options = {}, deps = { formats: default
76
85
  const program = loaded.loadedProgram.program.files[0]?.items ?? [];
77
86
  const assembled = assembleProgram(program);
78
87
  diagnostics.push(...assembled.diagnostics.filter((diagnostic) => analyzeRegisterContractsNow
79
- ? !isSuppressedUnknownSymbolInRegisterContractsMode(diagnostic, directCalls)
88
+ ? !isSuppressedUnknownSymbolInRegisterContractsMode(diagnostic, directCalls, options.registerContractsPolicy, options.registerContracts ?? options.registerCare)
80
89
  : true));
81
90
  sortDiagnosticsInPlace(diagnostics);
82
91
  if (hasErrors(diagnostics)) {
83
- return { diagnostics, artifacts: [] };
92
+ return { diagnostics, artifacts: registerContractsReportArtifacts(artifacts) };
84
93
  }
85
94
  const emittedArtifacts = await emitAssemblyArtifacts({
86
95
  entryFile: normalizedEntry,
@@ -100,6 +109,9 @@ export async function compile(entryFile, options = {}, deps = { formats: default
100
109
  function hasErrors(diagnostics) {
101
110
  return diagnostics.some((diagnostic) => diagnostic.severity === 'error');
102
111
  }
112
+ function registerContractsReportArtifacts(artifacts) {
113
+ return artifacts.filter((artifact) => artifact.kind === 'register-contracts-report');
114
+ }
103
115
  function sortDiagnosticsInPlace(diagnostics) {
104
116
  dedupeDiagnosticsInPlace(diagnostics);
105
117
  diagnostics.sort((left, right) => {
@@ -8,10 +8,13 @@ export function shouldAnalyzeRegisterContracts(options) {
8
8
  return (registerContractsMode !== 'off' ||
9
9
  options.emitRegisterReport === true ||
10
10
  options.emitRegisterInterface === true ||
11
+ options.emitRegisterInference === true ||
11
12
  options.emitRegisterAnnotations === true ||
12
13
  options.fixRegisterContracts === true ||
14
+ options.registerContractsPolicy !== undefined ||
13
15
  (options.acceptRegisterOutputCandidates?.length ?? 0) > 0 ||
14
- (options.registerContractsInterfaces?.length ?? options.registerCareInterfaces?.length ?? 0) > 0);
16
+ (options.registerContractsInterfaces?.length ?? options.registerCareInterfaces?.length ?? 0) > 0 ||
17
+ options.registerContractsBaseline !== undefined);
15
18
  }
16
19
  export async function runRegisterContracts(loadedProgram, options) {
17
20
  const diagnostics = [];
@@ -20,10 +23,24 @@ export async function runRegisterContracts(loadedProgram, options) {
20
23
  if (hasErrors(diagnostics)) {
21
24
  return { diagnostics, artifacts };
22
25
  }
26
+ const baselineReport = await loadBaselineReport(options.registerContractsBaseline, diagnostics);
27
+ if (hasErrors(diagnostics)) {
28
+ return { diagnostics, artifacts };
29
+ }
23
30
  const registerContracts = analyzeRegisterContracts(loadedProgram, {
24
31
  mode: options.registerContracts ?? options.registerCare ?? 'off',
32
+ ...(options.registerContractsPolicy !== undefined
33
+ ? { policy: options.registerContractsPolicy }
34
+ : {}),
25
35
  emitReport: options.emitRegisterReport === true,
36
+ ...(options.registerContractsReportFormat !== undefined
37
+ ? { reportFormat: options.registerContractsReportFormat }
38
+ : {}),
26
39
  emitInterface: options.emitRegisterInterface === true,
40
+ emitInference: options.emitRegisterInference === true,
41
+ ...(options.registerContractsInferenceFormat !== undefined
42
+ ? { inferenceFormat: options.registerContractsInferenceFormat }
43
+ : {}),
27
44
  emitAnnotations: options.emitRegisterAnnotations === true || options.fixRegisterContracts === true,
28
45
  fixRegisterContracts: options.fixRegisterContracts === true,
29
46
  acceptedOutputCandidates: parseAcceptedOutputCandidates(options.acceptRegisterOutputCandidates ?? []),
@@ -33,13 +50,36 @@ export async function runRegisterContracts(loadedProgram, options) {
33
50
  }
34
51
  : {}),
35
52
  ...(interfaceContracts.length > 0 ? { interfaceContracts } : {}),
53
+ ...(baselineReport !== undefined ? { baselineReport } : {}),
54
+ ...(options.registerContractsBaseline !== undefined
55
+ ? { baselineFile: normalize(options.registerContractsBaseline) }
56
+ : {}),
57
+ ratchet: options.registerContractsRatchet === true,
36
58
  });
37
59
  if (registerContracts.reportText !== undefined) {
38
- artifacts.push({ kind: 'register-contracts-report', text: registerContracts.reportText });
60
+ artifacts.push({
61
+ kind: 'register-contracts-report',
62
+ ...(registerContracts.reportFormat !== undefined
63
+ ? { format: registerContracts.reportFormat }
64
+ : {}),
65
+ text: registerContracts.reportText,
66
+ ...(registerContracts.reportJson !== undefined ? { json: registerContracts.reportJson } : {}),
67
+ ...(registerContracts.findings !== undefined ? { findings: registerContracts.findings } : {}),
68
+ });
39
69
  }
40
70
  if (registerContracts.interfaceText !== undefined) {
41
71
  artifacts.push({ kind: 'register-contracts-interface', text: registerContracts.interfaceText });
42
72
  }
73
+ if (registerContracts.inferenceText !== undefined) {
74
+ artifacts.push({
75
+ kind: 'register-contracts-inference',
76
+ format: registerContracts.inferenceFormat ?? 'json',
77
+ text: registerContracts.inferenceText,
78
+ ...(registerContracts.inferenceJson !== undefined
79
+ ? { json: registerContracts.inferenceJson }
80
+ : {}),
81
+ });
82
+ }
43
83
  if (registerContracts.annotations !== undefined && registerContracts.annotations.length > 0) {
44
84
  artifacts.push({
45
85
  kind: 'register-contracts-annotations',
@@ -52,6 +92,33 @@ export async function runRegisterContracts(loadedProgram, options) {
52
92
  diagnostics.push(...registerContracts.diagnostics);
53
93
  return { artifacts, diagnostics };
54
94
  }
95
+ async function loadBaselineReport(rawPath, diagnostics) {
96
+ if (rawPath === undefined)
97
+ return undefined;
98
+ const baselinePath = normalize(rawPath);
99
+ try {
100
+ const parsed = JSON.parse(await readFile(baselinePath, 'utf8'));
101
+ if (parsed.format !== 'azm-register-contracts-report' || !Array.isArray(parsed.findings)) {
102
+ diagnostics.push({
103
+ severity: 'error',
104
+ code: 'AZMN_REGISTER_CONTRACTS',
105
+ sourceName: baselinePath,
106
+ message: 'Register contracts baseline must be a JSON register-contracts report',
107
+ });
108
+ return undefined;
109
+ }
110
+ return parsed;
111
+ }
112
+ catch (error) {
113
+ diagnostics.push({
114
+ severity: 'error',
115
+ code: 'AZMN_REGISTER_CONTRACTS',
116
+ sourceName: baselinePath,
117
+ message: `Unable to read register contracts baseline: ${String(error)}`,
118
+ });
119
+ return undefined;
120
+ }
121
+ }
55
122
  async function loadInterfaceContracts(interfaces, diagnostics) {
56
123
  const interfaceContracts = [];
57
124
  for (const rawInterface of interfaces) {
@@ -4,4 +4,4 @@ export { DiagnosticIds } from './model/diagnostic.js';
4
4
  export type { AnalyzeProgramOptions, AnalyzeProgramResult, LoadedProgram, LoadProgramOptions, LoadProgramResult, AnalyzeProgramNextOptions, AnalyzeProgramNextResult, LoadedProgramNext, LoadProgramNextOptions, LoadProgramNextResult, } from './tooling/api.js';
5
5
  export type { CaseStyleMode } from './tooling/case-style.js';
6
6
  export type { Diagnostic, DiagnosticId, DiagnosticSeverity } from './model/diagnostic.js';
7
- export type { RegisterCareMode, RegisterCareOutputCandidate, RegisterCareUnit, RegisterContractsMode, RegisterContractsOutputCandidate, RegisterContractsUnit, } from './register-contracts/types.js';
7
+ export type { RegisterCareMode, RegisterCareOutputCandidate, RegisterCareUnit, RegisterContractsMode, RegisterContractsFinding, RegisterContractsFindingKind, RegisterContractsOutputCandidate, RegisterContractsUnit, } from './register-contracts/types.js';
@@ -1,14 +1,22 @@
1
1
  import { diagnostic } from '../semantics/diagnostics.js';
2
2
  export function validateImportVisibility(items, diagnostics) {
3
- const labels = collectLabelVisibility(items);
3
+ const symbols = collectSymbolVisibility(items);
4
4
  for (const item of items) {
5
- validateItemReferences(item, labels, diagnostics);
5
+ validateItemReferences(item, symbols, diagnostics);
6
6
  }
7
7
  }
8
- function collectLabelVisibility(items) {
8
+ function collectSymbolVisibility(items) {
9
9
  const labels = new Map();
10
+ const exactSymbols = new Set();
10
11
  const importedSourceUnits = importedUnitNames(items);
12
+ const symbolConflicts = buildSymbolConflictIndex(items);
11
13
  for (const item of items) {
14
+ for (const name of exactSymbolNames(item)) {
15
+ exactSymbols.add(name);
16
+ }
17
+ }
18
+ for (let index = 0; index < items.length; index += 1) {
19
+ const item = items[index];
12
20
  if (item.kind !== 'label')
13
21
  continue;
14
22
  labels.set(item.name, {
@@ -16,14 +24,75 @@ function collectLabelVisibility(items) {
16
24
  definingSourceUnit: item.span.sourceUnit,
17
25
  definingSourceName: item.span.sourceName,
18
26
  public: isPublicLabel(item, importedSourceUnits),
27
+ duplicateName: hasAddressPlanningNameConflict(item.name, symbolConflicts, items, index),
19
28
  });
20
29
  }
21
- return labels;
30
+ return { labels, exactSymbols };
31
+ }
32
+ function buildSymbolConflictIndex(items) {
33
+ const exact = new Map();
34
+ const declarationLower = new Map();
35
+ for (const item of items) {
36
+ for (const name of exactSymbolNames(item)) {
37
+ exact.set(name, (exact.get(name) ?? 0) + 1);
38
+ }
39
+ const declarationName = caseInsensitiveDeclarationName(item);
40
+ if (declarationName !== undefined) {
41
+ const key = declarationName.toLowerCase();
42
+ declarationLower.set(key, (declarationLower.get(key) ?? 0) + 1);
43
+ }
44
+ }
45
+ return { exact, declarationLower };
46
+ }
47
+ function hasAddressPlanningNameConflict(labelName, conflicts, items, labelIndex) {
48
+ return ((conflicts.exact.get(labelName) ?? 0) > 1 ||
49
+ (conflicts.declarationLower.get(labelName.toLowerCase()) ?? 0) > 0 ||
50
+ hasReportedEnumMemberConflict(labelName, items, labelIndex));
51
+ }
52
+ function hasReportedEnumMemberConflict(labelName, items, labelIndex) {
53
+ const lowerName = labelName.toLowerCase();
54
+ for (let index = 0; index < items.length; index += 1) {
55
+ const item = items[index];
56
+ if (item.kind !== 'enum')
57
+ continue;
58
+ for (const memberName of qualifiedEnumMemberNames(item)) {
59
+ if (memberName === labelName)
60
+ return true;
61
+ if (index > labelIndex && memberName.toLowerCase() === lowerName)
62
+ return true;
63
+ }
64
+ }
65
+ return false;
66
+ }
67
+ function exactSymbolNames(item) {
68
+ switch (item.kind) {
69
+ case 'label':
70
+ case 'equ':
71
+ return [item.name];
72
+ case 'enum':
73
+ return qualifiedEnumMemberNames(item);
74
+ default:
75
+ return [];
76
+ }
77
+ }
78
+ function qualifiedEnumMemberNames(item) {
79
+ return item.kind === 'enum' ? item.members.map((member) => `${item.name}.${member}`) : [];
80
+ }
81
+ function caseInsensitiveDeclarationName(item) {
82
+ switch (item.kind) {
83
+ case 'enum':
84
+ case 'type':
85
+ case 'type-alias':
86
+ return item.name;
87
+ default:
88
+ return undefined;
89
+ }
22
90
  }
23
91
  function importedUnitNames(items) {
24
92
  const units = new Set();
25
93
  for (const item of items) {
26
- if (item.span.sourceRelation === 'import' && item.span.sourceUnit !== undefined) {
94
+ if (item.span.sourceUnitRelation === 'import' &&
95
+ item.span.sourceUnit !== undefined) {
27
96
  units.add(item.span.sourceUnit);
28
97
  }
29
98
  }
@@ -34,39 +103,39 @@ function isPublicLabel(item, importedSourceUnits) {
34
103
  item.span.sourceUnit === undefined ||
35
104
  !importedSourceUnits.has(item.span.sourceUnit));
36
105
  }
37
- function validateItemReferences(item, labels, diagnostics) {
106
+ function validateItemReferences(item, symbols, diagnostics) {
38
107
  switch (item.kind) {
39
108
  case 'org':
40
- validateExpression(item.expression, item.span, labels, diagnostics);
109
+ validateExpression(item.expression, item.span, symbols, diagnostics);
41
110
  return;
42
111
  case 'equ':
43
- validateExpression(item.expression, item.span, labels, diagnostics);
112
+ validateExpression(item.expression, item.span, symbols, diagnostics);
44
113
  return;
45
114
  case 'db':
46
115
  for (const value of item.values) {
47
- validateDataValue(value, item.span, labels, diagnostics);
116
+ validateDataValue(value, item.span, symbols, diagnostics);
48
117
  }
49
118
  return;
50
119
  case 'dw':
51
120
  for (const value of item.values) {
52
- validateExpression(value, item.span, labels, diagnostics);
121
+ validateExpression(value, item.span, symbols, diagnostics);
53
122
  }
54
123
  return;
55
124
  case 'ds':
56
- validateExpression(item.size, item.span, labels, diagnostics);
125
+ validateExpression(item.size, item.span, symbols, diagnostics);
57
126
  if (item.fill !== undefined) {
58
- validateExpression(item.fill, item.span, labels, diagnostics);
127
+ validateExpression(item.fill, item.span, symbols, diagnostics);
59
128
  }
60
129
  return;
61
130
  case 'align':
62
- validateExpression(item.alignment, item.span, labels, diagnostics);
131
+ validateExpression(item.alignment, item.span, symbols, diagnostics);
63
132
  return;
64
133
  case 'binfrom':
65
134
  case 'binto':
66
- validateExpression(item.expression, item.span, labels, diagnostics);
135
+ validateExpression(item.expression, item.span, symbols, diagnostics);
67
136
  return;
68
137
  case 'instruction':
69
- validateInstruction(item.instruction, item.span, labels, diagnostics);
138
+ validateInstruction(item.instruction, item.span, symbols, diagnostics);
70
139
  return;
71
140
  case 'label':
72
141
  case 'comment':
@@ -78,14 +147,14 @@ function validateItemReferences(item, labels, diagnostics) {
78
147
  return;
79
148
  }
80
149
  }
81
- function validateDataValue(value, span, labels, diagnostics) {
150
+ function validateDataValue(value, span, symbols, diagnostics) {
82
151
  if ('kind' in value && value.kind === 'string-fragment')
83
152
  return;
84
- validateExpression(value, span, labels, diagnostics);
153
+ validateExpression(value, span, symbols, diagnostics);
85
154
  }
86
- function validateInstruction(instruction, span, labels, diagnostics) {
155
+ function validateInstruction(instruction, span, symbols, diagnostics) {
87
156
  for (const expression of instructionExpressions(instruction)) {
88
- validateExpression(expression, span, labels, diagnostics);
157
+ validateExpression(expression, span, symbols, diagnostics);
89
158
  }
90
159
  }
91
160
  function instructionExpressions(instruction) {
@@ -154,49 +223,55 @@ function operandExpressions(operand) {
154
223
  return [];
155
224
  }
156
225
  }
157
- function validateExpression(expression, span, labels, diagnostics) {
226
+ function validateExpression(expression, span, symbols, diagnostics) {
158
227
  switch (expression.kind) {
159
228
  case 'symbol':
160
- validateSymbolReference(expression.name, span, labels, diagnostics);
229
+ validateSymbolReference(expression.name, span, symbols, diagnostics);
161
230
  return;
162
231
  case 'byte-function':
163
232
  case 'unary':
164
- validateExpression(expression.expression, span, labels, diagnostics);
233
+ validateExpression(expression.expression, span, symbols, diagnostics);
165
234
  return;
166
235
  case 'binary':
167
- validateExpression(expression.left, span, labels, diagnostics);
168
- validateExpression(expression.right, span, labels, diagnostics);
236
+ validateExpression(expression.left, span, symbols, diagnostics);
237
+ validateExpression(expression.right, span, symbols, diagnostics);
169
238
  return;
170
239
  case 'layout-cast':
171
- validateExpression(expression.base, span, labels, diagnostics);
240
+ validateExpression(expression.base, span, symbols, diagnostics);
172
241
  for (const part of expression.path) {
173
242
  if (part.kind === 'index') {
174
- validateExpression(part.expression, span, labels, diagnostics);
243
+ validateExpression(part.expression, span, symbols, diagnostics);
175
244
  }
176
245
  }
177
246
  return;
178
247
  case 'number':
179
248
  case 'current-location':
180
- case 'type-size':
181
249
  case 'sizeof':
182
250
  case 'offset':
183
251
  return;
252
+ case 'type-size':
253
+ if (expression.typeExpr.length === undefined) {
254
+ validateSymbolReference(expression.typeExpr.name, span, symbols, diagnostics);
255
+ }
256
+ return;
184
257
  }
185
258
  }
186
- function validateSymbolReference(name, referenceSpan, labels, diagnostics) {
187
- const label = lookupLabel(labels, name);
188
- if (!label || label.public)
259
+ function validateSymbolReference(name, referenceSpan, symbols, diagnostics) {
260
+ const label = lookupLabel(symbols, name);
261
+ if (!label || label.duplicateName || label.public)
189
262
  return;
190
263
  if (referenceSpan.sourceUnit === label.definingSourceUnit)
191
264
  return;
192
265
  diagnostics.push(diagnostic(referenceSpan, `symbol "${name}" is private to ${label.definingSourceName}; export it with @${label.name} or keep the reference inside that file`));
193
266
  }
194
- function lookupLabel(labels, name) {
195
- const direct = labels.get(name);
267
+ function lookupLabel(symbols, name) {
268
+ const direct = symbols.labels.get(name);
196
269
  if (direct)
197
270
  return direct;
271
+ if (symbols.exactSymbols.has(name))
272
+ return undefined;
198
273
  const lowerName = name.toLowerCase();
199
- for (const [key, label] of labels) {
274
+ for (const [key, label] of symbols.labels) {
200
275
  if (key.toLowerCase() === lowerName)
201
276
  return label;
202
277
  }
@@ -6,6 +6,7 @@ interface ArtifactPaths {
6
6
  readonly asm80: string;
7
7
  readonly registerContractsReport: string;
8
8
  readonly registerContractsInterface: string;
9
+ readonly registerContractsInference: string;
9
10
  }
10
11
  interface ArtifactWriteResult {
11
12
  readonly primaryPath?: string;
@@ -57,6 +57,11 @@ function queueRegisterContractsArtifacts(writes, byKind, paths) {
57
57
  writes.push(writeTextArtifact(paths.registerContractsInterface, iface.text));
58
58
  registerContractsPath ??= paths.registerContractsInterface;
59
59
  }
60
+ const inference = byKind.get('register-contracts-inference');
61
+ if (inference?.kind === 'register-contracts-inference') {
62
+ writes.push(writeTextArtifact(paths.registerContractsInference, inference.text));
63
+ registerContractsPath ??= paths.registerContractsInference;
64
+ }
60
65
  return registerContractsPath;
61
66
  }
62
67
  function queueRegisterContractsAnnotationArtifacts(writes, byKind) {
@@ -1,4 +1,4 @@
1
- import type { RegisterContractsMode } from '../register-contracts/types.js';
1
+ import type { RegisterContractsInferenceFormat, RegisterContractsMode, RegisterContractsReportFormat } from '../register-contracts/types.js';
2
2
  import type { CaseStyleMode } from '../tooling/case-style.js';
3
3
  import { cliUsage } from './usage.js';
4
4
  export type CliExit = {
@@ -16,7 +16,12 @@ export type CliOptions = {
16
16
  caseStyle: CaseStyleMode;
17
17
  registerContracts: RegisterContractsMode;
18
18
  emitRegisterReport: boolean;
19
+ registerContractsReportFormat: RegisterContractsReportFormat;
20
+ registerContractsBaseline: string | undefined;
21
+ registerContractsRatchet: boolean;
19
22
  emitRegisterInterface: boolean;
23
+ emitRegisterInference: boolean;
24
+ registerContractsInferenceFormat: RegisterContractsInferenceFormat;
20
25
  emitRegisterAnnotations: boolean;
21
26
  fixRegisterContracts: boolean;
22
27
  acceptRegisterOutputCandidates: string[];
@@ -38,6 +38,20 @@ const BOOLEAN_FLAG_ACTIONS = [
38
38
  state.emitRegisterInterface = true;
39
39
  },
40
40
  },
41
+ {
42
+ flags: ['--reg-infer'],
43
+ apply: (state) => {
44
+ state.emitRegisterInference = true;
45
+ },
46
+ },
47
+ {
48
+ flags: ['--reg-ratchet'],
49
+ apply: (state) => {
50
+ state.registerContractsRatchet = true;
51
+ state.emitRegisterReport = true;
52
+ state.registerContractsReportFormat = 'json';
53
+ },
54
+ },
41
55
  {
42
56
  flags: ['--fix'],
43
57
  apply: (state) => {
@@ -67,7 +81,12 @@ function createDefaultCliState() {
67
81
  caseStyle: 'off',
68
82
  registerContracts: 'off',
69
83
  emitRegisterReport: false,
84
+ registerContractsReportFormat: 'text',
85
+ registerContractsBaseline: undefined,
86
+ registerContractsRatchet: false,
70
87
  emitRegisterInterface: false,
88
+ emitRegisterInference: false,
89
+ registerContractsInferenceFormat: 'json',
71
90
  emitRegisterAnnotations: false,
72
91
  fixRegisterContracts: false,
73
92
  acceptRegisterOutputCandidates: [],
@@ -199,6 +218,37 @@ function parseRegisterProfileArg(arg, argv, indexRef, state) {
199
218
  state.registerContractsProfile = parsed.value;
200
219
  return true;
201
220
  }
221
+ function parseRegisterReportFormatArg(arg, argv, indexRef, state) {
222
+ const parsed = readMatchedFlagValue(arg, argv, indexRef, ['--reg-report-format']);
223
+ if (!parsed)
224
+ return false;
225
+ if (parsed.value !== 'text' && parsed.value !== 'json') {
226
+ fail(`Unsupported ${parsed.flag} "${parsed.value}" (expected text|json)`);
227
+ }
228
+ state.emitRegisterReport = true;
229
+ state.registerContractsReportFormat = parsed.value;
230
+ return true;
231
+ }
232
+ function parseRegisterInferenceFormatArg(arg, argv, indexRef, state) {
233
+ const parsed = readMatchedFlagValue(arg, argv, indexRef, ['--reg-infer-format']);
234
+ if (!parsed)
235
+ return false;
236
+ if (parsed.value !== 'json' && parsed.value !== 'markdown') {
237
+ fail(`Unsupported ${parsed.flag} "${parsed.value}" (expected json|markdown)`);
238
+ }
239
+ state.emitRegisterInference = true;
240
+ state.registerContractsInferenceFormat = parsed.value;
241
+ return true;
242
+ }
243
+ function parseRegisterBaselineArg(arg, argv, indexRef, state) {
244
+ const parsed = readMatchedFlagValue(arg, argv, indexRef, ['--reg-baseline']);
245
+ if (!parsed)
246
+ return false;
247
+ state.registerContractsBaseline = parsed.value;
248
+ state.emitRegisterReport = true;
249
+ state.registerContractsReportFormat = 'json';
250
+ return true;
251
+ }
202
252
  function parseRegisterInterfaceArg(arg, argv, indexRef, state) {
203
253
  if (arg !== '--interface' && !arg.startsWith('--interface='))
204
254
  return false;
@@ -264,7 +314,12 @@ function finalizeCliOptions(state) {
264
314
  caseStyle: state.caseStyle,
265
315
  registerContracts: state.registerContracts,
266
316
  emitRegisterReport: state.emitRegisterReport,
317
+ registerContractsReportFormat: state.registerContractsBaseline !== undefined ? 'json' : state.registerContractsReportFormat,
318
+ registerContractsBaseline: state.registerContractsBaseline,
319
+ registerContractsRatchet: state.registerContractsRatchet,
267
320
  emitRegisterInterface: state.emitRegisterInterface,
321
+ emitRegisterInference: state.emitRegisterInference,
322
+ registerContractsInferenceFormat: state.registerContractsInferenceFormat,
268
323
  emitRegisterAnnotations: state.emitRegisterAnnotations,
269
324
  fixRegisterContracts: state.fixRegisterContracts,
270
325
  acceptRegisterOutputCandidates: state.acceptRegisterOutputCandidates,
@@ -299,6 +354,7 @@ function emitsRegisterContractsArtifact(state) {
299
354
  state.registerContracts !== 'off',
300
355
  state.emitRegisterReport,
301
356
  state.emitRegisterInterface,
357
+ state.emitRegisterInference,
302
358
  state.emitRegisterAnnotations,
303
359
  state.fixRegisterContracts,
304
360
  state.acceptRegisterOutputCandidates.length > 0,
@@ -324,6 +380,9 @@ const VALUE_ARG_PARSERS = [
324
380
  (arg, { argv, indexRef, state }) => parseDirectiveAliasFileArg(arg, argv, indexRef, state),
325
381
  (arg, { argv, indexRef, state }) => parseRegisterContractsArg(arg, argv, indexRef, state),
326
382
  (arg, { argv, indexRef, state }) => parseRegisterProfileArg(arg, argv, indexRef, state),
383
+ (arg, { argv, indexRef, state }) => parseRegisterReportFormatArg(arg, argv, indexRef, state),
384
+ (arg, { argv, indexRef, state }) => parseRegisterInferenceFormatArg(arg, argv, indexRef, state),
385
+ (arg, { argv, indexRef, state }) => parseRegisterBaselineArg(arg, argv, indexRef, state),
327
386
  (arg, { argv, indexRef, state }) => parseAcceptOutputArg(arg, argv, indexRef, state),
328
387
  (arg, { argv, indexRef, state }) => parseRegisterInterfaceArg(arg, argv, indexRef, state),
329
388
  (arg, { argv, indexRef, state }) => parseIncludeArg(arg, argv, indexRef, state),
@@ -19,11 +19,11 @@ export async function runCli(argv) {
19
19
  }
20
20
  if (sortedDiagnostics.some((diagnostic) => diagnostic.severity === 'error')) {
21
21
  if (compileResult.artifacts.length > 0) {
22
- await writeArtifacts(base, compileResult.artifacts, parsed.outputType);
22
+ await writeArtifacts(base, compileResult.artifacts, parsed.outputType, parsed.registerContractsReportFormat);
23
23
  }
24
24
  return 1;
25
25
  }
26
- const primaryPath = await writeArtifacts(base, compileResult.artifacts, parsed.outputType);
26
+ const primaryPath = await writeArtifacts(base, compileResult.artifacts, parsed.outputType, parsed.registerContractsReportFormat);
27
27
  if (primaryPath !== undefined) {
28
28
  process.stdout.write(primaryPath);
29
29
  }
@@ -12,7 +12,12 @@ export function cliUsage() {
12
12
  ' --register-contracts <m> Register contracts mode: off|audit|warn|error|strict',
13
13
  ' --rc <m> Register contracts mode alias for --register-contracts',
14
14
  ' --reg-report Emit register contracts report artifact',
15
+ ' --reg-report-format <f> Report format: text|json (default: text)',
16
+ ' --reg-baseline <f> Compare against a JSON register contracts report',
17
+ ' --reg-ratchet Fail if register contract findings increase',
15
18
  ' --reg-interface Emit inferred register contracts interface (.asmi)',
19
+ ' --reg-infer Emit inferred register contracts review artifact',
20
+ ' --reg-infer-format <f> Inference format: json|markdown (default: json)',
16
21
  ' --contracts Rewrite source with inferred register contracts',
17
22
  ' --fix Enable contract rewrite and conservative fixes',
18
23
  ' --accept-out <x> Accept register contracts output candidates',
@@ -17,5 +17,5 @@ export declare function compareDiagnosticsForCli(a: {
17
17
  message: string;
18
18
  }): number;
19
19
  export declare function artifactBase(entryFile: string, outputType: 'hex' | 'bin', outputPath?: string): string;
20
- export declare function writeArtifacts(base: string, artifacts: readonly Artifact[], outputType: 'hex' | 'bin'): Promise<string | undefined>;
20
+ export declare function writeArtifacts(base: string, artifacts: readonly Artifact[], outputType: 'hex' | 'bin', registerContractsReportFormat?: 'text' | 'json'): Promise<string | undefined>;
21
21
  export declare function buildCompileOptions(parsed: CliOptions, base: string): CompileNextFunctionOptions;