Moving towards multiple register parsers, to handle more exotic register types

This commit is contained in:
2025-10-10 13:46:21 +01:00
parent 5aa4c33059
commit 20764d74d3
6 changed files with 69 additions and 56 deletions

View File

@@ -74,12 +74,6 @@ export default function RegisterBrowser({ registers }: RegisterBrowserProps) {
register.description.toLowerCase().includes(searchTerm.toLowerCase()) register.description.toLowerCase().includes(searchTerm.toLowerCase())
); );
const getDefaultActiveKey = (register: Register) => {
if (register.common) return 'common';
if (register.read) return 'read';
if (register.write) return 'write';
return '';
};
return ( return (
<Container fluid> <Container fluid>
@@ -92,12 +86,9 @@ export default function RegisterBrowser({ registers }: RegisterBrowserProps) {
/> />
</Form.Group> </Form.Group>
<Row> <Row>
{filteredRegisters.map(register => { {filteredRegisters.map(register => (
const defaultActiveKey = getDefaultActiveKey(register); <RegisterDetail key={register.hex_address} register={register} />
return ( ))}
<RegisterDetail key={register.hex_address} register={register} defaultActiveKey={defaultActiveKey} />
);
})}
</Row> </Row>
</Container> </Container>
); );

View File

@@ -10,15 +10,12 @@ import * as Icon from 'react-bootstrap-icons';
/** /**
* A client-side component that displays the details of a single register. * A client-side component that displays the details of a single register.
* @param register The register object to display. * @param register The register object to display.
* @param defaultActiveKey The default active tab to display.
* @returns A React component that displays the register details. * @returns A React component that displays the register details.
*/ */
export default function RegisterDetail({ export default function RegisterDetail({
register, register,
defaultActiveKey,
}: { }: {
register: Register; register: Register;
defaultActiveKey?: string;
}) { }) {
const [showSource, setShowSource] = useState(false); const [showSource, setShowSource] = useState(false);
@@ -27,9 +24,7 @@ export default function RegisterDetail({
<Card> <Card>
<Card.Header> <Card.Header>
<code>{register.hex_address}</code> ( {register.dec_address} ) &nbsp; <code>{register.hex_address}</code> ( {register.dec_address} ) &nbsp;
{/*<Link href={`https://wiki.specnext.dev/${encodeURIComponent((register.name).replace(' ','_'))}_Register`} className="text-decoration-none">*/}
<strong>{register.name}</strong> {register.issue_4_only && <span className="badge bg-danger">Issue 4 Only</span>} <strong>{register.name}</strong> {register.issue_4_only && <span className="badge bg-danger">Issue 4 Only</span>}
{/*</Link>*/}
<div className="float-end small text-muted"> <div className="float-end small text-muted">
<Link href={`https://wiki.specnext.dev/${encodeURIComponent((register.name).replace(' ','_'))}_Register`} className="text-decoration-none btn btn-sm btn-primary" title="Open wiki"> <Link href={`https://wiki.specnext.dev/${encodeURIComponent((register.name).replace(' ','_'))}_Register`} className="text-decoration-none btn btn-sm btn-primary" title="Open wiki">
<Icon.Wikipedia /> <Icon.Wikipedia />
@@ -45,20 +40,27 @@ export default function RegisterDetail({
</div> </div>
</Card.Header> </Card.Header>
<Card.Body> <Card.Body>
<Tabs defaultActiveKey={defaultActiveKey} id={`register-tabs-${register.hex_address}`}> { register.modes.map((mode, idx) => (
{register.common && <Tab eventKey="common" title="Read/Write">{renderAccess(register.common)}</Tab>} <div key={idx} className={idx > 0 ? 'mt-4' : ''}>
{register.read && <Tab eventKey="read" title="Read">{renderAccess(register.read)}</Tab>} {register.modes.length > 1 && (
{register.write && <Tab eventKey="write" title="Write">{renderAccess(register.write)}</Tab>} <h5 className="mb-3">Mode {idx + 1}</h5>
)}
<Tabs id={`register-tabs-${register.hex_address}-${idx}`}>
{mode.common && <Tab eventKey="common" title="Read/Write">{renderAccess(mode.common)}</Tab>}
{mode.read && <Tab eventKey="read" title="Read">{renderAccess(mode.read)}</Tab>}
{mode.write && <Tab eventKey="write" title="Write">{renderAccess(mode.write)}</Tab>}
</Tabs> </Tabs>
{register.notes.map((note, index) => ( {mode.notes && mode.notes.map((note, index) => (
<p key={index} className="small text-muted">{note.ref} {note.text}</p> <p key={index} className="small text-muted">{note.ref} {note.text}</p>
))} ))}
{register.text && register.text.length > 0 && ( {mode.text && mode.text.length > 0 && (
<div className="mt-3"> <div className="mt-3">
<h5>Notes:</h5> <h6>Notes:</h6>
<pre>{register.text}</pre> <pre>{mode.text}</pre>
</div> </div>
)} )}
</div>
))}
</Card.Body> </Card.Body>
</Card> </Card>

View File

@@ -13,7 +13,6 @@ export default async function RegisterDetailPage({ params }: { params: { hex: st
if (!register) return notFound(); if (!register) return notFound();
const defaultActiveKey = register.common ? 'common' : (register.read ? 'read' : (register.write ? 'write' : undefined));
return ( return (
<Container fluid className="py-4"> <Container fluid className="py-4">
@@ -21,7 +20,7 @@ export default async function RegisterDetailPage({ params }: { params: { hex: st
<Link href="/registers" className="btn btn-secondary"> Back to Registers</Link> <Link href="/registers" className="btn btn-secondary"> Back to Registers</Link>
</div> </div>
<Row> <Row>
<RegisterDetail register={register} defaultActiveKey={defaultActiveKey} /> <RegisterDetail register={register} />
</Row> </Row>
</Container> </Container>
); );

View File

@@ -18,16 +18,21 @@ export interface RegisterAccess {
operations: RegisterBitwiseOperation[]; operations: RegisterBitwiseOperation[];
notes: Note[]; notes: Note[];
} }
export interface Register {
hex_address: string; export interface RegisterDetail {
dec_address: number | string;
name: string;
description: string;
read?: RegisterAccess; read?: RegisterAccess;
write?: RegisterAccess; write?: RegisterAccess;
common?: RegisterAccess; common?: RegisterAccess;
text: string; text: string;
notes: Note[]; notes: Note[];
}
export interface Register {
hex_address: string;
dec_address: number | string;
name: string;
modes: RegisterDetail[];
description: string;
issue_4_only: boolean; issue_4_only: boolean;
source: string[]; source: string[];
} }
@@ -84,8 +89,7 @@ export function processRegisterBlock(paragraph: string, registers: Register[]) {
dec_address: dec, dec_address: dec,
name: regName, name: regName,
description: description, description: description,
notes: [], modes: [],
text: "",
issue_4_only: false, issue_4_only: false,
source: [] source: []
}; };

View File

@@ -1,9 +1,11 @@
import {Register, RegisterAccess} from "@/utils/register_parser"; import {Register, RegisterAccess, RegisterDetail} from "@/utils/register_parser";
export const parseDescriptionDefault = (reg: Register, description: string) => { export const parseDescriptionDefault = (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: [] };
// Prepare a new RegisterDetail for this description block
const detail: RegisterDetail = { read: undefined, write: undefined, common: undefined, text: '', notes: [] };
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;
@@ -14,25 +16,34 @@ export const parseDescriptionDefault = (reg: Register, description: string) => {
if (trimmedLine.startsWith('//')) continue; if (trimmedLine.startsWith('//')) continue;
if (trimmedLine.startsWith('(R)')) { if (trimmedLine.startsWith('(R)')) {
if (currentAccess) reg[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) reg[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) reg[currentAccess] = accessData; if (currentAccess) {
detail[currentAccess] = accessData;
}
accessData = { operations: [], notes: [] }; accessData = { operations: [], notes: [] };
currentAccess = 'common'; currentAccess = 'common';
continue; continue;
} }
if (line.startsWith(trimmedLine)) { if (line.startsWith(trimmedLine)) {
if (currentAccess) reg[currentAccess] = accessData; if (currentAccess) {
detail[currentAccess] = accessData;
}
accessData = { operations: [], notes: [] }; accessData = { operations: [], notes: [] };
currentAccess = null; currentAccess = null;
} }
@@ -81,17 +92,20 @@ 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) {
reg.notes.push({ detail.notes.push({
ref: noteMatch[1], ref: noteMatch[1],
text: noteMatch[2], text: noteMatch[2],
}); });
} }
} else if (trimmedLine) { } else if (trimmedLine) {
reg.text += `${line}\n`; detail.text += `${line}\n`;
} }
} }
} }
if (currentAccess) { if (currentAccess) {
reg[currentAccess] = accessData; detail[currentAccess] = accessData;
} }
// Push the parsed detail into modes
reg.modes = reg.modes || [];
reg.modes.push(detail);
}; };

View File

@@ -1,12 +1,13 @@
// Special-case parser for 0xF0 (XDEV CMD): treat headings beginning with '*' inside access blocks // Special-case parser for 0xF0 (XDEV CMD): treat headings beginning with '*' inside access blocks
// as descriptive text instead of notes, so sub-modes become part of the section descriptions. // as descriptive text instead of notes, so sub-modes become part of the section descriptions.
import {Register, RegisterAccess} 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: [] };
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;
@@ -17,25 +18,25 @@ export const parseDescriptionF0 = (reg: Register, description: string) => {
if (trimmedLine.startsWith('//')) continue; if (trimmedLine.startsWith('//')) continue;
if (trimmedLine.startsWith('(R)')) { if (trimmedLine.startsWith('(R)')) {
if (currentAccess) reg[currentAccess] = accessData; if (currentAccess) 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) reg[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) reg[currentAccess] = accessData; if (currentAccess) detail[currentAccess] = accessData;
accessData = { operations: [], notes: [] }; accessData = { operations: [], notes: [] };
currentAccess = 'common'; currentAccess = 'common';
continue; continue;
} }
if (line.startsWith(trimmedLine)) { if (line.startsWith(trimmedLine)) {
if (currentAccess) reg[currentAccess] = accessData; if (currentAccess) detail[currentAccess] = accessData;
accessData = { operations: [], notes: [] }; accessData = { operations: [], notes: [] };
currentAccess = null; currentAccess = null;
} }
@@ -79,20 +80,22 @@ export const parseDescriptionF0 = (reg: Register, description: string) => {
} }
} else { } else {
if (trimmedLine.startsWith('*')) { if (trimmedLine.startsWith('*')) {
// Outside access blocks, keep notes as-is // Outside access blocks, keep notes as-is but attach to detail now
const noteMatch = trimmedLine.match(/^(\*+)\s*(.*)/); const noteMatch = trimmedLine.match(/^(\*+)\s*(.*)/);
if (noteMatch) { if (noteMatch) {
reg.notes.push({ detail.notes.push({
ref: noteMatch[1], ref: noteMatch[1],
text: noteMatch[2], text: noteMatch[2],
}); });
} }
} else if (trimmedLine) { } else if (trimmedLine) {
reg.text += `${line}\n`; detail.text += `${line}\n`;
} }
} }
} }
if (currentAccess) { if (currentAccess) {
reg[currentAccess] = accessData; detail[currentAccess] = accessData;
} }
reg.modes = reg.modes || [];
reg.modes.push(detail);
}; };