@henols/c64-re-tools 2.0.0-rc.2 → 2.0.0-rc.21

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 (126) hide show
  1. package/README.md +28 -62
  2. package/distribution/package.ts +1 -16
  3. package/distribution/plugin.ts +5 -3
  4. package/package.json +1 -1
  5. package/skills/c64-assembler/SKILL.md +25 -6
  6. package/skills/c64-assembler/scripts/assemble.ts +45 -10
  7. package/skills/c64-assembler/scripts/lib/compare.ts +48 -0
  8. package/skills/c64-basic/SKILL.md +15 -7
  9. package/skills/c64-basic/scripts/basic.ts +4 -4
  10. package/skills/c64-disk/SKILL.md +34 -11
  11. package/skills/c64-disk/scripts/disk.ts +65 -10
  12. package/skills/c64-disk/scripts/lib/containers.ts +124 -0
  13. package/skills/c64-docs/SKILL.md +138 -0
  14. package/skills/c64-docs/references/page-types.md +141 -0
  15. package/skills/c64-docs/references/style.md +43 -0
  16. package/skills/c64-docs/scripts/generate.ts +102 -0
  17. package/skills/c64-docs/scripts/lib/links.ts +155 -0
  18. package/skills/c64-docs/scripts/lib/markdown.ts +79 -0
  19. package/skills/c64-docs/scripts/lib/reference.ts +406 -0
  20. package/skills/c64-docs/scripts/lib/source-map.ts +81 -0
  21. package/skills/c64-docs/scripts/link.ts +207 -0
  22. package/skills/c64-emulator/SKILL.md +29 -8
  23. package/skills/c64-knowledge/SKILL.md +201 -15
  24. package/skills/c64-knowledge/scripts/knowledge.ts +927 -78
  25. package/skills/c64-knowledge/scripts/lib/listing.ts +162 -0
  26. package/skills/c64-memory-map/SKILL.md +14 -2
  27. package/skills/c64-memory-map/scripts/memmap.ts +15 -4
  28. package/skills/c64-provenance/SKILL.md +100 -11
  29. package/skills/c64-provenance/scripts/lib/compare.ts +152 -0
  30. package/skills/c64-provenance/scripts/lib/library.ts +584 -0
  31. package/skills/c64-provenance/scripts/lib/runtime.ts +191 -0
  32. package/skills/c64-provenance/scripts/lib/separate.ts +140 -0
  33. package/skills/c64-provenance/scripts/provenance.ts +477 -24
  34. package/skills/c64-reverse-engineering/SKILL.md +90 -11
  35. package/skills/c64-source/SKILL.md +174 -0
  36. package/skills/c64-source/scripts/generate.ts +788 -0
  37. package/skills/c64-source/scripts/lib/loader.ts +630 -0
  38. package/skills/c64-source/scripts/lib/source.ts +1121 -0
  39. package/skills/c64-static-analysis/SKILL.md +53 -13
  40. package/skills/c64-static-analysis/scripts/analyze.ts +260 -31
  41. package/skills/c64-static-analysis/scripts/lib/coverage.ts +72 -0
  42. package/skills/c64-static-analysis/scripts/{findings.ts → lib/findings.ts} +11 -5
  43. package/skills/c64-static-analysis/scripts/lib/idioms.ts +158 -0
  44. package/skills/c64-static-analysis/scripts/lib/instructions.ts +105 -0
  45. package/skills/c64-static-analysis/scripts/lib/macros.ts +233 -0
  46. package/skills/c64-static-analysis/scripts/lib/pitfalls.ts +68 -0
  47. package/skills/c64-static-analysis/scripts/lib/pointers.ts +108 -0
  48. package/skills/c64-testing/SKILL.md +57 -4
  49. package/skills/c64-testing/references/scenario-format.md +50 -6
  50. package/skills/c64-testing/scripts/checklist.ts +3 -3
  51. package/skills/c64-testing/scripts/diff.ts +167 -0
  52. package/skills/c64-testing/scripts/lib/diff.ts +70 -0
  53. package/skills/c64-testing/scripts/{scenario.ts → lib/scenario.ts} +183 -152
  54. package/skills/c64-testing/scripts/run.ts +241 -0
  55. package/skills/c64-testing/scripts/test.ts +20 -10
  56. package/skills/c64-unpacker/SKILL.md +88 -21
  57. package/skills/c64-unpacker/scripts/lib/evidence.ts +207 -0
  58. package/skills/c64-unpacker/scripts/lib/loads.ts +209 -0
  59. package/skills/c64-unpacker/scripts/lib/machine.ts +80 -0
  60. package/skills/c64-unpacker/scripts/lib/stages.ts +142 -0
  61. package/skills/c64-unpacker/scripts/unpack.ts +545 -61
  62. package/src/c64.ts +240 -12
  63. package/src/cli/cleanup.ts +1 -1
  64. package/src/cli/main.ts +14 -8
  65. package/src/cli/status.ts +105 -11
  66. package/src/graphics.ts +158 -0
  67. package/src/host/main.ts +57 -31
  68. package/src/host/server.ts +30 -47
  69. package/src/host/tools/c1541.ts +34 -5
  70. package/src/host/tools/index.ts +6 -2
  71. package/src/host/tools/petcat.ts +10 -3
  72. package/src/host/tools/status.ts +23 -6
  73. package/src/host/vice/adapter.ts +64 -20
  74. package/src/host/vice/chips.ts +11 -1
  75. package/src/host/vice/process.ts +19 -8
  76. package/src/host/vice/screen.ts +4 -44
  77. package/src/host/vice/session.ts +343 -60
  78. package/src/host-client/autostart.ts +55 -0
  79. package/src/host-client/connect.ts +14 -14
  80. package/src/host-client/coverage.ts +36 -0
  81. package/src/host-client/image.ts +63 -0
  82. package/src/host-client/steps.ts +175 -0
  83. package/src/host-client/tools.ts +7 -6
  84. package/src/host-client/transfer.ts +60 -10
  85. package/src/host-client/vice-session.ts +107 -28
  86. package/src/knowledge/conflicts.ts +161 -0
  87. package/src/knowledge/database.ts +150 -38
  88. package/src/knowledge/expression.ts +209 -0
  89. package/src/knowledge/graph.ts +104 -0
  90. package/src/knowledge/history.ts +105 -27
  91. package/src/knowledge/import.ts +141 -85
  92. package/src/knowledge/join.ts +211 -0
  93. package/src/knowledge/program.ts +258 -0
  94. package/src/knowledge/read.ts +445 -63
  95. package/src/knowledge/schema.ts +277 -65
  96. package/src/knowledge/text.ts +415 -0
  97. package/src/knowledge/write.ts +1145 -150
  98. package/src/mcp/main.ts +44 -19
  99. package/src/mcp/output.ts +39 -0
  100. package/src/mcp/server.ts +15 -4
  101. package/src/mcp/tools/debug.ts +202 -31
  102. package/src/mcp/tools/execution.ts +39 -5
  103. package/src/mcp/tools/input.ts +7 -4
  104. package/src/mcp/tools/machine.ts +14 -2
  105. package/src/mcp/tools/media.ts +44 -16
  106. package/src/mcp/tools/memory.ts +68 -12
  107. package/src/mcp/tools/video.ts +20 -9
  108. package/src/native/discover.ts +44 -6
  109. package/src/native/ghidra/analyze.ts +2 -2
  110. package/src/native/ghidra/index.ts +19 -3
  111. package/src/native/ghidra/language/c64rt_6510.slaspec +4 -1
  112. package/src/native/ghidra/scripts/C64Export.java +33 -1
  113. package/src/native/ghidra/scripts/C64Prepare.java +7 -1
  114. package/src/native/status.ts +15 -3
  115. package/src/native/types.ts +43 -8
  116. package/src/opcodes.ts +133 -0
  117. package/src/package.ts +21 -0
  118. package/{skills/c64-memory-map/scripts → src}/platform.ts +87 -7
  119. package/src/project.ts +166 -3
  120. package/src/protocol/messages.ts +17 -10
  121. package/src/protocol/tools.ts +27 -22
  122. package/src/protocol/vice.ts +255 -33
  123. package/src/script.ts +49 -9
  124. package/src/trace.ts +55 -23
  125. package/skills/c64-provenance/scripts/compare.ts +0 -92
  126. package/skills/c64-unpacker/scripts/evidence.ts +0 -135
@@ -1,137 +1,444 @@
1
- // Current-state knowledge queries. A project without a database
2
- // reads as empty knowledge.
1
+ // Current-state knowledge queries. A project without knowledge
2
+ // reads as empty knowledge. Each query reads one address space (D53); the
3
+ // default is the C64.
3
4
 
4
5
  import type { DatabaseSync } from "node:sqlite";
5
6
 
6
- import type { CommentPlacement, Origin, ReferenceKind, RegionType, SymbolKind } from "./schema.ts";
7
+ import { KnowledgeError } from "./database.ts";
8
+ import { DEFAULT_SPACE, type CommentPlacement, type OperandSelect, type Origin, type ReferenceKind, type RegionType, type Space, type SuppressionEntity, type SymbolKind } from "./schema.ts";
9
+ import { NO_REVISION } from "./text.ts";
7
10
 
8
11
  export interface SymbolRow {
12
+ space: Space;
9
13
  address: number;
10
14
  name: string;
11
15
  kind: SymbolKind;
12
16
  origin: Origin;
13
- revision: number;
17
+ revision: string;
14
18
  }
15
19
 
16
20
  export interface RegionRow {
21
+ space: Space;
17
22
  start: number;
18
23
  end: number;
19
24
  type: RegionType;
25
+ /** Bytes per element of a table; the length is a multiple of it (D42). */
26
+ element?: number;
20
27
  origin: Origin;
21
- revision: number;
28
+ revision: string;
22
29
  }
23
30
 
24
31
  export interface CommentRow {
32
+ space: Space;
25
33
  address: number;
26
34
  placement: CommentPlacement;
27
35
  text: string;
28
36
  origin: Origin;
29
- revision: number;
37
+ revision: string;
30
38
  }
31
39
 
32
40
  export interface ReferenceRow {
41
+ space: Space;
33
42
  from: number;
34
43
  to: number;
35
44
  kind: ReferenceKind;
36
45
  origin: Origin;
37
- revision: number;
46
+ revision: string;
38
47
  }
39
48
 
49
+ /** A place that a user/LLM removal blocks for every analyzer (D36). `to` and `kind` are set for a reference only. */
50
+ export interface SuppressionRow {
51
+ space: Space;
52
+ entity: SuppressionEntity;
53
+ start: number;
54
+ end: number;
55
+ to?: number;
56
+ kind?: ReferenceKind;
57
+ origin: Extract<Origin, "user" | "llm">;
58
+ revision: string;
59
+ }
60
+
61
+ /** Where a file of the generated source starts (D48): it runs to the next module or the end of its segment. */
62
+ export interface ModuleRow {
63
+ space: Space;
64
+ start: number;
65
+ file: string;
66
+ title?: string;
67
+ origin: Extract<Origin, "user" | "llm">;
68
+ revision: string;
69
+ }
70
+
71
+ /**
72
+ * How the operand of the instruction at `address` is written (D65): a byte
73
+ * (lo, hi) or the value of a symbol plus an offset, or expression text (expr).
74
+ */
75
+ export interface OperandRow {
76
+ space: Space;
77
+ address: number;
78
+ select: OperandSelect;
79
+ symbol?: string;
80
+ /** Added to the symbol before lo or hi takes its byte; 0 for expr. */
81
+ offset: number;
82
+ expression?: string;
83
+ origin: Extract<Origin, "user" | "llm">;
84
+ revision: string;
85
+ }
86
+
87
+ /**
88
+ * Code that replaces [start, end] when the build defines the switch as 1
89
+ * (D72). The code fits the range; NOP fills the rest. Empty code is only NOP.
90
+ */
91
+ export interface PatchRow {
92
+ space: Space;
93
+ start: number;
94
+ end: number;
95
+ switch: string;
96
+ code: string;
97
+ comment?: string;
98
+ origin: Extract<Origin, "user" | "llm">;
99
+ revision: string;
100
+ }
101
+
102
+ /** An ACME macro of the generated source (D73). The body names a parameter with a dot in front: .dst. */
103
+ export interface MacroRow {
104
+ space: Space;
105
+ name: string;
106
+ params: string[];
107
+ body: string;
108
+ comment?: string;
109
+ origin: Extract<Origin, "user" | "llm">;
110
+ revision: string;
111
+ }
112
+
113
+ /** The instructions in [start, end] written as one use of a macro, with one expression for each parameter (D73). */
114
+ export interface MacroUseRow {
115
+ space: Space;
116
+ start: number;
117
+ end: number;
118
+ macro: string;
119
+ args: string[];
120
+ origin: Extract<Origin, "user" | "llm">;
121
+ revision: string;
122
+ }
123
+
124
+ /** A list of params or args as the knowledge stores it: the items with ", " between them. */
125
+ export const listText = (items: readonly string[]) => items.join(", ");
126
+ const items = (text: string) => (text.trim() === "" ? [] : text.split(",").map((item) => item.trim()));
127
+
40
128
  type Raw = Record<string, unknown>;
41
129
 
42
- export const SYMBOL_COLUMNS = "address, name, kind, origin, valid_from_revision";
43
- export const REGION_COLUMNS = "start_address, end_address, type, origin, valid_from_revision";
44
- export const COMMENT_COLUMNS = "address, placement, text, origin, valid_from_revision";
45
- export const REFERENCE_COLUMNS = "from_address, to_address, kind, origin, valid_from_revision";
130
+ export const SYMBOL_COLUMNS = "space, address, name, kind, origin, valid_from_revision";
131
+ export const REGION_COLUMNS = "space, start_address, end_address, type, element, origin, valid_from_revision";
132
+ export const COMMENT_COLUMNS = "space, address, placement, text, origin, valid_from_revision";
133
+ export const REFERENCE_COLUMNS = "space, from_address, to_address, kind, origin, valid_from_revision";
134
+ export const SUPPRESSION_COLUMNS = "space, entity, start_address, end_address, to_address, kind, origin, valid_from_revision";
135
+ export const MODULE_COLUMNS = "space, start_address, file, title, origin, valid_from_revision";
136
+ export const OPERAND_COLUMNS = "space, address, selector, symbol, offset, expression, origin, valid_from_revision";
137
+ export const PATCH_COLUMNS = "space, start_address, end_address, switch_name, code, comment, origin, valid_from_revision";
138
+ export const MACRO_COLUMNS = "space, name, params, body, comment, origin, valid_from_revision";
139
+ export const MACRO_USE_COLUMNS = "space, start_address, end_address, macro, args, origin, valid_from_revision";
46
140
 
47
141
  export function toSymbol(row: Raw): SymbolRow {
48
- return { address: row.address as number, name: row.name as string, kind: row.kind as SymbolKind, origin: row.origin as Origin, revision: row.valid_from_revision as number };
142
+ return { space: row.space as Space, address: row.address as number, name: row.name as string, kind: row.kind as SymbolKind, origin: row.origin as Origin, revision: row.valid_from_revision as string };
49
143
  }
50
144
 
51
145
  export function toRegion(row: Raw): RegionRow {
52
- return { start: row.start_address as number, end: row.end_address as number, type: row.type as RegionType, origin: row.origin as Origin, revision: row.valid_from_revision as number };
146
+ const region: RegionRow = {
147
+ space: row.space as Space,
148
+ start: row.start_address as number,
149
+ end: row.end_address as number,
150
+ type: row.type as RegionType,
151
+ origin: row.origin as Origin,
152
+ revision: row.valid_from_revision as string,
153
+ };
154
+ if (row.element !== null && row.element !== undefined) region.element = row.element as number;
155
+ return region;
53
156
  }
54
157
 
55
158
  export function toComment(row: Raw): CommentRow {
56
- return { address: row.address as number, placement: row.placement as CommentPlacement, text: row.text as string, origin: row.origin as Origin, revision: row.valid_from_revision as number };
159
+ return {
160
+ space: row.space as Space,
161
+ address: row.address as number,
162
+ placement: row.placement as CommentPlacement,
163
+ text: row.text as string,
164
+ origin: row.origin as Origin,
165
+ revision: row.valid_from_revision as string,
166
+ };
57
167
  }
58
168
 
59
169
  export function toReference(row: Raw): ReferenceRow {
60
- return { from: row.from_address as number, to: row.to_address as number, kind: row.kind as ReferenceKind, origin: row.origin as Origin, revision: row.valid_from_revision as number };
170
+ return { space: row.space as Space, from: row.from_address as number, to: row.to_address as number, kind: row.kind as ReferenceKind, origin: row.origin as Origin, revision: row.valid_from_revision as string };
61
171
  }
62
172
 
63
- export function currentRevision(db: DatabaseSync | undefined): number {
64
- if (db === undefined) return 0;
65
- return Number((db.prepare("SELECT value FROM meta WHERE key = 'current_revision'").get() as { value: string }).value);
173
+ export function toSuppression(row: Raw): SuppressionRow {
174
+ const suppression: SuppressionRow = {
175
+ space: row.space as Space,
176
+ entity: row.entity as SuppressionEntity,
177
+ start: row.start_address as number,
178
+ end: row.end_address as number,
179
+ origin: row.origin as SuppressionRow["origin"],
180
+ revision: row.valid_from_revision as string,
181
+ };
182
+ if (row.to_address !== null) suppression.to = row.to_address as number;
183
+ if (row.kind !== null) suppression.kind = row.kind as ReferenceKind;
184
+ return suppression;
66
185
  }
67
186
 
68
- export function symbolAt(db: DatabaseSync | undefined, address: number): SymbolRow | undefined {
69
- const row = db?.prepare(`SELECT ${SYMBOL_COLUMNS} FROM current_symbols WHERE address = ?`).get(address) as Raw | undefined;
187
+ export function toModule(row: Raw): ModuleRow {
188
+ const module: ModuleRow = { space: row.space as Space, start: row.start_address as number, file: row.file as string, origin: row.origin as ModuleRow["origin"], revision: row.valid_from_revision as string };
189
+ if (row.title !== null && row.title !== undefined) module.title = row.title as string;
190
+ return module;
191
+ }
192
+
193
+ export function toOperand(row: Raw): OperandRow {
194
+ const operand: OperandRow = {
195
+ space: row.space as Space,
196
+ address: row.address as number,
197
+ select: row.selector as OperandSelect,
198
+ offset: row.offset as number,
199
+ origin: row.origin as OperandRow["origin"],
200
+ revision: row.valid_from_revision as string,
201
+ };
202
+ if (row.symbol !== null && row.symbol !== undefined) operand.symbol = row.symbol as string;
203
+ if (row.expression !== null && row.expression !== undefined) operand.expression = row.expression as string;
204
+ return operand;
205
+ }
206
+
207
+ export function toPatch(row: Raw): PatchRow {
208
+ const patch: PatchRow = {
209
+ space: row.space as Space,
210
+ start: row.start_address as number,
211
+ end: row.end_address as number,
212
+ switch: row.switch_name as string,
213
+ code: row.code as string,
214
+ origin: row.origin as PatchRow["origin"],
215
+ revision: row.valid_from_revision as string,
216
+ };
217
+ if (row.comment !== null && row.comment !== undefined) patch.comment = row.comment as string;
218
+ return patch;
219
+ }
220
+
221
+ export function toMacro(row: Raw): MacroRow {
222
+ const macro: MacroRow = { space: row.space as Space, name: row.name as string, params: items(row.params as string), body: row.body as string, origin: row.origin as MacroRow["origin"], revision: row.valid_from_revision as string };
223
+ if (row.comment !== null && row.comment !== undefined) macro.comment = row.comment as string;
224
+ return macro;
225
+ }
226
+
227
+ export function toMacroUse(row: Raw): MacroUseRow {
228
+ return {
229
+ space: row.space as Space,
230
+ start: row.start_address as number,
231
+ end: row.end_address as number,
232
+ macro: row.macro as string,
233
+ args: items(row.args as string),
234
+ origin: row.origin as MacroUseRow["origin"],
235
+ revision: row.valid_from_revision as string,
236
+ };
237
+ }
238
+
239
+ /** The id of the newest revision, or "0" when the knowledge has none (D69). */
240
+ export function currentRevision(db: DatabaseSync | undefined): string {
241
+ if (db === undefined) return "0";
242
+ return (db.prepare("SELECT max(id) AS id FROM revisions").get() as { id: string | null }).id ?? "0";
243
+ }
244
+
245
+ /** At most this many ids are named when a start of an id fits several revisions. */
246
+ const NAMED_REVISIONS = 5;
247
+
248
+ /**
249
+ * The revision that `text` names (D69): "0" (no revision), a full revision id,
250
+ * or a start of an id that only one revision has.
251
+ */
252
+ export function resolveRevision(db: DatabaseSync | undefined, text: string): string {
253
+ if (text === NO_REVISION) return NO_REVISION;
254
+ if (!/^[0-9a-zT.-]+$/.test(text)) throw new KnowledgeError("invalid-input", `${JSON.stringify(text)} is not a revision id. A revision id looks like 20261015T214501.123-k3f9qz; its start is enough when only one revision has it.`);
255
+ const found = (db?.prepare("SELECT id FROM revisions WHERE substr(id, 1, ?) = ? ORDER BY id LIMIT ?").all(text.length, text, NAMED_REVISIONS + 1) ?? []) as Array<{ id: string }>;
256
+ if (found.length === 0) throw new KnowledgeError("not-found", `There is no revision ${text}.`);
257
+ if (found.length > 1) {
258
+ const named = found.slice(0, NAMED_REVISIONS).map((row) => row.id).join(", ");
259
+ throw new KnowledgeError("invalid-input", `${text} is the start of more than one revision: ${named}${found.length > NAMED_REVISIONS ? ", ..." : ""}. Give more of the id.`);
260
+ }
261
+ return found[0]!.id;
262
+ }
263
+
264
+ export function symbolAt(db: DatabaseSync | undefined, address: number, space: Space = DEFAULT_SPACE): SymbolRow | undefined {
265
+ const row = db?.prepare(`SELECT ${SYMBOL_COLUMNS} FROM current_symbols WHERE space = ? AND address = ?`).get(space, address) as Raw | undefined;
70
266
  return row === undefined ? undefined : toSymbol(row);
71
267
  }
72
268
 
73
- export function symbolNamed(db: DatabaseSync | undefined, name: string): SymbolRow | undefined {
74
- const row = db?.prepare(`SELECT ${SYMBOL_COLUMNS} FROM current_symbols WHERE name = ?`).get(name) as Raw | undefined;
269
+ export function symbolNamed(db: DatabaseSync | undefined, name: string, space: Space = DEFAULT_SPACE): SymbolRow | undefined {
270
+ const row = db?.prepare(`SELECT ${SYMBOL_COLUMNS} FROM current_symbols WHERE space = ? AND name = ?`).get(space, name) as Raw | undefined;
75
271
  return row === undefined ? undefined : toSymbol(row);
76
272
  }
77
273
 
274
+ /** The analyzer names at an address (D74): one for each analyzer, in the order heuristic, Ghidra, DXA. */
275
+ export function analyzerNamesAt(db: DatabaseSync | undefined, address: number, space: Space = DEFAULT_SPACE): SymbolRow[] {
276
+ if (db === undefined) return [];
277
+ return (db.prepare(`SELECT ${SYMBOL_COLUMNS} FROM current_analyzer_names WHERE space = ? AND address = ?`).all(space, address) as Raw[]).map(toSymbol).sort(byAnalyzer);
278
+ }
279
+
280
+ /** The analyzer names with this name: more than one when two analyzers gave it to different addresses. */
281
+ export function analyzerNamed(db: DatabaseSync | undefined, name: string, space: Space = DEFAULT_SPACE): SymbolRow[] {
282
+ if (db === undefined) return [];
283
+ return (db.prepare(`SELECT ${SYMBOL_COLUMNS} FROM current_analyzer_names WHERE space = ? AND name = ?`).all(space, name) as Raw[]).map(toSymbol).sort(byAnalyzer);
284
+ }
285
+
286
+ /** The analyzers in the order that their names are used when no other name exists (D74). */
287
+ export const ANALYZER_NAME_ORDER: readonly Origin[] = ["heuristic", "ghidra", "dxa"];
288
+ const byAnalyzer = (a: SymbolRow, b: SymbolRow) => a.address - b.address || ANALYZER_NAME_ORDER.indexOf(a.origin) - ANALYZER_NAME_ORDER.indexOf(b.origin);
289
+
290
+ /** Current analyzer names in a range, in address order and then heuristic, Ghidra, DXA. */
291
+ export function listAnalyzerNames(db: DatabaseSync | undefined, range: Range = ALL, kind?: SymbolKind, origins?: OriginFilter, space: Space = DEFAULT_SPACE): SymbolRow[] {
292
+ if (db === undefined) return [];
293
+ return (db.prepare(`SELECT ${SYMBOL_COLUMNS} FROM current_analyzer_names WHERE space = ? AND address BETWEEN ? AND ?`).all(space, range.start, range.end) as Raw[])
294
+ .map(toSymbol)
295
+ .filter((row) => (kind === undefined || row.kind === kind) && kept(origins)(row))
296
+ .sort(byAnalyzer);
297
+ }
298
+
78
299
  /** Current regions that share at least one address with [start, end], in address order. */
79
- export function regionsOverlapping(db: DatabaseSync | undefined, start: number, end: number): RegionRow[] {
300
+ export function regionsOverlapping(db: DatabaseSync | undefined, start: number, end: number, space: Space = DEFAULT_SPACE): RegionRow[] {
80
301
  if (db === undefined) return [];
81
302
  return db
82
- .prepare(`SELECT ${REGION_COLUMNS} FROM current_regions WHERE start_address <= ? AND end_address >= ? ORDER BY start_address`)
83
- .all(end, start)
303
+ .prepare(`SELECT ${REGION_COLUMNS} FROM current_regions WHERE space = ? AND start_address <= ? AND end_address >= ? ORDER BY start_address`)
304
+ .all(space, end, start)
84
305
  .map((row) => toRegion(row as Raw));
85
306
  }
86
307
 
87
- export function commentsAt(db: DatabaseSync | undefined, address: number): CommentRow[] {
308
+ export function commentsAt(db: DatabaseSync | undefined, address: number, space: Space = DEFAULT_SPACE): CommentRow[] {
88
309
  if (db === undefined) return [];
89
310
  return db
90
- .prepare(`SELECT ${COMMENT_COLUMNS} FROM current_comments WHERE address = ? ORDER BY placement`)
91
- .all(address)
311
+ .prepare(`SELECT ${COMMENT_COLUMNS} FROM current_comments WHERE space = ? AND address = ? ORDER BY placement`)
312
+ .all(space, address)
92
313
  .map((row) => toComment(row as Raw));
93
314
  }
94
315
 
95
- export function referencesFrom(db: DatabaseSync | undefined, address: number): ReferenceRow[] {
316
+ export function referencesFrom(db: DatabaseSync | undefined, address: number, space: Space = DEFAULT_SPACE): ReferenceRow[] {
96
317
  if (db === undefined) return [];
97
318
  return db
98
- .prepare(`SELECT ${REFERENCE_COLUMNS} FROM current_references WHERE from_address = ? ORDER BY to_address, kind`)
99
- .all(address)
319
+ .prepare(`SELECT ${REFERENCE_COLUMNS} FROM current_references WHERE space = ? AND from_address = ? ORDER BY to_address, kind`)
320
+ .all(space, address)
100
321
  .map((row) => toReference(row as Raw));
101
322
  }
102
323
 
103
- export function referencesTo(db: DatabaseSync | undefined, address: number): ReferenceRow[] {
324
+ export function referencesTo(db: DatabaseSync | undefined, address: number, space: Space = DEFAULT_SPACE): ReferenceRow[] {
104
325
  if (db === undefined) return [];
105
326
  return db
106
- .prepare(`SELECT ${REFERENCE_COLUMNS} FROM current_references WHERE to_address = ? ORDER BY from_address, kind`)
107
- .all(address)
327
+ .prepare(`SELECT ${REFERENCE_COLUMNS} FROM current_references WHERE space = ? AND to_address = ? ORDER BY from_address, kind`)
328
+ .all(space, address)
108
329
  .map((row) => toReference(row as Raw));
109
330
  }
110
331
 
332
+ /** Current suppressions of one entity that share at least one address with [start, end], in address order. */
333
+ export function suppressionsOverlapping(db: DatabaseSync | undefined, entity: SuppressionEntity, start: number, end: number, space: Space = DEFAULT_SPACE): SuppressionRow[] {
334
+ if (db === undefined) return [];
335
+ return db
336
+ .prepare(`SELECT ${SUPPRESSION_COLUMNS} FROM current_suppressions WHERE space = ? AND entity = ? AND start_address <= ? AND end_address >= ? ORDER BY start_address, to_address, kind`)
337
+ .all(space, entity, end, start)
338
+ .map((row) => toSuppression(row as Raw));
339
+ }
340
+
341
+ export function symbolSuppressed(db: DatabaseSync | undefined, address: number, space: Space = DEFAULT_SPACE): boolean {
342
+ return suppressionsOverlapping(db, "symbol", address, address, space).length > 0;
343
+ }
344
+
345
+ export function referenceSuppressed(db: DatabaseSync | undefined, reference: { from: number; to: number; kind: ReferenceKind }, space: Space = DEFAULT_SPACE): boolean {
346
+ return suppressionsOverlapping(db, "reference", reference.from, reference.from, space).some((row) => row.to === reference.to && row.kind === reference.kind);
347
+ }
348
+
349
+ /** Current suppressions at an address: its symbol, the region range that holds it, references from or to it. */
350
+ export function suppressionsAt(db: DatabaseSync | undefined, address: number, space: Space = DEFAULT_SPACE): SuppressionRow[] {
351
+ if (db === undefined) return [];
352
+ return db
353
+ .prepare(
354
+ `SELECT ${SUPPRESSION_COLUMNS} FROM current_suppressions WHERE space = ? AND ((start_address <= ? AND end_address >= ?) OR to_address = ?) ORDER BY entity, start_address, to_address, kind`,
355
+ )
356
+ .all(space, address, address, address)
357
+ .map((row) => toSuppression(row as Raw));
358
+ }
359
+
360
+ export function moduleAt(db: DatabaseSync | undefined, start: number, space: Space = DEFAULT_SPACE): ModuleRow | undefined {
361
+ const row = db?.prepare(`SELECT ${MODULE_COLUMNS} FROM current_modules WHERE space = ? AND start_address = ?`).get(space, start) as Raw | undefined;
362
+ return row === undefined ? undefined : toModule(row);
363
+ }
364
+
365
+ export function moduleWithFile(db: DatabaseSync | undefined, file: string, space: Space = DEFAULT_SPACE): ModuleRow | undefined {
366
+ const row = db?.prepare(`SELECT ${MODULE_COLUMNS} FROM current_modules WHERE space = ? AND file = ?`).get(space, file) as Raw | undefined;
367
+ return row === undefined ? undefined : toModule(row);
368
+ }
369
+
370
+ export function operandAt(db: DatabaseSync | undefined, address: number, space: Space = DEFAULT_SPACE): OperandRow | undefined {
371
+ const row = db?.prepare(`SELECT ${OPERAND_COLUMNS} FROM current_operands WHERE space = ? AND address = ?`).get(space, address) as Raw | undefined;
372
+ return row === undefined ? undefined : toOperand(row);
373
+ }
374
+
375
+ /** Current patches that share at least one address with [start, end], in address order. */
376
+ export function patchesOverlapping(db: DatabaseSync | undefined, start: number, end: number, space: Space = DEFAULT_SPACE): PatchRow[] {
377
+ if (db === undefined) return [];
378
+ return db
379
+ .prepare(`SELECT ${PATCH_COLUMNS} FROM current_patches WHERE space = ? AND start_address <= ? AND end_address >= ? ORDER BY start_address`)
380
+ .all(space, end, start)
381
+ .map((row) => toPatch(row as Raw));
382
+ }
383
+
384
+ /** Current macro uses that share at least one address with [start, end], in address order. */
385
+ export function macroUsesOverlapping(db: DatabaseSync | undefined, start: number, end: number, space: Space = DEFAULT_SPACE): MacroUseRow[] {
386
+ if (db === undefined) return [];
387
+ return db
388
+ .prepare(`SELECT ${MACRO_USE_COLUMNS} FROM current_macro_uses WHERE space = ? AND start_address <= ? AND end_address >= ? ORDER BY start_address`)
389
+ .all(space, end, start)
390
+ .map((row) => toMacroUse(row as Raw));
391
+ }
392
+
393
+ export function macroNamed(db: DatabaseSync | undefined, name: string, space: Space = DEFAULT_SPACE): MacroRow | undefined {
394
+ const row = db?.prepare(`SELECT ${MACRO_COLUMNS} FROM current_macros WHERE space = ? AND name = ?`).get(space, name) as Raw | undefined;
395
+ return row === undefined ? undefined : toMacro(row);
396
+ }
397
+
111
398
  /** Everything currently known at one address: the main lookup for skills. */
112
399
  export interface AddressKnowledge {
400
+ space: Space;
113
401
  address: number;
114
402
  symbol?: SymbolRow;
403
+ /** The second names that analyzers gave the address (D74). */
404
+ analyzerNames: SymbolRow[];
115
405
  /** The current region that contains the address. */
116
406
  region?: RegionRow;
117
407
  comments: CommentRow[];
118
408
  referencesFrom: ReferenceRow[];
119
409
  referencesTo: ReferenceRow[];
120
- revision: number;
410
+ /** How the operand of the instruction here is written (D65). */
411
+ operand?: OperandRow;
412
+ /** The patch whose range holds the address (D72). */
413
+ patch?: PatchRow;
414
+ /** The macro use whose range holds the address (D73). */
415
+ macroUse?: MacroUseRow;
416
+ /** What user/LLM removals block here for the analyzers. */
417
+ suppressions: SuppressionRow[];
418
+ revision: string;
121
419
  }
122
420
 
123
- export function at(db: DatabaseSync | undefined, address: number): AddressKnowledge {
421
+ export function at(db: DatabaseSync | undefined, address: number, space: Space = DEFAULT_SPACE): AddressKnowledge {
124
422
  const result: AddressKnowledge = {
423
+ space,
125
424
  address,
126
- comments: commentsAt(db, address),
127
- referencesFrom: referencesFrom(db, address),
128
- referencesTo: referencesTo(db, address),
425
+ comments: commentsAt(db, address, space),
426
+ referencesFrom: referencesFrom(db, address, space),
427
+ referencesTo: referencesTo(db, address, space),
428
+ analyzerNames: analyzerNamesAt(db, address, space),
429
+ suppressions: suppressionsAt(db, address, space),
129
430
  revision: currentRevision(db),
130
431
  };
131
- const symbol = symbolAt(db, address);
432
+ const symbol = symbolAt(db, address, space);
132
433
  if (symbol !== undefined) result.symbol = symbol;
133
- const [region] = regionsOverlapping(db, address, address);
434
+ const [region] = regionsOverlapping(db, address, address, space);
134
435
  if (region !== undefined) result.region = region;
436
+ const operand = operandAt(db, address, space);
437
+ if (operand !== undefined) result.operand = operand;
438
+ const [patch] = patchesOverlapping(db, address, address, space);
439
+ if (patch !== undefined) result.patch = patch;
440
+ const [macroUse] = macroUsesOverlapping(db, address, address, space);
441
+ if (macroUse !== undefined) result.macroUse = macroUse;
135
442
  return result;
136
443
  }
137
444
 
@@ -142,50 +449,125 @@ export interface Range {
142
449
 
143
450
  const ALL: Range = { start: 0x0000, end: 0xffff };
144
451
 
145
- export function listSymbols(db: DatabaseSync | undefined, range: Range = ALL, kind?: SymbolKind): SymbolRow[] {
452
+ /** Rows of these origins only; undefined keeps every origin (D42). */
453
+ export type OriginFilter = readonly Origin[] | undefined;
454
+ const kept = (origins: OriginFilter) => (row: { origin: Origin }) => origins === undefined || origins.includes(row.origin);
455
+
456
+
457
+ export function listSymbols(db: DatabaseSync | undefined, range: Range = ALL, kind?: SymbolKind, origins?: OriginFilter, space: Space = DEFAULT_SPACE): SymbolRow[] {
146
458
  if (db === undefined) return [];
147
459
  const rows =
148
460
  kind === undefined
149
- ? db.prepare(`SELECT ${SYMBOL_COLUMNS} FROM current_symbols WHERE address BETWEEN ? AND ? ORDER BY address`).all(range.start, range.end)
150
- : db.prepare(`SELECT ${SYMBOL_COLUMNS} FROM current_symbols WHERE address BETWEEN ? AND ? AND kind = ? ORDER BY address`).all(range.start, range.end, kind);
151
- return rows.map((row) => toSymbol(row as Raw));
461
+ ? db.prepare(`SELECT ${SYMBOL_COLUMNS} FROM current_symbols WHERE space = ? AND address BETWEEN ? AND ? ORDER BY address`).all(space, range.start, range.end)
462
+ : db.prepare(`SELECT ${SYMBOL_COLUMNS} FROM current_symbols WHERE space = ? AND address BETWEEN ? AND ? AND kind = ? ORDER BY address`).all(space, range.start, range.end, kind);
463
+ return rows.map((row) => toSymbol(row as Raw)).filter(kept(origins));
152
464
  }
153
465
 
154
- export function listRegions(db: DatabaseSync | undefined, range: Range = ALL, type?: RegionType): RegionRow[] {
155
- return regionsOverlapping(db, range.start, range.end).filter((region) => type === undefined || region.type === type);
466
+ export function listRegions(db: DatabaseSync | undefined, range: Range = ALL, type?: RegionType, origins?: OriginFilter, space: Space = DEFAULT_SPACE): RegionRow[] {
467
+ return regionsOverlapping(db, range.start, range.end, space).filter((region) => (type === undefined || region.type === type) && kept(origins)(region));
156
468
  }
157
469
 
158
- export function listComments(db: DatabaseSync | undefined, range: Range = ALL): CommentRow[] {
470
+ export function listComments(db: DatabaseSync | undefined, range: Range = ALL, origins?: OriginFilter, space: Space = DEFAULT_SPACE): CommentRow[] {
159
471
  if (db === undefined) return [];
160
472
  return db
161
- .prepare(`SELECT ${COMMENT_COLUMNS} FROM current_comments WHERE address BETWEEN ? AND ? ORDER BY address, placement`)
162
- .all(range.start, range.end)
163
- .map((row) => toComment(row as Raw));
473
+ .prepare(`SELECT ${COMMENT_COLUMNS} FROM current_comments WHERE space = ? AND address BETWEEN ? AND ? ORDER BY address, placement`)
474
+ .all(space, range.start, range.end)
475
+ .map((row) => toComment(row as Raw))
476
+ .filter(kept(origins));
164
477
  }
165
478
 
166
479
  /** Current references whose source or target lies in the range. */
167
- export function listReferences(db: DatabaseSync | undefined, range: Range = ALL, kind?: ReferenceKind): ReferenceRow[] {
480
+ export function listReferences(db: DatabaseSync | undefined, range: Range = ALL, kind?: ReferenceKind, origins?: OriginFilter, space: Space = DEFAULT_SPACE): ReferenceRow[] {
168
481
  if (db === undefined) return [];
169
482
  return db
170
483
  .prepare(
171
- `SELECT ${REFERENCE_COLUMNS} FROM current_references WHERE (from_address BETWEEN ? AND ? OR to_address BETWEEN ? AND ?) ORDER BY from_address, to_address, kind`,
484
+ `SELECT ${REFERENCE_COLUMNS} FROM current_references WHERE space = ? AND (from_address BETWEEN ? AND ? OR to_address BETWEEN ? AND ?) ORDER BY from_address, to_address, kind`,
172
485
  )
173
- .all(range.start, range.end, range.start, range.end)
486
+ .all(space, range.start, range.end, range.start, range.end)
174
487
  .map((row) => toReference(row as Raw))
175
- .filter((reference) => kind === undefined || reference.kind === kind);
488
+ .filter((reference) => (kind === undefined || reference.kind === kind) && kept(origins)(reference));
176
489
  }
177
490
 
178
- /** Case-insensitive text search in current symbol names and comment texts. */
179
- export function search(db: DatabaseSync | undefined, text: string, limit = 50): { symbols: SymbolRow[]; comments: CommentRow[] } {
180
- if (db === undefined || text.length === 0) return { symbols: [], comments: [] };
491
+ /** Current suppressions whose place lies in the range: a reference by its source or target. */
492
+ export function listSuppressions(db: DatabaseSync | undefined, range: Range = ALL, space: Space = DEFAULT_SPACE): SuppressionRow[] {
493
+ if (db === undefined) return [];
494
+ return db
495
+ .prepare(
496
+ `SELECT ${SUPPRESSION_COLUMNS} FROM current_suppressions WHERE space = ? AND ((start_address <= ? AND end_address >= ?) OR to_address BETWEEN ? AND ?) ORDER BY start_address, entity, to_address, kind`,
497
+ )
498
+ .all(space, range.end, range.start, range.start, range.end)
499
+ .map((row) => toSuppression(row as Raw));
500
+ }
501
+
502
+ /** Current modules that start in the range, in address order. */
503
+ export function listModules(db: DatabaseSync | undefined, range: Range = ALL, origins?: OriginFilter, space: Space = DEFAULT_SPACE): ModuleRow[] {
504
+ if (db === undefined) return [];
505
+ return db
506
+ .prepare(`SELECT ${MODULE_COLUMNS} FROM current_modules WHERE space = ? AND start_address BETWEEN ? AND ? ORDER BY start_address`)
507
+ .all(space, range.start, range.end)
508
+ .map((row) => toModule(row as Raw))
509
+ .filter(kept(origins));
510
+ }
511
+
512
+ /** Current operand expressions of instructions in the range, in address order. */
513
+ export function listOperands(db: DatabaseSync | undefined, range: Range = ALL, origins?: OriginFilter, space: Space = DEFAULT_SPACE): OperandRow[] {
514
+ if (db === undefined) return [];
515
+ return db
516
+ .prepare(`SELECT ${OPERAND_COLUMNS} FROM current_operands WHERE space = ? AND address BETWEEN ? AND ? ORDER BY address`)
517
+ .all(space, range.start, range.end)
518
+ .map((row) => toOperand(row as Raw))
519
+ .filter(kept(origins));
520
+ }
521
+
522
+ /** Current patches that share an address with the range, of one switch or of all, in address order. */
523
+ export function listPatches(db: DatabaseSync | undefined, range: Range = ALL, switchName?: string, origins?: OriginFilter, space: Space = DEFAULT_SPACE): PatchRow[] {
524
+ return patchesOverlapping(db, range.start, range.end, space).filter((patch) => (switchName === undefined || patch.switch === switchName) && kept(origins)(patch));
525
+ }
526
+
527
+ /** Current macros, by name. */
528
+ export function listMacros(db: DatabaseSync | undefined, origins?: OriginFilter, space: Space = DEFAULT_SPACE): MacroRow[] {
529
+ if (db === undefined) return [];
530
+ return db
531
+ .prepare(`SELECT ${MACRO_COLUMNS} FROM current_macros WHERE space = ? ORDER BY name`)
532
+ .all(space)
533
+ .map((row) => toMacro(row as Raw))
534
+ .filter(kept(origins));
535
+ }
536
+
537
+ /** Current macro uses that share an address with the range, of one macro or of all, in address order. */
538
+ export function listMacroUses(db: DatabaseSync | undefined, range: Range = ALL, macro?: string, origins?: OriginFilter, space: Space = DEFAULT_SPACE): MacroUseRow[] {
539
+ return macroUsesOverlapping(db, range.start, range.end, space).filter((use) => (macro === undefined || use.macro === macro) && kept(origins)(use));
540
+ }
541
+
542
+ /** The spaces that hold current knowledge, the C64 first. */
543
+ export function listSpaces(db: DatabaseSync | undefined): Space[] {
544
+ if (db === undefined) return [];
545
+ const rows = db
546
+ .prepare(
547
+ `SELECT space FROM current_symbols UNION SELECT space FROM current_analyzer_names UNION SELECT space FROM current_regions UNION SELECT space FROM current_comments UNION SELECT space FROM current_references UNION SELECT space FROM current_suppressions UNION SELECT space FROM current_modules UNION SELECT space FROM current_operands UNION SELECT space FROM current_patches UNION SELECT space FROM current_macros UNION SELECT space FROM current_macro_uses UNION SELECT space FROM current_memory UNION SELECT space FROM current_entries UNION SELECT space FROM current_coverage`,
548
+ )
549
+ .all() as Array<{ space: Space }>;
550
+ const order = (space: Space) => (space === "c64" ? -2 : space === "drive8" ? -1 : Number(space.slice(4)));
551
+ return rows.map((row) => row.space).sort((a, b) => order(a) - order(b));
552
+ }
553
+
554
+ /** Case-insensitive text search in current symbol names and comment texts of one space. */
555
+ export function search(db: DatabaseSync | undefined, text: string, limit = 50, origins?: OriginFilter, space: Space = DEFAULT_SPACE): { symbols: SymbolRow[]; analyzerNames: SymbolRow[]; comments: CommentRow[] } {
556
+ if (db === undefined || text.length === 0) return { symbols: [], analyzerNames: [], comments: [] };
181
557
  const pattern = `%${text.replace(/[\\%_]/g, (character) => `\\${character}`)}%`;
558
+ const only = origins === undefined ? "" : ` AND origin IN (${origins.map(() => "?").join(", ")})`;
559
+ const values = origins ?? [];
182
560
  const symbols = db
183
- .prepare(`SELECT ${SYMBOL_COLUMNS} FROM current_symbols WHERE name LIKE ? ESCAPE '\\' ORDER BY address LIMIT ?`)
184
- .all(pattern, limit)
561
+ .prepare(`SELECT ${SYMBOL_COLUMNS} FROM current_symbols WHERE space = ? AND name LIKE ? ESCAPE '\\'${only} ORDER BY address LIMIT ?`)
562
+ .all(space, pattern, ...values, limit)
563
+ .map((row) => toSymbol(row as Raw));
564
+ const analyzerNames = db
565
+ .prepare(`SELECT ${SYMBOL_COLUMNS} FROM current_analyzer_names WHERE space = ? AND name LIKE ? ESCAPE '\\'${only} ORDER BY address LIMIT ?`)
566
+ .all(space, pattern, ...values, limit)
185
567
  .map((row) => toSymbol(row as Raw));
186
568
  const comments = db
187
- .prepare(`SELECT ${COMMENT_COLUMNS} FROM current_comments WHERE text LIKE ? ESCAPE '\\' ORDER BY address, placement LIMIT ?`)
188
- .all(pattern, limit)
569
+ .prepare(`SELECT ${COMMENT_COLUMNS} FROM current_comments WHERE space = ? AND text LIKE ? ESCAPE '\\'${only} ORDER BY address, placement LIMIT ?`)
570
+ .all(space, pattern, ...values, limit)
189
571
  .map((row) => toComment(row as Raw));
190
- return { symbols, comments };
572
+ return { symbols, analyzerNames, comments };
191
573
  }