Move parser out to on demand (maybe move to on-startup, or on schedule, later)

This commit is contained in:
2025-10-10 12:47:47 +01:00
parent 029aea7f0c
commit 7c827975ae
7 changed files with 215 additions and 360 deletions

View File

@@ -1,7 +1,7 @@
'use client';
import { useState } from 'react';
import { Register, RegisterAccess, Note } from './types';
import { Register, RegisterAccess, Note } from '@/utils/parser';
import { Form, Container, Row, Table, OverlayTrigger, Tooltip } from 'react-bootstrap';
import RegisterDetail from "@/app/registers/RegisterDetail";

View File

@@ -1,7 +1,7 @@
"use client";
import { Col, Card, Tabs, Tab } from 'react-bootstrap';
import { Register } from './types';
import { Register } from '@/utils/parser';
import { renderAccess } from './RegisterBrowser';
import Link from "next/link";
import * as Icon from 'react-bootstrap-icons';

View File

@@ -1,170 +1,12 @@
import { promises as fs } from 'fs';
import path from 'path';
import { notFound } from 'next/navigation';
import Link from 'next/link';
import { Register, RegisterAccess } from '@/app/registers/types';
import { Register } from '@/utils/parser';
import RegisterDetail from '@/app/registers/RegisterDetail';
import {Container, Row} from "react-bootstrap";
async function parseNextReg(fileContent: string): Promise<Register[]> {
const registers: Register[] = [];
const paragraphs = fileContent.split(/\n\s*\n/);
for (const paragraph of paragraphs) {
if (!paragraph.trim()) {
continue;
}
processRegisterBlock(paragraph, registers);
}
return registers;
}
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
};
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();
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 as Register).notes.push({
ref: noteMatch[1],
text: noteMatch[2],
});
}
}
else {
reg.text += `${line}\n`;
}
}
}
if (currentAccess) {
(reg as Register)[currentAccess] = accessData;
}
return reg;
};
if (hexList.length > 1) {
for (let i = 0; i < hexList.length; i++) {
const hexAddr = hexList[i];
const decAddr = (decList as string[])[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));
}
}
import { getRegisters } from '@/app/services/register.service';
export default async function RegisterDetailPage({ params }: { params: { hex: string } }) {
const filePath = path.join(process.cwd(), 'data', 'nextreg.txt');
const fileContent = await fs.readFile(filePath, 'utf8');
const registers = await parseNextReg(fileContent);
const registers = await getRegisters();
const targetHex = decodeURIComponent((await params).hex).toLowerCase();
const register = registers.find(r => r.hex_address.toLowerCase() === targetHex);

View File

@@ -1,174 +1,8 @@
import { promises as fs } from 'fs';
import path from 'path';
import RegisterBrowser from '@/app/registers/RegisterBrowser';
import { Register, RegisterAccess } from './types';
/**
* 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.
*/
async function parseNextReg(fileContent: string): Promise<Register[]> {
const registers: Register[] = [];
const paragraphs = fileContent.split(/\n\s*\n/);
for (const paragraph of paragraphs) {
if (!paragraph.trim()) {
continue;
}
processRegisterBlock(paragraph, registers);
}
return registers;
}
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
};
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();
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));
}
}
import { getRegisters } from '@/app/services/register.service';
export default async function RegistersPage() {
const filePath = path.join(process.cwd(), 'data', 'nextreg.txt');
const fileContent = await fs.readFile(filePath, 'utf8');
const registers = await parseNextReg(fileContent);
const registers = await getRegisters();
return (
<div className="container-fluid py-4">

View File

@@ -1,29 +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;
}

View File

@@ -0,0 +1,20 @@
import { promises as fs } from 'fs';
import path from 'path';
import { Register } from '@/utils/parser';
import { parseNextReg } from '@/utils/parser';
let registers: Register[] = [];
/**
* Gets the registers from the in-memory cache, or loads them from the file if not already loaded.
* @returns A promise that resolves to an array of Register objects.
*/
export async function getRegisters(): Promise<Register[]> {
if (registers.length === 0) {
const filePath = path.join(process.cwd(), 'data', 'nextreg.txt');
const fileContent = await fs.readFile(filePath, 'utf8');
registers = await parseNextReg(fileContent);
}
return registers;
}

188
src/utils/parser.ts Normal file
View File

@@ -0,0 +1,188 @@
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;
}
/**
* 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<Register[]> {
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
};
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();
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));
}
}