@jhlagado/azm 0.2.9 → 0.2.10

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/README.md CHANGED
@@ -120,9 +120,7 @@ Use `@Name:` for callable routine entries. The `@` marks a register contracts
120
120
  routine boundary; call sites still write the symbol name without `@`:
121
121
 
122
122
  ```asm
123
- ;! in A
124
- ;! out A
125
- ;! clobbers BC
123
+ ;! in A; out A; clobbers BC
126
124
  @MxMask:
127
125
  LD C,A
128
126
  OR A
@@ -406,9 +404,7 @@ register and stack assumptions visible before they become debugger sessions.
406
404
  Routine entry labels start with `@`:
407
405
 
408
406
  ```asm
409
- ;! in A,HL
410
- ;! out carry
411
- ;! clobbers B
407
+ ;! in A,HL; out carry; clobbers B
412
408
  @CheckTile:
413
409
  ld b,(hl)
414
410
  cp b
@@ -423,9 +419,11 @@ name:
423
419
  ```
424
420
 
425
421
  AZMDoc register contract comments use `;!` and may record inputs, outputs,
426
- clobbered registers and preserved registers. `clobbers B` means the routine may
427
- change `B`. `preserves B` means the value that enters in `B` is still present
428
- when the routine returns.
422
+ clobbered registers and preserved registers. Separate clauses on the same line
423
+ with semicolons. Older one-clause-per-line comments are still accepted, but AZM
424
+ generated annotations use the compact single-line form. `clobbers B` means the
425
+ routine may change `B`. `preserves B` means the value that enters in `B` is
426
+ still present when the routine returns.
429
427
 
430
428
  Run the analysis with:
431
429
 
@@ -2,6 +2,7 @@ function list(units) {
2
2
  return units.length === 0 ? '-' : units.join(',');
3
3
  }
4
4
  const FLAG_UNITS = new Set(['carry', 'zero', 'sign', 'parity', 'halfCarry']);
5
+ const FLAG_UNIT_LIST = ['carry', 'zero', 'sign', 'parity', 'halfCarry'];
5
6
  const CONTRACT_CARRIER_PAIRS = [
6
7
  { label: 'BC', hi: 'B', lo: 'C' },
7
8
  { label: 'DE', hi: 'D', lo: 'E' },
@@ -32,6 +33,14 @@ export function contractCarrierList(units) {
32
33
  }
33
34
  return parts.length === 0 ? '-' : parts.join(',');
34
35
  }
36
+ function sourceContractCarrierList(units) {
37
+ const unique = [...new Set(units)];
38
+ const hasAllFlags = FLAG_UNIT_LIST.every((unit) => unique.includes(unit));
39
+ const compacted = hasAllFlags
40
+ ? unique.filter((unit) => !FLAG_UNITS.has(unit)).concat('F')
41
+ : unique;
42
+ return contractCarrierList(compacted);
43
+ }
35
44
  function relationOutputUnits(relations) {
36
45
  return relations.flatMap((rel) => rel.out);
37
46
  }
@@ -59,9 +68,9 @@ function sourceContractEntries(summary) {
59
68
  const outputUnits = relationOutputUnits(summary.valueRelations);
60
69
  if (outputUnits.length > 0)
61
70
  out.push({ keyword: 'out', carriers: contractCarrierList(outputUnits) });
62
- const clobbers = summary.mayWrite.filter((unit) => !relationOut.has(unit) && !FLAG_UNITS.has(unit));
71
+ const clobbers = summary.mayWrite.filter((unit) => !relationOut.has(unit));
63
72
  if (clobbers.length > 0)
64
- out.push({ keyword: 'clobbers', carriers: contractCarrierList(clobbers) });
73
+ out.push({ keyword: 'clobbers', carriers: sourceContractCarrierList(clobbers) });
65
74
  return out;
66
75
  }
67
76
  function stackStatus(summary) {
@@ -152,5 +161,8 @@ export function renderRegisterContractsInterface(summaries) {
152
161
  return `${lines.join('\n')}\n`;
153
162
  }
154
163
  export function renderRegisterContractsSourceBlock(summary) {
155
- return sourceContractEntries(summary).map((entry) => `;! ${entry.keyword.padEnd(10)}${entry.carriers}`);
164
+ const entries = sourceContractEntries(summary);
165
+ if (entries.length === 0)
166
+ return [];
167
+ return [`;! ${entries.map((entry) => `${entry.keyword} ${entry.carriers}`).join('; ')}`];
156
168
  }
@@ -1,3 +1,4 @@
1
1
  import type { SmartComment } from './types.js';
2
2
  export declare function parseSmartCommentLine(line: string): SmartComment | undefined;
3
+ export declare function parseSmartCommentLines(line: string): SmartComment[];
3
4
  export declare function isCompactSourceCommentLine(line: string): boolean;
@@ -1,5 +1,6 @@
1
1
  import { expandCarrierList } from './carriers.js';
2
2
  const COMPACT_SOURCE_TAG_RE = /^;?\s*!\s*(in|out|clobbers|preserves)(?:\s+(.+))?$/i;
3
+ const COMPACT_SOURCE_CLAUSE_RE = /^(in|out|clobbers|preserves)(?:\s+(.+))?$/i;
3
4
  const COMPACT_SOURCE_LINE_RE = /^\s*;\s*!\s*(?:in|out|maybe-out|clobbers|preserves)(?:\s|$)/i;
4
5
  const CARRIER_RE = /^\{([^}]+)\}(?:\s+(.+))?$/;
5
6
  const CONTRACT_COMMENT_KINDS = new Set(['in', 'out', 'clobbers', 'preserves']);
@@ -35,17 +36,51 @@ function parseCarrierPayload(rest) {
35
36
  return { carriers, ...(name ? { name } : {}) };
36
37
  }
37
38
  export function parseSmartCommentLine(line) {
39
+ return parseSmartCommentLines(line)[0];
40
+ }
41
+ export function parseSmartCommentLines(line) {
38
42
  const trimmed = line.trim();
39
43
  const expectOut = parseExpectOutComment(trimmed);
40
44
  if (expectOut !== undefined)
41
- return expectOut;
45
+ return [expectOut];
46
+ const semicolonSeparated = parseSemicolonSeparatedSourceComments(trimmed);
47
+ if (semicolonSeparated.length > 0)
48
+ return semicolonSeparated;
42
49
  const match = COMPACT_SOURCE_TAG_RE.exec(trimmed);
43
- if (!match)
44
- return undefined;
45
- const tag = match[1].toLowerCase();
46
- if (!CONTRACT_COMMENT_KINDS.has(tag))
47
- return undefined;
48
- return parseCarrierComment(tag, match[2]?.trim());
50
+ if (match) {
51
+ const tag = match[1].toLowerCase();
52
+ if (!CONTRACT_COMMENT_KINDS.has(tag))
53
+ return [];
54
+ const comment = parseCarrierComment(tag, match[2]?.trim());
55
+ return comment === undefined ? [] : [comment];
56
+ }
57
+ return [];
58
+ }
59
+ function parseSemicolonSeparatedSourceComments(trimmed) {
60
+ const sourcePrefix = /^;?\s*!\s*/.exec(trimmed);
61
+ if (sourcePrefix === null)
62
+ return [];
63
+ const content = trimmed.slice(sourcePrefix[0].length);
64
+ const parts = content
65
+ .split(';')
66
+ .map((part) => part.trim())
67
+ .filter(Boolean);
68
+ if (parts.length <= 1)
69
+ return [];
70
+ const comments = [];
71
+ for (const part of parts) {
72
+ const match = COMPACT_SOURCE_CLAUSE_RE.exec(part);
73
+ if (!match)
74
+ return [];
75
+ const tag = match[1].toLowerCase();
76
+ if (!CONTRACT_COMMENT_KINDS.has(tag))
77
+ return [];
78
+ const comment = parseCarrierComment(tag, match[2]?.trim());
79
+ if (comment === undefined)
80
+ return [];
81
+ comments.push(comment);
82
+ }
83
+ return comments;
49
84
  }
50
85
  function parseExpectOutComment(trimmed) {
51
86
  const expectOut = /^;?\s*expects\s+out\s+(.+)$/i.exec(trimmed);
@@ -1,5 +1,5 @@
1
1
  import type { LocatedSmartComment, RegisterContractsRoutine, RoutineContract } from './types.js';
2
- import { parseSmartCommentLine } from './smartCommentParsing.js';
3
- export { parseSmartCommentLine };
2
+ import { parseSmartCommentLine, parseSmartCommentLines } from './smartCommentParsing.js';
3
+ export { parseSmartCommentLine, parseSmartCommentLines };
4
4
  export declare function parseSmartComments(sourceLineComments: ReadonlyMap<string, ReadonlyMap<number, string>>): LocatedSmartComment[];
5
5
  export declare function buildRoutineContracts(comments: LocatedSmartComment[], routines?: RegisterContractsRoutine[], sourceTexts?: ReadonlyMap<string, string>): Map<string, RoutineContract>;
@@ -1,12 +1,11 @@
1
1
  import { collectPrecedingCommentBlock } from './smartCommentBlocks.js';
2
- import { parseSmartCommentLine } from './smartCommentParsing.js';
3
- export { parseSmartCommentLine };
2
+ import { parseSmartCommentLine, parseSmartCommentLines } from './smartCommentParsing.js';
3
+ export { parseSmartCommentLine, parseSmartCommentLines };
4
4
  export function parseSmartComments(sourceLineComments) {
5
5
  const out = [];
6
6
  for (const [file, comments] of sourceLineComments) {
7
7
  for (const [line, text] of comments) {
8
- const parsed = parseSmartCommentLine(`;${text}`);
9
- if (parsed) {
8
+ for (const parsed of parseSmartCommentLines(`;${text}`)) {
10
9
  out.push({ file, line, comment: parsed });
11
10
  }
12
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jhlagado/azm",
3
- "version": "0.2.9",
3
+ "version": "0.2.10",
4
4
  "description": "AZM assembler for the Z80 family (Node.js CLI)",
5
5
  "license": "GPL-3.0-only",
6
6
  "engines": {