@henols/c64-re-tools 2.0.0-rc.11 → 2.0.0-rc.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -5
- package/package.json +1 -1
- package/skills/c64-assembler/SKILL.md +11 -2
- package/skills/c64-assembler/scripts/assemble.ts +4 -4
- package/skills/c64-basic/SKILL.md +8 -1
- package/skills/c64-basic/scripts/basic.ts +3 -3
- package/skills/c64-disk/SKILL.md +10 -3
- package/skills/c64-disk/scripts/disk.ts +4 -4
- package/skills/c64-emulator/SKILL.md +4 -0
- package/skills/c64-knowledge/SKILL.md +62 -12
- package/skills/c64-knowledge/scripts/knowledge.ts +352 -102
- package/skills/c64-memory-map/SKILL.md +7 -0
- package/skills/c64-memory-map/scripts/memmap.ts +1 -1
- package/skills/c64-provenance/SKILL.md +56 -3
- package/skills/c64-provenance/scripts/library.ts +584 -0
- package/skills/c64-provenance/scripts/provenance.ts +201 -9
- package/skills/c64-reverse-engineering/SKILL.md +16 -2
- package/skills/c64-source/SKILL.md +38 -7
- package/skills/c64-source/scripts/generate.ts +213 -29
- package/skills/c64-source/scripts/loader.ts +3 -1
- package/skills/c64-source/scripts/opcodes.ts +19 -0
- package/skills/c64-source/scripts/source.ts +387 -35
- package/skills/c64-static-analysis/SKILL.md +11 -2
- package/skills/c64-static-analysis/scripts/analyze.ts +95 -8
- package/skills/c64-static-analysis/scripts/macros.ts +233 -0
- package/skills/c64-testing/SKILL.md +8 -1
- package/skills/c64-testing/references/scenario-format.md +1 -1
- package/skills/c64-testing/scripts/checklist.ts +2 -2
- package/skills/c64-testing/scripts/run.ts +4 -4
- package/skills/c64-testing/scripts/scenario.ts +1 -1
- package/skills/c64-testing/scripts/test.ts +4 -4
- package/skills/c64-unpacker/SKILL.md +11 -4
- package/skills/c64-unpacker/scripts/unpack.ts +4 -4
- package/src/cli/main.ts +5 -3
- package/src/host-client/image.ts +1 -1
- package/src/host-client/transfer.ts +34 -10
- package/src/host-client/vice-session.ts +9 -9
- package/src/knowledge/conflicts.ts +157 -0
- package/src/knowledge/database.ts +150 -38
- package/src/knowledge/history.ts +54 -20
- package/src/knowledge/import.ts +3 -2
- package/src/knowledge/read.ts +169 -20
- package/src/knowledge/schema.ts +115 -72
- package/src/knowledge/text.ts +391 -0
- package/src/knowledge/write.ts +508 -51
- package/src/mcp/output.ts +14 -7
- package/src/mcp/tools/debug.ts +8 -6
- package/src/mcp/tools/media.ts +17 -13
- package/src/mcp/tools/memory.ts +9 -5
- package/src/mcp/tools/video.ts +6 -5
- package/src/platform.ts +28 -5
- package/src/project.ts +166 -3
- package/src/protocol/tools.ts +1 -1
- package/src/script.ts +46 -7
package/src/knowledge/history.ts
CHANGED
|
@@ -6,32 +6,43 @@ import type { DatabaseSync } from "node:sqlite";
|
|
|
6
6
|
import { KnowledgeError } from "./database.ts";
|
|
7
7
|
import {
|
|
8
8
|
COMMENT_COLUMNS,
|
|
9
|
+
MACRO_COLUMNS,
|
|
10
|
+
MACRO_USE_COLUMNS,
|
|
9
11
|
MODULE_COLUMNS,
|
|
10
12
|
OPERAND_COLUMNS,
|
|
13
|
+
PATCH_COLUMNS,
|
|
11
14
|
REFERENCE_COLUMNS,
|
|
12
15
|
REGION_COLUMNS,
|
|
16
|
+
resolveRevision,
|
|
13
17
|
SUPPRESSION_COLUMNS,
|
|
14
18
|
SYMBOL_COLUMNS,
|
|
15
19
|
toComment,
|
|
20
|
+
toMacro,
|
|
21
|
+
toMacroUse,
|
|
16
22
|
toModule,
|
|
17
23
|
toOperand,
|
|
24
|
+
toPatch,
|
|
18
25
|
toReference,
|
|
19
26
|
toRegion,
|
|
20
27
|
toSuppression,
|
|
21
28
|
toSymbol,
|
|
22
29
|
type CommentRow,
|
|
30
|
+
type MacroRow,
|
|
31
|
+
type MacroUseRow,
|
|
23
32
|
type ModuleRow,
|
|
24
33
|
type OperandRow,
|
|
34
|
+
type PatchRow,
|
|
25
35
|
type ReferenceRow,
|
|
26
36
|
type RegionRow,
|
|
27
37
|
type SuppressionRow,
|
|
28
38
|
type SymbolRow,
|
|
29
39
|
} from "./read.ts";
|
|
30
40
|
import { DEFAULT_SPACE, type EvidenceKind, type Origin, type Space } from "./schema.ts";
|
|
41
|
+
import { compareText } from "./text.ts";
|
|
31
42
|
import type { Evidence } from "./write.ts";
|
|
32
43
|
|
|
33
44
|
export interface Revision {
|
|
34
|
-
id:
|
|
45
|
+
id: string;
|
|
35
46
|
createdAt: string;
|
|
36
47
|
origin: Origin;
|
|
37
48
|
operation: string;
|
|
@@ -44,19 +55,22 @@ export interface Revision {
|
|
|
44
55
|
|
|
45
56
|
/** One knowledge row with its validity: from its first revision until the revision that closed it. */
|
|
46
57
|
export type HistoryEntry =
|
|
47
|
-
| { entity: "symbol"; row: SymbolRow; from:
|
|
48
|
-
| { entity: "region"; row: RegionRow; from:
|
|
49
|
-
| { entity: "comment"; row: CommentRow; from:
|
|
50
|
-
| { entity: "reference"; row: ReferenceRow; from:
|
|
51
|
-
| { entity: "suppression"; row: SuppressionRow; from:
|
|
52
|
-
| { entity: "module"; row: ModuleRow; from:
|
|
53
|
-
| { entity: "operand"; row: OperandRow; from:
|
|
58
|
+
| { entity: "symbol"; row: SymbolRow; from: string; to?: string }
|
|
59
|
+
| { entity: "region"; row: RegionRow; from: string; to?: string }
|
|
60
|
+
| { entity: "comment"; row: CommentRow; from: string; to?: string }
|
|
61
|
+
| { entity: "reference"; row: ReferenceRow; from: string; to?: string }
|
|
62
|
+
| { entity: "suppression"; row: SuppressionRow; from: string; to?: string }
|
|
63
|
+
| { entity: "module"; row: ModuleRow; from: string; to?: string }
|
|
64
|
+
| { entity: "operand"; row: OperandRow; from: string; to?: string }
|
|
65
|
+
| { entity: "patch"; row: PatchRow; from: string; to?: string }
|
|
66
|
+
| { entity: "macro"; row: MacroRow; from: string; to?: string }
|
|
67
|
+
| { entity: "macro-use"; row: MacroUseRow; from: string; to?: string };
|
|
54
68
|
|
|
55
69
|
type Raw = Record<string, unknown>;
|
|
56
70
|
|
|
57
71
|
function toRevision(row: Raw): Revision {
|
|
58
72
|
const revision: Revision = {
|
|
59
|
-
id: row.id as
|
|
73
|
+
id: row.id as string,
|
|
60
74
|
createdAt: row.created_at as string,
|
|
61
75
|
origin: row.origin as Origin,
|
|
62
76
|
operation: row.operation as string,
|
|
@@ -68,13 +82,14 @@ function toRevision(row: Raw): Revision {
|
|
|
68
82
|
}
|
|
69
83
|
|
|
70
84
|
function entry<E extends HistoryEntry["entity"]>(entity: E, row: unknown, raw: Raw): HistoryEntry {
|
|
71
|
-
const result = { entity, row, from: raw.valid_from_revision as
|
|
72
|
-
if (raw.valid_to_revision !== null) result.to = raw.valid_to_revision as
|
|
85
|
+
const result = { entity, row, from: raw.valid_from_revision as string } as HistoryEntry;
|
|
86
|
+
if (raw.valid_to_revision !== null) result.to = raw.valid_to_revision as string;
|
|
73
87
|
return result;
|
|
74
88
|
}
|
|
75
89
|
|
|
90
|
+
/** Revision ids sort in time order as text (D69); a current row sorts after every closed one. */
|
|
76
91
|
function bySequence(a: HistoryEntry, b: HistoryEntry): number {
|
|
77
|
-
return a.from
|
|
92
|
+
return compareText(a.from, b.from) || compareText(a.to ?? "~", b.to ?? "~");
|
|
78
93
|
}
|
|
79
94
|
|
|
80
95
|
/** Every row, current or closed, that concerns one address of a space, oldest first. */
|
|
@@ -97,9 +112,23 @@ export function historyAt(db: DatabaseSync | undefined, address: number, space:
|
|
|
97
112
|
}
|
|
98
113
|
for (const raw of db.prepare(`SELECT ${MODULE_COLUMNS}${validity} FROM modules WHERE space = ? AND start_address = ?`).all(space, address) as Raw[]) entries.push(entry("module", toModule(raw), raw));
|
|
99
114
|
for (const raw of db.prepare(`SELECT ${OPERAND_COLUMNS}${validity} FROM operands WHERE space = ? AND address = ?`).all(space, address) as Raw[]) entries.push(entry("operand", toOperand(raw), raw));
|
|
115
|
+
for (const raw of db.prepare(`SELECT ${PATCH_COLUMNS}${validity} FROM patches WHERE space = ? AND start_address <= ? AND end_address >= ?`).all(space, address, address) as Raw[]) {
|
|
116
|
+
entries.push(entry("patch", toPatch(raw), raw));
|
|
117
|
+
}
|
|
118
|
+
for (const raw of db.prepare(`SELECT ${MACRO_USE_COLUMNS}${validity} FROM macro_uses WHERE space = ? AND start_address <= ? AND end_address >= ?`).all(space, address, address) as Raw[]) {
|
|
119
|
+
entries.push(entry("macro-use", toMacroUse(raw), raw));
|
|
120
|
+
}
|
|
100
121
|
return entries.sort(bySequence);
|
|
101
122
|
}
|
|
102
123
|
|
|
124
|
+
/** Every row of a macro with this name, and every use of it, oldest first. */
|
|
125
|
+
export function historyOfMacro(db: DatabaseSync | undefined, name: string, space: Space = DEFAULT_SPACE): HistoryEntry[] {
|
|
126
|
+
if (db === undefined) return [];
|
|
127
|
+
const macros = (db.prepare(`SELECT ${MACRO_COLUMNS}, valid_to_revision FROM macros WHERE space = ? AND name = ?`).all(space, name) as Raw[]).map((raw) => entry("macro", toMacro(raw), raw));
|
|
128
|
+
const uses = (db.prepare(`SELECT ${MACRO_USE_COLUMNS}, valid_to_revision FROM macro_uses WHERE space = ? AND macro = ?`).all(space, name) as Raw[]).map((raw) => entry("macro-use", toMacroUse(raw), raw));
|
|
129
|
+
return [...macros, ...uses].sort(bySequence);
|
|
130
|
+
}
|
|
131
|
+
|
|
103
132
|
/** Every symbol row of a space that ever had this name, oldest first: a name can move between addresses. */
|
|
104
133
|
export function historyNamed(db: DatabaseSync | undefined, name: string, space: Space = DEFAULT_SPACE): HistoryEntry[] {
|
|
105
134
|
if (db === undefined) return [];
|
|
@@ -109,7 +138,7 @@ export function historyNamed(db: DatabaseSync | undefined, name: string, space:
|
|
|
109
138
|
}
|
|
110
139
|
|
|
111
140
|
/** Why a revision was written and what it rests on: its description and its evidence, when it has them. */
|
|
112
|
-
export function revisionNote(db: DatabaseSync | undefined, id:
|
|
141
|
+
export function revisionNote(db: DatabaseSync | undefined, id: string): { reason?: string; evidence?: Evidence[] } {
|
|
113
142
|
const raw = db?.prepare("SELECT * FROM revisions WHERE id = ?").get(id) as Raw | undefined;
|
|
114
143
|
if (db === undefined || raw === undefined) return {};
|
|
115
144
|
const { description, evidence } = withEvidence(db, toRevision(raw));
|
|
@@ -125,13 +154,13 @@ function withEvidence(db: DatabaseSync, revision: Revision): Revision {
|
|
|
125
154
|
}
|
|
126
155
|
|
|
127
156
|
/** Revisions, newest first; with `before`, only revisions older than it. */
|
|
128
|
-
export function revisions(db: DatabaseSync | undefined, options: { limit?: number; before?:
|
|
157
|
+
export function revisions(db: DatabaseSync | undefined, options: { limit?: number; before?: string } = {}): Revision[] {
|
|
129
158
|
if (db === undefined) return [];
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
.prepare("SELECT * FROM revisions
|
|
133
|
-
.all(options.before
|
|
134
|
-
).map((row) => withEvidence(db, toRevision(row)));
|
|
159
|
+
const rows =
|
|
160
|
+
options.before === undefined
|
|
161
|
+
? db.prepare("SELECT * FROM revisions ORDER BY id DESC LIMIT ?").all(options.limit ?? 50)
|
|
162
|
+
: db.prepare("SELECT * FROM revisions WHERE id < ? ORDER BY id DESC LIMIT ?").all(resolveRevision(db, options.before), options.limit ?? 50);
|
|
163
|
+
return (rows as Raw[]).map((row) => withEvidence(db, toRevision(row)));
|
|
135
164
|
}
|
|
136
165
|
|
|
137
166
|
export interface RevisionChanges {
|
|
@@ -143,7 +172,8 @@ export interface RevisionChanges {
|
|
|
143
172
|
}
|
|
144
173
|
|
|
145
174
|
/** One revision and exactly what it changed. */
|
|
146
|
-
export function revision(db: DatabaseSync | undefined,
|
|
175
|
+
export function revision(db: DatabaseSync | undefined, text: string): RevisionChanges {
|
|
176
|
+
const id = resolveRevision(db, text);
|
|
147
177
|
const raw = db?.prepare("SELECT * FROM revisions WHERE id = ?").get(id) as Raw | undefined;
|
|
148
178
|
if (db === undefined || raw === undefined) throw new KnowledgeError("not-found", `There is no revision ${id}.`);
|
|
149
179
|
const tables = [
|
|
@@ -154,6 +184,9 @@ export function revision(db: DatabaseSync | undefined, id: number): RevisionChan
|
|
|
154
184
|
["suppression", "suppressions", SUPPRESSION_COLUMNS, toSuppression],
|
|
155
185
|
["module", "modules", MODULE_COLUMNS, toModule],
|
|
156
186
|
["operand", "operands", OPERAND_COLUMNS, toOperand],
|
|
187
|
+
["patch", "patches", PATCH_COLUMNS, toPatch],
|
|
188
|
+
["macro", "macros", MACRO_COLUMNS, toMacro],
|
|
189
|
+
["macro-use", "macro_uses", MACRO_USE_COLUMNS, toMacroUse],
|
|
157
190
|
] as const;
|
|
158
191
|
const added: HistoryEntry[] = [];
|
|
159
192
|
const closed: HistoryEntry[] = [];
|
|
@@ -167,3 +200,4 @@ export function revision(db: DatabaseSync | undefined, id: number): RevisionChan
|
|
|
167
200
|
}
|
|
168
201
|
return { revision: withEvidence(db, toRevision(raw)), added, closed };
|
|
169
202
|
}
|
|
203
|
+
|
package/src/knowledge/import.ts
CHANGED
|
@@ -55,7 +55,7 @@ export interface NormalizedFindings {
|
|
|
55
55
|
|
|
56
56
|
export interface ImportContext {
|
|
57
57
|
/** The revision the caller read; the import is refused if knowledge changed since. */
|
|
58
|
-
expectedRevision?:
|
|
58
|
+
expectedRevision?: string;
|
|
59
59
|
description?: string;
|
|
60
60
|
/** The SHA-256 of the analyzed image, as lowercase hex. */
|
|
61
61
|
inputHash?: string;
|
|
@@ -92,7 +92,7 @@ export interface ImportCounts {
|
|
|
92
92
|
|
|
93
93
|
export interface ImportResult {
|
|
94
94
|
/** The new revision, or null when nothing changed. */
|
|
95
|
-
revision:
|
|
95
|
+
revision: string | null;
|
|
96
96
|
symbols: ImportCounts;
|
|
97
97
|
regions: ImportCounts;
|
|
98
98
|
references: ImportCounts;
|
|
@@ -386,6 +386,7 @@ export function importFindings(db: DatabaseSync, findings: NormalizedFindings, c
|
|
|
386
386
|
importSymbols(db, change, findings, coverage, result);
|
|
387
387
|
importRegions(db, change, findings, coverage, result);
|
|
388
388
|
importReferences(db, change, findings, coverage, result);
|
|
389
|
+
change.check();
|
|
389
390
|
result.revision = change.revision;
|
|
390
391
|
return result;
|
|
391
392
|
});
|
package/src/knowledge/read.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
// Current-state knowledge queries. A project without
|
|
1
|
+
// Current-state knowledge queries. A project without knowledge
|
|
2
2
|
// reads as empty knowledge. Each query reads one address space (D53); the
|
|
3
3
|
// default is the C64.
|
|
4
4
|
|
|
5
5
|
import type { DatabaseSync } from "node:sqlite";
|
|
6
6
|
|
|
7
|
+
import { KnowledgeError } from "./database.ts";
|
|
7
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";
|
|
8
10
|
|
|
9
11
|
export interface SymbolRow {
|
|
10
12
|
space: Space;
|
|
@@ -12,7 +14,7 @@ export interface SymbolRow {
|
|
|
12
14
|
name: string;
|
|
13
15
|
kind: SymbolKind;
|
|
14
16
|
origin: Origin;
|
|
15
|
-
revision:
|
|
17
|
+
revision: string;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
export interface RegionRow {
|
|
@@ -23,7 +25,7 @@ export interface RegionRow {
|
|
|
23
25
|
/** Bytes per element of a table; the length is a multiple of it (D42). */
|
|
24
26
|
element?: number;
|
|
25
27
|
origin: Origin;
|
|
26
|
-
revision:
|
|
28
|
+
revision: string;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
export interface CommentRow {
|
|
@@ -32,7 +34,7 @@ export interface CommentRow {
|
|
|
32
34
|
placement: CommentPlacement;
|
|
33
35
|
text: string;
|
|
34
36
|
origin: Origin;
|
|
35
|
-
revision:
|
|
37
|
+
revision: string;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
export interface ReferenceRow {
|
|
@@ -41,7 +43,7 @@ export interface ReferenceRow {
|
|
|
41
43
|
to: number;
|
|
42
44
|
kind: ReferenceKind;
|
|
43
45
|
origin: Origin;
|
|
44
|
-
revision:
|
|
46
|
+
revision: string;
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
/** A place that a user/LLM removal blocks for every analyzer (D36). `to` and `kind` are set for a reference only. */
|
|
@@ -53,7 +55,7 @@ export interface SuppressionRow {
|
|
|
53
55
|
to?: number;
|
|
54
56
|
kind?: ReferenceKind;
|
|
55
57
|
origin: Extract<Origin, "user" | "llm">;
|
|
56
|
-
revision:
|
|
58
|
+
revision: string;
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
/** Where a file of the generated source starts (D48): it runs to the next module or the end of its segment. */
|
|
@@ -63,7 +65,7 @@ export interface ModuleRow {
|
|
|
63
65
|
file: string;
|
|
64
66
|
title?: string;
|
|
65
67
|
origin: Extract<Origin, "user" | "llm">;
|
|
66
|
-
revision:
|
|
68
|
+
revision: string;
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
/**
|
|
@@ -79,9 +81,50 @@ export interface OperandRow {
|
|
|
79
81
|
offset: number;
|
|
80
82
|
expression?: string;
|
|
81
83
|
origin: Extract<Origin, "user" | "llm">;
|
|
82
|
-
revision:
|
|
84
|
+
revision: string;
|
|
83
85
|
}
|
|
84
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
|
+
|
|
85
128
|
type Raw = Record<string, unknown>;
|
|
86
129
|
|
|
87
130
|
export const SYMBOL_COLUMNS = "space, address, name, kind, origin, valid_from_revision";
|
|
@@ -91,9 +134,12 @@ export const REFERENCE_COLUMNS = "space, from_address, to_address, kind, origin,
|
|
|
91
134
|
export const SUPPRESSION_COLUMNS = "space, entity, start_address, end_address, to_address, kind, origin, valid_from_revision";
|
|
92
135
|
export const MODULE_COLUMNS = "space, start_address, file, title, origin, valid_from_revision";
|
|
93
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";
|
|
94
140
|
|
|
95
141
|
export function toSymbol(row: Raw): SymbolRow {
|
|
96
|
-
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
|
|
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 };
|
|
97
143
|
}
|
|
98
144
|
|
|
99
145
|
export function toRegion(row: Raw): RegionRow {
|
|
@@ -103,7 +149,7 @@ export function toRegion(row: Raw): RegionRow {
|
|
|
103
149
|
end: row.end_address as number,
|
|
104
150
|
type: row.type as RegionType,
|
|
105
151
|
origin: row.origin as Origin,
|
|
106
|
-
revision: row.valid_from_revision as
|
|
152
|
+
revision: row.valid_from_revision as string,
|
|
107
153
|
};
|
|
108
154
|
if (row.element !== null && row.element !== undefined) region.element = row.element as number;
|
|
109
155
|
return region;
|
|
@@ -116,12 +162,12 @@ export function toComment(row: Raw): CommentRow {
|
|
|
116
162
|
placement: row.placement as CommentPlacement,
|
|
117
163
|
text: row.text as string,
|
|
118
164
|
origin: row.origin as Origin,
|
|
119
|
-
revision: row.valid_from_revision as
|
|
165
|
+
revision: row.valid_from_revision as string,
|
|
120
166
|
};
|
|
121
167
|
}
|
|
122
168
|
|
|
123
169
|
export function toReference(row: Raw): ReferenceRow {
|
|
124
|
-
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
|
|
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 };
|
|
125
171
|
}
|
|
126
172
|
|
|
127
173
|
export function toSuppression(row: Raw): SuppressionRow {
|
|
@@ -131,7 +177,7 @@ export function toSuppression(row: Raw): SuppressionRow {
|
|
|
131
177
|
start: row.start_address as number,
|
|
132
178
|
end: row.end_address as number,
|
|
133
179
|
origin: row.origin as SuppressionRow["origin"],
|
|
134
|
-
revision: row.valid_from_revision as
|
|
180
|
+
revision: row.valid_from_revision as string,
|
|
135
181
|
};
|
|
136
182
|
if (row.to_address !== null) suppression.to = row.to_address as number;
|
|
137
183
|
if (row.kind !== null) suppression.kind = row.kind as ReferenceKind;
|
|
@@ -139,7 +185,7 @@ export function toSuppression(row: Raw): SuppressionRow {
|
|
|
139
185
|
}
|
|
140
186
|
|
|
141
187
|
export function toModule(row: Raw): ModuleRow {
|
|
142
|
-
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
|
|
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 };
|
|
143
189
|
if (row.title !== null && row.title !== undefined) module.title = row.title as string;
|
|
144
190
|
return module;
|
|
145
191
|
}
|
|
@@ -151,16 +197,68 @@ export function toOperand(row: Raw): OperandRow {
|
|
|
151
197
|
select: row.selector as OperandSelect,
|
|
152
198
|
offset: row.offset as number,
|
|
153
199
|
origin: row.origin as OperandRow["origin"],
|
|
154
|
-
revision: row.valid_from_revision as
|
|
200
|
+
revision: row.valid_from_revision as string,
|
|
155
201
|
};
|
|
156
202
|
if (row.symbol !== null && row.symbol !== undefined) operand.symbol = row.symbol as string;
|
|
157
203
|
if (row.expression !== null && row.expression !== undefined) operand.expression = row.expression as string;
|
|
158
204
|
return operand;
|
|
159
205
|
}
|
|
160
206
|
|
|
161
|
-
export function
|
|
162
|
-
|
|
163
|
-
|
|
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;
|
|
164
262
|
}
|
|
165
263
|
|
|
166
264
|
export function symbolAt(db: DatabaseSync | undefined, address: number, space: Space = DEFAULT_SPACE): SymbolRow | undefined {
|
|
@@ -249,6 +347,29 @@ export function operandAt(db: DatabaseSync | undefined, address: number, space:
|
|
|
249
347
|
return row === undefined ? undefined : toOperand(row);
|
|
250
348
|
}
|
|
251
349
|
|
|
350
|
+
/** Current patches that share at least one address with [start, end], in address order. */
|
|
351
|
+
export function patchesOverlapping(db: DatabaseSync | undefined, start: number, end: number, space: Space = DEFAULT_SPACE): PatchRow[] {
|
|
352
|
+
if (db === undefined) return [];
|
|
353
|
+
return db
|
|
354
|
+
.prepare(`SELECT ${PATCH_COLUMNS} FROM current_patches WHERE space = ? AND start_address <= ? AND end_address >= ? ORDER BY start_address`)
|
|
355
|
+
.all(space, end, start)
|
|
356
|
+
.map((row) => toPatch(row as Raw));
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** Current macro uses that share at least one address with [start, end], in address order. */
|
|
360
|
+
export function macroUsesOverlapping(db: DatabaseSync | undefined, start: number, end: number, space: Space = DEFAULT_SPACE): MacroUseRow[] {
|
|
361
|
+
if (db === undefined) return [];
|
|
362
|
+
return db
|
|
363
|
+
.prepare(`SELECT ${MACRO_USE_COLUMNS} FROM current_macro_uses WHERE space = ? AND start_address <= ? AND end_address >= ? ORDER BY start_address`)
|
|
364
|
+
.all(space, end, start)
|
|
365
|
+
.map((row) => toMacroUse(row as Raw));
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export function macroNamed(db: DatabaseSync | undefined, name: string, space: Space = DEFAULT_SPACE): MacroRow | undefined {
|
|
369
|
+
const row = db?.prepare(`SELECT ${MACRO_COLUMNS} FROM current_macros WHERE space = ? AND name = ?`).get(space, name) as Raw | undefined;
|
|
370
|
+
return row === undefined ? undefined : toMacro(row);
|
|
371
|
+
}
|
|
372
|
+
|
|
252
373
|
/** Everything currently known at one address: the main lookup for skills. */
|
|
253
374
|
export interface AddressKnowledge {
|
|
254
375
|
space: Space;
|
|
@@ -261,9 +382,13 @@ export interface AddressKnowledge {
|
|
|
261
382
|
referencesTo: ReferenceRow[];
|
|
262
383
|
/** How the operand of the instruction here is written (D65). */
|
|
263
384
|
operand?: OperandRow;
|
|
385
|
+
/** The patch whose range holds the address (D72). */
|
|
386
|
+
patch?: PatchRow;
|
|
387
|
+
/** The macro use whose range holds the address (D73). */
|
|
388
|
+
macroUse?: MacroUseRow;
|
|
264
389
|
/** What user/LLM removals block here for the analyzers. */
|
|
265
390
|
suppressions: SuppressionRow[];
|
|
266
|
-
revision:
|
|
391
|
+
revision: string;
|
|
267
392
|
}
|
|
268
393
|
|
|
269
394
|
export function at(db: DatabaseSync | undefined, address: number, space: Space = DEFAULT_SPACE): AddressKnowledge {
|
|
@@ -282,6 +407,10 @@ export function at(db: DatabaseSync | undefined, address: number, space: Space =
|
|
|
282
407
|
if (region !== undefined) result.region = region;
|
|
283
408
|
const operand = operandAt(db, address, space);
|
|
284
409
|
if (operand !== undefined) result.operand = operand;
|
|
410
|
+
const [patch] = patchesOverlapping(db, address, address, space);
|
|
411
|
+
if (patch !== undefined) result.patch = patch;
|
|
412
|
+
const [macroUse] = macroUsesOverlapping(db, address, address, space);
|
|
413
|
+
if (macroUse !== undefined) result.macroUse = macroUse;
|
|
285
414
|
return result;
|
|
286
415
|
}
|
|
287
416
|
|
|
@@ -362,12 +491,32 @@ export function listOperands(db: DatabaseSync | undefined, range: Range = ALL, o
|
|
|
362
491
|
.filter(kept(origins));
|
|
363
492
|
}
|
|
364
493
|
|
|
494
|
+
/** Current patches that share an address with the range, of one switch or of all, in address order. */
|
|
495
|
+
export function listPatches(db: DatabaseSync | undefined, range: Range = ALL, switchName?: string, origins?: OriginFilter, space: Space = DEFAULT_SPACE): PatchRow[] {
|
|
496
|
+
return patchesOverlapping(db, range.start, range.end, space).filter((patch) => (switchName === undefined || patch.switch === switchName) && kept(origins)(patch));
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/** Current macros, by name. */
|
|
500
|
+
export function listMacros(db: DatabaseSync | undefined, origins?: OriginFilter, space: Space = DEFAULT_SPACE): MacroRow[] {
|
|
501
|
+
if (db === undefined) return [];
|
|
502
|
+
return db
|
|
503
|
+
.prepare(`SELECT ${MACRO_COLUMNS} FROM current_macros WHERE space = ? ORDER BY name`)
|
|
504
|
+
.all(space)
|
|
505
|
+
.map((row) => toMacro(row as Raw))
|
|
506
|
+
.filter(kept(origins));
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/** Current macro uses that share an address with the range, of one macro or of all, in address order. */
|
|
510
|
+
export function listMacroUses(db: DatabaseSync | undefined, range: Range = ALL, macro?: string, origins?: OriginFilter, space: Space = DEFAULT_SPACE): MacroUseRow[] {
|
|
511
|
+
return macroUsesOverlapping(db, range.start, range.end, space).filter((use) => (macro === undefined || use.macro === macro) && kept(origins)(use));
|
|
512
|
+
}
|
|
513
|
+
|
|
365
514
|
/** The spaces that hold current knowledge, the C64 first. */
|
|
366
515
|
export function listSpaces(db: DatabaseSync | undefined): Space[] {
|
|
367
516
|
if (db === undefined) return [];
|
|
368
517
|
const rows = db
|
|
369
518
|
.prepare(
|
|
370
|
-
`SELECT space FROM current_symbols 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`,
|
|
519
|
+
`SELECT space FROM current_symbols 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`,
|
|
371
520
|
)
|
|
372
521
|
.all() as Array<{ space: Space }>;
|
|
373
522
|
const order = (space: Space) => (space === "c64" ? -2 : space === "drive8" ? -1 : Number(space.slice(4)));
|