@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.
- package/README.md +28 -62
- package/distribution/package.ts +1 -16
- package/distribution/plugin.ts +5 -3
- package/package.json +1 -1
- package/skills/c64-assembler/SKILL.md +25 -6
- package/skills/c64-assembler/scripts/assemble.ts +45 -10
- package/skills/c64-assembler/scripts/lib/compare.ts +48 -0
- package/skills/c64-basic/SKILL.md +15 -7
- package/skills/c64-basic/scripts/basic.ts +4 -4
- package/skills/c64-disk/SKILL.md +34 -11
- package/skills/c64-disk/scripts/disk.ts +65 -10
- package/skills/c64-disk/scripts/lib/containers.ts +124 -0
- package/skills/c64-docs/SKILL.md +138 -0
- package/skills/c64-docs/references/page-types.md +141 -0
- package/skills/c64-docs/references/style.md +43 -0
- package/skills/c64-docs/scripts/generate.ts +102 -0
- package/skills/c64-docs/scripts/lib/links.ts +155 -0
- package/skills/c64-docs/scripts/lib/markdown.ts +79 -0
- package/skills/c64-docs/scripts/lib/reference.ts +406 -0
- package/skills/c64-docs/scripts/lib/source-map.ts +81 -0
- package/skills/c64-docs/scripts/link.ts +207 -0
- package/skills/c64-emulator/SKILL.md +29 -8
- package/skills/c64-knowledge/SKILL.md +201 -15
- package/skills/c64-knowledge/scripts/knowledge.ts +927 -78
- package/skills/c64-knowledge/scripts/lib/listing.ts +162 -0
- package/skills/c64-memory-map/SKILL.md +14 -2
- package/skills/c64-memory-map/scripts/memmap.ts +15 -4
- package/skills/c64-provenance/SKILL.md +100 -11
- package/skills/c64-provenance/scripts/lib/compare.ts +152 -0
- package/skills/c64-provenance/scripts/lib/library.ts +584 -0
- package/skills/c64-provenance/scripts/lib/runtime.ts +191 -0
- package/skills/c64-provenance/scripts/lib/separate.ts +140 -0
- package/skills/c64-provenance/scripts/provenance.ts +477 -24
- package/skills/c64-reverse-engineering/SKILL.md +90 -11
- package/skills/c64-source/SKILL.md +174 -0
- package/skills/c64-source/scripts/generate.ts +788 -0
- package/skills/c64-source/scripts/lib/loader.ts +630 -0
- package/skills/c64-source/scripts/lib/source.ts +1121 -0
- package/skills/c64-static-analysis/SKILL.md +53 -13
- package/skills/c64-static-analysis/scripts/analyze.ts +260 -31
- package/skills/c64-static-analysis/scripts/lib/coverage.ts +72 -0
- package/skills/c64-static-analysis/scripts/{findings.ts → lib/findings.ts} +11 -5
- package/skills/c64-static-analysis/scripts/lib/idioms.ts +158 -0
- package/skills/c64-static-analysis/scripts/lib/instructions.ts +105 -0
- package/skills/c64-static-analysis/scripts/lib/macros.ts +233 -0
- package/skills/c64-static-analysis/scripts/lib/pitfalls.ts +68 -0
- package/skills/c64-static-analysis/scripts/lib/pointers.ts +108 -0
- package/skills/c64-testing/SKILL.md +57 -4
- package/skills/c64-testing/references/scenario-format.md +50 -6
- package/skills/c64-testing/scripts/checklist.ts +3 -3
- package/skills/c64-testing/scripts/diff.ts +167 -0
- package/skills/c64-testing/scripts/lib/diff.ts +70 -0
- package/skills/c64-testing/scripts/{scenario.ts → lib/scenario.ts} +183 -152
- package/skills/c64-testing/scripts/run.ts +241 -0
- package/skills/c64-testing/scripts/test.ts +20 -10
- package/skills/c64-unpacker/SKILL.md +88 -21
- package/skills/c64-unpacker/scripts/lib/evidence.ts +207 -0
- package/skills/c64-unpacker/scripts/lib/loads.ts +209 -0
- package/skills/c64-unpacker/scripts/lib/machine.ts +80 -0
- package/skills/c64-unpacker/scripts/lib/stages.ts +142 -0
- package/skills/c64-unpacker/scripts/unpack.ts +545 -61
- package/src/c64.ts +240 -12
- package/src/cli/cleanup.ts +1 -1
- package/src/cli/main.ts +14 -8
- package/src/cli/status.ts +105 -11
- package/src/graphics.ts +158 -0
- package/src/host/main.ts +57 -31
- package/src/host/server.ts +30 -47
- package/src/host/tools/c1541.ts +34 -5
- package/src/host/tools/index.ts +6 -2
- package/src/host/tools/petcat.ts +10 -3
- package/src/host/tools/status.ts +23 -6
- package/src/host/vice/adapter.ts +64 -20
- package/src/host/vice/chips.ts +11 -1
- package/src/host/vice/process.ts +19 -8
- package/src/host/vice/screen.ts +4 -44
- package/src/host/vice/session.ts +343 -60
- package/src/host-client/autostart.ts +55 -0
- package/src/host-client/connect.ts +14 -14
- package/src/host-client/coverage.ts +36 -0
- package/src/host-client/image.ts +63 -0
- package/src/host-client/steps.ts +175 -0
- package/src/host-client/tools.ts +7 -6
- package/src/host-client/transfer.ts +60 -10
- package/src/host-client/vice-session.ts +107 -28
- package/src/knowledge/conflicts.ts +161 -0
- package/src/knowledge/database.ts +150 -38
- package/src/knowledge/expression.ts +209 -0
- package/src/knowledge/graph.ts +104 -0
- package/src/knowledge/history.ts +105 -27
- package/src/knowledge/import.ts +141 -85
- package/src/knowledge/join.ts +211 -0
- package/src/knowledge/program.ts +258 -0
- package/src/knowledge/read.ts +445 -63
- package/src/knowledge/schema.ts +277 -65
- package/src/knowledge/text.ts +415 -0
- package/src/knowledge/write.ts +1145 -150
- package/src/mcp/main.ts +44 -19
- package/src/mcp/output.ts +39 -0
- package/src/mcp/server.ts +15 -4
- package/src/mcp/tools/debug.ts +202 -31
- package/src/mcp/tools/execution.ts +39 -5
- package/src/mcp/tools/input.ts +7 -4
- package/src/mcp/tools/machine.ts +14 -2
- package/src/mcp/tools/media.ts +44 -16
- package/src/mcp/tools/memory.ts +68 -12
- package/src/mcp/tools/video.ts +20 -9
- package/src/native/discover.ts +44 -6
- package/src/native/ghidra/analyze.ts +2 -2
- package/src/native/ghidra/index.ts +19 -3
- package/src/native/ghidra/language/c64rt_6510.slaspec +4 -1
- package/src/native/ghidra/scripts/C64Export.java +33 -1
- package/src/native/ghidra/scripts/C64Prepare.java +7 -1
- package/src/native/status.ts +15 -3
- package/src/native/types.ts +43 -8
- package/src/opcodes.ts +133 -0
- package/src/package.ts +21 -0
- package/{skills/c64-memory-map/scripts → src}/platform.ts +87 -7
- package/src/project.ts +166 -3
- package/src/protocol/messages.ts +17 -10
- package/src/protocol/tools.ts +27 -22
- package/src/protocol/vice.ts +255 -33
- package/src/script.ts +49 -9
- package/src/trace.ts +55 -23
- package/skills/c64-provenance/scripts/compare.ts +0 -92
- package/skills/c64-unpacker/scripts/evidence.ts +0 -135
package/src/knowledge/read.ts
CHANGED
|
@@ -1,137 +1,444 @@
|
|
|
1
|
-
// Current-state knowledge queries. A project without
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
69
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
179
|
-
export function
|
|
180
|
-
if (db === undefined
|
|
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
|
}
|