From 5aa4c330591740f1111f3b95cb85754575fcf601 Mon Sep 17 00:00:00 2001 From: "D. Rimron-Soutter" Date: Fri, 10 Oct 2025 13:29:45 +0100 Subject: [PATCH] Move parsers to unique files --- src/app/registers/RegisterBrowser.tsx | 2 +- src/app/registers/RegisterDetail.tsx | 2 +- src/app/registers/[hex]/page.tsx | 2 +- src/app/services/register.service.ts | 4 +- src/app/utils/parser.ts | 0 src/utils/parser.ts | 191 ---------------------- src/utils/register_parser.ts | 118 +++++++++++++ src/utils/register_parsers/reg_default.ts | 97 +++++++++++ src/utils/register_parsers/reg_f0.ts | 98 +++++++++++ 9 files changed, 318 insertions(+), 196 deletions(-) delete mode 100644 src/app/utils/parser.ts delete mode 100644 src/utils/parser.ts create mode 100644 src/utils/register_parser.ts create mode 100644 src/utils/register_parsers/reg_default.ts create mode 100644 src/utils/register_parsers/reg_f0.ts diff --git a/src/app/registers/RegisterBrowser.tsx b/src/app/registers/RegisterBrowser.tsx index 307c5dc..47cc47d 100644 --- a/src/app/registers/RegisterBrowser.tsx +++ b/src/app/registers/RegisterBrowser.tsx @@ -1,7 +1,7 @@ 'use client'; import { useState } from 'react'; -import { Register, RegisterAccess, Note } from '@/utils/parser'; +import { Register, RegisterAccess, Note } from '@/utils/register_parser'; import { Form, Container, Row, Table, OverlayTrigger, Tooltip } from 'react-bootstrap'; import RegisterDetail from "@/app/registers/RegisterDetail"; diff --git a/src/app/registers/RegisterDetail.tsx b/src/app/registers/RegisterDetail.tsx index aaaab6c..443daf4 100644 --- a/src/app/registers/RegisterDetail.tsx +++ b/src/app/registers/RegisterDetail.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import { Col, Card, Tabs, Tab, Button, Modal } from 'react-bootstrap'; -import { Register } from '@/utils/parser'; +import { Register } from '@/utils/register_parser'; import { renderAccess } from './RegisterBrowser'; import Link from "next/link"; import * as Icon from 'react-bootstrap-icons'; diff --git a/src/app/registers/[hex]/page.tsx b/src/app/registers/[hex]/page.tsx index eecc078..635f0e4 100644 --- a/src/app/registers/[hex]/page.tsx +++ b/src/app/registers/[hex]/page.tsx @@ -1,6 +1,6 @@ import { notFound } from 'next/navigation'; import Link from 'next/link'; -import { Register } from '@/utils/parser'; +import { Register } from '@/utils/register_parser'; import RegisterDetail from '@/app/registers/RegisterDetail'; import {Container, Row} from "react-bootstrap"; import { getRegisters } from '@/app/services/register.service'; diff --git a/src/app/services/register.service.ts b/src/app/services/register.service.ts index 2a93df9..dfa71de 100644 --- a/src/app/services/register.service.ts +++ b/src/app/services/register.service.ts @@ -1,8 +1,8 @@ import { promises as fs } from 'fs'; import path from 'path'; -import { Register } from '@/utils/parser'; -import { parseNextReg } from '@/utils/parser'; +import { Register } from '@/utils/register_parser'; +import { parseNextReg } from '@/utils/register_parser'; let registers: Register[] = []; diff --git a/src/app/utils/parser.ts b/src/app/utils/parser.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/utils/parser.ts b/src/utils/parser.ts deleted file mode 100644 index af3c0ec..0000000 --- a/src/utils/parser.ts +++ /dev/null @@ -1,191 +0,0 @@ -export interface BitwiseOperation { - bits: string; - description: string; - value?: string; - footnoteRef?: string; -} - -export interface Note { - ref: string; - text: string; -} - -export interface RegisterAccess { - description?: string; - operations: BitwiseOperation[]; - notes: Note[]; -} -export interface Register { - hex_address: string; - dec_address: number | string; - name: string; - description: string; - read?: RegisterAccess; - write?: RegisterAccess; - common?: RegisterAccess; - text: string; - notes: Note[]; - issue_4_only: boolean; - source: string[]; -} - -/** - * Parses the content of the nextreg.txt file and returns an array of register objects. - * @param fileContent The content of the nextreg.txt file. - * @returns A promise that resolves to an array of Register objects. - */ -export async function parseNextReg(fileContent: string): Promise { - const registers: Register[] = []; - const paragraphs = fileContent.split(/\n\s*\n/); - - for (const paragraph of paragraphs) { - if (!paragraph.trim()) { - continue; - } - processRegisterBlock(paragraph, registers); - } - - return registers; -} - -export function processRegisterBlock(paragraph: string, registers: Register[]) { - const lines = paragraph.trim().split('\n'); - const firstLine = lines[0]; - - const registerMatch = firstLine.match(/([0-9a-fA-F,x]+)\s*\((.*?)\)\s*=>\s*(.*)/); - - if (!registerMatch) { - return; - } - - const hexAddresses = registerMatch[1].trim(); - const decAddresses = registerMatch[2].trim(); - const name = registerMatch[3] ? registerMatch[3].trim() : ''; - const description = lines.slice(1).join('\n').trim(); - - const hexList = hexAddresses.split(',').map(h => h.trim()); - const decList = decAddresses.includes('-') ? decAddresses.split('-') : decAddresses.split(',').map(d => d.trim()); - - const createRegister = (hex: string, dec: string | number, regName: string): Register => { - const reg: Register = { - hex_address: hex, - dec_address: dec, - name: regName, - description: description, - notes: [], - text: "", - issue_4_only: false, - source: [] - }; - - const descriptionLines = description.split('\n'); - let currentAccess: 'read' | 'write' | 'common' | null = null; - let accessData: RegisterAccess = { operations: [], notes: [] }; - - for (const line of descriptionLines) { - 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('(R)')) { - if (currentAccess) reg[currentAccess] = accessData; - accessData = { operations: [], notes: [] }; - currentAccess = 'read'; - continue; - } - if (trimmedLine.startsWith('(W)')) { - if (currentAccess) reg[currentAccess] = accessData; - accessData = { operations: [], notes: [] }; - currentAccess = 'write'; - continue; - } - if (trimmedLine.startsWith('(R/W')) { - if (currentAccess) reg[currentAccess] = accessData; - accessData = { operations: [], notes: [] }; - currentAccess = 'common'; - continue; - } - if (line.startsWith(trimmedLine)) { - if (currentAccess) reg[currentAccess] = accessData; - accessData = { operations: [], notes: [] }; - currentAccess = null; - } - - if (currentAccess) { - const bitMatch = trimmedLine.match(/^(bits?|bit)\s+([\d:-]+)\s*=\s*(.*)/); - const valueMatch = !line.match(/^\s+/) && trimmedLine.match(/^([01\s]+)\s*=\s*(.*)/); - - if (bitMatch) { - let bitDescription = bitMatch[3]; - const footnoteMatch = bitDescription.match(/(\*+)$/); - let footnoteRef: string | undefined = undefined; - if (footnoteMatch) { - footnoteRef = footnoteMatch[1]; - bitDescription = bitDescription.substring(0, bitDescription.length - footnoteRef.length).trim(); - } - accessData.operations.push({ - bits: bitMatch[2], - description: bitDescription, - footnoteRef: footnoteRef, - }); - } else if (valueMatch) { - accessData.operations.push({ - bits: valueMatch[1].trim().replace(/\s/g, ''), - description: valueMatch[2].trim(), - }); - } else if (trimmedLine.startsWith('*')) { - const noteMatch = trimmedLine.match(/^(\*+)\s*(.*)/); - if (noteMatch) { - accessData.notes.push({ - ref: noteMatch[1], - text: noteMatch[2], - }); - } - } else if (trimmedLine) { - if (line.match(/^\s+/) && accessData.operations.length > 0) { - accessData.operations[accessData.operations.length - 1].description += `\n${line}`; - } else { - - if (!accessData.description) { - accessData.description = ''; - } - accessData.description += `\n${trimmedLine}`; - } - } - } else { - if (trimmedLine.startsWith('*')) { - const noteMatch = trimmedLine.match(/^(\*+)\s*(.*)/); - if (noteMatch) { - reg.notes.push({ - ref: noteMatch[1], - text: noteMatch[2], - }); - } - } - else { - reg.text += `${line}\n`; - } - } - } - if (currentAccess) { - reg[currentAccess] = accessData; - } - - return reg; - }; - - if (hexList.length > 1) { - for (let i = 0; i < hexList.length; i++) { - const hexAddr = hexList[i]; - const decAddr = decList[i] || decAddresses; - const dec = isNaN(parseInt(decAddr, 10)) ? decAddr : parseInt(decAddr, 10); - registers.push(createRegister(hexAddr, dec, `${name} (${hexAddr})`)); - } - } else { - const dec = isNaN(parseInt(decAddresses, 10)) ? decAddresses : parseInt(decAddresses, 10); - registers.push(createRegister(hexAddresses, dec, name)); - } -} diff --git a/src/utils/register_parser.ts b/src/utils/register_parser.ts new file mode 100644 index 0000000..0e83084 --- /dev/null +++ b/src/utils/register_parser.ts @@ -0,0 +1,118 @@ +import { parseDescriptionDefault } from "./register_parsers/reg_default"; +import { parseDescriptionF0 } from "./register_parsers/reg_f0"; + +export interface RegisterBitwiseOperation { + bits: string; + description: string; + value?: string; + footnoteRef?: string; +} + +export interface Note { + ref: string; + text: string; +} + +export interface RegisterAccess { + description?: string; + operations: RegisterBitwiseOperation[]; + notes: Note[]; +} +export interface Register { + hex_address: string; + dec_address: number | string; + name: string; + description: string; + read?: RegisterAccess; + write?: RegisterAccess; + common?: RegisterAccess; + text: string; + notes: Note[]; + issue_4_only: boolean; + source: string[]; +} + +/** + * Parses the content of the nextreg.txt file and returns an array of register objects. + * @param fileContent The content of the nextreg.txt file. + * @returns A promise that resolves to an array of Register objects. + */ +export async function parseNextReg(fileContent: string): Promise { + const registers: Register[] = []; + const paragraphs = fileContent.split(/\n\s*\n/); + + for (const paragraph of paragraphs) { + if (!paragraph.trim()) { + continue; + } + processRegisterBlock(paragraph, registers); + } + + return registers; +} + +export function processRegisterBlock(paragraph: string, registers: Register[]) { + const lines = paragraph.trim().split('\n'); + const firstLine = lines[0]; + + const registerMatch = firstLine.match(/([0-9a-fA-F,x]+)\s*\((.*?)\)\s*=>\s*(.*)/); + + if (!registerMatch) { + return; + } + + const hexAddresses = registerMatch[1].trim(); + const decAddresses = registerMatch[2].trim(); + const name = registerMatch[3] ? registerMatch[3].trim() : ''; + const description = lines.slice(1).join('\n').trim(); + + const hexList = hexAddresses.split(',').map(h => h.trim()); + const decList = decAddresses.includes('-') ? decAddresses.split('-') : decAddresses.split(',').map(d => d.trim()); + + const normalizeHex = (hex: string): string => { + let h = hex.trim(); + if (!/^0x/i.test(h)) { + h = '0x' + h; + } + return '0x' + h.slice(2).toUpperCase(); + }; + + + const createRegister = (hex: string, dec: string | number, regName: string): Register => { + const reg: Register = { + hex_address: hex, + dec_address: dec, + name: regName, + description: description, + notes: [], + text: "", + issue_4_only: false, + source: [] + }; + + // Dispatch to appropriate parser based on hex + const hexKey = normalizeHex(hex); + switch (hexKey) { + case '0xF0': + parseDescriptionF0(reg, description); + break; + default: + parseDescriptionDefault(reg, description); + break; + } + + return reg; + }; + + if (hexList.length > 1) { + for (let i = 0; i < hexList.length; i++) { + const hexAddr = hexList[i]; + const decAddr = decList[i] || decAddresses; + const dec = isNaN(parseInt(decAddr, 10)) ? decAddr : parseInt(decAddr, 10); + registers.push(createRegister(hexAddr, dec, `${name} (${hexAddr})`)); + } + } else { + const dec = isNaN(parseInt(decAddresses, 10)) ? decAddresses : parseInt(decAddresses, 10); + registers.push(createRegister(hexAddresses, dec, name)); + } +} diff --git a/src/utils/register_parsers/reg_default.ts b/src/utils/register_parsers/reg_default.ts new file mode 100644 index 0000000..42713fc --- /dev/null +++ b/src/utils/register_parsers/reg_default.ts @@ -0,0 +1,97 @@ +import {Register, RegisterAccess} from "@/utils/register_parser"; + +export const parseDescriptionDefault = (reg: Register, description: string) => { + const descriptionLines = description.split('\n'); + let currentAccess: 'read' | 'write' | 'common' | null = null; + let accessData: RegisterAccess = { operations: [], notes: [] }; + + for (const line of descriptionLines) { + 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('(R)')) { + if (currentAccess) reg[currentAccess] = accessData; + accessData = { operations: [], notes: [] }; + currentAccess = 'read'; + continue; + } + if (trimmedLine.startsWith('(W)')) { + if (currentAccess) reg[currentAccess] = accessData; + accessData = { operations: [], notes: [] }; + currentAccess = 'write'; + continue; + } + if (trimmedLine.startsWith('(R/W')) { + if (currentAccess) reg[currentAccess] = accessData; + accessData = { operations: [], notes: [] }; + currentAccess = 'common'; + continue; + } + if (line.startsWith(trimmedLine)) { + if (currentAccess) reg[currentAccess] = accessData; + accessData = { operations: [], notes: [] }; + currentAccess = null; + } + + if (currentAccess) { + const bitMatch = trimmedLine.match(/^(bits?|bit)\s+([\d:-]+)\s*=\s*(.*)/); + const valueMatch = !line.match(/^\s+/) && trimmedLine.match(/^([01\s]+)\s*=\s*(.*)/); + + if (bitMatch) { + let bitDescription = bitMatch[3]; + const footnoteMatch = bitDescription.match(/(\*+)$/); + let footnoteRef: string | undefined = undefined; + if (footnoteMatch) { + footnoteRef = footnoteMatch[1]; + bitDescription = bitDescription.substring(0, bitDescription.length - footnoteRef.length).trim(); + } + accessData.operations.push({ + bits: bitMatch[2], + description: bitDescription, + footnoteRef: footnoteRef, + }); + } else if (valueMatch) { + accessData.operations.push({ + bits: valueMatch[1].trim().replace(/\s/g, ''), + description: valueMatch[2].trim(), + }); + } else if (trimmedLine.startsWith('*')) { + const noteMatch = trimmedLine.match(/^(\*+)\s*(.*)/); + if (noteMatch) { + accessData.notes.push({ + ref: noteMatch[1], + text: noteMatch[2], + }); + } + } else if (trimmedLine) { + if (line.match(/^\s+/) && accessData.operations.length > 0) { + accessData.operations[accessData.operations.length - 1].description += `\n${line}`; + } else { + if (!accessData.description) { + accessData.description = ''; + } + accessData.description += `\n${trimmedLine}`; + } + } + } else { + if (trimmedLine.startsWith('*')) { + const noteMatch = trimmedLine.match(/^(\*+)\s*(.*)/); + if (noteMatch) { + reg.notes.push({ + ref: noteMatch[1], + text: noteMatch[2], + }); + } + } else if (trimmedLine) { + reg.text += `${line}\n`; + } + } + } + if (currentAccess) { + reg[currentAccess] = accessData; + } +}; \ No newline at end of file diff --git a/src/utils/register_parsers/reg_f0.ts b/src/utils/register_parsers/reg_f0.ts new file mode 100644 index 0000000..8bca351 --- /dev/null +++ b/src/utils/register_parsers/reg_f0.ts @@ -0,0 +1,98 @@ + +// 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. +import {Register, RegisterAccess} from "@/utils/register_parser"; + +export const parseDescriptionF0 = (reg: Register, description: string) => { + const descriptionLines = description.split('\n'); + let currentAccess: 'read' | 'write' | 'common' | null = null; + let accessData: RegisterAccess = { operations: [], notes: [] }; + + for (const line of descriptionLines) { + 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('(R)')) { + if (currentAccess) reg[currentAccess] = accessData; + accessData = { operations: [], notes: [] }; + currentAccess = 'read'; + continue; + } + if (trimmedLine.startsWith('(W)')) { + if (currentAccess) reg[currentAccess] = accessData; + accessData = { operations: [], notes: [] }; + currentAccess = 'write'; + continue; + } + if (trimmedLine.startsWith('(R/W')) { + if (currentAccess) reg[currentAccess] = accessData; + accessData = { operations: [], notes: [] }; + currentAccess = 'common'; + continue; + } + if (line.startsWith(trimmedLine)) { + if (currentAccess) reg[currentAccess] = accessData; + accessData = { operations: [], notes: [] }; + currentAccess = null; + } + + if (currentAccess) { + const bitMatch = trimmedLine.match(/^(bits?|bit)\s+([\d:-]+)\s*=\s*(.*)/); + const valueMatch = !line.match(/^\s+/) && trimmedLine.match(/^([01\s]+)\s*=\s*(.*)/); + + if (bitMatch) { + let bitDescription = bitMatch[3]; + const footnoteMatch = bitDescription.match(/(\*+)$/); + let footnoteRef: string | undefined = undefined; + if (footnoteMatch) { + footnoteRef = footnoteMatch[1]; + bitDescription = bitDescription.substring(0, bitDescription.length - footnoteRef.length).trim(); + } + accessData.operations.push({ + bits: bitMatch[2], + description: bitDescription, + footnoteRef: footnoteRef, + }); + } else if (valueMatch) { + accessData.operations.push({ + bits: valueMatch[1].trim().replace(/\s/g, ''), + description: valueMatch[2].trim(), + }); + } else if (trimmedLine.startsWith('*')) { + // SPECIAL: treat star lines as headings in description rather than notes + const heading = trimmedLine.replace(/^\*+\s*/, '').trim(); + if (!accessData.description) accessData.description = ''; + accessData.description += (accessData.description ? '\n' : '') + heading; + } else if (trimmedLine) { + if (line.match(/^\s+/) && accessData.operations.length > 0) { + accessData.operations[accessData.operations.length - 1].description += `\n${line}`; + } else { + if (!accessData.description) { + accessData.description = ''; + } + accessData.description += `\n${trimmedLine}`; + } + } + } else { + if (trimmedLine.startsWith('*')) { + // Outside access blocks, keep notes as-is + const noteMatch = trimmedLine.match(/^(\*+)\s*(.*)/); + if (noteMatch) { + reg.notes.push({ + ref: noteMatch[1], + text: noteMatch[2], + }); + } + } else if (trimmedLine) { + reg.text += `${line}\n`; + } + } + } + if (currentAccess) { + reg[currentAccess] = accessData; + } +};