Moving towards a "custom" parser for NextReg 0xF0

This commit is contained in:
2025-10-10 17:17:35 +01:00
parent 20764d74d3
commit 7031088bcd
7 changed files with 184 additions and 125 deletions

View File

@@ -12,13 +12,14 @@ interface RegisterBrowserProps {
/** /**
* Renders the access details for a register, including its description, operations, and notes. * Renders the access details for a register, including its description, operations, and notes.
* @param access The register access data to render. * @param access The register access data to render.
* @param extraNotes Non access footnotes to include in the tooltip.
* @returns A React component that displays the register access details. * @returns A React component that displays the register access details.
*/ */
export function renderAccess(access: RegisterAccess) { export function renderAccess(access: RegisterAccess, extraNotes: Note[] = []) {
const renderTooltip = (notes: Note[]) => ( const renderTooltip = (notes: Note[]) => (
<Tooltip id="tooltip"> <Tooltip id="tooltip">
{notes.map((note, index) => ( {notes.map((note, index) => (
<div key={index}>{note.text}</div> <div key={index}><code>{note.ref}</code> {note.text}</div>
))} ))}
</Tooltip> </Tooltip>
); );
@@ -36,7 +37,11 @@ export function renderAccess(access: RegisterAccess) {
</thead> </thead>
<tbody> <tbody>
{access.operations.map((op, index) => { {access.operations.map((op, index) => {
const notes = access.notes.filter(note => note.ref === op.footnoteRef); const access_notes = access.notes.filter(note => note.ref === op.footnoteRef);
const extra_notes = extraNotes.filter(note => note.ref === op.footnoteRef);
const notes = [...access_notes, ...extra_notes].filter(note => note.text.length > 0);
return ( return (
<tr key={index}> <tr key={index}>
<td>{op.bits}</td> <td>{op.bits}</td>

View File

@@ -19,6 +19,8 @@ export default function RegisterDetail({
}) { }) {
const [showSource, setShowSource] = useState(false); const [showSource, setShowSource] = useState(false);
console.log("RENDERING: ", register.name, "FROM", register);
return ( return (
<Col key={register.hex_address} xs={12} className="mb-4"> <Col key={register.hex_address} xs={12} className="mb-4">
<Card> <Card>
@@ -42,25 +44,33 @@ export default function RegisterDetail({
<Card.Body> <Card.Body>
{ register.modes.map((mode, idx) => ( { register.modes.map((mode, idx) => (
<div key={idx} className={idx > 0 ? 'mt-4' : ''}> <div key={idx} className={idx > 0 ? 'mt-4' : ''}>
{register.modes.length > 1 && ( <div>
<h5 className="mb-3">Mode {idx + 1}</h5> {mode.modeName?.length && (
<h5 className="mb-3">{mode.modeName}</h5>
)} )}
<Tabs id={`register-tabs-${register.hex_address}-${idx}`}> <Tabs id={`register-tabs-${register.hex_address}-${idx}`}>
{mode.common && <Tab eventKey="common" title="Read/Write">{renderAccess(mode.common)}</Tab>} {mode.common && <Tab eventKey="common" title="Read/Write">{renderAccess(mode.common, register.notes)}</Tab>}
{mode.read && <Tab eventKey="read" title="Read">{renderAccess(mode.read)}</Tab>} {mode.read && <Tab eventKey="read" title="Read">{renderAccess(mode.read, register.notes)}</Tab>}
{mode.write && <Tab eventKey="write" title="Write">{renderAccess(mode.write)}</Tab>} {mode.write && <Tab eventKey="write" title="Write">{renderAccess(mode.write, register.notes)}</Tab>}
</Tabs> </Tabs>
{mode.notes && mode.notes.map((note, index) => ( </div>
<p key={index} className="small text-muted">{note.ref} {note.text}</p>
))}
{mode.text && mode.text.length > 0 && ( {mode.text && mode.text.length > 0 && (
<div className="mt-3"> <div className="mt-3">
<h6>Notes:</h6> <h6>Mode Notes:</h6>
<pre>{mode.text}</pre> <pre>{mode.text}</pre>
</div> </div>
)} )}
</div> </div>
))} ))}
{register.notes && register.notes.map((note, index) => (
<p key={index} className="small text-muted">{note.ref}: {note.text}</p>
))}
{register.text && register.text.length > 0 && (
<div className="mt-3">
<h6>Notes:</h6>
<pre>{register.text}</pre>
</div>
)}
</Card.Body> </Card.Body>
</Card> </Card>

View File

@@ -1,5 +1,6 @@
// Pulse 5.3.8 // Pulse 5.3.8
// Bootswatch // Bootswatch
@use 'sass:color';
$theme: "pulse" !default; $theme: "pulse" !default;
@@ -86,7 +87,7 @@ $progress-bar-bg: $primary !default;
$list-group-bg: $gray-900 !default; $list-group-bg: $gray-900 !default;
$list-group-border-color: transparent !default; $list-group-border-color: transparent !default;
$list-group-hover-bg: lighten($list-group-bg, 10%) !default; $list-group-hover-bg: color.scale($list-group-bg, $lightness:10%) !default;
$list-group-active-color: $white !default; $list-group-active-color: $white !default;
$list-group-active-bg: $list-group-bg !default; $list-group-active-bg: $list-group-bg !default;
$list-group-disabled-color: lighten($list-group-bg, 30%) !default; $list-group-disabled-color: color.scale($list-group-bg, $lightness:30%) !default;

View File

@@ -11,10 +11,10 @@ let registers: Register[] = [];
* @returns A promise that resolves to an array of Register objects. * @returns A promise that resolves to an array of Register objects.
*/ */
export async function getRegisters(): Promise<Register[]> { export async function getRegisters(): Promise<Register[]> {
if (registers.length === 0) { // if (registers.length === 0) {
const filePath = path.join(process.cwd(), 'data', 'nextreg.txt'); const filePath = path.join(process.cwd(), 'data', 'nextreg.txt');
const fileContent = await fs.readFile(filePath, 'utf8'); const fileContent = await fs.readFile(filePath, 'utf8');
registers = await parseNextReg(fileContent); registers = await parseNextReg(fileContent);
} // }
return registers; return registers;
} }

View File

@@ -20,11 +20,11 @@ export interface RegisterAccess {
} }
export interface RegisterDetail { export interface RegisterDetail {
modeName?: string;
read?: RegisterAccess; read?: RegisterAccess;
write?: RegisterAccess; write?: RegisterAccess;
common?: RegisterAccess; common?: RegisterAccess;
text: string; text: string;
notes: Note[];
} }
export interface Register { export interface Register {
@@ -35,6 +35,8 @@ export interface Register {
description: string; description: string;
issue_4_only: boolean; issue_4_only: boolean;
source: string[]; source: string[];
text: string;
notes: Note[];
} }
/** /**
@@ -91,7 +93,9 @@ export function processRegisterBlock(paragraph: string, registers: Register[]) {
description: description, description: description,
modes: [], modes: [],
issue_4_only: false, issue_4_only: false,
source: [] source: [],
text: "",
notes: [],
}; };
// Dispatch to appropriate parser based on hex // Dispatch to appropriate parser based on hex

View File

@@ -5,11 +5,12 @@ export const parseDescriptionDefault = (reg: Register, description: string) => {
let currentAccess: 'read' | 'write' | 'common' | null = null; let currentAccess: 'read' | 'write' | 'common' | null = null;
let accessData: RegisterAccess = { operations: [], notes: [] }; let accessData: RegisterAccess = { operations: [], notes: [] };
// Prepare a new RegisterDetail for this description block // Prepare a new RegisterDetail for this description block
const detail: RegisterDetail = { read: undefined, write: undefined, common: undefined, text: '', notes: [] }; const detail: RegisterDetail = { read: undefined, write: undefined, common: undefined, text: ''};
for (const line of descriptionLines) { for (const line of descriptionLines) {
if (line.includes('Issue 4 Only')) reg.issue_4_only = true; if (line.includes('Issue 4 Only')) reg.issue_4_only = true;
const spaces_at_start = line.match(/^(\s*)/)?.[0].length || 0;
const trimmedLine = line.trim(); const trimmedLine = line.trim();
reg.source.push(line); reg.source.push(line);
@@ -50,7 +51,7 @@ export const parseDescriptionDefault = (reg: Register, description: string) => {
if (currentAccess) { if (currentAccess) {
const bitMatch = trimmedLine.match(/^(bits?|bit)\s+([\d:-]+)\s*=\s*(.*)/); const bitMatch = trimmedLine.match(/^(bits?|bit)\s+([\d:-]+)\s*=\s*(.*)/);
const valueMatch = !line.match(/^\s+/) && trimmedLine.match(/^([01\s]+)\s*=\s*(.*)/); // const valueMatch = !line.match(/^\s+/) && trimmedLine.match(/^([01\s]+)\s*=\s*(.*)/);
if (bitMatch) { if (bitMatch) {
let bitDescription = bitMatch[3]; let bitDescription = bitMatch[3];
@@ -65,13 +66,15 @@ export const parseDescriptionDefault = (reg: Register, description: string) => {
description: bitDescription, description: bitDescription,
footnoteRef: footnoteRef, footnoteRef: footnoteRef,
}); });
} else if (valueMatch) { // } else if (valueMatch) {
accessData.operations.push({ // console.error("VALUE MATCH",valueMatch);
bits: valueMatch[1].trim().replace(/\s/g, ''), // accessData.operations.push({
description: valueMatch[2].trim(), // bits: valueMatch[1].trim().replace(/\s/g, ''),
}); // description: valueMatch[2].trim(),
// });
} else if (trimmedLine.startsWith('*')) { } else if (trimmedLine.startsWith('*')) {
const noteMatch = trimmedLine.match(/^(\*+)\s*(.*)/); const noteMatch = trimmedLine.match(/^(\*+)\s*(.*)/);
console.log("NOTE MATCH",noteMatch);
if (noteMatch) { if (noteMatch) {
accessData.notes.push({ accessData.notes.push({
ref: noteMatch[1], ref: noteMatch[1],
@@ -79,6 +82,12 @@ export const parseDescriptionDefault = (reg: Register, description: string) => {
}); });
} }
} else if (trimmedLine) { } else if (trimmedLine) {
if(spaces_at_start == 2) {
reg.text += `${line}\n`;
continue;
}
// console.log("LINE",line);
console.log(line.match(/^\s+/), line);
if (line.match(/^\s+/) && accessData.operations.length > 0) { if (line.match(/^\s+/) && accessData.operations.length > 0) {
accessData.operations[accessData.operations.length - 1].description += `\n${line}`; accessData.operations[accessData.operations.length - 1].description += `\n${line}`;
} else { } else {
@@ -92,7 +101,7 @@ export const parseDescriptionDefault = (reg: Register, description: string) => {
if (trimmedLine.startsWith('*')) { if (trimmedLine.startsWith('*')) {
const noteMatch = trimmedLine.match(/^(\*+)\s*(.*)/); const noteMatch = trimmedLine.match(/^(\*+)\s*(.*)/);
if (noteMatch) { if (noteMatch) {
detail.notes.push({ reg.notes.push({
ref: noteMatch[1], ref: noteMatch[1],
text: noteMatch[2], text: noteMatch[2],
}); });

View File

@@ -1,45 +1,81 @@
// Special-case parser for 0xF0 (XDEV CMD): treat headings beginning with '*' inside access blocks // Special-case parser for 0xF0 (XDEV CMD): implement multi-mode parsing.
// as descriptive text instead of notes, so sub-modes become part of the section descriptions. // Rules:
// - A line that begins with exactly two spaces (" ") and then a non-* character starts a new mode; the trimmed text is modeName.
// - Lines with three or more leading spaces (>=3) belong to the current mode.
// - A line with exactly two spaces followed by '*' is a parent (register-level) note, not a mode note.
// - Inside access blocks for F0, lines starting with '*' are headings for description (not notes).
import { Register, RegisterAccess, RegisterDetail } from "@/utils/register_parser"; import { Register, RegisterAccess, RegisterDetail } from "@/utils/register_parser";
export const parseDescriptionF0 = (reg: Register, description: string) => { export const parseDescriptionF0 = (reg: Register, description: string) => {
const descriptionLines = description.split('\n'); const descriptionLines = description.split('\n');
let currentAccess: 'read' | 'write' | 'common' | null = null; let currentAccess: 'read' | 'write' | 'common' | null = null;
let accessData: RegisterAccess = { operations: [], notes: [] }; let accessData: RegisterAccess = { operations: [], notes: [] };
const detail: RegisterDetail = { read: undefined, write: undefined, common: undefined, text: '', notes: [] }; // Prepare a new RegisterDetail for this description block
let detail: RegisterDetail = { read: undefined, write: undefined, common: undefined, text: ''};
reg.modes = reg.modes || [];
for (const line of descriptionLines) { for (const line of descriptionLines) {
reg.source.push(line);
const spaces_at_start = line.match(/^(\s*)/)?.[0].length || 0;
const trimmedLine = line.trim();
if(spaces_at_start == 2) {
if (trimmedLine.startsWith('*')) {
console.log("PARENT",trimmedLine);
const noteMatch = trimmedLine.match(/^(\*+)\s*(.*)/);
if (noteMatch) {
reg.notes.push({
ref: noteMatch[1],
text: noteMatch[2],
});
}
continue;
} else {
if (currentAccess) {
// finalize previous access block into detail
detail[currentAccess] = accessData;
}
reg.modes.push(detail);
detail = {read: undefined, write: undefined, common: undefined, text: ''};
detail.modeName = trimmedLine;
accessData = {operations: [], notes: []};
currentAccess = null;
continue;
}
}
if (line.includes('Issue 4 Only')) reg.issue_4_only = true; if (line.includes('Issue 4 Only')) reg.issue_4_only = true;
const trimmedLine = line.trim();
reg.source.push(line);
if (trimmedLine.startsWith('//')) continue; if (trimmedLine.startsWith('//')) continue;
if (trimmedLine.startsWith('(R)')) { if (trimmedLine.startsWith('(R)')) {
if (currentAccess) detail[currentAccess] = accessData; if (currentAccess) {
// finalize previous access block into detail
detail[currentAccess] = accessData;
}
accessData = { operations: [], notes: [] }; accessData = { operations: [], notes: [] };
currentAccess = 'read'; currentAccess = 'read';
continue; continue;
} }
if (trimmedLine.startsWith('(W)')) { if (trimmedLine.startsWith('(W)')) {
if (currentAccess) detail[currentAccess] = accessData; if (currentAccess) {
detail[currentAccess] = accessData;
}
accessData = { operations: [], notes: [] }; accessData = { operations: [], notes: [] };
currentAccess = 'write'; currentAccess = 'write';
continue; continue;
} }
if (trimmedLine.startsWith('(R/W')) { if (trimmedLine.startsWith('(R/W')) {
if (currentAccess) detail[currentAccess] = accessData; if (currentAccess) {
detail[currentAccess] = accessData;
}
accessData = { operations: [], notes: [] }; accessData = { operations: [], notes: [] };
currentAccess = 'common'; currentAccess = 'common';
continue; continue;
} }
if (line.startsWith(trimmedLine)) {
if (currentAccess) detail[currentAccess] = accessData;
accessData = { operations: [], notes: [] };
currentAccess = null;
}
if (currentAccess) { if (currentAccess) {
const bitMatch = trimmedLine.match(/^(bits?|bit)\s+([\d:-]+)\s*=\s*(.*)/); const bitMatch = trimmedLine.match(/^(bits?|bit)\s+([\d:-]+)\s*=\s*(.*)/);
@@ -50,6 +86,7 @@ export const parseDescriptionF0 = (reg: Register, description: string) => {
const footnoteMatch = bitDescription.match(/(\*+)$/); const footnoteMatch = bitDescription.match(/(\*+)$/);
let footnoteRef: string | undefined = undefined; let footnoteRef: string | undefined = undefined;
if (footnoteMatch) { if (footnoteMatch) {
console.log("FOOTNOTE",footnoteMatch);
footnoteRef = footnoteMatch[1]; footnoteRef = footnoteMatch[1];
bitDescription = bitDescription.substring(0, bitDescription.length - footnoteRef.length).trim(); bitDescription = bitDescription.substring(0, bitDescription.length - footnoteRef.length).trim();
} }
@@ -63,12 +100,17 @@ export const parseDescriptionF0 = (reg: Register, description: string) => {
bits: valueMatch[1].trim().replace(/\s/g, ''), bits: valueMatch[1].trim().replace(/\s/g, ''),
description: valueMatch[2].trim(), description: valueMatch[2].trim(),
}); });
} else if (trimmedLine.startsWith('*')) { } else if (trimmedLine.startsWith('*') && spaces_at_start > 2) {
// SPECIAL: treat star lines as headings in description rather than notes const noteMatch = trimmedLine.match(/^(\*+)\s*(.*)/);
const heading = trimmedLine.replace(/^\*+\s*/, '').trim(); if (noteMatch) {
if (!accessData.description) accessData.description = ''; reg.notes.push({
accessData.description += (accessData.description ? '\n' : '') + heading; ref: noteMatch[1],
text: noteMatch[2],
});
}
}
} else if (trimmedLine) { } else if (trimmedLine) {
console.log("LINE", trimmedLine);
if (line.match(/^\s+/) && accessData.operations.length > 0) { if (line.match(/^\s+/) && accessData.operations.length > 0) {
accessData.operations[accessData.operations.length - 1].description += `\n${line}`; accessData.operations[accessData.operations.length - 1].description += `\n${line}`;
} else { } else {
@@ -78,24 +120,12 @@ export const parseDescriptionF0 = (reg: Register, description: string) => {
accessData.description += `\n${trimmedLine}`; accessData.description += `\n${trimmedLine}`;
} }
} }
} else {
if (trimmedLine.startsWith('*')) {
// Outside access blocks, keep notes as-is but attach to detail now
const noteMatch = trimmedLine.match(/^(\*+)\s*(.*)/);
if (noteMatch) {
detail.notes.push({
ref: noteMatch[1],
text: noteMatch[2],
});
}
} else if (trimmedLine) {
detail.text += `${line}\n`;
}
}
} }
if (currentAccess) { if (currentAccess) {
detail[currentAccess] = accessData; detail[currentAccess] = accessData;
console.log("FINAL",detail,currentAccess);
} }
reg.modes = reg.modes || [];
// Push the parsed detail into modes
reg.modes.push(detail); reg.modes.push(detail);
}; };